diff --git a/contrib/libs/simdjson/.yandex_meta/__init__.py b/contrib/libs/simdjson/.yandex_meta/__init__.py new file mode 100644 index 000000000000..1654d7994d4b --- /dev/null +++ b/contrib/libs/simdjson/.yandex_meta/__init__.py @@ -0,0 +1,24 @@ +from devtools.yamaker.project import CMakeNinjaNixProject + + +simdjson = CMakeNinjaNixProject( + owners=["g:cpp-contrib"], + nixattr="simdjson", + arcdir="contrib/libs/simdjson", + disable_includes=[ + "CppCoreCheck\\Warnings.h", + "simdjson/nonstd/string_view.hpp", + "simdjson/ppc64/", + "sys/byteorder.h", + "ppc64.cpp", + "lasx.cpp", + "lasxintrin.h", + "lsx.cpp", + "lsxintrin.h", + ], + addincl_global={".": {"./include"}}, + copy_sources=[ + "src/arm64.cpp", + "include/**/*.h", + ], +) diff --git a/contrib/libs/simdjson/.yandex_meta/devtools.copyrights.report b/contrib/libs/simdjson/.yandex_meta/devtools.copyrights.report new file mode 100644 index 000000000000..48e3aa94697f --- /dev/null +++ b/contrib/libs/simdjson/.yandex_meta/devtools.copyrights.report @@ -0,0 +1,154 @@ +# File format ($ symbol means the beginning of a line): +# +# $ # this message +# $ # ======================= +# $ # comments (all commentaries should starts with some number of spaces and # symbol) +# $ IGNORE_FILES {file1.ext1} {file2.ext2} - (optional) ignore listed files when generating license macro and credits +# $ RENAME {original license id} TO {new license id} # user comments - (optional) use {new license id} instead {original license id} in ya.make files +# $ # user comments +# $ +# ${action} {license id} {license text hash} +# $BELONGS ./ya/make/file/relative/path/1/ya.make ./ya/make/2/ya.make +# ${all_file_action} filename +# $ # user commentaries (many lines) +# $ generated description - files with this license, license text... (some number of lines that starts with some number of spaces, do not modify) +# ${action} {license spdx} {license text hash} +# $BELONGS ./ya/make/file/relative/path/3/ya.make +# ${all_file_action} filename +# $ # user commentaries +# $ generated description +# $ ... +# +# You can modify action, all_file_action and add commentaries +# Available actions: +# keep - keep license in contrib and use in credits +# skip - skip license +# remove - remove all files with this license +# rename - save license text/links into licenses texts file, but not store SPDX into LINCENSE macro. You should store correct license id into devtools.license.spdx.txt file +# +# {all file action} records will be generated when license text contains filename that exists on filesystem (in contrib directory) +# We suppose that that files can contain some license info +# Available all file actions: +# FILE_IGNORE - ignore file (do nothing) +# FILE_INCLUDE - include all file data into licenses text file +# ======================= + +KEEP COPYRIGHT_SERVICE_LABEL 16fd5597ca30d730d6cebcdf3be2ec28 +BELONGS ya.make + Note: matched license text is too long. Read it in the source files. + Scancode info: + Original SPDX id: COPYRIGHT_SERVICE_LABEL + Score : 100.00 + Match type : COPYRIGHT + Files with this license: + include/simdjson/internal/instruction_set.h [5:14] + src/internal/isadetection.h [5:14] + +KEEP COPYRIGHT_SERVICE_LABEL 1ce2e39c07413c844104d4a1610b8621 +BELONGS ya.make + License text: + Copyright 2018-2023 The simdjson authors + Scancode info: + Original SPDX id: COPYRIGHT_SERVICE_LABEL + Score : 100.00 + Match type : COPYRIGHT + Files with this license: + LICENSE [189:189] + +KEEP COPYRIGHT_SERVICE_LABEL 2067bdaf1585921cf47dfb0b810c88f7 +BELONGS ya.make + Note: matched license text is too long. Read it in the source files. + Scancode info: + Original SPDX id: COPYRIGHT_SERVICE_LABEL + Score : 100.00 + Match type : COPYRIGHT + Files with this license: + include/simdjson/internal/instruction_set.h [5:14] + src/internal/isadetection.h [5:14] + +KEEP COPYRIGHT_SERVICE_LABEL 28cd8898b5e8ce155410a78873d77042 +BELONGS ya.make + Note: matched license text is too long. Read it in the source files. + Scancode info: + Original SPDX id: COPYRIGHT_SERVICE_LABEL + Score : 100.00 + Match type : COPYRIGHT + Files with this license: + include/simdjson/internal/instruction_set.h [5:14] + src/internal/isadetection.h [5:14] + +KEEP COPYRIGHT_SERVICE_LABEL 41b5314d4933d0efce14f33661eb953a +BELONGS ya.make + Note: matched license text is too long. Read it in the source files. + Scancode info: + Original SPDX id: COPYRIGHT_SERVICE_LABEL + Score : 100.00 + Match type : COPYRIGHT + Files with this license: + include/simdjson/internal/instruction_set.h [5:14] + src/internal/isadetection.h [5:14] + +KEEP COPYRIGHT_SERVICE_LABEL 7679e942759de26f76fdd9b1cb62d060 +BELONGS ya.make + Note: matched license text is too long. Read it in the source files. + Scancode info: + Original SPDX id: COPYRIGHT_SERVICE_LABEL + Score : 100.00 + Match type : COPYRIGHT + Files with this license: + include/simdjson/internal/instruction_set.h [5:14] + src/internal/isadetection.h [5:14] + +KEEP COPYRIGHT_SERVICE_LABEL abfbab3d484099905de04f84551ac607 +BELONGS ya.make + Note: matched license text is too long. Read it in the source files. + Scancode info: + Original SPDX id: COPYRIGHT_SERVICE_LABEL + Score : 100.00 + Match type : COPYRIGHT + Files with this license: + include/simdjson/internal/instruction_set.h [5:14] + src/internal/isadetection.h [5:14] + +KEEP COPYRIGHT_SERVICE_LABEL ae617de2789c827ff457d4d4ee938f87 +BELONGS ya.make + Note: matched license text is too long. Read it in the source files. + Scancode info: + Original SPDX id: COPYRIGHT_SERVICE_LABEL + Score : 100.00 + Match type : COPYRIGHT + Files with this license: + include/simdjson/internal/instruction_set.h [5:14] + src/internal/isadetection.h [5:14] + +KEEP COPYRIGHT_SERVICE_LABEL b52919ca831b9272ba54a8178e0f03e1 +BELONGS ya.make + Note: matched license text is too long. Read it in the source files. + Scancode info: + Original SPDX id: COPYRIGHT_SERVICE_LABEL + Score : 100.00 + Match type : COPYRIGHT + Files with this license: + include/simdjson/internal/instruction_set.h [5:14] + src/internal/isadetection.h [5:14] + +KEEP COPYRIGHT_SERVICE_LABEL ce7525b41b9b057bb4906c82b576055d +BELONGS ya.make + Note: matched license text is too long. Read it in the source files. + Scancode info: + Original SPDX id: COPYRIGHT_SERVICE_LABEL + Score : 100.00 + Match type : COPYRIGHT + Files with this license: + src/to_chars.cpp [21:27] + +KEEP COPYRIGHT_SERVICE_LABEL e79b33a1cc4840182edc20a4b9b36924 +BELONGS ya.make + Note: matched license text is too long. Read it in the source files. + Scancode info: + Original SPDX id: COPYRIGHT_SERVICE_LABEL + Score : 100.00 + Match type : COPYRIGHT + Files with this license: + include/simdjson/internal/instruction_set.h [5:14] + src/internal/isadetection.h [5:14] diff --git a/contrib/libs/simdjson/.yandex_meta/devtools.licenses.report b/contrib/libs/simdjson/.yandex_meta/devtools.licenses.report new file mode 100644 index 000000000000..8f4e95f877bc --- /dev/null +++ b/contrib/libs/simdjson/.yandex_meta/devtools.licenses.report @@ -0,0 +1,209 @@ +# File format ($ symbol means the beginning of a line): +# +# $ # this message +# $ # ======================= +# $ # comments (all commentaries should starts with some number of spaces and # symbol) +# $ IGNORE_FILES {file1.ext1} {file2.ext2} - (optional) ignore listed files when generating license macro and credits +# $ RENAME {original license id} TO {new license id} # user comments - (optional) use {new license id} instead {original license id} in ya.make files +# $ # user comments +# $ +# ${action} {license id} {license text hash} +# $BELONGS ./ya/make/file/relative/path/1/ya.make ./ya/make/2/ya.make +# ${all_file_action} filename +# $ # user commentaries (many lines) +# $ generated description - files with this license, license text... (some number of lines that starts with some number of spaces, do not modify) +# ${action} {license spdx} {license text hash} +# $BELONGS ./ya/make/file/relative/path/3/ya.make +# ${all_file_action} filename +# $ # user commentaries +# $ generated description +# $ ... +# +# You can modify action, all_file_action and add commentaries +# Available actions: +# keep - keep license in contrib and use in credits +# skip - skip license +# remove - remove all files with this license +# rename - save license text/links into licenses texts file, but not store SPDX into LINCENSE macro. You should store correct license id into devtools.license.spdx.txt file +# +# {all file action} records will be generated when license text contains filename that exists on filesystem (in contrib directory) +# We suppose that that files can contain some license info +# Available all file actions: +# FILE_IGNORE - ignore file (do nothing) +# FILE_INCLUDE - include all file data into licenses text file +# ======================= + +SKIP BSL-1.0 089f5df1fde5e9be79fa00a24c4269ff +BELONGS ya.make + Note: matched license text is too long. Read it in the source files. + Scancode info: + Original SPDX id: BSL-1.0 + Score : 44.00 + Match type : REFERENCE + Links : http://www.boost.org/LICENSE_1_0.txt, http://www.boost.org/users/license.html, https://spdx.org/licenses/BSL-1.0 + Files with this license: + README.md [227:227] + +SKIP LicenseRef-scancode-unknown-license-reference 0d48e0b09865a98a90db20ea37b36bb8 +BELONGS ya.make + License text: + licensed under + Scancode info: + Original SPDX id: LicenseRef-scancode-unknown-license-reference + Score : 11.00 + Match type : INTRO + Links : https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/licenses/unknown-license-reference.LICENSE + Files with this license: + README.md [231:231] + +KEEP BSD-3-Clause 1932361280194a7b208a1a5671cb21a2 +BELONGS ya.make +FILE_INCLUDE CONTRIBUTORS found in files: include/simdjson/internal/instruction_set.h at line 33, include/simdjson/internal/instruction_set.h at line 36, src/internal/isadetection.h at line 33, src/internal/isadetection.h at line 36 + Note: matched license text is too long. Read it in the source files. + Scancode info: + Original SPDX id: BSD-3-Clause + Score : 96.38 + Match type : TEXT + Links : http://www.opensource.org/licenses/BSD-3-Clause, https://spdx.org/licenses/BSD-3-Clause + Files with this license: + include/simdjson/internal/instruction_set.h [18:43] + src/internal/isadetection.h [18:43] + +SKIP Apache-2.0 25f6b0abe41c238db48ab155a5e5bee3 +BELONGS ya.make + License text: + [license img]: https://img.shields.io/badge/License-Apache%202-blue.svg + Scancode info: + Original SPDX id: Apache-2.0 + Score : 95.00 + Match type : REFERENCE + Links : http://www.apache.org/licenses/, http://www.apache.org/licenses/LICENSE-2.0, https://spdx.org/licenses/Apache-2.0 + Files with this license: + README.md [212:212] + +SKIP BSL-1.0 2a9212d785cde4078c2f6803e544de21 +BELONGS ya.make + Note: matched license text is too long. Read it in the source files. + Scancode info: + Original SPDX id: BSL-1.0 + Score : 99.00 + Match type : REFERENCE + Links : http://www.boost.org/LICENSE_1_0.txt, http://www.boost.org/users/license.html, https://spdx.org/licenses/BSL-1.0 + Files with this license: + README.md [227:227] + +SKIP Apache-2.0 500a503129337bb5adf5977ce11879cd +BELONGS ya.make + License text: + This code is made available under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html). + Scancode info: + Original SPDX id: Apache-2.0 + Score : 100.00 + Match type : NOTICE + Links : http://www.apache.org/licenses/, http://www.apache.org/licenses/LICENSE-2.0, https://spdx.org/licenses/Apache-2.0 + Files with this license: + README.md [223:223] + +SKIP BSL-1.0 77dd56e30840a227692d435b4aecdb95 +BELONGS ya.make + Note: matched license text is too long. Read it in the source files. + Scancode info: + Original SPDX id: BSL-1.0 + Score : 99.00 + Match type : REFERENCE + Links : http://www.boost.org/LICENSE_1_0.txt, http://www.boost.org/users/license.html, https://spdx.org/licenses/BSL-1.0 + Files with this license: + README.md [227:227] + +SKIP Apache-2.0 871555b1be031365ad101c8aa7104482 +BELONGS ya.make + License text: + This code is made available under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html). + Scancode info: + Original SPDX id: Apache-2.0 + Score : 100.00 + Match type : REFERENCE + Links : http://www.apache.org/licenses/, http://www.apache.org/licenses/LICENSE-2.0, https://spdx.org/licenses/Apache-2.0 + Files with this license: + README.md [223:223] + +KEEP Apache-2.0 97b415d82a3bce8bcbc7213fa2ac85c1 +BELONGS ya.make + Note: matched license text is too long. Read it in the source files. + Scancode info: + Original SPDX id: Apache-2.0 + Score : 99.81 + Match type : TEXT + Links : http://www.apache.org/licenses/, http://www.apache.org/licenses/LICENSE-2.0, https://spdx.org/licenses/Apache-2.0 + Files with this license: + LICENSE [1:201] + +SKIP Apache-2.0 a7953e3caf13357c57a3aadc5910c07c +BELONGS ya.make + License text: + // credit: based on code from Google Fuchsia (Apache Licensed) + Scancode info: + Original SPDX id: Apache-2.0 + Score : 95.00 + Match type : NOTICE + Links : http://www.apache.org/licenses/, http://www.apache.org/licenses/LICENSE-2.0, https://spdx.org/licenses/Apache-2.0 + Files with this license: + src/fallback.cpp [310:310] + +SKIP MIT ab9e87f2e8d2cf76674b63680fb52c50 +BELONGS ya.make + Note: matched license text is too long. Read it in the source files. + Scancode info: + Original SPDX id: MIT + Score : 100.00 + Match type : NOTICE + Links : http://opensource.org/licenses/mit-license.php, https://spdx.org/licenses/MIT + Files with this license: + README.md [229:229] + +SKIP Apache-2.0 c23a044f4165feb9568f486ca3b30fc8 +BELONGS ya.make + Note: matched license text is too long. Read it in the source files. + Scancode info: + Original SPDX id: Apache-2.0 + Score : 90.00 + Match type : NOTICE + Links : http://www.apache.org/licenses/, http://www.apache.org/licenses/LICENSE-2.0, https://spdx.org/licenses/Apache-2.0 + Files with this license: + README.md [227:227] + +SKIP BSD-3-Clause d77bd60dc7ee5f9c3b221f6edd94bbac +BELONGS ya.make + License text: + 3-clause BSD. + Scancode info: + Original SPDX id: BSD-3-Clause + Score : 100.00 + Match type : REFERENCE + Links : http://www.opensource.org/licenses/BSD-3-Clause, https://spdx.org/licenses/BSD-3-Clause + Files with this license: + README.md [231:231] + +SKIP MIT dd09705e3ec59af63c705c8f5f3eadb2 +BELONGS ya.make + License text: + Under Windows, we build some tools using the windows/dirent_portable.h file (which is outside our library code): it is under the liberal (business-friendly) MIT license. + Scancode info: + Original SPDX id: MIT + Score : 100.00 + Match type : REFERENCE + Links : http://opensource.org/licenses/mit-license.php, https://spdx.org/licenses/MIT + Files with this license: + README.md [225:225] + +KEEP MIT f0fe4686586f118327c3bc63fe4027de +BELONGS ya.make + License text: + The code is distributed under the MIT license, Copyright (c) 2009 Florian + Scancode info: + Original SPDX id: MIT + Score : 100.00 + Match type : NOTICE + Links : http://opensource.org/licenses/mit-license.php, https://spdx.org/licenses/MIT + Files with this license: + src/to_chars.cpp [21:21] diff --git a/contrib/libs/simdjson/.yandex_meta/licenses.list.txt b/contrib/libs/simdjson/.yandex_meta/licenses.list.txt new file mode 100644 index 000000000000..ac7d9c335034 --- /dev/null +++ b/contrib/libs/simdjson/.yandex_meta/licenses.list.txt @@ -0,0 +1,309 @@ +====================Apache-2.0==================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2023 The simdjson authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +====================BSD-3-Clause==================== +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the names of Facebook, Deepmind Technologies, NYU, NEC Laboratories +America and IDIAP Research Institute nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +====================COPYRIGHT==================== + Copyright 2018-2023 The simdjson authors + + +====================COPYRIGHT==================== +Copyright (c) 2016- Facebook, Inc (Adam Paszke) +Copyright (c) 2014- Facebook, Inc (Soumith Chintala) +Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert) +Copyright (c) 2012-2014 Deepmind Technologies (Koray Kavukcuoglu) +Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu) +Copyright (c) 2011-2013 NYU (Clement Farabet) +Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert, Leon Bottou, +Iain Melvin, Jason Weston) Copyright (c) 2006 Idiap Research Institute +(Samy Bengio) Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert, +Samy Bengio, Johnny Mariethoz) + + +====================COPYRIGHT==================== +The code is distributed under the MIT license, Copyright (c) 2009 Florian +Loitsch. For a detailed description of the algorithm see: [1] Loitsch, "Printing +Floating-Point Numbers Quickly and Accurately with Integers", Proceedings of the +ACM SIGPLAN 2010 Conference on Programming Language Design and Implementation, +PLDI 2010 [2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and +Accurately", Proceedings of the ACM SIGPLAN 1996 Conference on Programming +Language Design and Implementation, PLDI 1996 + + +====================File: CONTRIBUTORS==================== +# contributors (in no particular order) +Thomas Navennec +Kai Wolf +Tyler Kennedy +Frank Wessels +George Fotopoulos +Heinz N. Gies +Emil Gedda +Wojciech Muła +Georgios Floros +Dong Xie +Nan Xiao +Egor Bogatov +Jinxi Wang +Luiz Fernando Peres +Wouter Bolsterlee +Anish Karandikar +Reini Urban +Tom Dyson +Ihor Dotsenko +Alexey Milovidov +Chang Liu +Sunny Gleason +John Keiser +Zach Bjornson +Vitaly Baranov +Juho Lauri +Michael Eisel +Io Daza Dillon +Paul Dreik +Jeremie Piotte +Matthew Wilson +Dušan Jovanović +Matjaž Ostroveršnik +Nong Li +Furkan Taşkale +Brendan Knapp +Danila Kutenin +Pavel Pavlov +Hao Chen +Nicolas Boyer +Kim Walisch and Jatin Bhateja (AVX-512 bitset decoder) +Fangzheng Zhang and Weiqiang Wan (AVX-512 kernel) +# if you have contributed to the project and your name does not +# appear in this list, please let us know! + + +====================MIT==================== +The code is distributed under the MIT license, Copyright (c) 2009 Florian diff --git a/contrib/libs/simdjson/.yandex_meta/override.nix b/contrib/libs/simdjson/.yandex_meta/override.nix new file mode 100644 index 000000000000..be3844a37d49 --- /dev/null +++ b/contrib/libs/simdjson/.yandex_meta/override.nix @@ -0,0 +1,14 @@ +pkgs: attrs: with pkgs; rec { + version = "3.11.3"; + + src = fetchFromGitHub { + owner = "simdjson"; + repo = "simdjson"; + rev = "v${version}"; + hash = "sha256-Gh9/vOfhEh3RXT4cSb6KpDqjYS0d1kje1JDbDiWTR0o="; + }; + + cmakeFlags = attrs.cmakeFlags ++ [ + "-DSIMDJSON_ENABLE_THREADS=OFF" + ]; +} diff --git a/contrib/libs/simdjson/AUTHORS b/contrib/libs/simdjson/AUTHORS new file mode 100644 index 000000000000..e23c6beb4b3a --- /dev/null +++ b/contrib/libs/simdjson/AUTHORS @@ -0,0 +1,4 @@ +# List of authors for copyright purposes, in no particular order +Daniel Lemire +Geoff Langdale +John Keiser diff --git a/contrib/libs/simdjson/CONTRIBUTING.md b/contrib/libs/simdjson/CONTRIBUTING.md new file mode 100644 index 000000000000..a6b70a0bf246 --- /dev/null +++ b/contrib/libs/simdjson/CONTRIBUTING.md @@ -0,0 +1,103 @@ +Contributing +============ + +The simdjson library is an open project written in C++. Contributions are invited. Contributors +agree to the project's license. + +We have an extensive list of issues, and contributions toward any of these issues is invited. +Contributions can take the form of code samples, better documentation or design ideas. + +In particular, the following contributions are invited: + +- The library is focused on performance. Well-documented performance optimization are invited. +- Fixes to known or newly discovered bugs are always welcome. Typically, a bug fix should come with + a test demonstrating that the bug has been fixed. +- The simdjson library is advanced software and maintainability and flexibility are always a + concern. Specific contributions to improve maintainability and flexibility are invited. + +We discourage the following types of contributions: + +- Code refactoring. We all have our preferences as to how code should be written, but unnecessary + refactoring can waste time and introduce new bugs. If you believe that refactoring is needed, you + first must explain how it helps in concrete terms. Does it improve the performance? +- Applications of new language features for their own sake. Using advanced C++ language constructs + is actually a negative as it may reduce portability (to old compilers, old standard libraries and + systems) and reduce accessibility (to programmers that have not kept up), so it must be offsetted + by clear gains like performance or maintainability. When in doubt, avoid advanced C++ features + (beyond C++11). +- Style formatting. In general, please abstain from reformatting code just to make it look prettier. + Though code formatting is important, it can also be a waste of time if several contributors try to + tweak the code base toward their own preference. Please do not introduce unneeded white-space + changes. + +In short, most code changes should either bring new features or better performance. We want to avoid unmotivated code changes. + + +Specific rules +---------- + +We have few hard rules, but we have some: + +- Printing to standard output or standard error (`stderr`, `stdout`, `std::cerr`, `std::cout`) in the core library is forbidden. This follows from the [Writing R Extensions](https://cran.r-project.org/doc/manuals/R-exts.html) manual which states that "Compiled code should not write to stdout or stderr". +- Calls to `abort()` are forbidden in the core library. This follows from the [Writing R Extensions](https://cran.r-project.org/doc/manuals/R-exts.html) manual which states that "Under no circumstances should your compiled code ever call abort or exit". +- All source code files (.h, .cpp) must be ASCII. +- All C macros introduced in public headers need to be prefixed with either `SIMDJSON_` or `simdjson_`. +- We avoid trailing white space characters within lines. That is, your lines of code should not terminate with unnecessary spaces. Generally, please avoid making unnecessary changes to white-space characters when contributing code. + +Tools, tests and benchmarks are not held to these same strict rules. + +General Guidelines +---------- + +Contributors are encouraged to : + +- Document their changes. Though we do not enforce a rule regarding code comments, we prefer that non-trivial algorithms and techniques be somewhat documented in the code. +- Follow as much as possible the existing code style. We do not enforce a specific code style, but we prefer consistency. We avoid contractions (isn't, aren't) in the comments. +- Modify as few lines of code as possible when working on an issue. The more lines you modify, the harder it is for your fellow human beings to understand what is going on. +- Tools may report "problems" with the code, but we never delegate programming to tools: if there is a problem with the code, we need to understand it. Thus we will not "fix" code merely to please a static analyzer. +- Provide tests for any new feature. We will not merge a new feature without tests. +- Run before/after benchmarks so that we can appreciate the effect of the changes on the performance. + +Pull Requests +-------------- + +Pull requests are always invited. However, we ask that you follow these guidelines: + +- It is wise to discuss your ideas first as part of an issue before you start coding. If you omit this step and code first, be prepared to have your code receive scrutiny and be dropped. +- Users should provide a rationale for their changes. Does it improve performance? Does it add a feature? Does it improve maintainability? Does it fix a bug? This must be explicitly stated as part of the pull request. Do not propose changes based on taste or intuition. We do not delegate programming to tools: that some tool suggested a code change is not reason enough to change the code. + 1. When your code improves performance, please document the gains with a benchmark using hard numbers. + 2. If your code fixes a bug, please either fix a failing test, or propose a new test. + 3. Other types of changes must be clearly motivated. We openly discourage changes with no identifiable benefits. +- Changes should be focused and minimal. You should change as few lines of code as possible. Please do not reformat or touch files needlessly. +- New features must be accompanied by new tests, in general. +- Your code should pass our continuous-integration tests. It is your responsibility to ensure that your proposal pass the tests. We do not merge pull requests that would break our build. + - An exception to this would be changes to non-code files, such as documentation and assets, or trivial changes to code, such as comments, where it is encouraged to explicitly ask for skipping a CI run using the `[skip ci]` prefix in your Pull Request title **and** in the first line of the most recent commit in a push. Example for such a commit: `[skip ci] Fixed typo in power_of_ten's docs` + This benefits the project in such a way that the CI pipeline is not burdened by running jobs on changes that don't change any behavior in the code, which reduces wait times for other Pull Requests that do change behavior and require testing. + +If the benefits of your proposed code remain unclear, we may choose to discard your code: that is not an insult, we frequently discard our own code. We may also consider various alternatives and choose another path. Again, that is not an insult or a sign that you have wasted your time. + +Style +----- + +Our formatting style is inspired by the LLVM style. +The simdjson library is written using the snake case: when a variable or a function is a phrase, each space is replaced by an underscore character, and the first letter of each word written in lowercase. Compile-time constants are written entirely in uppercase with the same underscore convention. + +Code of Conduct +--------------- + +Though we do not have a formal code of conduct, we will not tolerate bullying, bigotry or +intimidation. Everyone is welcome to contribute. If you have concerns, you can raise them privately with the core team members (e.g., D. Lemire, J. Keiser). + +We welcome contributions from women and less represented groups. If you need help, please reach out. + +Consider the following points when engaging with the project: + +- We discourage arguments from authority: ideas are discusssed on their own merits and not based on who stated it. +- Be mindful that what you may view as an aggression is maybe merely a difference of opinion or a misunderstanding. +- Be mindful that a collection of small aggressions, even if mild in isolation, can become harmful. + +Getting Started Hacking +----------------------- + +An overview of simdjson's directory structure, with pointers to architecture and design +considerations and other helpful notes, can be found at [HACKING.md](HACKING.md). diff --git a/contrib/libs/simdjson/CONTRIBUTORS b/contrib/libs/simdjson/CONTRIBUTORS new file mode 100644 index 000000000000..557127a9edba --- /dev/null +++ b/contrib/libs/simdjson/CONTRIBUTORS @@ -0,0 +1,45 @@ +# contributors (in no particular order) +Thomas Navennec +Kai Wolf +Tyler Kennedy +Frank Wessels +George Fotopoulos +Heinz N. Gies +Emil Gedda +Wojciech Muła +Georgios Floros +Dong Xie +Nan Xiao +Egor Bogatov +Jinxi Wang +Luiz Fernando Peres +Wouter Bolsterlee +Anish Karandikar +Reini Urban +Tom Dyson +Ihor Dotsenko +Alexey Milovidov +Chang Liu +Sunny Gleason +John Keiser +Zach Bjornson +Vitaly Baranov +Juho Lauri +Michael Eisel +Io Daza Dillon +Paul Dreik +Jeremie Piotte +Matthew Wilson +Dušan Jovanović +Matjaž Ostroveršnik +Nong Li +Furkan Taşkale +Brendan Knapp +Danila Kutenin +Pavel Pavlov +Hao Chen +Nicolas Boyer +Kim Walisch and Jatin Bhateja (AVX-512 bitset decoder) +Fangzheng Zhang and Weiqiang Wan (AVX-512 kernel) +# if you have contributed to the project and your name does not +# appear in this list, please let us know! diff --git a/contrib/libs/simdjson/HACKING.md b/contrib/libs/simdjson/HACKING.md new file mode 100644 index 000000000000..8ee62672f7de --- /dev/null +++ b/contrib/libs/simdjson/HACKING.md @@ -0,0 +1,332 @@ + +Hacking simdjson +================ + +Here is wisdom about how to build, test and run simdjson from within the repository. This is mostly useful for people who plan to contribute simdjson, or maybe study the design. + +If you plan to contribute to simdjson, please read our [CONTRIBUTING](https://github.com/simdjson/simdjson/blob/master/CONTRIBUTING.md) guide. + +- [Hacking simdjson](#hacking-simdjson) + - [Build Quickstart](#build-quickstart) + - [Design notes](#design-notes) + - [Developer mode](#developer-mode) + - [Directory Structure and Source](#directory-structure-and-source) + - [Runtime Dispatching](#runtime-dispatching) + - [Regenerating Single-Header Files](#regenerating-single-header-files) + - [Usage (CMake on 64-bit platforms like Linux, FreeBSD or macOS)](#usage-cmake-on-64-bit-platforms-like-linux-freebsd-or-macos) + - [Usage (CMake on 64-bit Windows using Visual Studio 2019 or better)](#usage-cmake-on-64-bit-windows-using-visual-studio-2019-or-better) + - [Various References](#various-references) + +Build Quickstart +------------------------------ + +```bash +mkdir build +cd build +cmake -D SIMDJSON_DEVELOPER_MODE=ON .. +cmake --build . +``` + +Design notes +------------------------------ + +The parser works in two stages: + +- Stage 1. (Find marks) Identifies quickly structure elements, strings, and so forth. We validate UTF-8 encoding at that stage. +- Stage 2. (Structure building) Involves constructing a "tree" of sort (materialized as a tape) to navigate through the data. Strings and numbers are parsed at this stage. + + +The role of stage 1 is to identify pseudo-structural characters as quickly as possible. A character is pseudo-structural if and only if: + +1. Not enclosed in quotes, AND +2. Is a non-whitespace character, AND +3. Its preceding character is either: + (a) a structural character, OR + (b) whitespace OR + (c) the final quote in a string. + +This helps as we redefine some new characters as pseudo-structural such as the characters 1, G, n in the following: + +> { "foo" : 1.5, "bar" : 1.5 GEOFF_IS_A_DUMMY bla bla , "baz", null } + +Stage 1 also does unicode validation. + +Stage 2 handles all of the rest: number parsings, recognizing atoms like true, false, null, and so forth. + +Developer mode +-------------- + +Build system targets that are only useful for developers of the simdjson +library are behind the `SIMDJSON_DEVELOPER_MODE` option. Enabling this option +makes tests, examples, benchmarks and other developer targets available. Not +enabling this option means that you are a consumer of simdjson and thus you +only get the library targets and options. + +Developer mode is forced to be on when the `CI` environment variable is set to +a value that CMake recognizes as "on", which is set to `true` in all of the CI +workflows used by simdjson. + +Directory Structure and Source +------------------------------ + +simdjson's source structure, from the top level, looks like this: + +* **CMakeLists.txt:** The main build system. +* **include:** User-facing declarations and inline definitions (most user-facing functions are inlined). + * simdjson.h: the `simdjson` namespace. A "main include" that includes files from include/simdjson/. This is equivalent to + the distributed simdjson.h. + * simdjson/*.h: Declarations for public simdjson classes and functions. + * simdjson/*-inl.h: Definitions for public simdjson classes and functions. + * simdjson/internal/*.h: the `simdjson::internal` namespace. Private classes and functions used by the rest of simdjson. + * simdjson/dom.h: the `simdjson::dom` namespace. Includes all public DOM classes. + * simdjson/dom/*.h: Declarations/definitions for individual DOM classes. + * simdjson/arm64|fallback|haswell|icelake|ppc64|westmere.h: `simdjson::` namespace. Common implementation-specific tools like number and string parsing, as well as minification. + * simdjson/arm64|fallback|haswell|icelake|ppc64|westmere/*.h: implementation-specific functions such as , etc. + * simdjson/generic/*.h: the bulk of the actual code, written generically and compiled for each implementation, using functions defined in the implementation's .h files. + * simdjson/generic/dependencies.h: dependencies on common, non-implementation-specific simdjson classes. This will be included before including amalgamated.h. + * simdjson/generic/amalgamated.h: all generic ondemand classes for an implementation. + * simdjson/ondemand.h: the `simdjson::ondemand` namespace. Includes all public ondemand classes. + * simdjson/builtin.h: the `simdjson::builtin` namespace. Aliased to the most universal implementation available. + * simdjson/builtin/ondemand.h: the `simdjson::builtin::ondemand` namespace. + * simdjson/arm64|fallback|haswell|icelake|ppc64|westmere/ondemand.h: the `simdjson::::ondemand` namespace. On-Demand compiled for the specific implementation. + * simdjson/generic/ondemand/*.h: individual On-Demand classes, generically written. + * simdjson/generic/ondemand/dependencies.h: dependencies on common, non-implementation-specific simdjson classes. This will be included before including amalgamated.h. + * simdjson/generic/ondemand/amalgamated.h: all generic ondemand classes for an implementation. +* **src:** The source files for non-inlined functionality (e.g. the architecture-specific parser + implementations). + * simdjson.cpp: A "main source" that includes all implementation files from src/. This is + equivalent to the distributed simdjson.cpp. + * *.cpp: other misc. implementations, such as `simdjson::implementation` and the minifier. + * arm64|fallback|haswell|icelake|ppc64|westmere.cpp: Architecture-specific parser implementations. + * generic/*.h: `simdjson::` namespace. Generic implementation of the parser, particularly the `dom_parser_implementation`. + * generic/stage1/*.h: `simdjson::::stage1` namespace. Generic implementation of the simd-heavy tokenizer/indexer pass of the simdjson parser. Used for the On-Demand interface + * generic/stage2/*.h: `simdjson::::stage2` namespace. Generic implementation of the tape creator, which consumes the index from stage 1 and actually parses numbers and string and such. Used for the DOM interface. + +Other important files and directories: +* **.drone.yml:** Definitions for Drone CI. +* **.appveyor.yml:** Definitions for Appveyor CI (Windows). +* **.circleci:** Definitions for Circle CI. +* **.github/workflows:** Definitions for GitHub Actions (CI). +* **singleheader:** Contains generated `simdjson.h` and `simdjson.cpp` that we release. The files `singleheader/simdjson.h` and `singleheader/simdjson.cpp` should never be edited by hand. +* **singleheader/amalgamate.py:** Generates `singleheader/simdjson.h` and `singleheader/simdjson.cpp` for release (python script). +* **benchmark:** This is where we do benchmarking. Benchmarking is core to every change we make; the + cardinal rule is don't regress performance without knowing exactly why, and what you're trading + for it. Many of our benchmarks are microbenchmarks. We are effectively doing controlled scientific experiments for the purpose of understanding what affects our performance. So we simplify as much as possible. We try to avoid irrelevant factors such as page faults, interrupts, unnecessary system calls. We recommend checking the performance as follows: + ```bash + mkdir build + cd build + cmake -D SIMDJSON_DEVELOPER_MODE=ON .. + cmake --build . --config Release + benchmark/dom/parse ../jsonexamples/twitter.json + ``` + The last line becomes `./benchmark/Release/parse.exe ../jsonexample/twitter.json` under Windows. You may also use Google Benchmark: + ```bash + mkdir build + cd build + cmake -D SIMDJSON_DEVELOPER_MODE=ON .. + cmake --build . --target bench_parse_call --config Release + ./benchmark/bench_parse_call + ``` + The last line becomes `./benchmark/Release/bench_parse_call.exe` under Windows. Under Windows, you can also build with the clang compiler by adding `-T ClangCL` to the call to `cmake ..`: `cmake -T ClangCL ..`. +* **fuzz:** The source for fuzz testing. This lets us explore important edge and middle cases +* **fuzz:** The source for fuzz testing. This lets us explore important edge and middle cases + automatically, and is run in CI. +* **jsonchecker:** A set of JSON files used to check different functionality of the parser. + * **pass*.json:** Files that should pass validation. + * **fail*.json:** Files that should fail validation. + * **jsonchecker/minefield/y_*.json:** Files that should pass validation. + * **jsonchecker/minefield/n_*.json:** Files that should fail validation. +* **jsonexamples:** A wide spread of useful, real-world JSON files with different characteristics + and sizes. +* **test:** The tests are here. basictests.cpp and errortests.cpp are the primary ones. +* **tools:** Source for executables that can be distributed with simdjson. Some examples: + * `json2json mydoc.json` parses the document, constructs a model and then dumps back the result to standard output. + * `json2json -d mydoc.json` parses the document, constructs a model and then dumps model (as a tape) to standard output. The tape format is described in the accompanying file `tape.md`. + * `minify mydoc.json` minifies the JSON document, outputting the result to standard output. Minifying means to remove the unneeded white space characters. + * `jsonpointer mydoc.json ... ` parses the document, constructs a model and then processes a series of [JSON Pointer paths](https://tools.ietf.org/html/rfc6901). The result is itself a JSON document. + + +> **Don't modify the files in singleheader/ directly; these are automatically generated.** + + +While simdjson distributes just two files from the singleheader/ directory, we *maintain* the code in +multiple files under include/ and src/. The files include/simdjson.h and src/simdjson.cpp are the "spine" for +these, and you can include them as if they were the corresponding singleheader/ files. + + + +Runtime Dispatching +-------------------- + +A key feature of simdjson is the ability to compile different processing kernels, optimized for specific instruction sets, and to select +the most appropriate kernel at runtime. This ensures that users get the very best performance while still enabling simdjson to run everywhere. +This technique is frequently called runtime dispatching. The simdjson achieves runtime dispatching entirely in C++: we do not assume +that the user is building the code using CMake, for example. + +To make runtime dispatching work, it is critical that the code be compiled for the lowest supported processor. In particular, you should +not use flags such as -mavx2, /arch:AVX2 and so forth while compiling simdjson. When you do so, you allow the compiler to use advanced +instructions. In turn, these advanced instructions present in the code may cause a runtime failure if the runtime processor does not +support them. Even a simple loop, compiled with these flags, might generate binary code that only run on advanced processors. + +So we compile simdjson for a generic processor. Our users should do the same if they want simdjson's runtime dispatch to work. It is important +to understand that if runtime dispatching does not work, then simdjson will cause crashes on older processors. Of course, if a user chooses +to compile their code for a specific instruction set (e.g., AVX2), they are responsible for the failures if they later run their code +on a processor that does not support AVX2. Yet, if we were to entice these users to do so, we would share the blame: thus we carefully instruct +users to compile their code in a generic way without doing anything to enable advanced instructions. + + +We only use runtime dispatching on x64 (AMD/Intel) platforms, at the moment. On ARM processors, we would need a standard way to query, at runtime, +the processor for its supported features. We do not know how to do so on ARM systems in general. Thankfully it is not yet a concern: 64-bit ARM +processors are fairly uniform as far as the instruction sets they support. + + +In all cases, simdjson uses advanced instructions by relying on "intrinsic functions": we do not write assembly code. The intrinsic functions +are special functions that the compiler might recognize and translate into fast code. To make runtime dispatching work, we rely on the fact that +the header providing these instructions +(intrin.h under Visual Studio, x86intrin.h elsewhere) defines all of the intrinsic functions, including those that are not supported +processor. + +At this point, we are require to use one of two main strategies. + +1. On POSIX systems, the main compilers (LLVM clang, GNU gcc) allow us to use any intrinsic function after including the header, but they fail to inline the resulting instruction if the target processor does not support them. Because we compile for a generic processor, we would not be able to use most intrinsic functions. Thankfully, more recent versions of these compilers allow us to flag a region of code with a specific target, so that we can compile only some of the code with support for advanced instructions. Thus in our C++, one might notice macros like `TARGET_HASWELL`. It is then our responsibility, at runtime, to only run the regions of code (that we call kernels) matching the properties of the runtime processor. The benefit of this approach is that the compiler not only let us use intrinsic functions, but it can also optimize the rest of the code in the kernel with advanced instructions we enabled. + +2. Under Visual Studio, the problem is somewhat simpler. Visual Studio will not only provide the intrinsic functions, but it will also allow us to use them. They will compile just fine. It is at runtime that they may cause a crash. So we do not need to mark regions of code for compilation toward advanced processors (e.g., with `TARGET_HASWELL` macros). The downside of the Visual Studio approach is that the compiler is not allowed to use advanced instructions others than those we specify. In principle, this means that Visual Studio has weaker optimization opportunities. + + + +We also handle the special case where a user is compiling using LLVM clang under Windows, [using the Visual Studio toolchain](https://devblogs.microsoft.com/cppblog/clang-llvm-support-in-visual-studio/). If you compile with LLVM clang under Visual Studio, then the header files (intrin.h or x86intrin.h) no longer provides the intrinsic functions that are unsupported by the processor. This appears to be deliberate on the part of the LLVM engineers. With a few lines of code, we handle this scenario just like LLVM clang under a POSIX system, but forcing the inclusion of the specific headers, and rolling our own intrinsic function as needed. + + + + + +Regenerating Single-Header Files +--------------------------------------- + +The simdjson.h and simdjson.cpp files in the singleheader directory are not always up-to-date with the rest of the code; they are only ever +systematically regenerated on releases. To ensure you have the latest code, you can regenerate them by running this at the top level: + +```bash +mkdir build +cd build +cmake -D SIMDJSON_DEVELOPER_MODE=ON .. +cmake --build . # needed, because currently dependencies do not work fully for the amalgamate target +cmake --build . --target amalgamate +``` + +You need to have python3 installed on your system. + +The amalgamator script `amalgamate.py` generates singleheader/simdjson.h by +reading through include/simdjson.h, copy/pasting each header file into the amalgamated file at the +point it gets included (but only once per header). singleheader/simdjson.cpp is generated from +src/simdjson.cpp the same way, except files under generic/ may be included and copy/pasted multiple +times. + +## Usage (CMake on 64-bit platforms like Linux, FreeBSD or macOS) + +Requirements: In addition to git, we require a recent version of CMake as well as bash. + +1. On macOS, the easiest way to install cmake might be to use [brew](https://brew.sh) and then type +``` +brew install cmake +``` +2. Under Linux, you might be able to install CMake as follows: +``` +apt-get update -qq +apt-get install -y cmake +``` +3. On FreeBSD, you might be able to install bash and CMake as follows: +``` +pkg update -f +pkg install bash +pkg install cmake +``` + +You need a recent compiler like clang or gcc. We recommend at least GNU GCC/G++ 7 or LLVM clang 6. + + +Building: While in the project repository, do the following: + +``` +mkdir build +cd build +cmake -D SIMDJSON_DEVELOPER_MODE=ON .. +cmake --build . +ctest +``` + +CMake will build a library. By default, it builds a static library (e.g., libsimdjson.a on Linux). + +You can build a shared library: + +``` +mkdir buildshared +cd buildshared +cmake -D BUILD_SHARED_LIBS=ON -D SIMDJSON_DEVELOPER_MODE=ON .. +cmake --build . +ctest +``` + +In some cases, you may want to specify your compiler, especially if the default compiler on your system is too old. You need to tell cmake which compiler you wish to use by setting the CC and CXX variables. Under bash, you can do so with commands such as `export CC=gcc-7` and `export CXX=g++-7`. You can also do it as part of the `cmake` command: `cmake -DCMAKE_CXX_COMPILER=g++ ..`. You may proceed as follows: + +``` +brew install gcc@8 +mkdir build +cd build +export CXX=g++-8 CC=gcc-8 +cmake -D SIMDJSON_DEVELOPER_MODE=ON .. +cmake --build . +ctest +``` + +If your compiler does not default on C++11 support or better you may get failing tests. If so, you may be able to exclude the failing tests by replacing `ctest` with `ctest -E "^quickstart$"`. + +Note that the name of directory (`build`) is arbitrary, you can name it as you want (e.g., `buildgcc`) and you can have as many different such directories as you would like (one per configuration). + +## Usage (CMake on 64-bit Windows using Visual Studio 2019 or better) + +Recent versions of Visual Studio support CMake natively, [please refer to the Visual Studio documentation](https://learn.microsoft.com/en-us/cpp/build/cmake-projects-in-visual-studio?view=msvc-170). + +We assume you have a common 64-bit Windows PC with at least Visual Studio 2019. + +- Grab the simdjson code from GitHub, e.g., by cloning it using [GitHub Desktop](https://desktop.github.com/). +- Install [CMake](https://cmake.org/download/). When you install it, make sure to ask that `cmake` be made available from the command line. Please choose a recent version of cmake. +- Create a subdirectory within simdjson, such as `build`. +- Using a shell, go to this newly created directory. You can start a shell directly from GitHub Desktop (Repository > Open in Command Prompt). +- Type `cmake ..` in the shell while in the `build` repository. +- This last command (`cmake ...`) created a Visual Studio solution file in the newly created directory (e.g., `simdjson.sln`). Open this file in Visual Studio. You should now be able to build the project and run the tests. For example, in the `Solution Explorer` window (available from the `View` menu), right-click `ALL_BUILD` and select `Build`. To test the code, still in the `Solution Explorer` window, select `RUN_TESTS` and select `Build`. + + +Though having Visual Studio installed is necessary, one can build simdjson using only cmake commands: + +- `mkdir build` +- `cd build` +- `cmake ..` +- `cmake --build . --config Release` + + +Furthermore, if you have installed LLVM clang on Windows, for example as a component of Visual Studio 2019, you can configure and build simdjson using LLVM clang on Windows using cmake: + +- `mkdir build` +- `cd build` +- `cmake -T ClangCL ..` +- `cmake --build . --config Release` + +## Various References + +- [How to implement atoi using SIMD?](https://stackoverflow.com/questions/35127060/how-to-implement-atoi-using-simd) +- [Parsing JSON is a Minefield 💣](http://seriot.ch/parsing_json.php) +- https://tools.ietf.org/html/rfc7159 +- http://rapidjson.org/md_doc_sax.html +- https://github.com/Geal/parser_benchmarks/tree/master/json +- Gron: A command line tool that makes JSON greppable https://news.ycombinator.com/item?id=16727665 +- GoogleGson https://github.com/google/gson +- Jackson https://github.com/FasterXML/jackson +- https://www.yelp.com/dataset_challenge +- RapidJSON. http://rapidjson.org/ + +Inspiring links: + +- https://auth0.com/blog/beating-json-performance-with-protobuf/ +- https://gist.github.com/shijuvar/25ad7de9505232c87034b8359543404a +- https://github.com/frankmcsherry/blog/blob/master/posts/2018-02-11.md diff --git a/contrib/libs/simdjson/LICENSE b/contrib/libs/simdjson/LICENSE new file mode 100644 index 000000000000..71f65b598d90 --- /dev/null +++ b/contrib/libs/simdjson/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2023 The simdjson authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/contrib/libs/simdjson/README.md b/contrib/libs/simdjson/README.md new file mode 100644 index 000000000000..2cc209eafd01 --- /dev/null +++ b/contrib/libs/simdjson/README.md @@ -0,0 +1,231 @@ + +[![Ubuntu 20.04 CI](https://github.com/simdjson/simdjson/workflows/Ubuntu%2020.04%20CI%20(GCC%209)/badge.svg)](https://simdjson.org/plots.html) +[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/simdjson.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:simdjson) +[![][license img]][license] + +[![Doxygen Documentation](https://img.shields.io/badge/docs-doxygen-green.svg)](https://simdjson.github.io/simdjson/) + +simdjson : Parsing gigabytes of JSON per second +=============================================== + + +JSON is everywhere on the Internet. Servers spend a *lot* of time parsing it. We need a fresh +approach. The simdjson library uses commonly available SIMD instructions and microparallel algorithms +to parse JSON 4x faster than RapidJSON and 25x faster than JSON for Modern C++. + +* **Fast:** Over 4x faster than commonly used production-grade JSON parsers. +* **Record Breaking Features:** Minify JSON at 6 GB/s, validate UTF-8 at 13 GB/s, NDJSON at 3.5 GB/s. +* **Easy:** First-class, easy to use and carefully documented APIs. +* **Strict:** Full JSON and UTF-8 validation, lossless parsing. Performance with no compromises. +* **Automatic:** Selects a CPU-tailored parser at runtime. No configuration needed. +* **Reliable:** From memory allocation to error handling, simdjson's design avoids surprises. +* **Peer Reviewed:** Our research appears in venues like VLDB Journal, Software: Practice and Experience. + +This library is part of the [Awesome Modern C++](https://awesomecpp.com) list. + +Table of Contents +----------------- + +* [Real-world usage](#real-world-usage) +* [Quick Start](#quick-start) +* [Documentation](#documentation) +* [Godbolt](#godbolt) +* [Performance results](#performance-results) +* [Packages](#packages) +* [Bindings and Ports of simdjson](#bindings-and-ports-of-simdjson) +* [About simdjson](#about-simdjson) +* [Funding](#funding) +* [Contributing to simdjson](#contributing-to-simdjson) +* [License](#license) + + +Real-world usage +---------------- + +- [Node.js](https://nodejs.org/) +- [ClickHouse](https://github.com/ClickHouse/ClickHouse) +- [Meta Velox](https://velox-lib.io) +- [Google Pax](https://github.com/google/paxml) +- [milvus](https://github.com/milvus-io/milvus) +- [QuestDB](https://questdb.io/blog/questdb-release-8-0-3/) +- [Clang Build Analyzer](https://github.com/aras-p/ClangBuildAnalyzer) +- [Shopify HeapProfiler](https://github.com/Shopify/heap-profiler) +- [StarRocks](https://github.com/StarRocks/starrocks) +- [Microsoft FishStore](https://github.com/microsoft/FishStore) +- [Intel PCM](https://github.com/intel/pcm) +- [WatermelonDB](https://github.com/Nozbe/WatermelonDB) +- [Apache Doris](https://github.com/apache/doris) +- [Dgraph](https://github.com/dgraph-io/dgraph) +- [UJRPC](https://github.com/unum-cloud/ujrpc) +- [fastgltf](https://github.com/spnda/fastgltf) +- [vast](https://github.com/tenzir/vast) +- [ada-url](https://github.com/ada-url/ada) +- [fastgron](https://github.com/adamritter/fastgron) +- [WasmEdge](https://wasmedge.org) + +If you are planning to use simdjson in a product, please work from one of our releases. + +Quick Start +----------- + +The simdjson library is easily consumable with a single .h and .cpp file. + +0. Prerequisites: `g++` (version 7 or better) or `clang++` (version 6 or better), and a 64-bit + system with a command-line shell (e.g., Linux, macOS, freeBSD). We also support programming + environments like Visual Studio and Xcode, but different steps are needed. Users of clang++ may need to specify the C++ version (e.g., `c++ -std=c++17`) since clang++ tends to default on C++98. +1. Pull [simdjson.h](singleheader/simdjson.h) and [simdjson.cpp](singleheader/simdjson.cpp) into a + directory, along with the sample file [twitter.json](jsonexamples/twitter.json). You can download them with the `wget` utility: + + ``` + wget https://raw.githubusercontent.com/simdjson/simdjson/master/singleheader/simdjson.h https://raw.githubusercontent.com/simdjson/simdjson/master/singleheader/simdjson.cpp https://raw.githubusercontent.com/simdjson/simdjson/master/jsonexamples/twitter.json + ``` +2. Create `quickstart.cpp`: + +```c++ +#include +#include "simdjson.h" +using namespace simdjson; +int main(void) { + ondemand::parser parser; + padded_string json = padded_string::load("twitter.json"); + ondemand::document tweets = parser.iterate(json); + std::cout << uint64_t(tweets["search_metadata"]["count"]) << " results." << std::endl; +} +``` +3. `c++ -o quickstart quickstart.cpp simdjson.cpp` +4. `./quickstart` + + ``` + 100 results. + ``` + + +Documentation +------------- + +Usage documentation is available: + +* [Basics](doc/basics.md) is an overview of how to use simdjson and its APIs. +* [Performance](doc/performance.md) shows some more advanced scenarios and how to tune for them. +* [Implementation Selection](doc/implementation-selection.md) describes runtime CPU detection and + how you can work with it. +* [API](https://simdjson.github.io/simdjson/) contains the automatically generated API documentation. + +Godbolt +------------- + +Some users may want to browse code along with the compiled assembly. You want to check out the following lists of examples: +* [simdjson examples with errors handled through exceptions](https://godbolt.org/z/7G5qE4sr9) +* [simdjson examples with errors without exceptions](https://godbolt.org/z/e9dWb9E4v) + +Performance results +------------------- + +The simdjson library uses three-quarters less instructions than state-of-the-art parser [RapidJSON](https://rapidjson.org). To our knowledge, simdjson is the first fully-validating JSON parser +to run at [gigabytes per second](https://en.wikipedia.org/wiki/Gigabyte) (GB/s) on commodity processors. It can parse millions of JSON documents per second on a single core. + +The following figure represents parsing speed in GB/s for parsing various files +on an Intel Skylake processor (3.4 GHz) using the GNU GCC 10 compiler (with the -O3 flag). +We compare against the best and fastest C++ libraries on benchmarks that load and process the data. +The simdjson library offers full unicode ([UTF-8](https://en.wikipedia.org/wiki/UTF-8)) validation and exact +number parsing. + + + +The simdjson library offers high speed whether it processes tiny files (e.g., 300 bytes) +or larger files (e.g., 3MB). The following plot presents parsing +speed for [synthetic files over various sizes generated with a script](https://github.com/simdjson/simdjson_experiments_vldb2019/blob/master/experiments/growing/gen.py) on a 3.4 GHz Skylake processor (GNU GCC 9, -O3). + + + +[All our experiments are reproducible](https://github.com/simdjson/simdjson_experiments_vldb2019). + + +For NDJSON files, we can exceed 3 GB/s with [our multithreaded parsing functions](https://github.com/simdjson/simdjson/blob/master/doc/parse_many.md). + + +Packages +------------------------------ +[![Packaging status](https://repology.org/badge/vertical-allrepos/simdjson.svg)](https://repology.org/project/simdjson/versions) + + +Bindings and Ports of simdjson +------------------------------ + +We distinguish between "bindings" (which just wrap the C++ code) and a port to another programming language (which reimplements everything). + +- [ZippyJSON](https://github.com/michaeleisel/zippyjson): Swift bindings for the simdjson project. +- [libpy_simdjson](https://github.com/gerrymanoim/libpy_simdjson/): high-speed Python bindings for simdjson using [libpy](https://github.com/quantopian/libpy). +- [pysimdjson](https://github.com/TkTech/pysimdjson): Python bindings for the simdjson project. +- [cysimdjson](https://github.com/TeskaLabs/cysimdjson): high-speed Python bindings for the simdjson project. +- [simdjson-rs](https://github.com/simd-lite): Rust port. +- [simdjson-rust](https://github.com/SunDoge/simdjson-rust): Rust wrapper (bindings). +- [SimdJsonSharp](https://github.com/EgorBo/SimdJsonSharp): C# version for .NET Core (bindings and full port). +- [simdjson_nodejs](https://github.com/luizperes/simdjson_nodejs): Node.js bindings for the simdjson project. +- [simdjson_php](https://github.com/crazyxman/simdjson_php): PHP bindings for the simdjson project. +- [simdjson_ruby](https://github.com/saka1/simdjson_ruby): Ruby bindings for the simdjson project. +- [fast_jsonparser](https://github.com/anilmaurya/fast_jsonparser): Ruby bindings for the simdjson project. +- [simdjson-go](https://github.com/minio/simdjson-go): Go port using Golang assembly. +- [rcppsimdjson](https://github.com/eddelbuettel/rcppsimdjson): R bindings. +- [simdjson_erlang](https://github.com/ChomperT/simdjson_erlang): erlang bindings. +- [simdjsone](https://github.com/saleyn/simdjsone): erlang bindings. +- [lua-simdjson](https://github.com/FourierTransformer/lua-simdjson): lua bindings. +- [hermes-json](https://hackage.haskell.org/package/hermes-json): haskell bindings. +- [simdjzon](https://github.com/travisstaloch/simdjzon): zig port. +- [JSON-Simd](https://github.com/rawleyfowler/JSON-simd): Raku bindings. +- [JSON::SIMD](https://metacpan.org/pod/JSON::SIMD): Perl bindings; fully-featured JSON module that uses simdjson for decoding. +- [gemmaJSON](https://github.com/sainttttt/gemmaJSON): Nim JSON parser based on simdjson bindings. +- [simdjson-java](https://github.com/simdjson/simdjson-java): Java port. + +About simdjson +-------------- + +The simdjson library takes advantage of modern microarchitectures, parallelizing with SIMD vector +instructions, reducing branch misprediction, and reducing data dependency to take advantage of each +CPU's multiple execution cores. + +Our default front-end is called On-Demand, and we wrote a paper about it: + +- John Keiser, Daniel Lemire, [On-Demand JSON: A Better Way to Parse Documents?](http://arxiv.org/abs/2312.17149), Software: Practice and Experience 54 (6), 2024. + +Some people [enjoy reading the first (2019) simdjson paper](https://arxiv.org/abs/1902.08318): A description of the design +and implementation of simdjson is in our research article: +- Geoff Langdale, Daniel Lemire, [Parsing Gigabytes of JSON per Second](https://arxiv.org/abs/1902.08318), VLDB Journal 28 (6), 2019. + +We have an in-depth paper focused on the UTF-8 validation: + +- John Keiser, Daniel Lemire, [Validating UTF-8 In Less Than One Instruction Per Byte](https://arxiv.org/abs/2010.03090), Software: Practice & Experience 51 (5), 2021. + +We also have an informal [blog post providing some background and context](https://branchfree.org/2019/02/25/paper-parsing-gigabytes-of-json-per-second/). + +For the video inclined,
+[![simdjson at QCon San Francisco 2019](http://img.youtube.com/vi/wlvKAT7SZIQ/0.jpg)](http://www.youtube.com/watch?v=wlvKAT7SZIQ)
+(It was the best voted talk, we're kinda proud of it.) + +Funding +------- + +The work is supported by the Natural Sciences and Engineering Research Council of Canada under grants +RGPIN-2017-03910 and RGPIN-2024-03787. + +[license]: LICENSE +[license img]: https://img.shields.io/badge/License-Apache%202-blue.svg + +Contributing to simdjson +------------------------ + +Head over to [CONTRIBUTING.md](CONTRIBUTING.md) for information on contributing to simdjson, and +[HACKING.md](HACKING.md) for information on source, building, and architecture/design. + +License +------- + +This code is made available under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html). + +Under Windows, we build some tools using the windows/dirent_portable.h file (which is outside our library code): it is under the liberal (business-friendly) MIT license. + +For compilers that do not support [C++17](https://en.wikipedia.org/wiki/C%2B%2B17), we bundle the string-view library which is published under the [Boost license](http://www.boost.org/LICENSE_1_0.txt). Like the Apache license, the Boost license is a permissive license allowing commercial redistribution. + +For efficient number serialization, we bundle Florian Loitsch's implementation of the Grisu2 algorithm for binary to decimal floating-point numbers. The implementation was slightly modified by JSON for Modern C++ library. Both Florian Loitsch's implementation and JSON for Modern C++ are provided under the MIT license. + +For runtime dispatching, we use some code from the PyTorch project licensed under 3-clause BSD. diff --git a/contrib/libs/simdjson/SECURITY.md b/contrib/libs/simdjson/SECURITY.md new file mode 100644 index 000000000000..87c4c9b35e31 --- /dev/null +++ b/contrib/libs/simdjson/SECURITY.md @@ -0,0 +1,7 @@ +# Security Policy + +## Reporting a Vulnerability + +Please use the following contact information for reporting a vulnerability: + +- [Daniel Lemire](https://github.com/lemire) - daniel@lemire.me diff --git a/contrib/libs/simdjson/include/simdjson.h b/contrib/libs/simdjson/include/simdjson.h new file mode 100644 index 000000000000..194435f520a3 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson.h @@ -0,0 +1,56 @@ +#ifndef SIMDJSON_H +#define SIMDJSON_H + +/** + * @mainpage + * + * Check the [README.md](https://github.com/simdjson/simdjson/blob/master/README.md#simdjson--parsing-gigabytes-of-json-per-second). + * + * Sample code. See https://github.com/simdjson/simdjson/blob/master/doc/basics.md for more examples. + + #include "simdjson.h" + + int main(void) { + // load from `twitter.json` file: + simdjson::dom::parser parser; + simdjson::dom::element tweets = parser.load("twitter.json"); + std::cout << tweets["search_metadata"]["count"] << " results." << std::endl; + + // Parse and iterate through an array of objects + auto abstract_json = R"( [ + { "12345" : {"a":12.34, "b":56.78, "c": 9998877} }, + { "12545" : {"a":11.44, "b":12.78, "c": 11111111} } + ] )"_padded; + + for (simdjson::dom::object obj : parser.parse(abstract_json)) { + for(const auto key_value : obj) { + cout << "key: " << key_value.key << " : "; + simdjson::dom::object innerobj = key_value.value; + cout << "a: " << double(innerobj["a"]) << ", "; + cout << "b: " << double(innerobj["b"]) << ", "; + cout << "c: " << int64_t(innerobj["c"]) << endl; + } + } + } + */ + +#include "simdjson/common_defs.h" + +// This provides the public API for simdjson. +// DOM and ondemand are amalgamated separately, in simdjson.h +#include "simdjson/simdjson_version.h" + +#include "simdjson/base.h" + +#include "simdjson/error.h" +#include "simdjson/error-inl.h" +#include "simdjson/implementation.h" +#include "simdjson/minify.h" +#include "simdjson/padded_string.h" +#include "simdjson/padded_string-inl.h" +#include "simdjson/padded_string_view.h" +#include "simdjson/padded_string_view-inl.h" + +#include "simdjson/dom.h" +#include "simdjson/ondemand.h" +#endif // SIMDJSON_H diff --git a/contrib/libs/simdjson/include/simdjson/arm64.h b/contrib/libs/simdjson/include/simdjson/arm64.h new file mode 100644 index 000000000000..1493c3562a5d --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/arm64.h @@ -0,0 +1,8 @@ +#ifndef SIMDJSON_ARM64_H +#define SIMDJSON_ARM64_H + +#include "simdjson/arm64/begin.h" +#include "simdjson/generic/amalgamated.h" +#include "simdjson/arm64/end.h" + +#endif // SIMDJSON_ARM64_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/arm64/base.h b/contrib/libs/simdjson/include/simdjson/arm64/base.h new file mode 100644 index 000000000000..8f23d18d80e4 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/arm64/base.h @@ -0,0 +1,26 @@ +#ifndef SIMDJSON_ARM64_BASE_H +#define SIMDJSON_ARM64_BASE_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +/** + * Implementation for NEON (ARMv8). + */ +namespace arm64 { + +class implementation; + +namespace { +namespace simd { +template struct simd8; +template struct simd8x64; +} // namespace simd +} // unnamed namespace + +} // namespace arm64 +} // namespace simdjson + +#endif // SIMDJSON_ARM64_BASE_H diff --git a/contrib/libs/simdjson/include/simdjson/arm64/begin.h b/contrib/libs/simdjson/include/simdjson/arm64/begin.h new file mode 100644 index 000000000000..ee48cec05150 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/arm64/begin.h @@ -0,0 +1,10 @@ +#define SIMDJSON_IMPLEMENTATION arm64 +#include "simdjson/arm64/base.h" +#include "simdjson/arm64/intrinsics.h" +#include "simdjson/arm64/bitmanipulation.h" +#include "simdjson/arm64/bitmask.h" +#include "simdjson/arm64/numberparsing_defs.h" +#include "simdjson/arm64/simd.h" +#include "simdjson/arm64/stringparsing_defs.h" + +#define SIMDJSON_SKIP_BACKSLASH_SHORT_CIRCUIT 1 \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/arm64/bitmanipulation.h b/contrib/libs/simdjson/include/simdjson/arm64/bitmanipulation.h new file mode 100644 index 000000000000..fc51beafaec3 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/arm64/bitmanipulation.h @@ -0,0 +1,112 @@ +#ifndef SIMDJSON_ARM64_BITMANIPULATION_H +#define SIMDJSON_ARM64_BITMANIPULATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/arm64/base.h" +#include "simdjson/arm64/intrinsics.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace arm64 { +namespace { + +// We sometimes call trailing_zero on inputs that are zero, +// but the algorithms do not end up using the returned value. +// Sadly, sanitizers are not smart enough to figure it out. +SIMDJSON_NO_SANITIZE_UNDEFINED +// This function can be used safely even if not all bytes have been +// initialized. +// See issue https://github.com/simdjson/simdjson/issues/1965 +SIMDJSON_NO_SANITIZE_MEMORY +simdjson_inline int trailing_zeroes(uint64_t input_num) { +#if SIMDJSON_REGULAR_VISUAL_STUDIO + unsigned long ret; + // Search the mask data from least significant bit (LSB) + // to the most significant bit (MSB) for a set bit (1). + _BitScanForward64(&ret, input_num); + return (int)ret; +#else // SIMDJSON_REGULAR_VISUAL_STUDIO + return __builtin_ctzll(input_num); +#endif // SIMDJSON_REGULAR_VISUAL_STUDIO +} + +/* result might be undefined when input_num is zero */ +simdjson_inline uint64_t clear_lowest_bit(uint64_t input_num) { + return input_num & (input_num-1); +} + +// We sometimes call leading_zeroes on inputs that are zero, +// but the algorithms do not end up using the returned value. +// Sadly, sanitizers are not smart enough to figure it out. +// Applies only when SIMDJSON_PREFER_REVERSE_BITS is defined and true. +// (See below.) +SIMDJSON_NO_SANITIZE_UNDEFINED +/* result might be undefined when input_num is zero */ +simdjson_inline int leading_zeroes(uint64_t input_num) { +#if SIMDJSON_REGULAR_VISUAL_STUDIO + unsigned long leading_zero = 0; + // Search the mask data from most significant bit (MSB) + // to least significant bit (LSB) for a set bit (1). + if (_BitScanReverse64(&leading_zero, input_num)) + return (int)(63 - leading_zero); + else + return 64; +#else + return __builtin_clzll(input_num); +#endif// SIMDJSON_REGULAR_VISUAL_STUDIO +} + +/* result might be undefined when input_num is zero */ +simdjson_inline int count_ones(uint64_t input_num) { + return vaddv_u8(vcnt_u8(vcreate_u8(input_num))); +} + + +#if defined(__GNUC__) // catches clang and gcc +/** + * ARM has a fast 64-bit "bit reversal function" that is handy. However, + * it is not generally available as an intrinsic function under Visual + * Studio (though this might be changing). Even under clang/gcc, we + * apparently need to invoke inline assembly. + */ +/* + * We use SIMDJSON_PREFER_REVERSE_BITS as a hint that algorithms that + * work well with bit reversal may use it. + */ +#define SIMDJSON_PREFER_REVERSE_BITS 1 + +/* reverse the bits */ +simdjson_inline uint64_t reverse_bits(uint64_t input_num) { + uint64_t rev_bits; + __asm("rbit %0, %1" : "=r"(rev_bits) : "r"(input_num)); + return rev_bits; +} + +/** + * Flips bit at index 63 - lz. Thus if you have 'leading_zeroes' leading zeroes, + * then this will set to zero the leading bit. It is possible for leading_zeroes to be + * greating or equal to 63 in which case we trigger undefined behavior, but the output + * of such undefined behavior is never used. + **/ +SIMDJSON_NO_SANITIZE_UNDEFINED +simdjson_inline uint64_t zero_leading_bit(uint64_t rev_bits, int leading_zeroes) { + return rev_bits ^ (uint64_t(0x8000000000000000) >> leading_zeroes); +} + +#endif + +simdjson_inline bool add_overflow(uint64_t value1, uint64_t value2, uint64_t *result) { +#if SIMDJSON_REGULAR_VISUAL_STUDIO + *result = value1 + value2; + return *result < value1; +#else + return __builtin_uaddll_overflow(value1, value2, + reinterpret_cast(result)); +#endif +} + +} // unnamed namespace +} // namespace arm64 +} // namespace simdjson + +#endif // SIMDJSON_ARM64_BITMANIPULATION_H diff --git a/contrib/libs/simdjson/include/simdjson/arm64/bitmask.h b/contrib/libs/simdjson/include/simdjson/arm64/bitmask.h new file mode 100644 index 000000000000..5d6121bcc7b1 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/arm64/bitmask.h @@ -0,0 +1,44 @@ +#ifndef SIMDJSON_ARM64_BITMASK_H +#define SIMDJSON_ARM64_BITMASK_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/arm64/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace arm64 { +namespace { + +// +// Perform a "cumulative bitwise xor," flipping bits each time a 1 is encountered. +// +// For example, prefix_xor(00100100) == 00011100 +// +simdjson_inline uint64_t prefix_xor(uint64_t bitmask) { + ///////////// + // We could do this with PMULL, but it is apparently slow. + // + //#ifdef __ARM_FEATURE_CRYPTO // some ARM processors lack this extension + //return vmull_p64(-1ULL, bitmask); + //#else + // Analysis by @sebpop: + // When diffing the assembly for src/stage1_find_marks.cpp I see that the eors are all spread out + // in between other vector code, so effectively the extra cycles of the sequence do not matter + // because the GPR units are idle otherwise and the critical path is on the FP side. + // Also the PMULL requires two extra fmovs: GPR->FP (3 cycles in N1, 5 cycles in A72 ) + // and FP->GPR (2 cycles on N1 and 5 cycles on A72.) + /////////// + bitmask ^= bitmask << 1; + bitmask ^= bitmask << 2; + bitmask ^= bitmask << 4; + bitmask ^= bitmask << 8; + bitmask ^= bitmask << 16; + bitmask ^= bitmask << 32; + return bitmask; +} + +} // unnamed namespace +} // namespace arm64 +} // namespace simdjson + +#endif diff --git a/contrib/libs/simdjson/include/simdjson/arm64/end.h b/contrib/libs/simdjson/include/simdjson/arm64/end.h new file mode 100644 index 000000000000..b92378df97fd --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/arm64/end.h @@ -0,0 +1,6 @@ +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/arm64/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#undef SIMDJSON_SKIP_BACKSLASH_SHORT_CIRCUIT +#undef SIMDJSON_IMPLEMENTATION diff --git a/contrib/libs/simdjson/include/simdjson/arm64/implementation.h b/contrib/libs/simdjson/include/simdjson/arm64/implementation.h new file mode 100644 index 000000000000..c9b7dd753507 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/arm64/implementation.h @@ -0,0 +1,31 @@ +#ifndef SIMDJSON_ARM64_IMPLEMENTATION_H +#define SIMDJSON_ARM64_IMPLEMENTATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/base.h" +#include "simdjson/implementation.h" +#include "simdjson/internal/instruction_set.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace arm64 { + +/** + * @private + */ +class implementation final : public simdjson::implementation { +public: + simdjson_inline implementation() : simdjson::implementation("arm64", "ARM NEON", internal::instruction_set::NEON) {} + simdjson_warn_unused error_code create_dom_parser_implementation( + size_t capacity, + size_t max_length, + std::unique_ptr& dst + ) const noexcept final; + simdjson_warn_unused error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept final; + simdjson_warn_unused bool validate_utf8(const char *buf, size_t len) const noexcept final; +}; + +} // namespace arm64 +} // namespace simdjson + +#endif // SIMDJSON_ARM64_IMPLEMENTATION_H diff --git a/contrib/libs/simdjson/include/simdjson/arm64/intrinsics.h b/contrib/libs/simdjson/include/simdjson/arm64/intrinsics.h new file mode 100644 index 000000000000..049d99861020 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/arm64/intrinsics.h @@ -0,0 +1,14 @@ +#ifndef SIMDJSON_ARM64_INTRINSICS_H +#define SIMDJSON_ARM64_INTRINSICS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/arm64/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +// This should be the correct header whether +// you use visual studio or other compilers. +#include + +static_assert(sizeof(uint8x16_t) <= simdjson::SIMDJSON_PADDING, "insufficient padding for arm64"); + +#endif // SIMDJSON_ARM64_INTRINSICS_H diff --git a/contrib/libs/simdjson/include/simdjson/arm64/numberparsing_defs.h b/contrib/libs/simdjson/include/simdjson/arm64/numberparsing_defs.h new file mode 100644 index 000000000000..a765cb9a42fb --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/arm64/numberparsing_defs.h @@ -0,0 +1,62 @@ +#ifndef SIMDJSON_ARM64_NUMBERPARSING_DEFS_H +#define SIMDJSON_ARM64_NUMBERPARSING_DEFS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/arm64/base.h" +#include "simdjson/arm64/intrinsics.h" +#include "simdjson/internal/numberparsing_tables.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include + +#if SIMDJSON_REGULAR_VISUAL_STUDIO && SIMDJSON_IS_ARM64 +// __umulh requires intrin.h +#include +#endif // SIMDJSON_REGULAR_VISUAL_STUDIO && SIMDJSON_IS_ARM64 + +namespace simdjson { +namespace arm64 { +namespace numberparsing { + +// we don't have SSE, so let us use a scalar function +// credit: https://johnnylee-sde.github.io/Fast-numeric-string-to-int/ +/** @private */ +static simdjson_inline uint32_t parse_eight_digits_unrolled(const uint8_t *chars) { + uint64_t val; + std::memcpy(&val, chars, sizeof(uint64_t)); + val = (val & 0x0F0F0F0F0F0F0F0F) * 2561 >> 8; + val = (val & 0x00FF00FF00FF00FF) * 6553601 >> 16; + return uint32_t((val & 0x0000FFFF0000FFFF) * 42949672960001 >> 32); +} + +simdjson_inline internal::value128 full_multiplication(uint64_t value1, uint64_t value2) { + internal::value128 answer; +#if SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS +#if SIMDJSON_IS_ARM64 + // ARM64 has native support for 64-bit multiplications, no need to emultate + answer.high = __umulh(value1, value2); + answer.low = value1 * value2; +#else + answer.low = _umul128(value1, value2, &answer.high); // _umul128 not available on ARM64 +#endif // SIMDJSON_IS_ARM64 +#else // SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS + __uint128_t r = (static_cast<__uint128_t>(value1)) * value2; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); +#endif + return answer; +} + +} // namespace numberparsing +} // namespace arm64 +} // namespace simdjson + +#ifndef SIMDJSON_SWAR_NUMBER_PARSING +#if SIMDJSON_IS_BIG_ENDIAN +#define SIMDJSON_SWAR_NUMBER_PARSING 0 +#else +#define SIMDJSON_SWAR_NUMBER_PARSING 1 +#endif +#endif + +#endif // SIMDJSON_ARM64_NUMBERPARSING_DEFS_H diff --git a/contrib/libs/simdjson/include/simdjson/arm64/ondemand.h b/contrib/libs/simdjson/include/simdjson/arm64/ondemand.h new file mode 100644 index 000000000000..67134394675e --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/arm64/ondemand.h @@ -0,0 +1,8 @@ +#ifndef SIMDJSON_ARM64_ONDEMAND_H +#define SIMDJSON_ARM64_ONDEMAND_H + +#include "simdjson/arm64/begin.h" +#include "simdjson/generic/ondemand/amalgamated.h" +#include "simdjson/arm64/end.h" + +#endif // SIMDJSON_ARM64_ONDEMAND_H diff --git a/contrib/libs/simdjson/include/simdjson/arm64/simd.h b/contrib/libs/simdjson/include/simdjson/arm64/simd.h new file mode 100644 index 000000000000..3881e7db7a11 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/arm64/simd.h @@ -0,0 +1,497 @@ +#ifndef SIMDJSON_ARM64_SIMD_H +#define SIMDJSON_ARM64_SIMD_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/arm64/base.h" +#include "simdjson/arm64/bitmanipulation.h" +#include "simdjson/internal/simdprune_tables.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace arm64 { +namespace { +namespace simd { + +#if SIMDJSON_REGULAR_VISUAL_STUDIO +namespace { +// Start of private section with Visual Studio workaround + + +#ifndef simdjson_make_uint8x16_t +#define simdjson_make_uint8x16_t(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, \ + x13, x14, x15, x16) \ + ([=]() { \ + uint8_t array[16] = {x1, x2, x3, x4, x5, x6, x7, x8, \ + x9, x10, x11, x12, x13, x14, x15, x16}; \ + return vld1q_u8(array); \ + }()) +#endif +#ifndef simdjson_make_int8x16_t +#define simdjson_make_int8x16_t(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, \ + x13, x14, x15, x16) \ + ([=]() { \ + int8_t array[16] = {x1, x2, x3, x4, x5, x6, x7, x8, \ + x9, x10, x11, x12, x13, x14, x15, x16}; \ + return vld1q_s8(array); \ + }()) +#endif + +#ifndef simdjson_make_uint8x8_t +#define simdjson_make_uint8x8_t(x1, x2, x3, x4, x5, x6, x7, x8) \ + ([=]() { \ + uint8_t array[8] = {x1, x2, x3, x4, x5, x6, x7, x8}; \ + return vld1_u8(array); \ + }()) +#endif +#ifndef simdjson_make_int8x8_t +#define simdjson_make_int8x8_t(x1, x2, x3, x4, x5, x6, x7, x8) \ + ([=]() { \ + int8_t array[8] = {x1, x2, x3, x4, x5, x6, x7, x8}; \ + return vld1_s8(array); \ + }()) +#endif +#ifndef simdjson_make_uint16x8_t +#define simdjson_make_uint16x8_t(x1, x2, x3, x4, x5, x6, x7, x8) \ + ([=]() { \ + uint16_t array[8] = {x1, x2, x3, x4, x5, x6, x7, x8}; \ + return vld1q_u16(array); \ + }()) +#endif +#ifndef simdjson_make_int16x8_t +#define simdjson_make_int16x8_t(x1, x2, x3, x4, x5, x6, x7, x8) \ + ([=]() { \ + int16_t array[8] = {x1, x2, x3, x4, x5, x6, x7, x8}; \ + return vld1q_s16(array); \ + }()) +#endif + +// End of private section with Visual Studio workaround +} // namespace +#endif // SIMDJSON_REGULAR_VISUAL_STUDIO + + + template + struct simd8; + + // + // Base class of simd8 and simd8, both of which use uint8x16_t internally. + // + template> + struct base_u8 { + uint8x16_t value; + static const int SIZE = sizeof(value); + + // Conversion from/to SIMD register + simdjson_inline base_u8(const uint8x16_t _value) : value(_value) {} + simdjson_inline operator const uint8x16_t&() const { return this->value; } + simdjson_inline operator uint8x16_t&() { return this->value; } + + // Bit operations + simdjson_inline simd8 operator|(const simd8 other) const { return vorrq_u8(*this, other); } + simdjson_inline simd8 operator&(const simd8 other) const { return vandq_u8(*this, other); } + simdjson_inline simd8 operator^(const simd8 other) const { return veorq_u8(*this, other); } + simdjson_inline simd8 bit_andnot(const simd8 other) const { return vbicq_u8(*this, other); } + simdjson_inline simd8 operator~() const { return *this ^ 0xFFu; } + simdjson_inline simd8& operator|=(const simd8 other) { auto this_cast = static_cast*>(this); *this_cast = *this_cast | other; return *this_cast; } + simdjson_inline simd8& operator&=(const simd8 other) { auto this_cast = static_cast*>(this); *this_cast = *this_cast & other; return *this_cast; } + simdjson_inline simd8& operator^=(const simd8 other) { auto this_cast = static_cast*>(this); *this_cast = *this_cast ^ other; return *this_cast; } + + friend simdjson_inline Mask operator==(const simd8 lhs, const simd8 rhs) { return vceqq_u8(lhs, rhs); } + + template + simdjson_inline simd8 prev(const simd8 prev_chunk) const { + return vextq_u8(prev_chunk, *this, 16 - N); + } + }; + + // SIMD byte mask type (returned by things like eq and gt) + template<> + struct simd8: base_u8 { + typedef uint16_t bitmask_t; + typedef uint32_t bitmask2_t; + + static simdjson_inline simd8 splat(bool _value) { return vmovq_n_u8(uint8_t(-(!!_value))); } + + simdjson_inline simd8(const uint8x16_t _value) : base_u8(_value) {} + // False constructor + simdjson_inline simd8() : simd8(vdupq_n_u8(0)) {} + // Splat constructor + simdjson_inline simd8(bool _value) : simd8(splat(_value)) {} + + // We return uint32_t instead of uint16_t because that seems to be more efficient for most + // purposes (cutting it down to uint16_t costs performance in some compilers). + simdjson_inline uint32_t to_bitmask() const { +#if SIMDJSON_REGULAR_VISUAL_STUDIO + const uint8x16_t bit_mask = simdjson_make_uint8x16_t(0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, + 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80); +#else + const uint8x16_t bit_mask = {0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, + 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80}; +#endif + auto minput = *this & bit_mask; + uint8x16_t tmp = vpaddq_u8(minput, minput); + tmp = vpaddq_u8(tmp, tmp); + tmp = vpaddq_u8(tmp, tmp); + return vgetq_lane_u16(vreinterpretq_u16_u8(tmp), 0); + } + simdjson_inline bool any() const { return vmaxvq_u32(vreinterpretq_u32_u8(*this)) != 0; } + }; + + // Unsigned bytes + template<> + struct simd8: base_u8 { + static simdjson_inline uint8x16_t splat(uint8_t _value) { return vmovq_n_u8(_value); } + static simdjson_inline uint8x16_t zero() { return vdupq_n_u8(0); } + static simdjson_inline uint8x16_t load(const uint8_t* values) { return vld1q_u8(values); } + + simdjson_inline simd8(const uint8x16_t _value) : base_u8(_value) {} + // Zero constructor + simdjson_inline simd8() : simd8(zero()) {} + // Array constructor + simdjson_inline simd8(const uint8_t values[16]) : simd8(load(values)) {} + // Splat constructor + simdjson_inline simd8(uint8_t _value) : simd8(splat(_value)) {} + // Member-by-member initialization +#if SIMDJSON_REGULAR_VISUAL_STUDIO + simdjson_inline simd8( + uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, + uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15 + ) : simd8(simdjson_make_uint8x16_t( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + )) {} +#else + simdjson_inline simd8( + uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, + uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15 + ) : simd8(uint8x16_t{ + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + }) {} +#endif + + // Repeat 16 values as many times as necessary (usually for lookup tables) + simdjson_inline static simd8 repeat_16( + uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, + uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15 + ) { + return simd8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + ); + } + + // Store to array + simdjson_inline void store(uint8_t dst[16]) const { return vst1q_u8(dst, *this); } + + // Saturated math + simdjson_inline simd8 saturating_add(const simd8 other) const { return vqaddq_u8(*this, other); } + simdjson_inline simd8 saturating_sub(const simd8 other) const { return vqsubq_u8(*this, other); } + + // Addition/subtraction are the same for signed and unsigned + simdjson_inline simd8 operator+(const simd8 other) const { return vaddq_u8(*this, other); } + simdjson_inline simd8 operator-(const simd8 other) const { return vsubq_u8(*this, other); } + simdjson_inline simd8& operator+=(const simd8 other) { *this = *this + other; return *this; } + simdjson_inline simd8& operator-=(const simd8 other) { *this = *this - other; return *this; } + + // Order-specific operations + simdjson_inline uint8_t max_val() const { return vmaxvq_u8(*this); } + simdjson_inline uint8_t min_val() const { return vminvq_u8(*this); } + simdjson_inline simd8 max_val(const simd8 other) const { return vmaxq_u8(*this, other); } + simdjson_inline simd8 min_val(const simd8 other) const { return vminq_u8(*this, other); } + simdjson_inline simd8 operator<=(const simd8 other) const { return vcleq_u8(*this, other); } + simdjson_inline simd8 operator>=(const simd8 other) const { return vcgeq_u8(*this, other); } + simdjson_inline simd8 operator<(const simd8 other) const { return vcltq_u8(*this, other); } + simdjson_inline simd8 operator>(const simd8 other) const { return vcgtq_u8(*this, other); } + // Same as >, but instead of guaranteeing all 1's == true, false = 0 and true = nonzero. For ARM, returns all 1's. + simdjson_inline simd8 gt_bits(const simd8 other) const { return simd8(*this > other); } + // Same as <, but instead of guaranteeing all 1's == true, false = 0 and true = nonzero. For ARM, returns all 1's. + simdjson_inline simd8 lt_bits(const simd8 other) const { return simd8(*this < other); } + + // Bit-specific operations + simdjson_inline simd8 any_bits_set(simd8 bits) const { return vtstq_u8(*this, bits); } + simdjson_inline bool any_bits_set_anywhere() const { return this->max_val() != 0; } + simdjson_inline bool any_bits_set_anywhere(simd8 bits) const { return (*this & bits).any_bits_set_anywhere(); } + template + simdjson_inline simd8 shr() const { return vshrq_n_u8(*this, N); } + template + simdjson_inline simd8 shl() const { return vshlq_n_u8(*this, N); } + + // Perform a lookup assuming the value is between 0 and 16 (undefined behavior for out of range values) + template + simdjson_inline simd8 lookup_16(simd8 lookup_table) const { + return lookup_table.apply_lookup_16_to(*this); + } + + + // Copies to 'output" all bytes corresponding to a 0 in the mask (interpreted as a bitset). + // Passing a 0 value for mask would be equivalent to writing out every byte to output. + // Only the first 16 - count_ones(mask) bytes of the result are significant but 16 bytes + // get written. + // Design consideration: it seems like a function with the + // signature simd8 compress(uint16_t mask) would be + // sensible, but the AVX ISA makes this kind of approach difficult. + template + simdjson_inline void compress(uint16_t mask, L * output) const { + using internal::thintable_epi8; + using internal::BitsSetTable256mul2; + using internal::pshufb_combine_table; + // this particular implementation was inspired by work done by @animetosho + // we do it in two steps, first 8 bytes and then second 8 bytes + uint8_t mask1 = uint8_t(mask); // least significant 8 bits + uint8_t mask2 = uint8_t(mask >> 8); // most significant 8 bits + // next line just loads the 64-bit values thintable_epi8[mask1] and + // thintable_epi8[mask2] into a 128-bit register, using only + // two instructions on most compilers. + uint64x2_t shufmask64 = {thintable_epi8[mask1], thintable_epi8[mask2]}; + uint8x16_t shufmask = vreinterpretq_u8_u64(shufmask64); + // we increment by 0x08 the second half of the mask +#if SIMDJSON_REGULAR_VISUAL_STUDIO + uint8x16_t inc = simdjson_make_uint8x16_t(0, 0, 0, 0, 0, 0, 0, 0, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08); +#else + uint8x16_t inc = {0, 0, 0, 0, 0, 0, 0, 0, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}; +#endif + shufmask = vaddq_u8(shufmask, inc); + // this is the version "nearly pruned" + uint8x16_t pruned = vqtbl1q_u8(*this, shufmask); + // we still need to put the two halves together. + // we compute the popcount of the first half: + int pop1 = BitsSetTable256mul2[mask1]; + // then load the corresponding mask, what it does is to write + // only the first pop1 bytes from the first 8 bytes, and then + // it fills in with the bytes from the second 8 bytes + some filling + // at the end. + uint8x16_t compactmask = vld1q_u8(reinterpret_cast(pshufb_combine_table + pop1 * 8)); + uint8x16_t answer = vqtbl1q_u8(pruned, compactmask); + vst1q_u8(reinterpret_cast(output), answer); + } + + // Copies all bytes corresponding to a 0 in the low half of the mask (interpreted as a + // bitset) to output1, then those corresponding to a 0 in the high half to output2. + template + simdjson_inline void compress_halves(uint16_t mask, L *output1, L *output2) const { + using internal::thintable_epi8; + uint8_t mask1 = uint8_t(mask); // least significant 8 bits + uint8_t mask2 = uint8_t(mask >> 8); // most significant 8 bits + uint8x8_t compactmask1 = vcreate_u8(thintable_epi8[mask1]); + uint8x8_t compactmask2 = vcreate_u8(thintable_epi8[mask2]); + // we increment by 0x08 the second half of the mask +#if SIMDJSON_REGULAR_VISUAL_STUDIO + uint8x8_t inc = simdjson_make_uint8x8_t(0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08); +#else + uint8x8_t inc = {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}; +#endif + compactmask2 = vadd_u8(compactmask2, inc); + // store each result (with the second store possibly overlapping the first) + vst1_u8((uint8_t*)output1, vqtbl1_u8(*this, compactmask1)); + vst1_u8((uint8_t*)output2, vqtbl1_u8(*this, compactmask2)); + } + + template + simdjson_inline simd8 lookup_16( + L replace0, L replace1, L replace2, L replace3, + L replace4, L replace5, L replace6, L replace7, + L replace8, L replace9, L replace10, L replace11, + L replace12, L replace13, L replace14, L replace15) const { + return lookup_16(simd8::repeat_16( + replace0, replace1, replace2, replace3, + replace4, replace5, replace6, replace7, + replace8, replace9, replace10, replace11, + replace12, replace13, replace14, replace15 + )); + } + + template + simdjson_inline simd8 apply_lookup_16_to(const simd8 original) { + return vqtbl1q_u8(*this, simd8(original)); + } + }; + + // Signed bytes + template<> + struct simd8 { + int8x16_t value; + + static simdjson_inline simd8 splat(int8_t _value) { return vmovq_n_s8(_value); } + static simdjson_inline simd8 zero() { return vdupq_n_s8(0); } + static simdjson_inline simd8 load(const int8_t values[16]) { return vld1q_s8(values); } + + // Conversion from/to SIMD register + simdjson_inline simd8(const int8x16_t _value) : value{_value} {} + simdjson_inline operator const int8x16_t&() const { return this->value; } + simdjson_inline operator int8x16_t&() { return this->value; } + + // Zero constructor + simdjson_inline simd8() : simd8(zero()) {} + // Splat constructor + simdjson_inline simd8(int8_t _value) : simd8(splat(_value)) {} + // Array constructor + simdjson_inline simd8(const int8_t* values) : simd8(load(values)) {} + // Member-by-member initialization +#if SIMDJSON_REGULAR_VISUAL_STUDIO + simdjson_inline simd8( + int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, + int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15 + ) : simd8(simdjson_make_int8x16_t( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + )) {} +#else + simdjson_inline simd8( + int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, + int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15 + ) : simd8(int8x16_t{ + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + }) {} +#endif + // Repeat 16 values as many times as necessary (usually for lookup tables) + simdjson_inline static simd8 repeat_16( + int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, + int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15 + ) { + return simd8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + ); + } + + // Store to array + simdjson_inline void store(int8_t dst[16]) const { return vst1q_s8(dst, *this); } + + // Explicit conversion to/from unsigned + // + // Under Visual Studio/ARM64 uint8x16_t and int8x16_t are apparently the same type. + // In theory, we could check this occurrence with std::same_as and std::enabled_if but it is C++14 + // and relatively ugly and hard to read. +#ifndef SIMDJSON_REGULAR_VISUAL_STUDIO + simdjson_inline explicit simd8(const uint8x16_t other): simd8(vreinterpretq_s8_u8(other)) {} +#endif + simdjson_inline explicit operator simd8() const { return vreinterpretq_u8_s8(this->value); } + + // Math + simdjson_inline simd8 operator+(const simd8 other) const { return vaddq_s8(*this, other); } + simdjson_inline simd8 operator-(const simd8 other) const { return vsubq_s8(*this, other); } + simdjson_inline simd8& operator+=(const simd8 other) { *this = *this + other; return *this; } + simdjson_inline simd8& operator-=(const simd8 other) { *this = *this - other; return *this; } + + // Order-sensitive comparisons + simdjson_inline simd8 max_val(const simd8 other) const { return vmaxq_s8(*this, other); } + simdjson_inline simd8 min_val(const simd8 other) const { return vminq_s8(*this, other); } + simdjson_inline simd8 operator>(const simd8 other) const { return vcgtq_s8(*this, other); } + simdjson_inline simd8 operator<(const simd8 other) const { return vcltq_s8(*this, other); } + simdjson_inline simd8 operator==(const simd8 other) const { return vceqq_s8(*this, other); } + + template + simdjson_inline simd8 prev(const simd8 prev_chunk) const { + return vextq_s8(prev_chunk, *this, 16 - N); + } + + // Perform a lookup assuming no value is larger than 16 + template + simdjson_inline simd8 lookup_16(simd8 lookup_table) const { + return lookup_table.apply_lookup_16_to(*this); + } + template + simdjson_inline simd8 lookup_16( + L replace0, L replace1, L replace2, L replace3, + L replace4, L replace5, L replace6, L replace7, + L replace8, L replace9, L replace10, L replace11, + L replace12, L replace13, L replace14, L replace15) const { + return lookup_16(simd8::repeat_16( + replace0, replace1, replace2, replace3, + replace4, replace5, replace6, replace7, + replace8, replace9, replace10, replace11, + replace12, replace13, replace14, replace15 + )); + } + + template + simdjson_inline simd8 apply_lookup_16_to(const simd8 original) { + return vqtbl1q_s8(*this, simd8(original)); + } + }; + + template + struct simd8x64 { + static constexpr int NUM_CHUNKS = 64 / sizeof(simd8); + static_assert(NUM_CHUNKS == 4, "ARM kernel should use four registers per 64-byte block."); + const simd8 chunks[NUM_CHUNKS]; + + simd8x64(const simd8x64& o) = delete; // no copy allowed + simd8x64& operator=(const simd8& other) = delete; // no assignment allowed + simd8x64() = delete; // no default constructor allowed + + simdjson_inline simd8x64(const simd8 chunk0, const simd8 chunk1, const simd8 chunk2, const simd8 chunk3) : chunks{chunk0, chunk1, chunk2, chunk3} {} + simdjson_inline simd8x64(const T ptr[64]) : chunks{simd8::load(ptr), simd8::load(ptr+16), simd8::load(ptr+32), simd8::load(ptr+48)} {} + + simdjson_inline void store(T ptr[64]) const { + this->chunks[0].store(ptr+sizeof(simd8)*0); + this->chunks[1].store(ptr+sizeof(simd8)*1); + this->chunks[2].store(ptr+sizeof(simd8)*2); + this->chunks[3].store(ptr+sizeof(simd8)*3); + } + + simdjson_inline simd8 reduce_or() const { + return (this->chunks[0] | this->chunks[1]) | (this->chunks[2] | this->chunks[3]); + } + + + simdjson_inline uint64_t compress(uint64_t mask, T * output) const { + uint64_t popcounts = vget_lane_u64(vreinterpret_u64_u8(vcnt_u8(vcreate_u8(~mask))), 0); + // compute the prefix sum of the popcounts of each byte + uint64_t offsets = popcounts * 0x0101010101010101; + this->chunks[0].compress_halves(uint16_t(mask), output, &output[popcounts & 0xFF]); + this->chunks[1].compress_halves(uint16_t(mask >> 16), &output[(offsets >> 8) & 0xFF], &output[(offsets >> 16) & 0xFF]); + this->chunks[2].compress_halves(uint16_t(mask >> 32), &output[(offsets >> 24) & 0xFF], &output[(offsets >> 32) & 0xFF]); + this->chunks[3].compress_halves(uint16_t(mask >> 48), &output[(offsets >> 40) & 0xFF], &output[(offsets >> 48) & 0xFF]); + return offsets >> 56; + } + + simdjson_inline uint64_t to_bitmask() const { +#if SIMDJSON_REGULAR_VISUAL_STUDIO + const uint8x16_t bit_mask = simdjson_make_uint8x16_t( + 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, + 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80 + ); +#else + const uint8x16_t bit_mask = { + 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, + 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80 + }; +#endif + // Add each of the elements next to each other, successively, to stuff each 8 byte mask into one. + uint8x16_t sum0 = vpaddq_u8(this->chunks[0] & bit_mask, this->chunks[1] & bit_mask); + uint8x16_t sum1 = vpaddq_u8(this->chunks[2] & bit_mask, this->chunks[3] & bit_mask); + sum0 = vpaddq_u8(sum0, sum1); + sum0 = vpaddq_u8(sum0, sum0); + return vgetq_lane_u64(vreinterpretq_u64_u8(sum0), 0); + } + + simdjson_inline uint64_t eq(const T m) const { + const simd8 mask = simd8::splat(m); + return simd8x64( + this->chunks[0] == mask, + this->chunks[1] == mask, + this->chunks[2] == mask, + this->chunks[3] == mask + ).to_bitmask(); + } + + simdjson_inline uint64_t lteq(const T m) const { + const simd8 mask = simd8::splat(m); + return simd8x64( + this->chunks[0] <= mask, + this->chunks[1] <= mask, + this->chunks[2] <= mask, + this->chunks[3] <= mask + ).to_bitmask(); + } + }; // struct simd8x64 + +} // namespace simd +} // unnamed namespace +} // namespace arm64 +} // namespace simdjson + +#endif // SIMDJSON_ARM64_SIMD_H diff --git a/contrib/libs/simdjson/include/simdjson/arm64/stringparsing_defs.h b/contrib/libs/simdjson/include/simdjson/arm64/stringparsing_defs.h new file mode 100644 index 000000000000..30d02faff3f9 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/arm64/stringparsing_defs.h @@ -0,0 +1,53 @@ +#ifndef SIMDJSON_ARM64_STRINGPARSING_DEFS_H +#define SIMDJSON_ARM64_STRINGPARSING_DEFS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/arm64/base.h" +#include "simdjson/arm64/simd.h" +#include "simdjson/arm64/bitmanipulation.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace arm64 { +namespace { + +using namespace simd; + +// Holds backslashes and quotes locations. +struct backslash_and_quote { +public: + static constexpr uint32_t BYTES_PROCESSED = 32; + simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); + + simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; } + simdjson_inline bool has_backslash() { return bs_bits != 0; } + simdjson_inline int quote_index() { return trailing_zeroes(quote_bits); } + simdjson_inline int backslash_index() { return trailing_zeroes(bs_bits); } + + uint32_t bs_bits; + uint32_t quote_bits; +}; // struct backslash_and_quote + +simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { + // this can read up to 31 bytes beyond the buffer size, but we require + // SIMDJSON_PADDING of padding + static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "backslash and quote finder must process fewer than SIMDJSON_PADDING bytes"); + simd8 v0(src); + simd8 v1(src + sizeof(v0)); + v0.store(dst); + v1.store(dst + sizeof(v0)); + + // Getting a 64-bit bitmask is much cheaper than multiple 16-bit bitmasks on ARM; therefore, we + // smash them together into a 64-byte mask and get the bitmask from there. + uint64_t bs_and_quote = simd8x64(v0 == '\\', v1 == '\\', v0 == '"', v1 == '"').to_bitmask(); + return { + uint32_t(bs_and_quote), // bs_bits + uint32_t(bs_and_quote >> 32) // quote_bits + }; +} + +} // unnamed namespace +} // namespace arm64 +} // namespace simdjson + +#endif // SIMDJSON_ARM64_STRINGPARSING_DEFS_H diff --git a/contrib/libs/simdjson/include/simdjson/base.h b/contrib/libs/simdjson/include/simdjson/base.h new file mode 100644 index 000000000000..e6e2ab9bfee3 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/base.h @@ -0,0 +1,61 @@ +/** + * @file Base declarations for all simdjson headers + * @private + */ +#ifndef SIMDJSON_BASE_H +#define SIMDJSON_BASE_H + +#include "simdjson/common_defs.h" +#include "simdjson/compiler_check.h" +#include "simdjson/error.h" +#include "simdjson/portability.h" +#include "simdjson/concepts.h" + +/** + * @brief The top level simdjson namespace, containing everything the library provides. + */ +namespace simdjson { + +SIMDJSON_PUSH_DISABLE_UNUSED_WARNINGS + +/** The maximum document size supported by simdjson. */ +constexpr size_t SIMDJSON_MAXSIZE_BYTES = 0xFFFFFFFF; + +/** + * The amount of padding needed in a buffer to parse JSON. + * + * The input buf should be readable up to buf + SIMDJSON_PADDING + * this is a stopgap; there should be a better description of the + * main loop and its behavior that abstracts over this + * See https://github.com/simdjson/simdjson/issues/174 + */ +constexpr size_t SIMDJSON_PADDING = 64; + +/** + * By default, simdjson supports this many nested objects and arrays. + * + * This is the default for parser::max_depth(). + */ +constexpr size_t DEFAULT_MAX_DEPTH = 1024; + +SIMDJSON_POP_DISABLE_UNUSED_WARNINGS + +class implementation; +struct padded_string; +class padded_string_view; +enum class stage1_mode; + +namespace internal { + +template +class atomic_ptr; +class dom_parser_implementation; +class escape_json_string; +class tape_ref; +struct value128; +enum class tape_type; + +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_BASE_H diff --git a/contrib/libs/simdjson/include/simdjson/builtin.h b/contrib/libs/simdjson/include/simdjson/builtin.h new file mode 100644 index 000000000000..4788007f88c9 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/builtin.h @@ -0,0 +1,33 @@ +#ifndef SIMDJSON_BUILTIN_H +#define SIMDJSON_BUILTIN_H + +#include "simdjson/builtin/base.h" +#include "simdjson/builtin/implementation.h" + +#include "simdjson/generic/dependencies.h" + +#define SIMDJSON_CONDITIONAL_INCLUDE + +#if SIMDJSON_BUILTIN_IMPLEMENTATION_IS(arm64) +#include "simdjson/arm64.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(fallback) +#include "simdjson/fallback.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(haswell) +#include "simdjson/haswell.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(icelake) +#include "simdjson/icelake.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(ppc64) +#include "simdjson/ppc64.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(westmere) +#include "simdjson/westmere.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lsx) +#include "simdjson/lsx.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lasx) +#include "simdjson/lasx.h" +#else +#error Unknown SIMDJSON_BUILTIN_IMPLEMENTATION +#endif + +#undef SIMDJSON_CONDITIONAL_INCLUDE + +#endif // SIMDJSON_BUILTIN_H diff --git a/contrib/libs/simdjson/include/simdjson/builtin/base.h b/contrib/libs/simdjson/include/simdjson/builtin/base.h new file mode 100644 index 000000000000..ce1678013e48 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/builtin/base.h @@ -0,0 +1,41 @@ +#ifndef SIMDJSON_BUILTIN_BASE_H +#define SIMDJSON_BUILTIN_BASE_H + +#include "simdjson/base.h" +#include "simdjson/implementation_detection.h" + +namespace simdjson { +#if SIMDJSON_BUILTIN_IMPLEMENTATION_IS(arm64) + namespace arm64 {} +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(fallback) + namespace fallback {} +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(haswell) + namespace haswell {} +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(icelake) + namespace icelake {} +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(ppc64) + namespace ppc64 {} +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(westmere) + namespace westmere {} +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lsx) + namespace lsx {} +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lasx) + namespace lasx {} +#else +#error Unknown SIMDJSON_BUILTIN_IMPLEMENTATION +#endif + + /** + * Represents the best statically linked simdjson implementation that can be used by the compiling + * program. + * + * Detects what options the program is compiled against, and picks the minimum implementation that + * will work on any computer that can run the program. For example, if you compile with g++ + * -march=westmere, it will pick the westmere implementation. The haswell implementation will + * still be available, and can be selected at runtime, but the builtin implementation (and any + * code that uses it) will use westmere. + */ + namespace builtin = SIMDJSON_BUILTIN_IMPLEMENTATION; +} // namespace simdjson + +#endif // SIMDJSON_BUILTIN_BASE_H diff --git a/contrib/libs/simdjson/include/simdjson/builtin/implementation.h b/contrib/libs/simdjson/include/simdjson/builtin/implementation.h new file mode 100644 index 000000000000..68a175d07ae3 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/builtin/implementation.h @@ -0,0 +1,42 @@ +#ifndef SIMDJSON_BUILTIN_IMPLEMENTATION_H +#define SIMDJSON_BUILTIN_IMPLEMENTATION_H + +#include "simdjson/builtin/base.h" + +#include "simdjson/generic/dependencies.h" + +#define SIMDJSON_CONDITIONAL_INCLUDE + +#if SIMDJSON_BUILTIN_IMPLEMENTATION_IS(arm64) +#include "simdjson/arm64/implementation.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(fallback) +#include "simdjson/fallback/implementation.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(haswell) +#include "simdjson/haswell/implementation.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(icelake) +#include "simdjson/icelake/implementation.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(ppc64) +#error #include "simdjson/ppc64/implementation.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(westmere) +#include "simdjson/westmere/implementation.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lsx) +#include "simdjson/lsx/implementation.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lasx) +#include "simdjson/lasx/implementation.h" +#else +#error Unknown SIMDJSON_BUILTIN_IMPLEMENTATION +#endif + +#undef SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { + /** + * Function which returns a pointer to an implementation matching the "builtin" implementation. + * The builtin implementation is the best statically linked simdjson implementation that can be used by the compiling + * program. If you compile with g++ -march=haswell, this will return the haswell implementation. + * It is handy to be able to check what builtin was used: builtin_implementation()->name(). + */ + const implementation * builtin_implementation(); +} // namespace simdjson + +#endif // SIMDJSON_BUILTIN_IMPLEMENTATION_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/builtin/ondemand.h b/contrib/libs/simdjson/include/simdjson/builtin/ondemand.h new file mode 100644 index 000000000000..483fa8760adf --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/builtin/ondemand.h @@ -0,0 +1,40 @@ +#ifndef SIMDJSON_BUILTIN_ONDEMAND_H +#define SIMDJSON_BUILTIN_ONDEMAND_H + +#include "simdjson/builtin.h" +#include "simdjson/builtin/base.h" + +#include "simdjson/generic/ondemand/dependencies.h" + +#define SIMDJSON_CONDITIONAL_INCLUDE + +#if SIMDJSON_BUILTIN_IMPLEMENTATION_IS(arm64) +#include "simdjson/arm64/ondemand.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(fallback) +#include "simdjson/fallback/ondemand.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(haswell) +#include "simdjson/haswell/ondemand.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(icelake) +#include "simdjson/icelake/ondemand.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(ppc64) +#error #include "simdjson/ppc64/ondemand.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(westmere) +#include "simdjson/westmere/ondemand.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lsx) +#include "simdjson/lsx/ondemand.h" +#elif SIMDJSON_BUILTIN_IMPLEMENTATION_IS(lasx) +#include "simdjson/lasx/ondemand.h" +#else +#error Unknown SIMDJSON_BUILTIN_IMPLEMENTATION +#endif + +#undef SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { + /** + * @copydoc simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand + */ + namespace ondemand = SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand; +} // namespace simdjson + +#endif // SIMDJSON_BUILTIN_ONDEMAND_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/common_defs.h b/contrib/libs/simdjson/include/simdjson/common_defs.h new file mode 100644 index 000000000000..d0ae083ecba9 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/common_defs.h @@ -0,0 +1,347 @@ +#ifndef SIMDJSON_COMMON_DEFS_H +#define SIMDJSON_COMMON_DEFS_H + +#include +#include "simdjson/compiler_check.h" +#include "simdjson/portability.h" + +namespace simdjson { +namespace internal { +/** + * @private + * Our own implementation of the C++17 to_chars function. + * Defined in src/to_chars + */ +char *to_chars(char *first, const char *last, double value); +/** + * @private + * A number parsing routine. + * Defined in src/from_chars + */ +double from_chars(const char *first) noexcept; +double from_chars(const char *first, const char* end) noexcept; +} + +#ifndef SIMDJSON_EXCEPTIONS +#if __cpp_exceptions +#define SIMDJSON_EXCEPTIONS 1 +#else +#define SIMDJSON_EXCEPTIONS 0 +#endif +#endif + +} // namespace simdjson + +#if defined(__GNUC__) + // Marks a block with a name so that MCA analysis can see it. + #define SIMDJSON_BEGIN_DEBUG_BLOCK(name) __asm volatile("# LLVM-MCA-BEGIN " #name); + #define SIMDJSON_END_DEBUG_BLOCK(name) __asm volatile("# LLVM-MCA-END " #name); + #define SIMDJSON_DEBUG_BLOCK(name, block) BEGIN_DEBUG_BLOCK(name); block; END_DEBUG_BLOCK(name); +#else + #define SIMDJSON_BEGIN_DEBUG_BLOCK(name) + #define SIMDJSON_END_DEBUG_BLOCK(name) + #define SIMDJSON_DEBUG_BLOCK(name, block) +#endif + +// Align to N-byte boundary +#define SIMDJSON_ROUNDUP_N(a, n) (((a) + ((n)-1)) & ~((n)-1)) +#define SIMDJSON_ROUNDDOWN_N(a, n) ((a) & ~((n)-1)) + +#define SIMDJSON_ISALIGNED_N(ptr, n) (((uintptr_t)(ptr) & ((n)-1)) == 0) + +#if SIMDJSON_REGULAR_VISUAL_STUDIO + // We could use [[deprecated]] but it requires C++14 + #define simdjson_deprecated __declspec(deprecated) + + #define simdjson_really_inline __forceinline + #define simdjson_never_inline __declspec(noinline) + + #define simdjson_unused + #define simdjson_warn_unused + + #ifndef simdjson_likely + #define simdjson_likely(x) x + #endif + #ifndef simdjson_unlikely + #define simdjson_unlikely(x) x + #endif + + #define SIMDJSON_PUSH_DISABLE_WARNINGS __pragma(warning( push )) + #define SIMDJSON_PUSH_DISABLE_ALL_WARNINGS __pragma(warning( push, 0 )) + #define SIMDJSON_DISABLE_VS_WARNING(WARNING_NUMBER) __pragma(warning( disable : WARNING_NUMBER )) + // Get rid of Intellisense-only warnings (Code Analysis) + // Though __has_include is C++17, it is supported in Visual Studio 2017 or better (_MSC_VER>=1910). + #ifdef __has_include + #if __has_include() + #error #include + #define SIMDJSON_DISABLE_UNDESIRED_WARNINGS SIMDJSON_DISABLE_VS_WARNING(ALL_CPPCORECHECK_WARNINGS) + #endif + #endif + + #ifndef SIMDJSON_DISABLE_UNDESIRED_WARNINGS + #define SIMDJSON_DISABLE_UNDESIRED_WARNINGS + #endif + + #define SIMDJSON_DISABLE_DEPRECATED_WARNING SIMDJSON_DISABLE_VS_WARNING(4996) + #define SIMDJSON_DISABLE_STRICT_OVERFLOW_WARNING + #define SIMDJSON_POP_DISABLE_WARNINGS __pragma(warning( pop )) + + #define SIMDJSON_PUSH_DISABLE_UNUSED_WARNINGS + #define SIMDJSON_POP_DISABLE_UNUSED_WARNINGS + +#else // SIMDJSON_REGULAR_VISUAL_STUDIO + // We could use [[deprecated]] but it requires C++14 + #define simdjson_deprecated __attribute__((deprecated)) + + #define simdjson_really_inline inline __attribute__((always_inline)) + #define simdjson_never_inline inline __attribute__((noinline)) + + #define simdjson_unused __attribute__((unused)) + #define simdjson_warn_unused __attribute__((warn_unused_result)) + + #ifndef simdjson_likely + #define simdjson_likely(x) __builtin_expect(!!(x), 1) + #endif + #ifndef simdjson_unlikely + #define simdjson_unlikely(x) __builtin_expect(!!(x), 0) + #endif + + #define SIMDJSON_PUSH_DISABLE_WARNINGS _Pragma("GCC diagnostic push") + // gcc doesn't seem to disable all warnings with all and extra, add warnings here as necessary + // We do it separately for clang since it has different warnings. + #ifdef __clang__ + // clang is missing -Wmaybe-uninitialized. + #define SIMDJSON_PUSH_DISABLE_ALL_WARNINGS SIMDJSON_PUSH_DISABLE_WARNINGS \ + SIMDJSON_DISABLE_GCC_WARNING(-Weffc++) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wall) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wconversion) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wextra) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wattributes) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wimplicit-fallthrough) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wnon-virtual-dtor) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wreturn-type) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wshadow) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wunused-parameter) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wunused-variable) + #else // __clang__ + #define SIMDJSON_PUSH_DISABLE_ALL_WARNINGS SIMDJSON_PUSH_DISABLE_WARNINGS \ + SIMDJSON_DISABLE_GCC_WARNING(-Weffc++) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wall) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wconversion) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wextra) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wattributes) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wimplicit-fallthrough) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wnon-virtual-dtor) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wreturn-type) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wshadow) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wunused-parameter) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wunused-variable) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wmaybe-uninitialized) \ + SIMDJSON_DISABLE_GCC_WARNING(-Wformat-security) + #endif // __clang__ + + #define SIMDJSON_PRAGMA(P) _Pragma(#P) + #define SIMDJSON_DISABLE_GCC_WARNING(WARNING) SIMDJSON_PRAGMA(GCC diagnostic ignored #WARNING) + #if SIMDJSON_CLANG_VISUAL_STUDIO + #define SIMDJSON_DISABLE_UNDESIRED_WARNINGS SIMDJSON_DISABLE_GCC_WARNING(-Wmicrosoft-include) + #else + #define SIMDJSON_DISABLE_UNDESIRED_WARNINGS + #endif + #define SIMDJSON_DISABLE_DEPRECATED_WARNING SIMDJSON_DISABLE_GCC_WARNING(-Wdeprecated-declarations) + #define SIMDJSON_DISABLE_STRICT_OVERFLOW_WARNING SIMDJSON_DISABLE_GCC_WARNING(-Wstrict-overflow) + #define SIMDJSON_POP_DISABLE_WARNINGS _Pragma("GCC diagnostic pop") + + #define SIMDJSON_PUSH_DISABLE_UNUSED_WARNINGS SIMDJSON_PUSH_DISABLE_WARNINGS \ + SIMDJSON_DISABLE_GCC_WARNING(-Wunused) + #define SIMDJSON_POP_DISABLE_UNUSED_WARNINGS SIMDJSON_POP_DISABLE_WARNINGS + + + +#endif // MSC_VER + +#if defined(simdjson_inline) + // Prefer the user's definition of simdjson_inline; don't define it ourselves. +#elif defined(__GNUC__) && !defined(__OPTIMIZE__) + // If optimizations are disabled, forcing inlining can lead to significant + // code bloat and high compile times. Don't use simdjson_really_inline for + // unoptimized builds. + #define simdjson_inline inline +#else + // Force inlining for most simdjson functions. + #define simdjson_inline simdjson_really_inline +#endif + +#if SIMDJSON_VISUAL_STUDIO + /** + * Windows users need to do some extra work when building + * or using a dynamic library (DLL). When building, we need + * to set SIMDJSON_DLLIMPORTEXPORT to __declspec(dllexport). + * When *using* the DLL, the user needs to set + * SIMDJSON_DLLIMPORTEXPORT __declspec(dllimport). + * + * Static libraries not need require such work. + * + * It does not matter here whether you are using + * the regular visual studio or clang under visual + * studio, you still need to handle these issues. + * + * Non-Windows systems do not have this complexity. + */ + #if SIMDJSON_BUILDING_WINDOWS_DYNAMIC_LIBRARY + // We set SIMDJSON_BUILDING_WINDOWS_DYNAMIC_LIBRARY when we build a DLL under Windows. + // It should never happen that both SIMDJSON_BUILDING_WINDOWS_DYNAMIC_LIBRARY and + // SIMDJSON_USING_WINDOWS_DYNAMIC_LIBRARY are set. + #define SIMDJSON_DLLIMPORTEXPORT __declspec(dllexport) + #elif SIMDJSON_USING_WINDOWS_DYNAMIC_LIBRARY + // Windows user who call a dynamic library should set SIMDJSON_USING_WINDOWS_DYNAMIC_LIBRARY to 1. + #define SIMDJSON_DLLIMPORTEXPORT __declspec(dllimport) + #else + // We assume by default static linkage + #define SIMDJSON_DLLIMPORTEXPORT + #endif + +/** + * Workaround for the vcpkg package manager. Only vcpkg should + * ever touch the next line. The SIMDJSON_USING_LIBRARY macro is otherwise unused. + */ +#if SIMDJSON_USING_LIBRARY +#define SIMDJSON_DLLIMPORTEXPORT __declspec(dllimport) +#endif +/** + * End of workaround for the vcpkg package manager. + */ +#else + #define SIMDJSON_DLLIMPORTEXPORT +#endif + +// C++17 requires string_view. +#if SIMDJSON_CPLUSPLUS17 +#define SIMDJSON_HAS_STRING_VIEW +#include // by the standard, this has to be safe. +#endif + +// This macro (__cpp_lib_string_view) has to be defined +// for C++17 and better, but if it is otherwise defined, +// we are going to assume that string_view is available +// even if we do not have C++17 support. +#ifdef __cpp_lib_string_view +#define SIMDJSON_HAS_STRING_VIEW +#endif + +// Some systems have string_view even if we do not have C++17 support, +// and even if __cpp_lib_string_view is undefined, it is the case +// with Apple clang version 11. +// We must handle it. *This is important.* +#ifndef SIMDJSON_HAS_STRING_VIEW +#if defined __has_include +// do not combine the next #if with the previous one (unsafe) +#if __has_include () +// now it is safe to trigger the include +#include // though the file is there, it does not follow that we got the implementation +#if defined(_LIBCPP_STRING_VIEW) +// Ah! So we under libc++ which under its Library Fundamentals Technical Specification, which preceded C++17, +// included string_view. +// This means that we have string_view *even though* we may not have C++17. +#define SIMDJSON_HAS_STRING_VIEW +#endif // _LIBCPP_STRING_VIEW +#endif // __has_include () +#endif // defined __has_include +#endif // def SIMDJSON_HAS_STRING_VIEW +// end of complicated but important routine to try to detect string_view. + +// +// Backfill std::string_view using nonstd::string_view on systems where +// we expect that string_view is missing. Important: if we get this wrong, +// we will end up with two string_view definitions and potential trouble. +// That is why we work so hard above to avoid it. +// +#ifndef SIMDJSON_HAS_STRING_VIEW +SIMDJSON_PUSH_DISABLE_ALL_WARNINGS +#error #include "simdjson/nonstd/string_view.hpp" +SIMDJSON_POP_DISABLE_WARNINGS + +namespace std { + using string_view = nonstd::string_view; +} +#endif // SIMDJSON_HAS_STRING_VIEW +#undef SIMDJSON_HAS_STRING_VIEW // We are not going to need this macro anymore. + +/// If EXPR is an error, returns it. +#define SIMDJSON_TRY(EXPR) { auto _err = (EXPR); if (_err) { return _err; } } + +// Unless the programmer has already set SIMDJSON_DEVELOPMENT_CHECKS, +// we want to set it under debug builds. We detect a debug build +// under Visual Studio when the _DEBUG macro is set. Under the other +// compilers, we use the fact that they define __OPTIMIZE__ whenever +// they allow optimizations. +// It is possible that this could miss some cases where SIMDJSON_DEVELOPMENT_CHECKS +// is helpful, but the programmer can set the macro SIMDJSON_DEVELOPMENT_CHECKS. +// It could also wrongly set SIMDJSON_DEVELOPMENT_CHECKS (e.g., if the programmer +// sets _DEBUG in a release build under Visual Studio, or if some compiler fails to +// set the __OPTIMIZE__ macro). +#ifndef SIMDJSON_DEVELOPMENT_CHECKS +#ifdef _MSC_VER +// Visual Studio seems to set _DEBUG for debug builds. +#ifdef _DEBUG +#define SIMDJSON_DEVELOPMENT_CHECKS 1 +#endif // _DEBUG +#else // _MSC_VER +// All other compilers appear to set __OPTIMIZE__ to a positive integer +// when the compiler is optimizing. +#ifndef __OPTIMIZE__ +#define SIMDJSON_DEVELOPMENT_CHECKS 1 +#endif // __OPTIMIZE__ +#endif // _MSC_VER +#endif // SIMDJSON_DEVELOPMENT_CHECKS + +// The SIMDJSON_CHECK_EOF macro is a feature flag for the "don't require padding" +// feature. + +#if SIMDJSON_CPLUSPLUS17 +// if we have C++, then fallthrough is a default attribute +# define simdjson_fallthrough [[fallthrough]] +// check if we have __attribute__ support +#elif defined(__has_attribute) +// check if we have the __fallthrough__ attribute +#if __has_attribute(__fallthrough__) +// we are good to go: +# define simdjson_fallthrough __attribute__((__fallthrough__)) +#endif // __has_attribute(__fallthrough__) +#endif // SIMDJSON_CPLUSPLUS17 +// on some systems, we simply do not have support for fallthrough, so use a default: +#ifndef simdjson_fallthrough +# define simdjson_fallthrough do {} while (0) /* fallthrough */ +#endif // simdjson_fallthrough + +#if SIMDJSON_DEVELOPMENT_CHECKS +#define SIMDJSON_DEVELOPMENT_ASSERT(expr) do { assert ((expr)); } while (0) +#else +#define SIMDJSON_DEVELOPMENT_ASSERT(expr) do { } while (0) +#endif + +#ifndef SIMDJSON_UTF8VALIDATION +#define SIMDJSON_UTF8VALIDATION 1 +#endif + +#ifdef __has_include +// How do we detect that a compiler supports vbmi2? +// For sure if the following header is found, we are ok? +#if __has_include() +#define SIMDJSON_COMPILER_SUPPORTS_VBMI2 1 +#endif +#endif + +#ifdef _MSC_VER +#if _MSC_VER >= 1920 +// Visual Studio 2019 and up support VBMI2 under x64 even if the header +// avx512vbmi2intrin.h is not found. +#define SIMDJSON_COMPILER_SUPPORTS_VBMI2 1 +#endif +#endif + +// By default, we allow AVX512. +#ifndef SIMDJSON_AVX512_ALLOWED +#define SIMDJSON_AVX512_ALLOWED 1 +#endif + +#endif // SIMDJSON_COMMON_DEFS_H diff --git a/contrib/libs/simdjson/include/simdjson/compiler_check.h b/contrib/libs/simdjson/include/simdjson/compiler_check.h new file mode 100644 index 000000000000..b306ab3247f9 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/compiler_check.h @@ -0,0 +1,66 @@ +#ifndef SIMDJSON_COMPILER_CHECK_H +#define SIMDJSON_COMPILER_CHECK_H + +#ifndef __cplusplus +#error simdjson requires a C++ compiler +#endif + +#ifndef SIMDJSON_CPLUSPLUS +#if defined(_MSVC_LANG) && !defined(__clang__) +#define SIMDJSON_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG) +#else +#define SIMDJSON_CPLUSPLUS __cplusplus +#endif +#endif + +// C++ 23 +#if !defined(SIMDJSON_CPLUSPLUS23) && (SIMDJSON_CPLUSPLUS >= 202302L) +#define SIMDJSON_CPLUSPLUS23 1 +#endif + +// C++ 20 +#if !defined(SIMDJSON_CPLUSPLUS20) && (SIMDJSON_CPLUSPLUS >= 202002L) +#define SIMDJSON_CPLUSPLUS20 1 +#endif + +// C++ 17 +#if !defined(SIMDJSON_CPLUSPLUS17) && (SIMDJSON_CPLUSPLUS >= 201703L) +#define SIMDJSON_CPLUSPLUS17 1 +#endif + +// C++ 14 +#if !defined(SIMDJSON_CPLUSPLUS14) && (SIMDJSON_CPLUSPLUS >= 201402L) +#define SIMDJSON_CPLUSPLUS14 1 +#endif + +// C++ 11 +#if !defined(SIMDJSON_CPLUSPLUS11) && (SIMDJSON_CPLUSPLUS >= 201103L) +#define SIMDJSON_CPLUSPLUS11 1 +#endif + +#ifndef SIMDJSON_CPLUSPLUS11 +#error simdjson requires a compiler compliant with the C++11 standard +#endif + +#ifndef SIMDJSON_IF_CONSTEXPR +#if SIMDJSON_CPLUSPLUS17 +#define SIMDJSON_IF_CONSTEXPR if constexpr +#else +#define SIMDJSON_IF_CONSTEXPR if +#endif +#endif + +#ifdef __has_include +#if __has_include() +#include +#endif +#endif + +#if defined(__cpp_concepts) && !defined(SIMDJSON_CONCEPT_DISABLED) +#include +#define SIMDJSON_SUPPORTS_DESERIALIZATION 1 +#else // defined(__cpp_concepts) && !defined(SIMDJSON_CONCEPT_DISABLED) +#define SIMDJSON_SUPPORTS_DESERIALIZATION 0 +#endif // defined(__cpp_concepts) && !defined(SIMDJSON_CONCEPT_DISABLED) + +#endif // SIMDJSON_COMPILER_CHECK_H diff --git a/contrib/libs/simdjson/include/simdjson/concepts.h b/contrib/libs/simdjson/include/simdjson/concepts.h new file mode 100644 index 000000000000..649314bc1b88 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/concepts.h @@ -0,0 +1,113 @@ +#ifndef SIMDJSON_CONCEPTS_H +#define SIMDJSON_CONCEPTS_H +#if SIMDJSON_SUPPORTS_DESERIALIZATION + +#include +#include + +namespace simdjson { +namespace concepts { + +namespace details { +#define SIMDJSON_IMPL_CONCEPT(name, method) \ + template \ + concept supports_##name = !std::is_const_v && requires { \ + typename std::remove_cvref_t::value_type; \ + requires requires(typename std::remove_cvref_t::value_type &&val, \ + T obj) { \ + obj.method(std::move(val)); \ + requires !requires { obj = std::move(val); }; \ + }; \ + }; + +SIMDJSON_IMPL_CONCEPT(emplace_back, emplace_back) +SIMDJSON_IMPL_CONCEPT(emplace, emplace) +SIMDJSON_IMPL_CONCEPT(push_back, push_back) +SIMDJSON_IMPL_CONCEPT(add, add) +SIMDJSON_IMPL_CONCEPT(push, push) +SIMDJSON_IMPL_CONCEPT(append, append) +SIMDJSON_IMPL_CONCEPT(insert, insert) +SIMDJSON_IMPL_CONCEPT(op_append, operator+=) + +#undef SIMDJSON_IMPL_CONCEPT +} // namespace details + +/// Check if T is a container that we can append to, including: +/// std::vector, std::deque, std::list, std::string, ... +template +concept appendable_containers = + details::supports_emplace_back || details::supports_emplace || + details::supports_push_back || details::supports_push || + details::supports_add || details::supports_append || + details::supports_insert; + +/// Insert into the container however possible +template +constexpr decltype(auto) emplace_one(T &vec, Args &&...args) { + if constexpr (details::supports_emplace_back) { + return vec.emplace_back(std::forward(args)...); + } else if constexpr (details::supports_emplace) { + return vec.emplace(std::forward(args)...); + } else if constexpr (details::supports_push_back) { + return vec.push_back(std::forward(args)...); + } else if constexpr (details::supports_push) { + return vec.push(std::forward(args)...); + } else if constexpr (details::supports_add) { + return vec.add(std::forward(args)...); + } else if constexpr (details::supports_append) { + return vec.append(std::forward(args)...); + } else if constexpr (details::supports_insert) { + return vec.insert(std::forward(args)...); + } else if constexpr (details::supports_op_append && sizeof...(Args) == 1) { + return vec.operator+=(std::forward(args)...); + } else { + static_assert(!sizeof(T *), + "We don't know how to add things to this container"); + } +} + +/// This checks if the container will return a reference to the newly added +/// element after an insert which for example `std::vector::emplace_back` does +/// since C++17; this will allow some optimizations. +template +concept returns_reference = appendable_containers && requires { + typename std::remove_cvref_t::reference; + requires requires(typename std::remove_cvref_t::value_type &&val, T obj) { + { + emplace_one(obj, std::move(val)) + } -> std::same_as::reference>; + }; +}; + +template +concept smart_pointer = requires(std::remove_cvref_t ptr) { + // Check if T has a member type named element_type + typename std::remove_cvref_t::element_type; + + // Check if T has a get() member function + { + ptr.get() + } -> std::same_as::element_type *>; + + // Check if T can be dereferenced + { *ptr } -> std::same_as::element_type &>; +}; + +template +concept optional_type = requires(std::remove_cvref_t obj) { + typename std::remove_cvref_t::value_type; + { obj.value() } -> std::same_as::value_type&>; + requires requires(typename std::remove_cvref_t::value_type &&val) { + obj.emplace(std::move(val)); + obj = std::move(val); + { + obj.value_or(val) + } -> std::convertible_to::value_type>; + }; + { static_cast(obj) } -> std::same_as; // convertible to bool +}; + +} // namespace concepts +} // namespace simdjson +#endif // SIMDJSON_SUPPORTS_DESERIALIZATION +#endif // SIMDJSON_CONCEPTS_H diff --git a/contrib/libs/simdjson/include/simdjson/dom.h b/contrib/libs/simdjson/include/simdjson/dom.h new file mode 100644 index 000000000000..bcf22ac22dc4 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/dom.h @@ -0,0 +1,23 @@ +#ifndef SIMDJSON_DOM_H +#define SIMDJSON_DOM_H + +#include "simdjson/dom/base.h" +#include "simdjson/dom/array.h" +#include "simdjson/dom/document_stream.h" +#include "simdjson/dom/document.h" +#include "simdjson/dom/element.h" +#include "simdjson/dom/object.h" +#include "simdjson/dom/parser.h" +#include "simdjson/dom/serialization.h" + +// Inline functions +#include "simdjson/dom/array-inl.h" +#include "simdjson/dom/document_stream-inl.h" +#include "simdjson/dom/document-inl.h" +#include "simdjson/dom/element-inl.h" +#include "simdjson/dom/object-inl.h" +#include "simdjson/dom/parser-inl.h" +#include "simdjson/internal/tape_ref-inl.h" +#include "simdjson/dom/serialization-inl.h" + +#endif // SIMDJSON_DOM_H diff --git a/contrib/libs/simdjson/include/simdjson/dom/array-inl.h b/contrib/libs/simdjson/include/simdjson/dom/array-inl.h new file mode 100644 index 000000000000..f0b587dc6e01 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/dom/array-inl.h @@ -0,0 +1,195 @@ +#ifndef SIMDJSON_ARRAY_INL_H +#define SIMDJSON_ARRAY_INL_H + +#include + +#include "simdjson/dom/base.h" +#include "simdjson/dom/array.h" +#include "simdjson/dom/element.h" +#include "simdjson/error-inl.h" +#include "simdjson/jsonpathutil.h" +#include "simdjson/internal/tape_ref-inl.h" + +#include + +namespace simdjson { + +// +// simdjson_result inline implementation +// +simdjson_inline simdjson_result::simdjson_result() noexcept + : internal::simdjson_result_base() {} +simdjson_inline simdjson_result::simdjson_result(dom::array value) noexcept + : internal::simdjson_result_base(std::forward(value)) {} +simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept + : internal::simdjson_result_base(error) {} + +#if SIMDJSON_EXCEPTIONS + +inline dom::array::iterator simdjson_result::begin() const noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first.begin(); +} +inline dom::array::iterator simdjson_result::end() const noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first.end(); +} +inline size_t simdjson_result::size() const noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first.size(); +} + +#endif // SIMDJSON_EXCEPTIONS + +inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) const noexcept { + if (error()) { return error(); } + return first.at_pointer(json_pointer); +} + + inline simdjson_result simdjson_result::at_path(std::string_view json_path) const noexcept { + auto json_pointer = json_path_to_pointer_conversion(json_path); + if (json_pointer == "-1") { return INVALID_JSON_POINTER; } + return at_pointer(json_pointer); + } + +inline simdjson_result simdjson_result::at(size_t index) const noexcept { + if (error()) { return error(); } + return first.at(index); +} + +namespace dom { + +// +// array inline implementation +// +simdjson_inline array::array() noexcept : tape{} {} +simdjson_inline array::array(const internal::tape_ref &_tape) noexcept : tape{_tape} {} +inline array::iterator array::begin() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + return internal::tape_ref(tape.doc, tape.json_index + 1); +} +inline array::iterator array::end() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + return internal::tape_ref(tape.doc, tape.after_element() - 1); +} +inline size_t array::size() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + return tape.scope_count(); +} +inline size_t array::number_of_slots() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + return tape.matching_brace_index() - tape.json_index; +} +inline simdjson_result array::at_pointer(std::string_view json_pointer) const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + if(json_pointer.empty()) { // an empty string means that we return the current node + return element(this->tape); // copy the current node + } else if(json_pointer[0] != '/') { // otherwise there is an error + return INVALID_JSON_POINTER; + } + json_pointer = json_pointer.substr(1); + // - means "the append position" or "the element after the end of the array" + // We don't support this, because we're returning a real element, not a position. + if (json_pointer == "-") { return INDEX_OUT_OF_BOUNDS; } + + // Read the array index + size_t array_index = 0; + size_t i; + for (i = 0; i < json_pointer.length() && json_pointer[i] != '/'; i++) { + uint8_t digit = uint8_t(json_pointer[i] - '0'); + // Check for non-digit in array index. If it's there, we're trying to get a field in an object + if (digit > 9) { return INCORRECT_TYPE; } + array_index = array_index*10 + digit; + } + + // 0 followed by other digits is invalid + if (i > 1 && json_pointer[0] == '0') { return INVALID_JSON_POINTER; } // "JSON pointer array index has other characters after 0" + + // Empty string is invalid; so is a "/" with no digits before it + if (i == 0) { return INVALID_JSON_POINTER; } // "Empty string in JSON pointer array index" + + // Get the child + auto child = array(tape).at(array_index); + // If there is an error, it ends here + if(child.error()) { + return child; + } + // If there is a /, we're not done yet, call recursively. + if (i < json_pointer.length()) { + child = child.at_pointer(json_pointer.substr(i)); + } + return child; +} + +inline simdjson_result array::at_path(std::string_view json_path) const noexcept { + auto json_pointer = json_path_to_pointer_conversion(json_path); + if (json_pointer == "-1") { return INVALID_JSON_POINTER; } + return at_pointer(json_pointer); +} + +inline simdjson_result array::at(size_t index) const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + size_t i=0; + for (auto element : *this) { + if (i == index) { return element; } + i++; + } + return INDEX_OUT_OF_BOUNDS; +} + +inline array::operator element() const noexcept { + return element(tape); +} + +// +// array::iterator inline implementation +// +simdjson_inline array::iterator::iterator(const internal::tape_ref &_tape) noexcept : tape{_tape} { } +inline element array::iterator::operator*() const noexcept { + return element(tape); +} +inline array::iterator& array::iterator::operator++() noexcept { + tape.json_index = tape.after_element(); + return *this; +} +inline array::iterator array::iterator::operator++(int) noexcept { + array::iterator out = *this; + ++*this; + return out; +} +inline bool array::iterator::operator!=(const array::iterator& other) const noexcept { + return tape.json_index != other.tape.json_index; +} +inline bool array::iterator::operator==(const array::iterator& other) const noexcept { + return tape.json_index == other.tape.json_index; +} +inline bool array::iterator::operator<(const array::iterator& other) const noexcept { + return tape.json_index < other.tape.json_index; +} +inline bool array::iterator::operator<=(const array::iterator& other) const noexcept { + return tape.json_index <= other.tape.json_index; +} +inline bool array::iterator::operator>=(const array::iterator& other) const noexcept { + return tape.json_index >= other.tape.json_index; +} +inline bool array::iterator::operator>(const array::iterator& other) const noexcept { + return tape.json_index > other.tape.json_index; +} + +} // namespace dom + + +} // namespace simdjson + +#include "simdjson/dom/element-inl.h" + +#if defined(__cpp_lib_ranges) +static_assert(std::ranges::view); +static_assert(std::ranges::sized_range); +#if SIMDJSON_EXCEPTIONS +static_assert(std::ranges::view>); +static_assert(std::ranges::sized_range>); +#endif // SIMDJSON_EXCEPTIONS +#endif // defined(__cpp_lib_ranges) + +#endif // SIMDJSON_ARRAY_INL_H diff --git a/contrib/libs/simdjson/include/simdjson/dom/array.h b/contrib/libs/simdjson/include/simdjson/dom/array.h new file mode 100644 index 000000000000..f54e3bc19ea4 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/dom/array.h @@ -0,0 +1,199 @@ +#ifndef SIMDJSON_DOM_ARRAY_H +#define SIMDJSON_DOM_ARRAY_H + +#include "simdjson/dom/base.h" +#include "simdjson/internal/tape_ref.h" + +namespace simdjson { +namespace dom { + +/** + * JSON array. + */ +class array { +public: + /** Create a new, invalid array */ + simdjson_inline array() noexcept; + + class iterator { + public: + using value_type = element; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = value_type; + using iterator_category = std::forward_iterator_tag; + + /** + * Get the actual value + */ + inline reference operator*() const noexcept; + /** + * Get the next value. + * + * Part of the std::iterator interface. + */ + inline iterator& operator++() noexcept; + /** + * Get the next value. + * + * Part of the std::iterator interface. + */ + inline iterator operator++(int) noexcept; + /** + * Check if these values come from the same place in the JSON. + * + * Part of the std::iterator interface. + */ + inline bool operator!=(const iterator& other) const noexcept; + inline bool operator==(const iterator& other) const noexcept; + + inline bool operator<(const iterator& other) const noexcept; + inline bool operator<=(const iterator& other) const noexcept; + inline bool operator>=(const iterator& other) const noexcept; + inline bool operator>(const iterator& other) const noexcept; + + iterator() noexcept = default; + iterator(const iterator&) noexcept = default; + iterator& operator=(const iterator&) noexcept = default; + private: + simdjson_inline iterator(const internal::tape_ref &tape) noexcept; + internal::tape_ref tape; + friend class array; + }; + + /** + * Return the first array element. + * + * Part of the std::iterable interface. + */ + inline iterator begin() const noexcept; + /** + * One past the last array element. + * + * Part of the std::iterable interface. + */ + inline iterator end() const noexcept; + /** + * Get the size of the array (number of immediate children). + * It is a saturated value with a maximum of 0xFFFFFF: if the value + * is 0xFFFFFF then the size is 0xFFFFFF or greater. + */ + inline size_t size() const noexcept; + /** + * Get the total number of slots used by this array on the tape. + * + * Note that this is not the same thing as `size()`, which reports the + * number of actual elements within an array (not counting its children). + * + * Since an element can use 1 or 2 slots on the tape, you can only use this + * to figure out the total size of an array (including its children, + * recursively) if you know its structure ahead of time. + **/ + inline size_t number_of_slots() const noexcept; + /** + * Get the value associated with the given JSON pointer. We use the RFC 6901 + * https://tools.ietf.org/html/rfc6901 standard, interpreting the current node + * as the root of its own JSON document. + * + * dom::parser parser; + * array a = parser.parse(R"([ { "foo": { "a": [ 10, 20, 30 ] }} ])"_padded); + * a.at_pointer("/0/foo/a/1") == 20 + * a.at_pointer("0")["foo"]["a"].at(1) == 20 + * + * @return The value associated with the given JSON pointer, or: + * - NO_SUCH_FIELD if a field does not exist in an object + * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length + * - INCORRECT_TYPE if a non-integer is used to access an array + * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed + */ + inline simdjson_result at_pointer(std::string_view json_pointer) const noexcept; + + /** + * Get the value associated with the given JSONPath expression. We only support + * JSONPath queries that trivially convertible to JSON Pointer queries: key + * names and array indices. + * + * https://datatracker.ietf.org/doc/html/draft-normington-jsonpath-00 + * + * @return The value associated with the given JSONPath expression, or: + * - INVALID_JSON_POINTER if the JSONPath to JSON Pointer conversion fails + * - NO_SUCH_FIELD if a field does not exist in an object + * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length + * - INCORRECT_TYPE if a non-integer is used to access an array + */ + inline simdjson_result at_path(std::string_view json_path) const noexcept; + + /** + * Get the value at the given index. This function has linear-time complexity and + * is equivalent to the following: + * + * size_t i=0; + * for (auto element : *this) { + * if (i == index) { return element; } + * i++; + * } + * return INDEX_OUT_OF_BOUNDS; + * + * Avoid calling the at() function repeatedly. + * + * @return The value at the given index, or: + * - INDEX_OUT_OF_BOUNDS if the array index is larger than an array length + */ + inline simdjson_result at(size_t index) const noexcept; + + /** + * Implicitly convert object to element + */ + inline operator element() const noexcept; + +private: + simdjson_inline array(const internal::tape_ref &tape) noexcept; + internal::tape_ref tape; + friend class element; + friend struct simdjson_result; + template + friend class simdjson::internal::string_builder; +}; + + +} // namespace dom + +/** The result of a JSON conversion that may fail. */ +template<> +struct simdjson_result : public internal::simdjson_result_base { +public: + simdjson_inline simdjson_result() noexcept; ///< @private + simdjson_inline simdjson_result(dom::array value) noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + + inline simdjson_result at_pointer(std::string_view json_pointer) const noexcept; + inline simdjson_result at_path(std::string_view json_path) const noexcept; + inline simdjson_result at(size_t index) const noexcept; + +#if SIMDJSON_EXCEPTIONS + inline dom::array::iterator begin() const noexcept(false); + inline dom::array::iterator end() const noexcept(false); + inline size_t size() const noexcept(false); +#endif // SIMDJSON_EXCEPTIONS +}; + + + +} // namespace simdjson + +#if defined(__cpp_lib_ranges) +#include + +namespace std { +namespace ranges { +template<> +inline constexpr bool enable_view = true; +#if SIMDJSON_EXCEPTIONS +template<> +inline constexpr bool enable_view> = true; +#endif // SIMDJSON_EXCEPTIONS +} // namespace ranges +} // namespace std +#endif // defined(__cpp_lib_ranges) + +#endif // SIMDJSON_DOM_ARRAY_H diff --git a/contrib/libs/simdjson/include/simdjson/dom/base.h b/contrib/libs/simdjson/include/simdjson/dom/base.h new file mode 100644 index 000000000000..b862277a2113 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/dom/base.h @@ -0,0 +1,54 @@ +#ifndef SIMDJSON_DOM_BASE_H +#define SIMDJSON_DOM_BASE_H + +#include "simdjson/base.h" + +namespace simdjson { + +/** + * @brief A DOM API on top of the simdjson parser. + */ +namespace dom { + +/** The default batch size for parser.parse_many() and parser.load_many() */ +static constexpr size_t DEFAULT_BATCH_SIZE = 1000000; +/** + * Some adversary might try to set the batch size to 0 or 1, which might cause problems. + * We set a minimum of 32B since anything else is highly likely to be an error. In practice, + * most users will want a much larger batch size. + * + * All non-negative MINIMAL_BATCH_SIZE values should be 'safe' except that, obviously, no JSON + * document can ever span 0 or 1 byte and that very large values would create memory allocation issues. + */ +static constexpr size_t MINIMAL_BATCH_SIZE = 32; + +/** + * It is wasteful to allocate memory for tiny documents (e.g., 4 bytes). + */ +static constexpr size_t MINIMAL_DOCUMENT_CAPACITY = 32; + +class array; +class document; +class document_stream; +class element; +class key_value_pair; +class object; +class parser; + +#ifdef SIMDJSON_THREADS_ENABLED +struct stage1_worker; +#endif // SIMDJSON_THREADS_ENABLED + +} // namespace dom + +namespace internal { + +template +class string_builder; +class tape_ref; + +} // namespace internal + +} // namespace simdjson + +#endif // SIMDJSON_DOM_BASE_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/dom/document-inl.h b/contrib/libs/simdjson/include/simdjson/dom/document-inl.h new file mode 100644 index 000000000000..40d74b999e0b --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/dom/document-inl.h @@ -0,0 +1,159 @@ +#ifndef SIMDJSON_DOCUMENT_INL_H +#define SIMDJSON_DOCUMENT_INL_H + +// Inline implementations go in here. + +#include "simdjson/dom/base.h" +#include "simdjson/dom/document.h" +#include "simdjson/dom/element-inl.h" +#include "simdjson/internal/tape_ref-inl.h" +#include "simdjson/internal/jsonformatutils.h" + +#include + +namespace simdjson { +namespace dom { + +// +// document inline implementation +// +inline element document::root() const noexcept { + return element(internal::tape_ref(this, 1)); +} +simdjson_warn_unused +inline size_t document::capacity() const noexcept { + return allocated_capacity; +} + +simdjson_warn_unused +inline error_code document::allocate(size_t capacity) noexcept { + if (capacity == 0) { + string_buf.reset(); + tape.reset(); + allocated_capacity = 0; + return SUCCESS; + } + + // a pathological input like "[[[[..." would generate capacity tape elements, so + // need a capacity of at least capacity + 1, but it is also possible to do + // worse with "[7,7,7,7,6,7,7,7,6,7,7,6,[7,7,7,7,6,7,7,7,6,7,7,6,7,7,7,7,7,7,6" + //where capacity + 1 tape elements are + // generated, see issue https://github.com/simdjson/simdjson/issues/345 + size_t tape_capacity = SIMDJSON_ROUNDUP_N(capacity + 3, 64); + // a document with only zero-length strings... could have capacity/3 string + // and we would need capacity/3 * 5 bytes on the string buffer + size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * capacity / 3 + SIMDJSON_PADDING, 64); + string_buf.reset( new (std::nothrow) uint8_t[string_capacity]); + tape.reset(new (std::nothrow) uint64_t[tape_capacity]); + if(!(string_buf && tape)) { + allocated_capacity = 0; + string_buf.reset(); + tape.reset(); + return MEMALLOC; + } + // Technically the allocated_capacity might be larger than capacity + // so the next line is pessimistic. + allocated_capacity = capacity; + return SUCCESS; +} + +inline bool document::dump_raw_tape(std::ostream &os) const noexcept { + uint32_t string_length; + size_t tape_idx = 0; + uint64_t tape_val = tape[tape_idx]; + uint8_t type = uint8_t(tape_val >> 56); + os << tape_idx << " : " << type; + tape_idx++; + size_t how_many = 0; + if (type == 'r') { + how_many = size_t(tape_val & internal::JSON_VALUE_MASK); + } else { + // Error: no starting root node? + return false; + } + os << "\t// pointing to " << how_many << " (right after last node)\n"; + uint64_t payload; + for (; tape_idx < how_many; tape_idx++) { + os << tape_idx << " : "; + tape_val = tape[tape_idx]; + payload = tape_val & internal::JSON_VALUE_MASK; + type = uint8_t(tape_val >> 56); + switch (type) { + case '"': // we have a string + os << "string \""; + std::memcpy(&string_length, string_buf.get() + payload, sizeof(uint32_t)); + os << internal::escape_json_string(std::string_view( + reinterpret_cast(string_buf.get() + payload + sizeof(uint32_t)), + string_length + )); + os << '"'; + os << '\n'; + break; + case 'l': // we have a long int + if (tape_idx + 1 >= how_many) { + return false; + } + os << "integer " << static_cast(tape[++tape_idx]) << "\n"; + break; + case 'u': // we have a long uint + if (tape_idx + 1 >= how_many) { + return false; + } + os << "unsigned integer " << tape[++tape_idx] << "\n"; + break; + case 'd': // we have a double + os << "float "; + if (tape_idx + 1 >= how_many) { + return false; + } + double answer; + std::memcpy(&answer, &tape[++tape_idx], sizeof(answer)); + os << answer << '\n'; + break; + case 'n': // we have a null + os << "null\n"; + break; + case 't': // we have a true + os << "true\n"; + break; + case 'f': // we have a false + os << "false\n"; + break; + case '{': // we have an object + os << "{\t// pointing to next tape location " << uint32_t(payload) + << " (first node after the scope), " + << " saturated count " + << ((payload >> 32) & internal::JSON_COUNT_MASK)<< "\n"; + break; case '}': // we end an object + os << "}\t// pointing to previous tape location " << uint32_t(payload) + << " (start of the scope)\n"; + break; + case '[': // we start an array + os << "[\t// pointing to next tape location " << uint32_t(payload) + << " (first node after the scope), " + << " saturated count " + << ((payload >> 32) & internal::JSON_COUNT_MASK)<< "\n"; + break; + case ']': // we end an array + os << "]\t// pointing to previous tape location " << uint32_t(payload) + << " (start of the scope)\n"; + break; + case 'r': // we start and end with the root node + // should we be hitting the root node? + return false; + default: + return false; + } + } + tape_val = tape[tape_idx]; + payload = tape_val & internal::JSON_VALUE_MASK; + type = uint8_t(tape_val >> 56); + os << tape_idx << " : " << type << "\t// pointing to " << payload + << " (start root)\n"; + return true; +} + +} // namespace dom +} // namespace simdjson + +#endif // SIMDJSON_DOCUMENT_INL_H diff --git a/contrib/libs/simdjson/include/simdjson/dom/document.h b/contrib/libs/simdjson/include/simdjson/dom/document.h new file mode 100644 index 000000000000..6c5e284bb47f --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/dom/document.h @@ -0,0 +1,91 @@ +#ifndef SIMDJSON_DOM_DOCUMENT_H +#define SIMDJSON_DOM_DOCUMENT_H + +#include "simdjson/dom/base.h" + +#include + +namespace simdjson { +namespace dom { + +/** + * A parsed JSON document. + * + * This class cannot be copied, only moved, to avoid unintended allocations. + */ +class document { +public: + /** + * Create a document container with zero capacity. + * + * The parser will allocate capacity as needed. + */ + document() noexcept = default; + ~document() noexcept = default; + + /** + * Take another document's buffers. + * + * @param other The document to take. Its capacity is zeroed and it is invalidated. + */ + document(document &&other) noexcept = default; + /** @private */ + document(const document &) = delete; // Disallow copying + /** + * Take another document's buffers. + * + * @param other The document to take. Its capacity is zeroed. + */ + document &operator=(document &&other) noexcept = default; + /** @private */ + document &operator=(const document &) = delete; // Disallow copying + + /** + * Get the root element of this document as a JSON array. + */ + element root() const noexcept; + + /** + * @private Dump the raw tape for debugging. + * + * @param os the stream to output to. + * @return false if the tape is likely wrong (e.g., you did not parse a valid JSON). + */ + bool dump_raw_tape(std::ostream &os) const noexcept; + + /** @private Structural values. */ + std::unique_ptr tape{}; + + /** @private String values. + * + * Should be at least byte_capacity. + */ + std::unique_ptr string_buf{}; + /** @private Allocate memory to support + * input JSON documents of up to len bytes. + * + * When calling this function, you lose + * all the data. + * + * The memory allocation is strict: you + * can you use this function to increase + * or lower the amount of allocated memory. + * Passsing zero clears the memory. + */ + error_code allocate(size_t len) noexcept; + /** @private Capacity in bytes, in terms + * of how many bytes of input JSON we can + * support. + */ + size_t capacity() const noexcept; + + +private: + size_t allocated_capacity{0}; + friend class parser; +}; // class document + +} // namespace dom +} // namespace simdjson + +#endif // SIMDJSON_DOM_DOCUMENT_H diff --git a/contrib/libs/simdjson/include/simdjson/dom/document_stream-inl.h b/contrib/libs/simdjson/include/simdjson/dom/document_stream-inl.h new file mode 100644 index 000000000000..b7062481f217 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/dom/document_stream-inl.h @@ -0,0 +1,348 @@ +#ifndef SIMDJSON_DOCUMENT_STREAM_INL_H +#define SIMDJSON_DOCUMENT_STREAM_INL_H + +#include "simdjson/dom/base.h" +#include "simdjson/dom/document_stream.h" +#include "simdjson/dom/element-inl.h" +#include "simdjson/dom/parser-inl.h" +#include "simdjson/error-inl.h" +#include "simdjson/internal/dom_parser_implementation.h" + +namespace simdjson { +namespace dom { + +#ifdef SIMDJSON_THREADS_ENABLED + +inline void stage1_worker::finish() { + // After calling "run" someone would call finish() to wait + // for the end of the processing. + // This function will wait until either the thread has done + // the processing or, else, the destructor has been called. + std::unique_lock lock(locking_mutex); + cond_var.wait(lock, [this]{return has_work == false;}); +} + +inline stage1_worker::~stage1_worker() { + // The thread may never outlive the stage1_worker instance + // and will always be stopped/joined before the stage1_worker + // instance is gone. + stop_thread(); +} + +inline void stage1_worker::start_thread() { + std::unique_lock lock(locking_mutex); + if(thread.joinable()) { + return; // This should never happen but we never want to create more than one thread. + } + thread = std::thread([this]{ + while(true) { + std::unique_lock thread_lock(locking_mutex); + // We wait for either "run" or "stop_thread" to be called. + cond_var.wait(thread_lock, [this]{return has_work || !can_work;}); + // If, for some reason, the stop_thread() method was called (i.e., the + // destructor of stage1_worker is called, then we want to immediately destroy + // the thread (and not do any more processing). + if(!can_work) { + break; + } + this->owner->stage1_thread_error = this->owner->run_stage1(*this->stage1_thread_parser, + this->_next_batch_start); + this->has_work = false; + // The condition variable call should be moved after thread_lock.unlock() for performance + // reasons but thread sanitizers may report it as a data race if we do. + // See https://stackoverflow.com/questions/35775501/c-should-condition-variable-be-notified-under-lock + cond_var.notify_one(); // will notify "finish" + thread_lock.unlock(); + } + } + ); +} + + +inline void stage1_worker::stop_thread() { + std::unique_lock lock(locking_mutex); + // We have to make sure that all locks can be released. + can_work = false; + has_work = false; + cond_var.notify_all(); + lock.unlock(); + if(thread.joinable()) { + thread.join(); + } +} + +inline void stage1_worker::run(document_stream * ds, dom::parser * stage1, size_t next_batch_start) { + std::unique_lock lock(locking_mutex); + owner = ds; + _next_batch_start = next_batch_start; + stage1_thread_parser = stage1; + has_work = true; + // The condition variable call should be moved after thread_lock.unlock() for performance + // reasons but thread sanitizers may report it as a data race if we do. + // See https://stackoverflow.com/questions/35775501/c-should-condition-variable-be-notified-under-lock + cond_var.notify_one(); // will notify the thread lock that we have work + lock.unlock(); +} +#endif + +simdjson_inline document_stream::document_stream( + dom::parser &_parser, + const uint8_t *_buf, + size_t _len, + size_t _batch_size +) noexcept + : parser{&_parser}, + buf{_buf}, + len{_len}, + batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size}, + error{SUCCESS} +#ifdef SIMDJSON_THREADS_ENABLED + , use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change +#endif +{ +#ifdef SIMDJSON_THREADS_ENABLED + if(worker.get() == nullptr) { + error = MEMALLOC; + } +#endif +} + +simdjson_inline document_stream::document_stream() noexcept + : parser{nullptr}, + buf{nullptr}, + len{0}, + batch_size{0}, + error{UNINITIALIZED} +#ifdef SIMDJSON_THREADS_ENABLED + , use_thread(false) +#endif +{ +} + +simdjson_inline document_stream::~document_stream() noexcept { +#ifdef SIMDJSON_THREADS_ENABLED + worker.reset(); +#endif +} + +simdjson_inline document_stream::iterator::iterator() noexcept + : stream{nullptr}, finished{true} { +} + +simdjson_inline document_stream::iterator document_stream::begin() noexcept { + start(); + // If there are no documents, we're finished. + return iterator(this, error == EMPTY); +} + +simdjson_inline document_stream::iterator document_stream::end() noexcept { + return iterator(this, true); +} + +simdjson_inline document_stream::iterator::iterator(document_stream* _stream, bool is_end) noexcept + : stream{_stream}, finished{is_end} { +} + +simdjson_inline document_stream::iterator::reference document_stream::iterator::operator*() noexcept { + // Note that in case of error, we do not yet mark + // the iterator as "finished": this detection is done + // in the operator++ function since it is possible + // to call operator++ repeatedly while omitting + // calls to operator*. + if (stream->error) { return stream->error; } + return stream->parser->doc.root(); +} + +simdjson_inline document_stream::iterator& document_stream::iterator::operator++() noexcept { + // If there is an error, then we want the iterator + // to be finished, no matter what. (E.g., we do not + // keep generating documents with errors, or go beyond + // a document with errors.) + // + // Users do not have to call "operator*()" when they use operator++, + // so we need to end the stream in the operator++ function. + // + // Note that setting finished = true is essential otherwise + // we would enter an infinite loop. + if (stream->error) { finished = true; } + // Note that stream->error() is guarded against error conditions + // (it will immediately return if stream->error casts to false). + // In effect, this next function does nothing when (stream->error) + // is true (hence the risk of an infinite loop). + stream->next(); + // If that was the last document, we're finished. + // It is the only type of error we do not want to appear + // in operator*. + if (stream->error == EMPTY) { finished = true; } + // If we had any other kind of error (not EMPTY) then we want + // to pass it along to the operator* and we cannot mark the result + // as "finished" just yet. + return *this; +} + +simdjson_inline bool document_stream::iterator::operator!=(const document_stream::iterator &other) const noexcept { + return finished != other.finished; +} + +inline void document_stream::start() noexcept { + if (error) { return; } + error = parser->ensure_capacity(batch_size); + if (error) { return; } + // Always run the first stage 1 parse immediately + batch_start = 0; + error = run_stage1(*parser, batch_start); + while(error == EMPTY) { + // In exceptional cases, we may start with an empty block + batch_start = next_batch_start(); + if (batch_start >= len) { return; } + error = run_stage1(*parser, batch_start); + } + if (error) { return; } +#ifdef SIMDJSON_THREADS_ENABLED + if (use_thread && next_batch_start() < len) { + // Kick off the first thread if needed + error = stage1_thread_parser.ensure_capacity(batch_size); + if (error) { return; } + worker->start_thread(); + start_stage1_thread(); + if (error) { return; } + } +#endif // SIMDJSON_THREADS_ENABLED + next(); +} + +simdjson_inline size_t document_stream::iterator::current_index() const noexcept { + return stream->doc_index; +} + +simdjson_inline std::string_view document_stream::iterator::source() const noexcept { + const char* start = reinterpret_cast(stream->buf) + current_index(); + bool object_or_array = ((*start == '[') || (*start == '{')); + if(object_or_array) { + size_t next_doc_index = stream->batch_start + stream->parser->implementation->structural_indexes[stream->parser->implementation->next_structural_index - 1]; + return std::string_view(start, next_doc_index - current_index() + 1); + } else { + size_t next_doc_index = stream->batch_start + stream->parser->implementation->structural_indexes[stream->parser->implementation->next_structural_index]; + size_t svlen = next_doc_index - current_index(); + while(svlen > 1 && (std::isspace(start[svlen-1]) || start[svlen-1] == '\0')) { + svlen--; + } + return std::string_view(start, svlen); + } +} + + +inline void document_stream::next() noexcept { + // We always exit at once, once in an error condition. + if (error) { return; } + + // Load the next document from the batch + doc_index = batch_start + parser->implementation->structural_indexes[parser->implementation->next_structural_index]; + error = parser->implementation->stage2_next(parser->doc); + // If that was the last document in the batch, load another batch (if available) + while (error == EMPTY) { + batch_start = next_batch_start(); + if (batch_start >= len) { break; } + +#ifdef SIMDJSON_THREADS_ENABLED + if(use_thread) { + load_from_stage1_thread(); + } else { + error = run_stage1(*parser, batch_start); + } +#else + error = run_stage1(*parser, batch_start); +#endif + if (error) { continue; } // If the error was EMPTY, we may want to load another batch. + // Run stage 2 on the first document in the batch + doc_index = batch_start + parser->implementation->structural_indexes[parser->implementation->next_structural_index]; + error = parser->implementation->stage2_next(parser->doc); + } +} +inline size_t document_stream::size_in_bytes() const noexcept { + return len; +} + +inline size_t document_stream::truncated_bytes() const noexcept { + if(error == CAPACITY) { return len - batch_start; } + return parser->implementation->structural_indexes[parser->implementation->n_structural_indexes] - parser->implementation->structural_indexes[parser->implementation->n_structural_indexes + 1]; +} + +inline size_t document_stream::next_batch_start() const noexcept { + return batch_start + parser->implementation->structural_indexes[parser->implementation->n_structural_indexes]; +} + +inline error_code document_stream::run_stage1(dom::parser &p, size_t _batch_start) noexcept { + size_t remaining = len - _batch_start; + if (remaining <= batch_size) { + return p.implementation->stage1(&buf[_batch_start], remaining, stage1_mode::streaming_final); + } else { + return p.implementation->stage1(&buf[_batch_start], batch_size, stage1_mode::streaming_partial); + } +} + +#ifdef SIMDJSON_THREADS_ENABLED + +inline void document_stream::load_from_stage1_thread() noexcept { + worker->finish(); + // Swap to the parser that was loaded up in the thread. Make sure the parser has + // enough memory to swap to, as well. + std::swap(*parser, stage1_thread_parser); + error = stage1_thread_error; + if (error) { return; } + + // If there's anything left, start the stage 1 thread! + if (next_batch_start() < len) { + start_stage1_thread(); + } +} + +inline void document_stream::start_stage1_thread() noexcept { + // we call the thread on a lambda that will update + // this->stage1_thread_error + // there is only one thread that may write to this value + // TODO this is NOT exception-safe. + this->stage1_thread_error = UNINITIALIZED; // In case something goes wrong, make sure it's an error + size_t _next_batch_start = this->next_batch_start(); + + worker->run(this, & this->stage1_thread_parser, _next_batch_start); +} + +#endif // SIMDJSON_THREADS_ENABLED + +} // namespace dom + +simdjson_inline simdjson_result::simdjson_result() noexcept + : simdjson_result_base() { +} +simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept + : simdjson_result_base(error) { +} +simdjson_inline simdjson_result::simdjson_result(dom::document_stream &&value) noexcept + : simdjson_result_base(std::forward(value)) { +} + +#if SIMDJSON_EXCEPTIONS +simdjson_inline dom::document_stream::iterator simdjson_result::begin() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first.begin(); +} +simdjson_inline dom::document_stream::iterator simdjson_result::end() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first.end(); +} +#else // SIMDJSON_EXCEPTIONS +#ifndef SIMDJSON_DISABLE_DEPRECATED_API +simdjson_inline dom::document_stream::iterator simdjson_result::begin() noexcept { + first.error = error(); + return first.begin(); +} +simdjson_inline dom::document_stream::iterator simdjson_result::end() noexcept { + first.error = error(); + return first.end(); +} +#endif // SIMDJSON_DISABLE_DEPRECATED_API +#endif // SIMDJSON_EXCEPTIONS + +} // namespace simdjson +#endif // SIMDJSON_DOCUMENT_STREAM_INL_H diff --git a/contrib/libs/simdjson/include/simdjson/dom/document_stream.h b/contrib/libs/simdjson/include/simdjson/dom/document_stream.h new file mode 100644 index 000000000000..308f20e25ae0 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/dom/document_stream.h @@ -0,0 +1,322 @@ +#ifndef SIMDJSON_DOCUMENT_STREAM_H +#define SIMDJSON_DOCUMENT_STREAM_H + +#include "simdjson/dom/base.h" +#include "simdjson/dom/parser.h" + +#ifdef SIMDJSON_THREADS_ENABLED +#include +#include +#include +#endif + +namespace simdjson { +namespace dom { + +#ifdef SIMDJSON_THREADS_ENABLED +/** @private Custom worker class **/ +struct stage1_worker { + stage1_worker() noexcept = default; + stage1_worker(const stage1_worker&) = delete; + stage1_worker(stage1_worker&&) = delete; + stage1_worker operator=(const stage1_worker&) = delete; + ~stage1_worker(); + /** + * We only start the thread when it is needed, not at object construction, this may throw. + * You should only call this once. + **/ + void start_thread(); + /** + * Start a stage 1 job. You should first call 'run', then 'finish'. + * You must call start_thread once before. + */ + void run(document_stream * ds, dom::parser * stage1, size_t next_batch_start); + /** Wait for the run to finish (blocking). You should first call 'run', then 'finish'. **/ + void finish(); + +private: + + /** + * Normally, we would never stop the thread. But we do in the destructor. + * This function is only safe assuming that you are not waiting for results. You + * should have called run, then finish, and be done. + **/ + void stop_thread(); + + std::thread thread{}; + /** These three variables define the work done by the thread. **/ + dom::parser * stage1_thread_parser{}; + size_t _next_batch_start{}; + document_stream * owner{}; + /** + * We have two state variables. This could be streamlined to one variable in the future but + * we use two for clarity. + */ + bool has_work{false}; + bool can_work{true}; + + /** + * We lock using a mutex. + */ + std::mutex locking_mutex{}; + std::condition_variable cond_var{}; +}; +#endif + +/** + * A forward-only stream of documents. + * + * Produced by parser::parse_many. + * + */ +class document_stream { +public: + /** + * Construct an uninitialized document_stream. + * + * ```c++ + * document_stream docs; + * error = parser.parse_many(json).get(docs); + * ``` + */ + simdjson_inline document_stream() noexcept; + /** Move one document_stream to another. */ + simdjson_inline document_stream(document_stream &&other) noexcept = default; + /** Move one document_stream to another. */ + simdjson_inline document_stream &operator=(document_stream &&other) noexcept = default; + + simdjson_inline ~document_stream() noexcept; + /** + * Returns the input size in bytes. + */ + inline size_t size_in_bytes() const noexcept; + /** + * After iterating through the stream, this method + * returns the number of bytes that were not parsed at the end + * of the stream. If truncated_bytes() differs from zero, + * then the input was truncated maybe because incomplete JSON + * documents were found at the end of the stream. You + * may need to process the bytes in the interval [size_in_bytes()-truncated_bytes(), size_in_bytes()). + * + * You should only call truncated_bytes() after streaming through all + * documents, like so: + * + * document_stream stream = parser.parse_many(json,window); + * for(auto doc : stream) { + * // do something with doc + * } + * size_t truncated = stream.truncated_bytes(); + * + */ + inline size_t truncated_bytes() const noexcept; + /** + * An iterator through a forward-only stream of documents. + */ + class iterator { + public: + using value_type = simdjson_result; + using reference = value_type; + + using difference_type = std::ptrdiff_t; + + using iterator_category = std::input_iterator_tag; + + /** + * Default constructor. + */ + simdjson_inline iterator() noexcept; + /** + * Get the current document (or error). + */ + simdjson_inline reference operator*() noexcept; + /** + * Advance to the next document (prefix). + */ + inline iterator& operator++() noexcept; + /** + * Check if we're at the end yet. + * @param other the end iterator to compare to. + */ + simdjson_inline bool operator!=(const iterator &other) const noexcept; + /** + * @private + * + * Gives the current index in the input document in bytes. + * + * document_stream stream = parser.parse_many(json,window); + * for(auto i = stream.begin(); i != stream.end(); ++i) { + * auto doc = *i; + * size_t index = i.current_index(); + * } + * + * This function (current_index()) is experimental and the usage + * may change in future versions of simdjson: we find the API somewhat + * awkward and we would like to offer something friendlier. + */ + simdjson_inline size_t current_index() const noexcept; + /** + * @private + * + * Gives a view of the current document. + * + * document_stream stream = parser.parse_many(json,window); + * for(auto i = stream.begin(); i != stream.end(); ++i) { + * auto doc = *i; + * std::string_view v = i->source(); + * } + * + * The returned string_view instance is simply a map to the (unparsed) + * source string: it may thus include white-space characters and all manner + * of padding. + * + * This function (source()) is experimental and the usage + * may change in future versions of simdjson: we find the API somewhat + * awkward and we would like to offer something friendlier. + */ + simdjson_inline std::string_view source() const noexcept; + + private: + simdjson_inline iterator(document_stream *s, bool finished) noexcept; + /** The document_stream we're iterating through. */ + document_stream* stream; + /** Whether we're finished or not. */ + bool finished; + friend class document_stream; + }; + + /** + * Start iterating the documents in the stream. + */ + simdjson_inline iterator begin() noexcept; + /** + * The end of the stream, for iterator comparison purposes. + */ + simdjson_inline iterator end() noexcept; + +private: + + document_stream &operator=(const document_stream &) = delete; // Disallow copying + document_stream(const document_stream &other) = delete; // Disallow copying + + /** + * Construct a document_stream. Does not allocate or parse anything until the iterator is + * used. + * + * @param parser is a reference to the parser instance used to generate this document_stream + * @param buf is the raw byte buffer we need to process + * @param len is the length of the raw byte buffer in bytes + * @param batch_size is the size of the windows (must be strictly greater or equal to the largest JSON document) + */ + simdjson_inline document_stream( + dom::parser &parser, + const uint8_t *buf, + size_t len, + size_t batch_size + ) noexcept; + + /** + * Parse the first document in the buffer. Used by begin(), to handle allocation and + * initialization. + */ + inline void start() noexcept; + + /** + * Parse the next document found in the buffer previously given to document_stream. + * + * The content should be a valid JSON document encoded as UTF-8. If there is a + * UTF-8 BOM, the parser skips it. + * + * You do NOT need to pre-allocate a parser. This function takes care of + * pre-allocating a capacity defined by the batch_size defined when creating the + * document_stream object. + * + * The function returns simdjson::EMPTY if there is no more data to be parsed. + * + * The function returns simdjson::SUCCESS (as integer = 0) in case of success + * and indicates that the buffer has successfully been parsed to the end. + * Every document it contained has been parsed without error. + * + * The function returns an error code from simdjson/simdjson.h in case of failure + * such as simdjson::CAPACITY, simdjson::MEMALLOC, simdjson::DEPTH_ERROR and so forth; + * the simdjson::error_message function converts these error codes into a string). + * + * You can also check validity by calling parser.is_valid(). The same parser can + * and should be reused for the other documents in the buffer. + */ + inline void next() noexcept; + + /** + * Pass the next batch through stage 1 and return when finished. + * When threads are enabled, this may wait for the stage 1 thread to finish. + */ + inline void load_batch() noexcept; + + /** Get the next document index. */ + inline size_t next_batch_start() const noexcept; + + /** Pass the next batch through stage 1 with the given parser. */ + inline error_code run_stage1(dom::parser &p, size_t batch_start) noexcept; + + dom::parser *parser; + const uint8_t *buf; + size_t len; + size_t batch_size; + /** The error (or lack thereof) from the current document. */ + error_code error; + size_t batch_start{0}; + size_t doc_index{}; +#ifdef SIMDJSON_THREADS_ENABLED + /** Indicates whether we use threads. Note that this needs to be a constant during the execution of the parsing. */ + bool use_thread; + + inline void load_from_stage1_thread() noexcept; + + /** Start a thread to run stage 1 on the next batch. */ + inline void start_stage1_thread() noexcept; + + /** Wait for the stage 1 thread to finish and capture the results. */ + inline void finish_stage1_thread() noexcept; + + /** The error returned from the stage 1 thread. */ + error_code stage1_thread_error{UNINITIALIZED}; + /** The thread used to run stage 1 against the next batch in the background. */ + friend struct stage1_worker; + std::unique_ptr worker{new(std::nothrow) stage1_worker()}; + /** + * The parser used to run stage 1 in the background. Will be swapped + * with the regular parser when finished. + */ + dom::parser stage1_thread_parser{}; +#endif // SIMDJSON_THREADS_ENABLED + + friend class dom::parser; + friend struct simdjson_result; + friend struct internal::simdjson_result_base; + +}; // class document_stream + +} // namespace dom + +template<> +struct simdjson_result : public internal::simdjson_result_base { +public: + simdjson_inline simdjson_result() noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + simdjson_inline simdjson_result(dom::document_stream &&value) noexcept; ///< @private + +#if SIMDJSON_EXCEPTIONS + simdjson_inline dom::document_stream::iterator begin() noexcept(false); + simdjson_inline dom::document_stream::iterator end() noexcept(false); +#else // SIMDJSON_EXCEPTIONS +#ifndef SIMDJSON_DISABLE_DEPRECATED_API + [[deprecated("parse_many() and load_many() may return errors. Use document_stream stream; error = parser.parse_many().get(doc); instead.")]] + simdjson_inline dom::document_stream::iterator begin() noexcept; + [[deprecated("parse_many() and load_many() may return errors. Use document_stream stream; error = parser.parse_many().get(doc); instead.")]] + simdjson_inline dom::document_stream::iterator end() noexcept; +#endif // SIMDJSON_DISABLE_DEPRECATED_API +#endif // SIMDJSON_EXCEPTIONS +}; // struct simdjson_result + +} // namespace simdjson + +#endif // SIMDJSON_DOCUMENT_STREAM_H diff --git a/contrib/libs/simdjson/include/simdjson/dom/element-inl.h b/contrib/libs/simdjson/include/simdjson/dom/element-inl.h new file mode 100644 index 000000000000..1466609c2b0c --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/dom/element-inl.h @@ -0,0 +1,484 @@ +#ifndef SIMDJSON_ELEMENT_INL_H +#define SIMDJSON_ELEMENT_INL_H + +#include "simdjson/dom/base.h" +#include "simdjson/dom/element.h" +#include "simdjson/dom/document.h" +#include "simdjson/dom/object.h" +#include "simdjson/internal/tape_type.h" + +#include "simdjson/dom/object-inl.h" +#include "simdjson/error-inl.h" +#include "simdjson/jsonpathutil.h" + +#include +#include + +namespace simdjson { + +// +// simdjson_result inline implementation +// +simdjson_inline simdjson_result::simdjson_result() noexcept + : internal::simdjson_result_base() {} +simdjson_inline simdjson_result::simdjson_result(dom::element &&value) noexcept + : internal::simdjson_result_base(std::forward(value)) {} +simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept + : internal::simdjson_result_base(error) {} +inline simdjson_result simdjson_result::type() const noexcept { + if (error()) { return error(); } + return first.type(); +} + +template +simdjson_inline bool simdjson_result::is() const noexcept { + return !error() && first.is(); +} +template +simdjson_inline simdjson_result simdjson_result::get() const noexcept { + if (error()) { return error(); } + return first.get(); +} +template +simdjson_warn_unused simdjson_inline error_code simdjson_result::get(T &value) const noexcept { + if (error()) { return error(); } + return first.get(value); +} + +simdjson_inline simdjson_result simdjson_result::get_array() const noexcept { + if (error()) { return error(); } + return first.get_array(); +} +simdjson_inline simdjson_result simdjson_result::get_object() const noexcept { + if (error()) { return error(); } + return first.get_object(); +} +simdjson_inline simdjson_result simdjson_result::get_c_str() const noexcept { + if (error()) { return error(); } + return first.get_c_str(); +} +simdjson_inline simdjson_result simdjson_result::get_string_length() const noexcept { + if (error()) { return error(); } + return first.get_string_length(); +} +simdjson_inline simdjson_result simdjson_result::get_string() const noexcept { + if (error()) { return error(); } + return first.get_string(); +} +simdjson_inline simdjson_result simdjson_result::get_int64() const noexcept { + if (error()) { return error(); } + return first.get_int64(); +} +simdjson_inline simdjson_result simdjson_result::get_uint64() const noexcept { + if (error()) { return error(); } + return first.get_uint64(); +} +simdjson_inline simdjson_result simdjson_result::get_double() const noexcept { + if (error()) { return error(); } + return first.get_double(); +} +simdjson_inline simdjson_result simdjson_result::get_bool() const noexcept { + if (error()) { return error(); } + return first.get_bool(); +} + +simdjson_inline bool simdjson_result::is_array() const noexcept { + return !error() && first.is_array(); +} +simdjson_inline bool simdjson_result::is_object() const noexcept { + return !error() && first.is_object(); +} +simdjson_inline bool simdjson_result::is_string() const noexcept { + return !error() && first.is_string(); +} +simdjson_inline bool simdjson_result::is_int64() const noexcept { + return !error() && first.is_int64(); +} +simdjson_inline bool simdjson_result::is_uint64() const noexcept { + return !error() && first.is_uint64(); +} +simdjson_inline bool simdjson_result::is_double() const noexcept { + return !error() && first.is_double(); +} +simdjson_inline bool simdjson_result::is_number() const noexcept { + return !error() && first.is_number(); +} +simdjson_inline bool simdjson_result::is_bool() const noexcept { + return !error() && first.is_bool(); +} + +simdjson_inline bool simdjson_result::is_null() const noexcept { + return !error() && first.is_null(); +} + +simdjson_inline simdjson_result simdjson_result::operator[](std::string_view key) const noexcept { + if (error()) { return error(); } + return first[key]; +} +simdjson_inline simdjson_result simdjson_result::operator[](const char *key) const noexcept { + if (error()) { return error(); } + return first[key]; +} +simdjson_inline simdjson_result simdjson_result::at_pointer(const std::string_view json_pointer) const noexcept { + if (error()) { return error(); } + return first.at_pointer(json_pointer); +} +simdjson_inline simdjson_result simdjson_result::at_path(const std::string_view json_path) const noexcept { + auto json_pointer = json_path_to_pointer_conversion(json_path); + if (json_pointer == "-1") { return INVALID_JSON_POINTER; } + return at_pointer(json_pointer); +} +#ifndef SIMDJSON_DISABLE_DEPRECATED_API +[[deprecated("For standard compliance, use at_pointer instead, and prefix your pointers with a slash '/', see RFC6901 ")]] +simdjson_inline simdjson_result simdjson_result::at(const std::string_view json_pointer) const noexcept { +SIMDJSON_PUSH_DISABLE_WARNINGS +SIMDJSON_DISABLE_DEPRECATED_WARNING + if (error()) { return error(); } + return first.at(json_pointer); +SIMDJSON_POP_DISABLE_WARNINGS +} +#endif // SIMDJSON_DISABLE_DEPRECATED_API +simdjson_inline simdjson_result simdjson_result::at(size_t index) const noexcept { + if (error()) { return error(); } + return first.at(index); +} +simdjson_inline simdjson_result simdjson_result::at_key(std::string_view key) const noexcept { + if (error()) { return error(); } + return first.at_key(key); +} +simdjson_inline simdjson_result simdjson_result::at_key_case_insensitive(std::string_view key) const noexcept { + if (error()) { return error(); } + return first.at_key_case_insensitive(key); +} + +#if SIMDJSON_EXCEPTIONS + +simdjson_inline simdjson_result::operator bool() const noexcept(false) { + return get(); +} +simdjson_inline simdjson_result::operator const char *() const noexcept(false) { + return get(); +} +simdjson_inline simdjson_result::operator std::string_view() const noexcept(false) { + return get(); +} +simdjson_inline simdjson_result::operator uint64_t() const noexcept(false) { + return get(); +} +simdjson_inline simdjson_result::operator int64_t() const noexcept(false) { + return get(); +} +simdjson_inline simdjson_result::operator double() const noexcept(false) { + return get(); +} +simdjson_inline simdjson_result::operator dom::array() const noexcept(false) { + return get(); +} +simdjson_inline simdjson_result::operator dom::object() const noexcept(false) { + return get(); +} + +simdjson_inline dom::array::iterator simdjson_result::begin() const noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first.begin(); +} +simdjson_inline dom::array::iterator simdjson_result::end() const noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first.end(); +} + +#endif // SIMDJSON_EXCEPTIONS + +namespace dom { + +// +// element inline implementation +// +simdjson_inline element::element() noexcept : tape{} {} +simdjson_inline element::element(const internal::tape_ref &_tape) noexcept : tape{_tape} { } + +inline element_type element::type() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + auto tape_type = tape.tape_ref_type(); + return tape_type == internal::tape_type::FALSE_VALUE ? element_type::BOOL : static_cast(tape_type); +} + +inline simdjson_result element::get_bool() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + if(tape.is_true()) { + return true; + } else if(tape.is_false()) { + return false; + } + return INCORRECT_TYPE; +} +inline simdjson_result element::get_c_str() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + switch (tape.tape_ref_type()) { + case internal::tape_type::STRING: { + return tape.get_c_str(); + } + default: + return INCORRECT_TYPE; + } +} +inline simdjson_result element::get_string_length() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + switch (tape.tape_ref_type()) { + case internal::tape_type::STRING: { + return tape.get_string_length(); + } + default: + return INCORRECT_TYPE; + } +} +inline simdjson_result element::get_string() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + switch (tape.tape_ref_type()) { + case internal::tape_type::STRING: + return tape.get_string_view(); + default: + return INCORRECT_TYPE; + } +} +inline simdjson_result element::get_uint64() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + if(simdjson_unlikely(!tape.is_uint64())) { // branch rarely taken + if(tape.is_int64()) { + int64_t result = tape.next_tape_value(); + if (result < 0) { + return NUMBER_OUT_OF_RANGE; + } + return uint64_t(result); + } + return INCORRECT_TYPE; + } + return tape.next_tape_value(); +} +inline simdjson_result element::get_int64() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + if(simdjson_unlikely(!tape.is_int64())) { // branch rarely taken + if(tape.is_uint64()) { + uint64_t result = tape.next_tape_value(); + // Wrapping max in parens to handle Windows issue: https://stackoverflow.com/questions/11544073/how-do-i-deal-with-the-max-macro-in-windows-h-colliding-with-max-in-std + if (result > uint64_t((std::numeric_limits::max)())) { + return NUMBER_OUT_OF_RANGE; + } + return static_cast(result); + } + return INCORRECT_TYPE; + } + return tape.next_tape_value(); +} +inline simdjson_result element::get_double() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + // Performance considerations: + // 1. Querying tape_ref_type() implies doing a shift, it is fast to just do a straight + // comparison. + // 2. Using a switch-case relies on the compiler guessing what kind of code generation + // we want... But the compiler cannot know that we expect the type to be "double" + // most of the time. + // We can expect get to refer to a double type almost all the time. + // It is important to craft the code accordingly so that the compiler can use this + // information. (This could also be solved with profile-guided optimization.) + if(simdjson_unlikely(!tape.is_double())) { // branch rarely taken + if(tape.is_uint64()) { + return double(tape.next_tape_value()); + } else if(tape.is_int64()) { + return double(tape.next_tape_value()); + } + return INCORRECT_TYPE; + } + // this is common: + return tape.next_tape_value(); +} +inline simdjson_result element::get_array() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + switch (tape.tape_ref_type()) { + case internal::tape_type::START_ARRAY: + return array(tape); + default: + return INCORRECT_TYPE; + } +} +inline simdjson_result element::get_object() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + switch (tape.tape_ref_type()) { + case internal::tape_type::START_OBJECT: + return object(tape); + default: + return INCORRECT_TYPE; + } +} + +template +simdjson_warn_unused simdjson_inline error_code element::get(T &value) const noexcept { + return get().get(value); +} +// An element-specific version prevents recursion with simdjson_result::get(value) +template<> +simdjson_warn_unused simdjson_inline error_code element::get(element &value) const noexcept { + value = element(tape); + return SUCCESS; +} +template +inline void element::tie(T &value, error_code &error) && noexcept { + error = get(value); +} + +template +simdjson_inline bool element::is() const noexcept { + auto result = get(); + return !result.error(); +} + +template<> inline simdjson_result element::get() const noexcept { return get_array(); } +template<> inline simdjson_result element::get() const noexcept { return get_object(); } +template<> inline simdjson_result element::get() const noexcept { return get_c_str(); } +template<> inline simdjson_result element::get() const noexcept { return get_string(); } +template<> inline simdjson_result element::get() const noexcept { return get_int64(); } +template<> inline simdjson_result element::get() const noexcept { return get_uint64(); } +template<> inline simdjson_result element::get() const noexcept { return get_double(); } +template<> inline simdjson_result element::get() const noexcept { return get_bool(); } + +inline bool element::is_array() const noexcept { return is(); } +inline bool element::is_object() const noexcept { return is(); } +inline bool element::is_string() const noexcept { return is(); } +inline bool element::is_int64() const noexcept { return is(); } +inline bool element::is_uint64() const noexcept { return is(); } +inline bool element::is_double() const noexcept { return is(); } +inline bool element::is_bool() const noexcept { return is(); } +inline bool element::is_number() const noexcept { return is_int64() || is_uint64() || is_double(); } + +inline bool element::is_null() const noexcept { + return tape.is_null_on_tape(); +} + +#if SIMDJSON_EXCEPTIONS + +inline element::operator bool() const noexcept(false) { return get(); } +inline element::operator const char*() const noexcept(false) { return get(); } +inline element::operator std::string_view() const noexcept(false) { return get(); } +inline element::operator uint64_t() const noexcept(false) { return get(); } +inline element::operator int64_t() const noexcept(false) { return get(); } +inline element::operator double() const noexcept(false) { return get(); } +inline element::operator array() const noexcept(false) { return get(); } +inline element::operator object() const noexcept(false) { return get(); } + +inline array::iterator element::begin() const noexcept(false) { + return get().begin(); +} +inline array::iterator element::end() const noexcept(false) { + return get().end(); +} + +#endif // SIMDJSON_EXCEPTIONS + +inline simdjson_result element::operator[](std::string_view key) const noexcept { + return at_key(key); +} +inline simdjson_result element::operator[](const char *key) const noexcept { + return at_key(key); +} + +inline bool is_pointer_well_formed(std::string_view json_pointer) noexcept { + if (simdjson_unlikely(json_pointer[0] != '/')) { + return false; + } + size_t escape = json_pointer.find('~'); + if (escape == std::string_view::npos) { + return true; + } + if (escape == json_pointer.size() - 1) { + return false; + } + if (json_pointer[escape + 1] != '0' && json_pointer[escape + 1] != '1') { + return false; + } + return true; +} + +inline simdjson_result element::at_pointer(std::string_view json_pointer) const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + switch (tape.tape_ref_type()) { + case internal::tape_type::START_OBJECT: + return object(tape).at_pointer(json_pointer); + case internal::tape_type::START_ARRAY: + return array(tape).at_pointer(json_pointer); + default: { + if (!json_pointer.empty()) { // a non-empty string can be invalid, or accessing a primitive (issue 2154) + if (is_pointer_well_formed(json_pointer)) { + return NO_SUCH_FIELD; + } + return INVALID_JSON_POINTER; + } + // an empty string means that we return the current node + dom::element copy(*this); + return simdjson_result(std::move(copy)); + } + } +} +inline simdjson_result element::at_path(std::string_view json_path) const noexcept { + auto json_pointer = json_path_to_pointer_conversion(json_path); + if (json_pointer == "-1") { return INVALID_JSON_POINTER; } + return at_pointer(json_pointer); +} +#ifndef SIMDJSON_DISABLE_DEPRECATED_API +[[deprecated("For standard compliance, use at_pointer instead, and prefix your pointers with a slash '/', see RFC6901 ")]] +inline simdjson_result element::at(std::string_view json_pointer) const noexcept { + // version 0.4 of simdjson allowed non-compliant pointers + auto std_pointer = (json_pointer.empty() ? "" : "/") + std::string(json_pointer.begin(), json_pointer.end()); + return at_pointer(std_pointer); +} +#endif // SIMDJSON_DISABLE_DEPRECATED_API + +inline simdjson_result element::at(size_t index) const noexcept { + return get().at(index); +} +inline simdjson_result element::at_key(std::string_view key) const noexcept { + return get().at_key(key); +} +inline simdjson_result element::at_key_case_insensitive(std::string_view key) const noexcept { + return get().at_key_case_insensitive(key); +} +inline bool element::operator<(const element &other) const noexcept { + return tape.json_index < other.tape.json_index; +} +inline bool element::operator==(const element &other) const noexcept { + return tape.json_index == other.tape.json_index; +} + +inline bool element::dump_raw_tape(std::ostream &out) const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + return tape.doc->dump_raw_tape(out); +} + + +inline std::ostream& operator<<(std::ostream& out, element_type type) { + switch (type) { + case element_type::ARRAY: + return out << "array"; + case element_type::OBJECT: + return out << "object"; + case element_type::INT64: + return out << "int64_t"; + case element_type::UINT64: + return out << "uint64_t"; + case element_type::DOUBLE: + return out << "double"; + case element_type::STRING: + return out << "string"; + case element_type::BOOL: + return out << "bool"; + case element_type::NULL_VALUE: + return out << "null"; + default: + return out << "unexpected content!!!"; // abort() usage is forbidden in the library + } +} + +} // namespace dom + +} // namespace simdjson + +#endif // SIMDJSON_ELEMENT_INL_H diff --git a/contrib/libs/simdjson/include/simdjson/dom/element.h b/contrib/libs/simdjson/include/simdjson/dom/element.h new file mode 100644 index 000000000000..d39c83576305 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/dom/element.h @@ -0,0 +1,571 @@ +#ifndef SIMDJSON_DOM_ELEMENT_H +#define SIMDJSON_DOM_ELEMENT_H + +#include "simdjson/dom/base.h" +#include "simdjson/dom/array.h" + +namespace simdjson { +namespace dom { + +/** + * The actual concrete type of a JSON element + * This is the type it is most easily cast to with get<>. + */ +enum class element_type { + ARRAY = '[', ///< dom::array + OBJECT = '{', ///< dom::object + INT64 = 'l', ///< int64_t + UINT64 = 'u', ///< uint64_t: any integer that fits in uint64_t but *not* int64_t + DOUBLE = 'd', ///< double: Any number with a "." or "e" that fits in double. + STRING = '"', ///< std::string_view + BOOL = 't', ///< bool + NULL_VALUE = 'n' ///< null +}; + +/** + * A JSON element. + * + * References an element in a JSON document, representing a JSON null, boolean, string, number, + * array or object. + */ +class element { +public: + /** Create a new, invalid element. */ + simdjson_inline element() noexcept; + + /** The type of this element. */ + simdjson_inline element_type type() const noexcept; + + /** + * Cast this element to an array. + * + * @returns An object that can be used to iterate the array, or: + * INCORRECT_TYPE if the JSON element is not an array. + */ + inline simdjson_result get_array() const noexcept; + /** + * Cast this element to an object. + * + * @returns An object that can be used to look up or iterate the object's fields, or: + * INCORRECT_TYPE if the JSON element is not an object. + */ + inline simdjson_result get_object() const noexcept; + /** + * Cast this element to a null-terminated C string. + * + * The string is guaranteed to be valid UTF-8. + * + * The length of the string is given by get_string_length(). Because JSON strings + * may contain null characters, it may be incorrect to use strlen to determine the + * string length. + * + * It is possible to get a single string_view instance which represents both the string + * content and its length: see get_string(). + * + * @returns A pointer to a null-terminated UTF-8 string. This string is stored in the parser and will + * be invalidated the next time it parses a document or when it is destroyed. + * Returns INCORRECT_TYPE if the JSON element is not a string. + */ + inline simdjson_result get_c_str() const noexcept; + /** + * Gives the length in bytes of the string. + * + * It is possible to get a single string_view instance which represents both the string + * content and its length: see get_string(). + * + * @returns A string length in bytes. + * Returns INCORRECT_TYPE if the JSON element is not a string. + */ + inline simdjson_result get_string_length() const noexcept; + /** + * Cast this element to a string. + * + * The string is guaranteed to be valid UTF-8. + * + * @returns An UTF-8 string. The string is stored in the parser and will be invalidated the next time it + * parses a document or when it is destroyed. + * Returns INCORRECT_TYPE if the JSON element is not a string. + */ + inline simdjson_result get_string() const noexcept; + /** + * Cast this element to a signed integer. + * + * @returns A signed 64-bit integer. + * Returns INCORRECT_TYPE if the JSON element is not an integer, or NUMBER_OUT_OF_RANGE + * if it is negative. + */ + inline simdjson_result get_int64() const noexcept; + /** + * Cast this element to an unsigned integer. + * + * @returns An unsigned 64-bit integer. + * Returns INCORRECT_TYPE if the JSON element is not an integer, or NUMBER_OUT_OF_RANGE + * if it is too large. + */ + inline simdjson_result get_uint64() const noexcept; + /** + * Cast this element to a double floating-point. + * + * @returns A double value. + * Returns INCORRECT_TYPE if the JSON element is not a number. + */ + inline simdjson_result get_double() const noexcept; + /** + * Cast this element to a bool. + * + * @returns A bool value. + * Returns INCORRECT_TYPE if the JSON element is not a boolean. + */ + inline simdjson_result get_bool() const noexcept; + + /** + * Whether this element is a json array. + * + * Equivalent to is(). + */ + inline bool is_array() const noexcept; + /** + * Whether this element is a json object. + * + * Equivalent to is(). + */ + inline bool is_object() const noexcept; + /** + * Whether this element is a json string. + * + * Equivalent to is() or is(). + */ + inline bool is_string() const noexcept; + /** + * Whether this element is a json number that fits in a signed 64-bit integer. + * + * Equivalent to is(). + */ + inline bool is_int64() const noexcept; + /** + * Whether this element is a json number that fits in an unsigned 64-bit integer. + * + * Equivalent to is(). + */ + inline bool is_uint64() const noexcept; + /** + * Whether this element is a json number that fits in a double. + * + * Equivalent to is(). + */ + inline bool is_double() const noexcept; + + /** + * Whether this element is a json number. + * + * Both integers and floating points will return true. + */ + inline bool is_number() const noexcept; + + /** + * Whether this element is a json `true` or `false`. + * + * Equivalent to is(). + */ + inline bool is_bool() const noexcept; + /** + * Whether this element is a json `null`. + */ + inline bool is_null() const noexcept; + + /** + * Tell whether the value can be cast to provided type (T). + * + * Supported types: + * - Boolean: bool + * - Number: double, uint64_t, int64_t + * - String: std::string_view, const char * + * - Array: dom::array + * - Object: dom::object + * + * @tparam T bool, double, uint64_t, int64_t, std::string_view, const char *, dom::array, dom::object + */ + template + simdjson_inline bool is() const noexcept; + + /** + * Get the value as the provided type (T). + * + * Supported types: + * - Boolean: bool + * - Number: double, uint64_t, int64_t + * - String: std::string_view, const char * + * - Array: dom::array + * - Object: dom::object + * + * You may use get_double(), get_bool(), get_uint64(), get_int64(), + * get_object(), get_array() or get_string() instead. + * + * @tparam T bool, double, uint64_t, int64_t, std::string_view, const char *, dom::array, dom::object + * + * @returns The value cast to the given type, or: + * INCORRECT_TYPE if the value cannot be cast to the given type. + */ + + template + inline simdjson_result get() const noexcept { + // Unless the simdjson library provides an inline implementation, calling this method should + // immediately fail. + static_assert(!sizeof(T), "The get method with given type is not implemented by the simdjson library. " + "The supported types are Boolean (bool), numbers (double, uint64_t, int64_t), " + "strings (std::string_view, const char *), arrays (dom::array) and objects (dom::object). " + "We recommend you use get_double(), get_bool(), get_uint64(), get_int64(), " + "get_object(), get_array() or get_string() instead of the get template."); + } + + /** + * Get the value as the provided type (T). + * + * Supported types: + * - Boolean: bool + * - Number: double, uint64_t, int64_t + * - String: std::string_view, const char * + * - Array: dom::array + * - Object: dom::object + * + * @tparam T bool, double, uint64_t, int64_t, std::string_view, const char *, dom::array, dom::object + * + * @param value The variable to set to the value. May not be set if there is an error. + * + * @returns The error that occurred, or SUCCESS if there was no error. + */ + template + simdjson_warn_unused simdjson_inline error_code get(T &value) const noexcept; + + /** + * Get the value as the provided type (T), setting error if it's not the given type. + * + * Supported types: + * - Boolean: bool + * - Number: double, uint64_t, int64_t + * - String: std::string_view, const char * + * - Array: dom::array + * - Object: dom::object + * + * @tparam T bool, double, uint64_t, int64_t, std::string_view, const char *, dom::array, dom::object + * + * @param value The variable to set to the given type. value is undefined if there is an error. + * @param error The variable to store the error. error is set to error_code::SUCCEED if there is an error. + */ + template + inline void tie(T &value, error_code &error) && noexcept; + +#if SIMDJSON_EXCEPTIONS + /** + * Read this element as a boolean. + * + * @return The boolean value + * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not a boolean. + */ + inline operator bool() const noexcept(false); + + /** + * Read this element as a null-terminated UTF-8 string. + * + * Be mindful that JSON allows strings to contain null characters. + * + * Does *not* convert other types to a string; requires that the JSON type of the element was + * an actual string. + * + * @return The string value. + * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not a string. + */ + inline explicit operator const char*() const noexcept(false); + + /** + * Read this element as a null-terminated UTF-8 string. + * + * Does *not* convert other types to a string; requires that the JSON type of the element was + * an actual string. + * + * @return The string value. + * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not a string. + */ + inline operator std::string_view() const noexcept(false); + + /** + * Read this element as an unsigned integer. + * + * @return The integer value. + * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an integer + * @exception simdjson_error(NUMBER_OUT_OF_RANGE) if the integer does not fit in 64 bits or is negative + */ + inline operator uint64_t() const noexcept(false); + /** + * Read this element as an signed integer. + * + * @return The integer value. + * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an integer + * @exception simdjson_error(NUMBER_OUT_OF_RANGE) if the integer does not fit in 64 bits + */ + inline operator int64_t() const noexcept(false); + /** + * Read this element as an double. + * + * @return The double value. + * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not a number + */ + inline operator double() const noexcept(false); + /** + * Read this element as a JSON array. + * + * @return The JSON array. + * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an array + */ + inline operator array() const noexcept(false); + /** + * Read this element as a JSON object (key/value pairs). + * + * @return The JSON object. + * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an object + */ + inline operator object() const noexcept(false); + + /** + * Iterate over each element in this array. + * + * @return The beginning of the iteration. + * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an array + */ + inline dom::array::iterator begin() const noexcept(false); + + /** + * Iterate over each element in this array. + * + * @return The end of the iteration. + * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an array + */ + inline dom::array::iterator end() const noexcept(false); +#endif // SIMDJSON_EXCEPTIONS + + /** + * Get the value associated with the given key. + * + * The key will be matched against **unescaped** JSON: + * + * dom::parser parser; + * int64_t(parser.parse(R"({ "a\n": 1 })"_padded)["a\n"]) == 1 + * parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get_uint64().error() == NO_SUCH_FIELD + * + * @return The value associated with this field, or: + * - NO_SUCH_FIELD if the field does not exist in the object + * - INCORRECT_TYPE if this is not an object + */ + inline simdjson_result operator[](std::string_view key) const noexcept; + + /** + * Get the value associated with the given key. + * + * The key will be matched against **unescaped** JSON: + * + * dom::parser parser; + * int64_t(parser.parse(R"({ "a\n": 1 })"_padded)["a\n"]) == 1 + * parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get_uint64().error() == NO_SUCH_FIELD + * + * @return The value associated with this field, or: + * - NO_SUCH_FIELD if the field does not exist in the object + * - INCORRECT_TYPE if this is not an object + */ + inline simdjson_result operator[](const char *key) const noexcept; + simdjson_result operator[](int) const noexcept = delete; + + + /** + * Get the value associated with the given JSON pointer. We use the RFC 6901 + * https://tools.ietf.org/html/rfc6901 standard. + * + * dom::parser parser; + * element doc = parser.parse(R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded); + * doc.at_pointer("/foo/a/1") == 20 + * doc.at_pointer("/foo")["a"].at(1) == 20 + * doc.at_pointer("")["foo"]["a"].at(1) == 20 + * + * It is allowed for a key to be the empty string: + * + * dom::parser parser; + * object obj = parser.parse(R"({ "": { "a": [ 10, 20, 30 ] }})"_padded); + * obj.at_pointer("//a/1") == 20 + * + * @return The value associated with the given JSON pointer, or: + * - NO_SUCH_FIELD if a field does not exist in an object + * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length + * - INCORRECT_TYPE if a non-integer is used to access an array + * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed + */ + inline simdjson_result at_pointer(const std::string_view json_pointer) const noexcept; + + /** + * Get the value associated with the given JSONPath expression. We only support + * JSONPath queries that trivially convertible to JSON Pointer queries: key + * names and array indices. + * + * https://datatracker.ietf.org/doc/html/draft-normington-jsonpath-00 + * + * @return The value associated with the given JSONPath expression, or: + * - INVALID_JSON_POINTER if the JSONPath to JSON Pointer conversion fails + * - NO_SUCH_FIELD if a field does not exist in an object + * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length + * - INCORRECT_TYPE if a non-integer is used to access an array + */ + inline simdjson_result at_path(std::string_view json_path) const noexcept; + +#ifndef SIMDJSON_DISABLE_DEPRECATED_API + /** + * + * Version 0.4 of simdjson used an incorrect interpretation of the JSON Pointer standard + * and allowed the following : + * + * dom::parser parser; + * element doc = parser.parse(R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded); + * doc.at("foo/a/1") == 20 + * + * Though it is intuitive, it is not compliant with RFC 6901 + * https://tools.ietf.org/html/rfc6901 + * + * For standard compliance, use the at_pointer function instead. + * + * @return The value associated with the given JSON pointer, or: + * - NO_SUCH_FIELD if a field does not exist in an object + * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length + * - INCORRECT_TYPE if a non-integer is used to access an array + * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed + */ + [[deprecated("For standard compliance, use at_pointer instead, and prefix your pointers with a slash '/', see RFC6901 ")]] + inline simdjson_result at(const std::string_view json_pointer) const noexcept; +#endif // SIMDJSON_DISABLE_DEPRECATED_API + + /** + * Get the value at the given index. + * + * @return The value at the given index, or: + * - INDEX_OUT_OF_BOUNDS if the array index is larger than an array length + */ + inline simdjson_result at(size_t index) const noexcept; + + /** + * Get the value associated with the given key. + * + * The key will be matched against **unescaped** JSON: + * + * dom::parser parser; + * int64_t(parser.parse(R"({ "a\n": 1 })"_padded)["a\n"]) == 1 + * parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get_uint64().error() == NO_SUCH_FIELD + * + * @return The value associated with this field, or: + * - NO_SUCH_FIELD if the field does not exist in the object + */ + inline simdjson_result at_key(std::string_view key) const noexcept; + + /** + * Get the value associated with the given key in a case-insensitive manner. + * + * Note: The key will be matched against **unescaped** JSON. + * + * @return The value associated with this field, or: + * - NO_SUCH_FIELD if the field does not exist in the object + */ + inline simdjson_result at_key_case_insensitive(std::string_view key) const noexcept; + + /** + * operator< defines a total order for element allowing to use them in + * ordered C++ STL containers + * + * @return TRUE if the key appears before the other one in the tape + */ + inline bool operator<(const element &other) const noexcept; + + /** + * operator== allows to verify if two element values reference the + * same JSON item + * + * @return TRUE if the two values references the same JSON element + */ + inline bool operator==(const element &other) const noexcept; + + /** @private for debugging. Prints out the root element. */ + inline bool dump_raw_tape(std::ostream &out) const noexcept; + +private: + simdjson_inline element(const internal::tape_ref &tape) noexcept; + internal::tape_ref tape; + friend class document; + friend class object; + friend class array; + friend struct simdjson_result; + template + friend class simdjson::internal::string_builder; + +}; + +} // namespace dom + +/** The result of a JSON navigation that may fail. */ +template<> +struct simdjson_result : public internal::simdjson_result_base { +public: + simdjson_inline simdjson_result() noexcept; ///< @private + simdjson_inline simdjson_result(dom::element &&value) noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + + simdjson_inline simdjson_result type() const noexcept; + template + simdjson_inline bool is() const noexcept; + template + simdjson_inline simdjson_result get() const noexcept; + template + simdjson_warn_unused simdjson_inline error_code get(T &value) const noexcept; + + simdjson_inline simdjson_result get_array() const noexcept; + simdjson_inline simdjson_result get_object() const noexcept; + simdjson_inline simdjson_result get_c_str() const noexcept; + simdjson_inline simdjson_result get_string_length() const noexcept; + simdjson_inline simdjson_result get_string() const noexcept; + simdjson_inline simdjson_result get_int64() const noexcept; + simdjson_inline simdjson_result get_uint64() const noexcept; + simdjson_inline simdjson_result get_double() const noexcept; + simdjson_inline simdjson_result get_bool() const noexcept; + + simdjson_inline bool is_array() const noexcept; + simdjson_inline bool is_object() const noexcept; + simdjson_inline bool is_string() const noexcept; + simdjson_inline bool is_int64() const noexcept; + simdjson_inline bool is_uint64() const noexcept; + simdjson_inline bool is_double() const noexcept; + simdjson_inline bool is_number() const noexcept; + simdjson_inline bool is_bool() const noexcept; + simdjson_inline bool is_null() const noexcept; + + simdjson_inline simdjson_result operator[](std::string_view key) const noexcept; + simdjson_inline simdjson_result operator[](const char *key) const noexcept; + simdjson_result operator[](int) const noexcept = delete; + simdjson_inline simdjson_result at_pointer(const std::string_view json_pointer) const noexcept; + simdjson_inline simdjson_result at_path(const std::string_view json_path) const noexcept; + [[deprecated("For standard compliance, use at_pointer instead, and prefix your pointers with a slash '/', see RFC6901 ")]] + simdjson_inline simdjson_result at(const std::string_view json_pointer) const noexcept; + simdjson_inline simdjson_result at(size_t index) const noexcept; + simdjson_inline simdjson_result at_key(std::string_view key) const noexcept; + simdjson_inline simdjson_result at_key_case_insensitive(std::string_view key) const noexcept; + +#if SIMDJSON_EXCEPTIONS + simdjson_inline operator bool() const noexcept(false); + simdjson_inline explicit operator const char*() const noexcept(false); + simdjson_inline operator std::string_view() const noexcept(false); + simdjson_inline operator uint64_t() const noexcept(false); + simdjson_inline operator int64_t() const noexcept(false); + simdjson_inline operator double() const noexcept(false); + simdjson_inline operator dom::array() const noexcept(false); + simdjson_inline operator dom::object() const noexcept(false); + + simdjson_inline dom::array::iterator begin() const noexcept(false); + simdjson_inline dom::array::iterator end() const noexcept(false); +#endif // SIMDJSON_EXCEPTIONS +}; + +} // namespace simdjson + +#endif // SIMDJSON_DOM_DOCUMENT_H diff --git a/contrib/libs/simdjson/include/simdjson/dom/object-inl.h b/contrib/libs/simdjson/include/simdjson/dom/object-inl.h new file mode 100644 index 000000000000..3bfd7a051c7a --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/dom/object-inl.h @@ -0,0 +1,275 @@ +#ifndef SIMDJSON_OBJECT_INL_H +#define SIMDJSON_OBJECT_INL_H + +#include "simdjson/dom/base.h" +#include "simdjson/dom/object.h" +#include "simdjson/dom/document.h" + +#include "simdjson/dom/element-inl.h" +#include "simdjson/error-inl.h" +#include "simdjson/jsonpathutil.h" + +#include + +namespace simdjson { + +// +// simdjson_result inline implementation +// +simdjson_inline simdjson_result::simdjson_result() noexcept + : internal::simdjson_result_base() {} +simdjson_inline simdjson_result::simdjson_result(dom::object value) noexcept + : internal::simdjson_result_base(std::forward(value)) {} +simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept + : internal::simdjson_result_base(error) {} + +inline simdjson_result simdjson_result::operator[](std::string_view key) const noexcept { + if (error()) { return error(); } + return first[key]; +} +inline simdjson_result simdjson_result::operator[](const char *key) const noexcept { + if (error()) { return error(); } + return first[key]; +} +inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) const noexcept { + if (error()) { return error(); } + return first.at_pointer(json_pointer); +} +inline simdjson_result simdjson_result::at_path(std::string_view json_path) const noexcept { + auto json_pointer = json_path_to_pointer_conversion(json_path); + if (json_pointer == "-1") { return INVALID_JSON_POINTER; } + return at_pointer(json_pointer); +} +inline simdjson_result simdjson_result::at_key(std::string_view key) const noexcept { + if (error()) { return error(); } + return first.at_key(key); +} +inline simdjson_result simdjson_result::at_key_case_insensitive(std::string_view key) const noexcept { + if (error()) { return error(); } + return first.at_key_case_insensitive(key); +} + +#if SIMDJSON_EXCEPTIONS + +inline dom::object::iterator simdjson_result::begin() const noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first.begin(); +} +inline dom::object::iterator simdjson_result::end() const noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first.end(); +} +inline size_t simdjson_result::size() const noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first.size(); +} + +#endif // SIMDJSON_EXCEPTIONS + +namespace dom { + +// +// object inline implementation +// +simdjson_inline object::object() noexcept : tape{} {} +simdjson_inline object::object(const internal::tape_ref &_tape) noexcept : tape{_tape} { } +inline object::iterator object::begin() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + return internal::tape_ref(tape.doc, tape.json_index + 1); +} +inline object::iterator object::end() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + return internal::tape_ref(tape.doc, tape.after_element() - 1); +} +inline size_t object::size() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + return tape.scope_count(); +} + +inline simdjson_result object::operator[](std::string_view key) const noexcept { + return at_key(key); +} +inline simdjson_result object::operator[](const char *key) const noexcept { + return at_key(key); +} +inline simdjson_result object::at_pointer(std::string_view json_pointer) const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + if(json_pointer.empty()) { // an empty string means that we return the current node + return element(this->tape); // copy the current node + } else if(json_pointer[0] != '/') { // otherwise there is an error + return INVALID_JSON_POINTER; + } + json_pointer = json_pointer.substr(1); + size_t slash = json_pointer.find('/'); + std::string_view key = json_pointer.substr(0, slash); + // Grab the child with the given key + simdjson_result child; + + // If there is an escape character in the key, unescape it and then get the child. + size_t escape = key.find('~'); + if (escape != std::string_view::npos) { + // Unescape the key + std::string unescaped(key); + do { + switch (unescaped[escape+1]) { + case '0': + unescaped.replace(escape, 2, "~"); + break; + case '1': + unescaped.replace(escape, 2, "/"); + break; + default: + return INVALID_JSON_POINTER; // "Unexpected ~ escape character in JSON pointer"); + } + escape = unescaped.find('~', escape+1); + } while (escape != std::string::npos); + child = at_key(unescaped); + } else { + child = at_key(key); + } + if(child.error()) { + return child; // we do not continue if there was an error + } + // If there is a /, we have to recurse and look up more of the path + if (slash != std::string_view::npos) { + child = child.at_pointer(json_pointer.substr(slash)); + } + return child; +} + +inline simdjson_result object::at_path(std::string_view json_path) const noexcept { + auto json_pointer = json_path_to_pointer_conversion(json_path); + if (json_pointer == "-1") { return INVALID_JSON_POINTER; } + return at_pointer(json_pointer); +} + +inline simdjson_result object::at_key(std::string_view key) const noexcept { + iterator end_field = end(); + for (iterator field = begin(); field != end_field; ++field) { + if (field.key_equals(key)) { + return field.value(); + } + } + return NO_SUCH_FIELD; +} +// In case you wonder why we need this, please see +// https://github.com/simdjson/simdjson/issues/323 +// People do seek keys in a case-insensitive manner. +inline simdjson_result object::at_key_case_insensitive(std::string_view key) const noexcept { + iterator end_field = end(); + for (iterator field = begin(); field != end_field; ++field) { + if (field.key_equals_case_insensitive(key)) { + return field.value(); + } + } + return NO_SUCH_FIELD; +} + +inline object::operator element() const noexcept { + return element(tape); +} + +// +// object::iterator inline implementation +// +simdjson_inline object::iterator::iterator(const internal::tape_ref &_tape) noexcept : tape{_tape} { } +inline const key_value_pair object::iterator::operator*() const noexcept { + return key_value_pair(key(), value()); +} +inline bool object::iterator::operator!=(const object::iterator& other) const noexcept { + return tape.json_index != other.tape.json_index; +} +inline bool object::iterator::operator==(const object::iterator& other) const noexcept { + return tape.json_index == other.tape.json_index; +} +inline bool object::iterator::operator<(const object::iterator& other) const noexcept { + return tape.json_index < other.tape.json_index; +} +inline bool object::iterator::operator<=(const object::iterator& other) const noexcept { + return tape.json_index <= other.tape.json_index; +} +inline bool object::iterator::operator>=(const object::iterator& other) const noexcept { + return tape.json_index >= other.tape.json_index; +} +inline bool object::iterator::operator>(const object::iterator& other) const noexcept { + return tape.json_index > other.tape.json_index; +} +inline object::iterator& object::iterator::operator++() noexcept { + tape.json_index++; + tape.json_index = tape.after_element(); + return *this; +} +inline object::iterator object::iterator::operator++(int) noexcept { + object::iterator out = *this; + ++*this; + return out; +} +inline std::string_view object::iterator::key() const noexcept { + return tape.get_string_view(); +} +inline uint32_t object::iterator::key_length() const noexcept { + return tape.get_string_length(); +} +inline const char* object::iterator::key_c_str() const noexcept { + return reinterpret_cast(&tape.doc->string_buf[size_t(tape.tape_value()) + sizeof(uint32_t)]); +} +inline element object::iterator::value() const noexcept { + return element(internal::tape_ref(tape.doc, tape.json_index + 1)); +} + +/** + * Design notes: + * Instead of constructing a string_view and then comparing it with a + * user-provided strings, it is probably more performant to have dedicated + * functions taking as a parameter the string we want to compare against + * and return true when they are equal. That avoids the creation of a temporary + * std::string_view. Though it is possible for the compiler to avoid entirely + * any overhead due to string_view, relying too much on compiler magic is + * problematic: compiler magic sometimes fail, and then what do you do? + * Also, enticing users to rely on high-performance function is probably better + * on the long run. + */ + +inline bool object::iterator::key_equals(std::string_view o) const noexcept { + // We use the fact that the key length can be computed quickly + // without access to the string buffer. + const uint32_t len = key_length(); + if(o.size() == len) { + // We avoid construction of a temporary string_view instance. + return (memcmp(o.data(), key_c_str(), len) == 0); + } + return false; +} + +inline bool object::iterator::key_equals_case_insensitive(std::string_view o) const noexcept { + // We use the fact that the key length can be computed quickly + // without access to the string buffer. + const uint32_t len = key_length(); + if(o.size() == len) { + // See For case-insensitive string comparisons, avoid char-by-char functions + // https://lemire.me/blog/2020/04/30/for-case-insensitive-string-comparisons-avoid-char-by-char-functions/ + // Note that it might be worth rolling our own strncasecmp function, with vectorization. + return (simdjson_strncasecmp(o.data(), key_c_str(), len) == 0); + } + return false; +} +// +// key_value_pair inline implementation +// +inline key_value_pair::key_value_pair(std::string_view _key, element _value) noexcept : + key(_key), value(_value) {} + +} // namespace dom + +} // namespace simdjson + +#if defined(__cpp_lib_ranges) +static_assert(std::ranges::view); +static_assert(std::ranges::sized_range); +#if SIMDJSON_EXCEPTIONS +static_assert(std::ranges::view>); +static_assert(std::ranges::sized_range>); +#endif // SIMDJSON_EXCEPTIONS +#endif // defined(__cpp_lib_ranges) + +#endif // SIMDJSON_OBJECT_INL_H diff --git a/contrib/libs/simdjson/include/simdjson/dom/object.h b/contrib/libs/simdjson/include/simdjson/dom/object.h new file mode 100644 index 000000000000..83f1392d990f --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/dom/object.h @@ -0,0 +1,292 @@ +#ifndef SIMDJSON_DOM_OBJECT_H +#define SIMDJSON_DOM_OBJECT_H + +#include "simdjson/dom/base.h" +#include "simdjson/dom/element.h" +#include "simdjson/internal/tape_ref.h" + +namespace simdjson { +namespace dom { + +/** + * JSON object. + */ +class object { +public: + /** Create a new, invalid object */ + simdjson_inline object() noexcept; + + class iterator { + public: + using value_type = const key_value_pair; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = value_type; + using iterator_category = std::forward_iterator_tag; + + /** + * Get the actual key/value pair + */ + inline reference operator*() const noexcept; + /** + * Get the next key/value pair. + * + * Part of the std::iterator interface. + * + */ + inline iterator& operator++() noexcept; + /** + * Get the next key/value pair. + * + * Part of the std::iterator interface. + * + */ + inline iterator operator++(int) noexcept; + /** + * Check if these values come from the same place in the JSON. + * + * Part of the std::iterator interface. + */ + inline bool operator!=(const iterator& other) const noexcept; + inline bool operator==(const iterator& other) const noexcept; + + inline bool operator<(const iterator& other) const noexcept; + inline bool operator<=(const iterator& other) const noexcept; + inline bool operator>=(const iterator& other) const noexcept; + inline bool operator>(const iterator& other) const noexcept; + /** + * Get the key of this key/value pair. + */ + inline std::string_view key() const noexcept; + /** + * Get the length (in bytes) of the key in this key/value pair. + * You should expect this function to be faster than key().size(). + */ + inline uint32_t key_length() const noexcept; + /** + * Returns true if the key in this key/value pair is equal + * to the provided string_view. + */ + inline bool key_equals(std::string_view o) const noexcept; + /** + * Returns true if the key in this key/value pair is equal + * to the provided string_view in a case-insensitive manner. + * Case comparisons may only be handled correctly for ASCII strings. + */ + inline bool key_equals_case_insensitive(std::string_view o) const noexcept; + /** + * Get the key of this key/value pair. + */ + inline const char *key_c_str() const noexcept; + /** + * Get the value of this key/value pair. + */ + inline element value() const noexcept; + + iterator() noexcept = default; + iterator(const iterator&) noexcept = default; + iterator& operator=(const iterator&) noexcept = default; + private: + simdjson_inline iterator(const internal::tape_ref &tape) noexcept; + + internal::tape_ref tape; + + friend class object; + }; + + /** + * Return the first key/value pair. + * + * Part of the std::iterable interface. + */ + inline iterator begin() const noexcept; + /** + * One past the last key/value pair. + * + * Part of the std::iterable interface. + */ + inline iterator end() const noexcept; + /** + * Get the size of the object (number of keys). + * It is a saturated value with a maximum of 0xFFFFFF: if the value + * is 0xFFFFFF then the size is 0xFFFFFF or greater. + */ + inline size_t size() const noexcept; + /** + * Get the value associated with the given key. + * + * The key will be matched against **unescaped** JSON: + * + * dom::parser parser; + * int64_t(parser.parse(R"({ "a\n": 1 })"_padded)["a\n"]) == 1 + * parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get_uint64().error() == NO_SUCH_FIELD + * + * This function has linear-time complexity: the keys are checked one by one. + * + * @return The value associated with this field, or: + * - NO_SUCH_FIELD if the field does not exist in the object + * - INCORRECT_TYPE if this is not an object + */ + inline simdjson_result operator[](std::string_view key) const noexcept; + + /** + * Get the value associated with the given key. + * + * The key will be matched against **unescaped** JSON: + * + * dom::parser parser; + * int64_t(parser.parse(R"({ "a\n": 1 })"_padded)["a\n"]) == 1 + * parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get_uint64().error() == NO_SUCH_FIELD + * + * This function has linear-time complexity: the keys are checked one by one. + * + * @return The value associated with this field, or: + * - NO_SUCH_FIELD if the field does not exist in the object + * - INCORRECT_TYPE if this is not an object + */ + inline simdjson_result operator[](const char *key) const noexcept; + simdjson_result operator[](int) const noexcept = delete; + + /** + * Get the value associated with the given JSON pointer. We use the RFC 6901 + * https://tools.ietf.org/html/rfc6901 standard, interpreting the current node + * as the root of its own JSON document. + * + * dom::parser parser; + * object obj = parser.parse(R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded); + * obj.at_pointer("/foo/a/1") == 20 + * obj.at_pointer("/foo")["a"].at(1) == 20 + * + * It is allowed for a key to be the empty string: + * + * dom::parser parser; + * object obj = parser.parse(R"({ "": { "a": [ 10, 20, 30 ] }})"_padded); + * obj.at_pointer("//a/1") == 20 + * obj.at_pointer("/")["a"].at(1) == 20 + * + * @return The value associated with the given JSON pointer, or: + * - NO_SUCH_FIELD if a field does not exist in an object + * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length + * - INCORRECT_TYPE if a non-integer is used to access an array + * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed + */ + inline simdjson_result at_pointer(std::string_view json_pointer) const noexcept; + + /** + * Get the value associated with the given JSONPath expression. We only support + * JSONPath queries that trivially convertible to JSON Pointer queries: key + * names and array indices. + * + * https://datatracker.ietf.org/doc/html/draft-normington-jsonpath-00 + * + * @return The value associated with the given JSONPath expression, or: + * - INVALID_JSON_POINTER if the JSONPath to JSON Pointer conversion fails + * - NO_SUCH_FIELD if a field does not exist in an object + * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length + * - INCORRECT_TYPE if a non-integer is used to access an array + */ + inline simdjson_result at_path(std::string_view json_path) const noexcept; + + /** + * Get the value associated with the given key. + * + * The key will be matched against **unescaped** JSON: + * + * dom::parser parser; + * int64_t(parser.parse(R"({ "a\n": 1 })"_padded)["a\n"]) == 1 + * parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get_uint64().error() == NO_SUCH_FIELD + * + * This function has linear-time complexity: the keys are checked one by one. + * + * @return The value associated with this field, or: + * - NO_SUCH_FIELD if the field does not exist in the object + */ + inline simdjson_result at_key(std::string_view key) const noexcept; + + /** + * Get the value associated with the given key in a case-insensitive manner. + * It is only guaranteed to work over ASCII inputs. + * + * Note: The key will be matched against **unescaped** JSON. + * + * This function has linear-time complexity: the keys are checked one by one. + * + * @return The value associated with this field, or: + * - NO_SUCH_FIELD if the field does not exist in the object + */ + inline simdjson_result at_key_case_insensitive(std::string_view key) const noexcept; + + /** + * Implicitly convert object to element + */ + inline operator element() const noexcept; + +private: + simdjson_inline object(const internal::tape_ref &tape) noexcept; + + internal::tape_ref tape; + + friend class element; + friend struct simdjson_result; + template + friend class simdjson::internal::string_builder; +}; + +/** + * Key/value pair in an object. + */ +class key_value_pair { +public: + /** key in the key-value pair **/ + std::string_view key; + /** value in the key-value pair **/ + element value; + +private: + simdjson_inline key_value_pair(std::string_view _key, element _value) noexcept; + friend class object; +}; + +} // namespace dom + +/** The result of a JSON conversion that may fail. */ +template<> +struct simdjson_result : public internal::simdjson_result_base { +public: + simdjson_inline simdjson_result() noexcept; ///< @private + simdjson_inline simdjson_result(dom::object value) noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + + inline simdjson_result operator[](std::string_view key) const noexcept; + inline simdjson_result operator[](const char *key) const noexcept; + simdjson_result operator[](int) const noexcept = delete; + inline simdjson_result at_pointer(std::string_view json_pointer) const noexcept; + inline simdjson_result at_path(std::string_view json_path) const noexcept; + inline simdjson_result at_key(std::string_view key) const noexcept; + inline simdjson_result at_key_case_insensitive(std::string_view key) const noexcept; + +#if SIMDJSON_EXCEPTIONS + inline dom::object::iterator begin() const noexcept(false); + inline dom::object::iterator end() const noexcept(false); + inline size_t size() const noexcept(false); +#endif // SIMDJSON_EXCEPTIONS +}; + +} // namespace simdjson + +#if defined(__cpp_lib_ranges) +#include + +namespace std { +namespace ranges { +template<> +inline constexpr bool enable_view = true; +#if SIMDJSON_EXCEPTIONS +template<> +inline constexpr bool enable_view> = true; +#endif // SIMDJSON_EXCEPTIONS +} // namespace ranges +} // namespace std +#endif // defined(__cpp_lib_ranges) + +#endif // SIMDJSON_DOM_OBJECT_H diff --git a/contrib/libs/simdjson/include/simdjson/dom/parser-inl.h b/contrib/libs/simdjson/include/simdjson/dom/parser-inl.h new file mode 100644 index 000000000000..14ba6c83330d --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/dom/parser-inl.h @@ -0,0 +1,258 @@ +#ifndef SIMDJSON_PARSER_INL_H +#define SIMDJSON_PARSER_INL_H + +#include "simdjson/dom/base.h" +#include "simdjson/dom/document_stream.h" +#include "simdjson/implementation.h" +#include "simdjson/internal/dom_parser_implementation.h" + +#include "simdjson/error-inl.h" +#include "simdjson/padded_string-inl.h" +#include "simdjson/dom/document_stream-inl.h" +#include "simdjson/dom/element-inl.h" + +#include +#include /* memcmp */ + +namespace simdjson { +namespace dom { + +// +// parser inline implementation +// +simdjson_inline parser::parser(size_t max_capacity) noexcept + : _max_capacity{max_capacity}, + loaded_bytes(nullptr) { +} +simdjson_inline parser::parser(parser &&other) noexcept = default; +simdjson_inline parser &parser::operator=(parser &&other) noexcept = default; + +inline bool parser::is_valid() const noexcept { return valid; } +inline int parser::get_error_code() const noexcept { return error; } +inline std::string parser::get_error_message() const noexcept { return error_message(error); } + +inline bool parser::dump_raw_tape(std::ostream &os) const noexcept { + return valid ? doc.dump_raw_tape(os) : false; +} + +inline simdjson_result parser::read_file(const std::string &path) noexcept { + // Open the file + SIMDJSON_PUSH_DISABLE_WARNINGS + SIMDJSON_DISABLE_DEPRECATED_WARNING // Disable CRT_SECURE warning on MSVC: manually verified this is safe + std::FILE *fp = std::fopen(path.c_str(), "rb"); + SIMDJSON_POP_DISABLE_WARNINGS + + if (fp == nullptr) { + return IO_ERROR; + } + + // Get the file size + int ret; +#if SIMDJSON_VISUAL_STUDIO && !SIMDJSON_IS_32BITS + ret = _fseeki64(fp, 0, SEEK_END); +#else + ret = std::fseek(fp, 0, SEEK_END); +#endif // _WIN64 + if(ret < 0) { + std::fclose(fp); + return IO_ERROR; + } +#if SIMDJSON_VISUAL_STUDIO && !SIMDJSON_IS_32BITS + __int64 len = _ftelli64(fp); + if(len == -1L) { + std::fclose(fp); + return IO_ERROR; + } +#else + long len = std::ftell(fp); + if((len < 0) || (len == LONG_MAX)) { + std::fclose(fp); + return IO_ERROR; + } +#endif + + // Make sure we have enough capacity to load the file + if (_loaded_bytes_capacity < size_t(len)) { + loaded_bytes.reset( internal::allocate_padded_buffer(len) ); + if (!loaded_bytes) { + std::fclose(fp); + return MEMALLOC; + } + _loaded_bytes_capacity = len; + } + + // Read the string + std::rewind(fp); + size_t bytes_read = std::fread(loaded_bytes.get(), 1, len, fp); + if (std::fclose(fp) != 0 || bytes_read != size_t(len)) { + return IO_ERROR; + } + + return bytes_read; +} + +inline simdjson_result parser::load(const std::string &path) & noexcept { + return load_into_document(doc, path); +} + +inline simdjson_result parser::load_into_document(document& provided_doc, const std::string &path) & noexcept { + size_t len; + auto _error = read_file(path).get(len); + if (_error) { return _error; } + return parse_into_document(provided_doc, loaded_bytes.get(), len, false); +} + +inline simdjson_result parser::load_many(const std::string &path, size_t batch_size) noexcept { + size_t len; + auto _error = read_file(path).get(len); + if (_error) { return _error; } + if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; } + return document_stream(*this, reinterpret_cast(loaded_bytes.get()), len, batch_size); +} + +inline simdjson_result parser::parse_into_document(document& provided_doc, const uint8_t *buf, size_t len, bool realloc_if_needed) & noexcept { + // Important: we need to ensure that document has enough capacity. + // Important: It is possible that provided_doc is actually the internal 'doc' within the parser!!! + error_code _error = ensure_capacity(provided_doc, len); + if (_error) { return _error; } + if (realloc_if_needed) { + // Make sure we have enough capacity to copy len bytes + if (!loaded_bytes || _loaded_bytes_capacity < len) { + loaded_bytes.reset( internal::allocate_padded_buffer(len) ); + if (!loaded_bytes) { + return MEMALLOC; + } + _loaded_bytes_capacity = len; + } + std::memcpy(static_cast(loaded_bytes.get()), buf, len); + buf = reinterpret_cast(loaded_bytes.get()); + } + + if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) { + buf += 3; + len -= 3; + } + _error = implementation->parse(buf, len, provided_doc); + + if (_error) { return _error; } + + return provided_doc.root(); +} + +simdjson_inline simdjson_result parser::parse_into_document(document& provided_doc, const char *buf, size_t len, bool realloc_if_needed) & noexcept { + return parse_into_document(provided_doc, reinterpret_cast(buf), len, realloc_if_needed); +} +simdjson_inline simdjson_result parser::parse_into_document(document& provided_doc, const std::string &s) & noexcept { + return parse_into_document(provided_doc, s.data(), s.length(), s.capacity() - s.length() < SIMDJSON_PADDING); +} +simdjson_inline simdjson_result parser::parse_into_document(document& provided_doc, const padded_string &s) & noexcept { + return parse_into_document(provided_doc, s.data(), s.length(), false); +} + + +inline simdjson_result parser::parse(const uint8_t *buf, size_t len, bool realloc_if_needed) & noexcept { + return parse_into_document(doc, buf, len, realloc_if_needed); +} + +simdjson_inline simdjson_result parser::parse(const char *buf, size_t len, bool realloc_if_needed) & noexcept { + return parse(reinterpret_cast(buf), len, realloc_if_needed); +} +simdjson_inline simdjson_result parser::parse(const std::string &s) & noexcept { + return parse(s.data(), s.length(), s.capacity() - s.length() < SIMDJSON_PADDING); +} +simdjson_inline simdjson_result parser::parse(const padded_string &s) & noexcept { + return parse(s.data(), s.length(), false); +} +simdjson_inline simdjson_result parser::parse(const padded_string_view &v) & noexcept { + return parse(v.data(), v.length(), false); +} + +inline simdjson_result parser::parse_many(const uint8_t *buf, size_t len, size_t batch_size) noexcept { + if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; } + if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) { + buf += 3; + len -= 3; + } + return document_stream(*this, buf, len, batch_size); +} +inline simdjson_result parser::parse_many(const char *buf, size_t len, size_t batch_size) noexcept { + return parse_many(reinterpret_cast(buf), len, batch_size); +} +inline simdjson_result parser::parse_many(const std::string &s, size_t batch_size) noexcept { + return parse_many(s.data(), s.length(), batch_size); +} +inline simdjson_result parser::parse_many(const padded_string &s, size_t batch_size) noexcept { + return parse_many(s.data(), s.length(), batch_size); +} + +simdjson_inline size_t parser::capacity() const noexcept { + return implementation ? implementation->capacity() : 0; +} +simdjson_inline size_t parser::max_capacity() const noexcept { + return _max_capacity; +} +simdjson_pure simdjson_inline size_t parser::max_depth() const noexcept { + return implementation ? implementation->max_depth() : DEFAULT_MAX_DEPTH; +} + +simdjson_warn_unused +inline error_code parser::allocate(size_t capacity, size_t max_depth) noexcept { + // + // Reallocate implementation if needed + // + error_code err; + if (implementation) { + err = implementation->allocate(capacity, max_depth); + } else { + err = simdjson::get_active_implementation()->create_dom_parser_implementation(capacity, max_depth, implementation); + } + if (err) { return err; } + return SUCCESS; +} + +#ifndef SIMDJSON_DISABLE_DEPRECATED_API +simdjson_warn_unused +inline bool parser::allocate_capacity(size_t capacity, size_t max_depth) noexcept { + return !allocate(capacity, max_depth); +} +#endif // SIMDJSON_DISABLE_DEPRECATED_API + +inline error_code parser::ensure_capacity(size_t desired_capacity) noexcept { + return ensure_capacity(doc, desired_capacity); +} + + +inline error_code parser::ensure_capacity(document& target_document, size_t desired_capacity) noexcept { + // 1. It is wasteful to allocate a document and a parser for documents spanning less than MINIMAL_DOCUMENT_CAPACITY bytes. + // 2. If we allow desired_capacity = 0 then it is possible to exit this function with implementation == nullptr. + if(desired_capacity < MINIMAL_DOCUMENT_CAPACITY) { desired_capacity = MINIMAL_DOCUMENT_CAPACITY; } + // If we don't have enough capacity, (try to) automatically bump it. + // If the document needs allocation, do it too. + // Both in one if statement to minimize unlikely branching. + // + // Note: we must make sure that this function is called if capacity() == 0. We do so because we + // ensure that desired_capacity > 0. + if (simdjson_unlikely(capacity() < desired_capacity || target_document.capacity() < desired_capacity)) { + if (desired_capacity > max_capacity()) { + return error = CAPACITY; + } + error_code err1 = target_document.capacity() < desired_capacity ? target_document.allocate(desired_capacity) : SUCCESS; + error_code err2 = capacity() < desired_capacity ? allocate(desired_capacity, max_depth()) : SUCCESS; + if(err1 != SUCCESS) { return error = err1; } + if(err2 != SUCCESS) { return error = err2; } + } + return SUCCESS; +} + +simdjson_inline void parser::set_max_capacity(size_t max_capacity) noexcept { + if(max_capacity > MINIMAL_DOCUMENT_CAPACITY) { + _max_capacity = max_capacity; + } else { + _max_capacity = MINIMAL_DOCUMENT_CAPACITY; + } +} + +} // namespace dom +} // namespace simdjson + +#endif // SIMDJSON_PARSER_INL_H diff --git a/contrib/libs/simdjson/include/simdjson/dom/parser.h b/contrib/libs/simdjson/include/simdjson/dom/parser.h new file mode 100644 index 000000000000..7c28a712f0a6 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/dom/parser.h @@ -0,0 +1,671 @@ +#ifndef SIMDJSON_DOM_PARSER_H +#define SIMDJSON_DOM_PARSER_H + +#include "simdjson/dom/base.h" +#include "simdjson/dom/document.h" + +namespace simdjson { + +namespace dom { + +/** + * A persistent document parser. + * + * The parser is designed to be reused, holding the internal buffers necessary to do parsing, + * as well as memory for a single document. The parsed document is overwritten on each parse. + * + * This class cannot be copied, only moved, to avoid unintended allocations. + * + * @note Moving a parser instance may invalidate "dom::element" instances. If you need to + * preserve both the "dom::element" instances and the parser, consider wrapping the parser + * instance in a std::unique_ptr instance: + * + * std::unique_ptr parser(new dom::parser{}); + * auto error = parser->load(f).get(root); + * + * You can then move std::unique_ptr safely. + * + * @note This is not thread safe: one parser cannot produce two documents at the same time! + */ +class parser { +public: + /** + * Create a JSON parser. + * + * The new parser will have zero capacity. + * + * @param max_capacity The maximum document length the parser can automatically handle. The parser + * will allocate more capacity on an as needed basis (when it sees documents too big to handle) + * up to this amount. The parser still starts with zero capacity no matter what this number is: + * to allocate an initial capacity, call allocate() after constructing the parser. + * Defaults to SIMDJSON_MAXSIZE_BYTES (the largest single document simdjson can process). + */ + simdjson_inline explicit parser(size_t max_capacity = SIMDJSON_MAXSIZE_BYTES) noexcept; + /** + * Take another parser's buffers and state. + * + * @param other The parser to take. Its capacity is zeroed. + */ + simdjson_inline parser(parser &&other) noexcept; + parser(const parser &) = delete; ///< @private Disallow copying + /** + * Take another parser's buffers and state. + * + * @param other The parser to take. Its capacity is zeroed. + */ + simdjson_inline parser &operator=(parser &&other) noexcept; + parser &operator=(const parser &) = delete; ///< @private Disallow copying + + /** Deallocate the JSON parser. */ + ~parser()=default; + + /** + * Load a JSON document from a file and return a reference to it. + * + * dom::parser parser; + * const element doc = parser.load("jsonexamples/twitter.json"); + * + * The function is eager: the file's content is loaded in memory inside the parser instance + * and immediately parsed. The file can be deleted after the `parser.load` call. + * + * ### IMPORTANT: Document Lifetime + * + * The JSON document still lives in the parser: this is the most efficient way to parse JSON + * documents because it reuses the same buffers, but you *must* use the document before you + * destroy the parser or call parse() again. + * + * Moving the parser instance is safe, but it invalidates the element instances. You may store + * the parser instance without moving it by wrapping it inside an `unique_ptr` instance like + * so: `std::unique_ptr parser(new dom::parser{});`. + * + * ### Parser Capacity + * + * If the parser's current capacity is less than the file length, it will allocate enough capacity + * to handle it (up to max_capacity). + * + * ## Windows and Unicode + * + * Windows users who need to read files with non-ANSI characters in the + * name should set their code page to UTF-8 (65001) before calling this + * function. This should be the default with Windows 11 and better. + * Further, they may use the AreFileApisANSI function to determine whether + * the filename is interpreted using the ANSI or the system default OEM + * codepage, and they may call SetFileApisToOEM accordingly. + * + * @param path The path to load. + * @return The document, or an error: + * - IO_ERROR if there was an error opening or reading the file. + * Be mindful that on some 32-bit systems, + * the file size might be limited to 2 GB. + * - MEMALLOC if the parser does not have enough capacity and memory allocation fails. + * - CAPACITY if the parser does not have enough capacity and len > max_capacity. + * - other json errors if parsing fails. You should not rely on these errors to always the same for the + * same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware). + */ + inline simdjson_result load(const std::string &path) & noexcept; + inline simdjson_result load(const std::string &path) && = delete ; + + /** + * Load a JSON document from a file into a provide document instance and return a temporary reference to it. + * It is similar to the function `load` except that instead of parsing into the internal + * `document` instance associated with the parser, it allows the user to provide a document + * instance. + * + * dom::parser parser; + * dom::document doc; + * element doc_root = parser.load_into_document(doc, "jsonexamples/twitter.json"); + * + * The function is eager: the file's content is loaded in memory inside the parser instance + * and immediately parsed. The file can be deleted after the `parser.load_into_document` call. + * + * ### IMPORTANT: Document Lifetime + * + * After the call to load_into_document, the parser is no longer needed. + * + * The JSON document lives in the document instance: you must keep the document + * instance alive while you navigate through it (i.e., used the returned value from + * load_into_document). You are encourage to reuse the document instance + * many times with new data to avoid reallocations: + * + * dom::document doc; + * element doc_root1 = parser.load_into_document(doc, "jsonexamples/twitter.json"); + * //... doc_root1 is a pointer inside doc + * element doc_root2 = parser.load_into_document(doc, "jsonexamples/twitter.json"); + * //... doc_root2 is a pointer inside doc + * // at this point doc_root1 is no longer safe + * + * Moving the document instance is safe, but it invalidates the element instances. After + * moving a document, you can recover safe access to the document root with its `root()` method. + * + * @param doc The document instance where the parsed data will be stored (on success). + * @param path The path to load. + * @return The document, or an error: + * - IO_ERROR if there was an error opening or reading the file. + * Be mindful that on some 32-bit systems, + * the file size might be limited to 2 GB. + * - MEMALLOC if the parser does not have enough capacity and memory allocation fails. + * - CAPACITY if the parser does not have enough capacity and len > max_capacity. + * - other json errors if parsing fails. You should not rely on these errors to always the same for the + * same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware). + */ + inline simdjson_result load_into_document(document& doc, const std::string &path) & noexcept; + inline simdjson_result load_into_document(document& doc, const std::string &path) && =delete; + + /** + * Parse a JSON document and return a temporary reference to it. + * + * dom::parser parser; + * element doc_root = parser.parse(buf, len); + * + * The function eagerly parses the input: the input can be modified and discarded after + * the `parser.parse(buf, len)` call has completed. + * + * ### IMPORTANT: Document Lifetime + * + * The JSON document still lives in the parser: this is the most efficient way to parse JSON + * documents because it reuses the same buffers, but you *must* use the document before you + * destroy the parser or call parse() again. + * + * Moving the parser instance is safe, but it invalidates the element instances. You may store + * the parser instance without moving it by wrapping it inside an `unique_ptr` instance like + * so: `std::unique_ptr parser(new dom::parser{});`. + * + * ### REQUIRED: Buffer Padding + * + * The buffer must have at least SIMDJSON_PADDING extra allocated bytes. It does not matter what + * those bytes are initialized to, as long as they are allocated. These bytes will be read: if you + * using a sanitizer that verifies that no uninitialized byte is read, then you should initialize the + * SIMDJSON_PADDING bytes to avoid runtime warnings. + * + * If realloc_if_needed is true (the default), it is assumed that the buffer does *not* have enough padding, + * and it is copied into an enlarged temporary buffer before parsing. Thus the following is safe: + * + * const char *json = R"({"key":"value"})"; + * const size_t json_len = std::strlen(json); + * simdjson::dom::parser parser; + * simdjson::dom::element element = parser.parse(json, json_len); + * + * If you set realloc_if_needed to false (e.g., parser.parse(json, json_len, false)), + * you must provide a buffer with at least SIMDJSON_PADDING extra bytes at the end. + * The benefit of setting realloc_if_needed to false is that you avoid a temporary + * memory allocation and a copy. + * + * The padded bytes may be read. It is not important how you initialize + * these bytes though we recommend a sensible default like null character values or spaces. + * For example, the following low-level code is safe: + * + * const char *json = R"({"key":"value"})"; + * const size_t json_len = std::strlen(json); + * std::unique_ptr padded_json_copy{new char[json_len + SIMDJSON_PADDING]}; + * std::memcpy(padded_json_copy.get(), json, json_len); + * std::memset(padded_json_copy.get() + json_len, '\0', SIMDJSON_PADDING); + * simdjson::dom::parser parser; + * simdjson::dom::element element = parser.parse(padded_json_copy.get(), json_len, false); + * + * ### std::string references + * + * If you pass a mutable std::string reference (std::string&), the parser will seek to extend + * its capacity to SIMDJSON_PADDING bytes beyond the end of the string. + * + * Whenever you pass an std::string reference, the parser will access the bytes beyond the end of + * the string but before the end of the allocated memory (std::string::capacity()). + * If you are using a sanitizer that checks for reading uninitialized bytes or std::string's + * container-overflow checks, you may encounter sanitizer warnings. + * You can safely ignore these warnings. Or you can call simdjson::pad(std::string&) to pad the + * string with SIMDJSON_PADDING spaces: this function returns a simdjson::padding_string_view + * which can be be passed to the parser's parse function: + * + * std::string json = R"({ "foo": 1 } { "foo": 2 } { "foo": 3 } )"; + * element doc = parser.parse(simdjson::pad(json)); + * + * ### Parser Capacity + * + * If the parser's current capacity is less than len, it will allocate enough capacity + * to handle it (up to max_capacity). + * + * @param buf The JSON to parse. Must have at least len + SIMDJSON_PADDING allocated bytes, unless + * realloc_if_needed is true. + * @param len The length of the JSON. + * @param realloc_if_needed Whether to reallocate and enlarge the JSON buffer to add padding. + * @return An element pointing at the root of the document, or an error: + * - MEMALLOC if realloc_if_needed is true or the parser does not have enough capacity, + * and memory allocation fails. + * - CAPACITY if the parser does not have enough capacity and len > max_capacity. + * - other json errors if parsing fails. You should not rely on these errors to always the same for the + * same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware). + */ + inline simdjson_result parse(const uint8_t *buf, size_t len, bool realloc_if_needed = true) & noexcept; + inline simdjson_result parse(const uint8_t *buf, size_t len, bool realloc_if_needed = true) && =delete; + /** @overload parse(const uint8_t *buf, size_t len, bool realloc_if_needed) */ + simdjson_inline simdjson_result parse(const char *buf, size_t len, bool realloc_if_needed = true) & noexcept; + simdjson_inline simdjson_result parse(const char *buf, size_t len, bool realloc_if_needed = true) && =delete; + /** @overload parse(const uint8_t *buf, size_t len, bool realloc_if_needed) */ + simdjson_inline simdjson_result parse(const std::string &s) & noexcept; + simdjson_inline simdjson_result parse(const std::string &s) && =delete; + /** @overload parse(const uint8_t *buf, size_t len, bool realloc_if_needed) */ + simdjson_inline simdjson_result parse(const padded_string &s) & noexcept; + simdjson_inline simdjson_result parse(const padded_string &s) && =delete; + /** @overload parse(const uint8_t *buf, size_t len, bool realloc_if_needed) */ + simdjson_inline simdjson_result parse(const padded_string_view &v) & noexcept; + simdjson_inline simdjson_result parse(const padded_string_view &v) && =delete; + + /** @private We do not want to allow implicit conversion from C string to std::string. */ + simdjson_inline simdjson_result parse(const char *buf) noexcept = delete; + + /** + * Parse a JSON document into a provide document instance and return a temporary reference to it. + * It is similar to the function `parse` except that instead of parsing into the internal + * `document` instance associated with the parser, it allows the user to provide a document + * instance. + * + * dom::parser parser; + * dom::document doc; + * element doc_root = parser.parse_into_document(doc, buf, len); + * + * The function eagerly parses the input: the input can be modified and discarded after + * the `parser.parse(buf, len)` call has completed. + * + * ### IMPORTANT: Document Lifetime + * + * After the call to parse_into_document, the parser is no longer needed. + * + * The JSON document lives in the document instance: you must keep the document + * instance alive while you navigate through it (i.e., used the returned value from + * parse_into_document). You are encourage to reuse the document instance + * many times with new data to avoid reallocations: + * + * dom::document doc; + * element doc_root1 = parser.parse_into_document(doc, buf1, len); + * //... doc_root1 is a pointer inside doc + * element doc_root2 = parser.parse_into_document(doc, buf1, len); + * //... doc_root2 is a pointer inside doc + * // at this point doc_root1 is no longer safe + * + * Moving the document instance is safe, but it invalidates the element instances. After + * moving a document, you can recover safe access to the document root with its `root()` method. + * + * @param doc The document instance where the parsed data will be stored (on success). + * @param buf The JSON to parse. Must have at least len + SIMDJSON_PADDING allocated bytes, unless + * realloc_if_needed is true. + * @param len The length of the JSON. + * @param realloc_if_needed Whether to reallocate and enlarge the JSON buffer to add padding. + * @return An element pointing at the root of document, or an error: + * - MEMALLOC if realloc_if_needed is true or the parser does not have enough capacity, + * and memory allocation fails. + * - CAPACITY if the parser does not have enough capacity and len > max_capacity. + * - other json errors if parsing fails. You should not rely on these errors to always the same for the + * same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware). + */ + inline simdjson_result parse_into_document(document& doc, const uint8_t *buf, size_t len, bool realloc_if_needed = true) & noexcept; + inline simdjson_result parse_into_document(document& doc, const uint8_t *buf, size_t len, bool realloc_if_needed = true) && =delete; + /** @overload parse_into_document(const uint8_t *buf, size_t len, bool realloc_if_needed) */ + simdjson_inline simdjson_result parse_into_document(document& doc, const char *buf, size_t len, bool realloc_if_needed = true) & noexcept; + simdjson_inline simdjson_result parse_into_document(document& doc, const char *buf, size_t len, bool realloc_if_needed = true) && =delete; + /** @overload parse_into_document(const uint8_t *buf, size_t len, bool realloc_if_needed) */ + simdjson_inline simdjson_result parse_into_document(document& doc, const std::string &s) & noexcept; + simdjson_inline simdjson_result parse_into_document(document& doc, const std::string &s) && =delete; + /** @overload parse_into_document(const uint8_t *buf, size_t len, bool realloc_if_needed) */ + simdjson_inline simdjson_result parse_into_document(document& doc, const padded_string &s) & noexcept; + simdjson_inline simdjson_result parse_into_document(document& doc, const padded_string &s) && =delete; + + /** @private We do not want to allow implicit conversion from C string to std::string. */ + simdjson_inline simdjson_result parse_into_document(document& doc, const char *buf) noexcept = delete; + + /** + * Load a file containing many JSON documents. + * + * dom::parser parser; + * for (const element doc : parser.load_many(path)) { + * cout << std::string(doc["title"]) << endl; + * } + * + * The file is loaded in memory and can be safely deleted after the `parser.load_many(path)` + * function has returned. The memory is held by the `parser` instance. + * + * The function is lazy: it may be that no more than one JSON document at a time is parsed. + * And, possibly, no document many have been parsed when the `parser.load_many(path)` function + * returned. + * + * If there is a UTF-8 BOM, the parser skips it. + * + * ### Format + * + * The file must contain a series of one or more JSON documents, concatenated into a single + * buffer, separated by whitespace. It effectively parses until it has a fully valid document, + * then starts parsing the next document at that point. (It does this with more parallelism and + * lookahead than you might think, though.) + * + * Documents that consist of an object or array may omit the whitespace between them, concatenating + * with no separator. documents that consist of a single primitive (i.e. documents that are not + * arrays or objects) MUST be separated with whitespace. + * + * The documents must not exceed batch_size bytes (by default 1MB) or they will fail to parse. + * Setting batch_size to excessively large or excesively small values may impact negatively the + * performance. + * + * ### Error Handling + * + * All errors are returned during iteration: if there is a global error such as memory allocation, + * it will be yielded as the first result. Iteration always stops after the first error. + * + * As with all other simdjson methods, non-exception error handling is readily available through + * the same interface, requiring you to check the error before using the document: + * + * dom::parser parser; + * dom::document_stream docs; + * auto error = parser.load_many(path).get(docs); + * if (error) { cerr << error << endl; exit(1); } + * for (auto doc : docs) { + * std::string_view title; + * if ((error = doc["title"].get(title)) { cerr << error << endl; exit(1); } + * cout << title << endl; + * } + * + * ### Threads + * + * When compiled with SIMDJSON_THREADS_ENABLED, this method will use a single thread under the + * hood to do some lookahead. + * + * ### Parser Capacity + * + * If the parser's current capacity is less than batch_size, it will allocate enough capacity + * to handle it (up to max_capacity). + * + * @param path File name pointing at the concatenated JSON to parse. + * @param batch_size The batch size to use. MUST be larger than the largest document. The sweet + * spot is cache-related: small enough to fit in cache, yet big enough to + * parse as many documents as possible in one tight loop. + * Defaults to 1MB (as simdjson::dom::DEFAULT_BATCH_SIZE), which has been a reasonable sweet + * spot in our tests. + * If you set the batch_size to a value smaller than simdjson::dom::MINIMAL_BATCH_SIZE + * (currently 32B), it will be replaced by simdjson::dom::MINIMAL_BATCH_SIZE. + * @return The stream, or an error. An empty input will yield 0 documents rather than an EMPTY error. Errors: + * - IO_ERROR if there was an error opening or reading the file. + * - MEMALLOC if the parser does not have enough capacity and memory allocation fails. + * - CAPACITY if the parser does not have enough capacity and batch_size > max_capacity. + * - other json errors if parsing fails. You should not rely on these errors to always the same for the + * same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware). + */ + inline simdjson_result load_many(const std::string &path, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept; + + /** + * Parse a buffer containing many JSON documents. + * + * dom::parser parser; + * for (element doc : parser.parse_many(buf, len)) { + * cout << std::string(doc["title"]) << endl; + * } + * + * No copy of the input buffer is made. + * + * The function is lazy: it may be that no more than one JSON document at a time is parsed. + * And, possibly, no document many have been parsed when the `parser.load_many(path)` function + * returned. + * + * The caller is responsabile to ensure that the input string data remains unchanged and is + * not deleted during the loop. In particular, the following is unsafe and will not compile: + * + * auto docs = parser.parse_many("[\"temporary data\"]"_padded); + * // here the string "[\"temporary data\"]" may no longer exist in memory + * // the parser instance may not have even accessed the input yet + * for (element doc : docs) { + * cout << std::string(doc["title"]) << endl; + * } + * + * The following is safe: + * + * auto json = "[\"temporary data\"]"_padded; + * auto docs = parser.parse_many(json); + * for (element doc : docs) { + * cout << std::string(doc["title"]) << endl; + * } + * + * If there is a UTF-8 BOM, the parser skips it. + * + * ### Format + * + * The buffer must contain a series of one or more JSON documents, concatenated into a single + * buffer, separated by whitespace. It effectively parses until it has a fully valid document, + * then starts parsing the next document at that point. (It does this with more parallelism and + * lookahead than you might think, though.) + * + * documents that consist of an object or array may omit the whitespace between them, concatenating + * with no separator. documents that consist of a single primitive (i.e. documents that are not + * arrays or objects) MUST be separated with whitespace. + * + * The documents must not exceed batch_size bytes (by default 1MB) or they will fail to parse. + * Setting batch_size to excessively large or excesively small values may impact negatively the + * performance. + * + * ### Error Handling + * + * All errors are returned during iteration: if there is a global error such as memory allocation, + * it will be yielded as the first result. Iteration always stops after the first error. + * + * As with all other simdjson methods, non-exception error handling is readily available through + * the same interface, requiring you to check the error before using the document: + * + * dom::parser parser; + * dom::document_stream docs; + * auto error = parser.load_many(path).get(docs); + * if (error) { cerr << error << endl; exit(1); } + * for (auto doc : docs) { + * std::string_view title; + * if ((error = doc["title"].get(title)) { cerr << error << endl; exit(1); } + * cout << title << endl; + * } + * + * ### REQUIRED: Buffer Padding + * + * The buffer must have at least SIMDJSON_PADDING extra allocated bytes. It does not matter what + * those bytes are initialized to, as long as they are allocated. These bytes will be read: if you + * using a sanitizer that verifies that no uninitialized byte is read, then you should initialize the + * SIMDJSON_PADDING bytes to avoid runtime warnings. + * + * ### Threads + * + * When compiled with SIMDJSON_THREADS_ENABLED, this method will use a single thread under the + * hood to do some lookahead. + * + * ### Parser Capacity + * + * If the parser's current capacity is less than batch_size, it will allocate enough capacity + * to handle it (up to max_capacity). + * + * @param buf The concatenated JSON to parse. Must have at least len + SIMDJSON_PADDING allocated bytes. + * @param len The length of the concatenated JSON. + * @param batch_size The batch size to use. MUST be larger than the largest document. The sweet + * spot is cache-related: small enough to fit in cache, yet big enough to + * parse as many documents as possible in one tight loop. + * Defaults to 10MB, which has been a reasonable sweet spot in our tests. + * @return The stream, or an error. An empty input will yield 0 documents rather than an EMPTY error. Errors: + * - MEMALLOC if the parser does not have enough capacity and memory allocation fails + * - CAPACITY if the parser does not have enough capacity and batch_size > max_capacity. + * - other json errors if parsing fails. You should not rely on these errors to always the same for the + * same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware). + */ + inline simdjson_result parse_many(const uint8_t *buf, size_t len, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept; + /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */ + inline simdjson_result parse_many(const char *buf, size_t len, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept; + /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */ + inline simdjson_result parse_many(const std::string &s, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept; + inline simdjson_result parse_many(const std::string &&s, size_t batch_size) = delete;// unsafe + /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */ + inline simdjson_result parse_many(const padded_string &s, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept; + inline simdjson_result parse_many(const padded_string &&s, size_t batch_size) = delete;// unsafe + + /** @private We do not want to allow implicit conversion from C string to std::string. */ + simdjson_result parse_many(const char *buf, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept = delete; + + /** + * Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length + * and `max_depth` depth. + * + * @param capacity The new capacity. + * @param max_depth The new max_depth. Defaults to DEFAULT_MAX_DEPTH. + * @return The error, if there is one. + */ + simdjson_warn_unused inline error_code allocate(size_t capacity, size_t max_depth = DEFAULT_MAX_DEPTH) noexcept; + +#ifndef SIMDJSON_DISABLE_DEPRECATED_API + /** + * @private deprecated because it returns bool instead of error_code, which is our standard for + * failures. Use allocate() instead. + * + * Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length + * and `max_depth` depth. + * + * @param capacity The new capacity. + * @param max_depth The new max_depth. Defaults to DEFAULT_MAX_DEPTH. + * @return true if successful, false if allocation failed. + */ + [[deprecated("Use allocate() instead.")]] + simdjson_warn_unused inline bool allocate_capacity(size_t capacity, size_t max_depth = DEFAULT_MAX_DEPTH) noexcept; +#endif // SIMDJSON_DISABLE_DEPRECATED_API + /** + * The largest document this parser can support without reallocating. + * + * @return Current capacity, in bytes. + */ + simdjson_inline size_t capacity() const noexcept; + + /** + * The largest document this parser can automatically support. + * + * The parser may reallocate internal buffers as needed up to this amount. + * + * @return Maximum capacity, in bytes. + */ + simdjson_inline size_t max_capacity() const noexcept; + + /** + * The maximum level of nested object and arrays supported by this parser. + * + * @return Maximum depth, in bytes. + */ + simdjson_pure simdjson_inline size_t max_depth() const noexcept; + + /** + * Set max_capacity. This is the largest document this parser can automatically support. + * + * The parser may reallocate internal buffers as needed up to this amount as documents are passed + * to it. + * + * Note: To avoid limiting the memory to an absurd value, such as zero or two bytes, + * iff you try to set max_capacity to a value lower than MINIMAL_DOCUMENT_CAPACITY, + * then the maximal capacity is set to MINIMAL_DOCUMENT_CAPACITY. + * + * This call will not allocate or deallocate, even if capacity is currently above max_capacity. + * + * @param max_capacity The new maximum capacity, in bytes. + */ + simdjson_inline void set_max_capacity(size_t max_capacity) noexcept; + +#ifdef SIMDJSON_THREADS_ENABLED + /** + * The parser instance can use threads when they are available to speed up some + * operations. It is enabled by default. Changing this attribute will change the + * behavior of the parser for future operations. Set to true by default. + */ + bool threaded{true}; +#else + /** + * When SIMDJSON_THREADS_ENABLED is not defined, the parser instance cannot use threads. + */ + bool threaded{false}; +#endif + /** @private Use the new DOM API instead */ + class Iterator; + /** @private Use simdjson_error instead */ + using InvalidJSON [[deprecated("Use simdjson_error instead")]] = simdjson_error; + + /** @private [for benchmarking access] The implementation to use */ + std::unique_ptr implementation{}; + + /** @private Use `if (parser.parse(...).error())` instead */ + bool valid{false}; + /** @private Use `parser.parse(...).error()` instead */ + error_code error{UNINITIALIZED}; + + /** @private Use `parser.parse(...).value()` instead */ + document doc{}; + + /** @private returns true if the document parsed was valid */ + [[deprecated("Use the result of parser.parse() instead")]] + inline bool is_valid() const noexcept; + + /** + * @private return an error code corresponding to the last parsing attempt, see + * simdjson.h will return UNINITIALIZED if no parsing was attempted + */ + [[deprecated("Use the result of parser.parse() instead")]] + inline int get_error_code() const noexcept; + + /** @private return the string equivalent of "get_error_code" */ + [[deprecated("Use error_message() on the result of parser.parse() instead, or cout << error")]] + inline std::string get_error_message() const noexcept; + + /** @private */ + [[deprecated("Use cout << on the result of parser.parse() instead")]] + inline bool print_json(std::ostream &os) const noexcept; + + /** @private Private and deprecated: use `parser.parse(...).doc.dump_raw_tape()` instead */ + inline bool dump_raw_tape(std::ostream &os) const noexcept; + + +private: + /** + * The maximum document length this parser will automatically support. + * + * The parser will not be automatically allocated above this amount. + */ + size_t _max_capacity; + + /** + * The loaded buffer (reused each time load() is called) + */ + std::unique_ptr loaded_bytes; + + /** Capacity of loaded_bytes buffer. */ + size_t _loaded_bytes_capacity{0}; + + // all nodes are stored on the doc.tape using a 64-bit word. + // + // strings, double and ints are stored as + // a 64-bit word with a pointer to the actual value + // + // + // + // for objects or arrays, store [ or { at the beginning and } and ] at the + // end. For the openings ([ or {), we annotate them with a reference to the + // location on the doc.tape of the end, and for then closings (} and ]), we + // annotate them with a reference to the location of the opening + // + // + + /** + * Ensure we have enough capacity to handle at least desired_capacity bytes, + * and auto-allocate if not. This also allocates memory if needed in the + * internal document. + */ + inline error_code ensure_capacity(size_t desired_capacity) noexcept; + /** + * Ensure we have enough capacity to handle at least desired_capacity bytes, + * and auto-allocate if not. This also allocates memory if needed in the + * provided document. + */ + inline error_code ensure_capacity(document& doc, size_t desired_capacity) noexcept; + + /** Read the file into loaded_bytes */ + inline simdjson_result read_file(const std::string &path) noexcept; + + friend class parser::Iterator; + friend class document_stream; + + +}; // class parser + +} // namespace dom +} // namespace simdjson + +#endif // SIMDJSON_DOM_PARSER_H diff --git a/contrib/libs/simdjson/include/simdjson/dom/serialization-inl.h b/contrib/libs/simdjson/include/simdjson/dom/serialization-inl.h new file mode 100644 index 000000000000..0c52a26cb1c4 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/dom/serialization-inl.h @@ -0,0 +1,536 @@ + +#ifndef SIMDJSON_SERIALIZATION_INL_H +#define SIMDJSON_SERIALIZATION_INL_H + +#include "simdjson/dom/base.h" +#include "simdjson/dom/serialization.h" +#include "simdjson/dom/parser.h" +#include "simdjson/internal/tape_type.h" + +#include "simdjson/dom/array-inl.h" +#include "simdjson/dom/object-inl.h" +#include "simdjson/internal/tape_ref-inl.h" + +#include + +namespace simdjson { +namespace dom { +inline bool parser::print_json(std::ostream &os) const noexcept { + if (!valid) { return false; } + simdjson::internal::string_builder<> sb; + sb.append(doc.root()); + std::string_view answer = sb.str(); + os << answer; + return true; +} + +inline std::ostream& operator<<(std::ostream& out, simdjson::dom::element value) { + simdjson::internal::string_builder<> sb; + sb.append(value); + return (out << sb.str()); +} +#if SIMDJSON_EXCEPTIONS +inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x) { + if (x.error()) { throw simdjson::simdjson_error(x.error()); } + return (out << x.value()); +} +#endif +inline std::ostream& operator<<(std::ostream& out, simdjson::dom::array value) { + simdjson::internal::string_builder<> sb; + sb.append(value); + return (out << sb.str()); +} +#if SIMDJSON_EXCEPTIONS +inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x) { + if (x.error()) { throw simdjson::simdjson_error(x.error()); } + return (out << x.value()); +} +#endif +inline std::ostream& operator<<(std::ostream& out, simdjson::dom::object value) { + simdjson::internal::string_builder<> sb; + sb.append(value); + return (out << sb.str()); +} +#if SIMDJSON_EXCEPTIONS +inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x) { + if (x.error()) { throw simdjson::simdjson_error(x.error()); } + return (out << x.value()); +} +#endif + +} // namespace dom + +/*** + * Number utility functions + **/ +namespace { +/**@private + * Escape sequence like \b or \u0001 + * We expect that most compilers will use 8 bytes for this data structure. + **/ +struct escape_sequence { + uint8_t length; + const char string[7]; // technically, we only ever need 6 characters, we pad to 8 +}; +/**@private + * This converts a signed integer into a character sequence. + * The caller is responsible for providing enough memory (at least + * 20 characters.) + * Though various runtime libraries provide itoa functions, + * it is not part of the C++ standard. The C++17 standard + * adds the to_chars functions which would do as well, but + * we want to support C++11. + */ +static char *fast_itoa(char *output, int64_t value) noexcept { + // This is a standard implementation of itoa. + char buffer[20]; + uint64_t value_positive; + // In general, negating a signed integer is unsafe. + if(value < 0) { + *output++ = '-'; + // Doing value_positive = -value; while avoiding + // undefined behavior warnings. + // It assumes two complement's which is universal at this + // point in time. + std::memcpy(&value_positive, &value, sizeof(value)); + value_positive = (~value_positive) + 1; // this is a negation + } else { + value_positive = value; + } + // We work solely with value_positive. It *might* be easier + // for an optimizing compiler to deal with an unsigned variable + // as far as performance goes. + const char *const end_buffer = buffer + 20; + char *write_pointer = buffer + 19; + // A faster approach is possible if we expect large integers: + // unroll the loop (work in 100s, 1000s) and use some kind of + // memoization. + while(value_positive >= 10) { + *write_pointer-- = char('0' + (value_positive % 10)); + value_positive /= 10; + } + *write_pointer = char('0' + value_positive); + size_t len = end_buffer - write_pointer; + std::memcpy(output, write_pointer, len); + return output + len; +} +/**@private + * This converts an unsigned integer into a character sequence. + * The caller is responsible for providing enough memory (at least + * 19 characters.) + * Though various runtime libraries provide itoa functions, + * it is not part of the C++ standard. The C++17 standard + * adds the to_chars functions which would do as well, but + * we want to support C++11. + */ +static char *fast_itoa(char *output, uint64_t value) noexcept { + // This is a standard implementation of itoa. + char buffer[20]; + const char *const end_buffer = buffer + 20; + char *write_pointer = buffer + 19; + // A faster approach is possible if we expect large integers: + // unroll the loop (work in 100s, 1000s) and use some kind of + // memoization. + while(value >= 10) { + *write_pointer-- = char('0' + (value % 10)); + value /= 10; + }; + *write_pointer = char('0' + value); + size_t len = end_buffer - write_pointer; + std::memcpy(output, write_pointer, len); + return output + len; +} + + +} // anonymous namespace +namespace internal { + +/*** + * Minifier/formatter code. + **/ + +template +simdjson_inline void base_formatter::number(uint64_t x) { + char number_buffer[24]; + char *newp = fast_itoa(number_buffer, x); + buffer.insert(buffer.end(), number_buffer, newp); +} + +template +simdjson_inline void base_formatter::number(int64_t x) { + char number_buffer[24]; + char *newp = fast_itoa(number_buffer, x); + buffer.insert(buffer.end(), number_buffer, newp); +} + +template +simdjson_inline void base_formatter::number(double x) { + char number_buffer[24]; + // Currently, passing the nullptr to the second argument is + // safe because our implementation does not check the second + // argument. + char *newp = internal::to_chars(number_buffer, nullptr, x); + buffer.insert(buffer.end(), number_buffer, newp); +} + +template +simdjson_inline void base_formatter::start_array() { one_char('['); } + + +template +simdjson_inline void base_formatter::end_array() { one_char(']'); } + +template +simdjson_inline void base_formatter::start_object() { one_char('{'); } + +template +simdjson_inline void base_formatter::end_object() { one_char('}'); } + +template +simdjson_inline void base_formatter::comma() { one_char(','); } + +template +simdjson_inline void base_formatter::true_atom() { + const char * s = "true"; + buffer.insert(buffer.end(), s, s + 4); +} + +template +simdjson_inline void base_formatter::false_atom() { + const char * s = "false"; + buffer.insert(buffer.end(), s, s + 5); +} + +template +simdjson_inline void base_formatter::null_atom() { + const char * s = "null"; + buffer.insert(buffer.end(), s, s + 4); +} + +template +simdjson_inline void base_formatter::one_char(char c) { buffer.push_back(c); } + +template +simdjson_inline void base_formatter::key(std::string_view unescaped) { + string(unescaped); + one_char(':'); +} + +template +simdjson_inline void base_formatter::string(std::string_view unescaped) { + one_char('\"'); + size_t i = 0; + // Fast path for the case where we have no control character, no ", and no backslash. + // This should include most keys. + // + // We would like to use 'bool' but some compilers take offense to bitwise operation + // with bool types. + constexpr static char needs_escaping[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + for(;i + 8 <= unescaped.length(); i += 8) { + // Poor's man vectorization. This could get much faster if we used SIMD. + // + // It is not the case that replacing '|' with '||' would be neutral performance-wise. + if(needs_escaping[uint8_t(unescaped[i])] | needs_escaping[uint8_t(unescaped[i+1])] + | needs_escaping[uint8_t(unescaped[i+2])] | needs_escaping[uint8_t(unescaped[i+3])] + | needs_escaping[uint8_t(unescaped[i+4])] | needs_escaping[uint8_t(unescaped[i+5])] + | needs_escaping[uint8_t(unescaped[i+6])] | needs_escaping[uint8_t(unescaped[i+7])] + ) { break; } + } + for(;i < unescaped.length(); i++) { + if(needs_escaping[uint8_t(unescaped[i])]) { break; } + } + // The following is also possible and omits a 256-byte table, but it is slower: + // for (; (i < unescaped.length()) && (uint8_t(unescaped[i]) > 0x1F) + // && (unescaped[i] != '\"') && (unescaped[i] != '\\'); i++) {} + + // At least for long strings, the following should be fast. We could + // do better by integrating the checks and the insertion. + buffer.insert(buffer.end(), unescaped.data(), unescaped.data() + i); + // We caught a control character if we enter this loop (slow). + // Note that we are do not restart from the beginning, but rather we continue + // from the point where we encountered something that requires escaping. + for (; i < unescaped.length(); i++) { + switch (unescaped[i]) { + case '\"': + { + const char * s = "\\\""; + buffer.insert(buffer.end(), s, s + 2); + } + break; + case '\\': + { + const char * s = "\\\\"; + buffer.insert(buffer.end(), s, s + 2); + } + break; + default: + if (uint8_t(unescaped[i]) <= 0x1F) { + // If packed, this uses 8 * 32 bytes. + // Note that we expect most compilers to embed this code in the data + // section. + constexpr static escape_sequence escaped[32] = { + {6, "\\u0000"}, {6, "\\u0001"}, {6, "\\u0002"}, {6, "\\u0003"}, + {6, "\\u0004"}, {6, "\\u0005"}, {6, "\\u0006"}, {6, "\\u0007"}, + {2, "\\b"}, {2, "\\t"}, {2, "\\n"}, {6, "\\u000b"}, + {2, "\\f"}, {2, "\\r"}, {6, "\\u000e"}, {6, "\\u000f"}, + {6, "\\u0010"}, {6, "\\u0011"}, {6, "\\u0012"}, {6, "\\u0013"}, + {6, "\\u0014"}, {6, "\\u0015"}, {6, "\\u0016"}, {6, "\\u0017"}, + {6, "\\u0018"}, {6, "\\u0019"}, {6, "\\u001a"}, {6, "\\u001b"}, + {6, "\\u001c"}, {6, "\\u001d"}, {6, "\\u001e"}, {6, "\\u001f"}}; + auto u = escaped[uint8_t(unescaped[i])]; + buffer.insert(buffer.end(), u.string, u.string + u.length); + } else { + one_char(unescaped[i]); + } + } // switch + } // for + one_char('\"'); +} + + +template +inline void base_formatter::clear() { + buffer.clear(); +} + +template +simdjson_inline std::string_view base_formatter::str() const { + return std::string_view(buffer.data(), buffer.size()); +} + +simdjson_inline void mini_formatter::print_newline() { + return; +} + +simdjson_inline void mini_formatter::print_indents(size_t depth) { + (void)depth; + return; +} + +simdjson_inline void mini_formatter::print_space() { + return; +} + +simdjson_inline void pretty_formatter::print_newline() { + one_char('\n'); +} + +simdjson_inline void pretty_formatter::print_indents(size_t depth) { + if(this->indent_step <= 0) { + return; + } + for(size_t i = 0; i < this->indent_step * depth; i++) { + one_char(' '); + } +} + +simdjson_inline void pretty_formatter::print_space() { + one_char(' '); +} + +/*** + * String building code. + **/ + +template +inline void string_builder::append(simdjson::dom::element value) { + // using tape_type = simdjson::internal::tape_type; + size_t depth = 0; + constexpr size_t MAX_DEPTH = 16; + bool is_object[MAX_DEPTH]; + is_object[0] = false; + bool after_value = false; + + internal::tape_ref iter(value.tape); + do { + // print commas after each value + if (after_value) { + format.comma(); + format.print_newline(); + } + + format.print_indents(depth); + + // If we are in an object, print the next key and :, and skip to the next + // value. + if (is_object[depth]) { + format.key(iter.get_string_view()); + format.print_space(); + iter.json_index++; + } + switch (iter.tape_ref_type()) { + + // Arrays + case tape_type::START_ARRAY: { + // If we're too deep, we need to recurse to go deeper. + depth++; + if (simdjson_unlikely(depth >= MAX_DEPTH)) { + append(simdjson::dom::array(iter)); + iter.json_index = iter.matching_brace_index() - 1; // Jump to the ] + depth--; + break; + } + + // Output start [ + format.start_array(); + iter.json_index++; + + // Handle empty [] (we don't want to come back around and print commas) + if (iter.tape_ref_type() == tape_type::END_ARRAY) { + format.end_array(); + depth--; + break; + } + + is_object[depth] = false; + after_value = false; + format.print_newline(); + continue; + } + + // Objects + case tape_type::START_OBJECT: { + // If we're too deep, we need to recurse to go deeper. + depth++; + if (simdjson_unlikely(depth >= MAX_DEPTH)) { + append(simdjson::dom::object(iter)); + iter.json_index = iter.matching_brace_index() - 1; // Jump to the } + depth--; + break; + } + + // Output start { + format.start_object(); + iter.json_index++; + + // Handle empty {} (we don't want to come back around and print commas) + if (iter.tape_ref_type() == tape_type::END_OBJECT) { + format.end_object(); + depth--; + break; + } + + is_object[depth] = true; + after_value = false; + format.print_newline(); + continue; + } + + // Scalars + case tape_type::STRING: + format.string(iter.get_string_view()); + break; + case tape_type::INT64: + format.number(iter.next_tape_value()); + iter.json_index++; // numbers take up 2 spots, so we need to increment + // extra + break; + case tape_type::UINT64: + format.number(iter.next_tape_value()); + iter.json_index++; // numbers take up 2 spots, so we need to increment + // extra + break; + case tape_type::DOUBLE: + format.number(iter.next_tape_value()); + iter.json_index++; // numbers take up 2 spots, so we need to increment + // extra + break; + case tape_type::TRUE_VALUE: + format.true_atom(); + break; + case tape_type::FALSE_VALUE: + format.false_atom(); + break; + case tape_type::NULL_VALUE: + format.null_atom(); + break; + + // These are impossible + case tape_type::END_ARRAY: + case tape_type::END_OBJECT: + case tape_type::ROOT: + SIMDJSON_UNREACHABLE(); + } + iter.json_index++; + after_value = true; + + // Handle multiple ends in a row + while (depth != 0 && (iter.tape_ref_type() == tape_type::END_ARRAY || + iter.tape_ref_type() == tape_type::END_OBJECT)) { + format.print_newline(); + depth--; + format.print_indents(depth); + if (iter.tape_ref_type() == tape_type::END_ARRAY) { + format.end_array(); + } else { + format.end_object(); + } + iter.json_index++; + } + + // Stop when we're at depth 0 + } while (depth != 0); + + format.print_newline(); +} + +template +inline void string_builder::append(simdjson::dom::object value) { + format.start_object(); + auto pair = value.begin(); + auto end = value.end(); + if (pair != end) { + append(*pair); + for (++pair; pair != end; ++pair) { + format.comma(); + append(*pair); + } + } + format.end_object(); +} + +template +inline void string_builder::append(simdjson::dom::array value) { + format.start_array(); + auto iter = value.begin(); + auto end = value.end(); + if (iter != end) { + append(*iter); + for (++iter; iter != end; ++iter) { + format.comma(); + append(*iter); + } + } + format.end_array(); +} + +template +simdjson_inline void string_builder::append(simdjson::dom::key_value_pair kv) { + format.key(kv.key); + append(kv.value); +} + +template +simdjson_inline void string_builder::clear() { + format.clear(); +} + +template +simdjson_inline std::string_view string_builder::str() const { + return format.str(); +} + + +} // namespace internal +} // namespace simdjson + +#endif diff --git a/contrib/libs/simdjson/include/simdjson/dom/serialization.h b/contrib/libs/simdjson/include/simdjson/dom/serialization.h new file mode 100644 index 000000000000..87c735bbbd81 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/dom/serialization.h @@ -0,0 +1,260 @@ +#ifndef SIMDJSON_SERIALIZATION_H +#define SIMDJSON_SERIALIZATION_H + +#include "simdjson/dom/base.h" +#include "simdjson/dom/element.h" +#include "simdjson/dom/object.h" + +#include + +namespace simdjson { + +/** + * The string_builder template and mini_formatter class + * are not part of our public API and are subject to change + * at any time! + */ +namespace internal { + +template +class base_formatter { +public: + /** Add a comma **/ + simdjson_inline void comma(); + /** Start an array, prints [ **/ + simdjson_inline void start_array(); + /** End an array, prints ] **/ + simdjson_inline void end_array(); + /** Start an array, prints { **/ + simdjson_inline void start_object(); + /** Start an array, prints } **/ + simdjson_inline void end_object(); + /** Prints a true **/ + simdjson_inline void true_atom(); + /** Prints a false **/ + simdjson_inline void false_atom(); + /** Prints a null **/ + simdjson_inline void null_atom(); + /** Prints a number **/ + simdjson_inline void number(int64_t x); + /** Prints a number **/ + simdjson_inline void number(uint64_t x); + /** Prints a number **/ + simdjson_inline void number(double x); + /** Prints a key (string + colon) **/ + simdjson_inline void key(std::string_view unescaped); + /** Prints a string. The string is escaped as needed. **/ + simdjson_inline void string(std::string_view unescaped); + /** Clears out the content. **/ + simdjson_inline void clear(); + /** + * Get access to the buffer, it is owned by the instance, but + * the user can make a copy. + **/ + simdjson_inline std::string_view str() const; + + /** Prints one character **/ + simdjson_inline void one_char(char c); + + simdjson_inline void call_print_newline() { + static_cast(this)->print_newline(); + } + + simdjson_inline void call_print_indents(size_t depth) { + static_cast(this)->print_indents(depth); + } + + simdjson_inline void call_print_space() { + static_cast(this)->print_space(); + } + +protected: + // implementation details (subject to change) + /** Backing buffer **/ + std::vector buffer{}; // not ideal! +}; + + +/** + * @private This is the class that we expect to use with the string_builder + * template. It tries to produce a compact version of the JSON element + * as quickly as possible. + */ +class mini_formatter : public base_formatter { +public: + simdjson_inline void print_newline(); + + simdjson_inline void print_indents(size_t depth); + + simdjson_inline void print_space(); +}; + +class pretty_formatter : public base_formatter { +public: + simdjson_inline void print_newline(); + + simdjson_inline void print_indents(size_t depth); + + simdjson_inline void print_space(); + +protected: + int indent_step = 4; +}; + +/** + * @private The string_builder template allows us to construct + * a string from a document element. It is parametrized + * by a "formatter" which handles the details. Thus + * the string_builder template could support both minification + * and prettification, and various other tradeoffs. + */ +template +class string_builder { +public: + /** Construct an initially empty builder, would print the empty string **/ + string_builder() = default; + /** Append an element to the builder (to be printed) **/ + inline void append(simdjson::dom::element value); + /** Append an array to the builder (to be printed) **/ + inline void append(simdjson::dom::array value); + /** Append an object to the builder (to be printed) **/ + inline void append(simdjson::dom::object value); + /** Reset the builder (so that it would print the empty string) **/ + simdjson_inline void clear(); + /** + * Get access to the string. The string_view is owned by the builder + * and it is invalid to use it after the string_builder has been + * destroyed. + * However you can make a copy of the string_view on memory that you + * own. + */ + simdjson_inline std::string_view str() const; + /** Append a key_value_pair to the builder (to be printed) **/ + simdjson_inline void append(simdjson::dom::key_value_pair value); +private: + formatter format{}; +}; + +} // internal + +namespace dom { + +/** + * Print JSON to an output stream. + * + * @param out The output stream. + * @param value The element. + * @throw if there is an error with the underlying output stream. simdjson itself will not throw. + */ +inline std::ostream& operator<<(std::ostream& out, simdjson::dom::element value); +#if SIMDJSON_EXCEPTIONS +inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x); +#endif +/** + * Print JSON to an output stream. + * + * @param out The output stream. + * @param value The array. + * @throw if there is an error with the underlying output stream. simdjson itself will not throw. + */ +inline std::ostream& operator<<(std::ostream& out, simdjson::dom::array value); +#if SIMDJSON_EXCEPTIONS +inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x); +#endif +/** + * Print JSON to an output stream. + * + * @param out The output stream. + * @param value The object. + * @throw if there is an error with the underlying output stream. simdjson itself will not throw. + */ +inline std::ostream& operator<<(std::ostream& out, simdjson::dom::object value); +#if SIMDJSON_EXCEPTIONS +inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x); +#endif +} // namespace dom + +/** + * Converts JSON to a string. + * + * dom::parser parser; + * element doc = parser.parse(" [ 1 , 2 , 3 ] "_padded); + * cout << to_string(doc) << endl; // prints [1,2,3] + * + */ +template +std::string to_string(T x) { + // in C++, to_string is standard: http://www.cplusplus.com/reference/string/to_string/ + // Currently minify and to_string are identical but in the future, they may + // differ. + simdjson::internal::string_builder<> sb; + sb.append(x); + std::string_view answer = sb.str(); + return std::string(answer.data(), answer.size()); +} +#if SIMDJSON_EXCEPTIONS +template +std::string to_string(simdjson_result x) { + if (x.error()) { throw simdjson_error(x.error()); } + return to_string(x.value()); +} +#endif + +/** + * Minifies a JSON element or document, printing the smallest possible valid JSON. + * + * dom::parser parser; + * element doc = parser.parse(" [ 1 , 2 , 3 ] "_padded); + * cout << minify(doc) << endl; // prints [1,2,3] + * + */ +template +std::string minify(T x) { + return to_string(x); +} + +#if SIMDJSON_EXCEPTIONS +template +std::string minify(simdjson_result x) { + if (x.error()) { throw simdjson_error(x.error()); } + return to_string(x.value()); +} +#endif + +/** + * Prettifies a JSON element or document, printing the valid JSON with indentation. + * + * dom::parser parser; + * element doc = parser.parse(" [ 1 , 2 , 3 ] "_padded); + * + * // Prints: + * // { + * // [ + * // 1, + * // 2, + * // 3 + * // ] + * // } + * cout << prettify(doc) << endl; + * + */ +template +std::string prettify(T x) { + simdjson::internal::string_builder sb; + sb.append(x); + std::string_view answer = sb.str(); + return std::string(answer.data(), answer.size()); +} + +#if SIMDJSON_EXCEPTIONS +template +std::string prettify(simdjson_result x) { + if (x.error()) { throw simdjson_error(x.error()); } + return to_string(x.value()); +} +#endif + +} // namespace simdjson + + +#endif diff --git a/contrib/libs/simdjson/include/simdjson/error-inl.h b/contrib/libs/simdjson/include/simdjson/error-inl.h new file mode 100644 index 000000000000..5cf61d9c3076 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/error-inl.h @@ -0,0 +1,184 @@ +#ifndef SIMDJSON_ERROR_INL_H +#define SIMDJSON_ERROR_INL_H + +#include "simdjson/error.h" + +#include + +namespace simdjson { +namespace internal { + // We store the error code so we can validate the error message is associated with the right code + struct error_code_info { + error_code code; + const char* message; // do not use a fancy std::string where a simple C string will do (no alloc, no destructor) + }; + // These MUST match the codes in error_code. We check this constraint in basictests. + extern SIMDJSON_DLLIMPORTEXPORT const error_code_info error_codes[]; +} // namespace internal + + +inline const char *error_message(error_code error) noexcept { + // If you're using error_code, we're trusting you got it from the enum. + return internal::error_codes[int(error)].message; +} + +// deprecated function +#ifndef SIMDJSON_DISABLE_DEPRECATED_API +inline const std::string error_message(int error) noexcept { + if (error < 0 || error >= error_code::NUM_ERROR_CODES) { + return internal::error_codes[UNEXPECTED_ERROR].message; + } + return internal::error_codes[error].message; +} +#endif // SIMDJSON_DISABLE_DEPRECATED_API + +inline std::ostream& operator<<(std::ostream& out, error_code error) noexcept { + return out << error_message(error); +} + +namespace internal { + +// +// internal::simdjson_result_base inline implementation +// + +template +simdjson_inline void simdjson_result_base::tie(T &value, error_code &error) && noexcept { + error = this->second; + if (!error) { + value = std::forward>(*this).first; + } +} + +template +simdjson_warn_unused simdjson_inline error_code simdjson_result_base::get(T &value) && noexcept { + error_code error; + std::forward>(*this).tie(value, error); + return error; +} + +template +simdjson_inline error_code simdjson_result_base::error() const noexcept { + return this->second; +} + +#if SIMDJSON_EXCEPTIONS + +template +simdjson_inline T& simdjson_result_base::value() & noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return this->first; +} + +template +simdjson_inline T&& simdjson_result_base::value() && noexcept(false) { + return std::forward>(*this).take_value(); +} + +template +simdjson_inline T&& simdjson_result_base::take_value() && noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return std::forward(this->first); +} + +template +simdjson_inline simdjson_result_base::operator T&&() && noexcept(false) { + return std::forward>(*this).take_value(); +} + +#endif // SIMDJSON_EXCEPTIONS + +template +simdjson_inline const T& simdjson_result_base::value_unsafe() const& noexcept { + return this->first; +} + +template +simdjson_inline T&& simdjson_result_base::value_unsafe() && noexcept { + return std::forward(this->first); +} + +template +simdjson_inline simdjson_result_base::simdjson_result_base(T &&value, error_code error) noexcept + : std::pair(std::forward(value), error) {} +template +simdjson_inline simdjson_result_base::simdjson_result_base(error_code error) noexcept + : simdjson_result_base(T{}, error) {} +template +simdjson_inline simdjson_result_base::simdjson_result_base(T &&value) noexcept + : simdjson_result_base(std::forward(value), SUCCESS) {} +template +simdjson_inline simdjson_result_base::simdjson_result_base() noexcept + : simdjson_result_base(T{}, UNINITIALIZED) {} + +} // namespace internal + +/// +/// simdjson_result inline implementation +/// + +template +simdjson_inline void simdjson_result::tie(T &value, error_code &error) && noexcept { + std::forward>(*this).tie(value, error); +} + +template +simdjson_warn_unused simdjson_inline error_code simdjson_result::get(T &value) && noexcept { + return std::forward>(*this).get(value); +} + +template +simdjson_inline error_code simdjson_result::error() const noexcept { + return internal::simdjson_result_base::error(); +} + +#if SIMDJSON_EXCEPTIONS + +template +simdjson_inline T& simdjson_result::value() & noexcept(false) { + return internal::simdjson_result_base::value(); +} + +template +simdjson_inline T&& simdjson_result::value() && noexcept(false) { + return std::forward>(*this).value(); +} + +template +simdjson_inline T&& simdjson_result::take_value() && noexcept(false) { + return std::forward>(*this).take_value(); +} + +template +simdjson_inline simdjson_result::operator T&&() && noexcept(false) { + return std::forward>(*this).take_value(); +} + +#endif // SIMDJSON_EXCEPTIONS + +template +simdjson_inline const T& simdjson_result::value_unsafe() const& noexcept { + return internal::simdjson_result_base::value_unsafe(); +} + +template +simdjson_inline T&& simdjson_result::value_unsafe() && noexcept { + return std::forward>(*this).value_unsafe(); +} + +template +simdjson_inline simdjson_result::simdjson_result(T &&value, error_code error) noexcept + : internal::simdjson_result_base(std::forward(value), error) {} +template +simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept + : internal::simdjson_result_base(error) {} +template +simdjson_inline simdjson_result::simdjson_result(T &&value) noexcept + : internal::simdjson_result_base(std::forward(value)) {} +template +simdjson_inline simdjson_result::simdjson_result() noexcept + : internal::simdjson_result_base() {} + +} // namespace simdjson + +#endif // SIMDJSON_ERROR_INL_H diff --git a/contrib/libs/simdjson/include/simdjson/error.h b/contrib/libs/simdjson/include/simdjson/error.h new file mode 100644 index 000000000000..2f3443d9b6e1 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/error.h @@ -0,0 +1,318 @@ +#ifndef SIMDJSON_ERROR_H +#define SIMDJSON_ERROR_H + +#include "simdjson/base.h" + +#include +#include + +namespace simdjson { + +/** + * All possible errors returned by simdjson. These error codes are subject to change + * and not all simdjson kernel returns the same error code given the same input: it is not + * well defined which error a given input should produce. + * + * Only SUCCESS evaluates to false as a Boolean. All other error codes will evaluate + * to true as a Boolean. + */ +enum error_code { + SUCCESS = 0, ///< No error + CAPACITY, ///< This parser can't support a document that big + MEMALLOC, ///< Error allocating memory, most likely out of memory + TAPE_ERROR, ///< Something went wrong, this is a generic error + DEPTH_ERROR, ///< Your document exceeds the user-specified depth limitation + STRING_ERROR, ///< Problem while parsing a string + T_ATOM_ERROR, ///< Problem while parsing an atom starting with the letter 't' + F_ATOM_ERROR, ///< Problem while parsing an atom starting with the letter 'f' + N_ATOM_ERROR, ///< Problem while parsing an atom starting with the letter 'n' + NUMBER_ERROR, ///< Problem while parsing a number + BIGINT_ERROR, ///< The integer value exceeds 64 bits + UTF8_ERROR, ///< the input is not valid UTF-8 + UNINITIALIZED, ///< unknown error, or uninitialized document + EMPTY, ///< no structural element found + UNESCAPED_CHARS, ///< found unescaped characters in a string. + UNCLOSED_STRING, ///< missing quote at the end + UNSUPPORTED_ARCHITECTURE, ///< unsupported architecture + INCORRECT_TYPE, ///< JSON element has a different type than user expected + NUMBER_OUT_OF_RANGE, ///< JSON number does not fit in 64 bits + INDEX_OUT_OF_BOUNDS, ///< JSON array index too large + NO_SUCH_FIELD, ///< JSON field not found in object + IO_ERROR, ///< Error reading a file + INVALID_JSON_POINTER, ///< Invalid JSON pointer syntax + INVALID_URI_FRAGMENT, ///< Invalid URI fragment + UNEXPECTED_ERROR, ///< indicative of a bug in simdjson + PARSER_IN_USE, ///< parser is already in use. + OUT_OF_ORDER_ITERATION, ///< tried to iterate an array or object out of order (checked when SIMDJSON_DEVELOPMENT_CHECKS=1) + INSUFFICIENT_PADDING, ///< The JSON doesn't have enough padding for simdjson to safely parse it. + INCOMPLETE_ARRAY_OR_OBJECT, ///< The document ends early. + SCALAR_DOCUMENT_AS_VALUE, ///< A scalar document is treated as a value. + OUT_OF_BOUNDS, ///< Attempted to access location outside of document. + TRAILING_CONTENT, ///< Unexpected trailing content in the JSON input + NUM_ERROR_CODES +}; + +/** + * It is the convention throughout the code that the macro SIMDJSON_DEVELOPMENT_CHECKS determines whether + * we check for OUT_OF_ORDER_ITERATION. The logic behind it is that these errors only occurs when the code + * that was written while breaking some simdjson::ondemand requirement. They should not occur in released + * code after these issues were fixed. + */ + +/** + * Get the error message for the given error code. + * + * dom::parser parser; + * dom::element doc; + * auto error = parser.parse("foo",3).get(doc); + * if (error) { printf("Error: %s\n", error_message(error)); } + * + * @return The error message. + */ +inline const char *error_message(error_code error) noexcept; + +/** + * Write the error message to the output stream + */ +inline std::ostream& operator<<(std::ostream& out, error_code error) noexcept; + +/** + * Exception thrown when an exception-supporting simdjson method is called + */ +struct simdjson_error : public std::exception { + /** + * Create an exception from a simdjson error code. + * @param error The error code + */ + simdjson_error(error_code error) noexcept : _error{error} { } + /** The error message */ + const char *what() const noexcept override { return error_message(error()); } + /** The error code */ + error_code error() const noexcept { return _error; } +private: + /** The error code that was used */ + error_code _error; +}; + +namespace internal { + +/** + * The result of a simdjson operation that could fail. + * + * Gives the option of reading error codes, or throwing an exception by casting to the desired result. + * + * This is a base class for implementations that want to add functions to the result type for + * chaining. + * + * Override like: + * + * struct simdjson_result : public internal::simdjson_result_base { + * simdjson_result() noexcept : internal::simdjson_result_base() {} + * simdjson_result(error_code error) noexcept : internal::simdjson_result_base(error) {} + * simdjson_result(T &&value) noexcept : internal::simdjson_result_base(std::forward(value)) {} + * simdjson_result(T &&value, error_code error) noexcept : internal::simdjson_result_base(value, error) {} + * // Your extra methods here + * } + * + * Then any method returning simdjson_result will be chainable with your methods. + */ +template +struct simdjson_result_base : protected std::pair { + + /** + * Create a new empty result with error = UNINITIALIZED. + */ + simdjson_inline simdjson_result_base() noexcept; + + /** + * Create a new error result. + */ + simdjson_inline simdjson_result_base(error_code error) noexcept; + + /** + * Create a new successful result. + */ + simdjson_inline simdjson_result_base(T &&value) noexcept; + + /** + * Create a new result with both things (use if you don't want to branch when creating the result). + */ + simdjson_inline simdjson_result_base(T &&value, error_code error) noexcept; + + /** + * Move the value and the error to the provided variables. + * + * @param value The variable to assign the value to. May not be set if there is an error. + * @param error The variable to assign the error to. Set to SUCCESS if there is no error. + */ + simdjson_inline void tie(T &value, error_code &error) && noexcept; + + /** + * Move the value to the provided variable. + * + * @param value The variable to assign the value to. May not be set if there is an error. + */ + simdjson_inline error_code get(T &value) && noexcept; + + /** + * The error. + */ + simdjson_inline error_code error() const noexcept; + +#if SIMDJSON_EXCEPTIONS + + /** + * Get the result value. + * + * @throw simdjson_error if there was an error. + */ + simdjson_inline T& value() & noexcept(false); + + /** + * Take the result value (move it). + * + * @throw simdjson_error if there was an error. + */ + simdjson_inline T&& value() && noexcept(false); + + /** + * Take the result value (move it). + * + * @throw simdjson_error if there was an error. + */ + simdjson_inline T&& take_value() && noexcept(false); + + /** + * Cast to the value (will throw on error). + * + * @throw simdjson_error if there was an error. + */ + simdjson_inline operator T&&() && noexcept(false); +#endif // SIMDJSON_EXCEPTIONS + + /** + * Get the result value. This function is safe if and only + * the error() method returns a value that evaluates to false. + */ + simdjson_inline const T& value_unsafe() const& noexcept; + + /** + * Take the result value (move it). This function is safe if and only + * the error() method returns a value that evaluates to false. + */ + simdjson_inline T&& value_unsafe() && noexcept; + +}; // struct simdjson_result_base + +} // namespace internal + +/** + * The result of a simdjson operation that could fail. + * + * Gives the option of reading error codes, or throwing an exception by casting to the desired result. + */ +template +struct simdjson_result : public internal::simdjson_result_base { + /** + * @private Create a new empty result with error = UNINITIALIZED. + */ + simdjson_inline simdjson_result() noexcept; + /** + * @private Create a new successful result. + */ + simdjson_inline simdjson_result(T &&value) noexcept; + /** + * @private Create a new error result. + */ + simdjson_inline simdjson_result(error_code error_code) noexcept; + /** + * @private Create a new result with both things (use if you don't want to branch when creating the result). + */ + simdjson_inline simdjson_result(T &&value, error_code error) noexcept; + + /** + * Move the value and the error to the provided variables. + * + * @param value The variable to assign the value to. May not be set if there is an error. + * @param error The variable to assign the error to. Set to SUCCESS if there is no error. + */ + simdjson_inline void tie(T &value, error_code &error) && noexcept; + + /** + * Move the value to the provided variable. + * + * @param value The variable to assign the value to. May not be set if there is an error. + */ + simdjson_warn_unused simdjson_inline error_code get(T &value) && noexcept; + + /** + * The error. + */ + simdjson_inline error_code error() const noexcept; + +#if SIMDJSON_EXCEPTIONS + + /** + * Get the result value. + * + * @throw simdjson_error if there was an error. + */ + simdjson_inline T& value() & noexcept(false); + + /** + * Take the result value (move it). + * + * @throw simdjson_error if there was an error. + */ + simdjson_inline T&& value() && noexcept(false); + + /** + * Take the result value (move it). + * + * @throw simdjson_error if there was an error. + */ + simdjson_inline T&& take_value() && noexcept(false); + + /** + * Cast to the value (will throw on error). + * + * @throw simdjson_error if there was an error. + */ + simdjson_inline operator T&&() && noexcept(false); +#endif // SIMDJSON_EXCEPTIONS + + /** + * Get the result value. This function is safe if and only + * the error() method returns a value that evaluates to false. + */ + simdjson_inline const T& value_unsafe() const& noexcept; + + /** + * Take the result value (move it). This function is safe if and only + * the error() method returns a value that evaluates to false. + */ + simdjson_inline T&& value_unsafe() && noexcept; + +}; // struct simdjson_result + +#if SIMDJSON_EXCEPTIONS + +template +inline std::ostream& operator<<(std::ostream& out, simdjson_result value) { return out << value.value(); } +#endif // SIMDJSON_EXCEPTIONS + +#ifndef SIMDJSON_DISABLE_DEPRECATED_API +/** + * @deprecated This is an alias and will be removed, use error_code instead + */ +using ErrorValues [[deprecated("This is an alias and will be removed, use error_code instead")]] = error_code; + +/** + * @deprecated Error codes should be stored and returned as `error_code`, use `error_message()` instead. + */ +[[deprecated("Error codes should be stored and returned as `error_code`, use `error_message()` instead.")]] +inline const std::string error_message(int error) noexcept; +#endif // SIMDJSON_DISABLE_DEPRECATED_API +} // namespace simdjson + +#endif // SIMDJSON_ERROR_H diff --git a/contrib/libs/simdjson/include/simdjson/fallback.h b/contrib/libs/simdjson/include/simdjson/fallback.h new file mode 100644 index 000000000000..4588cdc00f07 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/fallback.h @@ -0,0 +1,8 @@ +#ifndef SIMDJSON_FALLBACK_H +#define SIMDJSON_FALLBACK_H + +#include "simdjson/fallback/begin.h" +#include "simdjson/generic/amalgamated.h" +#include "simdjson/fallback/end.h" + +#endif // SIMDJSON_FALLBACK_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/fallback/base.h b/contrib/libs/simdjson/include/simdjson/fallback/base.h new file mode 100644 index 000000000000..99cb37423d76 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/fallback/base.h @@ -0,0 +1,19 @@ +#ifndef SIMDJSON_FALLBACK_BASE_H +#define SIMDJSON_FALLBACK_BASE_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +/** + * Fallback implementation (runs on any machine). + */ +namespace fallback { + +class implementation; + +} // namespace fallback +} // namespace simdjson + +#endif // SIMDJSON_FALLBACK_BASE_H diff --git a/contrib/libs/simdjson/include/simdjson/fallback/begin.h b/contrib/libs/simdjson/include/simdjson/fallback/begin.h new file mode 100644 index 000000000000..74749f6d456e --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/fallback/begin.h @@ -0,0 +1,5 @@ +#define SIMDJSON_IMPLEMENTATION fallback +#include "simdjson/fallback/base.h" +#include "simdjson/fallback/bitmanipulation.h" +#include "simdjson/fallback/stringparsing_defs.h" +#include "simdjson/fallback/numberparsing_defs.h" diff --git a/contrib/libs/simdjson/include/simdjson/fallback/bitmanipulation.h b/contrib/libs/simdjson/include/simdjson/fallback/bitmanipulation.h new file mode 100644 index 000000000000..ba47dcccfbf9 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/fallback/bitmanipulation.h @@ -0,0 +1,48 @@ +#ifndef SIMDJSON_FALLBACK_BITMANIPULATION_H +#define SIMDJSON_FALLBACK_BITMANIPULATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/fallback/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace fallback { +namespace { + +#if defined(_MSC_VER) && !defined(_M_ARM64) && !defined(_M_X64) +static inline unsigned char _BitScanForward64(unsigned long* ret, uint64_t x) { + unsigned long x0 = (unsigned long)x, top, bottom; + _BitScanForward(&top, (unsigned long)(x >> 32)); + _BitScanForward(&bottom, x0); + *ret = x0 ? bottom : 32 + top; + return x != 0; +} +static unsigned char _BitScanReverse64(unsigned long* ret, uint64_t x) { + unsigned long x1 = (unsigned long)(x >> 32), top, bottom; + _BitScanReverse(&top, x1); + _BitScanReverse(&bottom, (unsigned long)x); + *ret = x1 ? top + 32 : bottom; + return x != 0; +} +#endif + +/* result might be undefined when input_num is zero */ +simdjson_inline int leading_zeroes(uint64_t input_num) { +#ifdef _MSC_VER + unsigned long leading_zero = 0; + // Search the mask data from most significant bit (MSB) + // to least significant bit (LSB) for a set bit (1). + if (_BitScanReverse64(&leading_zero, input_num)) + return (int)(63 - leading_zero); + else + return 64; +#else + return __builtin_clzll(input_num); +#endif// _MSC_VER +} + +} // unnamed namespace +} // namespace fallback +} // namespace simdjson + +#endif // SIMDJSON_FALLBACK_BITMANIPULATION_H diff --git a/contrib/libs/simdjson/include/simdjson/fallback/end.h b/contrib/libs/simdjson/include/simdjson/fallback/end.h new file mode 100644 index 000000000000..fbd14132b132 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/fallback/end.h @@ -0,0 +1,5 @@ +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/fallback/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#undef SIMDJSON_IMPLEMENTATION diff --git a/contrib/libs/simdjson/include/simdjson/fallback/implementation.h b/contrib/libs/simdjson/include/simdjson/fallback/implementation.h new file mode 100644 index 000000000000..523f06d2e920 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/fallback/implementation.h @@ -0,0 +1,34 @@ +#ifndef SIMDJSON_FALLBACK_IMPLEMENTATION_H +#define SIMDJSON_FALLBACK_IMPLEMENTATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/fallback/base.h" +#include "simdjson/implementation.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace fallback { + +/** + * @private + */ +class implementation final : public simdjson::implementation { +public: + simdjson_inline implementation() : simdjson::implementation( + "fallback", + "Generic fallback implementation", + 0 + ) {} + simdjson_warn_unused error_code create_dom_parser_implementation( + size_t capacity, + size_t max_length, + std::unique_ptr& dst + ) const noexcept final; + simdjson_warn_unused error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept final; + simdjson_warn_unused bool validate_utf8(const char *buf, size_t len) const noexcept final; +}; + +} // namespace fallback +} // namespace simdjson + +#endif // SIMDJSON_FALLBACK_IMPLEMENTATION_H diff --git a/contrib/libs/simdjson/include/simdjson/fallback/numberparsing_defs.h b/contrib/libs/simdjson/include/simdjson/fallback/numberparsing_defs.h new file mode 100644 index 000000000000..490a298dda2e --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/fallback/numberparsing_defs.h @@ -0,0 +1,86 @@ +#ifndef SIMDJSON_FALLBACK_NUMBERPARSING_DEFS_H +#define SIMDJSON_FALLBACK_NUMBERPARSING_DEFS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/fallback/base.h" +#include "simdjson/internal/numberparsing_tables.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include + +#ifdef JSON_TEST_NUMBERS // for unit testing +void found_invalid_number(const uint8_t *buf); +void found_integer(int64_t result, const uint8_t *buf); +void found_unsigned_integer(uint64_t result, const uint8_t *buf); +void found_float(double result, const uint8_t *buf); +#endif + +namespace simdjson { +namespace fallback { +namespace numberparsing { + +// credit: https://johnnylee-sde.github.io/Fast-numeric-string-to-int/ +/** @private */ +static simdjson_inline uint32_t parse_eight_digits_unrolled(const char *chars) { + uint64_t val; + memcpy(&val, chars, sizeof(uint64_t)); + val = (val & 0x0F0F0F0F0F0F0F0F) * 2561 >> 8; + val = (val & 0x00FF00FF00FF00FF) * 6553601 >> 16; + return uint32_t((val & 0x0000FFFF0000FFFF) * 42949672960001 >> 32); +} + +/** @private */ +static simdjson_inline uint32_t parse_eight_digits_unrolled(const uint8_t *chars) { + return parse_eight_digits_unrolled(reinterpret_cast(chars)); +} + +#if SIMDJSON_IS_32BITS // _umul128 for x86, arm +// this is a slow emulation routine for 32-bit +// +static simdjson_inline uint64_t __emulu(uint32_t x, uint32_t y) { + return x * (uint64_t)y; +} +static simdjson_inline uint64_t _umul128(uint64_t ab, uint64_t cd, uint64_t *hi) { + uint64_t ad = __emulu((uint32_t)(ab >> 32), (uint32_t)cd); + uint64_t bd = __emulu((uint32_t)ab, (uint32_t)cd); + uint64_t adbc = ad + __emulu((uint32_t)ab, (uint32_t)(cd >> 32)); + uint64_t adbc_carry = !!(adbc < ad); + uint64_t lo = bd + (adbc << 32); + *hi = __emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + + (adbc_carry << 32) + !!(lo < bd); + return lo; +} +#endif + +/** @private */ +simdjson_inline internal::value128 full_multiplication(uint64_t value1, uint64_t value2) { + internal::value128 answer; +#if SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS +#if SIMDJSON_IS_ARM64 + // ARM64 has native support for 64-bit multiplications, no need to emultate + answer.high = __umulh(value1, value2); + answer.low = value1 * value2; +#else + answer.low = _umul128(value1, value2, &answer.high); // _umul128 not available on ARM64 +#endif // SIMDJSON_IS_ARM64 +#else // SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS + __uint128_t r = (static_cast<__uint128_t>(value1)) * value2; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); +#endif + return answer; +} + +} // namespace numberparsing +} // namespace fallback +} // namespace simdjson + +#ifndef SIMDJSON_SWAR_NUMBER_PARSING +#if SIMDJSON_IS_BIG_ENDIAN +#define SIMDJSON_SWAR_NUMBER_PARSING 0 +#else +#define SIMDJSON_SWAR_NUMBER_PARSING 1 +#endif +#endif + +#endif // SIMDJSON_FALLBACK_NUMBERPARSING_DEFS_H diff --git a/contrib/libs/simdjson/include/simdjson/fallback/ondemand.h b/contrib/libs/simdjson/include/simdjson/fallback/ondemand.h new file mode 100644 index 000000000000..513b7483fd63 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/fallback/ondemand.h @@ -0,0 +1,8 @@ +#ifndef SIMDJSON_FALLBACK_ONDEMAND_H +#define SIMDJSON_FALLBACK_ONDEMAND_H + +#include "simdjson/fallback/begin.h" +#include "simdjson/generic/ondemand/amalgamated.h" +#include "simdjson/fallback/end.h" + +#endif // SIMDJSON_FALLBACK_ONDEMAND_H diff --git a/contrib/libs/simdjson/include/simdjson/fallback/stringparsing_defs.h b/contrib/libs/simdjson/include/simdjson/fallback/stringparsing_defs.h new file mode 100644 index 000000000000..64f23c4b03d1 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/fallback/stringparsing_defs.h @@ -0,0 +1,36 @@ +#ifndef SIMDJSON_FALLBACK_STRINGPARSING_DEFS_H +#define SIMDJSON_FALLBACK_STRINGPARSING_DEFS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/fallback/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace fallback { +namespace { + +// Holds backslashes and quotes locations. +struct backslash_and_quote { +public: + static constexpr uint32_t BYTES_PROCESSED = 1; + simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); + + simdjson_inline bool has_quote_first() { return c == '"'; } + simdjson_inline bool has_backslash() { return c == '\\'; } + simdjson_inline int quote_index() { return c == '"' ? 0 : 1; } + simdjson_inline int backslash_index() { return c == '\\' ? 0 : 1; } + + uint8_t c; +}; // struct backslash_and_quote + +simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { + // store to dest unconditionally - we can overwrite the bits we don't like later + dst[0] = src[0]; + return { src[0] }; +} + +} // unnamed namespace +} // namespace fallback +} // namespace simdjson + +#endif // SIMDJSON_FALLBACK_STRINGPARSING_DEFS_H diff --git a/contrib/libs/simdjson/include/simdjson/generic/amalgamated.h b/contrib/libs/simdjson/include/simdjson/generic/amalgamated.h new file mode 100644 index 000000000000..4b235fa9f7b1 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/amalgamated.h @@ -0,0 +1,12 @@ +#if defined(SIMDJSON_CONDITIONAL_INCLUDE) && !defined(SIMDJSON_GENERIC_DEPENDENCIES_H) +#error simdjson/generic/dependencies.h must be included before simdjson/generic/amalgamated.h! +#endif + +#include "simdjson/generic/base.h" +#include "simdjson/generic/jsoncharutils.h" +#include "simdjson/generic/atomparsing.h" +#include "simdjson/generic/dom_parser_implementation.h" +#include "simdjson/generic/implementation_simdjson_result_base.h" +#include "simdjson/generic/numberparsing.h" + +#include "simdjson/generic/implementation_simdjson_result_base-inl.h" diff --git a/contrib/libs/simdjson/include/simdjson/generic/atomparsing.h b/contrib/libs/simdjson/include/simdjson/generic/atomparsing.h new file mode 100644 index 000000000000..2eeb7b81a263 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/atomparsing.h @@ -0,0 +1,77 @@ +#ifndef SIMDJSON_GENERIC_ATOMPARSING_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ATOMPARSING_H +#include "simdjson/generic/base.h" +#include "simdjson/generic/jsoncharutils.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +/// @private +namespace atomparsing { + +// The string_to_uint32 is exclusively used to map literal strings to 32-bit values. +// We use memcpy instead of a pointer cast to avoid undefined behaviors since we cannot +// be certain that the character pointer will be properly aligned. +// You might think that using memcpy makes this function expensive, but you'd be wrong. +// All decent optimizing compilers (GCC, clang, Visual Studio) will compile string_to_uint32("false"); +// to the compile-time constant 1936482662. +simdjson_inline uint32_t string_to_uint32(const char* str) { uint32_t val; std::memcpy(&val, str, sizeof(uint32_t)); return val; } + + +// Again in str4ncmp we use a memcpy to avoid undefined behavior. The memcpy may appear expensive. +// Yet all decent optimizing compilers will compile memcpy to a single instruction, just about. +simdjson_warn_unused +simdjson_inline uint32_t str4ncmp(const uint8_t *src, const char* atom) { + uint32_t srcval; // we want to avoid unaligned 32-bit loads (undefined in C/C++) + static_assert(sizeof(uint32_t) <= SIMDJSON_PADDING, "SIMDJSON_PADDING must be larger than 4 bytes"); + std::memcpy(&srcval, src, sizeof(uint32_t)); + return srcval ^ string_to_uint32(atom); +} + +simdjson_warn_unused +simdjson_inline bool is_valid_true_atom(const uint8_t *src) { + return (str4ncmp(src, "true") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0; +} + +simdjson_warn_unused +simdjson_inline bool is_valid_true_atom(const uint8_t *src, size_t len) { + if (len > 4) { return is_valid_true_atom(src); } + else if (len == 4) { return !str4ncmp(src, "true"); } + else { return false; } +} + +simdjson_warn_unused +simdjson_inline bool is_valid_false_atom(const uint8_t *src) { + return (str4ncmp(src+1, "alse") | jsoncharutils::is_not_structural_or_whitespace(src[5])) == 0; +} + +simdjson_warn_unused +simdjson_inline bool is_valid_false_atom(const uint8_t *src, size_t len) { + if (len > 5) { return is_valid_false_atom(src); } + else if (len == 5) { return !str4ncmp(src+1, "alse"); } + else { return false; } +} + +simdjson_warn_unused +simdjson_inline bool is_valid_null_atom(const uint8_t *src) { + return (str4ncmp(src, "null") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0; +} + +simdjson_warn_unused +simdjson_inline bool is_valid_null_atom(const uint8_t *src, size_t len) { + if (len > 4) { return is_valid_null_atom(src); } + else if (len == 4) { return !str4ncmp(src, "null"); } + else { return false; } +} + +} // namespace atomparsing +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ATOMPARSING_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/base.h b/contrib/libs/simdjson/include/simdjson/generic/base.h new file mode 100644 index 000000000000..65180837229f --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/base.h @@ -0,0 +1,51 @@ +#ifndef SIMDJSON_GENERIC_BASE_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_BASE_H +#include "simdjson/base.h" +// If we haven't got an implementation yet, we're in the editor, editing a generic file! Just +// use the most advanced one we can so the most possible stuff can be tested. +#ifndef SIMDJSON_IMPLEMENTATION +#include "simdjson/implementation_detection.h" +#if SIMDJSON_IMPLEMENTATION_ICELAKE +#include "simdjson/icelake/begin.h" +#elif SIMDJSON_IMPLEMENTATION_HASWELL +#include "simdjson/haswell/begin.h" +#elif SIMDJSON_IMPLEMENTATION_WESTMERE +#include "simdjson/westmere/begin.h" +#elif SIMDJSON_IMPLEMENTATION_ARM64 +#include "simdjson/arm64/begin.h" +#elif SIMDJSON_IMPLEMENTATION_PPC64 +#error #include "simdjson/ppc64/begin.h" +#elif SIMDJSON_IMPLEMENTATION_LSX +#include "simdjson/lsx/begin.h" +#elif SIMDJSON_IMPLEMENTATION_LASX +#include "simdjson/lasx/begin.h" +#elif SIMDJSON_IMPLEMENTATION_FALLBACK +#include "simdjson/fallback/begin.h" +#else +#error "All possible implementations (including fallback) have been disabled! simdjson will not run." +#endif +#endif // SIMDJSON_IMPLEMENTATION +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { + +struct open_container; +class dom_parser_implementation; + +/** + * The type of a JSON number + */ +enum class number_type { + floating_point_number=1, /// a binary64 number + signed_integer, /// a signed integer that fits in a 64-bit word using two's complement + unsigned_integer, /// a positive integer larger or equal to 1<<63 + big_integer /// a big integer that does not fit in a 64-bit word +}; + +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_BASE_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/dependencies.h b/contrib/libs/simdjson/include/simdjson/generic/dependencies.h new file mode 100644 index 000000000000..1720ddb0aa06 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/dependencies.h @@ -0,0 +1,19 @@ +#ifdef SIMDJSON_CONDITIONAL_INCLUDE +#error simdjson/generic/dependencies.h must be included before defining SIMDJSON_CONDITIONAL_INCLUDE! +#endif + +#ifndef SIMDJSON_GENERIC_DEPENDENCIES_H +#define SIMDJSON_GENERIC_DEPENDENCIES_H + +// Internal headers needed for generics. +// All includes referencing simdjson headers *not* under simdjson/generic must be here! +// Otherwise, amalgamation will fail. +#include "simdjson/base.h" +#include "simdjson/implementation.h" +#include "simdjson/implementation_detection.h" +#include "simdjson/internal/instruction_set.h" +#include "simdjson/internal/dom_parser_implementation.h" +#include "simdjson/internal/jsoncharutils_tables.h" +#include "simdjson/internal/numberparsing_tables.h" +#include "simdjson/internal/simdprune_tables.h" +#endif // SIMDJSON_GENERIC_DEPENDENCIES_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/dom_parser_implementation.h b/contrib/libs/simdjson/include/simdjson/generic/dom_parser_implementation.h new file mode 100644 index 000000000000..e51d2c1279b4 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/dom_parser_implementation.h @@ -0,0 +1,89 @@ +#ifndef SIMDJSON_GENERIC_DOM_PARSER_IMPLEMENTATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_DOM_PARSER_IMPLEMENTATION_H +#include "simdjson/generic/base.h" +#include "simdjson/internal/dom_parser_implementation.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { + +// expectation: sizeof(open_container) = 64/8. +struct open_container { + uint32_t tape_index; // where, on the tape, does the scope ([,{) begins + uint32_t count; // how many elements in the scope +}; // struct open_container + +static_assert(sizeof(open_container) == 64/8, "Open container must be 64 bits"); + +class dom_parser_implementation final : public internal::dom_parser_implementation { +public: + /** Tape location of each open { or [ */ + std::unique_ptr open_containers{}; + /** Whether each open container is a [ or { */ + std::unique_ptr is_array{}; + /** Buffer passed to stage 1 */ + const uint8_t *buf{}; + /** Length passed to stage 1 */ + size_t len{0}; + /** Document passed to stage 2 */ + dom::document *doc{}; + + inline dom_parser_implementation() noexcept; + inline dom_parser_implementation(dom_parser_implementation &&other) noexcept; + inline dom_parser_implementation &operator=(dom_parser_implementation &&other) noexcept; + dom_parser_implementation(const dom_parser_implementation &) = delete; + dom_parser_implementation &operator=(const dom_parser_implementation &) = delete; + + simdjson_warn_unused error_code parse(const uint8_t *buf, size_t len, dom::document &doc) noexcept final; + simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; + simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; + simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; + simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; + inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; + inline simdjson_warn_unused error_code set_max_depth(size_t max_depth) noexcept final; +private: + simdjson_inline simdjson_warn_unused error_code set_capacity_stage1(size_t capacity); + +}; + +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { + +inline dom_parser_implementation::dom_parser_implementation() noexcept = default; +inline dom_parser_implementation::dom_parser_implementation(dom_parser_implementation &&other) noexcept = default; +inline dom_parser_implementation &dom_parser_implementation::operator=(dom_parser_implementation &&other) noexcept = default; + +// Leaving these here so they can be inlined if so desired +inline simdjson_warn_unused error_code dom_parser_implementation::set_capacity(size_t capacity) noexcept { + if(capacity > SIMDJSON_MAXSIZE_BYTES) { return CAPACITY; } + // Stage 1 index output + size_t max_structures = SIMDJSON_ROUNDUP_N(capacity, 64) + 2 + 7; + structural_indexes.reset( new (std::nothrow) uint32_t[max_structures] ); + if (!structural_indexes) { _capacity = 0; return MEMALLOC; } + structural_indexes[0] = 0; + n_structural_indexes = 0; + + _capacity = capacity; + return SUCCESS; +} + +inline simdjson_warn_unused error_code dom_parser_implementation::set_max_depth(size_t max_depth) noexcept { + // Stage 2 stacks + open_containers.reset(new (std::nothrow) open_container[max_depth]); + is_array.reset(new (std::nothrow) bool[max_depth]); + if (!is_array || !open_containers) { _max_depth = 0; return MEMALLOC; } + + _max_depth = max_depth; + return SUCCESS; +} + +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_DOM_PARSER_IMPLEMENTATION_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/implementation_simdjson_result_base-inl.h b/contrib/libs/simdjson/include/simdjson/generic/implementation_simdjson_result_base-inl.h new file mode 100644 index 000000000000..4990ad7ea150 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/implementation_simdjson_result_base-inl.h @@ -0,0 +1,90 @@ +#ifndef SIMDJSON_GENERIC_IMPLEMENTATION_SIMDJSON_RESULT_BASE_INL_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_IMPLEMENTATION_SIMDJSON_RESULT_BASE_INL_H +#include "simdjson/generic/base.h" +#include "simdjson/generic/implementation_simdjson_result_base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { + +// +// internal::implementation_simdjson_result_base inline implementation +// + +template +simdjson_inline void implementation_simdjson_result_base::tie(T &value, error_code &error) && noexcept { + error = this->second; + if (!error) { + value = std::forward>(*this).first; + } +} + +template +simdjson_warn_unused simdjson_inline error_code implementation_simdjson_result_base::get(T &value) && noexcept { + error_code error; + std::forward>(*this).tie(value, error); + return error; +} + +template +simdjson_inline error_code implementation_simdjson_result_base::error() const noexcept { + return this->second; +} + +#if SIMDJSON_EXCEPTIONS + +template +simdjson_inline T& implementation_simdjson_result_base::value() & noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return this->first; +} + +template +simdjson_inline T&& implementation_simdjson_result_base::value() && noexcept(false) { + return std::forward>(*this).take_value(); +} + +template +simdjson_inline T&& implementation_simdjson_result_base::take_value() && noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return std::forward(this->first); +} + +template +simdjson_inline implementation_simdjson_result_base::operator T&&() && noexcept(false) { + return std::forward>(*this).take_value(); +} + +#endif // SIMDJSON_EXCEPTIONS + +template +simdjson_inline const T& implementation_simdjson_result_base::value_unsafe() const& noexcept { + return this->first; +} + +template +simdjson_inline T& implementation_simdjson_result_base::value_unsafe() & noexcept { + return this->first; +} + +template +simdjson_inline T&& implementation_simdjson_result_base::value_unsafe() && noexcept { + return std::forward(this->first); +} + +template +simdjson_inline implementation_simdjson_result_base::implementation_simdjson_result_base(T &&value, error_code error) noexcept + : first{std::forward(value)}, second{error} {} +template +simdjson_inline implementation_simdjson_result_base::implementation_simdjson_result_base(error_code error) noexcept + : implementation_simdjson_result_base(T{}, error) {} +template +simdjson_inline implementation_simdjson_result_base::implementation_simdjson_result_base(T &&value) noexcept + : implementation_simdjson_result_base(std::forward(value), SUCCESS) {} + +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_IMPLEMENTATION_SIMDJSON_RESULT_BASE_INL_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/implementation_simdjson_result_base.h b/contrib/libs/simdjson/include/simdjson/generic/implementation_simdjson_result_base.h new file mode 100644 index 000000000000..aaf2bce1267c --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/implementation_simdjson_result_base.h @@ -0,0 +1,134 @@ +#ifndef SIMDJSON_GENERIC_IMPLEMENTATION_SIMDJSON_RESULT_BASE_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_IMPLEMENTATION_SIMDJSON_RESULT_BASE_H +#include "simdjson/generic/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { + +// This is a near copy of include/error.h's implementation_simdjson_result_base, except it doesn't use std::pair +// so we can avoid inlining errors +// TODO reconcile these! +/** + * The result of a simdjson operation that could fail. + * + * Gives the option of reading error codes, or throwing an exception by casting to the desired result. + * + * This is a base class for implementations that want to add functions to the result type for + * chaining. + * + * Override like: + * + * struct simdjson_result : public internal::implementation_simdjson_result_base { + * simdjson_result() noexcept : internal::implementation_simdjson_result_base() {} + * simdjson_result(error_code error) noexcept : internal::implementation_simdjson_result_base(error) {} + * simdjson_result(T &&value) noexcept : internal::implementation_simdjson_result_base(std::forward(value)) {} + * simdjson_result(T &&value, error_code error) noexcept : internal::implementation_simdjson_result_base(value, error) {} + * // Your extra methods here + * } + * + * Then any method returning simdjson_result will be chainable with your methods. + */ +template +struct implementation_simdjson_result_base { + + /** + * Create a new empty result with error = UNINITIALIZED. + */ + simdjson_inline implementation_simdjson_result_base() noexcept = default; + + /** + * Create a new error result. + */ + simdjson_inline implementation_simdjson_result_base(error_code error) noexcept; + + /** + * Create a new successful result. + */ + simdjson_inline implementation_simdjson_result_base(T &&value) noexcept; + + /** + * Create a new result with both things (use if you don't want to branch when creating the result). + */ + simdjson_inline implementation_simdjson_result_base(T &&value, error_code error) noexcept; + + /** + * Move the value and the error to the provided variables. + * + * @param value The variable to assign the value to. May not be set if there is an error. + * @param error The variable to assign the error to. Set to SUCCESS if there is no error. + */ + simdjson_inline void tie(T &value, error_code &error) && noexcept; + + /** + * Move the value to the provided variable. + * + * @param value The variable to assign the value to. May not be set if there is an error. + */ + simdjson_inline error_code get(T &value) && noexcept; + + /** + * The error. + */ + simdjson_inline error_code error() const noexcept; + +#if SIMDJSON_EXCEPTIONS + + /** + * Get the result value. + * + * @throw simdjson_error if there was an error. + */ + simdjson_inline T& value() & noexcept(false); + + /** + * Take the result value (move it). + * + * @throw simdjson_error if there was an error. + */ + simdjson_inline T&& value() && noexcept(false); + + /** + * Take the result value (move it). + * + * @throw simdjson_error if there was an error. + */ + simdjson_inline T&& take_value() && noexcept(false); + + /** + * Cast to the value (will throw on error). + * + * @throw simdjson_error if there was an error. + */ + simdjson_inline operator T&&() && noexcept(false); + + +#endif // SIMDJSON_EXCEPTIONS + + /** + * Get the result value. This function is safe if and only + * the error() method returns a value that evaluates to false. + */ + simdjson_inline const T& value_unsafe() const& noexcept; + /** + * Get the result value. This function is safe if and only + * the error() method returns a value that evaluates to false. + */ + simdjson_inline T& value_unsafe() & noexcept; + /** + * Take the result value (move it). This function is safe if and only + * the error() method returns a value that evaluates to false. + */ + simdjson_inline T&& value_unsafe() && noexcept; +protected: + /** users should never directly access first and second. **/ + T first{}; /** Users should never directly access 'first'. **/ + error_code second{UNINITIALIZED}; /** Users should never directly access 'second'. **/ +}; // struct implementation_simdjson_result_base + +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_IMPLEMENTATION_SIMDJSON_RESULT_BASE_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/jsoncharutils.h b/contrib/libs/simdjson/include/simdjson/generic/jsoncharutils.h new file mode 100644 index 000000000000..a79b1f8507d4 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/jsoncharutils.h @@ -0,0 +1,104 @@ +#ifndef SIMDJSON_GENERIC_JSONCHARUTILS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_JSONCHARUTILS_H +#include "simdjson/generic/base.h" +#include "simdjson/internal/jsoncharutils_tables.h" +#include "simdjson/internal/numberparsing_tables.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +namespace jsoncharutils { + +// return non-zero if not a structural or whitespace char +// zero otherwise +simdjson_inline uint32_t is_not_structural_or_whitespace(uint8_t c) { + return internal::structural_or_whitespace_negated[c]; +} + +simdjson_inline uint32_t is_structural_or_whitespace(uint8_t c) { + return internal::structural_or_whitespace[c]; +} + +// returns a value with the high 16 bits set if not valid +// otherwise returns the conversion of the 4 hex digits at src into the bottom +// 16 bits of the 32-bit return register +// +// see +// https://lemire.me/blog/2019/04/17/parsing-short-hexadecimal-strings-efficiently/ +static inline uint32_t hex_to_u32_nocheck( + const uint8_t *src) { // strictly speaking, static inline is a C-ism + uint32_t v1 = internal::digit_to_val32[630 + src[0]]; + uint32_t v2 = internal::digit_to_val32[420 + src[1]]; + uint32_t v3 = internal::digit_to_val32[210 + src[2]]; + uint32_t v4 = internal::digit_to_val32[0 + src[3]]; + return v1 | v2 | v3 | v4; +} + +// given a code point cp, writes to c +// the utf-8 code, outputting the length in +// bytes, if the length is zero, the code point +// is invalid +// +// This can possibly be made faster using pdep +// and clz and table lookups, but JSON documents +// have few escaped code points, and the following +// function looks cheap. +// +// Note: we assume that surrogates are treated separately +// +simdjson_inline size_t codepoint_to_utf8(uint32_t cp, uint8_t *c) { + if (cp <= 0x7F) { + c[0] = uint8_t(cp); + return 1; // ascii + } + if (cp <= 0x7FF) { + c[0] = uint8_t((cp >> 6) + 192); + c[1] = uint8_t((cp & 63) + 128); + return 2; // universal plane + // Surrogates are treated elsewhere... + //} //else if (0xd800 <= cp && cp <= 0xdfff) { + // return 0; // surrogates // could put assert here + } else if (cp <= 0xFFFF) { + c[0] = uint8_t((cp >> 12) + 224); + c[1] = uint8_t(((cp >> 6) & 63) + 128); + c[2] = uint8_t((cp & 63) + 128); + return 3; + } else if (cp <= 0x10FFFF) { // if you know you have a valid code point, this + // is not needed + c[0] = uint8_t((cp >> 18) + 240); + c[1] = uint8_t(((cp >> 12) & 63) + 128); + c[2] = uint8_t(((cp >> 6) & 63) + 128); + c[3] = uint8_t((cp & 63) + 128); + return 4; + } + // will return 0 when the code point was too large. + return 0; // bad r +} + +#if SIMDJSON_IS_32BITS // _umul128 for x86, arm +// this is a slow emulation routine for 32-bit +// +static simdjson_inline uint64_t __emulu(uint32_t x, uint32_t y) { + return x * (uint64_t)y; +} +static simdjson_inline uint64_t _umul128(uint64_t ab, uint64_t cd, uint64_t *hi) { + uint64_t ad = __emulu((uint32_t)(ab >> 32), (uint32_t)cd); + uint64_t bd = __emulu((uint32_t)ab, (uint32_t)cd); + uint64_t adbc = ad + __emulu((uint32_t)ab, (uint32_t)(cd >> 32)); + uint64_t adbc_carry = !!(adbc < ad); + uint64_t lo = bd + (adbc << 32); + *hi = __emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + + (adbc_carry << 32) + !!(lo < bd); + return lo; +} +#endif + +} // namespace jsoncharutils +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_JSONCHARUTILS_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/numberparsing.h b/contrib/libs/simdjson/include/simdjson/generic/numberparsing.h new file mode 100644 index 000000000000..5b8fdfcd781a --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/numberparsing.h @@ -0,0 +1,1309 @@ +#ifndef SIMDJSON_GENERIC_NUMBERPARSING_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_NUMBERPARSING_H +#include "simdjson/generic/base.h" +#include "simdjson/generic/jsoncharutils.h" +#include "simdjson/internal/numberparsing_tables.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include +#include +#include + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace numberparsing { + +#ifdef JSON_TEST_NUMBERS +#define INVALID_NUMBER(SRC) (found_invalid_number((SRC)), NUMBER_ERROR) +#define WRITE_INTEGER(VALUE, SRC, WRITER) (found_integer((VALUE), (SRC)), (WRITER).append_s64((VALUE))) +#define WRITE_UNSIGNED(VALUE, SRC, WRITER) (found_unsigned_integer((VALUE), (SRC)), (WRITER).append_u64((VALUE))) +#define WRITE_DOUBLE(VALUE, SRC, WRITER) (found_float((VALUE), (SRC)), (WRITER).append_double((VALUE))) +#define BIGINT_NUMBER(SRC) (found_invalid_number((SRC)), BIGINT_ERROR) +#else +#define INVALID_NUMBER(SRC) (NUMBER_ERROR) +#define WRITE_INTEGER(VALUE, SRC, WRITER) (WRITER).append_s64((VALUE)) +#define WRITE_UNSIGNED(VALUE, SRC, WRITER) (WRITER).append_u64((VALUE)) +#define WRITE_DOUBLE(VALUE, SRC, WRITER) (WRITER).append_double((VALUE)) +#define BIGINT_NUMBER(SRC) (BIGINT_ERROR) +#endif + +namespace { + +// Convert a mantissa, an exponent and a sign bit into an ieee64 double. +// The real_exponent needs to be in [0, 2046] (technically real_exponent = 2047 would be acceptable). +// The mantissa should be in [0,1<<53). The bit at index (1ULL << 52) while be zeroed. +simdjson_inline double to_double(uint64_t mantissa, uint64_t real_exponent, bool negative) { + double d; + mantissa &= ~(1ULL << 52); + mantissa |= real_exponent << 52; + mantissa |= ((static_cast(negative)) << 63); + std::memcpy(&d, &mantissa, sizeof(d)); + return d; +} + +// Attempts to compute i * 10^(power) exactly; and if "negative" is +// true, negate the result. +// This function will only work in some cases, when it does not work, success is +// set to false. This should work *most of the time* (like 99% of the time). +// We assume that power is in the [smallest_power, +// largest_power] interval: the caller is responsible for this check. +simdjson_inline bool compute_float_64(int64_t power, uint64_t i, bool negative, double &d) { + // we start with a fast path + // It was described in + // Clinger WD. How to read floating point numbers accurately. + // ACM SIGPLAN Notices. 1990 +#ifndef FLT_EVAL_METHOD +#error "FLT_EVAL_METHOD should be defined, please include cfloat." +#endif +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + // We cannot be certain that x/y is rounded to nearest. + if (0 <= power && power <= 22 && i <= 9007199254740991) +#else + if (-22 <= power && power <= 22 && i <= 9007199254740991) +#endif + { + // convert the integer into a double. This is lossless since + // 0 <= i <= 2^53 - 1. + d = double(i); + // + // The general idea is as follows. + // If 0 <= s < 2^53 and if 10^0 <= p <= 10^22 then + // 1) Both s and p can be represented exactly as 64-bit floating-point + // values + // (binary64). + // 2) Because s and p can be represented exactly as floating-point values, + // then s * p + // and s / p will produce correctly rounded values. + // + if (power < 0) { + d = d / simdjson::internal::power_of_ten[-power]; + } else { + d = d * simdjson::internal::power_of_ten[power]; + } + if (negative) { + d = -d; + } + return true; + } + // When 22 < power && power < 22 + 16, we could + // hope for another, secondary fast path. It was + // described by David M. Gay in "Correctly rounded + // binary-decimal and decimal-binary conversions." (1990) + // If you need to compute i * 10^(22 + x) for x < 16, + // first compute i * 10^x, if you know that result is exact + // (e.g., when i * 10^x < 2^53), + // then you can still proceed and do (i * 10^x) * 10^22. + // Is this worth your time? + // You need 22 < power *and* power < 22 + 16 *and* (i * 10^(x-22) < 2^53) + // for this second fast path to work. + // If you you have 22 < power *and* power < 22 + 16, and then you + // optimistically compute "i * 10^(x-22)", there is still a chance that you + // have wasted your time if i * 10^(x-22) >= 2^53. It makes the use cases of + // this optimization maybe less common than we would like. Source: + // http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ + // also used in RapidJSON: https://rapidjson.org/strtod_8h_source.html + + // The fast path has now failed, so we are failing back on the slower path. + + // In the slow path, we need to adjust i so that it is > 1<<63 which is always + // possible, except if i == 0, so we handle i == 0 separately. + if(i == 0) { + d = negative ? -0.0 : 0.0; + return true; + } + + + // The exponent is 1024 + 63 + power + // + floor(log(5**power)/log(2)). + // The 1024 comes from the ieee64 standard. + // The 63 comes from the fact that we use a 64-bit word. + // + // Computing floor(log(5**power)/log(2)) could be + // slow. Instead we use a fast function. + // + // For power in (-400,350), we have that + // (((152170 + 65536) * power ) >> 16); + // is equal to + // floor(log(5**power)/log(2)) + power when power >= 0 + // and it is equal to + // ceil(log(5**-power)/log(2)) + power when power < 0 + // + // The 65536 is (1<<16) and corresponds to + // (65536 * power) >> 16 ---> power + // + // ((152170 * power ) >> 16) is equal to + // floor(log(5**power)/log(2)) + // + // Note that this is not magic: 152170/(1<<16) is + // approximatively equal to log(5)/log(2). + // The 1<<16 value is a power of two; we could use a + // larger power of 2 if we wanted to. + // + int64_t exponent = (((152170 + 65536) * power) >> 16) + 1024 + 63; + + + // We want the most significant bit of i to be 1. Shift if needed. + int lz = leading_zeroes(i); + i <<= lz; + + + // We are going to need to do some 64-bit arithmetic to get a precise product. + // We use a table lookup approach. + // It is safe because + // power >= smallest_power + // and power <= largest_power + // We recover the mantissa of the power, it has a leading 1. It is always + // rounded down. + // + // We want the most significant 64 bits of the product. We know + // this will be non-zero because the most significant bit of i is + // 1. + const uint32_t index = 2 * uint32_t(power - simdjson::internal::smallest_power); + // Optimization: It may be that materializing the index as a variable might confuse some compilers and prevent effective complex-addressing loads. (Done for code clarity.) + // + // The full_multiplication function computes the 128-bit product of two 64-bit words + // with a returned value of type value128 with a "low component" corresponding to the + // 64-bit least significant bits of the product and with a "high component" corresponding + // to the 64-bit most significant bits of the product. + simdjson::internal::value128 firstproduct = full_multiplication(i, simdjson::internal::power_of_five_128[index]); + // Both i and power_of_five_128[index] have their most significant bit set to 1 which + // implies that the either the most or the second most significant bit of the product + // is 1. We pack values in this manner for efficiency reasons: it maximizes the use + // we make of the product. It also makes it easy to reason about the product: there + // is 0 or 1 leading zero in the product. + + // Unless the least significant 9 bits of the high (64-bit) part of the full + // product are all 1s, then we know that the most significant 55 bits are + // exact and no further work is needed. Having 55 bits is necessary because + // we need 53 bits for the mantissa but we have to have one rounding bit and + // we can waste a bit if the most significant bit of the product is zero. + if((firstproduct.high & 0x1FF) == 0x1FF) { + // We want to compute i * 5^q, but only care about the top 55 bits at most. + // Consider the scenario where q>=0. Then 5^q may not fit in 64-bits. Doing + // the full computation is wasteful. So we do what is called a "truncated + // multiplication". + // We take the most significant 64-bits, and we put them in + // power_of_five_128[index]. Usually, that's good enough to approximate i * 5^q + // to the desired approximation using one multiplication. Sometimes it does not suffice. + // Then we store the next most significant 64 bits in power_of_five_128[index + 1], and + // then we get a better approximation to i * 5^q. + // + // That's for when q>=0. The logic for q<0 is somewhat similar but it is somewhat + // more complicated. + // + // There is an extra layer of complexity in that we need more than 55 bits of + // accuracy in the round-to-even scenario. + // + // The full_multiplication function computes the 128-bit product of two 64-bit words + // with a returned value of type value128 with a "low component" corresponding to the + // 64-bit least significant bits of the product and with a "high component" corresponding + // to the 64-bit most significant bits of the product. + simdjson::internal::value128 secondproduct = full_multiplication(i, simdjson::internal::power_of_five_128[index + 1]); + firstproduct.low += secondproduct.high; + if(secondproduct.high > firstproduct.low) { firstproduct.high++; } + // As it has been proven by Noble Mushtak and Daniel Lemire in "Fast Number Parsing Without + // Fallback" (https://arxiv.org/abs/2212.06644), at this point we are sure that the product + // is sufficiently accurate, and more computation is not needed. + } + uint64_t lower = firstproduct.low; + uint64_t upper = firstproduct.high; + // The final mantissa should be 53 bits with a leading 1. + // We shift it so that it occupies 54 bits with a leading 1. + /////// + uint64_t upperbit = upper >> 63; + uint64_t mantissa = upper >> (upperbit + 9); + lz += int(1 ^ upperbit); + + // Here we have mantissa < (1<<54). + int64_t real_exponent = exponent - lz; + if (simdjson_unlikely(real_exponent <= 0)) { // we have a subnormal? + // Here have that real_exponent <= 0 so -real_exponent >= 0 + if(-real_exponent + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure. + d = negative ? -0.0 : 0.0; + return true; + } + // next line is safe because -real_exponent + 1 < 0 + mantissa >>= -real_exponent + 1; + // Thankfully, we can't have both "round-to-even" and subnormals because + // "round-to-even" only occurs for powers close to 0. + mantissa += (mantissa & 1); // round up + mantissa >>= 1; + // There is a weird scenario where we don't have a subnormal but just. + // Suppose we start with 2.2250738585072013e-308, we end up + // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal + // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round + // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer + // subnormal, but we can only know this after rounding. + // So we only declare a subnormal if we are smaller than the threshold. + real_exponent = (mantissa < (uint64_t(1) << 52)) ? 0 : 1; + d = to_double(mantissa, real_exponent, negative); + return true; + } + // We have to round to even. The "to even" part + // is only a problem when we are right in between two floats + // which we guard against. + // If we have lots of trailing zeros, we may fall right between two + // floating-point values. + // + // The round-to-even cases take the form of a number 2m+1 which is in (2^53,2^54] + // times a power of two. That is, it is right between a number with binary significand + // m and another number with binary significand m+1; and it must be the case + // that it cannot be represented by a float itself. + // + // We must have that w * 10 ^q == (2m+1) * 2^p for some power of two 2^p. + // Recall that 10^q = 5^q * 2^q. + // When q >= 0, we must have that (2m+1) is divible by 5^q, so 5^q <= 2^54. We have that + // 5^23 <= 2^54 and it is the last power of five to qualify, so q <= 23. + // When q<0, we have w >= (2m+1) x 5^{-q}. We must have that w<2^{64} so + // (2m+1) x 5^{-q} < 2^{64}. We have that 2m+1>2^{53}. Hence, we must have + // 2^{53} x 5^{-q} < 2^{64}. + // Hence we have 5^{-q} < 2^{11}$ or q>= -4. + // + // We require lower <= 1 and not lower == 0 because we could not prove that + // that lower == 0 is implied; but we could prove that lower <= 1 is a necessary and sufficient test. + if (simdjson_unlikely((lower <= 1) && (power >= -4) && (power <= 23) && ((mantissa & 3) == 1))) { + if((mantissa << (upperbit + 64 - 53 - 2)) == upper) { + mantissa &= ~1; // flip it so that we do not round up + } + } + + mantissa += mantissa & 1; + mantissa >>= 1; + + // Here we have mantissa < (1<<53), unless there was an overflow + if (mantissa >= (1ULL << 53)) { + ////////// + // This will happen when parsing values such as 7.2057594037927933e+16 + //////// + mantissa = (1ULL << 52); + real_exponent++; + } + mantissa &= ~(1ULL << 52); + // we have to check that real_exponent is in range, otherwise we bail out + if (simdjson_unlikely(real_exponent > 2046)) { + // We have an infinite value!!! We could actually throw an error here if we could. + return false; + } + d = to_double(mantissa, real_exponent, negative); + return true; +} + +// We call a fallback floating-point parser that might be slow. Note +// it will accept JSON numbers, but the JSON spec. is more restrictive so +// before you call parse_float_fallback, you need to have validated the input +// string with the JSON grammar. +// It will return an error (false) if the parsed number is infinite. +// The string parsing itself always succeeds. We know that there is at least +// one digit. +static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { + *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr)); + // We do not accept infinite values. + + // Detecting finite values in a portable manner is ridiculously hard, ideally + // we would want to do: + // return !std::isfinite(*outDouble); + // but that mysteriously fails under legacy/old libc++ libraries, see + // https://github.com/simdjson/simdjson/issues/1286 + // + // Therefore, fall back to this solution (the extra parens are there + // to handle that max may be a macro on windows). + return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); +} + +static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { + *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); + // We do not accept infinite values. + + // Detecting finite values in a portable manner is ridiculously hard, ideally + // we would want to do: + // return !std::isfinite(*outDouble); + // but that mysteriously fails under legacy/old libc++ libraries, see + // https://github.com/simdjson/simdjson/issues/1286 + // + // Therefore, fall back to this solution (the extra parens are there + // to handle that max may be a macro on windows). + return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); +} + +// check quickly whether the next 8 chars are made of digits +// at a glance, it looks better than Mula's +// http://0x80.pl/articles/swar-digits-validate.html +simdjson_inline bool is_made_of_eight_digits_fast(const uint8_t *chars) { + uint64_t val; + // this can read up to 7 bytes beyond the buffer size, but we require + // SIMDJSON_PADDING of padding + static_assert(7 <= SIMDJSON_PADDING, "SIMDJSON_PADDING must be bigger than 7"); + std::memcpy(&val, chars, 8); + // a branchy method might be faster: + // return (( val & 0xF0F0F0F0F0F0F0F0 ) == 0x3030303030303030) + // && (( (val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0 ) == + // 0x3030303030303030); + return (((val & 0xF0F0F0F0F0F0F0F0) | + (((val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0) >> 4)) == + 0x3333333333333333); +} + +template +SIMDJSON_NO_SANITIZE_UNDEFINED // We deliberately allow overflow here and check later +simdjson_inline bool parse_digit(const uint8_t c, I &i) { + const uint8_t digit = static_cast(c - '0'); + if (digit > 9) { + return false; + } + // PERF NOTE: multiplication by 10 is cheaper than arbitrary integer multiplication + i = 10 * i + digit; // might overflow, we will handle the overflow later + return true; +} + +simdjson_inline bool is_digit(const uint8_t c) { + return static_cast(c - '0') <= 9; +} + +simdjson_inline error_code parse_decimal_after_separator(simdjson_unused const uint8_t *const src, const uint8_t *&p, uint64_t &i, int64_t &exponent) { + // we continue with the fiction that we have an integer. If the + // floating point number is representable as x * 10^z for some integer + // z that fits in 53 bits, then we will be able to convert back the + // the integer into a float in a lossless manner. + const uint8_t *const first_after_period = p; + +#ifdef SIMDJSON_SWAR_NUMBER_PARSING +#if SIMDJSON_SWAR_NUMBER_PARSING + // this helps if we have lots of decimals! + // this turns out to be frequent enough. + if (is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); + p += 8; + } +#endif // SIMDJSON_SWAR_NUMBER_PARSING +#endif // #ifdef SIMDJSON_SWAR_NUMBER_PARSING + // Unrolling the first digit makes a small difference on some implementations (e.g. westmere) + if (parse_digit(*p, i)) { ++p; } + while (parse_digit(*p, i)) { p++; } + exponent = first_after_period - p; + // Decimal without digits (123.) is illegal + if (exponent == 0) { + return INVALID_NUMBER(src); + } + return SUCCESS; +} + +simdjson_inline error_code parse_exponent(simdjson_unused const uint8_t *const src, const uint8_t *&p, int64_t &exponent) { + // Exp Sign: -123.456e[-]78 + bool neg_exp = ('-' == *p); + if (neg_exp || '+' == *p) { p++; } // Skip + as well + + // Exponent: -123.456e-[78] + auto start_exp = p; + int64_t exp_number = 0; + while (parse_digit(*p, exp_number)) { ++p; } + // It is possible for parse_digit to overflow. + // In particular, it could overflow to INT64_MIN, and we cannot do - INT64_MIN. + // Thus we *must* check for possible overflow before we negate exp_number. + + // Performance notes: it may seem like combining the two "simdjson_unlikely checks" below into + // a single simdjson_unlikely path would be faster. The reasoning is sound, but the compiler may + // not oblige and may, in fact, generate two distinct paths in any case. It might be + // possible to do uint64_t(p - start_exp - 1) >= 18 but it could end up trading off + // instructions for a simdjson_likely branch, an unconclusive gain. + + // If there were no digits, it's an error. + if (simdjson_unlikely(p == start_exp)) { + return INVALID_NUMBER(src); + } + // We have a valid positive exponent in exp_number at this point, except that + // it may have overflowed. + + // If there were more than 18 digits, we may have overflowed the integer. We have to do + // something!!!! + if (simdjson_unlikely(p > start_exp+18)) { + // Skip leading zeroes: 1e000000000000000000001 is technically valid and does not overflow + while (*start_exp == '0') { start_exp++; } + // 19 digits could overflow int64_t and is kind of absurd anyway. We don't + // support exponents smaller than -999,999,999,999,999,999 and bigger + // than 999,999,999,999,999,999. + // We can truncate. + // Note that 999999999999999999 is assuredly too large. The maximal ieee64 value before + // infinity is ~1.8e308. The smallest subnormal is ~5e-324. So, actually, we could + // truncate at 324. + // Note that there is no reason to fail per se at this point in time. + // E.g., 0e999999999999999999999 is a fine number. + if (p > start_exp+18) { exp_number = 999999999999999999; } + } + // At this point, we know that exp_number is a sane, positive, signed integer. + // It is <= 999,999,999,999,999,999. As long as 'exponent' is in + // [-8223372036854775808, 8223372036854775808], we won't overflow. Because 'exponent' + // is bounded in magnitude by the size of the JSON input, we are fine in this universe. + // To sum it up: the next line should never overflow. + exponent += (neg_exp ? -exp_number : exp_number); + return SUCCESS; +} + +simdjson_inline bool check_if_integer(const uint8_t *const src, size_t max_length) { + const uint8_t *const srcend = src + max_length; + bool negative = (*src == '-'); // we can always read at least one character after the '-' + const uint8_t *p = src + uint8_t(negative); + if(p == srcend) { return false; } + if(*p == '0') { + ++p; + if(p == srcend) { return true; } + if(jsoncharutils::is_not_structural_or_whitespace(*p)) { return false; } + return true; + } + while(p != srcend && is_digit(*p)) { ++p; } + if(p == srcend) { return true; } + if(jsoncharutils::is_not_structural_or_whitespace(*p)) { return false; } + return true; +} + +simdjson_inline size_t significant_digits(const uint8_t * start_digits, size_t digit_count) { + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + const uint8_t *start = start_digits; + while ((*start == '0') || (*start == '.')) { ++start; } + // we over-decrement by one when there is a '.' + return digit_count - size_t(start - start_digits); +} + +} // unnamed namespace + +/** @private */ +static error_code slow_float_parsing(simdjson_unused const uint8_t * src, double* answer) { + if (parse_float_fallback(src, answer)) { + return SUCCESS; + } + return INVALID_NUMBER(src); +} + +/** @private */ +template +simdjson_inline error_code write_float(const uint8_t *const src, bool negative, uint64_t i, const uint8_t * start_digits, size_t digit_count, int64_t exponent, W &writer) { + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon in practice. + // + // 9999999999999999999 < 2**64 so we can accommodate 19 digits. + // If we have a decimal separator, then digit_count - 1 is the number of digits, but we + // may not have a decimal separator! + if (simdjson_unlikely(digit_count > 19 && significant_digits(start_digits, digit_count) > 19)) { + // Ok, chances are good that we had an overflow! + // this is almost never going to get called!!! + // we start anew, going slowly!!! + // This will happen in the following examples: + // 10000000000000000000000000000000000000000000e+308 + // 3.1415926535897932384626433832795028841971693993751 + // + // NOTE: We do not pass a reference to the to slow_float_parsing. If we passed our writer + // reference to it, it would force it to be stored in memory, preventing the compiler from + // picking it apart and putting into registers. i.e. if we pass it as reference, + // it gets slow. + double d; + error_code error = slow_float_parsing(src, &d); + writer.append_double(d); + return error; + } + // NOTE: it's weird that the simdjson_unlikely() only wraps half the if, but it seems to get slower any other + // way we've tried: https://github.com/simdjson/simdjson/pull/990#discussion_r448497331 + // To future reader: we'd love if someone found a better way, or at least could explain this result! + if (simdjson_unlikely(exponent < simdjson::internal::smallest_power) || (exponent > simdjson::internal::largest_power)) { + // + // Important: smallest_power is such that it leads to a zero value. + // Observe that 18446744073709551615e-343 == 0, i.e. (2**64 - 1) e -343 is zero + // so something x 10^-343 goes to zero, but not so with something x 10^-342. + static_assert(simdjson::internal::smallest_power <= -342, "smallest_power is not small enough"); + // + if((exponent < simdjson::internal::smallest_power) || (i == 0)) { + // E.g. Parse "-0.0e-999" into the same value as "-0.0". See https://en.wikipedia.org/wiki/Signed_zero + WRITE_DOUBLE(negative ? -0.0 : 0.0, src, writer); + return SUCCESS; + } else { // (exponent > largest_power) and (i != 0) + // We have, for sure, an infinite value and simdjson refuses to parse infinite values. + return INVALID_NUMBER(src); + } + } + double d; + if (!compute_float_64(exponent, i, negative, d)) { + // we are almost never going to get here. + if (!parse_float_fallback(src, &d)) { return INVALID_NUMBER(src); } + } + WRITE_DOUBLE(d, src, writer); + return SUCCESS; +} + +// parse the number at src +// define JSON_TEST_NUMBERS for unit testing +// +// It is assumed that the number is followed by a structural ({,},],[) character +// or a white space character. If that is not the case (e.g., when the JSON +// document is made of a single number), then it is necessary to copy the +// content and append a space before calling this function. +// +// Our objective is accurate parsing (ULP of 0) at high speed. +template +simdjson_inline error_code parse_number(const uint8_t *const src, W &writer); + +// for performance analysis, it is sometimes useful to skip parsing +#ifdef SIMDJSON_SKIPNUMBERPARSING + +template +simdjson_inline error_code parse_number(const uint8_t *const, W &writer) { + writer.append_s64(0); // always write zero + return SUCCESS; // always succeeds +} + +simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src) noexcept { return 0; } +simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src) noexcept { return 0; } +simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * const src) noexcept { return 0; } +simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { return 0; } +simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t * const src) noexcept { return 0; } +simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * const src) noexcept { return 0; } +simdjson_unused simdjson_inline bool is_negative(const uint8_t * src) noexcept { return false; } +simdjson_unused simdjson_inline simdjson_result is_integer(const uint8_t * src) noexcept { return false; } +simdjson_unused simdjson_inline simdjson_result get_number_type(const uint8_t * src) noexcept { return number_type::signed_integer; } +#else + +// parse the number at src +// define JSON_TEST_NUMBERS for unit testing +// +// It is assumed that the number is followed by a structural ({,},],[) character +// or a white space character. If that is not the case (e.g., when the JSON +// document is made of a single number), then it is necessary to copy the +// content and append a space before calling this function. +// +// Our objective is accurate parsing (ULP of 0) at high speed. +template +simdjson_inline error_code parse_number(const uint8_t *const src, W &writer) { + // + // Check for minus sign + // + bool negative = (*src == '-'); + const uint8_t *p = src + uint8_t(negative); + + // + // Parse the integer part. + // + // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare + const uint8_t *const start_digits = p; + uint64_t i = 0; + while (parse_digit(*p, i)) { p++; } + + // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. + // Optimization note: size_t is expected to be unsigned. + size_t digit_count = size_t(p - start_digits); + if (digit_count == 0 || ('0' == *start_digits && digit_count > 1)) { return INVALID_NUMBER(src); } + + // + // Handle floats if there is a . or e (or both) + // + int64_t exponent = 0; + bool is_float = false; + if ('.' == *p) { + is_float = true; + ++p; + SIMDJSON_TRY( parse_decimal_after_separator(src, p, i, exponent) ); + digit_count = int(p - start_digits); // used later to guard against overflows + } + if (('e' == *p) || ('E' == *p)) { + is_float = true; + ++p; + SIMDJSON_TRY( parse_exponent(src, p, exponent) ); + } + if (is_float) { + const bool dirty_end = jsoncharutils::is_not_structural_or_whitespace(*p); + SIMDJSON_TRY( write_float(src, negative, i, start_digits, digit_count, exponent, writer) ); + if (dirty_end) { return INVALID_NUMBER(src); } + return SUCCESS; + } + + // The longest negative 64-bit number is 19 digits. + // The longest positive 64-bit number is 20 digits. + // We do it this way so we don't trigger this branch unless we must. + size_t longest_digit_count = negative ? 19 : 20; + if (digit_count > longest_digit_count) { return BIGINT_NUMBER(src); } + if (digit_count == longest_digit_count) { + if (negative) { + // Anything negative above INT64_MAX+1 is invalid + if (i > uint64_t(INT64_MAX)+1) { return BIGINT_NUMBER(src); } + WRITE_INTEGER(~i+1, src, writer); + if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return INVALID_NUMBER(src); } + return SUCCESS; + // Positive overflow check: + // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the + // biggest uint64_t. + // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. + // If we got here, it's a 20 digit number starting with the digit "1". + // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller + // than 1,553,255,926,290,448,384. + // - That is smaller than the smallest possible 20-digit number the user could write: + // 10,000,000,000,000,000,000. + // - Therefore, if the number is positive and lower than that, it's overflow. + // - The value we are looking at is less than or equal to INT64_MAX. + // + } else if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INVALID_NUMBER(src); } + } + + // Write unsigned if it does not fit in a signed integer. + if (i > uint64_t(INT64_MAX)) { + WRITE_UNSIGNED(i, src, writer); + } else { + WRITE_INTEGER(negative ? (~i+1) : i, src, writer); + } + if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return INVALID_NUMBER(src); } + return SUCCESS; +} + +// Inlineable functions +namespace { + +// This table can be used to characterize the final character of an integer +// string. For JSON structural character and allowable white space characters, +// we return SUCCESS. For 'e', '.' and 'E', we return INCORRECT_TYPE. Otherwise +// we return NUMBER_ERROR. +// Optimization note: we could easily reduce the size of the table by half (to 128) +// at the cost of an extra branch. +// Optimization note: we want the values to use at most 8 bits (not, e.g., 32 bits): +static_assert(error_code(uint8_t(NUMBER_ERROR))== NUMBER_ERROR, "bad NUMBER_ERROR cast"); +static_assert(error_code(uint8_t(SUCCESS))== SUCCESS, "bad NUMBER_ERROR cast"); +static_assert(error_code(uint8_t(INCORRECT_TYPE))== INCORRECT_TYPE, "bad NUMBER_ERROR cast"); + +const uint8_t integer_string_finisher[256] = { + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, + SUCCESS, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, + NUMBER_ERROR, INCORRECT_TYPE, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, INCORRECT_TYPE, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, SUCCESS, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, INCORRECT_TYPE, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, + SUCCESS, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, + NUMBER_ERROR}; + +// Parse any number from 0 to 18,446,744,073,709,551,615 +simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src) noexcept { + const uint8_t *p = src; + // + // Parse the integer part. + // + // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare + const uint8_t *const start_digits = p; + uint64_t i = 0; + while (parse_digit(*p, i)) { p++; } + + // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. + // Optimization note: size_t is expected to be unsigned. + size_t digit_count = size_t(p - start_digits); + // The longest positive 64-bit number is 20 digits. + // We do it this way so we don't trigger this branch unless we must. + // Optimization note: the compiler can probably merge + // ((digit_count == 0) || (digit_count > 20)) + // into a single branch since digit_count is unsigned. + if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } + // Here digit_count > 0. + if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } + // We can do the following... + // if (!jsoncharutils::is_structural_or_whitespace(*p)) { + // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; + // } + // as a single table lookup: + if (integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } + + if (digit_count == 20) { + // Positive overflow check: + // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the + // biggest uint64_t. + // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. + // If we got here, it's a 20 digit number starting with the digit "1". + // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller + // than 1,553,255,926,290,448,384. + // - That is smaller than the smallest possible 20-digit number the user could write: + // 10,000,000,000,000,000,000. + // - Therefore, if the number is positive and lower than that, it's overflow. + // - The value we are looking at is less than or equal to INT64_MAX. + // + if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } + } + + return i; +} + + +// Parse any number from 0 to 18,446,744,073,709,551,615 +// Never read at src_end or beyond +simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { + const uint8_t *p = src; + // + // Parse the integer part. + // + // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare + const uint8_t *const start_digits = p; + uint64_t i = 0; + while ((p != src_end) && parse_digit(*p, i)) { p++; } + + // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. + // Optimization note: size_t is expected to be unsigned. + size_t digit_count = size_t(p - start_digits); + // The longest positive 64-bit number is 20 digits. + // We do it this way so we don't trigger this branch unless we must. + // Optimization note: the compiler can probably merge + // ((digit_count == 0) || (digit_count > 20)) + // into a single branch since digit_count is unsigned. + if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } + // Here digit_count > 0. + if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } + // We can do the following... + // if (!jsoncharutils::is_structural_or_whitespace(*p)) { + // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; + // } + // as a single table lookup: + if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } + + if (digit_count == 20) { + // Positive overflow check: + // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the + // biggest uint64_t. + // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. + // If we got here, it's a 20 digit number starting with the digit "1". + // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller + // than 1,553,255,926,290,448,384. + // - That is smaller than the smallest possible 20-digit number the user could write: + // 10,000,000,000,000,000,000. + // - Therefore, if the number is positive and lower than that, it's overflow. + // - The value we are looking at is less than or equal to INT64_MAX. + // + if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } + } + + return i; +} + +// Parse any number from 0 to 18,446,744,073,709,551,615 +simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { + const uint8_t *p = src + 1; + // + // Parse the integer part. + // + // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare + const uint8_t *const start_digits = p; + uint64_t i = 0; + while (parse_digit(*p, i)) { p++; } + + // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. + // Optimization note: size_t is expected to be unsigned. + size_t digit_count = size_t(p - start_digits); + // The longest positive 64-bit number is 20 digits. + // We do it this way so we don't trigger this branch unless we must. + // Optimization note: the compiler can probably merge + // ((digit_count == 0) || (digit_count > 20)) + // into a single branch since digit_count is unsigned. + if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } + // Here digit_count > 0. + if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } + // We can do the following... + // if (!jsoncharutils::is_structural_or_whitespace(*p)) { + // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; + // } + // as a single table lookup: + if (*p != '"') { return NUMBER_ERROR; } + + if (digit_count == 20) { + // Positive overflow check: + // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the + // biggest uint64_t. + // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. + // If we got here, it's a 20 digit number starting with the digit "1". + // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller + // than 1,553,255,926,290,448,384. + // - That is smaller than the smallest possible 20-digit number the user could write: + // 10,000,000,000,000,000,000. + // - Therefore, if the number is positive and lower than that, it's overflow. + // - The value we are looking at is less than or equal to INT64_MAX. + // + // Note: we use src[1] and not src[0] because src[0] is the quote character in this + // instance. + if (src[1] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } + } + + return i; +} + +// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 +simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t *src) noexcept { + // + // Check for minus sign + // + bool negative = (*src == '-'); + const uint8_t *p = src + uint8_t(negative); + + // + // Parse the integer part. + // + // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare + const uint8_t *const start_digits = p; + uint64_t i = 0; + while (parse_digit(*p, i)) { p++; } + + // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. + // Optimization note: size_t is expected to be unsigned. + size_t digit_count = size_t(p - start_digits); + // We go from + // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 + // so we can never represent numbers that have more than 19 digits. + size_t longest_digit_count = 19; + // Optimization note: the compiler can probably merge + // ((digit_count == 0) || (digit_count > longest_digit_count)) + // into a single branch since digit_count is unsigned. + if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } + // Here digit_count > 0. + if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } + // We can do the following... + // if (!jsoncharutils::is_structural_or_whitespace(*p)) { + // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; + // } + // as a single table lookup: + if(integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } + // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. + // Performance note: This check is only needed when digit_count == longest_digit_count but it is + // so cheap that we might as well always make it. + if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } + return negative ? (~i+1) : i; +} + +// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 +// Never read at src_end or beyond +simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { + // + // Check for minus sign + // + if(src == src_end) { return NUMBER_ERROR; } + bool negative = (*src == '-'); + const uint8_t *p = src + uint8_t(negative); + + // + // Parse the integer part. + // + // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare + const uint8_t *const start_digits = p; + uint64_t i = 0; + while ((p != src_end) && parse_digit(*p, i)) { p++; } + + // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. + // Optimization note: size_t is expected to be unsigned. + size_t digit_count = size_t(p - start_digits); + // We go from + // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 + // so we can never represent numbers that have more than 19 digits. + size_t longest_digit_count = 19; + // Optimization note: the compiler can probably merge + // ((digit_count == 0) || (digit_count > longest_digit_count)) + // into a single branch since digit_count is unsigned. + if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } + // Here digit_count > 0. + if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } + // We can do the following... + // if (!jsoncharutils::is_structural_or_whitespace(*p)) { + // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; + // } + // as a single table lookup: + if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } + // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. + // Performance note: This check is only needed when digit_count == longest_digit_count but it is + // so cheap that we might as well always make it. + if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } + return negative ? (~i+1) : i; +} + +// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 +simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { + // + // Check for minus sign + // + bool negative = (*(src + 1) == '-'); + src += uint8_t(negative) + 1; + + // + // Parse the integer part. + // + // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare + const uint8_t *const start_digits = src; + uint64_t i = 0; + while (parse_digit(*src, i)) { src++; } + + // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. + // Optimization note: size_t is expected to be unsigned. + size_t digit_count = size_t(src - start_digits); + // We go from + // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 + // so we can never represent numbers that have more than 19 digits. + size_t longest_digit_count = 19; + // Optimization note: the compiler can probably merge + // ((digit_count == 0) || (digit_count > longest_digit_count)) + // into a single branch since digit_count is unsigned. + if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } + // Here digit_count > 0. + if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } + // We can do the following... + // if (!jsoncharutils::is_structural_or_whitespace(*src)) { + // return (*src == '.' || *src == 'e' || *src == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; + // } + // as a single table lookup: + if(*src != '"') { return NUMBER_ERROR; } + // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. + // Performance note: This check is only needed when digit_count == longest_digit_count but it is + // so cheap that we might as well always make it. + if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } + return negative ? (~i+1) : i; +} + +simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src) noexcept { + // + // Check for minus sign + // + bool negative = (*src == '-'); + src += uint8_t(negative); + + // + // Parse the integer part. + // + uint64_t i = 0; + const uint8_t *p = src; + p += parse_digit(*p, i); + bool leading_zero = (i == 0); + while (parse_digit(*p, i)) { p++; } + // no integer digits, or 0123 (zero must be solo) + if ( p == src ) { return INCORRECT_TYPE; } + if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } + + // + // Parse the decimal part. + // + int64_t exponent = 0; + bool overflow; + if (simdjson_likely(*p == '.')) { + p++; + const uint8_t *start_decimal_digits = p; + if (!parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits + p++; + while (parse_digit(*p, i)) { p++; } + exponent = -(p - start_decimal_digits); + + // Overflow check. More than 19 digits (minus the decimal) may be overflow. + overflow = p-src-1 > 19; + if (simdjson_unlikely(overflow && leading_zero)) { + // Skip leading 0.00000 and see if it still overflows + const uint8_t *start_digits = src + 2; + while (*start_digits == '0') { start_digits++; } + overflow = p-start_digits > 19; + } + } else { + overflow = p-src > 19; + } + + // + // Parse the exponent + // + if (*p == 'e' || *p == 'E') { + p++; + bool exp_neg = *p == '-'; + p += exp_neg || *p == '+'; + + uint64_t exp = 0; + const uint8_t *start_exp_digits = p; + while (parse_digit(*p, exp)) { p++; } + // no exp digits, or 20+ exp digits + if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + + exponent += exp_neg ? 0-exp : exp; + } + + if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } + + overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; + + // + // Assemble (or slow-parse) the float + // + double d; + if (simdjson_likely(!overflow)) { + if (compute_float_64(exponent, i, negative, d)) { return d; } + } + if (!parse_float_fallback(src - uint8_t(negative), &d)) { + return NUMBER_ERROR; + } + return d; +} + +simdjson_unused simdjson_inline bool is_negative(const uint8_t * src) noexcept { + return (*src == '-'); +} + +simdjson_unused simdjson_inline simdjson_result is_integer(const uint8_t * src) noexcept { + bool negative = (*src == '-'); + src += uint8_t(negative); + const uint8_t *p = src; + while(static_cast(*p - '0') <= 9) { p++; } + if ( p == src ) { return NUMBER_ERROR; } + if (jsoncharutils::is_structural_or_whitespace(*p)) { return true; } + return false; +} + +simdjson_unused simdjson_inline simdjson_result get_number_type(const uint8_t * src) noexcept { + bool negative = (*src == '-'); + src += uint8_t(negative); + const uint8_t *p = src; + while(static_cast(*p - '0') <= 9) { p++; } + size_t digit_count = size_t(p - src); + if ( p == src ) { return NUMBER_ERROR; } + if (jsoncharutils::is_structural_or_whitespace(*p)) { + static const uint8_t * smaller_big_integer = reinterpret_cast("9223372036854775808"); + // We have an integer. + if(simdjson_unlikely(digit_count > 20)) { + return number_type::big_integer; + } + // If the number is negative and valid, it must be a signed integer. + if(negative) { + if (simdjson_unlikely(digit_count > 19)) return number_type::big_integer; + if (simdjson_unlikely(digit_count == 19 && memcmp(src, smaller_big_integer, 19) > 0)) { + return number_type::big_integer; + } + return number_type::signed_integer; + } + // Let us check if we have a big integer (>=2**64). + static const uint8_t * two_to_sixtyfour = reinterpret_cast("18446744073709551616"); + if((digit_count > 20) || (digit_count == 20 && memcmp(src, two_to_sixtyfour, 20) >= 0)) { + return number_type::big_integer; + } + // The number is positive and smaller than 18446744073709551616 (or 2**64). + // We want values larger or equal to 9223372036854775808 to be unsigned + // integers, and the other values to be signed integers. + if((digit_count == 20) || (digit_count >= 19 && memcmp(src, smaller_big_integer, 19) >= 0)) { + return number_type::unsigned_integer; + } + return number_type::signed_integer; + } + // Hopefully, we have 'e' or 'E' or '.'. + return number_type::floating_point_number; +} + +// Never read at src_end or beyond +simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { + if(src == src_end) { return NUMBER_ERROR; } + // + // Check for minus sign + // + bool negative = (*src == '-'); + src += uint8_t(negative); + + // + // Parse the integer part. + // + uint64_t i = 0; + const uint8_t *p = src; + if(p == src_end) { return NUMBER_ERROR; } + p += parse_digit(*p, i); + bool leading_zero = (i == 0); + while ((p != src_end) && parse_digit(*p, i)) { p++; } + // no integer digits, or 0123 (zero must be solo) + if ( p == src ) { return INCORRECT_TYPE; } + if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } + + // + // Parse the decimal part. + // + int64_t exponent = 0; + bool overflow; + if (simdjson_likely((p != src_end) && (*p == '.'))) { + p++; + const uint8_t *start_decimal_digits = p; + if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits + p++; + while ((p != src_end) && parse_digit(*p, i)) { p++; } + exponent = -(p - start_decimal_digits); + + // Overflow check. More than 19 digits (minus the decimal) may be overflow. + overflow = p-src-1 > 19; + if (simdjson_unlikely(overflow && leading_zero)) { + // Skip leading 0.00000 and see if it still overflows + const uint8_t *start_digits = src + 2; + while (*start_digits == '0') { start_digits++; } + overflow = start_digits-src > 19; + } + } else { + overflow = p-src > 19; + } + + // + // Parse the exponent + // + if ((p != src_end) && (*p == 'e' || *p == 'E')) { + p++; + if(p == src_end) { return NUMBER_ERROR; } + bool exp_neg = *p == '-'; + p += exp_neg || *p == '+'; + + uint64_t exp = 0; + const uint8_t *start_exp_digits = p; + while ((p != src_end) && parse_digit(*p, exp)) { p++; } + // no exp digits, or 20+ exp digits + if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + + exponent += exp_neg ? 0-exp : exp; + } + + if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } + + overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; + + // + // Assemble (or slow-parse) the float + // + double d; + if (simdjson_likely(!overflow)) { + if (compute_float_64(exponent, i, negative, d)) { return d; } + } + if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { + return NUMBER_ERROR; + } + return d; +} + +simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { + // + // Check for minus sign + // + bool negative = (*(src + 1) == '-'); + src += uint8_t(negative) + 1; + + // + // Parse the integer part. + // + uint64_t i = 0; + const uint8_t *p = src; + p += parse_digit(*p, i); + bool leading_zero = (i == 0); + while (parse_digit(*p, i)) { p++; } + // no integer digits, or 0123 (zero must be solo) + if ( p == src ) { return INCORRECT_TYPE; } + if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } + + // + // Parse the decimal part. + // + int64_t exponent = 0; + bool overflow; + if (simdjson_likely(*p == '.')) { + p++; + const uint8_t *start_decimal_digits = p; + if (!parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits + p++; + while (parse_digit(*p, i)) { p++; } + exponent = -(p - start_decimal_digits); + + // Overflow check. More than 19 digits (minus the decimal) may be overflow. + overflow = p-src-1 > 19; + if (simdjson_unlikely(overflow && leading_zero)) { + // Skip leading 0.00000 and see if it still overflows + const uint8_t *start_digits = src + 2; + while (*start_digits == '0') { start_digits++; } + overflow = p-start_digits > 19; + } + } else { + overflow = p-src > 19; + } + + // + // Parse the exponent + // + if (*p == 'e' || *p == 'E') { + p++; + bool exp_neg = *p == '-'; + p += exp_neg || *p == '+'; + + uint64_t exp = 0; + const uint8_t *start_exp_digits = p; + while (parse_digit(*p, exp)) { p++; } + // no exp digits, or 20+ exp digits + if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + + exponent += exp_neg ? 0-exp : exp; + } + + if (*p != '"') { return NUMBER_ERROR; } + + overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; + + // + // Assemble (or slow-parse) the float + // + double d; + if (simdjson_likely(!overflow)) { + if (compute_float_64(exponent, i, negative, d)) { return d; } + } + if (!parse_float_fallback(src - uint8_t(negative), &d)) { + return NUMBER_ERROR; + } + return d; +} + +} // unnamed namespace +#endif // SIMDJSON_SKIPNUMBERPARSING + +} // namespace numberparsing + +inline std::ostream& operator<<(std::ostream& out, number_type type) noexcept { + switch (type) { + case number_type::signed_integer: out << "integer in [-9223372036854775808,9223372036854775808)"; break; + case number_type::unsigned_integer: out << "unsigned integer in [9223372036854775808,18446744073709551616)"; break; + case number_type::floating_point_number: out << "floating-point number (binary64)"; break; + case number_type::big_integer: out << "big integer"; break; + default: SIMDJSON_UNREACHABLE(); + } + return out; +} + +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_NUMBERPARSING_H diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/amalgamated.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/amalgamated.h new file mode 100644 index 000000000000..e1fac8d2ab2e --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/amalgamated.h @@ -0,0 +1,48 @@ +#if defined(SIMDJSON_CONDITIONAL_INCLUDE) && !defined(SIMDJSON_GENERIC_ONDEMAND_DEPENDENCIES_H) +#error simdjson/generic/ondemand/dependencies.h must be included before simdjson/generic/ondemand/amalgamated.h! +#endif + +// Stuff other things depend on +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/deserialize.h" +#include "simdjson/generic/ondemand/value_iterator.h" +#include "simdjson/generic/ondemand/value.h" +#include "simdjson/generic/ondemand/logger.h" +#include "simdjson/generic/ondemand/token_iterator.h" +#include "simdjson/generic/ondemand/json_iterator.h" +#include "simdjson/generic/ondemand/json_type.h" +#include "simdjson/generic/ondemand/raw_json_string.h" +#include "simdjson/generic/ondemand/parser.h" + +// All other declarations +#include "simdjson/generic/ondemand/array.h" +#include "simdjson/generic/ondemand/array_iterator.h" +#include "simdjson/generic/ondemand/document.h" +#include "simdjson/generic/ondemand/document_stream.h" +#include "simdjson/generic/ondemand/field.h" +#include "simdjson/generic/ondemand/object.h" +#include "simdjson/generic/ondemand/object_iterator.h" +#include "simdjson/generic/ondemand/serialization.h" + +// Deserialization for standard types +#include "simdjson/generic/ondemand/std_deserialize.h" + +// Inline definitions +#include "simdjson/generic/ondemand/array-inl.h" +#include "simdjson/generic/ondemand/array_iterator-inl.h" +#include "simdjson/generic/ondemand/value-inl.h" +#include "simdjson/generic/ondemand/document-inl.h" +#include "simdjson/generic/ondemand/document_stream-inl.h" +#include "simdjson/generic/ondemand/field-inl.h" +#include "simdjson/generic/ondemand/json_iterator-inl.h" +#include "simdjson/generic/ondemand/json_type-inl.h" +#include "simdjson/generic/ondemand/logger-inl.h" +#include "simdjson/generic/ondemand/object-inl.h" +#include "simdjson/generic/ondemand/object_iterator-inl.h" +#include "simdjson/generic/ondemand/parser-inl.h" +#include "simdjson/generic/ondemand/raw_json_string-inl.h" +#include "simdjson/generic/ondemand/serialization-inl.h" +#include "simdjson/generic/ondemand/token_iterator-inl.h" +#include "simdjson/generic/ondemand/value_iterator-inl.h" + + diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/array-inl.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/array-inl.h new file mode 100644 index 000000000000..e7bc89d9b13b --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/array-inl.h @@ -0,0 +1,237 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_ARRAY_INL_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_ARRAY_INL_H +#include "simdjson/jsonpathutil.h" +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/array.h" +#include "simdjson/generic/ondemand/array_iterator-inl.h" +#include "simdjson/generic/ondemand/json_iterator.h" +#include "simdjson/generic/ondemand/value.h" +#include "simdjson/generic/ondemand/value_iterator-inl.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +// +// ### Live States +// +// While iterating or looking up values, depth >= iter->depth. at_start may vary. Error is +// always SUCCESS: +// +// - Start: This is the state when the array is first found and the iterator is just past the `{`. +// In this state, at_start == true. +// - Next: After we hand a scalar value to the user, or an array/object which they then fully +// iterate over, the iterator is at the `,` before the next value (or `]`). In this state, +// depth == iter->depth, at_start == false, and error == SUCCESS. +// - Unfinished Business: When we hand an array/object to the user which they do not fully +// iterate over, we need to finish that iteration by skipping child values until we reach the +// Next state. In this state, depth > iter->depth, at_start == false, and error == SUCCESS. +// +// ## Error States +// +// In error states, we will yield exactly one more value before stopping. iter->depth == depth +// and at_start is always false. We decrement after yielding the error, moving to the Finished +// state. +// +// - Chained Error: When the array iterator is part of an error chain--for example, in +// `for (auto tweet : doc["tweets"])`, where the tweet element may be missing or not be an +// array--we yield that error in the loop, exactly once. In this state, error != SUCCESS and +// iter->depth == depth, and at_start == false. We decrement depth when we yield the error. +// - Missing Comma Error: When the iterator ++ method discovers there is no comma between elements, +// we flag that as an error and treat it exactly the same as a Chained Error. In this state, +// error == TAPE_ERROR, iter->depth == depth, and at_start == false. +// +// ## Terminal State +// +// The terminal state has iter->depth < depth. at_start is always false. +// +// - Finished: When we have reached a `]` or have reported an error, we are finished. We signal this +// by decrementing depth. In this state, iter->depth < depth, at_start == false, and +// error == SUCCESS. +// + +simdjson_inline array::array(const value_iterator &_iter) noexcept + : iter{_iter} +{ +} + +simdjson_inline simdjson_result array::start(value_iterator &iter) noexcept { + // We don't need to know if the array is empty to start iteration, but we do want to know if there + // is an error--thus `simdjson_unused`. + simdjson_unused bool has_value; + SIMDJSON_TRY( iter.start_array().get(has_value) ); + return array(iter); +} +simdjson_inline simdjson_result array::start_root(value_iterator &iter) noexcept { + simdjson_unused bool has_value; + SIMDJSON_TRY( iter.start_root_array().get(has_value) ); + return array(iter); +} +simdjson_inline simdjson_result array::started(value_iterator &iter) noexcept { + bool has_value; + SIMDJSON_TRY(iter.started_array().get(has_value)); + return array(iter); +} + +simdjson_inline simdjson_result array::begin() noexcept { +#if SIMDJSON_DEVELOPMENT_CHECKS + if (!iter.is_at_iterator_start()) { return OUT_OF_ORDER_ITERATION; } +#endif + return array_iterator(iter); +} +simdjson_inline simdjson_result array::end() noexcept { + return array_iterator(iter); +} +simdjson_inline error_code array::consume() noexcept { + auto error = iter.json_iter().skip_child(iter.depth()-1); + if(error) { iter.abandon(); } + return error; +} + +simdjson_inline simdjson_result array::raw_json() noexcept { + const uint8_t * starting_point{iter.peek_start()}; + auto error = consume(); + if(error) { return error; } + // After 'consume()', we could be left pointing just beyond the document, but that + // is ok because we are not going to dereference the final pointer position, we just + // use it to compute the length in bytes. + const uint8_t * final_point{iter._json_iter->unsafe_pointer()}; + return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); +} + +SIMDJSON_PUSH_DISABLE_WARNINGS +SIMDJSON_DISABLE_STRICT_OVERFLOW_WARNING +simdjson_inline simdjson_result array::count_elements() & noexcept { + size_t count{0}; + // Important: we do not consume any of the values. + for(simdjson_unused auto v : *this) { count++; } + // The above loop will always succeed, but we want to report errors. + if(iter.error()) { return iter.error(); } + // We need to move back at the start because we expect users to iterate through + // the array after counting the number of elements. + iter.reset_array(); + return count; +} +SIMDJSON_POP_DISABLE_WARNINGS + +simdjson_inline simdjson_result array::is_empty() & noexcept { + bool is_not_empty; + auto error = iter.reset_array().get(is_not_empty); + if(error) { return error; } + return !is_not_empty; +} + +inline simdjson_result array::reset() & noexcept { + return iter.reset_array(); +} + +inline simdjson_result array::at_pointer(std::string_view json_pointer) noexcept { + if (json_pointer[0] != '/') { return INVALID_JSON_POINTER; } + json_pointer = json_pointer.substr(1); + // - means "the append position" or "the element after the end of the array" + // We don't support this, because we're returning a real element, not a position. + if (json_pointer == "-") { return INDEX_OUT_OF_BOUNDS; } + + // Read the array index + size_t array_index = 0; + size_t i; + for (i = 0; i < json_pointer.length() && json_pointer[i] != '/'; i++) { + uint8_t digit = uint8_t(json_pointer[i] - '0'); + // Check for non-digit in array index. If it's there, we're trying to get a field in an object + if (digit > 9) { return INCORRECT_TYPE; } + array_index = array_index*10 + digit; + } + + // 0 followed by other digits is invalid + if (i > 1 && json_pointer[0] == '0') { return INVALID_JSON_POINTER; } // "JSON pointer array index has other characters after 0" + + // Empty string is invalid; so is a "/" with no digits before it + if (i == 0) { return INVALID_JSON_POINTER; } // "Empty string in JSON pointer array index" + // Get the child + auto child = at(array_index); + // If there is an error, it ends here + if(child.error()) { + return child; + } + + // If there is a /, we're not done yet, call recursively. + if (i < json_pointer.length()) { + child = child.at_pointer(json_pointer.substr(i)); + } + return child; +} + +inline simdjson_result array::at_path(std::string_view json_path) noexcept { + auto json_pointer = json_path_to_pointer_conversion(json_path); + if (json_pointer == "-1") { return INVALID_JSON_POINTER; } + return at_pointer(json_pointer); +} + +simdjson_inline simdjson_result array::at(size_t index) noexcept { + size_t i = 0; + for (auto value : *this) { + if (i == index) { return value; } + i++; + } + return INDEX_OUT_OF_BOUNDS; +} + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +simdjson_inline simdjson_result::simdjson_result( + SIMDJSON_IMPLEMENTATION::ondemand::array &&value +) noexcept + : implementation_simdjson_result_base( + std::forward(value) + ) +{ +} +simdjson_inline simdjson_result::simdjson_result( + error_code error +) noexcept + : implementation_simdjson_result_base(error) +{ +} + +simdjson_inline simdjson_result simdjson_result::begin() noexcept { + if (error()) { return error(); } + return first.begin(); +} +simdjson_inline simdjson_result simdjson_result::end() noexcept { + if (error()) { return error(); } + return first.end(); +} +simdjson_inline simdjson_result simdjson_result::count_elements() & noexcept { + if (error()) { return error(); } + return first.count_elements(); +} +simdjson_inline simdjson_result simdjson_result::is_empty() & noexcept { + if (error()) { return error(); } + return first.is_empty(); +} +simdjson_inline simdjson_result simdjson_result::at(size_t index) noexcept { + if (error()) { return error(); } + return first.at(index); +} +simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { + if (error()) { return error(); } + return first.at_pointer(json_pointer); +} +simdjson_inline simdjson_result simdjson_result::at_path(std::string_view json_path) noexcept { + if (error()) { return error(); } + return first.at_path(json_path); +} +simdjson_inline simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_ARRAY_INL_H diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/array.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/array.h new file mode 100644 index 000000000000..0d1cbbd4e879 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/array.h @@ -0,0 +1,217 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_ARRAY_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_ARRAY_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/implementation_simdjson_result_base.h" +#include "simdjson/generic/ondemand/value_iterator.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +/** + * A forward-only JSON array. + */ +class array { +public: + /** + * Create a new invalid array. + * + * Exists so you can declare a variable and later assign to it before use. + */ + simdjson_inline array() noexcept = default; + + /** + * Begin array iteration. + * + * Part of the std::iterable interface. + */ + simdjson_inline simdjson_result begin() noexcept; + /** + * Sentinel representing the end of the array. + * + * Part of the std::iterable interface. + */ + simdjson_inline simdjson_result end() noexcept; + /** + * This method scans the array and counts the number of elements. + * The count_elements method should always be called before you have begun + * iterating through the array: it is expected that you are pointing at + * the beginning of the array. + * The runtime complexity is linear in the size of the array. After + * calling this function, if successful, the array is 'rewinded' at its + * beginning as if it had never been accessed. If the JSON is malformed (e.g., + * there is a missing comma), then an error is returned and it is no longer + * safe to continue. Note that count_elements() does not validate the JSON values, + * only the structure of the array. + * + * To check that an array is empty, it is more performant to use + * the is_empty() method. + */ + simdjson_inline simdjson_result count_elements() & noexcept; + /** + * This method scans the beginning of the array and checks whether the + * array is empty. + * The runtime complexity is constant time. After + * calling this function, if successful, the array is 'rewinded' at its + * beginning as if it had never been accessed. If the JSON is malformed (e.g., + * there is a missing comma), then an error is returned and it is no longer + * safe to continue. + */ + simdjson_inline simdjson_result is_empty() & noexcept; + /** + * Reset the iterator so that we are pointing back at the + * beginning of the array. You should still consume values only once even if you + * can iterate through the array more than once. If you unescape a string + * within the array more than once, you have unsafe code. Note that rewinding + * an array means that you may need to reparse it anew: it is not a free + * operation. + * + * @returns true if the array contains some elements (not empty) + */ + inline simdjson_result reset() & noexcept; + /** + * Get the value associated with the given JSON pointer. We use the RFC 6901 + * https://tools.ietf.org/html/rfc6901 standard, interpreting the current node + * as the root of its own JSON document. + * + * ondemand::parser parser; + * auto json = R"([ { "foo": { "a": [ 10, 20, 30 ] }} ])"_padded; + * auto doc = parser.iterate(json); + * doc.at_pointer("/0/foo/a/1") == 20 + * + * Note that at_pointer() called on the document automatically calls the document's rewind + * method between each call. It invalidates all previously accessed arrays, objects and values + * that have not been consumed. Yet it is not the case when calling at_pointer on an array + * instance: there is no rewind and no invalidation. + * + * You may only call at_pointer on an array after it has been created, but before it has + * been first accessed. When calling at_pointer on an array, the pointer is advanced to + * the location indicated by the JSON pointer (in case of success). It is no longer possible + * to call at_pointer on the same array. + * + * Also note that at_pointer() relies on find_field() which implies that we do not unescape keys when matching. + * + * @return The value associated with the given JSON pointer, or: + * - NO_SUCH_FIELD if a field does not exist in an object + * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length + * - INCORRECT_TYPE if a non-integer is used to access an array + * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed + */ + inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; + + /** + * Get the value associated with the given JSONPath expression. We only support + * JSONPath queries that trivially convertible to JSON Pointer queries: key + * names and array indices. + * + * https://datatracker.ietf.org/doc/html/draft-normington-jsonpath-00 + * + * @return The value associated with the given JSONPath expression, or: + * - INVALID_JSON_POINTER if the JSONPath to JSON Pointer conversion fails + * - NO_SUCH_FIELD if a field does not exist in an object + * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length + * - INCORRECT_TYPE if a non-integer is used to access an array + */ + inline simdjson_result at_path(std::string_view json_path) noexcept; + + /** + * Consumes the array and returns a string_view instance corresponding to the + * array as represented in JSON. It points inside the original document. + */ + simdjson_inline simdjson_result raw_json() noexcept; + + /** + * Get the value at the given index. This function has linear-time complexity. + * This function should only be called once on an array instance since the array iterator is not reset between each call. + * + * @return The value at the given index, or: + * - INDEX_OUT_OF_BOUNDS if the array index is larger than an array length + */ + simdjson_inline simdjson_result at(size_t index) noexcept; +protected: + /** + * Go to the end of the array, no matter where you are right now. + */ + simdjson_inline error_code consume() noexcept; + + /** + * Begin array iteration. + * + * @param iter The iterator. Must be where the initial [ is expected. Will be *moved* into the + * resulting array. + * @error INCORRECT_TYPE if the iterator is not at [. + */ + static simdjson_inline simdjson_result start(value_iterator &iter) noexcept; + /** + * Begin array iteration from the root. + * + * @param iter The iterator. Must be where the initial [ is expected. Will be *moved* into the + * resulting array. + * @error INCORRECT_TYPE if the iterator is not at [. + * @error TAPE_ERROR if there is no closing ] at the end of the document. + */ + static simdjson_inline simdjson_result start_root(value_iterator &iter) noexcept; + /** + * Begin array iteration. + * + * This version of the method should be called after the initial [ has been verified, and is + * intended for use by switch statements that check the type of a value. + * + * @param iter The iterator. Must be after the initial [. Will be *moved* into the resulting array. + */ + static simdjson_inline simdjson_result started(value_iterator &iter) noexcept; + + /** + * Create an array at the given Internal array creation. Call array::start() or array::started() instead of this. + * + * @param iter The iterator. Must either be at the start of the first element with iter.is_alive() + * == true, or past the [] with is_alive() == false if the array is empty. Will be *moved* + * into the resulting array. + */ + simdjson_inline array(const value_iterator &iter) noexcept; + + /** + * Iterator marking current position. + * + * iter.is_alive() == false indicates iteration is complete. + */ + value_iterator iter{}; + + friend class value; + friend class document; + friend struct simdjson_result; + friend struct simdjson_result; + friend class array_iterator; +}; + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +template<> +struct simdjson_result : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::array &&value) noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + simdjson_inline simdjson_result() noexcept = default; + + simdjson_inline simdjson_result begin() noexcept; + simdjson_inline simdjson_result end() noexcept; + inline simdjson_result count_elements() & noexcept; + inline simdjson_result is_empty() & noexcept; + inline simdjson_result reset() & noexcept; + simdjson_inline simdjson_result at(size_t index) noexcept; + simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; + simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; + simdjson_inline simdjson_result raw_json() noexcept; + +}; + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_ARRAY_H diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/array_iterator-inl.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/array_iterator-inl.h new file mode 100644 index 000000000000..6e4ba8140dea --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/array_iterator-inl.h @@ -0,0 +1,78 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_ARRAY_ITERATOR_INL_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_ARRAY_ITERATOR_INL_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/array_iterator.h" +#include "simdjson/generic/ondemand/value-inl.h" +#include "simdjson/generic/ondemand/value_iterator-inl.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +simdjson_inline array_iterator::array_iterator(const value_iterator &_iter) noexcept + : iter{_iter} +{} + +simdjson_inline simdjson_result array_iterator::operator*() noexcept { + if (iter.error()) { iter.abandon(); return iter.error(); } + return value(iter.child()); +} +simdjson_inline bool array_iterator::operator==(const array_iterator &other) const noexcept { + return !(*this != other); +} +simdjson_inline bool array_iterator::operator!=(const array_iterator &) const noexcept { + return iter.is_open(); +} +simdjson_inline array_iterator &array_iterator::operator++() noexcept { + error_code error; + // PERF NOTE this is a safety rail ... users should exit loops as soon as they receive an error, so we'll never get here. + // However, it does not seem to make a perf difference, so we add it out of an abundance of caution. + if (( error = iter.error() )) { return *this; } + if (( error = iter.skip_child() )) { return *this; } + if (( error = iter.has_next_element().error() )) { return *this; } + return *this; +} + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +simdjson_inline simdjson_result::simdjson_result( + SIMDJSON_IMPLEMENTATION::ondemand::array_iterator &&value +) noexcept + : SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base(std::forward(value)) +{ + first.iter.assert_is_valid(); +} +simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept + : SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base({}, error) +{ +} + +simdjson_inline simdjson_result simdjson_result::operator*() noexcept { + if (error()) { return error(); } + return *first; +} +simdjson_inline bool simdjson_result::operator==(const simdjson_result &other) const noexcept { + if (!first.iter.is_valid()) { return !error(); } + return first == other.first; +} +simdjson_inline bool simdjson_result::operator!=(const simdjson_result &other) const noexcept { + if (!first.iter.is_valid()) { return error(); } + return first != other.first; +} +simdjson_inline simdjson_result &simdjson_result::operator++() noexcept { + // Clear the error if there is one, so we don't yield it twice + if (error()) { second = SUCCESS; return *this; } + ++(first); + return *this; +} + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_ARRAY_ITERATOR_INL_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/array_iterator.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/array_iterator.h new file mode 100644 index 000000000000..0957be9c79cd --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/array_iterator.h @@ -0,0 +1,96 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_ARRAY_ITERATOR_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_ARRAY_ITERATOR_H +#include "simdjson/generic/implementation_simdjson_result_base.h" +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/value_iterator.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +/** + * A forward-only JSON array. + * + * This is an input_iterator, meaning: + * - It is forward-only + * - * must be called exactly once per element. + * - ++ must be called exactly once in between each * (*, ++, *, ++, * ...) + */ +class array_iterator { +public: + /** Create a new, invalid array iterator. */ + simdjson_inline array_iterator() noexcept = default; + + // + // Iterator interface + // + + /** + * Get the current element. + * + * Part of the std::iterator interface. + */ + simdjson_inline simdjson_result operator*() noexcept; // MUST ONLY BE CALLED ONCE PER ITERATION. + /** + * Check if we are at the end of the JSON. + * + * Part of the std::iterator interface. + * + * @return true if there are no more elements in the JSON array. + */ + simdjson_inline bool operator==(const array_iterator &) const noexcept; + /** + * Check if there are more elements in the JSON array. + * + * Part of the std::iterator interface. + * + * @return true if there are more elements in the JSON array. + */ + simdjson_inline bool operator!=(const array_iterator &) const noexcept; + /** + * Move to the next element. + * + * Part of the std::iterator interface. + */ + simdjson_inline array_iterator &operator++() noexcept; + +private: + value_iterator iter{}; + + simdjson_inline array_iterator(const value_iterator &iter) noexcept; + + friend class array; + friend class value; + friend struct simdjson_result; +}; + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +template<> +struct simdjson_result : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::array_iterator &&value) noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + simdjson_inline simdjson_result() noexcept = default; + + // + // Iterator interface + // + + simdjson_inline simdjson_result operator*() noexcept; // MUST ONLY BE CALLED ONCE PER ITERATION. + simdjson_inline bool operator==(const simdjson_result &) const noexcept; + simdjson_inline bool operator!=(const simdjson_result &) const noexcept; + simdjson_inline simdjson_result &operator++() noexcept; +}; + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_ARRAY_ITERATOR_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/base.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/base.h new file mode 100644 index 000000000000..89bd0c01bba0 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/base.h @@ -0,0 +1,47 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_BASE_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_BASE_H +#include "simdjson/generic/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +/** + * A fast, simple, DOM-like interface that parses JSON as you use it. + * + * Designed for maximum speed and a lower memory profile. + */ +namespace ondemand { + +/** Represents the depth of a JSON value (number of nested arrays/objects). */ +using depth_t = int32_t; + +/** @copydoc simdjson::SIMDJSON_IMPLEMENTATION::number_type */ +using number_type = simdjson::SIMDJSON_IMPLEMENTATION::number_type; + +/** @private Position in the JSON buffer indexes */ +using token_position = const uint32_t *; + +class array; +class array_iterator; +class document; +class document_reference; +class document_stream; +class field; +class json_iterator; +enum class json_type; +struct number; +class object; +class object_iterator; +class parser; +class raw_json_string; +class token_iterator; +class value; +class value_iterator; + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_BASE_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/dependencies.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/dependencies.h new file mode 100644 index 000000000000..3c4fdc3a62ef --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/dependencies.h @@ -0,0 +1,18 @@ +#ifdef SIMDJSON_CONDITIONAL_INCLUDE +#error simdjson/generic/ondemand/dependencies.h must be included before defining SIMDJSON_CONDITIONAL_INCLUDE! +#endif + +#ifndef SIMDJSON_GENERIC_ONDEMAND_DEPENDENCIES_H +#define SIMDJSON_GENERIC_ONDEMAND_DEPENDENCIES_H + +// Internal headers needed for ondemand generics. +// All includes not under simdjson/generic/ondemand must be here! +// Otherwise, amalgamation will fail. +#include "simdjson/dom/base.h" // for MINIMAL_DOCUMENT_CAPACITY +#include "simdjson/implementation.h" +#include "simdjson/padded_string.h" +#include "simdjson/padded_string_view.h" +#include "simdjson/internal/dom_parser_implementation.h" +#include "simdjson/jsonpathutil.h" + +#endif // SIMDJSON_GENERIC_ONDEMAND_DEPENDENCIES_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/deserialize.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/deserialize.h new file mode 100644 index 000000000000..02abed33aba2 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/deserialize.h @@ -0,0 +1,123 @@ +#if SIMDJSON_SUPPORTS_DESERIALIZATION + +#ifndef SIMDJSON_ONDEMAND_DESERIALIZE_H +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_ONDEMAND_DESERIALIZE_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/array.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include +namespace simdjson { + +namespace tag_invoke_fn_ns { +void tag_invoke(); + +struct tag_invoke_fn { + template + requires requires(Tag tag, Args &&...args) { + tag_invoke(std::forward(tag), std::forward(args)...); + } + constexpr auto operator()(Tag tag, Args &&...args) const + noexcept(noexcept(tag_invoke(std::forward(tag), + std::forward(args)...))) + -> decltype(tag_invoke(std::forward(tag), + std::forward(args)...)) { + return tag_invoke(std::forward(tag), std::forward(args)...); + } +}; +} // namespace tag_invoke_fn_ns + +inline namespace tag_invoke_ns { +inline constexpr tag_invoke_fn_ns::tag_invoke_fn tag_invoke = {}; +} // namespace tag_invoke_ns + +template +concept tag_invocable = requires(Tag tag, Args... args) { + tag_invoke(std::forward(tag), std::forward(args)...); +}; + +template +concept nothrow_tag_invocable = + tag_invocable && requires(Tag tag, Args... args) { + { + tag_invoke(std::forward(tag), std::forward(args)...) + } noexcept; + }; + +template +using tag_invoke_result = + std::invoke_result; + +template +using tag_invoke_result_t = + std::invoke_result_t; + +template using tag_t = std::decay_t; + + +struct deserialize_tag; + +/// These types are deserializable in a built-in way +template struct is_builtin_deserializable : std::false_type {}; +template <> struct is_builtin_deserializable : std::true_type {}; +template <> struct is_builtin_deserializable : std::true_type {}; +template <> struct is_builtin_deserializable : std::true_type {}; +template <> struct is_builtin_deserializable : std::true_type {}; +template <> struct is_builtin_deserializable : std::true_type {}; +template <> struct is_builtin_deserializable : std::true_type {}; +template <> struct is_builtin_deserializable : std::true_type {}; +template <> struct is_builtin_deserializable : std::true_type {}; +template <> struct is_builtin_deserializable : std::true_type {}; + +template +concept is_builtin_deserializable_v = is_builtin_deserializable::value; + +template +concept custom_deserializable = tag_invocable; + +template +concept deserializable = custom_deserializable || is_builtin_deserializable_v; + +template +concept nothrow_custom_deserializable = nothrow_tag_invocable; + +// built-in types are noexcept and if an error happens, the value simply gets ignored and the error is returned. +template +concept nothrow_deserializable = nothrow_custom_deserializable || is_builtin_deserializable_v; + +/// Deserialize Tag +inline constexpr struct deserialize_tag { + using value_type = SIMDJSON_IMPLEMENTATION::ondemand::value; + using document_type = SIMDJSON_IMPLEMENTATION::ondemand::document; + using document_reference_type = SIMDJSON_IMPLEMENTATION::ondemand::document_reference; + + // Customization Point for value + template + requires custom_deserializable + [[nodiscard]] constexpr /* error_code */ auto operator()(value_type &object, T& output) const noexcept(nothrow_custom_deserializable) { + return tag_invoke(*this, object, output); + } + + // Customization Point for document + template + requires custom_deserializable + [[nodiscard]] constexpr /* error_code */ auto operator()(document_type &object, T& output) const noexcept(nothrow_custom_deserializable) { + return tag_invoke(*this, object, output); + } + + // Customization Point for document reference + template + requires custom_deserializable + [[nodiscard]] constexpr /* error_code */ auto operator()(document_reference_type &object, T& output) const noexcept(nothrow_custom_deserializable) { + return tag_invoke(*this, object, output); + } + + +} deserialize{}; + +} // namespace simdjson + +#endif // SIMDJSON_ONDEMAND_DESERIALIZE_H +#endif // SIMDJSON_SUPPORTS_DESERIALIZATION + diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/document-inl.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/document-inl.h new file mode 100644 index 000000000000..ec889d35ba78 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/document-inl.h @@ -0,0 +1,965 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_DOCUMENT_INL_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_DOCUMENT_INL_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/array_iterator.h" +#include "simdjson/generic/ondemand/document.h" +#include "simdjson/generic/ondemand/json_type.h" +#include "simdjson/generic/ondemand/raw_json_string.h" +#include "simdjson/generic/ondemand/value.h" +#include "simdjson/generic/ondemand/value-inl.h" +#include "simdjson/generic/ondemand/array-inl.h" +#include "simdjson/generic/ondemand/json_iterator-inl.h" +#include "simdjson/generic/ondemand/object-inl.h" +#include "simdjson/generic/ondemand/value_iterator-inl.h" +#include "simdjson/generic/ondemand/deserialize.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +simdjson_inline document::document(ondemand::json_iterator &&_iter) noexcept + : iter{std::forward(_iter)} +{ + logger::log_start_value(iter, "document"); +} + +simdjson_inline document document::start(json_iterator &&iter) noexcept { + return document(std::forward(iter)); +} + +inline void document::rewind() noexcept { + iter.rewind(); +} + +inline std::string document::to_debug_string() noexcept { + return iter.to_string(); +} + +inline simdjson_result document::current_location() const noexcept { + return iter.current_location(); +} + +inline int32_t document::current_depth() const noexcept { + return iter.depth(); +} + +inline bool document::at_end() const noexcept { + return iter.at_end(); +} + + +inline bool document::is_alive() noexcept { + return iter.is_alive(); +} +simdjson_inline value_iterator document::resume_value_iterator() noexcept { + return value_iterator(&iter, 1, iter.root_position()); +} +simdjson_inline value_iterator document::get_root_value_iterator() noexcept { + return resume_value_iterator(); +} +simdjson_inline simdjson_result document::start_or_resume_object() noexcept { + if (iter.at_root()) { + return get_object(); + } else { + return object::resume(resume_value_iterator()); + } +} +simdjson_inline simdjson_result document::get_value() noexcept { + // Make sure we start any arrays or objects before returning, so that start_root_() + // gets called. + + // It is the convention throughout the code that the macro `SIMDJSON_DEVELOPMENT_CHECKS` determines whether + // we check for OUT_OF_ORDER_ITERATION. Proper on::demand code should never trigger this error. +#if SIMDJSON_DEVELOPMENT_CHECKS + if (!iter.at_root()) { return OUT_OF_ORDER_ITERATION; } +#endif + // assert_at_root() serves two purposes: in Debug mode, whether or not + // SIMDJSON_DEVELOPMENT_CHECKS is set or not, it checks that we are at the root of + // the document (this will typically be redundant). In release mode, it generates + // SIMDJSON_ASSUME statements to allow the compiler to make assumptions. + iter.assert_at_root(); + switch (*iter.peek()) { + case '[': { + // The following lines check that the document ends with ]. + auto value_iterator = get_root_value_iterator(); + auto error = value_iterator.check_root_array(); + if(error) { return error; } + return value(get_root_value_iterator()); + } + case '{': { + // The following lines would check that the document ends with }. + auto value_iterator = get_root_value_iterator(); + auto error = value_iterator.check_root_object(); + if(error) { return error; } + return value(get_root_value_iterator()); + } + default: + // Unfortunately, scalar documents are a special case in simdjson and they cannot + // be safely converted to value instances. + return SCALAR_DOCUMENT_AS_VALUE; + } +} +simdjson_inline simdjson_result document::get_array() & noexcept { + auto value = get_root_value_iterator(); + return array::start_root(value); +} +simdjson_inline simdjson_result document::get_object() & noexcept { + auto value = get_root_value_iterator(); + return object::start_root(value); +} + +/** + * We decided that calling 'get_double()' on the JSON document '1.233 blabla' should + * give an error, so we check for trailing content. We want to disallow trailing + * content. + * Thus, in several implementations below, we pass a 'true' parameter value to + * a get_root_value_iterator() method: this indicates that we disallow trailing content. + */ + +simdjson_inline simdjson_result document::get_uint64() noexcept { + return get_root_value_iterator().get_root_uint64(true); +} +simdjson_inline simdjson_result document::get_uint64_in_string() noexcept { + return get_root_value_iterator().get_root_uint64_in_string(true); +} +simdjson_inline simdjson_result document::get_int64() noexcept { + return get_root_value_iterator().get_root_int64(true); +} +simdjson_inline simdjson_result document::get_int64_in_string() noexcept { + return get_root_value_iterator().get_root_int64_in_string(true); +} +simdjson_inline simdjson_result document::get_double() noexcept { + return get_root_value_iterator().get_root_double(true); +} +simdjson_inline simdjson_result document::get_double_in_string() noexcept { + return get_root_value_iterator().get_root_double_in_string(true); +} +simdjson_inline simdjson_result document::get_string(bool allow_replacement) noexcept { + return get_root_value_iterator().get_root_string(true, allow_replacement); +} +template +simdjson_inline error_code document::get_string(string_type& receiver, bool allow_replacement) noexcept { + return get_root_value_iterator().get_root_string(receiver, true, allow_replacement); +} +simdjson_inline simdjson_result document::get_wobbly_string() noexcept { + return get_root_value_iterator().get_root_wobbly_string(true); +} +simdjson_inline simdjson_result document::get_raw_json_string() noexcept { + return get_root_value_iterator().get_root_raw_json_string(true); +} +simdjson_inline simdjson_result document::get_bool() noexcept { + return get_root_value_iterator().get_root_bool(true); +} +simdjson_inline simdjson_result document::is_null() noexcept { + return get_root_value_iterator().is_root_null(true); +} + +template<> simdjson_inline simdjson_result document::get() & noexcept { return get_array(); } +template<> simdjson_inline simdjson_result document::get() & noexcept { return get_object(); } +template<> simdjson_inline simdjson_result document::get() & noexcept { return get_raw_json_string(); } +template<> simdjson_inline simdjson_result document::get() & noexcept { return get_string(false); } +template<> simdjson_inline simdjson_result document::get() & noexcept { return get_double(); } +template<> simdjson_inline simdjson_result document::get() & noexcept { return get_uint64(); } +template<> simdjson_inline simdjson_result document::get() & noexcept { return get_int64(); } +template<> simdjson_inline simdjson_result document::get() & noexcept { return get_bool(); } +template<> simdjson_inline simdjson_result document::get() & noexcept { return get_value(); } + +template<> simdjson_inline error_code document::get(array& out) & noexcept { return get_array().get(out); } +template<> simdjson_inline error_code document::get(object& out) & noexcept { return get_object().get(out); } +template<> simdjson_inline error_code document::get(raw_json_string& out) & noexcept { return get_raw_json_string().get(out); } +template<> simdjson_inline error_code document::get(std::string_view& out) & noexcept { return get_string(false).get(out); } +template<> simdjson_inline error_code document::get(double& out) & noexcept { return get_double().get(out); } +template<> simdjson_inline error_code document::get(uint64_t& out) & noexcept { return get_uint64().get(out); } +template<> simdjson_inline error_code document::get(int64_t& out) & noexcept { return get_int64().get(out); } +template<> simdjson_inline error_code document::get(bool& out) & noexcept { return get_bool().get(out); } +template<> simdjson_inline error_code document::get(value& out) & noexcept { return get_value().get(out); } + +template<> simdjson_deprecated simdjson_inline simdjson_result document::get() && noexcept { return get_raw_json_string(); } +template<> simdjson_deprecated simdjson_inline simdjson_result document::get() && noexcept { return get_string(false); } +template<> simdjson_deprecated simdjson_inline simdjson_result document::get() && noexcept { return std::forward(*this).get_double(); } +template<> simdjson_deprecated simdjson_inline simdjson_result document::get() && noexcept { return std::forward(*this).get_uint64(); } +template<> simdjson_deprecated simdjson_inline simdjson_result document::get() && noexcept { return std::forward(*this).get_int64(); } +template<> simdjson_deprecated simdjson_inline simdjson_result document::get() && noexcept { return std::forward(*this).get_bool(); } +template<> simdjson_deprecated simdjson_inline simdjson_result document::get() && noexcept { return get_value(); } + +#if SIMDJSON_EXCEPTIONS +template +simdjson_deprecated simdjson_inline document::operator T() && noexcept(false) { return get(); } +template +simdjson_inline document::operator T() & noexcept(false) { return get(); } +simdjson_inline document::operator array() & noexcept(false) { return get_array(); } +simdjson_inline document::operator object() & noexcept(false) { return get_object(); } +simdjson_inline document::operator uint64_t() noexcept(false) { return get_uint64(); } +simdjson_inline document::operator int64_t() noexcept(false) { return get_int64(); } +simdjson_inline document::operator double() noexcept(false) { return get_double(); } +simdjson_inline document::operator std::string_view() noexcept(false) { return get_string(false); } +simdjson_inline document::operator raw_json_string() noexcept(false) { return get_raw_json_string(); } +simdjson_inline document::operator bool() noexcept(false) { return get_bool(); } +simdjson_inline document::operator value() noexcept(false) { return get_value(); } + +#endif +simdjson_inline simdjson_result document::count_elements() & noexcept { + auto a = get_array(); + simdjson_result answer = a.count_elements(); + /* If there was an array, we are now left pointing at its first element. */ + if(answer.error() == SUCCESS) { rewind(); } + return answer; +} +simdjson_inline simdjson_result document::count_fields() & noexcept { + auto a = get_object(); + simdjson_result answer = a.count_fields(); + /* If there was an object, we are now left pointing at its first element. */ + if(answer.error() == SUCCESS) { rewind(); } + return answer; +} +simdjson_inline simdjson_result document::at(size_t index) & noexcept { + auto a = get_array(); + return a.at(index); +} +simdjson_inline simdjson_result document::begin() & noexcept { + return get_array().begin(); +} +simdjson_inline simdjson_result document::end() & noexcept { + return {}; +} + +simdjson_inline simdjson_result document::find_field(std::string_view key) & noexcept { + return start_or_resume_object().find_field(key); +} +simdjson_inline simdjson_result document::find_field(const char *key) & noexcept { + return start_or_resume_object().find_field(key); +} +simdjson_inline simdjson_result document::find_field_unordered(std::string_view key) & noexcept { + return start_or_resume_object().find_field_unordered(key); +} +simdjson_inline simdjson_result document::find_field_unordered(const char *key) & noexcept { + return start_or_resume_object().find_field_unordered(key); +} +simdjson_inline simdjson_result document::operator[](std::string_view key) & noexcept { + return start_or_resume_object()[key]; +} +simdjson_inline simdjson_result document::operator[](const char *key) & noexcept { + return start_or_resume_object()[key]; +} + +simdjson_inline error_code document::consume() noexcept { + bool scalar = false; + auto error = is_scalar().get(scalar); + if(error) { return error; } + if(scalar) { + iter.return_current_and_advance(); + return SUCCESS; + } + error = iter.skip_child(0); + if(error) { iter.abandon(); } + return error; +} + +simdjson_inline simdjson_result document::raw_json() noexcept { + auto _iter = get_root_value_iterator(); + const uint8_t * starting_point{_iter.peek_start()}; + auto error = consume(); + if(error) { return error; } + // After 'consume()', we could be left pointing just beyond the document, but that + // is ok because we are not going to dereference the final pointer position, we just + // use it to compute the length in bytes. + const uint8_t * final_point{iter.unsafe_pointer()}; + return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); +} + +simdjson_inline simdjson_result document::type() noexcept { + return get_root_value_iterator().type(); +} + +simdjson_inline simdjson_result document::is_scalar() noexcept { + // For more speed, we could do: + // return iter.is_single_token(); + json_type this_type; + auto error = type().get(this_type); + if(error) { return error; } + return ! ((this_type == json_type::array) || (this_type == json_type::object)); +} + +simdjson_inline simdjson_result document::is_string() noexcept { + json_type this_type; + auto error = type().get(this_type); + if(error) { return error; } + return (this_type == json_type::string); +} + +simdjson_inline bool document::is_negative() noexcept { + return get_root_value_iterator().is_root_negative(); +} + +simdjson_inline simdjson_result document::is_integer() noexcept { + return get_root_value_iterator().is_root_integer(true); +} + +simdjson_inline simdjson_result document::get_number_type() noexcept { + return get_root_value_iterator().get_root_number_type(true); +} + +simdjson_inline simdjson_result document::get_number() noexcept { + return get_root_value_iterator().get_root_number(true); +} + + +simdjson_inline simdjson_result document::raw_json_token() noexcept { + auto _iter = get_root_value_iterator(); + return std::string_view(reinterpret_cast(_iter.peek_start()), _iter.peek_root_length()); +} + +simdjson_inline simdjson_result document::at_pointer(std::string_view json_pointer) noexcept { + rewind(); // Rewind the document each time at_pointer is called + if (json_pointer.empty()) { + return this->get_value(); + } + json_type t; + SIMDJSON_TRY(type().get(t)); + switch (t) + { + case json_type::array: + return (*this).get_array().at_pointer(json_pointer); + case json_type::object: + return (*this).get_object().at_pointer(json_pointer); + default: + return INVALID_JSON_POINTER; + } +} + +simdjson_inline simdjson_result document::at_path(std::string_view json_path) noexcept { + rewind(); // Rewind the document each time at_pointer is called + if (json_path.empty()) { + return this->get_value(); + } + json_type t; + SIMDJSON_TRY(type().get(t)); + switch (t) { + case json_type::array: + return (*this).get_array().at_path(json_path); + case json_type::object: + return (*this).get_object().at_path(json_path); + default: + return INVALID_JSON_POINTER; + } +} + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +simdjson_inline simdjson_result::simdjson_result( + SIMDJSON_IMPLEMENTATION::ondemand::document &&value +) noexcept : + implementation_simdjson_result_base( + std::forward(value) + ) +{ +} +simdjson_inline simdjson_result::simdjson_result( + error_code error +) noexcept : + implementation_simdjson_result_base( + error + ) +{ +} +simdjson_inline simdjson_result simdjson_result::count_elements() & noexcept { + if (error()) { return error(); } + return first.count_elements(); +} +simdjson_inline simdjson_result simdjson_result::count_fields() & noexcept { + if (error()) { return error(); } + return first.count_fields(); +} +simdjson_inline simdjson_result simdjson_result::at(size_t index) & noexcept { + if (error()) { return error(); } + return first.at(index); +} +simdjson_inline error_code simdjson_result::rewind() noexcept { + if (error()) { return error(); } + first.rewind(); + return SUCCESS; +} +simdjson_inline simdjson_result simdjson_result::begin() & noexcept { + if (error()) { return error(); } + return first.begin(); +} +simdjson_inline simdjson_result simdjson_result::end() & noexcept { + return {}; +} +simdjson_inline simdjson_result simdjson_result::find_field_unordered(std::string_view key) & noexcept { + if (error()) { return error(); } + return first.find_field_unordered(key); +} +simdjson_inline simdjson_result simdjson_result::find_field_unordered(const char *key) & noexcept { + if (error()) { return error(); } + return first.find_field_unordered(key); +} +simdjson_inline simdjson_result simdjson_result::operator[](std::string_view key) & noexcept { + if (error()) { return error(); } + return first[key]; +} +simdjson_inline simdjson_result simdjson_result::operator[](const char *key) & noexcept { + if (error()) { return error(); } + return first[key]; +} +simdjson_inline simdjson_result simdjson_result::find_field(std::string_view key) & noexcept { + if (error()) { return error(); } + return first.find_field(key); +} +simdjson_inline simdjson_result simdjson_result::find_field(const char *key) & noexcept { + if (error()) { return error(); } + return first.find_field(key); +} +simdjson_inline simdjson_result simdjson_result::get_array() & noexcept { + if (error()) { return error(); } + return first.get_array(); +} +simdjson_inline simdjson_result simdjson_result::get_object() & noexcept { + if (error()) { return error(); } + return first.get_object(); +} +simdjson_inline simdjson_result simdjson_result::get_uint64() noexcept { + if (error()) { return error(); } + return first.get_uint64(); +} +simdjson_inline simdjson_result simdjson_result::get_uint64_in_string() noexcept { + if (error()) { return error(); } + return first.get_uint64_in_string(); +} +simdjson_inline simdjson_result simdjson_result::get_int64() noexcept { + if (error()) { return error(); } + return first.get_int64(); +} +simdjson_inline simdjson_result simdjson_result::get_int64_in_string() noexcept { + if (error()) { return error(); } + return first.get_int64_in_string(); +} +simdjson_inline simdjson_result simdjson_result::get_double() noexcept { + if (error()) { return error(); } + return first.get_double(); +} +simdjson_inline simdjson_result simdjson_result::get_double_in_string() noexcept { + if (error()) { return error(); } + return first.get_double_in_string(); +} +simdjson_inline simdjson_result simdjson_result::get_string(bool allow_replacement) noexcept { + if (error()) { return error(); } + return first.get_string(allow_replacement); +} +template +simdjson_inline error_code simdjson_result::get_string(string_type& receiver, bool allow_replacement) noexcept { + if (error()) { return error(); } + return first.get_string(receiver, allow_replacement); +} +simdjson_inline simdjson_result simdjson_result::get_wobbly_string() noexcept { + if (error()) { return error(); } + return first.get_wobbly_string(); +} +simdjson_inline simdjson_result simdjson_result::get_raw_json_string() noexcept { + if (error()) { return error(); } + return first.get_raw_json_string(); +} +simdjson_inline simdjson_result simdjson_result::get_bool() noexcept { + if (error()) { return error(); } + return first.get_bool(); +} +simdjson_inline simdjson_result simdjson_result::get_value() noexcept { + if (error()) { return error(); } + return first.get_value(); +} +simdjson_inline simdjson_result simdjson_result::is_null() noexcept { + if (error()) { return error(); } + return first.is_null(); +} + +template +simdjson_inline simdjson_result simdjson_result::get() & noexcept { + if (error()) { return error(); } + return first.get(); +} +template +simdjson_deprecated simdjson_inline simdjson_result simdjson_result::get() && noexcept { + if (error()) { return error(); } + return std::forward(first).get(); +} +template +simdjson_inline error_code simdjson_result::get(T &out) & noexcept { + if (error()) { return error(); } + return first.get(out); +} +template +simdjson_inline error_code simdjson_result::get(T &out) && noexcept { + if (error()) { return error(); } + return std::forward(first).get(out); +} + +template<> simdjson_inline simdjson_result simdjson_result::get() & noexcept = delete; +template<> simdjson_deprecated simdjson_inline simdjson_result simdjson_result::get() && noexcept { + if (error()) { return error(); } + return std::forward(first); +} +template<> simdjson_inline error_code simdjson_result::get(SIMDJSON_IMPLEMENTATION::ondemand::document &out) & noexcept = delete; +template<> simdjson_inline error_code simdjson_result::get(SIMDJSON_IMPLEMENTATION::ondemand::document &out) && noexcept { + if (error()) { return error(); } + out = std::forward(first); + return SUCCESS; +} + +simdjson_inline simdjson_result simdjson_result::type() noexcept { + if (error()) { return error(); } + return first.type(); +} + +simdjson_inline simdjson_result simdjson_result::is_scalar() noexcept { + if (error()) { return error(); } + return first.is_scalar(); +} + +simdjson_inline simdjson_result simdjson_result::is_string() noexcept { + if (error()) { return error(); } + return first.is_string(); +} + +simdjson_inline bool simdjson_result::is_negative() noexcept { + if (error()) { return error(); } + return first.is_negative(); +} + +simdjson_inline simdjson_result simdjson_result::is_integer() noexcept { + if (error()) { return error(); } + return first.is_integer(); +} + +simdjson_inline simdjson_result simdjson_result::get_number_type() noexcept { + if (error()) { return error(); } + return first.get_number_type(); +} + +simdjson_inline simdjson_result simdjson_result::get_number() noexcept { + if (error()) { return error(); } + return first.get_number(); +} + + +#if SIMDJSON_EXCEPTIONS +template ::value == false>::type> +simdjson_inline simdjson_result::operator T() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator SIMDJSON_IMPLEMENTATION::ondemand::array() & noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator SIMDJSON_IMPLEMENTATION::ondemand::object() & noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator uint64_t() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator int64_t() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator double() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator std::string_view() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator bool() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +#endif + + +simdjson_inline simdjson_result simdjson_result::current_location() noexcept { + if (error()) { return error(); } + return first.current_location(); +} + +simdjson_inline bool simdjson_result::at_end() const noexcept { + if (error()) { return error(); } + return first.at_end(); +} + + +simdjson_inline int32_t simdjson_result::current_depth() const noexcept { + if (error()) { return error(); } + return first.current_depth(); +} + +simdjson_inline simdjson_result simdjson_result::raw_json_token() noexcept { + if (error()) { return error(); } + return first.raw_json_token(); +} + +simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { + if (error()) { return error(); } + return first.at_pointer(json_pointer); +} + +simdjson_inline simdjson_result simdjson_result::at_path(std::string_view json_path) noexcept { + if (error()) { return error(); } + return first.at_path(json_path); +} + +} // namespace simdjson + + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +simdjson_inline document_reference::document_reference() noexcept : doc{nullptr} {} +simdjson_inline document_reference::document_reference(document &d) noexcept : doc(&d) {} +simdjson_inline void document_reference::rewind() noexcept { doc->rewind(); } +simdjson_inline simdjson_result document_reference::get_array() & noexcept { return doc->get_array(); } +simdjson_inline simdjson_result document_reference::get_object() & noexcept { return doc->get_object(); } +/** + * The document_reference instances are used primarily/solely for streams of JSON + * documents. + * We decided that calling 'get_double()' on the JSON document '1.233 blabla' should + * give an error, so we check for trailing content. + * + * However, for streams of JSON documents, we want to be able to start from + * "321" "321" "321" + * and parse it successfully as a stream of JSON documents, calling get_uint64_in_string() + * successfully each time. + * + * To achieve this result, we pass a 'false' to a get_root_value_iterator() method: + * this indicates that we allow trailing content. + */ +simdjson_inline simdjson_result document_reference::get_uint64() noexcept { return doc->get_root_value_iterator().get_root_uint64(false); } +simdjson_inline simdjson_result document_reference::get_uint64_in_string() noexcept { return doc->get_root_value_iterator().get_root_uint64_in_string(false); } +simdjson_inline simdjson_result document_reference::get_int64() noexcept { return doc->get_root_value_iterator().get_root_int64(false); } +simdjson_inline simdjson_result document_reference::get_int64_in_string() noexcept { return doc->get_root_value_iterator().get_root_int64_in_string(false); } +simdjson_inline simdjson_result document_reference::get_double() noexcept { return doc->get_root_value_iterator().get_root_double(false); } +simdjson_inline simdjson_result document_reference::get_double_in_string() noexcept { return doc->get_root_value_iterator().get_root_double(false); } +simdjson_inline simdjson_result document_reference::get_string(bool allow_replacement) noexcept { return doc->get_root_value_iterator().get_root_string(false, allow_replacement); } +template +simdjson_inline error_code document_reference::get_string(string_type& receiver, bool allow_replacement) noexcept { return doc->get_root_value_iterator().get_root_string(receiver, false, allow_replacement); } +simdjson_inline simdjson_result document_reference::get_wobbly_string() noexcept { return doc->get_root_value_iterator().get_root_wobbly_string(false); } +simdjson_inline simdjson_result document_reference::get_raw_json_string() noexcept { return doc->get_root_value_iterator().get_root_raw_json_string(false); } +simdjson_inline simdjson_result document_reference::get_bool() noexcept { return doc->get_root_value_iterator().get_root_bool(false); } +simdjson_inline simdjson_result document_reference::get_value() noexcept { return doc->get_value(); } +simdjson_inline simdjson_result document_reference::is_null() noexcept { return doc->get_root_value_iterator().is_root_null(false); } +template<> simdjson_inline simdjson_result document_reference::get() & noexcept { return get_array(); } +template<> simdjson_inline simdjson_result document_reference::get() & noexcept { return get_object(); } +template<> simdjson_inline simdjson_result document_reference::get() & noexcept { return get_raw_json_string(); } +template<> simdjson_inline simdjson_result document_reference::get() & noexcept { return get_string(false); } +template<> simdjson_inline simdjson_result document_reference::get() & noexcept { return get_double(); } +template<> simdjson_inline simdjson_result document_reference::get() & noexcept { return get_uint64(); } +template<> simdjson_inline simdjson_result document_reference::get() & noexcept { return get_int64(); } +template<> simdjson_inline simdjson_result document_reference::get() & noexcept { return get_bool(); } +template<> simdjson_inline simdjson_result document_reference::get() & noexcept { return get_value(); } +#if SIMDJSON_EXCEPTIONS +template +simdjson_inline document_reference::operator T() noexcept(false) { return get(); } +simdjson_inline document_reference::operator array() & noexcept(false) { return array(*doc); } +simdjson_inline document_reference::operator object() & noexcept(false) { return object(*doc); } +simdjson_inline document_reference::operator uint64_t() noexcept(false) { return get_uint64(); } +simdjson_inline document_reference::operator int64_t() noexcept(false) { return get_int64(); } +simdjson_inline document_reference::operator double() noexcept(false) { return get_double(); } +simdjson_inline document_reference::operator std::string_view() noexcept(false) { return std::string_view(*doc); } +simdjson_inline document_reference::operator raw_json_string() noexcept(false) { return get_raw_json_string(); } +simdjson_inline document_reference::operator bool() noexcept(false) { return get_bool(); } +simdjson_inline document_reference::operator value() noexcept(false) { return value(*doc); } +#endif +simdjson_inline simdjson_result document_reference::count_elements() & noexcept { return doc->count_elements(); } +simdjson_inline simdjson_result document_reference::count_fields() & noexcept { return doc->count_fields(); } +simdjson_inline simdjson_result document_reference::at(size_t index) & noexcept { return doc->at(index); } +simdjson_inline simdjson_result document_reference::begin() & noexcept { return doc->begin(); } +simdjson_inline simdjson_result document_reference::end() & noexcept { return doc->end(); } +simdjson_inline simdjson_result document_reference::find_field(std::string_view key) & noexcept { return doc->find_field(key); } +simdjson_inline simdjson_result document_reference::find_field(const char *key) & noexcept { return doc->find_field(key); } +simdjson_inline simdjson_result document_reference::operator[](std::string_view key) & noexcept { return (*doc)[key]; } +simdjson_inline simdjson_result document_reference::operator[](const char *key) & noexcept { return (*doc)[key]; } +simdjson_inline simdjson_result document_reference::find_field_unordered(std::string_view key) & noexcept { return doc->find_field_unordered(key); } +simdjson_inline simdjson_result document_reference::find_field_unordered(const char *key) & noexcept { return doc->find_field_unordered(key); } +simdjson_inline simdjson_result document_reference::type() noexcept { return doc->type(); } +simdjson_inline simdjson_result document_reference::is_scalar() noexcept { return doc->is_scalar(); } +simdjson_inline simdjson_result document_reference::is_string() noexcept { return doc->is_string(); } +simdjson_inline simdjson_result document_reference::current_location() noexcept { return doc->current_location(); } +simdjson_inline int32_t document_reference::current_depth() const noexcept { return doc->current_depth(); } +simdjson_inline bool document_reference::is_negative() noexcept { return doc->is_negative(); } +simdjson_inline simdjson_result document_reference::is_integer() noexcept { return doc->get_root_value_iterator().is_root_integer(false); } +simdjson_inline simdjson_result document_reference::get_number_type() noexcept { return doc->get_root_value_iterator().get_root_number_type(false); } +simdjson_inline simdjson_result document_reference::get_number() noexcept { return doc->get_root_value_iterator().get_root_number(false); } +simdjson_inline simdjson_result document_reference::raw_json_token() noexcept { return doc->raw_json_token(); } +simdjson_inline simdjson_result document_reference::at_pointer(std::string_view json_pointer) noexcept { return doc->at_pointer(json_pointer); } +simdjson_inline simdjson_result document_reference::at_path(std::string_view json_path) noexcept { return doc->at_path(json_path); } +simdjson_inline simdjson_result document_reference::raw_json() noexcept { return doc->raw_json();} +simdjson_inline document_reference::operator document&() const noexcept { return *doc; } + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + + + +namespace simdjson { +simdjson_inline simdjson_result::simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::document_reference value, error_code error) + noexcept : implementation_simdjson_result_base(std::forward(value), error) {} + + +simdjson_inline simdjson_result simdjson_result::count_elements() & noexcept { + if (error()) { return error(); } + return first.count_elements(); +} +simdjson_inline simdjson_result simdjson_result::count_fields() & noexcept { + if (error()) { return error(); } + return first.count_fields(); +} +simdjson_inline simdjson_result simdjson_result::at(size_t index) & noexcept { + if (error()) { return error(); } + return first.at(index); +} +simdjson_inline error_code simdjson_result::rewind() noexcept { + if (error()) { return error(); } + first.rewind(); + return SUCCESS; +} +simdjson_inline simdjson_result simdjson_result::begin() & noexcept { + if (error()) { return error(); } + return first.begin(); +} +simdjson_inline simdjson_result simdjson_result::end() & noexcept { + return {}; +} +simdjson_inline simdjson_result simdjson_result::find_field_unordered(std::string_view key) & noexcept { + if (error()) { return error(); } + return first.find_field_unordered(key); +} +simdjson_inline simdjson_result simdjson_result::find_field_unordered(const char *key) & noexcept { + if (error()) { return error(); } + return first.find_field_unordered(key); +} +simdjson_inline simdjson_result simdjson_result::operator[](std::string_view key) & noexcept { + if (error()) { return error(); } + return first[key]; +} +simdjson_inline simdjson_result simdjson_result::operator[](const char *key) & noexcept { + if (error()) { return error(); } + return first[key]; +} +simdjson_inline simdjson_result simdjson_result::find_field(std::string_view key) & noexcept { + if (error()) { return error(); } + return first.find_field(key); +} +simdjson_inline simdjson_result simdjson_result::find_field(const char *key) & noexcept { + if (error()) { return error(); } + return first.find_field(key); +} +simdjson_inline simdjson_result simdjson_result::get_array() & noexcept { + if (error()) { return error(); } + return first.get_array(); +} +simdjson_inline simdjson_result simdjson_result::get_object() & noexcept { + if (error()) { return error(); } + return first.get_object(); +} +simdjson_inline simdjson_result simdjson_result::get_uint64() noexcept { + if (error()) { return error(); } + return first.get_uint64(); +} +simdjson_inline simdjson_result simdjson_result::get_uint64_in_string() noexcept { + if (error()) { return error(); } + return first.get_uint64_in_string(); +} +simdjson_inline simdjson_result simdjson_result::get_int64() noexcept { + if (error()) { return error(); } + return first.get_int64(); +} +simdjson_inline simdjson_result simdjson_result::get_int64_in_string() noexcept { + if (error()) { return error(); } + return first.get_int64_in_string(); +} +simdjson_inline simdjson_result simdjson_result::get_double() noexcept { + if (error()) { return error(); } + return first.get_double(); +} +simdjson_inline simdjson_result simdjson_result::get_double_in_string() noexcept { + if (error()) { return error(); } + return first.get_double_in_string(); +} +simdjson_inline simdjson_result simdjson_result::get_string(bool allow_replacement) noexcept { + if (error()) { return error(); } + return first.get_string(allow_replacement); +} +template +simdjson_inline error_code simdjson_result::get_string(string_type& receiver, bool allow_replacement) noexcept { + if (error()) { return error(); } + return first.get_string(receiver, allow_replacement); +} +simdjson_inline simdjson_result simdjson_result::get_wobbly_string() noexcept { + if (error()) { return error(); } + return first.get_wobbly_string(); +} +simdjson_inline simdjson_result simdjson_result::get_raw_json_string() noexcept { + if (error()) { return error(); } + return first.get_raw_json_string(); +} +simdjson_inline simdjson_result simdjson_result::get_bool() noexcept { + if (error()) { return error(); } + return first.get_bool(); +} +simdjson_inline simdjson_result simdjson_result::get_value() noexcept { + if (error()) { return error(); } + return first.get_value(); +} +simdjson_inline simdjson_result simdjson_result::is_null() noexcept { + if (error()) { return error(); } + return first.is_null(); +} +template +simdjson_inline simdjson_result simdjson_result::get() & noexcept { + if (error()) { return error(); } + return first.get(); +} +template +simdjson_inline simdjson_result simdjson_result::get() && noexcept { + if (error()) { return error(); } + return std::forward(first).get(); +} +template +simdjson_inline error_code simdjson_result::get(T &out) & noexcept { + if (error()) { return error(); } + return first.get(out); +} +template +simdjson_inline error_code simdjson_result::get(T &out) && noexcept { + if (error()) { return error(); } + return std::forward(first).get(out); +} +simdjson_inline simdjson_result simdjson_result::type() noexcept { + if (error()) { return error(); } + return first.type(); +} +simdjson_inline simdjson_result simdjson_result::is_scalar() noexcept { + if (error()) { return error(); } + return first.is_scalar(); +} +simdjson_inline simdjson_result simdjson_result::is_string() noexcept { + if (error()) { return error(); } + return first.is_string(); +} +template <> +simdjson_inline error_code simdjson_result::get(SIMDJSON_IMPLEMENTATION::ondemand::document_reference &out) & noexcept { + if (error()) { return error(); } + out = first; + return SUCCESS; +} +template <> +simdjson_inline error_code simdjson_result::get(SIMDJSON_IMPLEMENTATION::ondemand::document_reference &out) && noexcept { + if (error()) { return error(); } + out = first; + return SUCCESS; +} +simdjson_inline simdjson_result simdjson_result::is_negative() noexcept { + if (error()) { return error(); } + return first.is_negative(); +} +simdjson_inline simdjson_result simdjson_result::is_integer() noexcept { + if (error()) { return error(); } + return first.is_integer(); +} +simdjson_inline simdjson_result simdjson_result::get_number_type() noexcept { + if (error()) { return error(); } + return first.get_number_type(); +} +simdjson_inline simdjson_result simdjson_result::get_number() noexcept { + if (error()) { return error(); } + return first.get_number(); +} +#if SIMDJSON_EXCEPTIONS +template +simdjson_inline simdjson_result::operator T() noexcept(false) { + static_assert(std::is_same::value == false, "You should not call get when T is a document"); + static_assert(std::is_same::value == false, "You should not call get when T is a document"); + if (error()) { throw simdjson_error(error()); } + return first.get(); +} +simdjson_inline simdjson_result::operator SIMDJSON_IMPLEMENTATION::ondemand::array() & noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator SIMDJSON_IMPLEMENTATION::ondemand::object() & noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator uint64_t() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator int64_t() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator double() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator std::string_view() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator bool() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +#endif + +simdjson_inline simdjson_result simdjson_result::current_location() noexcept { + if (error()) { return error(); } + return first.current_location(); +} + +simdjson_inline simdjson_result simdjson_result::raw_json_token() noexcept { + if (error()) { return error(); } + return first.raw_json_token(); +} + +simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { + if (error()) { return error(); } + return first.at_pointer(json_pointer); +} + +simdjson_inline simdjson_result simdjson_result::at_path(std::string_view json_path) noexcept { + if (error()) { + return error(); + } + return first.at_path(json_path); +} + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_DOCUMENT_INL_H diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/document.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/document.h new file mode 100644 index 000000000000..c109f15b8bda --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/document.h @@ -0,0 +1,1036 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_DOCUMENT_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_DOCUMENT_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/json_iterator.h" +#include "simdjson/generic/ondemand/deserialize.h" +#include "simdjson/generic/ondemand/value.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +/** + * A JSON document. It holds a json_iterator instance. + * + * Used by tokens to get text, and string buffer location. + * + * You must keep the document around during iteration. + */ +class document { +public: + /** + * Create a new invalid document. + * + * Exists so you can declare a variable and later assign to it before use. + */ + simdjson_inline document() noexcept = default; + simdjson_inline document(const document &other) noexcept = delete; // pass your documents by reference, not by copy + simdjson_inline document(document &&other) noexcept = default; + simdjson_inline document &operator=(const document &other) noexcept = delete; + simdjson_inline document &operator=(document &&other) noexcept = default; + + /** + * Cast this JSON value to an array. + * + * @returns An object that can be used to iterate the array. + * @returns INCORRECT_TYPE If the JSON value is not an array. + */ + simdjson_inline simdjson_result get_array() & noexcept; + /** + * Cast this JSON value to an object. + * + * @returns An object that can be used to look up or iterate fields. + * @returns INCORRECT_TYPE If the JSON value is not an object. + */ + simdjson_inline simdjson_result get_object() & noexcept; + /** + * Cast this JSON value to an unsigned integer. + * + * @returns A signed 64-bit integer. + * @returns INCORRECT_TYPE If the JSON value is not a 64-bit unsigned integer. + */ + simdjson_inline simdjson_result get_uint64() noexcept; + /** + * Cast this JSON value (inside string) to an unsigned integer. + * + * @returns A signed 64-bit integer. + * @returns INCORRECT_TYPE If the JSON value is not a 64-bit unsigned integer. + */ + simdjson_inline simdjson_result get_uint64_in_string() noexcept; + /** + * Cast this JSON value to a signed integer. + * + * @returns A signed 64-bit integer. + * @returns INCORRECT_TYPE If the JSON value is not a 64-bit integer. + */ + simdjson_inline simdjson_result get_int64() noexcept; + /** + * Cast this JSON value (inside string) to a signed integer. + * + * @returns A signed 64-bit integer. + * @returns INCORRECT_TYPE If the JSON value is not a 64-bit integer. + */ + simdjson_inline simdjson_result get_int64_in_string() noexcept; + /** + * Cast this JSON value to a double. + * + * @returns A double. + * @returns INCORRECT_TYPE If the JSON value is not a valid floating-point number. + */ + simdjson_inline simdjson_result get_double() noexcept; + + /** + * Cast this JSON value (inside string) to a double. + * + * @returns A double. + * @returns INCORRECT_TYPE If the JSON value is not a valid floating-point number. + */ + simdjson_inline simdjson_result get_double_in_string() noexcept; + /** + * Cast this JSON value to a string. + * + * The string is guaranteed to be valid UTF-8. + * + * Important: Calling get_string() twice on the same document is an error. + * + * @param Whether to allow a replacement character for unmatched surrogate pairs. + * @returns An UTF-8 string. The string is stored in the parser and will be invalidated the next + * time it parses a document or when it is destroyed. + * @returns INCORRECT_TYPE if the JSON value is not a string. + */ + simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; + /** + * Attempts to fill the provided std::string reference with the parsed value of the current string. + * + * The string is guaranteed to be valid UTF-8. + * + * Important: a value should be consumed once. Calling get_string() twice on the same value + * is an error. + * + * Performance: This method may be slower than get_string() or get_string(bool) because it may need to allocate memory. + * We recommend you avoid allocating an std::string unless you need to. + * + * @returns INCORRECT_TYPE if the JSON value is not a string. Otherwise, we return SUCCESS. + */ + template + simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; + /** + * Cast this JSON value to a string. + * + * The string is not guaranteed to be valid UTF-8. See https://simonsapin.github.io/wtf-8/ + * + * Important: Calling get_wobbly_string() twice on the same document is an error. + * + * @returns An UTF-8 string. The string is stored in the parser and will be invalidated the next + * time it parses a document or when it is destroyed. + * @returns INCORRECT_TYPE if the JSON value is not a string. + */ + simdjson_inline simdjson_result get_wobbly_string() noexcept; + /** + * Cast this JSON value to a raw_json_string. + * + * The string is guaranteed to be valid UTF-8, and may have escapes in it (e.g. \\ or \n). + * + * @returns A pointer to the raw JSON for the given string. + * @returns INCORRECT_TYPE if the JSON value is not a string. + */ + simdjson_inline simdjson_result get_raw_json_string() noexcept; + /** + * Cast this JSON value to a bool. + * + * @returns A bool value. + * @returns INCORRECT_TYPE if the JSON value is not true or false. + */ + simdjson_inline simdjson_result get_bool() noexcept; + /** + * Cast this JSON value to a value when the document is an object or an array. + * + * You must not have begun iterating through the object or array. When + * SIMDJSON_DEVELOPMENT_CHECKS is set to 1 (which is the case when building in Debug mode + * by default), and you have already begun iterating, + * you will get an OUT_OF_ORDER_ITERATION error. If you have begun iterating, you can use + * rewind() to reset the document to its initial state before calling this method. + * + * @returns A value if a JSON array or object cannot be found. + * @returns SCALAR_DOCUMENT_AS_VALUE error is the document is a scalar (see is_scalar() function). + */ + simdjson_inline simdjson_result get_value() noexcept; + + /** + * Checks if this JSON value is null. If and only if the value is + * null, then it is consumed (we advance). If we find a token that + * begins with 'n' but is not 'null', then an error is returned. + * + * @returns Whether the value is null. + * @returns INCORRECT_TYPE If the JSON value begins with 'n' and is not 'null'. + */ + simdjson_inline simdjson_result is_null() noexcept; + + /** + * Get this value as the given type. + * + * Supported types: object, array, raw_json_string, string_view, uint64_t, int64_t, double, bool + * + * You may use get_double(), get_bool(), get_uint64(), get_int64(), + * get_object(), get_array(), get_raw_json_string(), or get_string() instead. + * + * @returns A value of the given type, parsed from the JSON. + * @returns INCORRECT_TYPE If the JSON value is not the given type. + */ + template + simdjson_inline simdjson_result get() & +#if SIMDJSON_SUPPORTS_DESERIALIZATION + noexcept(custom_deserializable ? nothrow_custom_deserializable : true) +#else + noexcept +#endif + { + static_assert(std::is_default_constructible::value, "Cannot initialize the specified type."); + T out{}; + SIMDJSON_TRY(get(out)); + return out; + } + /** + * @overload template simdjson_result get() & noexcept + * + * We disallow the use tag_invoke CPO on a moved document; it may create UB + * if user uses `ondemand::array` or `ondemand::object` in their custom type. + * + * The member function is still remains specialize-able for compatibility + * reasons, but we completely disallow its use when a tag_invoke customization + * is provided. + */ + template + simdjson_inline simdjson_result get() && +#if SIMDJSON_SUPPORTS_DESERIALIZATION + noexcept(custom_deserializable ? nothrow_custom_deserializable : true) +#else + noexcept +#endif + { + static_assert(!std::is_same::value && !std::is_same::value, "You should never hold either an ondemand::array or ondemand::object without a corresponding ondemand::document being alive; that would be Undefined Behaviour."); + return static_cast(*this).get(); + } + + /** + * Get this value as the given type. + * + * Supported types: object, array, raw_json_string, string_view, uint64_t, int64_t, double, bool, value + * + * Be mindful that the document instance must remain in scope while you are accessing object, array and value instances. + * + * @param out This is set to a value of the given type, parsed from the JSON. If there is an error, this may not be initialized. + * @returns INCORRECT_TYPE If the JSON value is not an object. + * @returns SUCCESS If the parse succeeded and the out parameter was set to the value. + */ + template + simdjson_inline error_code get(T &out) & +#if SIMDJSON_SUPPORTS_DESERIALIZATION + noexcept(custom_deserializable ? nothrow_custom_deserializable : true) +#else + noexcept +#endif + { +#if SIMDJSON_SUPPORTS_DESERIALIZATION + if constexpr (custom_deserializable) { + return deserialize(*this, out); + } else { +#endif // SIMDJSON_SUPPORTS_DESERIALIZATION + // Unless the simdjson library or the user provides an inline implementation, calling this method should + // immediately fail. + static_assert(!sizeof(T), "The get method with given type is not implemented by the simdjson library. " + "The supported types are ondemand::object, ondemand::array, raw_json_string, std::string_view, uint64_t, " + "int64_t, double, and bool. We recommend you use get_double(), get_bool(), get_uint64(), get_int64(), " + " get_object(), get_array(), get_raw_json_string(), or get_string() instead of the get template." + " You may also add support for custom types, see our documentation."); + static_cast(out); // to get rid of unused errors + return UNINITIALIZED; +#if SIMDJSON_SUPPORTS_DESERIALIZATION + } +#endif + } + /** @overload template error_code get(T &out) & noexcept */ + template simdjson_deprecated simdjson_inline error_code get(T &out) && noexcept; + +#if SIMDJSON_EXCEPTIONS + /** + * Cast this JSON value to an instance of type T. The programmer is responsible for + * providing an implementation of get for the type T, if T is not one of the types + * supported by the library (object, array, raw_json_string, string_view, uint64_t, etc.) + * + * See https://github.com/simdjson/simdjson/blob/master/doc/basics.md#adding-support-for-custom-types + * + * @returns An instance of type T + */ + template + explicit simdjson_inline operator T() & noexcept(false); + template + explicit simdjson_deprecated simdjson_inline operator T() && noexcept(false); + + /** + * Cast this JSON value to an array. + * + * @returns An object that can be used to iterate the array. + * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not an array. + */ + simdjson_inline operator array() & noexcept(false); + /** + * Cast this JSON value to an object. + * + * @returns An object that can be used to look up or iterate fields. + * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not an object. + */ + simdjson_inline operator object() & noexcept(false); + /** + * Cast this JSON value to an unsigned integer. + * + * @returns A signed 64-bit integer. + * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit unsigned integer. + */ + simdjson_inline operator uint64_t() noexcept(false); + /** + * Cast this JSON value to a signed integer. + * + * @returns A signed 64-bit integer. + * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit integer. + */ + simdjson_inline operator int64_t() noexcept(false); + /** + * Cast this JSON value to a double. + * + * @returns A double. + * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a valid floating-point number. + */ + simdjson_inline operator double() noexcept(false); + /** + * Cast this JSON value to a string. + * + * The string is guaranteed to be valid UTF-8. + * + * @returns An UTF-8 string. The string is stored in the parser and will be invalidated the next + * time it parses a document or when it is destroyed. + * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string. + */ + simdjson_inline operator std::string_view() noexcept(false); + /** + * Cast this JSON value to a raw_json_string. + * + * The string is guaranteed to be valid UTF-8, and may have escapes in it (e.g. \\ or \n). + * + * @returns A pointer to the raw JSON for the given string. + * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string. + */ + simdjson_inline operator raw_json_string() noexcept(false); + /** + * Cast this JSON value to a bool. + * + * @returns A bool value. + * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not true or false. + */ + simdjson_inline operator bool() noexcept(false); + /** + * Cast this JSON value to a value when the document is an object or an array. + * + * You must not have begun iterating through the object or array. When + * SIMDJSON_DEVELOPMENT_CHECKS is defined, and you have already begun iterating, + * you will get an OUT_OF_ORDER_ITERATION error. If you have begun iterating, you can use + * rewind() to reset the document to its initial state before calling this method. + * + * @returns A value value if a JSON array or object cannot be found. + * @exception SCALAR_DOCUMENT_AS_VALUE error is the document is a scalar (see is_scalar() function). + */ + simdjson_inline operator value() noexcept(false); +#endif + /** + * This method scans the array and counts the number of elements. + * The count_elements method should always be called before you have begun + * iterating through the array: it is expected that you are pointing at + * the beginning of the array. + * The runtime complexity is linear in the size of the array. After + * calling this function, if successful, the array is 'rewinded' at its + * beginning as if it had never been accessed. If the JSON is malformed (e.g., + * there is a missing comma), then an error is returned and it is no longer + * safe to continue. Note that count_elements() does not validate the JSON values, + * only the structure of the array. + */ + simdjson_inline simdjson_result count_elements() & noexcept; + /** + * This method scans the object and counts the number of key-value pairs. + * The count_fields method should always be called before you have begun + * iterating through the object: it is expected that you are pointing at + * the beginning of the object. + * The runtime complexity is linear in the size of the object. After + * calling this function, if successful, the object is 'rewinded' at its + * beginning as if it had never been accessed. If the JSON is malformed (e.g., + * there is a missing comma), then an error is returned and it is no longer + * safe to continue. + * + * To check that an object is empty, it is more performant to use + * the is_empty() method. + */ + simdjson_inline simdjson_result count_fields() & noexcept; + /** + * Get the value at the given index in the array. This function has linear-time complexity. + * This function should only be called once on an array instance since the array iterator is not reset between each call. + * + * @return The value at the given index, or: + * - INDEX_OUT_OF_BOUNDS if the array index is larger than an array length + */ + simdjson_inline simdjson_result at(size_t index) & noexcept; + /** + * Begin array iteration. + * + * Part of the std::iterable interface. + */ + simdjson_inline simdjson_result begin() & noexcept; + /** + * Sentinel representing the end of the array. + * + * Part of the std::iterable interface. + */ + simdjson_inline simdjson_result end() & noexcept; + + /** + * Look up a field by name on an object (order-sensitive). + * + * The following code reads z, then y, then x, and thus will not retrieve x or y if fed the + * JSON `{ "x": 1, "y": 2, "z": 3 }`: + * + * ```c++ + * simdjson::ondemand::parser parser; + * auto obj = parser.parse(R"( { "x": 1, "y": 2, "z": 3 } )"_padded); + * double z = obj.find_field("z"); + * double y = obj.find_field("y"); + * double x = obj.find_field("x"); + * ``` + * + * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys. + * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. + * + * + * You must consume the fields on an object one at a time. A request for a new key + * invalidates previous field values: it makes them unsafe. E.g., the array + * given by content["bids"].get_array() should not be accessed after you have called + * content["asks"].get_array(). You can detect such mistakes by first compiling and running + * the code in Debug mode (or with the macro `SIMDJSON_DEVELOPMENT_CHECKS` set to 1): an + * OUT_OF_ORDER_ITERATION error is generated. + * + * You are expected to access keys only once. You should access the value corresponding to + * a key a single time. Doing object["mykey"].to_string()and then again object["mykey"].to_string() + * is an error. + * + * @param key The key to look up. + * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. + */ + simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; + /** @overload simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; */ + simdjson_inline simdjson_result find_field(const char *key) & noexcept; + + /** + * Look up a field by name on an object, without regard to key order. + * + * **Performance Notes:** This is a bit less performant than find_field(), though its effect varies + * and often appears negligible. It starts out normally, starting out at the last field; but if + * the field is not found, it scans from the beginning of the object to see if it missed it. That + * missing case has a non-cache-friendly bump and lots of extra scanning, especially if the object + * in question is large. The fact that the extra code is there also bumps the executable size. + * + * It is the default, however, because it would be highly surprising (and hard to debug) if the + * default behavior failed to look up a field just because it was in the wrong order--and many + * APIs assume this. Therefore, you must be explicit if you want to treat objects as out of order. + * + * Use find_field() if you are sure fields will be in order (or are willing to treat it as if the + * field was not there when they are not in order). + * + * You must consume the fields on an object one at a time. A request for a new key + * invalidates previous field values: it makes them unsafe. E.g., the array + * given by content["bids"].get_array() should not be accessed after you have called + * content["asks"].get_array(). You can detect such mistakes by first compiling and running + * the code in Debug mode (or with the macro `SIMDJSON_DEVELOPMENT_CHECKS` set to 1): an + * OUT_OF_ORDER_ITERATION error is generated. + * + * You are expected to access keys only once. You should access the value corresponding to a key + * a single time. Doing object["mykey"].to_string() and then again object["mykey"].to_string() + * is an error. + * + * @param key The key to look up. + * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. + */ + simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ + simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; + /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ + simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; + /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ + simdjson_inline simdjson_result operator[](const char *key) & noexcept; + simdjson_result operator[](int) & noexcept = delete; + + /** + * Get the type of this JSON value. It does not validate or consume the value. + * E.g., you must still call "is_null()" to check that a value is null even if + * "type()" returns json_type::null. + * + * NOTE: If you're only expecting a value to be one type (a typical case), it's generally + * better to just call .get_double, .get_string, etc. and check for INCORRECT_TYPE (or just + * let it throw an exception). + * + * @error TAPE_ERROR when the JSON value is a bad token like "}" "," or "alse". + */ + simdjson_inline simdjson_result type() noexcept; + + /** + * Checks whether the document is a scalar (string, number, null, Boolean). + * Returns false when there it is an array or object. + * + * @returns true if the type is string, number, null, Boolean + * @error TAPE_ERROR when the JSON value is a bad token like "}" "," or "alse". + */ + simdjson_inline simdjson_result is_scalar() noexcept; + + /** + * Checks whether the document is a string. + * + * @returns true if the type is string + * @error TAPE_ERROR when the JSON value is a bad token like "}" "," or "alse". + */ + simdjson_inline simdjson_result is_string() noexcept; + + /** + * Checks whether the document is a negative number. + * + * @returns true if the number if negative. + */ + simdjson_inline bool is_negative() noexcept; + /** + * Checks whether the document is an integer number. Note that + * this requires to partially parse the number string. If + * the value is determined to be an integer, it may still + * not parse properly as an integer in subsequent steps + * (e.g., it might overflow). + * + * @returns true if the number if negative. + */ + simdjson_inline simdjson_result is_integer() noexcept; + /** + * Determine the number type (integer or floating-point number) as quickly + * as possible. This function does not fully validate the input. It is + * useful when you only need to classify the numbers, without parsing them. + * + * If you are planning to retrieve the value or you need full validation, + * consider using the get_number() method instead: it will fully parse + * and validate the input, and give you access to the type: + * get_number().get_number_type(). + * + * get_number_type() is number_type::unsigned_integer if we have + * an integer greater or equal to 9223372036854775808 and no larger than 18446744073709551615. + * get_number_type() is number_type::signed_integer if we have an + * integer that is less than 9223372036854775808 and greater or equal to -9223372036854775808. + * get_number_type() is number_type::big_integer if we have an integer outside + * of those ranges (either larger than 18446744073709551615 or smaller than -9223372036854775808). + * Otherwise, get_number_type() has value number_type::floating_point_number + * + * This function requires processing the number string, but it is expected + * to be faster than get_number().get_number_type() because it is does not + * parse the number value. + * + * @returns the type of the number + */ + simdjson_inline simdjson_result get_number_type() noexcept; + + /** + * Attempt to parse an ondemand::number. An ondemand::number may + * contain an integer value or a floating-point value, the simdjson + * library will autodetect the type. Thus it is a dynamically typed + * number. Before accessing the value, you must determine the detected + * type. + * + * number.get_number_type() is number_type::signed_integer if we have + * an integer in [-9223372036854775808,9223372036854775808) + * You can recover the value by calling number.get_int64() and you + * have that number.is_int64() is true. + * + * number.get_number_type() is number_type::unsigned_integer if we have + * an integer in [9223372036854775808,18446744073709551616) + * You can recover the value by calling number.get_uint64() and you + * have that number.is_uint64() is true. + * + * Otherwise, number.get_number_type() has value number_type::floating_point_number + * and we have a binary64 number. + * You can recover the value by calling number.get_double() and you + * have that number.is_double() is true. + * + * You must check the type before accessing the value: it is an error + * to call "get_int64()" when number.get_number_type() is not + * number_type::signed_integer and when number.is_int64() is false. + */ + simdjson_warn_unused simdjson_inline simdjson_result get_number() noexcept; + + /** + * Get the raw JSON for this token. + * + * The string_view will always point into the input buffer. + * + * The string_view will start at the beginning of the token, and include the entire token + * *as well as all spaces until the next token (or EOF).* This means, for example, that a + * string token always begins with a " and is always terminated by the final ", possibly + * followed by a number of spaces. + * + * The string_view is *not* null-terminated. If this is a scalar (string, number, + * boolean, or null), the character after the end of the string_view may be the padded buffer. + * + * Tokens include: + * - { + * - [ + * - "a string (possibly with UTF-8 or backslashed characters like \\\")". + * - -1.2e-100 + * - true + * - false + * - null + */ + simdjson_inline simdjson_result raw_json_token() noexcept; + + /** + * Reset the iterator inside the document instance so we are pointing back at the + * beginning of the document, as if it had just been created. It invalidates all + * values, objects and arrays that you have created so far (including unescaped strings). + */ + inline void rewind() noexcept; + /** + * Returns debugging information. + */ + inline std::string to_debug_string() noexcept; + /** + * Some unrecoverable error conditions may render the document instance unusable. + * The is_alive() method returns true when the document is still suitable. + */ + inline bool is_alive() noexcept; + + /** + * Returns the current location in the document if in bounds. + */ + inline simdjson_result current_location() const noexcept; + + /** + * Returns true if this document has been fully parsed. + * If you have consumed the whole document and at_end() returns + * false, then there may be trailing content. + */ + inline bool at_end() const noexcept; + + /** + * Returns the current depth in the document if in bounds. + * + * E.g., + * 0 = finished with document + * 1 = document root value (could be [ or {, not yet known) + * 2 = , or } inside root array/object + * 3 = key or value inside root array/object. + */ + simdjson_inline int32_t current_depth() const noexcept; + + /** + * Get the value associated with the given JSON pointer. We use the RFC 6901 + * https://tools.ietf.org/html/rfc6901 standard. + * + * ondemand::parser parser; + * auto json = R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded; + * auto doc = parser.iterate(json); + * doc.at_pointer("/foo/a/1") == 20 + * + * It is allowed for a key to be the empty string: + * + * ondemand::parser parser; + * auto json = R"({ "": { "a": [ 10, 20, 30 ] }})"_padded; + * auto doc = parser.iterate(json); + * doc.at_pointer("//a/1") == 20 + * + * Key values are matched exactly, without unescaping or Unicode normalization. + * We do a byte-by-byte comparison. E.g. + * + * const padded_string json = "{\"\\u00E9\":123}"_padded; + * auto doc = parser.iterate(json); + * doc.at_pointer("/\\u00E9") == 123 + * doc.at_pointer((const char*)u8"/\u00E9") returns an error (NO_SUCH_FIELD) + * + * Note that at_pointer() automatically calls rewind between each call. Thus + * all values, objects and arrays that you have created so far (including unescaped strings) + * are invalidated. After calling at_pointer, you need to consume the result: string values + * should be stored in your own variables, arrays should be decoded and stored in your own array-like + * structures and so forth. + * + * Also note that at_pointer() relies on find_field() which implies that we do not unescape keys when matching + * + * @return The value associated with the given JSON pointer, or: + * - NO_SUCH_FIELD if a field does not exist in an object + * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length + * - INCORRECT_TYPE if a non-integer is used to access an array + * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed + * - SCALAR_DOCUMENT_AS_VALUE if the json_pointer is empty and the document is not a scalar (see is_scalar() function). + */ + simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; + + /** + * Get the value associated with the given JSONPath expression. We only support + * JSONPath queries that trivially convertible to JSON Pointer queries: key + * names and array indices. + * + * https://datatracker.ietf.org/doc/html/draft-normington-jsonpath-00 + * + * Key values are matched exactly, without unescaping or Unicode normalization. + * We do a byte-by-byte comparison. E.g. + * + * const padded_string json = "{\"\\u00E9\":123}"_padded; + * auto doc = parser.iterate(json); + * doc.at_path(".\\u00E9") == 123 + * doc.at_path((const char*)u8".\u00E9") returns an error (NO_SUCH_FIELD) + * + * @return The value associated with the given JSONPath expression, or: + * - INVALID_JSON_POINTER if the JSONPath to JSON Pointer conversion fails + * - NO_SUCH_FIELD if a field does not exist in an object + * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length + * - INCORRECT_TYPE if a non-integer is used to access an array + */ + simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; + + /** + * Consumes the document and returns a string_view instance corresponding to the + * document as represented in JSON. It points inside the original byte array containing + * the JSON document. + */ + simdjson_inline simdjson_result raw_json() noexcept; +protected: + /** + * Consumes the document. + */ + simdjson_inline error_code consume() noexcept; + + simdjson_inline document(ondemand::json_iterator &&iter) noexcept; + simdjson_inline const uint8_t *text(uint32_t idx) const noexcept; + + simdjson_inline value_iterator resume_value_iterator() noexcept; + simdjson_inline value_iterator get_root_value_iterator() noexcept; + simdjson_inline simdjson_result start_or_resume_object() noexcept; + static simdjson_inline document start(ondemand::json_iterator &&iter) noexcept; + + // + // Fields + // + json_iterator iter{}; ///< Current position in the document + static constexpr depth_t DOCUMENT_DEPTH = 0; ///< document depth is always 0 + + friend class array_iterator; + friend class value; + friend class ondemand::parser; + friend class object; + friend class array; + friend class field; + friend class token; + friend class document_stream; + friend class document_reference; +}; + + +/** + * A document_reference is a thin wrapper around a document reference instance. + * The document_reference instances are used primarily/solely for streams of JSON + * documents. They differ from document instances when parsing a scalar value + * (a document that is not an array or an object). In the case of a document, + * we expect the document to be fully consumed. In the case of a document_reference, + * we allow trailing content. + */ +class document_reference { +public: + simdjson_inline document_reference() noexcept; + simdjson_inline document_reference(document &d) noexcept; + simdjson_inline document_reference(const document_reference &other) noexcept = default; + simdjson_inline document_reference& operator=(const document_reference &other) noexcept = default; + simdjson_inline void rewind() noexcept; + simdjson_inline simdjson_result get_array() & noexcept; + simdjson_inline simdjson_result get_object() & noexcept; + simdjson_inline simdjson_result get_uint64() noexcept; + simdjson_inline simdjson_result get_uint64_in_string() noexcept; + simdjson_inline simdjson_result get_int64() noexcept; + simdjson_inline simdjson_result get_int64_in_string() noexcept; + simdjson_inline simdjson_result get_double() noexcept; + simdjson_inline simdjson_result get_double_in_string() noexcept; + simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; + template + simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; + simdjson_inline simdjson_result get_wobbly_string() noexcept; + simdjson_inline simdjson_result get_raw_json_string() noexcept; + simdjson_inline simdjson_result get_bool() noexcept; + simdjson_inline simdjson_result get_value() noexcept; + + simdjson_inline simdjson_result is_null() noexcept; + template + simdjson_inline simdjson_result get() & +#if SIMDJSON_SUPPORTS_DESERIALIZATION + noexcept(custom_deserializable ? nothrow_custom_deserializable : true) +#else + noexcept +#endif + { + static_assert(std::is_default_constructible::value, "Cannot initialize the specified type."); + T out{}; + SIMDJSON_TRY(get(out)); + return out; + } + template + simdjson_inline simdjson_result get() && +#if SIMDJSON_SUPPORTS_DESERIALIZATION + noexcept(custom_deserializable ? nothrow_custom_deserializable : true) +#else + noexcept +#endif + { + static_assert(!std::is_same::value && !std::is_same::value, "You should never hold either an ondemand::array or ondemand::object without a corresponding ondemand::document_reference being alive; that would be Undefined Behaviour."); + return static_cast(*this).get(); + } + + /** + * Get this value as the given type. + * + * Supported types: object, array, raw_json_string, string_view, uint64_t, int64_t, double, bool, value + * + * Be mindful that the document instance must remain in scope while you are accessing object, array and value instances. + * + * @param out This is set to a value of the given type, parsed from the JSON. If there is an error, this may not be initialized. + * @returns INCORRECT_TYPE If the JSON value is not an object. + * @returns SUCCESS If the parse succeeded and the out parameter was set to the value. + */ + template + simdjson_inline error_code get(T &out) & +#if SIMDJSON_SUPPORTS_DESERIALIZATION + noexcept(custom_deserializable ? nothrow_custom_deserializable : true) +#else + noexcept +#endif + { +#if SIMDJSON_SUPPORTS_DESERIALIZATION + if constexpr (custom_deserializable) { + return deserialize(*this, out); + } else { +#endif // SIMDJSON_SUPPORTS_DESERIALIZATION + // Unless the simdjson library or the user provides an inline implementation, calling this method should + // immediately fail. + static_assert(!sizeof(T), "The get method with given type is not implemented by the simdjson library. " + "The supported types are ondemand::object, ondemand::array, raw_json_string, std::string_view, uint64_t, " + "int64_t, double, and bool. We recommend you use get_double(), get_bool(), get_uint64(), get_int64(), " + " get_object(), get_array(), get_raw_json_string(), or get_string() instead of the get template." + " You may also add support for custom types, see our documentation."); + static_cast(out); // to get rid of unused errors + return UNINITIALIZED; +#if SIMDJSON_SUPPORTS_DESERIALIZATION + } +#endif + } + /** @overload template error_code get(T &out) & noexcept */ + template simdjson_inline error_code get(T &out) && noexcept; + simdjson_inline simdjson_result raw_json() noexcept; + simdjson_inline operator document&() const noexcept; +#if SIMDJSON_EXCEPTIONS + template + explicit simdjson_inline operator T() noexcept(false); + simdjson_inline operator array() & noexcept(false); + simdjson_inline operator object() & noexcept(false); + simdjson_inline operator uint64_t() noexcept(false); + simdjson_inline operator int64_t() noexcept(false); + simdjson_inline operator double() noexcept(false); + simdjson_inline operator std::string_view() noexcept(false); + simdjson_inline operator raw_json_string() noexcept(false); + simdjson_inline operator bool() noexcept(false); + simdjson_inline operator value() noexcept(false); +#endif + simdjson_inline simdjson_result count_elements() & noexcept; + simdjson_inline simdjson_result count_fields() & noexcept; + simdjson_inline simdjson_result at(size_t index) & noexcept; + simdjson_inline simdjson_result begin() & noexcept; + simdjson_inline simdjson_result end() & noexcept; + simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field(const char *key) & noexcept; + simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; + simdjson_inline simdjson_result operator[](const char *key) & noexcept; + simdjson_result operator[](int) & noexcept = delete; + simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; + + simdjson_inline simdjson_result type() noexcept; + simdjson_inline simdjson_result is_scalar() noexcept; + simdjson_inline simdjson_result is_string() noexcept; + + simdjson_inline simdjson_result current_location() noexcept; + simdjson_inline int32_t current_depth() const noexcept; + simdjson_inline bool is_negative() noexcept; + simdjson_inline simdjson_result is_integer() noexcept; + simdjson_inline simdjson_result get_number_type() noexcept; + simdjson_inline simdjson_result get_number() noexcept; + simdjson_inline simdjson_result raw_json_token() noexcept; + simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; + simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; + +private: + document *doc{nullptr}; +}; +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +template<> +struct simdjson_result : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::document &&value) noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + simdjson_inline simdjson_result() noexcept = default; + simdjson_inline error_code rewind() noexcept; + + simdjson_inline simdjson_result get_array() & noexcept; + simdjson_inline simdjson_result get_object() & noexcept; + simdjson_inline simdjson_result get_uint64() noexcept; + simdjson_inline simdjson_result get_uint64_in_string() noexcept; + simdjson_inline simdjson_result get_int64() noexcept; + simdjson_inline simdjson_result get_int64_in_string() noexcept; + simdjson_inline simdjson_result get_double() noexcept; + simdjson_inline simdjson_result get_double_in_string() noexcept; + simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; + template + simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; + simdjson_inline simdjson_result get_wobbly_string() noexcept; + simdjson_inline simdjson_result get_raw_json_string() noexcept; + simdjson_inline simdjson_result get_bool() noexcept; + simdjson_inline simdjson_result get_value() noexcept; + simdjson_inline simdjson_result is_null() noexcept; + + template simdjson_inline simdjson_result get() & noexcept; + template simdjson_deprecated simdjson_inline simdjson_result get() && noexcept; + + template simdjson_inline error_code get(T &out) & noexcept; + template simdjson_inline error_code get(T &out) && noexcept; +#if SIMDJSON_EXCEPTIONS + template ::value == false>::type> + explicit simdjson_inline operator T() noexcept(false); + simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::array() & noexcept(false); + simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::object() & noexcept(false); + simdjson_inline operator uint64_t() noexcept(false); + simdjson_inline operator int64_t() noexcept(false); + simdjson_inline operator double() noexcept(false); + simdjson_inline operator std::string_view() noexcept(false); + simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false); + simdjson_inline operator bool() noexcept(false); + simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false); +#endif + simdjson_inline simdjson_result count_elements() & noexcept; + simdjson_inline simdjson_result count_fields() & noexcept; + simdjson_inline simdjson_result at(size_t index) & noexcept; + simdjson_inline simdjson_result begin() & noexcept; + simdjson_inline simdjson_result end() & noexcept; + simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field(const char *key) & noexcept; + simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; + simdjson_inline simdjson_result operator[](const char *key) & noexcept; + simdjson_result operator[](int) & noexcept = delete; + simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; + simdjson_inline simdjson_result type() noexcept; + simdjson_inline simdjson_result is_scalar() noexcept; + simdjson_inline simdjson_result is_string() noexcept; + simdjson_inline simdjson_result current_location() noexcept; + simdjson_inline int32_t current_depth() const noexcept; + simdjson_inline bool at_end() const noexcept; + simdjson_inline bool is_negative() noexcept; + simdjson_inline simdjson_result is_integer() noexcept; + simdjson_inline simdjson_result get_number_type() noexcept; + simdjson_inline simdjson_result get_number() noexcept; + /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ + simdjson_inline simdjson_result raw_json_token() noexcept; + + simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; + simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; +}; + + +} // namespace simdjson + + + +namespace simdjson { + +template<> +struct simdjson_result : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::document_reference value, error_code error) noexcept; + simdjson_inline simdjson_result() noexcept = default; + simdjson_inline error_code rewind() noexcept; + + simdjson_inline simdjson_result get_array() & noexcept; + simdjson_inline simdjson_result get_object() & noexcept; + simdjson_inline simdjson_result get_uint64() noexcept; + simdjson_inline simdjson_result get_uint64_in_string() noexcept; + simdjson_inline simdjson_result get_int64() noexcept; + simdjson_inline simdjson_result get_int64_in_string() noexcept; + simdjson_inline simdjson_result get_double() noexcept; + simdjson_inline simdjson_result get_double_in_string() noexcept; + simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; + template + simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; + simdjson_inline simdjson_result get_wobbly_string() noexcept; + simdjson_inline simdjson_result get_raw_json_string() noexcept; + simdjson_inline simdjson_result get_bool() noexcept; + simdjson_inline simdjson_result get_value() noexcept; + simdjson_inline simdjson_result is_null() noexcept; + + template simdjson_inline simdjson_result get() & noexcept; + template simdjson_inline simdjson_result get() && noexcept; + + template simdjson_inline error_code get(T &out) & noexcept; + template simdjson_inline error_code get(T &out) && noexcept; +#if SIMDJSON_EXCEPTIONS + template + explicit simdjson_inline operator T() noexcept(false); + simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::array() & noexcept(false); + simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::object() & noexcept(false); + simdjson_inline operator uint64_t() noexcept(false); + simdjson_inline operator int64_t() noexcept(false); + simdjson_inline operator double() noexcept(false); + simdjson_inline operator std::string_view() noexcept(false); + simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false); + simdjson_inline operator bool() noexcept(false); + simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::value() noexcept(false); +#endif + simdjson_inline simdjson_result count_elements() & noexcept; + simdjson_inline simdjson_result count_fields() & noexcept; + simdjson_inline simdjson_result at(size_t index) & noexcept; + simdjson_inline simdjson_result begin() & noexcept; + simdjson_inline simdjson_result end() & noexcept; + simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field(const char *key) & noexcept; + simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; + simdjson_inline simdjson_result operator[](const char *key) & noexcept; + simdjson_result operator[](int) & noexcept = delete; + simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; + simdjson_inline simdjson_result type() noexcept; + simdjson_inline simdjson_result is_scalar() noexcept; + simdjson_inline simdjson_result is_string() noexcept; + simdjson_inline simdjson_result current_location() noexcept; + simdjson_inline simdjson_result current_depth() const noexcept; + simdjson_inline simdjson_result is_negative() noexcept; + simdjson_inline simdjson_result is_integer() noexcept; + simdjson_inline simdjson_result get_number_type() noexcept; + simdjson_inline simdjson_result get_number() noexcept; + /** @copydoc simdjson_inline std::string_view document_reference::raw_json_token() const noexcept */ + simdjson_inline simdjson_result raw_json_token() noexcept; + + simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; + simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; +}; + + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_DOCUMENT_H diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/document_stream-inl.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/document_stream-inl.h new file mode 100644 index 000000000000..0dd10feac284 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/document_stream-inl.h @@ -0,0 +1,432 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_DOCUMENT_STREAM_INL_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_DOCUMENT_STREAM_INL_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/document_stream.h" +#include "simdjson/generic/ondemand/document-inl.h" +#include "simdjson/generic/implementation_simdjson_result_base-inl.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include +#include + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +#ifdef SIMDJSON_THREADS_ENABLED + +inline void stage1_worker::finish() { + // After calling "run" someone would call finish() to wait + // for the end of the processing. + // This function will wait until either the thread has done + // the processing or, else, the destructor has been called. + std::unique_lock lock(locking_mutex); + cond_var.wait(lock, [this]{return has_work == false;}); +} + +inline stage1_worker::~stage1_worker() { + // The thread may never outlive the stage1_worker instance + // and will always be stopped/joined before the stage1_worker + // instance is gone. + stop_thread(); +} + +inline void stage1_worker::start_thread() { + std::unique_lock lock(locking_mutex); + if(thread.joinable()) { + return; // This should never happen but we never want to create more than one thread. + } + thread = std::thread([this]{ + while(true) { + std::unique_lock thread_lock(locking_mutex); + // We wait for either "run" or "stop_thread" to be called. + cond_var.wait(thread_lock, [this]{return has_work || !can_work;}); + // If, for some reason, the stop_thread() method was called (i.e., the + // destructor of stage1_worker is called, then we want to immediately destroy + // the thread (and not do any more processing). + if(!can_work) { + break; + } + this->owner->stage1_thread_error = this->owner->run_stage1(*this->stage1_thread_parser, + this->_next_batch_start); + this->has_work = false; + // The condition variable call should be moved after thread_lock.unlock() for performance + // reasons but thread sanitizers may report it as a data race if we do. + // See https://stackoverflow.com/questions/35775501/c-should-condition-variable-be-notified-under-lock + cond_var.notify_one(); // will notify "finish" + thread_lock.unlock(); + } + } + ); +} + + +inline void stage1_worker::stop_thread() { + std::unique_lock lock(locking_mutex); + // We have to make sure that all locks can be released. + can_work = false; + has_work = false; + cond_var.notify_all(); + lock.unlock(); + if(thread.joinable()) { + thread.join(); + } +} + +inline void stage1_worker::run(document_stream * ds, parser * stage1, size_t next_batch_start) { + std::unique_lock lock(locking_mutex); + owner = ds; + _next_batch_start = next_batch_start; + stage1_thread_parser = stage1; + has_work = true; + // The condition variable call should be moved after thread_lock.unlock() for performance + // reasons but thread sanitizers may report it as a data race if we do. + // See https://stackoverflow.com/questions/35775501/c-should-condition-variable-be-notified-under-lock + cond_var.notify_one(); // will notify the thread lock that we have work + lock.unlock(); +} + +#endif // SIMDJSON_THREADS_ENABLED + +simdjson_inline document_stream::document_stream( + ondemand::parser &_parser, + const uint8_t *_buf, + size_t _len, + size_t _batch_size, + bool _allow_comma_separated +) noexcept + : parser{&_parser}, + buf{_buf}, + len{_len}, + batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size}, + allow_comma_separated{_allow_comma_separated}, + error{SUCCESS} + #ifdef SIMDJSON_THREADS_ENABLED + , use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change + #endif +{ +#ifdef SIMDJSON_THREADS_ENABLED + if(worker.get() == nullptr) { + error = MEMALLOC; + } +#endif +} + +simdjson_inline document_stream::document_stream() noexcept + : parser{nullptr}, + buf{nullptr}, + len{0}, + batch_size{0}, + allow_comma_separated{false}, + error{UNINITIALIZED} + #ifdef SIMDJSON_THREADS_ENABLED + , use_thread(false) + #endif +{ +} + +simdjson_inline document_stream::~document_stream() noexcept +{ + #ifdef SIMDJSON_THREADS_ENABLED + worker.reset(); + #endif +} + +inline size_t document_stream::size_in_bytes() const noexcept { + return len; +} + +inline size_t document_stream::truncated_bytes() const noexcept { + if(error == CAPACITY) { return len - batch_start; } + return parser->implementation->structural_indexes[parser->implementation->n_structural_indexes] - parser->implementation->structural_indexes[parser->implementation->n_structural_indexes + 1]; +} + +simdjson_inline document_stream::iterator::iterator() noexcept + : stream{nullptr}, finished{true} { +} + +simdjson_inline document_stream::iterator::iterator(document_stream* _stream, bool is_end) noexcept + : stream{_stream}, finished{is_end} { +} + +simdjson_inline simdjson_result document_stream::iterator::operator*() noexcept { + return simdjson_result(stream->doc, stream->error); +} + +simdjson_inline document_stream::iterator& document_stream::iterator::operator++() noexcept { + // If there is an error, then we want the iterator + // to be finished, no matter what. (E.g., we do not + // keep generating documents with errors, or go beyond + // a document with errors.) + // + // Users do not have to call "operator*()" when they use operator++, + // so we need to end the stream in the operator++ function. + // + // Note that setting finished = true is essential otherwise + // we would enter an infinite loop. + if (stream->error) { finished = true; } + // Note that stream->error() is guarded against error conditions + // (it will immediately return if stream->error casts to false). + // In effect, this next function does nothing when (stream->error) + // is true (hence the risk of an infinite loop). + stream->next(); + // If that was the last document, we're finished. + // It is the only type of error we do not want to appear + // in operator*. + if (stream->error == EMPTY) { finished = true; } + // If we had any other kind of error (not EMPTY) then we want + // to pass it along to the operator* and we cannot mark the result + // as "finished" just yet. + return *this; +} + +simdjson_inline bool document_stream::iterator::operator!=(const document_stream::iterator &other) const noexcept { + return finished != other.finished; +} + +simdjson_inline document_stream::iterator document_stream::begin() noexcept { + start(); + // If there are no documents, we're finished. + return iterator(this, error == EMPTY); +} + +simdjson_inline document_stream::iterator document_stream::end() noexcept { + return iterator(this, true); +} + +inline void document_stream::start() noexcept { + if (error) { return; } + error = parser->allocate(batch_size); + if (error) { return; } + // Always run the first stage 1 parse immediately + batch_start = 0; + error = run_stage1(*parser, batch_start); + while(error == EMPTY) { + // In exceptional cases, we may start with an empty block + batch_start = next_batch_start(); + if (batch_start >= len) { return; } + error = run_stage1(*parser, batch_start); + } + if (error) { return; } + doc_index = batch_start; + doc = document(json_iterator(&buf[batch_start], parser)); + doc.iter._streaming = true; + + #ifdef SIMDJSON_THREADS_ENABLED + if (use_thread && next_batch_start() < len) { + // Kick off the first thread on next batch if needed + error = stage1_thread_parser.allocate(batch_size); + if (error) { return; } + worker->start_thread(); + start_stage1_thread(); + if (error) { return; } + } + #endif // SIMDJSON_THREADS_ENABLED +} + +inline void document_stream::next() noexcept { + // We always enter at once once in an error condition. + if (error) { return; } + next_document(); + if (error) { return; } + auto cur_struct_index = doc.iter._root - parser->implementation->structural_indexes.get(); + doc_index = batch_start + parser->implementation->structural_indexes[cur_struct_index]; + + // Check if at end of structural indexes (i.e. at end of batch) + if(cur_struct_index >= static_cast(parser->implementation->n_structural_indexes)) { + error = EMPTY; + // Load another batch (if available) + while (error == EMPTY) { + batch_start = next_batch_start(); + if (batch_start >= len) { break; } + #ifdef SIMDJSON_THREADS_ENABLED + if(use_thread) { + load_from_stage1_thread(); + } else { + error = run_stage1(*parser, batch_start); + } + #else + error = run_stage1(*parser, batch_start); + #endif + /** + * Whenever we move to another window, we need to update all pointers to make + * it appear as if the input buffer started at the beginning of the window. + * + * Take this input: + * + * {"z":5} {"1":1,"2":2,"4":4} [7, 10, 9] [15, 11, 12, 13] [154, 110, 112, 1311] + * + * Say you process the following window... + * + * '{"z":5} {"1":1,"2":2,"4":4} [7, 10, 9]' + * + * When you do so, the json_iterator has a pointer at the beginning of the memory region + * (pointing at the beginning of '{"z"...'. + * + * When you move to the window that starts at... + * + * '[7, 10, 9] [15, 11, 12, 13] ... + * + * then it is not sufficient to just run stage 1. You also need to re-anchor the + * json_iterator so that it believes we are starting at '[7, 10, 9]...'. + * + * Under the DOM front-end, this gets done automatically because the parser owns + * the pointer the data, and when you call stage1 and then stage2 on the same + * parser, then stage2 will run on the pointer acquired by stage1. + * + * That is, stage1 calls "this->buf = _buf" so the parser remembers the buffer that + * we used. But json_iterator has no callback when stage1 is called on the parser. + * In fact, I think that the parser is unaware of json_iterator. + * + * + * So we need to re-anchor the json_iterator after each call to stage 1 so that + * all of the pointers are in sync. + */ + doc.iter = json_iterator(&buf[batch_start], parser); + doc.iter._streaming = true; + /** + * End of resync. + */ + + if (error) { continue; } // If the error was EMPTY, we may want to load another batch. + doc_index = batch_start; + } + } +} + +inline void document_stream::next_document() noexcept { + // Go to next place where depth=0 (document depth) + error = doc.iter.skip_child(0); + if (error) { return; } + // Always set depth=1 at the start of document + doc.iter._depth = 1; + // consume comma if comma separated is allowed + if (allow_comma_separated) { doc.iter.consume_character(','); } + // Resets the string buffer at the beginning, thus invalidating the strings. + doc.iter._string_buf_loc = parser->string_buf.get(); + doc.iter._root = doc.iter.position(); +} + +inline size_t document_stream::next_batch_start() const noexcept { + return batch_start + parser->implementation->structural_indexes[parser->implementation->n_structural_indexes]; +} + +inline error_code document_stream::run_stage1(ondemand::parser &p, size_t _batch_start) noexcept { + // This code only updates the structural index in the parser, it does not update any json_iterator + // instance. + size_t remaining = len - _batch_start; + if (remaining <= batch_size) { + return p.implementation->stage1(&buf[_batch_start], remaining, stage1_mode::streaming_final); + } else { + return p.implementation->stage1(&buf[_batch_start], batch_size, stage1_mode::streaming_partial); + } +} + +simdjson_inline size_t document_stream::iterator::current_index() const noexcept { + return stream->doc_index; +} + +simdjson_inline std::string_view document_stream::iterator::source() const noexcept { + auto depth = stream->doc.iter.depth(); + auto cur_struct_index = stream->doc.iter._root - stream->parser->implementation->structural_indexes.get(); + + // If at root, process the first token to determine if scalar value + if (stream->doc.iter.at_root()) { + switch (stream->buf[stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index]]) { + case '{': case '[': // Depth=1 already at start of document + break; + case '}': case ']': + depth--; + break; + default: // Scalar value document + // TODO: We could remove trailing whitespaces + // This returns a string spanning from start of value to the beginning of the next document (excluded) + { + auto next_index = stream->parser->implementation->structural_indexes[++cur_struct_index]; + // normally the length would be next_index - current_index() - 1, except for the last document + size_t svlen = next_index - current_index(); + const char *start = reinterpret_cast(stream->buf) + current_index(); + while(svlen > 1 && (std::isspace(start[svlen-1]) || start[svlen-1] == '\0')) { + svlen--; + } + return std::string_view(start, svlen); + } + } + cur_struct_index++; + } + + while (cur_struct_index <= static_cast(stream->parser->implementation->n_structural_indexes)) { + switch (stream->buf[stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index]]) { + case '{': case '[': + depth++; + break; + case '}': case ']': + depth--; + break; + } + if (depth == 0) { break; } + cur_struct_index++; + } + + return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->parser->implementation->structural_indexes[cur_struct_index] - current_index() + stream->batch_start + 1);; +} + +inline error_code document_stream::iterator::error() const noexcept { + return stream->error; +} + +#ifdef SIMDJSON_THREADS_ENABLED + +inline void document_stream::load_from_stage1_thread() noexcept { + worker->finish(); + // Swap to the parser that was loaded up in the thread. Make sure the parser has + // enough memory to swap to, as well. + std::swap(stage1_thread_parser,*parser); + error = stage1_thread_error; + if (error) { return; } + + // If there's anything left, start the stage 1 thread! + if (next_batch_start() < len) { + start_stage1_thread(); + } +} + +inline void document_stream::start_stage1_thread() noexcept { + // we call the thread on a lambda that will update + // this->stage1_thread_error + // there is only one thread that may write to this value + // TODO this is NOT exception-safe. + this->stage1_thread_error = UNINITIALIZED; // In case something goes wrong, make sure it's an error + size_t _next_batch_start = this->next_batch_start(); + + worker->run(this, & this->stage1_thread_parser, _next_batch_start); +} + +#endif // SIMDJSON_THREADS_ENABLED + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +simdjson_inline simdjson_result::simdjson_result( + error_code error +) noexcept : + implementation_simdjson_result_base(error) +{ +} +simdjson_inline simdjson_result::simdjson_result( + SIMDJSON_IMPLEMENTATION::ondemand::document_stream &&value +) noexcept : + implementation_simdjson_result_base( + std::forward(value) + ) +{ +} + +} + +#endif // SIMDJSON_GENERIC_ONDEMAND_DOCUMENT_STREAM_INL_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/document_stream.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/document_stream.h new file mode 100644 index 000000000000..ef1265fb3386 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/document_stream.h @@ -0,0 +1,337 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_DOCUMENT_STREAM_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_DOCUMENT_STREAM_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/implementation_simdjson_result_base.h" +#include "simdjson/generic/ondemand/document.h" +#include "simdjson/generic/ondemand/parser.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#ifdef SIMDJSON_THREADS_ENABLED +#include +#include +#include +#endif + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +#ifdef SIMDJSON_THREADS_ENABLED +/** @private Custom worker class **/ +struct stage1_worker { + stage1_worker() noexcept = default; + stage1_worker(const stage1_worker&) = delete; + stage1_worker(stage1_worker&&) = delete; + stage1_worker operator=(const stage1_worker&) = delete; + ~stage1_worker(); + /** + * We only start the thread when it is needed, not at object construction, this may throw. + * You should only call this once. + **/ + void start_thread(); + /** + * Start a stage 1 job. You should first call 'run', then 'finish'. + * You must call start_thread once before. + */ + void run(document_stream * ds, parser * stage1, size_t next_batch_start); + /** Wait for the run to finish (blocking). You should first call 'run', then 'finish'. **/ + void finish(); + +private: + + /** + * Normally, we would never stop the thread. But we do in the destructor. + * This function is only safe assuming that you are not waiting for results. You + * should have called run, then finish, and be done. + **/ + void stop_thread(); + + std::thread thread{}; + /** These three variables define the work done by the thread. **/ + ondemand::parser * stage1_thread_parser{}; + size_t _next_batch_start{}; + document_stream * owner{}; + /** + * We have two state variables. This could be streamlined to one variable in the future but + * we use two for clarity. + */ + bool has_work{false}; + bool can_work{true}; + + /** + * We lock using a mutex. + */ + std::mutex locking_mutex{}; + std::condition_variable cond_var{}; + + friend class document_stream; +}; +#endif // SIMDJSON_THREADS_ENABLED + +/** + * A forward-only stream of documents. + * + * Produced by parser::iterate_many. + * + */ +class document_stream { +public: + /** + * Construct an uninitialized document_stream. + * + * ```c++ + * document_stream docs; + * auto error = parser.iterate_many(json).get(docs); + * ``` + */ + simdjson_inline document_stream() noexcept; + /** Move one document_stream to another. */ + simdjson_inline document_stream(document_stream &&other) noexcept = default; + /** Move one document_stream to another. */ + simdjson_inline document_stream &operator=(document_stream &&other) noexcept = default; + + simdjson_inline ~document_stream() noexcept; + + /** + * Returns the input size in bytes. + */ + inline size_t size_in_bytes() const noexcept; + + /** + * After iterating through the stream, this method + * returns the number of bytes that were not parsed at the end + * of the stream. If truncated_bytes() differs from zero, + * then the input was truncated maybe because incomplete JSON + * documents were found at the end of the stream. You + * may need to process the bytes in the interval [size_in_bytes()-truncated_bytes(), size_in_bytes()). + * + * You should only call truncated_bytes() after streaming through all + * documents, like so: + * + * document_stream stream = parser.iterate_many(json,window); + * for(auto & doc : stream) { + * // do something with doc + * } + * size_t truncated = stream.truncated_bytes(); + * + */ + inline size_t truncated_bytes() const noexcept; + + class iterator { + public: + using value_type = simdjson_result; + using reference = simdjson_result; + using pointer = void; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + /** + * Default constructor. + */ + simdjson_inline iterator() noexcept; + /** + * Get the current document (or error). + */ + simdjson_inline reference operator*() noexcept; + /** + * Advance to the next document (prefix). + */ + inline iterator& operator++() noexcept; + /** + * Check if we're at the end yet. + * @param other the end iterator to compare to. + */ + simdjson_inline bool operator!=(const iterator &other) const noexcept; + /** + * @private + * + * Gives the current index in the input document in bytes. + * + * document_stream stream = parser.parse_many(json,window); + * for(auto i = stream.begin(); i != stream.end(); ++i) { + * auto doc = *i; + * size_t index = i.current_index(); + * } + * + * This function (current_index()) is experimental and the usage + * may change in future versions of simdjson: we find the API somewhat + * awkward and we would like to offer something friendlier. + */ + simdjson_inline size_t current_index() const noexcept; + + /** + * @private + * + * Gives a view of the current document at the current position. + * + * document_stream stream = parser.iterate_many(json,window); + * for(auto i = stream.begin(); i != stream.end(); ++i) { + * std::string_view v = i.source(); + * } + * + * The returned string_view instance is simply a map to the (unparsed) + * source string: it may thus include white-space characters and all manner + * of padding. + * + * This function (source()) is experimental and the usage + * may change in future versions of simdjson: we find the API somewhat + * awkward and we would like to offer something friendlier. + * + */ + simdjson_inline std::string_view source() const noexcept; + + /** + * Returns error of the stream (if any). + */ + inline error_code error() const noexcept; + + private: + simdjson_inline iterator(document_stream *s, bool finished) noexcept; + /** The document_stream we're iterating through. */ + document_stream* stream; + /** Whether we're finished or not. */ + bool finished; + + friend class document; + friend class document_stream; + friend class json_iterator; + }; + + /** + * Start iterating the documents in the stream. + */ + simdjson_inline iterator begin() noexcept; + /** + * The end of the stream, for iterator comparison purposes. + */ + simdjson_inline iterator end() noexcept; + +private: + + document_stream &operator=(const document_stream &) = delete; // Disallow copying + document_stream(const document_stream &other) = delete; // Disallow copying + + /** + * Construct a document_stream. Does not allocate or parse anything until the iterator is + * used. + * + * @param parser is a reference to the parser instance used to generate this document_stream + * @param buf is the raw byte buffer we need to process + * @param len is the length of the raw byte buffer in bytes + * @param batch_size is the size of the windows (must be strictly greater or equal to the largest JSON document) + */ + simdjson_inline document_stream( + ondemand::parser &parser, + const uint8_t *buf, + size_t len, + size_t batch_size, + bool allow_comma_separated + ) noexcept; + + /** + * Parse the first document in the buffer. Used by begin(), to handle allocation and + * initialization. + */ + inline void start() noexcept; + + /** + * Parse the next document found in the buffer previously given to document_stream. + * + * The content should be a valid JSON document encoded as UTF-8. If there is a + * UTF-8 BOM, the parser skips it. + * + * You do NOT need to pre-allocate a parser. This function takes care of + * pre-allocating a capacity defined by the batch_size defined when creating the + * document_stream object. + * + * The function returns simdjson::EMPTY if there is no more data to be parsed. + * + * The function returns simdjson::SUCCESS (as integer = 0) in case of success + * and indicates that the buffer has successfully been parsed to the end. + * Every document it contained has been parsed without error. + * + * The function returns an error code from simdjson/simdjson.h in case of failure + * such as simdjson::CAPACITY, simdjson::MEMALLOC, simdjson::DEPTH_ERROR and so forth; + * the simdjson::error_message function converts these error codes into a string). + * + * You can also check validity by calling parser.is_valid(). The same parser can + * and should be reused for the other documents in the buffer. + */ + inline void next() noexcept; + + /** Move the json_iterator of the document to the location of the next document in the stream. */ + inline void next_document() noexcept; + + /** Get the next document index. */ + inline size_t next_batch_start() const noexcept; + + /** Pass the next batch through stage 1 with the given parser. */ + inline error_code run_stage1(ondemand::parser &p, size_t batch_start) noexcept; + + // Fields + ondemand::parser *parser; + const uint8_t *buf; + size_t len; + size_t batch_size; + bool allow_comma_separated; + /** + * We are going to use just one document instance. The document owns + * the json_iterator. It implies that we only ever pass a reference + * to the document to the users. + */ + document doc{}; + /** The error (or lack thereof) from the current document. */ + error_code error; + size_t batch_start{0}; + size_t doc_index{}; + + #ifdef SIMDJSON_THREADS_ENABLED + /** Indicates whether we use threads. Note that this needs to be a constant during the execution of the parsing. */ + bool use_thread; + + inline void load_from_stage1_thread() noexcept; + + /** Start a thread to run stage 1 on the next batch. */ + inline void start_stage1_thread() noexcept; + + /** Wait for the stage 1 thread to finish and capture the results. */ + inline void finish_stage1_thread() noexcept; + + /** The error returned from the stage 1 thread. */ + error_code stage1_thread_error{UNINITIALIZED}; + /** The thread used to run stage 1 against the next batch in the background. */ + std::unique_ptr worker{new(std::nothrow) stage1_worker()}; + /** + * The parser used to run stage 1 in the background. Will be swapped + * with the regular parser when finished. + */ + ondemand::parser stage1_thread_parser{}; + + friend struct stage1_worker; + #endif // SIMDJSON_THREADS_ENABLED + + friend class parser; + friend class document; + friend class json_iterator; + friend struct simdjson_result; + friend struct simdjson::internal::simdjson_result_base; +}; // document_stream + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { +template<> +struct simdjson_result : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::document_stream &&value) noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + simdjson_inline simdjson_result() noexcept = default; +}; + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_DOCUMENT_STREAM_H diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/field-inl.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/field-inl.h new file mode 100644 index 000000000000..ae5189536f8e --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/field-inl.h @@ -0,0 +1,129 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_FIELD_INL_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_FIELD_INL_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/field.h" +#include "simdjson/generic/ondemand/value-inl.h" +#include "simdjson/generic/ondemand/value_iterator-inl.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +// clang 6 does not think the default constructor can be noexcept, so we make it explicit +simdjson_inline field::field() noexcept : std::pair() {} + +simdjson_inline field::field(raw_json_string key, ondemand::value &&value) noexcept + : std::pair(key, std::forward(value)) +{ +} + +simdjson_inline simdjson_result field::start(value_iterator &parent_iter) noexcept { + raw_json_string key; + SIMDJSON_TRY( parent_iter.field_key().get(key) ); + SIMDJSON_TRY( parent_iter.field_value() ); + return field::start(parent_iter, key); +} + +simdjson_inline simdjson_result field::start(const value_iterator &parent_iter, raw_json_string key) noexcept { + return field(key, parent_iter.child()); +} + +simdjson_inline simdjson_warn_unused simdjson_result field::unescaped_key(bool allow_replacement) noexcept { + SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() but Visual Studio won't let us. + simdjson_result answer = first.unescape(second.iter.json_iter(), allow_replacement); + first.consume(); + return answer; +} + +template +simdjson_inline simdjson_warn_unused error_code field::unescaped_key(string_type& receiver, bool allow_replacement) noexcept { + std::string_view key; + SIMDJSON_TRY( unescaped_key(allow_replacement).get(key) ); + receiver = key; + return SUCCESS; +} + +simdjson_inline raw_json_string field::key() const noexcept { + SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. + return first; +} + + +simdjson_inline std::string_view field::key_raw_json_token() const noexcept { + SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. + return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(-1) - first.buf + 1); +} + +simdjson_inline std::string_view field::escaped_key() const noexcept { + SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. + auto end_quote = second.iter._json_iter->token.peek(-1); + while(*end_quote != '"') end_quote--; + return std::string_view(reinterpret_cast(first.buf), end_quote - first.buf); +} + +simdjson_inline value &field::value() & noexcept { + return second; +} + +simdjson_inline value field::value() && noexcept { + return std::forward(*this).second; +} + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +simdjson_inline simdjson_result::simdjson_result( + SIMDJSON_IMPLEMENTATION::ondemand::field &&value +) noexcept : + implementation_simdjson_result_base( + std::forward(value) + ) +{ +} +simdjson_inline simdjson_result::simdjson_result( + error_code error +) noexcept : + implementation_simdjson_result_base(error) +{ +} + +simdjson_inline simdjson_result simdjson_result::key() noexcept { + if (error()) { return error(); } + return first.key(); +} + +simdjson_inline simdjson_result simdjson_result::key_raw_json_token() noexcept { + if (error()) { return error(); } + return first.key_raw_json_token(); +} + +simdjson_inline simdjson_result simdjson_result::escaped_key() noexcept { + if (error()) { return error(); } + return first.escaped_key(); +} + +simdjson_inline simdjson_result simdjson_result::unescaped_key(bool allow_replacement) noexcept { + if (error()) { return error(); } + return first.unescaped_key(allow_replacement); +} + +template +simdjson_inline error_code simdjson_result::unescaped_key(string_type &receiver, bool allow_replacement) noexcept { + if (error()) { return error(); } + return first.unescaped_key(receiver, allow_replacement); +} + +simdjson_inline simdjson_result simdjson_result::value() noexcept { + if (error()) { return error(); } + return std::move(first.value()); +} + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_FIELD_INL_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/field.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/field.h new file mode 100644 index 000000000000..71344362f9ef --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/field.h @@ -0,0 +1,113 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_FIELD_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_FIELD_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/implementation_simdjson_result_base.h" +#include "simdjson/generic/ondemand/raw_json_string.h" +#include "simdjson/generic/ondemand/value.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +/** + * A JSON field (key/value pair) in an object. + * + * Returned from object iteration. + * + * Extends from std::pair so you can use C++ algorithms that rely on pairs. + */ +class field : public std::pair { +public: + /** + * Create a new invalid field. + * + * Exists so you can declare a variable and later assign to it before use. + */ + simdjson_inline field() noexcept; + + /** + * Get the key as a string_view (for higher speed, consider raw_key). + * We deliberately use a more cumbersome name (unescaped_key) to force users + * to think twice about using it. + * + * This consumes the key: once you have called unescaped_key(), you cannot + * call it again nor can you call key(). + */ + simdjson_inline simdjson_warn_unused simdjson_result unescaped_key(bool allow_replacement = false) noexcept; + /** + * Get the key as a string_view (for higher speed, consider raw_key). + * We deliberately use a more cumbersome name (unescaped_key) to force users + * to think twice about using it. The content is stored in the receiver. + * + * This consumes the key: once you have called unescaped_key(), you cannot + * call it again nor can you call key(). + */ + template + simdjson_inline simdjson_warn_unused error_code unescaped_key(string_type& receiver, bool allow_replacement = false) noexcept; + /** + * Get the key as a raw_json_string. Can be used for direct comparison with + * an unescaped C string: e.g., key() == "test". This does not count as + * consumption of the content: you can safely call it repeatedly. + * See escaped_key() for a similar function which returns + * a more convenient std::string_view result. + */ + simdjson_inline raw_json_string key() const noexcept; + /** + * Get the unprocessed key as a string_view. This includes the quotes and may include + * some spaces after the last quote. This does not count as + * consumption of the content: you can safely call it repeatedly. + * See escaped_key(). + */ + simdjson_inline std::string_view key_raw_json_token() const noexcept; + /** + * Get the key as a string_view. This does not include the quotes and + * the string is unprocessed key so it may contain escape characters + * (e.g., \uXXXX or \n). It does not count as a consumption of the content: + * you can safely call it repeatedly. Use unescaped_key() to get the unescaped key. + */ + simdjson_inline std::string_view escaped_key() const noexcept; + /** + * Get the field value. + */ + simdjson_inline ondemand::value &value() & noexcept; + /** + * @overload ondemand::value &ondemand::value() & noexcept + */ + simdjson_inline ondemand::value value() && noexcept; + +protected: + simdjson_inline field(raw_json_string key, ondemand::value &&value) noexcept; + static simdjson_inline simdjson_result start(value_iterator &parent_iter) noexcept; + static simdjson_inline simdjson_result start(const value_iterator &parent_iter, raw_json_string key) noexcept; + friend struct simdjson_result; + friend class object_iterator; +}; + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +template<> +struct simdjson_result : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::field &&value) noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + simdjson_inline simdjson_result() noexcept = default; + + simdjson_inline simdjson_result unescaped_key(bool allow_replacement = false) noexcept; + template + simdjson_inline error_code unescaped_key(string_type &receiver, bool allow_replacement = false) noexcept; + simdjson_inline simdjson_result key() noexcept; + simdjson_inline simdjson_result key_raw_json_token() noexcept; + simdjson_inline simdjson_result escaped_key() noexcept; + simdjson_inline simdjson_result value() noexcept; +}; + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_FIELD_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/json_iterator-inl.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/json_iterator-inl.h new file mode 100644 index 000000000000..6a054af813d5 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/json_iterator-inl.h @@ -0,0 +1,444 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_INL_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_INL_H +#include "simdjson/internal/dom_parser_implementation.h" +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/json_iterator.h" +#include "simdjson/generic/ondemand/parser.h" +#include "simdjson/generic/ondemand/raw_json_string.h" +#include "simdjson/generic/ondemand/logger-inl.h" +#include "simdjson/generic/ondemand/parser-inl.h" +#include "simdjson/generic/ondemand/token_iterator-inl.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +simdjson_inline json_iterator::json_iterator(json_iterator &&other) noexcept + : token(std::forward(other.token)), + parser{other.parser}, + _string_buf_loc{other._string_buf_loc}, + error{other.error}, + _depth{other._depth}, + _root{other._root}, + _streaming{other._streaming} +{ + other.parser = nullptr; +} +simdjson_inline json_iterator &json_iterator::operator=(json_iterator &&other) noexcept { + token = other.token; + parser = other.parser; + _string_buf_loc = other._string_buf_loc; + error = other.error; + _depth = other._depth; + _root = other._root; + _streaming = other._streaming; + other.parser = nullptr; + return *this; +} + +simdjson_inline json_iterator::json_iterator(const uint8_t *buf, ondemand::parser *_parser) noexcept + : token(buf, &_parser->implementation->structural_indexes[0]), + parser{_parser}, + _string_buf_loc{parser->string_buf.get()}, + _depth{1}, + _root{parser->implementation->structural_indexes.get()}, + _streaming{false} + +{ + logger::log_headers(); +#if SIMDJSON_CHECK_EOF + assert_more_tokens(); +#endif +} + +#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON +simdjson_inline json_iterator::json_iterator(const uint8_t *buf, ondemand::parser *_parser, bool streaming) noexcept + : token(buf, &_parser->implementation->structural_indexes[0]), + parser{_parser}, + _string_buf_loc{parser->string_buf.get()}, + _depth{1}, + _root{parser->implementation->structural_indexes.get()}, + _streaming{streaming} + +{ + logger::log_headers(); +#if SIMDJSON_CHECK_EOF + assert_more_tokens(); +#endif +} +#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON + +inline void json_iterator::rewind() noexcept { + token.set_position( root_position() ); + logger::log_headers(); // We start again + _string_buf_loc = parser->string_buf.get(); + _depth = 1; +} + +inline bool json_iterator::balanced() const noexcept { + token_iterator ti(token); + int32_t count{0}; + ti.set_position( root_position() ); + while(ti.peek() <= peek_last()) { + switch (*ti.return_current_and_advance()) + { + case '[': case '{': + count++; + break; + case ']': case '}': + count--; + break; + default: + break; + } + } + return count == 0; +} + + +// GCC 7 warns when the first line of this function is inlined away into oblivion due to the caller +// relating depth and parent_depth, which is a desired effect. The warning does not show up if the +// skip_child() function is not marked inline). +SIMDJSON_PUSH_DISABLE_WARNINGS +SIMDJSON_DISABLE_STRICT_OVERFLOW_WARNING +simdjson_warn_unused simdjson_inline error_code json_iterator::skip_child(depth_t parent_depth) noexcept { + if (depth() <= parent_depth) { return SUCCESS; } + switch (*return_current_and_advance()) { + // TODO consider whether matching braces is a requirement: if non-matching braces indicates + // *missing* braces, then future lookups are not in the object/arrays they think they are, + // violating the rule "validate enough structure that the user can be confident they are + // looking at the right values." + // PERF TODO we can eliminate the switch here with a lookup of how much to add to depth + + // For the first open array/object in a value, we've already incremented depth, so keep it the same + // We never stop at colon, but if we did, it wouldn't affect depth + case '[': case '{': case ':': + logger::log_start_value(*this, "skip"); + break; + // If there is a comma, we have just finished a value in an array/object, and need to get back in + case ',': + logger::log_value(*this, "skip"); + break; + // ] or } means we just finished a value and need to jump out of the array/object + case ']': case '}': + logger::log_end_value(*this, "skip"); + _depth--; + if (depth() <= parent_depth) { return SUCCESS; } +#if SIMDJSON_CHECK_EOF + // If there are no more tokens, the parent is incomplete. + if (at_end()) { return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "Missing [ or { at start"); } +#endif // SIMDJSON_CHECK_EOF + break; + case '"': + if(*peek() == ':') { + // We are at a key!!! + // This might happen if you just started an object and you skip it immediately. + // Performance note: it would be nice to get rid of this check as it is somewhat + // expensive. + // https://github.com/simdjson/simdjson/issues/1742 + logger::log_value(*this, "key"); + return_current_and_advance(); // eat up the ':' + break; // important!!! + } + simdjson_fallthrough; + // Anything else must be a scalar value + default: + // For the first scalar, we will have incremented depth already, so we decrement it here. + logger::log_value(*this, "skip"); + _depth--; + if (depth() <= parent_depth) { return SUCCESS; } + break; + } + + // Now that we've considered the first value, we only increment/decrement for arrays/objects + while (position() < end_position()) { + switch (*return_current_and_advance()) { + case '[': case '{': + logger::log_start_value(*this, "skip"); + _depth++; + break; + // TODO consider whether matching braces is a requirement: if non-matching braces indicates + // *missing* braces, then future lookups are not in the object/arrays they think they are, + // violating the rule "validate enough structure that the user can be confident they are + // looking at the right values." + // PERF TODO we can eliminate the switch here with a lookup of how much to add to depth + case ']': case '}': + logger::log_end_value(*this, "skip"); + _depth--; + if (depth() <= parent_depth) { return SUCCESS; } + break; + default: + logger::log_value(*this, "skip", ""); + break; + } + } + + return report_error(TAPE_ERROR, "not enough close braces"); +} + +SIMDJSON_POP_DISABLE_WARNINGS + +simdjson_inline bool json_iterator::at_root() const noexcept { + return position() == root_position(); +} + +simdjson_inline bool json_iterator::is_single_token() const noexcept { + return parser->implementation->n_structural_indexes == 1; +} + +simdjson_inline bool json_iterator::streaming() const noexcept { + return _streaming; +} + +simdjson_inline token_position json_iterator::root_position() const noexcept { + return _root; +} + +simdjson_inline void json_iterator::assert_at_document_depth() const noexcept { + SIMDJSON_ASSUME( _depth == 1 ); +} + +simdjson_inline void json_iterator::assert_at_root() const noexcept { + SIMDJSON_ASSUME( _depth == 1 ); +#ifndef SIMDJSON_CLANG_VISUAL_STUDIO + // Under Visual Studio, the next SIMDJSON_ASSUME fails with: the argument + // has side effects that will be discarded. + SIMDJSON_ASSUME( token.position() == _root ); +#endif +} + +simdjson_inline void json_iterator::assert_more_tokens(uint32_t required_tokens) const noexcept { + assert_valid_position(token._position + required_tokens - 1); +} + +simdjson_inline void json_iterator::assert_valid_position(token_position position) const noexcept { +#ifndef SIMDJSON_CLANG_VISUAL_STUDIO + SIMDJSON_ASSUME( position >= &parser->implementation->structural_indexes[0] ); + SIMDJSON_ASSUME( position < &parser->implementation->structural_indexes[parser->implementation->n_structural_indexes] ); +#endif +} + +simdjson_inline bool json_iterator::at_end() const noexcept { + return position() == end_position(); +} +simdjson_inline token_position json_iterator::end_position() const noexcept { + uint32_t n_structural_indexes{parser->implementation->n_structural_indexes}; + return &parser->implementation->structural_indexes[n_structural_indexes]; +} + +inline std::string json_iterator::to_string() const noexcept { + if( !is_alive() ) { return "dead json_iterator instance"; } + const char * current_structural = reinterpret_cast(token.peek()); + return std::string("json_iterator [ depth : ") + std::to_string(_depth) + + std::string(", structural : '") + std::string(current_structural,1) + + std::string("', offset : ") + std::to_string(token.current_offset()) + + std::string("', error : ") + error_message(error) + + std::string(" ]"); +} + +inline simdjson_result json_iterator::current_location() const noexcept { + if (!is_alive()) { // Unrecoverable error + if (!at_root()) { + return reinterpret_cast(token.peek(-1)); + } else { + return reinterpret_cast(token.peek()); + } + } + if (at_end()) { + return OUT_OF_BOUNDS; + } + return reinterpret_cast(token.peek()); +} + +simdjson_inline bool json_iterator::is_alive() const noexcept { + return parser; +} + +simdjson_inline void json_iterator::abandon() noexcept { + parser = nullptr; + _depth = 0; +} + +simdjson_inline const uint8_t *json_iterator::return_current_and_advance() noexcept { +#if SIMDJSON_CHECK_EOF + assert_more_tokens(); +#endif // SIMDJSON_CHECK_EOF + return token.return_current_and_advance(); +} + +simdjson_inline const uint8_t *json_iterator::unsafe_pointer() const noexcept { + // deliberately done without safety guard: + return token.peek(); +} + +simdjson_inline const uint8_t *json_iterator::peek(int32_t delta) const noexcept { +#if SIMDJSON_CHECK_EOF + assert_more_tokens(delta+1); +#endif // SIMDJSON_CHECK_EOF + return token.peek(delta); +} + +simdjson_inline uint32_t json_iterator::peek_length(int32_t delta) const noexcept { +#if SIMDJSON_CHECK_EOF + assert_more_tokens(delta+1); +#endif // #if SIMDJSON_CHECK_EOF + return token.peek_length(delta); +} + +simdjson_inline const uint8_t *json_iterator::peek(token_position position) const noexcept { + // todo: currently we require end-of-string buffering, but the following + // assert_valid_position should be turned on if/when we lift that condition. + // assert_valid_position(position); + // This is almost surely related to SIMDJSON_CHECK_EOF but given that SIMDJSON_CHECK_EOF + // is ON by default, we have no choice but to disable it for real with a comment. + return token.peek(position); +} + +simdjson_inline uint32_t json_iterator::peek_length(token_position position) const noexcept { +#if SIMDJSON_CHECK_EOF + assert_valid_position(position); +#endif // SIMDJSON_CHECK_EOF + return token.peek_length(position); +} +simdjson_inline uint32_t json_iterator::peek_root_length(token_position position) const noexcept { +#if SIMDJSON_CHECK_EOF + assert_valid_position(position); +#endif // SIMDJSON_CHECK_EOF + return token.peek_root_length(position); +} + +simdjson_inline token_position json_iterator::last_position() const noexcept { + // The following line fails under some compilers... + // SIMDJSON_ASSUME(parser->implementation->n_structural_indexes > 0); + // since it has side-effects. + uint32_t n_structural_indexes{parser->implementation->n_structural_indexes}; + SIMDJSON_ASSUME(n_structural_indexes > 0); + return &parser->implementation->structural_indexes[n_structural_indexes - 1]; +} +simdjson_inline const uint8_t *json_iterator::peek_last() const noexcept { + return token.peek(last_position()); +} + +simdjson_inline void json_iterator::ascend_to(depth_t parent_depth) noexcept { + SIMDJSON_ASSUME(parent_depth >= 0 && parent_depth < INT32_MAX - 1); + SIMDJSON_ASSUME(_depth == parent_depth + 1); + _depth = parent_depth; +} + +simdjson_inline void json_iterator::descend_to(depth_t child_depth) noexcept { + SIMDJSON_ASSUME(child_depth >= 1 && child_depth < INT32_MAX); + SIMDJSON_ASSUME(_depth == child_depth - 1); + _depth = child_depth; +} + +simdjson_inline depth_t json_iterator::depth() const noexcept { + return _depth; +} + +simdjson_inline uint8_t *&json_iterator::string_buf_loc() noexcept { + return _string_buf_loc; +} + +simdjson_inline error_code json_iterator::report_error(error_code _error, const char *message) noexcept { + SIMDJSON_ASSUME(_error != SUCCESS && _error != UNINITIALIZED && _error != INCORRECT_TYPE && _error != NO_SUCH_FIELD); + logger::log_error(*this, message); + error = _error; + return error; +} + +simdjson_inline token_position json_iterator::position() const noexcept { + return token.position(); +} + +simdjson_inline simdjson_result json_iterator::unescape(raw_json_string in, bool allow_replacement) noexcept { +#if SIMDJSON_DEVELOPMENT_CHECKS + auto result = parser->unescape(in, _string_buf_loc, allow_replacement); + SIMDJSON_ASSUME(!parser->string_buffer_overflow(_string_buf_loc)); + return result; +#else + return parser->unescape(in, _string_buf_loc, allow_replacement); +#endif +} + +simdjson_inline simdjson_result json_iterator::unescape_wobbly(raw_json_string in) noexcept { +#if SIMDJSON_DEVELOPMENT_CHECKS + auto result = parser->unescape_wobbly(in, _string_buf_loc); + SIMDJSON_ASSUME(!parser->string_buffer_overflow(_string_buf_loc)); + return result; +#else + return parser->unescape_wobbly(in, _string_buf_loc); +#endif +} + +simdjson_inline void json_iterator::reenter_child(token_position position, depth_t child_depth) noexcept { + SIMDJSON_ASSUME(child_depth >= 1 && child_depth < INT32_MAX); + SIMDJSON_ASSUME(_depth == child_depth - 1); +#if SIMDJSON_DEVELOPMENT_CHECKS +#ifndef SIMDJSON_CLANG_VISUAL_STUDIO + SIMDJSON_ASSUME(size_t(child_depth) < parser->max_depth()); + SIMDJSON_ASSUME(position >= parser->start_positions[child_depth]); +#endif +#endif + token.set_position(position); + _depth = child_depth; +} + +simdjson_inline error_code json_iterator::consume_character(char c) noexcept { + if (*peek() == c) { + return_current_and_advance(); + return SUCCESS; + } + return TAPE_ERROR; +} + +#if SIMDJSON_DEVELOPMENT_CHECKS + +simdjson_inline token_position json_iterator::start_position(depth_t depth) const noexcept { + SIMDJSON_ASSUME(size_t(depth) < parser->max_depth()); + return size_t(depth) < parser->max_depth() ? parser->start_positions[depth] : 0; +} + +simdjson_inline void json_iterator::set_start_position(depth_t depth, token_position position) noexcept { + SIMDJSON_ASSUME(size_t(depth) < parser->max_depth()); + if(size_t(depth) < parser->max_depth()) { parser->start_positions[depth] = position; } +} + +#endif + + +simdjson_inline error_code json_iterator::optional_error(error_code _error, const char *message) noexcept { + SIMDJSON_ASSUME(_error == INCORRECT_TYPE || _error == NO_SUCH_FIELD); + logger::log_error(*this, message); + return _error; +} + + +simdjson_warn_unused simdjson_inline bool json_iterator::copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept { + // This function is not expected to be called in performance-sensitive settings. + // Let us guard against silly cases: + if((N < max_len) || (N == 0)) { return false; } + // Copy to the buffer. + std::memcpy(tmpbuf, json, max_len); + if(N > max_len) { // We pad whatever remains with ' '. + std::memset(tmpbuf + max_len, ' ', N - max_len); + } + return true; +} + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +simdjson_inline simdjson_result::simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::json_iterator &&value) noexcept + : implementation_simdjson_result_base(std::forward(value)) {} +simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept + : implementation_simdjson_result_base(error) {} + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_INL_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/json_iterator.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/json_iterator.h new file mode 100644 index 000000000000..ee9872cb6c62 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/json_iterator.h @@ -0,0 +1,338 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/implementation_simdjson_result_base.h" +#include "simdjson/generic/ondemand/token_iterator.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +/** + * Iterates through JSON tokens, keeping track of depth and string buffer. + * + * @private This is not intended for external use. + */ +class json_iterator { +protected: + token_iterator token{}; + ondemand::parser *parser{}; + /** + * Next free location in the string buffer. + * + * Used by raw_json_string::unescape() to have a place to unescape strings to. + */ + uint8_t *_string_buf_loc{}; + /** + * JSON error, if there is one. + * + * INCORRECT_TYPE and NO_SUCH_FIELD are *not* stored here, ever. + * + * PERF NOTE: we *hope* this will be elided into control flow, as it is only used (a) in the first + * iteration of the loop, or (b) for the final iteration after a missing comma is found in ++. If + * this is not elided, we should make sure it's at least not using up a register. Failing that, + * we should store it in document so there's only one of them. + */ + error_code error{SUCCESS}; + /** + * Depth of the current token in the JSON. + * + * - 0 = finished with document + * - 1 = document root value (could be [ or {, not yet known) + * - 2 = , or } inside root array/object + * - 3 = key or value inside root array/object. + */ + depth_t _depth{}; + /** + * Beginning of the document indexes. + * Normally we have root == parser->implementation->structural_indexes.get() + * but this may differ, especially in streaming mode (where we have several + * documents); + */ + token_position _root{}; + /** + * Normally, a json_iterator operates over a single document, but in + * some cases, we may have a stream of documents. This attribute is meant + * as meta-data: the json_iterator works the same irrespective of the + * value of this attribute. + */ + bool _streaming{false}; + +public: + simdjson_inline json_iterator() noexcept = default; + simdjson_inline json_iterator(json_iterator &&other) noexcept; + simdjson_inline json_iterator &operator=(json_iterator &&other) noexcept; + simdjson_inline explicit json_iterator(const json_iterator &other) noexcept = default; + simdjson_inline json_iterator &operator=(const json_iterator &other) noexcept = default; + /** + * Skips a JSON value, whether it is a scalar, array or object. + */ + simdjson_warn_unused simdjson_inline error_code skip_child(depth_t parent_depth) noexcept; + + /** + * Tell whether the iterator is still at the start + */ + simdjson_inline bool at_root() const noexcept; + + /** + * Tell whether we should be expected to run in streaming + * mode (iterating over many documents). It is pure metadata + * that does not affect how the iterator works. It is used by + * start_root_array() and start_root_object(). + */ + simdjson_inline bool streaming() const noexcept; + + /** + * Get the root value iterator + */ + simdjson_inline token_position root_position() const noexcept; + /** + * Assert that we are at the document depth (== 1) + */ + simdjson_inline void assert_at_document_depth() const noexcept; + /** + * Assert that we are at the root of the document + */ + simdjson_inline void assert_at_root() const noexcept; + + /** + * Tell whether the iterator is at the EOF mark + */ + simdjson_inline bool at_end() const noexcept; + + /** + * Tell whether the iterator is live (has not been moved). + */ + simdjson_inline bool is_alive() const noexcept; + + /** + * Abandon this iterator, setting depth to 0 (as if the document is finished). + */ + simdjson_inline void abandon() noexcept; + + /** + * Advance the current token without modifying depth. + */ + simdjson_inline const uint8_t *return_current_and_advance() noexcept; + + /** + * Returns true if there is a single token in the index (i.e., it is + * a JSON with a scalar value such as a single number). + * + * @return whether there is a single token + */ + simdjson_inline bool is_single_token() const noexcept; + + /** + * Assert that there are at least the given number of tokens left. + * + * Has no effect in release builds. + */ + simdjson_inline void assert_more_tokens(uint32_t required_tokens=1) const noexcept; + /** + * Assert that the given position addresses an actual token (is within bounds). + * + * Has no effect in release builds. + */ + simdjson_inline void assert_valid_position(token_position position) const noexcept; + /** + * Get the JSON text for a given token (relative). + * + * This is not null-terminated; it is a view into the JSON. + * + * @param delta The relative position of the token to retrieve. e.g. 0 = next token, -1 = prev token. + * + * TODO consider a string_view, assuming the length will get stripped out by the optimizer when + * it is not used ... + */ + simdjson_inline const uint8_t *peek(int32_t delta=0) const noexcept; + /** + * Get the maximum length of the JSON text for the current token (or relative). + * + * The length will include any whitespace at the end of the token. + * + * @param delta The relative position of the token to retrieve. e.g. 0 = next token, -1 = prev token. + */ + simdjson_inline uint32_t peek_length(int32_t delta=0) const noexcept; + /** + * Get a pointer to the current location in the input buffer. + * + * This is not null-terminated; it is a view into the JSON. + * + * You may be pointing outside of the input buffer: it is not generally + * safe to dereference this pointer. + */ + simdjson_inline const uint8_t *unsafe_pointer() const noexcept; + /** + * Get the JSON text for a given token. + * + * This is not null-terminated; it is a view into the JSON. + * + * @param position The position of the token to retrieve. + * + * TODO consider a string_view, assuming the length will get stripped out by the optimizer when + * it is not used ... + */ + simdjson_inline const uint8_t *peek(token_position position) const noexcept; + /** + * Get the maximum length of the JSON text for the current token (or relative). + * + * The length will include any whitespace at the end of the token. + * + * @param position The position of the token to retrieve. + */ + simdjson_inline uint32_t peek_length(token_position position) const noexcept; + /** + * Get the maximum length of the JSON text for the current root token. + * + * The length will include any whitespace at the end of the token. + * + * @param position The position of the token to retrieve. + */ + simdjson_inline uint32_t peek_root_length(token_position position) const noexcept; + /** + * Get the JSON text for the last token in the document. + * + * This is not null-terminated; it is a view into the JSON. + * + * TODO consider a string_view, assuming the length will get stripped out by the optimizer when + * it is not used ... + */ + simdjson_inline const uint8_t *peek_last() const noexcept; + + /** + * Ascend one level. + * + * Validates that the depth - 1 == parent_depth. + * + * @param parent_depth the expected parent depth. + */ + simdjson_inline void ascend_to(depth_t parent_depth) noexcept; + + /** + * Descend one level. + * + * Validates that the new depth == child_depth. + * + * @param child_depth the expected child depth. + */ + simdjson_inline void descend_to(depth_t child_depth) noexcept; + simdjson_inline void descend_to(depth_t child_depth, int32_t delta) noexcept; + + /** + * Get current depth. + */ + simdjson_inline depth_t depth() const noexcept; + + /** + * Get current (writeable) location in the string buffer. + */ + simdjson_inline uint8_t *&string_buf_loc() noexcept; + + /** + * Report an unrecoverable error, preventing further iteration. + * + * @param error The error to report. Must not be SUCCESS, UNINITIALIZED, INCORRECT_TYPE, or NO_SUCH_FIELD. + * @param message An error message to report with the error. + */ + simdjson_inline error_code report_error(error_code error, const char *message) noexcept; + + /** + * Log error, but don't stop iteration. + * @param error The error to report. Must be INCORRECT_TYPE, or NO_SUCH_FIELD. + * @param message An error message to report with the error. + */ + simdjson_inline error_code optional_error(error_code error, const char *message) noexcept; + + /** + * Take an input in json containing max_len characters and attempt to copy it over to tmpbuf, a buffer with + * N bytes of capacity. It will return false if N is too small (smaller than max_len) of if it is zero. + * The buffer (tmpbuf) is padded with space characters. + */ + simdjson_warn_unused simdjson_inline bool copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept; + + simdjson_inline token_position position() const noexcept; + /** + * Write the raw_json_string to the string buffer and return a string_view. + * Each raw_json_string should be unescaped once, or else the string buffer might + * overflow. + */ + simdjson_inline simdjson_result unescape(raw_json_string in, bool allow_replacement) noexcept; + simdjson_inline simdjson_result unescape_wobbly(raw_json_string in) noexcept; + + simdjson_inline void reenter_child(token_position position, depth_t child_depth) noexcept; + + simdjson_inline error_code consume_character(char c) noexcept; +#if SIMDJSON_DEVELOPMENT_CHECKS + simdjson_inline token_position start_position(depth_t depth) const noexcept; + simdjson_inline void set_start_position(depth_t depth, token_position position) noexcept; +#endif + + /* Useful for debugging and logging purposes. */ + inline std::string to_string() const noexcept; + + /** + * Returns the current location in the document if in bounds. + */ + inline simdjson_result current_location() const noexcept; + + /** + * Updates this json iterator so that it is back at the beginning of the document, + * as if it had just been created. + */ + inline void rewind() noexcept; + /** + * This checks whether the {,},[,] are balanced so that the document + * ends with proper zero depth. This requires scanning the whole document + * and it may be expensive. It is expected that it will be rarely called. + * It does not attempt to match { with } and [ with ]. + */ + inline bool balanced() const noexcept; +protected: + simdjson_inline json_iterator(const uint8_t *buf, ondemand::parser *parser) noexcept; +#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON + simdjson_inline json_iterator(const uint8_t *buf, ondemand::parser *parser, bool streaming) noexcept; +#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON + /// The last token before the end + simdjson_inline token_position last_position() const noexcept; + /// The token *at* the end. This points at gibberish and should only be used for comparison. + simdjson_inline token_position end_position() const noexcept; + /// The end of the buffer. + simdjson_inline token_position end() const noexcept; + + friend class document; + friend class document_stream; + friend class object; + friend class array; + friend class value; + friend class raw_json_string; + friend class parser; + friend class value_iterator; + friend class field; + template + friend simdjson_inline void logger::log_line(const json_iterator &iter, const char *title_prefix, const char *title, std::string_view detail, int delta, int depth_delta, logger::log_level level, Args&&... args) noexcept; + template + friend simdjson_inline void logger::log_line(const json_iterator &iter, token_position index, depth_t depth, const char *title_prefix, const char *title, std::string_view detail, logger::log_level level, Args&&... args) noexcept; +}; // json_iterator + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +template<> +struct simdjson_result : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::json_iterator &&value) noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + + simdjson_inline simdjson_result() noexcept = default; +}; + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_ITERATOR_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/json_type-inl.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/json_type-inl.h new file mode 100644 index 000000000000..7fa5eeb30f27 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/json_type-inl.h @@ -0,0 +1,117 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_JSON_TYPE_INL_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_JSON_TYPE_INL_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/json_type.h" +#include "simdjson/generic/implementation_simdjson_result_base-inl.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +inline std::ostream& operator<<(std::ostream& out, json_type type) noexcept { + switch (type) { + case json_type::array: out << "array"; break; + case json_type::object: out << "object"; break; + case json_type::number: out << "number"; break; + case json_type::string: out << "string"; break; + case json_type::boolean: out << "boolean"; break; + case json_type::null: out << "null"; break; + default: SIMDJSON_UNREACHABLE(); + } + return out; +} + +#if SIMDJSON_EXCEPTIONS +inline std::ostream& operator<<(std::ostream& out, simdjson_result &type) noexcept(false) { + return out << type.value(); +} +#endif + + + +simdjson_inline number_type number::get_number_type() const noexcept { + return type; +} + +simdjson_inline bool number::is_uint64() const noexcept { + return get_number_type() == number_type::unsigned_integer; +} + +simdjson_inline uint64_t number::get_uint64() const noexcept { + return payload.unsigned_integer; +} + +simdjson_inline number::operator uint64_t() const noexcept { + return get_uint64(); +} + +simdjson_inline bool number::is_int64() const noexcept { + return get_number_type() == number_type::signed_integer; +} + +simdjson_inline int64_t number::get_int64() const noexcept { + return payload.signed_integer; +} + +simdjson_inline number::operator int64_t() const noexcept { + return get_int64(); +} + +simdjson_inline bool number::is_double() const noexcept { + return get_number_type() == number_type::floating_point_number; +} + +simdjson_inline double number::get_double() const noexcept { + return payload.floating_point_number; +} + +simdjson_inline number::operator double() const noexcept { + return get_double(); +} + +simdjson_inline double number::as_double() const noexcept { + if(is_double()) { + return payload.floating_point_number; + } + if(is_int64()) { + return double(payload.signed_integer); + } + return double(payload.unsigned_integer); +} + +simdjson_inline void number::append_s64(int64_t value) noexcept { + payload.signed_integer = value; + type = number_type::signed_integer; +} + +simdjson_inline void number::append_u64(uint64_t value) noexcept { + payload.unsigned_integer = value; + type = number_type::unsigned_integer; +} + +simdjson_inline void number::append_double(double value) noexcept { + payload.floating_point_number = value; + type = number_type::floating_point_number; +} + +simdjson_inline void number::skip_double() noexcept { + type = number_type::floating_point_number; +} + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +simdjson_inline simdjson_result::simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::json_type &&value) noexcept + : implementation_simdjson_result_base(std::forward(value)) {} +simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept + : implementation_simdjson_result_base(error) {} + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_TYPE_INL_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/json_type.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/json_type.h new file mode 100644 index 000000000000..b5a970433e8c --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/json_type.h @@ -0,0 +1,160 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_JSON_TYPE_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_JSON_TYPE_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/implementation_simdjson_result_base.h" +#include "simdjson/generic/numberparsing.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +/** + * The type of a JSON value. + */ +enum class json_type { + // Start at 1 to catch uninitialized / default values more easily + array=1, ///< A JSON array ( [ 1, 2, 3 ... ] ) + object, ///< A JSON object ( { "a": 1, "b" 2, ... } ) + number, ///< A JSON number ( 1 or -2.3 or 4.5e6 ...) + string, ///< A JSON string ( "a" or "hello world\n" ...) + boolean, ///< A JSON boolean (true or false) + null ///< A JSON null (null) +}; + +/** + * A type representing a JSON number. + * The design of the struct is deliberately straight-forward. All + * functions return standard values with no error check. + */ +struct number { + + /** + * return the automatically determined type of + * the number: number_type::floating_point_number, + * number_type::signed_integer or number_type::unsigned_integer. + * + * enum class number_type { + * floating_point_number=1, /// a binary64 number + * signed_integer, /// a signed integer that fits in a 64-bit word using two's complement + * unsigned_integer /// a positive integer larger or equal to 1<<63 + * }; + */ + simdjson_inline ondemand::number_type get_number_type() const noexcept; + /** + * return true if the automatically determined type of + * the number is number_type::unsigned_integer. + */ + simdjson_inline bool is_uint64() const noexcept; + /** + * return the value as a uint64_t, only valid if is_uint64() is true. + */ + simdjson_inline uint64_t get_uint64() const noexcept; + simdjson_inline operator uint64_t() const noexcept; + + /** + * return true if the automatically determined type of + * the number is number_type::signed_integer. + */ + simdjson_inline bool is_int64() const noexcept; + /** + * return the value as a int64_t, only valid if is_int64() is true. + */ + simdjson_inline int64_t get_int64() const noexcept; + simdjson_inline operator int64_t() const noexcept; + + + /** + * return true if the automatically determined type of + * the number is number_type::floating_point_number. + */ + simdjson_inline bool is_double() const noexcept; + /** + * return the value as a double, only valid if is_double() is true. + */ + simdjson_inline double get_double() const noexcept; + simdjson_inline operator double() const noexcept; + + /** + * Convert the number to a double. Though it always succeed, the conversion + * may be lossy if the number cannot be represented exactly. + */ + simdjson_inline double as_double() const noexcept; + + +protected: + /** + * The next block of declaration is designed so that we can call the number parsing + * functions on a number type. They are protected and should never be used outside + * of the core simdjson library. + */ + friend class value_iterator; + template + friend error_code numberparsing::write_float(const uint8_t *const src, bool negative, uint64_t i, const uint8_t * start_digits, size_t digit_count, int64_t exponent, W &writer); + template + friend error_code numberparsing::parse_number(const uint8_t *const src, W &writer); + /** Store a signed 64-bit value to the number. */ + simdjson_inline void append_s64(int64_t value) noexcept; + /** Store an unsigned 64-bit value to the number. */ + simdjson_inline void append_u64(uint64_t value) noexcept; + /** Store a double value to the number. */ + simdjson_inline void append_double(double value) noexcept; + /** Specifies that the value is a double, but leave it undefined. */ + simdjson_inline void skip_double() noexcept; + /** + * End of friend declarations. + */ + + /** + * Our attributes are a union type (size = 64 bits) + * followed by a type indicator. + */ + union { + double floating_point_number; + int64_t signed_integer; + uint64_t unsigned_integer; + } payload{0}; + number_type type{number_type::signed_integer}; +}; + +/** + * Write the JSON type to the output stream + * + * @param out The output stream. + * @param type The json_type. + */ +inline std::ostream& operator<<(std::ostream& out, json_type type) noexcept; + +#if SIMDJSON_EXCEPTIONS +/** + * Send JSON type to an output stream. + * + * @param out The output stream. + * @param type The json_type. + * @throw simdjson_error if the result being printed has an error. If there is an error with the + * underlying output stream, that error will be propagated (simdjson_error will not be + * thrown). + */ +inline std::ostream& operator<<(std::ostream& out, simdjson_result &type) noexcept(false); +#endif + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +template<> +struct simdjson_result : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::json_type &&value) noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + simdjson_inline simdjson_result() noexcept = default; + simdjson_inline ~simdjson_result() noexcept = default; ///< @private +}; + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_JSON_TYPE_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/logger-inl.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/logger-inl.h new file mode 100644 index 000000000000..268fb2d1be12 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/logger-inl.h @@ -0,0 +1,225 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_LOGGER_INL_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_LOGGER_INL_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/logger.h" +#include "simdjson/generic/ondemand/json_iterator.h" +#include "simdjson/generic/ondemand/value_iterator.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include +#include + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { +namespace logger { + +static constexpr const char * DASHES = "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"; +static constexpr const int LOG_EVENT_LEN = 20; +static constexpr const int LOG_BUFFER_LEN = 30; +static constexpr const int LOG_SMALL_BUFFER_LEN = 10; +static int log_depth = 0; // Not threadsafe. Log only. + +// Helper to turn unprintable or newline characters into spaces +static inline char printable_char(char c) { + if (c >= 0x20) { + return c; + } else { + return ' '; + } +} + +template +static inline std::string string_format(const std::string& format, const Args&... args) +{ + SIMDJSON_PUSH_DISABLE_ALL_WARNINGS + int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; + auto size = static_cast(size_s); + if (size <= 0) return std::string(); + std::unique_ptr buf(new char[size]); + std::snprintf(buf.get(), size, format.c_str(), args...); + SIMDJSON_POP_DISABLE_WARNINGS + return std::string(buf.get(), buf.get() + size - 1); +} + +static inline log_level get_log_level_from_env() +{ + SIMDJSON_PUSH_DISABLE_WARNINGS + SIMDJSON_DISABLE_DEPRECATED_WARNING // Disable CRT_SECURE warning on MSVC: manually verified this is safe + char *lvl = getenv("SIMDJSON_LOG_LEVEL"); + SIMDJSON_POP_DISABLE_WARNINGS + if (lvl && simdjson_strcasecmp(lvl, "ERROR") == 0) { return log_level::error; } + return log_level::info; +} + +static inline log_level log_threshold() +{ + static log_level threshold = get_log_level_from_env(); + return threshold; +} + +static inline bool should_log(log_level level) +{ + return level >= log_threshold(); +} + +inline void log_event(const json_iterator &iter, const char *type, std::string_view detail, int delta, int depth_delta) noexcept { + log_line(iter, "", type, detail, delta, depth_delta, log_level::info); +} + +inline void log_value(const json_iterator &iter, token_position index, depth_t depth, const char *type, std::string_view detail) noexcept { + log_line(iter, index, depth, "", type, detail, log_level::info); +} +inline void log_value(const json_iterator &iter, const char *type, std::string_view detail, int delta, int depth_delta) noexcept { + log_line(iter, "", type, detail, delta, depth_delta, log_level::info); +} + +inline void log_start_value(const json_iterator &iter, token_position index, depth_t depth, const char *type, std::string_view detail) noexcept { + log_line(iter, index, depth, "+", type, detail, log_level::info); + if (LOG_ENABLED) { log_depth++; } +} +inline void log_start_value(const json_iterator &iter, const char *type, int delta, int depth_delta) noexcept { + log_line(iter, "+", type, "", delta, depth_delta, log_level::info); + if (LOG_ENABLED) { log_depth++; } +} + +inline void log_end_value(const json_iterator &iter, const char *type, int delta, int depth_delta) noexcept { + if (LOG_ENABLED) { log_depth--; } + log_line(iter, "-", type, "", delta, depth_delta, log_level::info); +} + +inline void log_error(const json_iterator &iter, const char *error, const char *detail, int delta, int depth_delta) noexcept { + log_line(iter, "ERROR: ", error, detail, delta, depth_delta, log_level::error); +} +inline void log_error(const json_iterator &iter, token_position index, depth_t depth, const char *error, const char *detail) noexcept { + log_line(iter, index, depth, "ERROR: ", error, detail, log_level::error); +} + +inline void log_event(const value_iterator &iter, const char *type, std::string_view detail, int delta, int depth_delta) noexcept { + log_event(iter.json_iter(), type, detail, delta, depth_delta); +} + +inline void log_value(const value_iterator &iter, const char *type, std::string_view detail, int delta, int depth_delta) noexcept { + log_value(iter.json_iter(), type, detail, delta, depth_delta); +} + +inline void log_start_value(const value_iterator &iter, const char *type, int delta, int depth_delta) noexcept { + log_start_value(iter.json_iter(), type, delta, depth_delta); +} + +inline void log_end_value(const value_iterator &iter, const char *type, int delta, int depth_delta) noexcept { + log_end_value(iter.json_iter(), type, delta, depth_delta); +} + +inline void log_error(const value_iterator &iter, const char *error, const char *detail, int delta, int depth_delta) noexcept { + log_error(iter.json_iter(), error, detail, delta, depth_delta); +} + +inline void log_headers() noexcept { + if (LOG_ENABLED) { + if (simdjson_unlikely(should_log(log_level::info))) { + // Technically a static variable is not thread-safe, but if you are using threads and logging... well... + static bool displayed_hint{false}; + log_depth = 0; + printf("\n"); + if (!displayed_hint) { + // We only print this helpful header once. + printf("# Logging provides the depth and position of the iterator user-visible steps:\n"); + printf("# +array says 'this is where we were when we discovered the start array'\n"); + printf( + "# -array says 'this is where we were when we ended the array'\n"); + printf("# skip says 'this is a structural or value I am skipping'\n"); + printf("# +/-skip says 'this is a start/end array or object I am skipping'\n"); + printf("#\n"); + printf("# The indentation of the terms (array, string,...) indicates the depth,\n"); + printf("# in addition to the depth being displayed.\n"); + printf("#\n"); + printf("# Every token in the document has a single depth determined by the tokens before it,\n"); + printf("# and is not affected by what the token actually is.\n"); + printf("#\n"); + printf("# Not all structural elements are presented as tokens in the logs.\n"); + printf("#\n"); + printf("# We never give control to the user within an empty array or an empty object.\n"); + printf("#\n"); + printf("# Inside an array, having a depth greater than the array's depth means that\n"); + printf("# we are pointing inside a value.\n"); + printf("# Having a depth equal to the array means that we are pointing right before a value.\n"); + printf("# Having a depth smaller than the array means that we have moved beyond the array.\n"); + displayed_hint = true; + } + printf("\n"); + printf("| %-*s ", LOG_EVENT_LEN, "Event"); + printf("| %-*s ", LOG_BUFFER_LEN, "Buffer"); + printf("| %-*s ", LOG_SMALL_BUFFER_LEN, "Next"); + // printf("| %-*s ", 5, "Next#"); + printf("| %-*s ", 5, "Depth"); + printf("| Detail "); + printf("|\n"); + + printf("|%.*s", LOG_EVENT_LEN + 2, DASHES); + printf("|%.*s", LOG_BUFFER_LEN + 2, DASHES); + printf("|%.*s", LOG_SMALL_BUFFER_LEN + 2, DASHES); + // printf("|%.*s", 5+2, DASHES); + printf("|%.*s", 5 + 2, DASHES); + printf("|--------"); + printf("|\n"); + fflush(stdout); + } + } +} + +template +inline void log_line(const json_iterator &iter, const char *title_prefix, const char *title, std::string_view detail, int delta, int depth_delta, log_level level, Args&&... args) noexcept { + log_line(iter, iter.position()+delta, depth_t(iter.depth()+depth_delta), title_prefix, title, detail, level, std::forward(args)...); +} + +template +inline void log_line(const json_iterator &iter, token_position index, depth_t depth, const char *title_prefix, const char *title, std::string_view detail, log_level level, Args&&... args) noexcept { + if (LOG_ENABLED) { + if (simdjson_unlikely(should_log(level))) { + const int indent = depth * 2; + const auto buf = iter.token.buf; + auto msg = string_format(title, std::forward(args)...); + printf("| %*s%s%-*s ", indent, "", title_prefix, + LOG_EVENT_LEN - indent - int(strlen(title_prefix)), msg.c_str()); + { + // Print the current structural. + printf("| "); + // Before we begin, the index might point right before the document. + // This could be unsafe, see https://github.com/simdjson/simdjson/discussions/1938 + if (index < iter._root) { + printf("%*s", LOG_BUFFER_LEN, ""); + } else { + auto current_structural = &buf[*index]; + for (int i = 0; i < LOG_BUFFER_LEN; i++) { + printf("%c", printable_char(current_structural[i])); + } + } + printf(" "); + } + { + // Print the next structural. + printf("| "); + auto next_structural = &buf[*(index + 1)]; + for (int i = 0; i < LOG_SMALL_BUFFER_LEN; i++) { + printf("%c", printable_char(next_structural[i])); + } + printf(" "); + } + // printf("| %5u ", *(index+1)); + printf("| %5i ", depth); + printf("| %6.*s ", int(detail.size()), detail.data()); + printf("|\n"); + fflush(stdout); + } + } +} + +} // namespace logger +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_LOGGER_INL_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/logger.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/logger.h new file mode 100644 index 000000000000..7696600f98c2 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/logger.h @@ -0,0 +1,58 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_LOGGER_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_LOGGER_H +#include "simdjson/generic/ondemand/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +// Logging should be free unless SIMDJSON_VERBOSE_LOGGING is set. Importantly, it is critical +// that the call to the log functions be side-effect free. Thus, for example, you should not +// create temporary std::string instances. +namespace logger { + +enum class log_level : int32_t { + info = 0, + error = 1 +}; + +#if SIMDJSON_VERBOSE_LOGGING + static constexpr const bool LOG_ENABLED = true; +#else + static constexpr const bool LOG_ENABLED = false; +#endif + +// We do not want these functions to be 'really inlined' since real inlining is +// for performance purposes and if you are using the loggers, you do not care about +// performance (or should not). +static inline void log_headers() noexcept; +// If args are provided, title will be treated as format string +template +static inline void log_line(const json_iterator &iter, token_position index, depth_t depth, const char *title_prefix, const char *title, std::string_view detail, logger::log_level level, Args&&... args) noexcept; +template +static inline void log_line(const json_iterator &iter, const char *title_prefix, const char *title, std::string_view detail, int delta, int depth_delta, logger::log_level level, Args&&... args) noexcept; +static inline void log_event(const json_iterator &iter, const char *type, std::string_view detail="", int delta=0, int depth_delta=0) noexcept; +static inline void log_value(const json_iterator &iter, token_position index, depth_t depth, const char *type, std::string_view detail="") noexcept; +static inline void log_value(const json_iterator &iter, const char *type, std::string_view detail="", int delta=-1, int depth_delta=0) noexcept; +static inline void log_start_value(const json_iterator &iter, token_position index, depth_t depth, const char *type, std::string_view detail="") noexcept; +static inline void log_start_value(const json_iterator &iter, const char *type, int delta=-1, int depth_delta=0) noexcept; +static inline void log_end_value(const json_iterator &iter, const char *type, int delta=-1, int depth_delta=0) noexcept; + +static inline void log_error(const json_iterator &iter, token_position index, depth_t depth, const char *error, const char *detail="") noexcept; +static inline void log_error(const json_iterator &iter, const char *error, const char *detail="", int delta=-1, int depth_delta=0) noexcept; + +static inline void log_event(const value_iterator &iter, const char *type, std::string_view detail="", int delta=0, int depth_delta=0) noexcept; +static inline void log_value(const value_iterator &iter, const char *type, std::string_view detail="", int delta=-1, int depth_delta=0) noexcept; +static inline void log_start_value(const value_iterator &iter, const char *type, int delta=-1, int depth_delta=0) noexcept; +static inline void log_end_value(const value_iterator &iter, const char *type, int delta=-1, int depth_delta=0) noexcept; +static inline void log_error(const value_iterator &iter, const char *error, const char *detail="", int delta=-1, int depth_delta=0) noexcept; + +} // namespace logger +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_LOGGER_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/object-inl.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/object-inl.h new file mode 100644 index 000000000000..624d66838306 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/object-inl.h @@ -0,0 +1,276 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_OBJECT_INL_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_OBJECT_INL_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/field.h" +#include "simdjson/generic/ondemand/object.h" +#include "simdjson/generic/ondemand/object_iterator.h" +#include "simdjson/generic/ondemand/raw_json_string.h" +#include "simdjson/generic/ondemand/json_iterator.h" +#include "simdjson/generic/ondemand/value-inl.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +simdjson_inline simdjson_result object::find_field_unordered(const std::string_view key) & noexcept { + bool has_value; + SIMDJSON_TRY( iter.find_field_unordered_raw(key).get(has_value) ); + if (!has_value) { + logger::log_line(iter.json_iter(), "ERROR: ", "Cannot find key %.*s", "", -1, 0, logger::log_level::error, static_cast(key.size()), key.data()); + return NO_SUCH_FIELD; + } + return value(iter.child()); +} +simdjson_inline simdjson_result object::find_field_unordered(const std::string_view key) && noexcept { + bool has_value; + SIMDJSON_TRY( iter.find_field_unordered_raw(key).get(has_value) ); + if (!has_value) { + logger::log_line(iter.json_iter(), "ERROR: ", "Cannot find key %.*s", "", -1, 0, logger::log_level::error, static_cast(key.size()), key.data()); + return NO_SUCH_FIELD; + } + return value(iter.child()); +} +simdjson_inline simdjson_result object::operator[](const std::string_view key) & noexcept { + return find_field_unordered(key); +} +simdjson_inline simdjson_result object::operator[](const std::string_view key) && noexcept { + return std::forward(*this).find_field_unordered(key); +} +simdjson_inline simdjson_result object::find_field(const std::string_view key) & noexcept { + bool has_value; + SIMDJSON_TRY( iter.find_field_raw(key).get(has_value) ); + if (!has_value) { + logger::log_line(iter.json_iter(), "ERROR: ", "Cannot find key %.*s", "", -1, 0, logger::log_level::error, static_cast(key.size()), key.data()); + return NO_SUCH_FIELD; + } + return value(iter.child()); +} +simdjson_inline simdjson_result object::find_field(const std::string_view key) && noexcept { + bool has_value; + SIMDJSON_TRY( iter.find_field_raw(key).get(has_value) ); + if (!has_value) { + logger::log_line(iter.json_iter(), "ERROR: ", "Cannot find key %.*s", "", -1, 0, logger::log_level::error, static_cast(key.size()), key.data()); + return NO_SUCH_FIELD; + } + return value(iter.child()); +} + +simdjson_inline simdjson_result object::start(value_iterator &iter) noexcept { + SIMDJSON_TRY( iter.start_object().error() ); + return object(iter); +} +simdjson_inline simdjson_result object::start_root(value_iterator &iter) noexcept { + SIMDJSON_TRY( iter.start_root_object().error() ); + return object(iter); +} +simdjson_inline error_code object::consume() noexcept { + if(iter.is_at_key()) { + /** + * whenever you are pointing at a key, calling skip_child() is + * unsafe because you will hit a string and you will assume that + * it is string value, and this mistake will lead you to make bad + * depth computation. + */ + /** + * We want to 'consume' the key. We could really + * just do _json_iter->return_current_and_advance(); at this + * point, but, for clarity, we will use the high-level API to + * eat the key. We assume that the compiler optimizes away + * most of the work. + */ + simdjson_unused raw_json_string actual_key; + auto error = iter.field_key().get(actual_key); + if (error) { iter.abandon(); return error; }; + // Let us move to the value while we are at it. + if ((error = iter.field_value())) { iter.abandon(); return error; } + } + auto error_skip = iter.json_iter().skip_child(iter.depth()-1); + if(error_skip) { iter.abandon(); } + return error_skip; +} + +simdjson_inline simdjson_result object::raw_json() noexcept { + const uint8_t * starting_point{iter.peek_start()}; + auto error = consume(); + if(error) { return error; } + const uint8_t * final_point{iter._json_iter->peek()}; + return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); +} + +simdjson_inline simdjson_result object::started(value_iterator &iter) noexcept { + SIMDJSON_TRY( iter.started_object().error() ); + return object(iter); +} + +simdjson_inline object object::resume(const value_iterator &iter) noexcept { + return iter; +} + +simdjson_inline object::object(const value_iterator &_iter) noexcept + : iter{_iter} +{ +} + +simdjson_inline simdjson_result object::begin() noexcept { +#if SIMDJSON_DEVELOPMENT_CHECKS + if (!iter.is_at_iterator_start()) { return OUT_OF_ORDER_ITERATION; } +#endif + return object_iterator(iter); +} +simdjson_inline simdjson_result object::end() noexcept { + return object_iterator(iter); +} + +inline simdjson_result object::at_pointer(std::string_view json_pointer) noexcept { + if (json_pointer[0] != '/') { return INVALID_JSON_POINTER; } + json_pointer = json_pointer.substr(1); + size_t slash = json_pointer.find('/'); + std::string_view key = json_pointer.substr(0, slash); + // Grab the child with the given key + simdjson_result child; + + // If there is an escape character in the key, unescape it and then get the child. + size_t escape = key.find('~'); + if (escape != std::string_view::npos) { + // Unescape the key + std::string unescaped(key); + do { + switch (unescaped[escape+1]) { + case '0': + unescaped.replace(escape, 2, "~"); + break; + case '1': + unescaped.replace(escape, 2, "/"); + break; + default: + return INVALID_JSON_POINTER; // "Unexpected ~ escape character in JSON pointer"); + } + escape = unescaped.find('~', escape+1); + } while (escape != std::string::npos); + child = find_field(unescaped); // Take note find_field does not unescape keys when matching + } else { + child = find_field(key); + } + if(child.error()) { + return child; // we do not continue if there was an error + } + // If there is a /, we have to recurse and look up more of the path + if (slash != std::string_view::npos) { + child = child.at_pointer(json_pointer.substr(slash)); + } + return child; +} + +inline simdjson_result object::at_path(std::string_view json_path) noexcept { + auto json_pointer = json_path_to_pointer_conversion(json_path); + if (json_pointer == "-1") { + return INVALID_JSON_POINTER; + } + return at_pointer(json_pointer); +} + +simdjson_inline simdjson_result object::count_fields() & noexcept { + size_t count{0}; + // Important: we do not consume any of the values. + for(simdjson_unused auto v : *this) { count++; } + // The above loop will always succeed, but we want to report errors. + if(iter.error()) { return iter.error(); } + // We need to move back at the start because we expect users to iterate through + // the object after counting the number of elements. + iter.reset_object(); + return count; +} + +simdjson_inline simdjson_result object::is_empty() & noexcept { + bool is_not_empty; + auto error = iter.reset_object().get(is_not_empty); + if(error) { return error; } + return !is_not_empty; +} + +simdjson_inline simdjson_result object::reset() & noexcept { + return iter.reset_object(); +} + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +simdjson_inline simdjson_result::simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::object &&value) noexcept + : implementation_simdjson_result_base(std::forward(value)) {} +simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept + : implementation_simdjson_result_base(error) {} + +simdjson_inline simdjson_result simdjson_result::begin() noexcept { + if (error()) { return error(); } + return first.begin(); +} +simdjson_inline simdjson_result simdjson_result::end() noexcept { + if (error()) { return error(); } + return first.end(); +} +simdjson_inline simdjson_result simdjson_result::find_field_unordered(std::string_view key) & noexcept { + if (error()) { return error(); } + return first.find_field_unordered(key); +} +simdjson_inline simdjson_result simdjson_result::find_field_unordered(std::string_view key) && noexcept { + if (error()) { return error(); } + return std::forward(first).find_field_unordered(key); +} +simdjson_inline simdjson_result simdjson_result::operator[](std::string_view key) & noexcept { + if (error()) { return error(); } + return first[key]; +} +simdjson_inline simdjson_result simdjson_result::operator[](std::string_view key) && noexcept { + if (error()) { return error(); } + return std::forward(first)[key]; +} +simdjson_inline simdjson_result simdjson_result::find_field(std::string_view key) & noexcept { + if (error()) { return error(); } + return first.find_field(key); +} +simdjson_inline simdjson_result simdjson_result::find_field(std::string_view key) && noexcept { + if (error()) { return error(); } + return std::forward(first).find_field(key); +} + +simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { + if (error()) { return error(); } + return first.at_pointer(json_pointer); +} + +simdjson_inline simdjson_result simdjson_result::at_path( + std::string_view json_path) noexcept { + if (error()) { + return error(); + } + return first.at_path(json_path); +} + +inline simdjson_result simdjson_result::reset() noexcept { + if (error()) { return error(); } + return first.reset(); +} + +inline simdjson_result simdjson_result::is_empty() noexcept { + if (error()) { return error(); } + return first.is_empty(); +} + +simdjson_inline simdjson_result simdjson_result::count_fields() & noexcept { + if (error()) { return error(); } + return first.count_fields(); +} + +simdjson_inline simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_OBJECT_INL_H diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/object.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/object.h new file mode 100644 index 000000000000..8e3ed9af39f5 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/object.h @@ -0,0 +1,258 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_OBJECT_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_OBJECT_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/implementation_simdjson_result_base.h" +#include "simdjson/generic/ondemand/value_iterator.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +/** + * A forward-only JSON object field iterator. + */ +class object { +public: + /** + * Create a new invalid object. + * + * Exists so you can declare a variable and later assign to it before use. + */ + simdjson_inline object() noexcept = default; + + simdjson_inline simdjson_result begin() noexcept; + simdjson_inline simdjson_result end() noexcept; + /** + * Look up a field by name on an object (order-sensitive). + * + * The following code reads z, then y, then x, and thus will not retrieve x or y if fed the + * JSON `{ "x": 1, "y": 2, "z": 3 }`: + * + * ```c++ + * simdjson::ondemand::parser parser; + * auto obj = parser.parse(R"( { "x": 1, "y": 2, "z": 3 } )"_padded); + * double z = obj.find_field("z"); + * double y = obj.find_field("y"); + * double x = obj.find_field("x"); + * ``` + * If you have multiple fields with a matching key ({"x": 1, "x": 1}) be mindful + * that only one field is returned. + * + * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys. + * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. + * + * You must consume the fields on an object one at a time. A request for a new key + * invalidates previous field values: it makes them unsafe. The value instance you get + * from `content["bids"]` becomes invalid when you call `content["asks"]`. The array + * given by content["bids"].get_array() should not be accessed after you have called + * content["asks"].get_array(). You can detect such mistakes by first compiling and running + * the code in Debug mode (or with the macro `SIMDJSON_DEVELOPMENT_CHECKS` set to 1): an + * OUT_OF_ORDER_ITERATION error is generated. + * + * You are expected to access keys only once. You should access the value corresponding to a + * key a single time. Doing object["mykey"].to_string() and then again object["mykey"].to_string() + * is an error. + * + * If you expect to have keys with escape characters, please review our documentation. + * + * @param key The key to look up. + * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. + */ + simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; + /** @overload simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; */ + simdjson_inline simdjson_result find_field(std::string_view key) && noexcept; + + /** + * Look up a field by name on an object, without regard to key order. + * + * **Performance Notes:** This is a bit less performant than find_field(), though its effect varies + * and often appears negligible. It starts out normally, starting out at the last field; but if + * the field is not found, it scans from the beginning of the object to see if it missed it. That + * missing case has a non-cache-friendly bump and lots of extra scanning, especially if the object + * in question is large. The fact that the extra code is there also bumps the executable size. + * + * It is the default, however, because it would be highly surprising (and hard to debug) if the + * default behavior failed to look up a field just because it was in the wrong order--and many + * APIs assume this. Therefore, you must be explicit if you want to treat objects as out of order. + * + * Use find_field() if you are sure fields will be in order (or are willing to treat it as if the + * field was not there when they are not in order). + * + * If you have multiple fields with a matching key ({"x": 1, "x": 1}) be mindful + * that only one field is returned. + * + * You must consume the fields on an object one at a time. A request for a new key + * invalidates previous field values: it makes them unsafe. The value instance you get + * from `content["bids"]` becomes invalid when you call `content["asks"]`. The array + * given by content["bids"].get_array() should not be accessed after you have called + * content["asks"].get_array(). You can detect such mistakes by first compiling and running + * the code in Debug mode (or with the macro `SIMDJSON_DEVELOPMENT_CHECKS` set to 1): an + * OUT_OF_ORDER_ITERATION error is generated. + * + * You are expected to access keys only once. You should access the value corresponding to a key + * a single time. Doing object["mykey"].to_string() and then again object["mykey"].to_string() is an error. + * + * If you expect to have keys with escape characters, please review our documentation. + * + * @param key The key to look up. + * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. + */ + simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ + simdjson_inline simdjson_result find_field_unordered(std::string_view key) && noexcept; + /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ + simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; + /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ + simdjson_inline simdjson_result operator[](std::string_view key) && noexcept; + + /** + * Get the value associated with the given JSON pointer. We use the RFC 6901 + * https://tools.ietf.org/html/rfc6901 standard, interpreting the current node + * as the root of its own JSON document. + * + * ondemand::parser parser; + * auto json = R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded; + * auto doc = parser.iterate(json); + * doc.at_pointer("/foo/a/1") == 20 + * + * It is allowed for a key to be the empty string: + * + * ondemand::parser parser; + * auto json = R"({ "": { "a": [ 10, 20, 30 ] }})"_padded; + * auto doc = parser.iterate(json); + * doc.at_pointer("//a/1") == 20 + * + * Note that at_pointer() called on the document automatically calls the document's rewind + * method between each call. It invalidates all previously accessed arrays, objects and values + * that have not been consumed. Yet it is not the case when calling at_pointer on an object + * instance: there is no rewind and no invalidation. + * + * You may call at_pointer more than once on an object, but each time the pointer is advanced + * to be within the value matched by the key indicated by the JSON pointer query. Thus any preceding + * key (as well as the current key) can no longer be used with following JSON pointer calls. + * + * Also note that at_pointer() relies on find_field() which implies that we do not unescape keys when matching. + * + * @return The value associated with the given JSON pointer, or: + * - NO_SUCH_FIELD if a field does not exist in an object + * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length + * - INCORRECT_TYPE if a non-integer is used to access an array + * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed + */ + inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; + + /** + * Get the value associated with the given JSONPath expression. We only support + * JSONPath queries that trivially convertible to JSON Pointer queries: key + * names and array indices. + * + * @return The value associated with the given JSONPath expression, or: + * - INVALID_JSON_POINTER if the JSONPath to JSON Pointer conversion fails + * - NO_SUCH_FIELD if a field does not exist in an object + * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length + * - INCORRECT_TYPE if a non-integer is used to access an array + */ + inline simdjson_result at_path(std::string_view json_path) noexcept; + + /** + * Reset the iterator so that we are pointing back at the + * beginning of the object. You should still consume values only once even if you + * can iterate through the object more than once. If you unescape a string or a key + * within the object more than once, you have unsafe code. Note that rewinding an object + * means that you may need to reparse it anew: it is not a free operation. + * + * @returns true if the object contains some elements (not empty) + */ + inline simdjson_result reset() & noexcept; + /** + * This method scans the beginning of the object and checks whether the + * object is empty. + * The runtime complexity is constant time. After + * calling this function, if successful, the object is 'rewinded' at its + * beginning as if it had never been accessed. If the JSON is malformed (e.g., + * there is a missing comma), then an error is returned and it is no longer + * safe to continue. + */ + inline simdjson_result is_empty() & noexcept; + /** + * This method scans the object and counts the number of key-value pairs. + * The count_fields method should always be called before you have begun + * iterating through the object: it is expected that you are pointing at + * the beginning of the object. + * The runtime complexity is linear in the size of the object. After + * calling this function, if successful, the object is 'rewinded' at its + * beginning as if it had never been accessed. If the JSON is malformed (e.g., + * there is a missing comma), then an error is returned and it is no longer + * safe to continue. + * + * To check that an object is empty, it is more performant to use + * the is_empty() method. + * + * Performance hint: You should only call count_fields() as a last + * resort as it may require scanning the document twice or more. + */ + simdjson_inline simdjson_result count_fields() & noexcept; + /** + * Consumes the object and returns a string_view instance corresponding to the + * object as represented in JSON. It points inside the original byte array containing + * the JSON document. + */ + simdjson_inline simdjson_result raw_json() noexcept; + +protected: + /** + * Go to the end of the object, no matter where you are right now. + */ + simdjson_inline error_code consume() noexcept; + static simdjson_inline simdjson_result start(value_iterator &iter) noexcept; + static simdjson_inline simdjson_result start_root(value_iterator &iter) noexcept; + static simdjson_inline simdjson_result started(value_iterator &iter) noexcept; + static simdjson_inline object resume(const value_iterator &iter) noexcept; + simdjson_inline object(const value_iterator &iter) noexcept; + + simdjson_warn_unused simdjson_inline error_code find_field_raw(const std::string_view key) noexcept; + + value_iterator iter{}; + + friend class value; + friend class document; + friend struct simdjson_result; +}; + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +template<> +struct simdjson_result : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::object &&value) noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + simdjson_inline simdjson_result() noexcept = default; + + simdjson_inline simdjson_result begin() noexcept; + simdjson_inline simdjson_result end() noexcept; + simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field(std::string_view key) && noexcept; + simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field_unordered(std::string_view key) && noexcept; + simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; + simdjson_inline simdjson_result operator[](std::string_view key) && noexcept; + simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; + simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; + + inline simdjson_result reset() noexcept; + inline simdjson_result is_empty() noexcept; + inline simdjson_result count_fields() & noexcept; + inline simdjson_result raw_json() noexcept; + +}; + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_OBJECT_H diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/object_iterator-inl.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/object_iterator-inl.h new file mode 100644 index 000000000000..36294ce7185e --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/object_iterator-inl.h @@ -0,0 +1,138 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_OBJECT_ITERATOR_INL_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_OBJECT_ITERATOR_INL_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/object_iterator.h" +#include "simdjson/generic/ondemand/field-inl.h" +#include "simdjson/generic/ondemand/value_iterator-inl.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +// +// object_iterator +// + +simdjson_inline object_iterator::object_iterator(const value_iterator &_iter) noexcept + : iter{_iter} +{} + +simdjson_inline simdjson_result object_iterator::operator*() noexcept { + error_code error = iter.error(); + if (error) { iter.abandon(); return error; } + auto result = field::start(iter); + // TODO this is a safety rail ... users should exit loops as soon as they receive an error. + // Nonetheless, let's see if performance is OK with this if statement--the compiler may give it to us for free. + if (result.error()) { iter.abandon(); } + return result; +} +simdjson_inline bool object_iterator::operator==(const object_iterator &other) const noexcept { + return !(*this != other); +} +simdjson_inline bool object_iterator::operator!=(const object_iterator &) const noexcept { + return iter.is_open(); +} + +SIMDJSON_PUSH_DISABLE_WARNINGS +SIMDJSON_DISABLE_STRICT_OVERFLOW_WARNING +simdjson_inline object_iterator &object_iterator::operator++() noexcept { + // TODO this is a safety rail ... users should exit loops as soon as they receive an error. + // Nonetheless, let's see if performance is OK with this if statement--the compiler may give it to us for free. + if (!iter.is_open()) { return *this; } // Iterator will be released if there is an error + + simdjson_unused error_code error; + if ((error = iter.skip_child() )) { return *this; } + + simdjson_unused bool has_value; + if ((error = iter.has_next_field().get(has_value) )) { return *this; }; + return *this; +} +SIMDJSON_POP_DISABLE_WARNINGS + +// +// ### Live States +// +// While iterating or looking up values, depth >= iter.depth. at_start may vary. Error is +// always SUCCESS: +// +// - Start: This is the state when the object is first found and the iterator is just past the {. +// In this state, at_start == true. +// - Next: After we hand a scalar value to the user, or an array/object which they then fully +// iterate over, the iterator is at the , or } before the next value. In this state, +// depth == iter.depth, at_start == false, and error == SUCCESS. +// - Unfinished Business: When we hand an array/object to the user which they do not fully +// iterate over, we need to finish that iteration by skipping child values until we reach the +// Next state. In this state, depth > iter.depth, at_start == false, and error == SUCCESS. +// +// ## Error States +// +// In error states, we will yield exactly one more value before stopping. iter.depth == depth +// and at_start is always false. We decrement after yielding the error, moving to the Finished +// state. +// +// - Chained Error: When the object iterator is part of an error chain--for example, in +// `for (auto tweet : doc["tweets"])`, where the tweet field may be missing or not be an +// object--we yield that error in the loop, exactly once. In this state, error != SUCCESS and +// iter.depth == depth, and at_start == false. We decrement depth when we yield the error. +// - Missing Comma Error: When the iterator ++ method discovers there is no comma between fields, +// we flag that as an error and treat it exactly the same as a Chained Error. In this state, +// error == TAPE_ERROR, iter.depth == depth, and at_start == false. +// +// Errors that occur while reading a field to give to the user (such as when the key is not a +// string or the field is missing a colon) are yielded immediately. Depth is then decremented, +// moving to the Finished state without transitioning through an Error state at all. +// +// ## Terminal State +// +// The terminal state has iter.depth < depth. at_start is always false. +// +// - Finished: When we have reached a }, we are finished. We signal this by decrementing depth. +// In this state, iter.depth < depth, at_start == false, and error == SUCCESS. +// + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +simdjson_inline simdjson_result::simdjson_result( + SIMDJSON_IMPLEMENTATION::ondemand::object_iterator &&value +) noexcept + : implementation_simdjson_result_base(std::forward(value)) +{ + first.iter.assert_is_valid(); +} +simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept + : implementation_simdjson_result_base({}, error) +{ +} + +simdjson_inline simdjson_result simdjson_result::operator*() noexcept { + if (error()) { return error(); } + return *first; +} +// If we're iterating and there is an error, return the error once. +simdjson_inline bool simdjson_result::operator==(const simdjson_result &other) const noexcept { + if (!first.iter.is_valid()) { return !error(); } + return first == other.first; +} +// If we're iterating and there is an error, return the error once. +simdjson_inline bool simdjson_result::operator!=(const simdjson_result &other) const noexcept { + if (!first.iter.is_valid()) { return error(); } + return first != other.first; +} +// Checks for ']' and ',' +simdjson_inline simdjson_result &simdjson_result::operator++() noexcept { + // Clear the error if there is one, so we don't yield it twice + if (error()) { second = SUCCESS; return *this; } + ++first; + return *this; +} + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_OBJECT_ITERATOR_INL_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/object_iterator.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/object_iterator.h new file mode 100644 index 000000000000..1cdc3f43b359 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/object_iterator.h @@ -0,0 +1,80 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_OBJECT_ITERATOR_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_OBJECT_ITERATOR_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/implementation_simdjson_result_base.h" +#include "simdjson/generic/ondemand/value_iterator.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +class object_iterator { +public: + /** + * Create a new invalid object_iterator. + * + * Exists so you can declare a variable and later assign to it before use. + */ + simdjson_inline object_iterator() noexcept = default; + + // + // Iterator interface + // + + // Reads key and value, yielding them to the user. + // MUST ONLY BE CALLED ONCE PER ITERATION. + simdjson_inline simdjson_result operator*() noexcept; + // Assumes it's being compared with the end. true if depth < iter->depth. + simdjson_inline bool operator==(const object_iterator &) const noexcept; + // Assumes it's being compared with the end. true if depth >= iter->depth. + simdjson_inline bool operator!=(const object_iterator &) const noexcept; + // Checks for ']' and ',' + simdjson_inline object_iterator &operator++() noexcept; + +private: + /** + * The underlying JSON iterator. + * + * PERF NOTE: expected to be elided in favor of the parent document: this is set when the object + * is first used, and never changes afterwards. + */ + value_iterator iter{}; + + simdjson_inline object_iterator(const value_iterator &iter) noexcept; + friend struct simdjson_result; + friend class object; +}; + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +template<> +struct simdjson_result : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::object_iterator &&value) noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + simdjson_inline simdjson_result() noexcept = default; + + // + // Iterator interface + // + + // Reads key and value, yielding them to the user. + simdjson_inline simdjson_result operator*() noexcept; // MUST ONLY BE CALLED ONCE PER ITERATION. + // Assumes it's being compared with the end. true if depth < iter->depth. + simdjson_inline bool operator==(const simdjson_result &) const noexcept; + // Assumes it's being compared with the end. true if depth >= iter->depth. + simdjson_inline bool operator!=(const simdjson_result &) const noexcept; + // Checks for ']' and ',' + simdjson_inline simdjson_result &operator++() noexcept; +}; + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_OBJECT_ITERATOR_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/parser-inl.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/parser-inl.h new file mode 100644 index 000000000000..8350fe73831b --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/parser-inl.h @@ -0,0 +1,205 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_PARSER_INL_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_PARSER_INL_H +#include "simdjson/padded_string.h" +#include "simdjson/padded_string_view.h" +#include "simdjson/implementation.h" +#include "simdjson/internal/dom_parser_implementation.h" +#include "simdjson/dom/base.h" // for MINIMAL_DOCUMENT_CAPACITY +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/document_stream.h" +#include "simdjson/generic/ondemand/parser.h" +#include "simdjson/generic/ondemand/raw_json_string.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +simdjson_inline parser::parser(size_t max_capacity) noexcept + : _max_capacity{max_capacity} { +} + +simdjson_warn_unused simdjson_inline error_code parser::allocate(size_t new_capacity, size_t new_max_depth) noexcept { + if (new_capacity > max_capacity()) { return CAPACITY; } + if (string_buf && new_capacity == capacity() && new_max_depth == max_depth()) { return SUCCESS; } + + // string_capacity copied from document::allocate + _capacity = 0; + size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * new_capacity / 3 + SIMDJSON_PADDING, 64); + string_buf.reset(new (std::nothrow) uint8_t[string_capacity]); +#if SIMDJSON_DEVELOPMENT_CHECKS + start_positions.reset(new (std::nothrow) token_position[new_max_depth]); +#endif + if (implementation) { + SIMDJSON_TRY( implementation->set_capacity(new_capacity) ); + SIMDJSON_TRY( implementation->set_max_depth(new_max_depth) ); + } else { + SIMDJSON_TRY( simdjson::get_active_implementation()->create_dom_parser_implementation(new_capacity, new_max_depth, implementation) ); + } + _capacity = new_capacity; + _max_depth = new_max_depth; + return SUCCESS; +} +#if SIMDJSON_DEVELOPMENT_CHECKS +simdjson_inline simdjson_warn_unused bool parser::string_buffer_overflow(const uint8_t *string_buf_loc) const noexcept { + return (string_buf_loc < string_buf.get()) || (size_t(string_buf_loc - string_buf.get()) >= capacity()); +} +#endif + +simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(padded_string_view json) & noexcept { + if (json.padding() < SIMDJSON_PADDING) { return INSUFFICIENT_PADDING; } + + json.remove_utf8_bom(); + + // Allocate if needed + if (capacity() < json.length() || !string_buf) { + SIMDJSON_TRY( allocate(json.length(), max_depth()) ); + } + + // Run stage 1. + SIMDJSON_TRY( implementation->stage1(reinterpret_cast(json.data()), json.length(), stage1_mode::regular) ); + return document::start({ reinterpret_cast(json.data()), this }); +} + +#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON +simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_allow_incomplete_json(padded_string_view json) & noexcept { + if (json.padding() < SIMDJSON_PADDING) { return INSUFFICIENT_PADDING; } + + json.remove_utf8_bom(); + + // Allocate if needed + if (capacity() < json.length() || !string_buf) { + SIMDJSON_TRY( allocate(json.length(), max_depth()) ); + } + + // Run stage 1. + const simdjson::error_code err = implementation->stage1(reinterpret_cast(json.data()), json.length(), stage1_mode::regular); + if (err) { + if (err != UNCLOSED_STRING) + return err; + } + return document::start({ reinterpret_cast(json.data()), this, true }); +} +#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON + +simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(const char *json, size_t len, size_t allocated) & noexcept { + return iterate(padded_string_view(json, len, allocated)); +} + +simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(const uint8_t *json, size_t len, size_t allocated) & noexcept { + return iterate(padded_string_view(json, len, allocated)); +} + +simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(std::string_view json, size_t allocated) & noexcept { + return iterate(padded_string_view(json, allocated)); +} + +simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(std::string &json) & noexcept { + if(json.capacity() - json.size() < SIMDJSON_PADDING) { + json.reserve(json.size() + SIMDJSON_PADDING); + } + return iterate(padded_string_view(json)); +} + +simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(const std::string &json) & noexcept { + return iterate(padded_string_view(json)); +} + +simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(const simdjson_result &result) & noexcept { + // We don't presently have a way to temporarily get a const T& from a simdjson_result without throwing an exception + SIMDJSON_TRY( result.error() ); + padded_string_view json = result.value_unsafe(); + return iterate(json); +} + +simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(const simdjson_result &result) & noexcept { + // We don't presently have a way to temporarily get a const T& from a simdjson_result without throwing an exception + SIMDJSON_TRY( result.error() ); + const padded_string &json = result.value_unsafe(); + return iterate(json); +} + +simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_raw(padded_string_view json) & noexcept { + if (json.padding() < SIMDJSON_PADDING) { return INSUFFICIENT_PADDING; } + + json.remove_utf8_bom(); + + // Allocate if needed + if (capacity() < json.length()) { + SIMDJSON_TRY( allocate(json.length(), max_depth()) ); + } + + // Run stage 1. + SIMDJSON_TRY( implementation->stage1(reinterpret_cast(json.data()), json.length(), stage1_mode::regular) ); + return json_iterator(reinterpret_cast(json.data()), this); +} + +inline simdjson_result parser::iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept { + if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; } + if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) { + buf += 3; + len -= 3; + } + if(allow_comma_separated && batch_size < len) { batch_size = len; } + return document_stream(*this, buf, len, batch_size, allow_comma_separated); +} +inline simdjson_result parser::iterate_many(const char *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept { + return iterate_many(reinterpret_cast(buf), len, batch_size, allow_comma_separated); +} +inline simdjson_result parser::iterate_many(const std::string &s, size_t batch_size, bool allow_comma_separated) noexcept { + return iterate_many(s.data(), s.length(), batch_size, allow_comma_separated); +} +inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size, bool allow_comma_separated) noexcept { + return iterate_many(s.data(), s.length(), batch_size, allow_comma_separated); +} + +simdjson_pure simdjson_inline size_t parser::capacity() const noexcept { + return _capacity; +} +simdjson_pure simdjson_inline size_t parser::max_capacity() const noexcept { + return _max_capacity; +} +simdjson_pure simdjson_inline size_t parser::max_depth() const noexcept { + return _max_depth; +} + +simdjson_inline void parser::set_max_capacity(size_t max_capacity) noexcept { + if(max_capacity < dom::MINIMAL_DOCUMENT_CAPACITY) { + _max_capacity = max_capacity; + } else { + _max_capacity = dom::MINIMAL_DOCUMENT_CAPACITY; + } +} + +simdjson_inline simdjson_warn_unused simdjson_result parser::unescape(raw_json_string in, uint8_t *&dst, bool allow_replacement) const noexcept { + uint8_t *end = implementation->parse_string(in.buf, dst, allow_replacement); + if (!end) { return STRING_ERROR; } + std::string_view result(reinterpret_cast(dst), end-dst); + dst = end; + return result; +} + +simdjson_inline simdjson_warn_unused simdjson_result parser::unescape_wobbly(raw_json_string in, uint8_t *&dst) const noexcept { + uint8_t *end = implementation->parse_wobbly_string(in.buf, dst); + if (!end) { return STRING_ERROR; } + std::string_view result(reinterpret_cast(dst), end-dst); + dst = end; + return result; +} + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +simdjson_inline simdjson_result::simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::parser &&value) noexcept + : implementation_simdjson_result_base(std::forward(value)) {} +simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept + : implementation_simdjson_result_base(error) {} + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_PARSER_INL_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/parser.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/parser.h new file mode 100644 index 000000000000..5e22f5554353 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/parser.h @@ -0,0 +1,392 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_PARSER_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_PARSER_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/implementation_simdjson_result_base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +/** + * The default batch size for document_stream instances for this On-Demand kernel. + * Note that different On-Demand kernel may use a different DEFAULT_BATCH_SIZE value + * in the future. + */ +static constexpr size_t DEFAULT_BATCH_SIZE = 1000000; +/** + * Some adversary might try to set the batch size to 0 or 1, which might cause problems. + * We set a minimum of 32B since anything else is highly likely to be an error. In practice, + * most users will want a much larger batch size. + * + * All non-negative MINIMAL_BATCH_SIZE values should be 'safe' except that, obviously, no JSON + * document can ever span 0 or 1 byte and that very large values would create memory allocation issues. + */ +static constexpr size_t MINIMAL_BATCH_SIZE = 32; + +/** + * A JSON fragment iterator. + * + * This holds the actual iterator as well as the buffer for writing strings. + */ +class parser { +public: + /** + * Create a JSON parser. + * + * The new parser will have zero capacity. + */ + inline explicit parser(size_t max_capacity = SIMDJSON_MAXSIZE_BYTES) noexcept; + + inline parser(parser &&other) noexcept = default; + simdjson_inline parser(const parser &other) = delete; + simdjson_inline parser &operator=(const parser &other) = delete; + simdjson_inline parser &operator=(parser &&other) noexcept = default; + + /** Deallocate the JSON parser. */ + inline ~parser() noexcept = default; + + /** + * Start iterating an on-demand JSON document. + * + * ondemand::parser parser; + * document doc = parser.iterate(json); + * + * It is expected that the content is a valid UTF-8 file, containing a valid JSON document. + * Otherwise the iterate method may return an error. In particular, the whole input should be + * valid: we do not attempt to tolerate incorrect content either before or after a JSON + * document. If there is a UTF-8 BOM, the parser skips it. + * + * ### IMPORTANT: Validate what you use + * + * Calling iterate on an invalid JSON document may not immediately trigger an error. The call to + * iterate does not parse and validate the whole document. + * + * ### IMPORTANT: Buffer Lifetime + * + * Because parsing is done while you iterate, you *must* keep the JSON buffer around at least as + * long as the document iteration. + * + * ### IMPORTANT: Document Lifetime + * + * Only one iteration at a time can happen per parser, and the parser *must* be kept alive during + * iteration to ensure intermediate buffers can be accessed. Any document must be destroyed before + * you call parse() again or destroy the parser. + * + * ### REQUIRED: Buffer Padding + * + * The buffer must have at least SIMDJSON_PADDING extra allocated bytes. It does not matter what + * those bytes are initialized to, as long as they are allocated. These bytes will be read: if you + * using a sanitizer that verifies that no uninitialized byte is read, then you should initialize the + * SIMDJSON_PADDING bytes to avoid runtime warnings. + * + * ### std::string references + * + * If you pass a mutable std::string reference (std::string&), the parser will seek to extend + * its capacity to SIMDJSON_PADDING bytes beyond the end of the string. + * + * Whenever you pass an std::string reference, the parser will access the bytes beyond the end of + * the string but before the end of the allocated memory (std::string::capacity()). + * If you are using a sanitizer that checks for reading uninitialized bytes or std::string's + * container-overflow checks, you may encounter sanitizer warnings. + * You can safely ignore these warnings. Or you can call simdjson::pad(std::string&) to pad the + * string with SIMDJSON_PADDING spaces: this function returns a simdjson::padding_string_view + * which can be be passed to the parser's iterate function: + * + * std::string json = R"({ "foo": 1 } { "foo": 2 } { "foo": 3 } )"; + * document doc = parser.iterate(simdjson::pad(json)); + * + * @param json The JSON to parse. + * @param len The length of the JSON. + * @param capacity The number of bytes allocated in the JSON (must be at least len+SIMDJSON_PADDING). + * + * @return The document, or an error: + * - INSUFFICIENT_PADDING if the input has less than SIMDJSON_PADDING extra bytes. + * - MEMALLOC if realloc_if_needed the parser does not have enough capacity, and memory + * allocation fails. + * - EMPTY if the document is all whitespace. + * - UTF8_ERROR if the document is not valid UTF-8. + * - UNESCAPED_CHARS if a string contains control characters that must be escaped + * - UNCLOSED_STRING if there is an unclosed string in the document. + */ + simdjson_warn_unused simdjson_result iterate(padded_string_view json) & noexcept; +#ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON + simdjson_warn_unused simdjson_result iterate_allow_incomplete_json(padded_string_view json) & noexcept; +#endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON + /** @overload simdjson_result iterate(padded_string_view json) & noexcept */ + simdjson_warn_unused simdjson_result iterate(const char *json, size_t len, size_t capacity) & noexcept; + /** @overload simdjson_result iterate(padded_string_view json) & noexcept */ + simdjson_warn_unused simdjson_result iterate(const uint8_t *json, size_t len, size_t capacity) & noexcept; + /** @overload simdjson_result iterate(padded_string_view json) & noexcept */ + simdjson_warn_unused simdjson_result iterate(std::string_view json, size_t capacity) & noexcept; + /** @overload simdjson_result iterate(padded_string_view json) & noexcept */ + simdjson_warn_unused simdjson_result iterate(const std::string &json) & noexcept; + /** @overload simdjson_result iterate(padded_string_view json) & noexcept */ + simdjson_warn_unused simdjson_result iterate(std::string &json) & noexcept; + /** @overload simdjson_result iterate(padded_string_view json) & noexcept */ + simdjson_warn_unused simdjson_result iterate(const simdjson_result &json) & noexcept; + /** @overload simdjson_result iterate(padded_string_view json) & noexcept */ + simdjson_warn_unused simdjson_result iterate(const simdjson_result &json) & noexcept; + /** @overload simdjson_result iterate(padded_string_view json) & noexcept */ + simdjson_warn_unused simdjson_result iterate(padded_string &&json) & noexcept = delete; + + /** + * @private + * + * Start iterating an on-demand JSON document. + * + * ondemand::parser parser; + * json_iterator doc = parser.iterate(json); + * + * ### IMPORTANT: Buffer Lifetime + * + * Because parsing is done while you iterate, you *must* keep the JSON buffer around at least as + * long as the document iteration. + * + * ### IMPORTANT: Document Lifetime + * + * Only one iteration at a time can happen per parser, and the parser *must* be kept alive during + * iteration to ensure intermediate buffers can be accessed. Any document must be destroyed before + * you call parse() again or destroy the parser. + * + * The ondemand::document instance holds the iterator. The document must remain in scope + * while you are accessing instances of ondemand::value, ondemand::object, ondemand::array. + * + * ### REQUIRED: Buffer Padding + * + * The buffer must have at least SIMDJSON_PADDING extra allocated bytes. It does not matter what + * those bytes are initialized to, as long as they are allocated. These bytes will be read: if you + * using a sanitizer that verifies that no uninitialized byte is read, then you should initialize the + * SIMDJSON_PADDING bytes to avoid runtime warnings. + * + * @param json The JSON to parse. + * + * @return The iterator, or an error: + * - INSUFFICIENT_PADDING if the input has less than SIMDJSON_PADDING extra bytes. + * - MEMALLOC if realloc_if_needed the parser does not have enough capacity, and memory + * allocation fails. + * - EMPTY if the document is all whitespace. + * - UTF8_ERROR if the document is not valid UTF-8. + * - UNESCAPED_CHARS if a string contains control characters that must be escaped + * - UNCLOSED_STRING if there is an unclosed string in the document. + */ + simdjson_warn_unused simdjson_result iterate_raw(padded_string_view json) & noexcept; + + + /** + * Parse a buffer containing many JSON documents. + * + * auto json = R"({ "foo": 1 } { "foo": 2 } { "foo": 3 } )"_padded; + * ondemand::parser parser; + * ondemand::document_stream docs = parser.iterate_many(json); + * for (auto & doc : docs) { + * std::cout << doc["foo"] << std::endl; + * } + * // Prints 1 2 3 + * + * No copy of the input buffer is made. + * + * The function is lazy: it may be that no more than one JSON document at a time is parsed. + * + * The caller is responsabile to ensure that the input string data remains unchanged and is + * not deleted during the loop. + * + * ### Format + * + * The buffer must contain a series of one or more JSON documents, concatenated into a single + * buffer, separated by ASCII whitespace. It effectively parses until it has a fully valid document, + * then starts parsing the next document at that point. (It does this with more parallelism and + * lookahead than you might think, though.) + * + * documents that consist of an object or array may omit the whitespace between them, concatenating + * with no separator. Documents that consist of a single primitive (i.e. documents that are not + * arrays or objects) MUST be separated with ASCII whitespace. + * + * The characters inside a JSON document, and between JSON documents, must be valid Unicode (UTF-8). + * If there is a UTF-8 BOM, the parser skips it. + * + * The documents must not exceed batch_size bytes (by default 1MB) or they will fail to parse. + * Setting batch_size to excessively large or excessively small values may impact negatively the + * performance. + * + * ### REQUIRED: Buffer Padding + * + * The buffer must have at least SIMDJSON_PADDING extra allocated bytes. It does not matter what + * those bytes are initialized to, as long as they are allocated. These bytes will be read: if you + * using a sanitizer that verifies that no uninitialized byte is read, then you should initialize the + * SIMDJSON_PADDING bytes to avoid runtime warnings. + * + * ### Threads + * + * When compiled with SIMDJSON_THREADS_ENABLED, this method will use a single thread under the + * hood to do some lookahead. + * + * ### Parser Capacity + * + * If the parser's current capacity is less than batch_size, it will allocate enough capacity + * to handle it (up to max_capacity). + * + * @param buf The concatenated JSON to parse. + * @param len The length of the concatenated JSON. + * @param batch_size The batch size to use. MUST be larger than the largest document. The sweet + * spot is cache-related: small enough to fit in cache, yet big enough to + * parse as many documents as possible in one tight loop. + * Defaults to 10MB, which has been a reasonable sweet spot in our tests. + * @param allow_comma_separated (defaults on false) This allows a mode where the documents are + * separated by commas instead of whitespace. It comes with a performance + * penalty because the entire document is indexed at once (and the document must be + * less than 4 GB), and there is no multithreading. In this mode, the batch_size parameter + * is effectively ignored, as it is set to at least the document size. + * @return The stream, or an error. An empty input will yield 0 documents rather than an EMPTY error. Errors: + * - MEMALLOC if the parser does not have enough capacity and memory allocation fails + * - CAPACITY if the parser does not have enough capacity and batch_size > max_capacity. + * - other json errors if parsing fails. You should not rely on these errors to always the same for the + * same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware). + */ + inline simdjson_result iterate_many(const uint8_t *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept; + /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */ + inline simdjson_result iterate_many(const char *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept; + /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */ + inline simdjson_result iterate_many(const std::string &s, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept; + inline simdjson_result iterate_many(const std::string &&s, size_t batch_size, bool allow_comma_separated = false) = delete;// unsafe + /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */ + inline simdjson_result iterate_many(const padded_string &s, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept; + inline simdjson_result iterate_many(const padded_string &&s, size_t batch_size, bool allow_comma_separated = false) = delete;// unsafe + + /** @private We do not want to allow implicit conversion from C string to std::string. */ + simdjson_result iterate_many(const char *buf, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept = delete; + + /** The capacity of this parser (the largest document it can process). */ + simdjson_pure simdjson_inline size_t capacity() const noexcept; + /** The maximum capacity of this parser (the largest document it is allowed to process). */ + simdjson_pure simdjson_inline size_t max_capacity() const noexcept; + simdjson_inline void set_max_capacity(size_t max_capacity) noexcept; + /** + * The maximum depth of this parser (the most deeply nested objects and arrays it can process). + * This parameter is only relevant when the macro SIMDJSON_DEVELOPMENT_CHECKS is set to true. + * The document's instance current_depth() method should be used to monitor the parsing + * depth and limit it if desired. + */ + simdjson_pure simdjson_inline size_t max_depth() const noexcept; + + /** + * Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length + * and `max_depth` depth. + * + * The max_depth parameter is only relevant when the macro SIMDJSON_DEVELOPMENT_CHECKS is set to true. + * The document's instance current_depth() method should be used to monitor the parsing + * depth and limit it if desired. + * + * @param capacity The new capacity. + * @param max_depth The new max_depth. Defaults to DEFAULT_MAX_DEPTH. + * @return The error, if there is one. + */ + simdjson_warn_unused error_code allocate(size_t capacity, size_t max_depth=DEFAULT_MAX_DEPTH) noexcept; + + #ifdef SIMDJSON_THREADS_ENABLED + /** + * The parser instance can use threads when they are available to speed up some + * operations. It is enabled by default. Changing this attribute will change the + * behavior of the parser for future operations. + */ + bool threaded{true}; + #else + /** + * When SIMDJSON_THREADS_ENABLED is not defined, the parser instance cannot use threads. + */ + bool threaded{false}; + #endif + /** + * Unescape this JSON string, replacing \\ with \, \n with newline, etc. to a user-provided buffer. + * The result must be valid UTF-8. + * The provided pointer is advanced to the end of the string by reference, and a string_view instance + * is returned. You can ensure that your buffer is large enough by allocating a block of memory at least + * as large as the input JSON plus SIMDJSON_PADDING and then unescape all strings to this one buffer. + * + * This unescape function is a low-level function. If you want a more user-friendly approach, you should + * avoid raw_json_string instances (e.g., by calling unescaped_key() instead of key() or get_string() + * instead of get_raw_json_string()). + * + * ## IMPORTANT: string_view lifetime + * + * The string_view is only valid as long as the bytes in dst. + * + * @param raw_json_string input + * @param dst A pointer to a buffer at least large enough to write this string as well as + * an additional SIMDJSON_PADDING bytes. + * @param allow_replacement Whether we allow a replacement if the input string contains unmatched surrogate pairs. + * @return A string_view pointing at the unescaped string in dst + * @error STRING_ERROR if escapes are incorrect. + */ + simdjson_inline simdjson_result unescape(raw_json_string in, uint8_t *&dst, bool allow_replacement = false) const noexcept; + + /** + * Unescape this JSON string, replacing \\ with \, \n with newline, etc. to a user-provided buffer. + * The result may not be valid UTF-8. See https://simonsapin.github.io/wtf-8/ + * The provided pointer is advanced to the end of the string by reference, and a string_view instance + * is returned. You can ensure that your buffer is large enough by allocating a block of memory at least + * as large as the input JSON plus SIMDJSON_PADDING and then unescape all strings to this one buffer. + * + * This unescape function is a low-level function. If you want a more user-friendly approach, you should + * avoid raw_json_string instances (e.g., by calling unescaped_key() instead of key() or get_string() + * instead of get_raw_json_string()). + * + * ## IMPORTANT: string_view lifetime + * + * The string_view is only valid as long as the bytes in dst. + * + * @param raw_json_string input + * @param dst A pointer to a buffer at least large enough to write this string as well as + * an additional SIMDJSON_PADDING bytes. + * @return A string_view pointing at the unescaped string in dst + * @error STRING_ERROR if escapes are incorrect. + */ + simdjson_inline simdjson_result unescape_wobbly(raw_json_string in, uint8_t *&dst) const noexcept; + +#if SIMDJSON_DEVELOPMENT_CHECKS + /** + * Returns true if string_buf_loc is outside of the allocated range for the + * the string buffer. When true, it indicates that the string buffer has overflowed. + * This is a development-time check that is not needed in production. It can be + * used to detect buffer overflows in the string buffer and usafe usage of the + * string buffer. + */ + bool string_buffer_overflow(const uint8_t *string_buf_loc) const noexcept; +#endif + +private: + /** @private [for benchmarking access] The implementation to use */ + std::unique_ptr implementation{}; + size_t _capacity{0}; + size_t _max_capacity; + size_t _max_depth{DEFAULT_MAX_DEPTH}; + std::unique_ptr string_buf{}; +#if SIMDJSON_DEVELOPMENT_CHECKS + std::unique_ptr start_positions{}; +#endif + + friend class json_iterator; + friend class document_stream; +}; + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +template<> +struct simdjson_result : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::parser &&value) noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + simdjson_inline simdjson_result() noexcept = default; +}; + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_PARSER_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/raw_json_string-inl.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/raw_json_string-inl.h new file mode 100644 index 000000000000..5b814dd801ef --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/raw_json_string-inl.h @@ -0,0 +1,203 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_RAW_JSON_STRING_INL_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_RAW_JSON_STRING_INL_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/raw_json_string.h" +#include "simdjson/generic/ondemand/json_iterator-inl.h" +#include "simdjson/generic/implementation_simdjson_result_base-inl.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { + +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +simdjson_inline raw_json_string::raw_json_string(const uint8_t * _buf) noexcept : buf{_buf} {} + +simdjson_inline const char * raw_json_string::raw() const noexcept { return reinterpret_cast(buf); } + + +simdjson_inline bool raw_json_string::is_free_from_unescaped_quote(std::string_view target) noexcept { + size_t pos{0}; + // if the content has no escape character, just scan through it quickly! + for(;pos < target.size() && target[pos] != '\\';pos++) {} + // slow path may begin. + bool escaping{false}; + for(;pos < target.size();pos++) { + if((target[pos] == '"') && !escaping) { + return false; + } else if(target[pos] == '\\') { + escaping = !escaping; + } else { + escaping = false; + } + } + return true; +} + +simdjson_inline bool raw_json_string::is_free_from_unescaped_quote(const char* target) noexcept { + size_t pos{0}; + // if the content has no escape character, just scan through it quickly! + for(;target[pos] && target[pos] != '\\';pos++) {} + // slow path may begin. + bool escaping{false}; + for(;target[pos];pos++) { + if((target[pos] == '"') && !escaping) { + return false; + } else if(target[pos] == '\\') { + escaping = !escaping; + } else { + escaping = false; + } + } + return true; +} + + +simdjson_inline bool raw_json_string::unsafe_is_equal(size_t length, std::string_view target) const noexcept { + // If we are going to call memcmp, then we must know something about the length of the raw_json_string. + return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); +} + +simdjson_inline bool raw_json_string::unsafe_is_equal(std::string_view target) const noexcept { + // Assumptions: does not contain unescaped quote characters, and + // the raw content is quote terminated within a valid JSON string. + if(target.size() <= SIMDJSON_PADDING) { + return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + } + const char * r{raw()}; + size_t pos{0}; + for(;pos < target.size();pos++) { + if(r[pos] != target[pos]) { return false; } + } + if(r[pos] != '"') { return false; } + return true; +} + +simdjson_inline bool raw_json_string::is_equal(std::string_view target) const noexcept { + const char * r{raw()}; + size_t pos{0}; + bool escaping{false}; + for(;pos < target.size();pos++) { + if(r[pos] != target[pos]) { return false; } + // if target is a compile-time constant and it is free from + // quotes, then the next part could get optimized away through + // inlining. + if((target[pos] == '"') && !escaping) { + // We have reached the end of the raw_json_string but + // the target is not done. + return false; + } else if(target[pos] == '\\') { + escaping = !escaping; + } else { + escaping = false; + } + } + if(r[pos] != '"') { return false; } + return true; +} + + +simdjson_inline bool raw_json_string::unsafe_is_equal(const char * target) const noexcept { + // Assumptions: 'target' does not contain unescaped quote characters, is null terminated and + // the raw content is quote terminated within a valid JSON string. + const char * r{raw()}; + size_t pos{0}; + for(;target[pos];pos++) { + if(r[pos] != target[pos]) { return false; } + } + if(r[pos] != '"') { return false; } + return true; +} + +simdjson_inline bool raw_json_string::is_equal(const char* target) const noexcept { + // Assumptions: does not contain unescaped quote characters, and + // the raw content is quote terminated within a valid JSON string. + const char * r{raw()}; + size_t pos{0}; + bool escaping{false}; + for(;target[pos];pos++) { + if(r[pos] != target[pos]) { return false; } + // if target is a compile-time constant and it is free from + // quotes, then the next part could get optimized away through + // inlining. + if((target[pos] == '"') && !escaping) { + // We have reached the end of the raw_json_string but + // the target is not done. + return false; + } else if(target[pos] == '\\') { + escaping = !escaping; + } else { + escaping = false; + } + } + if(r[pos] != '"') { return false; } + return true; +} + +simdjson_unused simdjson_inline bool operator==(const raw_json_string &a, std::string_view c) noexcept { + return a.unsafe_is_equal(c); +} + +simdjson_unused simdjson_inline bool operator==(std::string_view c, const raw_json_string &a) noexcept { + return a == c; +} + +simdjson_unused simdjson_inline bool operator!=(const raw_json_string &a, std::string_view c) noexcept { + return !(a == c); +} + +simdjson_unused simdjson_inline bool operator!=(std::string_view c, const raw_json_string &a) noexcept { + return !(a == c); +} + + +simdjson_inline simdjson_warn_unused simdjson_result raw_json_string::unescape(json_iterator &iter, bool allow_replacement) const noexcept { + return iter.unescape(*this, allow_replacement); +} + +simdjson_inline simdjson_warn_unused simdjson_result raw_json_string::unescape_wobbly(json_iterator &iter) const noexcept { + return iter.unescape_wobbly(*this); +} + +simdjson_unused simdjson_inline std::ostream &operator<<(std::ostream &out, const raw_json_string &str) noexcept { + bool in_escape = false; + const char *s = str.raw(); + while (true) { + switch (*s) { + case '\\': in_escape = !in_escape; break; + case '"': if (in_escape) { in_escape = false; } else { return out; } break; + default: if (in_escape) { in_escape = false; } + } + out << *s; + s++; + } +} + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +simdjson_inline simdjson_result::simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string &&value) noexcept + : implementation_simdjson_result_base(std::forward(value)) {} +simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept + : implementation_simdjson_result_base(error) {} + +simdjson_inline simdjson_result simdjson_result::raw() const noexcept { + if (error()) { return error(); } + return first.raw(); +} +simdjson_inline simdjson_warn_unused simdjson_result simdjson_result::unescape(SIMDJSON_IMPLEMENTATION::ondemand::json_iterator &iter, bool allow_replacement) const noexcept { + if (error()) { return error(); } + return first.unescape(iter, allow_replacement); +} +simdjson_inline simdjson_warn_unused simdjson_result simdjson_result::unescape_wobbly(SIMDJSON_IMPLEMENTATION::ondemand::json_iterator &iter) const noexcept { + if (error()) { return error(); } + return first.unescape_wobbly(iter); +} +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_RAW_JSON_STRING_INL_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/raw_json_string.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/raw_json_string.h new file mode 100644 index 000000000000..be50a402d378 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/raw_json_string.h @@ -0,0 +1,206 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_RAW_JSON_STRING_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_RAW_JSON_STRING_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/implementation_simdjson_result_base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +/** + * A string escaped per JSON rules, terminated with quote ("). They are used to represent + * unescaped keys inside JSON documents. + * + * (In other words, a pointer to the beginning of a string, just after the start quote, inside a + * JSON file.) + * + * This class is deliberately simplistic and has little functionality. You can + * compare a raw_json_string instance with an unescaped C string, but + * that is nearly all you can do. + * + * The raw_json_string is unescaped. If you wish to write an unescaped version of it to your own + * buffer, you may do so using the parser.unescape(string, buff) method, using an ondemand::parser + * instance. Doing so requires you to have a sufficiently large buffer. + * + * The raw_json_string instances originate typically from field instance which in turn represent + * key-value pairs from object instances. From a field instance, you get the raw_json_string + * instance by calling key(). You can, if you want a more usable string_view instance, call + * the unescaped_key() method on the field instance. You may also create a raw_json_string from + * any other string value, with the value.get_raw_json_string() method. Again, you can get + * a more usable string_view instance by calling get_string(). + * + */ +class raw_json_string { +public: + /** + * Create a new invalid raw_json_string. + * + * Exists so you can declare a variable and later assign to it before use. + */ + simdjson_inline raw_json_string() noexcept = default; + + /** + * Create a new invalid raw_json_string pointed at the given location in the JSON. + * + * The given location must be just *after* the beginning quote (") in the JSON file. + * + * It *must* be terminated by a ", and be a valid JSON string. + */ + simdjson_inline raw_json_string(const uint8_t * _buf) noexcept; + /** + * Get the raw pointer to the beginning of the string in the JSON (just after the "). + * + * It is possible for this function to return a null pointer if the instance + * has outlived its existence. + */ + simdjson_inline const char * raw() const noexcept; + + /** + * This compares the current instance to the std::string_view target: returns true if + * they are byte-by-byte equal (no escaping is done) on target.size() characters, + * and if the raw_json_string instance has a quote character at byte index target.size(). + * We never read more than length + 1 bytes in the raw_json_string instance. + * If length is smaller than target.size(), this will return false. + * + * The std::string_view instance may contain any characters. However, the caller + * is responsible for setting length so that length bytes may be read in the + * raw_json_string. + * + * Performance: the comparison may be done using memcmp which may be efficient + * for long strings. + */ + simdjson_inline bool unsafe_is_equal(size_t length, std::string_view target) const noexcept; + + /** + * This compares the current instance to the std::string_view target: returns true if + * they are byte-by-byte equal (no escaping is done). + * The std::string_view instance should not contain unescaped quote characters: + * the caller is responsible for this check. See is_free_from_unescaped_quote. + * + * Performance: the comparison is done byte-by-byte which might be inefficient for + * long strings. + * + * If target is a compile-time constant, and your compiler likes you, + * you should be able to do the following without performance penalty... + * + * static_assert(raw_json_string::is_free_from_unescaped_quote(target), ""); + * s.unsafe_is_equal(target); + */ + simdjson_inline bool unsafe_is_equal(std::string_view target) const noexcept; + + /** + * This compares the current instance to the C string target: returns true if + * they are byte-by-byte equal (no escaping is done). + * The provided C string should not contain an unescaped quote character: + * the caller is responsible for this check. See is_free_from_unescaped_quote. + * + * If target is a compile-time constant, and your compiler likes you, + * you should be able to do the following without performance penalty... + * + * static_assert(raw_json_string::is_free_from_unescaped_quote(target), ""); + * s.unsafe_is_equal(target); + */ + simdjson_inline bool unsafe_is_equal(const char* target) const noexcept; + + /** + * This compares the current instance to the std::string_view target: returns true if + * they are byte-by-byte equal (no escaping is done). + */ + simdjson_inline bool is_equal(std::string_view target) const noexcept; + + /** + * This compares the current instance to the C string target: returns true if + * they are byte-by-byte equal (no escaping is done). + */ + simdjson_inline bool is_equal(const char* target) const noexcept; + + /** + * Returns true if target is free from unescaped quote. If target is known at + * compile-time, we might expect the computation to happen at compile time with + * many compilers (not all!). + */ + static simdjson_inline bool is_free_from_unescaped_quote(std::string_view target) noexcept; + static simdjson_inline bool is_free_from_unescaped_quote(const char* target) noexcept; + +private: + + + /** + * This will set the inner pointer to zero, effectively making + * this instance unusable. + */ + simdjson_inline void consume() noexcept { buf = nullptr; } + + /** + * Checks whether the inner pointer is non-null and thus usable. + */ + simdjson_inline simdjson_warn_unused bool alive() const noexcept { return buf != nullptr; } + + /** + * Unescape this JSON string, replacing \\ with \, \n with newline, etc. + * The result will be a valid UTF-8. + * + * ## IMPORTANT: string_view lifetime + * + * The string_view is only valid until the next parse() call on the parser. + * + * @param iter A json_iterator, which contains a buffer where the string will be written. + * @param allow_replacement Whether we allow replacement of invalid surrogate pairs. + */ + simdjson_inline simdjson_warn_unused simdjson_result unescape(json_iterator &iter, bool allow_replacement) const noexcept; + + /** + * Unescape this JSON string, replacing \\ with \, \n with newline, etc. + * The result may not be a valid UTF-8. https://simonsapin.github.io/wtf-8/ + * + * ## IMPORTANT: string_view lifetime + * + * The string_view is only valid until the next parse() call on the parser. + * + * @param iter A json_iterator, which contains a buffer where the string will be written. + */ + simdjson_inline simdjson_warn_unused simdjson_result unescape_wobbly(json_iterator &iter) const noexcept; + const uint8_t * buf{}; + friend class object; + friend class field; + friend class parser; + friend struct simdjson_result; +}; + +simdjson_unused simdjson_inline std::ostream &operator<<(std::ostream &, const raw_json_string &) noexcept; + +/** + * Comparisons between raw_json_string and std::string_view instances are potentially unsafe: the user is responsible + * for providing a string with no unescaped quote. Note that unescaped quotes cannot be present in valid JSON strings. + */ +simdjson_unused simdjson_inline bool operator==(const raw_json_string &a, std::string_view c) noexcept; +simdjson_unused simdjson_inline bool operator==(std::string_view c, const raw_json_string &a) noexcept; +simdjson_unused simdjson_inline bool operator!=(const raw_json_string &a, std::string_view c) noexcept; +simdjson_unused simdjson_inline bool operator!=(std::string_view c, const raw_json_string &a) noexcept; + + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +template<> +struct simdjson_result : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string &&value) noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + simdjson_inline simdjson_result() noexcept = default; + simdjson_inline ~simdjson_result() noexcept = default; ///< @private + + simdjson_inline simdjson_result raw() const noexcept; + simdjson_inline simdjson_warn_unused simdjson_result unescape(SIMDJSON_IMPLEMENTATION::ondemand::json_iterator &iter, bool allow_replacement) const noexcept; + simdjson_inline simdjson_warn_unused simdjson_result unescape_wobbly(SIMDJSON_IMPLEMENTATION::ondemand::json_iterator &iter) const noexcept; +}; + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_RAW_JSON_STRING_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/serialization-inl.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/serialization-inl.h new file mode 100644 index 000000000000..77be39a11ced --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/serialization-inl.h @@ -0,0 +1,233 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_SERIALIZATION_INL_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_SERIALIZATION_INL_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/array.h" +#include "simdjson/generic/ondemand/document-inl.h" +#include "simdjson/generic/ondemand/json_type.h" +#include "simdjson/generic/ondemand/object.h" +#include "simdjson/generic/ondemand/serialization.h" +#include "simdjson/generic/ondemand/value.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { + +inline std::string_view trim(const std::string_view str) noexcept { + // We can almost surely do better by rolling our own find_first_not_of function. + size_t first = str.find_first_not_of(" \t\n\r"); + // If we have the empty string (just white space), then no trimming is possible, and + // we return the empty string_view. + if (std::string_view::npos == first) { return std::string_view(); } + size_t last = str.find_last_not_of(" \t\n\r"); + return str.substr(first, (last - first + 1)); +} + + +inline simdjson_result to_json_string(SIMDJSON_IMPLEMENTATION::ondemand::document& x) noexcept { + std::string_view v; + auto error = x.raw_json().get(v); + if(error) {return error; } + return trim(v); +} + +inline simdjson_result to_json_string(SIMDJSON_IMPLEMENTATION::ondemand::document_reference& x) noexcept { + std::string_view v; + auto error = x.raw_json().get(v); + if(error) {return error; } + return trim(v); +} + +inline simdjson_result to_json_string(SIMDJSON_IMPLEMENTATION::ondemand::value& x) noexcept { + /** + * If we somehow receive a value that has already been consumed, + * then the following code could be in trouble. E.g., we create + * an array as needed, but if an array was already created, then + * it could be bad. + */ + using namespace SIMDJSON_IMPLEMENTATION::ondemand; + SIMDJSON_IMPLEMENTATION::ondemand::json_type t; + auto error = x.type().get(t); + if(error != SUCCESS) { return error; } + switch (t) + { + case json_type::array: + { + SIMDJSON_IMPLEMENTATION::ondemand::array array; + error = x.get_array().get(array); + if(error) { return error; } + return to_json_string(array); + } + case json_type::object: + { + SIMDJSON_IMPLEMENTATION::ondemand::object object; + error = x.get_object().get(object); + if(error) { return error; } + return to_json_string(object); + } + default: + return trim(x.raw_json_token()); + } +} + +inline simdjson_result to_json_string(SIMDJSON_IMPLEMENTATION::ondemand::object& x) noexcept { + std::string_view v; + auto error = x.raw_json().get(v); + if(error) {return error; } + return trim(v); +} + +inline simdjson_result to_json_string(SIMDJSON_IMPLEMENTATION::ondemand::array& x) noexcept { + std::string_view v; + auto error = x.raw_json().get(v); + if(error) {return error; } + return trim(v); +} + +inline simdjson_result to_json_string(simdjson_result x) { + if (x.error()) { return x.error(); } + return to_json_string(x.value_unsafe()); +} + +inline simdjson_result to_json_string(simdjson_result x) { + if (x.error()) { return x.error(); } + return to_json_string(x.value_unsafe()); +} + +inline simdjson_result to_json_string(simdjson_result x) { + if (x.error()) { return x.error(); } + return to_json_string(x.value_unsafe()); +} + +inline simdjson_result to_json_string(simdjson_result x) { + if (x.error()) { return x.error(); } + return to_json_string(x.value_unsafe()); +} + +inline simdjson_result to_json_string(simdjson_result x) { + if (x.error()) { return x.error(); } + return to_json_string(x.value_unsafe()); +} +} // namespace simdjson + +namespace simdjson { namespace SIMDJSON_IMPLEMENTATION { namespace ondemand { + +#if SIMDJSON_EXCEPTIONS +inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_IMPLEMENTATION::ondemand::value x) { + std::string_view v; + auto error = simdjson::to_json_string(x).get(v); + if(error == simdjson::SUCCESS) { + return (out << v); + } else { + throw simdjson::simdjson_error(error); + } +} +inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x) { + if (x.error()) { throw simdjson::simdjson_error(x.error()); } + return (out << x.value()); +} +#else +inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_IMPLEMENTATION::ondemand::value x) { + std::string_view v; + auto error = simdjson::to_json_string(x).get(v); + if(error == simdjson::SUCCESS) { + return (out << v); + } else { + return (out << error); + } +} +#endif + +#if SIMDJSON_EXCEPTIONS +inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_IMPLEMENTATION::ondemand::array value) { + std::string_view v; + auto error = simdjson::to_json_string(value).get(v); + if(error == simdjson::SUCCESS) { + return (out << v); + } else { + throw simdjson::simdjson_error(error); + } +} +inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x) { + if (x.error()) { throw simdjson::simdjson_error(x.error()); } + return (out << x.value()); +} +#else +inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_IMPLEMENTATION::ondemand::array value) { + std::string_view v; + auto error = simdjson::to_json_string(value).get(v); + if(error == simdjson::SUCCESS) { + return (out << v); + } else { + return (out << error); + } +} +#endif + +#if SIMDJSON_EXCEPTIONS +inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_IMPLEMENTATION::ondemand::document& value) { + std::string_view v; + auto error = simdjson::to_json_string(value).get(v); + if(error == simdjson::SUCCESS) { + return (out << v); + } else { + throw simdjson::simdjson_error(error); + } +} +inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_IMPLEMENTATION::ondemand::document_reference& value) { + std::string_view v; + auto error = simdjson::to_json_string(value).get(v); + if(error == simdjson::SUCCESS) { + return (out << v); + } else { + throw simdjson::simdjson_error(error); + } +} +inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result&& x) { + if (x.error()) { throw simdjson::simdjson_error(x.error()); } + return (out << x.value()); +} +inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result&& x) { + if (x.error()) { throw simdjson::simdjson_error(x.error()); } + return (out << x.value()); +} +#else +inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_IMPLEMENTATION::ondemand::document& value) { + std::string_view v; + auto error = simdjson::to_json_string(value).get(v); + if(error == simdjson::SUCCESS) { + return (out << v); + } else { + return (out << error); + } +} +#endif + +#if SIMDJSON_EXCEPTIONS +inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_IMPLEMENTATION::ondemand::object value) { + std::string_view v; + auto error = simdjson::to_json_string(value).get(v); + if(error == simdjson::SUCCESS) { + return (out << v); + } else { + throw simdjson::simdjson_error(error); + } +} +inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x) { + if (x.error()) { throw simdjson::simdjson_error(x.error()); } + return (out << x.value()); +} +#else +inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_IMPLEMENTATION::ondemand::object value) { + std::string_view v; + auto error = simdjson::to_json_string(value).get(v); + if(error == simdjson::SUCCESS) { + return (out << v); + } else { + return (out << error); + } +} +#endif +}}} // namespace simdjson::SIMDJSON_IMPLEMENTATION::ondemand + +#endif // SIMDJSON_GENERIC_ONDEMAND_SERIALIZATION_INL_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/serialization.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/serialization.h new file mode 100644 index 000000000000..048c73cda81b --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/serialization.h @@ -0,0 +1,103 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_SERIALIZATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_SERIALIZATION_H +#include "simdjson/generic/ondemand/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +/** + * Create a string-view instance out of a document instance. The string-view instance + * contains JSON text that is suitable to be parsed as JSON again. It does not + * validate the content. + */ +inline simdjson_result to_json_string(SIMDJSON_IMPLEMENTATION::ondemand::document& x) noexcept; +/** + * Create a string-view instance out of a value instance. The string-view instance + * contains JSON text that is suitable to be parsed as JSON again. The value must + * not have been accessed previously. It does not + * validate the content. + */ +inline simdjson_result to_json_string(SIMDJSON_IMPLEMENTATION::ondemand::value& x) noexcept; +/** + * Create a string-view instance out of an object instance. The string-view instance + * contains JSON text that is suitable to be parsed as JSON again. It does not + * validate the content. + */ +inline simdjson_result to_json_string(SIMDJSON_IMPLEMENTATION::ondemand::object& x) noexcept; +/** + * Create a string-view instance out of an array instance. The string-view instance + * contains JSON text that is suitable to be parsed as JSON again. It does not + * validate the content. + */ +inline simdjson_result to_json_string(SIMDJSON_IMPLEMENTATION::ondemand::array& x) noexcept; +inline simdjson_result to_json_string(simdjson_result x); +inline simdjson_result to_json_string(simdjson_result x); +inline simdjson_result to_json_string(simdjson_result x); +inline simdjson_result to_json_string(simdjson_result x); +} // namespace simdjson + +/** + * We want to support argument-dependent lookup (ADL). + * Hence we should define operator<< in the namespace + * where the argument (here value, object, etc.) resides. + * Credit: @madhur4127 + * See https://github.com/simdjson/simdjson/issues/1768 + */ +namespace simdjson { namespace SIMDJSON_IMPLEMENTATION { namespace ondemand { + +/** + * Print JSON to an output stream. It does not + * validate the content. + * + * @param out The output stream. + * @param value The element. + * @throw if there is an error with the underlying output stream. simdjson itself will not throw. + */ +inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_IMPLEMENTATION::ondemand::value x); +#if SIMDJSON_EXCEPTIONS +inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x); +#endif +/** + * Print JSON to an output stream. It does not + * validate the content. + * + * @param out The output stream. + * @param value The array. + * @throw if there is an error with the underlying output stream. simdjson itself will not throw. + */ +inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_IMPLEMENTATION::ondemand::array value); +#if SIMDJSON_EXCEPTIONS +inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x); +#endif +/** + * Print JSON to an output stream. It does not + * validate the content. + * + * @param out The output stream. + * @param value The array. + * @throw if there is an error with the underlying output stream. simdjson itself will not throw. + */ +inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_IMPLEMENTATION::ondemand::document& value); +#if SIMDJSON_EXCEPTIONS +inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result&& x); +#endif +inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_IMPLEMENTATION::ondemand::document_reference& value); +#if SIMDJSON_EXCEPTIONS +inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result&& x); +#endif +/** + * Print JSON to an output stream. It does not + * validate the content. + * + * @param out The output stream. + * @param value The object. + * @throw if there is an error with the underlying output stream. simdjson itself will not throw. + */ +inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_IMPLEMENTATION::ondemand::object value); +#if SIMDJSON_EXCEPTIONS +inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x); +#endif +}}} // namespace simdjson::SIMDJSON_IMPLEMENTATION::ondemand + +#endif // SIMDJSON_GENERIC_ONDEMAND_SERIALIZATION_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/std_deserialize.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/std_deserialize.h new file mode 100644 index 000000000000..ce8b9fa3fe8d --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/std_deserialize.h @@ -0,0 +1,166 @@ +#if SIMDJSON_SUPPORTS_DESERIALIZATION + +#ifndef SIMDJSON_ONDEMAND_DESERIALIZE_H +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_ONDEMAND_DESERIALIZE_H +#include "simdjson/generic/ondemand/array.h" +#include "simdjson/generic/ondemand/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include +#include + +namespace simdjson { +template +constexpr bool require_custom_serialization = false; + +////////////////////////////// +// Number deserialization +////////////////////////////// + +template + requires(!require_custom_serialization) +error_code tag_invoke(deserialize_tag, auto &val, T &out) noexcept { + using limits = std::numeric_limits; + + uint64_t x; + SIMDJSON_TRY(val.get_uint64().get(x)); + if (x > (limits::max)()) { + return NUMBER_OUT_OF_RANGE; + } + out = static_cast(x); + return SUCCESS; +} + +template + requires(!require_custom_serialization) +error_code tag_invoke(deserialize_tag, auto &val, T &out) noexcept { + double x; + SIMDJSON_TRY(val.get_double().get(x)); + out = static_cast(x); + return SUCCESS; +} + +template + requires(!require_custom_serialization) +error_code tag_invoke(deserialize_tag, auto &val, T &out) noexcept { + using limits = std::numeric_limits; + + int64_t x; + SIMDJSON_TRY(val.get_int64().get(x)); + if (x > (limits::max)() || x < (limits::min)()) { + return NUMBER_OUT_OF_RANGE; + } + out = static_cast(x); + return SUCCESS; +} + +/** + * STL containers have several constructors including one that takes a single + * size argument. Thus, some compilers (Visual Studio) will not be able to + * disambiguate between the size and container constructor. Users should + * explicitly specify the type of the container as needed: e.g., + * doc.get>(). + */ +template + requires(!require_custom_serialization) +error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(false) { + using value_type = typename std::remove_cvref_t::value_type; + static_assert( + deserializable, + "The specified type inside the container must itself be deserializable"); + static_assert( + std::is_default_constructible_v, + "The specified type inside the container must default constructible."); + + SIMDJSON_IMPLEMENTATION::ondemand::array arr; + SIMDJSON_TRY(val.get_array().get(arr)); + for (auto v : arr) { + if constexpr (concepts::returns_reference) { + if (auto const err = v.get().get(concepts::emplace_one(out)); + err) { + // If an error occurs, the empty element that we just inserted gets + // removed. We're not using a temp variable because if T is a heavy + // type, we want the valid path to be the fast path and the slow path be + // the path that has errors in it. + if constexpr (requires { out.pop_back(); }) { + static_cast(out.pop_back()); + } + return err; + } + } else { + value_type temp; + if (auto const err = v.get().get(temp); err) { + return err; + } + concepts::emplace_one(out, std::move(temp)); + } + } + return SUCCESS; +} + + + +/** + * This CPO (Customization Point Object) will help deserialize into + * smart pointers. + * + * If constructing T is nothrow, this conversion should be nothrow as well since + * we return MEMALLOC if we're not able to allocate memory instead of throwing + * the error message. + * + * @tparam T The type inside the smart pointer + * @tparam ValT document/value type + * @param val document/value + * @param out a reference to the smart pointer + * @return status of the conversion + */ +template + requires(!require_custom_serialization) +error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(nothrow_deserializable::element_type, ValT>) { + using element_type = typename std::remove_cvref_t::element_type; + + // For better error messages, don't use these as constraints on + // the tag_invoke CPO. + static_assert( + deserializable, + "The specified type inside the unique_ptr must itself be deserializable"); + static_assert( + std::is_default_constructible_v, + "The specified type inside the unique_ptr must default constructible."); + + auto ptr = new (std::nothrow) element_type(); + if (ptr == nullptr) { + return MEMALLOC; + } + SIMDJSON_TRY(val.template get(*ptr)); + out.reset(ptr); + return SUCCESS; +} + +/** + * This CPO (Customization Point Object) will help deserialize into optional types. + */ +template + requires(!require_custom_serialization) +error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(nothrow_deserializable::value_type, ValT>) { + using value_type = typename std::remove_cvref_t::value_type; + + static_assert( + deserializable, + "The specified type inside the unique_ptr must itself be deserializable"); + static_assert( + std::is_default_constructible_v, + "The specified type inside the unique_ptr must default constructible."); + + if (!out) { + out.emplace(); + } + SIMDJSON_TRY(val.template get(out.value())); + return SUCCESS; +} + +} // namespace simdjson + +#endif // SIMDJSON_ONDEMAND_DESERIALIZE_H +#endif // SIMDJSON_SUPPORTS_DESERIALIZATION diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/token_iterator-inl.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/token_iterator-inl.h new file mode 100644 index 000000000000..c93a10d82901 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/token_iterator-inl.h @@ -0,0 +1,94 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_TOKEN_ITERATOR_INL_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_TOKEN_ITERATOR_INL_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/token_iterator.h" +#include "simdjson/generic/implementation_simdjson_result_base-inl.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +simdjson_inline token_iterator::token_iterator( + const uint8_t *_buf, + token_position position +) noexcept : buf{_buf}, _position{position} +{ +} + +simdjson_inline uint32_t token_iterator::current_offset() const noexcept { + return *(_position); +} + + +simdjson_inline const uint8_t *token_iterator::return_current_and_advance() noexcept { + return &buf[*(_position++)]; +} + +simdjson_inline const uint8_t *token_iterator::peek(token_position position) const noexcept { + return &buf[*position]; +} +simdjson_inline uint32_t token_iterator::peek_index(token_position position) const noexcept { + return *position; +} +simdjson_inline uint32_t token_iterator::peek_length(token_position position) const noexcept { + return *(position+1) - *position; +} + +simdjson_inline uint32_t token_iterator::peek_root_length(token_position position) const noexcept { + return *(position+2) - *(position) > *(position+1) - *(position) ? + *(position+1) - *(position) + : *(position+2) - *(position); +} +simdjson_inline const uint8_t *token_iterator::peek(int32_t delta) const noexcept { + return &buf[*(_position+delta)]; +} +simdjson_inline uint32_t token_iterator::peek_index(int32_t delta) const noexcept { + return *(_position+delta); +} +simdjson_inline uint32_t token_iterator::peek_length(int32_t delta) const noexcept { + return *(_position+delta+1) - *(_position+delta); +} + +simdjson_inline token_position token_iterator::position() const noexcept { + return _position; +} +simdjson_inline void token_iterator::set_position(token_position target_position) noexcept { + _position = target_position; +} + +simdjson_inline bool token_iterator::operator==(const token_iterator &other) const noexcept { + return _position == other._position; +} +simdjson_inline bool token_iterator::operator!=(const token_iterator &other) const noexcept { + return _position != other._position; +} +simdjson_inline bool token_iterator::operator>(const token_iterator &other) const noexcept { + return _position > other._position; +} +simdjson_inline bool token_iterator::operator>=(const token_iterator &other) const noexcept { + return _position >= other._position; +} +simdjson_inline bool token_iterator::operator<(const token_iterator &other) const noexcept { + return _position < other._position; +} +simdjson_inline bool token_iterator::operator<=(const token_iterator &other) const noexcept { + return _position <= other._position; +} + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +simdjson_inline simdjson_result::simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::token_iterator &&value) noexcept + : implementation_simdjson_result_base(std::forward(value)) {} +simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept + : implementation_simdjson_result_base(error) {} + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_TOKEN_ITERATOR_INL_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/token_iterator.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/token_iterator.h new file mode 100644 index 000000000000..dc1b4fac4cbf --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/token_iterator.h @@ -0,0 +1,158 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_TOKEN_ITERATOR_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_TOKEN_ITERATOR_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/implementation_simdjson_result_base.h" +#include "simdjson/generic/ondemand/logger.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +/** + * Iterates through JSON tokens (`{` `}` `[` `]` `,` `:` `""` `123` `true` `false` `null`) + * detected by stage 1. + * + * @private This is not intended for external use. + */ +class token_iterator { +public: + /** + * Create a new invalid token_iterator. + * + * Exists so you can declare a variable and later assign to it before use. + */ + simdjson_inline token_iterator() noexcept = default; + simdjson_inline token_iterator(token_iterator &&other) noexcept = default; + simdjson_inline token_iterator &operator=(token_iterator &&other) noexcept = default; + simdjson_inline token_iterator(const token_iterator &other) noexcept = default; + simdjson_inline token_iterator &operator=(const token_iterator &other) noexcept = default; + + /** + * Advance to the next token (returning the current one). + */ + simdjson_inline const uint8_t *return_current_and_advance() noexcept; + /** + * Reports the current offset in bytes from the start of the underlying buffer. + */ + simdjson_inline uint32_t current_offset() const noexcept; + /** + * Get the JSON text for a given token (relative). + * + * This is not null-terminated; it is a view into the JSON. + * + * @param delta The relative position of the token to retrieve. e.g. 0 = current token, + * 1 = next token, -1 = prev token. + * + * TODO consider a string_view, assuming the length will get stripped out by the optimizer when + * it is not used... + */ + simdjson_inline const uint8_t *peek(int32_t delta=0) const noexcept; + /** + * Get the maximum length of the JSON text for a given token. + * + * The length will include any whitespace at the end of the token. + * + * @param delta The relative position of the token to retrieve. e.g. 0 = current token, + * 1 = next token, -1 = prev token. + */ + simdjson_inline uint32_t peek_length(int32_t delta=0) const noexcept; + + /** + * Get the JSON text for a given token. + * + * This is not null-terminated; it is a view into the JSON. + * + * @param position The position of the token. + * + */ + simdjson_inline const uint8_t *peek(token_position position) const noexcept; + /** + * Get the maximum length of the JSON text for a given token. + * + * The length will include any whitespace at the end of the token. + * + * @param position The position of the token. + */ + simdjson_inline uint32_t peek_length(token_position position) const noexcept; + /** + * Get the maximum length of the JSON text for a root token. + * + * The length will include any whitespace at the end of the token. + * + * @param position The position of the token (start of the document). + */ + simdjson_inline uint32_t peek_root_length(token_position position) const noexcept; + /** + * Return the current index. + */ + simdjson_inline token_position position() const noexcept; + /** + * Reset to a previously saved index. + */ + simdjson_inline void set_position(token_position target_position) noexcept; + + // NOTE: we don't support a full C++ iterator interface, because we expect people to make + // different calls to advance the iterator based on *their own* state. + + simdjson_inline bool operator==(const token_iterator &other) const noexcept; + simdjson_inline bool operator!=(const token_iterator &other) const noexcept; + simdjson_inline bool operator>(const token_iterator &other) const noexcept; + simdjson_inline bool operator>=(const token_iterator &other) const noexcept; + simdjson_inline bool operator<(const token_iterator &other) const noexcept; + simdjson_inline bool operator<=(const token_iterator &other) const noexcept; + +protected: + simdjson_inline token_iterator(const uint8_t *buf, token_position position) noexcept; + + /** + * Get the index of the JSON text for a given token (relative). + * + * This is not null-terminated; it is a view into the JSON. + * + * @param delta The relative position of the token to retrieve. e.g. 0 = current token, + * 1 = next token, -1 = prev token. + */ + simdjson_inline uint32_t peek_index(int32_t delta=0) const noexcept; + /** + * Get the index of the JSON text for a given token. + * + * This is not null-terminated; it is a view into the JSON. + * + * @param position The position of the token. + * + */ + simdjson_inline uint32_t peek_index(token_position position) const noexcept; + + const uint8_t *buf{}; + token_position _position{}; + + friend class json_iterator; + friend class value_iterator; + friend class object; + template + friend simdjson_inline void logger::log_line(const json_iterator &iter, const char *title_prefix, const char *title, std::string_view detail, int delta, int depth_delta, logger::log_level level, Args&&... args) noexcept; + template + friend simdjson_inline void logger::log_line(const json_iterator &iter, token_position index, depth_t depth, const char *title_prefix, const char *title, std::string_view detail, logger::log_level level, Args&&... args) noexcept; +}; + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +template<> +struct simdjson_result : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::token_iterator &&value) noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + simdjson_inline simdjson_result() noexcept = default; + simdjson_inline ~simdjson_result() noexcept = default; ///< @private +}; + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_TOKEN_ITERATOR_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/value-inl.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/value-inl.h new file mode 100644 index 000000000000..a28809095cb4 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/value-inl.h @@ -0,0 +1,551 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_VALUE_INL_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_VALUE_INL_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/ondemand/array.h" +#include "simdjson/generic/ondemand/array_iterator.h" +#include "simdjson/generic/ondemand/json_iterator.h" +#include "simdjson/generic/ondemand/json_type.h" +#include "simdjson/generic/ondemand/object.h" +#include "simdjson/generic/ondemand/raw_json_string.h" +#include "simdjson/generic/ondemand/value.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +simdjson_inline value::value(const value_iterator &_iter) noexcept + : iter{_iter} +{ +} +simdjson_inline value value::start(const value_iterator &iter) noexcept { + return iter; +} +simdjson_inline value value::resume(const value_iterator &iter) noexcept { + return iter; +} + +simdjson_inline simdjson_result value::get_array() noexcept { + return array::start(iter); +} +simdjson_inline simdjson_result value::get_object() noexcept { + return object::start(iter); +} +simdjson_inline simdjson_result value::start_or_resume_object() noexcept { + if (iter.at_start()) { + return get_object(); + } else { + return object::resume(iter); + } +} + +simdjson_inline simdjson_result value::get_raw_json_string() noexcept { + return iter.get_raw_json_string(); +} +simdjson_inline simdjson_result value::get_string(bool allow_replacement) noexcept { + return iter.get_string(allow_replacement); +} +template +simdjson_inline error_code value::get_string(string_type& receiver, bool allow_replacement) noexcept { + return iter.get_string(receiver, allow_replacement); +} +simdjson_inline simdjson_result value::get_wobbly_string() noexcept { + return iter.get_wobbly_string(); +} +simdjson_inline simdjson_result value::get_double() noexcept { + return iter.get_double(); +} +simdjson_inline simdjson_result value::get_double_in_string() noexcept { + return iter.get_double_in_string(); +} +simdjson_inline simdjson_result value::get_uint64() noexcept { + return iter.get_uint64(); +} +simdjson_inline simdjson_result value::get_uint64_in_string() noexcept { + return iter.get_uint64_in_string(); +} +simdjson_inline simdjson_result value::get_int64() noexcept { + return iter.get_int64(); +} +simdjson_inline simdjson_result value::get_int64_in_string() noexcept { + return iter.get_int64_in_string(); +} +simdjson_inline simdjson_result value::get_bool() noexcept { + return iter.get_bool(); +} +simdjson_inline simdjson_result value::is_null() noexcept { + return iter.is_null(); +} + +template<> simdjson_inline simdjson_result value::get() noexcept { return get_array(); } +template<> simdjson_inline simdjson_result value::get() noexcept { return get_object(); } +template<> simdjson_inline simdjson_result value::get() noexcept { return get_raw_json_string(); } +template<> simdjson_inline simdjson_result value::get() noexcept { return get_string(false); } +template<> simdjson_inline simdjson_result value::get() noexcept { return get_number(); } +template<> simdjson_inline simdjson_result value::get() noexcept { return get_double(); } +template<> simdjson_inline simdjson_result value::get() noexcept { return get_uint64(); } +template<> simdjson_inline simdjson_result value::get() noexcept { return get_int64(); } +template<> simdjson_inline simdjson_result value::get() noexcept { return get_bool(); } + + +template<> simdjson_inline error_code value::get(array& out) noexcept { return get_array().get(out); } +template<> simdjson_inline error_code value::get(object& out) noexcept { return get_object().get(out); } +template<> simdjson_inline error_code value::get(raw_json_string& out) noexcept { return get_raw_json_string().get(out); } +template<> simdjson_inline error_code value::get(std::string_view& out) noexcept { return get_string(false).get(out); } +template<> simdjson_inline error_code value::get(number& out) noexcept { return get_number().get(out); } +template<> simdjson_inline error_code value::get(double& out) noexcept { return get_double().get(out); } +template<> simdjson_inline error_code value::get(uint64_t& out) noexcept { return get_uint64().get(out); } +template<> simdjson_inline error_code value::get(int64_t& out) noexcept { return get_int64().get(out); } +template<> simdjson_inline error_code value::get(bool& out) noexcept { return get_bool().get(out); } + +#if SIMDJSON_EXCEPTIONS +template +simdjson_inline value::operator T() noexcept(false) { + return get(); +} +simdjson_inline value::operator array() noexcept(false) { + return get_array(); +} +simdjson_inline value::operator object() noexcept(false) { + return get_object(); +} +simdjson_inline value::operator uint64_t() noexcept(false) { + return get_uint64(); +} +simdjson_inline value::operator int64_t() noexcept(false) { + return get_int64(); +} +simdjson_inline value::operator double() noexcept(false) { + return get_double(); +} +simdjson_inline value::operator std::string_view() noexcept(false) { + return get_string(false); +} +simdjson_inline value::operator raw_json_string() noexcept(false) { + return get_raw_json_string(); +} +simdjson_inline value::operator bool() noexcept(false) { + return get_bool(); +} +#endif + +simdjson_inline simdjson_result value::begin() & noexcept { + return get_array().begin(); +} +simdjson_inline simdjson_result value::end() & noexcept { + return {}; +} +simdjson_inline simdjson_result value::count_elements() & noexcept { + simdjson_result answer; + auto a = get_array(); + answer = a.count_elements(); + // count_elements leaves you pointing inside the array, at the first element. + // We need to move back so that the user can create a new array (which requires that + // we point at '['). + iter.move_at_start(); + return answer; +} +simdjson_inline simdjson_result value::count_fields() & noexcept { + simdjson_result answer; + auto a = get_object(); + answer = a.count_fields(); + iter.move_at_start(); + return answer; +} +simdjson_inline simdjson_result value::at(size_t index) noexcept { + auto a = get_array(); + return a.at(index); +} + +simdjson_inline simdjson_result value::find_field(std::string_view key) noexcept { + return start_or_resume_object().find_field(key); +} +simdjson_inline simdjson_result value::find_field(const char *key) noexcept { + return start_or_resume_object().find_field(key); +} + +simdjson_inline simdjson_result value::find_field_unordered(std::string_view key) noexcept { + return start_or_resume_object().find_field_unordered(key); +} +simdjson_inline simdjson_result value::find_field_unordered(const char *key) noexcept { + return start_or_resume_object().find_field_unordered(key); +} + +simdjson_inline simdjson_result value::operator[](std::string_view key) noexcept { + return start_or_resume_object()[key]; +} +simdjson_inline simdjson_result value::operator[](const char *key) noexcept { + return start_or_resume_object()[key]; +} + +simdjson_inline simdjson_result value::type() noexcept { + return iter.type(); +} + +simdjson_inline simdjson_result value::is_scalar() noexcept { + json_type this_type; + auto error = type().get(this_type); + if(error) { return error; } + return ! ((this_type == json_type::array) || (this_type == json_type::object)); +} + +simdjson_inline simdjson_result value::is_string() noexcept { + json_type this_type; + auto error = type().get(this_type); + if(error) { return error; } + return (this_type == json_type::string); +} + + +simdjson_inline bool value::is_negative() noexcept { + return iter.is_negative(); +} + +simdjson_inline simdjson_result value::is_integer() noexcept { + return iter.is_integer(); +} +simdjson_warn_unused simdjson_inline simdjson_result value::get_number_type() noexcept { + return iter.get_number_type(); +} +simdjson_warn_unused simdjson_inline simdjson_result value::get_number() noexcept { + return iter.get_number(); +} + +simdjson_inline std::string_view value::raw_json_token() noexcept { + return std::string_view(reinterpret_cast(iter.peek_start()), iter.peek_start_length()); +} + +simdjson_inline simdjson_result value::raw_json() noexcept { + json_type t; + SIMDJSON_TRY(type().get(t)); + switch (t) + { + case json_type::array: { + ondemand::array array; + SIMDJSON_TRY(get_array().get(array)); + return array.raw_json(); + } + case json_type::object: { + ondemand::object object; + SIMDJSON_TRY(get_object().get(object)); + return object.raw_json(); + } + default: + return raw_json_token(); + } +} + +simdjson_inline simdjson_result value::current_location() noexcept { + return iter.json_iter().current_location(); +} + +simdjson_inline int32_t value::current_depth() const noexcept{ + return iter.json_iter().depth(); +} + +inline bool is_pointer_well_formed(std::string_view json_pointer) noexcept { + if (simdjson_unlikely(json_pointer.empty())) { // can't be + return false; + } + if (simdjson_unlikely(json_pointer[0] != '/')) { + return false; + } + size_t escape = json_pointer.find('~'); + if (escape == std::string_view::npos) { + return true; + } + if (escape == json_pointer.size() - 1) { + return false; + } + if (json_pointer[escape + 1] != '0' && json_pointer[escape + 1] != '1') { + return false; + } + return true; +} + +simdjson_inline simdjson_result value::at_pointer(std::string_view json_pointer) noexcept { + json_type t; + SIMDJSON_TRY(type().get(t)); + switch (t) + { + case json_type::array: + return (*this).get_array().at_pointer(json_pointer); + case json_type::object: + return (*this).get_object().at_pointer(json_pointer); + default: + // a non-empty string can be invalid, or accessing a primitive (issue 2154) + if (is_pointer_well_formed(json_pointer)) { + return NO_SUCH_FIELD; + } + return INVALID_JSON_POINTER; + } +} + +simdjson_inline simdjson_result value::at_path(std::string_view json_path) noexcept { + json_type t; + SIMDJSON_TRY(type().get(t)); + switch (t) { + case json_type::array: + return (*this).get_array().at_path(json_path); + case json_type::object: + return (*this).get_object().at_path(json_path); + default: + return INVALID_JSON_POINTER; + } +} + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +simdjson_inline simdjson_result::simdjson_result( + SIMDJSON_IMPLEMENTATION::ondemand::value &&value +) noexcept : + implementation_simdjson_result_base( + std::forward(value) + ) +{ +} +simdjson_inline simdjson_result::simdjson_result( + error_code error +) noexcept : + implementation_simdjson_result_base(error) +{ +} +simdjson_inline simdjson_result simdjson_result::count_elements() & noexcept { + if (error()) { return error(); } + return first.count_elements(); +} +simdjson_inline simdjson_result simdjson_result::count_fields() & noexcept { + if (error()) { return error(); } + return first.count_fields(); +} +simdjson_inline simdjson_result simdjson_result::at(size_t index) noexcept { + if (error()) { return error(); } + return first.at(index); +} +simdjson_inline simdjson_result simdjson_result::begin() & noexcept { + if (error()) { return error(); } + return first.begin(); +} +simdjson_inline simdjson_result simdjson_result::end() & noexcept { + if (error()) { return error(); } + return {}; +} + +simdjson_inline simdjson_result simdjson_result::find_field(std::string_view key) noexcept { + if (error()) { return error(); } + return first.find_field(key); +} +simdjson_inline simdjson_result simdjson_result::find_field(const char *key) noexcept { + if (error()) { return error(); } + return first.find_field(key); +} + +simdjson_inline simdjson_result simdjson_result::find_field_unordered(std::string_view key) noexcept { + if (error()) { return error(); } + return first.find_field_unordered(key); +} +simdjson_inline simdjson_result simdjson_result::find_field_unordered(const char *key) noexcept { + if (error()) { return error(); } + return first.find_field_unordered(key); +} + +simdjson_inline simdjson_result simdjson_result::operator[](std::string_view key) noexcept { + if (error()) { return error(); } + return first[key]; +} +simdjson_inline simdjson_result simdjson_result::operator[](const char *key) noexcept { + if (error()) { return error(); } + return first[key]; +} + +simdjson_inline simdjson_result simdjson_result::get_array() noexcept { + if (error()) { return error(); } + return first.get_array(); +} +simdjson_inline simdjson_result simdjson_result::get_object() noexcept { + if (error()) { return error(); } + return first.get_object(); +} +simdjson_inline simdjson_result simdjson_result::get_uint64() noexcept { + if (error()) { return error(); } + return first.get_uint64(); +} +simdjson_inline simdjson_result simdjson_result::get_uint64_in_string() noexcept { + if (error()) { return error(); } + return first.get_uint64_in_string(); +} +simdjson_inline simdjson_result simdjson_result::get_int64() noexcept { + if (error()) { return error(); } + return first.get_int64(); +} +simdjson_inline simdjson_result simdjson_result::get_int64_in_string() noexcept { + if (error()) { return error(); } + return first.get_int64_in_string(); +} +simdjson_inline simdjson_result simdjson_result::get_double() noexcept { + if (error()) { return error(); } + return first.get_double(); +} +simdjson_inline simdjson_result simdjson_result::get_double_in_string() noexcept { + if (error()) { return error(); } + return first.get_double_in_string(); +} +simdjson_inline simdjson_result simdjson_result::get_string(bool allow_replacement) noexcept { + if (error()) { return error(); } + return first.get_string(allow_replacement); +} +template +simdjson_inline error_code simdjson_result::get_string(string_type& receiver, bool allow_replacement) noexcept { + if (error()) { return error(); } + return first.get_string(receiver, allow_replacement); +} +simdjson_inline simdjson_result simdjson_result::get_wobbly_string() noexcept { + if (error()) { return error(); } + return first.get_wobbly_string(); +} +simdjson_inline simdjson_result simdjson_result::get_raw_json_string() noexcept { + if (error()) { return error(); } + return first.get_raw_json_string(); +} +simdjson_inline simdjson_result simdjson_result::get_bool() noexcept { + if (error()) { return error(); } + return first.get_bool(); +} +simdjson_inline simdjson_result simdjson_result::is_null() noexcept { + if (error()) { return error(); } + return first.is_null(); +} + +template<> simdjson_inline error_code simdjson_result::get(SIMDJSON_IMPLEMENTATION::ondemand::value &out) noexcept { + if (error()) { return error(); } + out = first; + return SUCCESS; +} + +template simdjson_inline simdjson_result simdjson_result::get() noexcept { + if (error()) { return error(); } + return first.get(); +} +template simdjson_inline error_code simdjson_result::get(T &out) noexcept { + if (error()) { return error(); } + return first.get(out); +} + +template<> simdjson_inline simdjson_result simdjson_result::get() noexcept { + if (error()) { return error(); } + return std::move(first); +} + +simdjson_inline simdjson_result simdjson_result::type() noexcept { + if (error()) { return error(); } + return first.type(); +} +simdjson_inline simdjson_result simdjson_result::is_scalar() noexcept { + if (error()) { return error(); } + return first.is_scalar(); +} +simdjson_inline simdjson_result simdjson_result::is_string() noexcept { + if (error()) { return error(); } + return first.is_string(); +} +simdjson_inline simdjson_result simdjson_result::is_negative() noexcept { + if (error()) { return error(); } + return first.is_negative(); +} +simdjson_inline simdjson_result simdjson_result::is_integer() noexcept { + if (error()) { return error(); } + return first.is_integer(); +} +simdjson_inline simdjson_result simdjson_result::get_number_type() noexcept { + if (error()) { return error(); } + return first.get_number_type(); +} +simdjson_inline simdjson_result simdjson_result::get_number() noexcept { + if (error()) { return error(); } + return first.get_number(); +} +#if SIMDJSON_EXCEPTIONS +template +simdjson_inline simdjson_result::operator T() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first.get(); +} +simdjson_inline simdjson_result::operator SIMDJSON_IMPLEMENTATION::ondemand::array() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator SIMDJSON_IMPLEMENTATION::ondemand::object() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator uint64_t() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator int64_t() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator double() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator std::string_view() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +simdjson_inline simdjson_result::operator bool() noexcept(false) { + if (error()) { throw simdjson_error(error()); } + return first; +} +#endif + +simdjson_inline simdjson_result simdjson_result::raw_json_token() noexcept { + if (error()) { return error(); } + return first.raw_json_token(); +} + +simdjson_inline simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + +simdjson_inline simdjson_result simdjson_result::current_location() noexcept { + if (error()) { return error(); } + return first.current_location(); +} + +simdjson_inline simdjson_result simdjson_result::current_depth() const noexcept { + if (error()) { return error(); } + return first.current_depth(); +} + +simdjson_inline simdjson_result simdjson_result::at_pointer( + std::string_view json_pointer) noexcept { + if (error()) { + return error(); + } + return first.at_pointer(json_pointer); +} + +simdjson_inline simdjson_result simdjson_result::at_path( + std::string_view json_path) noexcept { + if (error()) { + return error(); + } + return first.at_path(json_path); +} + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_VALUE_INL_H diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/value.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/value.h new file mode 100644 index 000000000000..7fa9d1ad25ad --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/value.h @@ -0,0 +1,827 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_VALUE_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_VALUE_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/implementation_simdjson_result_base.h" +#include "simdjson/generic/ondemand/value_iterator.h" +#include "simdjson/generic/ondemand/deserialize.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include + +namespace simdjson { + +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { +/** + * An ephemeral JSON value returned during iteration. It is only valid for as long as you do + * not access more data in the JSON document. + */ +class value { +public: + /** + * Create a new invalid value. + * + * Exists so you can declare a variable and later assign to it before use. + */ + simdjson_inline value() noexcept = default; + + /** + * Get this value as the given type. + * + * Supported types: object, array, raw_json_string, string_view, uint64_t, int64_t, double, bool + * + * You may use get_double(), get_bool(), get_uint64(), get_int64(), + * get_object(), get_array(), get_raw_json_string(), or get_string() instead. + * + * @returns A value of the given type, parsed from the JSON. + * @returns INCORRECT_TYPE If the JSON value is not the given type. + */ + template + simdjson_inline simdjson_result get() +#if SIMDJSON_SUPPORTS_DESERIALIZATION + noexcept(custom_deserializable ? nothrow_custom_deserializable : true) +#else + noexcept +#endif + { + static_assert(std::is_default_constructible::value, "The specified type is not default constructible."); + T out{}; + SIMDJSON_TRY(get(out)); + return out; + } + + + /** + * Get this value as the given type. + * + * Supported types: object, array, raw_json_string, string_view, uint64_t, int64_t, double, bool + * + * @param out This is set to a value of the given type, parsed from the JSON. If there is an error, this may not be initialized. + * @returns INCORRECT_TYPE If the JSON value is not an object. + * @returns SUCCESS If the parse succeeded and the out parameter was set to the value. + */ + template + simdjson_inline error_code get(T &out) +#if SIMDJSON_SUPPORTS_DESERIALIZATION + noexcept(custom_deserializable ? nothrow_custom_deserializable : true) +#else + noexcept +#endif + { +#if SIMDJSON_SUPPORTS_DESERIALIZATION + if constexpr (custom_deserializable) { + return deserialize(*this, out); + } else { +#endif // SIMDJSON_SUPPORTS_DESERIALIZATION + // Unless the simdjson library or the user provides an inline implementation, calling this method should + // immediately fail. + static_assert(!sizeof(T), "The get method with given type is not implemented by the simdjson library. " + "The supported types are ondemand::object, ondemand::array, raw_json_string, std::string_view, uint64_t, " + "int64_t, double, and bool. We recommend you use get_double(), get_bool(), get_uint64(), get_int64(), " + " get_object(), get_array(), get_raw_json_string(), or get_string() instead of the get template." + " You may also add support for custom types, see our documentation."); + static_cast(out); // to get rid of unused errors + return UNINITIALIZED; +#if SIMDJSON_SUPPORTS_DESERIALIZATION + } +#endif + } + + /** + * Cast this JSON value to an array. + * + * @returns An object that can be used to iterate the array. + * @returns INCORRECT_TYPE If the JSON value is not an array. + */ + simdjson_inline simdjson_result get_array() noexcept; + + /** + * Cast this JSON value to an object. + * + * @returns An object that can be used to look up or iterate fields. + * @returns INCORRECT_TYPE If the JSON value is not an object. + */ + simdjson_inline simdjson_result get_object() noexcept; + + /** + * Cast this JSON value to an unsigned integer. + * + * @returns A unsigned 64-bit integer. + * @returns INCORRECT_TYPE If the JSON value is not a 64-bit unsigned integer. + */ + simdjson_inline simdjson_result get_uint64() noexcept; + + /** + * Cast this JSON value (inside string) to a unsigned integer. + * + * @returns A unsigned 64-bit integer. + * @returns INCORRECT_TYPE If the JSON value is not a 64-bit unsigned integer. + */ + simdjson_inline simdjson_result get_uint64_in_string() noexcept; + + /** + * Cast this JSON value to a signed integer. + * + * @returns A signed 64-bit integer. + * @returns INCORRECT_TYPE If the JSON value is not a 64-bit integer. + */ + simdjson_inline simdjson_result get_int64() noexcept; + + /** + * Cast this JSON value (inside string) to a signed integer. + * + * @returns A signed 64-bit integer. + * @returns INCORRECT_TYPE If the JSON value is not a 64-bit integer. + */ + simdjson_inline simdjson_result get_int64_in_string() noexcept; + + /** + * Cast this JSON value to a double. + * + * @returns A double. + * @returns INCORRECT_TYPE If the JSON value is not a valid floating-point number. + */ + simdjson_inline simdjson_result get_double() noexcept; + + /** + * Cast this JSON value (inside string) to a double + * + * @returns A double. + * @returns INCORRECT_TYPE If the JSON value is not a valid floating-point number. + */ + simdjson_inline simdjson_result get_double_in_string() noexcept; + + /** + * Cast this JSON value to a string. + * + * The string is guaranteed to be valid UTF-8. + * + * Equivalent to get(). + * + * Important: a value should be consumed once. Calling get_string() twice on the same value + * is an error. + * + * In some instances, you may want to allow replacement of invalid Unicode sequences. + * You may do so by passing the allow_replacement parameter as true. In the following + * example, the string "431924697b\udff0L\u0001Y" is not valid Unicode. By passing true + * to get_string, we allow the replacement of the invalid Unicode sequences with the Unicode + * replacement character (U+FFFD). + * + * simdjson::ondemand::parser parser; + * auto json = R"({"deviceId":"431924697b\udff0L\u0001Y"})"_padded; + * simdjson::ondemand::document doc = parser.iterate(json); + * auto view = doc["deviceId"].get_string(true); + * + * @returns An UTF-8 string. The string is stored in the parser and will be invalidated the next + * time it parses a document or when it is destroyed. + * @returns INCORRECT_TYPE if the JSON value is not a string. + */ + simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; + + /** + * Attempts to fill the provided std::string reference with the parsed value of the current string. + * + * The string is guaranteed to be valid UTF-8. + * + * Important: a value should be consumed once. Calling get_string() twice on the same value + * is an error. + * + * Performance: This method may be slower than get_string() or get_string(bool) because it may need to allocate memory. + * We recommend you avoid allocating an std::string unless you need to. + * + * @returns INCORRECT_TYPE if the JSON value is not a string. Otherwise, we return SUCCESS. + */ + template + simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; + + /** + * Cast this JSON value to a "wobbly" string. + * + * The string is may not be a valid UTF-8 string. + * See https://simonsapin.github.io/wtf-8/ + * + * Important: a value should be consumed once. Calling get_wobbly_string() twice on the same value + * is an error. + * + * @returns An UTF-8 string. The string is stored in the parser and will be invalidated the next + * time it parses a document or when it is destroyed. + * @returns INCORRECT_TYPE if the JSON value is not a string. + */ + simdjson_inline simdjson_result get_wobbly_string() noexcept; + /** + * Cast this JSON value to a raw_json_string. + * + * The string is guaranteed to be valid UTF-8, and may have escapes in it (e.g. \\ or \n). + * + * @returns A pointer to the raw JSON for the given string. + * @returns INCORRECT_TYPE if the JSON value is not a string. + */ + simdjson_inline simdjson_result get_raw_json_string() noexcept; + + /** + * Cast this JSON value to a bool. + * + * @returns A bool value. + * @returns INCORRECT_TYPE if the JSON value is not true or false. + */ + simdjson_inline simdjson_result get_bool() noexcept; + + /** + * Checks if this JSON value is null. If and only if the value is + * null, then it is consumed (we advance). If we find a token that + * begins with 'n' but is not 'null', then an error is returned. + * + * @returns Whether the value is null. + * @returns INCORRECT_TYPE If the JSON value begins with 'n' and is not 'null'. + */ + simdjson_inline simdjson_result is_null() noexcept; + +#if SIMDJSON_EXCEPTIONS + /** + * Cast this JSON value to an instance of type T. The programmer is responsible for + * providing an implementation of get for the type T, if T is not one of the types + * supported by the library (object, array, raw_json_string, string_view, uint64_t, etc.). + * + * See https://github.com/simdjson/simdjson/blob/master/doc/basics.md#adding-support-for-custom-types + * + * @returns An instance of type T + */ + template + explicit simdjson_inline operator T() noexcept(false); + /** + * Cast this JSON value to an array. + * + * @returns An object that can be used to iterate the array. + * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not an array. + */ + simdjson_inline operator array() noexcept(false); + /** + * Cast this JSON value to an object. + * + * @returns An object that can be used to look up or iterate fields. + * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not an object. + */ + simdjson_inline operator object() noexcept(false); + /** + * Cast this JSON value to an unsigned integer. + * + * @returns A signed 64-bit integer. + * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit unsigned integer. + */ + simdjson_inline operator uint64_t() noexcept(false); + /** + * Cast this JSON value to a signed integer. + * + * @returns A signed 64-bit integer. + * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit integer. + */ + simdjson_inline operator int64_t() noexcept(false); + /** + * Cast this JSON value to a double. + * + * @returns A double. + * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a valid floating-point number. + */ + simdjson_inline operator double() noexcept(false); + /** + * Cast this JSON value to a string. + * + * The string is guaranteed to be valid UTF-8. + * + * Equivalent to get(). + * + * @returns An UTF-8 string. The string is stored in the parser and will be invalidated the next + * time it parses a document or when it is destroyed. + * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string. + */ + simdjson_inline operator std::string_view() noexcept(false); + /** + * Cast this JSON value to a raw_json_string. + * + * The string is guaranteed to be valid UTF-8, and may have escapes in it (e.g. \\ or \n). + * + * @returns A pointer to the raw JSON for the given string. + * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string. + */ + simdjson_inline operator raw_json_string() noexcept(false); + /** + * Cast this JSON value to a bool. + * + * @returns A bool value. + * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not true or false. + */ + simdjson_inline operator bool() noexcept(false); +#endif + + /** + * Begin array iteration. + * + * Part of the std::iterable interface. + * + * @returns INCORRECT_TYPE If the JSON value is not an array. + */ + simdjson_inline simdjson_result begin() & noexcept; + /** + * Sentinel representing the end of the array. + * + * Part of the std::iterable interface. + */ + simdjson_inline simdjson_result end() & noexcept; + /** + * This method scans the array and counts the number of elements. + * The count_elements method should always be called before you have begun + * iterating through the array: it is expected that you are pointing at + * the beginning of the array. + * The runtime complexity is linear in the size of the array. After + * calling this function, if successful, the array is 'rewinded' at its + * beginning as if it had never been accessed. If the JSON is malformed (e.g., + * there is a missing comma), then an error is returned and it is no longer + * safe to continue. + * + * Performance hint: You should only call count_elements() as a last + * resort as it may require scanning the document twice or more. + */ + simdjson_inline simdjson_result count_elements() & noexcept; + /** + * This method scans the object and counts the number of key-value pairs. + * The count_fields method should always be called before you have begun + * iterating through the object: it is expected that you are pointing at + * the beginning of the object. + * The runtime complexity is linear in the size of the object. After + * calling this function, if successful, the object is 'rewinded' at its + * beginning as if it had never been accessed. If the JSON is malformed (e.g., + * there is a missing comma), then an error is returned and it is no longer + * safe to continue. + * + * To check that an object is empty, it is more performant to use + * the is_empty() method on the object instance. + * + * Performance hint: You should only call count_fields() as a last + * resort as it may require scanning the document twice or more. + */ + simdjson_inline simdjson_result count_fields() & noexcept; + /** + * Get the value at the given index in the array. This function has linear-time complexity. + * This function should only be called once on an array instance since the array iterator is not reset between each call. + * + * @return The value at the given index, or: + * - INDEX_OUT_OF_BOUNDS if the array index is larger than an array length + */ + simdjson_inline simdjson_result at(size_t index) noexcept; + /** + * Look up a field by name on an object (order-sensitive). + * + * The following code reads z, then y, then x, and thus will not retrieve x or y if fed the + * JSON `{ "x": 1, "y": 2, "z": 3 }`: + * + * ```c++ + * simdjson::ondemand::parser parser; + * auto obj = parser.parse(R"( { "x": 1, "y": 2, "z": 3 } )"_padded); + * double z = obj.find_field("z"); + * double y = obj.find_field("y"); + * double x = obj.find_field("x"); + * ``` + * If you have multiple fields with a matching key ({"x": 1, "x": 1}) be mindful + * that only one field is returned. + + * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys. + * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. + * + * @param key The key to look up. + * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. + */ + simdjson_inline simdjson_result find_field(std::string_view key) noexcept; + /** @overload simdjson_inline simdjson_result find_field(std::string_view key) noexcept; */ + simdjson_inline simdjson_result find_field(const char *key) noexcept; + + /** + * Look up a field by name on an object, without regard to key order. + * + * **Performance Notes:** This is a bit less performant than find_field(), though its effect varies + * and often appears negligible. It starts out normally, starting out at the last field; but if + * the field is not found, it scans from the beginning of the object to see if it missed it. That + * missing case has a non-cache-friendly bump and lots of extra scanning, especially if the object + * in question is large. The fact that the extra code is there also bumps the executable size. + * + * It is the default, however, because it would be highly surprising (and hard to debug) if the + * default behavior failed to look up a field just because it was in the wrong order--and many + * APIs assume this. Therefore, you must be explicit if you want to treat objects as out of order. + * + * If you have multiple fields with a matching key ({"x": 1, "x": 1}) be mindful + * that only one field is returned. + * + * Use find_field() if you are sure fields will be in order (or are willing to treat it as if the + * field as not there when they are not in order). + * + * @param key The key to look up. + * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. + */ + simdjson_inline simdjson_result find_field_unordered(std::string_view key) noexcept; + /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) noexcept; */ + simdjson_inline simdjson_result find_field_unordered(const char *key) noexcept; + /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) noexcept; */ + simdjson_inline simdjson_result operator[](std::string_view key) noexcept; + /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) noexcept; */ + simdjson_inline simdjson_result operator[](const char *key) noexcept; + simdjson_result operator[](int) noexcept = delete; + + /** + * Get the type of this JSON value. It does not validate or consume the value. + * E.g., you must still call "is_null()" to check that a value is null even if + * "type()" returns json_type::null. + * + * NOTE: If you're only expecting a value to be one type (a typical case), it's generally + * better to just call .get_double, .get_string, etc. and check for INCORRECT_TYPE (or just + * let it throw an exception). + * + * @return The type of JSON value (json_type::array, json_type::object, json_type::string, + * json_type::number, json_type::boolean, or json_type::null). + * @error TAPE_ERROR when the JSON value is a bad token like "}" "," or "alse". + */ + simdjson_inline simdjson_result type() noexcept; + + /** + * Checks whether the value is a scalar (string, number, null, Boolean). + * Returns false when there it is an array or object. + * + * @returns true if the type is string, number, null, Boolean + * @error TAPE_ERROR when the JSON value is a bad token like "}" "," or "alse". + */ + simdjson_inline simdjson_result is_scalar() noexcept; + /** + * Checks whether the value is a string. + * + * @returns true if the type is string + * @error TAPE_ERROR when the JSON value is a bad token like "}" "," or "alse". + */ + simdjson_inline simdjson_result is_string() noexcept; + + /** + * Checks whether the value is a negative number. + * + * @returns true if the number if negative. + */ + simdjson_inline bool is_negative() noexcept; + /** + * Checks whether the value is an integer number. Note that + * this requires to partially parse the number string. If + * the value is determined to be an integer, it may still + * not parse properly as an integer in subsequent steps + * (e.g., it might overflow). + * + * Performance note: if you call this function systematically + * before parsing a number, you may have fallen for a performance + * anti-pattern. + * + * @returns true if the number if negative. + */ + simdjson_inline simdjson_result is_integer() noexcept; + /** + * Determine the number type (integer or floating-point number) as quickly + * as possible. This function does not fully validate the input. It is + * useful when you only need to classify the numbers, without parsing them. + * + * If you are planning to retrieve the value or you need full validation, + * consider using the get_number() method instead: it will fully parse + * and validate the input, and give you access to the type: + * get_number().get_number_type(). + * + * get_number_type() is number_type::unsigned_integer if we have + * an integer greater or equal to 9223372036854775808. + * get_number_type() is number_type::signed_integer if we have an + * integer that is less than 9223372036854775808. + * get_number_type() is number_type::big_integer for integers that do not fit in 64 bits, + * in which case the digit_count is set to the length of the big integer string. + * Otherwise, get_number_type() has value number_type::floating_point_number. + * + * This function requires processing the number string, but it is expected + * to be faster than get_number().get_number_type() because it is does not + * parse the number value. + * + * @returns the type of the number + */ + simdjson_inline simdjson_result get_number_type() noexcept; + + /** + * Attempt to parse an ondemand::number. An ondemand::number may + * contain an integer value or a floating-point value, the simdjson + * library will autodetect the type. Thus it is a dynamically typed + * number. Before accessing the value, you must determine the detected + * type. + * + * number.get_number_type() is number_type::signed_integer if we have + * an integer in [-9223372036854775808,9223372036854775808) + * You can recover the value by calling number.get_int64() and you + * have that number.is_int64() is true. + * + * number.get_number_type() is number_type::unsigned_integer if we have + * an integer in [9223372036854775808,18446744073709551616) + * You can recover the value by calling number.get_uint64() and you + * have that number.is_uint64() is true. + * + * For integers that do not fit in 64 bits, the function returns BIGINT_ERROR error code. + * + * Otherwise, number.get_number_type() has value number_type::floating_point_number + * and we have a binary64 number. + * You can recover the value by calling number.get_double() and you + * have that number.is_double() is true. + * + * You must check the type before accessing the value: it is an error + * to call "get_int64()" when number.get_number_type() is not + * number_type::signed_integer and when number.is_int64() is false. + * + * Performance note: this is designed with performance in mind. When + * calling 'get_number()', you scan the number string only once, determining + * efficiently the type and storing it in an efficient manner. + */ + simdjson_warn_unused simdjson_inline simdjson_result get_number() noexcept; + + /** + * Get the raw JSON for this token. + * + * The string_view will always point into the input buffer. + * + * The string_view will start at the beginning of the token, and include the entire token + * *as well as all spaces until the next token (or EOF).* This means, for example, that a + * string token always begins with a " and is always terminated by the final ", possibly + * followed by a number of spaces. + * + * The string_view is *not* null-terminated. However, if this is a scalar (string, number, + * boolean, or null), the character after the end of the string_view is guaranteed to be + * a non-space token. + * + * Tokens include: + * - { + * - [ + * - "a string (possibly with UTF-8 or backslashed characters like \\\")". + * - -1.2e-100 + * - true + * - false + * - null + * + * See also value::raw_json(). + */ + simdjson_inline std::string_view raw_json_token() noexcept; + + /** + * Get a string_view pointing at this value in the JSON document. + * If this element is an array or an object, it consumes the array or the object + * and returns a string_view instance corresponding to the + * array as represented in JSON. It points inside the original document. + * If this element is a scalar (string, number, Boolean, null), it returns what + * raw_json_token() would return. + */ + simdjson_inline simdjson_result raw_json() noexcept; + + /** + * Returns the current location in the document if in bounds. + */ + simdjson_inline simdjson_result current_location() noexcept; + + /** + * Returns the current depth in the document if in bounds. + * + * E.g., + * 0 = finished with document + * 1 = document root value (could be [ or {, not yet known) + * 2 = , or } inside root array/object + * 3 = key or value inside root array/object. + */ + simdjson_inline int32_t current_depth() const noexcept; + + /** + * Get the value associated with the given JSON pointer. We use the RFC 6901 + * https://tools.ietf.org/html/rfc6901 standard. + * + * ondemand::parser parser; + * auto json = R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded; + * auto doc = parser.iterate(json); + * doc.at_pointer("/foo/a/1") == 20 + * + * It is allowed for a key to be the empty string: + * + * ondemand::parser parser; + * auto json = R"({ "": { "a": [ 10, 20, 30 ] }})"_padded; + * auto doc = parser.iterate(json); + * doc.at_pointer("//a/1") == 20 + * + * Note that at_pointer() called on the document automatically calls the document's rewind + * method between each call. It invalidates all previously accessed arrays, objects and values + * that have not been consumed. + * + * Calling at_pointer() on non-document instances (e.g., arrays and objects) is not + * standardized (by RFC 6901). We provide some experimental support for JSON pointers + * on non-document instances. Yet it is not the case when calling at_pointer on an array + * or an object instance: there is no rewind and no invalidation. + * + * You may only call at_pointer on an array after it has been created, but before it has + * been first accessed. When calling at_pointer on an array, the pointer is advanced to + * the location indicated by the JSON pointer (in case of success). It is no longer possible + * to call at_pointer on the same array. + * + * You may call at_pointer more than once on an object, but each time the pointer is advanced + * to be within the value matched by the key indicated by the JSON pointer query. Thus any preceding + * key (as well as the current key) can no longer be used with following JSON pointer calls. + * + * Also note that at_pointer() relies on find_field() which implies that we do not unescape keys when matching + * + * @return The value associated with the given JSON pointer, or: + * - NO_SUCH_FIELD if a field does not exist in an object + * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length + * - INCORRECT_TYPE if a non-integer is used to access an array + * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed + */ + simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; + + /** + * Get the value associated with the given JSONPath expression. We only support + * JSONPath queries that trivially convertible to JSON Pointer queries: key + * names and array indices. + * + * @return The value associated with the given JSONPath expression, or: + * - INVALID_JSON_POINTER if the JSONPath to JSON Pointer conversion fails + * - NO_SUCH_FIELD if a field does not exist in an object + * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length + * - INCORRECT_TYPE if a non-integer is used to access an array + */ + simdjson_inline simdjson_result at_path(std::string_view at_path) noexcept; + + +protected: + /** + * Create a value. + */ + simdjson_inline value(const value_iterator &iter) noexcept; + + /** + * Skip this value, allowing iteration to continue. + */ + simdjson_inline void skip() noexcept; + + /** + * Start a value at the current position. + * + * (It should already be started; this is just a self-documentation method.) + */ + static simdjson_inline value start(const value_iterator &iter) noexcept; + + /** + * Resume a value. + */ + static simdjson_inline value resume(const value_iterator &iter) noexcept; + + /** + * Get the object, starting or resuming it as necessary + */ + simdjson_inline simdjson_result start_or_resume_object() noexcept; + + // simdjson_inline void log_value(const char *type) const noexcept; + // simdjson_inline void log_error(const char *message) const noexcept; + + value_iterator iter{}; + + friend class document; + friend class array_iterator; + friend class field; + friend class object; + friend struct simdjson_result; + friend struct simdjson_result; + friend class field; +}; + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +template<> +struct simdjson_result : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::value &&value) noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + simdjson_inline simdjson_result() noexcept = default; + + simdjson_inline simdjson_result get_array() noexcept; + simdjson_inline simdjson_result get_object() noexcept; + + simdjson_inline simdjson_result get_uint64() noexcept; + simdjson_inline simdjson_result get_uint64_in_string() noexcept; + simdjson_inline simdjson_result get_int64() noexcept; + simdjson_inline simdjson_result get_int64_in_string() noexcept; + simdjson_inline simdjson_result get_double() noexcept; + simdjson_inline simdjson_result get_double_in_string() noexcept; + simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; + template + simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; + simdjson_inline simdjson_result get_wobbly_string() noexcept; + simdjson_inline simdjson_result get_raw_json_string() noexcept; + simdjson_inline simdjson_result get_bool() noexcept; + simdjson_inline simdjson_result is_null() noexcept; + + template simdjson_inline simdjson_result get() noexcept; + + template simdjson_inline error_code get(T &out) noexcept; + +#if SIMDJSON_EXCEPTIONS + template + explicit simdjson_inline operator T() noexcept(false); + simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::array() noexcept(false); + simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::object() noexcept(false); + simdjson_inline operator uint64_t() noexcept(false); + simdjson_inline operator int64_t() noexcept(false); + simdjson_inline operator double() noexcept(false); + simdjson_inline operator std::string_view() noexcept(false); + simdjson_inline operator SIMDJSON_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false); + simdjson_inline operator bool() noexcept(false); +#endif + simdjson_inline simdjson_result count_elements() & noexcept; + simdjson_inline simdjson_result count_fields() & noexcept; + simdjson_inline simdjson_result at(size_t index) noexcept; + simdjson_inline simdjson_result begin() & noexcept; + simdjson_inline simdjson_result end() & noexcept; + + /** + * Look up a field by name on an object (order-sensitive). + * + * The following code reads z, then y, then x, and thus will not retrieve x or y if fed the + * JSON `{ "x": 1, "y": 2, "z": 3 }`: + * + * ```c++ + * simdjson::ondemand::parser parser; + * auto obj = parser.parse(R"( { "x": 1, "y": 2, "z": 3 } )"_padded); + * double z = obj.find_field("z"); + * double y = obj.find_field("y"); + * double x = obj.find_field("x"); + * ``` + * + * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys. + * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. + * + * @param key The key to look up. + * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. + */ + simdjson_inline simdjson_result find_field(std::string_view key) noexcept; + /** @overload simdjson_inline simdjson_result find_field(std::string_view key) noexcept; */ + simdjson_inline simdjson_result find_field(const char *key) noexcept; + + /** + * Look up a field by name on an object, without regard to key order. + * + * **Performance Notes:** This is a bit less performant than find_field(), though its effect varies + * and often appears negligible. It starts out normally, starting out at the last field; but if + * the field is not found, it scans from the beginning of the object to see if it missed it. That + * missing case has a non-cache-friendly bump and lots of extra scanning, especially if the object + * in question is large. The fact that the extra code is there also bumps the executable size. + * + * It is the default, however, because it would be highly surprising (and hard to debug) if the + * default behavior failed to look up a field just because it was in the wrong order--and many + * APIs assume this. Therefore, you must be explicit if you want to treat objects as out of order. + * + * Use find_field() if you are sure fields will be in order (or are willing to treat it as if the + * field as not there when they are not in order). + * + * @param key The key to look up. + * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. + */ + simdjson_inline simdjson_result find_field_unordered(std::string_view key) noexcept; + /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) noexcept; */ + simdjson_inline simdjson_result find_field_unordered(const char *key) noexcept; + /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) noexcept; */ + simdjson_inline simdjson_result operator[](std::string_view key) noexcept; + /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) noexcept; */ + simdjson_inline simdjson_result operator[](const char *key) noexcept; + simdjson_result operator[](int) noexcept = delete; + + /** + * Get the type of this JSON value. + * + * NOTE: If you're only expecting a value to be one type (a typical case), it's generally + * better to just call .get_double, .get_string, etc. and check for INCORRECT_TYPE (or just + * let it throw an exception). + */ + simdjson_inline simdjson_result type() noexcept; + simdjson_inline simdjson_result is_scalar() noexcept; + simdjson_inline simdjson_result is_string() noexcept; + simdjson_inline simdjson_result is_negative() noexcept; + simdjson_inline simdjson_result is_integer() noexcept; + simdjson_inline simdjson_result get_number_type() noexcept; + simdjson_inline simdjson_result get_number() noexcept; + + /** @copydoc simdjson_inline std::string_view value::raw_json_token() const noexcept */ + simdjson_inline simdjson_result raw_json_token() noexcept; + simdjson_inline simdjson_result raw_json() noexcept; + + /** @copydoc simdjson_inline simdjson_result current_location() noexcept */ + simdjson_inline simdjson_result current_location() noexcept; + /** @copydoc simdjson_inline int32_t current_depth() const noexcept */ + simdjson_inline simdjson_result current_depth() const noexcept; + simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; + simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; +}; + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_VALUE_H diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/value_iterator-inl.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/value_iterator-inl.h new file mode 100644 index 000000000000..21016f8b97d6 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/value_iterator-inl.h @@ -0,0 +1,1093 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_VALUE_ITERATOR_INL_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_VALUE_ITERATOR_INL_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/atomparsing.h" +#include "simdjson/generic/numberparsing.h" +#include "simdjson/generic/ondemand/json_iterator.h" +#include "simdjson/generic/ondemand/value_iterator.h" +#include "simdjson/generic/ondemand/json_type-inl.h" +#include "simdjson/generic/ondemand/raw_json_string-inl.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +simdjson_inline value_iterator::value_iterator( + json_iterator *json_iter, + depth_t depth, + token_position start_position +) noexcept : _json_iter{json_iter}, _depth{depth}, _start_position{start_position} +{ +} + +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::start_object() noexcept { + SIMDJSON_TRY( start_container('{', "Not an object", "object") ); + return started_object(); +} + +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::start_root_object() noexcept { + SIMDJSON_TRY( start_container('{', "Not an object", "object") ); + return started_root_object(); +} + +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_object() noexcept { + assert_at_container_start(); +#if SIMDJSON_DEVELOPMENT_CHECKS + _json_iter->set_start_position(_depth, start_position()); +#endif + if (*_json_iter->peek() == '}') { + logger::log_value(*_json_iter, "empty object"); + _json_iter->return_current_and_advance(); + end_container(); + return false; + } + return true; +} + +simdjson_warn_unused simdjson_inline error_code value_iterator::check_root_object() noexcept { + // When in streaming mode, we cannot expect peek_last() to be the last structural element of the + // current document. It only works in the normal mode where we have indexed a single document. + // Note that adding a check for 'streaming' is not expensive since we only have at most + // one root element. + if ( ! _json_iter->streaming() ) { + // The following lines do not fully protect against garbage content within the + // object: e.g., `{"a":2} foo }`. Users concerned with garbage content should + // call `at_end()` on the document instance at the end of the processing to + // ensure that the processing has finished at the end. + // + if (*_json_iter->peek_last() != '}') { + _json_iter->abandon(); + return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "missing } at end"); + } + // If the last character is } *and* the first gibberish character is also '}' + // then on-demand could accidentally go over. So we need additional checks. + // https://github.com/simdjson/simdjson/issues/1834 + // Checking that the document is balanced requires a full scan which is potentially + // expensive, but it only happens in edge cases where the first padding character is + // a closing bracket. + if ((*_json_iter->peek(_json_iter->end_position()) == '}') && (!_json_iter->balanced())) { + _json_iter->abandon(); + // The exact error would require more work. It will typically be an unclosed object. + return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "the document is unbalanced"); + } + } + return SUCCESS; +} + +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_root_object() noexcept { + auto error = check_root_object(); + if(error) { return error; } + return started_object(); +} + +simdjson_warn_unused simdjson_inline error_code value_iterator::end_container() noexcept { +#if SIMDJSON_CHECK_EOF + if (depth() > 1 && at_end()) { return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "missing parent ] or }"); } + // if (depth() <= 1 && !at_end()) { return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "missing [ or { at start"); } +#endif // SIMDJSON_CHECK_EOF + _json_iter->ascend_to(depth()-1); + return SUCCESS; +} + +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::has_next_field() noexcept { + assert_at_next(); + + // It's illegal to call this unless there are more tokens: anything that ends in } or ] is + // obligated to verify there are more tokens if they are not the top level. + switch (*_json_iter->return_current_and_advance()) { + case '}': + logger::log_end_value(*_json_iter, "object"); + SIMDJSON_TRY( end_container() ); + return false; + case ',': + return true; + default: + return report_error(TAPE_ERROR, "Missing comma between object fields"); + } +} + +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::find_field_raw(const std::string_view key) noexcept { + error_code error; + bool has_value; + // + // Initially, the object can be in one of a few different places: + // + // 1. The start of the object, at the first field: + // + // ``` + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 2, index 1) + // ``` + if (at_first_field()) { + has_value = true; + + // + // 2. When a previous search did not yield a value or the object is empty: + // + // ``` + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 0) + // { } + // ^ (depth 0, index 2) + // ``` + // + } else if (!is_open()) { +#if SIMDJSON_DEVELOPMENT_CHECKS + // If we're past the end of the object, we're being iterated out of order. + // Note: this is not perfect detection. It's possible the user is inside some other object; if so, + // this object iterator will blithely scan that object for fields. + if (_json_iter->depth() < depth() - 1) { return OUT_OF_ORDER_ITERATION; } +#endif + return false; + + // 3. When a previous search found a field or an iterator yielded a value: + // + // ``` + // // When a field was not fully consumed (or not even touched at all) + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 2) + // // When a field was fully consumed + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 1) + // // When the last field was fully consumed + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 1) + // ``` + // + } else { + if ((error = skip_child() )) { abandon(); return error; } + if ((error = has_next_field().get(has_value) )) { abandon(); return error; } +#if SIMDJSON_DEVELOPMENT_CHECKS + if (_json_iter->start_position(_depth) != start_position()) { return OUT_OF_ORDER_ITERATION; } +#endif + } + while (has_value) { + // Get the key and colon, stopping at the value. + raw_json_string actual_key; + // size_t max_key_length = _json_iter->peek_length() - 2; // -2 for the two quotes + // Note: _json_iter->peek_length() - 2 might overflow if _json_iter->peek_length() < 2. + // field_key() advances the pointer and checks that '"' is found (corresponding to a key). + // The depth is left unchanged by field_key(). + if ((error = field_key().get(actual_key) )) { abandon(); return error; }; + // field_value() will advance and check that we find a ':' separating the + // key and the value. It will also increment the depth by one. + if ((error = field_value() )) { abandon(); return error; } + // If it matches, stop and return + // We could do it this way if we wanted to allow arbitrary + // key content (including escaped quotes). + //if (actual_key.unsafe_is_equal(max_key_length, key)) { + // Instead we do the following which may trigger buffer overruns if the + // user provides an adversarial key (containing a well placed unescaped quote + // character and being longer than the number of bytes remaining in the JSON + // input). + if (actual_key.unsafe_is_equal(key)) { + logger::log_event(*this, "match", key, -2); + // If we return here, then we return while pointing at the ':' that we just checked. + return true; + } + + // No match: skip the value and see if , or } is next + logger::log_event(*this, "no match", key, -2); + // The call to skip_child is meant to skip over the value corresponding to the key. + // After skip_child(), we are right before the next comma (',') or the final brace ('}'). + SIMDJSON_TRY( skip_child() ); // Skip the value entirely + // The has_next_field() advances the pointer and check that either ',' or '}' is found. + // It returns true if ',' is found, false otherwise. If anything other than ',' or '}' is found, + // then we are in error and we abort. + if ((error = has_next_field().get(has_value) )) { abandon(); return error; } + } + + // If the loop ended, we're out of fields to look at. + return false; +} + +SIMDJSON_PUSH_DISABLE_WARNINGS +SIMDJSON_DISABLE_STRICT_OVERFLOW_WARNING +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::find_field_unordered_raw(const std::string_view key) noexcept { + /** + * When find_field_unordered_raw is called, we can either be pointing at the + * first key, pointing outside (at the closing brace) or if a key was matched + * we can be either pointing right afterthe ':' right before the value (that we need skip), + * or we may have consumed the value and we might be at a comma or at the + * final brace (ready for a call to has_next_field()). + */ + error_code error; + bool has_value; + + // First, we scan from that point to the end. + // If we don't find a match, we may loop back around, and scan from the beginning to that point. + token_position search_start = _json_iter->position(); + + // We want to know whether we need to go back to the beginning. + bool at_first = at_first_field(); + /////////////// + // Initially, the object can be in one of a few different places: + // + // 1. At the first key: + // + // ``` + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 2, index 1) + // ``` + // + if (at_first) { + has_value = true; + + // 2. When a previous search did not yield a value or the object is empty: + // + // ``` + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 0) + // { } + // ^ (depth 0, index 2) + // ``` + // + } else if (!is_open()) { + +#if SIMDJSON_DEVELOPMENT_CHECKS + // If we're past the end of the object, we're being iterated out of order. + // Note: this is not perfect detection. It's possible the user is inside some other object; if so, + // this object iterator will blithely scan that object for fields. + if (_json_iter->depth() < depth() - 1) { return OUT_OF_ORDER_ITERATION; } +#endif + SIMDJSON_TRY(reset_object().get(has_value)); + at_first = true; + // 3. When a previous search found a field or an iterator yielded a value: + // + // ``` + // // When a field was not fully consumed (or not even touched at all) + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 2) + // // When a field was fully consumed + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 1) + // // When the last field was fully consumed + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 1) + // ``` + // + } else { + // If someone queried a key but they not did access the value, then we are left pointing + // at the ':' and we need to move forward through the value... If the value was + // processed then skip_child() does not move the iterator (but may adjust the depth). + if ((error = skip_child() )) { abandon(); return error; } + search_start = _json_iter->position(); + if ((error = has_next_field().get(has_value) )) { abandon(); return error; } +#if SIMDJSON_DEVELOPMENT_CHECKS + if (_json_iter->start_position(_depth) != start_position()) { return OUT_OF_ORDER_ITERATION; } +#endif + } + + // After initial processing, we will be in one of two states: + // + // ``` + // // At the beginning of a field + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 1) + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 1) + // // At the end of the object + // { "a": [ 1, 2 ], "b": [ 3, 4 ] } + // ^ (depth 0) + // ``` + // + // Next, we find a match starting from the current position. + while (has_value) { + SIMDJSON_ASSUME( _json_iter->_depth == _depth ); // We must be at the start of a field + + // Get the key and colon, stopping at the value. + raw_json_string actual_key; + // size_t max_key_length = _json_iter->peek_length() - 2; // -2 for the two quotes + // Note: _json_iter->peek_length() - 2 might overflow if _json_iter->peek_length() < 2. + // field_key() advances the pointer and checks that '"' is found (corresponding to a key). + // The depth is left unchanged by field_key(). + if ((error = field_key().get(actual_key) )) { abandon(); return error; }; + // field_value() will advance and check that we find a ':' separating the + // key and the value. It will also increment the depth by one. + if ((error = field_value() )) { abandon(); return error; } + + // If it matches, stop and return + // We could do it this way if we wanted to allow arbitrary + // key content (including escaped quotes). + // if (actual_key.unsafe_is_equal(max_key_length, key)) { + // Instead we do the following which may trigger buffer overruns if the + // user provides an adversarial key (containing a well placed unescaped quote + // character and being longer than the number of bytes remaining in the JSON + // input). + if (actual_key.unsafe_is_equal(key)) { + logger::log_event(*this, "match", key, -2); + // If we return here, then we return while pointing at the ':' that we just checked. + return true; + } + + // No match: skip the value and see if , or } is next + logger::log_event(*this, "no match", key, -2); + // The call to skip_child is meant to skip over the value corresponding to the key. + // After skip_child(), we are right before the next comma (',') or the final brace ('}'). + SIMDJSON_TRY( skip_child() ); + // The has_next_field() advances the pointer and check that either ',' or '}' is found. + // It returns true if ',' is found, false otherwise. If anything other than ',' or '}' is found, + // then we are in error and we abort. + if ((error = has_next_field().get(has_value) )) { abandon(); return error; } + } + // Performance note: it maybe wasteful to rewind to the beginning when there might be + // no other query following. Indeed, it would require reskipping the whole object. + // Instead, you can just stay where you are. If there is a new query, there is always time + // to rewind. + if(at_first) { return false; } + + // If we reach the end without finding a match, search the rest of the fields starting at the + // beginning of the object. + // (We have already run through the object before, so we've already validated its structure. We + // don't check errors in this bit.) + SIMDJSON_TRY(reset_object().get(has_value)); + while (true) { + SIMDJSON_ASSUME(has_value); // we should reach search_start before ever reaching the end of the object + SIMDJSON_ASSUME( _json_iter->_depth == _depth ); // We must be at the start of a field + + // Get the key and colon, stopping at the value. + raw_json_string actual_key; + // size_t max_key_length = _json_iter->peek_length() - 2; // -2 for the two quotes + // Note: _json_iter->peek_length() - 2 might overflow if _json_iter->peek_length() < 2. + // field_key() advances the pointer and checks that '"' is found (corresponding to a key). + // The depth is left unchanged by field_key(). + error = field_key().get(actual_key); SIMDJSON_ASSUME(!error); + // field_value() will advance and check that we find a ':' separating the + // key and the value. It will also increment the depth by one. + error = field_value(); SIMDJSON_ASSUME(!error); + + // If it matches, stop and return + // We could do it this way if we wanted to allow arbitrary + // key content (including escaped quotes). + // if (actual_key.unsafe_is_equal(max_key_length, key)) { + // Instead we do the following which may trigger buffer overruns if the + // user provides an adversarial key (containing a well placed unescaped quote + // character and being longer than the number of bytes remaining in the JSON + // input). + if (actual_key.unsafe_is_equal(key)) { + logger::log_event(*this, "match", key, -2); + // If we return here, then we return while pointing at the ':' that we just checked. + return true; + } + + // No match: skip the value and see if , or } is next + logger::log_event(*this, "no match", key, -2); + // The call to skip_child is meant to skip over the value corresponding to the key. + // After skip_child(), we are right before the next comma (',') or the final brace ('}'). + SIMDJSON_TRY( skip_child() ); + // If we reached the end of the key-value pair we started from, then we know + // that the key is not there so we return false. We are either right before + // the next comma or the final brace. + if(_json_iter->position() == search_start) { return false; } + // The has_next_field() advances the pointer and check that either ',' or '}' is found. + // It returns true if ',' is found, false otherwise. If anything other than ',' or '}' is found, + // then we are in error and we abort. + error = has_next_field().get(has_value); SIMDJSON_ASSUME(!error); + // If we make the mistake of exiting here, then we could be left pointing at a key + // in the middle of an object. That's not an allowable state. + } + // If the loop ended, we're out of fields to look at. The program should + // never reach this point. + return false; +} +SIMDJSON_POP_DISABLE_WARNINGS + +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::field_key() noexcept { + assert_at_next(); + + const uint8_t *key = _json_iter->return_current_and_advance(); + if (*(key++) != '"') { return report_error(TAPE_ERROR, "Object key is not a string"); } + return raw_json_string(key); +} + +simdjson_warn_unused simdjson_inline error_code value_iterator::field_value() noexcept { + assert_at_next(); + + if (*_json_iter->return_current_and_advance() != ':') { return report_error(TAPE_ERROR, "Missing colon in object field"); } + _json_iter->descend_to(depth()+1); + return SUCCESS; +} + +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::start_array() noexcept { + SIMDJSON_TRY( start_container('[', "Not an array", "array") ); + return started_array(); +} + +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::start_root_array() noexcept { + SIMDJSON_TRY( start_container('[', "Not an array", "array") ); + return started_root_array(); +} + +inline std::string value_iterator::to_string() const noexcept { + auto answer = std::string("value_iterator [ depth : ") + std::to_string(_depth) + std::string(", "); + if(_json_iter != nullptr) { answer += _json_iter->to_string(); } + answer += std::string(" ]"); + return answer; +} + +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_array() noexcept { + assert_at_container_start(); + if (*_json_iter->peek() == ']') { + logger::log_value(*_json_iter, "empty array"); + _json_iter->return_current_and_advance(); + SIMDJSON_TRY( end_container() ); + return false; + } + _json_iter->descend_to(depth()+1); +#if SIMDJSON_DEVELOPMENT_CHECKS + _json_iter->set_start_position(_depth, start_position()); +#endif + return true; +} + +simdjson_warn_unused simdjson_inline error_code value_iterator::check_root_array() noexcept { + // When in streaming mode, we cannot expect peek_last() to be the last structural element of the + // current document. It only works in the normal mode where we have indexed a single document. + // Note that adding a check for 'streaming' is not expensive since we only have at most + // one root element. + if ( ! _json_iter->streaming() ) { + // The following lines do not fully protect against garbage content within the + // array: e.g., `[1, 2] foo]`. Users concerned with garbage content should + // also call `at_end()` on the document instance at the end of the processing to + // ensure that the processing has finished at the end. + // + if (*_json_iter->peek_last() != ']') { + _json_iter->abandon(); + return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "missing ] at end"); + } + // If the last character is ] *and* the first gibberish character is also ']' + // then on-demand could accidentally go over. So we need additional checks. + // https://github.com/simdjson/simdjson/issues/1834 + // Checking that the document is balanced requires a full scan which is potentially + // expensive, but it only happens in edge cases where the first padding character is + // a closing bracket. + if ((*_json_iter->peek(_json_iter->end_position()) == ']') && (!_json_iter->balanced())) { + _json_iter->abandon(); + // The exact error would require more work. It will typically be an unclosed array. + return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "the document is unbalanced"); + } + } + return SUCCESS; +} + +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_root_array() noexcept { + auto error = check_root_array(); + if (error) { return error; } + return started_array(); +} + +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::has_next_element() noexcept { + assert_at_next(); + + logger::log_event(*this, "has_next_element"); + switch (*_json_iter->return_current_and_advance()) { + case ']': + logger::log_end_value(*_json_iter, "array"); + SIMDJSON_TRY( end_container() ); + return false; + case ',': + _json_iter->descend_to(depth()+1); + return true; + default: + return report_error(TAPE_ERROR, "Missing comma between array elements"); + } +} + +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::parse_bool(const uint8_t *json) const noexcept { + auto not_true = atomparsing::str4ncmp(json, "true"); + auto not_false = atomparsing::str4ncmp(json, "fals") | (json[4] ^ 'e'); + bool error = (not_true && not_false) || jsoncharutils::is_not_structural_or_whitespace(json[not_true ? 5 : 4]); + if (error) { return incorrect_type_error("Not a boolean"); } + return simdjson_result(!not_true); +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::parse_null(const uint8_t *json) const noexcept { + bool is_null_string = !atomparsing::str4ncmp(json, "null") && jsoncharutils::is_structural_or_whitespace(json[4]); + // if we start with 'n', we must be a null + if(!is_null_string && json[0]=='n') { return incorrect_type_error("Not a null but starts with n"); } + return is_null_string; +} + +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_string(bool allow_replacement) noexcept { + return get_raw_json_string().unescape(json_iter(), allow_replacement); +} +template +simdjson_warn_unused simdjson_inline error_code value_iterator::get_string(string_type& receiver, bool allow_replacement) noexcept { + std::string_view content; + auto err = get_string(allow_replacement).get(content); + if (err) { return err; } + receiver = content; + return SUCCESS; +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_wobbly_string() noexcept { + return get_raw_json_string().unescape_wobbly(json_iter()); +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_raw_json_string() noexcept { + auto json = peek_scalar("string"); + if (*json != '"') { return incorrect_type_error("Not a string"); } + advance_scalar("string"); + return raw_json_string(json+1); +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_uint64() noexcept { + auto result = numberparsing::parse_unsigned(peek_non_root_scalar("uint64")); + if(result.error() == SUCCESS) { advance_non_root_scalar("uint64"); } + return result; +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_uint64_in_string() noexcept { + auto result = numberparsing::parse_unsigned_in_string(peek_non_root_scalar("uint64")); + if(result.error() == SUCCESS) { advance_non_root_scalar("uint64"); } + return result; +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_int64() noexcept { + auto result = numberparsing::parse_integer(peek_non_root_scalar("int64")); + if(result.error() == SUCCESS) { advance_non_root_scalar("int64"); } + return result; +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_int64_in_string() noexcept { + auto result = numberparsing::parse_integer_in_string(peek_non_root_scalar("int64")); + if(result.error() == SUCCESS) { advance_non_root_scalar("int64"); } + return result; +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_double() noexcept { + auto result = numberparsing::parse_double(peek_non_root_scalar("double")); + if(result.error() == SUCCESS) { advance_non_root_scalar("double"); } + return result; +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_double_in_string() noexcept { + auto result = numberparsing::parse_double_in_string(peek_non_root_scalar("double")); + if(result.error() == SUCCESS) { advance_non_root_scalar("double"); } + return result; +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_bool() noexcept { + auto result = parse_bool(peek_non_root_scalar("bool")); + if(result.error() == SUCCESS) { advance_non_root_scalar("bool"); } + return result; +} +simdjson_inline simdjson_result value_iterator::is_null() noexcept { + bool is_null_value; + SIMDJSON_TRY(parse_null(peek_non_root_scalar("null")).get(is_null_value)); + if(is_null_value) { advance_non_root_scalar("null"); } + return is_null_value; +} +simdjson_inline bool value_iterator::is_negative() noexcept { + return numberparsing::is_negative(peek_non_root_scalar("numbersign")); +} +simdjson_inline bool value_iterator::is_root_negative() noexcept { + return numberparsing::is_negative(peek_root_scalar("numbersign")); +} +simdjson_inline simdjson_result value_iterator::is_integer() noexcept { + return numberparsing::is_integer(peek_non_root_scalar("integer")); +} +simdjson_inline simdjson_result value_iterator::get_number_type() noexcept { + return numberparsing::get_number_type(peek_non_root_scalar("integer")); +} +simdjson_inline simdjson_result value_iterator::get_number() noexcept { + number num; + error_code error = numberparsing::parse_number(peek_non_root_scalar("number"), num); + if(error) { return error; } + return num; +} + +simdjson_inline simdjson_result value_iterator::is_root_integer(bool check_trailing) noexcept { + auto max_len = peek_root_length(); + auto json = peek_root_scalar("is_root_integer"); + uint8_t tmpbuf[20+1+1]{}; // <20 digits> is the longest possible unsigned integer + tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { + return false; // if there are more than 20 characters, it cannot be represented as an integer. + } + auto answer = numberparsing::is_integer(tmpbuf); + // If the parsing was a success, we must still check that it is + // a single scalar. Note that we parse first because of cases like '[]' where + // getting TRAILING_CONTENT is wrong. + if(check_trailing && (answer.error() == SUCCESS) && (!_json_iter->is_single_token())) { return TRAILING_CONTENT; } + return answer; +} + +simdjson_inline simdjson_result value_iterator::get_root_number_type(bool check_trailing) noexcept { + auto max_len = peek_root_length(); + auto json = peek_root_scalar("number"); + // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, + // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest + // number: -0.e-308. + uint8_t tmpbuf[1074+8+1+1]; + tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { + if(numberparsing::check_if_integer(json, max_len)) { + if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } + logger::log_error(*_json_iter, start_position(), depth(), "Found big integer"); + return number_type::big_integer; + } + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters and not a big integer"); + return NUMBER_ERROR; + } + auto answer = numberparsing::get_number_type(tmpbuf); + if (check_trailing && (answer.error() == SUCCESS) && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } + return answer; +} +simdjson_inline simdjson_result value_iterator::get_root_number(bool check_trailing) noexcept { + auto max_len = peek_root_length(); + auto json = peek_root_scalar("number"); + // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, + // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest + // number: -0.e-308. + // NOTE: the current approach doesn't work for very big integer numbers containing more than 1074 digits. + uint8_t tmpbuf[1074+8+1+1]; + tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { + if(numberparsing::check_if_integer(json, max_len)) { + if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } + logger::log_error(*_json_iter, start_position(), depth(), "Found big integer"); + return BIGINT_ERROR; + } + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters and not a big integer"); + return NUMBER_ERROR; + } + number num; + error_code error = numberparsing::parse_number(tmpbuf, num); + if(error) { return error; } + if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } + advance_root_scalar("number"); + return num; +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_string(bool check_trailing, bool allow_replacement) noexcept { + return get_root_raw_json_string(check_trailing).unescape(json_iter(), allow_replacement); +} +template +simdjson_warn_unused simdjson_inline error_code value_iterator::get_root_string(string_type& receiver, bool check_trailing, bool allow_replacement) noexcept { + std::string_view content; + auto err = get_root_string(check_trailing, allow_replacement).get(content); + if (err) { return err; } + receiver = content; + return SUCCESS; +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_wobbly_string(bool check_trailing) noexcept { + return get_root_raw_json_string(check_trailing).unescape_wobbly(json_iter()); +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_raw_json_string(bool check_trailing) noexcept { + auto json = peek_scalar("string"); + if (*json != '"') { return incorrect_type_error("Not a string"); } + if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } + advance_scalar("string"); + return raw_json_string(json+1); +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_uint64(bool check_trailing) noexcept { + auto max_len = peek_root_length(); + auto json = peek_root_scalar("uint64"); + uint8_t tmpbuf[20+1+1]{}; // <20 digits> is the longest possible unsigned integer + tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + return NUMBER_ERROR; + } + auto result = numberparsing::parse_unsigned(tmpbuf); + if(result.error() == SUCCESS) { + if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } + advance_root_scalar("uint64"); + } + return result; +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_uint64_in_string(bool check_trailing) noexcept { + auto max_len = peek_root_length(); + auto json = peek_root_scalar("uint64"); + uint8_t tmpbuf[20+1+1]{}; // <20 digits> is the longest possible unsigned integer + tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + return NUMBER_ERROR; + } + auto result = numberparsing::parse_unsigned_in_string(tmpbuf); + if(result.error() == SUCCESS) { + if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } + advance_root_scalar("uint64"); + } + return result; +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_int64(bool check_trailing) noexcept { + auto max_len = peek_root_length(); + auto json = peek_root_scalar("int64"); + uint8_t tmpbuf[20+1+1]; // -<19 digits> is the longest possible integer + tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + return NUMBER_ERROR; + } + + auto result = numberparsing::parse_integer(tmpbuf); + if(result.error() == SUCCESS) { + if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } + advance_root_scalar("int64"); + } + return result; +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_int64_in_string(bool check_trailing) noexcept { + auto max_len = peek_root_length(); + auto json = peek_root_scalar("int64"); + uint8_t tmpbuf[20+1+1]; // -<19 digits> is the longest possible integer + tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + return NUMBER_ERROR; + } + + auto result = numberparsing::parse_integer_in_string(tmpbuf); + if(result.error() == SUCCESS) { + if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } + advance_root_scalar("int64"); + } + return result; +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_double(bool check_trailing) noexcept { + auto max_len = peek_root_length(); + auto json = peek_root_scalar("double"); + // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, + // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest + // number: -0.e-308. + uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. + tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); + return NUMBER_ERROR; + } + auto result = numberparsing::parse_double(tmpbuf); + if(result.error() == SUCCESS) { + if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } + advance_root_scalar("double"); + } + return result; +} + +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_double_in_string(bool check_trailing) noexcept { + auto max_len = peek_root_length(); + auto json = peek_root_scalar("double"); + // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, + // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest + // number: -0.e-308. + uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. + tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); + return NUMBER_ERROR; + } + auto result = numberparsing::parse_double_in_string(tmpbuf); + if(result.error() == SUCCESS) { + if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } + advance_root_scalar("double"); + } + return result; +} +simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_bool(bool check_trailing) noexcept { + auto max_len = peek_root_length(); + auto json = peek_root_scalar("bool"); + uint8_t tmpbuf[5+1+1]; // +1 for null termination + tmpbuf[5+1] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 5+1)) { return incorrect_type_error("Not a boolean"); } + auto result = parse_bool(tmpbuf); + if(result.error() == SUCCESS) { + if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } + advance_root_scalar("bool"); + } + return result; +} +simdjson_inline simdjson_result value_iterator::is_root_null(bool check_trailing) noexcept { + auto max_len = peek_root_length(); + auto json = peek_root_scalar("null"); + bool result = (max_len >= 4 && !atomparsing::str4ncmp(json, "null") && + (max_len == 4 || jsoncharutils::is_structural_or_whitespace(json[4]))); + if(result) { // we have something that looks like a null. + if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } + advance_root_scalar("null"); + } else if (json[0] == 'n') { + return incorrect_type_error("Not a null but starts with n"); + } + return result; +} + +simdjson_warn_unused simdjson_inline error_code value_iterator::skip_child() noexcept { + SIMDJSON_ASSUME( _json_iter->token._position > _start_position ); + SIMDJSON_ASSUME( _json_iter->_depth >= _depth ); + + return _json_iter->skip_child(depth()); +} + +simdjson_inline value_iterator value_iterator::child() const noexcept { + assert_at_child(); + return { _json_iter, depth()+1, _json_iter->token.position() }; +} + +// GCC 7 warns when the first line of this function is inlined away into oblivion due to the caller +// relating depth and iterator depth, which is a desired effect. It does not happen if is_open is +// marked non-inline. +SIMDJSON_PUSH_DISABLE_WARNINGS +SIMDJSON_DISABLE_STRICT_OVERFLOW_WARNING +simdjson_inline bool value_iterator::is_open() const noexcept { + return _json_iter->depth() >= depth(); +} +SIMDJSON_POP_DISABLE_WARNINGS + +simdjson_inline bool value_iterator::at_end() const noexcept { + return _json_iter->at_end(); +} + +simdjson_inline bool value_iterator::at_start() const noexcept { + return _json_iter->token.position() == start_position(); +} + +simdjson_inline bool value_iterator::at_first_field() const noexcept { + SIMDJSON_ASSUME( _json_iter->token._position > _start_position ); + return _json_iter->token.position() == start_position() + 1; +} + +simdjson_inline void value_iterator::abandon() noexcept { + _json_iter->abandon(); +} + +simdjson_warn_unused simdjson_inline depth_t value_iterator::depth() const noexcept { + return _depth; +} +simdjson_warn_unused simdjson_inline error_code value_iterator::error() const noexcept { + return _json_iter->error; +} +simdjson_warn_unused simdjson_inline uint8_t *&value_iterator::string_buf_loc() noexcept { + return _json_iter->string_buf_loc(); +} +simdjson_warn_unused simdjson_inline const json_iterator &value_iterator::json_iter() const noexcept { + return *_json_iter; +} +simdjson_warn_unused simdjson_inline json_iterator &value_iterator::json_iter() noexcept { + return *_json_iter; +} + +simdjson_inline const uint8_t *value_iterator::peek_start() const noexcept { + return _json_iter->peek(start_position()); +} +simdjson_inline uint32_t value_iterator::peek_start_length() const noexcept { + return _json_iter->peek_length(start_position()); +} +simdjson_inline uint32_t value_iterator::peek_root_length() const noexcept { + return _json_iter->peek_root_length(start_position()); +} + +simdjson_inline const uint8_t *value_iterator::peek_scalar(const char *type) noexcept { + logger::log_value(*_json_iter, start_position(), depth(), type); + // If we're not at the position anymore, we don't want to advance the cursor. + if (!is_at_start()) { return peek_start(); } + + // Get the JSON and advance the cursor, decreasing depth to signify that we have retrieved the value. + assert_at_start(); + return _json_iter->peek(); +} + +simdjson_inline void value_iterator::advance_scalar(const char *type) noexcept { + logger::log_value(*_json_iter, start_position(), depth(), type); + // If we're not at the position anymore, we don't want to advance the cursor. + if (!is_at_start()) { return; } + + // Get the JSON and advance the cursor, decreasing depth to signify that we have retrieved the value. + assert_at_start(); + _json_iter->return_current_and_advance(); + _json_iter->ascend_to(depth()-1); +} + +simdjson_inline error_code value_iterator::start_container(uint8_t start_char, const char *incorrect_type_message, const char *type) noexcept { + logger::log_start_value(*_json_iter, start_position(), depth(), type); + // If we're not at the position anymore, we don't want to advance the cursor. + const uint8_t *json; + if (!is_at_start()) { +#if SIMDJSON_DEVELOPMENT_CHECKS + if (!is_at_iterator_start()) { return OUT_OF_ORDER_ITERATION; } +#endif + json = peek_start(); + if (*json != start_char) { return incorrect_type_error(incorrect_type_message); } + } else { + assert_at_start(); + /** + * We should be prudent. Let us peek. If it is not the right type, we + * return an error. Only once we have determined that we have the right + * type are we allowed to advance! + */ + json = _json_iter->peek(); + if (*json != start_char) { return incorrect_type_error(incorrect_type_message); } + _json_iter->return_current_and_advance(); + } + + + return SUCCESS; +} + + +simdjson_inline const uint8_t *value_iterator::peek_root_scalar(const char *type) noexcept { + logger::log_value(*_json_iter, start_position(), depth(), type); + if (!is_at_start()) { return peek_start(); } + + assert_at_root(); + return _json_iter->peek(); +} +simdjson_inline const uint8_t *value_iterator::peek_non_root_scalar(const char *type) noexcept { + logger::log_value(*_json_iter, start_position(), depth(), type); + if (!is_at_start()) { return peek_start(); } + + assert_at_non_root_start(); + return _json_iter->peek(); +} + +simdjson_inline void value_iterator::advance_root_scalar(const char *type) noexcept { + logger::log_value(*_json_iter, start_position(), depth(), type); + if (!is_at_start()) { return; } + + assert_at_root(); + _json_iter->return_current_and_advance(); + _json_iter->ascend_to(depth()-1); +} +simdjson_inline void value_iterator::advance_non_root_scalar(const char *type) noexcept { + logger::log_value(*_json_iter, start_position(), depth(), type); + if (!is_at_start()) { return; } + + assert_at_non_root_start(); + _json_iter->return_current_and_advance(); + _json_iter->ascend_to(depth()-1); +} + +simdjson_inline error_code value_iterator::incorrect_type_error(const char *message) const noexcept { + logger::log_error(*_json_iter, start_position(), depth(), message); + return INCORRECT_TYPE; +} + +simdjson_inline bool value_iterator::is_at_start() const noexcept { + return position() == start_position(); +} + +simdjson_inline bool value_iterator::is_at_key() const noexcept { + // Keys are at the same depth as the object. + // Note here that we could be safer and check that we are within an object, + // but we do not. + return _depth == _json_iter->_depth && *_json_iter->peek() == '"'; +} + +simdjson_inline bool value_iterator::is_at_iterator_start() const noexcept { + // We can legitimately be either at the first value ([1]), or after the array if it's empty ([]). + auto delta = position() - start_position(); + return delta == 1 || delta == 2; +} + +inline void value_iterator::assert_at_start() const noexcept { + SIMDJSON_ASSUME( _json_iter->token._position == _start_position ); + SIMDJSON_ASSUME( _json_iter->_depth == _depth ); + SIMDJSON_ASSUME( _depth > 0 ); +} + +inline void value_iterator::assert_at_container_start() const noexcept { + SIMDJSON_ASSUME( _json_iter->token._position == _start_position + 1 ); + SIMDJSON_ASSUME( _json_iter->_depth == _depth ); + SIMDJSON_ASSUME( _depth > 0 ); +} + +inline void value_iterator::assert_at_next() const noexcept { + SIMDJSON_ASSUME( _json_iter->token._position > _start_position ); + SIMDJSON_ASSUME( _json_iter->_depth == _depth ); + SIMDJSON_ASSUME( _depth > 0 ); +} + +simdjson_inline void value_iterator::move_at_start() noexcept { + _json_iter->_depth = _depth; + _json_iter->token.set_position(_start_position); +} + +simdjson_inline void value_iterator::move_at_container_start() noexcept { + _json_iter->_depth = _depth; + _json_iter->token.set_position(_start_position + 1); +} + +simdjson_inline simdjson_result value_iterator::reset_array() noexcept { + if(error()) { return error(); } + move_at_container_start(); + return started_array(); +} + +simdjson_inline simdjson_result value_iterator::reset_object() noexcept { + if(error()) { return error(); } + move_at_container_start(); + return started_object(); +} + +inline void value_iterator::assert_at_child() const noexcept { + SIMDJSON_ASSUME( _json_iter->token._position > _start_position ); + SIMDJSON_ASSUME( _json_iter->_depth == _depth + 1 ); + SIMDJSON_ASSUME( _depth > 0 ); +} + +inline void value_iterator::assert_at_root() const noexcept { + assert_at_start(); + SIMDJSON_ASSUME( _depth == 1 ); +} + +inline void value_iterator::assert_at_non_root_start() const noexcept { + assert_at_start(); + SIMDJSON_ASSUME( _depth > 1 ); +} + +inline void value_iterator::assert_is_valid() const noexcept { + SIMDJSON_ASSUME( _json_iter != nullptr ); +} + +simdjson_inline bool value_iterator::is_valid() const noexcept { + return _json_iter != nullptr; +} + +simdjson_inline simdjson_result value_iterator::type() const noexcept { + switch (*peek_start()) { + case '{': + return json_type::object; + case '[': + return json_type::array; + case '"': + return json_type::string; + case 'n': + return json_type::null; + case 't': case 'f': + return json_type::boolean; + case '-': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + return json_type::number; + default: + return TAPE_ERROR; + } +} + +simdjson_inline token_position value_iterator::start_position() const noexcept { + return _start_position; +} + +simdjson_inline token_position value_iterator::position() const noexcept { + return _json_iter->position(); +} + +simdjson_inline token_position value_iterator::end_position() const noexcept { + return _json_iter->end_position(); +} + +simdjson_inline token_position value_iterator::last_position() const noexcept { + return _json_iter->last_position(); +} + +simdjson_inline error_code value_iterator::report_error(error_code error, const char *message) noexcept { + return _json_iter->report_error(error, message); +} + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +simdjson_inline simdjson_result::simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::value_iterator &&value) noexcept + : implementation_simdjson_result_base(std::forward(value)) {} +simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept + : implementation_simdjson_result_base(error) {} + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_VALUE_ITERATOR_INL_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/generic/ondemand/value_iterator.h b/contrib/libs/simdjson/include/simdjson/generic/ondemand/value_iterator.h new file mode 100644 index 000000000000..a01a8fb09e19 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/generic/ondemand/value_iterator.h @@ -0,0 +1,492 @@ +#ifndef SIMDJSON_GENERIC_ONDEMAND_VALUE_ITERATOR_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_GENERIC_ONDEMAND_VALUE_ITERATOR_H +#include "simdjson/generic/ondemand/base.h" +#include "simdjson/generic/implementation_simdjson_result_base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace ondemand { + +/** + * Iterates through a single JSON value at a particular depth. + * + * Does not keep track of the type of value: provides methods for objects, arrays and scalars and expects + * the caller to call the right ones. + * + * @private This is not intended for external use. + */ +class value_iterator { +protected: + /** The underlying JSON iterator */ + json_iterator *_json_iter{}; + /** The depth of this value */ + depth_t _depth{}; + /** + * The starting token index for this value + */ + token_position _start_position{}; + +public: + simdjson_inline value_iterator() noexcept = default; + + /** + * Denote that we're starting a document. + */ + simdjson_inline void start_document() noexcept; + + /** + * Skips a non-iterated or partially-iterated JSON value, whether it is a scalar, array or object. + * + * Optimized for scalars. + */ + simdjson_warn_unused simdjson_inline error_code skip_child() noexcept; + + /** + * Tell whether the iterator is at the EOF mark + */ + simdjson_inline bool at_end() const noexcept; + + /** + * Tell whether the iterator is at the start of the value + */ + simdjson_inline bool at_start() const noexcept; + + /** + * Tell whether the value is open--if the value has not been used, or the array/object is still open. + */ + simdjson_inline bool is_open() const noexcept; + + /** + * Tell whether the value is at an object's first field (just after the {). + */ + simdjson_inline bool at_first_field() const noexcept; + + /** + * Abandon all iteration. + */ + simdjson_inline void abandon() noexcept; + + /** + * Get the child value as a value_iterator. + */ + simdjson_inline value_iterator child_value() const noexcept; + + /** + * Get the depth of this value. + */ + simdjson_inline int32_t depth() const noexcept; + + /** + * Get the JSON type of this value. + * + * @error TAPE_ERROR when the JSON value is a bad token like "}" "," or "alse". + */ + simdjson_inline simdjson_result type() const noexcept; + + /** + * @addtogroup object Object iteration + * + * Methods to iterate and find object fields. These methods generally *assume* the value is + * actually an object; the caller is responsible for keeping track of that fact. + * + * @{ + */ + + /** + * Start an object iteration. + * + * @returns Whether the object had any fields (returns false for empty). + * @error INCORRECT_TYPE if there is no opening { + */ + simdjson_warn_unused simdjson_inline simdjson_result start_object() noexcept; + /** + * Start an object iteration from the root. + * + * @returns Whether the object had any fields (returns false for empty). + * @error INCORRECT_TYPE if there is no opening { + * @error TAPE_ERROR if there is no matching } at end of document + */ + simdjson_warn_unused simdjson_inline simdjson_result start_root_object() noexcept; + /** + * Checks whether an object could be started from the root. May be called by start_root_object. + * + * @returns SUCCESS if it is possible to safely start an object from the root (document level). + * @error INCORRECT_TYPE if there is no opening { + * @error TAPE_ERROR if there is no matching } at end of document + */ + simdjson_warn_unused simdjson_inline error_code check_root_object() noexcept; + /** + * Start an object iteration after the user has already checked and moved past the {. + * + * Does not move the iterator unless the object is empty ({}). + * + * @returns Whether the object had any fields (returns false for empty). + * @error INCOMPLETE_ARRAY_OR_OBJECT If there are no more tokens (implying the *parent* + * array or object is incomplete). + */ + simdjson_warn_unused simdjson_inline simdjson_result started_object() noexcept; + /** + * Start an object iteration from the root, after the user has already checked and moved past the {. + * + * Does not move the iterator unless the object is empty ({}). + * + * @returns Whether the object had any fields (returns false for empty). + * @error INCOMPLETE_ARRAY_OR_OBJECT If there are no more tokens (implying the *parent* + * array or object is incomplete). + */ + simdjson_warn_unused simdjson_inline simdjson_result started_root_object() noexcept; + + /** + * Moves to the next field in an object. + * + * Looks for , and }. If } is found, the object is finished and the iterator advances past it. + * Otherwise, it advances to the next value. + * + * @return whether there is another field in the object. + * @error TAPE_ERROR If there is a comma missing between fields. + * @error TAPE_ERROR If there is a comma, but not enough tokens remaining to have a key, :, and value. + */ + simdjson_warn_unused simdjson_inline simdjson_result has_next_field() noexcept; + + /** + * Get the current field's key. + */ + simdjson_warn_unused simdjson_inline simdjson_result field_key() noexcept; + + /** + * Pass the : in the field and move to its value. + */ + simdjson_warn_unused simdjson_inline error_code field_value() noexcept; + + /** + * Find the next field with the given key. + * + * Assumes you have called next_field() or otherwise matched the previous value. + * + * This means the iterator must be sitting at the next key: + * + * ``` + * { "a": 1, "b": 2 } + * ^ + * ``` + * + * Key is *raw JSON,* meaning it will be matched against the verbatim JSON without attempting to + * unescape it. This works well for typical ASCII and UTF-8 keys (almost all of them), but may + * fail to match some keys with escapes (\u, \n, etc.). + */ + simdjson_warn_unused simdjson_inline error_code find_field(const std::string_view key) noexcept; + + /** + * Find the next field with the given key, *without* unescaping. This assumes object order: it + * will not find the field if it was already passed when looking for some *other* field. + * + * Assumes you have called next_field() or otherwise matched the previous value. + * + * This means the iterator must be sitting at the next key: + * + * ``` + * { "a": 1, "b": 2 } + * ^ + * ``` + * + * Key is *raw JSON,* meaning it will be matched against the verbatim JSON without attempting to + * unescape it. This works well for typical ASCII and UTF-8 keys (almost all of them), but may + * fail to match some keys with escapes (\u, \n, etc.). + */ + simdjson_warn_unused simdjson_inline simdjson_result find_field_raw(const std::string_view key) noexcept; + + /** + * Find the field with the given key without regard to order, and *without* unescaping. + * + * This is an unordered object lookup: if the field is not found initially, it will cycle around and scan from the beginning. + * + * Assumes you have called next_field() or otherwise matched the previous value. + * + * This means the iterator must be sitting at the next key: + * + * ``` + * { "a": 1, "b": 2 } + * ^ + * ``` + * + * Key is *raw JSON,* meaning it will be matched against the verbatim JSON without attempting to + * unescape it. This works well for typical ASCII and UTF-8 keys (almost all of them), but may + * fail to match some keys with escapes (\u, \n, etc.). + */ + simdjson_warn_unused simdjson_inline simdjson_result find_field_unordered_raw(const std::string_view key) noexcept; + + /** @} */ + + /** + * @addtogroup array Array iteration + * Methods to iterate over array elements. These methods generally *assume* the value is actually + * an object; the caller is responsible for keeping track of that fact. + * @{ + */ + + /** + * Check for an opening [ and start an array iteration. + * + * @returns Whether the array had any elements (returns false for empty). + * @error INCORRECT_TYPE If there is no [. + */ + simdjson_warn_unused simdjson_inline simdjson_result start_array() noexcept; + /** + * Check for an opening [ and start an array iteration while at the root. + * + * @returns Whether the array had any elements (returns false for empty). + * @error INCORRECT_TYPE If there is no [. + * @error TAPE_ERROR if there is no matching ] at end of document + */ + simdjson_warn_unused simdjson_inline simdjson_result start_root_array() noexcept; + /** + * Checks whether an array could be started from the root. May be called by start_root_array. + * + * @returns SUCCESS if it is possible to safely start an array from the root (document level). + * @error INCORRECT_TYPE If there is no [. + * @error TAPE_ERROR if there is no matching ] at end of document + */ + simdjson_warn_unused simdjson_inline error_code check_root_array() noexcept; + /** + * Start an array iteration, after the user has already checked and moved past the [. + * + * Does not move the iterator unless the array is empty ([]). + * + * @returns Whether the array had any elements (returns false for empty). + * @error INCOMPLETE_ARRAY_OR_OBJECT If there are no more tokens (implying the *parent* + * array or object is incomplete). + */ + simdjson_warn_unused simdjson_inline simdjson_result started_array() noexcept; + /** + * Start an array iteration from the root, after the user has already checked and moved past the [. + * + * Does not move the iterator unless the array is empty ([]). + * + * @returns Whether the array had any elements (returns false for empty). + * @error INCOMPLETE_ARRAY_OR_OBJECT If there are no more tokens (implying the *parent* + * array or object is incomplete). + */ + simdjson_warn_unused simdjson_inline simdjson_result started_root_array() noexcept; + + /** + * Moves to the next element in an array. + * + * Looks for , and ]. If ] is found, the array is finished and the iterator advances past it. + * Otherwise, it advances to the next value. + * + * @return Whether there is another element in the array. + * @error TAPE_ERROR If there is a comma missing between elements. + */ + simdjson_warn_unused simdjson_inline simdjson_result has_next_element() noexcept; + + /** + * Get a child value iterator. + */ + simdjson_warn_unused simdjson_inline value_iterator child() const noexcept; + + /** @} */ + + /** + * @defgroup scalar Scalar values + * @addtogroup scalar + * @{ + */ + + simdjson_warn_unused simdjson_inline simdjson_result get_string(bool allow_replacement) noexcept; + template + simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement) noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_wobbly_string() noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_raw_json_string() noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_uint64() noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_uint64_in_string() noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_int64() noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_int64_in_string() noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_double() noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_double_in_string() noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_bool() noexcept; + simdjson_warn_unused simdjson_inline simdjson_result is_null() noexcept; + simdjson_warn_unused simdjson_inline bool is_negative() noexcept; + simdjson_warn_unused simdjson_inline simdjson_result is_integer() noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_number_type() noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_number() noexcept; + + simdjson_warn_unused simdjson_inline simdjson_result get_root_string(bool check_trailing, bool allow_replacement) noexcept; + template + simdjson_warn_unused simdjson_inline error_code get_root_string(string_type& receiver, bool check_trailing, bool allow_replacement) noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_root_wobbly_string(bool check_trailing) noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_root_raw_json_string(bool check_trailing) noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_root_uint64(bool check_trailing) noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_root_uint64_in_string(bool check_trailing) noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_root_int64(bool check_trailing) noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_root_int64_in_string(bool check_trailing) noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_root_double(bool check_trailing) noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_root_double_in_string(bool check_trailing) noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_root_bool(bool check_trailing) noexcept; + simdjson_warn_unused simdjson_inline bool is_root_negative() noexcept; + simdjson_warn_unused simdjson_inline simdjson_result is_root_integer(bool check_trailing) noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_root_number_type(bool check_trailing) noexcept; + simdjson_warn_unused simdjson_inline simdjson_result get_root_number(bool check_trailing) noexcept; + simdjson_warn_unused simdjson_inline simdjson_result is_root_null(bool check_trailing) noexcept; + + simdjson_inline error_code error() const noexcept; + simdjson_inline uint8_t *&string_buf_loc() noexcept; + simdjson_inline const json_iterator &json_iter() const noexcept; + simdjson_inline json_iterator &json_iter() noexcept; + + simdjson_inline void assert_is_valid() const noexcept; + simdjson_inline bool is_valid() const noexcept; + + /** @} */ +protected: + /** + * Restarts an array iteration. + * @returns Whether the array has any elements (returns false for empty). + */ + simdjson_inline simdjson_result reset_array() noexcept; + /** + * Restarts an object iteration. + * @returns Whether the object has any fields (returns false for empty). + */ + simdjson_inline simdjson_result reset_object() noexcept; + /** + * move_at_start(): moves us so that we are pointing at the beginning of + * the container. It updates the index so that at_start() is true and it + * syncs the depth. The user can then create a new container instance. + * + * Usage: used with value::count_elements(). + **/ + simdjson_inline void move_at_start() noexcept; + + /** + * move_at_container_start(): moves us so that we are pointing at the beginning of + * the container so that assert_at_container_start() passes. + * + * Usage: used with reset_array() and reset_object(). + **/ + simdjson_inline void move_at_container_start() noexcept; + /* Useful for debugging and logging purposes. */ + inline std::string to_string() const noexcept; + simdjson_inline value_iterator(json_iterator *json_iter, depth_t depth, token_position start_index) noexcept; + + simdjson_inline simdjson_result parse_null(const uint8_t *json) const noexcept; + simdjson_inline simdjson_result parse_bool(const uint8_t *json) const noexcept; + simdjson_inline const uint8_t *peek_start() const noexcept; + simdjson_inline uint32_t peek_start_length() const noexcept; + simdjson_inline uint32_t peek_root_length() const noexcept; + + /** + * The general idea of the advance_... methods and the peek_* methods + * is that you first peek and check that you have desired type. If you do, + * and only if you do, then you advance. + * + * We used to unconditionally advance. But this made reasoning about our + * current state difficult. + * Suppose you always advance. Look at the 'value' matching the key + * "shadowable" in the following example... + * + * ({"globals":{"a":{"shadowable":[}}}}) + * + * If the user thinks it is a Boolean and asks for it, then we check the '[', + * decide it is not a Boolean, but still move into the next character ('}'). Now + * we are left pointing at '}' right after a '['. And we have not yet reported + * an error, only that we do not have a Boolean. + * + * If, instead, you just stand your ground until it is content that you know, then + * you will only even move beyond the '[' if the user tells you that you have an + * array. So you will be at the '}' character inside the array and, hopefully, you + * will then catch the error because an array cannot start with '}', but the code + * processing Boolean values does not know this. + * + * So the contract is: first call 'peek_...' and then call 'advance_...' only + * if you have determined that it is a type you can handle. + * + * Unfortunately, it makes the code more verbose, longer and maybe more error prone. + */ + + simdjson_inline void advance_scalar(const char *type) noexcept; + simdjson_inline void advance_root_scalar(const char *type) noexcept; + simdjson_inline void advance_non_root_scalar(const char *type) noexcept; + + simdjson_inline const uint8_t *peek_scalar(const char *type) noexcept; + simdjson_inline const uint8_t *peek_root_scalar(const char *type) noexcept; + simdjson_inline const uint8_t *peek_non_root_scalar(const char *type) noexcept; + + + simdjson_inline error_code start_container(uint8_t start_char, const char *incorrect_type_message, const char *type) noexcept; + simdjson_inline error_code end_container() noexcept; + + /** + * Advance to a place expecting a value (increasing depth). + * + * @return The current token (the one left behind). + * @error TAPE_ERROR If the document ended early. + */ + simdjson_inline simdjson_result advance_to_value() noexcept; + + simdjson_inline error_code incorrect_type_error(const char *message) const noexcept; + simdjson_inline error_code error_unless_more_tokens(uint32_t tokens=1) const noexcept; + + simdjson_inline bool is_at_start() const noexcept; + /** + * is_at_iterator_start() returns true on an array or object after it has just been + * created, whether the instance is empty or not. + * + * Usage: used by array::begin() in debug mode (SIMDJSON_DEVELOPMENT_CHECKS) + */ + simdjson_inline bool is_at_iterator_start() const noexcept; + + /** + * Assuming that we are within an object, this returns true if we + * are pointing at a key. + * + * Usage: the skip_child() method should never be used while we are pointing + * at a key inside an object. + */ + simdjson_inline bool is_at_key() const noexcept; + + inline void assert_at_start() const noexcept; + inline void assert_at_container_start() const noexcept; + inline void assert_at_root() const noexcept; + inline void assert_at_child() const noexcept; + inline void assert_at_next() const noexcept; + inline void assert_at_non_root_start() const noexcept; + + /** Get the starting position of this value */ + simdjson_inline token_position start_position() const noexcept; + + /** @copydoc error_code json_iterator::position() const noexcept; */ + simdjson_inline token_position position() const noexcept; + /** @copydoc error_code json_iterator::end_position() const noexcept; */ + simdjson_inline token_position last_position() const noexcept; + /** @copydoc error_code json_iterator::end_position() const noexcept; */ + simdjson_inline token_position end_position() const noexcept; + /** @copydoc error_code json_iterator::report_error(error_code error, const char *message) noexcept; */ + simdjson_inline error_code report_error(error_code error, const char *message) noexcept; + + friend class document; + friend class object; + friend class array; + friend class value; + friend class field; +}; // value_iterator + +} // namespace ondemand +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +namespace simdjson { + +template<> +struct simdjson_result : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::value_iterator &&value) noexcept; ///< @private + simdjson_inline simdjson_result(error_code error) noexcept; ///< @private + simdjson_inline simdjson_result() noexcept = default; +}; + +} // namespace simdjson + +#endif // SIMDJSON_GENERIC_ONDEMAND_VALUE_ITERATOR_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/haswell.h b/contrib/libs/simdjson/include/simdjson/haswell.h new file mode 100644 index 000000000000..867b7a449115 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/haswell.h @@ -0,0 +1,8 @@ +#ifndef SIMDJSON_HASWELL_H +#define SIMDJSON_HASWELL_H + +#include "simdjson/haswell/begin.h" +#include "simdjson/generic/amalgamated.h" +#include "simdjson/haswell/end.h" + +#endif // SIMDJSON_HASWELL_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/haswell/base.h b/contrib/libs/simdjson/include/simdjson/haswell/base.h new file mode 100644 index 000000000000..73cc3ef1b4a5 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/haswell/base.h @@ -0,0 +1,27 @@ +#ifndef SIMDJSON_HASWELL_BASE_H +#define SIMDJSON_HASWELL_BASE_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +// The constructor may be executed on any host, so we take care not to use SIMDJSON_TARGET_HASWELL +namespace simdjson { +/** + * Implementation for Haswell (Intel AVX2). + */ +namespace haswell { + +class implementation; + +namespace { +namespace simd { +template struct simd8; +template struct simd8x64; +} // namespace simd +} // unnamed namespace + +} // namespace haswell +} // namespace simdjson + +#endif // SIMDJSON_HASWELL_BASE_H diff --git a/contrib/libs/simdjson/include/simdjson/haswell/begin.h b/contrib/libs/simdjson/include/simdjson/haswell/begin.h new file mode 100644 index 000000000000..2a245cdc2057 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/haswell/begin.h @@ -0,0 +1,20 @@ +#define SIMDJSON_IMPLEMENTATION haswell + +#include "simdjson/haswell/base.h" +#include "simdjson/haswell/intrinsics.h" + +#if !SIMDJSON_CAN_ALWAYS_RUN_HASWELL +// We enable bmi2 only if LLVM/clang is used, because GCC may not +// make good use of it. See https://github.com/simdjson/simdjson/pull/2243 +#if defined(__clang__) +SIMDJSON_TARGET_REGION("avx2,bmi,bmi2,pclmul,lzcnt,popcnt") +#else +SIMDJSON_TARGET_REGION("avx2,bmi,pclmul,lzcnt,popcnt") +#endif +#endif + +#include "simdjson/haswell/bitmanipulation.h" +#include "simdjson/haswell/bitmask.h" +#include "simdjson/haswell/numberparsing_defs.h" +#include "simdjson/haswell/simd.h" +#include "simdjson/haswell/stringparsing_defs.h" diff --git a/contrib/libs/simdjson/include/simdjson/haswell/bitmanipulation.h b/contrib/libs/simdjson/include/simdjson/haswell/bitmanipulation.h new file mode 100644 index 000000000000..9b0e59905c84 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/haswell/bitmanipulation.h @@ -0,0 +1,71 @@ +#ifndef SIMDJSON_HASWELL_BITMANIPULATION_H +#define SIMDJSON_HASWELL_BITMANIPULATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/haswell/base.h" +#include "simdjson/haswell/intrinsics.h" +#include "simdjson/haswell/bitmask.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace haswell { +namespace { + +// We sometimes call trailing_zero on inputs that are zero, +// but the algorithms do not end up using the returned value. +// Sadly, sanitizers are not smart enough to figure it out. +SIMDJSON_NO_SANITIZE_UNDEFINED +// This function can be used safely even if not all bytes have been +// initialized. +// See issue https://github.com/simdjson/simdjson/issues/1965 +SIMDJSON_NO_SANITIZE_MEMORY +simdjson_inline int trailing_zeroes(uint64_t input_num) { +#if SIMDJSON_REGULAR_VISUAL_STUDIO + return (int)_tzcnt_u64(input_num); +#else // SIMDJSON_REGULAR_VISUAL_STUDIO + //////// + // You might expect the next line to be equivalent to + // return (int)_tzcnt_u64(input_num); + // but the generated code differs and might be less efficient? + //////// + return __builtin_ctzll(input_num); +#endif // SIMDJSON_REGULAR_VISUAL_STUDIO +} + +/* result might be undefined when input_num is zero */ +simdjson_inline uint64_t clear_lowest_bit(uint64_t input_num) { + return _blsr_u64(input_num); +} + +/* result might be undefined when input_num is zero */ +simdjson_inline int leading_zeroes(uint64_t input_num) { + return int(_lzcnt_u64(input_num)); +} + +#if SIMDJSON_REGULAR_VISUAL_STUDIO +simdjson_inline unsigned __int64 count_ones(uint64_t input_num) { + // note: we do not support legacy 32-bit Windows in this kernel + return __popcnt64(input_num);// Visual Studio wants two underscores +} +#else +simdjson_inline long long int count_ones(uint64_t input_num) { + return _popcnt64(input_num); +} +#endif + +simdjson_inline bool add_overflow(uint64_t value1, uint64_t value2, + uint64_t *result) { +#if SIMDJSON_REGULAR_VISUAL_STUDIO + return _addcarry_u64(0, value1, value2, + reinterpret_cast(result)); +#else + return __builtin_uaddll_overflow(value1, value2, + reinterpret_cast(result)); +#endif +} + +} // unnamed namespace +} // namespace haswell +} // namespace simdjson + +#endif // SIMDJSON_HASWELL_BITMANIPULATION_H diff --git a/contrib/libs/simdjson/include/simdjson/haswell/bitmask.h b/contrib/libs/simdjson/include/simdjson/haswell/bitmask.h new file mode 100644 index 000000000000..310c3280bec7 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/haswell/bitmask.h @@ -0,0 +1,30 @@ +#ifndef SIMDJSON_HASWELL_BITMASK_H +#define SIMDJSON_HASWELL_BITMASK_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/haswell/base.h" +#include "simdjson/haswell/intrinsics.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace haswell { +namespace { + +// +// Perform a "cumulative bitwise xor," flipping bits each time a 1 is encountered. +// +// For example, prefix_xor(00100100) == 00011100 +// +simdjson_inline uint64_t prefix_xor(const uint64_t bitmask) { + // There should be no such thing with a processor supporting avx2 + // but not clmul. + __m128i all_ones = _mm_set1_epi8('\xFF'); + __m128i result = _mm_clmulepi64_si128(_mm_set_epi64x(0ULL, bitmask), all_ones, 0); + return _mm_cvtsi128_si64(result); +} + +} // unnamed namespace +} // namespace haswell +} // namespace simdjson + +#endif // SIMDJSON_HASWELL_BITMASK_H diff --git a/contrib/libs/simdjson/include/simdjson/haswell/end.h b/contrib/libs/simdjson/include/simdjson/haswell/end.h new file mode 100644 index 000000000000..421df3653c94 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/haswell/end.h @@ -0,0 +1,9 @@ +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/haswell/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#if !SIMDJSON_CAN_ALWAYS_RUN_HASWELL +SIMDJSON_UNTARGET_REGION +#endif + +#undef SIMDJSON_IMPLEMENTATION diff --git a/contrib/libs/simdjson/include/simdjson/haswell/implementation.h b/contrib/libs/simdjson/include/simdjson/haswell/implementation.h new file mode 100644 index 000000000000..6861e42983c1 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/haswell/implementation.h @@ -0,0 +1,36 @@ +#ifndef SIMDJSON_HASWELL_IMPLEMENTATION_H +#define SIMDJSON_HASWELL_IMPLEMENTATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/haswell/base.h" +#include "simdjson/implementation.h" +#include "simdjson/internal/instruction_set.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +// The constructor may be executed on any host, so we take care not to use SIMDJSON_TARGET_HASWELL +namespace simdjson { +namespace haswell { + +/** + * @private + */ +class implementation final : public simdjson::implementation { +public: + simdjson_inline implementation() : simdjson::implementation( + "haswell", + "Intel/AMD AVX2", + internal::instruction_set::AVX2 | internal::instruction_set::PCLMULQDQ | internal::instruction_set::BMI1 | internal::instruction_set::BMI2 + ) {} + simdjson_warn_unused error_code create_dom_parser_implementation( + size_t capacity, + size_t max_length, + std::unique_ptr& dst + ) const noexcept final; + simdjson_warn_unused error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept final; + simdjson_warn_unused bool validate_utf8(const char *buf, size_t len) const noexcept final; +}; + +} // namespace haswell +} // namespace simdjson + +#endif // SIMDJSON_HASWELL_IMPLEMENTATION_H diff --git a/contrib/libs/simdjson/include/simdjson/haswell/intrinsics.h b/contrib/libs/simdjson/include/simdjson/haswell/intrinsics.h new file mode 100644 index 000000000000..a4593dbb6987 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/haswell/intrinsics.h @@ -0,0 +1,52 @@ +#ifndef SIMDJSON_HASWELL_INTRINSICS_H +#define SIMDJSON_HASWELL_INTRINSICS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/haswell/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#if SIMDJSON_VISUAL_STUDIO +// under clang within visual studio, this will include +#include // visual studio or clang +#else +#include // elsewhere +#endif // SIMDJSON_VISUAL_STUDIO + +#if SIMDJSON_CLANG_VISUAL_STUDIO +/** + * You are not supposed, normally, to include these + * headers directly. Instead you should either include intrin.h + * or x86intrin.h. However, when compiling with clang + * under Windows (i.e., when _MSC_VER is set), these headers + * only get included *if* the corresponding features are detected + * from macros: + * e.g., if __AVX2__ is set... in turn, we normally set these + * macros by compiling against the corresponding architecture + * (e.g., arch:AVX2, -mavx2, etc.) which compiles the whole + * software with these advanced instructions. In simdjson, we + * want to compile the whole program for a generic target, + * and only target our specific kernels. As a workaround, + * we directly include the needed headers. These headers would + * normally guard against such usage, but we carefully included + * (or ) before, so the headers + * are fooled. + */ +#include // for _blsr_u64 +#include // for __lzcnt64 +#include // for most things (AVX2, AVX512, _popcnt64) +#include +#include +#include +#include +#include // for _mm_clmulepi64_si128 +// unfortunately, we may not get _blsr_u64, but, thankfully, clang +// has it as a macro. +#ifndef _blsr_u64 +// we roll our own +#define _blsr_u64(n) ((n - 1) & n) +#endif // _blsr_u64 +#endif // SIMDJSON_CLANG_VISUAL_STUDIO + +static_assert(sizeof(__m256i) <= simdjson::SIMDJSON_PADDING, "insufficient padding for haswell kernel."); + +#endif // SIMDJSON_HASWELL_INTRINSICS_H diff --git a/contrib/libs/simdjson/include/simdjson/haswell/numberparsing_defs.h b/contrib/libs/simdjson/include/simdjson/haswell/numberparsing_defs.h new file mode 100644 index 000000000000..5673e5e4f814 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/haswell/numberparsing_defs.h @@ -0,0 +1,61 @@ +#ifndef SIMDJSON_HASWELL_NUMBERPARSING_DEFS_H +#define SIMDJSON_HASWELL_NUMBERPARSING_DEFS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/haswell/base.h" +#include "simdjson/haswell/intrinsics.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/internal/numberparsing_tables.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace haswell { +namespace numberparsing { + +/** @private */ +static simdjson_inline uint32_t parse_eight_digits_unrolled(const uint8_t *chars) { + // this actually computes *16* values so we are being wasteful. + const __m128i ascii0 = _mm_set1_epi8('0'); + const __m128i mul_1_10 = + _mm_setr_epi8(10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1); + const __m128i mul_1_100 = _mm_setr_epi16(100, 1, 100, 1, 100, 1, 100, 1); + const __m128i mul_1_10000 = + _mm_setr_epi16(10000, 1, 10000, 1, 10000, 1, 10000, 1); + const __m128i input = _mm_sub_epi8( + _mm_loadu_si128(reinterpret_cast(chars)), ascii0); + const __m128i t1 = _mm_maddubs_epi16(input, mul_1_10); + const __m128i t2 = _mm_madd_epi16(t1, mul_1_100); + const __m128i t3 = _mm_packus_epi32(t2, t2); + const __m128i t4 = _mm_madd_epi16(t3, mul_1_10000); + return _mm_cvtsi128_si32( + t4); // only captures the sum of the first 8 digits, drop the rest +} + +/** @private */ +simdjson_inline internal::value128 full_multiplication(uint64_t value1, uint64_t value2) { + internal::value128 answer; +#if SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS +#if SIMDJSON_IS_ARM64 + // ARM64 has native support for 64-bit multiplications, no need to emultate + answer.high = __umulh(value1, value2); + answer.low = value1 * value2; +#else + answer.low = _umul128(value1, value2, &answer.high); // _umul128 not available on ARM64 +#endif // SIMDJSON_IS_ARM64 +#else // SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS + __uint128_t r = (static_cast<__uint128_t>(value1)) * value2; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); +#endif + return answer; +} + +} // namespace numberparsing +} // namespace haswell +} // namespace simdjson + +#define SIMDJSON_SWAR_NUMBER_PARSING 1 + +#endif // SIMDJSON_HASWELL_NUMBERPARSING_DEFS_H diff --git a/contrib/libs/simdjson/include/simdjson/haswell/ondemand.h b/contrib/libs/simdjson/include/simdjson/haswell/ondemand.h new file mode 100644 index 000000000000..b3aa993efff0 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/haswell/ondemand.h @@ -0,0 +1,8 @@ +#ifndef SIMDJSON_HASWELL_ONDEMAND_H +#define SIMDJSON_HASWELL_ONDEMAND_H + +#include "simdjson/haswell/begin.h" +#include "simdjson/generic/ondemand/amalgamated.h" +#include "simdjson/haswell/end.h" + +#endif // SIMDJSON_HASWELL_ONDEMAND_H diff --git a/contrib/libs/simdjson/include/simdjson/haswell/simd.h b/contrib/libs/simdjson/include/simdjson/haswell/simd.h new file mode 100644 index 000000000000..9c40f4f4fdd7 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/haswell/simd.h @@ -0,0 +1,372 @@ +#ifndef SIMDJSON_HASWELL_SIMD_H +#define SIMDJSON_HASWELL_SIMD_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/haswell/base.h" +#include "simdjson/haswell/intrinsics.h" +#include "simdjson/haswell/bitmanipulation.h" +#include "simdjson/internal/simdprune_tables.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace haswell { +namespace { +namespace simd { + + // Forward-declared so they can be used by splat and friends. + template + struct base { + __m256i value; + + // Zero constructor + simdjson_inline base() : value{__m256i()} {} + + // Conversion from SIMD register + simdjson_inline base(const __m256i _value) : value(_value) {} + + // Conversion to SIMD register + simdjson_inline operator const __m256i&() const { return this->value; } + simdjson_inline operator __m256i&() { return this->value; } + + // Bit operations + simdjson_inline Child operator|(const Child other) const { return _mm256_or_si256(*this, other); } + simdjson_inline Child operator&(const Child other) const { return _mm256_and_si256(*this, other); } + simdjson_inline Child operator^(const Child other) const { return _mm256_xor_si256(*this, other); } + simdjson_inline Child bit_andnot(const Child other) const { return _mm256_andnot_si256(other, *this); } + simdjson_inline Child& operator|=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast | other; return *this_cast; } + simdjson_inline Child& operator&=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast & other; return *this_cast; } + simdjson_inline Child& operator^=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast ^ other; return *this_cast; } + }; + + // Forward-declared so they can be used by splat and friends. + template + struct simd8; + + template> + struct base8: base> { + typedef uint32_t bitmask_t; + typedef uint64_t bitmask2_t; + + simdjson_inline base8() : base>() {} + simdjson_inline base8(const __m256i _value) : base>(_value) {} + + friend simdjson_really_inline Mask operator==(const simd8 lhs, const simd8 rhs) { return _mm256_cmpeq_epi8(lhs, rhs); } + + static const int SIZE = sizeof(base::value); + + template + simdjson_inline simd8 prev(const simd8 prev_chunk) const { + return _mm256_alignr_epi8(*this, _mm256_permute2x128_si256(prev_chunk, *this, 0x21), 16 - N); + } + }; + + // SIMD byte mask type (returned by things like eq and gt) + template<> + struct simd8: base8 { + static simdjson_inline simd8 splat(bool _value) { return _mm256_set1_epi8(uint8_t(-(!!_value))); } + + simdjson_inline simd8() : base8() {} + simdjson_inline simd8(const __m256i _value) : base8(_value) {} + // Splat constructor + simdjson_inline simd8(bool _value) : base8(splat(_value)) {} + + simdjson_inline int to_bitmask() const { return _mm256_movemask_epi8(*this); } + simdjson_inline bool any() const { return !_mm256_testz_si256(*this, *this); } + simdjson_inline simd8 operator~() const { return *this ^ true; } + }; + + template + struct base8_numeric: base8 { + static simdjson_inline simd8 splat(T _value) { return _mm256_set1_epi8(_value); } + static simdjson_inline simd8 zero() { return _mm256_setzero_si256(); } + static simdjson_inline simd8 load(const T values[32]) { + return _mm256_loadu_si256(reinterpret_cast(values)); + } + // Repeat 16 values as many times as necessary (usually for lookup tables) + static simdjson_inline simd8 repeat_16( + T v0, T v1, T v2, T v3, T v4, T v5, T v6, T v7, + T v8, T v9, T v10, T v11, T v12, T v13, T v14, T v15 + ) { + return simd8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + ); + } + + simdjson_inline base8_numeric() : base8() {} + simdjson_inline base8_numeric(const __m256i _value) : base8(_value) {} + + // Store to array + simdjson_inline void store(T dst[32]) const { return _mm256_storeu_si256(reinterpret_cast<__m256i *>(dst), *this); } + + // Addition/subtraction are the same for signed and unsigned + simdjson_inline simd8 operator+(const simd8 other) const { return _mm256_add_epi8(*this, other); } + simdjson_inline simd8 operator-(const simd8 other) const { return _mm256_sub_epi8(*this, other); } + simdjson_inline simd8& operator+=(const simd8 other) { *this = *this + other; return *static_cast*>(this); } + simdjson_inline simd8& operator-=(const simd8 other) { *this = *this - other; return *static_cast*>(this); } + + // Override to distinguish from bool version + simdjson_inline simd8 operator~() const { return *this ^ 0xFFu; } + + // Perform a lookup assuming the value is between 0 and 16 (undefined behavior for out of range values) + template + simdjson_inline simd8 lookup_16(simd8 lookup_table) const { + return _mm256_shuffle_epi8(lookup_table, *this); + } + + // Copies to 'output" all bytes corresponding to a 0 in the mask (interpreted as a bitset). + // Passing a 0 value for mask would be equivalent to writing out every byte to output. + // Only the first 32 - count_ones(mask) bytes of the result are significant but 32 bytes + // get written. + // Design consideration: it seems like a function with the + // signature simd8 compress(uint32_t mask) would be + // sensible, but the AVX ISA makes this kind of approach difficult. + template + simdjson_inline void compress(uint32_t mask, L * output) const { + using internal::thintable_epi8; + using internal::BitsSetTable256mul2; + using internal::pshufb_combine_table; + // this particular implementation was inspired by work done by @animetosho + // we do it in four steps, first 8 bytes and then second 8 bytes... + uint8_t mask1 = uint8_t(mask); // least significant 8 bits + uint8_t mask2 = uint8_t(mask >> 8); // second least significant 8 bits + uint8_t mask3 = uint8_t(mask >> 16); // ... + uint8_t mask4 = uint8_t(mask >> 24); // ... + // next line just loads the 64-bit values thintable_epi8[mask1] and + // thintable_epi8[mask2] into a 128-bit register, using only + // two instructions on most compilers. + __m256i shufmask = _mm256_set_epi64x(thintable_epi8[mask4], thintable_epi8[mask3], + thintable_epi8[mask2], thintable_epi8[mask1]); + // we increment by 0x08 the second half of the mask and so forth + shufmask = + _mm256_add_epi8(shufmask, _mm256_set_epi32(0x18181818, 0x18181818, + 0x10101010, 0x10101010, 0x08080808, 0x08080808, 0, 0)); + // this is the version "nearly pruned" + __m256i pruned = _mm256_shuffle_epi8(*this, shufmask); + // we still need to put the pieces back together. + // we compute the popcount of the first words: + int pop1 = BitsSetTable256mul2[mask1]; + int pop3 = BitsSetTable256mul2[mask3]; + + // then load the corresponding mask + // could be done with _mm256_loadu2_m128i but many standard libraries omit this intrinsic. + __m256i v256 = _mm256_castsi128_si256( + _mm_loadu_si128(reinterpret_cast(pshufb_combine_table + pop1 * 8))); + __m256i compactmask = _mm256_insertf128_si256(v256, + _mm_loadu_si128(reinterpret_cast(pshufb_combine_table + pop3 * 8)), 1); + __m256i almostthere = _mm256_shuffle_epi8(pruned, compactmask); + // We just need to write out the result. + // This is the tricky bit that is hard to do + // if we want to return a SIMD register, since there + // is no single-instruction approach to recombine + // the two 128-bit lanes with an offset. + __m128i v128; + v128 = _mm256_castsi256_si128(almostthere); + _mm_storeu_si128( reinterpret_cast<__m128i *>(output), v128); + v128 = _mm256_extractf128_si256(almostthere, 1); + _mm_storeu_si128( reinterpret_cast<__m128i *>(output + 16 - count_ones(mask & 0xFFFF)), v128); + } + + template + simdjson_inline simd8 lookup_16( + L replace0, L replace1, L replace2, L replace3, + L replace4, L replace5, L replace6, L replace7, + L replace8, L replace9, L replace10, L replace11, + L replace12, L replace13, L replace14, L replace15) const { + return lookup_16(simd8::repeat_16( + replace0, replace1, replace2, replace3, + replace4, replace5, replace6, replace7, + replace8, replace9, replace10, replace11, + replace12, replace13, replace14, replace15 + )); + } + }; + + // Signed bytes + template<> + struct simd8 : base8_numeric { + simdjson_inline simd8() : base8_numeric() {} + simdjson_inline simd8(const __m256i _value) : base8_numeric(_value) {} + // Splat constructor + simdjson_inline simd8(int8_t _value) : simd8(splat(_value)) {} + // Array constructor + simdjson_inline simd8(const int8_t values[32]) : simd8(load(values)) {} + // Member-by-member initialization + simdjson_inline simd8( + int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, + int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15, + int8_t v16, int8_t v17, int8_t v18, int8_t v19, int8_t v20, int8_t v21, int8_t v22, int8_t v23, + int8_t v24, int8_t v25, int8_t v26, int8_t v27, int8_t v28, int8_t v29, int8_t v30, int8_t v31 + ) : simd8(_mm256_setr_epi8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v16,v17,v18,v19,v20,v21,v22,v23, + v24,v25,v26,v27,v28,v29,v30,v31 + )) {} + // Repeat 16 values as many times as necessary (usually for lookup tables) + simdjson_inline static simd8 repeat_16( + int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, + int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15 + ) { + return simd8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + ); + } + + // Order-sensitive comparisons + simdjson_inline simd8 max_val(const simd8 other) const { return _mm256_max_epi8(*this, other); } + simdjson_inline simd8 min_val(const simd8 other) const { return _mm256_min_epi8(*this, other); } + simdjson_inline simd8 operator>(const simd8 other) const { return _mm256_cmpgt_epi8(*this, other); } + simdjson_inline simd8 operator<(const simd8 other) const { return _mm256_cmpgt_epi8(other, *this); } + }; + + // Unsigned bytes + template<> + struct simd8: base8_numeric { + simdjson_inline simd8() : base8_numeric() {} + simdjson_inline simd8(const __m256i _value) : base8_numeric(_value) {} + // Splat constructor + simdjson_inline simd8(uint8_t _value) : simd8(splat(_value)) {} + // Array constructor + simdjson_inline simd8(const uint8_t values[32]) : simd8(load(values)) {} + // Member-by-member initialization + simdjson_inline simd8( + uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, + uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15, + uint8_t v16, uint8_t v17, uint8_t v18, uint8_t v19, uint8_t v20, uint8_t v21, uint8_t v22, uint8_t v23, + uint8_t v24, uint8_t v25, uint8_t v26, uint8_t v27, uint8_t v28, uint8_t v29, uint8_t v30, uint8_t v31 + ) : simd8(_mm256_setr_epi8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v16,v17,v18,v19,v20,v21,v22,v23, + v24,v25,v26,v27,v28,v29,v30,v31 + )) {} + // Repeat 16 values as many times as necessary (usually for lookup tables) + simdjson_inline static simd8 repeat_16( + uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, + uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15 + ) { + return simd8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + ); + } + + // Saturated math + simdjson_inline simd8 saturating_add(const simd8 other) const { return _mm256_adds_epu8(*this, other); } + simdjson_inline simd8 saturating_sub(const simd8 other) const { return _mm256_subs_epu8(*this, other); } + + // Order-specific operations + simdjson_inline simd8 max_val(const simd8 other) const { return _mm256_max_epu8(*this, other); } + simdjson_inline simd8 min_val(const simd8 other) const { return _mm256_min_epu8(other, *this); } + // Same as >, but only guarantees true is nonzero (< guarantees true = -1) + simdjson_inline simd8 gt_bits(const simd8 other) const { return this->saturating_sub(other); } + // Same as <, but only guarantees true is nonzero (< guarantees true = -1) + simdjson_inline simd8 lt_bits(const simd8 other) const { return other.saturating_sub(*this); } + simdjson_inline simd8 operator<=(const simd8 other) const { return other.max_val(*this) == other; } + simdjson_inline simd8 operator>=(const simd8 other) const { return other.min_val(*this) == other; } + simdjson_inline simd8 operator>(const simd8 other) const { return this->gt_bits(other).any_bits_set(); } + simdjson_inline simd8 operator<(const simd8 other) const { return this->lt_bits(other).any_bits_set(); } + + // Bit-specific operations + simdjson_inline simd8 bits_not_set() const { return *this == uint8_t(0); } + simdjson_inline simd8 bits_not_set(simd8 bits) const { return (*this & bits).bits_not_set(); } + simdjson_inline simd8 any_bits_set() const { return ~this->bits_not_set(); } + simdjson_inline simd8 any_bits_set(simd8 bits) const { return ~this->bits_not_set(bits); } + simdjson_inline bool is_ascii() const { return _mm256_movemask_epi8(*this) == 0; } + simdjson_inline bool bits_not_set_anywhere() const { return _mm256_testz_si256(*this, *this); } + simdjson_inline bool any_bits_set_anywhere() const { return !bits_not_set_anywhere(); } + simdjson_inline bool bits_not_set_anywhere(simd8 bits) const { return _mm256_testz_si256(*this, bits); } + simdjson_inline bool any_bits_set_anywhere(simd8 bits) const { return !bits_not_set_anywhere(bits); } + template + simdjson_inline simd8 shr() const { return simd8(_mm256_srli_epi16(*this, N)) & uint8_t(0xFFu >> N); } + template + simdjson_inline simd8 shl() const { return simd8(_mm256_slli_epi16(*this, N)) & uint8_t(0xFFu << N); } + // Get one of the bits and make a bitmask out of it. + // e.g. value.get_bit<7>() gets the high bit + template + simdjson_inline int get_bit() const { return _mm256_movemask_epi8(_mm256_slli_epi16(*this, 7-N)); } + }; + + template + struct simd8x64 { + static constexpr int NUM_CHUNKS = 64 / sizeof(simd8); + static_assert(NUM_CHUNKS == 2, "Haswell kernel should use two registers per 64-byte block."); + const simd8 chunks[NUM_CHUNKS]; + + simd8x64(const simd8x64& o) = delete; // no copy allowed + simd8x64& operator=(const simd8& other) = delete; // no assignment allowed + simd8x64() = delete; // no default constructor allowed + + simdjson_inline simd8x64(const simd8 chunk0, const simd8 chunk1) : chunks{chunk0, chunk1} {} + simdjson_inline simd8x64(const T ptr[64]) : chunks{simd8::load(ptr), simd8::load(ptr+32)} {} + + simdjson_inline uint64_t compress(uint64_t mask, T * output) const { + uint32_t mask1 = uint32_t(mask); + uint32_t mask2 = uint32_t(mask >> 32); + this->chunks[0].compress(mask1, output); + this->chunks[1].compress(mask2, output + 32 - count_ones(mask1)); + return 64 - count_ones(mask); + } + + simdjson_inline void store(T ptr[64]) const { + this->chunks[0].store(ptr+sizeof(simd8)*0); + this->chunks[1].store(ptr+sizeof(simd8)*1); + } + + simdjson_inline uint64_t to_bitmask() const { + uint64_t r_lo = uint32_t(this->chunks[0].to_bitmask()); + uint64_t r_hi = this->chunks[1].to_bitmask(); + return r_lo | (r_hi << 32); + } + + simdjson_inline simd8 reduce_or() const { + return this->chunks[0] | this->chunks[1]; + } + + simdjson_inline simd8x64 bit_or(const T m) const { + const simd8 mask = simd8::splat(m); + return simd8x64( + this->chunks[0] | mask, + this->chunks[1] | mask + ); + } + + simdjson_inline uint64_t eq(const T m) const { + const simd8 mask = simd8::splat(m); + return simd8x64( + this->chunks[0] == mask, + this->chunks[1] == mask + ).to_bitmask(); + } + + simdjson_inline uint64_t eq(const simd8x64 &other) const { + return simd8x64( + this->chunks[0] == other.chunks[0], + this->chunks[1] == other.chunks[1] + ).to_bitmask(); + } + + simdjson_inline uint64_t lteq(const T m) const { + const simd8 mask = simd8::splat(m); + return simd8x64( + this->chunks[0] <= mask, + this->chunks[1] <= mask + ).to_bitmask(); + } + }; // struct simd8x64 + +} // namespace simd + +} // unnamed namespace +} // namespace haswell +} // namespace simdjson + +#endif // SIMDJSON_HASWELL_SIMD_H diff --git a/contrib/libs/simdjson/include/simdjson/haswell/stringparsing_defs.h b/contrib/libs/simdjson/include/simdjson/haswell/stringparsing_defs.h new file mode 100644 index 000000000000..f896a10e2c10 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/haswell/stringparsing_defs.h @@ -0,0 +1,48 @@ +#ifndef SIMDJSON_HASWELL_STRINGPARSING_DEFS_H +#define SIMDJSON_HASWELL_STRINGPARSING_DEFS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/haswell/base.h" +#include "simdjson/haswell/simd.h" +#include "simdjson/haswell/bitmanipulation.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace haswell { +namespace { + +using namespace simd; + +// Holds backslashes and quotes locations. +struct backslash_and_quote { +public: + static constexpr uint32_t BYTES_PROCESSED = 32; + simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); + + simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; } + simdjson_inline bool has_backslash() { return ((quote_bits - 1) & bs_bits) != 0; } + simdjson_inline int quote_index() { return trailing_zeroes(quote_bits); } + simdjson_inline int backslash_index() { return trailing_zeroes(bs_bits); } + + uint32_t bs_bits; + uint32_t quote_bits; +}; // struct backslash_and_quote + +simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { + // this can read up to 15 bytes beyond the buffer size, but we require + // SIMDJSON_PADDING of padding + static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "backslash and quote finder must process fewer than SIMDJSON_PADDING bytes"); + simd8 v(src); + // store to dest unconditionally - we can overwrite the bits we don't like later + v.store(dst); + return { + static_cast((v == '\\').to_bitmask()), // bs_bits + static_cast((v == '"').to_bitmask()), // quote_bits + }; +} + +} // unnamed namespace +} // namespace haswell +} // namespace simdjson + +#endif // SIMDJSON_HASWELL_STRINGPARSING_DEFS_H diff --git a/contrib/libs/simdjson/include/simdjson/icelake.h b/contrib/libs/simdjson/include/simdjson/icelake.h new file mode 100644 index 000000000000..964296034064 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/icelake.h @@ -0,0 +1,8 @@ +#ifndef SIMDJSON_ICELAKE_H +#define SIMDJSON_ICELAKE_H + +#include "simdjson/icelake/begin.h" +#include "simdjson/generic/amalgamated.h" +#include "simdjson/icelake/end.h" + +#endif // SIMDJSON_ICELAKE_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/icelake/base.h b/contrib/libs/simdjson/include/simdjson/icelake/base.h new file mode 100644 index 000000000000..f44c0d32a1f1 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/icelake/base.h @@ -0,0 +1,20 @@ +#ifndef SIMDJSON_ICELAKE_BASE_H +#define SIMDJSON_ICELAKE_BASE_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +// The constructor may be executed on any host, so we take care not to use SIMDJSON_TARGET_ICELAKE +namespace simdjson { +/** + * Implementation for Icelake (Intel AVX512). + */ +namespace icelake { + +class implementation; + +} // namespace icelake +} // namespace simdjson + +#endif // SIMDJSON_ICELAKE_BASE_H diff --git a/contrib/libs/simdjson/include/simdjson/icelake/begin.h b/contrib/libs/simdjson/include/simdjson/icelake/begin.h new file mode 100644 index 000000000000..60fe2cd6bf94 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/icelake/begin.h @@ -0,0 +1,13 @@ +#define SIMDJSON_IMPLEMENTATION icelake +#include "simdjson/icelake/base.h" +#include "simdjson/icelake/intrinsics.h" + +#if !SIMDJSON_CAN_ALWAYS_RUN_ICELAKE +SIMDJSON_TARGET_REGION("avx512f,avx512dq,avx512cd,avx512bw,avx512vbmi,avx512vbmi2,avx512vl,avx2,bmi,pclmul,lzcnt,popcnt") +#endif + +#include "simdjson/icelake/bitmanipulation.h" +#include "simdjson/icelake/bitmask.h" +#include "simdjson/icelake/simd.h" +#include "simdjson/icelake/stringparsing_defs.h" +#include "simdjson/icelake/numberparsing_defs.h" diff --git a/contrib/libs/simdjson/include/simdjson/icelake/bitmanipulation.h b/contrib/libs/simdjson/include/simdjson/icelake/bitmanipulation.h new file mode 100644 index 000000000000..5bcf7116015f --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/icelake/bitmanipulation.h @@ -0,0 +1,70 @@ +#ifndef SIMDJSON_ICELAKE_BITMANIPULATION_H +#define SIMDJSON_ICELAKE_BITMANIPULATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/icelake/base.h" +#include "simdjson/icelake/intrinsics.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace icelake { +namespace { + +// We sometimes call trailing_zero on inputs that are zero, +// but the algorithms do not end up using the returned value. +// Sadly, sanitizers are not smart enough to figure it out. +SIMDJSON_NO_SANITIZE_UNDEFINED +// This function can be used safely even if not all bytes have been +// initialized. +// See issue https://github.com/simdjson/simdjson/issues/1965 +SIMDJSON_NO_SANITIZE_MEMORY +simdjson_inline int trailing_zeroes(uint64_t input_num) { +#if SIMDJSON_REGULAR_VISUAL_STUDIO + return (int)_tzcnt_u64(input_num); +#else // SIMDJSON_REGULAR_VISUAL_STUDIO + //////// + // You might expect the next line to be equivalent to + // return (int)_tzcnt_u64(input_num); + // but the generated code differs and might be less efficient? + //////// + return __builtin_ctzll(input_num); +#endif // SIMDJSON_REGULAR_VISUAL_STUDIO +} + +/* result might be undefined when input_num is zero */ +simdjson_inline uint64_t clear_lowest_bit(uint64_t input_num) { + return _blsr_u64(input_num); +} + +/* result might be undefined when input_num is zero */ +simdjson_inline int leading_zeroes(uint64_t input_num) { + return int(_lzcnt_u64(input_num)); +} + +#if SIMDJSON_REGULAR_VISUAL_STUDIO +simdjson_inline unsigned __int64 count_ones(uint64_t input_num) { + // note: we do not support legacy 32-bit Windows + return __popcnt64(input_num);// Visual Studio wants two underscores +} +#else +simdjson_inline long long int count_ones(uint64_t input_num) { + return _popcnt64(input_num); +} +#endif + +simdjson_inline bool add_overflow(uint64_t value1, uint64_t value2, + uint64_t *result) { +#if SIMDJSON_REGULAR_VISUAL_STUDIO + return _addcarry_u64(0, value1, value2, + reinterpret_cast(result)); +#else + return __builtin_uaddll_overflow(value1, value2, + reinterpret_cast(result)); +#endif +} + +} // unnamed namespace +} // namespace icelake +} // namespace simdjson + +#endif // SIMDJSON_ICELAKE_BITMANIPULATION_H diff --git a/contrib/libs/simdjson/include/simdjson/icelake/bitmask.h b/contrib/libs/simdjson/include/simdjson/icelake/bitmask.h new file mode 100644 index 000000000000..ed55962fa490 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/icelake/bitmask.h @@ -0,0 +1,30 @@ +#ifndef SIMDJSON_ICELAKE_BITMASK_H +#define SIMDJSON_ICELAKE_BITMASK_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/icelake/base.h" +#include "simdjson/icelake/intrinsics.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace icelake { +namespace { + +// +// Perform a "cumulative bitwise xor," flipping bits each time a 1 is encountered. +// +// For example, prefix_xor(00100100) == 00011100 +// +simdjson_inline uint64_t prefix_xor(const uint64_t bitmask) { + // There should be no such thing with a processor supporting avx2 + // but not clmul. + __m128i all_ones = _mm_set1_epi8('\xFF'); + __m128i result = _mm_clmulepi64_si128(_mm_set_epi64x(0ULL, bitmask), all_ones, 0); + return _mm_cvtsi128_si64(result); +} + +} // unnamed namespace +} // namespace icelake +} // namespace simdjson + +#endif // SIMDJSON_ICELAKE_BITMASK_H diff --git a/contrib/libs/simdjson/include/simdjson/icelake/end.h b/contrib/libs/simdjson/include/simdjson/icelake/end.h new file mode 100644 index 000000000000..2accd5fb0d50 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/icelake/end.h @@ -0,0 +1,9 @@ +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/icelake/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#if !SIMDJSON_CAN_ALWAYS_RUN_ICELAKE +SIMDJSON_UNTARGET_REGION +#endif + +#undef SIMDJSON_IMPLEMENTATION diff --git a/contrib/libs/simdjson/include/simdjson/icelake/implementation.h b/contrib/libs/simdjson/include/simdjson/icelake/implementation.h new file mode 100644 index 000000000000..940c5f9923cc --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/icelake/implementation.h @@ -0,0 +1,36 @@ +#ifndef SIMDJSON_ICELAKE_IMPLEMENTATION_H +#define SIMDJSON_ICELAKE_IMPLEMENTATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/icelake/base.h" +#include "simdjson/implementation.h" +#include "simdjson/internal/instruction_set.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +// The constructor may be executed on any host, so we take care not to use SIMDJSON_TARGET_ICELAKE +namespace simdjson { +namespace icelake { + +/** + * @private + */ +class implementation final : public simdjson::implementation { +public: + simdjson_inline implementation() : simdjson::implementation( + "icelake", + "Intel/AMD AVX512", + internal::instruction_set::AVX2 | internal::instruction_set::PCLMULQDQ | internal::instruction_set::BMI1 | internal::instruction_set::BMI2 | internal::instruction_set::AVX512F | internal::instruction_set::AVX512DQ | internal::instruction_set::AVX512CD | internal::instruction_set::AVX512BW | internal::instruction_set::AVX512VL | internal::instruction_set::AVX512VBMI2 + ) {} + simdjson_warn_unused error_code create_dom_parser_implementation( + size_t capacity, + size_t max_length, + std::unique_ptr& dst + ) const noexcept final; + simdjson_warn_unused error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept final; + simdjson_warn_unused bool validate_utf8(const char *buf, size_t len) const noexcept final; +}; + +} // namespace icelake +} // namespace simdjson + +#endif // SIMDJSON_ICELAKE_IMPLEMENTATION_H diff --git a/contrib/libs/simdjson/include/simdjson/icelake/intrinsics.h b/contrib/libs/simdjson/include/simdjson/icelake/intrinsics.h new file mode 100644 index 000000000000..4c68c290c1e6 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/icelake/intrinsics.h @@ -0,0 +1,60 @@ +#ifndef SIMDJSON_ICELAKE_INTRINSICS_H +#define SIMDJSON_ICELAKE_INTRINSICS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/icelake/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#if SIMDJSON_VISUAL_STUDIO +// under clang within visual studio, this will include +#include // visual studio or clang +#else +#include // elsewhere +#endif // SIMDJSON_VISUAL_STUDIO + +#if SIMDJSON_CLANG_VISUAL_STUDIO +/** + * You are not supposed, normally, to include these + * headers directly. Instead you should either include intrin.h + * or x86intrin.h. However, when compiling with clang + * under Windows (i.e., when _MSC_VER is set), these headers + * only get included *if* the corresponding features are detected + * from macros: + * e.g., if __AVX2__ is set... in turn, we normally set these + * macros by compiling against the corresponding architecture + * (e.g., arch:AVX2, -mavx2, etc.) which compiles the whole + * software with these advanced instructions. In simdjson, we + * want to compile the whole program for a generic target, + * and only target our specific kernels. As a workaround, + * we directly include the needed headers. These headers would + * normally guard against such usage, but we carefully included + * (or ) before, so the headers + * are fooled. + */ +#include // for _blsr_u64 +#include // for __lzcnt64 +#include // for most things (AVX2, AVX512, _popcnt64) +#include +#include +#include +#include +#include // for _mm_clmulepi64_si128 +// Important: we need the AVX-512 headers: +#include +#include +#include +#include +#include +#include +#include +// unfortunately, we may not get _blsr_u64, but, thankfully, clang +// has it as a macro. +#ifndef _blsr_u64 +// we roll our own +#define _blsr_u64(n) ((n - 1) & n) +#endif // _blsr_u64 +#endif // SIMDJSON_CLANG_VISUAL_STUDIO + +static_assert(sizeof(__m512i) <= simdjson::SIMDJSON_PADDING, "insufficient padding for icelake"); + +#endif // SIMDJSON_ICELAKE_INTRINSICS_H diff --git a/contrib/libs/simdjson/include/simdjson/icelake/numberparsing_defs.h b/contrib/libs/simdjson/include/simdjson/icelake/numberparsing_defs.h new file mode 100644 index 000000000000..b095cf6c66c1 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/icelake/numberparsing_defs.h @@ -0,0 +1,57 @@ +#ifndef SIMDJSON_ICELAKE_NUMBERPARSING_DEFS_H +#define SIMDJSON_ICELAKE_NUMBERPARSING_DEFS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/icelake/base.h" +#include "simdjson/icelake/intrinsics.h" +#include "simdjson/internal/numberparsing_tables.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace icelake { +namespace numberparsing { + +static simdjson_inline uint32_t parse_eight_digits_unrolled(const uint8_t *chars) { + // this actually computes *16* values so we are being wasteful. + const __m128i ascii0 = _mm_set1_epi8('0'); + const __m128i mul_1_10 = + _mm_setr_epi8(10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1); + const __m128i mul_1_100 = _mm_setr_epi16(100, 1, 100, 1, 100, 1, 100, 1); + const __m128i mul_1_10000 = + _mm_setr_epi16(10000, 1, 10000, 1, 10000, 1, 10000, 1); + const __m128i input = _mm_sub_epi8( + _mm_loadu_si128(reinterpret_cast(chars)), ascii0); + const __m128i t1 = _mm_maddubs_epi16(input, mul_1_10); + const __m128i t2 = _mm_madd_epi16(t1, mul_1_100); + const __m128i t3 = _mm_packus_epi32(t2, t2); + const __m128i t4 = _mm_madd_epi16(t3, mul_1_10000); + return _mm_cvtsi128_si32( + t4); // only captures the sum of the first 8 digits, drop the rest +} + +/** @private */ +simdjson_inline internal::value128 full_multiplication(uint64_t value1, uint64_t value2) { + internal::value128 answer; +#if SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS +#if SIMDJSON_IS_ARM64 + // ARM64 has native support for 64-bit multiplications, no need to emultate + answer.high = __umulh(value1, value2); + answer.low = value1 * value2; +#else + answer.low = _umul128(value1, value2, &answer.high); // _umul128 not available on ARM64 +#endif // SIMDJSON_IS_ARM64 +#else // SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS + __uint128_t r = (static_cast<__uint128_t>(value1)) * value2; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); +#endif + return answer; +} + +} // namespace numberparsing +} // namespace icelake +} // namespace simdjson + +#define SIMDJSON_SWAR_NUMBER_PARSING 1 + +#endif // SIMDJSON_ICELAKE_NUMBERPARSING_DEFS_H diff --git a/contrib/libs/simdjson/include/simdjson/icelake/ondemand.h b/contrib/libs/simdjson/include/simdjson/icelake/ondemand.h new file mode 100644 index 000000000000..e2f13b4787a3 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/icelake/ondemand.h @@ -0,0 +1,8 @@ +#ifndef SIMDJSON_ICELAKE_ONDEMAND_H +#define SIMDJSON_ICELAKE_ONDEMAND_H + +#include "simdjson/icelake/begin.h" +#include "simdjson/generic/ondemand/amalgamated.h" +#include "simdjson/icelake/end.h" + +#endif // SIMDJSON_ICELAKE_ONDEMAND_H diff --git a/contrib/libs/simdjson/include/simdjson/icelake/simd.h b/contrib/libs/simdjson/include/simdjson/icelake/simd.h new file mode 100644 index 000000000000..04203f4b9a57 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/icelake/simd.h @@ -0,0 +1,372 @@ +#ifndef SIMDJSON_ICELAKE_SIMD_H +#define SIMDJSON_ICELAKE_SIMD_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/icelake/base.h" +#include "simdjson/icelake/intrinsics.h" +#include "simdjson/icelake/bitmanipulation.h" +#include "simdjson/internal/simdprune_tables.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#if defined(__GNUC__) && !defined(__clang__) +#if __GNUC__ == 8 +#define SIMDJSON_GCC8 1 +#endif // __GNUC__ == 8 +#endif // defined(__GNUC__) && !defined(__clang__) + +#if SIMDJSON_GCC8 +/** + * GCC 8 fails to provide _mm512_set_epi8. We roll our own. + */ +inline __m512i _mm512_set_epi8(uint8_t a0, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7, uint8_t a8, uint8_t a9, uint8_t a10, uint8_t a11, uint8_t a12, uint8_t a13, uint8_t a14, uint8_t a15, uint8_t a16, uint8_t a17, uint8_t a18, uint8_t a19, uint8_t a20, uint8_t a21, uint8_t a22, uint8_t a23, uint8_t a24, uint8_t a25, uint8_t a26, uint8_t a27, uint8_t a28, uint8_t a29, uint8_t a30, uint8_t a31, uint8_t a32, uint8_t a33, uint8_t a34, uint8_t a35, uint8_t a36, uint8_t a37, uint8_t a38, uint8_t a39, uint8_t a40, uint8_t a41, uint8_t a42, uint8_t a43, uint8_t a44, uint8_t a45, uint8_t a46, uint8_t a47, uint8_t a48, uint8_t a49, uint8_t a50, uint8_t a51, uint8_t a52, uint8_t a53, uint8_t a54, uint8_t a55, uint8_t a56, uint8_t a57, uint8_t a58, uint8_t a59, uint8_t a60, uint8_t a61, uint8_t a62, uint8_t a63) { + return _mm512_set_epi64(uint64_t(a7) + (uint64_t(a6) << 8) + (uint64_t(a5) << 16) + (uint64_t(a4) << 24) + (uint64_t(a3) << 32) + (uint64_t(a2) << 40) + (uint64_t(a1) << 48) + (uint64_t(a0) << 56), + uint64_t(a15) + (uint64_t(a14) << 8) + (uint64_t(a13) << 16) + (uint64_t(a12) << 24) + (uint64_t(a11) << 32) + (uint64_t(a10) << 40) + (uint64_t(a9) << 48) + (uint64_t(a8) << 56), + uint64_t(a23) + (uint64_t(a22) << 8) + (uint64_t(a21) << 16) + (uint64_t(a20) << 24) + (uint64_t(a19) << 32) + (uint64_t(a18) << 40) + (uint64_t(a17) << 48) + (uint64_t(a16) << 56), + uint64_t(a31) + (uint64_t(a30) << 8) + (uint64_t(a29) << 16) + (uint64_t(a28) << 24) + (uint64_t(a27) << 32) + (uint64_t(a26) << 40) + (uint64_t(a25) << 48) + (uint64_t(a24) << 56), + uint64_t(a39) + (uint64_t(a38) << 8) + (uint64_t(a37) << 16) + (uint64_t(a36) << 24) + (uint64_t(a35) << 32) + (uint64_t(a34) << 40) + (uint64_t(a33) << 48) + (uint64_t(a32) << 56), + uint64_t(a47) + (uint64_t(a46) << 8) + (uint64_t(a45) << 16) + (uint64_t(a44) << 24) + (uint64_t(a43) << 32) + (uint64_t(a42) << 40) + (uint64_t(a41) << 48) + (uint64_t(a40) << 56), + uint64_t(a55) + (uint64_t(a54) << 8) + (uint64_t(a53) << 16) + (uint64_t(a52) << 24) + (uint64_t(a51) << 32) + (uint64_t(a50) << 40) + (uint64_t(a49) << 48) + (uint64_t(a48) << 56), + uint64_t(a63) + (uint64_t(a62) << 8) + (uint64_t(a61) << 16) + (uint64_t(a60) << 24) + (uint64_t(a59) << 32) + (uint64_t(a58) << 40) + (uint64_t(a57) << 48) + (uint64_t(a56) << 56)); +} +#endif // SIMDJSON_GCC8 + + + +namespace simdjson { +namespace icelake { +namespace { +namespace simd { + + // Forward-declared so they can be used by splat and friends. + template + struct base { + __m512i value; + + // Zero constructor + simdjson_inline base() : value{__m512i()} {} + + // Conversion from SIMD register + simdjson_inline base(const __m512i _value) : value(_value) {} + + // Conversion to SIMD register + simdjson_inline operator const __m512i&() const { return this->value; } + simdjson_inline operator __m512i&() { return this->value; } + + // Bit operations + simdjson_inline Child operator|(const Child other) const { return _mm512_or_si512(*this, other); } + simdjson_inline Child operator&(const Child other) const { return _mm512_and_si512(*this, other); } + simdjson_inline Child operator^(const Child other) const { return _mm512_xor_si512(*this, other); } + simdjson_inline Child bit_andnot(const Child other) const { return _mm512_andnot_si512(other, *this); } + simdjson_inline Child& operator|=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast | other; return *this_cast; } + simdjson_inline Child& operator&=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast & other; return *this_cast; } + simdjson_inline Child& operator^=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast ^ other; return *this_cast; } + }; + + // Forward-declared so they can be used by splat and friends. + template + struct simd8; + + template> + struct base8: base> { + typedef uint32_t bitmask_t; + typedef uint64_t bitmask2_t; + + simdjson_inline base8() : base>() {} + simdjson_inline base8(const __m512i _value) : base>(_value) {} + + friend simdjson_really_inline uint64_t operator==(const simd8 lhs, const simd8 rhs) { + return _mm512_cmpeq_epi8_mask(lhs, rhs); + } + + static const int SIZE = sizeof(base::value); + + template + simdjson_inline simd8 prev(const simd8 prev_chunk) const { + // workaround for compilers unable to figure out that 16 - N is a constant (GCC 8) + constexpr int shift = 16 - N; + return _mm512_alignr_epi8(*this, _mm512_permutex2var_epi64(prev_chunk, _mm512_set_epi64(13, 12, 11, 10, 9, 8, 7, 6), *this), shift); + } + }; + + // SIMD byte mask type (returned by things like eq and gt) + template<> + struct simd8: base8 { + static simdjson_inline simd8 splat(bool _value) { return _mm512_set1_epi8(uint8_t(-(!!_value))); } + + simdjson_inline simd8() : base8() {} + simdjson_inline simd8(const __m512i _value) : base8(_value) {} + // Splat constructor + simdjson_inline simd8(bool _value) : base8(splat(_value)) {} + simdjson_inline bool any() const { return !!_mm512_test_epi8_mask (*this, *this); } + simdjson_inline simd8 operator~() const { return *this ^ true; } + }; + + template + struct base8_numeric: base8 { + static simdjson_inline simd8 splat(T _value) { return _mm512_set1_epi8(_value); } + static simdjson_inline simd8 zero() { return _mm512_setzero_si512(); } + static simdjson_inline simd8 load(const T values[64]) { + return _mm512_loadu_si512(reinterpret_cast(values)); + } + // Repeat 16 values as many times as necessary (usually for lookup tables) + static simdjson_inline simd8 repeat_16( + T v0, T v1, T v2, T v3, T v4, T v5, T v6, T v7, + T v8, T v9, T v10, T v11, T v12, T v13, T v14, T v15 + ) { + return simd8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + ); + } + + simdjson_inline base8_numeric() : base8() {} + simdjson_inline base8_numeric(const __m512i _value) : base8(_value) {} + + // Store to array + simdjson_inline void store(T dst[64]) const { return _mm512_storeu_si512(reinterpret_cast<__m512i *>(dst), *this); } + + // Addition/subtraction are the same for signed and unsigned + simdjson_inline simd8 operator+(const simd8 other) const { return _mm512_add_epi8(*this, other); } + simdjson_inline simd8 operator-(const simd8 other) const { return _mm512_sub_epi8(*this, other); } + simdjson_inline simd8& operator+=(const simd8 other) { *this = *this + other; return *static_cast*>(this); } + simdjson_inline simd8& operator-=(const simd8 other) { *this = *this - other; return *static_cast*>(this); } + + // Override to distinguish from bool version + simdjson_inline simd8 operator~() const { return *this ^ 0xFFu; } + + // Perform a lookup assuming the value is between 0 and 16 (undefined behavior for out of range values) + template + simdjson_inline simd8 lookup_16(simd8 lookup_table) const { + return _mm512_shuffle_epi8(lookup_table, *this); + } + + // Copies to 'output" all bytes corresponding to a 0 in the mask (interpreted as a bitset). + // Passing a 0 value for mask would be equivalent to writing out every byte to output. + // Only the first 32 - count_ones(mask) bytes of the result are significant but 32 bytes + // get written. + // Design consideration: it seems like a function with the + // signature simd8 compress(uint32_t mask) would be + // sensible, but the AVX ISA makes this kind of approach difficult. + template + simdjson_inline void compress(uint64_t mask, L * output) const { + _mm512_mask_compressstoreu_epi8 (output,~mask,*this); + } + + template + simdjson_inline simd8 lookup_16( + L replace0, L replace1, L replace2, L replace3, + L replace4, L replace5, L replace6, L replace7, + L replace8, L replace9, L replace10, L replace11, + L replace12, L replace13, L replace14, L replace15) const { + return lookup_16(simd8::repeat_16( + replace0, replace1, replace2, replace3, + replace4, replace5, replace6, replace7, + replace8, replace9, replace10, replace11, + replace12, replace13, replace14, replace15 + )); + } + }; + + // Signed bytes + template<> + struct simd8 : base8_numeric { + simdjson_inline simd8() : base8_numeric() {} + simdjson_inline simd8(const __m512i _value) : base8_numeric(_value) {} + // Splat constructor + simdjson_inline simd8(int8_t _value) : simd8(splat(_value)) {} + // Array constructor + simdjson_inline simd8(const int8_t values[64]) : simd8(load(values)) {} + // Member-by-member initialization + simdjson_inline simd8( + int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, + int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15, + int8_t v16, int8_t v17, int8_t v18, int8_t v19, int8_t v20, int8_t v21, int8_t v22, int8_t v23, + int8_t v24, int8_t v25, int8_t v26, int8_t v27, int8_t v28, int8_t v29, int8_t v30, int8_t v31, + int8_t v32, int8_t v33, int8_t v34, int8_t v35, int8_t v36, int8_t v37, int8_t v38, int8_t v39, + int8_t v40, int8_t v41, int8_t v42, int8_t v43, int8_t v44, int8_t v45, int8_t v46, int8_t v47, + int8_t v48, int8_t v49, int8_t v50, int8_t v51, int8_t v52, int8_t v53, int8_t v54, int8_t v55, + int8_t v56, int8_t v57, int8_t v58, int8_t v59, int8_t v60, int8_t v61, int8_t v62, int8_t v63 + ) : simd8(_mm512_set_epi8( + v63, v62, v61, v60, v59, v58, v57, v56, + v55, v54, v53, v52, v51, v50, v49, v48, + v47, v46, v45, v44, v43, v42, v41, v40, + v39, v38, v37, v36, v35, v34, v33, v32, + v31, v30, v29, v28, v27, v26, v25, v24, + v23, v22, v21, v20, v19, v18, v17, v16, + v15, v14, v13, v12, v11, v10, v9, v8, + v7, v6, v5, v4, v3, v2, v1, v0 + )) {} + + // Repeat 16 values as many times as necessary (usually for lookup tables) + simdjson_inline static simd8 repeat_16( + int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, + int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15 + ) { + return simd8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + ); + } + + // Order-sensitive comparisons + simdjson_inline simd8 max_val(const simd8 other) const { return _mm512_max_epi8(*this, other); } + simdjson_inline simd8 min_val(const simd8 other) const { return _mm512_min_epi8(*this, other); } + + simdjson_inline simd8 operator>(const simd8 other) const { return _mm512_maskz_abs_epi8(_mm512_cmpgt_epi8_mask(*this, other),_mm512_set1_epi8(uint8_t(0x80))); } + simdjson_inline simd8 operator<(const simd8 other) const { return _mm512_maskz_abs_epi8(_mm512_cmpgt_epi8_mask(other, *this),_mm512_set1_epi8(uint8_t(0x80))); } + }; + + // Unsigned bytes + template<> + struct simd8: base8_numeric { + simdjson_inline simd8() : base8_numeric() {} + simdjson_inline simd8(const __m512i _value) : base8_numeric(_value) {} + // Splat constructor + simdjson_inline simd8(uint8_t _value) : simd8(splat(_value)) {} + // Array constructor + simdjson_inline simd8(const uint8_t values[64]) : simd8(load(values)) {} + // Member-by-member initialization + simdjson_inline simd8( + uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, + uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15, + uint8_t v16, uint8_t v17, uint8_t v18, uint8_t v19, uint8_t v20, uint8_t v21, uint8_t v22, uint8_t v23, + uint8_t v24, uint8_t v25, uint8_t v26, uint8_t v27, uint8_t v28, uint8_t v29, uint8_t v30, uint8_t v31, + uint8_t v32, uint8_t v33, uint8_t v34, uint8_t v35, uint8_t v36, uint8_t v37, uint8_t v38, uint8_t v39, + uint8_t v40, uint8_t v41, uint8_t v42, uint8_t v43, uint8_t v44, uint8_t v45, uint8_t v46, uint8_t v47, + uint8_t v48, uint8_t v49, uint8_t v50, uint8_t v51, uint8_t v52, uint8_t v53, uint8_t v54, uint8_t v55, + uint8_t v56, uint8_t v57, uint8_t v58, uint8_t v59, uint8_t v60, uint8_t v61, uint8_t v62, uint8_t v63 + ) : simd8(_mm512_set_epi8( + v63, v62, v61, v60, v59, v58, v57, v56, + v55, v54, v53, v52, v51, v50, v49, v48, + v47, v46, v45, v44, v43, v42, v41, v40, + v39, v38, v37, v36, v35, v34, v33, v32, + v31, v30, v29, v28, v27, v26, v25, v24, + v23, v22, v21, v20, v19, v18, v17, v16, + v15, v14, v13, v12, v11, v10, v9, v8, + v7, v6, v5, v4, v3, v2, v1, v0 + )) {} + + // Repeat 16 values as many times as necessary (usually for lookup tables) + simdjson_inline static simd8 repeat_16( + uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, + uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15 + ) { + return simd8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + ); + } + + // Saturated math + simdjson_inline simd8 saturating_add(const simd8 other) const { return _mm512_adds_epu8(*this, other); } + simdjson_inline simd8 saturating_sub(const simd8 other) const { return _mm512_subs_epu8(*this, other); } + + // Order-specific operations + simdjson_inline simd8 max_val(const simd8 other) const { return _mm512_max_epu8(*this, other); } + simdjson_inline simd8 min_val(const simd8 other) const { return _mm512_min_epu8(other, *this); } + // Same as >, but only guarantees true is nonzero (< guarantees true = -1) + simdjson_inline simd8 gt_bits(const simd8 other) const { return this->saturating_sub(other); } + // Same as <, but only guarantees true is nonzero (< guarantees true = -1) + simdjson_inline simd8 lt_bits(const simd8 other) const { return other.saturating_sub(*this); } + simdjson_inline uint64_t operator<=(const simd8 other) const { return other.max_val(*this) == other; } + simdjson_inline uint64_t operator>=(const simd8 other) const { return other.min_val(*this) == other; } + simdjson_inline simd8 operator>(const simd8 other) const { return this->gt_bits(other).any_bits_set(); } + simdjson_inline simd8 operator<(const simd8 other) const { return this->lt_bits(other).any_bits_set(); } + + // Bit-specific operations + simdjson_inline simd8 bits_not_set() const { return _mm512_mask_blend_epi8(*this == uint8_t(0), _mm512_set1_epi8(0), _mm512_set1_epi8(-1)); } + simdjson_inline simd8 bits_not_set(simd8 bits) const { return (*this & bits).bits_not_set(); } + simdjson_inline simd8 any_bits_set() const { return ~this->bits_not_set(); } + simdjson_inline simd8 any_bits_set(simd8 bits) const { return ~this->bits_not_set(bits); } + + simdjson_inline bool is_ascii() const { return _mm512_movepi8_mask(*this) == 0; } + simdjson_inline bool bits_not_set_anywhere() const { + return !_mm512_test_epi8_mask(*this, *this); + } + simdjson_inline bool any_bits_set_anywhere() const { return !bits_not_set_anywhere(); } + simdjson_inline bool bits_not_set_anywhere(simd8 bits) const { return !_mm512_test_epi8_mask(*this, bits); } + simdjson_inline bool any_bits_set_anywhere(simd8 bits) const { return !bits_not_set_anywhere(bits); } + template + simdjson_inline simd8 shr() const { return simd8(_mm512_srli_epi16(*this, N)) & uint8_t(0xFFu >> N); } + template + simdjson_inline simd8 shl() const { return simd8(_mm512_slli_epi16(*this, N)) & uint8_t(0xFFu << N); } + // Get one of the bits and make a bitmask out of it. + // e.g. value.get_bit<7>() gets the high bit + template + simdjson_inline uint64_t get_bit() const { return _mm512_movepi8_mask(_mm512_slli_epi16(*this, 7-N)); } + }; + + template + struct simd8x64 { + static constexpr int NUM_CHUNKS = 64 / sizeof(simd8); + static_assert(NUM_CHUNKS == 1, "Icelake kernel should use one register per 64-byte block."); + const simd8 chunks[NUM_CHUNKS]; + + simd8x64(const simd8x64& o) = delete; // no copy allowed + simd8x64& operator=(const simd8& other) = delete; // no assignment allowed + simd8x64() = delete; // no default constructor allowed + + simdjson_inline simd8x64(const simd8 chunk0, const simd8 chunk1) : chunks{chunk0, chunk1} {} + simdjson_inline simd8x64(const simd8 chunk0) : chunks{chunk0} {} + simdjson_inline simd8x64(const T ptr[64]) : chunks{simd8::load(ptr)} {} + + simdjson_inline uint64_t compress(uint64_t mask, T * output) const { + this->chunks[0].compress(mask, output); + return 64 - count_ones(mask); + } + + simdjson_inline void store(T ptr[64]) const { + this->chunks[0].store(ptr+sizeof(simd8)*0); + } + + simdjson_inline simd8 reduce_or() const { + return this->chunks[0]; + } + + simdjson_inline simd8x64 bit_or(const T m) const { + const simd8 mask = simd8::splat(m); + return simd8x64( + this->chunks[0] | mask + ); + } + + simdjson_inline uint64_t eq(const T m) const { + const simd8 mask = simd8::splat(m); + return this->chunks[0] == mask; + } + + simdjson_inline uint64_t eq(const simd8x64 &other) const { + return this->chunks[0] == other.chunks[0]; + } + + simdjson_inline uint64_t lteq(const T m) const { + const simd8 mask = simd8::splat(m); + return this->chunks[0] <= mask; + } + }; // struct simd8x64 + +} // namespace simd + +} // unnamed namespace +} // namespace icelake +} // namespace simdjson + +#endif // SIMDJSON_ICELAKE_SIMD_H diff --git a/contrib/libs/simdjson/include/simdjson/icelake/stringparsing_defs.h b/contrib/libs/simdjson/include/simdjson/icelake/stringparsing_defs.h new file mode 100644 index 000000000000..4cc582737f25 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/icelake/stringparsing_defs.h @@ -0,0 +1,48 @@ +#ifndef SIMDJSON_ICELAKE_STRINGPARSING_DEFS_H +#define SIMDJSON_ICELAKE_STRINGPARSING_DEFS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/icelake/base.h" +#include "simdjson/icelake/simd.h" +#include "simdjson/icelake/bitmanipulation.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace icelake { +namespace { + +using namespace simd; + +// Holds backslashes and quotes locations. +struct backslash_and_quote { +public: + static constexpr uint32_t BYTES_PROCESSED = 64; + simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); + + simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; } + simdjson_inline bool has_backslash() { return ((quote_bits - 1) & bs_bits) != 0; } + simdjson_inline int quote_index() { return trailing_zeroes(quote_bits); } + simdjson_inline int backslash_index() { return trailing_zeroes(bs_bits); } + + uint64_t bs_bits; + uint64_t quote_bits; +}; // struct backslash_and_quote + +simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { + // this can read up to 15 bytes beyond the buffer size, but we require + // SIMDJSON_PADDING of padding + static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "backslash and quote finder must process fewer than SIMDJSON_PADDING bytes"); + simd8 v(src); + // store to dest unconditionally - we can overwrite the bits we don't like later + v.store(dst); + return { + static_cast(v == '\\'), // bs_bits + static_cast(v == '"'), // quote_bits + }; +} + +} // unnamed namespace +} // namespace icelake +} // namespace simdjson + +#endif // SIMDJSON_ICELAKE_STRINGPARSING_DEFS_H diff --git a/contrib/libs/simdjson/include/simdjson/implementation.h b/contrib/libs/simdjson/include/simdjson/implementation.h new file mode 100644 index 000000000000..19cb37162977 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/implementation.h @@ -0,0 +1,230 @@ +#ifndef SIMDJSON_IMPLEMENTATION_H +#define SIMDJSON_IMPLEMENTATION_H + +#include "simdjson/internal/atomic_ptr.h" +#include "simdjson/internal/dom_parser_implementation.h" + +#include + +namespace simdjson { + +/** + * Validate the UTF-8 string. + * + * @param buf the string to validate. + * @param len the length of the string in bytes. + * @return true if the string is valid UTF-8. + */ +simdjson_warn_unused bool validate_utf8(const char * buf, size_t len) noexcept; +/** + * Validate the UTF-8 string. + * + * @param sv the string_view to validate. + * @return true if the string is valid UTF-8. + */ +simdjson_inline simdjson_warn_unused bool validate_utf8(const std::string_view sv) noexcept { + return validate_utf8(sv.data(), sv.size()); +} + +/** + * Validate the UTF-8 string. + * + * @param p the string to validate. + * @return true if the string is valid UTF-8. + */ +simdjson_inline simdjson_warn_unused bool validate_utf8(const std::string& s) noexcept { + return validate_utf8(s.data(), s.size()); +} + +/** + * An implementation of simdjson for a particular CPU architecture. + * + * Also used to maintain the currently active implementation. The active implementation is + * automatically initialized on first use to the most advanced implementation supported by the host. + */ +class implementation { +public: + + /** + * The name of this implementation. + * + * const implementation *impl = simdjson::get_active_implementation(); + * cout << "simdjson is optimized for " << impl->name() << "(" << impl->description() << ")" << endl; + * + * @return the name of the implementation, e.g. "haswell", "westmere", "arm64". + */ + virtual std::string name() const { return std::string(_name); } + + /** + * The description of this implementation. + * + * const implementation *impl = simdjson::get_active_implementation(); + * cout << "simdjson is optimized for " << impl->name() << "(" << impl->description() << ")" << endl; + * + * @return the description of the implementation, e.g. "Intel/AMD AVX2", "Intel/AMD SSE4.2", "ARM NEON". + */ + virtual std::string description() const { return std::string(_description); } + + /** + * The instruction sets this implementation is compiled against + * and the current CPU match. This function may poll the current CPU/system + * and should therefore not be called too often if performance is a concern. + * + * @return true if the implementation can be safely used on the current system (determined at runtime). + */ + bool supported_by_runtime_system() const; + + /** + * @private For internal implementation use + * + * The instruction sets this implementation is compiled against. + * + * @return a mask of all required `internal::instruction_set::` values. + */ + virtual uint32_t required_instruction_sets() const { return _required_instruction_sets; } + + /** + * @private For internal implementation use + * + * const implementation *impl = simdjson::get_active_implementation(); + * cout << "simdjson is optimized for " << impl->name() << "(" << impl->description() << ")" << endl; + * + * @param capacity The largest document that will be passed to the parser. + * @param max_depth The maximum JSON object/array nesting this parser is expected to handle. + * @param dst The place to put the resulting parser implementation. + * @return the error code, or SUCCESS if there was no error. + */ + virtual error_code create_dom_parser_implementation( + size_t capacity, + size_t max_depth, + std::unique_ptr &dst + ) const noexcept = 0; + + /** + * @private For internal implementation use + * + * Minify the input string assuming that it represents a JSON string, does not parse or validate. + * + * Overridden by each implementation. + * + * @param buf the json document to minify. + * @param len the length of the json document. + * @param dst the buffer to write the minified document to. *MUST* be allocated up to len + SIMDJSON_PADDING bytes. + * @param dst_len the number of bytes written. Output only. + * @return the error code, or SUCCESS if there was no error. + */ + simdjson_warn_unused virtual error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept = 0; + + + /** + * Validate the UTF-8 string. + * + * Overridden by each implementation. + * + * @param buf the string to validate. + * @param len the length of the string in bytes. + * @return true if and only if the string is valid UTF-8. + */ + simdjson_warn_unused virtual bool validate_utf8(const char *buf, size_t len) const noexcept = 0; + +protected: + /** @private Construct an implementation with the given name and description. For subclasses. */ + simdjson_inline implementation( + std::string_view name, + std::string_view description, + uint32_t required_instruction_sets + ) : + _name(name), + _description(description), + _required_instruction_sets(required_instruction_sets) + { + } +protected: + ~implementation() = default; + +private: + /** + * The name of this implementation. + */ + std::string_view _name; + + /** + * The description of this implementation. + */ + std::string_view _description; + + /** + * Instruction sets required for this implementation. + */ + const uint32_t _required_instruction_sets; +}; + +/** @private */ +namespace internal { + +/** + * The list of available implementations compiled into simdjson. + */ +class available_implementation_list { +public: + /** Get the list of available implementations compiled into simdjson */ + simdjson_inline available_implementation_list() {} + /** Number of implementations */ + size_t size() const noexcept; + /** STL const begin() iterator */ + const implementation * const *begin() const noexcept; + /** STL const end() iterator */ + const implementation * const *end() const noexcept; + + /** + * Get the implementation with the given name. + * + * Case sensitive. + * + * const implementation *impl = simdjson::get_available_implementations()["westmere"]; + * if (!impl) { exit(1); } + * if (!imp->supported_by_runtime_system()) { exit(1); } + * simdjson::get_active_implementation() = impl; + * + * @param name the implementation to find, e.g. "westmere", "haswell", "arm64" + * @return the implementation, or nullptr if the parse failed. + */ + const implementation * operator[](const std::string_view &name) const noexcept { + for (const implementation * impl : *this) { + if (impl->name() == name) { return impl; } + } + return nullptr; + } + + /** + * Detect the most advanced implementation supported by the current host. + * + * This is used to initialize the implementation on startup. + * + * const implementation *impl = simdjson::available_implementation::detect_best_supported(); + * simdjson::get_active_implementation() = impl; + * + * @return the most advanced supported implementation for the current host, or an + * implementation that returns UNSUPPORTED_ARCHITECTURE if there is no supported + * implementation. Will never return nullptr. + */ + const implementation *detect_best_supported() const noexcept; +}; + +} // namespace internal + +/** + * The list of available implementations compiled into simdjson. + */ +extern SIMDJSON_DLLIMPORTEXPORT const internal::available_implementation_list& get_available_implementations(); + +/** + * The active implementation. + * + * Automatically initialized on first use to the most advanced implementation supported by this hardware. + */ +extern SIMDJSON_DLLIMPORTEXPORT internal::atomic_ptr& get_active_implementation(); + +} // namespace simdjson + +#endif // SIMDJSON_IMPLEMENTATION_H diff --git a/contrib/libs/simdjson/include/simdjson/implementation_detection.h b/contrib/libs/simdjson/include/simdjson/implementation_detection.h new file mode 100644 index 000000000000..0ff315b7adb1 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/implementation_detection.h @@ -0,0 +1,168 @@ +#ifndef SIMDJSON_IMPLEMENTATION_DETECTION_H +#define SIMDJSON_IMPLEMENTATION_DETECTION_H + +#include "simdjson/base.h" + +// 0 is reserved, because undefined SIMDJSON_IMPLEMENTATION equals 0 in preprocessor macros. +#define SIMDJSON_IMPLEMENTATION_ID_arm64 1 +#define SIMDJSON_IMPLEMENTATION_ID_fallback 2 +#define SIMDJSON_IMPLEMENTATION_ID_haswell 3 +#define SIMDJSON_IMPLEMENTATION_ID_icelake 4 +#define SIMDJSON_IMPLEMENTATION_ID_ppc64 5 +#define SIMDJSON_IMPLEMENTATION_ID_westmere 6 +#define SIMDJSON_IMPLEMENTATION_ID_lsx 7 +#define SIMDJSON_IMPLEMENTATION_ID_lasx 8 + +#define SIMDJSON_IMPLEMENTATION_ID_FOR(IMPL) SIMDJSON_CAT(SIMDJSON_IMPLEMENTATION_ID_, IMPL) +#define SIMDJSON_IMPLEMENTATION_ID SIMDJSON_IMPLEMENTATION_ID_FOR(SIMDJSON_IMPLEMENTATION) + +#define SIMDJSON_IMPLEMENTATION_IS(IMPL) SIMDJSON_IMPLEMENTATION_ID == SIMDJSON_IMPLEMENTATION_ID_FOR(IMPL) + +// +// First, figure out which implementations can be run. Doing it here makes it so we don't have to worry about the order +// in which we include them. +// + +#ifndef SIMDJSON_IMPLEMENTATION_ARM64 +#define SIMDJSON_IMPLEMENTATION_ARM64 (SIMDJSON_IS_ARM64) +#endif +#if SIMDJSON_IMPLEMENTATION_ARM64 && SIMDJSON_IS_ARM64 +#define SIMDJSON_CAN_ALWAYS_RUN_ARM64 1 +#else +#define SIMDJSON_CAN_ALWAYS_RUN_ARM64 0 +#endif + +// Default Icelake to on if this is x86-64. Even if we're not compiled for it, it could be selected +// at runtime. +#ifndef SIMDJSON_IMPLEMENTATION_ICELAKE +#define SIMDJSON_IMPLEMENTATION_ICELAKE ((SIMDJSON_IS_X86_64) && (SIMDJSON_AVX512_ALLOWED) && (SIMDJSON_COMPILER_SUPPORTS_VBMI2)) +#endif + +#ifdef _MSC_VER +// To see why (__BMI__) && (__PCLMUL__) && (__LZCNT__) are not part of this next line, see +// https://github.com/simdjson/simdjson/issues/1247 +#if ((SIMDJSON_IMPLEMENTATION_ICELAKE) && (__AVX2__) && (__AVX512F__) && (__AVX512DQ__) && (__AVX512CD__) && (__AVX512BW__) && (__AVX512VL__) && (__AVX512VBMI2__)) +#define SIMDJSON_CAN_ALWAYS_RUN_ICELAKE 1 +#else +#define SIMDJSON_CAN_ALWAYS_RUN_ICELAKE 0 +#endif + +#else + +#if ((SIMDJSON_IMPLEMENTATION_ICELAKE) && (__AVX2__) && (__BMI__) && (__PCLMUL__) && (__LZCNT__) && (__AVX512F__) && (__AVX512DQ__) && (__AVX512CD__) && (__AVX512BW__) && (__AVX512VL__) && (__AVX512VBMI2__)) +#define SIMDJSON_CAN_ALWAYS_RUN_ICELAKE 1 +#else +#define SIMDJSON_CAN_ALWAYS_RUN_ICELAKE 0 +#endif + +#endif + +// Default Haswell to on if this is x86-64. Even if we're not compiled for it, it could be selected +// at runtime. +#ifndef SIMDJSON_IMPLEMENTATION_HASWELL +#if SIMDJSON_CAN_ALWAYS_RUN_ICELAKE +// if icelake is always available, never enable haswell. +#define SIMDJSON_IMPLEMENTATION_HASWELL 0 +#else +#define SIMDJSON_IMPLEMENTATION_HASWELL SIMDJSON_IS_X86_64 +#endif +#endif +#ifdef _MSC_VER +// To see why (__BMI__) && (__PCLMUL__) && (__LZCNT__) are not part of this next line, see +// https://github.com/simdjson/simdjson/issues/1247 +#if ((SIMDJSON_IMPLEMENTATION_HASWELL) && (SIMDJSON_IS_X86_64) && (__AVX2__)) +#define SIMDJSON_CAN_ALWAYS_RUN_HASWELL 1 +#else +#define SIMDJSON_CAN_ALWAYS_RUN_HASWELL 0 +#endif + +#else + +#if ((SIMDJSON_IMPLEMENTATION_HASWELL) && (SIMDJSON_IS_X86_64) && (__AVX2__) && (__BMI__) && (__PCLMUL__) && (__LZCNT__)) +#define SIMDJSON_CAN_ALWAYS_RUN_HASWELL 1 +#else +#define SIMDJSON_CAN_ALWAYS_RUN_HASWELL 0 +#endif + +#endif + +// Default Westmere to on if this is x86-64. +#ifndef SIMDJSON_IMPLEMENTATION_WESTMERE +#if SIMDJSON_CAN_ALWAYS_RUN_ICELAKE || SIMDJSON_CAN_ALWAYS_RUN_HASWELL +// if icelake or haswell are always available, never enable westmere. +#define SIMDJSON_IMPLEMENTATION_WESTMERE 0 +#else +#define SIMDJSON_IMPLEMENTATION_WESTMERE SIMDJSON_IS_X86_64 +#endif +#endif + +#if (SIMDJSON_IMPLEMENTATION_WESTMERE && SIMDJSON_IS_X86_64 && __SSE4_2__ && __PCLMUL__) +#define SIMDJSON_CAN_ALWAYS_RUN_WESTMERE 1 +#else +#define SIMDJSON_CAN_ALWAYS_RUN_WESTMERE 0 +#endif + + +#ifndef SIMDJSON_IMPLEMENTATION_PPC64 +#define SIMDJSON_IMPLEMENTATION_PPC64 (SIMDJSON_IS_PPC64 && SIMDJSON_IS_PPC64_VMX) +#endif +#if SIMDJSON_IMPLEMENTATION_PPC64 && SIMDJSON_IS_PPC64 && SIMDJSON_IS_PPC64_VMX +#define SIMDJSON_CAN_ALWAYS_RUN_PPC64 1 +#else +#define SIMDJSON_CAN_ALWAYS_RUN_PPC64 0 +#endif + +#ifndef SIMDJSON_IMPLEMENTATION_LASX +#define SIMDJSON_IMPLEMENTATION_LASX (SIMDJSON_IS_LOONGARCH64 && __loongarch_asx) +#endif +#define SIMDJSON_CAN_ALWAYS_RUN_LASX (SIMDJSON_IMPLEMENTATION_LASX) + +#ifndef SIMDJSON_IMPLEMENTATION_LSX +#if SIMDJSON_CAN_ALWAYS_RUN_LASX +#define SIMDJSON_IMPLEMENTATION_LSX 0 +#else +#define SIMDJSON_IMPLEMENTATION_LSX (SIMDJSON_IS_LOONGARCH64 && __loongarch_sx) +#endif +#endif +#define SIMDJSON_CAN_ALWAYS_RUN_LSX (SIMDJSON_IMPLEMENTATION_LSX) + +// Default Fallback to on unless a builtin implementation has already been selected. +#ifndef SIMDJSON_IMPLEMENTATION_FALLBACK +#if SIMDJSON_CAN_ALWAYS_RUN_ARM64 || SIMDJSON_CAN_ALWAYS_RUN_ICELAKE || SIMDJSON_CAN_ALWAYS_RUN_HASWELL || SIMDJSON_CAN_ALWAYS_RUN_WESTMERE || SIMDJSON_CAN_ALWAYS_RUN_PPC64 || SIMDJSON_CAN_ALWAYS_RUN_LSX || SIMDJSON_CAN_ALWAYS_RUN_LASX +// if anything at all except fallback can always run, then disable fallback. +#define SIMDJSON_IMPLEMENTATION_FALLBACK 0 +#else +#define SIMDJSON_IMPLEMENTATION_FALLBACK 1 +#endif +#endif +#define SIMDJSON_CAN_ALWAYS_RUN_FALLBACK SIMDJSON_IMPLEMENTATION_FALLBACK + +// Determine the best builtin implementation +#ifndef SIMDJSON_BUILTIN_IMPLEMENTATION + +#if SIMDJSON_CAN_ALWAYS_RUN_ICELAKE +#define SIMDJSON_BUILTIN_IMPLEMENTATION icelake +#elif SIMDJSON_CAN_ALWAYS_RUN_HASWELL +#define SIMDJSON_BUILTIN_IMPLEMENTATION haswell +#elif SIMDJSON_CAN_ALWAYS_RUN_WESTMERE +#define SIMDJSON_BUILTIN_IMPLEMENTATION westmere +#elif SIMDJSON_CAN_ALWAYS_RUN_ARM64 +#define SIMDJSON_BUILTIN_IMPLEMENTATION arm64 +#elif SIMDJSON_CAN_ALWAYS_RUN_PPC64 +#define SIMDJSON_BUILTIN_IMPLEMENTATION ppc64 +#elif SIMDJSON_CAN_ALWAYS_RUN_LSX +#define SIMDJSON_BUILTIN_IMPLEMENTATION lsx +#elif SIMDJSON_CAN_ALWAYS_RUN_LASX +#define SIMDJSON_BUILTIN_IMPLEMENTATION lasx +#elif SIMDJSON_CAN_ALWAYS_RUN_FALLBACK +#define SIMDJSON_BUILTIN_IMPLEMENTATION fallback +#else +#error "All possible implementations (including fallback) have been disabled! simdjson will not run." +#endif + +#endif // SIMDJSON_BUILTIN_IMPLEMENTATION + +#define SIMDJSON_BUILTIN_IMPLEMENTATION_ID SIMDJSON_IMPLEMENTATION_ID_FOR(SIMDJSON_BUILTIN_IMPLEMENTATION) +#define SIMDJSON_BUILTIN_IMPLEMENTATION_IS(IMPL) SIMDJSON_BUILTIN_IMPLEMENTATION_ID == SIMDJSON_IMPLEMENTATION_ID_FOR(IMPL) + +#endif // SIMDJSON_IMPLEMENTATION_DETECTION_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/internal/atomic_ptr.h b/contrib/libs/simdjson/include/simdjson/internal/atomic_ptr.h new file mode 100644 index 000000000000..c4fe41b05a66 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/internal/atomic_ptr.h @@ -0,0 +1,31 @@ +#ifndef SIMDJSON_INTERNAL_ATOMIC_PTR_H +#define SIMDJSON_INTERNAL_ATOMIC_PTR_H + +#include "simdjson/base.h" +#include + +namespace simdjson { +namespace internal { + +template +class atomic_ptr { +public: + atomic_ptr(T *_ptr) : ptr{_ptr} {} + + operator const T*() const { return ptr.load(); } + const T& operator*() const { return *ptr; } + const T* operator->() const { return ptr.load(); } + + operator T*() { return ptr.load(); } + T& operator*() { return *ptr; } + T* operator->() { return ptr.load(); } + atomic_ptr& operator=(T *_ptr) { ptr = _ptr; return *this; } + +private: + std::atomic ptr; +}; + +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_INTERNAL_ATOMIC_PTR_H diff --git a/contrib/libs/simdjson/include/simdjson/internal/dom_parser_implementation.h b/contrib/libs/simdjson/include/simdjson/internal/dom_parser_implementation.h new file mode 100644 index 000000000000..a93fe38ff9e0 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/internal/dom_parser_implementation.h @@ -0,0 +1,252 @@ +#ifndef SIMDJSON_INTERNAL_DOM_PARSER_IMPLEMENTATION_H +#define SIMDJSON_INTERNAL_DOM_PARSER_IMPLEMENTATION_H + +#include "simdjson/base.h" +#include "simdjson/error.h" +#include + +namespace simdjson { + +namespace dom { +class document; +} // namespace dom + +/** +* This enum is used with the dom_parser_implementation::stage1 function. +* 1) The regular mode expects a fully formed JSON document. +* 2) The streaming_partial mode expects a possibly truncated +* input within a stream on JSON documents. +* 3) The stream_final mode allows us to truncate final +* unterminated strings. It is useful in conjunction with streaming_partial. +*/ +enum class stage1_mode { regular, streaming_partial, streaming_final}; + +/** + * Returns true if mode == streaming_partial or mode == streaming_final + */ +inline bool is_streaming(stage1_mode mode) { + // performance note: it is probably faster to check that mode is different + // from regular than checking that it is either streaming_partial or streaming_final. + return (mode != stage1_mode::regular); + // return (mode == stage1_mode::streaming_partial || mode == stage1_mode::streaming_final); +} + + +namespace internal { + + +/** + * An implementation of simdjson's DOM parser for a particular CPU architecture. + * + * This class is expected to be accessed only by pointer, and never move in memory (though the + * pointer can move). + */ +class dom_parser_implementation { +public: + + /** + * @private For internal implementation use + * + * Run a full JSON parse on a single document (stage1 + stage2). + * + * Guaranteed only to be called when capacity > document length. + * + * Overridden by each implementation. + * + * @param buf The json document to parse. *MUST* be allocated up to len + SIMDJSON_PADDING bytes. + * @param len The length of the json document. + * @return The error code, or SUCCESS if there was no error. + */ + simdjson_warn_unused virtual error_code parse(const uint8_t *buf, size_t len, dom::document &doc) noexcept = 0; + + /** + * @private For internal implementation use + * + * Stage 1 of the document parser. + * + * Guaranteed only to be called when capacity > document length. + * + * Overridden by each implementation. + * + * @param buf The json document to parse. + * @param len The length of the json document. + * @param streaming Whether this is being called by parser::parse_many. + * @return The error code, or SUCCESS if there was no error. + */ + simdjson_warn_unused virtual error_code stage1(const uint8_t *buf, size_t len, stage1_mode streaming) noexcept = 0; + + /** + * @private For internal implementation use + * + * Stage 2 of the document parser. + * + * Called after stage1(). + * + * Overridden by each implementation. + * + * @param doc The document to output to. + * @return The error code, or SUCCESS if there was no error. + */ + simdjson_warn_unused virtual error_code stage2(dom::document &doc) noexcept = 0; + + /** + * @private For internal implementation use + * + * Stage 2 of the document parser for parser::parse_many. + * + * Guaranteed only to be called after stage1(). + * Overridden by each implementation. + * + * @param doc The document to output to. + * @return The error code, SUCCESS if there was no error, or EMPTY if all documents have been parsed. + */ + simdjson_warn_unused virtual error_code stage2_next(dom::document &doc) noexcept = 0; + + /** + * Unescape a valid UTF-8 string from src to dst, stopping at a final unescaped quote. There + * must be an unescaped quote terminating the string. It returns the final output + * position as pointer. In case of error (e.g., the string has bad escaped codes), + * then null_ptr is returned. It is assumed that the output buffer is large + * enough. E.g., if src points at 'joe"', then dst needs to have four free bytes + + * SIMDJSON_PADDING bytes. + * + * Overridden by each implementation. + * + * @param str pointer to the beginning of a valid UTF-8 JSON string, must end with an unescaped quote. + * @param dst pointer to a destination buffer, it must point a region in memory of sufficient size. + * @param allow_replacement whether we allow a replacement character when the UTF-8 contains unmatched surrogate pairs. + * @return end of the of the written region (exclusive) or nullptr in case of error. + */ + simdjson_warn_unused virtual uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept = 0; + + /** + * Unescape a NON-valid UTF-8 string from src to dst, stopping at a final unescaped quote. There + * must be an unescaped quote terminating the string. It returns the final output + * position as pointer. In case of error (e.g., the string has bad escaped codes), + * then null_ptr is returned. It is assumed that the output buffer is large + * enough. E.g., if src points at 'joe"', then dst needs to have four free bytes + + * SIMDJSON_PADDING bytes. + * + * Overridden by each implementation. + * + * @param str pointer to the beginning of a possibly invalid UTF-8 JSON string, must end with an unescaped quote. + * @param dst pointer to a destination buffer, it must point a region in memory of sufficient size. + * @return end of the of the written region (exclusive) or nullptr in case of error. + */ + simdjson_warn_unused virtual uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept = 0; + + /** + * Change the capacity of this parser. + * + * The capacity can never exceed SIMDJSON_MAXSIZE_BYTES (e.g., 4 GB) + * and an CAPACITY error is returned if it is attempted. + * + * Generally used for reallocation. + * + * @param capacity The new capacity. + * @param max_depth The new max_depth. + * @return The error code, or SUCCESS if there was no error. + */ + virtual error_code set_capacity(size_t capacity) noexcept = 0; + + /** + * Change the max depth of this parser. + * + * Generally used for reallocation. + * + * @param capacity The new capacity. + * @param max_depth The new max_depth. + * @return The error code, or SUCCESS if there was no error. + */ + virtual error_code set_max_depth(size_t max_depth) noexcept = 0; + + /** + * Deallocate this parser. + */ + virtual ~dom_parser_implementation() = default; + + /** Number of structural indices passed from stage 1 to stage 2 */ + uint32_t n_structural_indexes{0}; + /** Structural indices passed from stage 1 to stage 2 */ + std::unique_ptr structural_indexes{}; + /** Next structural index to parse */ + uint32_t next_structural_index{0}; + + /** + * The largest document this parser can support without reallocating. + * + * @return Current capacity, in bytes. + */ + simdjson_pure simdjson_inline size_t capacity() const noexcept; + + /** + * The maximum level of nested object and arrays supported by this parser. + * + * @return Maximum depth, in bytes. + */ + simdjson_pure simdjson_inline size_t max_depth() const noexcept; + + /** + * Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length + * and `max_depth` depth. + * + * @param capacity The new capacity. + * @param max_depth The new max_depth. Defaults to DEFAULT_MAX_DEPTH. + * @return The error, if there is one. + */ + simdjson_warn_unused inline error_code allocate(size_t capacity, size_t max_depth) noexcept; + + +protected: + /** + * The maximum document length this parser supports. + * + * Buffers are large enough to handle any document up to this length. + */ + size_t _capacity{0}; + + /** + * The maximum depth (number of nested objects and arrays) supported by this parser. + * + * Defaults to DEFAULT_MAX_DEPTH. + */ + size_t _max_depth{0}; + + // Declaring these so that subclasses can use them to implement their constructors. + simdjson_inline dom_parser_implementation() noexcept; + simdjson_inline dom_parser_implementation(dom_parser_implementation &&other) noexcept; + simdjson_inline dom_parser_implementation &operator=(dom_parser_implementation &&other) noexcept; + + simdjson_inline dom_parser_implementation(const dom_parser_implementation &) noexcept = delete; + simdjson_inline dom_parser_implementation &operator=(const dom_parser_implementation &other) noexcept = delete; +}; // class dom_parser_implementation + +simdjson_inline dom_parser_implementation::dom_parser_implementation() noexcept = default; +simdjson_inline dom_parser_implementation::dom_parser_implementation(dom_parser_implementation &&other) noexcept = default; +simdjson_inline dom_parser_implementation &dom_parser_implementation::operator=(dom_parser_implementation &&other) noexcept = default; + +simdjson_pure simdjson_inline size_t dom_parser_implementation::capacity() const noexcept { + return _capacity; +} + +simdjson_pure simdjson_inline size_t dom_parser_implementation::max_depth() const noexcept { + return _max_depth; +} + +simdjson_warn_unused +inline error_code dom_parser_implementation::allocate(size_t capacity, size_t max_depth) noexcept { + if (this->max_depth() != max_depth) { + error_code err = set_max_depth(max_depth); + if (err) { return err; } + } + if (_capacity != capacity) { + error_code err = set_capacity(capacity); + if (err) { return err; } + } + return SUCCESS; +} + +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_INTERNAL_DOM_PARSER_IMPLEMENTATION_H diff --git a/contrib/libs/simdjson/include/simdjson/internal/instruction_set.h b/contrib/libs/simdjson/include/simdjson/internal/instruction_set.h new file mode 100644 index 000000000000..1dc0a81fb3c5 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/internal/instruction_set.h @@ -0,0 +1,77 @@ +/* From +https://github.com/endorno/pytorch/blob/master/torch/lib/TH/generic/simd/simd.h +Highly modified. + +Copyright (c) 2016- Facebook, Inc (Adam Paszke) +Copyright (c) 2014- Facebook, Inc (Soumith Chintala) +Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert) +Copyright (c) 2012-2014 Deepmind Technologies (Koray Kavukcuoglu) +Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu) +Copyright (c) 2011-2013 NYU (Clement Farabet) +Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert, Leon Bottou, +Iain Melvin, Jason Weston) Copyright (c) 2006 Idiap Research Institute +(Samy Bengio) Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert, +Samy Bengio, Johnny Mariethoz) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the names of Facebook, Deepmind Technologies, NYU, NEC Laboratories +America and IDIAP Research Institute nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef SIMDJSON_INTERNAL_INSTRUCTION_SET_H +#define SIMDJSON_INTERNAL_INSTRUCTION_SET_H + +namespace simdjson { +namespace internal { + +enum instruction_set { + DEFAULT = 0x0, + NEON = 0x1, + AVX2 = 0x4, + SSE42 = 0x8, + PCLMULQDQ = 0x10, + BMI1 = 0x20, + BMI2 = 0x40, + ALTIVEC = 0x80, + AVX512F = 0x100, + AVX512DQ = 0x200, + AVX512IFMA = 0x400, + AVX512PF = 0x800, + AVX512ER = 0x1000, + AVX512CD = 0x2000, + AVX512BW = 0x4000, + AVX512VL = 0x8000, + AVX512VBMI2 = 0x10000, + LSX = 0x20000, + LASX = 0x40000, +}; + +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_INTERNAL_INSTRUCTION_SET_H diff --git a/contrib/libs/simdjson/include/simdjson/internal/jsoncharutils_tables.h b/contrib/libs/simdjson/include/simdjson/internal/jsoncharutils_tables.h new file mode 100644 index 000000000000..d72bce122622 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/internal/jsoncharutils_tables.h @@ -0,0 +1,26 @@ +#ifndef SIMDJSON_INTERNAL_JSONCHARUTILS_TABLES_H +#define SIMDJSON_INTERNAL_JSONCHARUTILS_TABLES_H + +#include "simdjson/base.h" + +#ifdef JSON_TEST_STRINGS +void found_string(const uint8_t *buf, const uint8_t *parsed_begin, + const uint8_t *parsed_end); +void found_bad_string(const uint8_t *buf); +#endif + +namespace simdjson { +namespace internal { +// structural chars here are +// they are { 0x7b } 0x7d : 0x3a [ 0x5b ] 0x5d , 0x2c (and NULL) +// we are also interested in the four whitespace characters +// space 0x20, linefeed 0x0a, horizontal tab 0x09 and carriage return 0x0d + +extern SIMDJSON_DLLIMPORTEXPORT const bool structural_or_whitespace_negated[256]; +extern SIMDJSON_DLLIMPORTEXPORT const bool structural_or_whitespace[256]; +extern SIMDJSON_DLLIMPORTEXPORT const uint32_t digit_to_val32[886]; + +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_INTERNAL_JSONCHARUTILS_TABLES_H diff --git a/contrib/libs/simdjson/include/simdjson/internal/jsonformatutils.h b/contrib/libs/simdjson/include/simdjson/internal/jsonformatutils.h new file mode 100644 index 000000000000..43000ab43183 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/internal/jsonformatutils.h @@ -0,0 +1,64 @@ +#ifndef SIMDJSON_INTERNAL_JSONFORMATUTILS_H +#define SIMDJSON_INTERNAL_JSONFORMATUTILS_H + +#include "simdjson/base.h" +#include +#include +#include + +namespace simdjson { +namespace internal { + +inline std::ostream& operator<<(std::ostream& out, const escape_json_string &str); + +class escape_json_string { +public: + escape_json_string(std::string_view _str) noexcept : str{_str} {} + operator std::string() const noexcept { std::stringstream s; s << *this; return s.str(); } +private: + std::string_view str; + friend std::ostream& operator<<(std::ostream& out, const escape_json_string &unescaped); +}; + +inline std::ostream& operator<<(std::ostream& out, const escape_json_string &unescaped) { + for (size_t i=0; i(unescaped.str[i]) <= 0x1F) { + // TODO can this be done once at the beginning, or will it mess up << char? + std::ios::fmtflags f(out.flags()); + out << "\\u" << std::hex << std::setw(4) << std::setfill('0') << int(unescaped.str[i]); + out.flags(f); + } else { + out << unescaped.str[i]; + } + } + } + return out; +} + +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_INTERNAL_JSONFORMATUTILS_H diff --git a/contrib/libs/simdjson/include/simdjson/internal/numberparsing_tables.h b/contrib/libs/simdjson/include/simdjson/internal/numberparsing_tables.h new file mode 100644 index 000000000000..1762056f7506 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/internal/numberparsing_tables.h @@ -0,0 +1,59 @@ +#ifndef SIMDJSON_INTERNAL_NUMBERPARSING_TABLES_H +#define SIMDJSON_INTERNAL_NUMBERPARSING_TABLES_H + +#include "simdjson/base.h" + +namespace simdjson { +namespace internal { +/** + * The smallest non-zero float (binary64) is 2^-1074. + * We take as input numbers of the form w x 10^q where w < 2^64. + * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. + * However, we have that + * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^-1074. + * Thus it is possible for a number of the form w * 10^-342 where + * w is a 64-bit value to be a non-zero floating-point number. + ********* + * Any number of form w * 10^309 where w>= 1 is going to be + * infinite in binary64 so we never need to worry about powers + * of 5 greater than 308. + */ +constexpr int smallest_power = -342; +constexpr int largest_power = 308; + +/** + * Represents a 128-bit value. + * low: least significant 64 bits. + * high: most significant 64 bits. + */ +struct value128 { + uint64_t low; + uint64_t high; +}; + + +// Precomputed powers of ten from 10^0 to 10^22. These +// can be represented exactly using the double type. +extern SIMDJSON_DLLIMPORTEXPORT const double power_of_ten[]; + + +/** + * When mapping numbers from decimal to binary, + * we go from w * 10^q to m * 2^p but we have + * 10^q = 5^q * 2^q, so effectively + * we are trying to match + * w * 2^q * 5^q to m * 2^p. Thus the powers of two + * are not a concern since they can be represented + * exactly using the binary notation, only the powers of five + * affect the binary significand. + */ + + +// The truncated powers of five from 5^-342 all the way to 5^308 +// The mantissa is truncated to 128 bits, and +// never rounded up. Uses about 10KB. +extern SIMDJSON_DLLIMPORTEXPORT const uint64_t power_of_five_128[]; +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_INTERNAL_NUMBERPARSING_TABLES_H diff --git a/contrib/libs/simdjson/include/simdjson/internal/simdprune_tables.h b/contrib/libs/simdjson/include/simdjson/internal/simdprune_tables.h new file mode 100644 index 000000000000..7b8f4650c15c --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/internal/simdprune_tables.h @@ -0,0 +1,21 @@ +#ifndef SIMDJSON_INTERNAL_SIMDPRUNE_TABLES_H +#define SIMDJSON_INTERNAL_SIMDPRUNE_TABLES_H + +#include "simdjson/base.h" + +#include + +namespace simdjson { // table modified and copied from +namespace internal { // http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetTable + +extern SIMDJSON_DLLIMPORTEXPORT const unsigned char BitsSetTable256mul2[256]; + +extern SIMDJSON_DLLIMPORTEXPORT const uint8_t pshufb_combine_table[272]; + +// 256 * 8 bytes = 2kB, easily fits in cache. +extern SIMDJSON_DLLIMPORTEXPORT const uint64_t thintable_epi8[256]; + +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_INTERNAL_SIMDPRUNE_TABLES_H diff --git a/contrib/libs/simdjson/include/simdjson/internal/tape_ref-inl.h b/contrib/libs/simdjson/include/simdjson/internal/tape_ref-inl.h new file mode 100644 index 000000000000..a100d569e2b1 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/internal/tape_ref-inl.h @@ -0,0 +1,118 @@ +#ifndef SIMDJSON_TAPE_REF_INL_H +#define SIMDJSON_TAPE_REF_INL_H + +#include "simdjson/dom/document.h" +#include "simdjson/internal/tape_ref.h" +#include "simdjson/internal/tape_type.h" + +#include + +namespace simdjson { +namespace internal { + +constexpr const uint64_t JSON_VALUE_MASK = 0x00FFFFFFFFFFFFFF; +constexpr const uint32_t JSON_COUNT_MASK = 0xFFFFFF; + +// +// tape_ref inline implementation +// +simdjson_inline tape_ref::tape_ref() noexcept : doc{nullptr}, json_index{0} {} +simdjson_inline tape_ref::tape_ref(const dom::document *_doc, size_t _json_index) noexcept : doc{_doc}, json_index{_json_index} {} + + +simdjson_inline bool tape_ref::is_document_root() const noexcept { + return json_index == 1; // should we ever change the structure of the tape, this should get updated. +} +simdjson_inline bool tape_ref::usable() const noexcept { + return doc != nullptr; // when the document pointer is null, this tape_ref is uninitialized (should not be accessed). +} +// Some value types have a specific on-tape word value. It can be faster +// to check the type by doing a word-to-word comparison instead of extracting the +// most significant 8 bits. + +simdjson_inline bool tape_ref::is_double() const noexcept { + constexpr uint64_t tape_double = uint64_t(tape_type::DOUBLE)<<56; + return doc->tape[json_index] == tape_double; +} +simdjson_inline bool tape_ref::is_int64() const noexcept { + constexpr uint64_t tape_int64 = uint64_t(tape_type::INT64)<<56; + return doc->tape[json_index] == tape_int64; +} +simdjson_inline bool tape_ref::is_uint64() const noexcept { + constexpr uint64_t tape_uint64 = uint64_t(tape_type::UINT64)<<56; + return doc->tape[json_index] == tape_uint64; +} +simdjson_inline bool tape_ref::is_false() const noexcept { + constexpr uint64_t tape_false = uint64_t(tape_type::FALSE_VALUE)<<56; + return doc->tape[json_index] == tape_false; +} +simdjson_inline bool tape_ref::is_true() const noexcept { + constexpr uint64_t tape_true = uint64_t(tape_type::TRUE_VALUE)<<56; + return doc->tape[json_index] == tape_true; +} +simdjson_inline bool tape_ref::is_null_on_tape() const noexcept { + constexpr uint64_t tape_null = uint64_t(tape_type::NULL_VALUE)<<56; + return doc->tape[json_index] == tape_null; +} + +inline size_t tape_ref::after_element() const noexcept { + switch (tape_ref_type()) { + case tape_type::START_ARRAY: + case tape_type::START_OBJECT: + return matching_brace_index(); + case tape_type::UINT64: + case tape_type::INT64: + case tape_type::DOUBLE: + return json_index + 2; + default: + return json_index + 1; + } +} +simdjson_inline tape_type tape_ref::tape_ref_type() const noexcept { + return static_cast(doc->tape[json_index] >> 56); +} +simdjson_inline uint64_t internal::tape_ref::tape_value() const noexcept { + return doc->tape[json_index] & internal::JSON_VALUE_MASK; +} +simdjson_inline uint32_t internal::tape_ref::matching_brace_index() const noexcept { + return uint32_t(doc->tape[json_index]); +} +simdjson_inline uint32_t internal::tape_ref::scope_count() const noexcept { + return uint32_t((doc->tape[json_index] >> 32) & internal::JSON_COUNT_MASK); +} + +template +simdjson_inline T tape_ref::next_tape_value() const noexcept { + static_assert(sizeof(T) == sizeof(uint64_t), "next_tape_value() template parameter must be 64-bit"); + // Though the following is tempting... + // return *reinterpret_cast(&doc->tape[json_index + 1]); + // It is not generally safe. It is safer, and often faster to rely + // on memcpy. Yes, it is uglier, but it is also encapsulated. + T x; + std::memcpy(&x,&doc->tape[json_index + 1],sizeof(uint64_t)); + return x; +} + +simdjson_inline uint32_t internal::tape_ref::get_string_length() const noexcept { + size_t string_buf_index = size_t(tape_value()); + uint32_t len; + std::memcpy(&len, &doc->string_buf[string_buf_index], sizeof(len)); + return len; +} + +simdjson_inline const char * internal::tape_ref::get_c_str() const noexcept { + size_t string_buf_index = size_t(tape_value()); + return reinterpret_cast(&doc->string_buf[string_buf_index + sizeof(uint32_t)]); +} + +inline std::string_view internal::tape_ref::get_string_view() const noexcept { + return std::string_view( + get_c_str(), + get_string_length() + ); +} + +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_TAPE_REF_INL_H diff --git a/contrib/libs/simdjson/include/simdjson/internal/tape_ref.h b/contrib/libs/simdjson/include/simdjson/internal/tape_ref.h new file mode 100644 index 000000000000..922a05701022 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/internal/tape_ref.h @@ -0,0 +1,49 @@ +#ifndef SIMDJSON_INTERNAL_TAPE_REF_H +#define SIMDJSON_INTERNAL_TAPE_REF_H + +#include "simdjson/base.h" + +namespace simdjson { +namespace dom { +class document; +} // namespace dom + +namespace internal { + +/** + * A reference to an element on the tape. Internal only. + */ +class tape_ref { +public: + simdjson_inline tape_ref() noexcept; + simdjson_inline tape_ref(const dom::document *doc, size_t json_index) noexcept; + inline size_t after_element() const noexcept; + simdjson_inline tape_type tape_ref_type() const noexcept; + simdjson_inline uint64_t tape_value() const noexcept; + simdjson_inline bool is_double() const noexcept; + simdjson_inline bool is_int64() const noexcept; + simdjson_inline bool is_uint64() const noexcept; + simdjson_inline bool is_false() const noexcept; + simdjson_inline bool is_true() const noexcept; + simdjson_inline bool is_null_on_tape() const noexcept;// different name to avoid clash with is_null. + simdjson_inline uint32_t matching_brace_index() const noexcept; + simdjson_inline uint32_t scope_count() const noexcept; + template + simdjson_inline T next_tape_value() const noexcept; + simdjson_inline uint32_t get_string_length() const noexcept; + simdjson_inline const char * get_c_str() const noexcept; + inline std::string_view get_string_view() const noexcept; + simdjson_inline bool is_document_root() const noexcept; + simdjson_inline bool usable() const noexcept; + + /** The document this element references. */ + const dom::document *doc; + + /** The index of this element on `doc.tape[]` */ + size_t json_index; +}; + +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_INTERNAL_TAPE_REF_H diff --git a/contrib/libs/simdjson/include/simdjson/internal/tape_type.h b/contrib/libs/simdjson/include/simdjson/internal/tape_type.h new file mode 100644 index 000000000000..d43c57c7a49e --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/internal/tape_type.h @@ -0,0 +1,28 @@ +#ifndef SIMDJSON_INTERNAL_TAPE_TYPE_H +#define SIMDJSON_INTERNAL_TAPE_TYPE_H + +namespace simdjson { +namespace internal { + +/** + * The possible types in the tape. + */ +enum class tape_type { + ROOT = 'r', + START_ARRAY = '[', + START_OBJECT = '{', + END_ARRAY = ']', + END_OBJECT = '}', + STRING = '"', + INT64 = 'l', + UINT64 = 'u', + DOUBLE = 'd', + TRUE_VALUE = 't', + FALSE_VALUE = 'f', + NULL_VALUE = 'n' +}; // enum class tape_type + +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_INTERNAL_TAPE_TYPE_H diff --git a/contrib/libs/simdjson/include/simdjson/jsonioutil.h b/contrib/libs/simdjson/include/simdjson/jsonioutil.h new file mode 100644 index 000000000000..cff25ab69324 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/jsonioutil.h @@ -0,0 +1,22 @@ +#ifndef SIMDJSON_JSONIOUTIL_H +#define SIMDJSON_JSONIOUTIL_H + +#include "simdjson/base.h" +#include "simdjson/padded_string.h" + +#include "simdjson/padded_string-inl.h" + +namespace simdjson { + +#if SIMDJSON_EXCEPTIONS +#ifndef SIMDJSON_DISABLE_DEPRECATED_API +[[deprecated("Use padded_string::load() instead")]] +inline padded_string get_corpus(const char *path) { + return padded_string::load(path); +} +#endif // SIMDJSON_DISABLE_DEPRECATED_API +#endif // SIMDJSON_EXCEPTIONS + +} // namespace simdjson + +#endif // SIMDJSON_JSONIOUTIL_H diff --git a/contrib/libs/simdjson/include/simdjson/jsonpathutil.h b/contrib/libs/simdjson/include/simdjson/jsonpathutil.h new file mode 100644 index 000000000000..afcc74ccc1f7 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/jsonpathutil.h @@ -0,0 +1,64 @@ +#ifndef SIMDJSON_JSONPATHUTIL_H +#define SIMDJSON_JSONPATHUTIL_H + +#include +#include + +namespace simdjson { +/** + * Converts JSONPath to JSON Pointer. + * @param json_path The JSONPath string to be converted. + * @return A string containing the equivalent JSON Pointer. + */ +inline std::string json_path_to_pointer_conversion(std::string_view json_path) { + size_t i = 0; + + // if JSONPath starts with $, skip it + if (!json_path.empty() && json_path.front() == '$') { + i = 1; + } + if (json_path.empty() || (json_path[i] != '.' && + json_path[i] != '[')) { + return "-1"; // This is just a sentinel value, the caller should check for this and return an error. + } + + std::string result; + // Reserve space to reduce allocations, adjusting for potential increases due + // to escaping. + result.reserve(json_path.size() * 2); + + while (i < json_path.length()) { + if (json_path[i] == '.') { + result += '/'; + } else if (json_path[i] == '[') { + result += '/'; + ++i; // Move past the '[' + while (i < json_path.length() && json_path[i] != ']') { + if (json_path[i] == '~') { + result += "~0"; + } else if (json_path[i] == '/') { + result += "~1"; + } else { + result += json_path[i]; + } + ++i; + } + if (i == json_path.length() || json_path[i] != ']') { + return "-1"; // Using sentinel value that will be handled as an error by the caller. + } + } else { + if (json_path[i] == '~') { + result += "~0"; + } else if (json_path[i] == '/') { + result += "~1"; + } else { + result += json_path[i]; + } + } + ++i; + } + + return result; +} +} // namespace simdjson +#endif // SIMDJSON_JSONPATHUTIL_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/lasx.h b/contrib/libs/simdjson/include/simdjson/lasx.h new file mode 100644 index 000000000000..37a20c89863c --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lasx.h @@ -0,0 +1,8 @@ +#ifndef SIMDJSON_LASX_H +#define SIMDJSON_LASX_H + +#include "simdjson/lasx/begin.h" +#include "simdjson/generic/amalgamated.h" +#include "simdjson/lasx/end.h" + +#endif // SIMDJSON_LASX_H diff --git a/contrib/libs/simdjson/include/simdjson/lasx/base.h b/contrib/libs/simdjson/include/simdjson/lasx/base.h new file mode 100644 index 000000000000..9d9a866c3756 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lasx/base.h @@ -0,0 +1,26 @@ +#ifndef SIMDJSON_LASX_BASE_H +#define SIMDJSON_LASX_BASE_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +/** + * Implementation for LASX. + */ +namespace lasx { + +class implementation; + +namespace { +namespace simd { +template struct simd8; +template struct simd8x64; +} // namespace simd +} // unnamed namespace + +} // namespace lasx +} // namespace simdjson + +#endif // SIMDJSON_LASX_BASE_H diff --git a/contrib/libs/simdjson/include/simdjson/lasx/begin.h b/contrib/libs/simdjson/include/simdjson/lasx/begin.h new file mode 100644 index 000000000000..560eba737ce8 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lasx/begin.h @@ -0,0 +1,10 @@ +#define SIMDJSON_IMPLEMENTATION lasx +#include "simdjson/lasx/base.h" +#include "simdjson/lasx/intrinsics.h" +#include "simdjson/lasx/bitmanipulation.h" +#include "simdjson/lasx/bitmask.h" +#include "simdjson/lasx/numberparsing_defs.h" +#include "simdjson/lasx/simd.h" +#include "simdjson/lasx/stringparsing_defs.h" + +#define SIMDJSON_SKIP_BACKSLASH_SHORT_CIRCUIT 1 diff --git a/contrib/libs/simdjson/include/simdjson/lasx/bitmanipulation.h b/contrib/libs/simdjson/include/simdjson/lasx/bitmanipulation.h new file mode 100644 index 000000000000..962ddbf56bc4 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lasx/bitmanipulation.h @@ -0,0 +1,50 @@ +#ifndef SIMDJSON_LASX_BITMANIPULATION_H +#define SIMDJSON_LASX_BITMANIPULATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/lasx/base.h" +#include "simdjson/lasx/intrinsics.h" +#include "simdjson/lasx/bitmask.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace lasx { +namespace { + +// We sometimes call trailing_zero on inputs that are zero, +// but the algorithms do not end up using the returned value. +// Sadly, sanitizers are not smart enough to figure it out. +SIMDJSON_NO_SANITIZE_UNDEFINED +// This function can be used safely even if not all bytes have been +// initialized. +// See issue https://github.com/simdjson/simdjson/issues/1965 +SIMDJSON_NO_SANITIZE_MEMORY +simdjson_inline int trailing_zeroes(uint64_t input_num) { + return __builtin_ctzll(input_num); +} + +/* result might be undefined when input_num is zero */ +simdjson_inline uint64_t clear_lowest_bit(uint64_t input_num) { + return input_num & (input_num-1); +} + +/* result might be undefined when input_num is zero */ +simdjson_inline int leading_zeroes(uint64_t input_num) { + return __builtin_clzll(input_num); +} + +/* result might be undefined when input_num is zero */ +simdjson_inline int count_ones(uint64_t input_num) { + return __lasx_xvpickve2gr_w(__lasx_xvpcnt_d(__m256i(v4u64{input_num, 0, 0, 0})), 0); +} + +simdjson_inline bool add_overflow(uint64_t value1, uint64_t value2, uint64_t *result) { + return __builtin_uaddll_overflow(value1, value2, + reinterpret_cast(result)); +} + +} // unnamed namespace +} // namespace lasx +} // namespace simdjson + +#endif // SIMDJSON_LASX_BITMANIPULATION_H diff --git a/contrib/libs/simdjson/include/simdjson/lasx/bitmask.h b/contrib/libs/simdjson/include/simdjson/lasx/bitmask.h new file mode 100644 index 000000000000..e847c1d71000 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lasx/bitmask.h @@ -0,0 +1,31 @@ +#ifndef SIMDJSON_LASX_BITMASK_H +#define SIMDJSON_LASX_BITMASK_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/lasx/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace lasx { +namespace { + +// +// Perform a "cumulative bitwise xor," flipping bits each time a 1 is encountered. +// +// For example, prefix_xor(00100100) == 00011100 +// +simdjson_inline uint64_t prefix_xor(uint64_t bitmask) { + bitmask ^= bitmask << 1; + bitmask ^= bitmask << 2; + bitmask ^= bitmask << 4; + bitmask ^= bitmask << 8; + bitmask ^= bitmask << 16; + bitmask ^= bitmask << 32; + return bitmask; +} + +} // unnamed namespace +} // namespace lasx +} // namespace simdjson + +#endif diff --git a/contrib/libs/simdjson/include/simdjson/lasx/end.h b/contrib/libs/simdjson/include/simdjson/lasx/end.h new file mode 100644 index 000000000000..2f5ec807907f --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lasx/end.h @@ -0,0 +1,6 @@ +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/lasx/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#undef SIMDJSON_SKIP_BACKSLASH_SHORT_CIRCUIT +#undef SIMDJSON_IMPLEMENTATION diff --git a/contrib/libs/simdjson/include/simdjson/lasx/implementation.h b/contrib/libs/simdjson/include/simdjson/lasx/implementation.h new file mode 100644 index 000000000000..8aafbb8b88b1 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lasx/implementation.h @@ -0,0 +1,31 @@ +#ifndef SIMDJSON_LASX_IMPLEMENTATION_H +#define SIMDJSON_LASX_IMPLEMENTATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/base.h" +#include "simdjson/implementation.h" +#include "simdjson/internal/instruction_set.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace lasx { + +/** + * @private + */ +class implementation final : public simdjson::implementation { +public: + simdjson_inline implementation() : simdjson::implementation("lasx", "LoongArch ASX", internal::instruction_set::LASX) {} + simdjson_warn_unused error_code create_dom_parser_implementation( + size_t capacity, + size_t max_length, + std::unique_ptr& dst + ) const noexcept final; + simdjson_warn_unused error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept final; + simdjson_warn_unused bool validate_utf8(const char *buf, size_t len) const noexcept final; +}; + +} // namespace lasx +} // namespace simdjson + +#endif // SIMDJSON_LASX_IMPLEMENTATION_H diff --git a/contrib/libs/simdjson/include/simdjson/lasx/intrinsics.h b/contrib/libs/simdjson/include/simdjson/lasx/intrinsics.h new file mode 100644 index 000000000000..47b152d9a4d1 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lasx/intrinsics.h @@ -0,0 +1,14 @@ +#ifndef SIMDJSON_LASX_INTRINSICS_H +#define SIMDJSON_LASX_INTRINSICS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/lasx/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +// This should be the correct header whether +// you use visual studio or other compilers. +#error #include + +static_assert(sizeof(__m256i) <= simdjson::SIMDJSON_PADDING, "insufficient padding for LoongArch ASX"); + +#endif // SIMDJSON_LASX_INTRINSICS_H diff --git a/contrib/libs/simdjson/include/simdjson/lasx/numberparsing_defs.h b/contrib/libs/simdjson/include/simdjson/lasx/numberparsing_defs.h new file mode 100644 index 000000000000..3c4020c65a5d --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lasx/numberparsing_defs.h @@ -0,0 +1,47 @@ +#ifndef SIMDJSON_LASX_NUMBERPARSING_DEFS_H +#define SIMDJSON_LASX_NUMBERPARSING_DEFS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/lasx/base.h" +#include "simdjson/lasx/intrinsics.h" +#include "simdjson/internal/numberparsing_tables.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include + +namespace simdjson { +namespace lasx { +namespace numberparsing { + +// we don't have appropriate instructions, so let us use a scalar function +// credit: https://johnnylee-sde.github.io/Fast-numeric-string-to-int/ +/** @private */ +static simdjson_inline uint32_t parse_eight_digits_unrolled(const uint8_t *chars) { + uint64_t val; + std::memcpy(&val, chars, sizeof(uint64_t)); + val = (val & 0x0F0F0F0F0F0F0F0F) * 2561 >> 8; + val = (val & 0x00FF00FF00FF00FF) * 6553601 >> 16; + return uint32_t((val & 0x0000FFFF0000FFFF) * 42949672960001 >> 32); +} + +simdjson_inline internal::value128 full_multiplication(uint64_t value1, uint64_t value2) { + internal::value128 answer; + __uint128_t r = (static_cast<__uint128_t>(value1)) * value2; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); + return answer; +} + +} // namespace numberparsing +} // namespace lasx +} // namespace simdjson + +#ifndef SIMDJSON_SWAR_NUMBER_PARSING +#if SIMDJSON_IS_BIG_ENDIAN +#define SIMDJSON_SWAR_NUMBER_PARSING 0 +#else +#define SIMDJSON_SWAR_NUMBER_PARSING 1 +#endif +#endif + +#endif // SIMDJSON_LASX_NUMBERPARSING_DEFS_H diff --git a/contrib/libs/simdjson/include/simdjson/lasx/ondemand.h b/contrib/libs/simdjson/include/simdjson/lasx/ondemand.h new file mode 100644 index 000000000000..9f7ab96fa6dd --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lasx/ondemand.h @@ -0,0 +1,8 @@ +#ifndef SIMDJSON_LASX_ONDEMAND_H +#define SIMDJSON_LASX_ONDEMAND_H + +#include "simdjson/lasx/begin.h" +#include "simdjson/generic/ondemand/amalgamated.h" +#include "simdjson/lasx/end.h" + +#endif // SIMDJSON_LASX_ONDEMAND_H diff --git a/contrib/libs/simdjson/include/simdjson/lasx/simd.h b/contrib/libs/simdjson/include/simdjson/lasx/simd.h new file mode 100644 index 000000000000..907a8acb9b36 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lasx/simd.h @@ -0,0 +1,376 @@ +#ifndef SIMDJSON_LASX_SIMD_H +#define SIMDJSON_LASX_SIMD_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/lasx/base.h" +#include "simdjson/lasx/bitmanipulation.h" +#include "simdjson/internal/simdprune_tables.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace lasx { +namespace { +namespace simd { + + // Forward-declared so they can be used by splat and friends. + template + struct base { + __m256i value; + + // Zero constructor + simdjson_inline base() : value{__m256i()} {} + + // Conversion from SIMD register + simdjson_inline base(const __m256i _value) : value(_value) {} + + // Conversion to SIMD register + simdjson_inline operator const __m256i&() const { return this->value; } + simdjson_inline operator __m256i&() { return this->value; } + simdjson_inline operator const v32i8&() const { return (v32i8&)this->value; } + simdjson_inline operator v32i8&() { return (v32i8&)this->value; } + + // Bit operations + simdjson_inline Child operator|(const Child other) const { return __lasx_xvor_v(*this, other); } + simdjson_inline Child operator&(const Child other) const { return __lasx_xvand_v(*this, other); } + simdjson_inline Child operator^(const Child other) const { return __lasx_xvxor_v(*this, other); } + simdjson_inline Child bit_andnot(const Child other) const { return __lasx_xvandn_v(other, *this); } + simdjson_inline Child& operator|=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast | other; return *this_cast; } + simdjson_inline Child& operator&=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast & other; return *this_cast; } + simdjson_inline Child& operator^=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast ^ other; return *this_cast; } + }; + + // Forward-declared so they can be used by splat and friends. + template + struct simd8; + + template> + struct base8: base> { + simdjson_inline base8() : base>() {} + simdjson_inline base8(const __m256i _value) : base>(_value) {} + + friend simdjson_really_inline Mask operator==(const simd8 lhs, const simd8 rhs) { return __lasx_xvseq_b(lhs, rhs); } + + static const int SIZE = sizeof(base>::value); + + template + simdjson_inline simd8 prev(const simd8 prev_chunk) const { + __m256i hi = __lasx_xvbsll_v(*this, N); + __m256i lo = __lasx_xvbsrl_v(*this, 16 - N); + __m256i tmp = __lasx_xvbsrl_v(prev_chunk, 16 - N); + lo = __lasx_xvpermi_q(lo, tmp, 0x21); + return __lasx_xvor_v(hi, lo); + } + }; + + // SIMD byte mask type (returned by things like eq and gt) + template<> + struct simd8: base8 { + static simdjson_inline simd8 splat(bool _value) { return __lasx_xvreplgr2vr_b(uint8_t(-(!!_value))); } + + simdjson_inline simd8() : base8() {} + simdjson_inline simd8(const __m256i _value) : base8(_value) {} + // Splat constructor + simdjson_inline simd8(bool _value) : base8(splat(_value)) {} + + simdjson_inline int to_bitmask() const { + __m256i mask = __lasx_xvmskltz_b(*this); + return (__lasx_xvpickve2gr_w(mask, 4) << 16) | (__lasx_xvpickve2gr_w(mask, 0)); + } + simdjson_inline bool any() const { + __m256i v = __lasx_xvmsknz_b(*this); + return (0 == __lasx_xvpickve2gr_w(v, 0)) && (0 == __lasx_xvpickve2gr_w(v, 4)); + } + simdjson_inline simd8 operator~() const { return *this ^ true; } + }; + + template + struct base8_numeric: base8 { + static simdjson_inline simd8 splat(T _value) { + return __lasx_xvreplgr2vr_b(_value); + } + static simdjson_inline simd8 zero() { return __lasx_xvldi(0); } + static simdjson_inline simd8 load(const T values[32]) { + return __lasx_xvld(reinterpret_cast(values), 0); + } + // Repeat 16 values as many times as necessary (usually for lookup tables) + static simdjson_inline simd8 repeat_16( + T v0, T v1, T v2, T v3, T v4, T v5, T v6, T v7, + T v8, T v9, T v10, T v11, T v12, T v13, T v14, T v15 + ) { + return simd8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + ); + } + + simdjson_inline base8_numeric() : base8() {} + simdjson_inline base8_numeric(const __m256i _value) : base8(_value) {} + + // Store to array + simdjson_inline void store(T dst[32]) const { + return __lasx_xvst(*this, reinterpret_cast<__m256i *>(dst), 0); + } + + // Addition/subtraction are the same for signed and unsigned + simdjson_inline simd8 operator+(const simd8 other) const { return __lasx_xvadd_b(*this, other); } + simdjson_inline simd8 operator-(const simd8 other) const { return __lasx_xvsub_b(*this, other); } + simdjson_inline simd8& operator+=(const simd8 other) { *this = *this + other; return *static_cast*>(this); } + simdjson_inline simd8& operator-=(const simd8 other) { *this = *this - other; return *static_cast*>(this); } + + // Override to distinguish from bool version + simdjson_inline simd8 operator~() const { return *this ^ 0xFFu; } + + // Perform a lookup assuming the value is between 0 and 16 (undefined behavior for out of range values) + template + simdjson_inline simd8 lookup_16(simd8 lookup_table) const { + return __lasx_xvshuf_b(lookup_table, lookup_table, *this); + } + + // Copies to 'output" all bytes corresponding to a 0 in the mask (interpreted as a bitset). + // Passing a 0 value for mask would be equivalent to writing out every byte to output. + // Only the first 16 - count_ones(mask) bytes of the result are significant but 16 bytes + // get written. + template + simdjson_inline void compress(uint32_t mask, L * output) const { + using internal::thintable_epi8; + using internal::BitsSetTable256mul2; + using internal::pshufb_combine_table; + // this particular implementation was inspired by haswell + // lasx do it in 4 steps, first 8 bytes and then second 8 bytes... + uint8_t mask1 = uint8_t(mask); // least significant 8 bits + uint8_t mask2 = uint8_t(mask >> 8); // second significant 8 bits + uint8_t mask3 = uint8_t(mask >> 16); // ... + uint8_t mask4 = uint8_t(mask >> 24); // ... + // next line just loads the 64-bit values thintable_epi8[mask{1,2,3,4}] + // into a 256-bit register. + __m256i shufmask = {int64_t(thintable_epi8[mask1]), int64_t(thintable_epi8[mask2]) + 0x0808080808080808, int64_t(thintable_epi8[mask3]), int64_t(thintable_epi8[mask4]) + 0x0808080808080808}; + // this is the version "nearly pruned" + __m256i pruned = __lasx_xvshuf_b(*this, *this, shufmask); + // we still need to put the pieces back together. + // we compute the popcount of the first words: + int pop1 = BitsSetTable256mul2[mask1]; + int pop2 = BitsSetTable256mul2[mask2]; + int pop3 = BitsSetTable256mul2[mask3]; + + // then load the corresponding mask + __m256i masklo = __lasx_xvldx(reinterpret_cast(reinterpret_cast(pshufb_combine_table)), pop1 * 8); + __m256i maskhi = __lasx_xvldx(reinterpret_cast(reinterpret_cast(pshufb_combine_table)), pop3 * 8); + __m256i compactmask = __lasx_xvpermi_q(maskhi, masklo, 0x20); + __m256i answer = __lasx_xvshuf_b(pruned, pruned, compactmask); + __lasx_xvst(answer, reinterpret_cast(output), 0); + uint64_t value3 = __lasx_xvpickve2gr_du(answer, 2); + uint64_t value4 = __lasx_xvpickve2gr_du(answer, 3); + uint64_t *pos = reinterpret_cast(reinterpret_cast(output) + 16 - (pop1 + pop2) / 2); + pos[0] = value3; + pos[1] = value4; + } + + template + simdjson_inline simd8 lookup_16( + L replace0, L replace1, L replace2, L replace3, + L replace4, L replace5, L replace6, L replace7, + L replace8, L replace9, L replace10, L replace11, + L replace12, L replace13, L replace14, L replace15) const { + return lookup_16(simd8::repeat_16( + replace0, replace1, replace2, replace3, + replace4, replace5, replace6, replace7, + replace8, replace9, replace10, replace11, + replace12, replace13, replace14, replace15 + )); + } + }; + + // Signed bytes + template<> + struct simd8 : base8_numeric { + simdjson_inline simd8() : base8_numeric() {} + simdjson_inline simd8(const __m256i _value) : base8_numeric(_value) {} + // Splat constructor + simdjson_inline simd8(int8_t _value) : simd8(splat(_value)) {} + // Array constructor + simdjson_inline simd8(const int8_t values[32]) : simd8(load(values)) {} + // Member-by-member initialization + simdjson_inline simd8( + int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, + int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15, + int8_t v16, int8_t v17, int8_t v18, int8_t v19, int8_t v20, int8_t v21, int8_t v22, int8_t v23, + int8_t v24, int8_t v25, int8_t v26, int8_t v27, int8_t v28, int8_t v29, int8_t v30, int8_t v31 + ) : simd8({ + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v16,v17,v18,v19,v20,v21,v22,v23, + v24,v25,v26,v27,v28,v29,v30,v31 + }) {} + // Repeat 16 values as many times as necessary (usually for lookup tables) + simdjson_inline static simd8 repeat_16( + int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, + int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15 + ) { + return simd8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + ); + } + + // Order-sensitive comparisons + simdjson_inline simd8 max_val(const simd8 other) const { return __lasx_xvmax_b(*this, other); } + simdjson_inline simd8 min_val(const simd8 other) const { return __lasx_xvmin_b(*this, other); } + simdjson_inline simd8 operator>(const simd8 other) const { return __lasx_xvslt_b(other, *this); } + simdjson_inline simd8 operator<(const simd8 other) const { return __lasx_xvslt_b(*this, other); } + }; + + // Unsigned bytes + template<> + struct simd8: base8_numeric { + simdjson_inline simd8() : base8_numeric() {} + simdjson_inline simd8(const __m256i _value) : base8_numeric(_value) {} + // Splat constructor + simdjson_inline simd8(uint8_t _value) : simd8(splat(_value)) {} + // Array constructor + simdjson_inline simd8(const uint8_t values[32]) : simd8(load(values)) {} + // Member-by-member initialization + simdjson_inline simd8( + uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, + uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15, + uint8_t v16, uint8_t v17, uint8_t v18, uint8_t v19, uint8_t v20, uint8_t v21, uint8_t v22, uint8_t v23, + uint8_t v24, uint8_t v25, uint8_t v26, uint8_t v27, uint8_t v28, uint8_t v29, uint8_t v30, uint8_t v31 + ) : simd8(__m256i(v32u8{ + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v16,v17,v18,v19,v20,v21,v22,v23, + v24,v25,v26,v27,v28,v29,v30,v31 + })) {} + // Repeat 16 values as many times as necessary (usually for lookup tables) + simdjson_inline static simd8 repeat_16( + uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, + uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15 + ) { + return simd8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15, + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + ); + } + + // Saturated math + simdjson_inline simd8 saturating_add(const simd8 other) const { return __lasx_xvsadd_bu(*this, other); } + simdjson_inline simd8 saturating_sub(const simd8 other) const { return __lasx_xvssub_bu(*this, other); } + + // Order-specific operations + simdjson_inline simd8 max_val(const simd8 other) const { return __lasx_xvmax_bu(*this, other); } + simdjson_inline simd8 min_val(const simd8 other) const { return __lasx_xvmin_bu(other, *this); } + // Same as >, but only guarantees true is nonzero (< guarantees true = -1) + simdjson_inline simd8 gt_bits(const simd8 other) const { return this->saturating_sub(other); } + // Same as <, but only guarantees true is nonzero (< guarantees true = -1) + simdjson_inline simd8 lt_bits(const simd8 other) const { return other.saturating_sub(*this); } + simdjson_inline simd8 operator<=(const simd8 other) const { return other.max_val(*this) == other; } + simdjson_inline simd8 operator>=(const simd8 other) const { return other.min_val(*this) == other; } + simdjson_inline simd8 operator>(const simd8 other) const { return this->gt_bits(other).any_bits_set(); } + simdjson_inline simd8 operator<(const simd8 other) const { return this->lt_bits(other).any_bits_set(); } + + // Bit-specific operations + simdjson_inline simd8 bits_not_set() const { return *this == uint8_t(0); } + simdjson_inline simd8 bits_not_set(simd8 bits) const { return (*this & bits).bits_not_set(); } + simdjson_inline simd8 any_bits_set() const { return ~this->bits_not_set(); } + simdjson_inline simd8 any_bits_set(simd8 bits) const { return ~this->bits_not_set(bits); } + simdjson_inline bool is_ascii() const { + __m256i mask = __lasx_xvmskltz_b(*this); + return (0 == __lasx_xvpickve2gr_w(mask, 0)) && (0 == __lasx_xvpickve2gr_w(mask, 4)); + } + simdjson_inline bool bits_not_set_anywhere() const { + __m256i v = __lasx_xvmsknz_b(*this); + return (0 == __lasx_xvpickve2gr_w(v, 0)) && (0 == __lasx_xvpickve2gr_w(v, 4)); + } + simdjson_inline bool any_bits_set_anywhere() const { return !bits_not_set_anywhere(); } + simdjson_inline bool bits_not_set_anywhere(simd8 bits) const { + __m256i v = __lasx_xvmsknz_b(__lasx_xvand_v(*this, bits)); + return (0 == __lasx_xvpickve2gr_w(v, 0)) && (0 == __lasx_xvpickve2gr_w(v, 4)); + } + simdjson_inline bool any_bits_set_anywhere(simd8 bits) const { return !bits_not_set_anywhere(bits); } + template + simdjson_inline simd8 shr() const { return simd8(__lasx_xvsrli_b(*this, N)); } + template + simdjson_inline simd8 shl() const { return simd8(__lasx_xvslli_b(*this, N)); } + }; + + template + struct simd8x64 { + static constexpr int NUM_CHUNKS = 64 / sizeof(simd8); + static_assert(NUM_CHUNKS == 2, "LASX kernel should use two registers per 64-byte block."); + const simd8 chunks[NUM_CHUNKS]; + + simd8x64(const simd8x64& o) = delete; // no copy allowed + simd8x64& operator=(const simd8& other) = delete; // no assignment allowed + simd8x64() = delete; // no default constructor allowed + + simdjson_inline simd8x64(const simd8 chunk0, const simd8 chunk1) : chunks{chunk0, chunk1} {} + simdjson_inline simd8x64(const T ptr[64]) : chunks{simd8::load(ptr), simd8::load(ptr+32)} {} + + simdjson_inline uint64_t compress(uint64_t mask, T * output) const { + uint32_t mask1 = uint32_t(mask); + uint32_t mask2 = uint32_t(mask >> 32); + __m256i zcnt = __lasx_xvpcnt_w(__m256i(v4u64{~mask, 0, 0, 0})); + uint64_t zcnt1 = __lasx_xvpickve2gr_wu(zcnt, 0); + uint64_t zcnt2 = __lasx_xvpickve2gr_wu(zcnt, 1); + // There should be a critical value which processes in scaler is faster. + if (zcnt1) + this->chunks[0].compress(mask1, output); + if (zcnt2) + this->chunks[1].compress(mask2, output + zcnt1); + return zcnt1 + zcnt2; + } + + simdjson_inline void store(T ptr[64]) const { + this->chunks[0].store(ptr+sizeof(simd8)*0); + this->chunks[1].store(ptr+sizeof(simd8)*1); + } + + simdjson_inline uint64_t to_bitmask() const { + __m256i mask0 = __lasx_xvmskltz_b(this->chunks[0]); + __m256i mask1 = __lasx_xvmskltz_b(this->chunks[1]); + __m256i mask_tmp = __lasx_xvpickve_w(mask0, 4); + __m256i tmp = __lasx_xvpickve_w(mask1, 4); + mask0 = __lasx_xvinsve0_w(mask0, mask1, 1); + mask_tmp = __lasx_xvinsve0_w(mask_tmp, tmp, 1); + return __lasx_xvpickve2gr_du(__lasx_xvpackev_h(mask_tmp, mask0), 0); + } + + simdjson_inline simd8 reduce_or() const { + return this->chunks[0] | this->chunks[1]; + } + + simdjson_inline uint64_t eq(const T m) const { + const simd8 mask = simd8::splat(m); + return simd8x64( + this->chunks[0] == mask, + this->chunks[1] == mask + ).to_bitmask(); + } + + simdjson_inline uint64_t eq(const simd8x64 &other) const { + return simd8x64( + this->chunks[0] == other.chunks[0], + this->chunks[1] == other.chunks[1] + ).to_bitmask(); + } + + simdjson_inline uint64_t lteq(const T m) const { + const simd8 mask = simd8::splat(m); + return simd8x64( + this->chunks[0] <= mask, + this->chunks[1] <= mask + ).to_bitmask(); + } + }; // struct simd8x64 + +} // namespace simd +} // unnamed namespace +} // namespace lasx +} // namespace simdjson + +#endif // SIMDJSON_LASX_SIMD_H diff --git a/contrib/libs/simdjson/include/simdjson/lasx/stringparsing_defs.h b/contrib/libs/simdjson/include/simdjson/lasx/stringparsing_defs.h new file mode 100644 index 000000000000..fe7a7430e007 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lasx/stringparsing_defs.h @@ -0,0 +1,47 @@ +#ifndef SIMDJSON_LASX_STRINGPARSING_DEFS_H +#define SIMDJSON_LASX_STRINGPARSING_DEFS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/lasx/base.h" +#include "simdjson/lasx/simd.h" +#include "simdjson/lasx/bitmanipulation.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace lasx { +namespace { + +using namespace simd; + +// Holds backslashes and quotes locations. +struct backslash_and_quote { +public: + static constexpr uint32_t BYTES_PROCESSED = 32; + simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); + + simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; } + simdjson_inline bool has_backslash() { return bs_bits != 0; } + simdjson_inline int quote_index() { return trailing_zeroes(quote_bits); } + simdjson_inline int backslash_index() { return trailing_zeroes(bs_bits); } + + uint32_t bs_bits; + uint32_t quote_bits; +}; // struct backslash_and_quote + +simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { + // this can read up to 31 bytes beyond the buffer size, but we require + // SIMDJSON_PADDING of padding + static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "backslash and quote finder must process fewer than SIMDJSON_PADDING bytes"); + simd8 v(src); + v.store(dst); + return { + static_cast((v == '\\').to_bitmask()), // bs_bits + static_cast((v == '"').to_bitmask()), // quote_bits + }; +} + +} // unnamed namespace +} // namespace lasx +} // namespace simdjson + +#endif // SIMDJSON_LASX_STRINGPARSING_DEFS_H diff --git a/contrib/libs/simdjson/include/simdjson/lsx.h b/contrib/libs/simdjson/include/simdjson/lsx.h new file mode 100644 index 000000000000..1496e9ceb6fe --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lsx.h @@ -0,0 +1,8 @@ +#ifndef SIMDJSON_LSX_H +#define SIMDJSON_LSX_H + +#include "simdjson/lsx/begin.h" +#include "simdjson/generic/amalgamated.h" +#include "simdjson/lsx/end.h" + +#endif // SIMDJSON_LSX_H diff --git a/contrib/libs/simdjson/include/simdjson/lsx/base.h b/contrib/libs/simdjson/include/simdjson/lsx/base.h new file mode 100644 index 000000000000..ff02450184a8 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lsx/base.h @@ -0,0 +1,26 @@ +#ifndef SIMDJSON_LSX_BASE_H +#define SIMDJSON_LSX_BASE_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +/** + * Implementation for LSX. + */ +namespace lsx { + +class implementation; + +namespace { +namespace simd { +template struct simd8; +template struct simd8x64; +} // namespace simd +} // unnamed namespace + +} // namespace lsx +} // namespace simdjson + +#endif // SIMDJSON_LSX_BASE_H diff --git a/contrib/libs/simdjson/include/simdjson/lsx/begin.h b/contrib/libs/simdjson/include/simdjson/lsx/begin.h new file mode 100644 index 000000000000..78a92819a346 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lsx/begin.h @@ -0,0 +1,10 @@ +#define SIMDJSON_IMPLEMENTATION lsx +#include "simdjson/lsx/base.h" +#include "simdjson/lsx/intrinsics.h" +#include "simdjson/lsx/bitmanipulation.h" +#include "simdjson/lsx/bitmask.h" +#include "simdjson/lsx/numberparsing_defs.h" +#include "simdjson/lsx/simd.h" +#include "simdjson/lsx/stringparsing_defs.h" + +#define SIMDJSON_SKIP_BACKSLASH_SHORT_CIRCUIT 1 diff --git a/contrib/libs/simdjson/include/simdjson/lsx/bitmanipulation.h b/contrib/libs/simdjson/include/simdjson/lsx/bitmanipulation.h new file mode 100644 index 000000000000..96e1794bae54 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lsx/bitmanipulation.h @@ -0,0 +1,50 @@ +#ifndef SIMDJSON_LSX_BITMANIPULATION_H +#define SIMDJSON_LSX_BITMANIPULATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/lsx/base.h" +#include "simdjson/lsx/intrinsics.h" +#include "simdjson/lsx/bitmask.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace lsx { +namespace { + +// We sometimes call trailing_zero on inputs that are zero, +// but the algorithms do not end up using the returned value. +// Sadly, sanitizers are not smart enough to figure it out. +SIMDJSON_NO_SANITIZE_UNDEFINED +// This function can be used safely even if not all bytes have been +// initialized. +// See issue https://github.com/simdjson/simdjson/issues/1965 +SIMDJSON_NO_SANITIZE_MEMORY +simdjson_inline int trailing_zeroes(uint64_t input_num) { + return __builtin_ctzll(input_num); +} + +/* result might be undefined when input_num is zero */ +simdjson_inline uint64_t clear_lowest_bit(uint64_t input_num) { + return input_num & (input_num-1); +} + +/* result might be undefined when input_num is zero */ +simdjson_inline int leading_zeroes(uint64_t input_num) { + return __builtin_clzll(input_num); +} + +/* result might be undefined when input_num is zero */ +simdjson_inline int count_ones(uint64_t input_num) { + return __lsx_vpickve2gr_w(__lsx_vpcnt_d(__m128i(v2u64{input_num, 0})), 0); +} + +simdjson_inline bool add_overflow(uint64_t value1, uint64_t value2, uint64_t *result) { + return __builtin_uaddll_overflow(value1, value2, + reinterpret_cast(result)); +} + +} // unnamed namespace +} // namespace lsx +} // namespace simdjson + +#endif // SIMDJSON_LSX_BITMANIPULATION_H diff --git a/contrib/libs/simdjson/include/simdjson/lsx/bitmask.h b/contrib/libs/simdjson/include/simdjson/lsx/bitmask.h new file mode 100644 index 000000000000..3a9f0d768c5e --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lsx/bitmask.h @@ -0,0 +1,31 @@ +#ifndef SIMDJSON_LSX_BITMASK_H +#define SIMDJSON_LSX_BITMASK_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/lsx/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace lsx { +namespace { + +// +// Perform a "cumulative bitwise xor," flipping bits each time a 1 is encountered. +// +// For example, prefix_xor(00100100) == 00011100 +// +simdjson_inline uint64_t prefix_xor(uint64_t bitmask) { + bitmask ^= bitmask << 1; + bitmask ^= bitmask << 2; + bitmask ^= bitmask << 4; + bitmask ^= bitmask << 8; + bitmask ^= bitmask << 16; + bitmask ^= bitmask << 32; + return bitmask; +} + +} // unnamed namespace +} // namespace lsx +} // namespace simdjson + +#endif diff --git a/contrib/libs/simdjson/include/simdjson/lsx/end.h b/contrib/libs/simdjson/include/simdjson/lsx/end.h new file mode 100644 index 000000000000..0ae4d372286d --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lsx/end.h @@ -0,0 +1,6 @@ +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/lsx/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#undef SIMDJSON_SKIP_BACKSLASH_SHORT_CIRCUIT +#undef SIMDJSON_IMPLEMENTATION diff --git a/contrib/libs/simdjson/include/simdjson/lsx/implementation.h b/contrib/libs/simdjson/include/simdjson/lsx/implementation.h new file mode 100644 index 000000000000..14468777de71 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lsx/implementation.h @@ -0,0 +1,31 @@ +#ifndef SIMDJSON_LSX_IMPLEMENTATION_H +#define SIMDJSON_LSX_IMPLEMENTATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/base.h" +#include "simdjson/implementation.h" +#include "simdjson/internal/instruction_set.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace lsx { + +/** + * @private + */ +class implementation final : public simdjson::implementation { +public: + simdjson_inline implementation() : simdjson::implementation("lsx", "LoongArch SX", internal::instruction_set::LSX) {} + simdjson_warn_unused error_code create_dom_parser_implementation( + size_t capacity, + size_t max_length, + std::unique_ptr& dst + ) const noexcept final; + simdjson_warn_unused error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept final; + simdjson_warn_unused bool validate_utf8(const char *buf, size_t len) const noexcept final; +}; + +} // namespace lsx +} // namespace simdjson + +#endif // SIMDJSON_LSX_IMPLEMENTATION_H diff --git a/contrib/libs/simdjson/include/simdjson/lsx/intrinsics.h b/contrib/libs/simdjson/include/simdjson/lsx/intrinsics.h new file mode 100644 index 000000000000..c4cf1dd60942 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lsx/intrinsics.h @@ -0,0 +1,14 @@ +#ifndef SIMDJSON_LSX_INTRINSICS_H +#define SIMDJSON_LSX_INTRINSICS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/lsx/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +// This should be the correct header whether +// you use visual studio or other compilers. +#error #include + +static_assert(sizeof(__m128i) <= simdjson::SIMDJSON_PADDING, "insufficient padding for LoongArch SX"); + +#endif // SIMDJSON_LSX_INTRINSICS_H diff --git a/contrib/libs/simdjson/include/simdjson/lsx/numberparsing_defs.h b/contrib/libs/simdjson/include/simdjson/lsx/numberparsing_defs.h new file mode 100644 index 000000000000..4f90203b2313 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lsx/numberparsing_defs.h @@ -0,0 +1,47 @@ +#ifndef SIMDJSON_LSX_NUMBERPARSING_DEFS_H +#define SIMDJSON_LSX_NUMBERPARSING_DEFS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/lsx/base.h" +#include "simdjson/lsx/intrinsics.h" +#include "simdjson/internal/numberparsing_tables.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include + +namespace simdjson { +namespace lsx { +namespace numberparsing { + +// we don't have appropriate instructions, so let us use a scalar function +// credit: https://johnnylee-sde.github.io/Fast-numeric-string-to-int/ +/** @private */ +static simdjson_inline uint32_t parse_eight_digits_unrolled(const uint8_t *chars) { + uint64_t val; + std::memcpy(&val, chars, sizeof(uint64_t)); + val = (val & 0x0F0F0F0F0F0F0F0F) * 2561 >> 8; + val = (val & 0x00FF00FF00FF00FF) * 6553601 >> 16; + return uint32_t((val & 0x0000FFFF0000FFFF) * 42949672960001 >> 32); +} + +simdjson_inline internal::value128 full_multiplication(uint64_t value1, uint64_t value2) { + internal::value128 answer; + __uint128_t r = (static_cast<__uint128_t>(value1)) * value2; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); + return answer; +} + +} // namespace numberparsing +} // namespace lsx +} // namespace simdjson + +#ifndef SIMDJSON_SWAR_NUMBER_PARSING +#if SIMDJSON_IS_BIG_ENDIAN +#define SIMDJSON_SWAR_NUMBER_PARSING 0 +#else +#define SIMDJSON_SWAR_NUMBER_PARSING 1 +#endif +#endif + +#endif // SIMDJSON_LSX_NUMBERPARSING_DEFS_H diff --git a/contrib/libs/simdjson/include/simdjson/lsx/ondemand.h b/contrib/libs/simdjson/include/simdjson/lsx/ondemand.h new file mode 100644 index 000000000000..b1b612e17145 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lsx/ondemand.h @@ -0,0 +1,8 @@ +#ifndef SIMDJSON_LSX_ONDEMAND_H +#define SIMDJSON_LSX_ONDEMAND_H + +#include "simdjson/lsx/begin.h" +#include "simdjson/generic/ondemand/amalgamated.h" +#include "simdjson/lsx/end.h" + +#endif // SIMDJSON_LSX_ONDEMAND_H diff --git a/contrib/libs/simdjson/include/simdjson/lsx/simd.h b/contrib/libs/simdjson/include/simdjson/lsx/simd.h new file mode 100644 index 000000000000..3f0d66560d35 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lsx/simd.h @@ -0,0 +1,354 @@ +#ifndef SIMDJSON_LSX_SIMD_H +#define SIMDJSON_LSX_SIMD_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/lsx/base.h" +#include "simdjson/lsx/bitmanipulation.h" +#include "simdjson/internal/simdprune_tables.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace lsx { +namespace { +namespace simd { + + // Forward-declared so they can be used by splat and friends. + template + struct base { + __m128i value; + + // Zero constructor + simdjson_inline base() : value{__m128i()} {} + + // Conversion from SIMD register + simdjson_inline base(const __m128i _value) : value(_value) {} + + // Conversion to SIMD register + simdjson_inline operator const __m128i&() const { return this->value; } + simdjson_inline operator __m128i&() { return this->value; } + simdjson_inline operator const v16i8&() const { return (v16i8&)this->value; } + simdjson_inline operator v16i8&() { return (v16i8&)this->value; } + + // Bit operations + simdjson_inline Child operator|(const Child other) const { return __lsx_vor_v(*this, other); } + simdjson_inline Child operator&(const Child other) const { return __lsx_vand_v(*this, other); } + simdjson_inline Child operator^(const Child other) const { return __lsx_vxor_v(*this, other); } + simdjson_inline Child bit_andnot(const Child other) const { return __lsx_vandn_v(other, *this); } + simdjson_inline Child& operator|=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast | other; return *this_cast; } + simdjson_inline Child& operator&=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast & other; return *this_cast; } + simdjson_inline Child& operator^=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast ^ other; return *this_cast; } + }; + + // Forward-declared so they can be used by splat and friends. + template + struct simd8; + + template> + struct base8: base> { + simdjson_inline base8() : base>() {} + simdjson_inline base8(const __m128i _value) : base>(_value) {} + + friend simdjson_really_inline Mask operator==(const simd8 lhs, const simd8 rhs) { return __lsx_vseq_b(lhs, rhs); } + + static const int SIZE = sizeof(base>::value); + + template + simdjson_inline simd8 prev(const simd8 prev_chunk) const { + return __lsx_vor_v(__lsx_vbsll_v(*this, N), __lsx_vbsrl_v(prev_chunk, 16 - N)); + } + }; + + // SIMD byte mask type (returned by things like eq and gt) + template<> + struct simd8: base8 { + static simdjson_inline simd8 splat(bool _value) { + return __lsx_vreplgr2vr_b(uint8_t(-(!!_value))); + } + + simdjson_inline simd8() : base8() {} + simdjson_inline simd8(const __m128i _value) : base8(_value) {} + // Splat constructor + simdjson_inline simd8(bool _value) : base8(splat(_value)) {} + + simdjson_inline int to_bitmask() const { return __lsx_vpickve2gr_w(__lsx_vmskltz_b(*this), 0); } + simdjson_inline bool any() const { return 0 == __lsx_vpickve2gr_hu(__lsx_vmsknz_b(*this), 0); } + simdjson_inline simd8 operator~() const { return *this ^ true; } + }; + + template + struct base8_numeric: base8 { + static simdjson_inline simd8 splat(T _value) { return __lsx_vreplgr2vr_b(_value); } + static simdjson_inline simd8 zero() { return __lsx_vldi(0); } + static simdjson_inline simd8 load(const T values[16]) { + return __lsx_vld(reinterpret_cast(values), 0); + } + // Repeat 16 values as many times as necessary (usually for lookup tables) + static simdjson_inline simd8 repeat_16( + T v0, T v1, T v2, T v3, T v4, T v5, T v6, T v7, + T v8, T v9, T v10, T v11, T v12, T v13, T v14, T v15 + ) { + return simd8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + ); + } + + simdjson_inline base8_numeric() : base8() {} + simdjson_inline base8_numeric(const __m128i _value) : base8(_value) {} + + // Store to array + simdjson_inline void store(T dst[16]) const { + return __lsx_vst(*this, reinterpret_cast<__m128i *>(dst), 0); + } + + // Addition/subtraction are the same for signed and unsigned + simdjson_inline simd8 operator+(const simd8 other) const { return __lsx_vadd_b(*this, other); } + simdjson_inline simd8 operator-(const simd8 other) const { return __lsx_vsub_b(*this, other); } + simdjson_inline simd8& operator+=(const simd8 other) { *this = *this + other; return *static_cast*>(this); } + simdjson_inline simd8& operator-=(const simd8 other) { *this = *this - other; return *static_cast*>(this); } + + // Override to distinguish from bool version + simdjson_inline simd8 operator~() const { return *this ^ 0xFFu; } + + // Perform a lookup assuming the value is between 0 and 16 (undefined behavior for out of range values) + template + simdjson_inline simd8 lookup_16(simd8 lookup_table) const { + return __lsx_vshuf_b(lookup_table, lookup_table, *this); + } + + // Copies to 'output" all bytes corresponding to a 0 in the mask (interpreted as a bitset). + // Passing a 0 value for mask would be equivalent to writing out every byte to output. + // Only the first 16 - count_ones(mask) bytes of the result are significant but 16 bytes + // get written. + template + simdjson_inline void compress(uint16_t mask, L * output) const { + using internal::thintable_epi8; + using internal::BitsSetTable256mul2; + using internal::pshufb_combine_table; + // this particular implementation was inspired by haswell + // lsx do it in 2 steps, first 8 bytes and then second 8 bytes... + uint8_t mask1 = uint8_t(mask); // least significant 8 bits + uint8_t mask2 = uint8_t(mask >> 8); // second least significant 8 bits + // next line just loads the 64-bit values thintable_epi8[mask1] and + // thintable_epi8[mask2] into a 128-bit register. + __m128i shufmask = {int64_t(thintable_epi8[mask1]), int64_t(thintable_epi8[mask2]) + 0x0808080808080808}; + // this is the version "nearly pruned" + __m128i pruned = __lsx_vshuf_b(*this, *this, shufmask); + // we still need to put the pieces back together. + // we compute the popcount of the first words: + int pop1 = BitsSetTable256mul2[mask1]; + // then load the corresponding mask + __m128i compactmask = __lsx_vldx(reinterpret_cast(reinterpret_cast(pshufb_combine_table)), pop1 * 8); + __m128i answer = __lsx_vshuf_b(pruned, pruned, compactmask); + __lsx_vst(answer, reinterpret_cast(output), 0); + } + + template + simdjson_inline simd8 lookup_16( + L replace0, L replace1, L replace2, L replace3, + L replace4, L replace5, L replace6, L replace7, + L replace8, L replace9, L replace10, L replace11, + L replace12, L replace13, L replace14, L replace15) const { + return lookup_16(simd8::repeat_16( + replace0, replace1, replace2, replace3, + replace4, replace5, replace6, replace7, + replace8, replace9, replace10, replace11, + replace12, replace13, replace14, replace15 + )); + } + }; + + // Signed bytes + template<> + struct simd8 : base8_numeric { + simdjson_inline simd8() : base8_numeric() {} + simdjson_inline simd8(const __m128i _value) : base8_numeric(_value) {} + // Splat constructor + simdjson_inline simd8(int8_t _value) : simd8(splat(_value)) {} + // Array constructor + simdjson_inline simd8(const int8_t values[16]) : simd8(load(values)) {} + // Member-by-member initialization + simdjson_inline simd8( + int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, + int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15 + ) : simd8({ + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + }) {} + // Repeat 16 values as many times as necessary (usually for lookup tables) + simdjson_inline static simd8 repeat_16( + int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, + int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15 + ) { + return simd8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + ); + } + + // Order-sensitive comparisons + simdjson_inline simd8 max_val(const simd8 other) const { return __lsx_vmax_b(*this, other); } + simdjson_inline simd8 min_val(const simd8 other) const { return __lsx_vmin_b(*this, other); } + simdjson_inline simd8 operator>(const simd8 other) const { return __lsx_vslt_b(other, *this); } + simdjson_inline simd8 operator<(const simd8 other) const { return __lsx_vslt_b(*this, other); } + }; + + // Unsigned bytes + template<> + struct simd8: base8_numeric { + simdjson_inline simd8() : base8_numeric() {} + simdjson_inline simd8(const __m128i _value) : base8_numeric(_value) {} + // Splat constructor + simdjson_inline simd8(uint8_t _value) : simd8(splat(_value)) {} + // Array constructor + simdjson_inline simd8(const uint8_t values[16]) : simd8(load(values)) {} + // Member-by-member initialization + simdjson_inline simd8( + uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, + uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15 + ) : simd8(__m128i(v16u8{ + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + })) {} + // Repeat 16 values as many times as necessary (usually for lookup tables) + simdjson_inline static simd8 repeat_16( + uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, + uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15 + ) { + return simd8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + ); + } + + // Saturated math + simdjson_inline simd8 saturating_add(const simd8 other) const { return __lsx_vsadd_bu(*this, other); } + simdjson_inline simd8 saturating_sub(const simd8 other) const { return __lsx_vssub_bu(*this, other); } + + // Order-specific operations + simdjson_inline simd8 max_val(const simd8 other) const { return __lsx_vmax_bu(*this, other); } + simdjson_inline simd8 min_val(const simd8 other) const { return __lsx_vmin_bu(other, *this); } + // Same as >, but only guarantees true is nonzero (< guarantees true = -1) + simdjson_inline simd8 gt_bits(const simd8 other) const { return this->saturating_sub(other); } + // Same as <, but only guarantees true is nonzero (< guarantees true = -1) + simdjson_inline simd8 lt_bits(const simd8 other) const { return other.saturating_sub(*this); } + simdjson_inline simd8 operator<=(const simd8 other) const { return other.max_val(*this) == other; } + simdjson_inline simd8 operator>=(const simd8 other) const { return other.min_val(*this) == other; } + simdjson_inline simd8 operator>(const simd8 other) const { return this->gt_bits(other).any_bits_set(); } + simdjson_inline simd8 operator<(const simd8 other) const { return this->lt_bits(other).any_bits_set(); } + + // Bit-specific operations + simdjson_inline simd8 bits_not_set() const { return *this == uint8_t(0); } + simdjson_inline simd8 bits_not_set(simd8 bits) const { return (*this & bits).bits_not_set(); } + simdjson_inline simd8 any_bits_set() const { return ~this->bits_not_set(); } + simdjson_inline simd8 any_bits_set(simd8 bits) const { return ~this->bits_not_set(bits); } + simdjson_inline bool is_ascii() const { return 0 == __lsx_vpickve2gr_w(__lsx_vmskltz_b(*this), 0); } + simdjson_inline bool bits_not_set_anywhere() const { return 0 == __lsx_vpickve2gr_hu(__lsx_vmsknz_b(*this), 0); } + simdjson_inline bool any_bits_set_anywhere() const { return !bits_not_set_anywhere(); } + simdjson_inline bool bits_not_set_anywhere(simd8 bits) const { + return 0 == __lsx_vpickve2gr_hu(__lsx_vmsknz_b(__lsx_vand_v(*this, bits)), 0); + } + simdjson_inline bool any_bits_set_anywhere(simd8 bits) const { return !bits_not_set_anywhere(bits); } + template + simdjson_inline simd8 shr() const { return simd8(__lsx_vsrli_b(*this, N)); } + template + simdjson_inline simd8 shl() const { return simd8(__lsx_vslli_b(*this, N)); } + }; + + template + struct simd8x64 { + static constexpr int NUM_CHUNKS = 64 / sizeof(simd8); + static_assert(NUM_CHUNKS == 4, "LSX kernel should use four registers per 64-byte block."); + const simd8 chunks[NUM_CHUNKS]; + + simd8x64(const simd8x64& o) = delete; // no copy allowed + simd8x64& operator=(const simd8& other) = delete; // no assignment allowed + simd8x64() = delete; // no default constructor allowed + + simdjson_inline simd8x64(const simd8 chunk0, const simd8 chunk1, const simd8 chunk2, const simd8 chunk3) : chunks{chunk0, chunk1, chunk2, chunk3} {} + simdjson_inline simd8x64(const T ptr[64]) : chunks{simd8::load(ptr), simd8::load(ptr+16), simd8::load(ptr+32), simd8::load(ptr+48)} {} + + simdjson_inline uint64_t compress(uint64_t mask, T * output) const { + uint16_t mask1 = uint16_t(mask); + uint16_t mask2 = uint16_t(mask >> 16); + uint16_t mask3 = uint16_t(mask >> 32); + uint16_t mask4 = uint16_t(mask >> 48); + __m128i zcnt = __lsx_vpcnt_h(__m128i(v2u64{~mask, 0})); + uint64_t zcnt1 = __lsx_vpickve2gr_hu(zcnt, 0); + uint64_t zcnt2 = __lsx_vpickve2gr_hu(zcnt, 1); + uint64_t zcnt3 = __lsx_vpickve2gr_hu(zcnt, 2); + uint64_t zcnt4 = __lsx_vpickve2gr_hu(zcnt, 3); + uint8_t *voutput = reinterpret_cast(output); + // There should be a critical value which processes in scaler is faster. + if (zcnt1) + this->chunks[0].compress(mask1, reinterpret_cast(voutput)); + voutput += zcnt1; + if (zcnt2) + this->chunks[1].compress(mask2, reinterpret_cast(voutput)); + voutput += zcnt2; + if (zcnt3) + this->chunks[2].compress(mask3, reinterpret_cast(voutput)); + voutput += zcnt3; + if (zcnt4) + this->chunks[3].compress(mask4, reinterpret_cast(voutput)); + voutput += zcnt4; + return reinterpret_cast(voutput) - reinterpret_cast(output); + } + + simdjson_inline void store(T ptr[64]) const { + this->chunks[0].store(ptr+sizeof(simd8)*0); + this->chunks[1].store(ptr+sizeof(simd8)*1); + this->chunks[2].store(ptr+sizeof(simd8)*2); + this->chunks[3].store(ptr+sizeof(simd8)*3); + } + + simdjson_inline uint64_t to_bitmask() const { + __m128i mask1 = __lsx_vmskltz_b(this->chunks[0]); + __m128i mask2 = __lsx_vmskltz_b(this->chunks[1]); + __m128i mask3 = __lsx_vmskltz_b(this->chunks[2]); + __m128i mask4 = __lsx_vmskltz_b(this->chunks[3]); + mask1 = __lsx_vilvl_h(mask2, mask1); + mask2 = __lsx_vilvl_h(mask4, mask3); + return __lsx_vpickve2gr_du(__lsx_vilvl_w(mask2, mask1), 0); + } + + simdjson_inline simd8 reduce_or() const { + return (this->chunks[0] | this->chunks[1]) | (this->chunks[2] | this->chunks[3]); + } + + simdjson_inline uint64_t eq(const T m) const { + const simd8 mask = simd8::splat(m); + return simd8x64( + this->chunks[0] == mask, + this->chunks[1] == mask, + this->chunks[2] == mask, + this->chunks[3] == mask + ).to_bitmask(); + } + + simdjson_inline uint64_t eq(const simd8x64 &other) const { + return simd8x64( + this->chunks[0] == other.chunks[0], + this->chunks[1] == other.chunks[1], + this->chunks[2] == other.chunks[2], + this->chunks[3] == other.chunks[3] + ).to_bitmask(); + } + + simdjson_inline uint64_t lteq(const T m) const { + const simd8 mask = simd8::splat(m); + return simd8x64( + this->chunks[0] <= mask, + this->chunks[1] <= mask, + this->chunks[2] <= mask, + this->chunks[3] <= mask + ).to_bitmask(); + } + }; // struct simd8x64 + +} // namespace simd +} // unnamed namespace +} // namespace lsx +} // namespace simdjson + +#endif // SIMDJSON_LSX_SIMD_H diff --git a/contrib/libs/simdjson/include/simdjson/lsx/stringparsing_defs.h b/contrib/libs/simdjson/include/simdjson/lsx/stringparsing_defs.h new file mode 100644 index 000000000000..af493dc55f64 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/lsx/stringparsing_defs.h @@ -0,0 +1,53 @@ +#ifndef SIMDJSON_LSX_STRINGPARSING_DEFS_H +#define SIMDJSON_LSX_STRINGPARSING_DEFS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/lsx/base.h" +#include "simdjson/lsx/simd.h" +#include "simdjson/lsx/bitmanipulation.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace lsx { +namespace { + +using namespace simd; + +// Holds backslashes and quotes locations. +struct backslash_and_quote { +public: + static constexpr uint32_t BYTES_PROCESSED = 32; + simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); + + simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; } + simdjson_inline bool has_backslash() { return bs_bits != 0; } + simdjson_inline int quote_index() { return trailing_zeroes(quote_bits); } + simdjson_inline int backslash_index() { return trailing_zeroes(bs_bits); } + + uint32_t bs_bits; + uint32_t quote_bits; +}; // struct backslash_and_quote + +simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { + // this can read up to 31 bytes beyond the buffer size, but we require + // SIMDJSON_PADDING of padding + static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "backslash and quote finder must process fewer than SIMDJSON_PADDING bytes"); + simd8 v0(src); + simd8 v1(src + sizeof(v0)); + v0.store(dst); + v1.store(dst + sizeof(v0)); + + // Getting a 64-bit bitmask is much cheaper than multiple 16-bit bitmasks on LSX; therefore, we + // smash them together into a 64-byte mask and get the bitmask from there. + uint64_t bs_and_quote = simd8x64(v0 == '\\', v1 == '\\', v0 == '"', v1 == '"').to_bitmask(); + return { + uint32_t(bs_and_quote), // bs_bits + uint32_t(bs_and_quote >> 32) // quote_bits + }; +} + +} // unnamed namespace +} // namespace lsx +} // namespace simdjson + +#endif // SIMDJSON_LSX_STRINGPARSING_DEFS_H diff --git a/contrib/libs/simdjson/include/simdjson/minify.h b/contrib/libs/simdjson/include/simdjson/minify.h new file mode 100644 index 000000000000..8b8f217e4f83 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/minify.h @@ -0,0 +1,30 @@ +#ifndef SIMDJSON_MINIFY_H +#define SIMDJSON_MINIFY_H + +#include "simdjson/base.h" +#include "simdjson/padded_string.h" +#include +#include +#include + +namespace simdjson { + +/** + * + * Minify the input string assuming that it represents a JSON string, does not parse or validate. + * This function is much faster than parsing a JSON string and then writing a minified version of it. + * However, it does not validate the input. It will merely return an error in simple cases (e.g., if + * there is a string that was never terminated). + * + * + * @param buf the json document to minify. + * @param len the length of the json document. + * @param dst the buffer to write the minified document to. *MUST* be allocated up to len bytes. + * @param dst_len the number of bytes written. Output only. + * @return the error code, or SUCCESS if there was no error. + */ +simdjson_warn_unused error_code minify(const char *buf, size_t len, char *dst, size_t &dst_len) noexcept; + +} // namespace simdjson + +#endif // SIMDJSON_MINIFY_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/ondemand.h b/contrib/libs/simdjson/include/simdjson/ondemand.h new file mode 100644 index 000000000000..d17615910d02 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/ondemand.h @@ -0,0 +1,13 @@ +#ifndef SIMDJSON_ONDEMAND_H +#define SIMDJSON_ONDEMAND_H + +#include "simdjson/builtin/ondemand.h" + +namespace simdjson { + /** + * @copydoc simdjson::builtin::ondemand + */ + namespace ondemand = builtin::ondemand; +} // namespace simdjson + +#endif // SIMDJSON_ONDEMAND_H diff --git a/contrib/libs/simdjson/include/simdjson/padded_string-inl.h b/contrib/libs/simdjson/include/simdjson/padded_string-inl.h new file mode 100644 index 000000000000..34cc76cb90be --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/padded_string-inl.h @@ -0,0 +1,198 @@ +#ifndef SIMDJSON_PADDED_STRING_INL_H +#define SIMDJSON_PADDED_STRING_INL_H + +#include "simdjson/padded_string.h" +#include "simdjson/padded_string_view.h" + +#include "simdjson/error-inl.h" +#include "simdjson/padded_string_view-inl.h" + +#include + +namespace simdjson { +namespace internal { + +// The allocate_padded_buffer function is a low-level function to allocate memory +// with padding so we can read past the "length" bytes safely. It is used by +// the padded_string class automatically. It returns nullptr in case +// of error: the caller should check for a null pointer. +// The length parameter is the maximum size in bytes of the string. +// The caller is responsible to free the memory (e.g., delete[] (...)). +inline char *allocate_padded_buffer(size_t length) noexcept { + const size_t totalpaddedlength = length + SIMDJSON_PADDING; + if(totalpaddedlength(1UL<<20)) { + return nullptr; + } +#endif + + char *padded_buffer = new (std::nothrow) char[totalpaddedlength]; + if (padded_buffer == nullptr) { + return nullptr; + } + // We write nulls in the padded region to avoid having uninitialized + // content which may trigger warning for some sanitizers + std::memset(padded_buffer + length, 0, totalpaddedlength - length); + return padded_buffer; +} // allocate_padded_buffer() + +} // namespace internal + + +inline padded_string::padded_string() noexcept = default; +inline padded_string::padded_string(size_t length) noexcept + : viable_size(length), data_ptr(internal::allocate_padded_buffer(length)) { +} +inline padded_string::padded_string(const char *data, size_t length) noexcept + : viable_size(length), data_ptr(internal::allocate_padded_buffer(length)) { + if ((data != nullptr) && (data_ptr != nullptr)) { + std::memcpy(data_ptr, data, length); + } + if (data_ptr == nullptr) { + viable_size = 0; + } +} +#ifdef __cpp_char8_t +inline padded_string::padded_string(const char8_t *data, size_t length) noexcept + : viable_size(length), data_ptr(internal::allocate_padded_buffer(length)) { + if ((data != nullptr) && (data_ptr != nullptr)) { + std::memcpy(data_ptr, reinterpret_cast(data), length); + } + if (data_ptr == nullptr) { + viable_size = 0; + } +} +#endif +// note: do not pass std::string arguments by value +inline padded_string::padded_string(const std::string & str_ ) noexcept + : viable_size(str_.size()), data_ptr(internal::allocate_padded_buffer(str_.size())) { + if (data_ptr == nullptr) { + viable_size = 0; + } else { + std::memcpy(data_ptr, str_.data(), str_.size()); + } +} +// note: do pass std::string_view arguments by value +inline padded_string::padded_string(std::string_view sv_) noexcept + : viable_size(sv_.size()), data_ptr(internal::allocate_padded_buffer(sv_.size())) { + if(simdjson_unlikely(!data_ptr)) { + //allocation failed or zero size + viable_size = 0; + return; + } + if (sv_.size()) { + std::memcpy(data_ptr, sv_.data(), sv_.size()); + } +} +inline padded_string::padded_string(padded_string &&o) noexcept + : viable_size(o.viable_size), data_ptr(o.data_ptr) { + o.data_ptr = nullptr; // we take ownership + o.viable_size = 0; +} + +inline padded_string &padded_string::operator=(padded_string &&o) noexcept { + delete[] data_ptr; + data_ptr = o.data_ptr; + viable_size = o.viable_size; + o.data_ptr = nullptr; // we take ownership + o.viable_size = 0; + return *this; +} + +inline void padded_string::swap(padded_string &o) noexcept { + size_t tmp_viable_size = viable_size; + char *tmp_data_ptr = data_ptr; + viable_size = o.viable_size; + data_ptr = o.data_ptr; + o.data_ptr = tmp_data_ptr; + o.viable_size = tmp_viable_size; +} + +inline padded_string::~padded_string() noexcept { + delete[] data_ptr; +} + +inline size_t padded_string::size() const noexcept { return viable_size; } + +inline size_t padded_string::length() const noexcept { return viable_size; } + +inline const char *padded_string::data() const noexcept { return data_ptr; } + +inline char *padded_string::data() noexcept { return data_ptr; } + +inline padded_string::operator std::string_view() const { return std::string_view(data(), length()); } + +inline padded_string::operator padded_string_view() const noexcept { + return padded_string_view(data(), length(), length() + SIMDJSON_PADDING); +} + +inline simdjson_result padded_string::load(std::string_view filename) noexcept { + // Open the file + SIMDJSON_PUSH_DISABLE_WARNINGS + SIMDJSON_DISABLE_DEPRECATED_WARNING // Disable CRT_SECURE warning on MSVC: manually verified this is safe + std::FILE *fp = std::fopen(filename.data(), "rb"); + SIMDJSON_POP_DISABLE_WARNINGS + + if (fp == nullptr) { + return IO_ERROR; + } + + // Get the file size + int ret; +#if SIMDJSON_VISUAL_STUDIO && !SIMDJSON_IS_32BITS + ret = _fseeki64(fp, 0, SEEK_END); +#else + ret = std::fseek(fp, 0, SEEK_END); +#endif // _WIN64 + if(ret < 0) { + std::fclose(fp); + return IO_ERROR; + } +#if SIMDJSON_VISUAL_STUDIO && !SIMDJSON_IS_32BITS + __int64 llen = _ftelli64(fp); + if(llen == -1L) { + std::fclose(fp); + return IO_ERROR; + } +#else + long llen = std::ftell(fp); + if((llen < 0) || (llen == LONG_MAX)) { + std::fclose(fp); + return IO_ERROR; + } +#endif + + // Allocate the padded_string + size_t len = static_cast(llen); + padded_string s(len); + if (s.data() == nullptr) { + std::fclose(fp); + return MEMALLOC; + } + + // Read the padded_string + std::rewind(fp); + size_t bytes_read = std::fread(s.data(), 1, len, fp); + if (std::fclose(fp) != 0 || bytes_read != len) { + return IO_ERROR; + } + + return s; +} + +} // namespace simdjson + +inline simdjson::padded_string operator ""_padded(const char *str, size_t len) { + return simdjson::padded_string(str, len); +} +#ifdef __cpp_char8_t +inline simdjson::padded_string operator ""_padded(const char8_t *str, size_t len) { + return simdjson::padded_string(reinterpret_cast(str), len); +} +#endif +#endif // SIMDJSON_PADDED_STRING_INL_H diff --git a/contrib/libs/simdjson/include/simdjson/padded_string.h b/contrib/libs/simdjson/include/simdjson/padded_string.h new file mode 100644 index 000000000000..b0ab06d2763c --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/padded_string.h @@ -0,0 +1,183 @@ +#ifndef SIMDJSON_PADDED_STRING_H +#define SIMDJSON_PADDED_STRING_H + +#include "simdjson/base.h" +#include "simdjson/error.h" + +#include "simdjson/error-inl.h" + +#include +#include +#include +#include + +namespace simdjson { + +class padded_string_view; + +/** + * String with extra allocation for ease of use with parser::parse() + * + * This is a move-only class, it cannot be copied. + */ +struct padded_string final { + + /** + * Create a new, empty padded string. + */ + explicit inline padded_string() noexcept; + /** + * Create a new padded string buffer. + * + * @param length the size of the string. + */ + explicit inline padded_string(size_t length) noexcept; + /** + * Create a new padded string by copying the given input. + * + * @param data the buffer to copy + * @param length the number of bytes to copy + */ + explicit inline padded_string(const char *data, size_t length) noexcept; +#ifdef __cpp_char8_t + explicit inline padded_string(const char8_t *data, size_t length) noexcept; +#endif + /** + * Create a new padded string by copying the given input. + * + * @param str_ the string to copy + */ + inline padded_string(const std::string & str_ ) noexcept; + /** + * Create a new padded string by copying the given input. + * + * @param sv_ the string to copy + */ + inline padded_string(std::string_view sv_) noexcept; + /** + * Move one padded string into another. + * + * The original padded string will be reduced to zero capacity. + * + * @param o the string to move. + */ + inline padded_string(padded_string &&o) noexcept; + /** + * Move one padded string into another. + * + * The original padded string will be reduced to zero capacity. + * + * @param o the string to move. + */ + inline padded_string &operator=(padded_string &&o) noexcept; + inline void swap(padded_string &o) noexcept; + ~padded_string() noexcept; + + /** + * The length of the string. + * + * Does not include padding. + */ + size_t size() const noexcept; + + /** + * The length of the string. + * + * Does not include padding. + */ + size_t length() const noexcept; + + /** + * The string data. + **/ + const char *data() const noexcept; + const uint8_t *u8data() const noexcept { return static_cast(static_cast(data_ptr));} + + /** + * The string data. + **/ + char *data() noexcept; + + /** + * Create a std::string_view with the same content. + */ + operator std::string_view() const; + + /** + * Create a padded_string_view with the same content. + */ + operator padded_string_view() const noexcept; + + /** + * Load this padded string from a file. + * + * ## Windows and Unicode + * + * Windows users who need to read files with non-ANSI characters in the + * name should set their code page to UTF-8 (65001) before calling this + * function. This should be the default with Windows 11 and better. + * Further, they may use the AreFileApisANSI function to determine whether + * the filename is interpreted using the ANSI or the system default OEM + * codepage, and they may call SetFileApisToOEM accordingly. + * + * @return IO_ERROR on error. Be mindful that on some 32-bit systems, + * the file size might be limited to 2 GB. + * + * @param path the path to the file. + **/ + inline static simdjson_result load(std::string_view path) noexcept; + +private: + padded_string &operator=(const padded_string &o) = delete; + padded_string(const padded_string &o) = delete; + + size_t viable_size{0}; + char *data_ptr{nullptr}; + +}; // padded_string + +/** + * Send padded_string instance to an output stream. + * + * @param out The output stream. + * @param s The padded_string instance. + * @throw if there is an error with the underlying output stream. simdjson itself will not throw. + */ +inline std::ostream& operator<<(std::ostream& out, const padded_string& s) { return out << s.data(); } + +#if SIMDJSON_EXCEPTIONS +/** + * Send padded_string instance to an output stream. + * + * @param out The output stream. + * @param s The padded_string instance. + * @throw simdjson_error if the result being printed has an error. If there is an error with the + * underlying output stream, that error will be propagated (simdjson_error will not be + * thrown). + */ +inline std::ostream& operator<<(std::ostream& out, simdjson_result &s) noexcept(false) { return out << s.value(); } +#endif + +} // namespace simdjson + +// This is deliberately outside of simdjson so that people get it without having to use the namespace +inline simdjson::padded_string operator ""_padded(const char *str, size_t len); +#ifdef __cpp_char8_t +inline simdjson::padded_string operator ""_padded(const char8_t *str, size_t len); +#endif + +namespace simdjson { +namespace internal { + +// The allocate_padded_buffer function is a low-level function to allocate memory +// with padding so we can read past the "length" bytes safely. It is used by +// the padded_string class automatically. It returns nullptr in case +// of error: the caller should check for a null pointer. +// The length parameter is the maximum size in bytes of the string. +// The caller is responsible to free the memory (e.g., delete[] (...)). +inline char *allocate_padded_buffer(size_t length) noexcept; + +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_PADDED_STRING_H diff --git a/contrib/libs/simdjson/include/simdjson/padded_string_view-inl.h b/contrib/libs/simdjson/include/simdjson/padded_string_view-inl.h new file mode 100644 index 000000000000..3c727dbcd630 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/padded_string_view-inl.h @@ -0,0 +1,64 @@ +#ifndef SIMDJSON_PADDED_STRING_VIEW_INL_H +#define SIMDJSON_PADDED_STRING_VIEW_INL_H + +#include "simdjson/padded_string_view.h" +#include "simdjson/error-inl.h" + +#include /* memcmp */ + +namespace simdjson { + +inline padded_string_view::padded_string_view(const char* s, size_t len, size_t capacity) noexcept + : std::string_view(s, len), _capacity(capacity) +{ + if(_capacity < len) { _capacity = len; } +} + +inline padded_string_view::padded_string_view(const uint8_t* s, size_t len, size_t capacity) noexcept + : padded_string_view(reinterpret_cast(s), len, capacity) +{ +} +#ifdef __cpp_char8_t +inline padded_string_view::padded_string_view(const char8_t* s, size_t len, size_t capacity) noexcept + : padded_string_view(reinterpret_cast(s), len, capacity) +{ +} +#endif +inline padded_string_view::padded_string_view(const std::string &s) noexcept + : std::string_view(s), _capacity(s.capacity()) +{ +} + +inline padded_string_view::padded_string_view(std::string_view s, size_t capacity) noexcept + : std::string_view(s), _capacity(capacity) +{ + if(_capacity < s.length()) { _capacity = s.length(); } +} + +inline size_t padded_string_view::capacity() const noexcept { return _capacity; } + +inline size_t padded_string_view::padding() const noexcept { return capacity() - length(); } + +inline bool padded_string_view::remove_utf8_bom() noexcept { + if(length() < 3) { return false; } + if (std::memcmp(data(), "\xEF\xBB\xBF", 3) == 0) { + remove_prefix(3); + _capacity -= 3; + return true; + } + return false; +} + +#if SIMDJSON_EXCEPTIONS +inline std::ostream& operator<<(std::ostream& out, simdjson_result &s) noexcept(false) { return out << s.value(); } +#endif + +inline padded_string_view pad(std::string& s) noexcept { + const auto len = s.size(); + s.append(SIMDJSON_PADDING, ' '); + return padded_string_view(s.data(), len, s.size()); +} +} // namespace simdjson + + +#endif // SIMDJSON_PADDED_STRING_VIEW_INL_H diff --git a/contrib/libs/simdjson/include/simdjson/padded_string_view.h b/contrib/libs/simdjson/include/simdjson/padded_string_view.h new file mode 100644 index 000000000000..190c98fec729 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/padded_string_view.h @@ -0,0 +1,97 @@ +#ifndef SIMDJSON_PADDED_STRING_VIEW_H +#define SIMDJSON_PADDED_STRING_VIEW_H + +#include "simdjson/portability.h" +#include "simdjson/base.h" // for SIMDJSON_PADDING +#include "simdjson/error.h" + +#include +#include +#include +#include + +namespace simdjson { + +/** + * User-provided string that promises it has extra padded bytes at the end for use with parser::parse(). + */ +class padded_string_view : public std::string_view { +private: + size_t _capacity; + +public: + /** Create an empty padded_string_view. */ + inline padded_string_view() noexcept = default; + + /** + * Promise the given buffer has at least SIMDJSON_PADDING extra bytes allocated to it. + * + * @param s The string. + * @param len The length of the string (not including padding). + * @param capacity The allocated length of the string, including padding. If the capacity is less + * than the length, the capacity will be set to the length. + */ + explicit inline padded_string_view(const char* s, size_t len, size_t capacity) noexcept; + /** overload explicit inline padded_string_view(const char* s, size_t len) noexcept */ + explicit inline padded_string_view(const uint8_t* s, size_t len, size_t capacity) noexcept; +#ifdef __cpp_char8_t + explicit inline padded_string_view(const char8_t* s, size_t len, size_t capacity) noexcept; +#endif + /** + * Promise the given string has at least SIMDJSON_PADDING extra bytes allocated to it. + * + * The capacity of the string will be used to determine its padding. + * + * @param s The string. + */ + explicit inline padded_string_view(const std::string &s) noexcept; + + /** + * Promise the given string_view has at least SIMDJSON_PADDING extra bytes allocated to it. + * + * @param s The string. + * @param capacity The allocated length of the string, including padding. If the capacity is less + * than the length, the capacity will be set to the length. + */ + explicit inline padded_string_view(std::string_view s, size_t capacity) noexcept; + + /** The number of allocated bytes. */ + inline size_t capacity() const noexcept; + + /** + * Remove the UTF-8 Byte Order Mark (BOM) if it exists. + * + * @return whether a BOM was found and removed + */ + inline bool remove_utf8_bom() noexcept; + + /** The amount of padding on the string (capacity() - length()) */ + inline size_t padding() const noexcept; + +}; // padded_string_view + +#if SIMDJSON_EXCEPTIONS +/** + * Send padded_string instance to an output stream. + * + * @param out The output stream. + * @param s The padded_string_view. + * @throw simdjson_error if the result being printed has an error. If there is an error with the + * underlying output stream, that error will be propagated (simdjson_error will not be + * thrown). + */ +inline std::ostream& operator<<(std::ostream& out, simdjson_result &s) noexcept(false); +#endif + +/** + * Create a padded_string_view from a string. The string will be padded with SIMDJSON_PADDING + * space characters. The resulting padded_string_view will have a length equal to the original + * string. + * + * @param s The string. + * @return The padded string. + */ +inline padded_string_view pad(std::string& s) noexcept; +} // namespace simdjson + +#endif // SIMDJSON_PADDED_STRING_VIEW_H diff --git a/contrib/libs/simdjson/include/simdjson/portability.h b/contrib/libs/simdjson/include/simdjson/portability.h new file mode 100644 index 000000000000..5826903db827 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/portability.h @@ -0,0 +1,243 @@ +#ifndef SIMDJSON_PORTABILITY_H +#define SIMDJSON_PORTABILITY_H + +#include +#include +#include +#include +#include +#ifndef _WIN32 +// strcasecmp, strncasecmp +#include +#endif + +// We are using size_t without namespace std:: throughout the project +using std::size_t; + +#ifdef _MSC_VER +#define SIMDJSON_VISUAL_STUDIO 1 +/** + * We want to differentiate carefully between + * clang under visual studio and regular visual + * studio. + * + * Under clang for Windows, we enable: + * * target pragmas so that part and only part of the + * code gets compiled for advanced instructions. + * + */ +#ifdef __clang__ +// clang under visual studio +#define SIMDJSON_CLANG_VISUAL_STUDIO 1 +#else +// just regular visual studio (best guess) +#define SIMDJSON_REGULAR_VISUAL_STUDIO 1 +#endif // __clang__ +#endif // _MSC_VER + +#if (defined(__x86_64__) || defined(_M_AMD64)) && !defined(_M_ARM64EC) +#define SIMDJSON_IS_X86_64 1 +#elif defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) +#define SIMDJSON_IS_ARM64 1 +#elif defined(__riscv) && __riscv_xlen == 64 +#define SIMDJSON_IS_RISCV64 1 +#elif defined(__loongarch_lp64) +#define SIMDJSON_IS_LOONGARCH64 1 +#elif defined(__PPC64__) || defined(_M_PPC64) +#if defined(__ALTIVEC__) +#define SIMDJSON_IS_PPC64_VMX 1 +#endif // defined(__ALTIVEC__) +#else +#define SIMDJSON_IS_32BITS 1 + +#if defined(_M_IX86) || defined(__i386__) +#define SIMDJSON_IS_X86_32BITS 1 +#elif defined(__arm__) || defined(_M_ARM) +#define SIMDJSON_IS_ARM_32BITS 1 +#elif defined(__PPC__) || defined(_M_PPC) +#define SIMDJSON_IS_PPC_32BITS 1 +#endif + +#endif // defined(__x86_64__) || defined(_M_AMD64) +#ifndef SIMDJSON_IS_32BITS +#define SIMDJSON_IS_32BITS 0 +#endif + +#if SIMDJSON_IS_32BITS +#ifndef SIMDJSON_NO_PORTABILITY_WARNING +// In the future, we should allow programmers +// to get warning. +#endif // SIMDJSON_NO_PORTABILITY_WARNING +#endif // SIMDJSON_IS_32BITS + +#define SIMDJSON_CAT_IMPLEMENTATION_(a,...) a ## __VA_ARGS__ +#define SIMDJSON_CAT(a,...) SIMDJSON_CAT_IMPLEMENTATION_(a, __VA_ARGS__) + +#define SIMDJSON_STRINGIFY_IMPLEMENTATION_(a,...) #a SIMDJSON_STRINGIFY(__VA_ARGS__) +#define SIMDJSON_STRINGIFY(a,...) SIMDJSON_CAT_IMPLEMENTATION_(a, __VA_ARGS__) + +// this is almost standard? +#undef SIMDJSON_STRINGIFY_IMPLEMENTATION_ +#undef SIMDJSON_STRINGIFY +#define SIMDJSON_STRINGIFY_IMPLEMENTATION_(a) #a +#define SIMDJSON_STRINGIFY(a) SIMDJSON_STRINGIFY_IMPLEMENTATION_(a) + +// Our fast kernels require 64-bit systems. +// +// On 32-bit x86, we lack 64-bit popcnt, lzcnt, blsr instructions. +// Furthermore, the number of SIMD registers is reduced. +// +// On 32-bit ARM, we would have smaller registers. +// +// The simdjson users should still have the fallback kernel. It is +// slower, but it should run everywhere. + +// +// Enable valid runtime implementations, and select SIMDJSON_BUILTIN_IMPLEMENTATION +// + +// We are going to use runtime dispatch. +#if SIMDJSON_IS_X86_64 +#ifdef __clang__ +// clang does not have GCC push pop +// warning: clang attribute push can't be used within a namespace in clang up +// til 8.0 so SIMDJSON_TARGET_REGION and SIMDJSON_UNTARGET_REGION must be *outside* of a +// namespace. +#define SIMDJSON_TARGET_REGION(T) \ + _Pragma(SIMDJSON_STRINGIFY( \ + clang attribute push(__attribute__((target(T))), apply_to = function))) +#define SIMDJSON_UNTARGET_REGION _Pragma("clang attribute pop") +#elif defined(__GNUC__) +// GCC is easier +#define SIMDJSON_TARGET_REGION(T) \ + _Pragma("GCC push_options") _Pragma(SIMDJSON_STRINGIFY(GCC target(T))) +#define SIMDJSON_UNTARGET_REGION _Pragma("GCC pop_options") +#endif // clang then gcc + +#endif // x86 + +// Default target region macros don't do anything. +#ifndef SIMDJSON_TARGET_REGION +#define SIMDJSON_TARGET_REGION(T) +#define SIMDJSON_UNTARGET_REGION +#endif + +// Is threading enabled? +#if defined(_REENTRANT) || defined(_MT) +#ifndef SIMDJSON_THREADS_ENABLED +#define SIMDJSON_THREADS_ENABLED +#endif +#endif + +// workaround for large stack sizes under -O0. +// https://github.com/simdjson/simdjson/issues/691 +#ifdef __APPLE__ +#ifndef __OPTIMIZE__ +// Apple systems have small stack sizes in secondary threads. +// Lack of compiler optimization may generate high stack usage. +// Users may want to disable threads for safety, but only when +// in debug mode which we detect by the fact that the __OPTIMIZE__ +// macro is not defined. +#undef SIMDJSON_THREADS_ENABLED +#endif +#endif + + +#if defined(__clang__) +#define SIMDJSON_NO_SANITIZE_UNDEFINED __attribute__((no_sanitize("undefined"))) +#elif defined(__GNUC__) +#define SIMDJSON_NO_SANITIZE_UNDEFINED __attribute__((no_sanitize_undefined)) +#else +#define SIMDJSON_NO_SANITIZE_UNDEFINED +#endif + +#if defined(__clang__) || defined(__GNUC__) +#define simdjson_pure [[gnu::pure]] +#else +#define simdjson_pure +#endif + +#if defined(__clang__) || defined(__GNUC__) +#if defined(__has_feature) +# if __has_feature(memory_sanitizer) +#define SIMDJSON_NO_SANITIZE_MEMORY __attribute__((no_sanitize("memory"))) +# endif // if __has_feature(memory_sanitizer) +#endif // defined(__has_feature) +#endif +// make sure it is defined as 'nothing' if it is unapplicable. +#ifndef SIMDJSON_NO_SANITIZE_MEMORY +#define SIMDJSON_NO_SANITIZE_MEMORY +#endif + +#if SIMDJSON_VISUAL_STUDIO +// This is one case where we do not distinguish between +// regular visual studio and clang under visual studio. +// clang under Windows has _stricmp (like visual studio) but not strcasecmp (as clang normally has) +#define simdjson_strcasecmp _stricmp +#define simdjson_strncasecmp _strnicmp +#else +// The strcasecmp, strncasecmp, and strcasestr functions do not work with multibyte strings (e.g. UTF-8). +// So they are only useful for ASCII in our context. +// https://www.gnu.org/software/libunistring/manual/libunistring.html#char-_002a-strings +#define simdjson_strcasecmp strcasecmp +#define simdjson_strncasecmp strncasecmp +#endif + +#if defined(NDEBUG) || defined(__OPTIMIZE__) || (defined(_MSC_VER) && !defined(_DEBUG)) +// If NDEBUG is set, or __OPTIMIZE__ is set, or we are under MSVC in release mode, +// then do away with asserts and use __assume. +#if SIMDJSON_VISUAL_STUDIO +#define SIMDJSON_UNREACHABLE() __assume(0) +#define SIMDJSON_ASSUME(COND) __assume(COND) +#else +#define SIMDJSON_UNREACHABLE() __builtin_unreachable(); +#define SIMDJSON_ASSUME(COND) do { if (!(COND)) __builtin_unreachable(); } while (0) +#endif + +#else // defined(NDEBUG) || defined(__OPTIMIZE__) || (defined(_MSC_VER) && !defined(_DEBUG)) +// This should only ever be enabled in debug mode. +#define SIMDJSON_UNREACHABLE() assert(0); +#define SIMDJSON_ASSUME(COND) assert(COND) + +#endif + + + +#if defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__ +#define SIMDJSON_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#elif defined _WIN32 +#define SIMDJSON_IS_BIG_ENDIAN 0 +#else +#if defined(__APPLE__) || defined(__FreeBSD__) +#include +#elif defined(sun) || defined(__sun) +#error #include +#elif defined(__MVS__) +#include +#else +#ifdef __has_include +#if __has_include() +#include +#endif //__has_include() +#endif //__has_include +#endif +# +#ifndef __BYTE_ORDER__ +// safe choice +#define SIMDJSON_IS_BIG_ENDIAN 0 +#endif +# +#ifndef __ORDER_LITTLE_ENDIAN__ +// safe choice +#define SIMDJSON_IS_BIG_ENDIAN 0 +#endif +# +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define SIMDJSON_IS_BIG_ENDIAN 0 +#else +#define SIMDJSON_IS_BIG_ENDIAN 1 +#endif +#endif + + +#endif // SIMDJSON_PORTABILITY_H diff --git a/contrib/libs/simdjson/include/simdjson/ppc64.h b/contrib/libs/simdjson/include/simdjson/ppc64.h new file mode 100644 index 000000000000..8f563cf8d86e --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/ppc64.h @@ -0,0 +1,8 @@ +#ifndef SIMDJSON_PPC64_H +#define SIMDJSON_PPC64_H + +#error #include "simdjson/ppc64/begin.h" +#include "simdjson/generic/amalgamated.h" +#error #include "simdjson/ppc64/end.h" + +#endif // SIMDJSON_PPC64_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/ppc64/base.h b/contrib/libs/simdjson/include/simdjson/ppc64/base.h new file mode 100644 index 000000000000..6e460dfb8e88 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/ppc64/base.h @@ -0,0 +1,26 @@ +#ifndef SIMDJSON_PPC64_BASE_H +#define SIMDJSON_PPC64_BASE_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +/** + * Implementation for ALTIVEC (PPC64). + */ +namespace ppc64 { + +class implementation; + +namespace { +namespace simd { +template struct simd8; +template struct simd8x64; +} // namespace simd +} // unnamed namespace + +} // namespace ppc64 +} // namespace simdjson + +#endif // SIMDJSON_PPC64_BASE_H diff --git a/contrib/libs/simdjson/include/simdjson/ppc64/begin.h b/contrib/libs/simdjson/include/simdjson/ppc64/begin.h new file mode 100644 index 000000000000..36a8d4299698 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/ppc64/begin.h @@ -0,0 +1,10 @@ +#define SIMDJSON_IMPLEMENTATION ppc64 +#error #include "simdjson/ppc64/base.h" +#error #include "simdjson/ppc64/intrinsics.h" +#error #include "simdjson/ppc64/bitmanipulation.h" +#error #include "simdjson/ppc64/bitmask.h" +#error #include "simdjson/ppc64/numberparsing_defs.h" +#error #include "simdjson/ppc64/simd.h" +#error #include "simdjson/ppc64/stringparsing_defs.h" + +#define SIMDJSON_SKIP_BACKSLASH_SHORT_CIRCUIT 1 \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/ppc64/bitmanipulation.h b/contrib/libs/simdjson/include/simdjson/ppc64/bitmanipulation.h new file mode 100644 index 000000000000..b2dda09ceadf --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/ppc64/bitmanipulation.h @@ -0,0 +1,78 @@ +#ifndef SIMDJSON_PPC64_BITMANIPULATION_H +#define SIMDJSON_PPC64_BITMANIPULATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#error #include "simdjson/ppc64/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace ppc64 { +namespace { + +// We sometimes call trailing_zero on inputs that are zero, +// but the algorithms do not end up using the returned value. +// Sadly, sanitizers are not smart enough to figure it out. +SIMDJSON_NO_SANITIZE_UNDEFINED +// This function can be used safely even if not all bytes have been +// initialized. +// See issue https://github.com/simdjson/simdjson/issues/1965 +SIMDJSON_NO_SANITIZE_MEMORY +simdjson_inline int trailing_zeroes(uint64_t input_num) { +#if SIMDJSON_REGULAR_VISUAL_STUDIO + unsigned long ret; + // Search the mask data from least significant bit (LSB) + // to the most significant bit (MSB) for a set bit (1). + _BitScanForward64(&ret, input_num); + return (int)ret; +#else // SIMDJSON_REGULAR_VISUAL_STUDIO + return __builtin_ctzll(input_num); +#endif // SIMDJSON_REGULAR_VISUAL_STUDIO +} + +/* result might be undefined when input_num is zero */ +simdjson_inline uint64_t clear_lowest_bit(uint64_t input_num) { + return input_num & (input_num - 1); +} + +/* result might be undefined when input_num is zero */ +simdjson_inline int leading_zeroes(uint64_t input_num) { +#if SIMDJSON_REGULAR_VISUAL_STUDIO + unsigned long leading_zero = 0; + // Search the mask data from most significant bit (MSB) + // to least significant bit (LSB) for a set bit (1). + if (_BitScanReverse64(&leading_zero, input_num)) + return (int)(63 - leading_zero); + else + return 64; +#else + return __builtin_clzll(input_num); +#endif // SIMDJSON_REGULAR_VISUAL_STUDIO +} + +#if SIMDJSON_REGULAR_VISUAL_STUDIO +simdjson_inline int count_ones(uint64_t input_num) { + // note: we do not support legacy 32-bit Windows in this kernel + return __popcnt64(input_num); // Visual Studio wants two underscores +} +#else +simdjson_inline int count_ones(uint64_t input_num) { + return __builtin_popcountll(input_num); +} +#endif + +simdjson_inline bool add_overflow(uint64_t value1, uint64_t value2, + uint64_t *result) { +#if SIMDJSON_REGULAR_VISUAL_STUDIO + *result = value1 + value2; + return *result < value1; +#else + return __builtin_uaddll_overflow(value1, value2, + reinterpret_cast(result)); +#endif +} + +} // unnamed namespace +} // namespace ppc64 +} // namespace simdjson + +#endif // SIMDJSON_PPC64_BITMANIPULATION_H diff --git a/contrib/libs/simdjson/include/simdjson/ppc64/bitmask.h b/contrib/libs/simdjson/include/simdjson/ppc64/bitmask.h new file mode 100644 index 000000000000..25714ae133d4 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/ppc64/bitmask.h @@ -0,0 +1,46 @@ +#ifndef SIMDJSON_PPC64_BITMASK_H +#define SIMDJSON_PPC64_BITMASK_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#error #include "simdjson/ppc64/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace ppc64 { +namespace { + +// +// Perform a "cumulative bitwise xor," flipping bits each time a 1 is +// encountered. +// +// For example, prefix_xor(00100100) == 00011100 +// +simdjson_inline uint64_t prefix_xor(uint64_t bitmask) { + // You can use the version below, however gcc sometimes miscompiles + // vec_pmsum_be, it happens somewhere around between 8 and 9th version. + // The performance boost was not noticeable, falling back to a usual + // implementation. + // __vector unsigned long long all_ones = {~0ull, ~0ull}; + // __vector unsigned long long mask = {bitmask, 0}; + // // Clang and GCC return different values for pmsum for ull so cast it to one. + // // Generally it is not specified by ALTIVEC ISA what is returned by + // // vec_pmsum_be. + // #if defined(__LITTLE_ENDIAN__) + // return (uint64_t)(((__vector unsigned long long)vec_pmsum_be(all_ones, mask))[0]); + // #else + // return (uint64_t)(((__vector unsigned long long)vec_pmsum_be(all_ones, mask))[1]); + // #endif + bitmask ^= bitmask << 1; + bitmask ^= bitmask << 2; + bitmask ^= bitmask << 4; + bitmask ^= bitmask << 8; + bitmask ^= bitmask << 16; + bitmask ^= bitmask << 32; + return bitmask; +} + +} // unnamed namespace +} // namespace ppc64 +} // namespace simdjson + +#endif diff --git a/contrib/libs/simdjson/include/simdjson/ppc64/end.h b/contrib/libs/simdjson/include/simdjson/ppc64/end.h new file mode 100644 index 000000000000..701538b651d4 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/ppc64/end.h @@ -0,0 +1,6 @@ +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#error #include "simdjson/ppc64/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#undef SIMDJSON_SKIP_BACKSLASH_SHORT_CIRCUIT +#undef SIMDJSON_IMPLEMENTATION diff --git a/contrib/libs/simdjson/include/simdjson/ppc64/implementation.h b/contrib/libs/simdjson/include/simdjson/ppc64/implementation.h new file mode 100644 index 000000000000..505581f0cd2e --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/ppc64/implementation.h @@ -0,0 +1,40 @@ +#ifndef SIMDJSON_PPC64_IMPLEMENTATION_H +#define SIMDJSON_PPC64_IMPLEMENTATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#error #include "simdjson/ppc64/base.h" +#include "simdjson/implementation.h" +#include "simdjson/internal/instruction_set.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { + +/** + * Implementation for ALTIVEC (PPC64). + */ +namespace ppc64 { + +/** + * @private + */ +class implementation final : public simdjson::implementation { +public: + simdjson_inline implementation() + : simdjson::implementation("ppc64", "PPC64 ALTIVEC", + internal::instruction_set::ALTIVEC) {} + + simdjson_warn_unused error_code create_dom_parser_implementation( + size_t capacity, size_t max_length, + std::unique_ptr &dst) + const noexcept final; + simdjson_warn_unused error_code minify(const uint8_t *buf, size_t len, + uint8_t *dst, + size_t &dst_len) const noexcept final; + simdjson_warn_unused bool validate_utf8(const char *buf, + size_t len) const noexcept final; +}; + +} // namespace ppc64 +} // namespace simdjson + +#endif // SIMDJSON_PPC64_IMPLEMENTATION_H diff --git a/contrib/libs/simdjson/include/simdjson/ppc64/intrinsics.h b/contrib/libs/simdjson/include/simdjson/ppc64/intrinsics.h new file mode 100644 index 000000000000..96d90eaae3c0 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/ppc64/intrinsics.h @@ -0,0 +1,23 @@ +#ifndef SIMDJSON_PPC64_INTRINSICS_H +#define SIMDJSON_PPC64_INTRINSICS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#error #include "simdjson/ppc64/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +// This should be the correct header whether +// you use visual studio or other compilers. +#include + +// These are defined by altivec.h in GCC toolchain, it is safe to undef them. +#ifdef bool +#undef bool +#endif + +#ifdef vector +#undef vector +#endif + +static_assert(sizeof(__vector unsigned char) <= simdjson::SIMDJSON_PADDING, "insufficient padding for ppc64"); + +#endif // SIMDJSON_PPC64_INTRINSICS_H diff --git a/contrib/libs/simdjson/include/simdjson/ppc64/numberparsing_defs.h b/contrib/libs/simdjson/include/simdjson/ppc64/numberparsing_defs.h new file mode 100644 index 000000000000..b41082cdcea4 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/ppc64/numberparsing_defs.h @@ -0,0 +1,71 @@ +#ifndef SIMDJSON_PPC64_NUMBERPARSING_DEFS_H +#define SIMDJSON_PPC64_NUMBERPARSING_DEFS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#error #include "simdjson/ppc64/base.h" +#error #include "simdjson/ppc64/intrinsics.h" +#include "simdjson/internal/numberparsing_tables.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include + +#if defined(__linux__) +#include +#elif defined(__FreeBSD__) +#include +#endif + +namespace simdjson { +namespace ppc64 { +namespace numberparsing { + +// we don't have appropriate instructions, so let us use a scalar function +// credit: https://johnnylee-sde.github.io/Fast-numeric-string-to-int/ +/** @private */ +static simdjson_inline uint32_t parse_eight_digits_unrolled(const uint8_t *chars) { + uint64_t val; + std::memcpy(&val, chars, sizeof(uint64_t)); +#ifdef __BIG_ENDIAN__ +#if defined(__linux__) + val = bswap_64(val); +#elif defined(__FreeBSD__) + val = bswap64(val); +#endif +#endif + val = (val & 0x0F0F0F0F0F0F0F0F) * 2561 >> 8; + val = (val & 0x00FF00FF00FF00FF) * 6553601 >> 16; + return uint32_t((val & 0x0000FFFF0000FFFF) * 42949672960001 >> 32); +} + +/** @private */ +simdjson_inline internal::value128 full_multiplication(uint64_t value1, uint64_t value2) { + internal::value128 answer; +#if SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS +#if SIMDJSON_IS_ARM64 + // ARM64 has native support for 64-bit multiplications, no need to emultate + answer.high = __umulh(value1, value2); + answer.low = value1 * value2; +#else + answer.low = _umul128(value1, value2, &answer.high); // _umul128 not available on ARM64 +#endif // SIMDJSON_IS_ARM64 +#else // SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS + __uint128_t r = (static_cast<__uint128_t>(value1)) * value2; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); +#endif + return answer; +} + +} // namespace numberparsing +} // namespace ppc64 +} // namespace simdjson + +#ifndef SIMDJSON_SWAR_NUMBER_PARSING +#if SIMDJSON_IS_BIG_ENDIAN +#define SIMDJSON_SWAR_NUMBER_PARSING 0 +#else +#define SIMDJSON_SWAR_NUMBER_PARSING 1 +#endif +#endif + +#endif // SIMDJSON_PPC64_NUMBERPARSING_DEFS_H diff --git a/contrib/libs/simdjson/include/simdjson/ppc64/ondemand.h b/contrib/libs/simdjson/include/simdjson/ppc64/ondemand.h new file mode 100644 index 000000000000..a9df4b5971c3 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/ppc64/ondemand.h @@ -0,0 +1,8 @@ +#ifndef SIMDJSON_PPC64_ONDEMAND_H +#define SIMDJSON_PPC64_ONDEMAND_H + +#error #include "simdjson/ppc64/begin.h" +#include "simdjson/generic/ondemand/amalgamated.h" +#error #include "simdjson/ppc64/end.h" + +#endif // SIMDJSON_PPC64_ONDEMAND_H diff --git a/contrib/libs/simdjson/include/simdjson/ppc64/simd.h b/contrib/libs/simdjson/include/simdjson/ppc64/simd.h new file mode 100644 index 000000000000..d20fe9c6f3ce --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/ppc64/simd.h @@ -0,0 +1,472 @@ +#ifndef SIMDJSON_PPC64_SIMD_H +#define SIMDJSON_PPC64_SIMD_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#error #include "simdjson/ppc64/base.h" +#error #include "simdjson/ppc64/bitmanipulation.h" +#include "simdjson/internal/simdprune_tables.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include + +namespace simdjson { +namespace ppc64 { +namespace { +namespace simd { + +using __m128i = __vector unsigned char; + +template struct base { + __m128i value; + + // Zero constructor + simdjson_inline base() : value{__m128i()} {} + + // Conversion from SIMD register + simdjson_inline base(const __m128i _value) : value(_value) {} + + // Conversion to SIMD register + simdjson_inline operator const __m128i &() const { + return this->value; + } + simdjson_inline operator __m128i &() { return this->value; } + + // Bit operations + simdjson_inline Child operator|(const Child other) const { + return vec_or(this->value, (__m128i)other); + } + simdjson_inline Child operator&(const Child other) const { + return vec_and(this->value, (__m128i)other); + } + simdjson_inline Child operator^(const Child other) const { + return vec_xor(this->value, (__m128i)other); + } + simdjson_inline Child bit_andnot(const Child other) const { + return vec_andc(this->value, (__m128i)other); + } + simdjson_inline Child &operator|=(const Child other) { + auto this_cast = static_cast(this); + *this_cast = *this_cast | other; + return *this_cast; + } + simdjson_inline Child &operator&=(const Child other) { + auto this_cast = static_cast(this); + *this_cast = *this_cast & other; + return *this_cast; + } + simdjson_inline Child &operator^=(const Child other) { + auto this_cast = static_cast(this); + *this_cast = *this_cast ^ other; + return *this_cast; + } +}; + +template > +struct base8 : base> { + typedef uint16_t bitmask_t; + typedef uint32_t bitmask2_t; + + simdjson_inline base8() : base>() {} + simdjson_inline base8(const __m128i _value) : base>(_value) {} + + friend simdjson_inline Mask operator==(const simd8 lhs, const simd8 rhs) { + return (__m128i)vec_cmpeq(lhs.value, (__m128i)rhs); + } + + static const int SIZE = sizeof(base>::value); + + template + simdjson_inline simd8 prev(simd8 prev_chunk) const { + __m128i chunk = this->value; +#ifdef __LITTLE_ENDIAN__ + chunk = (__m128i)vec_reve(this->value); + prev_chunk = (__m128i)vec_reve((__m128i)prev_chunk); +#endif + chunk = (__m128i)vec_sld((__m128i)prev_chunk, (__m128i)chunk, 16 - N); +#ifdef __LITTLE_ENDIAN__ + chunk = (__m128i)vec_reve((__m128i)chunk); +#endif + return chunk; + } +}; + +// SIMD byte mask type (returned by things like eq and gt) +template <> struct simd8 : base8 { + static simdjson_inline simd8 splat(bool _value) { + return (__m128i)vec_splats((unsigned char)(-(!!_value))); + } + + simdjson_inline simd8() : base8() {} + simdjson_inline simd8(const __m128i _value) + : base8(_value) {} + // Splat constructor + simdjson_inline simd8(bool _value) + : base8(splat(_value)) {} + + simdjson_inline int to_bitmask() const { + __vector unsigned long long result; + const __m128i perm_mask = {0x78, 0x70, 0x68, 0x60, 0x58, 0x50, 0x48, 0x40, + 0x38, 0x30, 0x28, 0x20, 0x18, 0x10, 0x08, 0x00}; + + result = ((__vector unsigned long long)vec_vbpermq((__m128i)this->value, + (__m128i)perm_mask)); +#ifdef __LITTLE_ENDIAN__ + return static_cast(result[1]); +#else + return static_cast(result[0]); +#endif + } + simdjson_inline bool any() const { + return !vec_all_eq(this->value, (__m128i)vec_splats(0)); + } + simdjson_inline simd8 operator~() const { + return this->value ^ (__m128i)splat(true); + } +}; + +template struct base8_numeric : base8 { + static simdjson_inline simd8 splat(T value) { + (void)value; + return (__m128i)vec_splats(value); + } + static simdjson_inline simd8 zero() { return splat(0); } + static simdjson_inline simd8 load(const T values[16]) { + return (__m128i)(vec_vsx_ld(0, reinterpret_cast(values))); + } + // Repeat 16 values as many times as necessary (usually for lookup tables) + static simdjson_inline simd8 repeat_16(T v0, T v1, T v2, T v3, T v4, + T v5, T v6, T v7, T v8, T v9, + T v10, T v11, T v12, T v13, + T v14, T v15) { + return simd8(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14, v15); + } + + simdjson_inline base8_numeric() : base8() {} + simdjson_inline base8_numeric(const __m128i _value) + : base8(_value) {} + + // Store to array + simdjson_inline void store(T dst[16]) const { + vec_vsx_st(this->value, 0, reinterpret_cast<__m128i *>(dst)); + } + + // Override to distinguish from bool version + simdjson_inline simd8 operator~() const { return *this ^ 0xFFu; } + + // Addition/subtraction are the same for signed and unsigned + simdjson_inline simd8 operator+(const simd8 other) const { + return (__m128i)((__m128i)this->value + (__m128i)other); + } + simdjson_inline simd8 operator-(const simd8 other) const { + return (__m128i)((__m128i)this->value - (__m128i)other); + } + simdjson_inline simd8 &operator+=(const simd8 other) { + *this = *this + other; + return *static_cast *>(this); + } + simdjson_inline simd8 &operator-=(const simd8 other) { + *this = *this - other; + return *static_cast *>(this); + } + + // Perform a lookup assuming the value is between 0 and 16 (undefined behavior + // for out of range values) + template + simdjson_inline simd8 lookup_16(simd8 lookup_table) const { + return (__m128i)vec_perm((__m128i)lookup_table, (__m128i)lookup_table, this->value); + } + + // Copies to 'output" all bytes corresponding to a 0 in the mask (interpreted + // as a bitset). Passing a 0 value for mask would be equivalent to writing out + // every byte to output. Only the first 16 - count_ones(mask) bytes of the + // result are significant but 16 bytes get written. Design consideration: it + // seems like a function with the signature simd8 compress(uint32_t mask) + // would be sensible, but the AVX ISA makes this kind of approach difficult. + template + simdjson_inline void compress(uint16_t mask, L *output) const { + using internal::BitsSetTable256mul2; + using internal::pshufb_combine_table; + using internal::thintable_epi8; + // this particular implementation was inspired by work done by @animetosho + // we do it in two steps, first 8 bytes and then second 8 bytes + uint8_t mask1 = uint8_t(mask); // least significant 8 bits + uint8_t mask2 = uint8_t(mask >> 8); // most significant 8 bits + // next line just loads the 64-bit values thintable_epi8[mask1] and + // thintable_epi8[mask2] into a 128-bit register, using only + // two instructions on most compilers. +#ifdef __LITTLE_ENDIAN__ + __m128i shufmask = (__m128i)(__vector unsigned long long){ + thintable_epi8[mask1], thintable_epi8[mask2]}; +#else + __m128i shufmask = (__m128i)(__vector unsigned long long){ + thintable_epi8[mask2], thintable_epi8[mask1]}; + shufmask = (__m128i)vec_reve((__m128i)shufmask); +#endif + // we increment by 0x08 the second half of the mask + shufmask = ((__m128i)shufmask) + + ((__m128i)(__vector int){0, 0, 0x08080808, 0x08080808}); + + // this is the version "nearly pruned" + __m128i pruned = vec_perm(this->value, this->value, shufmask); + // we still need to put the two halves together. + // we compute the popcount of the first half: + int pop1 = BitsSetTable256mul2[mask1]; + // then load the corresponding mask, what it does is to write + // only the first pop1 bytes from the first 8 bytes, and then + // it fills in with the bytes from the second 8 bytes + some filling + // at the end. + __m128i compactmask = + vec_vsx_ld(0, reinterpret_cast(pshufb_combine_table + pop1 * 8)); + __m128i answer = vec_perm(pruned, (__m128i)vec_splats(0), compactmask); + vec_vsx_st(answer, 0, reinterpret_cast<__m128i *>(output)); + } + + template + simdjson_inline simd8 + lookup_16(L replace0, L replace1, L replace2, L replace3, L replace4, + L replace5, L replace6, L replace7, L replace8, L replace9, + L replace10, L replace11, L replace12, L replace13, L replace14, + L replace15) const { + return lookup_16(simd8::repeat_16( + replace0, replace1, replace2, replace3, replace4, replace5, replace6, + replace7, replace8, replace9, replace10, replace11, replace12, + replace13, replace14, replace15)); + } +}; + +// Signed bytes +template <> struct simd8 : base8_numeric { + simdjson_inline simd8() : base8_numeric() {} + simdjson_inline simd8(const __m128i _value) + : base8_numeric(_value) {} + // Splat constructor + simdjson_inline simd8(int8_t _value) : simd8(splat(_value)) {} + // Array constructor + simdjson_inline simd8(const int8_t *values) : simd8(load(values)) {} + // Member-by-member initialization + simdjson_inline simd8(int8_t v0, int8_t v1, int8_t v2, int8_t v3, + int8_t v4, int8_t v5, int8_t v6, int8_t v7, + int8_t v8, int8_t v9, int8_t v10, int8_t v11, + int8_t v12, int8_t v13, int8_t v14, int8_t v15) + : simd8((__m128i)(__vector signed char){v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10, v11, v12, v13, v14, + v15}) {} + // Repeat 16 values as many times as necessary (usually for lookup tables) + simdjson_inline static simd8 + repeat_16(int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, + int8_t v6, int8_t v7, int8_t v8, int8_t v9, int8_t v10, int8_t v11, + int8_t v12, int8_t v13, int8_t v14, int8_t v15) { + return simd8(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, + v13, v14, v15); + } + + // Order-sensitive comparisons + simdjson_inline simd8 + max_val(const simd8 other) const { + return (__m128i)vec_max((__vector signed char)this->value, + (__vector signed char)(__m128i)other); + } + simdjson_inline simd8 + min_val(const simd8 other) const { + return (__m128i)vec_min((__vector signed char)this->value, + (__vector signed char)(__m128i)other); + } + simdjson_inline simd8 + operator>(const simd8 other) const { + return (__m128i)vec_cmpgt((__vector signed char)this->value, + (__vector signed char)(__m128i)other); + } + simdjson_inline simd8 + operator<(const simd8 other) const { + return (__m128i)vec_cmplt((__vector signed char)this->value, + (__vector signed char)(__m128i)other); + } +}; + +// Unsigned bytes +template <> struct simd8 : base8_numeric { + simdjson_inline simd8() : base8_numeric() {} + simdjson_inline simd8(const __m128i _value) + : base8_numeric(_value) {} + // Splat constructor + simdjson_inline simd8(uint8_t _value) : simd8(splat(_value)) {} + // Array constructor + simdjson_inline simd8(const uint8_t *values) : simd8(load(values)) {} + // Member-by-member initialization + simdjson_inline + simd8(uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, + uint8_t v6, uint8_t v7, uint8_t v8, uint8_t v9, uint8_t v10, + uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15) + : simd8((__m128i){v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, + v13, v14, v15}) {} + // Repeat 16 values as many times as necessary (usually for lookup tables) + simdjson_inline static simd8 + repeat_16(uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, + uint8_t v5, uint8_t v6, uint8_t v7, uint8_t v8, uint8_t v9, + uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, + uint8_t v15) { + return simd8(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, + v13, v14, v15); + } + + // Saturated math + simdjson_inline simd8 + saturating_add(const simd8 other) const { + return (__m128i)vec_adds(this->value, (__m128i)other); + } + simdjson_inline simd8 + saturating_sub(const simd8 other) const { + return (__m128i)vec_subs(this->value, (__m128i)other); + } + + // Order-specific operations + simdjson_inline simd8 + max_val(const simd8 other) const { + return (__m128i)vec_max(this->value, (__m128i)other); + } + simdjson_inline simd8 + min_val(const simd8 other) const { + return (__m128i)vec_min(this->value, (__m128i)other); + } + // Same as >, but only guarantees true is nonzero (< guarantees true = -1) + simdjson_inline simd8 + gt_bits(const simd8 other) const { + return this->saturating_sub(other); + } + // Same as <, but only guarantees true is nonzero (< guarantees true = -1) + simdjson_inline simd8 + lt_bits(const simd8 other) const { + return other.saturating_sub(*this); + } + simdjson_inline simd8 + operator<=(const simd8 other) const { + return other.max_val(*this) == other; + } + simdjson_inline simd8 + operator>=(const simd8 other) const { + return other.min_val(*this) == other; + } + simdjson_inline simd8 + operator>(const simd8 other) const { + return this->gt_bits(other).any_bits_set(); + } + simdjson_inline simd8 + operator<(const simd8 other) const { + return this->gt_bits(other).any_bits_set(); + } + + // Bit-specific operations + simdjson_inline simd8 bits_not_set() const { + return (__m128i)vec_cmpeq(this->value, (__m128i)vec_splats(uint8_t(0))); + } + simdjson_inline simd8 bits_not_set(simd8 bits) const { + return (*this & bits).bits_not_set(); + } + simdjson_inline simd8 any_bits_set() const { + return ~this->bits_not_set(); + } + simdjson_inline simd8 any_bits_set(simd8 bits) const { + return ~this->bits_not_set(bits); + } + simdjson_inline bool bits_not_set_anywhere() const { + return vec_all_eq(this->value, (__m128i)vec_splats(0)); + } + simdjson_inline bool any_bits_set_anywhere() const { + return !bits_not_set_anywhere(); + } + simdjson_inline bool bits_not_set_anywhere(simd8 bits) const { + return vec_all_eq(vec_and(this->value, (__m128i)bits), + (__m128i)vec_splats(0)); + } + simdjson_inline bool any_bits_set_anywhere(simd8 bits) const { + return !bits_not_set_anywhere(bits); + } + template simdjson_inline simd8 shr() const { + return simd8( + (__m128i)vec_sr(this->value, (__m128i)vec_splat_u8(N))); + } + template simdjson_inline simd8 shl() const { + return simd8( + (__m128i)vec_sl(this->value, (__m128i)vec_splat_u8(N))); + } +}; + +template struct simd8x64 { + static constexpr int NUM_CHUNKS = 64 / sizeof(simd8); + static_assert(NUM_CHUNKS == 4, + "PPC64 kernel should use four registers per 64-byte block."); + const simd8 chunks[NUM_CHUNKS]; + + simd8x64(const simd8x64 &o) = delete; // no copy allowed + simd8x64 & + operator=(const simd8& other) = delete; // no assignment allowed + simd8x64() = delete; // no default constructor allowed + + simdjson_inline simd8x64(const simd8 chunk0, const simd8 chunk1, + const simd8 chunk2, const simd8 chunk3) + : chunks{chunk0, chunk1, chunk2, chunk3} {} + simdjson_inline simd8x64(const T ptr[64]) + : chunks{simd8::load(ptr), simd8::load(ptr + 16), + simd8::load(ptr + 32), simd8::load(ptr + 48)} {} + + simdjson_inline void store(T ptr[64]) const { + this->chunks[0].store(ptr + sizeof(simd8) * 0); + this->chunks[1].store(ptr + sizeof(simd8) * 1); + this->chunks[2].store(ptr + sizeof(simd8) * 2); + this->chunks[3].store(ptr + sizeof(simd8) * 3); + } + + simdjson_inline simd8 reduce_or() const { + return (this->chunks[0] | this->chunks[1]) | + (this->chunks[2] | this->chunks[3]); + } + + simdjson_inline uint64_t compress(uint64_t mask, T *output) const { + this->chunks[0].compress(uint16_t(mask), output); + this->chunks[1].compress(uint16_t(mask >> 16), + output + 16 - count_ones(mask & 0xFFFF)); + this->chunks[2].compress(uint16_t(mask >> 32), + output + 32 - count_ones(mask & 0xFFFFFFFF)); + this->chunks[3].compress(uint16_t(mask >> 48), + output + 48 - count_ones(mask & 0xFFFFFFFFFFFF)); + return 64 - count_ones(mask); + } + + simdjson_inline uint64_t to_bitmask() const { + uint64_t r0 = uint32_t(this->chunks[0].to_bitmask()); + uint64_t r1 = this->chunks[1].to_bitmask(); + uint64_t r2 = this->chunks[2].to_bitmask(); + uint64_t r3 = this->chunks[3].to_bitmask(); + return r0 | (r1 << 16) | (r2 << 32) | (r3 << 48); + } + + simdjson_inline uint64_t eq(const T m) const { + const simd8 mask = simd8::splat(m); + return simd8x64(this->chunks[0] == mask, this->chunks[1] == mask, + this->chunks[2] == mask, this->chunks[3] == mask) + .to_bitmask(); + } + + simdjson_inline uint64_t eq(const simd8x64 &other) const { + return simd8x64(this->chunks[0] == other.chunks[0], + this->chunks[1] == other.chunks[1], + this->chunks[2] == other.chunks[2], + this->chunks[3] == other.chunks[3]) + .to_bitmask(); + } + + simdjson_inline uint64_t lteq(const T m) const { + const simd8 mask = simd8::splat(m); + return simd8x64(this->chunks[0] <= mask, this->chunks[1] <= mask, + this->chunks[2] <= mask, this->chunks[3] <= mask) + .to_bitmask(); + } +}; // struct simd8x64 + +} // namespace simd +} // unnamed namespace +} // namespace ppc64 +} // namespace simdjson + +#endif // SIMDJSON_PPC64_SIMD_INPUT_H diff --git a/contrib/libs/simdjson/include/simdjson/ppc64/stringparsing_defs.h b/contrib/libs/simdjson/include/simdjson/ppc64/stringparsing_defs.h new file mode 100644 index 000000000000..b1bd46e521a9 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/ppc64/stringparsing_defs.h @@ -0,0 +1,65 @@ +#ifndef SIMDJSON_PPC64_STRINGPARSING_DEFS_H +#define SIMDJSON_PPC64_STRINGPARSING_DEFS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#error #include "simdjson/ppc64/base.h" +#error #include "simdjson/ppc64/bitmanipulation.h" +#error #include "simdjson/ppc64/simd.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace ppc64 { +namespace { + +using namespace simd; + +// Holds backslashes and quotes locations. +struct backslash_and_quote { +public: + static constexpr uint32_t BYTES_PROCESSED = 32; + simdjson_inline static backslash_and_quote + copy_and_find(const uint8_t *src, uint8_t *dst); + + simdjson_inline bool has_quote_first() { + return ((bs_bits - 1) & quote_bits) != 0; + } + simdjson_inline bool has_backslash() { return bs_bits != 0; } + simdjson_inline int quote_index() { + return trailing_zeroes(quote_bits); + } + simdjson_inline int backslash_index() { + return trailing_zeroes(bs_bits); + } + + uint32_t bs_bits; + uint32_t quote_bits; +}; // struct backslash_and_quote + +simdjson_inline backslash_and_quote +backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { + // this can read up to 31 bytes beyond the buffer size, but we require + // SIMDJSON_PADDING of padding + static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), + "backslash and quote finder must process fewer than " + "SIMDJSON_PADDING bytes"); + simd8 v0(src); + simd8 v1(src + sizeof(v0)); + v0.store(dst); + v1.store(dst + sizeof(v0)); + + // Getting a 64-bit bitmask is much cheaper than multiple 16-bit bitmasks on + // PPC; therefore, we smash them together into a 64-byte mask and get the + // bitmask from there. + uint64_t bs_and_quote = + simd8x64(v0 == '\\', v1 == '\\', v0 == '"', v1 == '"').to_bitmask(); + return { + uint32_t(bs_and_quote), // bs_bits + uint32_t(bs_and_quote >> 32) // quote_bits + }; +} + +} // unnamed namespace +} // namespace ppc64 +} // namespace simdjson + +#endif // SIMDJSON_PPC64_STRINGPARSING_DEFS_H diff --git a/contrib/libs/simdjson/include/simdjson/simdjson.h b/contrib/libs/simdjson/include/simdjson/simdjson.h new file mode 100644 index 000000000000..551673aefb02 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/simdjson.h @@ -0,0 +1,11 @@ +/** + * @file + * @deprecated We will be removing this file so it is not confused with the top level simdjson.h + */ +#ifndef SIMDJSON_SIMDJSON_H +#define SIMDJSON_SIMDJSON_H + +#include "simdjson/compiler_check.h" +#include "simdjson/error.h" + +#endif // SIMDJSON_SIMDJSON_H diff --git a/contrib/libs/simdjson/include/simdjson/simdjson_version.h b/contrib/libs/simdjson/include/simdjson/simdjson_version.h new file mode 100644 index 000000000000..7c6ef648b059 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/simdjson_version.h @@ -0,0 +1,26 @@ +// /include/simdjson/simdjson_version.h automatically generated by release.py, +// do not change by hand +#ifndef SIMDJSON_SIMDJSON_VERSION_H +#define SIMDJSON_SIMDJSON_VERSION_H + +/** The version of simdjson being used (major.minor.revision) */ +#define SIMDJSON_VERSION "3.11.3" + +namespace simdjson { +enum { + /** + * The major version (MAJOR.minor.revision) of simdjson being used. + */ + SIMDJSON_VERSION_MAJOR = 3, + /** + * The minor version (major.MINOR.revision) of simdjson being used. + */ + SIMDJSON_VERSION_MINOR = 11, + /** + * The revision (major.minor.REVISION) of simdjson being used. + */ + SIMDJSON_VERSION_REVISION = 3 +}; +} // namespace simdjson + +#endif // SIMDJSON_SIMDJSON_VERSION_H diff --git a/contrib/libs/simdjson/include/simdjson/westmere.h b/contrib/libs/simdjson/include/simdjson/westmere.h new file mode 100644 index 000000000000..f05ba1145c0b --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/westmere.h @@ -0,0 +1,8 @@ +#ifndef SIMDJSON_WESTMERE_H +#define SIMDJSON_WESTMERE_H + +#include "simdjson/westmere/begin.h" +#include "simdjson/generic/amalgamated.h" +#include "simdjson/westmere/end.h" + +#endif // SIMDJSON_WESTMERE_H \ No newline at end of file diff --git a/contrib/libs/simdjson/include/simdjson/westmere/base.h b/contrib/libs/simdjson/include/simdjson/westmere/base.h new file mode 100644 index 000000000000..82ad333a8dcb --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/westmere/base.h @@ -0,0 +1,29 @@ +#ifndef SIMDJSON_WESTMERE_BASE_H +#define SIMDJSON_WESTMERE_BASE_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +// The constructor may be executed on any host, so we take care not to use SIMDJSON_TARGET_WESTMERE +namespace simdjson { +/** + * Implementation for Westmere (Intel SSE4.2). + */ +namespace westmere { + +class implementation; + +namespace { +namespace simd { + +template struct simd8; +template struct simd8x64; + +} // namespace simd +} // unnamed namespace + +} // namespace westmere +} // namespace simdjson + +#endif // SIMDJSON_WESTMERE_BASE_H diff --git a/contrib/libs/simdjson/include/simdjson/westmere/begin.h b/contrib/libs/simdjson/include/simdjson/westmere/begin.h new file mode 100644 index 000000000000..d807e6d9995c --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/westmere/begin.h @@ -0,0 +1,13 @@ +#define SIMDJSON_IMPLEMENTATION westmere +#include "simdjson/westmere/base.h" +#include "simdjson/westmere/intrinsics.h" + +#if !SIMDJSON_CAN_ALWAYS_RUN_WESTMERE +SIMDJSON_TARGET_REGION("sse4.2,pclmul,popcnt") +#endif + +#include "simdjson/westmere/bitmanipulation.h" +#include "simdjson/westmere/bitmask.h" +#include "simdjson/westmere/numberparsing_defs.h" +#include "simdjson/westmere/simd.h" +#include "simdjson/westmere/stringparsing_defs.h" diff --git a/contrib/libs/simdjson/include/simdjson/westmere/bitmanipulation.h b/contrib/libs/simdjson/include/simdjson/westmere/bitmanipulation.h new file mode 100644 index 000000000000..7cd29a406a71 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/westmere/bitmanipulation.h @@ -0,0 +1,79 @@ +#ifndef SIMDJSON_WESTMERE_BITMANIPULATION_H +#define SIMDJSON_WESTMERE_BITMANIPULATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/westmere/base.h" +#include "simdjson/westmere/intrinsics.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace westmere { +namespace { + +// We sometimes call trailing_zero on inputs that are zero, +// but the algorithms do not end up using the returned value. +// Sadly, sanitizers are not smart enough to figure it out. +SIMDJSON_NO_SANITIZE_UNDEFINED +// This function can be used safely even if not all bytes have been +// initialized. +// See issue https://github.com/simdjson/simdjson/issues/1965 +SIMDJSON_NO_SANITIZE_MEMORY +simdjson_inline int trailing_zeroes(uint64_t input_num) { +#if SIMDJSON_REGULAR_VISUAL_STUDIO + unsigned long ret; + // Search the mask data from least significant bit (LSB) + // to the most significant bit (MSB) for a set bit (1). + _BitScanForward64(&ret, input_num); + return (int)ret; +#else // SIMDJSON_REGULAR_VISUAL_STUDIO + return __builtin_ctzll(input_num); +#endif // SIMDJSON_REGULAR_VISUAL_STUDIO +} + +/* result might be undefined when input_num is zero */ +simdjson_inline uint64_t clear_lowest_bit(uint64_t input_num) { + return input_num & (input_num-1); +} + +/* result might be undefined when input_num is zero */ +simdjson_inline int leading_zeroes(uint64_t input_num) { +#if SIMDJSON_REGULAR_VISUAL_STUDIO + unsigned long leading_zero = 0; + // Search the mask data from most significant bit (MSB) + // to least significant bit (LSB) for a set bit (1). + if (_BitScanReverse64(&leading_zero, input_num)) + return (int)(63 - leading_zero); + else + return 64; +#else + return __builtin_clzll(input_num); +#endif// SIMDJSON_REGULAR_VISUAL_STUDIO +} + +#if SIMDJSON_REGULAR_VISUAL_STUDIO +simdjson_inline unsigned __int64 count_ones(uint64_t input_num) { + // note: we do not support legacy 32-bit Windows in this kernel + return __popcnt64(input_num);// Visual Studio wants two underscores +} +#else +simdjson_inline long long int count_ones(uint64_t input_num) { + return _popcnt64(input_num); +} +#endif + +simdjson_inline bool add_overflow(uint64_t value1, uint64_t value2, + uint64_t *result) { +#if SIMDJSON_REGULAR_VISUAL_STUDIO + return _addcarry_u64(0, value1, value2, + reinterpret_cast(result)); +#else + return __builtin_uaddll_overflow(value1, value2, + reinterpret_cast(result)); +#endif +} + +} // unnamed namespace +} // namespace westmere +} // namespace simdjson + +#endif // SIMDJSON_WESTMERE_BITMANIPULATION_H diff --git a/contrib/libs/simdjson/include/simdjson/westmere/bitmask.h b/contrib/libs/simdjson/include/simdjson/westmere/bitmask.h new file mode 100644 index 000000000000..cd79b724119d --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/westmere/bitmask.h @@ -0,0 +1,30 @@ +#ifndef SIMDJSON_WESTMERE_BITMASK_H +#define SIMDJSON_WESTMERE_BITMASK_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/westmere/base.h" +#include "simdjson/westmere/intrinsics.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace westmere { +namespace { + +// +// Perform a "cumulative bitwise xor," flipping bits each time a 1 is encountered. +// +// For example, prefix_xor(00100100) == 00011100 +// +simdjson_inline uint64_t prefix_xor(const uint64_t bitmask) { + // There should be no such thing with a processing supporting avx2 + // but not clmul. + __m128i all_ones = _mm_set1_epi8('\xFF'); + __m128i result = _mm_clmulepi64_si128(_mm_set_epi64x(0ULL, bitmask), all_ones, 0); + return _mm_cvtsi128_si64(result); +} + +} // unnamed namespace +} // namespace westmere +} // namespace simdjson + +#endif // SIMDJSON_WESTMERE_BITMASK_H diff --git a/contrib/libs/simdjson/include/simdjson/westmere/end.h b/contrib/libs/simdjson/include/simdjson/westmere/end.h new file mode 100644 index 000000000000..bd4a94049033 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/westmere/end.h @@ -0,0 +1,9 @@ +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/westmere/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#if !SIMDJSON_CAN_ALWAYS_RUN_WESTMERE +SIMDJSON_UNTARGET_REGION +#endif + +#undef SIMDJSON_IMPLEMENTATION diff --git a/contrib/libs/simdjson/include/simdjson/westmere/implementation.h b/contrib/libs/simdjson/include/simdjson/westmere/implementation.h new file mode 100644 index 000000000000..37392be2ae9f --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/westmere/implementation.h @@ -0,0 +1,32 @@ +#ifndef SIMDJSON_WESTMERE_IMPLEMENTATION_H +#define SIMDJSON_WESTMERE_IMPLEMENTATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/westmere/base.h" +#include "simdjson/implementation.h" +#include "simdjson/internal/instruction_set.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +// The constructor may be executed on any host, so we take care not to use SIMDJSON_TARGET_WESTMERE +namespace simdjson { +namespace westmere { + +/** + * @private + */ +class implementation final : public simdjson::implementation { +public: + simdjson_inline implementation() : simdjson::implementation("westmere", "Intel/AMD SSE4.2", internal::instruction_set::SSE42 | internal::instruction_set::PCLMULQDQ) {} + simdjson_warn_unused error_code create_dom_parser_implementation( + size_t capacity, + size_t max_length, + std::unique_ptr& dst + ) const noexcept final; + simdjson_warn_unused error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept final; + simdjson_warn_unused bool validate_utf8(const char *buf, size_t len) const noexcept final; +}; + +} // namespace westmere +} // namespace simdjson + +#endif // SIMDJSON_WESTMERE_IMPLEMENTATION_H diff --git a/contrib/libs/simdjson/include/simdjson/westmere/intrinsics.h b/contrib/libs/simdjson/include/simdjson/westmere/intrinsics.h new file mode 100644 index 000000000000..63a351c80900 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/westmere/intrinsics.h @@ -0,0 +1,31 @@ +#ifndef SIMDJSON_WESTMERE_INTRINSICS_H +#define SIMDJSON_WESTMERE_INTRINSICS_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/westmere/base.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#if SIMDJSON_VISUAL_STUDIO +// under clang within visual studio, this will include +#include // visual studio or clang +#else +#include // elsewhere +#endif // SIMDJSON_VISUAL_STUDIO + + +#if SIMDJSON_CLANG_VISUAL_STUDIO +/** + * You are not supposed, normally, to include these + * headers directly. Instead you should either include intrin.h + * or x86intrin.h. However, when compiling with clang + * under Windows (i.e., when _MSC_VER is set), these headers + * only get included *if* the corresponding features are detected + * from macros: + */ +#include // for _mm_alignr_epi8 +#include // for _mm_clmulepi64_si128 +#endif + +static_assert(sizeof(__m128i) <= simdjson::SIMDJSON_PADDING, "insufficient padding for westmere"); + +#endif // SIMDJSON_WESTMERE_INTRINSICS_H diff --git a/contrib/libs/simdjson/include/simdjson/westmere/numberparsing_defs.h b/contrib/libs/simdjson/include/simdjson/westmere/numberparsing_defs.h new file mode 100644 index 000000000000..05cfccfd10d4 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/westmere/numberparsing_defs.h @@ -0,0 +1,59 @@ +#ifndef SIMDJSON_WESTMERE_NUMBERPARSING_DEFS_H +#define SIMDJSON_WESTMERE_NUMBERPARSING_DEFS_H + +#include "simdjson/westmere/base.h" +#include "simdjson/westmere/intrinsics.h" + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/internal/numberparsing_tables.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace westmere { +namespace numberparsing { + +/** @private */ +static simdjson_inline uint32_t parse_eight_digits_unrolled(const uint8_t *chars) { + // this actually computes *16* values so we are being wasteful. + const __m128i ascii0 = _mm_set1_epi8('0'); + const __m128i mul_1_10 = + _mm_setr_epi8(10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1); + const __m128i mul_1_100 = _mm_setr_epi16(100, 1, 100, 1, 100, 1, 100, 1); + const __m128i mul_1_10000 = + _mm_setr_epi16(10000, 1, 10000, 1, 10000, 1, 10000, 1); + const __m128i input = _mm_sub_epi8( + _mm_loadu_si128(reinterpret_cast(chars)), ascii0); + const __m128i t1 = _mm_maddubs_epi16(input, mul_1_10); + const __m128i t2 = _mm_madd_epi16(t1, mul_1_100); + const __m128i t3 = _mm_packus_epi32(t2, t2); + const __m128i t4 = _mm_madd_epi16(t3, mul_1_10000); + return _mm_cvtsi128_si32( + t4); // only captures the sum of the first 8 digits, drop the rest +} + +/** @private */ +simdjson_inline internal::value128 full_multiplication(uint64_t value1, uint64_t value2) { + internal::value128 answer; +#if SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS +#if SIMDJSON_IS_ARM64 + // ARM64 has native support for 64-bit multiplications, no need to emultate + answer.high = __umulh(value1, value2); + answer.low = value1 * value2; +#else + answer.low = _umul128(value1, value2, &answer.high); // _umul128 not available on ARM64 +#endif // SIMDJSON_IS_ARM64 +#else // SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS + __uint128_t r = (static_cast<__uint128_t>(value1)) * value2; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); +#endif + return answer; +} + +} // namespace numberparsing +} // namespace westmere +} // namespace simdjson + +#define SIMDJSON_SWAR_NUMBER_PARSING 1 + +#endif // SIMDJSON_WESTMERE_NUMBERPARSING_DEFS_H diff --git a/contrib/libs/simdjson/include/simdjson/westmere/ondemand.h b/contrib/libs/simdjson/include/simdjson/westmere/ondemand.h new file mode 100644 index 000000000000..40d4ce2551b3 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/westmere/ondemand.h @@ -0,0 +1,8 @@ +#ifndef SIMDJSON_WESTMERE_ONDEMAND_H +#define SIMDJSON_WESTMERE_ONDEMAND_H + +#include "simdjson/westmere/begin.h" +#include "simdjson/generic/ondemand/amalgamated.h" +#include "simdjson/westmere/end.h" + +#endif // SIMDJSON_WESTMERE_IMPLEMENTATION_H diff --git a/contrib/libs/simdjson/include/simdjson/westmere/simd.h b/contrib/libs/simdjson/include/simdjson/westmere/simd.h new file mode 100644 index 000000000000..28329d9642eb --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/westmere/simd.h @@ -0,0 +1,338 @@ +#ifndef SIMDJSON_WESTMERE_SIMD_H +#define SIMDJSON_WESTMERE_SIMD_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include "simdjson/westmere/base.h" +#include "simdjson/westmere/bitmanipulation.h" +#include "simdjson/internal/simdprune_tables.h" +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace westmere { +namespace { +namespace simd { + + template + struct base { + __m128i value; + + // Zero constructor + simdjson_inline base() : value{__m128i()} {} + + // Conversion from SIMD register + simdjson_inline base(const __m128i _value) : value(_value) {} + + // Conversion to SIMD register + simdjson_inline operator const __m128i&() const { return this->value; } + simdjson_inline operator __m128i&() { return this->value; } + + // Bit operations + simdjson_inline Child operator|(const Child other) const { return _mm_or_si128(*this, other); } + simdjson_inline Child operator&(const Child other) const { return _mm_and_si128(*this, other); } + simdjson_inline Child operator^(const Child other) const { return _mm_xor_si128(*this, other); } + simdjson_inline Child bit_andnot(const Child other) const { return _mm_andnot_si128(other, *this); } + simdjson_inline Child& operator|=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast | other; return *this_cast; } + simdjson_inline Child& operator&=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast & other; return *this_cast; } + simdjson_inline Child& operator^=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast ^ other; return *this_cast; } + }; + + template> + struct base8: base> { + typedef uint16_t bitmask_t; + typedef uint32_t bitmask2_t; + + simdjson_inline base8() : base>() {} + simdjson_inline base8(const __m128i _value) : base>(_value) {} + + friend simdjson_inline Mask operator==(const simd8 lhs, const simd8 rhs) { return _mm_cmpeq_epi8(lhs, rhs); } + + static const int SIZE = sizeof(base>::value); + + template + simdjson_inline simd8 prev(const simd8 prev_chunk) const { + return _mm_alignr_epi8(*this, prev_chunk, 16 - N); + } + }; + + // SIMD byte mask type (returned by things like eq and gt) + template<> + struct simd8: base8 { + static simdjson_inline simd8 splat(bool _value) { return _mm_set1_epi8(uint8_t(-(!!_value))); } + + simdjson_inline simd8() : base8() {} + simdjson_inline simd8(const __m128i _value) : base8(_value) {} + // Splat constructor + simdjson_inline simd8(bool _value) : base8(splat(_value)) {} + + simdjson_inline int to_bitmask() const { return _mm_movemask_epi8(*this); } + simdjson_inline bool any() const { return !_mm_testz_si128(*this, *this); } + simdjson_inline simd8 operator~() const { return *this ^ true; } + }; + + template + struct base8_numeric: base8 { + static simdjson_inline simd8 splat(T _value) { return _mm_set1_epi8(_value); } + static simdjson_inline simd8 zero() { return _mm_setzero_si128(); } + static simdjson_inline simd8 load(const T values[16]) { + return _mm_loadu_si128(reinterpret_cast(values)); + } + // Repeat 16 values as many times as necessary (usually for lookup tables) + static simdjson_inline simd8 repeat_16( + T v0, T v1, T v2, T v3, T v4, T v5, T v6, T v7, + T v8, T v9, T v10, T v11, T v12, T v13, T v14, T v15 + ) { + return simd8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + ); + } + + simdjson_inline base8_numeric() : base8() {} + simdjson_inline base8_numeric(const __m128i _value) : base8(_value) {} + + // Store to array + simdjson_inline void store(T dst[16]) const { return _mm_storeu_si128(reinterpret_cast<__m128i *>(dst), *this); } + + // Override to distinguish from bool version + simdjson_inline simd8 operator~() const { return *this ^ 0xFFu; } + + // Addition/subtraction are the same for signed and unsigned + simdjson_inline simd8 operator+(const simd8 other) const { return _mm_add_epi8(*this, other); } + simdjson_inline simd8 operator-(const simd8 other) const { return _mm_sub_epi8(*this, other); } + simdjson_inline simd8& operator+=(const simd8 other) { *this = *this + other; return *static_cast*>(this); } + simdjson_inline simd8& operator-=(const simd8 other) { *this = *this - other; return *static_cast*>(this); } + + // Perform a lookup assuming the value is between 0 and 16 (undefined behavior for out of range values) + template + simdjson_inline simd8 lookup_16(simd8 lookup_table) const { + return _mm_shuffle_epi8(lookup_table, *this); + } + + // Copies to 'output" all bytes corresponding to a 0 in the mask (interpreted as a bitset). + // Passing a 0 value for mask would be equivalent to writing out every byte to output. + // Only the first 16 - count_ones(mask) bytes of the result are significant but 16 bytes + // get written. + // Design consideration: it seems like a function with the + // signature simd8 compress(uint32_t mask) would be + // sensible, but the AVX ISA makes this kind of approach difficult. + template + simdjson_inline void compress(uint16_t mask, L * output) const { + using internal::thintable_epi8; + using internal::BitsSetTable256mul2; + using internal::pshufb_combine_table; + // this particular implementation was inspired by work done by @animetosho + // we do it in two steps, first 8 bytes and then second 8 bytes + uint8_t mask1 = uint8_t(mask); // least significant 8 bits + uint8_t mask2 = uint8_t(mask >> 8); // most significant 8 bits + // next line just loads the 64-bit values thintable_epi8[mask1] and + // thintable_epi8[mask2] into a 128-bit register, using only + // two instructions on most compilers. + __m128i shufmask = _mm_set_epi64x(thintable_epi8[mask2], thintable_epi8[mask1]); + // we increment by 0x08 the second half of the mask + shufmask = + _mm_add_epi8(shufmask, _mm_set_epi32(0x08080808, 0x08080808, 0, 0)); + // this is the version "nearly pruned" + __m128i pruned = _mm_shuffle_epi8(*this, shufmask); + // we still need to put the two halves together. + // we compute the popcount of the first half: + int pop1 = BitsSetTable256mul2[mask1]; + // then load the corresponding mask, what it does is to write + // only the first pop1 bytes from the first 8 bytes, and then + // it fills in with the bytes from the second 8 bytes + some filling + // at the end. + __m128i compactmask = + _mm_loadu_si128(reinterpret_cast(pshufb_combine_table + pop1 * 8)); + __m128i answer = _mm_shuffle_epi8(pruned, compactmask); + _mm_storeu_si128(reinterpret_cast<__m128i *>(output), answer); + } + + template + simdjson_inline simd8 lookup_16( + L replace0, L replace1, L replace2, L replace3, + L replace4, L replace5, L replace6, L replace7, + L replace8, L replace9, L replace10, L replace11, + L replace12, L replace13, L replace14, L replace15) const { + return lookup_16(simd8::repeat_16( + replace0, replace1, replace2, replace3, + replace4, replace5, replace6, replace7, + replace8, replace9, replace10, replace11, + replace12, replace13, replace14, replace15 + )); + } + }; + + // Signed bytes + template<> + struct simd8 : base8_numeric { + simdjson_inline simd8() : base8_numeric() {} + simdjson_inline simd8(const __m128i _value) : base8_numeric(_value) {} + // Splat constructor + simdjson_inline simd8(int8_t _value) : simd8(splat(_value)) {} + // Array constructor + simdjson_inline simd8(const int8_t* values) : simd8(load(values)) {} + // Member-by-member initialization + simdjson_inline simd8( + int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, + int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15 + ) : simd8(_mm_setr_epi8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + )) {} + // Repeat 16 values as many times as necessary (usually for lookup tables) + simdjson_inline static simd8 repeat_16( + int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, + int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15 + ) { + return simd8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + ); + } + + // Order-sensitive comparisons + simdjson_inline simd8 max_val(const simd8 other) const { return _mm_max_epi8(*this, other); } + simdjson_inline simd8 min_val(const simd8 other) const { return _mm_min_epi8(*this, other); } + simdjson_inline simd8 operator>(const simd8 other) const { return _mm_cmpgt_epi8(*this, other); } + simdjson_inline simd8 operator<(const simd8 other) const { return _mm_cmpgt_epi8(other, *this); } + }; + + // Unsigned bytes + template<> + struct simd8: base8_numeric { + simdjson_inline simd8() : base8_numeric() {} + simdjson_inline simd8(const __m128i _value) : base8_numeric(_value) {} + // Splat constructor + simdjson_inline simd8(uint8_t _value) : simd8(splat(_value)) {} + // Array constructor + simdjson_inline simd8(const uint8_t* values) : simd8(load(values)) {} + // Member-by-member initialization + simdjson_inline simd8( + uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, + uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15 + ) : simd8(_mm_setr_epi8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + )) {} + // Repeat 16 values as many times as necessary (usually for lookup tables) + simdjson_inline static simd8 repeat_16( + uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, + uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15 + ) { + return simd8( + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10,v11,v12,v13,v14,v15 + ); + } + + // Saturated math + simdjson_inline simd8 saturating_add(const simd8 other) const { return _mm_adds_epu8(*this, other); } + simdjson_inline simd8 saturating_sub(const simd8 other) const { return _mm_subs_epu8(*this, other); } + + // Order-specific operations + simdjson_inline simd8 max_val(const simd8 other) const { return _mm_max_epu8(*this, other); } + simdjson_inline simd8 min_val(const simd8 other) const { return _mm_min_epu8(*this, other); } + // Same as >, but only guarantees true is nonzero (< guarantees true = -1) + simdjson_inline simd8 gt_bits(const simd8 other) const { return this->saturating_sub(other); } + // Same as <, but only guarantees true is nonzero (< guarantees true = -1) + simdjson_inline simd8 lt_bits(const simd8 other) const { return other.saturating_sub(*this); } + simdjson_inline simd8 operator<=(const simd8 other) const { return other.max_val(*this) == other; } + simdjson_inline simd8 operator>=(const simd8 other) const { return other.min_val(*this) == other; } + simdjson_inline simd8 operator>(const simd8 other) const { return this->gt_bits(other).any_bits_set(); } + simdjson_inline simd8 operator<(const simd8 other) const { return this->gt_bits(other).any_bits_set(); } + + // Bit-specific operations + simdjson_inline simd8 bits_not_set() const { return *this == uint8_t(0); } + simdjson_inline simd8 bits_not_set(simd8 bits) const { return (*this & bits).bits_not_set(); } + simdjson_inline simd8 any_bits_set() const { return ~this->bits_not_set(); } + simdjson_inline simd8 any_bits_set(simd8 bits) const { return ~this->bits_not_set(bits); } + simdjson_inline bool is_ascii() const { return _mm_movemask_epi8(*this) == 0; } + simdjson_inline bool bits_not_set_anywhere() const { return _mm_testz_si128(*this, *this); } + simdjson_inline bool any_bits_set_anywhere() const { return !bits_not_set_anywhere(); } + simdjson_inline bool bits_not_set_anywhere(simd8 bits) const { return _mm_testz_si128(*this, bits); } + simdjson_inline bool any_bits_set_anywhere(simd8 bits) const { return !bits_not_set_anywhere(bits); } + template + simdjson_inline simd8 shr() const { return simd8(_mm_srli_epi16(*this, N)) & uint8_t(0xFFu >> N); } + template + simdjson_inline simd8 shl() const { return simd8(_mm_slli_epi16(*this, N)) & uint8_t(0xFFu << N); } + // Get one of the bits and make a bitmask out of it. + // e.g. value.get_bit<7>() gets the high bit + template + simdjson_inline int get_bit() const { return _mm_movemask_epi8(_mm_slli_epi16(*this, 7-N)); } + }; + + template + struct simd8x64 { + static constexpr int NUM_CHUNKS = 64 / sizeof(simd8); + static_assert(NUM_CHUNKS == 4, "Westmere kernel should use four registers per 64-byte block."); + const simd8 chunks[NUM_CHUNKS]; + + simd8x64(const simd8x64& o) = delete; // no copy allowed + simd8x64& operator=(const simd8& other) = delete; // no assignment allowed + simd8x64() = delete; // no default constructor allowed + + simdjson_inline simd8x64(const simd8 chunk0, const simd8 chunk1, const simd8 chunk2, const simd8 chunk3) : chunks{chunk0, chunk1, chunk2, chunk3} {} + simdjson_inline simd8x64(const T ptr[64]) : chunks{simd8::load(ptr), simd8::load(ptr+16), simd8::load(ptr+32), simd8::load(ptr+48)} {} + + simdjson_inline void store(T ptr[64]) const { + this->chunks[0].store(ptr+sizeof(simd8)*0); + this->chunks[1].store(ptr+sizeof(simd8)*1); + this->chunks[2].store(ptr+sizeof(simd8)*2); + this->chunks[3].store(ptr+sizeof(simd8)*3); + } + + simdjson_inline simd8 reduce_or() const { + return (this->chunks[0] | this->chunks[1]) | (this->chunks[2] | this->chunks[3]); + } + + simdjson_inline uint64_t compress(uint64_t mask, T * output) const { + this->chunks[0].compress(uint16_t(mask), output); + this->chunks[1].compress(uint16_t(mask >> 16), output + 16 - count_ones(mask & 0xFFFF)); + this->chunks[2].compress(uint16_t(mask >> 32), output + 32 - count_ones(mask & 0xFFFFFFFF)); + this->chunks[3].compress(uint16_t(mask >> 48), output + 48 - count_ones(mask & 0xFFFFFFFFFFFF)); + return 64 - count_ones(mask); + } + + simdjson_inline uint64_t to_bitmask() const { + uint64_t r0 = uint32_t(this->chunks[0].to_bitmask() ); + uint64_t r1 = this->chunks[1].to_bitmask() ; + uint64_t r2 = this->chunks[2].to_bitmask() ; + uint64_t r3 = this->chunks[3].to_bitmask() ; + return r0 | (r1 << 16) | (r2 << 32) | (r3 << 48); + } + + simdjson_inline uint64_t eq(const T m) const { + const simd8 mask = simd8::splat(m); + return simd8x64( + this->chunks[0] == mask, + this->chunks[1] == mask, + this->chunks[2] == mask, + this->chunks[3] == mask + ).to_bitmask(); + } + + simdjson_inline uint64_t eq(const simd8x64 &other) const { + return simd8x64( + this->chunks[0] == other.chunks[0], + this->chunks[1] == other.chunks[1], + this->chunks[2] == other.chunks[2], + this->chunks[3] == other.chunks[3] + ).to_bitmask(); + } + + simdjson_inline uint64_t lteq(const T m) const { + const simd8 mask = simd8::splat(m); + return simd8x64( + this->chunks[0] <= mask, + this->chunks[1] <= mask, + this->chunks[2] <= mask, + this->chunks[3] <= mask + ).to_bitmask(); + } + }; // struct simd8x64 + +} // namespace simd +} // unnamed namespace +} // namespace westmere +} // namespace simdjson + +#endif // SIMDJSON_WESTMERE_SIMD_INPUT_H diff --git a/contrib/libs/simdjson/include/simdjson/westmere/stringparsing_defs.h b/contrib/libs/simdjson/include/simdjson/westmere/stringparsing_defs.h new file mode 100644 index 000000000000..439f19cbc731 --- /dev/null +++ b/contrib/libs/simdjson/include/simdjson/westmere/stringparsing_defs.h @@ -0,0 +1,47 @@ +#ifndef SIMDJSON_WESTMERE_STRINGPARSING_DEFS_H +#define SIMDJSON_WESTMERE_STRINGPARSING_DEFS_H + +#include "simdjson/westmere/bitmanipulation.h" +#include "simdjson/westmere/simd.h" + +namespace simdjson { +namespace westmere { +namespace { + +using namespace simd; + +// Holds backslashes and quotes locations. +struct backslash_and_quote { +public: + static constexpr uint32_t BYTES_PROCESSED = 32; + simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); + + simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; } + simdjson_inline bool has_backslash() { return bs_bits != 0; } + simdjson_inline int quote_index() { return trailing_zeroes(quote_bits); } + simdjson_inline int backslash_index() { return trailing_zeroes(bs_bits); } + + uint32_t bs_bits; + uint32_t quote_bits; +}; // struct backslash_and_quote + +simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { + // this can read up to 31 bytes beyond the buffer size, but we require + // SIMDJSON_PADDING of padding + static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "backslash and quote finder must process fewer than SIMDJSON_PADDING bytes"); + simd8 v0(src); + simd8 v1(src + 16); + v0.store(dst); + v1.store(dst + 16); + uint64_t bs_and_quote = simd8x64(v0 == '\\', v1 == '\\', v0 == '"', v1 == '"').to_bitmask(); + return { + uint32_t(bs_and_quote), // bs_bits + uint32_t(bs_and_quote >> 32) // quote_bits + }; +} + +} // unnamed namespace +} // namespace westmere +} // namespace simdjson + +#endif // SIMDJSON_WESTMERE_STRINGPARSING_DEFS_H diff --git a/contrib/libs/simdjson/src/arm64.cpp b/contrib/libs/simdjson/src/arm64.cpp new file mode 100644 index 000000000000..e017ce01678c --- /dev/null +++ b/contrib/libs/simdjson/src/arm64.cpp @@ -0,0 +1,173 @@ +#ifndef SIMDJSON_SRC_ARM64_CPP +#define SIMDJSON_SRC_ARM64_CPP + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include +#include + +#include +#include +#include +#include + +// +// Stage 1 +// +namespace simdjson { +namespace arm64 { + +simdjson_warn_unused error_code implementation::create_dom_parser_implementation( + size_t capacity, + size_t max_depth, + std::unique_ptr& dst +) const noexcept { + dst.reset( new (std::nothrow) dom_parser_implementation() ); + if (!dst) { return MEMALLOC; } + if (auto err = dst->set_capacity(capacity)) + return err; + if (auto err = dst->set_max_depth(max_depth)) + return err; + return SUCCESS; +} + +namespace { + +using namespace simd; + +simdjson_inline json_character_block json_character_block::classify(const simd::simd8x64& in) { + // Functional programming causes trouble with Visual Studio. + // Keeping this version in comments since it is much nicer: + // auto v = in.map([&](simd8 chunk) { + // auto nib_lo = chunk & 0xf; + // auto nib_hi = chunk.shr<4>(); + // auto shuf_lo = nib_lo.lookup_16(16, 0, 0, 0, 0, 0, 0, 0, 0, 8, 12, 1, 2, 9, 0, 0); + // auto shuf_hi = nib_hi.lookup_16(8, 0, 18, 4, 0, 1, 0, 1, 0, 0, 0, 3, 2, 1, 0, 0); + // return shuf_lo & shuf_hi; + // }); + const simd8 table1(16, 0, 0, 0, 0, 0, 0, 0, 0, 8, 12, 1, 2, 9, 0, 0); + const simd8 table2(8, 0, 18, 4, 0, 1, 0, 1, 0, 0, 0, 3, 2, 1, 0, 0); + + simd8x64 v( + (in.chunks[0] & 0xf).lookup_16(table1) & (in.chunks[0].shr<4>()).lookup_16(table2), + (in.chunks[1] & 0xf).lookup_16(table1) & (in.chunks[1].shr<4>()).lookup_16(table2), + (in.chunks[2] & 0xf).lookup_16(table1) & (in.chunks[2].shr<4>()).lookup_16(table2), + (in.chunks[3] & 0xf).lookup_16(table1) & (in.chunks[3].shr<4>()).lookup_16(table2) + ); + + + // We compute whitespace and op separately. If the code later only use one or the + // other, given the fact that all functions are aggressively inlined, we can + // hope that useless computations will be omitted. This is namely case when + // minifying (we only need whitespace). *However* if we only need spaces, + // it is likely that we will still compute 'v' above with two lookup_16: one + // could do it a bit cheaper. This is in contrast with the x64 implementations + // where we can, efficiently, do the white space and structural matching + // separately. One reason for this difference is that on ARM NEON, the table + // lookups either zero or leave unchanged the characters exceeding 0xF whereas + // on x64, the equivalent instruction (pshufb) automatically applies a mask, + // ignoring the 4 most significant bits. Thus the x64 implementation is + // optimized differently. This being said, if you use this code strictly + // just for minification (or just to identify the structural characters), + // there is a small untaken optimization opportunity here. We deliberately + // do not pick it up. + + uint64_t op = simd8x64( + v.chunks[0].any_bits_set(0x7), + v.chunks[1].any_bits_set(0x7), + v.chunks[2].any_bits_set(0x7), + v.chunks[3].any_bits_set(0x7) + ).to_bitmask(); + + uint64_t whitespace = simd8x64( + v.chunks[0].any_bits_set(0x18), + v.chunks[1].any_bits_set(0x18), + v.chunks[2].any_bits_set(0x18), + v.chunks[3].any_bits_set(0x18) + ).to_bitmask(); + + return { whitespace, op }; +} + +simdjson_inline bool is_ascii(const simd8x64& input) { + simd8 bits = input.reduce_or(); + return bits.max_val() < 0x80u; +} + +simdjson_unused simdjson_inline simd8 must_be_continuation(const simd8 prev1, const simd8 prev2, const simd8 prev3) { + simd8 is_second_byte = prev1 >= uint8_t(0xc0u); + simd8 is_third_byte = prev2 >= uint8_t(0xe0u); + simd8 is_fourth_byte = prev3 >= uint8_t(0xf0u); + // Use ^ instead of | for is_*_byte, because ^ is commutative, and the caller is using ^ as well. + // This will work fine because we only have to report errors for cases with 0-1 lead bytes. + // Multiple lead bytes implies 2 overlapping multibyte characters, and if that happens, there is + // guaranteed to be at least *one* lead byte that is part of only 1 other multibyte character. + // The error will be detected there. + return is_second_byte ^ is_third_byte ^ is_fourth_byte; +} + +simdjson_inline simd8 must_be_2_3_continuation(const simd8 prev2, const simd8 prev3) { + simd8 is_third_byte = prev2.saturating_sub(0xe0u-0x80); // Only 111_____ will be >= 0x80 + simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-0x80); // Only 1111____ will be >= 0x80 + return is_third_byte | is_fourth_byte; +} + +} // unnamed namespace +} // namespace arm64 +} // namespace simdjson + +// +// Stage 2 +// + +// +// Implementation-specific overrides +// +namespace simdjson { +namespace arm64 { + +simdjson_warn_unused error_code implementation::minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept { + return arm64::stage1::json_minifier::minify<64>(buf, len, dst, dst_len); +} + +simdjson_warn_unused error_code dom_parser_implementation::stage1(const uint8_t *_buf, size_t _len, stage1_mode streaming) noexcept { + this->buf = _buf; + this->len = _len; + return arm64::stage1::json_structural_indexer::index<64>(buf, len, *this, streaming); +} + +simdjson_warn_unused bool implementation::validate_utf8(const char *buf, size_t len) const noexcept { + return arm64::stage1::generic_validate_utf8(buf,len); +} + +simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document &_doc) noexcept { + return stage2::tape_builder::parse_document(*this, _doc); +} + +simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { + return stage2::tape_builder::parse_document(*this, _doc); +} + +SIMDJSON_NO_SANITIZE_MEMORY +simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept { + return arm64::stringparsing::parse_string(src, dst, allow_replacement); +} + +simdjson_warn_unused uint8_t *dom_parser_implementation::parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept { + return arm64::stringparsing::parse_wobbly_string(src, dst); +} + +simdjson_warn_unused error_code dom_parser_implementation::parse(const uint8_t *_buf, size_t _len, dom::document &_doc) noexcept { + auto error = stage1(_buf, _len, stage1_mode::regular); + if (error) { return error; } + return stage2(_doc); +} + +} // namespace arm64 +} // namespace simdjson + +#include + +#endif // SIMDJSON_SRC_ARM64_CPP diff --git a/contrib/libs/simdjson/src/base.h b/contrib/libs/simdjson/src/base.h new file mode 100644 index 000000000000..67873b2f9c5c --- /dev/null +++ b/contrib/libs/simdjson/src/base.h @@ -0,0 +1,6 @@ +#ifndef SIMDJSON_SRC_BASE_H +#define SIMDJSON_SRC_BASE_H + +#include + +#endif // SIMDJSON_SRC_BASE_H diff --git a/contrib/libs/simdjson/src/fallback.cpp b/contrib/libs/simdjson/src/fallback.cpp new file mode 100644 index 000000000000..dbecb2f32aee --- /dev/null +++ b/contrib/libs/simdjson/src/fallback.cpp @@ -0,0 +1,411 @@ +#ifndef SIMDJSON_SRC_FALLBACK_CPP +#define SIMDJSON_SRC_FALLBACK_CPP + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +// +// Stage 1 +// + +namespace simdjson { +namespace fallback { + +simdjson_warn_unused error_code implementation::create_dom_parser_implementation( + size_t capacity, + size_t max_depth, + std::unique_ptr& dst +) const noexcept { + dst.reset( new (std::nothrow) SIMDJSON_IMPLEMENTATION::dom_parser_implementation() ); + if (!dst) { return MEMALLOC; } + if (auto err = dst->set_capacity(capacity)) + return err; + if (auto err = dst->set_max_depth(max_depth)) + return err; + return SUCCESS; +} + +namespace { +namespace stage1 { + +class structural_scanner { +public: + +simdjson_inline structural_scanner(dom_parser_implementation &_parser, stage1_mode _partial) + : buf{_parser.buf}, + next_structural_index{_parser.structural_indexes.get()}, + parser{_parser}, + len{static_cast(_parser.len)}, + partial{_partial} { +} + +simdjson_inline void add_structural() { + *next_structural_index = idx; + next_structural_index++; +} + +simdjson_inline bool is_continuation(uint8_t c) { + return (c & 0xc0) == 0x80; +} + +simdjson_inline void validate_utf8_character() { + // Continuation + if (simdjson_unlikely((buf[idx] & 0x40) == 0)) { + // extra continuation + error = UTF8_ERROR; + idx++; + return; + } + + // 2-byte + if ((buf[idx] & 0x20) == 0) { + // missing continuation + if (simdjson_unlikely(idx+1 > len || !is_continuation(buf[idx+1]))) { + if (idx+1 > len && is_streaming(partial)) { idx = len; return; } + error = UTF8_ERROR; + idx++; + return; + } + // overlong: 1100000_ 10______ + if (buf[idx] <= 0xc1) { error = UTF8_ERROR; } + idx += 2; + return; + } + + // 3-byte + if ((buf[idx] & 0x10) == 0) { + // missing continuation + if (simdjson_unlikely(idx+2 > len || !is_continuation(buf[idx+1]) || !is_continuation(buf[idx+2]))) { + if (idx+2 > len && is_streaming(partial)) { idx = len; return; } + error = UTF8_ERROR; + idx++; + return; + } + // overlong: 11100000 100_____ ________ + if (buf[idx] == 0xe0 && buf[idx+1] <= 0x9f) { error = UTF8_ERROR; } + // surrogates: U+D800-U+DFFF 11101101 101_____ + if (buf[idx] == 0xed && buf[idx+1] >= 0xa0) { error = UTF8_ERROR; } + idx += 3; + return; + } + + // 4-byte + // missing continuation + if (simdjson_unlikely(idx+3 > len || !is_continuation(buf[idx+1]) || !is_continuation(buf[idx+2]) || !is_continuation(buf[idx+3]))) { + if (idx+2 > len && is_streaming(partial)) { idx = len; return; } + error = UTF8_ERROR; + idx++; + return; + } + // overlong: 11110000 1000____ ________ ________ + if (buf[idx] == 0xf0 && buf[idx+1] <= 0x8f) { error = UTF8_ERROR; } + // too large: > U+10FFFF: + // 11110100 (1001|101_)____ + // 1111(1___|011_|0101) 10______ + // also includes 5, 6, 7 and 8 byte characters: + // 11111___ + if (buf[idx] == 0xf4 && buf[idx+1] >= 0x90) { error = UTF8_ERROR; } + if (buf[idx] >= 0xf5) { error = UTF8_ERROR; } + idx += 4; +} + +// Returns true if the string is unclosed. +simdjson_inline bool validate_string() { + idx++; // skip first quote + while (idx < len && buf[idx] != '"') { + if (buf[idx] == '\\') { + idx += 2; + } else if (simdjson_unlikely(buf[idx] & 0x80)) { + validate_utf8_character(); + } else { + if (buf[idx] < 0x20) { error = UNESCAPED_CHARS; } + idx++; + } + } + if (idx >= len) { return true; } + return false; +} + +simdjson_inline bool is_whitespace_or_operator(uint8_t c) { + switch (c) { + case '{': case '}': case '[': case ']': case ',': case ':': + case ' ': case '\r': case '\n': case '\t': + return true; + default: + return false; + } +} + +// +// Parse the entire input in STEP_SIZE-byte chunks. +// +simdjson_inline error_code scan() { + bool unclosed_string = false; + for (;idx 0) { + if(parser.structural_indexes[0] == 0) { + // If the buffer is partial and we started at index 0 but the document is + // incomplete, it's too big to parse. + return CAPACITY; + } else { + // It is possible that the document could be parsed, we just had a lot + // of white space. + parser.n_structural_indexes = 0; + return EMPTY; + } + } + parser.n_structural_indexes = new_structural_indexes; + } else if(partial == stage1_mode::streaming_final) { + if(unclosed_string) { parser.n_structural_indexes--; } + // We truncate the input to the end of the last complete document (or zero). + // Because partial == stage1_mode::streaming_final, it means that we may + // silently ignore trailing garbage. Though it sounds bad, we do it + // deliberately because many people who have streams of JSON documents + // will truncate them for processing. E.g., imagine that you are uncompressing + // the data from a size file or receiving it in chunks from the network. You + // may not know where exactly the last document will be. Meanwhile the + // document_stream instances allow people to know the JSON documents they are + // parsing (see the iterator.source() method). + parser.n_structural_indexes = find_next_document_index(parser); + // We store the initial n_structural_indexes so that the client can see + // whether we used truncation. If initial_n_structural_indexes == parser.n_structural_indexes, + // then this will query parser.structural_indexes[parser.n_structural_indexes] which is len, + // otherwise, it will copy some prior index. + parser.structural_indexes[parser.n_structural_indexes + 1] = parser.structural_indexes[parser.n_structural_indexes]; + // This next line is critical, do not change it unless you understand what you are + // doing. + parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len); + if (parser.n_structural_indexes == 0) { return EMPTY; } + } else if(unclosed_string) { error = UNCLOSED_STRING; } + return error; +} + +private: + const uint8_t *buf; + uint32_t *next_structural_index; + dom_parser_implementation &parser; + uint32_t len; + uint32_t idx{0}; + error_code error{SUCCESS}; + stage1_mode partial; +}; // structural_scanner + +} // namespace stage1 +} // unnamed namespace + +simdjson_warn_unused error_code dom_parser_implementation::stage1(const uint8_t *_buf, size_t _len, stage1_mode partial) noexcept { + this->buf = _buf; + this->len = _len; + stage1::structural_scanner scanner(*this, partial); + return scanner.scan(); +} + +// big table for the minifier +static uint8_t jump_table[256 * 3] = { + 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, + 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, + 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, + 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, + 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, + 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, + 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, + 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, + 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, + 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, + 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, + 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, + 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, + 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, + 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, + 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, + 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, + 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, + 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, + 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, + 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, + 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, + 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, + 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, + 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, + 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, + 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, + 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, + 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, + 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, +}; + +simdjson_warn_unused error_code implementation::minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept { + size_t i = 0, pos = 0; + uint8_t quote = 0; + uint8_t nonescape = 1; + + while (i < len) { + unsigned char c = buf[i]; + uint8_t *meta = jump_table + 3 * c; + + quote = quote ^ (meta[0] & nonescape); + dst[pos] = c; + pos += meta[2] | quote; + + i += 1; + nonescape = uint8_t(~nonescape) | (meta[1]); + } + dst_len = pos; // we intentionally do not work with a reference + // for fear of aliasing + return quote ? UNCLOSED_STRING : SUCCESS; +} + +// credit: based on code from Google Fuchsia (Apache Licensed) +simdjson_warn_unused bool implementation::validate_utf8(const char *buf, size_t len) const noexcept { + const uint8_t *data = reinterpret_cast(buf); + uint64_t pos = 0; + uint32_t code_point = 0; + while (pos < len) { + // check of the next 8 bytes are ascii. + uint64_t next_pos = pos + 16; + if (next_pos <= len) { // if it is safe to read 8 more bytes, check that they are ascii + uint64_t v1; + memcpy(&v1, data + pos, sizeof(uint64_t)); + uint64_t v2; + memcpy(&v2, data + pos + sizeof(uint64_t), sizeof(uint64_t)); + uint64_t v{v1 | v2}; + if ((v & 0x8080808080808080) == 0) { + pos = next_pos; + continue; + } + } + unsigned char byte = data[pos]; + if (byte < 0x80) { + pos++; + continue; + } else if ((byte & 0xe0) == 0xc0) { + next_pos = pos + 2; + if (next_pos > len) { return false; } + if ((data[pos + 1] & 0xc0) != 0x80) { return false; } + // range check + code_point = (byte & 0x1f) << 6 | (data[pos + 1] & 0x3f); + if (code_point < 0x80 || 0x7ff < code_point) { return false; } + } else if ((byte & 0xf0) == 0xe0) { + next_pos = pos + 3; + if (next_pos > len) { return false; } + if ((data[pos + 1] & 0xc0) != 0x80) { return false; } + if ((data[pos + 2] & 0xc0) != 0x80) { return false; } + // range check + code_point = (byte & 0x0f) << 12 | + (data[pos + 1] & 0x3f) << 6 | + (data[pos + 2] & 0x3f); + if (code_point < 0x800 || 0xffff < code_point || + (0xd7ff < code_point && code_point < 0xe000)) { + return false; + } + } else if ((byte & 0xf8) == 0xf0) { // 0b11110000 + next_pos = pos + 4; + if (next_pos > len) { return false; } + if ((data[pos + 1] & 0xc0) != 0x80) { return false; } + if ((data[pos + 2] & 0xc0) != 0x80) { return false; } + if ((data[pos + 3] & 0xc0) != 0x80) { return false; } + // range check + code_point = + (byte & 0x07) << 18 | (data[pos + 1] & 0x3f) << 12 | + (data[pos + 2] & 0x3f) << 6 | (data[pos + 3] & 0x3f); + if (code_point <= 0xffff || 0x10ffff < code_point) { return false; } + } else { + // we may have a continuation + return false; + } + pos = next_pos; + } + return true; +} + +} // namespace fallback +} // namespace simdjson + +// +// Stage 2 +// + +namespace simdjson { +namespace fallback { + +simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document &_doc) noexcept { + return stage2::tape_builder::parse_document(*this, _doc); +} + +simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { + return stage2::tape_builder::parse_document(*this, _doc); +} + +SIMDJSON_NO_SANITIZE_MEMORY +simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst, bool replacement_char) const noexcept { + return fallback::stringparsing::parse_string(src, dst, replacement_char); +} + +simdjson_warn_unused uint8_t *dom_parser_implementation::parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept { + return fallback::stringparsing::parse_wobbly_string(src, dst); +} + +simdjson_warn_unused error_code dom_parser_implementation::parse(const uint8_t *_buf, size_t _len, dom::document &_doc) noexcept { + auto error = stage1(_buf, _len, stage1_mode::regular); + if (error) { return error; } + return stage2(_doc); +} + +} // namespace fallback +} // namespace simdjson + +#include + +#endif // SIMDJSON_SRC_FALLBACK_CPP diff --git a/contrib/libs/simdjson/src/from_chars.cpp b/contrib/libs/simdjson/src/from_chars.cpp new file mode 100644 index 000000000000..34d62a3d7d22 --- /dev/null +++ b/contrib/libs/simdjson/src/from_chars.cpp @@ -0,0 +1,606 @@ +#ifndef SIMDJSON_SRC_FROM_CHARS_CPP +#define SIMDJSON_SRC_FROM_CHARS_CPP + +#include + +#include +#include +#include + +namespace simdjson { +namespace internal { + +/** + * The code in the internal::from_chars function is meant to handle the floating-point number parsing + * when we have more than 19 digits in the decimal mantissa. This should only be seen + * in adversarial scenarios: we do not expect production systems to even produce + * such floating-point numbers. + * + * The parser is based on work by Nigel Tao (at https://github.com/google/wuffs/) + * who credits Ken Thompson for the design (via a reference to the Go source + * code). See + * https://github.com/google/wuffs/blob/aa46859ea40c72516deffa1b146121952d6dfd3b/internal/cgen/base/floatconv-submodule-data.c + * https://github.com/google/wuffs/blob/46cd8105f47ca07ae2ba8e6a7818ef9c0df6c152/internal/cgen/base/floatconv-submodule-code.c + * It is probably not very fast but it is a fallback that should almost never be + * called in real life. Google Wuffs is published under APL 2.0. + **/ + +namespace { +constexpr uint32_t max_digits = 768; +constexpr int32_t decimal_point_range = 2047; +} // namespace + +struct adjusted_mantissa { + uint64_t mantissa; + int power2; + adjusted_mantissa() : mantissa(0), power2(0) {} +}; + +struct decimal { + uint32_t num_digits; + int32_t decimal_point; + bool negative; + bool truncated; + uint8_t digits[max_digits]; +}; + +template struct binary_format { + static constexpr int mantissa_explicit_bits(); + static constexpr int minimum_exponent(); + static constexpr int infinite_power(); + static constexpr int sign_index(); +}; + +template <> constexpr int binary_format::mantissa_explicit_bits() { + return 52; +} + +template <> constexpr int binary_format::minimum_exponent() { + return -1023; +} +template <> constexpr int binary_format::infinite_power() { + return 0x7FF; +} + +template <> constexpr int binary_format::sign_index() { return 63; } + +bool is_integer(char c) noexcept { return (c >= '0' && c <= '9'); } + +// This should always succeed since it follows a call to parse_number. +decimal parse_decimal(const char *&p) noexcept { + decimal answer; + answer.num_digits = 0; + answer.decimal_point = 0; + answer.truncated = false; + answer.negative = (*p == '-'); + if ((*p == '-') || (*p == '+')) { + ++p; + } + + while (*p == '0') { + ++p; + } + while (is_integer(*p)) { + if (answer.num_digits < max_digits) { + answer.digits[answer.num_digits] = uint8_t(*p - '0'); + } + answer.num_digits++; + ++p; + } + if (*p == '.') { + ++p; + const char *first_after_period = p; + // if we have not yet encountered a zero, we have to skip it as well + if (answer.num_digits == 0) { + // skip zeros + while (*p == '0') { + ++p; + } + } + while (is_integer(*p)) { + if (answer.num_digits < max_digits) { + answer.digits[answer.num_digits] = uint8_t(*p - '0'); + } + answer.num_digits++; + ++p; + } + answer.decimal_point = int32_t(first_after_period - p); + } + if(answer.num_digits > 0) { + const char *preverse = p - 1; + int32_t trailing_zeros = 0; + while ((*preverse == '0') || (*preverse == '.')) { + if(*preverse == '0') { trailing_zeros++; }; + --preverse; + } + answer.decimal_point += int32_t(answer.num_digits); + answer.num_digits -= uint32_t(trailing_zeros); + } + if(answer.num_digits > max_digits ) { + answer.num_digits = max_digits; + answer.truncated = true; + } + if (('e' == *p) || ('E' == *p)) { + ++p; + bool neg_exp = false; + if ('-' == *p) { + neg_exp = true; + ++p; + } else if ('+' == *p) { + ++p; + } + int32_t exp_number = 0; // exponential part + while (is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + if (exp_number < 0x10000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + answer.decimal_point += (neg_exp ? -exp_number : exp_number); + } + return answer; +} + +// This should always succeed since it follows a call to parse_number. +// Will not read at or beyond the "end" pointer. +decimal parse_decimal(const char *&p, const char * end) noexcept { + decimal answer; + answer.num_digits = 0; + answer.decimal_point = 0; + answer.truncated = false; + if(p == end) { return answer; } // should never happen + answer.negative = (*p == '-'); + if ((*p == '-') || (*p == '+')) { + ++p; + } + + while ((p != end) && (*p == '0')) { + ++p; + } + while ((p != end) && is_integer(*p)) { + if (answer.num_digits < max_digits) { + answer.digits[answer.num_digits] = uint8_t(*p - '0'); + } + answer.num_digits++; + ++p; + } + if ((p != end) && (*p == '.')) { + ++p; + if(p == end) { return answer; } // should never happen + const char *first_after_period = p; + // if we have not yet encountered a zero, we have to skip it as well + if (answer.num_digits == 0) { + // skip zeros + while (*p == '0') { + ++p; + } + } + while ((p != end) && is_integer(*p)) { + if (answer.num_digits < max_digits) { + answer.digits[answer.num_digits] = uint8_t(*p - '0'); + } + answer.num_digits++; + ++p; + } + answer.decimal_point = int32_t(first_after_period - p); + } + if(answer.num_digits > 0) { + const char *preverse = p - 1; + int32_t trailing_zeros = 0; + while ((*preverse == '0') || (*preverse == '.')) { + if(*preverse == '0') { trailing_zeros++; }; + --preverse; + } + answer.decimal_point += int32_t(answer.num_digits); + answer.num_digits -= uint32_t(trailing_zeros); + } + if(answer.num_digits > max_digits ) { + answer.num_digits = max_digits; + answer.truncated = true; + } + if ((p != end) && (('e' == *p) || ('E' == *p))) { + ++p; + if(p == end) { return answer; } // should never happen + bool neg_exp = false; + if ('-' == *p) { + neg_exp = true; + ++p; + } else if ('+' == *p) { + ++p; + } + int32_t exp_number = 0; // exponential part + while ((p != end) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + if (exp_number < 0x10000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + answer.decimal_point += (neg_exp ? -exp_number : exp_number); + } + return answer; +} + +namespace { + +// remove all final zeroes +inline void trim(decimal &h) { + while ((h.num_digits > 0) && (h.digits[h.num_digits - 1] == 0)) { + h.num_digits--; + } +} + +uint32_t number_of_digits_decimal_left_shift(decimal &h, uint32_t shift) { + shift &= 63; + const static uint16_t number_of_digits_decimal_left_shift_table[65] = { + 0x0000, 0x0800, 0x0801, 0x0803, 0x1006, 0x1009, 0x100D, 0x1812, 0x1817, + 0x181D, 0x2024, 0x202B, 0x2033, 0x203C, 0x2846, 0x2850, 0x285B, 0x3067, + 0x3073, 0x3080, 0x388E, 0x389C, 0x38AB, 0x38BB, 0x40CC, 0x40DD, 0x40EF, + 0x4902, 0x4915, 0x4929, 0x513E, 0x5153, 0x5169, 0x5180, 0x5998, 0x59B0, + 0x59C9, 0x61E3, 0x61FD, 0x6218, 0x6A34, 0x6A50, 0x6A6D, 0x6A8B, 0x72AA, + 0x72C9, 0x72E9, 0x7B0A, 0x7B2B, 0x7B4D, 0x8370, 0x8393, 0x83B7, 0x83DC, + 0x8C02, 0x8C28, 0x8C4F, 0x9477, 0x949F, 0x94C8, 0x9CF2, 0x051C, 0x051C, + 0x051C, 0x051C, + }; + uint32_t x_a = number_of_digits_decimal_left_shift_table[shift]; + uint32_t x_b = number_of_digits_decimal_left_shift_table[shift + 1]; + uint32_t num_new_digits = x_a >> 11; + uint32_t pow5_a = 0x7FF & x_a; + uint32_t pow5_b = 0x7FF & x_b; + const static uint8_t + number_of_digits_decimal_left_shift_table_powers_of_5[0x051C] = { + 5, 2, 5, 1, 2, 5, 6, 2, 5, 3, 1, 2, 5, 1, 5, 6, 2, 5, 7, 8, 1, 2, 5, + 3, 9, 0, 6, 2, 5, 1, 9, 5, 3, 1, 2, 5, 9, 7, 6, 5, 6, 2, 5, 4, 8, 8, + 2, 8, 1, 2, 5, 2, 4, 4, 1, 4, 0, 6, 2, 5, 1, 2, 2, 0, 7, 0, 3, 1, 2, + 5, 6, 1, 0, 3, 5, 1, 5, 6, 2, 5, 3, 0, 5, 1, 7, 5, 7, 8, 1, 2, 5, 1, + 5, 2, 5, 8, 7, 8, 9, 0, 6, 2, 5, 7, 6, 2, 9, 3, 9, 4, 5, 3, 1, 2, 5, + 3, 8, 1, 4, 6, 9, 7, 2, 6, 5, 6, 2, 5, 1, 9, 0, 7, 3, 4, 8, 6, 3, 2, + 8, 1, 2, 5, 9, 5, 3, 6, 7, 4, 3, 1, 6, 4, 0, 6, 2, 5, 4, 7, 6, 8, 3, + 7, 1, 5, 8, 2, 0, 3, 1, 2, 5, 2, 3, 8, 4, 1, 8, 5, 7, 9, 1, 0, 1, 5, + 6, 2, 5, 1, 1, 9, 2, 0, 9, 2, 8, 9, 5, 5, 0, 7, 8, 1, 2, 5, 5, 9, 6, + 0, 4, 6, 4, 4, 7, 7, 5, 3, 9, 0, 6, 2, 5, 2, 9, 8, 0, 2, 3, 2, 2, 3, + 8, 7, 6, 9, 5, 3, 1, 2, 5, 1, 4, 9, 0, 1, 1, 6, 1, 1, 9, 3, 8, 4, 7, + 6, 5, 6, 2, 5, 7, 4, 5, 0, 5, 8, 0, 5, 9, 6, 9, 2, 3, 8, 2, 8, 1, 2, + 5, 3, 7, 2, 5, 2, 9, 0, 2, 9, 8, 4, 6, 1, 9, 1, 4, 0, 6, 2, 5, 1, 8, + 6, 2, 6, 4, 5, 1, 4, 9, 2, 3, 0, 9, 5, 7, 0, 3, 1, 2, 5, 9, 3, 1, 3, + 2, 2, 5, 7, 4, 6, 1, 5, 4, 7, 8, 5, 1, 5, 6, 2, 5, 4, 6, 5, 6, 6, 1, + 2, 8, 7, 3, 0, 7, 7, 3, 9, 2, 5, 7, 8, 1, 2, 5, 2, 3, 2, 8, 3, 0, 6, + 4, 3, 6, 5, 3, 8, 6, 9, 6, 2, 8, 9, 0, 6, 2, 5, 1, 1, 6, 4, 1, 5, 3, + 2, 1, 8, 2, 6, 9, 3, 4, 8, 1, 4, 4, 5, 3, 1, 2, 5, 5, 8, 2, 0, 7, 6, + 6, 0, 9, 1, 3, 4, 6, 7, 4, 0, 7, 2, 2, 6, 5, 6, 2, 5, 2, 9, 1, 0, 3, + 8, 3, 0, 4, 5, 6, 7, 3, 3, 7, 0, 3, 6, 1, 3, 2, 8, 1, 2, 5, 1, 4, 5, + 5, 1, 9, 1, 5, 2, 2, 8, 3, 6, 6, 8, 5, 1, 8, 0, 6, 6, 4, 0, 6, 2, 5, + 7, 2, 7, 5, 9, 5, 7, 6, 1, 4, 1, 8, 3, 4, 2, 5, 9, 0, 3, 3, 2, 0, 3, + 1, 2, 5, 3, 6, 3, 7, 9, 7, 8, 8, 0, 7, 0, 9, 1, 7, 1, 2, 9, 5, 1, 6, + 6, 0, 1, 5, 6, 2, 5, 1, 8, 1, 8, 9, 8, 9, 4, 0, 3, 5, 4, 5, 8, 5, 6, + 4, 7, 5, 8, 3, 0, 0, 7, 8, 1, 2, 5, 9, 0, 9, 4, 9, 4, 7, 0, 1, 7, 7, + 2, 9, 2, 8, 2, 3, 7, 9, 1, 5, 0, 3, 9, 0, 6, 2, 5, 4, 5, 4, 7, 4, 7, + 3, 5, 0, 8, 8, 6, 4, 6, 4, 1, 1, 8, 9, 5, 7, 5, 1, 9, 5, 3, 1, 2, 5, + 2, 2, 7, 3, 7, 3, 6, 7, 5, 4, 4, 3, 2, 3, 2, 0, 5, 9, 4, 7, 8, 7, 5, + 9, 7, 6, 5, 6, 2, 5, 1, 1, 3, 6, 8, 6, 8, 3, 7, 7, 2, 1, 6, 1, 6, 0, + 2, 9, 7, 3, 9, 3, 7, 9, 8, 8, 2, 8, 1, 2, 5, 5, 6, 8, 4, 3, 4, 1, 8, + 8, 6, 0, 8, 0, 8, 0, 1, 4, 8, 6, 9, 6, 8, 9, 9, 4, 1, 4, 0, 6, 2, 5, + 2, 8, 4, 2, 1, 7, 0, 9, 4, 3, 0, 4, 0, 4, 0, 0, 7, 4, 3, 4, 8, 4, 4, + 9, 7, 0, 7, 0, 3, 1, 2, 5, 1, 4, 2, 1, 0, 8, 5, 4, 7, 1, 5, 2, 0, 2, + 0, 0, 3, 7, 1, 7, 4, 2, 2, 4, 8, 5, 3, 5, 1, 5, 6, 2, 5, 7, 1, 0, 5, + 4, 2, 7, 3, 5, 7, 6, 0, 1, 0, 0, 1, 8, 5, 8, 7, 1, 1, 2, 4, 2, 6, 7, + 5, 7, 8, 1, 2, 5, 3, 5, 5, 2, 7, 1, 3, 6, 7, 8, 8, 0, 0, 5, 0, 0, 9, + 2, 9, 3, 5, 5, 6, 2, 1, 3, 3, 7, 8, 9, 0, 6, 2, 5, 1, 7, 7, 6, 3, 5, + 6, 8, 3, 9, 4, 0, 0, 2, 5, 0, 4, 6, 4, 6, 7, 7, 8, 1, 0, 6, 6, 8, 9, + 4, 5, 3, 1, 2, 5, 8, 8, 8, 1, 7, 8, 4, 1, 9, 7, 0, 0, 1, 2, 5, 2, 3, + 2, 3, 3, 8, 9, 0, 5, 3, 3, 4, 4, 7, 2, 6, 5, 6, 2, 5, 4, 4, 4, 0, 8, + 9, 2, 0, 9, 8, 5, 0, 0, 6, 2, 6, 1, 6, 1, 6, 9, 4, 5, 2, 6, 6, 7, 2, + 3, 6, 3, 2, 8, 1, 2, 5, 2, 2, 2, 0, 4, 4, 6, 0, 4, 9, 2, 5, 0, 3, 1, + 3, 0, 8, 0, 8, 4, 7, 2, 6, 3, 3, 3, 6, 1, 8, 1, 6, 4, 0, 6, 2, 5, 1, + 1, 1, 0, 2, 2, 3, 0, 2, 4, 6, 2, 5, 1, 5, 6, 5, 4, 0, 4, 2, 3, 6, 3, + 1, 6, 6, 8, 0, 9, 0, 8, 2, 0, 3, 1, 2, 5, 5, 5, 5, 1, 1, 1, 5, 1, 2, + 3, 1, 2, 5, 7, 8, 2, 7, 0, 2, 1, 1, 8, 1, 5, 8, 3, 4, 0, 4, 5, 4, 1, + 0, 1, 5, 6, 2, 5, 2, 7, 7, 5, 5, 5, 7, 5, 6, 1, 5, 6, 2, 8, 9, 1, 3, + 5, 1, 0, 5, 9, 0, 7, 9, 1, 7, 0, 2, 2, 7, 0, 5, 0, 7, 8, 1, 2, 5, 1, + 3, 8, 7, 7, 7, 8, 7, 8, 0, 7, 8, 1, 4, 4, 5, 6, 7, 5, 5, 2, 9, 5, 3, + 9, 5, 8, 5, 1, 1, 3, 5, 2, 5, 3, 9, 0, 6, 2, 5, 6, 9, 3, 8, 8, 9, 3, + 9, 0, 3, 9, 0, 7, 2, 2, 8, 3, 7, 7, 6, 4, 7, 6, 9, 7, 9, 2, 5, 5, 6, + 7, 6, 2, 6, 9, 5, 3, 1, 2, 5, 3, 4, 6, 9, 4, 4, 6, 9, 5, 1, 9, 5, 3, + 6, 1, 4, 1, 8, 8, 8, 2, 3, 8, 4, 8, 9, 6, 2, 7, 8, 3, 8, 1, 3, 4, 7, + 6, 5, 6, 2, 5, 1, 7, 3, 4, 7, 2, 3, 4, 7, 5, 9, 7, 6, 8, 0, 7, 0, 9, + 4, 4, 1, 1, 9, 2, 4, 4, 8, 1, 3, 9, 1, 9, 0, 6, 7, 3, 8, 2, 8, 1, 2, + 5, 8, 6, 7, 3, 6, 1, 7, 3, 7, 9, 8, 8, 4, 0, 3, 5, 4, 7, 2, 0, 5, 9, + 6, 2, 2, 4, 0, 6, 9, 5, 9, 5, 3, 3, 6, 9, 1, 4, 0, 6, 2, 5, + }; + const uint8_t *pow5 = + &number_of_digits_decimal_left_shift_table_powers_of_5[pow5_a]; + uint32_t i = 0; + uint32_t n = pow5_b - pow5_a; + for (; i < n; i++) { + if (i >= h.num_digits) { + return num_new_digits - 1; + } else if (h.digits[i] == pow5[i]) { + continue; + } else if (h.digits[i] < pow5[i]) { + return num_new_digits - 1; + } else { + return num_new_digits; + } + } + return num_new_digits; +} + +} // end of anonymous namespace + +uint64_t round(decimal &h) { + if ((h.num_digits == 0) || (h.decimal_point < 0)) { + return 0; + } else if (h.decimal_point > 18) { + return UINT64_MAX; + } + // at this point, we know that h.decimal_point >= 0 + uint32_t dp = uint32_t(h.decimal_point); + uint64_t n = 0; + for (uint32_t i = 0; i < dp; i++) { + n = (10 * n) + ((i < h.num_digits) ? h.digits[i] : 0); + } + bool round_up = false; + if (dp < h.num_digits) { + round_up = h.digits[dp] >= 5; // normally, we round up + // but we may need to round to even! + if ((h.digits[dp] == 5) && (dp + 1 == h.num_digits)) { + round_up = h.truncated || ((dp > 0) && (1 & h.digits[dp - 1])); + } + } + if (round_up) { + n++; + } + return n; +} + +// computes h * 2^-shift +void decimal_left_shift(decimal &h, uint32_t shift) { + if (h.num_digits == 0) { + return; + } + uint32_t num_new_digits = number_of_digits_decimal_left_shift(h, shift); + int32_t read_index = int32_t(h.num_digits - 1); + uint32_t write_index = h.num_digits - 1 + num_new_digits; + uint64_t n = 0; + + while (read_index >= 0) { + n += uint64_t(h.digits[read_index]) << shift; + uint64_t quotient = n / 10; + uint64_t remainder = n - (10 * quotient); + if (write_index < max_digits) { + h.digits[write_index] = uint8_t(remainder); + } else if (remainder > 0) { + h.truncated = true; + } + n = quotient; + write_index--; + read_index--; + } + while (n > 0) { + uint64_t quotient = n / 10; + uint64_t remainder = n - (10 * quotient); + if (write_index < max_digits) { + h.digits[write_index] = uint8_t(remainder); + } else if (remainder > 0) { + h.truncated = true; + } + n = quotient; + write_index--; + } + h.num_digits += num_new_digits; + if (h.num_digits > max_digits) { + h.num_digits = max_digits; + } + h.decimal_point += int32_t(num_new_digits); + trim(h); +} + +// computes h * 2^shift +void decimal_right_shift(decimal &h, uint32_t shift) { + uint32_t read_index = 0; + uint32_t write_index = 0; + + uint64_t n = 0; + + while ((n >> shift) == 0) { + if (read_index < h.num_digits) { + n = (10 * n) + h.digits[read_index++]; + } else if (n == 0) { + return; + } else { + while ((n >> shift) == 0) { + n = 10 * n; + read_index++; + } + break; + } + } + h.decimal_point -= int32_t(read_index - 1); + if (h.decimal_point < -decimal_point_range) { // it is zero + h.num_digits = 0; + h.decimal_point = 0; + h.negative = false; + h.truncated = false; + return; + } + uint64_t mask = (uint64_t(1) << shift) - 1; + while (read_index < h.num_digits) { + uint8_t new_digit = uint8_t(n >> shift); + n = (10 * (n & mask)) + h.digits[read_index++]; + h.digits[write_index++] = new_digit; + } + while (n > 0) { + uint8_t new_digit = uint8_t(n >> shift); + n = 10 * (n & mask); + if (write_index < max_digits) { + h.digits[write_index++] = new_digit; + } else if (new_digit > 0) { + h.truncated = true; + } + } + h.num_digits = write_index; + trim(h); +} + +template adjusted_mantissa compute_float(decimal &d) { + adjusted_mantissa answer; + if (d.num_digits == 0) { + // should be zero + answer.power2 = 0; + answer.mantissa = 0; + return answer; + } + // At this point, going further, we can assume that d.num_digits > 0. + // We want to guard against excessive decimal point values because + // they can result in long running times. Indeed, we do + // shifts by at most 60 bits. We have that log(10**400)/log(2**60) ~= 22 + // which is fine, but log(10**299995)/log(2**60) ~= 16609 which is not + // fine (runs for a long time). + // + if(d.decimal_point < -324) { + // We have something smaller than 1e-324 which is always zero + // in binary64 and binary32. + // It should be zero. + answer.power2 = 0; + answer.mantissa = 0; + return answer; + } else if(d.decimal_point >= 310) { + // We have something at least as large as 0.1e310 which is + // always infinite. + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + + static const uint32_t max_shift = 60; + static const uint32_t num_powers = 19; + static const uint8_t powers[19] = { + 0, 3, 6, 9, 13, 16, 19, 23, 26, 29, // + 33, 36, 39, 43, 46, 49, 53, 56, 59, // + }; + int32_t exp2 = 0; + while (d.decimal_point > 0) { + uint32_t n = uint32_t(d.decimal_point); + uint32_t shift = (n < num_powers) ? powers[n] : max_shift; + decimal_right_shift(d, shift); + if (d.decimal_point < -decimal_point_range) { + // should be zero + answer.power2 = 0; + answer.mantissa = 0; + return answer; + } + exp2 += int32_t(shift); + } + // We shift left toward [1/2 ... 1]. + while (d.decimal_point <= 0) { + uint32_t shift; + if (d.decimal_point == 0) { + if (d.digits[0] >= 5) { + break; + } + shift = (d.digits[0] < 2) ? 2 : 1; + } else { + uint32_t n = uint32_t(-d.decimal_point); + shift = (n < num_powers) ? powers[n] : max_shift; + } + decimal_left_shift(d, shift); + if (d.decimal_point > decimal_point_range) { + // we want to get infinity: + answer.power2 = 0xFF; + answer.mantissa = 0; + return answer; + } + exp2 -= int32_t(shift); + } + // We are now in the range [1/2 ... 1] but the binary format uses [1 ... 2]. + exp2--; + constexpr int32_t minimum_exponent = binary::minimum_exponent(); + while ((minimum_exponent + 1) > exp2) { + uint32_t n = uint32_t((minimum_exponent + 1) - exp2); + if (n > max_shift) { + n = max_shift; + } + decimal_right_shift(d, n); + exp2 += int32_t(n); + } + if ((exp2 - minimum_exponent) >= binary::infinite_power()) { + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + + const int mantissa_size_in_bits = binary::mantissa_explicit_bits() + 1; + decimal_left_shift(d, mantissa_size_in_bits); + + uint64_t mantissa = round(d); + // It is possible that we have an overflow, in which case we need + // to shift back. + if (mantissa >= (uint64_t(1) << mantissa_size_in_bits)) { + decimal_right_shift(d, 1); + exp2 += 1; + mantissa = round(d); + if ((exp2 - minimum_exponent) >= binary::infinite_power()) { + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + } + answer.power2 = exp2 - binary::minimum_exponent(); + if (mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) { + answer.power2--; + } + answer.mantissa = + mantissa & ((uint64_t(1) << binary::mantissa_explicit_bits()) - 1); + return answer; +} + +template +adjusted_mantissa parse_long_mantissa(const char *first) { + decimal d = parse_decimal(first); + return compute_float(d); +} + +template +adjusted_mantissa parse_long_mantissa(const char *first, const char *end) { + decimal d = parse_decimal(first, end); + return compute_float(d); +} + +double from_chars(const char *first) noexcept { + bool negative = first[0] == '-'; + if (negative) { + first++; + } + adjusted_mantissa am = parse_long_mantissa>(first); + uint64_t word = am.mantissa; + word |= uint64_t(am.power2) + << binary_format::mantissa_explicit_bits(); + word = negative ? word | (uint64_t(1) << binary_format::sign_index()) + : word; + double value; + std::memcpy(&value, &word, sizeof(double)); + return value; +} + + +double from_chars(const char *first, const char *end) noexcept { + bool negative = first[0] == '-'; + if (negative) { + first++; + } + adjusted_mantissa am = parse_long_mantissa>(first, end); + uint64_t word = am.mantissa; + word |= uint64_t(am.power2) + << binary_format::mantissa_explicit_bits(); + word = negative ? word | (uint64_t(1) << binary_format::sign_index()) + : word; + double value; + std::memcpy(&value, &word, sizeof(double)); + return value; +} + +} // internal +} // simdjson + +#endif // SIMDJSON_SRC_FROM_CHARS_CPP \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/amalgamated.h b/contrib/libs/simdjson/src/generic/amalgamated.h new file mode 100644 index 000000000000..32154f602e59 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/amalgamated.h @@ -0,0 +1,7 @@ +#if defined(SIMDJSON_CONDITIONAL_INCLUDE) && !defined(SIMDJSON_SRC_GENERIC_DEPENDENCIES_H) +#error generic/dependencies.h must be included before generic/amalgamated.h! +#endif + +#include +#include +#include diff --git a/contrib/libs/simdjson/src/generic/base.h b/contrib/libs/simdjson/src/generic/base.h new file mode 100644 index 000000000000..77947651a8f8 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/base.h @@ -0,0 +1,19 @@ +#ifndef SIMDJSON_SRC_GENERIC_BASE_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_BASE_H +#include +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { + +struct json_character_block; + +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_BASE_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/dependencies.h b/contrib/libs/simdjson/src/generic/dependencies.h new file mode 100644 index 000000000000..ce5a2f01111a --- /dev/null +++ b/contrib/libs/simdjson/src/generic/dependencies.h @@ -0,0 +1,10 @@ +#ifdef SIMDJSON_CONDITIONAL_INCLUDE +#error generic/dependencies.h must be included before defining SIMDJSON_CONDITIONAL_INCLUDE! +#endif + +#ifndef SIMDJSON_SRC_GENERIC_DEPENDENCIES_H +#define SIMDJSON_SRC_GENERIC_DEPENDENCIES_H + +#include + +#endif // SIMDJSON_SRC_GENERIC_DEPENDENCIES_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/dom_parser_implementation.h b/contrib/libs/simdjson/src/generic/dom_parser_implementation.h new file mode 100644 index 000000000000..20f7813fcae5 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/dom_parser_implementation.h @@ -0,0 +1,21 @@ +#ifndef SIMDJSON_SRC_GENERIC_DOM_PARSER_IMPLEMENTATION_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_DOM_PARSER_IMPLEMENTATION_H +#include +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +// Interface a dom parser implementation must fulfill +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { + +simdjson_inline simd8 must_be_2_3_continuation(const simd8 prev2, const simd8 prev3); +simdjson_inline bool is_ascii(const simd8x64& input); + +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_DOM_PARSER_IMPLEMENTATION_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/json_character_block.h b/contrib/libs/simdjson/src/generic/json_character_block.h new file mode 100644 index 000000000000..7cce34c83b35 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/json_character_block.h @@ -0,0 +1,27 @@ +#ifndef SIMDJSON_SRC_GENERIC_JSON_CHARACTER_BLOCK_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_JSON_CHARACTER_BLOCK_H +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { + +struct json_character_block { + static simdjson_inline json_character_block classify(const simd::simd8x64& in); + + simdjson_inline uint64_t whitespace() const noexcept { return _whitespace; } + simdjson_inline uint64_t op() const noexcept { return _op; } + simdjson_inline uint64_t scalar() const noexcept { return ~(op() | whitespace()); } + + uint64_t _whitespace; + uint64_t _op; +}; + +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_JSON_CHARACTER_BLOCK_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/stage1/amalgamated.h b/contrib/libs/simdjson/src/generic/stage1/amalgamated.h new file mode 100644 index 000000000000..ed083677fd08 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage1/amalgamated.h @@ -0,0 +1,13 @@ +// Stuff other things depend on +#include +#include +#include +#include +#include +#include + +// All other declarations +#include +#include +#include +#include diff --git a/contrib/libs/simdjson/src/generic/stage1/base.h b/contrib/libs/simdjson/src/generic/stage1/base.h new file mode 100644 index 000000000000..a6413fadba44 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage1/base.h @@ -0,0 +1,35 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE1_BASE_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_STAGE1_BASE_H +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +namespace stage1 { + +class bit_indexer; +template +struct buf_block_reader; +struct json_block; +class json_minifier; +class json_scanner; +struct json_string_block; +class json_string_scanner; +class json_structural_indexer; + +} // namespace stage1 + +namespace utf8_validation { +struct utf8_checker; +} // namespace utf8_validation + +using utf8_validation::utf8_checker; + +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_STAGE1_BASE_H diff --git a/contrib/libs/simdjson/src/generic/stage1/buf_block_reader.h b/contrib/libs/simdjson/src/generic/stage1/buf_block_reader.h new file mode 100644 index 000000000000..b3e4ec7d5903 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage1/buf_block_reader.h @@ -0,0 +1,116 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE1_BUF_BLOCK_READER_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_STAGE1_BUF_BLOCK_READER_H +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +namespace stage1 { + +// Walks through a buffer in block-sized increments, loading the last part with spaces +template +struct buf_block_reader { +public: + simdjson_inline buf_block_reader(const uint8_t *_buf, size_t _len); + simdjson_inline size_t block_index(); + simdjson_inline bool has_full_block() const; + simdjson_inline const uint8_t *full_block() const; + /** + * Get the last block, padded with spaces. + * + * There will always be a last block, with at least 1 byte, unless len == 0 (in which case this + * function fills the buffer with spaces and returns 0. In particular, if len == STEP_SIZE there + * will be 0 full_blocks and 1 remainder block with STEP_SIZE bytes and no spaces for padding. + * + * @return the number of effective characters in the last block. + */ + simdjson_inline size_t get_remainder(uint8_t *dst) const; + simdjson_inline void advance(); +private: + const uint8_t *buf; + const size_t len; + const size_t lenminusstep; + size_t idx; +}; + +// Routines to print masks and text for debugging bitmask operations +simdjson_unused static char * format_input_text_64(const uint8_t *text) { + static char buf[sizeof(simd8x64) + 1]; + for (size_t i=0; i); i++) { + buf[i] = int8_t(text[i]) < ' ' ? '_' : int8_t(text[i]); + } + buf[sizeof(simd8x64)] = '\0'; + return buf; +} + +// Routines to print masks and text for debugging bitmask operations +simdjson_unused static char * format_input_text(const simd8x64& in) { + static char buf[sizeof(simd8x64) + 1]; + in.store(reinterpret_cast(buf)); + for (size_t i=0; i); i++) { + if (buf[i] < ' ') { buf[i] = '_'; } + } + buf[sizeof(simd8x64)] = '\0'; + return buf; +} + +simdjson_unused static char * format_input_text(const simd8x64& in, uint64_t mask) { + static char buf[sizeof(simd8x64) + 1]; + in.store(reinterpret_cast(buf)); + for (size_t i=0; i); i++) { + if (buf[i] <= ' ') { buf[i] = '_'; } + if (!(mask & (size_t(1) << i))) { buf[i] = ' '; } + } + buf[sizeof(simd8x64)] = '\0'; + return buf; +} + +simdjson_unused static char * format_mask(uint64_t mask) { + static char buf[sizeof(simd8x64) + 1]; + for (size_t i=0; i<64; i++) { + buf[i] = (mask & (size_t(1) << i)) ? 'X' : ' '; + } + buf[64] = '\0'; + return buf; +} + +template +simdjson_inline buf_block_reader::buf_block_reader(const uint8_t *_buf, size_t _len) : buf{_buf}, len{_len}, lenminusstep{len < STEP_SIZE ? 0 : len - STEP_SIZE}, idx{0} {} + +template +simdjson_inline size_t buf_block_reader::block_index() { return idx; } + +template +simdjson_inline bool buf_block_reader::has_full_block() const { + return idx < lenminusstep; +} + +template +simdjson_inline const uint8_t *buf_block_reader::full_block() const { + return &buf[idx]; +} + +template +simdjson_inline size_t buf_block_reader::get_remainder(uint8_t *dst) const { + if(len == idx) { return 0; } // memcpy(dst, null, 0) will trigger an error with some sanitizers + std::memset(dst, 0x20, STEP_SIZE); // std::memset STEP_SIZE because it's more efficient to write out 8 or 16 bytes at once. + std::memcpy(dst, buf + idx, len - idx); + return len - idx; +} + +template +simdjson_inline void buf_block_reader::advance() { + idx += STEP_SIZE; +} + +} // namespace stage1 +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_STAGE1_BUF_BLOCK_READER_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/stage1/dependencies.h b/contrib/libs/simdjson/src/generic/stage1/dependencies.h new file mode 100644 index 000000000000..dfd8d8fa93e0 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage1/dependencies.h @@ -0,0 +1,4 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE1_DEPENDENCIES_H +#define SIMDJSON_SRC_GENERIC_STAGE1_DEPENDENCIES_H + +#endif // SIMDJSON_SRC_GENERIC_STAGE1_DEPENDENCIES_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/stage1/find_next_document_index.h b/contrib/libs/simdjson/src/generic/stage1/find_next_document_index.h new file mode 100644 index 000000000000..162595438abd --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage1/find_next_document_index.h @@ -0,0 +1,105 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE1_FIND_NEXT_DOCUMENT_INDEX_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_STAGE1_FIND_NEXT_DOCUMENT_INDEX_H +#include +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +namespace stage1 { + +/** + * This algorithm is used to quickly identify the last structural position that + * makes up a complete document. + * + * It does this by going backwards and finding the last *document boundary* (a + * place where one value follows another without a comma between them). If the + * last document (the characters after the boundary) has an equal number of + * start and end brackets, it is considered complete. + * + * Simply put, we iterate over the structural characters, starting from + * the end. We consider that we found the end of a JSON document when the + * first element of the pair is NOT one of these characters: '{' '[' ':' ',' + * and when the second element is NOT one of these characters: '}' ']' ':' ','. + * + * This simple comparison works most of the time, but it does not cover cases + * where the batch's structural indexes contain a perfect amount of documents. + * In such a case, we do not have access to the structural index which follows + * the last document, therefore, we do not have access to the second element in + * the pair, and that means we cannot identify the last document. To fix this + * issue, we keep a count of the open and closed curly/square braces we found + * while searching for the pair. When we find a pair AND the count of open and + * closed curly/square braces is the same, we know that we just passed a + * complete document, therefore the last json buffer location is the end of the + * batch. + */ +simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &parser) { + // Variant: do not count separately, just figure out depth + if(parser.n_structural_indexes == 0) { return 0; } + auto arr_cnt = 0; + auto obj_cnt = 0; + for (auto i = parser.n_structural_indexes - 1; i > 0; i--) { + auto idxb = parser.structural_indexes[i]; + switch (parser.buf[idxb]) { + case ':': + case ',': + continue; + case '}': + obj_cnt--; + continue; + case ']': + arr_cnt--; + continue; + case '{': + obj_cnt++; + break; + case '[': + arr_cnt++; + break; + } + auto idxa = parser.structural_indexes[i - 1]; + switch (parser.buf[idxa]) { + case '{': + case '[': + case ':': + case ',': + continue; + } + // Last document is complete, so the next document will appear after! + if (!arr_cnt && !obj_cnt) { + return parser.n_structural_indexes; + } + // Last document is incomplete; mark the document at i + 1 as the next one + return i; + } + // If we made it to the end, we want to finish counting to see if we have a full document. + switch (parser.buf[parser.structural_indexes[0]]) { + case '}': + obj_cnt--; + break; + case ']': + arr_cnt--; + break; + case '{': + obj_cnt++; + break; + case '[': + arr_cnt++; + break; + } + if (!arr_cnt && !obj_cnt) { + // We have a complete document. + return parser.n_structural_indexes; + } + return 0; +} + +} // namespace stage1 +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_STAGE1_FIND_NEXT_DOCUMENT_INDEX_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/stage1/json_escape_scanner.h b/contrib/libs/simdjson/src/generic/stage1/json_escape_scanner.h new file mode 100644 index 000000000000..ee58e1ce5c84 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage1/json_escape_scanner.h @@ -0,0 +1,151 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE1_JSON_ESCAPE_SCANNER_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_STAGE1_JSON_ESCAPE_SCANNER_H +#include +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +namespace stage1 { + +/** + * Scans for escape characters in JSON, taking care with multiple backslashes (\\n vs. \n). + */ +struct json_escape_scanner { + /** The actual escape characters (the backslashes themselves). */ + uint64_t next_is_escaped = 0ULL; + + struct escaped_and_escape { + /** + * Mask of escaped characters. + * + * ``` + * \n \\n \\\n \\\\n \ + * 0100100010100101000 + * n \ \ n \ \ + * ``` + */ + uint64_t escaped; + /** + * Mask of escape characters. + * + * ``` + * \n \\n \\\n \\\\n \ + * 1001000101001010001 + * \ \ \ \ \ \ \ + * ``` + */ + uint64_t escape; + }; + + /** + * Get a mask of both escape and escaped characters (the characters following a backslash). + * + * @param potential_escape A mask of the character that can escape others (but could be + * escaped itself). e.g. block.eq('\\') + */ + simdjson_really_inline escaped_and_escape next(uint64_t backslash) noexcept { + +#if !SIMDJSON_SKIP_BACKSLASH_SHORT_CIRCUIT + if (!backslash) { return {next_escaped_without_backslashes(), 0}; } +#endif + + // | | Mask (shows characters instead of 1's) | Depth | Instructions | + // |--------------------------------|----------------------------------------|-------|---------------------| + // | string | `\\n_\\\n___\\\n___\\\\___\\\\__\\\` | | | + // | | ` even odd even odd odd` | | | + // | potential_escape | ` \ \\\ \\\ \\\\ \\\\ \\\` | 1 | 1 (backslash & ~first_is_escaped) + // | escape_and_terminal_code | ` \n \ \n \ \n \ \ \ \ \ \` | 5 | 5 (next_escape_and_terminal_code()) + // | escaped | `\ \ n \ n \ \ \ \ \ ` X | 6 | 7 (escape_and_terminal_code ^ (potential_escape | first_is_escaped)) + // | escape | ` \ \ \ \ \ \ \ \ \ \` | 6 | 8 (escape_and_terminal_code & backslash) + // | first_is_escaped | `\ ` | 7 (*) | 9 (escape >> 63) () + // (*) this is not needed until the next iteration + uint64_t escape_and_terminal_code = next_escape_and_terminal_code(backslash & ~this->next_is_escaped); + uint64_t escaped = escape_and_terminal_code ^ (backslash | this->next_is_escaped); + uint64_t escape = escape_and_terminal_code & backslash; + this->next_is_escaped = escape >> 63; + return {escaped, escape}; + } + +private: + static constexpr const uint64_t ODD_BITS = 0xAAAAAAAAAAAAAAAAULL; + + simdjson_really_inline uint64_t next_escaped_without_backslashes() noexcept { + uint64_t escaped = this->next_is_escaped; + this->next_is_escaped = 0; + return escaped; + } + + /** + * Returns a mask of the next escape characters (masking out escaped backslashes), along with + * any non-backslash escape codes. + * + * \n \\n \\\n \\\\n returns: + * \n \ \ \n \ \ + * 11 100 1011 10100 + * + * You are expected to mask out the first bit yourself if the previous block had a trailing + * escape. + * + * & the result with potential_escape to get just the escape characters. + * ^ the result with (potential_escape | first_is_escaped) to get escaped characters. + */ + static simdjson_really_inline uint64_t next_escape_and_terminal_code(uint64_t potential_escape) noexcept { + // If we were to just shift and mask out any odd bits, we'd actually get a *half* right answer: + // any even-aligned backslash runs would be correct! Odd-aligned backslash runs would be + // inverted (\\\ would be 010 instead of 101). + // + // ``` + // string: | ____\\\\_\\\\_____ | + // maybe_escaped | ODD | \ \ \ \ | + // even-aligned ^^^ ^^^^ odd-aligned + // ``` + // + // Taking that into account, our basic strategy is: + // + // 1. Use subtraction to produce a mask with 1's for even-aligned runs and 0's for + // odd-aligned runs. + // 2. XOR all odd bits, which masks out the odd bits in even-aligned runs, and brings IN the + // odd bits in odd-aligned runs. + // 3. & with backslash to clean up any stray bits. + // runs are set to 0, and then XORing with "odd": + // + // | | Mask (shows characters instead of 1's) | Instructions | + // |--------------------------------|----------------------------------------|---------------------| + // | string | `\\n_\\\n___\\\n___\\\\___\\\\__\\\` | + // | | ` even odd even odd odd` | + // | maybe_escaped | ` n \\n \\n \\\_ \\\_ \\` X | 1 (potential_escape << 1) + // | maybe_escaped_and_odd | ` \n_ \\n _ \\\n_ _ \\\__ _\\\_ \\\` | 1 (maybe_escaped | odd) + // | even_series_codes_and_odd | ` n_\\\ _ n_ _\\\\ _ _ ` | 1 (maybe_escaped_and_odd - potential_escape) + // | escape_and_terminal_code | ` \n \ \n \ \n \ \ \ \ \ \` | 1 (^ odd) + // + + // Escaped characters are characters following an escape. + uint64_t maybe_escaped = potential_escape << 1; + + // To distinguish odd from even escape sequences, therefore, we turn on any *starting* + // escapes that are on an odd byte. (We actually bring in all odd bits, for speed.) + // - Odd runs of backslashes are 0000, and the code at the end ("n" in \n or \\n) is 1. + // - Odd runs of backslashes are 1111, and the code at the end ("n" in \n or \\n) is 0. + // - All other odd bytes are 1, and even bytes are 0. + uint64_t maybe_escaped_and_odd_bits = maybe_escaped | ODD_BITS; + uint64_t even_series_codes_and_odd_bits = maybe_escaped_and_odd_bits - potential_escape; + + // Now we flip all odd bytes back with xor. This: + // - Makes odd runs of backslashes go from 0000 to 1010 + // - Makes even runs of backslashes go from 1111 to 1010 + // - Sets actually-escaped codes to 1 (the n in \n and \\n: \n = 11, \\n = 100) + // - Resets all other bytes to 0 + return even_series_codes_and_odd_bits ^ ODD_BITS; + } +}; + +} // namespace stage1 +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_STAGE1_JSON_STRING_SCANNER_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/stage1/json_minifier.h b/contrib/libs/simdjson/src/generic/stage1/json_minifier.h new file mode 100644 index 000000000000..22ddaf2dd837 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage1/json_minifier.h @@ -0,0 +1,104 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE1_JSON_MINIFIER_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_STAGE1_JSON_MINIFIER_H +#include +#include +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +// This file contains the common code every implementation uses in stage1 +// It is intended to be included multiple times and compiled multiple times +// We assume the file in which it is included already includes +// "simdjson/stage1.h" (this simplifies amalgation) + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +namespace stage1 { + +class json_minifier { +public: + template + static error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) noexcept; + +private: + simdjson_inline json_minifier(uint8_t *_dst) + : dst{_dst} + {} + template + simdjson_inline void step(const uint8_t *block_buf, buf_block_reader &reader) noexcept; + simdjson_inline void next(const simd::simd8x64& in, const json_block& block); + simdjson_inline error_code finish(uint8_t *dst_start, size_t &dst_len); + json_scanner scanner{}; + uint8_t *dst; +}; + +simdjson_inline void json_minifier::next(const simd::simd8x64& in, const json_block& block) { + uint64_t mask = block.whitespace(); + dst += in.compress(mask, dst); +} + +simdjson_inline error_code json_minifier::finish(uint8_t *dst_start, size_t &dst_len) { + error_code error = scanner.finish(); + if (error) { dst_len = 0; return error; } + dst_len = dst - dst_start; + return SUCCESS; +} + +template<> +simdjson_inline void json_minifier::step<128>(const uint8_t *block_buf, buf_block_reader<128> &reader) noexcept { + simd::simd8x64 in_1(block_buf); + simd::simd8x64 in_2(block_buf+64); + json_block block_1 = scanner.next(in_1); + json_block block_2 = scanner.next(in_2); + this->next(in_1, block_1); + this->next(in_2, block_2); + reader.advance(); +} + +template<> +simdjson_inline void json_minifier::step<64>(const uint8_t *block_buf, buf_block_reader<64> &reader) noexcept { + simd::simd8x64 in_1(block_buf); + json_block block_1 = scanner.next(in_1); + this->next(block_buf, block_1); + reader.advance(); +} + +template +error_code json_minifier::minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) noexcept { + buf_block_reader reader(buf, len); + json_minifier minifier(dst); + + // Index the first n-1 blocks + while (reader.has_full_block()) { + minifier.step(reader.full_block(), reader); + } + + // Index the last (remainder) block, padded with spaces + uint8_t block[STEP_SIZE]; + size_t remaining_bytes = reader.get_remainder(block); + if (remaining_bytes > 0) { + // We do not want to write directly to the output stream. Rather, we write + // to a local buffer (for safety). + uint8_t out_block[STEP_SIZE]; + uint8_t * const guarded_dst{minifier.dst}; + minifier.dst = out_block; + minifier.step(block, reader); + size_t to_write = minifier.dst - out_block; + // In some cases, we could be enticed to consider the padded spaces + // as part of the string. This is fine as long as we do not write more + // than we consumed. + if(to_write > remaining_bytes) { to_write = remaining_bytes; } + memcpy(guarded_dst, out_block, to_write); + minifier.dst = guarded_dst + to_write; + } + return minifier.finish(dst, dst_len); +} + +} // namespace stage1 +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_STAGE1_JSON_MINIFIER_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/stage1/json_scanner.h b/contrib/libs/simdjson/src/generic/stage1/json_scanner.h new file mode 100644 index 000000000000..9ef41fb349ee --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage1/json_scanner.h @@ -0,0 +1,168 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE1_JSON_SCANNER_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_STAGE1_JSON_SCANNER_H +#include +#include +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +namespace stage1 { + +/** + * A block of scanned json, with information on operators and scalars. + * + * We seek to identify pseudo-structural characters. Anything that is inside + * a string must be omitted (hence & ~_string.string_tail()). + * Otherwise, pseudo-structural characters come in two forms. + * 1. We have the structural characters ([,],{,},:, comma). The + * term 'structural character' is from the JSON RFC. + * 2. We have the 'scalar pseudo-structural characters'. + * Scalars are quotes, and any character except structural characters and white space. + * + * To identify the scalar pseudo-structural characters, we must look at what comes + * before them: it must be a space, a quote or a structural characters. + * Starting with simdjson v0.3, we identify them by + * negation: we identify everything that is followed by a non-quote scalar, + * and we negate that. Whatever remains must be a 'scalar pseudo-structural character'. + */ +struct json_block { +public: + // We spell out the constructors in the hope of resolving inlining issues with Visual Studio 2017 + simdjson_inline json_block(json_string_block&& string, json_character_block characters, uint64_t follows_potential_nonquote_scalar) : + _string(std::move(string)), _characters(characters), _follows_potential_nonquote_scalar(follows_potential_nonquote_scalar) {} + simdjson_inline json_block(json_string_block string, json_character_block characters, uint64_t follows_potential_nonquote_scalar) : + _string(string), _characters(characters), _follows_potential_nonquote_scalar(follows_potential_nonquote_scalar) {} + + /** + * The start of structurals. + * In simdjson prior to v0.3, these were called the pseudo-structural characters. + **/ + simdjson_inline uint64_t structural_start() const noexcept { return potential_structural_start() & ~_string.string_tail(); } + /** All JSON whitespace (i.e. not in a string) */ + simdjson_inline uint64_t whitespace() const noexcept { return non_quote_outside_string(_characters.whitespace()); } + + // Helpers + + /** Whether the given characters are inside a string (only works on non-quotes) */ + simdjson_inline uint64_t non_quote_inside_string(uint64_t mask) const noexcept { return _string.non_quote_inside_string(mask); } + /** Whether the given characters are outside a string (only works on non-quotes) */ + simdjson_inline uint64_t non_quote_outside_string(uint64_t mask) const noexcept { return _string.non_quote_outside_string(mask); } + + // string and escape characters + json_string_block _string; + // whitespace, structural characters ('operators'), scalars + json_character_block _characters; + // whether the previous character was a scalar + uint64_t _follows_potential_nonquote_scalar; +private: + // Potential structurals (i.e. disregarding strings) + + /** + * structural elements ([,],{,},:, comma) plus scalar starts like 123, true and "abc". + * They may reside inside a string. + **/ + simdjson_inline uint64_t potential_structural_start() const noexcept { return _characters.op() | potential_scalar_start(); } + /** + * The start of non-operator runs, like 123, true and "abc". + * It main reside inside a string. + **/ + simdjson_inline uint64_t potential_scalar_start() const noexcept { + // The term "scalar" refers to anything except structural characters and white space + // (so letters, numbers, quotes). + // Whenever it is preceded by something that is not a structural element ({,},[,],:, ") nor a white-space + // then we know that it is irrelevant structurally. + return _characters.scalar() & ~follows_potential_scalar(); + } + /** + * Whether the given character is immediately after a non-operator like 123, true. + * The characters following a quote are not included. + */ + simdjson_inline uint64_t follows_potential_scalar() const noexcept { + // _follows_potential_nonquote_scalar: is defined as marking any character that follows a character + // that is not a structural element ({,},[,],:, comma) nor a quote (") and that is not a + // white space. + // It is understood that within quoted region, anything at all could be marked (irrelevant). + return _follows_potential_nonquote_scalar; + } +}; + +/** + * Scans JSON for important bits: structural characters or 'operators', strings, and scalars. + * + * The scanner starts by calculating two distinct things: + * - string characters (taking \" into account) + * - structural characters or 'operators' ([]{},:, comma) + * and scalars (runs of non-operators like 123, true and "abc") + * + * To minimize data dependency (a key component of the scanner's speed), it finds these in parallel: + * in particular, the operator/scalar bit will find plenty of things that are actually part of + * strings. When we're done, json_block will fuse the two together by masking out tokens that are + * part of a string. + */ +class json_scanner { +public: + json_scanner() = default; + simdjson_inline json_block next(const simd::simd8x64& in); + // Returns either UNCLOSED_STRING or SUCCESS + simdjson_inline error_code finish(); + +private: + // Whether the last character of the previous iteration is part of a scalar token + // (anything except whitespace or a structural character/'operator'). + uint64_t prev_scalar = 0ULL; + json_string_scanner string_scanner{}; +}; + + +// +// Check if the current character immediately follows a matching character. +// +// For example, this checks for quotes with backslashes in front of them: +// +// const uint64_t backslashed_quote = in.eq('"') & immediately_follows(in.eq('\'), prev_backslash); +// +simdjson_inline uint64_t follows(const uint64_t match, uint64_t &overflow) { + const uint64_t result = match << 1 | overflow; + overflow = match >> 63; + return result; +} + +simdjson_inline json_block json_scanner::next(const simd::simd8x64& in) { + json_string_block strings = string_scanner.next(in); + // identifies the white-space and the structural characters + json_character_block characters = json_character_block::classify(in); + // The term "scalar" refers to anything except structural characters and white space + // (so letters, numbers, quotes). + // We want follows_scalar to mark anything that follows a non-quote scalar (so letters and numbers). + // + // A terminal quote should either be followed by a structural character (comma, brace, bracket, colon) + // or nothing. However, we still want ' "a string"true ' to mark the 't' of 'true' as a potential + // pseudo-structural character just like we would if we had ' "a string" true '; otherwise we + // may need to add an extra check when parsing strings. + // + // Performance: there are many ways to skin this cat. + const uint64_t nonquote_scalar = characters.scalar() & ~strings.quote(); + uint64_t follows_nonquote_scalar = follows(nonquote_scalar, prev_scalar); + // We are returning a function-local object so either we get a move constructor + // or we get copy elision. + return json_block( + strings,// strings is a function-local object so either it moves or the copy is elided. + characters, + follows_nonquote_scalar + ); +} + +simdjson_inline error_code json_scanner::finish() { + return string_scanner.finish(); +} + +} // namespace stage1 +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_STAGE1_JSON_SCANNER_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/stage1/json_string_scanner.h b/contrib/libs/simdjson/src/generic/stage1/json_string_scanner.h new file mode 100644 index 000000000000..fb71b99a2c4f --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage1/json_string_scanner.h @@ -0,0 +1,99 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE1_JSON_STRING_SCANNER_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_STAGE1_JSON_STRING_SCANNER_H +#include +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +namespace stage1 { + +struct json_string_block { + // We spell out the constructors in the hope of resolving inlining issues with Visual Studio 2017 + simdjson_really_inline json_string_block(uint64_t escaped, uint64_t quote, uint64_t in_string) : + _escaped(escaped), _quote(quote), _in_string(in_string) {} + + // Escaped characters (characters following an escape() character) + simdjson_really_inline uint64_t escaped() const { return _escaped; } + // Real (non-backslashed) quotes + simdjson_really_inline uint64_t quote() const { return _quote; } + // Only characters inside the string (not including the quotes) + simdjson_really_inline uint64_t string_content() const { return _in_string & ~_quote; } + // Return a mask of whether the given characters are inside a string (only works on non-quotes) + simdjson_really_inline uint64_t non_quote_inside_string(uint64_t mask) const { return mask & _in_string; } + // Return a mask of whether the given characters are inside a string (only works on non-quotes) + simdjson_really_inline uint64_t non_quote_outside_string(uint64_t mask) const { return mask & ~_in_string; } + // Tail of string (everything except the start quote) + simdjson_really_inline uint64_t string_tail() const { return _in_string ^ _quote; } + + // escaped characters (backslashed--does not include the hex characters after \u) + uint64_t _escaped; + // real quotes (non-escaped ones) + uint64_t _quote; + // string characters (includes start quote but not end quote) + uint64_t _in_string; +}; + +// Scans blocks for string characters, storing the state necessary to do so +class json_string_scanner { +public: + simdjson_really_inline json_string_block next(const simd::simd8x64& in); + // Returns either UNCLOSED_STRING or SUCCESS + simdjson_really_inline error_code finish(); + +private: + // Scans for escape characters + json_escape_scanner escape_scanner{}; + // Whether the last iteration was still inside a string (all 1's = true, all 0's = false). + uint64_t prev_in_string = 0ULL; +}; + +// +// Return a mask of all string characters plus end quotes. +// +// prev_escaped is overflow saying whether the next character is escaped. +// prev_in_string is overflow saying whether we're still in a string. +// +// Backslash sequences outside of quotes will be detected in stage 2. +// +simdjson_really_inline json_string_block json_string_scanner::next(const simd::simd8x64& in) { + const uint64_t backslash = in.eq('\\'); + const uint64_t escaped = escape_scanner.next(backslash).escaped; + const uint64_t quote = in.eq('"') & ~escaped; + + // + // prefix_xor flips on bits inside the string (and flips off the end quote). + // + // Then we xor with prev_in_string: if we were in a string already, its effect is flipped + // (characters inside strings are outside, and characters outside strings are inside). + // + const uint64_t in_string = prefix_xor(quote) ^ prev_in_string; + + // + // Check if we're still in a string at the end of the box so the next block will know + // + prev_in_string = uint64_t(static_cast(in_string) >> 63); + + // Use ^ to turn the beginning quote off, and the end quote on. + + // We are returning a function-local object so either we get a move constructor + // or we get copy elision. + return json_string_block(escaped, quote, in_string); +} + +simdjson_really_inline error_code json_string_scanner::finish() { + if (prev_in_string) { + return UNCLOSED_STRING; + } + return SUCCESS; +} + +} // namespace stage1 +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_STAGE1_JSON_STRING_SCANNER_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/stage1/json_structural_indexer.h b/contrib/libs/simdjson/src/generic/stage1/json_structural_indexer.h new file mode 100644 index 000000000000..d9370b0c66c7 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage1/json_structural_indexer.h @@ -0,0 +1,358 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE1_JSON_STRUCTURAL_INDEXER_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_STAGE1_JSON_STRUCTURAL_INDEXER_H +#include +#include +#include +#include +#include +#include +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +// This file contains the common code every implementation uses in stage1 +// It is intended to be included multiple times and compiled multiple times +// We assume the file in which it is included already includes +// "simdjson/stage1.h" (this simplifies amalgation) + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +namespace stage1 { + +class bit_indexer { +public: + uint32_t *tail; + + simdjson_inline bit_indexer(uint32_t *index_buf) : tail(index_buf) {} + +#if SIMDJSON_PREFER_REVERSE_BITS + /** + * ARM lacks a fast trailing zero instruction, but it has a fast + * bit reversal instruction and a fast leading zero instruction. + * Thus it may be profitable to reverse the bits (once) and then + * to rely on a sequence of instructions that call the leading + * zero instruction. + * + * Performance notes: + * The chosen routine is not optimal in terms of data dependency + * since zero_leading_bit might require two instructions. However, + * it tends to minimize the total number of instructions which is + * beneficial. + */ + simdjson_inline void write_index(uint32_t idx, uint64_t& rev_bits, int i) { + int lz = leading_zeroes(rev_bits); + this->tail[i] = static_cast(idx) + lz; + rev_bits = zero_leading_bit(rev_bits, lz); + } +#else + /** + * Under recent x64 systems, we often have both a fast trailing zero + * instruction and a fast 'clear-lower-bit' instruction so the following + * algorithm can be competitive. + */ + + simdjson_inline void write_index(uint32_t idx, uint64_t& bits, int i) { + this->tail[i] = idx + trailing_zeroes(bits); + bits = clear_lowest_bit(bits); + } +#endif // SIMDJSON_PREFER_REVERSE_BITS + + template + simdjson_inline int write_indexes(uint32_t idx, uint64_t& bits) { + write_index(idx, bits, START); + SIMDJSON_IF_CONSTEXPR (N > 1) { + write_indexes<(N-1>0?START+1:START), (N-1>=0?N-1:1)>(idx, bits); + } + return START+N; + } + + template + simdjson_inline int write_indexes_stepped(uint32_t idx, uint64_t& bits, int cnt) { + write_indexes(idx, bits); + SIMDJSON_IF_CONSTEXPR ((START+STEP) < END) { + if (simdjson_unlikely((START+STEP) < cnt)) { + write_indexes_stepped<(START+STEP(idx, bits, cnt); + } + } + return ((END-START) % STEP) == 0 ? END : (END-START) - ((END-START) % STEP) + STEP; + } + + // flatten out values in 'bits' assuming that they are are to have values of idx + // plus their position in the bitvector, and store these indexes at + // base_ptr[base] incrementing base as we go + // will potentially store extra values beyond end of valid bits, so base_ptr + // needs to be large enough to handle this + // + // If the kernel sets SIMDJSON_GENERIC_JSON_STRUCTURAL_INDEXER_CUSTOM_BIT_INDEXER, then it + // will provide its own version of the code. +#ifdef SIMDJSON_GENERIC_JSON_STRUCTURAL_INDEXER_CUSTOM_BIT_INDEXER + simdjson_inline void write(uint32_t idx, uint64_t bits); +#else + simdjson_inline void write(uint32_t idx, uint64_t bits) { + // In some instances, the next branch is expensive because it is mispredicted. + // Unfortunately, in other cases, + // it helps tremendously. + if (bits == 0) + return; + + int cnt = static_cast(count_ones(bits)); + +#if SIMDJSON_PREFER_REVERSE_BITS + bits = reverse_bits(bits); +#endif +#ifdef SIMDJSON_STRUCTURAL_INDEXER_STEP + static constexpr const int STEP = SIMDJSON_STRUCTURAL_INDEXER_STEP; +#else + static constexpr const int STEP = 4; +#endif + static constexpr const int STEP_UNTIL = 24; + + write_indexes_stepped<0, STEP_UNTIL, STEP>(idx, bits, cnt); + SIMDJSON_IF_CONSTEXPR (STEP_UNTIL < 64) { + if (simdjson_unlikely(STEP_UNTIL < cnt)) { + for (int i=STEP_UNTIL; itail += cnt; + } +#endif // SIMDJSON_GENERIC_JSON_STRUCTURAL_INDEXER_CUSTOM_BIT_INDEXER + +}; + +class json_structural_indexer { +public: + /** + * Find the important bits of JSON in a 128-byte chunk, and add them to structural_indexes. + * + * @param partial Setting the partial parameter to true allows the find_structural_bits to + * tolerate unclosed strings. The caller should still ensure that the input is valid UTF-8. If + * you are processing substrings, you may want to call on a function like trimmed_length_safe_utf8. + */ + template + static error_code index(const uint8_t *buf, size_t len, dom_parser_implementation &parser, stage1_mode partial) noexcept; + +private: + simdjson_inline json_structural_indexer(uint32_t *structural_indexes); + template + simdjson_inline void step(const uint8_t *block, buf_block_reader &reader) noexcept; + simdjson_inline void next(const simd::simd8x64& in, const json_block& block, size_t idx); + simdjson_inline error_code finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial); + + json_scanner scanner{}; + utf8_checker checker{}; + bit_indexer indexer; + uint64_t prev_structurals = 0; + uint64_t unescaped_chars_error = 0; +}; + +simdjson_inline json_structural_indexer::json_structural_indexer(uint32_t *structural_indexes) : indexer{structural_indexes} {} + +// Skip the last character if it is partial +simdjson_inline size_t trim_partial_utf8(const uint8_t *buf, size_t len) { + if (simdjson_unlikely(len < 3)) { + switch (len) { + case 2: + if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left + if (buf[len-2] >= 0xe0) { return len-2; } // 3- and 4-byte characters with only 2 bytes left + return len; + case 1: + if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left + return len; + case 0: + return len; + } + } + if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left + if (buf[len-2] >= 0xe0) { return len-2; } // 3- and 4-byte characters with only 1 byte left + if (buf[len-3] >= 0xf0) { return len-3; } // 4-byte characters with only 3 bytes left + return len; +} + +// +// PERF NOTES: +// We pipe 2 inputs through these stages: +// 1. Load JSON into registers. This takes a long time and is highly parallelizable, so we load +// 2 inputs' worth at once so that by the time step 2 is looking for them input, it's available. +// 2. Scan the JSON for critical data: strings, scalars and operators. This is the critical path. +// The output of step 1 depends entirely on this information. These functions don't quite use +// up enough CPU: the second half of the functions is highly serial, only using 1 execution core +// at a time. The second input's scans has some dependency on the first ones finishing it, but +// they can make a lot of progress before they need that information. +// 3. Step 1 does not use enough capacity, so we run some extra stuff while we're waiting for that +// to finish: utf-8 checks and generating the output from the last iteration. +// +// The reason we run 2 inputs at a time, is steps 2 and 3 are *still* not enough to soak up all +// available capacity with just one input. Running 2 at a time seems to give the CPU a good enough +// workout. +// +template +error_code json_structural_indexer::index(const uint8_t *buf, size_t len, dom_parser_implementation &parser, stage1_mode partial) noexcept { + if (simdjson_unlikely(len > parser.capacity())) { return CAPACITY; } + // We guard the rest of the code so that we can assume that len > 0 throughout. + if (len == 0) { return EMPTY; } + if (is_streaming(partial)) { + len = trim_partial_utf8(buf, len); + // If you end up with an empty window after trimming + // the partial UTF-8 bytes, then chances are good that you + // have an UTF-8 formatting error. + if(len == 0) { return UTF8_ERROR; } + } + buf_block_reader reader(buf, len); + json_structural_indexer indexer(parser.structural_indexes.get()); + + // Read all but the last block + while (reader.has_full_block()) { + indexer.step(reader.full_block(), reader); + } + // Take care of the last block (will always be there unless file is empty which is + // not supposed to happen.) + uint8_t block[STEP_SIZE]; + if (simdjson_unlikely(reader.get_remainder(block) == 0)) { return UNEXPECTED_ERROR; } + indexer.step(block, reader); + return indexer.finish(parser, reader.block_index(), len, partial); +} + +template<> +simdjson_inline void json_structural_indexer::step<128>(const uint8_t *block, buf_block_reader<128> &reader) noexcept { + simd::simd8x64 in_1(block); + simd::simd8x64 in_2(block+64); + json_block block_1 = scanner.next(in_1); + json_block block_2 = scanner.next(in_2); + this->next(in_1, block_1, reader.block_index()); + this->next(in_2, block_2, reader.block_index()+64); + reader.advance(); +} + +template<> +simdjson_inline void json_structural_indexer::step<64>(const uint8_t *block, buf_block_reader<64> &reader) noexcept { + simd::simd8x64 in_1(block); + json_block block_1 = scanner.next(in_1); + this->next(in_1, block_1, reader.block_index()); + reader.advance(); +} + +simdjson_inline void json_structural_indexer::next(const simd::simd8x64& in, const json_block& block, size_t idx) { + uint64_t unescaped = in.lteq(0x1F); +#if SIMDJSON_UTF8VALIDATION + checker.check_next_input(in); +#endif + indexer.write(uint32_t(idx-64), prev_structurals); // Output *last* iteration's structurals to the parser + prev_structurals = block.structural_start(); + unescaped_chars_error |= block.non_quote_inside_string(unescaped); +} + +simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial) { + // Write out the final iteration's structurals + indexer.write(uint32_t(idx-64), prev_structurals); + error_code error = scanner.finish(); + // We deliberately break down the next expression so that it is + // human readable. + const bool should_we_exit = is_streaming(partial) ? + ((error != SUCCESS) && (error != UNCLOSED_STRING)) // when partial we tolerate UNCLOSED_STRING + : (error != SUCCESS); // if partial is false, we must have SUCCESS + const bool have_unclosed_string = (error == UNCLOSED_STRING); + if (simdjson_unlikely(should_we_exit)) { return error; } + + if (unescaped_chars_error) { + return UNESCAPED_CHARS; + } + parser.n_structural_indexes = uint32_t(indexer.tail - parser.structural_indexes.get()); + /*** + * The On-Demand API requires special padding. + * + * This is related to https://github.com/simdjson/simdjson/issues/906 + * Basically, we want to make sure that if the parsing continues beyond the last (valid) + * structural character, it quickly stops. + * Only three structural characters can be repeated without triggering an error in JSON: [,] and }. + * We repeat the padding character (at 'len'). We don't know what it is, but if the parsing + * continues, then it must be [,] or }. + * Suppose it is ] or }. We backtrack to the first character, what could it be that would + * not trigger an error? It could be ] or } but no, because you can't start a document that way. + * It can't be a comma, a colon or any simple value. So the only way we could continue is + * if the repeated character is [. But if so, the document must start with [. But if the document + * starts with [, it should end with ]. If we enforce that rule, then we would get + * ][[ which is invalid. + * + * This is illustrated with the test array_iterate_unclosed_error() on the following input: + * R"({ "a": [,,)" + **/ + parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len); // used later in partial == stage1_mode::streaming_final + parser.structural_indexes[parser.n_structural_indexes + 1] = uint32_t(len); + parser.structural_indexes[parser.n_structural_indexes + 2] = 0; + parser.next_structural_index = 0; + // a valid JSON file cannot have zero structural indexes - we should have found something + if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { + return EMPTY; + } + if (simdjson_unlikely(parser.structural_indexes[parser.n_structural_indexes - 1] > len)) { + return UNEXPECTED_ERROR; + } + if (partial == stage1_mode::streaming_partial) { + // If we have an unclosed string, then the last structural + // will be the quote and we want to make sure to omit it. + if(have_unclosed_string) { + parser.n_structural_indexes--; + // a valid JSON file cannot have zero structural indexes - we should have found something + if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } + } + // We truncate the input to the end of the last complete document (or zero). + auto new_structural_indexes = find_next_document_index(parser); + if (new_structural_indexes == 0 && parser.n_structural_indexes > 0) { + if(parser.structural_indexes[0] == 0) { + // If the buffer is partial and we started at index 0 but the document is + // incomplete, it's too big to parse. + return CAPACITY; + } else { + // It is possible that the document could be parsed, we just had a lot + // of white space. + parser.n_structural_indexes = 0; + return EMPTY; + } + } + + parser.n_structural_indexes = new_structural_indexes; + } else if (partial == stage1_mode::streaming_final) { + if(have_unclosed_string) { parser.n_structural_indexes--; } + // We truncate the input to the end of the last complete document (or zero). + // Because partial == stage1_mode::streaming_final, it means that we may + // silently ignore trailing garbage. Though it sounds bad, we do it + // deliberately because many people who have streams of JSON documents + // will truncate them for processing. E.g., imagine that you are uncompressing + // the data from a size file or receiving it in chunks from the network. You + // may not know where exactly the last document will be. Meanwhile the + // document_stream instances allow people to know the JSON documents they are + // parsing (see the iterator.source() method). + parser.n_structural_indexes = find_next_document_index(parser); + // We store the initial n_structural_indexes so that the client can see + // whether we used truncation. If initial_n_structural_indexes == parser.n_structural_indexes, + // then this will query parser.structural_indexes[parser.n_structural_indexes] which is len, + // otherwise, it will copy some prior index. + parser.structural_indexes[parser.n_structural_indexes + 1] = parser.structural_indexes[parser.n_structural_indexes]; + // This next line is critical, do not change it unless you understand what you are + // doing. + parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len); + if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { + // We tolerate an unclosed string at the very end of the stream. Indeed, users + // often load their data in bulk without being careful and they want us to ignore + // the trailing garbage. + return EMPTY; + } + } + checker.check_eof(); + return checker.errors(); +} + +} // namespace stage1 +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +// Clear CUSTOM_BIT_INDEXER so other implementations can set it if they need to. +#undef SIMDJSON_GENERIC_JSON_STRUCTURAL_INDEXER_CUSTOM_BIT_INDEXER + +#endif // SIMDJSON_SRC_GENERIC_STAGE1_JSON_STRUCTURAL_INDEXER_H diff --git a/contrib/libs/simdjson/src/generic/stage1/utf8_lookup4_algorithm.h b/contrib/libs/simdjson/src/generic/stage1/utf8_lookup4_algorithm.h new file mode 100644 index 000000000000..1a196159b888 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage1/utf8_lookup4_algorithm.h @@ -0,0 +1,209 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE1_UTF8_LOOKUP4_ALGORITHM_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_STAGE1_UTF8_LOOKUP4_ALGORITHM_H +#include +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +namespace utf8_validation { + +using namespace simd; + + simdjson_inline simd8 check_special_cases(const simd8 input, const simd8 prev1) { +// Bit 0 = Too Short (lead byte/ASCII followed by lead byte/ASCII) +// Bit 1 = Too Long (ASCII followed by continuation) +// Bit 2 = Overlong 3-byte +// Bit 4 = Surrogate +// Bit 5 = Overlong 2-byte +// Bit 7 = Two Continuations + constexpr const uint8_t TOO_SHORT = 1<<0; // 11______ 0_______ + // 11______ 11______ + constexpr const uint8_t TOO_LONG = 1<<1; // 0_______ 10______ + constexpr const uint8_t OVERLONG_3 = 1<<2; // 11100000 100_____ + constexpr const uint8_t SURROGATE = 1<<4; // 11101101 101_____ + constexpr const uint8_t OVERLONG_2 = 1<<5; // 1100000_ 10______ + constexpr const uint8_t TWO_CONTS = 1<<7; // 10______ 10______ + constexpr const uint8_t TOO_LARGE = 1<<3; // 11110100 1001____ + // 11110100 101_____ + // 11110101 1001____ + // 11110101 101_____ + // 1111011_ 1001____ + // 1111011_ 101_____ + // 11111___ 1001____ + // 11111___ 101_____ + constexpr const uint8_t TOO_LARGE_1000 = 1<<6; + // 11110101 1000____ + // 1111011_ 1000____ + // 11111___ 1000____ + constexpr const uint8_t OVERLONG_4 = 1<<6; // 11110000 1000____ + + const simd8 byte_1_high = prev1.shr<4>().lookup_16( + // 0_______ ________ + TOO_LONG, TOO_LONG, TOO_LONG, TOO_LONG, + TOO_LONG, TOO_LONG, TOO_LONG, TOO_LONG, + // 10______ ________ + TWO_CONTS, TWO_CONTS, TWO_CONTS, TWO_CONTS, + // 1100____ ________ + TOO_SHORT | OVERLONG_2, + // 1101____ ________ + TOO_SHORT, + // 1110____ ________ + TOO_SHORT | OVERLONG_3 | SURROGATE, + // 1111____ ________ + TOO_SHORT | TOO_LARGE | TOO_LARGE_1000 | OVERLONG_4 + ); + constexpr const uint8_t CARRY = TOO_SHORT | TOO_LONG | TWO_CONTS; // These all have ____ in byte 1 . + const simd8 byte_1_low = (prev1 & 0x0F).lookup_16( + // ____0000 ________ + CARRY | OVERLONG_3 | OVERLONG_2 | OVERLONG_4, + // ____0001 ________ + CARRY | OVERLONG_2, + // ____001_ ________ + CARRY, + CARRY, + + // ____0100 ________ + CARRY | TOO_LARGE, + // ____0101 ________ + CARRY | TOO_LARGE | TOO_LARGE_1000, + // ____011_ ________ + CARRY | TOO_LARGE | TOO_LARGE_1000, + CARRY | TOO_LARGE | TOO_LARGE_1000, + + // ____1___ ________ + CARRY | TOO_LARGE | TOO_LARGE_1000, + CARRY | TOO_LARGE | TOO_LARGE_1000, + CARRY | TOO_LARGE | TOO_LARGE_1000, + CARRY | TOO_LARGE | TOO_LARGE_1000, + CARRY | TOO_LARGE | TOO_LARGE_1000, + // ____1101 ________ + CARRY | TOO_LARGE | TOO_LARGE_1000 | SURROGATE, + CARRY | TOO_LARGE | TOO_LARGE_1000, + CARRY | TOO_LARGE | TOO_LARGE_1000 + ); + const simd8 byte_2_high = input.shr<4>().lookup_16( + // ________ 0_______ + TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT, + TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT, + + // ________ 1000____ + TOO_LONG | OVERLONG_2 | TWO_CONTS | OVERLONG_3 | TOO_LARGE_1000 | OVERLONG_4, + // ________ 1001____ + TOO_LONG | OVERLONG_2 | TWO_CONTS | OVERLONG_3 | TOO_LARGE, + // ________ 101_____ + TOO_LONG | OVERLONG_2 | TWO_CONTS | SURROGATE | TOO_LARGE, + TOO_LONG | OVERLONG_2 | TWO_CONTS | SURROGATE | TOO_LARGE, + + // ________ 11______ + TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT + ); + return (byte_1_high & byte_1_low & byte_2_high); + } + simdjson_inline simd8 check_multibyte_lengths(const simd8 input, + const simd8 prev_input, const simd8 sc) { + simd8 prev2 = input.prev<2>(prev_input); + simd8 prev3 = input.prev<3>(prev_input); + simd8 must23 = must_be_2_3_continuation(prev2, prev3); + simd8 must23_80 = must23 & uint8_t(0x80); + return must23_80 ^ sc; + } + + // + // Return nonzero if there are incomplete multibyte characters at the end of the block: + // e.g. if there is a 4-byte character, but it's 3 bytes from the end. + // + simdjson_inline simd8 is_incomplete(const simd8 input) { + // If the previous input's last 3 bytes match this, they're too short (they ended at EOF): + // ... 1111____ 111_____ 11______ +#if SIMDJSON_IMPLEMENTATION_ICELAKE + static const uint8_t max_array[64] = { + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 0xf0u-1, 0xe0u-1, 0xc0u-1 + }; +#else + static const uint8_t max_array[32] = { + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 0xf0u-1, 0xe0u-1, 0xc0u-1 + }; +#endif + const simd8 max_value(&max_array[sizeof(max_array)-sizeof(simd8)]); + return input.gt_bits(max_value); + } + + struct utf8_checker { + // If this is nonzero, there has been a UTF-8 error. + simd8 error; + // The last input we received + simd8 prev_input_block; + // Whether the last input we received was incomplete (used for ASCII fast path) + simd8 prev_incomplete; + + // + // Check whether the current bytes are valid UTF-8. + // + simdjson_inline void check_utf8_bytes(const simd8 input, const simd8 prev_input) { + // Flip prev1...prev3 so we can easily determine if they are 2+, 3+ or 4+ lead bytes + // (2, 3, 4-byte leads become large positive numbers instead of small negative numbers) + simd8 prev1 = input.prev<1>(prev_input); + simd8 sc = check_special_cases(input, prev1); + this->error |= check_multibyte_lengths(input, prev_input, sc); + } + + // The only problem that can happen at EOF is that a multibyte character is too short + // or a byte value too large in the last bytes: check_special_cases only checks for bytes + // too large in the first of two bytes. + simdjson_inline void check_eof() { + // If the previous block had incomplete UTF-8 characters at the end, an ASCII block can't + // possibly finish them. + this->error |= this->prev_incomplete; + } + + simdjson_inline void check_next_input(const simd8x64& input) { + if(simdjson_likely(is_ascii(input))) { + this->error |= this->prev_incomplete; + } else { + // you might think that a for-loop would work, but under Visual Studio, it is not good enough. + static_assert((simd8x64::NUM_CHUNKS == 1) + ||(simd8x64::NUM_CHUNKS == 2) + || (simd8x64::NUM_CHUNKS == 4), + "We support one, two or four chunks per 64-byte block."); + SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 1) { + this->check_utf8_bytes(input.chunks[0], this->prev_input_block); + } else SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 2) { + this->check_utf8_bytes(input.chunks[0], this->prev_input_block); + this->check_utf8_bytes(input.chunks[1], input.chunks[0]); + } else SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 4) { + this->check_utf8_bytes(input.chunks[0], this->prev_input_block); + this->check_utf8_bytes(input.chunks[1], input.chunks[0]); + this->check_utf8_bytes(input.chunks[2], input.chunks[1]); + this->check_utf8_bytes(input.chunks[3], input.chunks[2]); + } + this->prev_incomplete = is_incomplete(input.chunks[simd8x64::NUM_CHUNKS-1]); + this->prev_input_block = input.chunks[simd8x64::NUM_CHUNKS-1]; + } + } + // do not forget to call check_eof! + simdjson_inline error_code errors() { + return this->error.any_bits_set_anywhere() ? error_code::UTF8_ERROR : error_code::SUCCESS; + } + + }; // struct utf8_checker +} // namespace utf8_validation + +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_STAGE1_UTF8_LOOKUP4_ALGORITHM_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/stage1/utf8_validator.h b/contrib/libs/simdjson/src/generic/stage1/utf8_validator.h new file mode 100644 index 000000000000..ffc651ad91fe --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage1/utf8_validator.h @@ -0,0 +1,45 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE1_UTF8_VALIDATOR_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_STAGE1_UTF8_VALIDATOR_H +#include +#include +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +namespace stage1 { + +/** + * Validates that the string is actual UTF-8. + */ +template +bool generic_validate_utf8(const uint8_t * input, size_t length) { + checker c{}; + buf_block_reader<64> reader(input, length); + while (reader.has_full_block()) { + simd::simd8x64 in(reader.full_block()); + c.check_next_input(in); + reader.advance(); + } + uint8_t block[64]{}; + reader.get_remainder(block); + simd::simd8x64 in(block); + c.check_next_input(in); + reader.advance(); + c.check_eof(); + return c.errors() == error_code::SUCCESS; +} + +bool generic_validate_utf8(const char * input, size_t length) { + return generic_validate_utf8(reinterpret_cast(input),length); +} + +} // namespace stage1 +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_STAGE1_UTF8_VALIDATOR_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/stage2/amalgamated.h b/contrib/libs/simdjson/src/generic/stage2/amalgamated.h new file mode 100644 index 000000000000..43e1d7c682c4 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage2/amalgamated.h @@ -0,0 +1,10 @@ +// Stuff other things depend on +#include +#include +#include + +// All other declarations +#include +#include +#include +#include diff --git a/contrib/libs/simdjson/src/generic/stage2/base.h b/contrib/libs/simdjson/src/generic/stage2/base.h new file mode 100644 index 000000000000..b2e987c40b8f --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage2/base.h @@ -0,0 +1,23 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE2_BASE_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_STAGE2_BASE_H +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +namespace stage2 { + +class json_iterator; +class structural_iterator; +struct tape_builder; +struct tape_writer; + +} // namespace stage2 +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_STAGE2_BASE_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/stage2/dependencies.h b/contrib/libs/simdjson/src/generic/stage2/dependencies.h new file mode 100644 index 000000000000..b5d502d2dee8 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage2/dependencies.h @@ -0,0 +1,7 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE2_DEPENDENCIES_H +#define SIMDJSON_SRC_GENERIC_STAGE2_DEPENDENCIES_H + +#include +#include + +#endif // SIMDJSON_SRC_GENERIC_STAGE2_DEPENDENCIES_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/stage2/json_iterator.h b/contrib/libs/simdjson/src/generic/stage2/json_iterator.h new file mode 100644 index 000000000000..810e8fc52526 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage2/json_iterator.h @@ -0,0 +1,328 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE2_JSON_ITERATOR_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_STAGE2_JSON_ITERATOR_H +#include +#include +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +namespace stage2 { + +class json_iterator { +public: + const uint8_t* const buf; + uint32_t *next_structural; + dom_parser_implementation &dom_parser; + uint32_t depth{0}; + + /** + * Walk the JSON document. + * + * The visitor receives callbacks when values are encountered. All callbacks pass the iterator as + * the first parameter; some callbacks have other parameters as well: + * + * - visit_document_start() - at the beginning. + * - visit_document_end() - at the end (if things were successful). + * + * - visit_array_start() - at the start `[` of a non-empty array. + * - visit_array_end() - at the end `]` of a non-empty array. + * - visit_empty_array() - when an empty array is encountered. + * + * - visit_object_end() - at the start `]` of a non-empty object. + * - visit_object_start() - at the end `]` of a non-empty object. + * - visit_empty_object() - when an empty object is encountered. + * - visit_key(const uint8_t *key) - when a key in an object field is encountered. key is + * guaranteed to point at the first quote of the string (`"key"`). + * - visit_primitive(const uint8_t *value) - when a value is a string, number, boolean or null. + * - visit_root_primitive(iter, uint8_t *value) - when the top-level value is a string, number, boolean or null. + * + * - increment_count(iter) - each time a value is found in an array or object. + */ + template + simdjson_warn_unused simdjson_inline error_code walk_document(V &visitor) noexcept; + + /** + * Create an iterator capable of walking a JSON document. + * + * The document must have already passed through stage 1. + */ + simdjson_inline json_iterator(dom_parser_implementation &_dom_parser, size_t start_structural_index); + + /** + * Look at the next token. + * + * Tokens can be strings, numbers, booleans, null, or operators (`[{]},:`)). + * + * They may include invalid JSON as well (such as `1.2.3` or `ture`). + */ + simdjson_inline const uint8_t *peek() const noexcept; + /** + * Advance to the next token. + * + * Tokens can be strings, numbers, booleans, null, or operators (`[{]},:`)). + * + * They may include invalid JSON as well (such as `1.2.3` or `ture`). + */ + simdjson_inline const uint8_t *advance() noexcept; + /** + * Get the remaining length of the document, from the start of the current token. + */ + simdjson_inline size_t remaining_len() const noexcept; + /** + * Check if we are at the end of the document. + * + * If this is true, there are no more tokens. + */ + simdjson_inline bool at_eof() const noexcept; + /** + * Check if we are at the beginning of the document. + */ + simdjson_inline bool at_beginning() const noexcept; + simdjson_inline uint8_t last_structural() const noexcept; + + /** + * Log that a value has been found. + * + * Set LOG_ENABLED=true in logger.h to see logging. + */ + simdjson_inline void log_value(const char *type) const noexcept; + /** + * Log the start of a multipart value. + * + * Set LOG_ENABLED=true in logger.h to see logging. + */ + simdjson_inline void log_start_value(const char *type) const noexcept; + /** + * Log the end of a multipart value. + * + * Set LOG_ENABLED=true in logger.h to see logging. + */ + simdjson_inline void log_end_value(const char *type) const noexcept; + /** + * Log an error. + * + * Set LOG_ENABLED=true in logger.h to see logging. + */ + simdjson_inline void log_error(const char *error) const noexcept; + + template + simdjson_warn_unused simdjson_inline error_code visit_root_primitive(V &visitor, const uint8_t *value) noexcept; + template + simdjson_warn_unused simdjson_inline error_code visit_primitive(V &visitor, const uint8_t *value) noexcept; +}; + +template +simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V &visitor) noexcept { + logger::log_start(); + + // + // Start the document + // + if (at_eof()) { return EMPTY; } + log_start_value("document"); + SIMDJSON_TRY( visitor.visit_document_start(*this) ); + + // + // Read first value + // + { + auto value = advance(); + + // Make sure the outer object or array is closed before continuing; otherwise, there are ways we + // could get into memory corruption. See https://github.com/simdjson/simdjson/issues/906 + if (!STREAMING) { + switch (*value) { + case '{': if (last_structural() != '}') { log_value("starting brace unmatched"); return TAPE_ERROR; }; break; + case '[': if (last_structural() != ']') { log_value("starting bracket unmatched"); return TAPE_ERROR; }; break; + } + } + + switch (*value) { + case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + default: SIMDJSON_TRY( visitor.visit_root_primitive(*this, value) ); break; + } + } + goto document_end; + +// +// Object parser states +// +object_begin: + log_start_value("object"); + depth++; + if (depth >= dom_parser.max_depth()) { log_error("Exceeded max depth!"); return DEPTH_ERROR; } + dom_parser.is_array[depth] = false; + SIMDJSON_TRY( visitor.visit_object_start(*this) ); + + { + auto key = advance(); + if (*key != '"') { log_error("Object does not start with a key"); return TAPE_ERROR; } + SIMDJSON_TRY( visitor.increment_count(*this) ); + SIMDJSON_TRY( visitor.visit_key(*this, key) ); + } + +object_field: + if (simdjson_unlikely( *advance() != ':' )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } + { + auto value = advance(); + switch (*value) { + case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; + } + } + +object_continue: + switch (*advance()) { + case ',': + SIMDJSON_TRY( visitor.increment_count(*this) ); + { + auto key = advance(); + if (simdjson_unlikely( *key != '"' )) { log_error("Key string missing at beginning of field in object"); return TAPE_ERROR; } + SIMDJSON_TRY( visitor.visit_key(*this, key) ); + } + goto object_field; + case '}': log_end_value("object"); SIMDJSON_TRY( visitor.visit_object_end(*this) ); goto scope_end; + default: log_error("No comma between object fields"); return TAPE_ERROR; + } + +scope_end: + depth--; + if (depth == 0) { goto document_end; } + if (dom_parser.is_array[depth]) { goto array_continue; } + goto object_continue; + +// +// Array parser states +// +array_begin: + log_start_value("array"); + depth++; + if (depth >= dom_parser.max_depth()) { log_error("Exceeded max depth!"); return DEPTH_ERROR; } + dom_parser.is_array[depth] = true; + SIMDJSON_TRY( visitor.visit_array_start(*this) ); + SIMDJSON_TRY( visitor.increment_count(*this) ); + +array_value: + { + auto value = advance(); + switch (*value) { + case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; + } + } + +array_continue: + switch (*advance()) { + case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); goto array_value; + case ']': log_end_value("array"); SIMDJSON_TRY( visitor.visit_array_end(*this) ); goto scope_end; + default: log_error("Missing comma between array values"); return TAPE_ERROR; + } + +document_end: + log_end_value("document"); + SIMDJSON_TRY( visitor.visit_document_end(*this) ); + + dom_parser.next_structural_index = uint32_t(next_structural - &dom_parser.structural_indexes[0]); + + // If we didn't make it to the end, it's an error + if ( !STREAMING && dom_parser.next_structural_index != dom_parser.n_structural_indexes ) { + log_error("More than one JSON value at the root of the document, or extra characters at the end of the JSON!"); + return TAPE_ERROR; + } + + return SUCCESS; + +} // walk_document() + +simdjson_inline json_iterator::json_iterator(dom_parser_implementation &_dom_parser, size_t start_structural_index) + : buf{_dom_parser.buf}, + next_structural{&_dom_parser.structural_indexes[start_structural_index]}, + dom_parser{_dom_parser} { +} + +simdjson_inline const uint8_t *json_iterator::peek() const noexcept { + return &buf[*(next_structural)]; +} +simdjson_inline const uint8_t *json_iterator::advance() noexcept { + return &buf[*(next_structural++)]; +} +simdjson_inline size_t json_iterator::remaining_len() const noexcept { + return dom_parser.len - *(next_structural-1); +} + +simdjson_inline bool json_iterator::at_eof() const noexcept { + return next_structural == &dom_parser.structural_indexes[dom_parser.n_structural_indexes]; +} +simdjson_inline bool json_iterator::at_beginning() const noexcept { + return next_structural == dom_parser.structural_indexes.get(); +} +simdjson_inline uint8_t json_iterator::last_structural() const noexcept { + return buf[dom_parser.structural_indexes[dom_parser.n_structural_indexes - 1]]; +} + +simdjson_inline void json_iterator::log_value(const char *type) const noexcept { + logger::log_line(*this, "", type, ""); +} + +simdjson_inline void json_iterator::log_start_value(const char *type) const noexcept { + logger::log_line(*this, "+", type, ""); + if (logger::LOG_ENABLED) { logger::log_depth++; } +} + +simdjson_inline void json_iterator::log_end_value(const char *type) const noexcept { + if (logger::LOG_ENABLED) { logger::log_depth--; } + logger::log_line(*this, "-", type, ""); +} + +simdjson_inline void json_iterator::log_error(const char *error) const noexcept { + logger::log_line(*this, "", "ERROR", error); +} + +template +simdjson_warn_unused simdjson_inline error_code json_iterator::visit_root_primitive(V &visitor, const uint8_t *value) noexcept { + switch (*value) { + case '"': return visitor.visit_root_string(*this, value); + case 't': return visitor.visit_root_true_atom(*this, value); + case 'f': return visitor.visit_root_false_atom(*this, value); + case 'n': return visitor.visit_root_null_atom(*this, value); + case '-': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + return visitor.visit_root_number(*this, value); + default: + log_error("Document starts with a non-value character"); + return TAPE_ERROR; + } +} +template +simdjson_warn_unused simdjson_inline error_code json_iterator::visit_primitive(V &visitor, const uint8_t *value) noexcept { + // Use the fact that most scalars are going to be either strings or numbers. + if(*value == '"') { + return visitor.visit_string(*this, value); + } else if (((*value - '0') < 10) || (*value == '-')) { + return visitor.visit_number(*this, value); + } + // true, false, null are uncommon. + switch (*value) { + case 't': return visitor.visit_true_atom(*this, value); + case 'f': return visitor.visit_false_atom(*this, value); + case 'n': return visitor.visit_null_atom(*this, value); + default: + log_error("Non-value found when value was expected!"); + return TAPE_ERROR; + } +} + +} // namespace stage2 +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_STAGE2_JSON_ITERATOR_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/stage2/logger.h b/contrib/libs/simdjson/src/generic/stage2/logger.h new file mode 100644 index 000000000000..60955495e8e1 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage2/logger.h @@ -0,0 +1,100 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE2_LOGGER_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_STAGE2_LOGGER_H +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include + + +// This is for an internal-only stage 2 specific logger. +// Set LOG_ENABLED = true to log what stage 2 is doing! +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +namespace logger { + + static constexpr const char * DASHES = "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"; + +#if SIMDJSON_VERBOSE_LOGGING + static constexpr const bool LOG_ENABLED = true; +#else + static constexpr const bool LOG_ENABLED = false; +#endif + static constexpr const int LOG_EVENT_LEN = 20; + static constexpr const int LOG_BUFFER_LEN = 30; + static constexpr const int LOG_SMALL_BUFFER_LEN = 10; + static constexpr const int LOG_INDEX_LEN = 5; + + static int log_depth; // Not threadsafe. Log only. + + // Helper to turn unprintable or newline characters into spaces + static simdjson_inline char printable_char(char c) { + if (c >= 0x20) { + return c; + } else { + return ' '; + } + } + + // Print the header and set up log_start + static simdjson_inline void log_start() { + if (LOG_ENABLED) { + log_depth = 0; + printf("\n"); + printf("| %-*s | %-*s | %-*s | %-*s | Detail |\n", LOG_EVENT_LEN, "Event", LOG_BUFFER_LEN, "Buffer", LOG_SMALL_BUFFER_LEN, "Next", 5, "Next#"); + printf("|%.*s|%.*s|%.*s|%.*s|--------|\n", LOG_EVENT_LEN+2, DASHES, LOG_BUFFER_LEN+2, DASHES, LOG_SMALL_BUFFER_LEN+2, DASHES, 5+2, DASHES); + } + } + + simdjson_unused static simdjson_inline void log_string(const char *message) { + if (LOG_ENABLED) { + printf("%s\n", message); + } + } + + // Logs a single line from the stage 2 DOM parser + template + static simdjson_inline void log_line(S &structurals, const char *title_prefix, const char *title, const char *detail) { + if (LOG_ENABLED) { + printf("| %*s%s%-*s ", log_depth*2, "", title_prefix, LOG_EVENT_LEN - log_depth*2 - int(strlen(title_prefix)), title); + auto current_index = structurals.at_beginning() ? nullptr : structurals.next_structural-1; + auto next_index = structurals.next_structural; + auto current = current_index ? &structurals.buf[*current_index] : reinterpret_cast(" "); + auto next = &structurals.buf[*next_index]; + { + // Print the next N characters in the buffer. + printf("| "); + // Otherwise, print the characters starting from the buffer position. + // Print spaces for unprintable or newline characters. + for (int i=0;i +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +// This file contains the common code every implementation uses +// It is intended to be included multiple times and compiled multiple times + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +/// @private +namespace stringparsing { + +// begin copypasta +// These chars yield themselves: " \ / +// b -> backspace, f -> formfeed, n -> newline, r -> cr, t -> horizontal tab +// u not handled in this table as it's complex +static const uint8_t escape_map[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x0. + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2f, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x4. + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5c, 0, 0, 0, // 0x5. + 0, 0, 0x08, 0, 0, 0, 0x0c, 0, 0, 0, 0, 0, 0, 0, 0x0a, 0, // 0x6. + 0, 0, 0x0d, 0, 0x09, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x7. + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +// handle a unicode codepoint +// write appropriate values into dest +// src will advance 6 bytes or 12 bytes +// dest will advance a variable amount (return via pointer) +// return true if the unicode codepoint was valid +// We work in little-endian then swap at write time +simdjson_warn_unused +simdjson_inline bool handle_unicode_codepoint(const uint8_t **src_ptr, + uint8_t **dst_ptr, bool allow_replacement) { + // Use the default Unicode Character 'REPLACEMENT CHARACTER' (U+FFFD) + constexpr uint32_t substitution_code_point = 0xfffd; + // jsoncharutils::hex_to_u32_nocheck fills high 16 bits of the return value with 1s if the + // conversion is not valid; we defer the check for this to inside the + // multilingual plane check. + uint32_t code_point = jsoncharutils::hex_to_u32_nocheck(*src_ptr + 2); + *src_ptr += 6; + + // If we found a high surrogate, we must + // check for low surrogate for characters + // outside the Basic + // Multilingual Plane. + if (code_point >= 0xd800 && code_point < 0xdc00) { + const uint8_t *src_data = *src_ptr; + /* Compiler optimizations convert this to a single 16-bit load and compare on most platforms */ + if (((src_data[0] << 8) | src_data[1]) != ((static_cast ('\\') << 8) | static_cast ('u'))) { + if(!allow_replacement) { return false; } + code_point = substitution_code_point; + } else { + uint32_t code_point_2 = jsoncharutils::hex_to_u32_nocheck(src_data + 2); + + // We have already checked that the high surrogate is valid and + // (code_point - 0xd800) < 1024. + // + // Check that code_point_2 is in the range 0xdc00..0xdfff + // and that code_point_2 was parsed from valid hex. + uint32_t low_bit = code_point_2 - 0xdc00; + if (low_bit >> 10) { + if(!allow_replacement) { return false; } + code_point = substitution_code_point; + } else { + code_point = (((code_point - 0xd800) << 10) | low_bit) + 0x10000; + *src_ptr += 6; + } + + } + } else if (code_point >= 0xdc00 && code_point <= 0xdfff) { + // If we encounter a low surrogate (not preceded by a high surrogate) + // then we have an error. + if(!allow_replacement) { return false; } + code_point = substitution_code_point; + } + size_t offset = jsoncharutils::codepoint_to_utf8(code_point, *dst_ptr); + *dst_ptr += offset; + return offset > 0; +} + + +// handle a unicode codepoint using the wobbly convention +// https://simonsapin.github.io/wtf-8/ +// write appropriate values into dest +// src will advance 6 bytes or 12 bytes +// dest will advance a variable amount (return via pointer) +// return true if the unicode codepoint was valid +// We work in little-endian then swap at write time +simdjson_warn_unused +simdjson_inline bool handle_unicode_codepoint_wobbly(const uint8_t **src_ptr, + uint8_t **dst_ptr) { + // It is not ideal that this function is nearly identical to handle_unicode_codepoint. + // + // jsoncharutils::hex_to_u32_nocheck fills high 16 bits of the return value with 1s if the + // conversion is not valid; we defer the check for this to inside the + // multilingual plane check. + uint32_t code_point = jsoncharutils::hex_to_u32_nocheck(*src_ptr + 2); + *src_ptr += 6; + // If we found a high surrogate, we must + // check for low surrogate for characters + // outside the Basic + // Multilingual Plane. + if (code_point >= 0xd800 && code_point < 0xdc00) { + const uint8_t *src_data = *src_ptr; + /* Compiler optimizations convert this to a single 16-bit load and compare on most platforms */ + if (((src_data[0] << 8) | src_data[1]) == ((static_cast ('\\') << 8) | static_cast ('u'))) { + uint32_t code_point_2 = jsoncharutils::hex_to_u32_nocheck(src_data + 2); + uint32_t low_bit = code_point_2 - 0xdc00; + if ((low_bit >> 10) == 0) { + code_point = + (((code_point - 0xd800) << 10) | low_bit) + 0x10000; + *src_ptr += 6; + } + } + } + + size_t offset = jsoncharutils::codepoint_to_utf8(code_point, *dst_ptr); + *dst_ptr += offset; + return offset > 0; +} + + +/** + * Unescape a valid UTF-8 string from src to dst, stopping at a final unescaped quote. There + * must be an unescaped quote terminating the string. It returns the final output + * position as pointer. In case of error (e.g., the string has bad escaped codes), + * then null_ptr is returned. It is assumed that the output buffer is large + * enough. E.g., if src points at 'joe"', then dst needs to have four free bytes + + * SIMDJSON_PADDING bytes. + */ +simdjson_warn_unused simdjson_inline uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) { + while (1) { + // Copy the next n bytes, and find the backslash and quote in them. + auto bs_quote = backslash_and_quote::copy_and_find(src, dst); + // If the next thing is the end quote, copy and return + if (bs_quote.has_quote_first()) { + // we encountered quotes first. Move dst to point to quotes and exit + return dst + bs_quote.quote_index(); + } + if (bs_quote.has_backslash()) { + /* find out where the backspace is */ + auto bs_dist = bs_quote.backslash_index(); + uint8_t escape_char = src[bs_dist + 1]; + /* we encountered backslash first. Handle backslash */ + if (escape_char == 'u') { + /* move src/dst up to the start; they will be further adjusted + within the unicode codepoint handling code. */ + src += bs_dist; + dst += bs_dist; + if (!handle_unicode_codepoint(&src, &dst, allow_replacement)) { + return nullptr; + } + } else { + /* simple 1:1 conversion. Will eat bs_dist+2 characters in input and + * write bs_dist+1 characters to output + * note this may reach beyond the part of the buffer we've actually + * seen. I think this is ok */ + uint8_t escape_result = escape_map[escape_char]; + if (escape_result == 0u) { + return nullptr; /* bogus escape value is an error */ + } + dst[bs_dist] = escape_result; + src += bs_dist + 2; + dst += bs_dist + 1; + } + } else { + /* they are the same. Since they can't co-occur, it means we + * encountered neither. */ + src += backslash_and_quote::BYTES_PROCESSED; + dst += backslash_and_quote::BYTES_PROCESSED; + } + } +} + +simdjson_warn_unused simdjson_inline uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) { + // It is not ideal that this function is nearly identical to parse_string. + while (1) { + // Copy the next n bytes, and find the backslash and quote in them. + auto bs_quote = backslash_and_quote::copy_and_find(src, dst); + // If the next thing is the end quote, copy and return + if (bs_quote.has_quote_first()) { + // we encountered quotes first. Move dst to point to quotes and exit + return dst + bs_quote.quote_index(); + } + if (bs_quote.has_backslash()) { + /* find out where the backspace is */ + auto bs_dist = bs_quote.backslash_index(); + uint8_t escape_char = src[bs_dist + 1]; + /* we encountered backslash first. Handle backslash */ + if (escape_char == 'u') { + /* move src/dst up to the start; they will be further adjusted + within the unicode codepoint handling code. */ + src += bs_dist; + dst += bs_dist; + if (!handle_unicode_codepoint_wobbly(&src, &dst)) { + return nullptr; + } + } else { + /* simple 1:1 conversion. Will eat bs_dist+2 characters in input and + * write bs_dist+1 characters to output + * note this may reach beyond the part of the buffer we've actually + * seen. I think this is ok */ + uint8_t escape_result = escape_map[escape_char]; + if (escape_result == 0u) { + return nullptr; /* bogus escape value is an error */ + } + dst[bs_dist] = escape_result; + src += bs_dist + 2; + dst += bs_dist + 1; + } + } else { + /* they are the same. Since they can't co-occur, it means we + * encountered neither. */ + src += backslash_and_quote::BYTES_PROCESSED; + dst += backslash_and_quote::BYTES_PROCESSED; + } + } +} + +} // namespace stringparsing +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_STAGE2_STRINGPARSING_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/stage2/structural_iterator.h b/contrib/libs/simdjson/src/generic/stage2/structural_iterator.h new file mode 100644 index 000000000000..3f5ec4ff41d3 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage2/structural_iterator.h @@ -0,0 +1,64 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE2_STRUCTURAL_ITERATOR_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_STAGE2_STRUCTURAL_ITERATOR_H +#include +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +namespace stage2 { + +class structural_iterator { +public: + const uint8_t* const buf; + uint32_t *next_structural; + dom_parser_implementation &dom_parser; + + // Start a structural + simdjson_inline structural_iterator(dom_parser_implementation &_dom_parser, size_t start_structural_index) + : buf{_dom_parser.buf}, + next_structural{&_dom_parser.structural_indexes[start_structural_index]}, + dom_parser{_dom_parser} { + } + // Get the buffer position of the current structural character + simdjson_inline const uint8_t* current() { + return &buf[*(next_structural-1)]; + } + // Get the current structural character + simdjson_inline char current_char() { + return buf[*(next_structural-1)]; + } + // Get the next structural character without advancing + simdjson_inline char peek_next_char() { + return buf[*next_structural]; + } + simdjson_inline const uint8_t* peek() { + return &buf[*next_structural]; + } + simdjson_inline const uint8_t* advance() { + return &buf[*(next_structural++)]; + } + simdjson_inline char advance_char() { + return buf[*(next_structural++)]; + } + simdjson_inline size_t remaining_len() { + return dom_parser.len - *(next_structural-1); + } + + simdjson_inline bool at_end() { + return next_structural == &dom_parser.structural_indexes[dom_parser.n_structural_indexes]; + } + simdjson_inline bool at_beginning() { + return next_structural == dom_parser.structural_indexes.get(); + } +}; + +} // namespace stage2 +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_STAGE2_STRUCTURAL_ITERATOR_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/stage2/tape_builder.h b/contrib/libs/simdjson/src/generic/stage2/tape_builder.h new file mode 100644 index 000000000000..52931010fc01 --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage2/tape_builder.h @@ -0,0 +1,297 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE2_TAPE_BUILDER_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_STAGE2_TAPE_BUILDER_H +#include +#include +#include +#include +#include +#include +#include +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +namespace stage2 { + +struct tape_builder { + template + simdjson_warn_unused static simdjson_inline error_code parse_document( + dom_parser_implementation &dom_parser, + dom::document &doc) noexcept; + + /** Called when a non-empty document starts. */ + simdjson_warn_unused simdjson_inline error_code visit_document_start(json_iterator &iter) noexcept; + /** Called when a non-empty document ends without error. */ + simdjson_warn_unused simdjson_inline error_code visit_document_end(json_iterator &iter) noexcept; + + /** Called when a non-empty array starts. */ + simdjson_warn_unused simdjson_inline error_code visit_array_start(json_iterator &iter) noexcept; + /** Called when a non-empty array ends. */ + simdjson_warn_unused simdjson_inline error_code visit_array_end(json_iterator &iter) noexcept; + /** Called when an empty array is found. */ + simdjson_warn_unused simdjson_inline error_code visit_empty_array(json_iterator &iter) noexcept; + + /** Called when a non-empty object starts. */ + simdjson_warn_unused simdjson_inline error_code visit_object_start(json_iterator &iter) noexcept; + /** + * Called when a key in a field is encountered. + * + * primitive, visit_object_start, visit_empty_object, visit_array_start, or visit_empty_array + * will be called after this with the field value. + */ + simdjson_warn_unused simdjson_inline error_code visit_key(json_iterator &iter, const uint8_t *key) noexcept; + /** Called when a non-empty object ends. */ + simdjson_warn_unused simdjson_inline error_code visit_object_end(json_iterator &iter) noexcept; + /** Called when an empty object is found. */ + simdjson_warn_unused simdjson_inline error_code visit_empty_object(json_iterator &iter) noexcept; + + /** + * Called when a string, number, boolean or null is found. + */ + simdjson_warn_unused simdjson_inline error_code visit_primitive(json_iterator &iter, const uint8_t *value) noexcept; + /** + * Called when a string, number, boolean or null is found at the top level of a document (i.e. + * when there is no array or object and the entire document is a single string, number, boolean or + * null. + * + * This is separate from primitive() because simdjson's normal primitive parsing routines assume + * there is at least one more token after the value, which is only true in an array or object. + */ + simdjson_warn_unused simdjson_inline error_code visit_root_primitive(json_iterator &iter, const uint8_t *value) noexcept; + + simdjson_warn_unused simdjson_inline error_code visit_string(json_iterator &iter, const uint8_t *value, bool key = false) noexcept; + simdjson_warn_unused simdjson_inline error_code visit_number(json_iterator &iter, const uint8_t *value) noexcept; + simdjson_warn_unused simdjson_inline error_code visit_true_atom(json_iterator &iter, const uint8_t *value) noexcept; + simdjson_warn_unused simdjson_inline error_code visit_false_atom(json_iterator &iter, const uint8_t *value) noexcept; + simdjson_warn_unused simdjson_inline error_code visit_null_atom(json_iterator &iter, const uint8_t *value) noexcept; + + simdjson_warn_unused simdjson_inline error_code visit_root_string(json_iterator &iter, const uint8_t *value) noexcept; + simdjson_warn_unused simdjson_inline error_code visit_root_number(json_iterator &iter, const uint8_t *value) noexcept; + simdjson_warn_unused simdjson_inline error_code visit_root_true_atom(json_iterator &iter, const uint8_t *value) noexcept; + simdjson_warn_unused simdjson_inline error_code visit_root_false_atom(json_iterator &iter, const uint8_t *value) noexcept; + simdjson_warn_unused simdjson_inline error_code visit_root_null_atom(json_iterator &iter, const uint8_t *value) noexcept; + + /** Called each time a new field or element in an array or object is found. */ + simdjson_warn_unused simdjson_inline error_code increment_count(json_iterator &iter) noexcept; + + /** Next location to write to tape */ + tape_writer tape; +private: + /** Next write location in the string buf for stage 2 parsing */ + uint8_t *current_string_buf_loc; + + simdjson_inline tape_builder(dom::document &doc) noexcept; + + simdjson_inline uint32_t next_tape_index(json_iterator &iter) const noexcept; + simdjson_inline void start_container(json_iterator &iter) noexcept; + simdjson_warn_unused simdjson_inline error_code end_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept; + simdjson_warn_unused simdjson_inline error_code empty_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept; + simdjson_inline uint8_t *on_start_string(json_iterator &iter) noexcept; + simdjson_inline void on_end_string(uint8_t *dst) noexcept; +}; // struct tape_builder + +template +simdjson_warn_unused simdjson_inline error_code tape_builder::parse_document( + dom_parser_implementation &dom_parser, + dom::document &doc) noexcept { + dom_parser.doc = &doc; + json_iterator iter(dom_parser, STREAMING ? dom_parser.next_structural_index : 0); + tape_builder builder(doc); + return iter.walk_document(builder); +} + +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_primitive(json_iterator &iter, const uint8_t *value) noexcept { + return iter.visit_root_primitive(*this, value); +} +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_primitive(json_iterator &iter, const uint8_t *value) noexcept { + return iter.visit_primitive(*this, value); +} +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_empty_object(json_iterator &iter) noexcept { + return empty_container(iter, internal::tape_type::START_OBJECT, internal::tape_type::END_OBJECT); +} +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_empty_array(json_iterator &iter) noexcept { + return empty_container(iter, internal::tape_type::START_ARRAY, internal::tape_type::END_ARRAY); +} + +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_document_start(json_iterator &iter) noexcept { + start_container(iter); + return SUCCESS; +} +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_object_start(json_iterator &iter) noexcept { + start_container(iter); + return SUCCESS; +} +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_array_start(json_iterator &iter) noexcept { + start_container(iter); + return SUCCESS; +} + +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_object_end(json_iterator &iter) noexcept { + return end_container(iter, internal::tape_type::START_OBJECT, internal::tape_type::END_OBJECT); +} +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_array_end(json_iterator &iter) noexcept { + return end_container(iter, internal::tape_type::START_ARRAY, internal::tape_type::END_ARRAY); +} +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_document_end(json_iterator &iter) noexcept { + constexpr uint32_t start_tape_index = 0; + tape.append(start_tape_index, internal::tape_type::ROOT); + tape_writer::write(iter.dom_parser.doc->tape[start_tape_index], next_tape_index(iter), internal::tape_type::ROOT); + return SUCCESS; +} +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_key(json_iterator &iter, const uint8_t *key) noexcept { + return visit_string(iter, key, true); +} + +simdjson_warn_unused simdjson_inline error_code tape_builder::increment_count(json_iterator &iter) noexcept { + iter.dom_parser.open_containers[iter.depth].count++; // we have a key value pair in the object at parser.dom_parser.depth - 1 + return SUCCESS; +} + +simdjson_inline tape_builder::tape_builder(dom::document &doc) noexcept : tape{doc.tape.get()}, current_string_buf_loc{doc.string_buf.get()} {} + +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_string(json_iterator &iter, const uint8_t *value, bool key) noexcept { + iter.log_value(key ? "key" : "string"); + uint8_t *dst = on_start_string(iter); + dst = stringparsing::parse_string(value+1, dst, false); // We do not allow replacement when the escape characters are invalid. + if (dst == nullptr) { + iter.log_error("Invalid escape in string"); + return STRING_ERROR; + } + on_end_string(dst); + return SUCCESS; +} + +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_string(json_iterator &iter, const uint8_t *value) noexcept { + return visit_string(iter, value); +} + +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_number(json_iterator &iter, const uint8_t *value) noexcept { + iter.log_value("number"); + return numberparsing::parse_number(value, tape); +} + +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_number(json_iterator &iter, const uint8_t *value) noexcept { + // + // We need to make a copy to make sure that the string is space terminated. + // This is not about padding the input, which should already padded up + // to len + SIMDJSON_PADDING. However, we have no control at this stage + // on how the padding was done. What if the input string was padded with nulls? + // It is quite common for an input string to have an extra null character (C string). + // We do not want to allow 9\0 (where \0 is the null character) inside a JSON + // document, but the string "9\0" by itself is fine. So we make a copy and + // pad the input with spaces when we know that there is just one input element. + // This copy is relatively expensive, but it will almost never be called in + // practice unless you are in the strange scenario where you have many JSON + // documents made of single atoms. + // + std::unique_ptrcopy(new (std::nothrow) uint8_t[iter.remaining_len() + SIMDJSON_PADDING]); + if (copy.get() == nullptr) { return MEMALLOC; } + std::memcpy(copy.get(), value, iter.remaining_len()); + std::memset(copy.get() + iter.remaining_len(), ' ', SIMDJSON_PADDING); + error_code error = visit_number(iter, copy.get()); + return error; +} + +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_true_atom(json_iterator &iter, const uint8_t *value) noexcept { + iter.log_value("true"); + if (!atomparsing::is_valid_true_atom(value)) { return T_ATOM_ERROR; } + tape.append(0, internal::tape_type::TRUE_VALUE); + return SUCCESS; +} + +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_true_atom(json_iterator &iter, const uint8_t *value) noexcept { + iter.log_value("true"); + if (!atomparsing::is_valid_true_atom(value, iter.remaining_len())) { return T_ATOM_ERROR; } + tape.append(0, internal::tape_type::TRUE_VALUE); + return SUCCESS; +} + +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_false_atom(json_iterator &iter, const uint8_t *value) noexcept { + iter.log_value("false"); + if (!atomparsing::is_valid_false_atom(value)) { return F_ATOM_ERROR; } + tape.append(0, internal::tape_type::FALSE_VALUE); + return SUCCESS; +} + +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_false_atom(json_iterator &iter, const uint8_t *value) noexcept { + iter.log_value("false"); + if (!atomparsing::is_valid_false_atom(value, iter.remaining_len())) { return F_ATOM_ERROR; } + tape.append(0, internal::tape_type::FALSE_VALUE); + return SUCCESS; +} + +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_null_atom(json_iterator &iter, const uint8_t *value) noexcept { + iter.log_value("null"); + if (!atomparsing::is_valid_null_atom(value)) { return N_ATOM_ERROR; } + tape.append(0, internal::tape_type::NULL_VALUE); + return SUCCESS; +} + +simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_null_atom(json_iterator &iter, const uint8_t *value) noexcept { + iter.log_value("null"); + if (!atomparsing::is_valid_null_atom(value, iter.remaining_len())) { return N_ATOM_ERROR; } + tape.append(0, internal::tape_type::NULL_VALUE); + return SUCCESS; +} + +// private: + +simdjson_inline uint32_t tape_builder::next_tape_index(json_iterator &iter) const noexcept { + return uint32_t(tape.next_tape_loc - iter.dom_parser.doc->tape.get()); +} + +simdjson_warn_unused simdjson_inline error_code tape_builder::empty_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept { + auto start_index = next_tape_index(iter); + tape.append(start_index+2, start); + tape.append(start_index, end); + return SUCCESS; +} + +simdjson_inline void tape_builder::start_container(json_iterator &iter) noexcept { + iter.dom_parser.open_containers[iter.depth].tape_index = next_tape_index(iter); + iter.dom_parser.open_containers[iter.depth].count = 0; + tape.skip(); // We don't actually *write* the start element until the end. +} + +simdjson_warn_unused simdjson_inline error_code tape_builder::end_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept { + // Write the ending tape element, pointing at the start location + const uint32_t start_tape_index = iter.dom_parser.open_containers[iter.depth].tape_index; + tape.append(start_tape_index, end); + // Write the start tape element, pointing at the end location (and including count) + // count can overflow if it exceeds 24 bits... so we saturate + // the convention being that a cnt of 0xffffff or more is undetermined in value (>= 0xffffff). + const uint32_t count = iter.dom_parser.open_containers[iter.depth].count; + const uint32_t cntsat = count > 0xFFFFFF ? 0xFFFFFF : count; + tape_writer::write(iter.dom_parser.doc->tape[start_tape_index], next_tape_index(iter) | (uint64_t(cntsat) << 32), start); + return SUCCESS; +} + +simdjson_inline uint8_t *tape_builder::on_start_string(json_iterator &iter) noexcept { + // we advance the point, accounting for the fact that we have a NULL termination + tape.append(current_string_buf_loc - iter.dom_parser.doc->string_buf.get(), internal::tape_type::STRING); + return current_string_buf_loc + sizeof(uint32_t); +} + +simdjson_inline void tape_builder::on_end_string(uint8_t *dst) noexcept { + uint32_t str_length = uint32_t(dst - (current_string_buf_loc + sizeof(uint32_t))); + // TODO check for overflow in case someone has a crazy string (>=4GB?) + // But only add the overflow check when the document itself exceeds 4GB + // Currently unneeded because we refuse to parse docs larger or equal to 4GB. + memcpy(current_string_buf_loc, &str_length, sizeof(uint32_t)); + // NULL termination is still handy if you expect all your strings to + // be NULL terminated? It comes at a small cost + *dst = 0; + current_string_buf_loc = dst + 1; +} + +} // namespace stage2 +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_STAGE2_TAPE_BUILDER_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/generic/stage2/tape_writer.h b/contrib/libs/simdjson/src/generic/stage2/tape_writer.h new file mode 100644 index 000000000000..947aa6d0965c --- /dev/null +++ b/contrib/libs/simdjson/src/generic/stage2/tape_writer.h @@ -0,0 +1,117 @@ +#ifndef SIMDJSON_SRC_GENERIC_STAGE2_TAPE_WRITER_H + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#define SIMDJSON_SRC_GENERIC_STAGE2_TAPE_WRITER_H +#include +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { +namespace { +namespace stage2 { + +struct tape_writer { + /** The next place to write to tape */ + uint64_t *next_tape_loc; + + /** Write a signed 64-bit value to tape. */ + simdjson_inline void append_s64(int64_t value) noexcept; + + /** Write an unsigned 64-bit value to tape. */ + simdjson_inline void append_u64(uint64_t value) noexcept; + + /** Write a double value to tape. */ + simdjson_inline void append_double(double value) noexcept; + + /** + * Append a tape entry (an 8-bit type,and 56 bits worth of value). + */ + simdjson_inline void append(uint64_t val, internal::tape_type t) noexcept; + + /** + * Skip the current tape entry without writing. + * + * Used to skip the start of the container, since we'll come back later to fill it in when the + * container ends. + */ + simdjson_inline void skip() noexcept; + + /** + * Skip the number of tape entries necessary to write a large u64 or i64. + */ + simdjson_inline void skip_large_integer() noexcept; + + /** + * Skip the number of tape entries necessary to write a double. + */ + simdjson_inline void skip_double() noexcept; + + /** + * Write a value to a known location on tape. + * + * Used to go back and write out the start of a container after the container ends. + */ + simdjson_inline static void write(uint64_t &tape_loc, uint64_t val, internal::tape_type t) noexcept; + +private: + /** + * Append both the tape entry, and a supplementary value following it. Used for types that need + * all 64 bits, such as double and uint64_t. + */ + template + simdjson_inline void append2(uint64_t val, T val2, internal::tape_type t) noexcept; +}; // struct tape_writer + +simdjson_inline void tape_writer::append_s64(int64_t value) noexcept { + append2(0, value, internal::tape_type::INT64); +} + +simdjson_inline void tape_writer::append_u64(uint64_t value) noexcept { + append(0, internal::tape_type::UINT64); + *next_tape_loc = value; + next_tape_loc++; +} + +/** Write a double value to tape. */ +simdjson_inline void tape_writer::append_double(double value) noexcept { + append2(0, value, internal::tape_type::DOUBLE); +} + +simdjson_inline void tape_writer::skip() noexcept { + next_tape_loc++; +} + +simdjson_inline void tape_writer::skip_large_integer() noexcept { + next_tape_loc += 2; +} + +simdjson_inline void tape_writer::skip_double() noexcept { + next_tape_loc += 2; +} + +simdjson_inline void tape_writer::append(uint64_t val, internal::tape_type t) noexcept { + *next_tape_loc = val | ((uint64_t(char(t))) << 56); + next_tape_loc++; +} + +template +simdjson_inline void tape_writer::append2(uint64_t val, T val2, internal::tape_type t) noexcept { + append(val, t); + static_assert(sizeof(val2) == sizeof(*next_tape_loc), "Type is not 64 bits!"); + memcpy(next_tape_loc, &val2, sizeof(val2)); + next_tape_loc++; +} + +simdjson_inline void tape_writer::write(uint64_t &tape_loc, uint64_t val, internal::tape_type t) noexcept { + tape_loc = val | ((uint64_t(char(t))) << 56); +} + +} // namespace stage2 +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#endif // SIMDJSON_SRC_GENERIC_STAGE2_TAPE_WRITER_H \ No newline at end of file diff --git a/contrib/libs/simdjson/src/haswell.cpp b/contrib/libs/simdjson/src/haswell.cpp new file mode 100644 index 000000000000..d369a11fab00 --- /dev/null +++ b/contrib/libs/simdjson/src/haswell.cpp @@ -0,0 +1,170 @@ +#ifndef SIMDJSON_SRC_HASWELL_CPP +#define SIMDJSON_SRC_HASWELL_CPP + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include +#include + +#include +#include +#include +#include + +// +// Stage 1 +// + +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { + +simdjson_warn_unused error_code implementation::create_dom_parser_implementation( + size_t capacity, + size_t max_depth, + std::unique_ptr& dst +) const noexcept { + dst.reset( new (std::nothrow) dom_parser_implementation() ); + if (!dst) { return MEMALLOC; } + if (auto err = dst->set_capacity(capacity)) + return err; + if (auto err = dst->set_max_depth(max_depth)) + return err; + return SUCCESS; +} + +namespace { + +using namespace simd; + +// This identifies structural characters (comma, colon, braces, brackets), +// and ASCII white-space ('\r','\n','\t',' '). +simdjson_inline json_character_block json_character_block::classify(const simd::simd8x64& in) { + // These lookups rely on the fact that anything < 127 will match the lower 4 bits, which is why + // we can't use the generic lookup_16. + const auto whitespace_table = simd8::repeat_16(' ', 100, 100, 100, 17, 100, 113, 2, 100, '\t', '\n', 112, 100, '\r', 100, 100); + + // The 6 operators (:,[]{}) have these values: + // + // , 2C + // : 3A + // [ 5B + // { 7B + // ] 5D + // } 7D + // + // If you use | 0x20 to turn [ and ] into { and }, the lower 4 bits of each character is unique. + // We exploit this, using a simd 4-bit lookup to tell us which character match against, and then + // match it (against | 0x20). + // + // To prevent recognizing other characters, everything else gets compared with 0, which cannot + // match due to the | 0x20. + // + // NOTE: Due to the | 0x20, this ALSO treats and (control characters 0C and 1A) like , + // and :. This gets caught in stage 2, which checks the actual character to ensure the right + // operators are in the right places. + const auto op_table = simd8::repeat_16( + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, ':', '{', // : = 3A, [ = 5B, { = 7B + ',', '}', 0, 0 // , = 2C, ] = 5D, } = 7D + ); + + // We compute whitespace and op separately. If later code only uses one or the + // other, given the fact that all functions are aggressively inlined, we can + // hope that useless computations will be omitted. This is namely case when + // minifying (we only need whitespace). + + const uint64_t whitespace = in.eq({ + _mm256_shuffle_epi8(whitespace_table, in.chunks[0]), + _mm256_shuffle_epi8(whitespace_table, in.chunks[1]) + }); + // Turn [ and ] into { and } + const simd8x64 curlified{ + in.chunks[0] | 0x20, + in.chunks[1] | 0x20 + }; + const uint64_t op = curlified.eq({ + _mm256_shuffle_epi8(op_table, in.chunks[0]), + _mm256_shuffle_epi8(op_table, in.chunks[1]) + }); + + return { whitespace, op }; +} + +simdjson_inline bool is_ascii(const simd8x64& input) { + return input.reduce_or().is_ascii(); +} + +simdjson_unused simdjson_inline simd8 must_be_continuation(const simd8 prev1, const simd8 prev2, const simd8 prev3) { + simd8 is_second_byte = prev1.saturating_sub(0xc0u-1); // Only 11______ will be > 0 + simd8 is_third_byte = prev2.saturating_sub(0xe0u-1); // Only 111_____ will be > 0 + simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-1); // Only 1111____ will be > 0 + // Caller requires a bool (all 1's). All values resulting from the subtraction will be <= 64, so signed comparison is fine. + return simd8(is_second_byte | is_third_byte | is_fourth_byte) > int8_t(0); +} + +simdjson_inline simd8 must_be_2_3_continuation(const simd8 prev2, const simd8 prev3) { + simd8 is_third_byte = prev2.saturating_sub(0xe0u-0x80); // Only 111_____ will be >= 0x80 + simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-0x80); // Only 1111____ will be >= 0x80 + return is_third_byte | is_fourth_byte; +} + +} // unnamed namespace +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +// +// Stage 2 +// + +// +// Implementation-specific overrides +// +namespace simdjson { +namespace SIMDJSON_IMPLEMENTATION { + +simdjson_warn_unused error_code implementation::minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept { + return haswell::stage1::json_minifier::minify<128>(buf, len, dst, dst_len); +} + +simdjson_warn_unused error_code dom_parser_implementation::stage1(const uint8_t *_buf, size_t _len, stage1_mode streaming) noexcept { + this->buf = _buf; + this->len = _len; + return haswell::stage1::json_structural_indexer::index<128>(_buf, _len, *this, streaming); +} + +simdjson_warn_unused bool implementation::validate_utf8(const char *buf, size_t len) const noexcept { + return haswell::stage1::generic_validate_utf8(buf,len); +} + +simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document &_doc) noexcept { + return stage2::tape_builder::parse_document(*this, _doc); +} + +simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { + return stage2::tape_builder::parse_document(*this, _doc); +} + +SIMDJSON_NO_SANITIZE_MEMORY +simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst, bool replacement_char) const noexcept { + return haswell::stringparsing::parse_string(src, dst, replacement_char); +} + +simdjson_warn_unused uint8_t *dom_parser_implementation::parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept { + return haswell::stringparsing::parse_wobbly_string(src, dst); +} + +simdjson_warn_unused error_code dom_parser_implementation::parse(const uint8_t *_buf, size_t _len, dom::document &_doc) noexcept { + auto error = stage1(_buf, _len, stage1_mode::regular); + if (error) { return error; } + return stage2(_doc); +} + +} // namespace SIMDJSON_IMPLEMENTATION +} // namespace simdjson + +#include + +#endif // SIMDJSON_SRC_HASWELL_CPP \ No newline at end of file diff --git a/contrib/libs/simdjson/src/icelake.cpp b/contrib/libs/simdjson/src/icelake.cpp new file mode 100644 index 000000000000..c057d42787ac --- /dev/null +++ b/contrib/libs/simdjson/src/icelake.cpp @@ -0,0 +1,216 @@ +#ifndef SIMDJSON_SRC_ICELAKE_CPP +#define SIMDJSON_SRC_ICELAKE_CPP + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include +#include + +// defining SIMDJSON_GENERIC_JSON_STRUCTURAL_INDEXER_CUSTOM_BIT_INDEXER allows us to provide our own bit_indexer::write +#define SIMDJSON_GENERIC_JSON_STRUCTURAL_INDEXER_CUSTOM_BIT_INDEXER + +#include +#include +#include +#include + +#undef SIMDJSON_GENERIC_JSON_STRUCTURAL_INDEXER_CUSTOM_BIT_INDEXER + +// +// Stage 1 +// + +namespace simdjson { +namespace icelake { + +simdjson_warn_unused error_code implementation::create_dom_parser_implementation( + size_t capacity, + size_t max_depth, + std::unique_ptr& dst +) const noexcept { + dst.reset( new (std::nothrow) dom_parser_implementation() ); + if (!dst) { return MEMALLOC; } + if (auto err = dst->set_capacity(capacity)) + return err; + if (auto err = dst->set_max_depth(max_depth)) + return err; + return SUCCESS; +} + +namespace { + +using namespace simd; + +// This identifies structural characters (comma, colon, braces, brackets), +// and ASCII white-space ('\r','\n','\t',' '). +simdjson_inline json_character_block json_character_block::classify(const simd::simd8x64& in) { + // These lookups rely on the fact that anything < 127 will match the lower 4 bits, which is why + // we can't use the generic lookup_16. + const auto whitespace_table = simd8::repeat_16(' ', 100, 100, 100, 17, 100, 113, 2, 100, '\t', '\n', 112, 100, '\r', 100, 100); + + // The 6 operators (:,[]{}) have these values: + // + // , 2C + // : 3A + // [ 5B + // { 7B + // ] 5D + // } 7D + // + // If you use | 0x20 to turn [ and ] into { and }, the lower 4 bits of each character is unique. + // We exploit this, using a simd 4-bit lookup to tell us which character match against, and then + // match it (against | 0x20). + // + // To prevent recognizing other characters, everything else gets compared with 0, which cannot + // match due to the | 0x20. + // + // NOTE: Due to the | 0x20, this ALSO treats and (control characters 0C and 1A) like , + // and :. This gets caught in stage 2, which checks the actual character to ensure the right + // operators are in the right places. + const auto op_table = simd8::repeat_16( + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, ':', '{', // : = 3A, [ = 5B, { = 7B + ',', '}', 0, 0 // , = 2C, ] = 5D, } = 7D + ); + + // We compute whitespace and op separately. If later code only uses one or the + // other, given the fact that all functions are aggressively inlined, we can + // hope that useless computations will be omitted. This is namely case when + // minifying (we only need whitespace). + + const uint64_t whitespace = in.eq({ + _mm512_shuffle_epi8(whitespace_table, in.chunks[0]) + }); + // Turn [ and ] into { and } + const simd8x64 curlified{ + in.chunks[0] | 0x20 + }; + const uint64_t op = curlified.eq({ + _mm512_shuffle_epi8(op_table, in.chunks[0]) + }); + + return { whitespace, op }; +} + +simdjson_inline bool is_ascii(const simd8x64& input) { + return input.reduce_or().is_ascii(); +} + +simdjson_unused simdjson_inline simd8 must_be_continuation(const simd8 prev1, const simd8 prev2, const simd8 prev3) { + simd8 is_second_byte = prev1.saturating_sub(0xc0u-1); // Only 11______ will be > 0 + simd8 is_third_byte = prev2.saturating_sub(0xe0u-1); // Only 111_____ will be > 0 + simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-1); // Only 1111____ will be > 0 + // Caller requires a bool (all 1's). All values resulting from the subtraction will be <= 64, so signed comparison is fine. + return simd8(is_second_byte | is_third_byte | is_fourth_byte) > int8_t(0); +} + +simdjson_inline simd8 must_be_2_3_continuation(const simd8 prev2, const simd8 prev3) { + simd8 is_third_byte = prev2.saturating_sub(0xe0u-0x80); // Only 111_____ will be >= 0x80 + simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-0x80); // Only 1111____ will be >= 0x80 + return is_third_byte | is_fourth_byte; +} + +} // unnamed namespace +} // namespace icelake +} // namespace simdjson + +/** + * We provide a custom version of bit_indexer::write using + * naked intrinsics. + * TODO: make this code more elegant. + */ +// Under GCC 12, the intrinsic _mm512_extracti32x4_epi32 may generate 'maybe uninitialized'. +// as a workaround, we disable warnings within the following function. +SIMDJSON_PUSH_DISABLE_ALL_WARNINGS +namespace simdjson { namespace icelake { namespace { namespace stage1 { +simdjson_inline void bit_indexer::write(uint32_t idx, uint64_t bits) { + // In some instances, the next branch is expensive because it is mispredicted. + // Unfortunately, in other cases, + // it helps tremendously. + if (bits == 0) { return; } + + const __m512i indexes = _mm512_maskz_compress_epi8(bits, _mm512_set_epi32( + 0x3f3e3d3c, 0x3b3a3938, 0x37363534, 0x33323130, + 0x2f2e2d2c, 0x2b2a2928, 0x27262524, 0x23222120, + 0x1f1e1d1c, 0x1b1a1918, 0x17161514, 0x13121110, + 0x0f0e0d0c, 0x0b0a0908, 0x07060504, 0x03020100 + )); + const __m512i start_index = _mm512_set1_epi32(idx); + + const auto count = count_ones(bits); + __m512i t0 = _mm512_cvtepu8_epi32(_mm512_castsi512_si128(indexes)); + _mm512_storeu_si512(this->tail, _mm512_add_epi32(t0, start_index)); + + if(count > 16) { + const __m512i t1 = _mm512_cvtepu8_epi32(_mm512_extracti32x4_epi32(indexes, 1)); + _mm512_storeu_si512(this->tail + 16, _mm512_add_epi32(t1, start_index)); + if(count > 32) { + const __m512i t2 = _mm512_cvtepu8_epi32(_mm512_extracti32x4_epi32(indexes, 2)); + _mm512_storeu_si512(this->tail + 32, _mm512_add_epi32(t2, start_index)); + if(count > 48) { + const __m512i t3 = _mm512_cvtepu8_epi32(_mm512_extracti32x4_epi32(indexes, 3)); + _mm512_storeu_si512(this->tail + 48, _mm512_add_epi32(t3, start_index)); + } + } + } + this->tail += count; +} +}}}} +SIMDJSON_POP_DISABLE_WARNINGS + +// +// Stage 2 +// + +// +// Implementation-specific overrides +// +namespace simdjson { +namespace icelake { + +simdjson_warn_unused error_code implementation::minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept { + return icelake::stage1::json_minifier::minify<128>(buf, len, dst, dst_len); +} + +simdjson_warn_unused error_code dom_parser_implementation::stage1(const uint8_t *_buf, size_t _len, stage1_mode streaming) noexcept { + this->buf = _buf; + this->len = _len; + return icelake::stage1::json_structural_indexer::index<128>(_buf, _len, *this, streaming); +} + +simdjson_warn_unused bool implementation::validate_utf8(const char *buf, size_t len) const noexcept { + return icelake::stage1::generic_validate_utf8(buf,len); +} + +simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document &_doc) noexcept { + return stage2::tape_builder::parse_document(*this, _doc); +} + +simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { + return stage2::tape_builder::parse_document(*this, _doc); +} + +SIMDJSON_NO_SANITIZE_MEMORY +simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst, bool replacement_char) const noexcept { + return icelake::stringparsing::parse_string(src, dst, replacement_char); +} + +simdjson_warn_unused uint8_t *dom_parser_implementation::parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept { + return icelake::stringparsing::parse_wobbly_string(src, dst); +} + +simdjson_warn_unused error_code dom_parser_implementation::parse(const uint8_t *_buf, size_t _len, dom::document &_doc) noexcept { + auto error = stage1(_buf, _len, stage1_mode::regular); + if (error) { return error; } + return stage2(_doc); +} + +} // namespace icelake +} // namespace simdjson + +#include + +#endif // SIMDJSON_SRC_ICELAKE_CPP diff --git a/contrib/libs/simdjson/src/implementation.cpp b/contrib/libs/simdjson/src/implementation.cpp new file mode 100644 index 000000000000..4323e76bfebf --- /dev/null +++ b/contrib/libs/simdjson/src/implementation.cpp @@ -0,0 +1,330 @@ +#ifndef SIMDJSON_SRC_IMPLEMENTATION_CPP +#define SIMDJSON_SRC_IMPLEMENTATION_CPP + +#include +#include +#include +#include + +#include +#include + +namespace simdjson { + +bool implementation::supported_by_runtime_system() const { + uint32_t required_instruction_sets = this->required_instruction_sets(); + uint32_t supported_instruction_sets = internal::detect_supported_architectures(); + return ((supported_instruction_sets & required_instruction_sets) == required_instruction_sets); +} + +} // namespace simdjson + +#define SIMDJSON_CONDITIONAL_INCLUDE + +#if SIMDJSON_IMPLEMENTATION_ARM64 +#include +namespace simdjson { +namespace internal { +static const arm64::implementation* get_arm64_singleton() { + static const arm64::implementation arm64_singleton{}; + return &arm64_singleton; +} +} // namespace internal +} // namespace simdjson +#endif // SIMDJSON_IMPLEMENTATION_ARM64 + +#if SIMDJSON_IMPLEMENTATION_FALLBACK +#include +namespace simdjson { +namespace internal { +static const fallback::implementation* get_fallback_singleton() { + static const fallback::implementation fallback_singleton{}; + return &fallback_singleton; +} +} // namespace internal +} // namespace simdjson +#endif // SIMDJSON_IMPLEMENTATION_FALLBACK + + +#if SIMDJSON_IMPLEMENTATION_HASWELL +#include +namespace simdjson { +namespace internal { +static const haswell::implementation* get_haswell_singleton() { + static const haswell::implementation haswell_singleton{}; + return &haswell_singleton; +} +} // namespace internal +} // namespace simdjson +#endif + +#if SIMDJSON_IMPLEMENTATION_ICELAKE +#include +namespace simdjson { +namespace internal { +static const icelake::implementation* get_icelake_singleton() { + static const icelake::implementation icelake_singleton{}; + return &icelake_singleton; +} +} // namespace internal +} // namespace simdjson +#endif + +#if SIMDJSON_IMPLEMENTATION_PPC64 +#error #include +namespace simdjson { +namespace internal { +static const ppc64::implementation* get_ppc64_singleton() { + static const ppc64::implementation ppc64_singleton{}; + return &ppc64_singleton; +} +} // namespace internal +} // namespace simdjson +#endif // SIMDJSON_IMPLEMENTATION_PPC64 + +#if SIMDJSON_IMPLEMENTATION_WESTMERE +#include +namespace simdjson { +namespace internal { +static const simdjson::westmere::implementation* get_westmere_singleton() { + static const simdjson::westmere::implementation westmere_singleton{}; + return &westmere_singleton; +} +} // namespace internal +} // namespace simdjson +#endif // SIMDJSON_IMPLEMENTATION_WESTMERE + +#if SIMDJSON_IMPLEMENTATION_LSX +#include +namespace simdjson { +namespace internal { +static const simdjson::lsx::implementation* get_lsx_singleton() { + static const simdjson::lsx::implementation lsx_singleton{}; + return &lsx_singleton; +} +} // namespace internal +} // namespace simdjson +#endif // SIMDJSON_IMPLEMENTATION_LSX + +#if SIMDJSON_IMPLEMENTATION_LASX +#include +namespace simdjson { +namespace internal { +static const simdjson::lasx::implementation* get_lasx_singleton() { + static const simdjson::lasx::implementation lasx_singleton{}; + return &lasx_singleton; +} +} // namespace internal +} // namespace simdjson +#endif // SIMDJSON_IMPLEMENTATION_LASX + +#undef SIMDJSON_CONDITIONAL_INCLUDE + +namespace simdjson { +namespace internal { + +// When there is a single implementation, we should not pay a price +// for dispatching to the best implementation. We should just use the +// one we have. This is a compile-time check. +#define SIMDJSON_SINGLE_IMPLEMENTATION (SIMDJSON_IMPLEMENTATION_ICELAKE \ + + SIMDJSON_IMPLEMENTATION_HASWELL + SIMDJSON_IMPLEMENTATION_WESTMERE \ + + SIMDJSON_IMPLEMENTATION_ARM64 + SIMDJSON_IMPLEMENTATION_PPC64 \ + + SIMDJSON_IMPLEMENTATION_LSX + SIMDJSON_IMPLEMENTATION_LASX \ + + SIMDJSON_IMPLEMENTATION_FALLBACK == 1) + +#if SIMDJSON_SINGLE_IMPLEMENTATION + static const implementation* get_single_implementation() { + return +#if SIMDJSON_IMPLEMENTATION_ICELAKE + get_icelake_singleton(); +#endif +#if SIMDJSON_IMPLEMENTATION_HASWELL + get_haswell_singleton(); +#endif +#if SIMDJSON_IMPLEMENTATION_WESTMERE + get_westmere_singleton(); +#endif +#if SIMDJSON_IMPLEMENTATION_ARM64 + get_arm64_singleton(); +#endif +#if SIMDJSON_IMPLEMENTATION_PPC64 + get_ppc64_singleton(); +#endif +#if SIMDJSON_IMPLEMENTATION_LSX + get_lsx_singleton(); +#endif +#if SIMDJSON_IMPLEMENTATION_LASX + get_lasx_singleton(); +#endif +#if SIMDJSON_IMPLEMENTATION_FALLBACK + get_fallback_singleton(); +#endif +} +#endif + +// Static array of known implementations. We're hoping these get baked into the executable +// without requiring a static initializer. + +/** + * @private Detects best supported implementation on first use, and sets it + */ +class detect_best_supported_implementation_on_first_use final : public implementation { +public: + std::string name() const noexcept final { return set_best()->name(); } + std::string description() const noexcept final { return set_best()->description(); } + uint32_t required_instruction_sets() const noexcept final { return set_best()->required_instruction_sets(); } + simdjson_warn_unused error_code create_dom_parser_implementation( + size_t capacity, + size_t max_length, + std::unique_ptr& dst + ) const noexcept final { + return set_best()->create_dom_parser_implementation(capacity, max_length, dst); + } + simdjson_warn_unused error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept final { + return set_best()->minify(buf, len, dst, dst_len); + } + simdjson_warn_unused bool validate_utf8(const char * buf, size_t len) const noexcept final override { + return set_best()->validate_utf8(buf, len); + } + simdjson_inline detect_best_supported_implementation_on_first_use() noexcept : implementation("best_supported_detector", "Detects the best supported implementation and sets it", 0) {} +private: + const implementation *set_best() const noexcept; +}; + +static_assert(std::is_trivially_destructible::value, "detect_best_supported_implementation_on_first_use should be trivially destructible"); + +static const std::initializer_list& get_available_implementation_pointers() { + static const std::initializer_list available_implementation_pointers { +#if SIMDJSON_IMPLEMENTATION_ICELAKE + get_icelake_singleton(), +#endif +#if SIMDJSON_IMPLEMENTATION_HASWELL + get_haswell_singleton(), +#endif +#if SIMDJSON_IMPLEMENTATION_WESTMERE + get_westmere_singleton(), +#endif +#if SIMDJSON_IMPLEMENTATION_ARM64 + get_arm64_singleton(), +#endif +#if SIMDJSON_IMPLEMENTATION_PPC64 + get_ppc64_singleton(), +#endif +#if SIMDJSON_IMPLEMENTATION_LSX + get_lsx_singleton(), +#endif +#if SIMDJSON_IMPLEMENTATION_LASX + get_lasx_singleton(), +#endif +#if SIMDJSON_IMPLEMENTATION_FALLBACK + get_fallback_singleton(), +#endif + }; // available_implementation_pointers + return available_implementation_pointers; +} + +// So we can return UNSUPPORTED_ARCHITECTURE from the parser when there is no support +class unsupported_implementation final : public implementation { +public: + simdjson_warn_unused error_code create_dom_parser_implementation( + size_t, + size_t, + std::unique_ptr& + ) const noexcept final { + return UNSUPPORTED_ARCHITECTURE; + } + simdjson_warn_unused error_code minify(const uint8_t *, size_t, uint8_t *, size_t &) const noexcept final override { + return UNSUPPORTED_ARCHITECTURE; + } + simdjson_warn_unused bool validate_utf8(const char *, size_t) const noexcept final override { + return false; // Just refuse to validate. Given that we have a fallback implementation + // it seems unlikely that unsupported_implementation will ever be used. If it is used, + // then it will flag all strings as invalid. The alternative is to return an error_code + // from which the user has to figure out whether the string is valid UTF-8... which seems + // like a lot of work just to handle the very unlikely case that we have an unsupported + // implementation. And, when it does happen (that we have an unsupported implementation), + // what are the chances that the programmer has a fallback? Given that *we* provide the + // fallback, it implies that the programmer would need a fallback for our fallback. + } + unsupported_implementation() : implementation("unsupported", "Unsupported CPU (no detected SIMD instructions)", 0) {} +}; + +static_assert(std::is_trivially_destructible::value, "unsupported_singleton should be trivially destructible"); + +const unsupported_implementation* get_unsupported_singleton() { + static const unsupported_implementation unsupported_singleton{}; + return &unsupported_singleton; +} + +size_t available_implementation_list::size() const noexcept { + return internal::get_available_implementation_pointers().size(); +} +const implementation * const *available_implementation_list::begin() const noexcept { + return internal::get_available_implementation_pointers().begin(); +} +const implementation * const *available_implementation_list::end() const noexcept { + return internal::get_available_implementation_pointers().end(); +} +const implementation *available_implementation_list::detect_best_supported() const noexcept { + // They are prelisted in priority order, so we just go down the list + uint32_t supported_instruction_sets = internal::detect_supported_architectures(); + for (const implementation *impl : internal::get_available_implementation_pointers()) { + uint32_t required_instruction_sets = impl->required_instruction_sets(); + if ((supported_instruction_sets & required_instruction_sets) == required_instruction_sets) { return impl; } + } + return get_unsupported_singleton(); // this should never happen? +} + +const implementation *detect_best_supported_implementation_on_first_use::set_best() const noexcept { + SIMDJSON_PUSH_DISABLE_WARNINGS + SIMDJSON_DISABLE_DEPRECATED_WARNING // Disable CRT_SECURE warning on MSVC: manually verified this is safe + char *force_implementation_name = getenv("SIMDJSON_FORCE_IMPLEMENTATION"); + SIMDJSON_POP_DISABLE_WARNINGS + + if (force_implementation_name) { + auto force_implementation = get_available_implementations()[force_implementation_name]; + if (force_implementation) { + return get_active_implementation() = force_implementation; + } else { + // Note: abort() and stderr usage within the library is forbidden. + return get_active_implementation() = get_unsupported_singleton(); + } + } + return get_active_implementation() = get_available_implementations().detect_best_supported(); +} + +} // namespace internal + +SIMDJSON_DLLIMPORTEXPORT const internal::available_implementation_list& get_available_implementations() { + static const internal::available_implementation_list available_implementations{}; + return available_implementations; +} + +SIMDJSON_DLLIMPORTEXPORT internal::atomic_ptr& get_active_implementation() { +#if SIMDJSON_SINGLE_IMPLEMENTATION + // We immediately select the only implementation we have, skipping the + // detect_best_supported_implementation_on_first_use_singleton. + static internal::atomic_ptr active_implementation{internal::get_single_implementation()}; + return active_implementation; +#else + static const internal::detect_best_supported_implementation_on_first_use detect_best_supported_implementation_on_first_use_singleton; + static internal::atomic_ptr active_implementation{&detect_best_supported_implementation_on_first_use_singleton}; + return active_implementation; +#endif +} + +simdjson_warn_unused error_code minify(const char *buf, size_t len, char *dst, size_t &dst_len) noexcept { + return get_active_implementation()->minify(reinterpret_cast(buf), len, reinterpret_cast(dst), dst_len); +} +simdjson_warn_unused bool validate_utf8(const char *buf, size_t len) noexcept { + return get_active_implementation()->validate_utf8(buf, len); +} +const implementation * builtin_implementation() { + static const implementation * builtin_impl = get_available_implementations()[SIMDJSON_STRINGIFY(SIMDJSON_BUILTIN_IMPLEMENTATION)]; + assert(builtin_impl); + return builtin_impl; +} + +} // namespace simdjson + +#endif // SIMDJSON_SRC_IMPLEMENTATION_CPP diff --git a/contrib/libs/simdjson/src/internal/error_tables.cpp b/contrib/libs/simdjson/src/internal/error_tables.cpp new file mode 100644 index 000000000000..43499bbab09f --- /dev/null +++ b/contrib/libs/simdjson/src/internal/error_tables.cpp @@ -0,0 +1,48 @@ +#ifndef SIMDJSON_SRC_ERROR_TABLES_CPP +#define SIMDJSON_SRC_ERROR_TABLES_CPP + +#include +#include + +namespace simdjson { +namespace internal { + + SIMDJSON_DLLIMPORTEXPORT const error_code_info error_codes[] { + { SUCCESS, "SUCCESS: No error" }, + { CAPACITY, "CAPACITY: This parser can't support a document that big" }, + { MEMALLOC, "MEMALLOC: Error allocating memory, we're most likely out of memory" }, + { TAPE_ERROR, "TAPE_ERROR: The JSON document has an improper structure: missing or superfluous commas, braces, missing keys, etc." }, + { DEPTH_ERROR, "DEPTH_ERROR: The JSON document was too deep (too many nested objects and arrays)" }, + { STRING_ERROR, "STRING_ERROR: Problem while parsing a string" }, + { T_ATOM_ERROR, "T_ATOM_ERROR: Problem while parsing an atom starting with the letter 't'" }, + { F_ATOM_ERROR, "F_ATOM_ERROR: Problem while parsing an atom starting with the letter 'f'" }, + { N_ATOM_ERROR, "N_ATOM_ERROR: Problem while parsing an atom starting with the letter 'n'" }, + { NUMBER_ERROR, "NUMBER_ERROR: Problem while parsing a number" }, + { BIGINT_ERROR, "BIGINT_ERROR: Big integer value that cannot be represented using 64 bits" }, + { UTF8_ERROR, "UTF8_ERROR: The input is not valid UTF-8" }, + { UNINITIALIZED, "UNINITIALIZED: Uninitialized" }, + { EMPTY, "EMPTY: no JSON found" }, + { UNESCAPED_CHARS, "UNESCAPED_CHARS: Within strings, some characters must be escaped, we found unescaped characters" }, + { UNCLOSED_STRING, "UNCLOSED_STRING: A string is opened, but never closed." }, + { UNSUPPORTED_ARCHITECTURE, "UNSUPPORTED_ARCHITECTURE: simdjson does not have an implementation supported by this CPU architecture. Please report this error to the core team as it should never happen." }, + { INCORRECT_TYPE, "INCORRECT_TYPE: The JSON element does not have the requested type." }, + { NUMBER_OUT_OF_RANGE, "NUMBER_OUT_OF_RANGE: The JSON number is too large or too small to fit within the requested type." }, + { INDEX_OUT_OF_BOUNDS, "INDEX_OUT_OF_BOUNDS: Attempted to access an element of a JSON array that is beyond its length." }, + { NO_SUCH_FIELD, "NO_SUCH_FIELD: The JSON field referenced does not exist in this object." }, + { IO_ERROR, "IO_ERROR: Error reading the file." }, + { INVALID_JSON_POINTER, "INVALID_JSON_POINTER: Invalid JSON pointer syntax." }, + { INVALID_URI_FRAGMENT, "INVALID_URI_FRAGMENT: Invalid URI fragment syntax." }, + { UNEXPECTED_ERROR, "UNEXPECTED_ERROR: Unexpected error, consider reporting this problem as you may have found a bug in simdjson" }, + { PARSER_IN_USE, "PARSER_IN_USE: Cannot parse a new document while a document is still in use." }, + { OUT_OF_ORDER_ITERATION, "OUT_OF_ORDER_ITERATION: Objects and arrays can only be iterated when they are first encountered." }, + { INSUFFICIENT_PADDING, "INSUFFICIENT_PADDING: simdjson requires the input JSON string to have at least SIMDJSON_PADDING extra bytes allocated, beyond the string's length. Consider using the simdjson::padded_string class if needed." }, + { INCOMPLETE_ARRAY_OR_OBJECT, "INCOMPLETE_ARRAY_OR_OBJECT: JSON document ended early in the middle of an object or array." }, + { SCALAR_DOCUMENT_AS_VALUE, "SCALAR_DOCUMENT_AS_VALUE: A JSON document made of a scalar (number, Boolean, null or string) is treated as a value. Use get_bool(), get_double(), etc. on the document instead. "}, + { OUT_OF_BOUNDS, "OUT_OF_BOUNDS: Attempt to access location outside of document."}, + { TRAILING_CONTENT, "TRAILING_CONTENT: Unexpected trailing content in the JSON input."} + }; // error_messages[] + +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_SRC_ERROR_TABLES_CPP \ No newline at end of file diff --git a/contrib/libs/simdjson/src/internal/isadetection.h b/contrib/libs/simdjson/src/internal/isadetection.h new file mode 100644 index 000000000000..c873f7b74ecb --- /dev/null +++ b/contrib/libs/simdjson/src/internal/isadetection.h @@ -0,0 +1,247 @@ +/* From +https://github.com/endorno/pytorch/blob/master/torch/lib/TH/generic/simd/simd.h +Highly modified. + +Copyright (c) 2016- Facebook, Inc (Adam Paszke) +Copyright (c) 2014- Facebook, Inc (Soumith Chintala) +Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert) +Copyright (c) 2012-2014 Deepmind Technologies (Koray Kavukcuoglu) +Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu) +Copyright (c) 2011-2013 NYU (Clement Farabet) +Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert, Leon Bottou, +Iain Melvin, Jason Weston) Copyright (c) 2006 Idiap Research Institute +(Samy Bengio) Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert, +Samy Bengio, Johnny Mariethoz) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the names of Facebook, Deepmind Technologies, NYU, NEC Laboratories +America and IDIAP Research Institute nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef SIMDJSON_INTERNAL_ISADETECTION_H +#define SIMDJSON_INTERNAL_ISADETECTION_H + +#include "simdjson/internal/instruction_set.h" + +#include +#include +#if defined(_MSC_VER) +#include +#elif defined(HAVE_GCC_GET_CPUID) && defined(USE_GCC_GET_CPUID) +#include +#endif + +namespace simdjson { +namespace internal { + +#if defined(__PPC64__) + +static inline uint32_t detect_supported_architectures() { + return instruction_set::ALTIVEC; +} + +#elif defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) + +static inline uint32_t detect_supported_architectures() { + return instruction_set::NEON; +} + +#elif defined(__x86_64__) || defined(_M_AMD64) // x64 + + +namespace { +// Can be found on Intel ISA Reference for CPUID +constexpr uint32_t cpuid_avx2_bit = 1 << 5; ///< @private Bit 5 of EBX for EAX=0x7 +constexpr uint32_t cpuid_bmi1_bit = 1 << 3; ///< @private bit 3 of EBX for EAX=0x7 +constexpr uint32_t cpuid_bmi2_bit = 1 << 8; ///< @private bit 8 of EBX for EAX=0x7 +constexpr uint32_t cpuid_avx512f_bit = 1 << 16; ///< @private bit 16 of EBX for EAX=0x7 +constexpr uint32_t cpuid_avx512dq_bit = 1 << 17; ///< @private bit 17 of EBX for EAX=0x7 +constexpr uint32_t cpuid_avx512ifma_bit = 1 << 21; ///< @private bit 21 of EBX for EAX=0x7 +constexpr uint32_t cpuid_avx512pf_bit = 1 << 26; ///< @private bit 26 of EBX for EAX=0x7 +constexpr uint32_t cpuid_avx512er_bit = 1 << 27; ///< @private bit 27 of EBX for EAX=0x7 +constexpr uint32_t cpuid_avx512cd_bit = 1 << 28; ///< @private bit 28 of EBX for EAX=0x7 +constexpr uint32_t cpuid_avx512bw_bit = 1 << 30; ///< @private bit 30 of EBX for EAX=0x7 +constexpr uint32_t cpuid_avx512vl_bit = 1U << 31; ///< @private bit 31 of EBX for EAX=0x7 +constexpr uint32_t cpuid_avx512vbmi2_bit = 1 << 6; ///< @private bit 6 of ECX for EAX=0x7 +constexpr uint64_t cpuid_avx256_saved = uint64_t(1) << 2; ///< @private bit 2 = AVX +constexpr uint64_t cpuid_avx512_saved = uint64_t(7) << 5; ///< @private bits 5,6,7 = opmask, ZMM_hi256, hi16_ZMM +constexpr uint32_t cpuid_sse42_bit = 1 << 20; ///< @private bit 20 of ECX for EAX=0x1 +constexpr uint32_t cpuid_osxsave = (uint32_t(1) << 26) | (uint32_t(1) << 27); ///< @private bits 26+27 of ECX for EAX=0x1 +constexpr uint32_t cpuid_pclmulqdq_bit = 1 << 1; ///< @private bit 1 of ECX for EAX=0x1 +} + + + +static inline void cpuid(uint32_t *eax, uint32_t *ebx, uint32_t *ecx, + uint32_t *edx) { +#if defined(_MSC_VER) + int cpu_info[4]; + __cpuidex(cpu_info, *eax, *ecx); + *eax = cpu_info[0]; + *ebx = cpu_info[1]; + *ecx = cpu_info[2]; + *edx = cpu_info[3]; +#elif defined(HAVE_GCC_GET_CPUID) && defined(USE_GCC_GET_CPUID) + uint32_t level = *eax; + __get_cpuid(level, eax, ebx, ecx, edx); +#else + uint32_t a = *eax, b, c = *ecx, d; + asm volatile("cpuid\n\t" : "+a"(a), "=b"(b), "+c"(c), "=d"(d)); + *eax = a; + *ebx = b; + *ecx = c; + *edx = d; +#endif +} + + +static inline uint64_t xgetbv() { +#if defined(_MSC_VER) + return _xgetbv(0); +#else + uint32_t xcr0_lo, xcr0_hi; + asm volatile("xgetbv\n\t" : "=a" (xcr0_lo), "=d" (xcr0_hi) : "c" (0)); + return xcr0_lo | (uint64_t(xcr0_hi) << 32); +#endif +} + +static inline uint32_t detect_supported_architectures() { + uint32_t eax, ebx, ecx, edx; + uint32_t host_isa = 0x0; + + // EBX for EAX=0x1 + eax = 0x1; + ecx = 0x0; + cpuid(&eax, &ebx, &ecx, &edx); + + if (ecx & cpuid_sse42_bit) { + host_isa |= instruction_set::SSE42; + } else { + return host_isa; // everything after is redundant + } + + if (ecx & cpuid_pclmulqdq_bit) { + host_isa |= instruction_set::PCLMULQDQ; + } + + + if ((ecx & cpuid_osxsave) != cpuid_osxsave) { + return host_isa; + } + + // xgetbv for checking if the OS saves registers + uint64_t xcr0 = xgetbv(); + + if ((xcr0 & cpuid_avx256_saved) == 0) { + return host_isa; + } + + // ECX for EAX=0x7 + eax = 0x7; + ecx = 0x0; + cpuid(&eax, &ebx, &ecx, &edx); + if (ebx & cpuid_avx2_bit) { + host_isa |= instruction_set::AVX2; + } + if (ebx & cpuid_bmi1_bit) { + host_isa |= instruction_set::BMI1; + } + + if (ebx & cpuid_bmi2_bit) { + host_isa |= instruction_set::BMI2; + } + + if (!((xcr0 & cpuid_avx512_saved) == cpuid_avx512_saved)) { + return host_isa; + } + + if (ebx & cpuid_avx512f_bit) { + host_isa |= instruction_set::AVX512F; + } + + if (ebx & cpuid_avx512dq_bit) { + host_isa |= instruction_set::AVX512DQ; + } + + if (ebx & cpuid_avx512ifma_bit) { + host_isa |= instruction_set::AVX512IFMA; + } + + if (ebx & cpuid_avx512pf_bit) { + host_isa |= instruction_set::AVX512PF; + } + + if (ebx & cpuid_avx512er_bit) { + host_isa |= instruction_set::AVX512ER; + } + + if (ebx & cpuid_avx512cd_bit) { + host_isa |= instruction_set::AVX512CD; + } + + if (ebx & cpuid_avx512bw_bit) { + host_isa |= instruction_set::AVX512BW; + } + + if (ebx & cpuid_avx512vl_bit) { + host_isa |= instruction_set::AVX512VL; + } + + if (ecx & cpuid_avx512vbmi2_bit) { + host_isa |= instruction_set::AVX512VBMI2; + } + + return host_isa; +} + +#elif defined(__loongarch_sx) && !defined(__loongarch_asx) + +static inline uint32_t detect_supported_architectures() { + return instruction_set::LSX; +} + +#elif defined(__loongarch_asx) + +static inline uint32_t detect_supported_architectures() { + return instruction_set::LASX; +} + +#else // fallback + + +static inline uint32_t detect_supported_architectures() { + return instruction_set::DEFAULT; +} + + +#endif // end SIMD extension detection code + +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_INTERNAL_ISADETECTION_H diff --git a/contrib/libs/simdjson/src/internal/jsoncharutils_tables.cpp b/contrib/libs/simdjson/src/internal/jsoncharutils_tables.cpp new file mode 100644 index 000000000000..e16dbf355807 --- /dev/null +++ b/contrib/libs/simdjson/src/internal/jsoncharutils_tables.cpp @@ -0,0 +1,197 @@ +#ifndef SIMDJSON_SRC_JSONCHARUTILS_TABLES_CPP +#define SIMDJSON_SRC_JSONCHARUTILS_TABLES_CPP + +#include + +namespace simdjson { +namespace internal { + +// structural chars here are +// they are { 0x7b } 0x7d : 0x3a [ 0x5b ] 0x5d , 0x2c (and NULL) +// we are also interested in the four whitespace characters +// space 0x20, linefeed 0x0a, horizontal tab 0x09 and carriage return 0x0d + +SIMDJSON_DLLIMPORTEXPORT const bool structural_or_whitespace_negated[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + +SIMDJSON_DLLIMPORTEXPORT const bool structural_or_whitespace[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +SIMDJSON_DLLIMPORTEXPORT const uint32_t digit_to_val32[886] = { + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, + 0x6, 0x7, 0x8, 0x9, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xa, + 0xb, 0xc, 0xd, 0xe, 0xf, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xa, 0xb, 0xc, 0xd, 0xe, + 0xf, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0x0, 0x10, 0x20, 0x30, 0x40, 0x50, + 0x60, 0x70, 0x80, 0x90, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xa0, + 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, + 0xf0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0x0, 0x100, 0x200, 0x300, 0x400, 0x500, + 0x600, 0x700, 0x800, 0x900, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xa00, + 0xb00, 0xc00, 0xd00, 0xe00, 0xf00, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xa00, 0xb00, 0xc00, 0xd00, 0xe00, + 0xf00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0x0, 0x1000, 0x2000, 0x3000, 0x4000, 0x5000, + 0x6000, 0x7000, 0x8000, 0x9000, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xa000, + 0xb000, 0xc000, 0xd000, 0xe000, 0xf000, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xa000, 0xb000, 0xc000, 0xd000, 0xe000, + 0xf000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}; + +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_SRC_JSONCHARUTILS_TABLES_CPP \ No newline at end of file diff --git a/contrib/libs/simdjson/src/internal/numberparsing_tables.cpp b/contrib/libs/simdjson/src/internal/numberparsing_tables.cpp new file mode 100644 index 000000000000..74d97918cd83 --- /dev/null +++ b/contrib/libs/simdjson/src/internal/numberparsing_tables.cpp @@ -0,0 +1,681 @@ +#ifndef SIMDJSON_SRC_NUMBERPARSING_TABLES_CPP +#define SIMDJSON_SRC_NUMBERPARSING_TABLES_CPP + +#include +#include + +// Precomputed powers of ten from 10^0 to 10^22. These +// can be represented exactly using the double type. +SIMDJSON_DLLIMPORTEXPORT const double simdjson::internal::power_of_ten[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; + +/** + * When mapping numbers from decimal to binary, + * we go from w * 10^q to m * 2^p but we have + * 10^q = 5^q * 2^q, so effectively + * we are trying to match + * w * 2^q * 5^q to m * 2^p. Thus the powers of two + * are not a concern since they can be represented + * exactly using the binary notation, only the powers of five + * affect the binary significand. + */ + + +// The truncated powers of five from 5^-342 all the way to 5^308 +// The mantissa is truncated to 128 bits, and +// never rounded up. Uses about 10KB. +SIMDJSON_DLLIMPORTEXPORT const uint64_t simdjson::internal::power_of_five_128[]= { + 0xeef453d6923bd65a,0x113faa2906a13b3f, + 0x9558b4661b6565f8,0x4ac7ca59a424c507, + 0xbaaee17fa23ebf76,0x5d79bcf00d2df649, + 0xe95a99df8ace6f53,0xf4d82c2c107973dc, + 0x91d8a02bb6c10594,0x79071b9b8a4be869, + 0xb64ec836a47146f9,0x9748e2826cdee284, + 0xe3e27a444d8d98b7,0xfd1b1b2308169b25, + 0x8e6d8c6ab0787f72,0xfe30f0f5e50e20f7, + 0xb208ef855c969f4f,0xbdbd2d335e51a935, + 0xde8b2b66b3bc4723,0xad2c788035e61382, + 0x8b16fb203055ac76,0x4c3bcb5021afcc31, + 0xaddcb9e83c6b1793,0xdf4abe242a1bbf3d, + 0xd953e8624b85dd78,0xd71d6dad34a2af0d, + 0x87d4713d6f33aa6b,0x8672648c40e5ad68, + 0xa9c98d8ccb009506,0x680efdaf511f18c2, + 0xd43bf0effdc0ba48,0x212bd1b2566def2, + 0x84a57695fe98746d,0x14bb630f7604b57, + 0xa5ced43b7e3e9188,0x419ea3bd35385e2d, + 0xcf42894a5dce35ea,0x52064cac828675b9, + 0x818995ce7aa0e1b2,0x7343efebd1940993, + 0xa1ebfb4219491a1f,0x1014ebe6c5f90bf8, + 0xca66fa129f9b60a6,0xd41a26e077774ef6, + 0xfd00b897478238d0,0x8920b098955522b4, + 0x9e20735e8cb16382,0x55b46e5f5d5535b0, + 0xc5a890362fddbc62,0xeb2189f734aa831d, + 0xf712b443bbd52b7b,0xa5e9ec7501d523e4, + 0x9a6bb0aa55653b2d,0x47b233c92125366e, + 0xc1069cd4eabe89f8,0x999ec0bb696e840a, + 0xf148440a256e2c76,0xc00670ea43ca250d, + 0x96cd2a865764dbca,0x380406926a5e5728, + 0xbc807527ed3e12bc,0xc605083704f5ecf2, + 0xeba09271e88d976b,0xf7864a44c633682e, + 0x93445b8731587ea3,0x7ab3ee6afbe0211d, + 0xb8157268fdae9e4c,0x5960ea05bad82964, + 0xe61acf033d1a45df,0x6fb92487298e33bd, + 0x8fd0c16206306bab,0xa5d3b6d479f8e056, + 0xb3c4f1ba87bc8696,0x8f48a4899877186c, + 0xe0b62e2929aba83c,0x331acdabfe94de87, + 0x8c71dcd9ba0b4925,0x9ff0c08b7f1d0b14, + 0xaf8e5410288e1b6f,0x7ecf0ae5ee44dd9, + 0xdb71e91432b1a24a,0xc9e82cd9f69d6150, + 0x892731ac9faf056e,0xbe311c083a225cd2, + 0xab70fe17c79ac6ca,0x6dbd630a48aaf406, + 0xd64d3d9db981787d,0x92cbbccdad5b108, + 0x85f0468293f0eb4e,0x25bbf56008c58ea5, + 0xa76c582338ed2621,0xaf2af2b80af6f24e, + 0xd1476e2c07286faa,0x1af5af660db4aee1, + 0x82cca4db847945ca,0x50d98d9fc890ed4d, + 0xa37fce126597973c,0xe50ff107bab528a0, + 0xcc5fc196fefd7d0c,0x1e53ed49a96272c8, + 0xff77b1fcbebcdc4f,0x25e8e89c13bb0f7a, + 0x9faacf3df73609b1,0x77b191618c54e9ac, + 0xc795830d75038c1d,0xd59df5b9ef6a2417, + 0xf97ae3d0d2446f25,0x4b0573286b44ad1d, + 0x9becce62836ac577,0x4ee367f9430aec32, + 0xc2e801fb244576d5,0x229c41f793cda73f, + 0xf3a20279ed56d48a,0x6b43527578c1110f, + 0x9845418c345644d6,0x830a13896b78aaa9, + 0xbe5691ef416bd60c,0x23cc986bc656d553, + 0xedec366b11c6cb8f,0x2cbfbe86b7ec8aa8, + 0x94b3a202eb1c3f39,0x7bf7d71432f3d6a9, + 0xb9e08a83a5e34f07,0xdaf5ccd93fb0cc53, + 0xe858ad248f5c22c9,0xd1b3400f8f9cff68, + 0x91376c36d99995be,0x23100809b9c21fa1, + 0xb58547448ffffb2d,0xabd40a0c2832a78a, + 0xe2e69915b3fff9f9,0x16c90c8f323f516c, + 0x8dd01fad907ffc3b,0xae3da7d97f6792e3, + 0xb1442798f49ffb4a,0x99cd11cfdf41779c, + 0xdd95317f31c7fa1d,0x40405643d711d583, + 0x8a7d3eef7f1cfc52,0x482835ea666b2572, + 0xad1c8eab5ee43b66,0xda3243650005eecf, + 0xd863b256369d4a40,0x90bed43e40076a82, + 0x873e4f75e2224e68,0x5a7744a6e804a291, + 0xa90de3535aaae202,0x711515d0a205cb36, + 0xd3515c2831559a83,0xd5a5b44ca873e03, + 0x8412d9991ed58091,0xe858790afe9486c2, + 0xa5178fff668ae0b6,0x626e974dbe39a872, + 0xce5d73ff402d98e3,0xfb0a3d212dc8128f, + 0x80fa687f881c7f8e,0x7ce66634bc9d0b99, + 0xa139029f6a239f72,0x1c1fffc1ebc44e80, + 0xc987434744ac874e,0xa327ffb266b56220, + 0xfbe9141915d7a922,0x4bf1ff9f0062baa8, + 0x9d71ac8fada6c9b5,0x6f773fc3603db4a9, + 0xc4ce17b399107c22,0xcb550fb4384d21d3, + 0xf6019da07f549b2b,0x7e2a53a146606a48, + 0x99c102844f94e0fb,0x2eda7444cbfc426d, + 0xc0314325637a1939,0xfa911155fefb5308, + 0xf03d93eebc589f88,0x793555ab7eba27ca, + 0x96267c7535b763b5,0x4bc1558b2f3458de, + 0xbbb01b9283253ca2,0x9eb1aaedfb016f16, + 0xea9c227723ee8bcb,0x465e15a979c1cadc, + 0x92a1958a7675175f,0xbfacd89ec191ec9, + 0xb749faed14125d36,0xcef980ec671f667b, + 0xe51c79a85916f484,0x82b7e12780e7401a, + 0x8f31cc0937ae58d2,0xd1b2ecb8b0908810, + 0xb2fe3f0b8599ef07,0x861fa7e6dcb4aa15, + 0xdfbdcece67006ac9,0x67a791e093e1d49a, + 0x8bd6a141006042bd,0xe0c8bb2c5c6d24e0, + 0xaecc49914078536d,0x58fae9f773886e18, + 0xda7f5bf590966848,0xaf39a475506a899e, + 0x888f99797a5e012d,0x6d8406c952429603, + 0xaab37fd7d8f58178,0xc8e5087ba6d33b83, + 0xd5605fcdcf32e1d6,0xfb1e4a9a90880a64, + 0x855c3be0a17fcd26,0x5cf2eea09a55067f, + 0xa6b34ad8c9dfc06f,0xf42faa48c0ea481e, + 0xd0601d8efc57b08b,0xf13b94daf124da26, + 0x823c12795db6ce57,0x76c53d08d6b70858, + 0xa2cb1717b52481ed,0x54768c4b0c64ca6e, + 0xcb7ddcdda26da268,0xa9942f5dcf7dfd09, + 0xfe5d54150b090b02,0xd3f93b35435d7c4c, + 0x9efa548d26e5a6e1,0xc47bc5014a1a6daf, + 0xc6b8e9b0709f109a,0x359ab6419ca1091b, + 0xf867241c8cc6d4c0,0xc30163d203c94b62, + 0x9b407691d7fc44f8,0x79e0de63425dcf1d, + 0xc21094364dfb5636,0x985915fc12f542e4, + 0xf294b943e17a2bc4,0x3e6f5b7b17b2939d, + 0x979cf3ca6cec5b5a,0xa705992ceecf9c42, + 0xbd8430bd08277231,0x50c6ff782a838353, + 0xece53cec4a314ebd,0xa4f8bf5635246428, + 0x940f4613ae5ed136,0x871b7795e136be99, + 0xb913179899f68584,0x28e2557b59846e3f, + 0xe757dd7ec07426e5,0x331aeada2fe589cf, + 0x9096ea6f3848984f,0x3ff0d2c85def7621, + 0xb4bca50b065abe63,0xfed077a756b53a9, + 0xe1ebce4dc7f16dfb,0xd3e8495912c62894, + 0x8d3360f09cf6e4bd,0x64712dd7abbbd95c, + 0xb080392cc4349dec,0xbd8d794d96aacfb3, + 0xdca04777f541c567,0xecf0d7a0fc5583a0, + 0x89e42caaf9491b60,0xf41686c49db57244, + 0xac5d37d5b79b6239,0x311c2875c522ced5, + 0xd77485cb25823ac7,0x7d633293366b828b, + 0x86a8d39ef77164bc,0xae5dff9c02033197, + 0xa8530886b54dbdeb,0xd9f57f830283fdfc, + 0xd267caa862a12d66,0xd072df63c324fd7b, + 0x8380dea93da4bc60,0x4247cb9e59f71e6d, + 0xa46116538d0deb78,0x52d9be85f074e608, + 0xcd795be870516656,0x67902e276c921f8b, + 0x806bd9714632dff6,0xba1cd8a3db53b6, + 0xa086cfcd97bf97f3,0x80e8a40eccd228a4, + 0xc8a883c0fdaf7df0,0x6122cd128006b2cd, + 0xfad2a4b13d1b5d6c,0x796b805720085f81, + 0x9cc3a6eec6311a63,0xcbe3303674053bb0, + 0xc3f490aa77bd60fc,0xbedbfc4411068a9c, + 0xf4f1b4d515acb93b,0xee92fb5515482d44, + 0x991711052d8bf3c5,0x751bdd152d4d1c4a, + 0xbf5cd54678eef0b6,0xd262d45a78a0635d, + 0xef340a98172aace4,0x86fb897116c87c34, + 0x9580869f0e7aac0e,0xd45d35e6ae3d4da0, + 0xbae0a846d2195712,0x8974836059cca109, + 0xe998d258869facd7,0x2bd1a438703fc94b, + 0x91ff83775423cc06,0x7b6306a34627ddcf, + 0xb67f6455292cbf08,0x1a3bc84c17b1d542, + 0xe41f3d6a7377eeca,0x20caba5f1d9e4a93, + 0x8e938662882af53e,0x547eb47b7282ee9c, + 0xb23867fb2a35b28d,0xe99e619a4f23aa43, + 0xdec681f9f4c31f31,0x6405fa00e2ec94d4, + 0x8b3c113c38f9f37e,0xde83bc408dd3dd04, + 0xae0b158b4738705e,0x9624ab50b148d445, + 0xd98ddaee19068c76,0x3badd624dd9b0957, + 0x87f8a8d4cfa417c9,0xe54ca5d70a80e5d6, + 0xa9f6d30a038d1dbc,0x5e9fcf4ccd211f4c, + 0xd47487cc8470652b,0x7647c3200069671f, + 0x84c8d4dfd2c63f3b,0x29ecd9f40041e073, + 0xa5fb0a17c777cf09,0xf468107100525890, + 0xcf79cc9db955c2cc,0x7182148d4066eeb4, + 0x81ac1fe293d599bf,0xc6f14cd848405530, + 0xa21727db38cb002f,0xb8ada00e5a506a7c, + 0xca9cf1d206fdc03b,0xa6d90811f0e4851c, + 0xfd442e4688bd304a,0x908f4a166d1da663, + 0x9e4a9cec15763e2e,0x9a598e4e043287fe, + 0xc5dd44271ad3cdba,0x40eff1e1853f29fd, + 0xf7549530e188c128,0xd12bee59e68ef47c, + 0x9a94dd3e8cf578b9,0x82bb74f8301958ce, + 0xc13a148e3032d6e7,0xe36a52363c1faf01, + 0xf18899b1bc3f8ca1,0xdc44e6c3cb279ac1, + 0x96f5600f15a7b7e5,0x29ab103a5ef8c0b9, + 0xbcb2b812db11a5de,0x7415d448f6b6f0e7, + 0xebdf661791d60f56,0x111b495b3464ad21, + 0x936b9fcebb25c995,0xcab10dd900beec34, + 0xb84687c269ef3bfb,0x3d5d514f40eea742, + 0xe65829b3046b0afa,0xcb4a5a3112a5112, + 0x8ff71a0fe2c2e6dc,0x47f0e785eaba72ab, + 0xb3f4e093db73a093,0x59ed216765690f56, + 0xe0f218b8d25088b8,0x306869c13ec3532c, + 0x8c974f7383725573,0x1e414218c73a13fb, + 0xafbd2350644eeacf,0xe5d1929ef90898fa, + 0xdbac6c247d62a583,0xdf45f746b74abf39, + 0x894bc396ce5da772,0x6b8bba8c328eb783, + 0xab9eb47c81f5114f,0x66ea92f3f326564, + 0xd686619ba27255a2,0xc80a537b0efefebd, + 0x8613fd0145877585,0xbd06742ce95f5f36, + 0xa798fc4196e952e7,0x2c48113823b73704, + 0xd17f3b51fca3a7a0,0xf75a15862ca504c5, + 0x82ef85133de648c4,0x9a984d73dbe722fb, + 0xa3ab66580d5fdaf5,0xc13e60d0d2e0ebba, + 0xcc963fee10b7d1b3,0x318df905079926a8, + 0xffbbcfe994e5c61f,0xfdf17746497f7052, + 0x9fd561f1fd0f9bd3,0xfeb6ea8bedefa633, + 0xc7caba6e7c5382c8,0xfe64a52ee96b8fc0, + 0xf9bd690a1b68637b,0x3dfdce7aa3c673b0, + 0x9c1661a651213e2d,0x6bea10ca65c084e, + 0xc31bfa0fe5698db8,0x486e494fcff30a62, + 0xf3e2f893dec3f126,0x5a89dba3c3efccfa, + 0x986ddb5c6b3a76b7,0xf89629465a75e01c, + 0xbe89523386091465,0xf6bbb397f1135823, + 0xee2ba6c0678b597f,0x746aa07ded582e2c, + 0x94db483840b717ef,0xa8c2a44eb4571cdc, + 0xba121a4650e4ddeb,0x92f34d62616ce413, + 0xe896a0d7e51e1566,0x77b020baf9c81d17, + 0x915e2486ef32cd60,0xace1474dc1d122e, + 0xb5b5ada8aaff80b8,0xd819992132456ba, + 0xe3231912d5bf60e6,0x10e1fff697ed6c69, + 0x8df5efabc5979c8f,0xca8d3ffa1ef463c1, + 0xb1736b96b6fd83b3,0xbd308ff8a6b17cb2, + 0xddd0467c64bce4a0,0xac7cb3f6d05ddbde, + 0x8aa22c0dbef60ee4,0x6bcdf07a423aa96b, + 0xad4ab7112eb3929d,0x86c16c98d2c953c6, + 0xd89d64d57a607744,0xe871c7bf077ba8b7, + 0x87625f056c7c4a8b,0x11471cd764ad4972, + 0xa93af6c6c79b5d2d,0xd598e40d3dd89bcf, + 0xd389b47879823479,0x4aff1d108d4ec2c3, + 0x843610cb4bf160cb,0xcedf722a585139ba, + 0xa54394fe1eedb8fe,0xc2974eb4ee658828, + 0xce947a3da6a9273e,0x733d226229feea32, + 0x811ccc668829b887,0x806357d5a3f525f, + 0xa163ff802a3426a8,0xca07c2dcb0cf26f7, + 0xc9bcff6034c13052,0xfc89b393dd02f0b5, + 0xfc2c3f3841f17c67,0xbbac2078d443ace2, + 0x9d9ba7832936edc0,0xd54b944b84aa4c0d, + 0xc5029163f384a931,0xa9e795e65d4df11, + 0xf64335bcf065d37d,0x4d4617b5ff4a16d5, + 0x99ea0196163fa42e,0x504bced1bf8e4e45, + 0xc06481fb9bcf8d39,0xe45ec2862f71e1d6, + 0xf07da27a82c37088,0x5d767327bb4e5a4c, + 0x964e858c91ba2655,0x3a6a07f8d510f86f, + 0xbbe226efb628afea,0x890489f70a55368b, + 0xeadab0aba3b2dbe5,0x2b45ac74ccea842e, + 0x92c8ae6b464fc96f,0x3b0b8bc90012929d, + 0xb77ada0617e3bbcb,0x9ce6ebb40173744, + 0xe55990879ddcaabd,0xcc420a6a101d0515, + 0x8f57fa54c2a9eab6,0x9fa946824a12232d, + 0xb32df8e9f3546564,0x47939822dc96abf9, + 0xdff9772470297ebd,0x59787e2b93bc56f7, + 0x8bfbea76c619ef36,0x57eb4edb3c55b65a, + 0xaefae51477a06b03,0xede622920b6b23f1, + 0xdab99e59958885c4,0xe95fab368e45eced, + 0x88b402f7fd75539b,0x11dbcb0218ebb414, + 0xaae103b5fcd2a881,0xd652bdc29f26a119, + 0xd59944a37c0752a2,0x4be76d3346f0495f, + 0x857fcae62d8493a5,0x6f70a4400c562ddb, + 0xa6dfbd9fb8e5b88e,0xcb4ccd500f6bb952, + 0xd097ad07a71f26b2,0x7e2000a41346a7a7, + 0x825ecc24c873782f,0x8ed400668c0c28c8, + 0xa2f67f2dfa90563b,0x728900802f0f32fa, + 0xcbb41ef979346bca,0x4f2b40a03ad2ffb9, + 0xfea126b7d78186bc,0xe2f610c84987bfa8, + 0x9f24b832e6b0f436,0xdd9ca7d2df4d7c9, + 0xc6ede63fa05d3143,0x91503d1c79720dbb, + 0xf8a95fcf88747d94,0x75a44c6397ce912a, + 0x9b69dbe1b548ce7c,0xc986afbe3ee11aba, + 0xc24452da229b021b,0xfbe85badce996168, + 0xf2d56790ab41c2a2,0xfae27299423fb9c3, + 0x97c560ba6b0919a5,0xdccd879fc967d41a, + 0xbdb6b8e905cb600f,0x5400e987bbc1c920, + 0xed246723473e3813,0x290123e9aab23b68, + 0x9436c0760c86e30b,0xf9a0b6720aaf6521, + 0xb94470938fa89bce,0xf808e40e8d5b3e69, + 0xe7958cb87392c2c2,0xb60b1d1230b20e04, + 0x90bd77f3483bb9b9,0xb1c6f22b5e6f48c2, + 0xb4ecd5f01a4aa828,0x1e38aeb6360b1af3, + 0xe2280b6c20dd5232,0x25c6da63c38de1b0, + 0x8d590723948a535f,0x579c487e5a38ad0e, + 0xb0af48ec79ace837,0x2d835a9df0c6d851, + 0xdcdb1b2798182244,0xf8e431456cf88e65, + 0x8a08f0f8bf0f156b,0x1b8e9ecb641b58ff, + 0xac8b2d36eed2dac5,0xe272467e3d222f3f, + 0xd7adf884aa879177,0x5b0ed81dcc6abb0f, + 0x86ccbb52ea94baea,0x98e947129fc2b4e9, + 0xa87fea27a539e9a5,0x3f2398d747b36224, + 0xd29fe4b18e88640e,0x8eec7f0d19a03aad, + 0x83a3eeeef9153e89,0x1953cf68300424ac, + 0xa48ceaaab75a8e2b,0x5fa8c3423c052dd7, + 0xcdb02555653131b6,0x3792f412cb06794d, + 0x808e17555f3ebf11,0xe2bbd88bbee40bd0, + 0xa0b19d2ab70e6ed6,0x5b6aceaeae9d0ec4, + 0xc8de047564d20a8b,0xf245825a5a445275, + 0xfb158592be068d2e,0xeed6e2f0f0d56712, + 0x9ced737bb6c4183d,0x55464dd69685606b, + 0xc428d05aa4751e4c,0xaa97e14c3c26b886, + 0xf53304714d9265df,0xd53dd99f4b3066a8, + 0x993fe2c6d07b7fab,0xe546a8038efe4029, + 0xbf8fdb78849a5f96,0xde98520472bdd033, + 0xef73d256a5c0f77c,0x963e66858f6d4440, + 0x95a8637627989aad,0xdde7001379a44aa8, + 0xbb127c53b17ec159,0x5560c018580d5d52, + 0xe9d71b689dde71af,0xaab8f01e6e10b4a6, + 0x9226712162ab070d,0xcab3961304ca70e8, + 0xb6b00d69bb55c8d1,0x3d607b97c5fd0d22, + 0xe45c10c42a2b3b05,0x8cb89a7db77c506a, + 0x8eb98a7a9a5b04e3,0x77f3608e92adb242, + 0xb267ed1940f1c61c,0x55f038b237591ed3, + 0xdf01e85f912e37a3,0x6b6c46dec52f6688, + 0x8b61313bbabce2c6,0x2323ac4b3b3da015, + 0xae397d8aa96c1b77,0xabec975e0a0d081a, + 0xd9c7dced53c72255,0x96e7bd358c904a21, + 0x881cea14545c7575,0x7e50d64177da2e54, + 0xaa242499697392d2,0xdde50bd1d5d0b9e9, + 0xd4ad2dbfc3d07787,0x955e4ec64b44e864, + 0x84ec3c97da624ab4,0xbd5af13bef0b113e, + 0xa6274bbdd0fadd61,0xecb1ad8aeacdd58e, + 0xcfb11ead453994ba,0x67de18eda5814af2, + 0x81ceb32c4b43fcf4,0x80eacf948770ced7, + 0xa2425ff75e14fc31,0xa1258379a94d028d, + 0xcad2f7f5359a3b3e,0x96ee45813a04330, + 0xfd87b5f28300ca0d,0x8bca9d6e188853fc, + 0x9e74d1b791e07e48,0x775ea264cf55347e, + 0xc612062576589dda,0x95364afe032a81a0, + 0xf79687aed3eec551,0x3a83ddbd83f52210, + 0x9abe14cd44753b52,0xc4926a9672793580, + 0xc16d9a0095928a27,0x75b7053c0f178400, + 0xf1c90080baf72cb1,0x5324c68b12dd6800, + 0x971da05074da7bee,0xd3f6fc16ebca8000, + 0xbce5086492111aea,0x88f4bb1ca6bd0000, + 0xec1e4a7db69561a5,0x2b31e9e3d0700000, + 0x9392ee8e921d5d07,0x3aff322e62600000, + 0xb877aa3236a4b449,0x9befeb9fad487c3, + 0xe69594bec44de15b,0x4c2ebe687989a9b4, + 0x901d7cf73ab0acd9,0xf9d37014bf60a11, + 0xb424dc35095cd80f,0x538484c19ef38c95, + 0xe12e13424bb40e13,0x2865a5f206b06fba, + 0x8cbccc096f5088cb,0xf93f87b7442e45d4, + 0xafebff0bcb24aafe,0xf78f69a51539d749, + 0xdbe6fecebdedd5be,0xb573440e5a884d1c, + 0x89705f4136b4a597,0x31680a88f8953031, + 0xabcc77118461cefc,0xfdc20d2b36ba7c3e, + 0xd6bf94d5e57a42bc,0x3d32907604691b4d, + 0x8637bd05af6c69b5,0xa63f9a49c2c1b110, + 0xa7c5ac471b478423,0xfcf80dc33721d54, + 0xd1b71758e219652b,0xd3c36113404ea4a9, + 0x83126e978d4fdf3b,0x645a1cac083126ea, + 0xa3d70a3d70a3d70a,0x3d70a3d70a3d70a4, + 0xcccccccccccccccc,0xcccccccccccccccd, + 0x8000000000000000,0x0, + 0xa000000000000000,0x0, + 0xc800000000000000,0x0, + 0xfa00000000000000,0x0, + 0x9c40000000000000,0x0, + 0xc350000000000000,0x0, + 0xf424000000000000,0x0, + 0x9896800000000000,0x0, + 0xbebc200000000000,0x0, + 0xee6b280000000000,0x0, + 0x9502f90000000000,0x0, + 0xba43b74000000000,0x0, + 0xe8d4a51000000000,0x0, + 0x9184e72a00000000,0x0, + 0xb5e620f480000000,0x0, + 0xe35fa931a0000000,0x0, + 0x8e1bc9bf04000000,0x0, + 0xb1a2bc2ec5000000,0x0, + 0xde0b6b3a76400000,0x0, + 0x8ac7230489e80000,0x0, + 0xad78ebc5ac620000,0x0, + 0xd8d726b7177a8000,0x0, + 0x878678326eac9000,0x0, + 0xa968163f0a57b400,0x0, + 0xd3c21bcecceda100,0x0, + 0x84595161401484a0,0x0, + 0xa56fa5b99019a5c8,0x0, + 0xcecb8f27f4200f3a,0x0, + 0x813f3978f8940984,0x4000000000000000, + 0xa18f07d736b90be5,0x5000000000000000, + 0xc9f2c9cd04674ede,0xa400000000000000, + 0xfc6f7c4045812296,0x4d00000000000000, + 0x9dc5ada82b70b59d,0xf020000000000000, + 0xc5371912364ce305,0x6c28000000000000, + 0xf684df56c3e01bc6,0xc732000000000000, + 0x9a130b963a6c115c,0x3c7f400000000000, + 0xc097ce7bc90715b3,0x4b9f100000000000, + 0xf0bdc21abb48db20,0x1e86d40000000000, + 0x96769950b50d88f4,0x1314448000000000, + 0xbc143fa4e250eb31,0x17d955a000000000, + 0xeb194f8e1ae525fd,0x5dcfab0800000000, + 0x92efd1b8d0cf37be,0x5aa1cae500000000, + 0xb7abc627050305ad,0xf14a3d9e40000000, + 0xe596b7b0c643c719,0x6d9ccd05d0000000, + 0x8f7e32ce7bea5c6f,0xe4820023a2000000, + 0xb35dbf821ae4f38b,0xdda2802c8a800000, + 0xe0352f62a19e306e,0xd50b2037ad200000, + 0x8c213d9da502de45,0x4526f422cc340000, + 0xaf298d050e4395d6,0x9670b12b7f410000, + 0xdaf3f04651d47b4c,0x3c0cdd765f114000, + 0x88d8762bf324cd0f,0xa5880a69fb6ac800, + 0xab0e93b6efee0053,0x8eea0d047a457a00, + 0xd5d238a4abe98068,0x72a4904598d6d880, + 0x85a36366eb71f041,0x47a6da2b7f864750, + 0xa70c3c40a64e6c51,0x999090b65f67d924, + 0xd0cf4b50cfe20765,0xfff4b4e3f741cf6d, + 0x82818f1281ed449f,0xbff8f10e7a8921a4, + 0xa321f2d7226895c7,0xaff72d52192b6a0d, + 0xcbea6f8ceb02bb39,0x9bf4f8a69f764490, + 0xfee50b7025c36a08,0x2f236d04753d5b4, + 0x9f4f2726179a2245,0x1d762422c946590, + 0xc722f0ef9d80aad6,0x424d3ad2b7b97ef5, + 0xf8ebad2b84e0d58b,0xd2e0898765a7deb2, + 0x9b934c3b330c8577,0x63cc55f49f88eb2f, + 0xc2781f49ffcfa6d5,0x3cbf6b71c76b25fb, + 0xf316271c7fc3908a,0x8bef464e3945ef7a, + 0x97edd871cfda3a56,0x97758bf0e3cbb5ac, + 0xbde94e8e43d0c8ec,0x3d52eeed1cbea317, + 0xed63a231d4c4fb27,0x4ca7aaa863ee4bdd, + 0x945e455f24fb1cf8,0x8fe8caa93e74ef6a, + 0xb975d6b6ee39e436,0xb3e2fd538e122b44, + 0xe7d34c64a9c85d44,0x60dbbca87196b616, + 0x90e40fbeea1d3a4a,0xbc8955e946fe31cd, + 0xb51d13aea4a488dd,0x6babab6398bdbe41, + 0xe264589a4dcdab14,0xc696963c7eed2dd1, + 0x8d7eb76070a08aec,0xfc1e1de5cf543ca2, + 0xb0de65388cc8ada8,0x3b25a55f43294bcb, + 0xdd15fe86affad912,0x49ef0eb713f39ebe, + 0x8a2dbf142dfcc7ab,0x6e3569326c784337, + 0xacb92ed9397bf996,0x49c2c37f07965404, + 0xd7e77a8f87daf7fb,0xdc33745ec97be906, + 0x86f0ac99b4e8dafd,0x69a028bb3ded71a3, + 0xa8acd7c0222311bc,0xc40832ea0d68ce0c, + 0xd2d80db02aabd62b,0xf50a3fa490c30190, + 0x83c7088e1aab65db,0x792667c6da79e0fa, + 0xa4b8cab1a1563f52,0x577001b891185938, + 0xcde6fd5e09abcf26,0xed4c0226b55e6f86, + 0x80b05e5ac60b6178,0x544f8158315b05b4, + 0xa0dc75f1778e39d6,0x696361ae3db1c721, + 0xc913936dd571c84c,0x3bc3a19cd1e38e9, + 0xfb5878494ace3a5f,0x4ab48a04065c723, + 0x9d174b2dcec0e47b,0x62eb0d64283f9c76, + 0xc45d1df942711d9a,0x3ba5d0bd324f8394, + 0xf5746577930d6500,0xca8f44ec7ee36479, + 0x9968bf6abbe85f20,0x7e998b13cf4e1ecb, + 0xbfc2ef456ae276e8,0x9e3fedd8c321a67e, + 0xefb3ab16c59b14a2,0xc5cfe94ef3ea101e, + 0x95d04aee3b80ece5,0xbba1f1d158724a12, + 0xbb445da9ca61281f,0x2a8a6e45ae8edc97, + 0xea1575143cf97226,0xf52d09d71a3293bd, + 0x924d692ca61be758,0x593c2626705f9c56, + 0xb6e0c377cfa2e12e,0x6f8b2fb00c77836c, + 0xe498f455c38b997a,0xb6dfb9c0f956447, + 0x8edf98b59a373fec,0x4724bd4189bd5eac, + 0xb2977ee300c50fe7,0x58edec91ec2cb657, + 0xdf3d5e9bc0f653e1,0x2f2967b66737e3ed, + 0x8b865b215899f46c,0xbd79e0d20082ee74, + 0xae67f1e9aec07187,0xecd8590680a3aa11, + 0xda01ee641a708de9,0xe80e6f4820cc9495, + 0x884134fe908658b2,0x3109058d147fdcdd, + 0xaa51823e34a7eede,0xbd4b46f0599fd415, + 0xd4e5e2cdc1d1ea96,0x6c9e18ac7007c91a, + 0x850fadc09923329e,0x3e2cf6bc604ddb0, + 0xa6539930bf6bff45,0x84db8346b786151c, + 0xcfe87f7cef46ff16,0xe612641865679a63, + 0x81f14fae158c5f6e,0x4fcb7e8f3f60c07e, + 0xa26da3999aef7749,0xe3be5e330f38f09d, + 0xcb090c8001ab551c,0x5cadf5bfd3072cc5, + 0xfdcb4fa002162a63,0x73d9732fc7c8f7f6, + 0x9e9f11c4014dda7e,0x2867e7fddcdd9afa, + 0xc646d63501a1511d,0xb281e1fd541501b8, + 0xf7d88bc24209a565,0x1f225a7ca91a4226, + 0x9ae757596946075f,0x3375788de9b06958, + 0xc1a12d2fc3978937,0x52d6b1641c83ae, + 0xf209787bb47d6b84,0xc0678c5dbd23a49a, + 0x9745eb4d50ce6332,0xf840b7ba963646e0, + 0xbd176620a501fbff,0xb650e5a93bc3d898, + 0xec5d3fa8ce427aff,0xa3e51f138ab4cebe, + 0x93ba47c980e98cdf,0xc66f336c36b10137, + 0xb8a8d9bbe123f017,0xb80b0047445d4184, + 0xe6d3102ad96cec1d,0xa60dc059157491e5, + 0x9043ea1ac7e41392,0x87c89837ad68db2f, + 0xb454e4a179dd1877,0x29babe4598c311fb, + 0xe16a1dc9d8545e94,0xf4296dd6fef3d67a, + 0x8ce2529e2734bb1d,0x1899e4a65f58660c, + 0xb01ae745b101e9e4,0x5ec05dcff72e7f8f, + 0xdc21a1171d42645d,0x76707543f4fa1f73, + 0x899504ae72497eba,0x6a06494a791c53a8, + 0xabfa45da0edbde69,0x487db9d17636892, + 0xd6f8d7509292d603,0x45a9d2845d3c42b6, + 0x865b86925b9bc5c2,0xb8a2392ba45a9b2, + 0xa7f26836f282b732,0x8e6cac7768d7141e, + 0xd1ef0244af2364ff,0x3207d795430cd926, + 0x8335616aed761f1f,0x7f44e6bd49e807b8, + 0xa402b9c5a8d3a6e7,0x5f16206c9c6209a6, + 0xcd036837130890a1,0x36dba887c37a8c0f, + 0x802221226be55a64,0xc2494954da2c9789, + 0xa02aa96b06deb0fd,0xf2db9baa10b7bd6c, + 0xc83553c5c8965d3d,0x6f92829494e5acc7, + 0xfa42a8b73abbf48c,0xcb772339ba1f17f9, + 0x9c69a97284b578d7,0xff2a760414536efb, + 0xc38413cf25e2d70d,0xfef5138519684aba, + 0xf46518c2ef5b8cd1,0x7eb258665fc25d69, + 0x98bf2f79d5993802,0xef2f773ffbd97a61, + 0xbeeefb584aff8603,0xaafb550ffacfd8fa, + 0xeeaaba2e5dbf6784,0x95ba2a53f983cf38, + 0x952ab45cfa97a0b2,0xdd945a747bf26183, + 0xba756174393d88df,0x94f971119aeef9e4, + 0xe912b9d1478ceb17,0x7a37cd5601aab85d, + 0x91abb422ccb812ee,0xac62e055c10ab33a, + 0xb616a12b7fe617aa,0x577b986b314d6009, + 0xe39c49765fdf9d94,0xed5a7e85fda0b80b, + 0x8e41ade9fbebc27d,0x14588f13be847307, + 0xb1d219647ae6b31c,0x596eb2d8ae258fc8, + 0xde469fbd99a05fe3,0x6fca5f8ed9aef3bb, + 0x8aec23d680043bee,0x25de7bb9480d5854, + 0xada72ccc20054ae9,0xaf561aa79a10ae6a, + 0xd910f7ff28069da4,0x1b2ba1518094da04, + 0x87aa9aff79042286,0x90fb44d2f05d0842, + 0xa99541bf57452b28,0x353a1607ac744a53, + 0xd3fa922f2d1675f2,0x42889b8997915ce8, + 0x847c9b5d7c2e09b7,0x69956135febada11, + 0xa59bc234db398c25,0x43fab9837e699095, + 0xcf02b2c21207ef2e,0x94f967e45e03f4bb, + 0x8161afb94b44f57d,0x1d1be0eebac278f5, + 0xa1ba1ba79e1632dc,0x6462d92a69731732, + 0xca28a291859bbf93,0x7d7b8f7503cfdcfe, + 0xfcb2cb35e702af78,0x5cda735244c3d43e, + 0x9defbf01b061adab,0x3a0888136afa64a7, + 0xc56baec21c7a1916,0x88aaa1845b8fdd0, + 0xf6c69a72a3989f5b,0x8aad549e57273d45, + 0x9a3c2087a63f6399,0x36ac54e2f678864b, + 0xc0cb28a98fcf3c7f,0x84576a1bb416a7dd, + 0xf0fdf2d3f3c30b9f,0x656d44a2a11c51d5, + 0x969eb7c47859e743,0x9f644ae5a4b1b325, + 0xbc4665b596706114,0x873d5d9f0dde1fee, + 0xeb57ff22fc0c7959,0xa90cb506d155a7ea, + 0x9316ff75dd87cbd8,0x9a7f12442d588f2, + 0xb7dcbf5354e9bece,0xc11ed6d538aeb2f, + 0xe5d3ef282a242e81,0x8f1668c8a86da5fa, + 0x8fa475791a569d10,0xf96e017d694487bc, + 0xb38d92d760ec4455,0x37c981dcc395a9ac, + 0xe070f78d3927556a,0x85bbe253f47b1417, + 0x8c469ab843b89562,0x93956d7478ccec8e, + 0xaf58416654a6babb,0x387ac8d1970027b2, + 0xdb2e51bfe9d0696a,0x6997b05fcc0319e, + 0x88fcf317f22241e2,0x441fece3bdf81f03, + 0xab3c2fddeeaad25a,0xd527e81cad7626c3, + 0xd60b3bd56a5586f1,0x8a71e223d8d3b074, + 0x85c7056562757456,0xf6872d5667844e49, + 0xa738c6bebb12d16c,0xb428f8ac016561db, + 0xd106f86e69d785c7,0xe13336d701beba52, + 0x82a45b450226b39c,0xecc0024661173473, + 0xa34d721642b06084,0x27f002d7f95d0190, + 0xcc20ce9bd35c78a5,0x31ec038df7b441f4, + 0xff290242c83396ce,0x7e67047175a15271, + 0x9f79a169bd203e41,0xf0062c6e984d386, + 0xc75809c42c684dd1,0x52c07b78a3e60868, + 0xf92e0c3537826145,0xa7709a56ccdf8a82, + 0x9bbcc7a142b17ccb,0x88a66076400bb691, + 0xc2abf989935ddbfe,0x6acff893d00ea435, + 0xf356f7ebf83552fe,0x583f6b8c4124d43, + 0x98165af37b2153de,0xc3727a337a8b704a, + 0xbe1bf1b059e9a8d6,0x744f18c0592e4c5c, + 0xeda2ee1c7064130c,0x1162def06f79df73, + 0x9485d4d1c63e8be7,0x8addcb5645ac2ba8, + 0xb9a74a0637ce2ee1,0x6d953e2bd7173692, + 0xe8111c87c5c1ba99,0xc8fa8db6ccdd0437, + 0x910ab1d4db9914a0,0x1d9c9892400a22a2, + 0xb54d5e4a127f59c8,0x2503beb6d00cab4b, + 0xe2a0b5dc971f303a,0x2e44ae64840fd61d, + 0x8da471a9de737e24,0x5ceaecfed289e5d2, + 0xb10d8e1456105dad,0x7425a83e872c5f47, + 0xdd50f1996b947518,0xd12f124e28f77719, + 0x8a5296ffe33cc92f,0x82bd6b70d99aaa6f, + 0xace73cbfdc0bfb7b,0x636cc64d1001550b, + 0xd8210befd30efa5a,0x3c47f7e05401aa4e, + 0x8714a775e3e95c78,0x65acfaec34810a71, + 0xa8d9d1535ce3b396,0x7f1839a741a14d0d, + 0xd31045a8341ca07c,0x1ede48111209a050, + 0x83ea2b892091e44d,0x934aed0aab460432, + 0xa4e4b66b68b65d60,0xf81da84d5617853f, + 0xce1de40642e3f4b9,0x36251260ab9d668e, + 0x80d2ae83e9ce78f3,0xc1d72b7c6b426019, + 0xa1075a24e4421730,0xb24cf65b8612f81f, + 0xc94930ae1d529cfc,0xdee033f26797b627, + 0xfb9b7cd9a4a7443c,0x169840ef017da3b1, + 0x9d412e0806e88aa5,0x8e1f289560ee864e, + 0xc491798a08a2ad4e,0xf1a6f2bab92a27e2, + 0xf5b5d7ec8acb58a2,0xae10af696774b1db, + 0x9991a6f3d6bf1765,0xacca6da1e0a8ef29, + 0xbff610b0cc6edd3f,0x17fd090a58d32af3, + 0xeff394dcff8a948e,0xddfc4b4cef07f5b0, + 0x95f83d0a1fb69cd9,0x4abdaf101564f98e, + 0xbb764c4ca7a4440f,0x9d6d1ad41abe37f1, + 0xea53df5fd18d5513,0x84c86189216dc5ed, + 0x92746b9be2f8552c,0x32fd3cf5b4e49bb4, + 0xb7118682dbb66a77,0x3fbc8c33221dc2a1, + 0xe4d5e82392a40515,0xfabaf3feaa5334a, + 0x8f05b1163ba6832d,0x29cb4d87f2a7400e, + 0xb2c71d5bca9023f8,0x743e20e9ef511012, + 0xdf78e4b2bd342cf6,0x914da9246b255416, + 0x8bab8eefb6409c1a,0x1ad089b6c2f7548e, + 0xae9672aba3d0c320,0xa184ac2473b529b1, + 0xda3c0f568cc4f3e8,0xc9e5d72d90a2741e, + 0x8865899617fb1871,0x7e2fa67c7a658892, + 0xaa7eebfb9df9de8d,0xddbb901b98feeab7, + 0xd51ea6fa85785631,0x552a74227f3ea565, + 0x8533285c936b35de,0xd53a88958f87275f, + 0xa67ff273b8460356,0x8a892abaf368f137, + 0xd01fef10a657842c,0x2d2b7569b0432d85, + 0x8213f56a67f6b29b,0x9c3b29620e29fc73, + 0xa298f2c501f45f42,0x8349f3ba91b47b8f, + 0xcb3f2f7642717713,0x241c70a936219a73, + 0xfe0efb53d30dd4d7,0xed238cd383aa0110, + 0x9ec95d1463e8a506,0xf4363804324a40aa, + 0xc67bb4597ce2ce48,0xb143c6053edcd0d5, + 0xf81aa16fdc1b81da,0xdd94b7868e94050a, + 0x9b10a4e5e9913128,0xca7cf2b4191c8326, + 0xc1d4ce1f63f57d72,0xfd1c2f611f63a3f0, + 0xf24a01a73cf2dccf,0xbc633b39673c8cec, + 0x976e41088617ca01,0xd5be0503e085d813, + 0xbd49d14aa79dbc82,0x4b2d8644d8a74e18, + 0xec9c459d51852ba2,0xddf8e7d60ed1219e, + 0x93e1ab8252f33b45,0xcabb90e5c942b503, + 0xb8da1662e7b00a17,0x3d6a751f3b936243, + 0xe7109bfba19c0c9d,0xcc512670a783ad4, + 0x906a617d450187e2,0x27fb2b80668b24c5, + 0xb484f9dc9641e9da,0xb1f9f660802dedf6, + 0xe1a63853bbd26451,0x5e7873f8a0396973, + 0x8d07e33455637eb2,0xdb0b487b6423e1e8, + 0xb049dc016abc5e5f,0x91ce1a9a3d2cda62, + 0xdc5c5301c56b75f7,0x7641a140cc7810fb, + 0x89b9b3e11b6329ba,0xa9e904c87fcb0a9d, + 0xac2820d9623bf429,0x546345fa9fbdcd44, + 0xd732290fbacaf133,0xa97c177947ad4095, + 0x867f59a9d4bed6c0,0x49ed8eabcccc485d, + 0xa81f301449ee8c70,0x5c68f256bfff5a74, + 0xd226fc195c6a2f8c,0x73832eec6fff3111, + 0x83585d8fd9c25db7,0xc831fd53c5ff7eab, + 0xa42e74f3d032f525,0xba3e7ca8b77f5e55, + 0xcd3a1230c43fb26f,0x28ce1bd2e55f35eb, + 0x80444b5e7aa7cf85,0x7980d163cf5b81b3, + 0xa0555e361951c366,0xd7e105bcc332621f, + 0xc86ab5c39fa63440,0x8dd9472bf3fefaa7, + 0xfa856334878fc150,0xb14f98f6f0feb951, + 0x9c935e00d4b9d8d2,0x6ed1bf9a569f33d3, + 0xc3b8358109e84f07,0xa862f80ec4700c8, + 0xf4a642e14c6262c8,0xcd27bb612758c0fa, + 0x98e7e9cccfbd7dbd,0x8038d51cb897789c, + 0xbf21e44003acdd2c,0xe0470a63e6bd56c3, + 0xeeea5d5004981478,0x1858ccfce06cac74, + 0x95527a5202df0ccb,0xf37801e0c43ebc8, + 0xbaa718e68396cffd,0xd30560258f54e6ba, + 0xe950df20247c83fd,0x47c6b82ef32a2069, + 0x91d28b7416cdd27e,0x4cdc331d57fa5441, + 0xb6472e511c81471d,0xe0133fe4adf8e952, + 0xe3d8f9e563a198e5,0x58180fddd97723a6, + 0x8e679c2f5e44ff8f,0x570f09eaa7ea7648,}; + +#endif // SIMDJSON_SRC_NUMBERPARSING_TABLES_CPP \ No newline at end of file diff --git a/contrib/libs/simdjson/src/internal/simdprune_tables.cpp b/contrib/libs/simdjson/src/internal/simdprune_tables.cpp new file mode 100644 index 000000000000..6b159944bcb7 --- /dev/null +++ b/contrib/libs/simdjson/src/internal/simdprune_tables.cpp @@ -0,0 +1,138 @@ +#ifndef SIMDJSON_SRC_SIMDPRUNE_TABLES_CPP +#define SIMDJSON_SRC_SIMDPRUNE_TABLES_CPP + +#include + +#if SIMDJSON_IMPLEMENTATION_ARM64 || SIMDJSON_IMPLEMENTATION_ICELAKE || SIMDJSON_IMPLEMENTATION_HASWELL || SIMDJSON_IMPLEMENTATION_WESTMERE || SIMDJSON_IMPLEMENTATION_PPC64 || SIMDJSON_IMPLEMENTATION_LSX || SIMDJSON_IMPLEMENTATION_LASX + +#include + +namespace simdjson { // table modified and copied from +namespace internal { // http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetTable +SIMDJSON_DLLIMPORTEXPORT const unsigned char BitsSetTable256mul2[256] = { + 0, 2, 2, 4, 2, 4, 4, 6, 2, 4, 4, 6, 4, 6, 6, 8, 2, 4, 4, + 6, 4, 6, 6, 8, 4, 6, 6, 8, 6, 8, 8, 10, 2, 4, 4, 6, 4, 6, + 6, 8, 4, 6, 6, 8, 6, 8, 8, 10, 4, 6, 6, 8, 6, 8, 8, 10, 6, + 8, 8, 10, 8, 10, 10, 12, 2, 4, 4, 6, 4, 6, 6, 8, 4, 6, 6, 8, + 6, 8, 8, 10, 4, 6, 6, 8, 6, 8, 8, 10, 6, 8, 8, 10, 8, 10, 10, + 12, 4, 6, 6, 8, 6, 8, 8, 10, 6, 8, 8, 10, 8, 10, 10, 12, 6, 8, + 8, 10, 8, 10, 10, 12, 8, 10, 10, 12, 10, 12, 12, 14, 2, 4, 4, 6, 4, + 6, 6, 8, 4, 6, 6, 8, 6, 8, 8, 10, 4, 6, 6, 8, 6, 8, 8, 10, + 6, 8, 8, 10, 8, 10, 10, 12, 4, 6, 6, 8, 6, 8, 8, 10, 6, 8, 8, + 10, 8, 10, 10, 12, 6, 8, 8, 10, 8, 10, 10, 12, 8, 10, 10, 12, 10, 12, + 12, 14, 4, 6, 6, 8, 6, 8, 8, 10, 6, 8, 8, 10, 8, 10, 10, 12, 6, + 8, 8, 10, 8, 10, 10, 12, 8, 10, 10, 12, 10, 12, 12, 14, 6, 8, 8, 10, + 8, 10, 10, 12, 8, 10, 10, 12, 10, 12, 12, 14, 8, 10, 10, 12, 10, 12, 12, + 14, 10, 12, 12, 14, 12, 14, 14, 16}; + +SIMDJSON_DLLIMPORTEXPORT const uint8_t pshufb_combine_table[272] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xff, 0x00, 0x01, 0x02, 0x03, + 0x04, 0x05, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xff, 0xff, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x01, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +// 256 * 8 bytes = 2kB, easily fits in cache. +SIMDJSON_DLLIMPORTEXPORT const uint64_t thintable_epi8[256] = { + 0x0706050403020100, 0x0007060504030201, 0x0007060504030200, + 0x0000070605040302, 0x0007060504030100, 0x0000070605040301, + 0x0000070605040300, 0x0000000706050403, 0x0007060504020100, + 0x0000070605040201, 0x0000070605040200, 0x0000000706050402, + 0x0000070605040100, 0x0000000706050401, 0x0000000706050400, + 0x0000000007060504, 0x0007060503020100, 0x0000070605030201, + 0x0000070605030200, 0x0000000706050302, 0x0000070605030100, + 0x0000000706050301, 0x0000000706050300, 0x0000000007060503, + 0x0000070605020100, 0x0000000706050201, 0x0000000706050200, + 0x0000000007060502, 0x0000000706050100, 0x0000000007060501, + 0x0000000007060500, 0x0000000000070605, 0x0007060403020100, + 0x0000070604030201, 0x0000070604030200, 0x0000000706040302, + 0x0000070604030100, 0x0000000706040301, 0x0000000706040300, + 0x0000000007060403, 0x0000070604020100, 0x0000000706040201, + 0x0000000706040200, 0x0000000007060402, 0x0000000706040100, + 0x0000000007060401, 0x0000000007060400, 0x0000000000070604, + 0x0000070603020100, 0x0000000706030201, 0x0000000706030200, + 0x0000000007060302, 0x0000000706030100, 0x0000000007060301, + 0x0000000007060300, 0x0000000000070603, 0x0000000706020100, + 0x0000000007060201, 0x0000000007060200, 0x0000000000070602, + 0x0000000007060100, 0x0000000000070601, 0x0000000000070600, + 0x0000000000000706, 0x0007050403020100, 0x0000070504030201, + 0x0000070504030200, 0x0000000705040302, 0x0000070504030100, + 0x0000000705040301, 0x0000000705040300, 0x0000000007050403, + 0x0000070504020100, 0x0000000705040201, 0x0000000705040200, + 0x0000000007050402, 0x0000000705040100, 0x0000000007050401, + 0x0000000007050400, 0x0000000000070504, 0x0000070503020100, + 0x0000000705030201, 0x0000000705030200, 0x0000000007050302, + 0x0000000705030100, 0x0000000007050301, 0x0000000007050300, + 0x0000000000070503, 0x0000000705020100, 0x0000000007050201, + 0x0000000007050200, 0x0000000000070502, 0x0000000007050100, + 0x0000000000070501, 0x0000000000070500, 0x0000000000000705, + 0x0000070403020100, 0x0000000704030201, 0x0000000704030200, + 0x0000000007040302, 0x0000000704030100, 0x0000000007040301, + 0x0000000007040300, 0x0000000000070403, 0x0000000704020100, + 0x0000000007040201, 0x0000000007040200, 0x0000000000070402, + 0x0000000007040100, 0x0000000000070401, 0x0000000000070400, + 0x0000000000000704, 0x0000000703020100, 0x0000000007030201, + 0x0000000007030200, 0x0000000000070302, 0x0000000007030100, + 0x0000000000070301, 0x0000000000070300, 0x0000000000000703, + 0x0000000007020100, 0x0000000000070201, 0x0000000000070200, + 0x0000000000000702, 0x0000000000070100, 0x0000000000000701, + 0x0000000000000700, 0x0000000000000007, 0x0006050403020100, + 0x0000060504030201, 0x0000060504030200, 0x0000000605040302, + 0x0000060504030100, 0x0000000605040301, 0x0000000605040300, + 0x0000000006050403, 0x0000060504020100, 0x0000000605040201, + 0x0000000605040200, 0x0000000006050402, 0x0000000605040100, + 0x0000000006050401, 0x0000000006050400, 0x0000000000060504, + 0x0000060503020100, 0x0000000605030201, 0x0000000605030200, + 0x0000000006050302, 0x0000000605030100, 0x0000000006050301, + 0x0000000006050300, 0x0000000000060503, 0x0000000605020100, + 0x0000000006050201, 0x0000000006050200, 0x0000000000060502, + 0x0000000006050100, 0x0000000000060501, 0x0000000000060500, + 0x0000000000000605, 0x0000060403020100, 0x0000000604030201, + 0x0000000604030200, 0x0000000006040302, 0x0000000604030100, + 0x0000000006040301, 0x0000000006040300, 0x0000000000060403, + 0x0000000604020100, 0x0000000006040201, 0x0000000006040200, + 0x0000000000060402, 0x0000000006040100, 0x0000000000060401, + 0x0000000000060400, 0x0000000000000604, 0x0000000603020100, + 0x0000000006030201, 0x0000000006030200, 0x0000000000060302, + 0x0000000006030100, 0x0000000000060301, 0x0000000000060300, + 0x0000000000000603, 0x0000000006020100, 0x0000000000060201, + 0x0000000000060200, 0x0000000000000602, 0x0000000000060100, + 0x0000000000000601, 0x0000000000000600, 0x0000000000000006, + 0x0000050403020100, 0x0000000504030201, 0x0000000504030200, + 0x0000000005040302, 0x0000000504030100, 0x0000000005040301, + 0x0000000005040300, 0x0000000000050403, 0x0000000504020100, + 0x0000000005040201, 0x0000000005040200, 0x0000000000050402, + 0x0000000005040100, 0x0000000000050401, 0x0000000000050400, + 0x0000000000000504, 0x0000000503020100, 0x0000000005030201, + 0x0000000005030200, 0x0000000000050302, 0x0000000005030100, + 0x0000000000050301, 0x0000000000050300, 0x0000000000000503, + 0x0000000005020100, 0x0000000000050201, 0x0000000000050200, + 0x0000000000000502, 0x0000000000050100, 0x0000000000000501, + 0x0000000000000500, 0x0000000000000005, 0x0000000403020100, + 0x0000000004030201, 0x0000000004030200, 0x0000000000040302, + 0x0000000004030100, 0x0000000000040301, 0x0000000000040300, + 0x0000000000000403, 0x0000000004020100, 0x0000000000040201, + 0x0000000000040200, 0x0000000000000402, 0x0000000000040100, + 0x0000000000000401, 0x0000000000000400, 0x0000000000000004, + 0x0000000003020100, 0x0000000000030201, 0x0000000000030200, + 0x0000000000000302, 0x0000000000030100, 0x0000000000000301, + 0x0000000000000300, 0x0000000000000003, 0x0000000000020100, + 0x0000000000000201, 0x0000000000000200, 0x0000000000000002, + 0x0000000000000100, 0x0000000000000001, 0x0000000000000000, + 0x0000000000000000, +}; //static uint64_t thintable_epi8[256] + +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_IMPLEMENTATION_ARM64 || SIMDJSON_IMPLEMENTATION_ICELAKE || SIMDJSON_IMPLEMENTATION_HASWELL || SIMDJSON_IMPLEMENTATION_WESTMERE || SIMDJSON_IMPLEMENTATION_PPC64 || SIMDJSON_IMPLEMENTATION_LSX || SIMDJSON_IMPLEMENTATION_LASX + +#endif // SIMDJSON_SRC_SIMDPRUNE_TABLES_CPP diff --git a/contrib/libs/simdjson/src/simdjson.cpp b/contrib/libs/simdjson/src/simdjson.cpp new file mode 100644 index 000000000000..101279525336 --- /dev/null +++ b/contrib/libs/simdjson/src/simdjson.cpp @@ -0,0 +1,50 @@ +#define SIMDJSON_SRC_SIMDJSON_CPP + +#include + +SIMDJSON_PUSH_DISABLE_UNUSED_WARNINGS + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define SIMDJSON_CONDITIONAL_INCLUDE + +#if SIMDJSON_IMPLEMENTATION_ARM64 +#include +#endif +#if SIMDJSON_IMPLEMENTATION_HASWELL +#include +#endif +#if SIMDJSON_IMPLEMENTATION_ICELAKE +#include +#endif +#if SIMDJSON_IMPLEMENTATION_PPC64 +#error #include +#endif +#if SIMDJSON_IMPLEMENTATION_WESTMERE +#include +#endif +#if SIMDJSON_IMPLEMENTATION_LSX +#error #include +#endif +#if SIMDJSON_IMPLEMENTATION_LASX +#error #include +#endif +#if SIMDJSON_IMPLEMENTATION_FALLBACK +#include +#endif +#undef SIMDJSON_CONDITIONAL_INCLUDE + +SIMDJSON_POP_DISABLE_UNUSED_WARNINGS + diff --git a/contrib/libs/simdjson/src/to_chars.cpp b/contrib/libs/simdjson/src/to_chars.cpp new file mode 100644 index 000000000000..ce71ff6cdb92 --- /dev/null +++ b/contrib/libs/simdjson/src/to_chars.cpp @@ -0,0 +1,954 @@ +#ifndef SIMDJSON_SRC_TO_CHARS_CPP +#define SIMDJSON_SRC_TO_CHARS_CPP + +#include + +#include +#include +#include +#include + +namespace simdjson { +namespace internal { +/*! +implements the Grisu2 algorithm for binary to decimal floating-point +conversion. +Adapted from JSON for Modern C++ + +This implementation is a slightly modified version of the reference +implementation which may be obtained from +http://florian.loitsch.com/publications (bench.tar.gz). +The code is distributed under the MIT license, Copyright (c) 2009 Florian +Loitsch. For a detailed description of the algorithm see: [1] Loitsch, "Printing +Floating-Point Numbers Quickly and Accurately with Integers", Proceedings of the +ACM SIGPLAN 2010 Conference on Programming Language Design and Implementation, +PLDI 2010 [2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and +Accurately", Proceedings of the ACM SIGPLAN 1996 Conference on Programming +Language Design and Implementation, PLDI 1996 +*/ +namespace dtoa_impl { + +template +Target reinterpret_bits(const Source source) { + static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); + + Target target; + std::memcpy(&target, &source, sizeof(Source)); + return target; +} + +struct diyfp // f * 2^e +{ + static constexpr int kPrecision = 64; // = q + + std::uint64_t f = 0; + int e = 0; + + constexpr diyfp(std::uint64_t f_, int e_) noexcept : f(f_), e(e_) {} + + /*! + @brief returns x - y + @pre x.e == y.e and x.f >= y.f + */ + static diyfp sub(const diyfp &x, const diyfp &y) noexcept { + + return {x.f - y.f, x.e}; + } + + /*! + @brief returns x * y + @note The result is rounded. (Only the upper q bits are returned.) + */ + static diyfp mul(const diyfp &x, const diyfp &y) noexcept { + static_assert(kPrecision == 64, "internal error"); + + // Computes: + // f = round((x.f * y.f) / 2^q) + // e = x.e + y.e + q + + // Emulate the 64-bit * 64-bit multiplication: + // + // p = u * v + // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) + // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + + // 2^64 (u_hi v_hi ) = (p0 ) + 2^32 ((p1 ) + (p2 )) + // + 2^64 (p3 ) = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + + // 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) = + // (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + + // p2_hi + p3) = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) = (p0_lo ) + + // 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) + // + // (Since Q might be larger than 2^32 - 1) + // + // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) + // + // (Q_hi + H does not overflow a 64-bit int) + // + // = p_lo + 2^64 p_hi + + const std::uint64_t u_lo = x.f & 0xFFFFFFFFu; + const std::uint64_t u_hi = x.f >> 32u; + const std::uint64_t v_lo = y.f & 0xFFFFFFFFu; + const std::uint64_t v_hi = y.f >> 32u; + + const std::uint64_t p0 = u_lo * v_lo; + const std::uint64_t p1 = u_lo * v_hi; + const std::uint64_t p2 = u_hi * v_lo; + const std::uint64_t p3 = u_hi * v_hi; + + const std::uint64_t p0_hi = p0 >> 32u; + const std::uint64_t p1_lo = p1 & 0xFFFFFFFFu; + const std::uint64_t p1_hi = p1 >> 32u; + const std::uint64_t p2_lo = p2 & 0xFFFFFFFFu; + const std::uint64_t p2_hi = p2 >> 32u; + + std::uint64_t Q = p0_hi + p1_lo + p2_lo; + + // The full product might now be computed as + // + // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) + // p_lo = p0_lo + (Q << 32) + // + // But in this particular case here, the full p_lo is not required. + // Effectively we only need to add the highest bit in p_lo to p_hi (and + // Q_hi + 1 does not overflow). + + Q += std::uint64_t{1} << (64u - 32u - 1u); // round, ties up + + const std::uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32u); + + return {h, x.e + y.e + 64}; + } + + /*! + @brief normalize x such that the significand is >= 2^(q-1) + @pre x.f != 0 + */ + static diyfp normalize(diyfp x) noexcept { + + while ((x.f >> 63u) == 0) { + x.f <<= 1u; + x.e--; + } + + return x; + } + + /*! + @brief normalize x such that the result has the exponent E + @pre e >= x.e and the upper e - x.e bits of x.f must be zero. + */ + static diyfp normalize_to(const diyfp &x, + const int target_exponent) noexcept { + const int delta = x.e - target_exponent; + + return {x.f << delta, target_exponent}; + } +}; + +struct boundaries { + diyfp w; + diyfp minus; + diyfp plus; +}; + +/*! +Compute the (normalized) diyfp representing the input number 'value' and its +boundaries. +@pre value must be finite and positive +*/ +template boundaries compute_boundaries(FloatType value) { + + // Convert the IEEE representation into a diyfp. + // + // If v is denormal: + // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) + // If v is normalized: + // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) + + static_assert(std::numeric_limits::is_iec559, + "internal error: dtoa_short requires an IEEE-754 " + "floating-point implementation"); + + constexpr int kPrecision = + std::numeric_limits::digits; // = p (includes the hidden bit) + constexpr int kBias = + std::numeric_limits::max_exponent - 1 + (kPrecision - 1); + constexpr int kMinExp = 1 - kBias; + constexpr std::uint64_t kHiddenBit = std::uint64_t{1} + << (kPrecision - 1); // = 2^(p-1) + + using bits_type = typename std::conditional::type; + + const std::uint64_t bits = reinterpret_bits(value); + const std::uint64_t E = bits >> (kPrecision - 1); + const std::uint64_t F = bits & (kHiddenBit - 1); + + const bool is_denormal = E == 0; + const diyfp v = is_denormal + ? diyfp(F, kMinExp) + : diyfp(F + kHiddenBit, static_cast(E) - kBias); + + // Compute the boundaries m- and m+ of the floating-point value + // v = f * 2^e. + // + // Determine v- and v+, the floating-point predecessor and successor if v, + // respectively. + // + // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) + // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) + // + // v+ = v + 2^e + // + // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ + // between m- and m+ round to v, regardless of how the input rounding + // algorithm breaks ties. + // + // ---+-------------+-------------+-------------+-------------+--- (A) + // v- m- v m+ v+ + // + // -----------------+------+------+-------------+-------------+--- (B) + // v- m- v m+ v+ + + const bool lower_boundary_is_closer = F == 0 && E > 1; + const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); + const diyfp m_minus = lower_boundary_is_closer + ? diyfp(4 * v.f - 1, v.e - 2) // (B) + : diyfp(2 * v.f - 1, v.e - 1); // (A) + + // Determine the normalized w+ = m+. + const diyfp w_plus = diyfp::normalize(m_plus); + + // Determine w- = m- such that e_(w-) = e_(w+). + const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); + + return {diyfp::normalize(v), w_minus, w_plus}; +} + +// Given normalized diyfp w, Grisu needs to find a (normalized) cached +// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies +// within a certain range [alpha, gamma] (Definition 3.2 from [1]) +// +// alpha <= e = e_c + e_w + q <= gamma +// +// or +// +// f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q +// <= f_c * f_w * 2^gamma +// +// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies +// +// 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma +// +// or +// +// 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) +// +// The choice of (alpha,gamma) determines the size of the table and the form of +// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well +// in practice: +// +// The idea is to cut the number c * w = f * 2^e into two parts, which can be +// processed independently: An integral part p1, and a fractional part p2: +// +// f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e +// = (f div 2^-e) + (f mod 2^-e) * 2^e +// = p1 + p2 * 2^e +// +// The conversion of p1 into decimal form requires a series of divisions and +// modulos by (a power of) 10. These operations are faster for 32-bit than for +// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be +// achieved by choosing +// +// -e >= 32 or e <= -32 := gamma +// +// In order to convert the fractional part +// +// p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... +// +// into decimal form, the fraction is repeatedly multiplied by 10 and the digits +// d[-i] are extracted in order: +// +// (10 * p2) div 2^-e = d[-1] +// (10 * p2) mod 2^-e = d[-2] / 10^1 + ... +// +// The multiplication by 10 must not overflow. It is sufficient to choose +// +// 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. +// +// Since p2 = f mod 2^-e < 2^-e, +// +// -e <= 60 or e >= -60 := alpha + +constexpr int kAlpha = -60; +constexpr int kGamma = -32; + +struct cached_power // c = f * 2^e ~= 10^k +{ + std::uint64_t f; + int e; + int k; +}; + +/*! +For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached +power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c +satisfies (Definition 3.2 from [1]) + alpha <= e_c + e + q <= gamma. +*/ +inline cached_power get_cached_power_for_binary_exponent(int e) { + // Now + // + // alpha <= e_c + e + q <= gamma (1) + // ==> f_c * 2^alpha <= c * 2^e * 2^q + // + // and since the c's are normalized, 2^(q-1) <= f_c, + // + // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) + // ==> 2^(alpha - e - 1) <= c + // + // If c were an exact power of ten, i.e. c = 10^k, one may determine k as + // + // k = ceil( log_10( 2^(alpha - e - 1) ) ) + // = ceil( (alpha - e - 1) * log_10(2) ) + // + // From the paper: + // "In theory the result of the procedure could be wrong since c is rounded, + // and the computation itself is approximated [...]. In practice, however, + // this simple function is sufficient." + // + // For IEEE double precision floating-point numbers converted into + // normalized diyfp's w = f * 2^e, with q = 64, + // + // e >= -1022 (min IEEE exponent) + // -52 (p - 1) + // -52 (p - 1, possibly normalize denormal IEEE numbers) + // -11 (normalize the diyfp) + // = -1137 + // + // and + // + // e <= +1023 (max IEEE exponent) + // -52 (p - 1) + // -11 (normalize the diyfp) + // = 960 + // + // This binary exponent range [-1137,960] results in a decimal exponent + // range [-307,324]. One does not need to store a cached power for each + // k in this range. For each such k it suffices to find a cached power + // such that the exponent of the product lies in [alpha,gamma]. + // This implies that the difference of the decimal exponents of adjacent + // table entries must be less than or equal to + // + // floor( (gamma - alpha) * log_10(2) ) = 8. + // + // (A smaller distance gamma-alpha would require a larger table.) + + // NB: + // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. + + constexpr int kCachedPowersMinDecExp = -300; + constexpr int kCachedPowersDecStep = 8; + + static constexpr std::array kCachedPowers = {{ + {0xAB70FE17C79AC6CA, -1060, -300}, {0xFF77B1FCBEBCDC4F, -1034, -292}, + {0xBE5691EF416BD60C, -1007, -284}, {0x8DD01FAD907FFC3C, -980, -276}, + {0xD3515C2831559A83, -954, -268}, {0x9D71AC8FADA6C9B5, -927, -260}, + {0xEA9C227723EE8BCB, -901, -252}, {0xAECC49914078536D, -874, -244}, + {0x823C12795DB6CE57, -847, -236}, {0xC21094364DFB5637, -821, -228}, + {0x9096EA6F3848984F, -794, -220}, {0xD77485CB25823AC7, -768, -212}, + {0xA086CFCD97BF97F4, -741, -204}, {0xEF340A98172AACE5, -715, -196}, + {0xB23867FB2A35B28E, -688, -188}, {0x84C8D4DFD2C63F3B, -661, -180}, + {0xC5DD44271AD3CDBA, -635, -172}, {0x936B9FCEBB25C996, -608, -164}, + {0xDBAC6C247D62A584, -582, -156}, {0xA3AB66580D5FDAF6, -555, -148}, + {0xF3E2F893DEC3F126, -529, -140}, {0xB5B5ADA8AAFF80B8, -502, -132}, + {0x87625F056C7C4A8B, -475, -124}, {0xC9BCFF6034C13053, -449, -116}, + {0x964E858C91BA2655, -422, -108}, {0xDFF9772470297EBD, -396, -100}, + {0xA6DFBD9FB8E5B88F, -369, -92}, {0xF8A95FCF88747D94, -343, -84}, + {0xB94470938FA89BCF, -316, -76}, {0x8A08F0F8BF0F156B, -289, -68}, + {0xCDB02555653131B6, -263, -60}, {0x993FE2C6D07B7FAC, -236, -52}, + {0xE45C10C42A2B3B06, -210, -44}, {0xAA242499697392D3, -183, -36}, + {0xFD87B5F28300CA0E, -157, -28}, {0xBCE5086492111AEB, -130, -20}, + {0x8CBCCC096F5088CC, -103, -12}, {0xD1B71758E219652C, -77, -4}, + {0x9C40000000000000, -50, 4}, {0xE8D4A51000000000, -24, 12}, + {0xAD78EBC5AC620000, 3, 20}, {0x813F3978F8940984, 30, 28}, + {0xC097CE7BC90715B3, 56, 36}, {0x8F7E32CE7BEA5C70, 83, 44}, + {0xD5D238A4ABE98068, 109, 52}, {0x9F4F2726179A2245, 136, 60}, + {0xED63A231D4C4FB27, 162, 68}, {0xB0DE65388CC8ADA8, 189, 76}, + {0x83C7088E1AAB65DB, 216, 84}, {0xC45D1DF942711D9A, 242, 92}, + {0x924D692CA61BE758, 269, 100}, {0xDA01EE641A708DEA, 295, 108}, + {0xA26DA3999AEF774A, 322, 116}, {0xF209787BB47D6B85, 348, 124}, + {0xB454E4A179DD1877, 375, 132}, {0x865B86925B9BC5C2, 402, 140}, + {0xC83553C5C8965D3D, 428, 148}, {0x952AB45CFA97A0B3, 455, 156}, + {0xDE469FBD99A05FE3, 481, 164}, {0xA59BC234DB398C25, 508, 172}, + {0xF6C69A72A3989F5C, 534, 180}, {0xB7DCBF5354E9BECE, 561, 188}, + {0x88FCF317F22241E2, 588, 196}, {0xCC20CE9BD35C78A5, 614, 204}, + {0x98165AF37B2153DF, 641, 212}, {0xE2A0B5DC971F303A, 667, 220}, + {0xA8D9D1535CE3B396, 694, 228}, {0xFB9B7CD9A4A7443C, 720, 236}, + {0xBB764C4CA7A44410, 747, 244}, {0x8BAB8EEFB6409C1A, 774, 252}, + {0xD01FEF10A657842C, 800, 260}, {0x9B10A4E5E9913129, 827, 268}, + {0xE7109BFBA19C0C9D, 853, 276}, {0xAC2820D9623BF429, 880, 284}, + {0x80444B5E7AA7CF85, 907, 292}, {0xBF21E44003ACDD2D, 933, 300}, + {0x8E679C2F5E44FF8F, 960, 308}, {0xD433179D9C8CB841, 986, 316}, + {0x9E19DB92B4E31BA9, 1013, 324}, + }}; + + // This computation gives exactly the same results for k as + // k = ceil((kAlpha - e - 1) * 0.30102999566398114) + // for |e| <= 1500, but doesn't require floating-point operations. + // NB: log_10(2) ~= 78913 / 2^18 + const int f = kAlpha - e - 1; + const int k = (f * 78913) / (1 << 18) + static_cast(f > 0); + + const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / + kCachedPowersDecStep; + + const cached_power cached = kCachedPowers[static_cast(index)]; + + return cached; +} + +/*! +For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. +For n == 0, returns 1 and sets pow10 := 1. +*/ +inline int find_largest_pow10(const std::uint32_t n, std::uint32_t &pow10) { + // LCOV_EXCL_START + if (n >= 1000000000) { + pow10 = 1000000000; + return 10; + } + // LCOV_EXCL_STOP + else if (n >= 100000000) { + pow10 = 100000000; + return 9; + } else if (n >= 10000000) { + pow10 = 10000000; + return 8; + } else if (n >= 1000000) { + pow10 = 1000000; + return 7; + } else if (n >= 100000) { + pow10 = 100000; + return 6; + } else if (n >= 10000) { + pow10 = 10000; + return 5; + } else if (n >= 1000) { + pow10 = 1000; + return 4; + } else if (n >= 100) { + pow10 = 100; + return 3; + } else if (n >= 10) { + pow10 = 10; + return 2; + } else { + pow10 = 1; + return 1; + } +} + +inline void grisu2_round(char *buf, int len, std::uint64_t dist, + std::uint64_t delta, std::uint64_t rest, + std::uint64_t ten_k) { + + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // ten_k + // <------> + // <---- rest ----> + // --------------[------------------+----+--------------]-------------- + // w V + // = buf * 10^k + // + // ten_k represents a unit-in-the-last-place in the decimal representation + // stored in buf. + // Decrement buf by ten_k while this takes buf closer to w. + + // The tests are written in this order to avoid overflow in unsigned + // integer arithmetic. + + while (rest < dist && delta - rest >= ten_k && + (rest + ten_k < dist || dist - rest > rest + ten_k - dist)) { + buf[len - 1]--; + rest += ten_k; + } +} + +/*! +Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. +M- and M+ must be normalized and share the same exponent -60 <= e <= -32. +*/ +inline void grisu2_digit_gen(char *buffer, int &length, int &decimal_exponent, + diyfp M_minus, diyfp w, diyfp M_plus) { + static_assert(kAlpha >= -60, "internal error"); + static_assert(kGamma <= -32, "internal error"); + + // Generates the digits (and the exponent) of a decimal floating-point + // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's + // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= + // gamma. + // + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // Grisu2 generates the digits of M+ from left to right and stops as soon as + // V is in [M-,M+]. + + std::uint64_t delta = + diyfp::sub(M_plus, M_minus) + .f; // (significand of (M+ - M-), implicit exponent is e) + std::uint64_t dist = + diyfp::sub(M_plus, w) + .f; // (significand of (M+ - w ), implicit exponent is e) + + // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): + // + // M+ = f * 2^e + // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e + // = ((p1 ) * 2^-e + (p2 )) * 2^e + // = p1 + p2 * 2^e + + const diyfp one(std::uint64_t{1} << -M_plus.e, M_plus.e); + + auto p1 = static_cast( + M_plus.f >> + -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) + std::uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e + + // 1) + // + // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] + + std::uint32_t pow10; + const int k = find_largest_pow10(p1, pow10); + + // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) + // + // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) + // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) + // + // M+ = p1 + p2 * 2^e + // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e + // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e + // = d[k-1] * 10^(k-1) + ( rest) * 2^e + // + // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) + // + // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] + // + // but stop as soon as + // + // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e + + int n = k; + while (n > 0) { + // Invariants: + // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) + // pow10 = 10^(n-1) <= p1 < 10^n + // + const std::uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) + const std::uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) + // + // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e + // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) + // + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) + // + p1 = r; + n--; + // + // M+ = buffer * 10^n + (p1 + p2 * 2^e) + // pow10 = 10^n + // + + // Now check if enough digits have been generated. + // Compute + // + // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e + // + // Note: + // Since rest and delta share the same exponent e, it suffices to + // compare the significands. + const std::uint64_t rest = (std::uint64_t{p1} << -one.e) + p2; + if (rest <= delta) { + // V = buffer * 10^n, with M- <= V <= M+. + + decimal_exponent += n; + + // We may now just stop. But instead look if the buffer could be + // decremented to bring V closer to w. + // + // pow10 = 10^n is now 1 ulp in the decimal representation V. + // The rounding procedure works with diyfp's with an implicit + // exponent of e. + // + // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e + // + const std::uint64_t ten_n = std::uint64_t{pow10} << -one.e; + grisu2_round(buffer, length, dist, delta, rest, ten_n); + + return; + } + + pow10 /= 10; + // + // pow10 = 10^(n-1) <= p1 < 10^n + // Invariants restored. + } + + // 2) + // + // The digits of the integral part have been generated: + // + // M+ = d[k-1]...d[1]d[0] + p2 * 2^e + // = buffer + p2 * 2^e + // + // Now generate the digits of the fractional part p2 * 2^e. + // + // Note: + // No decimal point is generated: the exponent is adjusted instead. + // + // p2 actually represents the fraction + // + // p2 * 2^e + // = p2 / 2^-e + // = d[-1] / 10^1 + d[-2] / 10^2 + ... + // + // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) + // + // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m + // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) + // + // using + // + // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) + // = ( d) * 2^-e + ( r) + // + // or + // 10^m * p2 * 2^e = d + r * 2^e + // + // i.e. + // + // M+ = buffer + p2 * 2^e + // = buffer + 10^-m * (d + r * 2^e) + // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e + // + // and stop as soon as 10^-m * r * 2^e <= delta * 2^e + + int m = 0; + for (;;) { + // Invariant: + // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) + // * 2^e + // = buffer * 10^-m + 10^-m * (p2 ) + // * 2^e = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e = + // buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + + // (10*p2 mod 2^-e)) * 2^e + // + p2 *= 10; + const std::uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e + const std::uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e + // + // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) + // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + p2 = r; + m++; + // + // M+ = buffer * 10^-m + 10^-m * p2 * 2^e + // Invariant restored. + + // Check if enough digits have been generated. + // + // 10^-m * p2 * 2^e <= delta * 2^e + // p2 * 2^e <= 10^m * delta * 2^e + // p2 <= 10^m * delta + delta *= 10; + dist *= 10; + if (p2 <= delta) { + break; + } + } + + // V = buffer * 10^-m, with M- <= V <= M+. + + decimal_exponent -= m; + + // 1 ulp in the decimal representation is now 10^-m. + // Since delta and dist are now scaled by 10^m, we need to do the + // same with ulp in order to keep the units in sync. + // + // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e + // + const std::uint64_t ten_m = one.f; + grisu2_round(buffer, length, dist, delta, p2, ten_m); + + // By construction this algorithm generates the shortest possible decimal + // number (Loitsch, Theorem 6.2) which rounds back to w. + // For an input number of precision p, at least + // + // N = 1 + ceil(p * log_10(2)) + // + // decimal digits are sufficient to identify all binary floating-point + // numbers (Matula, "In-and-Out conversions"). + // This implies that the algorithm does not produce more than N decimal + // digits. + // + // N = 17 for p = 53 (IEEE double precision) + // N = 9 for p = 24 (IEEE single precision) +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +inline void grisu2(char *buf, int &len, int &decimal_exponent, diyfp m_minus, + diyfp v, diyfp m_plus) { + + // --------(-----------------------+-----------------------)-------- (A) + // m- v m+ + // + // --------------------(-----------+-----------------------)-------- (B) + // m- v m+ + // + // First scale v (and m- and m+) such that the exponent is in the range + // [alpha, gamma]. + + const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); + + const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k + + // The exponent of the products is = v.e + c_minus_k.e + q and is in the range + // [alpha,gamma] + const diyfp w = diyfp::mul(v, c_minus_k); + const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); + const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); + + // ----(---+---)---------------(---+---)---------------(---+---)---- + // w- w w+ + // = c*m- = c*v = c*m+ + // + // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and + // w+ are now off by a small amount. + // In fact: + // + // w - v * 10^k < 1 ulp + // + // To account for this inaccuracy, add resp. subtract 1 ulp. + // + // --------+---[---------------(---+---)---------------]---+-------- + // w- M- w M+ w+ + // + // Now any number in [M-, M+] (bounds included) will round to w when input, + // regardless of how the input rounding algorithm breaks ties. + // + // And digit_gen generates the shortest possible such number in [M-, M+]. + // Note that this does not mean that Grisu2 always generates the shortest + // possible number in the interval (m-, m+). + const diyfp M_minus(w_minus.f + 1, w_minus.e); + const diyfp M_plus(w_plus.f - 1, w_plus.e); + + decimal_exponent = -cached.k; // = -(-k) = k + + grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +template +void grisu2(char *buf, int &len, int &decimal_exponent, FloatType value) { + static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, + "internal error: not enough precision"); + + // If the neighbors (and boundaries) of 'value' are always computed for + // double-precision numbers, all float's can be recovered using strtod (and + // strtof). However, the resulting decimal representations are not exactly + // "short". + // + // The documentation for 'std::to_chars' + // (https://en.cppreference.com/w/cpp/utility/to_chars) says "value is + // converted to a string as if by std::sprintf in the default ("C") locale" + // and since sprintf promotes float's to double's, I think this is exactly + // what 'std::to_chars' does. On the other hand, the documentation for + // 'std::to_chars' requires that "parsing the representation using the + // corresponding std::from_chars function recovers value exactly". That + // indicates that single precision floating-point numbers should be recovered + // using 'std::strtof'. + // + // NB: If the neighbors are computed for single-precision numbers, there is a + // single float + // (7.0385307e-26f) which can't be recovered using strtod. The resulting + // double precision value is off by 1 ulp. +#if 0 + const boundaries w = compute_boundaries(static_cast(value)); +#else + const boundaries w = compute_boundaries(value); +#endif + + grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); +} + +/*! +@brief appends a decimal representation of e to buf +@return a pointer to the element following the exponent. +@pre -1000 < e < 1000 +*/ +inline char *append_exponent(char *buf, int e) { + + if (e < 0) { + e = -e; + *buf++ = '-'; + } else { + *buf++ = '+'; + } + + auto k = static_cast(e); + if (k < 10) { + // Always print at least two digits in the exponent. + // This is for compatibility with printf("%g"). + *buf++ = '0'; + *buf++ = static_cast('0' + k); + } else if (k < 100) { + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } else { + *buf++ = static_cast('0' + k / 100); + k %= 100; + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + + return buf; +} + +/*! +@brief prettify v = buf * 10^decimal_exponent +If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point +notation. Otherwise it will be printed in exponential notation. +@pre min_exp < 0 +@pre max_exp > 0 +*/ +inline char *format_buffer(char *buf, int len, int decimal_exponent, + int min_exp, int max_exp) { + + const int k = len; + const int n = len + decimal_exponent; + + // v = buf * 10^(n-k) + // k is the length of the buffer (number of decimal digits) + // n is the position of the decimal point relative to the start of the buffer. + + if (k <= n && n <= max_exp) { + // digits[000] + // len <= max_exp + 2 + + std::memset(buf + k, '0', static_cast(n) - static_cast(k)); + // Make it look like a floating-point number (#362, #378) + buf[n + 0] = '.'; + buf[n + 1] = '0'; + return buf + (static_cast(n)) + 2; + } + + if (0 < n && n <= max_exp) { + // dig.its + // len <= max_digits10 + 1 + std::memmove(buf + (static_cast(n) + 1), buf + n, + static_cast(k) - static_cast(n)); + buf[n] = '.'; + return buf + (static_cast(k) + 1U); + } + + if (min_exp < n && n <= 0) { + // 0.[000]digits + // len <= 2 + (-min_exp - 1) + max_digits10 + + std::memmove(buf + (2 + static_cast(-n)), buf, + static_cast(k)); + buf[0] = '0'; + buf[1] = '.'; + std::memset(buf + 2, '0', static_cast(-n)); + return buf + (2U + static_cast(-n) + static_cast(k)); + } + + if (k == 1) { + // dE+123 + // len <= 1 + 5 + + buf += 1; + } else { + // d.igitsE+123 + // len <= max_digits10 + 1 + 5 + + std::memmove(buf + 2, buf + 1, static_cast(k) - 1); + buf[1] = '.'; + buf += 1 + static_cast(k); + } + + *buf++ = 'e'; + return append_exponent(buf, n - 1); +} + +} // namespace dtoa_impl + +/*! +The format of the resulting decimal representation is similar to printf's %g +format. Returns an iterator pointing past-the-end of the decimal representation. +@note The input number must be finite, i.e. NaN's and Inf's are not supported. +@note The buffer must be large enough. +@note The result is NOT null-terminated. +*/ +char *to_chars(char *first, const char *last, double value) { + static_cast(last); // maybe unused - fix warning + bool negative = std::signbit(value); + if (negative) { + value = -value; + *first++ = '-'; + } + + if (value == 0) // +-0 + { + *first++ = '0'; + // Make it look like a floating-point number (#362, #378) + *first++ = '.'; + *first++ = '0'; + return first; + } + // Compute v = buffer * 10^decimal_exponent. + // The decimal digits are stored in the buffer, which needs to be interpreted + // as an unsigned decimal integer. + // len is the length of the buffer, i.e. the number of decimal digits. + int len = 0; + int decimal_exponent = 0; + dtoa_impl::grisu2(first, len, decimal_exponent, value); + // Format the buffer like printf("%.*g", prec, value) + constexpr int kMinExp = -4; + constexpr int kMaxExp = std::numeric_limits::digits10; + + return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, + kMaxExp); +} +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_SRC_TO_CHARS_CPP \ No newline at end of file diff --git a/contrib/libs/simdjson/src/westmere.cpp b/contrib/libs/simdjson/src/westmere.cpp new file mode 100644 index 000000000000..b38c40bb73f9 --- /dev/null +++ b/contrib/libs/simdjson/src/westmere.cpp @@ -0,0 +1,175 @@ +#ifndef SIMDJSON_SRC_WESTMERE_CPP +#define SIMDJSON_SRC_WESTMERE_CPP + +#ifndef SIMDJSON_CONDITIONAL_INCLUDE +#include +#endif // SIMDJSON_CONDITIONAL_INCLUDE + +#include +#include + +#include +#include +#include +#include + +// +// Stage 1 +// + +namespace simdjson { +namespace westmere { + +simdjson_warn_unused error_code implementation::create_dom_parser_implementation( + size_t capacity, + size_t max_depth, + std::unique_ptr& dst +) const noexcept { + dst.reset( new (std::nothrow) dom_parser_implementation() ); + if (!dst) { return MEMALLOC; } + if (auto err = dst->set_capacity(capacity)) + return err; + if (auto err = dst->set_max_depth(max_depth)) + return err; + return SUCCESS; +} + +namespace { + +using namespace simd; + +simdjson_inline json_character_block json_character_block::classify(const simd::simd8x64& in) { + // These lookups rely on the fact that anything < 127 will match the lower 4 bits, which is why + // we can't use the generic lookup_16. + auto whitespace_table = simd8::repeat_16(' ', 100, 100, 100, 17, 100, 113, 2, 100, '\t', '\n', 112, 100, '\r', 100, 100); + + // The 6 operators (:,[]{}) have these values: + // + // , 2C + // : 3A + // [ 5B + // { 7B + // ] 5D + // } 7D + // + // If you use | 0x20 to turn [ and ] into { and }, the lower 4 bits of each character is unique. + // We exploit this, using a simd 4-bit lookup to tell us which character match against, and then + // match it (against | 0x20). + // + // To prevent recognizing other characters, everything else gets compared with 0, which cannot + // match due to the | 0x20. + // + // NOTE: Due to the | 0x20, this ALSO treats and (control characters 0C and 1A) like , + // and :. This gets caught in stage 2, which checks the actual character to ensure the right + // operators are in the right places. + const auto op_table = simd8::repeat_16( + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, ':', '{', // : = 3A, [ = 5B, { = 7B + ',', '}', 0, 0 // , = 2C, ] = 5D, } = 7D + ); + + // We compute whitespace and op separately. If the code later only use one or the + // other, given the fact that all functions are aggressively inlined, we can + // hope that useless computations will be omitted. This is namely case when + // minifying (we only need whitespace). + + + const uint64_t whitespace = in.eq({ + _mm_shuffle_epi8(whitespace_table, in.chunks[0]), + _mm_shuffle_epi8(whitespace_table, in.chunks[1]), + _mm_shuffle_epi8(whitespace_table, in.chunks[2]), + _mm_shuffle_epi8(whitespace_table, in.chunks[3]) + }); + // Turn [ and ] into { and } + const simd8x64 curlified{ + in.chunks[0] | 0x20, + in.chunks[1] | 0x20, + in.chunks[2] | 0x20, + in.chunks[3] | 0x20 + }; + const uint64_t op = curlified.eq({ + _mm_shuffle_epi8(op_table, in.chunks[0]), + _mm_shuffle_epi8(op_table, in.chunks[1]), + _mm_shuffle_epi8(op_table, in.chunks[2]), + _mm_shuffle_epi8(op_table, in.chunks[3]) + }); + return { whitespace, op }; +} + +simdjson_inline bool is_ascii(const simd8x64& input) { + return input.reduce_or().is_ascii(); +} + +simdjson_unused simdjson_inline simd8 must_be_continuation(const simd8 prev1, const simd8 prev2, const simd8 prev3) { + simd8 is_second_byte = prev1.saturating_sub(0xc0u-1); // Only 11______ will be > 0 + simd8 is_third_byte = prev2.saturating_sub(0xe0u-1); // Only 111_____ will be > 0 + simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-1); // Only 1111____ will be > 0 + // Caller requires a bool (all 1's). All values resulting from the subtraction will be <= 64, so signed comparison is fine. + return simd8(is_second_byte | is_third_byte | is_fourth_byte) > int8_t(0); +} + +simdjson_inline simd8 must_be_2_3_continuation(const simd8 prev2, const simd8 prev3) { + simd8 is_third_byte = prev2.saturating_sub(0xe0u-0x80); // Only 111_____ will be >= 0x80 + simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-0x80); // Only 1111____ will be >= 0x80 + return is_third_byte | is_fourth_byte; +} + +} // unnamed namespace +} // namespace westmere +} // namespace simdjson + +// +// Stage 2 +// + +// +// Implementation-specific overrides +// + +namespace simdjson { +namespace westmere { + +simdjson_warn_unused error_code implementation::minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept { + return westmere::stage1::json_minifier::minify<64>(buf, len, dst, dst_len); +} + +simdjson_warn_unused error_code dom_parser_implementation::stage1(const uint8_t *_buf, size_t _len, stage1_mode streaming) noexcept { + this->buf = _buf; + this->len = _len; + return westmere::stage1::json_structural_indexer::index<64>(_buf, _len, *this, streaming); +} + +simdjson_warn_unused bool implementation::validate_utf8(const char *buf, size_t len) const noexcept { + return westmere::stage1::generic_validate_utf8(buf,len); +} + +simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document &_doc) noexcept { + return stage2::tape_builder::parse_document(*this, _doc); +} + +simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { + return stage2::tape_builder::parse_document(*this, _doc); +} + +SIMDJSON_NO_SANITIZE_MEMORY +simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst, bool replacement_char) const noexcept { + return westmere::stringparsing::parse_string(src, dst, replacement_char); +} + +simdjson_warn_unused uint8_t *dom_parser_implementation::parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept { + return westmere::stringparsing::parse_wobbly_string(src, dst); +} + +simdjson_warn_unused error_code dom_parser_implementation::parse(const uint8_t *_buf, size_t _len, dom::document &_doc) noexcept { + auto error = stage1(_buf, _len, stage1_mode::regular); + if (error) { return error; } + return stage2(_doc); +} + +} // namespace westmere +} // namespace simdjson + +#include + +#endif // SIMDJSON_SRC_WESTMERE_CPP \ No newline at end of file diff --git a/contrib/libs/simdjson/ya.make b/contrib/libs/simdjson/ya.make new file mode 100644 index 000000000000..b9fc6efde892 --- /dev/null +++ b/contrib/libs/simdjson/ya.make @@ -0,0 +1,35 @@ +# Generated by devtools/yamaker from nixpkgs 22.11. + +LIBRARY() + +LICENSE( + Apache-2.0 AND + BSD-3-Clause AND + MIT +) + +LICENSE_TEXTS(.yandex_meta/licenses.list.txt) + +VERSION(3.11.3) + +ORIGINAL_SOURCE(https://github.com/simdjson/simdjson/archive/v3.11.3.tar.gz) + +ADDINCL( + GLOBAL contrib/libs/simdjson/include + contrib/libs/simdjson/src +) + +NO_COMPILER_WARNINGS() + +NO_UTIL() + +CFLAGS( + -DSIMDJSON_AVX512_ALLOWED=1 + -DSIMDJSON_UTF8VALIDATION=1 +) + +SRCS( + src/simdjson.cpp +) + +END() diff --git a/to_integrate.txt b/to_integrate.txt new file mode 100644 index 000000000000..b1a977681f10 --- /dev/null +++ b/to_integrate.txt @@ -0,0 +1,227 @@ +prerequesits + ac92ce05cf8 azevaykin Tue Aug 27 14:52:18 2024 +0300 Datashard: DISK_SPACE_EXHAUSTED error (#8318) take only definititions and processing in kqp(ignore changes in datashards) + + +(+0) e3c9723c3e6 zverevgeny Sun Sep 15 09:20:22 2024 +0300 Turn on GCCountersNormalizer (#8166) +ignore e6d795feedd Nikita Vasilev Mon Sep 16 12:51:05 2024 +0300 HTAP: ProposeTx + EvWrite (#9236) +ignore 0ac99454709 Nikita Vasilev Mon Sep 16 15:01:42 2024 +0300 WriteActror settings (#9251) +ignore c9838ae91b7 Nikita Vasilev Mon Sep 16 16:58:00 2024 +0300 Fix CopyToChunked for empty batch (#9288) +(+!-)ignore 065294c2926 ivanmorozov333 Mon Sep 16 18:52:27 2024 +0300 signals, optimizations, logging, inserted data expiration (#9200) +(+0) a37a4ae7858 ivanmorozov333 Tue Sep 17 06:53:30 2024 +0300 fix portions cleaning in case table removed (#9325) +(+0) 5163b361b92 ivanmorozov333 Tue Sep 17 06:53:48 2024 +0300 fix htap test for deletion (#9313) +good 2c34e9fc72d Artem Alekseev Tue Sep 17 15:02:53 2024 +0300 Fix loading session without cursor (#9246) +conflict due to #8913 bfd40303545 Vladislav Gogov Tue Sep 17 15:04:20 2024 +0300 Test "DisabledAlterCompression" moved in KqpOlapCompression (#9114) //confilct +(+0) 3a4de795f7a ivanmorozov333 Tue Sep 17 19:31:55 2024 +0300 Fix addressing sparsed (#9382) +(+!) d8ee31dd2a8 ivanmorozov333 Wed Sep 18 19:17:38 2024 +0300 Correct schemas adaptation (#9425) +ignore 6e3fc43bac3 Nikita Vasilev Thu Sep 19 11:38:03 2024 +0300 Fix sinks order (#9345) +ignore 8d954a54ce7 Nikita Vasilev Thu Sep 19 18:31:57 2024 +0300 More tests for CTAS (#9497) +(+0) 6c407dccfff Semyon Fri Sep 20 15:10:07 2024 +0300 Dynamic deadline for CS scan (#9520) +good 89871e41055 ivanmorozov333 Fri Sep 20 16:38:16 2024 +0300 reduce memory in schemas loading (#9548) +ignore 076a8013aab ivanmorozov333 Fri Sep 20 20:23:54 2024 +0300 immediate write for bulk upsert (#9489) +good b2b16e80e3d ivanmorozov333 Sun Sep 22 19:30:47 2024 +0300 speed up register blob idx (#9581) +(+0) 6862d3c0e87 ivanmorozov333 Mon Sep 23 12:09:29 2024 +0300 fix mvcc tests. use write id as row feature for conflicts resolving (#9598) +good c3abe603dc2 Vladislav Gogov Mon Sep 23 15:58:16 2024 +0300 Fix off compression (#9612) +good bacb7fa9f3f ivanmorozov333 Mon Sep 23 17:36:42 2024 +0300 TPortionInfo::GetRecordsCount speed up (#9614) +good 5424e5a4f24 ivanmorozov333 Mon Sep 23 17:38:30 2024 +0300 dont move non-actualized buckets in rating scale (#9628) +(+!) ad65a256a06 ivanmorozov333 Mon Sep 23 20:20:51 2024 +0300 unmute olap-kqp-mvcc tests (#9655) +good 1002b94516f ivanmorozov333 Mon Sep 23 21:18:47 2024 +0300 remove useless locks broking checker (#9650) +(+0) 25fdf52ca30 ivanmorozov333 Tue Sep 24 13:26:10 2024 +0300 clean trash on versions switching (#9679) +good after ac92ce05cf8 4b099673cf0 ivanmorozov333 Wed Sep 25 11:12:21 2024 +0300 EvWrite codes unification with kqp (#9698) +good 14936b2c152 ivanmorozov333 Wed Sep 25 13:12:10 2024 +0300 correct snapshot for immediate writing (#9711) +good 7d337c04258 ivanmorozov333 Wed Sep 25 13:37:48 2024 +0300 clean useless case for evwrite (#9716) +good 48dc88fd932 Vladislav Gogov Thu Sep 26 11:15:06 2024 +0300 Added test: Alter compression for ColumnTable in TableStore (#9781) +good 7d208c76420 Alexander Avdonkin Thu Sep 26 17:32:50 2024 +0300 Implemented schema versions normalizer (#9627) +good f4914ce13fd Artem Alekseev Fri Sep 27 11:48:50 2024 +0300 Fix partitioning for empty tables (#9372) +good 06c6c6616b5 ivanmorozov333 Sat Sep 28 12:15:00 2024 +0300 fix dedup normalizer (#9849) +good e8a4121de1a ivanmorozov333 Sun Sep 29 07:11:51 2024 +0300 accessors actualization (#9857) +good 5dc26211e73 Vladislav Gogov Mon Sep 30 13:38:58 2024 +0300 Added muted test "KqpOlapScheme::DropColumnAfterScan" (#9882) +good ce9f20dfbd0 Vladislav Gogov Tue Oct 1 09:44:22 2024 +0300 Added muted test "KqpOlapScheme.DropColumnAfterInsert". (#9898) +conflict b5341bdbae6 ivanmorozov333 Tue Oct 1 10:20:42 2024 +0300 Register pathes for insert table (#9881) +good dcff71f700d Vladislav Gogov Wed Oct 2 10:05:19 2024 +0300 Fix #9889 (#9941) +(+!-) 01c8e9fbf44 ivanmorozov333 Thu Oct 3 14:45:42 2024 +0300 fix unregister group race (#10020) +ignore 631b912fbf6 Nikita Vasilev Fri Oct 4 17:46:58 2024 +0300 Enable Olap settings (#10097) +(+0) 66e070c8de2 ivanmorozov333 Sat Oct 5 13:05:26 2024 +0300 fix allocation cleaning race from separated thread after scope cleani… (#10096) +(+0) bff272f3055 Alexander Avdonkin Mon Oct 7 11:23:31 2024 +0300 Added debug logging for table stats (#10065) +!conflict 7258b98f426 ivanmorozov333 Wed Oct 9 07:46:53 2024 +0300 dont use insert table totally (#10088) +good fc0c41da413 Alexander Avdonkin Wed Oct 9 10:15:14 2024 +0300 Remove unused table versions along with schema versions in TSchemaVer… (#10058) +(+0) 528a81b57aa ivanmorozov333 Wed Oct 9 13:28:02 2024 +0300 disable validation for useless columns (#10240) +(+0) 8599b38e70f ivanmorozov333 Thu Oct 10 16:38:36 2024 +0300 fix groups allocation cleaning (#10274) +good 4e677a5f94e ivanmorozov333 Fri Oct 11 09:43:08 2024 +0300 timeout for shard data writing (#10275) +(+!) c73f7603234 Nikita Vasilev Mon Oct 14 17:30:37 2024 +0300 Fix wrong columns order in sinks (#10338) +(+0) 881e57596cf Nikita Vasilev Mon Oct 14 17:30:46 2024 +0300 Sink metrics & trace (#10397) +good e2fabd1d2d7 ivanmorozov333 Mon Oct 14 19:21:39 2024 +0300 fix splitter condition to avoid split micro-chunks (#10375) +(+0) 0025e009a6b Nikita Vasilev Wed Oct 16 11:38:32 2024 +0300 Fix sink empty batch (#10463) +good f0a6e7faaca Alexander Avdonkin Fri Oct 18 10:16:33 2024 +0300 Fixed clearing table stats after alter operaion (#10509) +good copy simdjson 25311e4bb54 Semyon Mon Oct 21 19:52:06 2024 +0300 Use simdjson for binary json construction for improved performance (#10464) +conflict 7a1e6972dd5 Vladislav Gogov Tue Oct 22 18:37:21 2024 +0300 New field COMPRESSION_LEVEL in Column Family (#10645) +good 9f0b7908659 Vladislav Gogov Wed Oct 23 14:43:50 2024 +0300 AlterColumnTable (#10672) +conflict acb4df38516 ivanmorozov333 Wed Oct 23 22:08:29 2024 +0300 compaction speedup (#10323) +good c71ca85ebe4 ivanmorozov333 Thu Oct 24 13:09:27 2024 +0300 fix tx volume calculation for inserted chunks (#10813) +good ab7ff708af5 ivanmorozov333 Thu Oct 24 13:09:43 2024 +0300 chunks count for limit in compaction (#10812) +good fb357e75228 ivanmorozov333 Thu Oct 24 13:10:00 2024 +0300 correct limit for indexation (count of chunks) (#10811) +good e2339333d2b ivanmorozov333 Thu Oct 24 13:10:16 2024 +0300 fix tx writing limit (#10810) +good 0002ddc821d ivanmorozov333 Thu Oct 24 13:50:27 2024 +0300 fix error processing on program apply (#10814) +good 4d5a0f8995d Alexander Avdonkin Thu Oct 24 15:17:25 2024 +0300 Added counters for local db tables loading times (#10402) +good 2f985f5a2f3 ivanmorozov333 Thu Oct 24 15:26:46 2024 +0300 prefetch necessary tables before loading (#10809) +good 28781863c2c Nikita Vasilev Thu Oct 24 19:21:25 2024 +0300 Improve tx defer (#10507) +conflict 86223caebb4 Vladislav Gogov Fri Oct 25 13:21:46 2024 +0300 Fix syntax for Column Family (#10781) +good fe19569e521 ivanmorozov333 Sat Oct 26 12:01:39 2024 +0300 remove schema from table version (#10878) +conflict e2ee1b2b1a2 ivanmorozov333 Mon Oct 28 12:04:08 2024 +0300 Clean max scalar (#10826) +good 29fac514c60 ivanmorozov333 Mon Oct 28 12:04:37 2024 +0300 Diff schemas (#10958) +good a0b88cae430 Semyon Mon Oct 28 17:04:03 2024 +0300 fix abort on invalid null value parsing in BinaryJson (#10991) +good d4eaec4b216 ivanmorozov333 Mon Oct 28 17:19:06 2024 +0300 correct and speed up compaction (#10867) +good 0a192421342 Alexander Avdonkin Tue Oct 29 12:49:33 2024 +0300 Added time counter for local db precharge (#10883) +conflict 3c950416150 ivanmorozov333 Tue Oct 29 14:55:33 2024 +0300 Records usage cleaning (#10971) +good 08f5064df57 ivanmorozov333 Tue Oct 29 19:39:01 2024 +0300 data accessor has to own portion info (#11060) +good 0b573b95534 ivanmorozov333 Wed Oct 30 10:41:32 2024 +0300 Use portion data accessor (#11074) +good 5130589708f Semyon Wed Oct 30 14:21:05 2024 +0300 fix compilation of SerializeToBinaryJson on macos (#10783) +good b9d09c35e7a ivanmorozov333 Thu Oct 31 10:08:01 2024 +0300 precalculate storage ids for index info (#11127) +good 21fa904d586 ivanmorozov333 Thu Oct 31 10:09:01 2024 +0300 actualize local db for columnshards (#11115) +good f6eeebf7bae ivanmorozov333 Fri Nov 1 10:36:20 2024 +0300 column chunks v1 schema (#11161) +good 4949d9b69f4 ivanmorozov333 Fri Nov 1 12:23:43 2024 +0300 fix v1 chunks processing (#11180) +good ee3d56fec68 zverevgeny Tue Nov 5 10:25:36 2024 +0300 clarify ActualizationIndex ownership (#11247) +good cd279f592ae zverevgeny Tue Nov 5 10:25:50 2024 +0300 delete empty files (#11237) +ignore 129f0f11bcf Nikita Vasilev Tue Nov 5 11:18:30 2024 +0300 EvWrite: Immediate & Prepare (#10913) +ignore 6595c0e3c59 Nikita Vasilev Tue Nov 5 14:07:58 2024 +0300 Fix shard ranges sort (#11257) +conflict 27078514593 Artem Alekseev Tue Nov 5 19:47:15 2024 +0300 Transfer scheme history to new partitions (#9959) +conflict dbd5f9510e9 ivanmorozov333 Wed Nov 6 15:54:23 2024 +0300 Async fetch portion data access info (#11246) +good c6c245400ce7 ivanmorozov333 Wed Nov 6 16:05:19 2024 +0300 fix normalizers for v1 migration chunks (#11308) +ignore 5c69ec9f47b Nikita Vasilev Wed Nov 6 16:47:52 2024 +0300 Metrics for buffer actor (#11304) +conflict 9b3c2bd67ef Artem Alekseev Mon Nov 11 16:27:26 2024 +0300 Remove destination session after partitioning finish (#11411) +ignore 6c7ed0db696 Nikita Vasilev Mon Nov 11 17:02:51 2024 +0300 Evwrite optimizations (#11428) +good 7e9ca336292 ivanmorozov333 Tue Nov 12 07:19:48 2024 +0300 Split portion and chunks (#11386) +good cf0394ab6e1 ivanmorozov333 Tue Nov 12 11:10:15 2024 +0300 fix test and correct normalizer conditions (#11504) +ignore f1bf2161753 Nikita Vasilev Tue Nov 12 12:22:10 2024 +0300 EvWrite: add mvcc snapshot (#11474) +good d478158f5ac ivanmorozov333 Tue Nov 12 15:18:24 2024 +0300 dont scan unappropriate portions in tiering (#11509) +good d4c693ff9c4 ivanmorozov333 Tue Nov 12 20:38:16 2024 +0300 fix error on start internal scanner (#11289) +ignore 3c0891063fc Nikita Vasilev Wed Nov 13 11:21:21 2024 +0300 Revert "EvWrite: add mvcc snapshot" (#11534) +good 7d51c2b807c ivanmorozov333 Wed Nov 13 15:50:58 2024 +0300 native memory control (#11559) +ignore ee51155da39 Nikita Vasilev Wed Nov 13 16:08:37 2024 +0300 Don't recreate snapshots in transaction (#11553) +ignore 2ff87221c76 Nikita Vasilev Thu Nov 14 12:43:34 2024 +0300 Fix reads from many shards (#11569) +good f2824309ebb ivanmorozov333 Thu Nov 14 17:57:52 2024 +0300 fix compaction memory prediction for special case (#11565) +good 1c441a94c03 ivanmorozov333 Thu Nov 14 20:55:52 2024 +0300 fix normalization processing (#11583) +good 6bf263a831f ivanmorozov333 Fri Nov 15 17:41:34 2024 +0300 Repair portions and async proto parser (#11636) +good 3d691abfb09 Semyon Mon Nov 18 10:46:05 2024 +0300 parse TTL syntax with tiering on KQP (#11613) +ignore 564ee81a692 Nikita Vasilev Mon Nov 18 10:55:14 2024 +0300 Async transform for CTAS (#11656) +good e1522ed130d ivanmorozov333 Mon Nov 18 14:59:56 2024 +0300 fix filter usage for sequential assembling (#11687) +good 9aed6fee027 ivanmorozov333 Mon Nov 18 15:00:19 2024 +0300 Correct get schema validations (#11686) +good 0cdb00dde77 ivanmorozov333 Mon Nov 18 16:33:24 2024 +0300 move nullable control into data checker (#11685) +good 53b8d42522a Alexander Avdonkin Mon Nov 18 17:16:37 2024 +0300 Send datasize stats by channel along with total (#11675) +conflict dbd9c12331f Semyon Tue Nov 19 01:02:47 2024 +0300 prohibit creating secrets with duplicating names (#11680) +(+0) c5d16f6b6b5 ivanmorozov333 Tue Nov 19 12:04:18 2024 +0300 fix exception processing (#11728) +good + fix build 481ccadd73b ivanmorozov333 Tue Nov 19 12:58:30 2024 +0300 Leak bs normalizer (#11682) +good 3d63b20af24 ivanmorozov333 Tue Nov 19 14:40:22 2024 +0300 fix chunks reorder on loading (#11736) +ignore 5933d413e15 zverevgeny Tue Nov 19 18:13:48 2024 +0300 Enable Column tables by default (#10906) +good 8357e1ce980 ivanmorozov333 Wed Nov 20 11:34:10 2024 +0300 fix reading logic in case memory control (#11768) +good 1c150358435 ivanmorozov333 Wed Nov 20 17:02:24 2024 +0300 accessors memory control for scan (#11792) +good 872bb4d25b2 Artem Alekseev Wed Nov 20 20:05:26 2024 +0300 Add portion_id to log scope in TReadPortionInfoWithBlobs::RestoreBatch (#11771) +good 0d8f95e0eef Artem Alekseev Thu Nov 21 11:45:36 2024 +0300 Sensor for number of shards within single BulkUpsert data (#11786) +good 59fa9fb62c9 ivanmorozov333 Thu Nov 21 12:03:45 2024 +0300 Accessors memory limit on background (#11816) +conflict 45513a5b190 Vladislav Gogov Thu Nov 21 13:38:02 2024 +0300 Column Family for ColumnTable (#9657) +conflict 4743a95fdc5 yentsovsemyon Thu Nov 21 13:46:41 2024 +0300 Extend TTL syntax to support tiers +ignore a03cc3d9e14 Nikita Vasilev Thu Nov 21 15:13:05 2024 +0300 Stock bench for Olap shards (#11757) +good 25f884a6c35 ivanmorozov333 Thu Nov 21 17:15:03 2024 +0300 fix filter usage for partial reading (#11835) +good 36d42e52673 Semyon Fri Nov 22 14:08:46 2024 +0300 add subcodes to tx-proxy reply code counters (#11862) +good afbc266232a ivanmorozov333 Mon Nov 25 12:27:39 2024 +0300 validate dangerouse construction (#11873) +good 346aedca9e3 ivanmorozov333 Mon Nov 25 13:50:25 2024 +0300 fix empty variable usage (#11926) +good a9250345974 zverevgeny Mon Nov 25 20:34:19 2024 +0300 fix request shard count sensor (#11962) +good 6a1eb437900 Semyon Tue Nov 26 12:10:24 2024 +0300 dry-run mode for CS normalizers (#11934) +conflict 6ed5294048f ivanmorozov333 Tue Nov 26 13:40:30 2024 +0300 Simple reader (#11894) +good e92e2246a00 ivanmorozov333 Wed Nov 27 09:59:12 2024 +0300 blob writing error processing for portion-write-mode (#12029) +good bbdc254af86 ivanmorozov333 Wed Nov 27 12:39:17 2024 +0300 fix simple reading with accessors fetching (#12000) +good 423f42888a5 ivanmorozov333 Wed Nov 27 12:40:01 2024 +0300 additional coredumps info (#11960) +conflict 3d45056da57 Artem Alekseev Wed Nov 27 14:36:34 2024 +0300 Fix scenario tests launch (#12044) +good 721ecf31f80 ivanmorozov333 Wed Nov 27 18:33:10 2024 +0300 Fix hanging control (#12051) +good 225bab2892c Semyon Wed Nov 27 19:22:43 2024 +0300 add tiering info to TTL in public api (#11390) +good 4da928050f3 Alexander Avdonkin Thu Nov 28 12:08:43 2024 +0300 Write last record with the same key from batch (#12048) +ignore 776b371efb8 Nikita Vasilev Thu Nov 28 12:34:07 2024 +0300 Fix CompareRanges (#12043) +conflict eace3cb5b08 Semyon Thu Nov 28 20:04:08 2024 +0300 tests of ttl utility functions on SS (#12092) +good f58f7ce1eda Semyon Fri Nov 29 15:29:43 2024 +0300 fix drop column & reset ttl in one TX on CS (#12127) +ignore 0924e1c53b7 morozov1one Fri Nov 29 20:28:24 2024 +0300 Upgrade mimalloc to 1.8.7 +good b458331fb7c Semyon Mon Dec 2 12:08:42 2024 +0300 minor fixes of TTL in SDK (#12152) +good ad82e860154 ivanmorozov333 Mon Dec 2 12:26:04 2024 +0300 lock categories to control different lock-purposes (#12163) +ignore e6331e9672e Nikita Vasilev Mon Dec 2 14:40:14 2024 +0300 Test for olap ACL (#12202) +good 6fc18f89146 ivanmorozov333 Mon Dec 2 15:51:00 2024 +0300 Speed up SIMPLE scanner (#12164) +good 73759b64031 Semyon Tue Dec 3 11:17:59 2024 +0300 fix TTL initialization from DB on CS (#12210) +ignore 9a920b4c8ac Nikita Vasilev Tue Dec 3 13:36:29 2024 +0300 Improve WriteActor (#12167) +good d080a30dcb4 ivanmorozov333 Tue Dec 3 17:18:24 2024 +0300 writing enabled flag for ev write (#12250) +good 14a54ab1584 ivanmorozov333 Tue Dec 3 18:31:04 2024 +0300 fix cleanup volume limits and speed up versions index copy (#12249) +ignore 5e818ab38b6 Nikita Vasilev Wed Dec 4 16:08:47 2024 +0300 Oltp EvWrite fixes (#12279) +good 4f34c1a2f1a ivanmorozov333 Wed Dec 4 17:17:54 2024 +0300 compaction. lc levels configuration (#12273) +good d6a279e3d04 Innokentii Mokin Wed Dec 4 02:43:41 2024 +0300 Fix olap linkage (#12265) +conflict b2da93a4827 Semyon Wed Dec 4 18:38:02 2024 +0300 configure tiering on CS via ttl (#12095) +good ffa509314d7 ivanmorozov333 Thu Dec 5 11:52:49 2024 +0300 Snapshot livetime control (#12301) +conflict e1bc51a4b13 Alexander Avdonkin Thu Dec 5 15:58:56 2024 +0300 Optionally allow nullable pk in column tables (#12286) +good a8ab3a25348 ivanmorozov333 Thu Dec 5 16:18:10 2024 +0300 correct synchronization after tablet reboot (#12296) +conflict d099a43b2b0 zverevgeny Thu Dec 5 21:57:40 2024 +0300 EnableImmediateWritingOnBulkUpsert by default (#12272) +good 44331c22f77 ivanmorozov333 Thu Dec 5 23:53:43 2024 +0300 fix script construction (#12332) +good f18424fe906 Vladislav Gogov Fri Dec 6 13:16:05 2024 +0300 Scenario test for issue: #11186 (#12138) +good b86caa55e2e ivanmorozov333 Fri Dec 6 18:22:25 2024 +0300 fix normalizer checker (#12350) +good 7a2b05cfd33 ivanmorozov333 Fri Dec 6 18:29:52 2024 +0300 fix compaction levels constuction (#12351) +good 2570d1a5a89 ivanmorozov333 Mon Dec 9 08:51:06 2024 +0300 dont use removed portions before remove from local_db - prevent race … (#12383) +good 2020867a79b ivanmorozov333 Mon Dec 9 08:51:22 2024 +0300 split logging services through huge messages volume in summary logs (#12382) +good 13826814ca4 ivanmorozov333 Mon Dec 9 08:51:37 2024 +0300 fix compaction policy modification (#12384) +good ed0816f73ba ivanmorozov333 Mon Dec 9 12:50:02 2024 +0300 Reader iterator unification (#12387) +good 717a5a0d81a ivanmorozov333 Mon Dec 9 12:50:18 2024 +0300 dont use default columns for merger (#12380) +good 45bb60635e3 ivanmorozov333 Mon Dec 9 12:50:38 2024 +0300 alter columns for many columns in time (#12378) +conflict 3c142023477 ivanmorozov333 Mon Dec 9 17:21:39 2024 +0300 mute blinking hive test (#12417) +ignore 8eefc61bc44 Nikita Vasilev Mon Dec 9 18:50:48 2024 +0300 Allow data query for olap (#12404) +good 3f0791d084d ivanmorozov333 Mon Dec 9 19:26:14 2024 +0300 portions index simplification (#12414) +good 456154dba0e ivanmorozov333 Tue Dec 10 11:30:02 2024 +0300 scan policy sql control (#12400) +good 34cd26ccc9a Semyon Tue Dec 10 11:58:41 2024 +0300 register memory allocated for metadata in tiering actualizer (#12348) +ignore 80aedb4f97e Nikita Vasilev Tue Dec 10 18:16:58 2024 +0300 Fix olap reads in data query (#12463) +good 7037279ff0c ivanmorozov333 Wed Dec 11 16:44:53 2024 +0300 scan optimization for filter applying in case simple chunks (#12476) +good 16418df6e17 ivanmorozov333 Wed Dec 11 20:13:40 2024 +0300 commit processing fixes (#12519) +good 251a0223ea5 ivanmorozov333 Thu Dec 12 11:29:08 2024 +0300 v2 portions usage only available (#12530) +good cd3d76a1f39 ivanmorozov333 Thu Dec 12 12:38:48 2024 +0300 fix validation in case removed table indexation (#12541) +ignore ec5a01220b5 Nikita Vasilev Thu Dec 12 14:37:03 2024 +0300 Collect Sink Stats (#12507) +good 46a0f523ca7 zverevgeny Thu Dec 12 18:27:22 2024 +0300 check supported store types (#12568) +good 7a1a6828a38 Semyon Thu Dec 12 22:02:06 2024 +0300 optimize memory allocation for schema versions on init (#12533) +ignore a2ec9639d28 Nikita Vasilev Fri Dec 13 15:11:07 2024 +0300 Test for Read Only Snapshots (#11574) +good 118d425064e ivanmorozov333 Fri Dec 13 18:48:05 2024 +0300 fix address construction for abstract chunked array (#12614) +good db99e50a68d ivanmorozov333 Fri Dec 13 18:51:35 2024 +0300 fix allocations validator (#12616) +good b40adfb0d89 ivanmorozov333 Sat Dec 14 17:36:11 2024 +0300 fix allocations manager limits control (#12622) +good 0fddef15fd2 ivanmorozov333 Sun Dec 15 10:40:36 2024 +0300 dont reallocate memory after merge through incorrect memory consumpti… (#12623) +good b0b1457738b ivanmorozov333 Mon Dec 16 10:40:12 2024 +0300 fix states usage for accessors request filling (#12553) +conflict 93b71f162d5 zverevgeny Mon Dec 16 15:29:17 2024 +0300 Run olap scenario tests on local ydb cluster (#12512) +conflict!! FQ 01126aca327 Semyon Mon Dec 16 18:46:40 2024 +0300 implement secret identification by name (#12641) +good a64b1fd27c7 ivanmorozov333 Mon Dec 16 20:06:54 2024 +0300 fix tx ask hard processing (#12648) +conflict 217c3483714 Vladislav Gogov Tue Dec 17 12:33:39 2024 +0300 Add a scenario test for alter/set compression (#11982) +good 99fdf00b8c8 Semyon Tue Dec 17 13:05:36 2024 +0300 optimize memory footprint of CS schemas (#12593) +good de64fffc83c Semyon Wed Dec 18 10:38:16 2024 +0300 new tiered_ttl mode in public TtlSettings (#12405) +good 3d7fe1260a0 ivanmorozov333 Wed Dec 18 13:34:53 2024 +0300 ask accessors for mark to remove portions too (#12699) +good ae2a5a5815e ivanmorozov333 Wed Dec 18 19:55:14 2024 +0300 fix memory hold after writing aborted (#12682) +good 26601741efa Semyon Thu Dec 19 14:13:39 2024 +0300 use external data sources as tiers in cs (#11581) +good c66a3198baa ivanmorozov333 Thu Dec 19 20:08:59 2024 +0300 fix sys view chunks reply construction (#12787) +good 737b0d53d59 ivanmorozov333 Thu Dec 19 22:06:11 2024 +0300 add validation for incorrect blob writing (#12758) +good a39dc2ad6fb ivanmorozov333 Fri Dec 20 00:29:28 2024 +0300 switchable slices filter (#12791) +good 888c474b76c Semyon Fri Dec 20 11:21:29 2024 +0300 share schemas between CS on same node (#12673) +good 12789bddf14 ivanmorozov333 Sat Dec 21 11:21:37 2024 +0300 scanners unification plain/simple for reuse code (#12847) +good c6318e69d00 ivanmorozov333 Mon Dec 23 10:28:39 2024 +0300 blobs fetcher unification (#12858) +good ac47d7690f8 ivanmorozov333 Mon Dec 23 10:29:15 2024 +0300 accessors fetching control (#12859) +conflict c35f0028b0f zverevgeny Tue Dec 24 14:50:24 2024 +0300 rework olap_workload (#12870) +ignore 3096657831a Nikita Vasilev Tue Dec 24 18:15:30 2024 +0300 Snapshot Isolation: kqp (#12825) +good cf344b64297 ivanmorozov333 Tue Dec 24 18:44:37 2024 +0300 fix coredumps simple (#12914) +good f6d8d599a2b ivanmorozov333 Tue Dec 24 21:59:50 2024 +0300 fix indexes usage (#12933) +good be43a4691eb ivanmorozov333 Tue Dec 24 22:00:07 2024 +0300 fix schema construction with cached objects (#12935) +good 08abadd8bb7 ivanmorozov333 Wed Dec 25 07:34:56 2024 +0300 fix accessors fetching queue processing (#12936) +good d014966628b ivanmorozov333 Wed Dec 25 09:31:56 2024 +0300 bloom filter for ngramms (#12893) +good 1c389709c59 zverevgeny Wed Dec 25 11:31:12 2024 +0300 make code analyzer happy (#12860) +good 46419b61704 ivanmorozov333 Wed Dec 25 16:13:38 2024 +0300 table have to be readable with snapshot livetime (#12964) +conflict 61f38fcfdda zverevgeny Wed Dec 25 19:49:12 2024 +0300 remove table with _ne suffix creation (#13002) +good cf03f2ffe85 zverevgeny Wed Dec 25 19:59:33 2024 +0300 rewrite suite_tests parser (#12949) +conflict ce8b8ec2913 zverevgeny Wed Dec 25 23:23:13 2024 +0300 do not switch ddl/dml when using query service (#13011) +good 866146dc9b3 zverevgeny Thu Dec 26 09:59:30 2024 +0300 Disable WorkloadInsertDelete (#13016) +good fc48c46e886 zverevgeny Thu Dec 26 11:38:55 2024 +0300 Run olap workload on pr checks (#13017) +good 2f6245280eb ivanmorozov333 Thu Dec 26 15:10:32 2024 +0300 bloom ngramms speed up (#12982) +good 1a329e93ffa zverevgeny Thu Dec 26 19:51:50 2024 +0300 test create column table with various column types (#13054) +good 094be61da20 ivanmorozov333 Fri Dec 27 08:39:52 2024 +0300 fetcher accessors signals (#13038) +conflict 916503adf99 zverevgeny Fri Dec 27 18:02:30 2024 +0300 check colunm tables creation with nullables columns (#13061) +good 307b994e27b ivanmorozov333 Sat Dec 28 08:40:38 2024 +0300 fix race on writing in case slow execution (asan tests) (#13085) +good 7155bd18b58 ivanmorozov333 Sat Dec 28 08:40:50 2024 +0300 speed up bloom construction (#13073) +good 03f9a13b030 ivanmorozov333 Sat Dec 28 08:41:02 2024 +0300 reuse code for portion meta (#13065) +good ba06b7b3a66 ivanmorozov333 Sat Dec 28 08:41:14 2024 +0300 correct change tasks validation (#13064) +good 22e791d972c zverevgeny Sat Dec 28 18:00:34 2024 +0300 claryfy query results comparision (#13090) +ignore??? 71ad71fea9e ivanmorozov333 Sat Dec 28 22:56:18 2024 +0300 fix incorrect processing event undelivering in scan fetcher (#13092) +ignore??? c2a9a462da2 ivanmorozov333 Sun Dec 29 10:40:40 2024 +0300 fix macros usage (#13126) +good 03cfdfdd067 ivanmorozov333 Tue Dec 31 13:13:32 2024 +0300 fix blob range construction and index control (#13131) diff --git a/ydb/core/base/events.h b/ydb/core/base/events.h index 704f503215e6..f45d6713eaea 100644 --- a/ydb/core/base/events.h +++ b/ydb/core/base/events.h @@ -181,6 +181,9 @@ struct TKikimrEvents : TEvents { ES_LIMITER = 4258, //ES_MEMORY = 4259, NB. exists in main ES_GROUPED_ALLOCATIONS_MANAGER = 4260, + ES_INCREMENTAL_RESTORE_SCAN = 4261, + ES_FEATURE_FLAGS = 4262, + ES_PRIORITY_QUEUE = 4263, }; }; diff --git a/ydb/core/driver_lib/run/kikimr_services_initializers.cpp b/ydb/core/driver_lib/run/kikimr_services_initializers.cpp index b59902fc2499..105a5ac303e8 100644 --- a/ydb/core/driver_lib/run/kikimr_services_initializers.cpp +++ b/ydb/core/driver_lib/run/kikimr_services_initializers.cpp @@ -182,6 +182,8 @@ #include #include #include +#include +#include #include #include #include @@ -2260,6 +2262,28 @@ void TCompDiskLimiterInitializer::InitializeServices(NActors::TActorSystemSetup* } } +TCompPrioritiesInitializer::TCompPrioritiesInitializer(const TKikimrRunConfig& runConfig) + : IKikimrServicesInitializer(runConfig) { +} + +void TCompPrioritiesInitializer::InitializeServices(NActors::TActorSystemSetup* setup, const NKikimr::TAppData* appData) { + NPrioritiesQueue::TConfig serviceConfig; + if (Config.HasCompPrioritiesConfig()) { + Y_ABORT_UNLESS(serviceConfig.DeserializeFromProto(Config.GetCompPrioritiesConfig())); + } + + if (serviceConfig.IsEnabled()) { + TIntrusivePtr<::NMonitoring::TDynamicCounters> tabletGroup = GetServiceCounters(appData->Counters, "tablets"); + TIntrusivePtr<::NMonitoring::TDynamicCounters> conveyorGroup = tabletGroup->GetSubgroup("type", "TX_COMP_PRIORITIES"); + + auto service = NPrioritiesQueue::TCompServiceOperator::CreateService(serviceConfig, conveyorGroup); + + setup->LocalServices.push_back(std::make_pair( + NPrioritiesQueue::TCompServiceOperator::MakeServiceId(NodeId), + TActorSetupCmd(service, TMailboxType::HTSwap, appData->UserPoolId))); + } +} + TCompConveyorInitializer::TCompConveyorInitializer(const TKikimrRunConfig& runConfig) : IKikimrServicesInitializer(runConfig) { } diff --git a/ydb/core/driver_lib/run/kikimr_services_initializers.h b/ydb/core/driver_lib/run/kikimr_services_initializers.h index 04f30522186f..579c4c5850fa 100644 --- a/ydb/core/driver_lib/run/kikimr_services_initializers.h +++ b/ydb/core/driver_lib/run/kikimr_services_initializers.h @@ -403,6 +403,12 @@ class TGroupedMemoryLimiterInitializer: public IKikimrServicesInitializer { void InitializeServices(NActors::TActorSystemSetup* setup, const NKikimr::TAppData* appData) override; }; +class TCompPrioritiesInitializer: public IKikimrServicesInitializer { +public: + TCompPrioritiesInitializer(const TKikimrRunConfig& runConfig); + void InitializeServices(NActors::TActorSystemSetup* setup, const NKikimr::TAppData* appData) override; +}; + class TCompConveyorInitializer: public IKikimrServicesInitializer { public: TCompConveyorInitializer(const TKikimrRunConfig& runConfig); diff --git a/ydb/core/driver_lib/run/run.cpp b/ydb/core/driver_lib/run/run.cpp index d2bd9600803b..b6c5db1ac9e1 100644 --- a/ydb/core/driver_lib/run/run.cpp +++ b/ydb/core/driver_lib/run/run.cpp @@ -1581,6 +1581,10 @@ TIntrusivePtr TKikimrRunner::CreateServiceInitializers sil->AddServiceInitializer(new TScanConveyorInitializer(runConfig)); } + if (serviceMask.EnableCompPriorities) { + sil->AddServiceInitializer(new TCompPrioritiesInitializer(runConfig)); + } + if (serviceMask.EnableCompConveyor) { sil->AddServiceInitializer(new TCompConveyorInitializer(runConfig)); } diff --git a/ydb/core/driver_lib/run/service_mask.h b/ydb/core/driver_lib/run/service_mask.h index 044557229c6b..9bb31d2df8b8 100644 --- a/ydb/core/driver_lib/run/service_mask.h +++ b/ydb/core/driver_lib/run/service_mask.h @@ -80,6 +80,7 @@ union TBasicKikimrServicesMask { bool EnableCompDiskLimiter:1; bool EnableGroupedMemoryLimiter:1; bool EnableAwsService:1; + bool EnableCompPriorities : 1; }; struct { diff --git a/ydb/core/formats/arrow/accessor/abstract/constructor.h b/ydb/core/formats/arrow/accessor/abstract/constructor.h index aa99260e097a..8cbef70a2a45 100644 --- a/ydb/core/formats/arrow/accessor/abstract/constructor.h +++ b/ydb/core/formats/arrow/accessor/abstract/constructor.h @@ -25,10 +25,23 @@ class IConstructor { virtual TString DoDebugString() const { return ""; } + virtual bool DoIsEqualWithSameTypeTo(const IConstructor& item) const = 0; + virtual std::shared_ptr DoConstruct( + const std::shared_ptr& columnData, const TChunkConstructionData& externalInfo) const = 0; public: virtual ~IConstructor() = default; + std::shared_ptr Construct( + const std::shared_ptr& columnData, const TChunkConstructionData& externalInfo) const { + AFL_VERIFY(columnData); + return DoConstruct(columnData, externalInfo); + } + + bool IsEqualWithSameTypeTo(const IConstructor& item) const { + return DoIsEqualWithSameTypeTo(item); + } + TString DebugString() const { return TStringBuilder() << GetClassName() << ":" << DoDebugString(); } @@ -65,10 +78,27 @@ class IConstructor { class TConstructorContainer: public NBackgroundTasks::TInterfaceProtoContainer { private: using TBase = NBackgroundTasks::TInterfaceProtoContainer; - public: using TBase::TBase; + bool IsEqualTo(const TConstructorContainer& item) const { + if (!GetObjectPtr() && !item.GetObjectPtr()) { + return true; + } else if (!!GetObjectPtr() && !!item.GetObjectPtr()) { + if (GetObjectPtr()->GetClassName() != item.GetObjectPtr()->GetClassName()) { + return false; + } + return GetObjectPtr()->IsEqualWithSameTypeTo(*item.GetObjectPtr()); + } else { + return false; + } + } + + std::shared_ptr Construct(const std::shared_ptr& batch, const TChunkConstructionData& externalInfo) const { + AFL_VERIFY(!!GetObjectPtr()); + return GetObjectPtr()->Construct(batch, externalInfo); + } + static TConstructorContainer GetDefaultConstructor(); }; diff --git a/ydb/core/formats/arrow/accessor/abstract/ya.make b/ydb/core/formats/arrow/accessor/abstract/ya.make index c40f1f297c18..4e07801b2285 100644 --- a/ydb/core/formats/arrow/accessor/abstract/ya.make +++ b/ydb/core/formats/arrow/accessor/abstract/ya.make @@ -4,6 +4,7 @@ PEERDIR( contrib/libs/apache/arrow ydb/library/conclusion ydb/services/metadata/abstract + ydb/library/actors/core ydb/library/formats/arrow/accessor/abstract ydb/library/formats/arrow/accessor/common ydb/library/formats/arrow/protos diff --git a/ydb/core/formats/arrow/accessor/plain/accessor.cpp b/ydb/core/formats/arrow/accessor/plain/accessor.cpp index c606f2e1952b..0e83b81b095e 100644 --- a/ydb/core/formats/arrow/accessor/plain/accessor.cpp +++ b/ydb/core/formats/arrow/accessor/plain/accessor.cpp @@ -43,11 +43,10 @@ class TChunkAccessor { return (ui64)ChunkedArray->num_chunks(); } ui64 GetChunkLength(const ui32 idx) const { - return (ui64)ChunkedArray->chunk(idx)->length(); + return (ui64)ChunkedArray->chunks()[idx]->length(); } void OnArray(const ui32 idx, const ui32 startPosition) const { - const auto& arr = ChunkedArray->chunk(idx); - *Result = IChunkedArray::TLocalDataAddress(arr, startPosition, idx); + *Result = IChunkedArray::TLocalDataAddress(ChunkedArray->chunk(idx), startPosition, idx); } }; diff --git a/ydb/core/formats/arrow/accessor/plain/accessor.h b/ydb/core/formats/arrow/accessor/plain/accessor.h index a00826161c40..73b8510080ae 100644 --- a/ydb/core/formats/arrow/accessor/plain/accessor.h +++ b/ydb/core/formats/arrow/accessor/plain/accessor.h @@ -32,6 +32,10 @@ class TTrivialArray: public IChunkedArray { } public: + const std::shared_ptr& GetArray() const { + return Array; + } + TTrivialArray(const std::shared_ptr& data) : TBase(data->length(), EType::Array, data->type()) , Array(data) { diff --git a/ydb/core/formats/arrow/accessor/plain/constructor.cpp b/ydb/core/formats/arrow/accessor/plain/constructor.cpp index 3ecf41502b33..c81b65023f35 100644 --- a/ydb/core/formats/arrow/accessor/plain/constructor.cpp +++ b/ydb/core/formats/arrow/accessor/plain/constructor.cpp @@ -2,8 +2,11 @@ #include "constructor.h" #include +#include #include + #include +#include namespace NKikimr::NArrow::NAccessor::NPlain { @@ -30,4 +33,17 @@ std::shared_ptr TConstructor::DoGetExpectedSchema(const std::shar return std::make_shared(arrow::FieldVector({ resultColumn })); } +std::shared_ptr TConstructor::DoConstruct( + const std::shared_ptr& columnData, const TChunkConstructionData& externalInfo) const { + auto schema = std::make_shared(arrow::FieldVector({ std::make_shared("val", externalInfo.GetColumnType()) })); + if (columnData->GetType() == IChunkedArray::EType::Array) { + const auto* arr = static_cast(columnData.get()); + return arrow::RecordBatch::Make(schema, columnData->GetRecordsCount(), { arr->GetArray() }); + } else { + auto chunked = columnData->GetChunkedArray(); + auto table = arrow::Table::Make(schema, { chunked }, columnData->GetRecordsCount()); + return NArrow::ToBatch(table, chunked->num_chunks() > 1); + } +} + } // namespace NKikimr::NArrow::NAccessor::NPlain diff --git a/ydb/core/formats/arrow/accessor/plain/constructor.h b/ydb/core/formats/arrow/accessor/plain/constructor.h index 57c366689eb0..243c1e47dec4 100644 --- a/ydb/core/formats/arrow/accessor/plain/constructor.h +++ b/ydb/core/formats/arrow/accessor/plain/constructor.h @@ -12,6 +12,13 @@ class TConstructor: public IConstructor { private: static inline auto Registrator = TFactory::TRegistrator(GetClassNameStatic()); + + virtual bool DoIsEqualWithSameTypeTo(const IConstructor& /*item*/) const override { + return true; + } + + virtual std::shared_ptr DoConstruct( + const std::shared_ptr& columnData, const TChunkConstructionData& externalInfo) const override; virtual TConclusion> DoConstruct( const std::shared_ptr& originalData, const TChunkConstructionData& externalInfo) const override; virtual NKikimrArrowAccessorProto::TConstructor DoSerializeToProto() const override; diff --git a/ydb/core/formats/arrow/accessor/sparsed/accessor.h b/ydb/core/formats/arrow/accessor/sparsed/accessor.h index 040224962239..07a1a2465cec 100644 --- a/ydb/core/formats/arrow/accessor/sparsed/accessor.h +++ b/ydb/core/formats/arrow/accessor/sparsed/accessor.h @@ -153,6 +153,11 @@ class TSparsedArray: public IChunkedArray { return chunk.GetScalar(index - chunk.GetStartPosition()); } + std::shared_ptr GetRecordBatchVerified() const { + AFL_VERIFY(Records.size() == 1)("size", Records.size()); + return Records.front().GetRecords(); + } + const TSparsedArrayChunk& GetSparsedChunk(const ui64 position) const { const auto pred = [](const ui64 position, const TSparsedArrayChunk& item) { return position < item.GetStartPosition(); diff --git a/ydb/core/formats/arrow/accessor/sparsed/constructor.cpp b/ydb/core/formats/arrow/accessor/sparsed/constructor.cpp index e3f45cd75327..2962636c367e 100644 --- a/ydb/core/formats/arrow/accessor/sparsed/constructor.cpp +++ b/ydb/core/formats/arrow/accessor/sparsed/constructor.cpp @@ -31,4 +31,10 @@ bool TConstructor::DoDeserializeFromProto(const NKikimrArrowAccessorProto::TCons return true; } +std::shared_ptr TConstructor::DoConstruct( + const std::shared_ptr& columnData, const TChunkConstructionData& externalInfo) const { + NArrow::NAccessor::TSparsedArray sparsed(*columnData, externalInfo.GetDefaultValue()); + return sparsed.GetRecordBatchVerified(); +} + } // namespace NKikimr::NArrow::NAccessor::NSparsed diff --git a/ydb/core/formats/arrow/accessor/sparsed/constructor.h b/ydb/core/formats/arrow/accessor/sparsed/constructor.h index 0ccf5efdd70f..a85b8918eafa 100644 --- a/ydb/core/formats/arrow/accessor/sparsed/constructor.h +++ b/ydb/core/formats/arrow/accessor/sparsed/constructor.h @@ -12,6 +12,13 @@ class TConstructor: public IConstructor { private: static inline auto Registrator = TFactory::TRegistrator(GetClassNameStatic()); + + virtual bool DoIsEqualWithSameTypeTo(const IConstructor& /*item*/) const override { + return true; + } + virtual std::shared_ptr DoConstruct( + const std::shared_ptr& columnData, const TChunkConstructionData& externalInfo) const override; + virtual TConclusion> DoConstruct( const std::shared_ptr& originalData, const TChunkConstructionData& externalInfo) const override; virtual NKikimrArrowAccessorProto::TConstructor DoSerializeToProto() const override; diff --git a/ydb/core/formats/arrow/arrow_filter.cpp b/ydb/core/formats/arrow/arrow_filter.cpp index 62b47a66e24c..91e3fd204745 100644 --- a/ydb/core/formats/arrow/arrow_filter.cpp +++ b/ydb/core/formats/arrow/arrow_filter.cpp @@ -1,21 +1,23 @@ #include "arrow_filter.h" -#include "switch/switch_type.h" -#include "common/container.h" + #include "common/adapter.h" +#include "common/container.h" +#include "switch/switch_type.h" + +#include +#include #include #include #include #include -#include -#include namespace NKikimr::NArrow { #define Y_VERIFY_OK(status) Y_ABORT_UNLESS(status.ok(), "%s", status.ToString().c_str()) namespace { -enum class ECompareResult: i8 { +enum class ECompareResult : i8 { LESS = -1, BORDER = 0, GREATER = 1 @@ -50,8 +52,7 @@ inline void UpdateCompare(const T& value, const T& border, ECompareResult& res) } template -bool CompareImpl(const std::shared_ptr& column, const T& border, - std::vector& rowsCmp) { +bool CompareImpl(const std::shared_ptr& column, const T& border, std::vector& rowsCmp) { bool hasBorder = false; ECompareResult* res = &rowsCmp[0]; auto array = std::static_pointer_cast(column); @@ -64,8 +65,7 @@ bool CompareImpl(const std::shared_ptr& column, const T& border, } template -bool CompareImpl(const std::shared_ptr& column, const T& border, - std::vector& rowsCmp) { +bool CompareImpl(const std::shared_ptr& column, const T& border, std::vector& rowsCmp) { bool hasBorder = false; ECompareResult* res = &rowsCmp[0]; @@ -82,8 +82,7 @@ bool CompareImpl(const std::shared_ptr& column, const T& bo /// @return true in case we have no borders in compare: no need for future keys, allow early exit template -bool Compare(const arrow::Datum& column, const std::shared_ptr& borderArray, - std::vector& rowsCmp) { +bool Compare(const arrow::Datum& column, const std::shared_ptr& borderArray, std::vector& rowsCmp) { auto border = GetValue(std::static_pointer_cast(borderArray), 0); switch (column.kind()) { @@ -98,8 +97,7 @@ bool Compare(const arrow::Datum& column, const std::shared_ptr& bo return false; } -bool SwitchCompare(const arrow::Datum& column, const std::shared_ptr& border, - std::vector& rowsCmp) { +bool SwitchCompare(const arrow::Datum& column, const std::shared_ptr& border, std::vector& rowsCmp) { Y_ABORT_UNLESS(border->length() == 1); // first time it's empty @@ -111,12 +109,11 @@ bool SwitchCompare(const arrow::Datum& column, const std::shared_ptr; using TArray = typename arrow::TypeTraits::ArrayType; return Compare(column, border, rowsCmp); - }); + }); } template -void CompositeCompare(std::shared_ptr some, std::shared_ptr borderBatch, - std::vector& rowsCmp) { +void CompositeCompare(std::shared_ptr some, std::shared_ptr borderBatch, std::vector& rowsCmp) { auto key = borderBatch->schema()->fields(); Y_ABORT_UNLESS(key.size()); @@ -130,11 +127,61 @@ void CompositeCompare(std::shared_ptr some, std::shared_ptrschema()->GetFieldByName(field->name())->type()->id() == typeId); if (SwitchCompare(column, border, rowsCmp)) { - break; // early exit in case we have all rows compared: no borders, can omit key tail + break; // early exit in case we have all rows compared: no borders, can omit key tail } } } +} // namespace + +TColumnFilter::TSlicesIterator::TSlicesIterator(const TColumnFilter& owner, const std::optional start, const std::optional count) + : Owner(owner) + , StartIndex(start) + , Count(count) { + AFL_VERIFY(!!StartIndex == !!Count); + AFL_VERIFY(Owner.GetFilter().size()); + if (StartIndex) { + AFL_VERIFY(*StartIndex + *Count <= owner.GetRecordsCountVerified())("start", *StartIndex)("count", *count)("size", owner.GetRecordsCount()); + } +} + +TColumnFilter::TApplyContext& TColumnFilter::TApplyContext::Slice(const ui32 start, const ui32 count) { + AFL_VERIFY(!StartPos && !Count); + StartPos = start; + Count = count; + return *this; +} + +ui32 TColumnFilter::TSlicesIterator::GetSliceSize() const { + AFL_VERIFY(IsValid()); + if (!StartIndex) { + return *CurrentIterator; + } else { + const ui32 startIndex = GetStartIndex(); + const ui32 finishIndex = std::min(CurrentStartIndex + *CurrentIterator, *StartIndex + *Count); + AFL_VERIFY(startIndex < finishIndex)("start", startIndex)("finish", finishIndex); + return finishIndex - startIndex; + } +} + +void TColumnFilter::TSlicesIterator::Start() { + CurrentStartIndex = 0; + CurrentIsFiltered = Owner.GetStartValue(); + CurrentIterator = Owner.GetFilter().begin(); + if (StartIndex) { + while (IsValid() && CurrentStartIndex + *CurrentIterator < *StartIndex) { + AFL_VERIFY(Next()); + } + AFL_VERIFY(IsValid()); + } +} + +bool TColumnFilter::TSlicesIterator::Next() { + AFL_VERIFY(IsValid()); + CurrentIsFiltered = !CurrentIsFiltered; + CurrentStartIndex += *CurrentIterator; + ++CurrentIterator; + return IsValid(); } bool TColumnFilter::TIterator::Next(const ui32 size) { @@ -193,7 +240,8 @@ TString TColumnFilter::TIterator::DebugString() const { return sb; } -std::shared_ptr TColumnFilter::BuildArrowFilter(const ui32 expectedSize, const std::optional startPos, const std::optional count) const { +std::shared_ptr TColumnFilter::BuildArrowFilter( + const ui32 expectedSize, const std::optional startPos, const std::optional count) const { AFL_VERIFY(!!startPos == !!count); auto& simpleFilter = BuildSimpleFilter(); arrow::BooleanBuilder builder; @@ -230,7 +278,7 @@ bool TColumnFilter::IsTotalDenyFilter() const { } void TColumnFilter::Reset(const ui32 count) { - Count = 0; + RecordsCount = 0; FilterPlain.reset(); Filter.clear(); Filter.reserve(count / 4); @@ -240,13 +288,13 @@ void TColumnFilter::Add(const bool value, const ui32 count) { if (!count) { return; } - if (Y_UNLIKELY(LastValue != value || !Count)) { + if (Y_UNLIKELY(LastValue != value || !RecordsCount)) { Filter.emplace_back(count); LastValue = value; } else { Filter.back() += count; } - Count += count; + RecordsCount += count; } ui32 TColumnFilter::CrossSize(const ui32 s1, const ui32 f1, const ui32 s2, const ui32 f2) { @@ -256,7 +304,8 @@ ui32 TColumnFilter::CrossSize(const ui32 s1, const ui32 f1, const ui32 s2, const return f - s; } -NKikimr::NArrow::TColumnFilter TColumnFilter::MakePredicateFilter(const arrow::Datum& datum, const arrow::Datum& border, ECompareType compareType) { +NKikimr::NArrow::TColumnFilter TColumnFilter::MakePredicateFilter( + const arrow::Datum& datum, const arrow::Datum& border, ECompareType compareType) { std::vector cmps; switch (datum.kind()) { @@ -311,17 +360,19 @@ NKikimr::NArrow::TColumnFilter TColumnFilter::MakePredicateFilter(const arrow::D } template -bool ApplyImpl(const TColumnFilter& filter, std::shared_ptr& batch, const std::optional startPos, const std::optional count) { +bool ApplyImpl(const TColumnFilter& filter, std::shared_ptr& batch, const TColumnFilter::TApplyContext& context) { if (!batch || !batch->num_rows()) { return false; } - AFL_VERIFY(!!startPos == !!count); if (!filter.IsEmpty()) { - if (startPos) { - AFL_VERIFY(filter.Size() >= *startPos + *count)("filter_size", filter.Size())("start", *startPos)("count", *count); - AFL_VERIFY(*count == (size_t)batch->num_rows())("count", *count)("batch_size", batch->num_rows()); + if (context.HasSlice()) { + AFL_VERIFY(filter.GetRecordsCountVerified() >= *context.GetStartPos() + *context.GetCount())( + "filter_size", filter.GetRecordsCountVerified())( + "start", context.GetStartPos())("count", context.GetCount()); + AFL_VERIFY(*context.GetCount() == (size_t)batch->num_rows())("count", context.GetCount())("batch_size", batch->num_rows()); } else { - AFL_VERIFY(filter.Size() == (size_t)batch->num_rows())("filter_size", filter.Size())("batch_size", batch->num_rows()); + AFL_VERIFY(filter.GetRecordsCountVerified() == (size_t)batch->num_rows())("filter_size", filter.GetRecordsCountVerified())( + "batch_size", batch->num_rows()); } } if (filter.IsTotalDenyFilter()) { @@ -331,20 +382,27 @@ bool ApplyImpl(const TColumnFilter& filter, std::shared_ptr& batch, const if (filter.IsTotalAllowFilter()) { return true; } - batch = NAdapter::TDataBuilderPolicy::ApplyArrowFilter(batch, filter.BuildArrowFilter(batch->num_rows(), startPos, count)); + if (context.GetTrySlices() && filter.GetFilter().size() * 10 < filter.GetRecordsCountVerified() && + filter.GetRecordsCountVerified() < filter.GetFilteredCountVerified() * 50) { + batch = + NAdapter::TDataBuilderPolicy::ApplySlicesFilter(batch, filter.BuildSlicesIterator(context.GetStartPos(), context.GetCount())); + } else { + batch = NAdapter::TDataBuilderPolicy::ApplyArrowFilter( + batch, filter.BuildArrowFilter(batch->num_rows(), context.GetStartPos(), context.GetCount())); + } return batch->num_rows(); } -bool TColumnFilter::Apply(std::shared_ptr& batch, const std::optional startPos, const std::optional count) const { - return ApplyImpl(*this, batch, startPos, count); +bool TColumnFilter::Apply(std::shared_ptr& batch, const TApplyContext& context) const { + return ApplyImpl(*this, batch, context); } -bool TColumnFilter::Apply(std::shared_ptr& batch, const std::optional startPos, const std::optional count) const { - return ApplyImpl(*this, batch, startPos, count); +bool TColumnFilter::Apply(std::shared_ptr& batch, const TApplyContext& context) const { + return ApplyImpl(*this, batch, context); } -bool TColumnFilter::Apply(std::shared_ptr& batch, const std::optional startPos, const std::optional count) const { - return ApplyImpl(*this, batch, startPos, count); +bool TColumnFilter::Apply(std::shared_ptr& batch, const TApplyContext& context) const { + return ApplyImpl(*this, batch, context); } void TColumnFilter::Apply(const ui32 expectedRecordsCount, std::vector& datums) const { @@ -382,9 +440,9 @@ void TColumnFilter::Apply(const ui32 expectedRecordsCount, std::vector& TColumnFilter::BuildSimpleFilter() const { if (!FilterPlain) { - Y_ABORT_UNLESS(Count); + Y_ABORT_UNLESS(RecordsCount); std::vector result; - result.resize(Count, true); + result.resize(RecordsCount, true); bool currentValue = GetStartValue(); ui32 currentPosition = 0; for (auto&& i : Filter) { @@ -433,12 +491,11 @@ class TColumnFilter::TMergerImpl { private: const TColumnFilter& Filter1; const TColumnFilter& Filter2; + public: TMergerImpl(const TColumnFilter& filter1, const TColumnFilter& filter2) : Filter1(filter1) - , Filter2(filter2) - { - + , Filter2(filter2) { } template @@ -450,7 +507,7 @@ class TColumnFilter::TMergerImpl { } else if (Filter2.empty()) { return TMergePolicy::MergeWithSimple(Filter1, Filter2.DefaultFilterValue); } else { - Y_ABORT_UNLESS(Filter1.Count == Filter2.Count); + Y_ABORT_UNLESS(Filter1.RecordsCount == Filter2.RecordsCount); auto it1 = Filter1.Filter.cbegin(); auto it2 = Filter2.Filter.cbegin(); @@ -495,11 +552,10 @@ class TColumnFilter::TMergerImpl { TColumnFilter result = TColumnFilter::BuildAllowFilter(); std::swap(resultFilter, result.Filter); std::swap(curCurrent, result.LastValue); - std::swap(count, result.Count); + std::swap(count, result.RecordsCount); return result; } } - }; TColumnFilter TColumnFilter::And(const TColumnFilter& extFilter) const { @@ -569,7 +625,7 @@ TColumnFilter TColumnFilter::CombineSequentialAnd(const TColumnFilter& extFilter TColumnFilter result = TColumnFilter::BuildAllowFilter(); std::swap(resultFilter, result.Filter); std::swap(curCurrent, result.LastValue); - std::swap(count, result.Count); + std::swap(count, result.RecordsCount); return result; } } @@ -580,7 +636,8 @@ TColumnFilter::TIterator TColumnFilter::GetIterator(const bool reverse, const ui } else if (IsTotalDenyFilter()) { return TIterator(reverse, expectedSize, false); } else { - AFL_VERIFY(expectedSize == Size())("expected", expectedSize)("size", Size())("reverse", reverse); + AFL_VERIFY(expectedSize == GetRecordsCountVerified())("expected", expectedSize)("count", GetRecordsCountVerified())( + "reverse", reverse); return TIterator(reverse, Filter, GetStartValue(reverse)); } } @@ -588,10 +645,10 @@ TColumnFilter::TIterator TColumnFilter::GetIterator(const bool reverse, const ui std::optional TColumnFilter::GetFilteredCount() const { if (!FilteredCount) { if (IsTotalAllowFilter()) { - if (!Count) { + if (!RecordsCount) { return {}; } else { - FilteredCount = Count; + FilteredCount = RecordsCount; } } else if (IsTotalDenyFilter()) { FilteredCount = 0; @@ -617,4 +674,25 @@ void TColumnFilter::Append(const TColumnFilter& filter) { } } +std::optional TColumnFilter::GetRecordsCount() const { + if (Filter.size()) { + AFL_VERIFY(RecordsCount); + return RecordsCount; + } else { + return std::nullopt; + } } + +ui32 TColumnFilter::GetRecordsCountVerified() const { + AFL_VERIFY(Filter.size()); + AFL_VERIFY(RecordsCount); + return RecordsCount; +} + +ui32 TColumnFilter::GetFilteredCountVerified() const { + const std::optional result = GetFilteredCount(); + AFL_VERIFY(!!result); + return *result; +} + +} // namespace NKikimr::NArrow diff --git a/ydb/core/formats/arrow/arrow_filter.h b/ydb/core/formats/arrow/arrow_filter.h index 347baf9f02e7..f2b9641d1c0e 100644 --- a/ydb/core/formats/arrow/arrow_filter.h +++ b/ydb/core/formats/arrow/arrow_filter.h @@ -4,6 +4,7 @@ #include #include #include + #include namespace NKikimr::NArrow { @@ -21,16 +22,74 @@ class TColumnFilter { private: bool DefaultFilterValue = true; bool LastValue = true; - ui32 Count = 0; - std::vector Filter; + ui32 RecordsCount = 0; + YDB_READONLY_DEF(std::vector, Filter); mutable std::optional> FilterPlain; mutable std::optional FilteredCount; TColumnFilter(const bool defaultFilterValue) - : DefaultFilterValue(defaultFilterValue) - { + : DefaultFilterValue(defaultFilterValue) { + } + + static ui32 CrossSize(const ui32 s1, const ui32 f1, const ui32 s2, const ui32 f2); + class TMergerImpl; + void Reset(const ui32 count); + void ResetCaches() const { + FilterPlain.reset(); + FilteredCount.reset(); + } + +public: + class TSlicesIterator { + private: + const TColumnFilter& Owner; + const std::optional StartIndex; + const std::optional Count; + ui32 CurrentStartIndex = 0; + bool CurrentIsFiltered = false; + std::vector::const_iterator CurrentIterator; + public: + TSlicesIterator(const TColumnFilter& owner, const std::optional start, const std::optional count); + + bool IsFiltered() const { + return CurrentIsFiltered; + } + + ui32 GetRecordsCount() const { + if (StartIndex) { + return *Count; + } else { + return Owner.GetRecordsCountVerified(); + } + } + + ui32 GetStartIndex() const { + if (!StartIndex) { + return CurrentStartIndex; + } else { + return std::max(CurrentStartIndex, *StartIndex); + } + } + + ui32 GetSliceSize() const; + + void Start(); + + bool IsValid() const { + return CurrentIterator != Owner.GetFilter().end() && (!StartIndex || CurrentStartIndex < *StartIndex + *Count); + } + bool Next(); + + }; + + TSlicesIterator BuildSlicesIterator(const std::optional startIndex, const std::optional count) const { + return TSlicesIterator(*this, startIndex, count); } + std::optional GetRecordsCount() const; + + ui32 GetRecordsCountVerified() const; + bool GetStartValue(const bool reverse = false) const { if (Filter.empty()) { return DefaultFilterValue; @@ -46,22 +105,16 @@ class TColumnFilter { } } - static ui32 CrossSize(const ui32 s1, const ui32 f1, const ui32 s2, const ui32 f2); - class TMergerImpl; - void Reset(const ui32 count); - void ResetCaches() const { - FilterPlain.reset(); - FilteredCount.reset(); - } -public: void Append(const TColumnFilter& filter); void Add(const bool value, const ui32 count = 1); std::optional GetFilteredCount() const; + ui32 GetFilteredCountVerified() const; const std::vector& BuildSimpleFilter() const; - std::shared_ptr BuildArrowFilter(const ui32 expectedSize, const std::optional startPos = {}, const std::optional count = {}) const; + std::shared_ptr BuildArrowFilter( + const ui32 expectedSize, const std::optional startPos = {}, const std::optional count = {}) const; ui64 GetDataSize() const { - return Filter.capacity() * sizeof(ui32) + Count * sizeof(bool); + return Filter.capacity() * sizeof(ui32) + RecordsCount * sizeof(bool); } static ui64 GetPredictedMemorySize(const ui32 recordsCount) { @@ -77,6 +130,7 @@ class TColumnFilter { bool CurrentValue; const i32 FinishPosition; const i32 DeltaPosition; + public: TString DebugString() const; @@ -84,8 +138,7 @@ class TColumnFilter { : FilterPointer(&filter) , CurrentValue(startValue) , FinishPosition(reverse ? -1 : FilterPointer->size()) - , DeltaPosition(reverse ? -1 : 1) - { + , DeltaPosition(reverse ? -1 : 1) { if (!FilterPointer->size()) { Position = FinishPosition; } else { @@ -158,11 +211,10 @@ class TColumnFilter { struct TAdapterLambda { private: TGetterLambda Getter; + public: TAdapterLambda(const TGetterLambda& getter) - : Getter(getter) - { - + : Getter(getter) { } bool operator[](const ui32 index) const { @@ -175,10 +227,6 @@ class TColumnFilter { return Reset(count, TAdapterLambda(getter)); } - ui32 Size() const { - return Count; - } - bool IsTotalAllowFilter() const; bool IsTotalDenyFilter() const; bool IsEmpty() const { @@ -199,13 +247,33 @@ class TColumnFilter { // It makes a filter using composite predicate static TColumnFilter MakePredicateFilter(const arrow::Datum& datum, const arrow::Datum& border, ECompareType compareType); - bool Apply(std::shared_ptr& batch, const std::optional startPos = {}, const std::optional count = {}) const; - bool Apply(std::shared_ptr& batch, const std::optional startPos = {}, const std::optional count = {}) const; - bool Apply(std::shared_ptr& batch, const std::optional startPos = {}, const std::optional count = {}) const; + class TApplyContext { + private: + YDB_READONLY_DEF(std::optional, StartPos); + YDB_READONLY_DEF(std::optional, Count); + YDB_ACCESSOR(bool, TrySlices, false); + + public: + TApplyContext() = default; + bool HasSlice() const { + return !!StartPos && !!Count; + } + + TApplyContext(const ui32 start, const ui32 count) + : StartPos(start) + , Count(count) { + } + + TApplyContext& Slice(const ui32 start, const ui32 count); + }; + + bool Apply(std::shared_ptr& batch, const TApplyContext& context = Default()) const; + bool Apply(std::shared_ptr& batch, const TApplyContext& context = Default()) const; + bool Apply(std::shared_ptr& batch, const TApplyContext& context = Default()) const; void Apply(const ui32 expectedRecordsCount, std::vector& datums) const; // Combines filters by 'and' operator (extFilter count is true positions count in self, thought extFitler patch exactly that positions) TColumnFilter CombineSequentialAnd(const TColumnFilter& extFilter) const Y_WARN_UNUSED_RESULT; }; -} +} // namespace NKikimr::NArrow diff --git a/ydb/core/formats/arrow/common/adapter.h b/ydb/core/formats/arrow/common/adapter.h index 18b2deeacc9b..f348f4376299 100644 --- a/ydb/core/formats/arrow/common/adapter.h +++ b/ydb/core/formats/arrow/common/adapter.h @@ -2,7 +2,9 @@ #include "container.h" #include +#include +#include #include #include @@ -46,6 +48,18 @@ class TDataBuilderPolicy { Y_ABORT_UNLESS(res->kind() == arrow::Datum::RECORD_BATCH); return res->record_batch(); } + [[nodiscard]] static std::shared_ptr ApplySlicesFilter( + const std::shared_ptr& batch, TColumnFilter::TSlicesIterator filter) { + AFL_VERIFY(filter.GetRecordsCount() == batch->num_rows()); + std::vector> slices; + for (filter.Start(); filter.IsValid(); filter.Next()) { + if (!filter.IsFiltered()) { + continue; + } + slices.emplace_back(batch->Slice(filter.GetStartIndex(), filter.GetSliceSize())); + } + return NArrow::ToBatch(TStatusValidator::GetValid(arrow::Table::FromRecordBatches(slices)), true); + } [[nodiscard]] static std::shared_ptr GetEmptySame(const std::shared_ptr& batch) { return batch->Slice(0, 0); } @@ -74,6 +88,17 @@ class TDataBuilderPolicy { Y_ABORT_UNLESS(res->kind() == arrow::Datum::TABLE); return res->table(); } + [[nodiscard]] static std::shared_ptr ApplySlicesFilter( + const std::shared_ptr& batch, TColumnFilter::TSlicesIterator filter) { + std::vector> slices; + for (filter.Start(); filter.IsValid(); filter.Next()) { + if (!filter.IsFiltered()) { + continue; + } + slices.emplace_back(batch->Slice(filter.GetStartIndex(), filter.GetSliceSize())); + } + return TStatusValidator::GetValid(arrow::ConcatenateTables(slices)); + } [[nodiscard]] static std::shared_ptr GetEmptySame(const std::shared_ptr& batch) { return batch->Slice(0, 0); } @@ -100,6 +125,11 @@ class TDataBuilderPolicy { auto table = batch->BuildTableVerified(); return std::make_shared(TDataBuilderPolicy::ApplyArrowFilter(table, filter)); } + [[nodiscard]] static std::shared_ptr ApplySlicesFilter( + const std::shared_ptr& batch, TColumnFilter::TSlicesIterator filter) { + auto table = batch->BuildTableVerified(); + return std::make_shared(TDataBuilderPolicy::ApplySlicesFilter(table, filter)); + } [[nodiscard]] static std::shared_ptr GetEmptySame(const std::shared_ptr& batch) { return batch->BuildEmptySame(); } diff --git a/ydb/core/formats/arrow/common/container.cpp b/ydb/core/formats/arrow/common/container.cpp index 7b159f2eef06..9100a9fa56a0 100644 --- a/ydb/core/formats/arrow/common/container.cpp +++ b/ydb/core/formats/arrow/common/container.cpp @@ -148,27 +148,32 @@ std::shared_ptr TGeneralContainer::BuildEmpt return std::make_shared(Schema, std::move(columns)); } -std::shared_ptr TGeneralContainer::BuildTableOptional(const std::optional>& columnNames /*= {}*/) const { +std::shared_ptr TGeneralContainer::BuildTableOptional(const TTableConstructionContext& context) const { std::vector> columns; std::vector> fields; for (i32 i = 0; i < Schema->num_fields(); ++i) { - if (columnNames && !columnNames->contains(Schema->field(i)->name())) { + if (context.GetColumnNames() && !context.GetColumnNames()->contains(Schema->field(i)->name())) { continue; } - columns.emplace_back(Columns[i]->GetChunkedArray()); + if (context.GetRecordsCount() || context.GetStartIndex()) { + columns.emplace_back(Columns[i]->Slice(context.GetStartIndex().value_or(0), + context.GetRecordsCount().value_or(GetRecordsCount() - context.GetStartIndex().value_or(0)))); + } else { + columns.emplace_back(Columns[i]->GetChunkedArray()); + } fields.emplace_back(Schema->field(i)); } if (fields.empty()) { return nullptr; } AFL_VERIFY(RecordsCount); - return arrow::Table::Make(std::make_shared(fields), columns, *RecordsCount); + return arrow::Table::Make(std::make_shared(fields), columns, context.GetRecordsCount().value_or(*RecordsCount)); } -std::shared_ptr TGeneralContainer::BuildTableVerified(const std::optional>& columnNames /*= {}*/) const { - auto result = BuildTableOptional(columnNames); +std::shared_ptr TGeneralContainer::BuildTableVerified(const TTableConstructionContext& context) const { + auto result = BuildTableOptional(context); AFL_VERIFY(result); - AFL_VERIFY(!columnNames || result->schema()->num_fields() == (i32)columnNames->size()); + AFL_VERIFY(!context.GetColumnNames() || result->schema()->num_fields() == (i32)context.GetColumnNames()->size()); return result; } diff --git a/ydb/core/formats/arrow/common/container.h b/ydb/core/formats/arrow/common/container.h index dacd5d62c0b0..23f3279e8dcb 100644 --- a/ydb/core/formats/arrow/common/container.h +++ b/ydb/core/formats/arrow/common/container.h @@ -62,8 +62,29 @@ class TGeneralContainer { return Columns[idx]; } - std::shared_ptr BuildTableVerified(const std::optional>& columnNames = {}) const; - std::shared_ptr BuildTableOptional(const std::optional>& columnNames = {}) const; + class TTableConstructionContext { + private: + YDB_ACCESSOR_DEF(std::optional>, ColumnNames); + YDB_ACCESSOR_DEF(std::optional, StartIndex); + YDB_ACCESSOR_DEF(std::optional, RecordsCount); + + public: + TTableConstructionContext() = default; + TTableConstructionContext(std::set&& columnNames) + : ColumnNames(std::move(columnNames)) { + } + + TTableConstructionContext(const std::set& columnNames) + : ColumnNames(columnNames) { + } + + void SetColumnNames(const std::vector& names) { + ColumnNames = std::set(names.begin(), names.end()); + } + }; + + std::shared_ptr BuildTableVerified(const TTableConstructionContext& context = Default()) const; + std::shared_ptr BuildTableOptional(const TTableConstructionContext& context = Default()) const; std::shared_ptr BuildEmptySame() const; diff --git a/ydb/core/formats/arrow/converter.cpp b/ydb/core/formats/arrow/converter.cpp index f0a38e2c8149..150211900da0 100644 --- a/ydb/core/formats/arrow/converter.cpp +++ b/ydb/core/formats/arrow/converter.cpp @@ -31,8 +31,8 @@ static bool ConvertData(TCell& cell, const NScheme::TTypeInfo& colType, TMemoryP } case NScheme::NTypeIds::JsonDocument: { const auto binaryJson = NBinaryJson::SerializeToBinaryJson(cell.AsBuf()); - if (!binaryJson.Defined()) { - errorMessage = "Invalid JSON for JsonDocument provided"; + if (binaryJson.IsFail()) { + errorMessage = "Invalid JSON for JsonDocument provided: " + binaryJson.GetErrorMessage(); return false; } const auto saved = memPool.AppendString(TStringBuf(binaryJson->Data(), binaryJson->Size())); @@ -98,8 +98,9 @@ static arrow::Status ConvertColumn(const NScheme::TTypeInfo colType, std::shared } } else { const auto binaryJson = NBinaryJson::SerializeToBinaryJson(valueBuf); - if (!binaryJson.Defined()) { - return arrow::Status::SerializationError("Cannot serialize json: ", valueBuf); + if (binaryJson.IsFail()) { + return arrow::Status::SerializationError("Cannot serialize json (", binaryJson.GetErrorMessage(), + "): ", valueBuf.SubStr(0, Min(valueBuf.Size(), size_t{1024}))); } auto appendResult = builder.Append(binaryJson->Data(), binaryJson->Size()); if (!appendResult.ok()) { diff --git a/ydb/core/formats/arrow/hash/calcer.cpp b/ydb/core/formats/arrow/hash/calcer.cpp index d5fa4a8dd6a3..8667f5b11107 100644 --- a/ydb/core/formats/arrow/hash/calcer.cpp +++ b/ydb/core/formats/arrow/hash/calcer.cpp @@ -9,7 +9,9 @@ namespace NKikimr::NArrow::NHash { -void TXX64::AppendField(const std::shared_ptr& scalar, NXX64::TStreamStringHashCalcer& hashCalcer) { +namespace { +template +void AppendFieldImpl(const std::shared_ptr& scalar, TStreamCalcer& hashCalcer) { AFL_VERIFY(scalar); NArrow::SwitchType(scalar->type->id(), [&](const auto& type) { using TWrap = std::decay_t; @@ -28,7 +30,8 @@ void TXX64::AppendField(const std::shared_ptr& scalar, NXX64::TSt }); } -void TXX64::AppendField(const std::shared_ptr& array, const int row, NArrow::NHash::NXX64::TStreamStringHashCalcer& hashCalcer) { +template +void AppendFieldImpl(const std::shared_ptr& array, const int row, TStreamCalcer& hashCalcer) { NArrow::SwitchType(array->type_id(), [&](const auto& type) { using TWrap = std::decay_t; using T = typename TWrap::T; @@ -49,6 +52,24 @@ void TXX64::AppendField(const std::shared_ptr& array, const int ro }); } +} // namespace + +void TXX64::AppendField(const std::shared_ptr& scalar, NXX64::TStreamStringHashCalcer& hashCalcer) { + AppendFieldImpl(scalar, hashCalcer); +} + +void TXX64::AppendField(const std::shared_ptr& scalar, NXX64::TStreamStringHashCalcer_H3& hashCalcer) { + AppendFieldImpl(scalar, hashCalcer); +} + +void TXX64::AppendField(const std::shared_ptr& array, const int row, NArrow::NHash::NXX64::TStreamStringHashCalcer& hashCalcer) { + AppendFieldImpl(array, row, hashCalcer); +} + +void TXX64::AppendField(const std::shared_ptr& array, const int row, NArrow::NHash::NXX64::TStreamStringHashCalcer_H3& hashCalcer) { + AppendFieldImpl(array, row, hashCalcer); +} + std::optional> TXX64::Execute(const std::shared_ptr& batch) const { std::vector> columns = GetColumns(batch); if (columns.empty()) { diff --git a/ydb/core/formats/arrow/hash/calcer.h b/ydb/core/formats/arrow/hash/calcer.h index 51dfe7858f8c..490a0e05e366 100644 --- a/ydb/core/formats/arrow/hash/calcer.h +++ b/ydb/core/formats/arrow/hash/calcer.h @@ -67,6 +67,8 @@ class TXX64 { static void AppendField(const std::shared_ptr& array, const int row, NXX64::TStreamStringHashCalcer& hashCalcer); static void AppendField(const std::shared_ptr& scalar, NXX64::TStreamStringHashCalcer& hashCalcer); + static void AppendField(const std::shared_ptr& array, const int row, NXX64::TStreamStringHashCalcer_H3& hashCalcer); + static void AppendField(const std::shared_ptr& scalar, NXX64::TStreamStringHashCalcer_H3& hashCalcer); static ui64 CalcHash(const std::shared_ptr& scalar); std::optional> Execute(const std::shared_ptr& batch) const; diff --git a/ydb/core/formats/arrow/permutations.cpp b/ydb/core/formats/arrow/permutations.cpp index 7e68cc91f267..2a7804c918ef 100644 --- a/ydb/core/formats/arrow/permutations.cpp +++ b/ydb/core/formats/arrow/permutations.cpp @@ -11,7 +11,6 @@ #include #include -#include namespace NKikimr::NArrow { @@ -41,10 +40,14 @@ std::shared_ptr MakeSortPermutation(const std::vector b; + return cmp == std::partial_ordering::equivalent ? a.GetPosition() > b.GetPosition() : cmp == std::partial_ordering::less; + }); } else { std::sort(points.begin(), points.end(), [](const TRawReplaceKey& a, const TRawReplaceKey& b) { - return a.CompareNotNull(b) == std::partial_ordering::less; + auto cmp = a.CompareNotNull(b); + return cmp == std::partial_ordering::equivalent ? a.GetPosition() > b.GetPosition() : cmp == std::partial_ordering::less; }); } diff --git a/ydb/core/formats/arrow/process_columns.cpp b/ydb/core/formats/arrow/process_columns.cpp index df3f8c382203..cdc18dd9d1db 100644 --- a/ydb/core/formats/arrow/process_columns.cpp +++ b/ydb/core/formats/arrow/process_columns.cpp @@ -77,10 +77,7 @@ TConclusion> AdaptColumnsImpl( columns.push_back(srcBatch->column(index)); fields.emplace_back(field); auto srcField = srcBatch->schema()->field(index); - if (field->Equals(srcField)) { - AFL_VERIFY(columns.back()->type()->Equals(field->type()))("event", "cannot_use_incoming_batch")("reason", "invalid_column_type")( - "column", field->name())("column_type", field->type()->ToString())("incoming_type", columns.back()->type()->ToString()); - } else { + if (!field->type()->Equals(srcField->type())) { AFL_ERROR(NKikimrServices::ARROW_HELPER)("event", "cannot_use_incoming_batch")("reason", "invalid_column_type")( "column", field->name())("column_type", field->ToString(true))("incoming_type", srcField->ToString(true)); return TConclusionStatus::Fail("incompatible column types"); @@ -210,24 +207,23 @@ NKikimr::TConclusion> TColumnOperator::Reorder( return ReorderImpl(incoming, columnNames); } namespace { -template -TConclusion BuildSequentialSubsetImpl(const std::shared_ptr& srcBatch, - const std::shared_ptr& dstSchema, const TColumnOperator::ECheckFieldTypesPolicy checkFieldTypesPolicy) { +template +TConclusion BuildSequentialSubsetImpl(const std::shared_ptr& srcBatch, const TSchemaLiteView& dstSchema, + const TColumnOperator::ECheckFieldTypesPolicy checkFieldTypesPolicy) { AFL_VERIFY(srcBatch); - AFL_VERIFY(dstSchema); - if (dstSchema->num_fields() < srcBatch->schema()->num_fields()) { + if (dstSchema.num_fields() < srcBatch->schema()->num_fields()) { AFL_ERROR(NKikimrServices::ARROW_HELPER)("event", "incorrect columns set: destination must been wider than source")( - "source", srcBatch->schema()->ToString())("destination", dstSchema->ToString()); + "source", srcBatch->schema()->ToString())("destination", dstSchema.ToString()); return TConclusionStatus::Fail("incorrect columns set: destination must been wider than source"); } std::set fieldIdx; auto itSrc = srcBatch->schema()->fields().begin(); - auto itDst = dstSchema->fields().begin(); - while (itSrc != srcBatch->schema()->fields().end() && itDst != dstSchema->fields().end()) { + auto itDst = dstSchema.begin(); + while (itSrc != srcBatch->schema()->fields().end() && itDst != dstSchema.end()) { if ((*itSrc)->name() != (*itDst)->name()) { ++itDst; } else { - fieldIdx.emplace(itDst - dstSchema->fields().begin()); + fieldIdx.emplace(itDst - dstSchema.begin()); if (checkFieldTypesPolicy != TColumnOperator::ECheckFieldTypesPolicy::Ignore && (*itDst)->Equals(*itSrc)) { switch (checkFieldTypesPolicy) { case TColumnOperator::ECheckFieldTypesPolicy::Error: { @@ -248,25 +244,24 @@ TConclusion BuildSequentialSubsetImpl(const std::shared_ptrfields().end() && itSrc != srcBatch->schema()->fields().end()) { + if (itDst == dstSchema.end() && itSrc != srcBatch->schema()->fields().end()) { AFL_ERROR(NKikimrServices::ARROW_HELPER)("event", "incorrect columns order in source set")("source", srcBatch->schema()->ToString())( - "destination", dstSchema->ToString()); + "destination", dstSchema.ToString()); return TConclusionStatus::Fail("incorrect columns order in source set"); } - return TSchemaSubset(fieldIdx, dstSchema->num_fields()); + return TSchemaSubset(fieldIdx, dstSchema.num_fields()); } } // namespace TConclusion TColumnOperator::BuildSequentialSubset( - const std::shared_ptr& incoming, const std::shared_ptr& dstSchema) { + const std::shared_ptr& incoming, const NArrow::TSchemaLiteView& dstSchema) { return BuildSequentialSubsetImpl(incoming, dstSchema, DifferentColumnTypesPolicy); } namespace { template TConclusion> AdaptIncomingToDestinationExtImpl(const std::shared_ptr& incoming, - const std::shared_ptr& dstSchema, const std::function& checker, - const std::function& nameResolver, - const TColumnOperator::ECheckFieldTypesPolicy differentColumnTypesPolicy, + const TSchemaLiteView& dstSchema, const std::function& checker, + const std::function& nameResolver, const TColumnOperator::ECheckFieldTypesPolicy differentColumnTypesPolicy, const TColumnOperator::EAbsentFieldPolicy absentColumnPolicy) { struct TFieldData { ui32 Index; @@ -276,23 +271,22 @@ TConclusion> AdaptIncomingToDestinationExtImpl(c } }; AFL_VERIFY(incoming); - AFL_VERIFY(dstSchema); std::vector resultColumns; resultColumns.reserve(incoming->num_columns()); ui32 idx = 0; for (auto& srcField : incoming->schema()->fields()) { const int dstIndex = nameResolver(srcField->name()); if (dstIndex > -1) { - const auto& dstField = dstSchema->GetFieldByIndexVerified(dstIndex); + const auto& dstField = dstSchema.GetFieldByIndexVerified(dstIndex); switch (differentColumnTypesPolicy) { case TColumnOperator::ECheckFieldTypesPolicy::Verify: - AFL_VERIFY(dstField->Equals(srcField))("event", "cannot_use_incoming_batch")("reason", "invalid_column_type")( + AFL_VERIFY(dstField->type()->Equals(srcField->type()))("event", "cannot_use_incoming_batch")("reason", "invalid_column_type")( "dst_column", dstField->ToString(true))("src_column", srcField->ToString(true)); break; case TColumnOperator::ECheckFieldTypesPolicy::Error: - if (!dstField->Equals(srcField)) { + if (!dstField->type()->Equals(srcField->type())) { AFL_ERROR(NKikimrServices::ARROW_HELPER)("event", "cannot_use_incoming_batch")("reason", "invalid_column_type")( - "dst_column", dstField->ToString(true))("src_column", srcField->ToString(true)); + "dst_column", dstField->type()->ToString())("src_column", srcField->type()->ToString())("name", srcField->name()); return TConclusionStatus::Fail("incompatible column types for '" + dstField->name() + "'"); } break; @@ -325,14 +319,14 @@ TConclusion> AdaptIncomingToDestinationExtImpl(c columns.reserve(resultColumns.size()); fields.reserve(resultColumns.size()); for (auto&& i : resultColumns) { - fields.emplace_back(dstSchema->field(i.Index)); + fields.emplace_back(dstSchema.field(i.Index)); columns.emplace_back(i.Column); } return NAdapter::TDataBuilderPolicy::Build(std::make_shared(fields), std::move(columns), incoming->num_rows()); } } // namespace TConclusion> TColumnOperator::AdaptIncomingToDestinationExt( - const std::shared_ptr& incoming, const std::shared_ptr& dstSchema, + const std::shared_ptr& incoming, const TSchemaLiteView& dstSchema, const std::function& checker, const std::function& nameResolver) const { return AdaptIncomingToDestinationExtImpl(incoming, dstSchema, checker, nameResolver, DifferentColumnTypesPolicy, AbsentColumnPolicy); } diff --git a/ydb/core/formats/arrow/process_columns.h b/ydb/core/formats/arrow/process_columns.h index c4b418ada529..2eb7e77330b7 100644 --- a/ydb/core/formats/arrow/process_columns.h +++ b/ydb/core/formats/arrow/process_columns.h @@ -8,6 +8,7 @@ namespace NKikimr::NArrow { class TSchemaSubset; class TSchemaLite; +class TSchemaLiteView; class TColumnOperator { public: @@ -59,7 +60,7 @@ class TColumnOperator { } TConclusion> AdaptIncomingToDestinationExt(const std::shared_ptr& incoming, - const std::shared_ptr& dstSchema, const std::function& checker, + const TSchemaLiteView& dstSchema, const std::function& checker, const std::function& nameResolver) const; std::shared_ptr Extract( @@ -73,7 +74,7 @@ class TColumnOperator { std::shared_ptr Extract(const std::shared_ptr& incoming, const std::vector& columnNames); TConclusion BuildSequentialSubset( - const std::shared_ptr& incoming, const std::shared_ptr& dstSchema); + const std::shared_ptr& incoming, const NArrow::TSchemaLiteView& dstSchema); TConclusion> Adapt( const std::shared_ptr& incoming, const std::shared_ptr& dstSchema, TSchemaSubset* subset = nullptr); diff --git a/ydb/core/formats/arrow/program.cpp b/ydb/core/formats/arrow/program.cpp index 50071d8490e3..974ee6bbacb9 100644 --- a/ydb/core/formats/arrow/program.cpp +++ b/ydb/core/formats/arrow/program.cpp @@ -554,9 +554,6 @@ arrow::Status TDatumBatch::AddColumn(const std::string& name, arrow::Datum&& col } auto field = arrow::field(name, column.type()); - if (!field || !field->type()->Equals(column.type())) { - return arrow::Status::Invalid("Cannot create field."); - } if (!column.is_scalar() && column.length() != Rows) { return arrow::Status::Invalid("Wrong column length."); } @@ -965,9 +962,20 @@ arrow::Result> TProgramStep::BuildFilter( if (Filters.empty()) { return nullptr; } - std::vector> batches = NArrow::SliceToRecordBatches(t->BuildTableVerified(GetColumnsInUsage(true))); + auto table = t->BuildTableVerified(GetColumnsInUsage(true)); + arrow::TableBatchReader reader(*table); NArrow::TColumnFilter fullLocal = NArrow::TColumnFilter::BuildAllowFilter(); - for (auto&& rb : batches) { + std::shared_ptr rb; + while (true) { + { + auto statusRead = reader.ReadNext(&rb); + if (!statusRead.ok()) { + return statusRead; + } + } + if (!rb) { + break; + } auto datumBatch = TDatumBatch::FromRecordBatch(rb); { auto statusAssign = ApplyAssignes(*datumBatch, NArrow::GetCustomExecContext()); @@ -977,10 +985,11 @@ arrow::Result> TProgramStep::BuildFilter( } NArrow::TColumnFilter local = NArrow::TColumnFilter::BuildAllowFilter(); NArrow::TStatusValidator::Validate(MakeCombinedFilter(*datumBatch, local)); - AFL_VERIFY(local.Size() == datumBatch->GetRecordsCount())("local", local.Size())("datum", datumBatch->GetRecordsCount()); + AFL_VERIFY(local.GetRecordsCountVerified() == datumBatch->GetRecordsCount())("local", local.GetRecordsCount())( + "datum", datumBatch->GetRecordsCount()); fullLocal.Append(local); } - AFL_VERIFY(fullLocal.Size() == t->num_rows())("filter", fullLocal.Size())("t", t->num_rows()); + AFL_VERIFY(fullLocal.GetRecordsCountVerified() == t->num_rows())("filter", fullLocal.GetRecordsCountVerified())("t", t->num_rows()); return std::make_shared(std::move(fullLocal)); } diff --git a/ydb/core/formats/arrow/reader/merger.cpp b/ydb/core/formats/arrow/reader/merger.cpp index 16b9733ad4c0..06b5d2be4b27 100644 --- a/ydb/core/formats/arrow/reader/merger.cpp +++ b/ydb/core/formats/arrow/reader/merger.cpp @@ -105,7 +105,7 @@ std::shared_ptr TMergePartialStream::SingleSourceDrain(const TSort *lastResultPosition = TCursor(keys, 0, SortSchema->field_names()); } if (SortHeap.Current().GetFilter()) { - SortHeap.Current().GetFilter()->Apply(result, pos.GetPosition() + (include ? 0 : 1), resultSize); + SortHeap.Current().GetFilter()->Apply(result, TColumnFilter::TApplyContext(pos.GetPosition() + (include ? 0 : 1), resultSize)); } } else { result = SortHeap.Current().GetKeyColumns().SliceData(startPos, resultSize); @@ -114,7 +114,7 @@ std::shared_ptr TMergePartialStream::SingleSourceDrain(const TSort *lastResultPosition = TCursor(keys, keys->num_rows() - 1, SortSchema->field_names()); } if (SortHeap.Current().GetFilter()) { - SortHeap.Current().GetFilter()->Apply(result, startPos, resultSize); + SortHeap.Current().GetFilter()->Apply(result, TColumnFilter::TApplyContext(startPos, resultSize)); } } if (!result || !result->num_rows()) { @@ -154,6 +154,7 @@ void TMergePartialStream::DrainCurrentPosition(TRecordBatchBuilder* builder, std Y_ABORT_UNLESS(SortHeap.Size()); Y_ABORT_UNLESS(!SortHeap.Current().IsControlPoint()); if (!SortHeap.Current().IsDeleted()) { +// AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("key_add", SortHeap.Current().GetKeyColumns().DebugJson().GetStringRobust()); if (builder) { builder->AddRecord(SortHeap.Current().GetKeyColumns()); } @@ -161,6 +162,8 @@ void TMergePartialStream::DrainCurrentPosition(TRecordBatchBuilder* builder, std *resultScanData = SortHeap.Current().GetKeyColumns().GetSorting(); *resultPosition = SortHeap.Current().GetKeyColumns().GetPosition(); } + } else { +// AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("key_skip", SortHeap.Current().GetKeyColumns().DebugJson().GetStringRobust()); } CheckSequenceInDebug(SortHeap.Current().GetKeyColumns()); const ui64 startPosition = SortHeap.Current().GetKeyColumns().GetPosition(); @@ -169,6 +172,7 @@ void TMergePartialStream::DrainCurrentPosition(TRecordBatchBuilder* builder, std bool isFirst = true; while (SortHeap.Size() && (isFirst || SortHeap.Current().GetKeyColumns().Compare(*startSorting, startPosition) == std::partial_ordering::equivalent)) { if (!isFirst) { +// AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("key_skip1", SortHeap.Current().GetKeyColumns().DebugJson().GetStringRobust()); auto& anotherIterator = SortHeap.Current(); if (PossibleSameVersionFlag) { AFL_VERIFY(anotherIterator.GetVersionColumns().Compare(*startVersion, startPosition) != std::partial_ordering::greater) diff --git a/ydb/core/formats/arrow/reader/position.cpp b/ydb/core/formats/arrow/reader/position.cpp index b728405769d7..e4f4d799a5a1 100644 --- a/ydb/core/formats/arrow/reader/position.cpp +++ b/ydb/core/formats/arrow/reader/position.cpp @@ -133,6 +133,15 @@ TSortableScanData::TSortableScanData( BuildPosition(position); } +TSortableScanData::TSortableScanData( + const ui64 position, const std::shared_ptr& batch) { + for (auto&& c : batch->columns()) { + Columns.emplace_back(std::make_shared(c)); + } + Fields = batch->schema()->fields(); + BuildPosition(position); +} + TSortableScanData::TSortableScanData(const ui64 position, const std::shared_ptr& batch, const std::vector& columns) { for (auto&& i : columns) { auto c = batch->GetColumnByName(i); diff --git a/ydb/core/formats/arrow/reader/position.h b/ydb/core/formats/arrow/reader/position.h index 78233e50b4a5..f7f6c41dc208 100644 --- a/ydb/core/formats/arrow/reader/position.h +++ b/ydb/core/formats/arrow/reader/position.h @@ -75,6 +75,7 @@ class TSortableScanData { return StartPosition <= position && position < FinishPosition; } public: + TSortableScanData(const ui64 position, const std::shared_ptr& batch); TSortableScanData(const ui64 position, const std::shared_ptr& batch, const std::vector& columns); TSortableScanData(const ui64 position, const std::shared_ptr& batch, const std::vector& columns); TSortableScanData(const ui64 position, const std::shared_ptr& batch, const std::vector& columns); @@ -357,6 +358,19 @@ class TSortableBatchPosition { Y_ABORT_UNLESS(Sorting->GetColumns().size()); } + template + TSortableBatchPosition(const std::shared_ptr& batch, const ui32 position, const bool reverseSort) + : Position(position) + , ReverseSort(reverseSort) { + Y_ABORT_UNLESS(batch); + Y_ABORT_UNLESS(batch->num_rows()); + RecordsCount = batch->num_rows(); + AFL_VERIFY(Position < RecordsCount)("position", Position)("count", RecordsCount); + Sorting = std::make_shared(Position, batch); + Y_DEBUG_ABORT_UNLESS(batch->ValidateFull().ok()); + Y_ABORT_UNLESS(Sorting->GetColumns().size()); + } + std::partial_ordering GetReverseForCompareResult(const std::partial_ordering directResult) const { if (directResult == std::partial_ordering::less) { return std::partial_ordering::greater; @@ -451,6 +465,8 @@ class TIntervalPositions { private: std::vector Positions; public: + using const_iterator = std::vector::const_iterator; + bool IsEmpty() const { return Positions.empty(); } @@ -459,6 +475,16 @@ class TIntervalPositions { return Positions.begin(); } + TString DebugString() const { + TStringBuilder sb; + sb << "["; + for (auto&& p : Positions) { + sb << p.DebugJson().GetStringRobust() << ";"; + } + sb << "]"; + return sb; + } + std::vector::const_iterator end() const { return Positions.end(); } @@ -484,19 +510,17 @@ class TIntervalPositions { void AddPosition(TIntervalPosition&& intervalPosition) { if (Positions.size()) { - AFL_VERIFY(Positions.back() < intervalPosition)("back", Positions.back().DebugJson())("pos", intervalPosition.DebugJson()); + AFL_VERIFY_DEBUG(Positions.back() < intervalPosition)("back", Positions.back().DebugJson())("pos", intervalPosition.DebugJson()); } Positions.emplace_back(std::move(intervalPosition)); } void AddPosition(TSortableBatchPosition&& position, const bool includePositionToLeftInterval) { - TIntervalPosition intervalPosition(std::move(position), includePositionToLeftInterval); - AddPosition(std::move(intervalPosition)); + AddPosition(TIntervalPosition(std::move(position), includePositionToLeftInterval)); } void AddPosition(const TSortableBatchPosition& position, const bool includePositionToLeftInterval) { - TIntervalPosition intervalPosition(position, includePositionToLeftInterval); - AddPosition(std::move(intervalPosition)); + AddPosition(TIntervalPosition(position, includePositionToLeftInterval)); } }; @@ -568,7 +592,11 @@ class TRWSortableBatchPosition: public TSortableBatchPosition, public TMoveOnly result.emplace_back(nullptr); return result; } + if (!it.IsValid()) { + return { batch }; + } TRWSortableBatchPosition pos(batch, 0, columnNames, {}, false); + it.SkipToUpper(pos); bool batchFinished = false; i64 recordsCountSplitted = 0; for (; it.IsValid() && !batchFinished; it.Next()) { @@ -624,6 +652,10 @@ class TRWSortableBatchPosition: public TSortableBatchPosition, public TMoveOnly const auto& CurrentPosition() const { return Current->first; } + + void SkipToUpper(const TSortableBatchPosition& /*toPos*/) { + return; + } }; template @@ -654,6 +686,10 @@ class TRWSortableBatchPosition: public TSortableBatchPosition, public TMoveOnly const auto& CurrentPosition() const { return *Current; } + + void SkipToUpper(const TSortableBatchPosition& /*toPos*/) { + return; + } }; template @@ -662,6 +698,49 @@ class TRWSortableBatchPosition: public TSortableBatchPosition, public TMoveOnly return SplitByBorders(batch, columnNames, it); } + class TIntervalPointsIterator { + private: + TIntervalPositions::const_iterator Current; + TIntervalPositions::const_iterator End; + + public: + TIntervalPointsIterator(const TIntervalPositions& container) + : Current(container.begin()) + , End(container.end()) { + } + + bool IsValid() const { + return Current != End; + } + + void Next() { + ++Current; + } + + const auto& CurrentPosition() const { + return Current->GetPosition(); + } + + struct TComparator { + bool operator()(const TIntervalPosition& pos, const TSortableBatchPosition& value) const { + return pos.GetPosition() < value; + } + bool operator()(const TSortableBatchPosition& value, const TIntervalPosition& pos) const { + return value < pos.GetPosition(); + } + + }; + + void SkipToUpper(const TSortableBatchPosition& toPos) { + Current = std::upper_bound(Current, End, toPos, TComparator()); + } + }; + + static std::vector> SplitByBordersInIntervalPositions( + const std::shared_ptr& batch, const std::vector& columnNames, const TIntervalPositions& container) { + TIntervalPointsIterator it(container); + return SplitByBorders(batch, columnNames, it); + } }; } diff --git a/ydb/core/formats/arrow/save_load/loader.cpp b/ydb/core/formats/arrow/save_load/loader.cpp index c9328f751d4a..24b01d7ff759 100644 --- a/ydb/core/formats/arrow/save_load/loader.cpp +++ b/ydb/core/formats/arrow/save_load/loader.cpp @@ -51,9 +51,24 @@ std::shared_ptr TColumnLoader::ApplyRawVerified(const TStrin return TStatusValidator::GetValid(Apply(data)); } +TChunkConstructionData TColumnLoader::BuildAccessorContext(const ui32 recordsCount) const { + return TChunkConstructionData(recordsCount, DefaultValue, ResultField->type()); +} + +TConclusion> TColumnLoader::ApplyConclusion(const TString& dataStr, const ui32 recordsCount) const { + auto result = Apply(dataStr); + if (result.ok()) { + return BuildAccessor(*result, BuildAccessorContext(recordsCount)); + } else { + AFL_ERROR(NKikimrServices::ARROW_HELPER)("event", "cannot_parse_blob")("data_size", dataStr.size())( + "expected_records_count", recordsCount)("problem", result.status().ToString()); + return TConclusionStatus::Fail(result.status().ToString()); + } +} + std::shared_ptr TColumnLoader::ApplyVerified(const TString& dataStr, const ui32 recordsCount) const { auto data = TStatusValidator::GetValid(Apply(dataStr)); - return BuildAccessor(data, TChunkConstructionData(recordsCount, DefaultValue, ResultField->type())); + return BuildAccessor(data, BuildAccessorContext(recordsCount)); } std::shared_ptr TColumnLoader::BuildAccessor( @@ -65,4 +80,19 @@ std::shared_ptr TColumnLoader::BuildD return AccessorConstructor->ConstructDefault(TChunkConstructionData(recordsCount, DefaultValue, ResultField->type())).DetachResult(); } +bool TColumnLoader::IsEqualTo(const TColumnLoader& item) const { + if (!!Transformer != !!item.Transformer) { + return false; + } else if (!!Transformer && !Transformer->IsEqualTo(*item.Transformer)) { + return false; + } + if (!Serializer.IsEqualTo(item.Serializer)) { + return false; + } + if (!AccessorConstructor.IsEqualTo(item.AccessorConstructor)) { + return false; + } + return true; +} + } // namespace NKikimr::NArrow::NAccessor diff --git a/ydb/core/formats/arrow/save_load/loader.h b/ydb/core/formats/arrow/save_load/loader.h index 2d3119ac3fa8..64ecb6c21168 100644 --- a/ydb/core/formats/arrow/save_load/loader.h +++ b/ydb/core/formats/arrow/save_load/loader.h @@ -25,17 +25,7 @@ class TColumnLoader { public: std::shared_ptr BuildDefaultAccessor(const ui32 recordsCount) const; - bool IsEqualTo(const TColumnLoader& item) const { - if (!!Transformer != !!item.Transformer) { - return false; - } else if (!!Transformer && !Transformer->IsEqualTo(*item.Transformer)) { - return false; - } - if (!Serializer.IsEqualTo(item.Serializer)) { - return false; - } - return true; - } + bool IsEqualTo(const TColumnLoader& item) const; TString DebugString() const; @@ -49,7 +39,9 @@ class TColumnLoader { const std::shared_ptr& GetField() const; + TChunkConstructionData BuildAccessorContext(const ui32 recordsCount) const; std::shared_ptr ApplyVerified(const TString& data, const ui32 expectedRecordsCount) const; + TConclusion> ApplyConclusion(const TString& data, const ui32 expectedRecordsCount) const; std::shared_ptr ApplyRawVerified(const TString& data) const; }; diff --git a/ydb/core/formats/arrow/serializer/native.cpp b/ydb/core/formats/arrow/serializer/native.cpp index 4b90286001d2..3432b6090983 100644 --- a/ydb/core/formats/arrow/serializer/native.cpp +++ b/ydb/core/formats/arrow/serializer/native.cpp @@ -97,7 +97,7 @@ TString TNativeSerializer::DoSerializePayload(const std::shared_ptrschema()).ok()); + AFL_VERIFY_DEBUG(Deserialize(str, batch->schema()).ok()); AFL_DEBUG(NKikimrServices::ARROW_HELPER)("event", "serialize")("size", str.size())("columns", batch->schema()->num_fields()); return str; } @@ -111,9 +111,8 @@ NKikimr::TConclusion> TNativeSerializer::Bui const int levelMin = codec->minimum_compression_level(); const int levelMax = codec->maximum_compression_level(); if (levelDef < levelMin || levelMax < levelDef) { - return TConclusionStatus::Fail( - TStringBuilder() << "incorrect level for codec. have to be: [" << levelMin << ":" << levelMax << "]" - ); + return TConclusionStatus::Fail(TStringBuilder() << "incorrect level for codec `" << arrow::util::Codec::GetCodecAsString(cType) + << "`. have to be: [" << levelMin << ":" << levelMax << "]"); } std::shared_ptr codecPtr = std::move(NArrow::TStatusValidator::GetValid(arrow::util::Codec::Create(cType, levelDef))); return codecPtr; @@ -178,8 +177,14 @@ NKikimr::TConclusionStatus TNativeSerializer::DoDeserializeFromProto(const NKiki } void TNativeSerializer::DoSerializeToProto(NKikimrSchemeOp::TOlapColumn::TSerializer& proto) const { - proto.MutableArrowCompression()->SetCodec(NArrow::CompressionToProto(Options.codec->compression_type())); - proto.MutableArrowCompression()->SetLevel(Options.codec->compression_level()); + if (Options.codec) { + proto.MutableArrowCompression()->SetCodec(NArrow::CompressionToProto(Options.codec->compression_type())); + if (arrow::util::Codec::SupportsCompressionLevel(Options.codec->compression_type())) { + proto.MutableArrowCompression()->SetLevel(Options.codec->compression_level()); + } + } else { + proto.MutableArrowCompression()->SetCodec(NArrow::CompressionToProto(arrow::Compression::UNCOMPRESSED)); + } } } diff --git a/ydb/core/formats/arrow/serializer/native.h b/ydb/core/formats/arrow/serializer/native.h index 260b1f73c324..092ce9d705e4 100644 --- a/ydb/core/formats/arrow/serializer/native.h +++ b/ydb/core/formats/arrow/serializer/native.h @@ -62,6 +62,18 @@ class TNativeSerializer: public ISerializer { virtual void DoSerializeToProto(NKikimrSchemeOp::TOlapColumn::TSerializer& proto) const override; public: + static std::shared_ptr GetUncompressed() { + static std::shared_ptr result = + std::make_shared(arrow::Compression::UNCOMPRESSED); + return result; + } + + static std::shared_ptr GetFast() { + static std::shared_ptr result = + std::make_shared(arrow::Compression::LZ4_FRAME); + return result; + } + virtual TString GetClassName() const override { return GetClassNameStatic(); } @@ -87,6 +99,20 @@ class TNativeSerializer: public ISerializer { Options.use_threads = false; } + + arrow::Compression::type GetCodecType() const { + if (Options.codec) { + return Options.codec->compression_type(); + } + return arrow::Compression::type::UNCOMPRESSED; + } + + std::optional GetCodecLevel() const { + if (Options.codec && arrow::util::Codec::SupportsCompressionLevel(Options.codec->compression_type())) { + return Options.codec->compression_level(); + } + return {}; + } }; } diff --git a/ydb/core/formats/arrow/serializer/parsing.cpp b/ydb/core/formats/arrow/serializer/parsing.cpp index 8a394073bdd8..4b57d86da83b 100644 --- a/ydb/core/formats/arrow/serializer/parsing.cpp +++ b/ydb/core/formats/arrow/serializer/parsing.cpp @@ -7,6 +7,18 @@ std::string CompressionToString(const arrow::Compression::type compression) { return arrow::util::Codec::GetCodecAsString(compression); } +std::string CompressionToString(const NKikimrSchemeOp::EColumnCodec compression) { + switch (compression) { + case NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain: + return "off"; + case NKikimrSchemeOp::EColumnCodec::ColumnCodecZSTD: + return "zstd"; + case NKikimrSchemeOp::EColumnCodec::ColumnCodecLZ4: + return "lz4"; + } + return ""; +} + std::optional CompressionFromString(const std::string& compressionStr) { auto result = arrow::util::Codec::GetCompressionType(compressionStr); if (!result.ok()) { diff --git a/ydb/core/formats/arrow/serializer/parsing.h b/ydb/core/formats/arrow/serializer/parsing.h index e1dbbf9badd9..4ea4371fa50a 100644 --- a/ydb/core/formats/arrow/serializer/parsing.h +++ b/ydb/core/formats/arrow/serializer/parsing.h @@ -9,9 +9,9 @@ namespace NKikimr::NArrow { std::string CompressionToString(const arrow::Compression::type compression); +std::string CompressionToString(const NKikimrSchemeOp::EColumnCodec compression); std::optional CompressionFromString(const std::string& compressionStr); NKikimrSchemeOp::EColumnCodec CompressionToProto(const arrow::Compression::type compression); std::optional CompressionFromProto(const NKikimrSchemeOp::EColumnCodec compression); - } diff --git a/ydb/core/formats/arrow/serializer/utils.cpp b/ydb/core/formats/arrow/serializer/utils.cpp new file mode 100644 index 000000000000..b33f7bc58a5c --- /dev/null +++ b/ydb/core/formats/arrow/serializer/utils.cpp @@ -0,0 +1,29 @@ +#include "parsing.h" +#include "utils.h" + +#include + +#include + +namespace NKikimr::NArrow { +bool SupportsCompressionLevel(const arrow::Compression::type compression) { + return arrow::util::Codec::SupportsCompressionLevel(compression); +} + +bool SupportsCompressionLevel(const NKikimrSchemeOp::EColumnCodec compression) { + return SupportsCompressionLevel(CompressionFromProto(compression).value()); +} + +std::optional MinimumCompressionLevel(const arrow::Compression::type compression) { + if (!SupportsCompressionLevel(compression)) { + return {}; + } + return NArrow::TStatusValidator::GetValid(arrow::util::Codec::MinimumCompressionLevel(compression)); +} +std::optional MaximumCompressionLevel(const arrow::Compression::type compression) { + if (!SupportsCompressionLevel(compression)) { + return {}; + } + return NArrow::TStatusValidator::GetValid(arrow::util::Codec::MaximumCompressionLevel(compression)); +} +} diff --git a/ydb/core/formats/arrow/serializer/utils.h b/ydb/core/formats/arrow/serializer/utils.h new file mode 100644 index 000000000000..954bc6dee9d7 --- /dev/null +++ b/ydb/core/formats/arrow/serializer/utils.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include +#include + +#include + +namespace NKikimr::NArrow { +bool SupportsCompressionLevel(const arrow::Compression::type compression); +bool SupportsCompressionLevel(const NKikimrSchemeOp::EColumnCodec compression); + +std::optional MinimumCompressionLevel(const arrow::Compression::type compression); +std::optional MaximumCompressionLevel(const arrow::Compression::type compression); +} diff --git a/ydb/core/formats/arrow/serializer/ya.make b/ydb/core/formats/arrow/serializer/ya.make index 8c9fb49fe08f..5100ce980ae5 100644 --- a/ydb/core/formats/arrow/serializer/ya.make +++ b/ydb/core/formats/arrow/serializer/ya.make @@ -13,6 +13,7 @@ SRCS( GLOBAL native.cpp stream.cpp parsing.cpp + utils.cpp ) END() diff --git a/ydb/core/formats/arrow/splitter/scheme_info.h b/ydb/core/formats/arrow/splitter/scheme_info.h index 0bb30e97300a..a65c197d1011 100644 --- a/ydb/core/formats/arrow/splitter/scheme_info.h +++ b/ydb/core/formats/arrow/splitter/scheme_info.h @@ -22,8 +22,6 @@ class ISchemaDetailInfo { NAccessor::TColumnSaver GetColumnSaver(const ui32 columnId) const; virtual std::shared_ptr GetField(const ui32 columnId) const = 0; virtual std::optional GetColumnSerializationStats(const ui32 columnId) const = 0; - virtual bool NeedMinMaxForColumn(const ui32 columnId) const = 0; - virtual bool IsSortedColumn(const ui32 columnId) const = 0; virtual std::optional GetBatchSerializationStats(const std::shared_ptr& rb) const = 0; }; } // namespace NKikimr::NArrow::NSplitter diff --git a/ydb/core/formats/arrow/ut/ut_arrow.cpp b/ydb/core/formats/arrow/ut/ut_arrow.cpp index b12fc5561b12..e5abf2701d41 100644 --- a/ydb/core/formats/arrow/ut/ut_arrow.cpp +++ b/ydb/core/formats/arrow/ut/ut_arrow.cpp @@ -178,7 +178,7 @@ struct TDataRow { std::vector cells(value.Cells().data(), value.Cells().data() + value.Cells().size()); auto binaryJson = NBinaryJson::SerializeToBinaryJson(TStringBuf(JsonDocument.data(), JsonDocument.size())); - UNIT_ASSERT(binaryJson.Defined()); + UNIT_ASSERT(binaryJson.IsSuccess()); cells[19] = TCell(binaryJson->Data(), binaryJson->Size()); return TOwnedCellVec(cells); diff --git a/ydb/core/formats/arrow/ut/ut_column_filter.cpp b/ydb/core/formats/arrow/ut/ut_column_filter.cpp index 77fce7344d2d..5a2ea779626c 100644 --- a/ydb/core/formats/arrow/ut/ut_column_filter.cpp +++ b/ydb/core/formats/arrow/ut/ut_column_filter.cpp @@ -11,7 +11,7 @@ Y_UNIT_TEST_SUITE(ColumnFilter) { TColumnFilter filter2({true, true, true, true, false}); auto result = filter1.Or(filter2); - UNIT_ASSERT_VALUES_EQUAL(result.Size(), 5); + UNIT_ASSERT_VALUES_EQUAL(result.GetRecordsCountVerified(), 5); auto resultVec = result.BuildSimpleFilter(); UNIT_ASSERT_VALUES_EQUAL(JoinSeq(",", resultVec), "1,1,1,1,0"); } @@ -21,7 +21,7 @@ Y_UNIT_TEST_SUITE(ColumnFilter) { TColumnFilter filter2({true, true, true, true, false}); auto result = filter1.CombineSequentialAnd(filter2); - UNIT_ASSERT_VALUES_EQUAL(result.Size(), 7); + UNIT_ASSERT_VALUES_EQUAL(result.GetRecordsCountVerified(), 7); auto resultVec = result.BuildSimpleFilter(); UNIT_ASSERT_VALUES_EQUAL(JoinSeq(",", resultVec), "1,1,1,0,1,0,0"); } diff --git a/ydb/core/grpc_services/rpc_create_table.cpp b/ydb/core/grpc_services/rpc_create_table.cpp index 88fed4de46d3..ab30d5c750b9 100644 --- a/ydb/core/grpc_services/rpc_create_table.cpp +++ b/ydb/core/grpc_services/rpc_create_table.cpp @@ -109,7 +109,6 @@ class TCreateTableRPC : public TRpcSchemeRequestActorSetName(tableName); auto schema = tableDesc->MutableSchema(); - schema->SetEngine(NKikimrSchemeOp::EColumnTableEngine::COLUMN_ENGINE_REPLACING_TIMESERIES); TString error; if (!FillColumnDescription(*tableDesc, req.columns(), code, error)) { @@ -136,7 +135,6 @@ class TCreateTableRPC : public TRpcSchemeRequestActorMutableTtlSettings()->SetUseTiering(req.tiering()); return true; } diff --git a/ydb/core/grpc_services/rpc_log_store.cpp b/ydb/core/grpc_services/rpc_log_store.cpp index 10d9ac929f5f..10e679cd7172 100644 --- a/ydb/core/grpc_services/rpc_log_store.cpp +++ b/ydb/core/grpc_services/rpc_log_store.cpp @@ -120,7 +120,6 @@ bool ConvertSchemaFromPublicToInternal(const Ydb::LogStore::Schema& from, NKikim } } - to.SetEngine(NKikimrSchemeOp::COLUMN_ENGINE_REPLACING_TIMESERIES); status = {}; return true; } @@ -128,11 +127,6 @@ bool ConvertSchemaFromPublicToInternal(const Ydb::LogStore::Schema& from, NKikim bool ConvertSchemaFromInternalToPublic(const NKikimrSchemeOp::TColumnTableSchema& from, Ydb::LogStore::Schema& to, Ydb::StatusIds::StatusCode& status, TString& error) { - if (from.GetEngine() != NKikimrSchemeOp::COLUMN_ENGINE_REPLACING_TIMESERIES) { - status = Ydb::StatusIds::INTERNAL_ERROR; - error = TStringBuilder() << "Unexpected table engine: " << NKikimrSchemeOp::EColumnTableEngine_Name(from.GetEngine()); - return false; - } to.mutable_primary_key()->CopyFrom(from.GetKeyColumnNames()); for (const auto& column : from.GetColumns()) { auto* col = to.add_columns(); @@ -440,8 +434,6 @@ class TCreateLogTableRPC : public TRpcSchemeRequestActorMutableTtlSettings()->MutableEnabled(), req->ttl_settings(), status, error)) { return Reply(status, error, NKikimrIssues::TIssuesIds::DEFAULT_ERROR, ctx); } - } else if (req->has_tiering_settings()) { - create->MutableTtlSettings()->SetUseTiering(req->tiering_settings().tiering_id()); } create->SetColumnShardCount(req->shards_count()); @@ -514,29 +506,8 @@ class TDescribeLogTableRPC : public TRpcSchemeRequestActormutable_date_type_column(); - outTTL.set_column_name(inTTL.GetColumnName()); - outTTL.set_expire_after_seconds(inTTL.GetExpireAfterSeconds()); - break; - } - - case NKikimrSchemeOp::TTTLSettings::UNIT_SECONDS: - case NKikimrSchemeOp::TTTLSettings::UNIT_MILLISECONDS: - case NKikimrSchemeOp::TTTLSettings::UNIT_MICROSECONDS: - case NKikimrSchemeOp::TTTLSettings::UNIT_NANOSECONDS: { - auto& outTTL = *describeLogTableResult.mutable_ttl_settings()->mutable_value_since_unix_epoch(); - outTTL.set_column_name(inTTL.GetColumnName()); - outTTL.set_column_unit(static_cast(inTTL.GetColumnUnit())); - outTTL.set_expire_after_seconds(inTTL.GetExpireAfterSeconds()); - break; - } - - default: - break; + if (!FillTtlSettings(*describeLogTableResult.mutable_ttl_settings(), tableDescription.GetTtlSettings().GetEnabled(), status, error)) { + return Reply(status, error, NKikimrIssues::TIssuesIds::DEFAULT_ERROR, ctx); } } @@ -626,12 +597,6 @@ class TAlterLogTableRPC : public TRpcSchemeRequestActorMutableAlterTtlSettings()->MutableDisabled(); } - if (req->has_set_tiering_settings()) { - alter->MutableAlterTtlSettings()->SetUseTiering(req->set_tiering_settings().tiering_id()); - } else if (req->has_drop_tiering_settings()) { - alter->MutableAlterTtlSettings()->SetUseTiering(""); - } - ctx.Send(MakeTxProxyID(), proposeRequest.release()); } }; diff --git a/ydb/core/io_formats/cell_maker/cell_maker.cpp b/ydb/core/io_formats/cell_maker/cell_maker.cpp index fdc01a237f03..55ec0834bf9f 100644 --- a/ydb/core/io_formats/cell_maker/cell_maker.cpp +++ b/ydb/core/io_formats/cell_maker/cell_maker.cpp @@ -97,8 +97,13 @@ namespace { return false; } - result = NBinaryJson::SerializeToBinaryJson(unescaped); - return result.Defined(); + auto serializedJson = NBinaryJson::SerializeToBinaryJson(unescaped); + if (serializedJson.IsFail()) { + return false; + } + + result = serializedJson.DetachResult(); + return true; } template <> @@ -384,8 +389,8 @@ bool MakeCell(TCell& cell, const NJson::TJsonValue& value, NScheme::TTypeInfo ty case NScheme::NTypeIds::Json: return TCellMaker::MakeDirect(cell, NFormats::WriteJson(value), pool, err); case NScheme::NTypeIds::JsonDocument: - if (const auto& result = NBinaryJson::SerializeToBinaryJson(NFormats::WriteJson(value))) { - return TCellMaker, TStringBuf>::MakeDirect(cell, result, pool, err, &BinaryJsonToStringBuf); + if (auto result = NBinaryJson::SerializeToBinaryJson(NFormats::WriteJson(value)); result.IsSuccess()) { + return TCellMaker, TStringBuf>::MakeDirect(cell, result.DetachResult(), pool, err, &BinaryJsonToStringBuf); } else { return false; } diff --git a/ydb/core/kqp/common/kqp_tx.cpp b/ydb/core/kqp/common/kqp_tx.cpp index d0c40ba629eb..7dd9c26ef87c 100644 --- a/ydb/core/kqp/common/kqp_tx.cpp +++ b/ydb/core/kqp/common/kqp_tx.cpp @@ -207,7 +207,7 @@ bool NeedSnapshot(const TKqpTransactionContext& txCtx, const NYql::TKikimrConfig } } - if (txCtx.HasUncommittedChangesRead || AppData()->FeatureFlags.GetEnableForceImmediateEffectsExecution()) { + if (txCtx.NeedUncommittedChangesFlush || AppData()->FeatureFlags.GetEnableForceImmediateEffectsExecution()) { return true; } @@ -344,5 +344,81 @@ bool HasOltpTableWriteInTx(const NKqpProto::TKqpPhyQuery& physicalQuery) { return false; } +bool HasUncommittedChangesRead(THashSet& modifiedTables, const NKqpProto::TKqpPhyQuery& physicalQuery) { + auto getTable = [](const NKqpProto::TKqpPhyTableId& table) { + return NKikimr::TTableId(table.GetOwnerId(), table.GetTableId()); + }; + + for (size_t index = 0; index < physicalQuery.TransactionsSize(); ++index) { + const auto &tx = physicalQuery.GetTransactions()[index]; + for (const auto &stage : tx.GetStages()) { + for (const auto &tableOp : stage.GetTableOps()) { + switch (tableOp.GetTypeCase()) { + case NKqpProto::TKqpPhyTableOperation::kReadRange: + case NKqpProto::TKqpPhyTableOperation::kLookup: + case NKqpProto::TKqpPhyTableOperation::kReadRanges: { + if (modifiedTables.contains(getTable(tableOp.GetTable()))) { + return true; + } + break; + } + case NKqpProto::TKqpPhyTableOperation::kReadOlapRange: + case NKqpProto::TKqpPhyTableOperation::kUpsertRows: + case NKqpProto::TKqpPhyTableOperation::kDeleteRows: + modifiedTables.insert(getTable(tableOp.GetTable())); + break; + default: + YQL_ENSURE(false, "unexpected type"); + } + } + + for (const auto& input : stage.GetInputs()) { + switch (input.GetTypeCase()) { + case NKqpProto::TKqpPhyConnection::kStreamLookup: + if (modifiedTables.contains(getTable(input.GetStreamLookup().GetTable()))) { + return true; + } + break; + case NKqpProto::TKqpPhyConnection::kSequencer: + return true; + case NKqpProto::TKqpPhyConnection::kUnionAll: + case NKqpProto::TKqpPhyConnection::kMap: + case NKqpProto::TKqpPhyConnection::kHashShuffle: + case NKqpProto::TKqpPhyConnection::kBroadcast: + case NKqpProto::TKqpPhyConnection::kMapShard: + case NKqpProto::TKqpPhyConnection::kShuffleShard: + case NKqpProto::TKqpPhyConnection::kResult: + case NKqpProto::TKqpPhyConnection::kValue: + case NKqpProto::TKqpPhyConnection::kMerge: + case NKqpProto::TKqpPhyConnection::TYPE_NOT_SET: + break; + } + } + + for (const auto& source : stage.GetSources()) { + if (source.GetTypeCase() == NKqpProto::TKqpSource::kReadRangesSource) { + if (modifiedTables.contains(getTable(source.GetReadRangesSource().GetTable()))) { + return true; + } + } else { + return true; + } + } + + for (const auto& sink : stage.GetSinks()) { + if (sink.GetTypeCase() == NKqpProto::TKqpSink::kInternalSink) { + YQL_ENSURE(sink.GetInternalSink().GetSettings().Is()); + NKikimrKqp::TKqpTableSinkSettings settings; + YQL_ENSURE(sink.GetInternalSink().GetSettings().UnpackTo(&settings), "Failed to unpack settings"); + modifiedTables.insert(getTable(settings.GetTable())); + } else { + return true; + } + } + } + } + return false; +} + } // namespace NKqp } // namespace NKikimr diff --git a/ydb/core/kqp/common/kqp_tx.h b/ydb/core/kqp/common/kqp_tx.h index 5121ffb73af1..700e9075236a 100644 --- a/ydb/core/kqp/common/kqp_tx.h +++ b/ydb/core/kqp/common/kqp_tx.h @@ -165,6 +165,8 @@ class TShardIdToTableInfo { }; using TShardIdToTableInfoPtr = std::shared_ptr; +bool HasUncommittedChangesRead(THashSet& modifiedTables, const NKqpProto::TKqpPhyQuery& physicalQuery); + class TKqpTransactionContext : public NYql::TKikimrTransactionContextBase { public: explicit TKqpTransactionContext(bool implicit, const NMiniKQL::IFunctionRegistry* funcRegistry, @@ -232,6 +234,11 @@ class TKqpTransactionContext : public NYql::TKikimrTransactionContextBase { ParamsState = MakeIntrusive(); SnapshotHandle.Snapshot = IKqpGateway::TKqpSnapshot::InvalidSnapshot; HasImmediateEffects = false; + + HasOlapTable = false; + HasOltpTable = false; + HasTableWrite = false; + NeedUncommittedChangesFlush = false; } TKqpTransactionInfo GetInfo() const; @@ -267,7 +274,7 @@ class TKqpTransactionContext : public NYql::TKikimrTransactionContextBase { } bool ShouldExecuteDeferredEffects() const { - if (HasUncommittedChangesRead || HasOlapTable) { + if (NeedUncommittedChangesFlush || HasOlapTable) { return !DeferredEffects.Empty(); } @@ -296,13 +303,20 @@ class TKqpTransactionContext : public NYql::TKikimrTransactionContextBase { } bool CanDeferEffects() const { - if (HasUncommittedChangesRead || AppData()->FeatureFlags.GetEnableForceImmediateEffectsExecution() || HasOlapTable) { + if (NeedUncommittedChangesFlush || AppData()->FeatureFlags.GetEnableForceImmediateEffectsExecution() || HasOlapTable) { return false; } return true; } + void ApplyPhysicalQuery(const NKqpProto::TKqpPhyQuery& phyQuery) { + NeedUncommittedChangesFlush = HasUncommittedChangesRead(ModifiedTablesSinceLastFlush, phyQuery); + if (NeedUncommittedChangesFlush) { + ModifiedTablesSinceLastFlush.clear(); + } + } + public: struct TParamsState : public TThrRefBase { ui32 LastIndex = 0; @@ -333,6 +347,9 @@ class TKqpTransactionContext : public NYql::TKikimrTransactionContextBase { bool HasOltpTable = false; bool HasTableWrite = false; + bool NeedUncommittedChangesFlush = false; + THashSet ModifiedTablesSinceLastFlush; + TShardIdToTableInfoPtr ShardIdToTableInfo = std::make_shared(); }; diff --git a/ydb/core/kqp/compute_actor/kqp_compute_events.h b/ydb/core/kqp/compute_actor/kqp_compute_events.h index a9dd127a64b0..3142aca26400 100644 --- a/ydb/core/kqp/compute_actor/kqp_compute_events.h +++ b/ydb/core/kqp/compute_actor/kqp_compute_events.h @@ -53,6 +53,7 @@ struct TEvScanData: public NActors::TEventLocal> SplittedBatches; TOwnedCellVec LastKey; + NKikimrKqp::TEvKqpScanCursor LastCursorProto; TDuration CpuTime; TDuration WaitTime; ui32 PageFaults = 0; // number of page faults occurred when filling in this message @@ -120,6 +121,7 @@ struct TEvScanData: public NActors::TEventLocalFinished = pbEv->Record.GetFinished(); ev->RequestedBytesLimitReached = pbEv->Record.GetRequestedBytesLimitReached(); ev->LastKey = TOwnedCellVec(TSerializedCellVec(pbEv->Record.GetLastKey()).GetCells()); + ev->LastCursorProto = pbEv->Record.GetLastCursor(); if (pbEv->Record.HasAvailablePacks()) { ev->AvailablePacks = pbEv->Record.GetAvailablePacks(); } @@ -153,6 +155,7 @@ struct TEvScanData: public NActors::TEventLocalRecord.SetPageFaults(PageFaults); Remote->Record.SetPageFault(PageFault); Remote->Record.SetLastKey(TSerializedCellVec::Serialize(LastKey)); + *Remote->Record.MutableLastCursor() = LastCursorProto; if (AvailablePacks) { Remote->Record.SetAvailablePacks(*AvailablePacks); } diff --git a/ydb/core/kqp/compute_actor/kqp_compute_state.h b/ydb/core/kqp/compute_actor/kqp_compute_state.h index 74311641732a..b4b71f3b7262 100644 --- a/ydb/core/kqp/compute_actor/kqp_compute_state.h +++ b/ydb/core/kqp/compute_actor/kqp_compute_state.h @@ -38,6 +38,7 @@ struct TShardState: public TCommonRetriesState { bool SubscribedOnTablet = false; TActorId ActorId; TOwnedCellVec LastKey; + std::optional LastCursorProto; std::optional AvailablePacks; TString PrintLastKey(TConstArrayRef keyTypes) const; diff --git a/ydb/core/kqp/compute_actor/kqp_scan_compute_manager.h b/ydb/core/kqp/compute_actor/kqp_scan_compute_manager.h index 2d684d2f6b09..6cd810b9bc18 100644 --- a/ydb/core/kqp/compute_actor/kqp_scan_compute_manager.h +++ b/ydb/core/kqp/compute_actor/kqp_scan_compute_manager.h @@ -14,7 +14,8 @@ namespace NKikimr::NKqp::NScanPrivate { class IExternalObjectsProvider { public: - virtual std::unique_ptr BuildEvKqpScan(const ui32 scanId, const ui32 gen, const TSmallVec& ranges) const = 0; + virtual std::unique_ptr BuildEvKqpScan(const ui32 scanId, const ui32 gen, const TSmallVec& ranges, + const std::optional& cursor) const = 0; virtual const TVector& GetKeyColumnTypes() const = 0; }; @@ -61,7 +62,7 @@ class TShardScannerInfo { const auto& keyColumnTypes = externalObjectsProvider.GetKeyColumnTypes(); auto ranges = state.GetScanRanges(keyColumnTypes); - auto ev = externalObjectsProvider.BuildEvKqpScan(ScanId, Generation, ranges); + auto ev = externalObjectsProvider.BuildEvKqpScan(ScanId, Generation, ranges, state.LastCursorProto); AFL_DEBUG(NKikimrServices::KQP_COMPUTE)("event", "start_scanner")("tablet_id", TabletId)("generation", Generation) ("info", state.ToString(keyColumnTypes))("range", DebugPrintRanges(keyColumnTypes, ranges, *AppData()->TypeRegistry)) diff --git a/ydb/core/kqp/compute_actor/kqp_scan_fetcher_actor.cpp b/ydb/core/kqp/compute_actor/kqp_scan_fetcher_actor.cpp index 85b95c78733b..70fd0dc8cd34 100644 --- a/ydb/core/kqp/compute_actor/kqp_scan_fetcher_actor.cpp +++ b/ydb/core/kqp/compute_actor/kqp_scan_fetcher_actor.cpp @@ -37,8 +37,7 @@ TKqpScanFetcherActor::TKqpScanFetcherActor(const NKikimrKqp::TKqpSnapshot& snaps , ShardsScanningPolicy(shardsScanningPolicy) , Counters(counters) , InFlightShards(ScanId, *this) - , InFlightComputes(ComputeActorIds) -{ + , InFlightComputes(ComputeActorIds) { Y_UNUSED(traceId); AFL_ENSURE(!Meta.GetReads().empty()); AFL_ENSURE(Meta.GetTable().GetTableKind() != (ui32)ETableKind::SysView); @@ -130,19 +129,19 @@ void TKqpScanFetcherActor::HandleExecute(TEvKqpCompute::TEvScanData::TPtr& ev) { ("ScanId", ev->Get()->ScanId) ("Finished", ev->Get()->Finished) ("Lock", [&]() { - TStringBuilder builder; - for (const auto& lock : ev->Get()->LocksInfo.Locks) { - builder << lock.ShortDebugString(); - } - return builder; - }()) + TStringBuilder builder; + for (const auto& lock : ev->Get()->LocksInfo.Locks) { + builder << lock.ShortDebugString(); + } + return builder; + }()) ("BrokenLocks", [&]() { - TStringBuilder builder; - for (const auto& lock : ev->Get()->LocksInfo.BrokenLocks) { - builder << lock.ShortDebugString(); - } - return builder; - }()); + TStringBuilder builder; + for (const auto& lock : ev->Get()->LocksInfo.BrokenLocks) { + builder << lock.ShortDebugString(); + } + return builder; + }()); TInstant startTime = TActivationContext::Now(); if (ev->Get()->Finished) { @@ -350,11 +349,12 @@ void TKqpScanFetcherActor::HandleExecute(TEvTxProxySchemeCache::TEvResolveKeySet if (!state.LastKey.empty()) { PendingShards.front().LastKey = std::move(state.LastKey); - while(!PendingShards.empty() && PendingShards.front().GetScanRanges(KeyColumnTypes).empty()) { + while (!PendingShards.empty() && PendingShards.front().GetScanRanges(KeyColumnTypes).empty()) { CA_LOG_D("Nothing to read " << PendingShards.front().ToString(KeyColumnTypes)); auto readShard = std::move(PendingShards.front()); PendingShards.pop_front(); PendingShards.front().LastKey = std::move(readShard.LastKey); + PendingShards.front().LastCursorProto = std::move(readShard.LastCursorProto); } AFL_ENSURE(!PendingShards.empty()); @@ -406,7 +406,8 @@ bool TKqpScanFetcherActor::SendScanFinished() { return true; } -std::unique_ptr TKqpScanFetcherActor::BuildEvKqpScan(const ui32 scanId, const ui32 gen, const TSmallVec& ranges) const { +std::unique_ptr TKqpScanFetcherActor::BuildEvKqpScan(const ui32 scanId, const ui32 gen, + const TSmallVec& ranges, const std::optional& cursor) const { auto ev = std::make_unique(); ev->Record.SetLocalPathId(ScanDataMeta.TableId.PathId.LocalPathId); for (auto& column : ScanDataMeta.GetColumns()) { @@ -420,6 +421,9 @@ std::unique_ptr TKqpScanFetcherActor::BuildEv } } ev->Record.MutableSkipNullKeys()->CopyFrom(Meta.GetSkipNullKeys()); + if (cursor) { + *ev->Record.MutableScanCursor() = *cursor; + } auto protoRanges = ev->Record.MutableRanges(); protoRanges->Reserve(ranges.size()); @@ -486,10 +490,11 @@ void TKqpScanFetcherActor::ProcessPendingScanDataItem(TEvKqpCompute::TEvScanData AFL_ENSURE(state->ActorId == ev->Sender)("expected", state->ActorId)("got", ev->Sender); state->LastKey = std::move(msg.LastKey); + state->LastCursorProto = std::move(msg.LastCursorProto); const ui64 rowsCount = msg.GetRowsCount(); AFL_ENSURE(!LockTxId || !msg.LocksInfo.Locks.empty() || !msg.LocksInfo.BrokenLocks.empty()); AFL_ENSURE(LockTxId || (msg.LocksInfo.Locks.empty() && msg.LocksInfo.BrokenLocks.empty())); - AFL_DEBUG(NKikimrServices::KQP_COMPUTE)("action","got EvScanData")("rows", rowsCount)("finished", msg.Finished)("exceeded", msg.RequestedBytesLimitReached) + AFL_DEBUG(NKikimrServices::KQP_COMPUTE)("action", "got EvScanData")("rows", rowsCount)("finished", msg.Finished)("exceeded", msg.RequestedBytesLimitReached) ("scan", ScanId)("packs_to_send", InFlightComputes.GetPacksToSendCount()) ("from", ev->Sender)("shards remain", PendingShards.size()) ("in flight scans", InFlightShards.GetScansCount()) diff --git a/ydb/core/kqp/compute_actor/kqp_scan_fetcher_actor.h b/ydb/core/kqp/compute_actor/kqp_scan_fetcher_actor.h index 64d7041d7218..d513939b4e18 100644 --- a/ydb/core/kqp/compute_actor/kqp_scan_fetcher_actor.h +++ b/ydb/core/kqp/compute_actor/kqp_scan_fetcher_actor.h @@ -108,7 +108,8 @@ class TKqpScanFetcherActor: public NActors::TActorBootstrapped BuildEvKqpScan(const ui32 scanId, const ui32 gen, const TSmallVec& ranges) const override; + virtual std::unique_ptr BuildEvKqpScan(const ui32 scanId, const ui32 gen, + const TSmallVec& ranges, const std::optional& cursor) const override; virtual const TVector& GetKeyColumnTypes() const override { return KeyColumnTypes; } diff --git a/ydb/core/kqp/executer_actor/kqp_data_executer.cpp b/ydb/core/kqp/executer_actor/kqp_data_executer.cpp index 61890e443702..d2b81f080c7a 100644 --- a/ydb/core/kqp/executer_actor/kqp_data_executer.cpp +++ b/ydb/core/kqp/executer_actor/kqp_data_executer.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -840,36 +841,12 @@ class TKqpDataExecuter: public TKqpExecuterBaseGetIssueCode(), statusConclusion->GetIssueGeneralText())); } + return ReplyErrorAndDie(statusConclusion->GetYdbStatusCode(), issues); } void PQTabletError(const NKikimrPQ::TEvProposeTransactionResult& result) { diff --git a/ydb/core/kqp/federated_query/kqp_federated_query_actors.cpp b/ydb/core/kqp/federated_query/kqp_federated_query_actors.cpp index c6150636e5df..14243df2c784 100644 --- a/ydb/core/kqp/federated_query/kqp_federated_query_actors.cpp +++ b/ydb/core/kqp/federated_query/kqp_federated_query_actors.cpp @@ -19,18 +19,19 @@ class TDescribeSecretsActor: public NActors::TActorBootstrapped secretValues; secretValues.reserve(SecretIds.size()); for (const auto& secretId: SecretIds) { - TString secretValue; - const bool isFound = snapshot->GetSecretValue(NMetadata::NSecret::TSecretIdOrValue::BuildAsId(secretId), secretValue); - if (!isFound) { - if (!AskSent) { - AskSent = true; - Send(NMetadata::NProvider::MakeServiceId(SelfId().NodeId()), new NMetadata::NProvider::TEvAskSnapshot(GetSecretsSnapshotParser())); - } else { - CompleteAndPassAway(TEvDescribeSecretsResponse::TDescription(Ydb::StatusIds::BAD_REQUEST, { NYql::TIssue("secret with name '" + secretId.GetSecretId() + "' not found") })); - } - return; + auto secretValue = snapshot->GetSecretValue(NMetadata::NSecret::TSecretIdOrValue::BuildAsId(secretId)); + if (secretValue.IsSuccess()) { + secretValues.push_back(secretValue.DetachResult()); + continue; } - secretValues.push_back(secretValue); + + if (!AskSent) { + AskSent = true; + Send(NMetadata::NProvider::MakeServiceId(SelfId().NodeId()), new NMetadata::NProvider::TEvAskSnapshot(GetSecretsSnapshotParser())); + } else { + CompleteAndPassAway(TEvDescribeSecretsResponse::TDescription(Ydb::StatusIds::BAD_REQUEST, { NYql::TIssue("secret with name '" + secretId.GetSecretId() + "' not found") })); + } + return; } CompleteAndPassAway(TEvDescribeSecretsResponse::TDescription(secretValues)); diff --git a/ydb/core/kqp/gateway/behaviour/tablestore/operations/alter_column.cpp b/ydb/core/kqp/gateway/behaviour/tablestore/operations/alter_column.cpp index b000a2fd94a4..95bcb35c6883 100644 --- a/ydb/core/kqp/gateway/behaviour/tablestore/operations/alter_column.cpp +++ b/ydb/core/kqp/gateway/behaviour/tablestore/operations/alter_column.cpp @@ -1,4 +1,6 @@ #include "alter_column.h" +#include +#include namespace NKikimr::NKqp::NColumnshard { @@ -38,20 +40,22 @@ TConclusionStatus TAlterColumnOperation::DoDeserialize(NYql::TObjectSettingsImpl } void TAlterColumnOperation::DoSerializeScheme(NKikimrSchemeOp::TAlterColumnTableSchema& schemaData) const { - auto* column = schemaData.AddAlterColumns(); - column->SetName(ColumnName); - if (StorageId && !!*StorageId) { - column->SetStorageId(*StorageId); - } - if (!!Serializer) { - Serializer.SerializeToProto(*column->MutableSerializer()); - } - if (!!AccessorConstructor) { - *column->MutableDataAccessorConstructor() = AccessorConstructor.SerializeToProto(); - } - *column->MutableDictionaryEncoding() = DictionaryEncodingDiff.SerializeToProto(); - if (DefaultValue) { - column->SetDefaultValue(*DefaultValue); + for (auto&& i : StringSplitter(ColumnName).SplitBySet(", ").SkipEmpty().ToList()) { + auto* column = schemaData.AddAlterColumns(); + column->SetName(i); + if (StorageId && !!*StorageId) { + column->SetStorageId(*StorageId); + } + if (!!Serializer) { + Serializer.SerializeToProto(*column->MutableSerializer()); + } + if (!!AccessorConstructor) { + *column->MutableDataAccessorConstructor() = AccessorConstructor.SerializeToProto(); + } + *column->MutableDictionaryEncoding() = DictionaryEncodingDiff.SerializeToProto(); + if (DefaultValue) { + column->SetDefaultValue(*DefaultValue); + } } } diff --git a/ydb/core/kqp/gateway/behaviour/tablestore/operations/upsert_opt.cpp b/ydb/core/kqp/gateway/behaviour/tablestore/operations/upsert_opt.cpp index 80452d88d9a3..edd38142ba2b 100644 --- a/ydb/core/kqp/gateway/behaviour/tablestore/operations/upsert_opt.cpp +++ b/ydb/core/kqp/gateway/behaviour/tablestore/operations/upsert_opt.cpp @@ -10,7 +10,12 @@ TConclusionStatus TUpsertOptionsOperation::DoDeserialize(NYql::TObjectSettingsIm return TConclusionStatus::Fail("Incorrect value for SCHEME_NEED_ACTUALIZATION: cannot parse as boolean"); } SchemeNeedActualization = *value; - ExternalGuaranteeExclusivePK = features.Extract("EXTERNAL_GUARANTEE_EXCLUSIVE_PK"); + ScanReaderPolicyName = features.Extract("SCAN_READER_POLICY_NAME"); + if (ScanReaderPolicyName) { + if (*ScanReaderPolicyName != "PLAIN" && *ScanReaderPolicyName != "SIMPLE") { + return TConclusionStatus::Fail("SCAN_READER_POLICY_NAME have to be in ['PLAIN', 'SIMPLE']"); + } + } if (const auto className = features.Extract("COMPACTION_PLANNER.CLASS_NAME")) { if (!CompactionPlannerConstructor.Initialize(*className)) { return TConclusionStatus::Fail("incorrect class name for compaction planner:" + *className); @@ -29,17 +34,38 @@ TConclusionStatus TUpsertOptionsOperation::DoDeserialize(NYql::TObjectSettingsIm } } + if (const auto className = features.Extract("METADATA_MEMORY_MANAGER.CLASS_NAME")) { + if (!MetadataManagerConstructor.Initialize(*className)) { + return TConclusionStatus::Fail("incorrect class name for metadata manager:" + *className); + } + + NJson::TJsonValue jsonData = NJson::JSON_MAP; + auto fValue = features.Extract("METADATA_MEMORY_MANAGER.FEATURES"); + if (fValue) { + if (!NJson::ReadJsonFastTree(*fValue, &jsonData)) { + return TConclusionStatus::Fail("incorrect json in request METADATA_MEMORY_MANAGER.FEATURES parameter"); + } + } + auto result = MetadataManagerConstructor->DeserializeFromJson(jsonData); + if (result.IsFail()) { + return result; + } + } + return TConclusionStatus::Success(); } void TUpsertOptionsOperation::DoSerializeScheme(NKikimrSchemeOp::TAlterColumnTableSchema& schemaData) const { schemaData.MutableOptions()->SetSchemeNeedActualization(SchemeNeedActualization); - if (ExternalGuaranteeExclusivePK) { - schemaData.MutableOptions()->SetExternalGuaranteeExclusivePK(*ExternalGuaranteeExclusivePK); + if (ScanReaderPolicyName) { + schemaData.MutableOptions()->SetScanReaderPolicyName(*ScanReaderPolicyName); } if (CompactionPlannerConstructor.HasObject()) { CompactionPlannerConstructor.SerializeToProto(*schemaData.MutableOptions()->MutableCompactionPlannerConstructor()); } + if (MetadataManagerConstructor.HasObject()) { + MetadataManagerConstructor.SerializeToProto(*schemaData.MutableOptions()->MutableMetadataManagerConstructor()); + } } } diff --git a/ydb/core/kqp/gateway/behaviour/tablestore/operations/upsert_opt.h b/ydb/core/kqp/gateway/behaviour/tablestore/operations/upsert_opt.h index 27c08addfbd5..ec678b69c73c 100644 --- a/ydb/core/kqp/gateway/behaviour/tablestore/operations/upsert_opt.h +++ b/ydb/core/kqp/gateway/behaviour/tablestore/operations/upsert_opt.h @@ -1,9 +1,10 @@ #include "abstract.h" #include +#include namespace NKikimr::NKqp { -class TUpsertOptionsOperation : public ITableStoreOperation { +class TUpsertOptionsOperation: public ITableStoreOperation { private: static TString GetTypeName() { return "UPSERT_OPTIONS"; @@ -12,8 +13,9 @@ class TUpsertOptionsOperation : public ITableStoreOperation { static inline const auto Registrator = TFactory::TRegistrator(GetTypeName()); private: bool SchemeNeedActualization = false; - std::optional ExternalGuaranteeExclusivePK; + std::optional ScanReaderPolicyName; NOlap::NStorageOptimizer::TOptimizerPlannerConstructorContainer CompactionPlannerConstructor; + NOlap::NDataAccessorControl::TMetadataManagerConstructorContainer MetadataManagerConstructor; public: TConclusionStatus DoDeserialize(NYql::TObjectSettingsImpl::TFeaturesExtractor& features) override; diff --git a/ydb/core/kqp/gateway/behaviour/tablestore/operations/ya.make b/ydb/core/kqp/gateway/behaviour/tablestore/operations/ya.make index e393435d9cc5..07776e960cfc 100644 --- a/ydb/core/kqp/gateway/behaviour/tablestore/operations/ya.make +++ b/ydb/core/kqp/gateway/behaviour/tablestore/operations/ya.make @@ -15,6 +15,7 @@ PEERDIR( ydb/services/metadata/manager ydb/core/formats/arrow/serializer ydb/core/tx/columnshard/engines/storage/optimizer/abstract + ydb/core/tx/columnshard/data_accessor/abstract ydb/core/kqp/gateway/utils ydb/core/protos ) diff --git a/ydb/core/kqp/gateway/kqp_ic_gateway.cpp b/ydb/core/kqp/gateway/kqp_ic_gateway.cpp index 6cef7ba661d5..4495080989ff 100644 --- a/ydb/core/kqp/gateway/kqp_ic_gateway.cpp +++ b/ydb/core/kqp/gateway/kqp_ic_gateway.cpp @@ -1073,10 +1073,9 @@ class TKikimrIcGateway : public IKqpGateway { return NotImplemented(); } - TFuture AlterColumnTable(const TString& cluster, - const NYql::TAlterColumnTableSettings& settings) override { + TFuture AlterColumnTable(const TString& cluster, Ydb::Table::AlterTableRequest&& req) override { Y_UNUSED(cluster); - Y_UNUSED(settings); + Y_UNUSED(req); return NotImplemented(); } diff --git a/ydb/core/kqp/gateway/kqp_metadata_loader.cpp b/ydb/core/kqp/gateway/kqp_metadata_loader.cpp index 0705cf43b56f..278bf2cf28df 100644 --- a/ydb/core/kqp/gateway/kqp_metadata_loader.cpp +++ b/ydb/core/kqp/gateway/kqp_metadata_loader.cpp @@ -156,10 +156,14 @@ TTableMetadataResult GetTableMetadataResult(const NSchemeCache::TSchemeCacheNavi switch (entry.Kind) { case EKind::KindTable: tableMeta->Kind = NYql::EKikimrTableKind::Datashard; + tableMeta->TableType = NYql::ETableType::Table; + tableMeta->StoreType = NYql::EStoreType::Row; break; case EKind::KindColumnTable: tableMeta->Kind = NYql::EKikimrTableKind::Olap; + tableMeta->TableType = NYql::ETableType::Table; + tableMeta->StoreType = NYql::EStoreType::Column; break; default: diff --git a/ydb/core/kqp/host/kqp_gateway_proxy.cpp b/ydb/core/kqp/host/kqp_gateway_proxy.cpp index b3bcbd8025f5..9be8a344b498 100644 --- a/ydb/core/kqp/host/kqp_gateway_proxy.cpp +++ b/ydb/core/kqp/host/kqp_gateway_proxy.cpp @@ -130,12 +130,17 @@ bool ConvertCreateTableSettingsToProto(NYql::TKikimrTableMetadataPtr metadata, Y familyProto->set_compression(Ydb::Table::ColumnFamily::COMPRESSION_NONE); } else if (to_lower(family.Compression.GetRef()) == "lz4") { familyProto->set_compression(Ydb::Table::ColumnFamily::COMPRESSION_LZ4); + } else if (to_lower(family.Compression.GetRef()) == "zstd") { + familyProto->set_compression(Ydb::Table::ColumnFamily::COMPRESSION_ZSTD); } else { code = Ydb::StatusIds::BAD_REQUEST; error = TStringBuilder() << "Unknown compression '" << family.Compression.GetRef() << "' for a column family"; return false; } } + if (family.CompressionLevel) { + familyProto->set_compression_level(family.CompressionLevel.GetRef()); + } } if (metadata->TableSettings.CompactionPolicy) { @@ -269,16 +274,6 @@ bool ConvertCreateTableSettingsToProto(NYql::TKikimrTableMetadataPtr metadata, Y } } - if (const auto& tiering = metadata->TableSettings.Tiering) { - if (tiering.IsSet()) { - proto.set_tiering(tiering.GetValueSet()); - } else { - code = Ydb::StatusIds::BAD_REQUEST; - error = "Can't reset TIERING"; - return false; - } - } - if (metadata->TableSettings.StoreExternalBlobs) { auto& storageSettings = *proto.mutable_storage_settings(); TString value = to_lower(metadata->TableSettings.StoreExternalBlobs.GetRef()); @@ -374,9 +369,59 @@ bool FillCreateTableDesc(NYql::TKikimrTableMetadataPtr metadata, NKikimrSchemeOp } template -void FillColumnTableSchema(NKikimrSchemeOp::TColumnTableSchema& schema, const T& metadata) -{ +bool FillColumnTableSchema(NKikimrSchemeOp::TColumnTableSchema& schema, const T& metadata, Ydb::StatusIds::StatusCode& code, TString& error) { Y_ENSURE(metadata.ColumnOrder.size() == metadata.Columns.size()); + + THashMap columnFamiliesByName; + ui32 columnFamilyId = 1; + for (const auto& family : metadata.ColumnFamilies) { + if (family.Data.Defined()) { + code = Ydb::StatusIds::BAD_REQUEST; + error = TStringBuilder() << "Field `DATA` is not supported for OLAP tables in column family '" << family.Name << "'"; + return false; + } + auto columnFamilyIt = columnFamiliesByName.find(family.Name); + if (!columnFamilyIt.IsEnd()) { + code = Ydb::StatusIds::BAD_REQUEST; + error = TStringBuilder() << "Duplicate column family `" << family.Name << '`'; + return false; + } + auto familyDescription = schema.AddColumnFamilies(); + familyDescription->SetName(family.Name); + if (familyDescription->GetName() == "default") { + familyDescription->SetId(0); + } else { + familyDescription->SetId(columnFamilyId++); + } + Y_ENSURE(columnFamiliesByName.emplace(familyDescription->GetName(), familyDescription->GetId()).second); + if (family.Compression.Defined()) { + NKikimrSchemeOp::EColumnCodec codec; + auto codecName = to_lower(family.Compression.GetRef()); + if (codecName == "off") { + codec = NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain; + } else if (codecName == "zstd") { + codec = NKikimrSchemeOp::EColumnCodec::ColumnCodecZSTD; + } else if (codecName == "lz4") { + codec = NKikimrSchemeOp::EColumnCodec::ColumnCodecLZ4; + } else { + code = Ydb::StatusIds::BAD_REQUEST; + error = TStringBuilder() << "Unknown compression '" << family.Compression.GetRef() << "' for a column family"; + return false; + } + familyDescription->SetColumnCodec(codec); + } else { + code = Ydb::StatusIds::BAD_REQUEST; + error = TStringBuilder() << "Compression is not set for column family'" << family.Name << "'"; + return false; + } + + if (family.CompressionLevel.Defined()) { + familyDescription->SetColumnCodecLevel(family.CompressionLevel.GetRef()); + } + } + + schema.SetNextColumnFamilyId(columnFamilyId); + for (const auto& name : metadata.ColumnOrder) { auto columnIt = metadata.Columns.find(name); Y_ENSURE(columnIt != metadata.Columns.end()); @@ -385,13 +430,34 @@ void FillColumnTableSchema(NKikimrSchemeOp::TColumnTableSchema& schema, const T& columnDesc.SetName(columnIt->second.Name); columnDesc.SetType(columnIt->second.Type); columnDesc.SetNotNull(columnIt->second.NotNull); + + auto columnType = NScheme::ProtoColumnTypeFromTypeInfoMod(columnIt->second.TypeInfo, columnIt->second.TypeMod); + if (columnType.TypeInfo) { + *columnDesc.MutableTypeInfo() = *columnType.TypeInfo; + } + + if (!columnFamiliesByName.empty()) { + TString columnFamilyName = "default"; + ui32 columnFamilyId = 0; + if (columnIt->second.Families.size()) { + columnFamilyName = *columnIt->second.Families.begin(); + auto columnFamilyIdIt = columnFamiliesByName.find(columnFamilyName); + if (columnFamilyIdIt.IsEnd()) { + code = Ydb::StatusIds::BAD_REQUEST; + error = TStringBuilder() << "Unknown column family `" << columnFamilyName << "` for column `" << columnDesc.GetName() << "`"; + return false; + } + columnFamilyId = columnFamilyIdIt->second; + } + columnDesc.SetColumnFamilyName(columnFamilyName); + columnDesc.SetColumnFamilyId(columnFamilyId); + } } for (const auto& keyColumn : metadata.KeyColumnNames) { schema.AddKeyColumnNames(keyColumn); } - - schema.SetEngine(NKikimrSchemeOp::EColumnTableEngine::COLUMN_ENGINE_REPLACING_TIMESERIES); + return true; } bool FillCreateColumnTableDesc(NYql::TKikimrTableMetadataPtr metadata, @@ -438,7 +504,15 @@ bool FillCreateColumnTableDesc(NYql::TKikimrTableMetadataPtr metadata, const auto& inputSettings = metadata->TableSettings.TtlSettings.GetValueSet(); auto& resultSettings = *tableDesc.MutableTtlSettings(); resultSettings.MutableEnabled()->SetColumnName(inputSettings.ColumnName); - resultSettings.MutableEnabled()->SetExpireAfterSeconds(inputSettings.ExpireAfter.Seconds()); + for (const auto& tier : inputSettings.Tiers) { + auto* tierProto = resultSettings.MutableEnabled()->AddTiers(); + tierProto->SetApplyAfterSeconds(tier.ApplyAfter.Seconds()); + if (tier.StorageName) { + tierProto->MutableEvictToExternalStorage()->SetStorage(*tier.StorageName); + } else { + tierProto->MutableDelete(); + } + } if (inputSettings.ColumnUnit) { resultSettings.MutableEnabled()->SetColumnUnit(static_cast(*inputSettings.ColumnUnit)); } @@ -1492,7 +1566,12 @@ class TKqpGatewayProxy : public IKikimrGateway { NKikimrSchemeOp::TColumnTableDescription* tableDesc = schemeTx.MutableCreateColumnTable(); tableDesc->SetName(pathPair.second); - FillColumnTableSchema(*tableDesc->MutableSchema(), *metadata); + if (!FillColumnTableSchema(*tableDesc->MutableSchema(), *metadata, code, error)) { + IKqpGateway::TGenericResult errResult; + errResult.AddIssue(NYql::TIssue(error)); + errResult.SetStatus(NYql::YqlStatusFromYdbStatus(code)); + return MakeFuture(std::move(errResult)); + } if (!FillCreateColumnTableDesc(metadata, *tableDesc, code, error)) { IKqpGateway::TGenericResult errResult; @@ -1519,9 +1598,7 @@ class TKqpGatewayProxy : public IKikimrGateway { } } - TFuture AlterColumnTable(const TString& cluster, - const TAlterColumnTableSettings& settings) override - { + TFuture AlterColumnTable(const TString& cluster, Ydb::Table::AlterTableRequest&& req) override { CHECK_PREPARED_DDL(AlterColumnTable); try { @@ -1529,20 +1606,16 @@ class TKqpGatewayProxy : public IKikimrGateway { return MakeFuture(ResultFromError("Invalid cluster: " + cluster)); } - std::pair pathPair; - { - TString error; - if (!NSchemeHelpers::SplitTablePath(settings.Table, GetDatabase(), pathPair, error, false)) { - return MakeFuture(ResultFromError(error)); - } - } - NKikimrSchemeOp::TModifyScheme schemeTx; - schemeTx.SetWorkingDir(pathPair.first); - schemeTx.SetOperationType(NKikimrSchemeOp::ESchemeOpAlterColumnTable); - NKikimrSchemeOp::TAlterColumnTable* alter = schemeTx.MutableAlterColumnTable(); - alter->SetName(settings.Table); + Ydb::StatusIds::StatusCode code; + TString error; + if (!BuildAlterColumnTableModifyScheme(&req, &schemeTx, code, error)) { + IKqpGateway::TGenericResult errResult; + errResult.AddIssue(NYql::TIssue(error)); + errResult.SetStatus(NYql::YqlStatusFromYdbStatus(code)); + return MakeFuture(errResult); + } if (IsPrepare()) { auto& phyQuery = *SessionCtx->Query().PreparingQuery->MutablePhysicalQuery(); @@ -1784,7 +1857,22 @@ class TKqpGatewayProxy : public IKikimrGateway { NKikimrSchemeOp::TColumnTableSchemaPreset* schemaPreset = storeDesc->AddSchemaPresets(); schemaPreset->SetName("default"); - FillColumnTableSchema(*schemaPreset->MutableSchema(), settings); + + if (!settings.ColumnFamilies.empty()) { + IKqpGateway::TGenericResult errResult; + errResult.AddIssue(NYql::TIssue("TableStore does not support column families")); + errResult.SetStatus(NYql::YqlStatusFromYdbStatus(Ydb::StatusIds::BAD_REQUEST)); + return MakeFuture(std::move(errResult)); + } + + Ydb::StatusIds::StatusCode code; + TString error; + if (!FillColumnTableSchema(*schemaPreset->MutableSchema(), settings, code, error)) { + IKqpGateway::TGenericResult errResult; + errResult.AddIssue(NYql::TIssue(error)); + errResult.SetStatus(NYql::YqlStatusFromYdbStatus(code)); + return MakeFuture(std::move(errResult)); + } if (IsPrepare()) { auto& phyQuery = *SessionCtx->Query().PreparingQuery->MutablePhysicalQuery(); diff --git a/ydb/core/kqp/provider/yql_kikimr_exec.cpp b/ydb/core/kqp/provider/yql_kikimr_exec.cpp index bbbda9b390f4..280fcc812038 100644 --- a/ydb/core/kqp/provider/yql_kikimr_exec.cpp +++ b/ydb/core/kqp/provider/yql_kikimr_exec.cpp @@ -182,7 +182,8 @@ namespace { return dropGroupSettings; } - TCreateTableStoreSettings ParseCreateTableStoreSettings(TKiCreateTable create, const TTableSettings& settings) { + TCreateTableStoreSettings ParseCreateTableStoreSettings( + TKiCreateTable create, const TTableSettings& settings, const TVector& columnFamilies) { TCreateTableStoreSettings out; out.TableStore = TString(create.Table()); out.ShardsCount = settings.MinPartitions ? *settings.MinPartitions : 0; @@ -215,6 +216,13 @@ namespace { columnMeta.NotNull = notNull; } + if (columnTuple.Size() > 3) { + auto families = columnTuple.Item(3).Cast(); + for (auto family : families) { + columnMeta.Families.push_back(TString(family.Value())); + } + } + out.ColumnOrder.push_back(columnName); out.Columns.insert(std::make_pair(columnName, columnMeta)); } @@ -224,6 +232,7 @@ namespace { out.Indexes.push_back(indexDesc); } #endif + out.ColumnFamilies = columnFamilies; return out; } @@ -292,12 +301,6 @@ namespace { }; } - TAlterColumnTableSettings ParseAlterColumnTableSettings(TKiAlterTable alter) { - return TAlterColumnTableSettings{ - .Table = TString(alter.Table()) - }; - } - TSequenceSettings ParseSequenceSettings(const TCoNameValueTupleList& sequenceSettings) { TSequenceSettings result; for (const auto& setting: sequenceSettings) { @@ -1228,8 +1231,8 @@ class TKiSinkCallableExecutionTransformer : public TAsyncCallbackTransformerCreateTableStore(cluster, - ParseCreateTableStoreSettings(maybeCreate.Cast(), table.Metadata->TableSettings), existingOk); + future = Gateway->CreateTableStore(cluster, ParseCreateTableStoreSettings(maybeCreate.Cast(), table.Metadata->TableSettings, + table.Metadata->ColumnFamilies), existingOk); break; } case ETableType::Table: @@ -1500,6 +1503,8 @@ class TKiSinkCallableExecutionTransformer : public TAsyncCallbackTransformerset_compression(Ydb::Table::ColumnFamily::COMPRESSION_NONE); } else if (to_lower(comp) == "lz4") { f->set_compression(Ydb::Table::ColumnFamily::COMPRESSION_LZ4); + } else if (to_lower(comp) == "zstd") { + f->set_compression(Ydb::Table::ColumnFamily::COMPRESSION_ZSTD); } else { auto errText = TStringBuilder() << "Unknown compression '" << comp << "' for a column family"; @@ -1508,6 +1513,9 @@ class TKiSinkCallableExecutionTransformer : public TAsyncCallbackTransformer(familySetting.Value().Cast().Literal().Cast().Value()); + f->set_compression_level(level); } else { ctx.AddError(TIssue(ctx.GetPosition(familySetting.Name().Pos()), TStringBuilder() << "Unknown column family setting name: " << name)); @@ -1574,13 +1582,6 @@ class TKiSinkCallableExecutionTransformer : public TAsyncCallbackTransformer().Literal().Cast().Value() - ); - alterTableRequest.set_set_tiering(tieringName); - } else if (name == "resetTiering") { - alterTableRequest.mutable_drop_tiering(); } else { ctx.AddError(TIssue(ctx.GetPosition(setting.Name().Pos()), TStringBuilder() << "Unknown table profile setting: " << name)); @@ -1851,10 +1852,11 @@ class TKiSinkCallableExecutionTransformer : public TAsyncCallbackTransformer future; - bool isTableStore = (table.Metadata->TableType == ETableType::TableStore); + bool isTableStore = (table.Metadata->TableType == ETableType::TableStore); // Doesn't set, so always false bool isColumn = (table.Metadata->StoreType == EStoreType::Column); if (isTableStore) { + AFL_VERIFY(false); if (!isColumn) { ctx.AddError(TIssue(ctx.GetPosition(input->Pos()), TStringBuilder() << "TABLESTORE with not COLUMN store")); @@ -1862,7 +1864,7 @@ class TKiSinkCallableExecutionTransformer : public TAsyncCallbackTransformerAlterTableStore(cluster, ParseAlterTableStoreSettings(maybeAlter.Cast())); } else if (isColumn) { - future = Gateway->AlterColumnTable(cluster, ParseAlterColumnTableSettings(maybeAlter.Cast())); + future = Gateway->AlterColumnTable(cluster, std::move(alterTableRequest)); } else { TMaybe requestType; if (!SessionCtx->Query().DocumentApiRestricted) { diff --git a/ydb/core/kqp/provider/yql_kikimr_gateway.cpp b/ydb/core/kqp/provider/yql_kikimr_gateway.cpp index 8b23ef1d6715..047d614c337f 100644 --- a/ydb/core/kqp/provider/yql_kikimr_gateway.cpp +++ b/ydb/core/kqp/provider/yql_kikimr_gateway.cpp @@ -138,15 +138,37 @@ bool TTtlSettings::TryParse(const NNodes::TCoNameValueTupleList& node, TTtlSetti if (name == "columnName") { YQL_ENSURE(field.Value().Maybe()); settings.ColumnName = field.Value().Cast().StringValue(); - } else if (name == "expireAfter") { - YQL_ENSURE(field.Value().Maybe()); - auto value = FromString(field.Value().Cast().Literal().Value()); - if (value < 0) { - error = "Interval value cannot be negative"; - return false; - } + } else if (name == "tiers") { + YQL_ENSURE(field.Value().Maybe()); + auto listNode = field.Value().Cast(); + + for (size_t i = 0; i < listNode.Size(); ++i) { + auto tierNode = listNode.Item(i); + + std::optional storageName; + TDuration evictionDelay; + YQL_ENSURE(tierNode.Maybe()); + for (const auto& tierField : tierNode.Cast()) { + auto tierFieldName = tierField.Name().Value(); + if (tierFieldName == "storageName") { + YQL_ENSURE(tierField.Value().Maybe()); + storageName = tierField.Value().Cast().StringValue(); + } else if (tierFieldName == "evictionDelay") { + YQL_ENSURE(tierField.Value().Maybe()); + auto value = FromString(tierField.Value().Cast().Literal().Value()); + if (value < 0) { + error = "Interval value cannot be negative"; + return false; + } + evictionDelay = TDuration::FromValue(value); + } else { + error = TStringBuilder() << "Unknown field: " << tierFieldName; + return false; + } + } - settings.ExpireAfter = TDuration::FromValue(value); + settings.Tiers.emplace_back(evictionDelay, storageName); + } } else if (name == "columnUnit") { YQL_ENSURE(field.Value().Maybe()); auto value = field.Value().Cast().StringValue(); @@ -299,15 +321,23 @@ bool ConvertReadReplicasSettingsToProto(const TString settings, Ydb::Table::Read } void ConvertTtlSettingsToProto(const NYql::TTtlSettings& settings, Ydb::Table::TtlSettings& proto) { - if (!settings.ColumnUnit) { - auto& opts = *proto.mutable_date_type_column(); - opts.set_column_name(settings.ColumnName); - opts.set_expire_after_seconds(settings.ExpireAfter.Seconds()); - } else { - auto& opts = *proto.mutable_value_since_unix_epoch(); - opts.set_column_name(settings.ColumnName); - opts.set_column_unit(static_cast(*settings.ColumnUnit)); - opts.set_expire_after_seconds(settings.ExpireAfter.Seconds()); + for (const auto& tier : settings.Tiers) { + auto* outTier = proto.mutable_tiered_ttl()->add_tiers(); + if (!settings.ColumnUnit) { + auto& expr = *outTier->mutable_date_type_column(); + expr.set_column_name(settings.ColumnName); + expr.set_expire_after_seconds(tier.ApplyAfter.Seconds()); + } else { + auto& expr = *outTier->mutable_value_since_unix_epoch(); + expr.set_column_name(settings.ColumnName); + expr.set_column_unit(static_cast(*settings.ColumnUnit)); + expr.set_expire_after_seconds(tier.ApplyAfter.Seconds()); + } + if (tier.StorageName) { + outTier->mutable_evict_to_external_storage()->set_storage(*tier.StorageName); + } else { + outTier->mutable_delete_(); + } } } diff --git a/ydb/core/kqp/provider/yql_kikimr_gateway.h b/ydb/core/kqp/provider/yql_kikimr_gateway.h index 0fc06dd02f47..44b770ed7dee 100644 --- a/ydb/core/kqp/provider/yql_kikimr_gateway.h +++ b/ydb/core/kqp/provider/yql_kikimr_gateway.h @@ -170,6 +170,7 @@ struct TColumnFamily { TString Name; TMaybe Data; TMaybe Compression; + TMaybe CompressionLevel; }; struct TTtlSettings { @@ -180,9 +181,14 @@ struct TTtlSettings { Nanoseconds = 4, }; + struct TTier { + TDuration ApplyAfter; + std::optional StorageName; + }; + TString ColumnName; - TDuration ExpireAfter; TMaybe ColumnUnit; + std::vector Tiers; static bool TryParse(const NNodes::TCoNameValueTupleList& node, TTtlSettings& settings, TString& error); }; @@ -200,7 +206,6 @@ struct TTableSettings { TMaybe KeyBloomFilter; TMaybe ReadReplicasSettings; TResetableSetting TtlSettings; - TResetableSetting Tiering; TMaybe PartitionByHashFunction; TMaybe StoreExternalBlobs; @@ -648,6 +653,7 @@ struct TCreateTableStoreSettings { TVector KeyColumnNames; TVector ColumnOrder; TVector Indexes; + TVector ColumnFamilies; }; struct TAlterTableStoreSettings { @@ -1006,7 +1012,7 @@ class IKikimrGateway : public TThrRefBase { virtual NThreading::TFuture CreateColumnTable( TKikimrTableMetadataPtr metadata, bool createDir, bool existingOk = false) = 0; - virtual NThreading::TFuture AlterColumnTable(const TString& cluster, const TAlterColumnTableSettings& settings) = 0; + virtual NThreading::TFuture AlterColumnTable(const TString& cluster, Ydb::Table::AlterTableRequest&& req) = 0; virtual NThreading::TFuture CreateTableStore(const TString& cluster, const TCreateTableStoreSettings& settings, bool existingOk = false) = 0; diff --git a/ydb/core/kqp/provider/yql_kikimr_provider.h b/ydb/core/kqp/provider/yql_kikimr_provider.h index 2c14581e15f0..5711a1ecff98 100644 --- a/ydb/core/kqp/provider/yql_kikimr_provider.h +++ b/ydb/core/kqp/provider/yql_kikimr_provider.h @@ -292,7 +292,6 @@ class TKikimrTransactionContextBase : public TThrRefBase { Invalidated = false; Readonly = false; Closed = false; - HasUncommittedChangesRead = false; } void SetTempTables(NKikimr::NKqp::TKqpTempTablesState::TConstPtr tempTablesState) { @@ -409,17 +408,6 @@ class TKikimrTransactionContextBase : public TThrRefBase { } auto& currentOps = TableOperations[table]; - const bool currentModify = currentOps & KikimrModifyOps(); - if (currentModify) { - if (KikimrReadOps() & newOp) { - HasUncommittedChangesRead = true; - } - - if ((*info)->GetHasIndexTables()) { - HasUncommittedChangesRead = true; - } - } - currentOps |= newOp; } @@ -429,7 +417,6 @@ class TKikimrTransactionContextBase : public TThrRefBase { virtual ~TKikimrTransactionContextBase() = default; public: - bool HasUncommittedChangesRead = false; THashMap TableOperations; THashMap TableByIdMap; TMaybe EffectiveIsolationLevel; diff --git a/ydb/core/kqp/provider/yql_kikimr_type_ann.cpp b/ydb/core/kqp/provider/yql_kikimr_type_ann.cpp index 593e8f24a714..49fbe3dbab46 100644 --- a/ydb/core/kqp/provider/yql_kikimr_type_ann.cpp +++ b/ydb/core/kqp/provider/yql_kikimr_type_ann.cpp @@ -989,6 +989,8 @@ virtual TStatus HandleCreateTable(TKiCreateTable create, TExprContext& ctx) over family.Compression = TString( familySetting.Value().Cast().Literal().Cast().Value() ); + } else if (name == "compression_level") { + family.CompressionLevel = FromString(familySetting.Value().Cast().Literal().Cast().Value()); } else { ctx.AddError(TIssue(ctx.GetPosition(familySetting.Name().Pos()), TStringBuilder() << "Unknown column family setting name: " << name)); @@ -1196,18 +1198,18 @@ virtual TStatus HandleCreateTable(TKiCreateTable create, TExprContext& ctx) over ctx.AddError(TIssue(ctx.GetPosition(setting.Name().Pos()), "Can't reset TTL settings")); return TStatus::Error; - } else if (name == "setTiering") { - meta->TableSettings.Tiering.Set(TString( - setting.Value().Cast().Literal().Cast().Value() - )); - } else if (name == "resetTiering") { - ctx.AddError(TIssue(ctx.GetPosition(setting.Name().Pos()), - "Can't reset TIERING")); - return TStatus::Error; } else if (name == "storeType") { - TMaybe storeType = TString(setting.Value().Cast().Value()); - if (storeType && to_lower(storeType.GetRef()) == "column") { - meta->StoreType = EStoreType::Column; + if (const TMaybe storeType = TString(setting.Value().Cast().Value())) { + const auto& val = to_lower(storeType.GetRef()); + if (val == "column") { + meta->StoreType = EStoreType::Column; + } else if (val == "row") { + //pass + } else { + ctx.AddError(TIssue(ctx.GetPosition(setting.Name().Pos()), + TStringBuilder() << "Unsupported table store type: " << storeType.GetRef())); + return TStatus::Error; + } } } else if (name == "partitionByHashFunction") { meta->TableSettings.PartitionByHashFunction = TString( diff --git a/ydb/core/kqp/proxy_service/kqp_script_executions_ut.cpp b/ydb/core/kqp/proxy_service/kqp_script_executions_ut.cpp index 280ef82f1fb3..113b4cfee997 100644 --- a/ydb/core/kqp/proxy_service/kqp_script_executions_ut.cpp +++ b/ydb/core/kqp/proxy_service/kqp_script_executions_ut.cpp @@ -35,6 +35,9 @@ NKikimrSchemeOp::TColumnDescription Col(const TString& columnName, NScheme::TTyp [[maybe_unused]] NKikimrSchemeOp::TTTLSettings TtlCol(const TString& columnName) { NKikimrSchemeOp::TTTLSettings settings; + auto* deleteTier = settings.MutableEnabled()->AddTiers(); + deleteTier->MutableDelete(); + deleteTier->SetApplyAfterSeconds(TDuration::Minutes(20).Seconds()); settings.MutableEnabled()->SetExpireAfterSeconds(TDuration::Minutes(20).Seconds()); settings.MutableEnabled()->SetColumnName(columnName); settings.MutableEnabled()->MutableSysSettings()->SetRunInterval(TDuration::Minutes(60).MicroSeconds()); diff --git a/ydb/core/kqp/runtime/kqp_write_actor.cpp b/ydb/core/kqp/runtime/kqp_write_actor.cpp index 605bc41bdd42..ef862fa62078 100644 --- a/ydb/core/kqp/runtime/kqp_write_actor.cpp +++ b/ydb/core/kqp/runtime/kqp_write_actor.cpp @@ -435,6 +435,20 @@ class TKqpDirectWriteActor : public TActorBootstrapped, pu } return; } + case NKikimrDataEvents::TEvWriteResult::STATUS_DISK_SPACE_EXHAUSTED: { + CA_LOG_E("Got DISK_SPACE_EXHAUSTED for table `" + << SchemeEntry->TableId.PathId.ToString() << "`." + << " ShardID=" << ev->Get()->Record.GetOrigin() << "," + << " Sink=" << this->SelfId() << "." + << getIssues().ToOneLineString()); + + RuntimeError( + TStringBuilder() << "Got DISK_SPACE_EXHAUSTED for table `" + << SchemeEntry->TableId.PathId.ToString() << "`.", + NYql::NDqProto::StatusIds::PRECONDITION_FAILED, + getIssues()); + return; + } case NKikimrDataEvents::TEvWriteResult::STATUS_OVERLOADED: { CA_LOG_W("Got OVERLOADED for table `" << SchemeEntry->TableId.PathId.ToString() << "`." diff --git a/ydb/core/kqp/session_actor/kqp_query_state.h b/ydb/core/kqp/session_actor/kqp_query_state.h index 4b7f95a40ca3..6c31e5660ac8 100644 --- a/ydb/core/kqp/session_actor/kqp_query_state.h +++ b/ydb/core/kqp/session_actor/kqp_query_state.h @@ -355,7 +355,7 @@ class TKqpQueryState : public TNonCopyable { return false; } - if (TxCtx->HasUncommittedChangesRead || AppData()->FeatureFlags.GetEnableForceImmediateEffectsExecution()) { + if (TxCtx->NeedUncommittedChangesFlush || AppData()->FeatureFlags.GetEnableForceImmediateEffectsExecution()) { if (tx && tx->GetHasEffects()) { YQL_ENSURE(tx->ResultsSize() == 0); // commit can be applied to the last transaction with effects diff --git a/ydb/core/kqp/session_actor/kqp_session_actor.cpp b/ydb/core/kqp/session_actor/kqp_session_actor.cpp index b2d1c49bf9dd..cb935e2adcc3 100644 --- a/ydb/core/kqp/session_actor/kqp_session_actor.cpp +++ b/ydb/core/kqp/session_actor/kqp_session_actor.cpp @@ -862,7 +862,9 @@ class TKqpSessionActor : public TActorBootstrapped { "Write transactions between column and row tables are disabled at current time."); return false; } + QueryState->TxCtx->SetTempTables(QueryState->TempTablesState); + QueryState->TxCtx->ApplyPhysicalQuery(phyQuery); auto [success, issues] = QueryState->TxCtx->ApplyTableOperations(phyQuery.GetTableOps(), phyQuery.GetTableInfos(), EKikimrQueryType::Dml); if (!success) { @@ -1232,7 +1234,7 @@ class TKqpSessionActor : public TActorBootstrapped { } else if (QueryState->ShouldAcquireLocks(tx) && (!txCtx.HasOlapTable || Settings.TableService.GetEnableOlapSink())) { request.AcquireLocksTxId = txCtx.Locks.GetLockTxId(); - if (txCtx.HasUncommittedChangesRead || Config->FeatureFlags.GetEnableForceImmediateEffectsExecution() || txCtx.HasOlapTable) { + if (!txCtx.CanDeferEffects()) { request.UseImmediateEffects = true; } } diff --git a/ydb/core/kqp/ut/common/columnshard.cpp b/ydb/core/kqp/ut/common/columnshard.cpp index 6318a8f1e7ea..cb4e49c92138 100644 --- a/ydb/core/kqp/ut/common/columnshard.cpp +++ b/ydb/core/kqp/ut/common/columnshard.cpp @@ -1,6 +1,9 @@ #include "columnshard.h" -#include + #include +#include +#include +#include extern "C" { #include @@ -8,27 +11,6 @@ extern "C" { namespace NKikimr { namespace NKqp { - - TString GetConfigProtoWithName(const TString & tierName) { - return TStringBuilder() << "Name : \"" << tierName << "\"\n" << - R"( - ObjectStorage : { - Endpoint: "fake" - Bucket: "fake" - SecretableAccessKey: { - Value: { - Data: "secretAccessKey" - } - } - SecretableSecretKey: { - Value: { - Data: "fakeSecret" - } - } - } - )"; - } - using namespace NYdb; TTestHelper::TTestHelper(const TKikimrSettings& settings) { @@ -36,9 +18,13 @@ namespace NKqp { if (!kikimrSettings.FeatureFlags.HasEnableTieringInColumnShard()) { kikimrSettings.SetEnableTieringInColumnShard(true); } + if (!kikimrSettings.FeatureFlags.HasEnableExternalDataSources()) { + kikimrSettings.SetEnableExternalDataSources(true); + } Kikimr = std::make_unique(kikimrSettings); - TableClient = std::make_unique(Kikimr->GetTableClient()); + TableClient = + std::make_unique(Kikimr->GetTableClient(NYdb::NTable::TClientSettings().AuthToken("root@builtin"))); Session = std::make_unique(TableClient->CreateSession().GetValueSync().GetSession()); } @@ -61,33 +47,29 @@ namespace NKqp { } void TTestHelper::CreateTier(const TString& tierName) { - auto result = GetSession().ExecuteSchemeQuery("CREATE OBJECT " + tierName + " (TYPE TIER) WITH tierConfig = `" + GetConfigProtoWithName(tierName) + "`").GetValueSync(); + auto result = GetSession().ExecuteSchemeQuery(R"( + UPSERT OBJECT `accessKey` (TYPE SECRET) WITH (value = `secretAccessKey`); + UPSERT OBJECT `secretKey` (TYPE SECRET) WITH (value = `fakeSecret`); + CREATE EXTERNAL DATA SOURCE `)" + tierName + R"(` WITH ( + SOURCE_TYPE="ObjectStorage", + LOCATION="http://fake.fake/fake", + AUTH_METHOD="AWS", + AWS_ACCESS_KEY_ID_SECRET_NAME="accessKey", + AWS_SECRET_ACCESS_KEY_SECRET_NAME="secretKey", + AWS_REGION="ru-central1" + ); + )").GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); } - TString TTestHelper::CreateTieringRule(const TString& tierName, const TString& columnName) { - const TString ruleName = tierName + "_" + columnName; - const TString configTieringStr = TStringBuilder() << R"({ - "rules" : [ - { - "tierName" : ")" << tierName << R"(", - "durationForEvict" : "10d" - } - ] - })"; - auto result = GetSession().ExecuteSchemeQuery("CREATE OBJECT IF NOT EXISTS " + ruleName + " (TYPE TIERING_RULE) WITH (defaultColumn = " + columnName + ", description = `" + configTieringStr + "`)").GetValueSync(); - UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); - return ruleName; - } - - void TTestHelper::SetTiering(const TString& tableName, const TString& ruleName) { - auto alterQuery = TStringBuilder() << "ALTER TABLE `" << tableName << "` SET (TIERING = '" << ruleName << "')"; + void TTestHelper::SetTiering(const TString& tableName, const TString& tierName, const TString& columnName) { + auto alterQuery = TStringBuilder() << "ALTER TABLE `" << tableName << "` SET TTL Interval(\"P10D\") TO EXTERNAL DATA SOURCE `" << tierName << "` ON `" << columnName << "`;"; auto result = GetSession().ExecuteSchemeQuery(alterQuery).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); } void TTestHelper::ResetTiering(const TString& tableName) { - auto alterQuery = TStringBuilder() << "ALTER TABLE `" << tableName << "` RESET (TIERING)"; + auto alterQuery = TStringBuilder() << "ALTER TABLE `" << tableName << "` RESET (TTL)"; auto result = GetSession().ExecuteSchemeQuery(alterQuery).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); } @@ -143,6 +125,114 @@ namespace NKqp { } } + void TTestHelper::SetCompression( + const TColumnTableBase& columnTable, const TString& columnName, const TCompression& compression, const NYdb::EStatus expectedStatus) { + auto alterQuery = columnTable.BuildAlterCompressionQuery(columnName, compression); + auto result = GetSession().ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), expectedStatus, result.GetIssues().ToString()); + } + + bool TTestHelper::TCompression::DeserializeFromProto(const NKikimrSchemeOp::TOlapColumn::TSerializer& serializer) { + if (!serializer.GetClassName()) { + return false; + } + if (serializer.GetClassName() == NArrow::NSerialization::TNativeSerializer::GetClassNameStatic()) { + SerializerClassName = serializer.GetClassName(); + if (!serializer.HasArrowCompression() || !serializer.GetArrowCompression().HasCodec()) { + return false; + } + CompressionType = serializer.GetArrowCompression().GetCodec(); + if (serializer.GetArrowCompression().HasLevel()) { + CompressionLevel = serializer.GetArrowCompression().GetLevel(); + } + } else { + return false; + } + + return true; + } + + TString TTestHelper::TCompression::BuildQuery() const { + TStringBuilder str; + str << "COMPRESSION=\"" << NArrow::CompressionToString(CompressionType) << "\""; + if (CompressionLevel.has_value()) { + str << ", COMPRESSION_LEVEL=" << CompressionLevel.value(); + } + return str; + } + + bool TTestHelper::TCompression::IsEqual(const TCompression& rhs, TString& errorMessage) const { + if (SerializerClassName != rhs.GetSerializerClassName()) { + errorMessage = TStringBuilder() << "different serializer class name: in left value `" << SerializerClassName + << "` and in right value `" << rhs.GetSerializerClassName() << "`"; + return false; + } + if (CompressionType != rhs.GetCompressionType()) { + errorMessage = TStringBuilder() << "different compression type: in left value `" << NArrow::CompressionToString(CompressionType) + << "` and in right value `" << NArrow::CompressionToString(rhs.GetCompressionType()) << "`"; + return false; + } + if (CompressionLevel.has_value() && rhs.GetCompressionLevel().has_value() && + CompressionLevel.value() != rhs.GetCompressionLevel().value()) { + errorMessage = TStringBuilder() << "different compression level: in left value `" << CompressionLevel.value() + << "` and in right value `" << rhs.GetCompressionLevel().value() << "`"; + return false; + } else if (CompressionLevel.has_value() && !rhs.GetCompressionLevel().has_value()) { + errorMessage = TStringBuilder() << "compression level is set in left value, but not set in right value"; + return false; + } else if (!CompressionLevel.has_value() && rhs.GetCompressionLevel().has_value()) { + errorMessage = TStringBuilder() << "compression level not set in left value, but set in right value"; + return false; + } + + return true; + } + + TString TTestHelper::TCompression::ToString() const { + return BuildQuery(); + } + + bool TTestHelper::TColumnFamily::DeserializeFromProto(const NKikimrSchemeOp::TFamilyDescription& family) { + if (!family.HasId() || !family.HasName() || !family.HasColumnCodec()) { + return false; + } + Id = family.GetId(); + FamilyName = family.GetName(); + Compression = TTestHelper::TCompression().SetCompressionType(family.GetColumnCodec()); + if (family.HasColumnCodecLevel()) { + Compression.SetCompressionLevel(family.GetColumnCodecLevel()); + } + return true; + } + + TString TTestHelper::TColumnFamily::BuildQuery() const { + TStringBuilder str; + str << "FAMILY " << FamilyName << " ("; + if (!Data.empty()) { + str << "DATA=\"" << Data << "\", "; + } + str << Compression.BuildQuery() << ")"; + return str; + } + + bool TTestHelper::TColumnFamily::IsEqual(const TColumnFamily& rhs, TString& errorMessage) const { + if (Id != rhs.GetId()) { + errorMessage = TStringBuilder() << "different family id: in left value `" << Id << "` and in right value `" << rhs.GetId() << "`"; + return false; + } + if (FamilyName != rhs.GetFamilyName()) { + errorMessage = TStringBuilder() << "different family name: in left value `" << FamilyName << "` and in right value `" + << rhs.GetFamilyName() << "`"; + return false; + } + + return Compression.IsEqual(rhs.GetCompression(), errorMessage); + } + + TString TTestHelper::TColumnFamily::ToString() const { + return BuildQuery(); + } + TString TTestHelper::TColumnSchema::BuildQuery() const { TStringBuilder str; str << Name << ' '; @@ -159,6 +249,9 @@ namespace NKqp { default: str << NScheme::GetTypeName(Type); } + if (!ColumnFamilyName.empty()) { + str << " FAMILY " << ColumnFamilyName; + } if (!NullableFlag) { str << " NOT NULL"; } @@ -167,12 +260,21 @@ namespace NKqp { TString TTestHelper::TColumnTableBase::BuildQuery() const { auto str = TStringBuilder() << "CREATE " << GetObjectType() << " `" << Name << "`"; - str << " (" << BuildColumnsStr(Schema) << ", PRIMARY KEY (" << JoinStrings(PrimaryKey, ", ") << "))"; + str << " (" << BuildColumnsStr(Schema) << ", PRIMARY KEY (" << JoinStrings(PrimaryKey, ", ") << ")"; + if (!ColumnFamilies.empty()) { + TVector families; + families.reserve(ColumnFamilies.size()); + for (const auto& family : ColumnFamilies) { + families.push_back(family.BuildQuery()); + } + str << ", " << JoinStrings(families, ", "); + } + str << ")"; if (!Sharding.empty()) { str << " PARTITION BY HASH(" << JoinStrings(Sharding, ", ") << ")"; } str << " WITH (STORE = COLUMN"; - str << ", AUTO_PARTITIONING_MIN_PARTITIONS_COUNT =" << MinPartitionsCount; + str << ", AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = " << MinPartitionsCount; if (TTLConf) { str << ", TTL = " << TTLConf->second << " ON " << TTLConf->first; } @@ -180,6 +282,18 @@ namespace NKqp { return str; } + TString TTestHelper::TColumnTableBase::BuildAlterCompressionQuery(const TString& columnName, const TCompression& compression) const { + auto str = TStringBuilder() << "ALTER OBJECT `" << Name << "` (TYPE " << GetObjectType() << ") SET"; + str << " (ACTION=ALTER_COLUMN, NAME=" << columnName << ", `SERIALIZER.CLASS_NAME`=`" << compression.GetSerializerClassName() << "`,"; + auto codec = NArrow::CompressionFromProto(compression.GetCompressionType()); + Y_VERIFY(codec.has_value()); + str << " `COMPRESSION.TYPE`=`" << NArrow::CompressionToString(codec.value()) << "`"; + if (compression.GetCompressionLevel().has_value()) { + str << "`COMPRESSION.LEVEL`=" << compression.GetCompressionLevel().value(); + } + str << ");"; + return str; + } std::shared_ptr TTestHelper::TColumnTableBase::GetArrowSchema(const TVector& columns) { std::vector> result; diff --git a/ydb/core/kqp/ut/common/columnshard.h b/ydb/core/kqp/ut/common/columnshard.h index d1be363fd5ef..6f777620ca0f 100644 --- a/ydb/core/kqp/ut/common/columnshard.h +++ b/ydb/core/kqp/ut/common/columnshard.h @@ -1,88 +1,128 @@ #pragma once #include "kqp_ut_common.h" + +#include + #include -#include #include #include +#include #include #include #include -#include #include namespace NKikimr { namespace NKqp { - class TTestHelper { +class TTestHelper { +public: + class TCompression { + YDB_ACCESSOR(TString, SerializerClassName, "ARROW_SERIALIZER"); + YDB_ACCESSOR_DEF(NKikimrSchemeOp::EColumnCodec, CompressionType); + YDB_ACCESSOR_DEF(std::optional, CompressionLevel); + public: - class TColumnSchema { - using TTypeDesc = void*; - YDB_ACCESSOR_DEF(TString, Name); - YDB_ACCESSOR_DEF(NScheme::TTypeId, Type); - YDB_ACCESSOR_DEF(TTypeDesc, TypeDesc); - YDB_FLAG_ACCESSOR(Nullable, true); - public: - TString BuildQuery() const; - }; - - using TUpdatesBuilder = NColumnShard::TTableUpdatesBuilder; - - class TColumnTableBase { - YDB_ACCESSOR_DEF(TString, Name); - YDB_ACCESSOR_DEF(TVector, Schema); - YDB_ACCESSOR_DEF(TVector, PrimaryKey); - YDB_ACCESSOR_DEF(TVector, Sharding); - YDB_ACCESSOR(ui32, MinPartitionsCount, 1); - - std::optional> TTLConf; - public: - TString BuildQuery() const; - std::shared_ptr GetArrowSchema(const TVector& columns); - - TColumnTableBase& SetTTL(const TString& columnName, const TString& ttlConf) { - TTLConf = std::make_pair(columnName, ttlConf); - return *this; - } - - private: - virtual TString GetObjectType() const = 0; - TString BuildColumnsStr(const TVector& clumns) const; - std::shared_ptr BuildField(const TString name, const NScheme::TTypeId typeId, void*const typeDesc, bool nullable) const; - }; - - class TColumnTable : public TColumnTableBase { - private: - TString GetObjectType() const override; - }; - - class TColumnTableStore : public TColumnTableBase { - private: - TString GetObjectType() const override; - }; + bool DeserializeFromProto(const NKikimrSchemeOp::TOlapColumn::TSerializer& serializer); + TString BuildQuery() const; - private: - std::unique_ptr Kikimr; - std::unique_ptr TableClient; - std::unique_ptr Session; + bool IsEqual(const TCompression& rhs, TString& errorMessage) const; + + TString ToString() const; + }; + + class TColumnFamily { + YDB_ACCESSOR(ui32, Id, 0); + YDB_ACCESSOR_DEF(TString, FamilyName); + YDB_ACCESSOR_DEF(TString, Data); + YDB_ACCESSOR_DEF(TCompression, Compression); + + public: + bool DeserializeFromProto(const NKikimrSchemeOp::TFamilyDescription& family); + TString BuildQuery() const; + + bool IsEqual(const TColumnFamily& rhs, TString& errorMessage) const; + + TString ToString() const; + }; + + class TColumnSchema { + using TTypeDesc = void*; + YDB_ACCESSOR_DEF(TString, Name); + YDB_ACCESSOR_DEF(NScheme::TTypeId, Type); + YDB_ACCESSOR_DEF(TTypeDesc, TypeDesc); + YDB_FLAG_ACCESSOR(Nullable, true); + YDB_ACCESSOR_DEF(TString, ColumnFamilyName); public: - TTestHelper(const TKikimrSettings& settings); - TKikimrRunner& GetKikimr(); - TTestActorRuntime& GetRuntime(); - NYdb::NTable::TSession& GetSession(); - void CreateTable(const TColumnTableBase& table, const NYdb::EStatus expectedStatus = NYdb::EStatus::SUCCESS); - void DropTable(const TString& tableName); - void CreateTier(const TString& tierName); - TString CreateTieringRule(const TString& tierName, const TString& columnName); - void SetTiering(const TString& tableName, const TString& ruleName); - void ResetTiering(const TString& tableName); - void BulkUpsert(const TColumnTable& table, TTestHelper::TUpdatesBuilder& updates, const Ydb::StatusIds_StatusCode& opStatus = Ydb::StatusIds::SUCCESS); - void BulkUpsert(const TColumnTable& table, std::shared_ptr batch, const Ydb::StatusIds_StatusCode& opStatus = Ydb::StatusIds::SUCCESS); - void ReadData(const TString& query, const TString& expected, const NYdb::EStatus opStatus = NYdb::EStatus::SUCCESS); - void RebootTablets(const TString& tableName); - void WaitTabletDeletionInHive(ui64 tabletId, TDuration duration); + TString BuildQuery() const; }; + using TUpdatesBuilder = NColumnShard::TTableUpdatesBuilder; + + class TColumnTableBase { + YDB_ACCESSOR_DEF(TString, Name); + YDB_ACCESSOR_DEF(TVector, Schema); + YDB_ACCESSOR_DEF(TVector, PrimaryKey); + YDB_ACCESSOR_DEF(TVector, Sharding); + YDB_ACCESSOR(ui32, MinPartitionsCount, 1); + YDB_ACCESSOR_DEF(TVector, ColumnFamilies); + + std::optional> TTLConf; + + public: + TString BuildQuery() const; + TString BuildAlterCompressionQuery(const TString& columnName, const TCompression& compression) const; + std::shared_ptr GetArrowSchema(const TVector& columns); + + TColumnTableBase& SetTTL(const TString& columnName, const TString& ttlConf) { + TTLConf = std::make_pair(columnName, ttlConf); + return *this; + } + + private: + virtual TString GetObjectType() const = 0; + TString BuildColumnsStr(const TVector& clumns) const; + std::shared_ptr BuildField(const TString name, const NScheme::TTypeId typeId, void*const typeDesc, bool nullable) const; + }; + + class TColumnTable: public TColumnTableBase { + private: + TString GetObjectType() const override; + }; + + class TColumnTableStore: public TColumnTableBase { + private: + TString GetObjectType() const override; + }; + +private: + std::unique_ptr Kikimr; + std::unique_ptr TableClient; + std::unique_ptr Session; + +public: + TTestHelper(const TKikimrSettings& settings); + TKikimrRunner& GetKikimr(); + TTestActorRuntime& GetRuntime(); + NYdb::NTable::TSession& GetSession(); + void CreateTable(const TColumnTableBase& table, const NYdb::EStatus expectedStatus = NYdb::EStatus::SUCCESS); + void DropTable(const TString& tableName); + void EnsureSecret(const TString& name, const TString& value); + void CreateTier(const TString& tierName); + TString CreateTieringRule(const TString& tierName, const TString& columnName); + void SetTiering(const TString& tableName, const TString& tierName, const TString& columnName); + void ResetTiering(const TString& tableName); + void BulkUpsert( + const TColumnTable& table, TTestHelper::TUpdatesBuilder& updates, const Ydb::StatusIds_StatusCode& opStatus = Ydb::StatusIds::SUCCESS); + void BulkUpsert(const TColumnTable& table, std::shared_ptr batch, + const Ydb::StatusIds_StatusCode& opStatus = Ydb::StatusIds::SUCCESS); + void ReadData(const TString& query, const TString& expected, const NYdb::EStatus opStatus = NYdb::EStatus::SUCCESS); + void RebootTablets(const TString& tableName); + void WaitTabletDeletionInHive(ui64 tabletId, TDuration duration); + void SetCompression(const TColumnTableBase& columnTable, const TString& columnName, const TCompression& compression, + const NYdb::EStatus expectedStatus = NYdb::EStatus::SUCCESS); +}; } } diff --git a/ydb/core/kqp/ut/common/kqp_ut_common.cpp b/ydb/core/kqp/ut/common/kqp_ut_common.cpp index 8cd33fc1aee0..11de0b37da9e 100644 --- a/ydb/core/kqp/ut/common/kqp_ut_common.cpp +++ b/ydb/core/kqp/ut/common/kqp_ut_common.cpp @@ -122,7 +122,6 @@ TKikimrRunner::TKikimrRunner(const TKikimrSettings& settings) { appConfig.MutableColumnShardConfig()->SetDisabledOnSchemeShard(false); ServerSettings->SetAppConfig(appConfig); ServerSettings->SetFeatureFlags(settings.FeatureFlags); - ServerSettings->FeatureFlags.SetEnableImmediateWritingOnBulkUpsert(true); ServerSettings->SetNodeCount(settings.NodeCount); ServerSettings->SetEnableKqpSpilling(enableSpilling); ServerSettings->SetEnableDataColumnForIndexTable(true); @@ -520,6 +519,7 @@ void TKikimrRunner::Initialize(const TKikimrSettings& settings) { SetupLogLevelFromTestParam(NKikimrServices::KQP_BLOBS_STORAGE); SetupLogLevelFromTestParam(NKikimrServices::KQP_WORKLOAD_SERVICE); SetupLogLevelFromTestParam(NKikimrServices::TX_COLUMNSHARD); + SetupLogLevelFromTestParam(NKikimrServices::TX_COLUMNSHARD_SCAN); SetupLogLevelFromTestParam(NKikimrServices::LOCAL_PGWIRE); RunCall([this, domain = settings.DomainRoot]{ diff --git a/ydb/core/kqp/ut/common/kqp_ut_common.h b/ydb/core/kqp/ut/common/kqp_ut_common.h index 7bb1b9deafb8..415f1a07f134 100644 --- a/ydb/core/kqp/ut/common/kqp_ut_common.h +++ b/ydb/core/kqp/ut/common/kqp_ut_common.h @@ -98,6 +98,9 @@ struct TKikimrSettings: public TTestFeatureFlagsHolder { exchangerSettings->SetMaxDelayMs(10); AppConfig.MutableColumnShardConfig()->SetDisabledOnSchemeShard(false); FeatureFlags.SetEnableSparsedColumns(true); + FeatureFlags.SetEnableWritePortionsOnInsert(true); + FeatureFlags.SetEnableParameterizedDecimal(true); + FeatureFlags.SetEnableTopicAutopartitioningForCDC(true); } TKikimrSettings& SetAppConfig(const NKikimrConfig::TAppConfig& value) { AppConfig = value; return *this; } @@ -157,9 +160,9 @@ class TKikimrRunner { NYdb::TDriverConfig GetDriverConfig() const { return DriverConfig; } - NYdb::NTable::TTableClient GetTableClient() const { - return NYdb::NTable::TTableClient(*Driver, NYdb::NTable::TClientSettings() - .UseQueryCache(false)); + NYdb::NTable::TTableClient GetTableClient( + NYdb::NTable::TClientSettings settings = NYdb::NTable::TClientSettings()) const { + return NYdb::NTable::TTableClient(*Driver, settings.UseQueryCache(false)); } NYdb::NQuery::TQueryClient GetQueryClient( diff --git a/ydb/core/kqp/ut/olap/aggregations_ut.cpp b/ydb/core/kqp/ut/olap/aggregations_ut.cpp index 28dcf8d19069..dfa8fbd078fd 100644 --- a/ydb/core/kqp/ut/olap/aggregations_ut.cpp +++ b/ydb/core/kqp/ut/olap/aggregations_ut.cpp @@ -75,6 +75,33 @@ Y_UNIT_TEST_SUITE(KqpOlapAggregations) { Cout << result << Endl; CompareYson(result, R"([[23000u;]])"); } + + { + auto alterQuery = TStringBuilder() << + R"( + ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_OPTIONS, `SCAN_READER_POLICY_NAME`=`SIMPLE`) + )"; + auto session = tableClient.CreateSession().GetValueSync().GetSession(); + auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); + } + + { + auto it = tableClient + .StreamExecuteScanQuery(R"( + --!syntax_v1 + + SELECT + COUNT(*) + FROM `/Root/olapStore/olapTable` + )") + .GetValueSync(); + + UNIT_ASSERT_C(it.IsSuccess(), it.GetIssues().ToString()); + TString result = StreamResultToYson(it); + Cout << result << Endl; + CompareYson(result, R"([[23000u;]])"); + } } Y_UNIT_TEST(AggregationCountPushdown) { @@ -95,7 +122,7 @@ Y_UNIT_TEST_SUITE(KqpOlapAggregations) { WriteTestData(kikimr, "/Root/olapStore/olapTable", 20000, 2000000, 7000); WriteTestData(kikimr, "/Root/olapStore/olapTable", 30000, 1000000, 11000); } - while (csController->GetInsertFinishedCounter().Val() == 0) { + while (csController->GetCompactionFinishedCounter().Val() == 0) { Cout << "Wait indexation..." << Endl; Sleep(TDuration::Seconds(2)); } @@ -374,7 +401,7 @@ Y_UNIT_TEST_SUITE(KqpOlapAggregations) { .AddExpectedPlanOptions("KqpOlapFilter") #if SSA_RUNTIME_VERSION >= 2U .AddExpectedPlanOptions("TKqpOlapAgg") - .MutableLimitChecker().SetExpectedResultCount(1) + .MutableLimitChecker().SetExpectedResultCount(2) #else .AddExpectedPlanOptions("Condense") #endif @@ -417,7 +444,7 @@ Y_UNIT_TEST_SUITE(KqpOlapAggregations) { .AddExpectedPlanOptions("KqpOlapFilter") #if SSA_RUNTIME_VERSION >= 2U .AddExpectedPlanOptions("TKqpOlapAgg") - .MutableLimitChecker().SetExpectedResultCount(1) + .MutableLimitChecker().SetExpectedResultCount(2) #else .AddExpectedPlanOptions("CombineCore") .AddExpectedPlanOptions("KqpOlapFilter") diff --git a/ydb/core/kqp/ut/olap/blobs_sharing_ut.cpp b/ydb/core/kqp/ut/olap/blobs_sharing_ut.cpp index ea97c44484f3..70d3a20fd7e2 100644 --- a/ydb/core/kqp/ut/olap/blobs_sharing_ut.cpp +++ b/ydb/core/kqp/ut/olap/blobs_sharing_ut.cpp @@ -1,14 +1,16 @@ -#include "helpers/typed_local.h" #include "helpers/local.h" +#include "helpers/typed_local.h" #include "helpers/writer.h" -#include -#include + +#include #include -#include #include -#include #include -#include +#include +#include +#include +#include + #include #include @@ -98,7 +100,7 @@ Y_UNIT_TEST_SUITE(KqpOlapBlobsSharing) { Controller->SetCompactionControl(NYDBTest::EOptimizerCompactionWeightControl::Disable); Controller->SetExpectedShardsCount(ShardsCount); Controller->SetOverridePeriodicWakeupActivationPeriod(TDuration::Seconds(1)); - Controller->SetOverrideReadTimeoutClean(TDuration::Seconds(1)); + Controller->SetOverrideMaxReadStaleness(TDuration::Seconds(1)); Tests::NCommon::TLoggerInit(Kikimr).SetComponents({ NKikimrServices::TX_COLUMNSHARD }, "CS").Initialize(); @@ -115,7 +117,7 @@ Y_UNIT_TEST_SUITE(KqpOlapBlobsSharing) { } void WaitNormalization() { - Controller->SetOverrideReadTimeoutClean(TDuration::Seconds(1)); + Controller->SetOverrideMaxReadStaleness(TDuration::Seconds(1)); Controller->SetCompactionControl(NYDBTest::EOptimizerCompactionWeightControl::Force); const auto start = TInstant::Now(); while (!Controller->IsTrivialLinks() && TInstant::Now() - start < TDuration::Seconds(30)) { @@ -124,11 +126,11 @@ Y_UNIT_TEST_SUITE(KqpOlapBlobsSharing) { } AFL_VERIFY(Controller->IsTrivialLinks()); Controller->CheckInvariants(); - Controller->SetOverrideReadTimeoutClean(TDuration::Minutes(5)); + Controller->SetOverrideMaxReadStaleness(TDuration::Minutes(5)); } void Execute(const ui64 destinationIdx, const std::vector& sourceIdxs, const bool move, const NOlap::TSnapshot& snapshot, const std::set& pathIdxs) { - Controller->SetOverrideReadTimeoutClean(TDuration::Seconds(1)); + Controller->SetOverrideMaxReadStaleness(TDuration::Seconds(1)); AFL_VERIFY(destinationIdx < ShardIds.size()); const ui64 destination = ShardIds[destinationIdx]; std::vector sources; @@ -196,7 +198,7 @@ Y_UNIT_TEST_SUITE(KqpOlapBlobsSharing) { CSTransferStatus->Reset(); AFL_VERIFY(!Controller->IsTrivialLinks()); Controller->CheckInvariants(); - Controller->SetOverrideReadTimeoutClean(TDuration::Minutes(5)); + Controller->SetOverrideMaxReadStaleness(TDuration::Minutes(5)); } }; Y_UNIT_TEST(BlobsSharingSplit1_1) { @@ -276,7 +278,7 @@ Y_UNIT_TEST_SUITE(KqpOlapBlobsSharing) { void WaitResharding(const TString& hint = "") { const TInstant start = TInstant::Now(); bool clean = false; - while (TInstant::Now() - start < TDuration::Seconds(20)) { + while (TInstant::Now() - start < TDuration::Seconds(200)) { NYdb::NOperation::TOperationClient operationClient(Kikimr.GetDriver()); auto result = operationClient.List().GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), NYdb::EStatus::SUCCESS, result.GetIssues().ToString()); @@ -321,9 +323,6 @@ Y_UNIT_TEST_SUITE(KqpOlapBlobsSharing) { void Execute() { TLocalHelper(Kikimr).SetShardingMethod(ShardingType).CreateTestOlapTable("olapTable", "olapStore", 24, 4); - - Tests::NCommon::TLoggerInit(Kikimr).SetComponents({ NKikimrServices::TX_COLUMNSHARD, NKikimrServices::TX_COLUMNSHARD_SCAN }, "CS").SetPriority(NActors::NLog::PRI_DEBUG).Initialize(); - { WriteTestData(Kikimr, "/Root/olapStore/olapTable", 1000000, 300000000, 10000); WriteTestData(Kikimr, "/Root/olapStore/olapTable", 1100000, 300100000, 10000); @@ -403,7 +402,7 @@ Y_UNIT_TEST_SUITE(KqpOlapBlobsSharing) { } Y_UNIT_TEST(TableReshardingModuloN) { - TShardingTypeTest().SetShardingType("HASH_FUNCTION_CONSISTENCY_64").Execute(); + TShardingTypeTest().SetShardingType("HASH_FUNCTION_MODULO_N").Execute(); } class TAsyncReshardingTest: public TReshardingTest { @@ -411,7 +410,7 @@ Y_UNIT_TEST_SUITE(KqpOlapBlobsSharing) { public: TAsyncReshardingTest() { - TLocalHelper(Kikimr).CreateTestOlapTable("olapTable", "olapStore", 24, 4); + TLocalHelper(Kikimr).CreateTestOlapTable("olapTable", "olapStore", 1024, 32); } void AddBatch(int numRows) { @@ -435,11 +434,36 @@ Y_UNIT_TEST_SUITE(KqpOlapBlobsSharing) { TReshardingTest::CheckCount(NumRows); } + void AddManyColumns() { + auto alterQuery = TStringBuilder() << "ALTER TABLESTORE `/Root/olapStore` "; + for (int i = 0; i < 10000; i++) { + alterQuery << " ADD COLUMN col_" << i << " Int8"; + if (i < 10000 - 1) { + alterQuery << ", "; + } + } + + auto session = TableClient.CreateSession().GetValueSync().GetSession(); + auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); + + UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); + } + + void RestartAllShards() { + for (i64 id : CSController->GetShardActualIds()) { + Kikimr.GetTestServer().GetRuntime()->Send(MakePipePerNodeCacheID(false), NActors::TActorId(), new TEvPipeCache::TEvForward(new TEvents::TEvPoisonPill(), id, false)); + } + } + void ChangeSchema() { - auto alterQuery = - "ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=ALTER_COLUMN, NAME=level, " - "`SERIALIZER.CLASS_NAME`=`ARROW_SERIALIZER`, " - "`COMPRESSION.TYPE`=`zstd`);"; + const char* alterQuery; + if (HasNewCol) { + alterQuery = "ALTER TABLESTORE `/Root/olapStore` DROP COLUMN new_col"; + } else { + alterQuery = "ALTER TABLESTORE `/Root/olapStore` ADD COLUMN new_col Int8"; + } + HasNewCol = !HasNewCol; + auto session = TableClient.CreateSession().GetValueSync().GetSession(); auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); @@ -454,6 +478,7 @@ Y_UNIT_TEST_SUITE(KqpOlapBlobsSharing) { ui64 LastPathId = 1000000; ui64 LastTs = 300000000; ui64 NumRows = 0; + ui64 HasNewCol = false; }; Y_UNIT_TEST(UpsertWhileSplitTest) { @@ -475,6 +500,18 @@ Y_UNIT_TEST_SUITE(KqpOlapBlobsSharing) { tester.CheckCount(); } + Y_UNIT_TEST(SplitEmpty) { + TAsyncReshardingTest tester; + + tester.CheckCount(); + + tester.StartResharding("SPLIT"); + + tester.CheckCount(); + tester.WaitResharding(); + tester.CheckCount(); + } + Y_UNIT_TEST(ChangeSchemaAndSplit) { TAsyncReshardingTest tester; tester.DisableCompaction(); @@ -486,6 +523,159 @@ Y_UNIT_TEST_SUITE(KqpOlapBlobsSharing) { tester.StartResharding("SPLIT"); tester.WaitResharding(); + tester.RestartAllShards(); + + tester.CheckCount(); + } + + Y_UNIT_TEST(MultipleSchemaVersions) { + TAsyncReshardingTest tester; + tester.DisableCompaction(); + + for (int i = 0; i < 3; i++) { + tester.AddBatch(1); + tester.ChangeSchema(); + } + + tester.StartResharding("SPLIT"); + tester.WaitResharding(); + + tester.RestartAllShards(); + + tester.CheckCount(); + } + + Y_UNIT_TEST(HugeSchemeHistory) { + TAsyncReshardingTest tester; + tester.DisableCompaction(); + + tester.AddManyColumns(); + + for (int i = 0; i < 100; i++) { + tester.AddBatch(1); + tester.ChangeSchema(); + } + + tester.StartResharding("SPLIT"); + tester.WaitResharding(); + + tester.RestartAllShards(); + + tester.CheckCount(); + } + + Y_UNIT_TEST(MultipleMerge) { + TAsyncReshardingTest tester; + tester.DisableCompaction(); + + tester.AddBatch(10000); + + for (int i = 0; i < 4; i++) { + tester.StartResharding("MERGE"); + tester.WaitResharding(); + } + + tester.RestartAllShards(); + + tester.CheckCount(); + } + + Y_UNIT_TEST(MultipleSplits) { + TAsyncReshardingTest tester; + tester.DisableCompaction(); + + tester.AddBatch(10000); + + for (int i = 0; i < 4; i++) { + tester.StartResharding("SPLIT"); + tester.WaitResharding(); + } + + tester.RestartAllShards(); + + tester.CheckCount(); + } + + Y_UNIT_TEST(MultipleSplitsThenMerges) { + TAsyncReshardingTest tester; + tester.DisableCompaction(); + + tester.AddBatch(10000); + + for (int i = 0; i < 4; i++) { + tester.StartResharding("SPLIT"); + tester.WaitResharding(); + } + + for (int i = 0; i < 8; i++) { + tester.StartResharding("MERGE"); + tester.WaitResharding(); + } + + tester.RestartAllShards(); + + tester.CheckCount(); + } + + Y_UNIT_TEST(MultipleSplitsWithRestartsAfterWait) { + TAsyncReshardingTest tester; + tester.DisableCompaction(); + + tester.AddBatch(10000); + + for (int i = 0; i < 4; i++) { + tester.StartResharding("SPLIT"); + tester.WaitResharding(); + tester.RestartAllShards(); + } + + tester.CheckCount(); + } + + Y_UNIT_TEST(MultipleSplitsWithRestartsWhenWait) { + TAsyncReshardingTest tester; + tester.DisableCompaction(); + + tester.AddBatch(10000); + + for (int i = 0; i < 4; i++) { + tester.StartResharding("SPLIT"); + tester.RestartAllShards(); + tester.WaitResharding(); + } + tester.RestartAllShards(); + + tester.CheckCount(); + } + + Y_UNIT_TEST(MultipleMergesWithRestartsAfterWait) { + TAsyncReshardingTest tester; + tester.DisableCompaction(); + + tester.AddBatch(10000); + + for (int i = 0; i < 4; i++) { + tester.StartResharding("MERGE"); + tester.WaitResharding(); + tester.RestartAllShards(); + } + + tester.CheckCount(); + } + + Y_UNIT_TEST(MultipleMergesWithRestartsWhenWait) { + TAsyncReshardingTest tester; + tester.DisableCompaction(); + + tester.AddBatch(10000); + + for (int i = 0; i < 4; i++) { + tester.StartResharding("MERGE"); + tester.RestartAllShards(); + tester.WaitResharding(); + } + tester.RestartAllShards(); + tester.CheckCount(); } } diff --git a/ydb/core/kqp/ut/olap/compression_ut.cpp b/ydb/core/kqp/ut/olap/compression_ut.cpp new file mode 100644 index 000000000000..b325324a1f3d --- /dev/null +++ b/ydb/core/kqp/ut/olap/compression_ut.cpp @@ -0,0 +1,67 @@ +#include + +namespace NKikimr::NKqp { + +Y_UNIT_TEST_SUITE(KqpOlapCompression) { + Y_UNIT_TEST(DisabledAlterCompression) { + TKikimrSettings settings = TKikimrSettings().SetWithSampleTables(false).SetEnableOlapCompression(false); + TTestHelper testHelper(settings); + TVector schema = { + TTestHelper::TColumnSchema().SetName("pk_int").SetType(NScheme::NTypeIds::Uint64).SetNullable(false) + }; + TTestHelper::TCompression compression = TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecZSTD); + + TTestHelper::TColumnTable standaloneTable; + standaloneTable.SetName("/Root/StandaloneTable").SetPrimaryKey({ "pk_int" }).SetSharding({ "pk_int" }).SetSchema(schema); + testHelper.CreateTable(standaloneTable); + testHelper.SetCompression(standaloneTable, "pk_int", compression, NYdb::EStatus::SCHEME_ERROR); + + TTestHelper::TColumnTableStore testTableStore; + testTableStore.SetName("/Root/TableStoreTest").SetPrimaryKey({ "pk_int" }).SetSchema(schema); + testHelper.CreateTable(testTableStore); + testHelper.SetCompression(testTableStore, "pk_int", compression, NYdb::EStatus::PRECONDITION_FAILED); + + TTestHelper::TColumnTable testTable; + testTable.SetName("/Root/TableStoreTest/ColumnTableTest").SetPrimaryKey({ "pk_int" }).SetSharding({ "pk_int" }).SetSchema(schema); + testHelper.CreateTable(testTable); + testHelper.SetCompression(testTable, "pk_int", compression, NYdb::EStatus::SCHEME_ERROR); + } + + Y_UNIT_TEST(OffCompression) { + TKikimrSettings settings = TKikimrSettings().SetWithSampleTables(false); + TTestHelper testHelper(settings); + TVector schema = { + TTestHelper::TColumnSchema().SetName("pk_int").SetType(NScheme::NTypeIds::Uint64).SetNullable(false) + }; + TTestHelper::TCompression compression = TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + + TTestHelper::TColumnTable standaloneTable; + standaloneTable.SetName("/Root/StandaloneTable").SetPrimaryKey({ "pk_int" }).SetSharding({ "pk_int" }).SetSchema(schema); + testHelper.CreateTable(standaloneTable); + testHelper.SetCompression(standaloneTable, "pk_int", compression); + + TTestHelper::TColumnTableStore testTableStore; + testTableStore.SetName("/Root/TableStoreTest").SetPrimaryKey({ "pk_int" }).SetSchema(schema); + testHelper.CreateTable(testTableStore); + testHelper.SetCompression(testTableStore, "pk_int", compression); + } + + Y_UNIT_TEST(TestAlterCompressionTableInTableStore) { + TKikimrSettings settings = TKikimrSettings().SetWithSampleTables(false); + TTestHelper testHelper(settings); + TVector schema = { + TTestHelper::TColumnSchema().SetName("pk_int").SetType(NScheme::NTypeIds::Uint64).SetNullable(false) + }; + TTestHelper::TCompression compression = TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecZSTD); + + TTestHelper::TColumnTableStore testTableStore; + testTableStore.SetName("/Root/TableStoreTest").SetPrimaryKey({ "pk_int" }).SetSchema(schema); + testHelper.CreateTable(testTableStore); + + TTestHelper::TColumnTable testTable; + testTable.SetName("/Root/TableStoreTest/ColumnTableTest").SetPrimaryKey({ "pk_int" }).SetSharding({ "pk_int" }).SetSchema(schema); + testHelper.CreateTable(testTable); + testHelper.SetCompression(testTable, "pk_int", compression, NYdb::EStatus::SCHEME_ERROR); + } +} +} diff --git a/ydb/core/kqp/ut/olap/helpers/local.h b/ydb/core/kqp/ut/olap/helpers/local.h index 9511ad1828ef..0ac389f99441 100644 --- a/ydb/core/kqp/ut/olap/helpers/local.h +++ b/ydb/core/kqp/ut/olap/helpers/local.h @@ -36,6 +36,10 @@ class TLocalHelper: public Tests::NCS::THelper { CreateOlapTablesWithStore(tableNames, storeName, storeShardsCount, tableShardsCount); } + void CreateTestOlapTableWithoutStore(TString tableName = "olapTable", ui32 tableShardsCount = 3) { + CreateOlapTables({tableName}, tableShardsCount); + } + using TBase::TBase; TLocalHelper(TKikimrRunner& runner) @@ -44,4 +48,4 @@ class TLocalHelper: public Tests::NCS::THelper { } }; -} \ No newline at end of file +} diff --git a/ydb/core/kqp/ut/olap/helpers/typed_local.cpp b/ydb/core/kqp/ut/olap/helpers/typed_local.cpp index 32c08c2c8925..8f66e429580b 100644 --- a/ydb/core/kqp/ut/olap/helpers/typed_local.cpp +++ b/ydb/core/kqp/ut/olap/helpers/typed_local.cpp @@ -13,7 +13,6 @@ TString TTypedLocalHelper::GetTestTableSchema() const { Columns { Name: "pk_int" Type: "Int64" NotNull: true } Columns { Name: "ts" Type: "Timestamp" } KeyColumnNames: "pk_int" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES )"; return result; } @@ -34,7 +33,6 @@ TString TTypedLocalHelper::GetMultiColumnTestTableSchema(ui32 reps) const { } result += R"( KeyColumnNames: "pk_int" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES )"; return result; } diff --git a/ydb/core/kqp/ut/olap/indexes_ut.cpp b/ydb/core/kqp/ut/olap/indexes_ut.cpp index 593fe44a5d80..a334bf858103 100644 --- a/ydb/core/kqp/ut/olap/indexes_ut.cpp +++ b/ydb/core/kqp/ut/olap/indexes_ut.cpp @@ -3,10 +3,11 @@ #include #include +#include #include -#include +#include -#include +#include #include @@ -14,23 +15,23 @@ namespace NKikimr::NKqp { Y_UNIT_TEST_SUITE(KqpOlapIndexes) { Y_UNIT_TEST(IndexesActualization) { - auto settings = TKikimrSettings() - .SetWithSampleTables(false); + auto settings = TKikimrSettings().SetWithSampleTables(false); TKikimrRunner kikimr(settings); auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); csController->SetOverridePeriodicWakeupActivationPeriod(TDuration::Seconds(1)); csController->SetOverrideLagForCompactionBeforeTierings(TDuration::Seconds(1)); csController->SetOverrideReduceMemoryIntervalLimit(1LLU << 30); + csController->SetOverrideMemoryLimitForPortionReading(1e+10); + csController->SetOverrideBlobSplitSettings(NOlap::NSplitter::TSplitSettings()); TLocalHelper(kikimr).CreateTestOlapTable(); auto tableClient = kikimr.GetTableClient(); - Tests::NCommon::TLoggerInit(kikimr).SetComponents({NKikimrServices::TX_COLUMNSHARD}, "CS").SetPriority(NActors::NLog::PRI_DEBUG).Initialize(); - - std::vector uids; - std::vector resourceIds; - std::vector levels; + Tests::NCommon::TLoggerInit(kikimr) + .SetComponents({ NKikimrServices::TX_COLUMNSHARD }, "CS") + .SetPriority(NActors::NLog::PRI_DEBUG) + .Initialize(); { WriteTestData(kikimr, "/Root/olapStore/olapTable", 1000000, 300000000, 10000); @@ -40,28 +41,12 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { WriteTestData(kikimr, "/Root/olapStore/olapTable", 1400000, 300400000, 10000); WriteTestData(kikimr, "/Root/olapStore/olapTable", 2000000, 200000000, 70000); WriteTestData(kikimr, "/Root/olapStore/olapTable", 3000000, 100000000, 110000); - - const auto filler = [&](const ui32 startRes, const ui32 startUid, const ui32 count) { - for (ui32 i = 0; i < count; ++i) { - uids.emplace_back("uid_" + ::ToString(startUid + i)); - resourceIds.emplace_back(::ToString(startRes + i)); - levels.emplace_back(i % 5); - } - }; - - filler(1000000, 300000000, 10000); - filler(1100000, 300100000, 10000); - filler(1200000, 300200000, 10000); - filler(1300000, 300300000, 10000); - filler(1400000, 300400000, 10000); - filler(2000000, 200000000, 70000); - filler(3000000, 100000000, 110000); - } + csController->WaitCompactions(TDuration::Seconds(5)); { auto alterQuery = TStringBuilder() << - R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=index_uid, TYPE=BLOOM_FILTER, + R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=index_uid, TYPE=BLOOM_FILTER, FEATURES=`{"column_names" : ["uid"], "false_positive_probability" : 0.05}`); )"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); @@ -69,7 +54,8 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); } { - auto alterQuery = TStringBuilder() << + auto alterQuery = + TStringBuilder() << R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=index_resource_id, TYPE=BLOOM_FILTER, FEATURES=`{"column_names" : ["resource_id", "level"], "false_positive_probability" : 0.05}`); )"; @@ -79,22 +65,25 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { } { - auto alterQuery = TStringBuilder() << - "ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_OPTIONS, SCHEME_NEED_ACTUALIZATION=`true`);"; + auto alterQuery = + TStringBuilder() + << "ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_OPTIONS, SCHEME_NEED_ACTUALIZATION=`true`);"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); } csController->WaitActualization(TDuration::Seconds(10)); { - auto it = tableClient.StreamExecuteScanQuery(R"( + auto it = tableClient + .StreamExecuteScanQuery(R"( --!syntax_v1 SELECT COUNT(*) FROM `/Root/olapStore/olapTable` WHERE ((resource_id = '2' AND level = 222222) OR (resource_id = '1' AND level = 111111) OR (resource_id LIKE '%11dd%')) AND uid = '222' - )").GetValueSync(); + )") + .GetValueSync(); UNIT_ASSERT_C(it.IsSuccess(), it.GetIssues().ToString()); TString result = StreamResultToYson(it); @@ -102,29 +91,33 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { Cerr << csController->GetIndexesSkippingOnSelect().Val() << " / " << csController->GetIndexesApprovedOnSelect().Val() << Endl; CompareYson(result, R"([[0u;]])"); AFL_VERIFY(csController->GetIndexesSkippedNoData().Val() == 0); - AFL_VERIFY(csController->GetIndexesApprovedOnSelect().Val() < csController->GetIndexesSkippingOnSelect().Val() * 0.4) - ("approve", csController->GetIndexesApprovedOnSelect().Val())("skip", csController->GetIndexesSkippingOnSelect().Val()); + AFL_VERIFY(csController->GetIndexesApprovedOnSelect().Val() < csController->GetIndexesSkippingOnSelect().Val()) + ("approve", csController->GetIndexesApprovedOnSelect().Val())("skip", csController->GetIndexesSkippingOnSelect().Val()); } } Y_UNIT_TEST(CountMinSketchIndex) { - auto settings = TKikimrSettings() - .SetWithSampleTables(false); + auto settings = TKikimrSettings().SetWithSampleTables(false); TKikimrRunner kikimr(settings); auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); csController->SetOverridePeriodicWakeupActivationPeriod(TDuration::Seconds(1)); csController->SetOverrideLagForCompactionBeforeTierings(TDuration::Seconds(1)); csController->SetOverrideReduceMemoryIntervalLimit(1LLU << 30); + csController->SetOverrideBlobSplitSettings(NOlap::NSplitter::TSplitSettings()); - TLocalHelper(kikimr).CreateTestOlapTable(); + TLocalHelper(kikimr).CreateTestOlapTableWithoutStore(); auto tableClient = kikimr.GetTableClient(); + auto& client = kikimr.GetTestClient(); - Tests::NCommon::TLoggerInit(kikimr).SetComponents({NKikimrServices::TX_COLUMNSHARD}, "CS").SetPriority(NActors::NLog::PRI_DEBUG).Initialize(); + Tests::NCommon::TLoggerInit(kikimr) + .SetComponents({ NKikimrServices::TX_COLUMNSHARD }, "CS") + .SetPriority(NActors::NLog::PRI_DEBUG) + .Initialize(); { auto alterQuery = TStringBuilder() << - R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=cms_ts, TYPE=COUNT_MIN_SKETCH, + R"(ALTER OBJECT `/Root/olapTable` (TYPE TABLE) SET (ACTION=UPSERT_INDEX, NAME=cms_ts, TYPE=COUNT_MIN_SKETCH, FEATURES=`{"column_names" : ['timestamp']}`); )"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); @@ -134,7 +127,7 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { { auto alterQuery = TStringBuilder() << - R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=cms_res_id, TYPE=COUNT_MIN_SKETCH, + R"(ALTER OBJECT `/Root/olapTable` (TYPE TABLE) SET (ACTION=UPSERT_INDEX, NAME=cms_res_id, TYPE=COUNT_MIN_SKETCH, FEATURES=`{"column_names" : ['resource_id']}`); )"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); @@ -144,7 +137,7 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { { auto alterQuery = TStringBuilder() << - R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=cms_uid, TYPE=COUNT_MIN_SKETCH, + R"(ALTER OBJECT `/Root/olapTable` (TYPE TABLE) SET (ACTION=UPSERT_INDEX, NAME=cms_uid, TYPE=COUNT_MIN_SKETCH, FEATURES=`{"column_names" : ['uid']}`); )"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); @@ -154,7 +147,7 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { { auto alterQuery = TStringBuilder() << - R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=cms_level, TYPE=COUNT_MIN_SKETCH, + R"(ALTER OBJECT `/Root/olapTable` (TYPE TABLE) SET (ACTION=UPSERT_INDEX, NAME=cms_level, TYPE=COUNT_MIN_SKETCH, FEATURES=`{"column_names" : ['level']}`); )"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); @@ -164,7 +157,7 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { { auto alterQuery = TStringBuilder() << - R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=cms_message, TYPE=COUNT_MIN_SKETCH, + R"(ALTER OBJECT `/Root/olapTable` (TYPE TABLE) SET (ACTION=UPSERT_INDEX, NAME=cms_message, TYPE=COUNT_MIN_SKETCH, FEATURES=`{"column_names" : ['message']}`); )"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); @@ -172,15 +165,31 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); } - WriteTestData(kikimr, "/Root/olapStore/olapTable", 1000000, 300000000, 10000); - WriteTestData(kikimr, "/Root/olapStore/olapTable", 1100000, 300100000, 10000); - WriteTestData(kikimr, "/Root/olapStore/olapTable", 1200000, 300200000, 10000); - WriteTestData(kikimr, "/Root/olapStore/olapTable", 1300000, 300300000, 10000); - WriteTestData(kikimr, "/Root/olapStore/olapTable", 1400000, 300400000, 10000); - WriteTestData(kikimr, "/Root/olapStore/olapTable", 2000000, 200000000, 70000); - WriteTestData(kikimr, "/Root/olapStore/olapTable", 3000000, 100000000, 110000); + WriteTestData(kikimr, "/Root/olapTable", 1000000, 300000000, 10000); + WriteTestData(kikimr, "/Root/olapTable", 1100000, 300100000, 10000); + WriteTestData(kikimr, "/Root/olapTable", 1200000, 300200000, 10000); + WriteTestData(kikimr, "/Root/olapTable", 1300000, 300300000, 10000); + WriteTestData(kikimr, "/Root/olapTable", 1400000, 300400000, 10000); + WriteTestData(kikimr, "/Root/olapTable", 2000000, 200000000, 70000); + WriteTestData(kikimr, "/Root/olapTable", 3000000, 100000000, 110000); csController->WaitActualization(TDuration::Seconds(10)); + + { + auto res = client.Ls("/Root/olapTable"); + auto description = res->Record.GetPathDescription().GetColumnTableDescription(); + auto indexes = description.GetSchema().GetIndexes(); + UNIT_ASSERT(indexes.size() == 5); + + std::unordered_set indexNames{ "cms_ts", "cms_res_id", "cms_uid", "cms_level", "cms_message" }; + for (const auto& i : indexes) { + Cerr << ">>> " << i.GetName() << " of class name " << i.GetClassName() << Endl; + UNIT_ASSERT(i.GetClassName() == "COUNT_MIN_SKETCH"); + UNIT_ASSERT(indexNames.erase(i.GetName())); + } + UNIT_ASSERT(indexNames.empty()); + } + { auto runtime = kikimr.GetTestServer().GetRuntime(); auto sender = runtime->AllocateEdgeActor(); @@ -195,8 +204,9 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { Cerr << ">>> path id: " << j << Endl; pathids.insert(j); } - if (++shard == 3) + if (++shard == 3) { break; + } } UNIT_ASSERT(pathids.size() == 1); @@ -207,10 +217,10 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { auto request = std::make_unique(); request->Record.MutableTable()->MutablePathId()->SetLocalId(pathId); - runtime->Send(MakePipePerNodeCacheID(false), sender, new TEvPipeCache::TEvForward( - request.release(), i, false)); - if (++shard == 3) + runtime->Send(MakePipePerNodeCacheID(false), sender, new TEvPipeCache::TEvForward(request.release(), i, false)); + if (++shard == 3) { break; + } } auto sketch = std::unique_ptr(TCountMinSketch::Create()); @@ -231,8 +241,7 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { } Y_UNIT_TEST(SchemeActualizationOnceOnStart) { - auto settings = TKikimrSettings() - .SetWithSampleTables(false); + auto settings = TKikimrSettings().SetWithSampleTables(false); TKikimrRunner kikimr(settings); auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); @@ -259,14 +268,14 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { filler(1000000, 300000000, 10000); filler(1100000, 300100000, 10000); - } const ui64 initCount = csController->GetActualizationRefreshSchemeCount().Val(); AFL_VERIFY(initCount == 3)("started_value", initCount); for (ui32 i = 0; i < 10; ++i) { - auto alterQuery = TStringBuilder() << - "ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_OPTIONS, SCHEME_NEED_ACTUALIZATION=`true`);"; + auto alterQuery = + TStringBuilder() + << "ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_OPTIONS, SCHEME_NEED_ACTUALIZATION=`true`);"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); @@ -275,25 +284,28 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { AFL_VERIFY(updatesCount == 30 + initCount)("after_modification", updatesCount); for (auto&& i : csController->GetShardActualIds()) { - kikimr.GetTestServer().GetRuntime()->Send(MakePipePerNodeCacheID(false), NActors::TActorId(), new TEvPipeCache::TEvForward( - new TEvents::TEvPoisonPill(), i, false)); + kikimr.GetTestServer().GetRuntime()->Send( + MakePipePerNodeCacheID(false), NActors::TActorId(), new TEvPipeCache::TEvForward(new TEvents::TEvPoisonPill(), i, false)); } { - auto it = tableClient.StreamExecuteScanQuery(R"( + auto it = tableClient + .StreamExecuteScanQuery(R"( --!syntax_v1 SELECT COUNT(*) FROM `/Root/olapStore/olapTable` - )").GetValueSync(); + )") + .GetValueSync(); UNIT_ASSERT_C(it.IsSuccess(), it.GetIssues().ToString()); TString result = StreamResultToYson(it); Cout << result << Endl; CompareYson(result, R"([[20000u;]])"); } - AFL_VERIFY(updatesCount + 3 /*tablets count*/ * 1 /*normalizers*/ == - (ui64)csController->GetActualizationRefreshSchemeCount().Val())("updates", updatesCount)("count", csController->GetActualizationRefreshSchemeCount().Val()); + AFL_VERIFY(updatesCount + 6 == + (ui64)csController->GetActualizationRefreshSchemeCount().Val())("updates", updatesCount)( + "count", csController->GetActualizationRefreshSchemeCount().Val()); } class TTestIndexesScenario { @@ -301,6 +313,31 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { TKikimrSettings Settings; std::unique_ptr Kikimr; YDB_ACCESSOR(TString, StorageId, "__DEFAULT"); + + ui64 SkipStart = 0; + ui64 NoDataStart = 0; + ui64 ApproveStart = 0; + + template + void ResetZeroLevel(TController& g) { + SkipStart = g->GetIndexesSkippingOnSelect().Val(); + ApproveStart = g->GetIndexesApprovedOnSelect().Val(); + NoDataStart = g->GetIndexesSkippedNoData().Val(); + } + + void ExecuteSQL(const TString& text, const TString& expectedResult) const { + auto tableClient = Kikimr->GetTableClient(); + auto it = tableClient.StreamExecuteScanQuery(text).GetValueSync(); + UNIT_ASSERT_C(it.IsSuccess(), it.GetIssues().ToString()); + TString result = StreamResultToYson(it); + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("result", result)("expected", expectedResult); + auto* controller = NYDBTest::TControllers::GetControllerAs(); + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("skip", controller->GetIndexesSkippingOnSelect().Val() - SkipStart)( + "check", controller->GetIndexesApprovedOnSelect().Val() - ApproveStart)( + "no_data", controller->GetIndexesSkippedNoData().Val() - NoDataStart); + CompareYson(result, expectedResult); + } + public: TTestIndexesScenario& Initialize() { Settings = TKikimrSettings().SetWithSampleTables(false); @@ -308,30 +345,44 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { return *this; } - void Execute() const { + void Execute() { + auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); + csController->SetOverrideReduceMemoryIntervalLimit(1LLU << 30); + csController->SetOverrideMemoryLimitForPortionReading(1e+10); + csController->SetOverrideBlobSplitSettings(NOlap::NSplitter::TSplitSettings()); TLocalHelper(*Kikimr).CreateTestOlapTable(); auto tableClient = Kikimr->GetTableClient(); // Tests::NCommon::TLoggerInit(kikimr).Initialize(); - auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); - csController->SetOverrideReduceMemoryIntervalLimit(1LLU << 30); - { - auto alterQuery = TStringBuilder() << Sprintf( - R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=index_uid, TYPE=BLOOM_FILTER, + auto alterQuery = + TStringBuilder() << Sprintf( + R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=index_uid, TYPE=BLOOM_FILTER, FEATURES=`{"column_names" : ["uid"], "false_positive_probability" : 0.05, "storage_id" : "%s"}`); - )", StorageId.data()); + )", + StorageId.data()); auto session = tableClient.CreateSession().GetValueSync().GetSession(); auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); } { - auto alterQuery = TStringBuilder() << Sprintf( - R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=index_resource_id, TYPE=BLOOM_FILTER, + auto alterQuery = + TStringBuilder() << Sprintf( + R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=index_ngramm_uid, TYPE=BLOOM_NGRAMM_FILTER, + FEATURES=`{"column_name" : "resource_id", "ngramm_size" : 3, "hashes_count" : 2, "filter_size_bytes" : 512, "records_count" : 1024}`); + )"); + auto session = tableClient.CreateSession().GetValueSync().GetSession(); + auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); + } + { + auto alterQuery = + TStringBuilder() << Sprintf( + R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=index_resource_id, TYPE=BLOOM_FILTER, FEATURES=`{"column_names" : ["resource_id", "level"], "false_positive_probability" : 0.05, "storage_id" : "%s"}`); - )", StorageId.data() - ); + )", + StorageId.data()); auto session = tableClient.CreateSession().GetValueSync().GetSession(); auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); @@ -342,13 +393,15 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { std::vector levels; { - WriteTestData(*Kikimr, "/Root/olapStore/olapTable", 1000000, 300000000, 10000); - WriteTestData(*Kikimr, "/Root/olapStore/olapTable", 1100000, 300100000, 10000); - WriteTestData(*Kikimr, "/Root/olapStore/olapTable", 1200000, 300200000, 10000); - WriteTestData(*Kikimr, "/Root/olapStore/olapTable", 1300000, 300300000, 10000); - WriteTestData(*Kikimr, "/Root/olapStore/olapTable", 1400000, 300400000, 10000); - WriteTestData(*Kikimr, "/Root/olapStore/olapTable", 2000000, 200000000, 70000); - WriteTestData(*Kikimr, "/Root/olapStore/olapTable", 3000000, 100000000, 110000); + for (ui32 i = 0; i < 2; ++i) { + WriteTestData(*Kikimr, "/Root/olapStore/olapTable", 1000000, 300000000, 10000); + WriteTestData(*Kikimr, "/Root/olapStore/olapTable", 1100000, 300100000, 10000); + WriteTestData(*Kikimr, "/Root/olapStore/olapTable", 1200000, 300200000, 10000); + WriteTestData(*Kikimr, "/Root/olapStore/olapTable", 1300000, 300300000, 10000); + WriteTestData(*Kikimr, "/Root/olapStore/olapTable", 1400000, 300400000, 10000); + WriteTestData(*Kikimr, "/Root/olapStore/olapTable", 2000000, 200000000, 70000); + WriteTestData(*Kikimr, "/Root/olapStore/olapTable", 3000000, 100000000, 110000); + } const auto filler = [&](const ui32 startRes, const ui32 startUid, const ui32 count) { for (ui32 i = 0; i < count; ++i) { @@ -365,23 +418,9 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { filler(1400000, 300400000, 10000); filler(2000000, 200000000, 70000); filler(3000000, 100000000, 110000); - } - { - auto it = tableClient.StreamExecuteScanQuery(R"( - --!syntax_v1 - - SELECT - COUNT(*) - FROM `/Root/olapStore/olapTable` - )").GetValueSync(); - - UNIT_ASSERT_C(it.IsSuccess(), it.GetIssues().ToString()); - TString result = StreamResultToYson(it); - Cout << result << Endl; - CompareYson(result, R"([[230000u;]])"); - } + ExecuteSQL(R"(SELECT COUNT(*) FROM `/Root/olapStore/olapTable`)", "[[230000u;]]"); AFL_VERIFY(csController->GetIndexesSkippingOnSelect().Val() == 0); AFL_VERIFY(csController->GetIndexesApprovedOnSelect().Val() == 0); @@ -395,52 +434,115 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { Cerr << "WAIT_COMPACTION: " << csController->GetCompactionStartedCounter().Val() << Endl; Sleep(TDuration::Seconds(1)); } + // important checker for control compactions (<=21) and control indexes constructed (>=21) + AFL_VERIFY(csController->GetCompactionStartedCounter().Val() == 21)("count", csController->GetCompactionStartedCounter().Val()); { - auto it = tableClient.StreamExecuteScanQuery(R"( - --!syntax_v1 + ExecuteSQL(R"(SELECT COUNT(*) + FROM `/Root/olapStore/olapTable` + WHERE resource_id LIKE '%110a151' AND resource_id LIKE '110a%' AND resource_id LIKE '%dd%')", "[[0u;]]"); + AFL_VERIFY(!csController->GetIndexesApprovedOnSelect().Val()); + AFL_VERIFY(csController->GetIndexesSkippingOnSelect().Val()); + } + { + ResetZeroLevel(csController); + ExecuteSQL(R"(SELECT COUNT(*) + FROM `/Root/olapStore/olapTable` + WHERE resource_id LIKE '%110a151%')", "[[0u;]]"); + AFL_VERIFY(!csController->GetIndexesApprovedOnSelect().Val()); + AFL_VERIFY(csController->GetIndexesSkippingOnSelect().Val() - SkipStart); + } + { + ResetZeroLevel(csController); + ExecuteSQL(R"(SELECT COUNT(*) + FROM `/Root/olapStore/olapTable` + WHERE ((resource_id = '2' AND level = 222222) OR (resource_id = '1' AND level = 111111) OR (resource_id LIKE '%11dd%')) AND uid = '222')", "[[0u;]]"); - SELECT - COUNT(*) - FROM `/Root/olapStore/olapTable` - WHERE ((resource_id = '2' AND level = 222222) OR (resource_id = '1' AND level = 111111) OR (resource_id LIKE '%11dd%')) AND uid = '222' - )").GetValueSync(); - - UNIT_ASSERT_C(it.IsSuccess(), it.GetIssues().ToString()); - TString result = StreamResultToYson(it); - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("result", result); - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("skip", csController->GetIndexesSkippingOnSelect().Val())("check", csController->GetIndexesApprovedOnSelect().Val()); - CompareYson(result, R"([[0u;]])"); - if (StorageId == "__LOCAL_METADATA") { - AFL_VERIFY(csController->GetIndexesSkippedNoData().Val()); - } else { - AFL_VERIFY(csController->GetIndexesSkippedNoData().Val() == 0)("val", csController->GetIndexesSkippedNoData().Val()); + AFL_VERIFY(csController->GetIndexesSkippedNoData().Val() == 0)("val", csController->GetIndexesSkippedNoData().Val()); + AFL_VERIFY(csController->GetIndexesApprovedOnSelect().Val() - ApproveStart < csController->GetIndexesSkippingOnSelect().Val() - SkipStart); + } + { + ResetZeroLevel(csController); + ui32 requestsCount = 100; + for (ui32 i = 0; i < requestsCount; ++i) { + const ui32 idx = RandomNumber(uids.size()); + const auto query = [](const TString& res, const TString& uid, const ui32 level) { + Y_UNUSED(uid); + Y_UNUSED(level); + TStringBuilder sb; + sb << "SELECT COUNT(*) FROM `/Root/olapStore/olapTable`" << Endl; + sb << "WHERE(" << Endl; + sb << "resource_id = '" << res << "' AND" << Endl; + sb << "uid= '" << uid << "' AND" << Endl; + sb << "level= " << level << Endl; + sb << ")"; + return sb; + }; + ExecuteSQL(query(resourceIds[idx], uids[idx], levels[idx]), "[[1u;]]"); } - AFL_VERIFY(csController->GetIndexesApprovedOnSelect().Val() < csController->GetIndexesSkippingOnSelect().Val()); + AFL_VERIFY((csController->GetIndexesApprovedOnSelect().Val() - ApproveStart) * 5 < csController->GetIndexesSkippingOnSelect().Val() - SkipStart) + ("approved", csController->GetIndexesApprovedOnSelect().Val() - ApproveStart)( + "skipped", csController->GetIndexesSkippingOnSelect().Val() - SkipStart); } - ui32 requestsCount = 100; - for (ui32 i = 0; i < requestsCount; ++i) { - const ui32 idx = RandomNumber(uids.size()); - const auto query = [](const TString& res, const TString& uid, const ui32 level) { - TStringBuilder sb; - sb << "SELECT COUNT(*) FROM `/Root/olapStore/olapTable`" << Endl; - sb << "WHERE(" << Endl; - sb << "resource_id = '" << res << "' AND" << Endl; - sb << "uid= '" << uid << "' AND" << Endl; - sb << "level= " << level << Endl; - sb << ")"; - return sb; - }; - auto it = tableClient.StreamExecuteScanQuery(query(resourceIds[idx], uids[idx], levels[idx])).GetValueSync(); - - UNIT_ASSERT_C(it.IsSuccess(), it.GetIssues().ToString()); - TString result = StreamResultToYson(it); - Cout << csController->GetIndexesSkippingOnSelect().Val() << " / " << csController->GetIndexesApprovedOnSelect().Val() << " / " << csController->GetIndexesSkippedNoData().Val() << Endl; - CompareYson(result, R"([[1u;]])"); + { + ResetZeroLevel(csController); + ui32 requestsCount = 300; + for (ui32 i = 0; i < requestsCount; ++i) { + const ui32 idx = RandomNumber(uids.size()); + const auto query = [](const TString& res, const TString& uid, const ui32 level) { + Y_UNUSED(uid); + Y_UNUSED(level); + TStringBuilder sb; + sb << "SELECT COUNT(*) FROM `/Root/olapStore/olapTable`" << Endl; + sb << "WHERE" << Endl; + sb << "resource_id LIKE '%" << res << "%'" << Endl; + return sb; + }; + ExecuteSQL(query(resourceIds[idx], uids[idx], levels[idx]), "[[1u;]]"); + } + AFL_VERIFY(csController->GetIndexesSkippingOnSelect().Val() - SkipStart)("approved", csController->GetIndexesApprovedOnSelect().Val() - ApproveStart)( + "skipped", csController->GetIndexesSkippingOnSelect().Val() - SkipStart); + } + { + ResetZeroLevel(csController); + ui32 requestsCount = 300; + for (ui32 i = 0; i < requestsCount; ++i) { + const ui32 idx = RandomNumber(uids.size()); + const auto query = [](const TString& res, const TString& uid, const ui32 level) { + Y_UNUSED(uid); + Y_UNUSED(level); + TStringBuilder sb; + sb << "SELECT COUNT(*) FROM `/Root/olapStore/olapTable`" << Endl; + sb << "WHERE" << Endl; + sb << "resource_id LIKE '" << res << "%'" << Endl; + return sb; + }; + ExecuteSQL(query(resourceIds[idx], uids[idx], levels[idx]), "[[1u;]]"); + } + AFL_VERIFY(csController->GetIndexesSkippingOnSelect().Val() - SkipStart)( + "approved", csController->GetIndexesApprovedOnSelect().Val() - ApproveStart)( + "skipped", csController->GetIndexesSkippingOnSelect().Val() - SkipStart); + } + { + ResetZeroLevel(csController); + ui32 requestsCount = 300; + for (ui32 i = 0; i < requestsCount; ++i) { + const ui32 idx = RandomNumber(uids.size()); + const auto query = [](const TString& res, const TString& uid, const ui32 level) { + Y_UNUSED(uid); + Y_UNUSED(level); + TStringBuilder sb; + sb << "SELECT COUNT(*) FROM `/Root/olapStore/olapTable`" << Endl; + sb << "WHERE" << Endl; + sb << "resource_id LIKE '%" << res << "'" << Endl; + return sb; + }; + ExecuteSQL(query(resourceIds[idx], uids[idx], levels[idx]), "[[1u;]]"); + } + AFL_VERIFY(csController->GetIndexesSkippingOnSelect().Val() - SkipStart)( + "approved", csController->GetIndexesApprovedOnSelect().Val() - ApproveStart)( + "skipped", csController->GetIndexesSkippingOnSelect().Val() - SkipStart); } - - AFL_VERIFY(csController->GetIndexesApprovedOnSelect().Val() < csController->GetIndexesSkippingOnSelect().Val()) - ("approved", csController->GetIndexesApprovedOnSelect().Val())("skipped", csController->GetIndexesSkippingOnSelect().Val()); } }; @@ -465,7 +567,7 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { { auto alterQuery = TStringBuilder() << - R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=index_uid, TYPE=BLOOM_FILTER, + R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=index_uid, TYPE=BLOOM_FILTER, FEATURES=`{"column_names" : ["uid"], "false_positive_probability" : 0.05}`); )"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); @@ -475,7 +577,7 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { { auto alterQuery = TStringBuilder() << - R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=index_uid, TYPE=BLOOM_FILTER, + R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=index_uid, TYPE=BLOOM_FILTER, FEATURES=`{"column_names" : ["uid", "resource_id"], "false_positive_probability" : 0.05}`); )"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); @@ -485,7 +587,7 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { { auto alterQuery = TStringBuilder() << - R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=index_uid, TYPE=BLOOM_FILTER, + R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=index_uid, TYPE=BLOOM_FILTER, FEATURES=`{"column_names" : ["uid"], "false_positive_probability" : 0.005}`); )"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); @@ -495,7 +597,7 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { { auto alterQuery = TStringBuilder() << - R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=index_uid, TYPE=BLOOM_FILTER, + R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=index_uid, TYPE=BLOOM_FILTER, FEATURES=`{"column_names" : ["uid"], "false_positive_probability" : 0.01}`); )"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); @@ -509,8 +611,7 @@ Y_UNIT_TEST_SUITE(KqpOlapIndexes) { auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); } - } } -} +} // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/ut/olap/kqp_olap_stats_ut.cpp b/ydb/core/kqp/ut/olap/kqp_olap_stats_ut.cpp index c1fcab4be0fd..bc9ae55b2ffc 100644 --- a/ydb/core/kqp/ut/olap/kqp_olap_stats_ut.cpp +++ b/ydb/core/kqp/ut/olap/kqp_olap_stats_ut.cpp @@ -12,7 +12,7 @@ using namespace NYdb::NTable; Y_UNIT_TEST_SUITE(KqpOlapStats) { constexpr size_t inserted_rows = 1000; constexpr size_t tables_in_store = 1000; - constexpr size_t size_single_table = 13152; + constexpr size_t size_single_table = 12688; const TVector schema = { TTestHelper::TColumnSchema().SetName("id").SetType(NScheme::NTypeIds::Int32).SetNullable(false), diff --git a/ydb/core/kqp/ut/olap/kqp_olap_ut.cpp b/ydb/core/kqp/ut/olap/kqp_olap_ut.cpp index 002053da8e4c..9165c99f37ea 100644 --- a/ydb/core/kqp/ut/olap/kqp_olap_ut.cpp +++ b/ydb/core/kqp/ut/olap/kqp_olap_ut.cpp @@ -116,7 +116,6 @@ Y_UNIT_TEST_SUITE(KqpOlap) { Columns { Name: "Datetime_column" Type: "Datetime" } #Columns { Name: "Interval_column" Type: "Interval" } KeyColumnNames: "key" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } } )"); @@ -1504,7 +1503,7 @@ Y_UNIT_TEST_SUITE(KqpOlap) { auto sender = runtime->AllocateEdgeActor(); InitRoot(server, sender); - Tests::NCommon::TLoggerInit(runtime).Initialize(); +// Tests::NCommon::TLoggerInit(runtime).Initialize(); const ui32 numShards = 10; const ui32 numIterations = 50; @@ -2444,11 +2443,11 @@ Y_UNIT_TEST_SUITE(KqpOlap) { tableInserter.AddRow().Add(2).Add("test_res_2").Add("val2").AddNull(); testHelper.BulkUpsert(testTable, tableInserter); } - while (csController->GetInsertFinishedCounter().Val() == 0) { - Cout << "Wait indexation..." << Endl; - Sleep(TDuration::Seconds(2)); - } - testHelper.ReadData("SELECT * FROM `/Root/ColumnTableTest` WHERE id=2", "[[2;\"test_res_2\";#;[\"val1\"]]]"); +// while (csController->GetCompactionFinishedCounter().Val() == 0) { +// Cout << "Wait indexation..." << Endl; +// Sleep(TDuration::Seconds(2)); +// } + testHelper.ReadData("SELECT * FROM `/Root/ColumnTableTest` WHERE id=2", "[[2;\"test_res_2\";#;[\"val2\"]]]"); } Y_UNIT_TEST(BulkUpsertUpdate) { @@ -2470,10 +2469,10 @@ Y_UNIT_TEST_SUITE(KqpOlap) { tableInserter.AddRow().Add(1).Add(10); testHelper.BulkUpsert(testTable, tableInserter); } - while (csController->GetInsertFinishedCounter().Val() < 1) { - Cout << "Wait indexation..." << Endl; - Sleep(TDuration::Seconds(2)); - } +// while (csController->GetCompactionFinishedCounter().Val() < 1) { +// Cout << "Wait compaction..." << Endl; +// Sleep(TDuration::Seconds(2)); +// } testHelper.ReadData("SELECT value FROM `/Root/ColumnTableTest` WHERE id = 1", "[[10]]"); { TTestHelper::TUpdatesBuilder tableInserter(testTable.GetArrowSchema(schema)); @@ -2481,8 +2480,8 @@ Y_UNIT_TEST_SUITE(KqpOlap) { testHelper.BulkUpsert(testTable, tableInserter); } testHelper.ReadData("SELECT value FROM `/Root/ColumnTableTest` WHERE id = 1", "[[110]]"); - while (csController->GetInsertFinishedCounter().Val() < 2) { - Cout << "Wait indexation..." << Endl; + while (csController->GetCompactionFinishedCounter().Val() < 1) { + Cout << "Wait compaction..." << Endl; Sleep(TDuration::Seconds(2)); } testHelper.ReadData("SELECT value FROM `/Root/ColumnTableTest` WHERE id = 1", "[[110]]"); @@ -2703,6 +2702,18 @@ Y_UNIT_TEST_SUITE(KqpOlap) { UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), NYdb::EStatus::GENERIC_ERROR, alterResult.GetIssues().ToString()); } + { + auto alterQuery = + TStringBuilder() << + R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_OPTIONS, `COMPACTION_PLANNER.CLASS_NAME`=`lc-buckets`, `COMPACTION_PLANNER.FEATURES`=` + {"levels" : [{"class_name" : "Zero", "portions_live_duration" : "180s", "expected_blobs_size" : 2048000}, + {"class_name" : "Zero", "expected_blobs_size" : 2048000}, {"class_name" : "Zero"}]}`); + )"; + auto session = tableClient.CreateSession().GetValueSync().GetSession(); + auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); + } + { auto it = tableClient.StreamExecuteScanQuery(R"( --!syntax_v1 @@ -2720,6 +2731,64 @@ Y_UNIT_TEST_SUITE(KqpOlap) { } + Y_UNIT_TEST(MetadataMemoryManager) { + auto settings = TKikimrSettings().SetWithSampleTables(false); + TKikimrRunner kikimr(settings); + + TLocalHelper(kikimr).CreateTestOlapTable(); + auto tableClient = kikimr.GetTableClient(); + + // Tests::NCommon::TLoggerInit(kikimr).Initialize(); + + auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); + csController->SetOverrideReduceMemoryIntervalLimit(1LLU << 30); + + WriteTestData(kikimr, "/Root/olapStore/olapTable", 1000000, 300000000, 10000); + WriteTestData(kikimr, "/Root/olapStore/olapTable", 1100000, 300100000, 10000); + { + auto it = tableClient + .StreamExecuteScanQuery(R"( + --!syntax_v1 + + SELECT + COUNT(*) + FROM `/Root/olapStore/olapTable` + )") + .GetValueSync(); + + UNIT_ASSERT_C(it.IsSuccess(), it.GetIssues().ToString()); + TString result = StreamResultToYson(it); + Cout << result << Endl; + CompareYson(result, R"([[20000u;]])"); + } + { + auto alterQuery = + TStringBuilder() << + R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_OPTIONS, `METADATA_MEMORY_MANAGER.CLASS_NAME`=`local_db`, + `METADATA_MEMORY_MANAGER.FEATURES`=`{"memory_cache_size" : 0}`); + )"; + auto session = tableClient.CreateSession().GetValueSync().GetSession(); + auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); + } + { + auto it = tableClient + .StreamExecuteScanQuery(R"( + --!syntax_v1 + + SELECT + COUNT(*) + FROM `/Root/olapStore/olapTable` + )") + .GetValueSync(); + + UNIT_ASSERT_C(it.IsSuccess(), it.GetIssues().ToString()); + TString result = StreamResultToYson(it); + Cout << result << Endl; + CompareYson(result, R"([[20000u;]])"); + } + } + Y_UNIT_TEST(NormalizeAbsentColumn) { auto settings = TKikimrSettings().SetWithSampleTables(false); TKikimrRunner kikimr(settings); diff --git a/ydb/core/kqp/ut/olap/sparsed_ut.cpp b/ydb/core/kqp/ut/olap/sparsed_ut.cpp index 73b75f2cc53f..bdd895e8e240 100644 --- a/ydb/core/kqp/ut/olap/sparsed_ut.cpp +++ b/ydb/core/kqp/ut/olap/sparsed_ut.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace NKikimr::NKqp { @@ -21,13 +22,12 @@ Y_UNIT_TEST_SUITE(KqpOlapSparsed) { const TString StoreName; ui32 MultiColumnRepCount = 100; static const ui32 SKIP_GROUPS = 7; - const TVector FIELD_NAMES{"utf", "int", "uint", "float", "double"}; + const TVector FIELD_NAMES{ "utf", "int", "uint", "float", "double" }; public: TSparsedDataTest(const TString& storeName) : Kikimr(Settings) , CSController(NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard()) - , StoreName(storeName) - { + , StoreName(storeName) { } @@ -79,7 +79,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSparsed) { Fill(&counts[0], &counts[FIELD_NAMES.size() * groupsCount], 0); - for (auto& row: rows) { + for (auto& row : rows) { auto incCounts = [&](ui32 i, const TString& column) { if (*NYdb::TValueParser(row.at(column)).GetOptionalBool()) { counts[i]++; @@ -94,7 +94,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSparsed) { incCounts(ind++, "def_float" + grStr); incCounts(ind++, "def_double" + grStr); } - } + } } void CheckAllFieldsTable(bool firstCall, ui32 countExpectation, ui32* defCountStart) { @@ -169,7 +169,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSparsed) { NArrow::NConstruction::TStringPoolFiller sPool(1000, 52, "abcde", frq); helper.FillTable(sPool, shiftKff, 10000); }, - [&](bool firstCall) { + [&](bool firstCall) { CheckTable("field", "'abcde'", firstCall, countExpectation, defCountStart); }); } @@ -181,7 +181,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSparsed) { TTypedLocalHelper helper("Utf8", Kikimr); helper.FillMultiColumnTable(MultiColumnRepCount, shiftKff, 10000); }, - [&](bool firstCall) { + [&](bool firstCall) { CheckAllFieldsTable(firstCall, countExpectation, defCountStart); }); } @@ -302,6 +302,47 @@ Y_UNIT_TEST_SUITE(KqpOlapSparsed) { TSparsedDataTest test(""); test.Execute(); } + + Y_UNIT_TEST(AccessorActualization) { + auto settings = TKikimrSettings().SetWithSampleTables(false); + TKikimrRunner kikimr(settings); + + auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); + csController->SetOverridePeriodicWakeupActivationPeriod(TDuration::Seconds(1)); + csController->SetOverrideLagForCompactionBeforeTierings(TDuration::Seconds(1)); + csController->SetOverrideReduceMemoryIntervalLimit(1LLU << 30); + + TLocalHelper helper(kikimr); + helper.SetOptionalStorageId(NOlap::NBlobOperations::TGlobal::DefaultStorageId); + helper.CreateTestOlapTable(); + auto tableClient = kikimr.GetTableClient(); + + auto session = tableClient.CreateSession().GetValueSync().GetSession(); + Tests::NCommon::TLoggerInit(kikimr).SetComponents({ NKikimrServices::TX_COLUMNSHARD }, "CS").SetPriority(NActors::NLog::PRI_DEBUG).Initialize(); + + WriteTestData(kikimr, "/Root/olapStore/olapTable", 1000000, 300000000, 10000); + csController->WaitIndexation(TDuration::Seconds(3)); + + { + auto result = session.ExecuteSchemeQuery("ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=ALTER_COLUMN, NAME=uid, `DATA_ACCESSOR_CONSTRUCTOR.CLASS_NAME`=`SPARSED`)").GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), NYdb::EStatus::SUCCESS, result.GetIssues().ToString()); + } + + { + auto result = session.ExecuteSchemeQuery("ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_OPTIONS, SCHEME_NEED_ACTUALIZATION=`true`)").GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), NYdb::EStatus::SUCCESS, result.GetIssues().ToString()); + } + csController->WaitActualization(TDuration::Seconds(5)); + { + auto it = tableClient.StreamExecuteScanQuery(R"( + --!syntax_v1 + SELECT count(uid) FROM `/Root/olapStore/olapTable` + )").GetValueSync(); + UNIT_ASSERT_C(it.IsSuccess(), it.GetIssues().ToString()); + Cerr << StreamResultToYson(it) << Endl; + } + } + } } // namespace diff --git a/ydb/core/kqp/ut/olap/statistics_ut.cpp b/ydb/core/kqp/ut/olap/statistics_ut.cpp index ece5e454bacb..094fefbd4028 100644 --- a/ydb/core/kqp/ut/olap/statistics_ut.cpp +++ b/ydb/core/kqp/ut/olap/statistics_ut.cpp @@ -1,4 +1,5 @@ #include "helpers/typed_local.h" + #include namespace NKikimr::NKqp { @@ -14,25 +15,30 @@ Y_UNIT_TEST_SUITE(KqpOlapStatistics) { helper.CreateTestOlapTable(); auto tableClient = kikimr.GetTableClient(); { - auto alterQuery = TStringBuilder() << R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=max_pk_int, TYPE=MAX, FEATURES=`{\"column_name\": \"pk_int\"}`))"; + auto alterQuery = + TStringBuilder() + << R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=max_pk_int, TYPE=MAX, FEATURES=`{\"column_name\": \"pk_int\"}`))"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); } { - auto alterQuery = TStringBuilder() << "ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=max_pk_int, TYPE=MAX, FEATURES=`{\"column_name\": \"field\"}`);"; + auto alterQuery = TStringBuilder() << "ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, " + "NAME=max_pk_int, TYPE=MAX, FEATURES=`{\"column_name\": \"field\"}`);"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); UNIT_ASSERT_VALUES_UNEQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); } { - auto alterQuery = TStringBuilder() << "ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=max_pk_int, TYPE=MAX, FEATURES=`{\"column_name\": \"pk_int\"}`);"; + auto alterQuery = TStringBuilder() << "ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, " + "NAME=max_pk_int, TYPE=MAX, FEATURES=`{\"column_name\": \"pk_int\"}`);"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); UNIT_ASSERT_VALUES_UNEQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); } { - auto alterQuery = TStringBuilder() << "ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=DROP_INDEX, NAME=max_pk_int);"; + auto alterQuery = TStringBuilder() + << "ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=DROP_INDEX, NAME=max_pk_int);"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); @@ -40,6 +46,44 @@ Y_UNIT_TEST_SUITE(KqpOlapStatistics) { } } + Y_UNIT_TEST(StatsUsageNotPK) { + auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); + { + auto settings = TKikimrSettings().SetWithSampleTables(false); + TKikimrRunner kikimr(settings); + Tests::NCommon::TLoggerInit(kikimr).Initialize(); + TTypedLocalHelper helper("Utf8", kikimr); + helper.CreateTestOlapTable(); + auto tableClient = kikimr.GetTableClient(); + { + auto alterQuery = TStringBuilder() << "ALTER TABLE `/Root/olapStore/olapTable` SET (TTL = Interval(\"P1D\") ON ts);"; + auto session = tableClient.CreateSession().GetValueSync().GetSession(); + auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_UNEQUAL(alterResult.GetStatus(), NYdb::EStatus::SUCCESS); + } + { + auto alterQuery = + TStringBuilder() + << R"(ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=max_ts, TYPE=MAX, FEATURES=`{\"column_name\": \"ts\"}`))"; + auto session = tableClient.CreateSession().GetValueSync().GetSession(); + auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); + } + { + auto alterQuery = TStringBuilder() << "ALTER TABLE `/Root/olapStore/olapTable` SET (TTL = Interval(\"P1D\") ON ts);"; + auto session = tableClient.CreateSession().GetValueSync().GetSession(); + auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL(alterResult.GetStatus(), NYdb::EStatus::SUCCESS); + } + { + auto alterQuery = TStringBuilder() << "ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=DROP_INDEX, NAME=max_ts);"; + auto session = tableClient.CreateSession().GetValueSync().GetSession(); + auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_UNEQUAL(alterResult.GetStatus(), NYdb::EStatus::SUCCESS); + } + } + } + Y_UNIT_TEST(StatsUsageWithTTL) { auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); { @@ -50,7 +94,8 @@ Y_UNIT_TEST_SUITE(KqpOlapStatistics) { helper.CreateTestOlapTable(); auto tableClient = kikimr.GetTableClient(); { - auto alterQuery = TStringBuilder() << "ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, TYPE=MAX, NAME=max_ts, FEATURES=`{\"column_name\": \"ts\"}`);"; + auto alterQuery = TStringBuilder() << "ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, TYPE=MAX, " + "NAME=max_ts, FEATURES=`{\"column_name\": \"ts\"}`);"; auto session = tableClient.CreateSession().GetValueSync().GetSession(); auto alterResult = session.ExecuteSchemeQuery(alterQuery).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), NYdb::EStatus::SUCCESS, alterResult.GetIssues().ToString()); @@ -71,4 +116,4 @@ Y_UNIT_TEST_SUITE(KqpOlapStatistics) { } } -} +} // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/ut/olap/sys_view_ut.cpp b/ydb/core/kqp/ut/olap/sys_view_ut.cpp index 937f0337b457..9d12b54efab6 100644 --- a/ydb/core/kqp/ut/olap/sys_view_ut.cpp +++ b/ydb/core/kqp/ut/olap/sys_view_ut.cpp @@ -28,6 +28,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { auto selectQuery = TString(R"( SELECT PathId, Kind, TabletId, Sum(Rows) as Rows FROM `/Root/olapStore/.sys/store_primary_index_portion_stats` + WHERE Activity == 1 GROUP BY PathId, Kind, TabletId ORDER BY TabletId, Kind, PathId )"); @@ -61,13 +62,14 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { WriteTestData(kikimr, "/Root/olapStore/olapTable_1", 0, 1000000 + i * 10000, 1000); WriteTestData(kikimr, "/Root/olapStore/olapTable_2", 0, 1000000 + i * 10000, 2000); } - csController->WaitCompactions(TDuration::Seconds(10)); + csController->WaitCompactions(TDuration::Seconds(5)); auto tableClient = kikimr.GetTableClient(); { auto selectQuery = TString(R"( SELECT PathId, Kind, TabletId FROM `/Root/olapStore/olapTable_1/.sys/primary_index_stats` + WHERE Activity = 1 GROUP BY PathId, TabletId, Kind ORDER BY PathId, TabletId, Kind )"); @@ -82,6 +84,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { auto selectQuery = TString(R"( SELECT PathId, Kind, TabletId FROM `/Root/olapStore/olapTable_2/.sys/primary_index_stats` + WHERE Activity = 1 GROUP BY PathId, TabletId, Kind ORDER BY PathId, TabletId, Kind )"); @@ -112,6 +115,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { ui64 bytesPK1; { auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); + csController->SetOverrideBlobSplitSettings(NOlap::NSplitter::TSplitSettings()); auto settings = TKikimrSettings() .SetWithSampleTables(false); TKikimrRunner kikimr(settings); @@ -124,6 +128,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { } auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); + csController->SetOverrideBlobSplitSettings(NOlap::NSplitter::TSplitSettings()); ui64 rawBytesUnpack1PK = 0; ui64 bytesUnpack1PK = 0; ui64 rawBytesPackAndUnpack2PK; @@ -168,10 +173,10 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { helper.ExecuteSchemeQuery("ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=ALTER_COLUMN, NAME=field, `SERIALIZER.CLASS_NAME`=`ARROW_SERIALIZER`, `COMPRESSION.TYPE`=`zstd`);"); csController->WaitCompactions(TDuration::Seconds(10)); } - const ui64 rawBytesUnpack = rawBytesUnpack1PK - rawBytesPK1; - const ui64 bytesUnpack = bytesUnpack1PK - bytesPK1; - const ui64 rawBytesPack = rawBytesPackAndUnpack2PK - rawBytesUnpack1PK - rawBytesPK1; - const ui64 bytesPack = bytesPackAndUnpack2PK - bytesUnpack1PK - bytesPK1; + const i64 rawBytesUnpack = rawBytesUnpack1PK - rawBytesPK1; + const i64 bytesUnpack = bytesUnpack1PK - bytesPK1; + const i64 rawBytesPack = rawBytesPackAndUnpack2PK - rawBytesUnpack1PK - rawBytesPK1; + const i64 bytesPack = bytesPackAndUnpack2PK - bytesUnpack1PK - bytesPK1; TStringBuilder result; result << "unpacked data: " << rawBytesUnpack << " / " << bytesUnpack << Endl; result << "packed data: " << rawBytesPack << " / " << bytesPack << Endl; @@ -292,6 +297,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { ui64 rawBytes1; ui64 bytes1; auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); + csController->SetSmallSizeDetector(Max()); auto settings = TKikimrSettings().SetWithSampleTables(false); TKikimrRunner kikimr(settings); Tests::NCommon::TLoggerInit(kikimr).Initialize(); @@ -308,6 +314,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { helper.ExecuteSchemeQuery("ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_INDEX, NAME=pk_int_max, TYPE=MAX, FEATURES=`{\"column_name\" : \"pk_int\"}`);"); helper.ExecuteSchemeQuery("ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_OPTIONS, SCHEME_NEED_ACTUALIZATION=`true`);"); csController->WaitActualization(TDuration::Seconds(40)); + csController->WaitCompactions(TDuration::Seconds(5)); { ui64 rawBytes2; ui64 bytes2; @@ -316,16 +323,16 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { AFL_VERIFY(bytes2 < bytes1 * 0.5)("f1", bytes1)("f2", bytes2); std::vector stats; helper.GetStats(stats, true); - AFL_VERIFY(stats.size() == 3); - for (auto&& i : stats) { - AFL_VERIFY(i.IsArray()); - AFL_VERIFY(i.GetArraySafe().size() == 1); - AFL_VERIFY(i.GetArraySafe()[0]["chunk_idx"].GetInteger() == 0); - AFL_VERIFY(i.GetArraySafe()[0]["entity_id"].GetInteger() == 4); - AFL_VERIFY(i.GetArraySafe()[0]["data"].GetIntegerRobust() >= 799992); - AFL_VERIFY(i.GetArraySafe()[0]["data"].GetIntegerRobust() <= 799999); - AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("json", i); - } + AFL_VERIFY(stats.size() == 3)("count", stats.size()); +// for (auto&& i : stats) { +// AFL_VERIFY(i.IsArray()); +// AFL_VERIFY(i.GetArraySafe().size() == 1); +// AFL_VERIFY(i.GetArraySafe()[0]["chunk_idx"].GetInteger() == 0); +// AFL_VERIFY(i.GetArraySafe()[0]["entity_id"].GetInteger() == 4); +// AFL_VERIFY(i.GetArraySafe()[0]["data"].GetIntegerRobust() >= 799992); +// AFL_VERIFY(i.GetArraySafe()[0]["data"].GetIntegerRobust() <= 799999); +// AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("json", i); +// } } } { @@ -333,13 +340,13 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { helper.ExecuteSchemeQuery("ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=UPSERT_OPTIONS, SCHEME_NEED_ACTUALIZATION=`true`);"); csController->WaitActualization(TDuration::Seconds(30)); { - // std::vector stats; - // helper.GetStats(stats, true); - // AFL_VERIFY(stats.size() == 3); - // for (auto&& i : stats) { - // AFL_VERIFY(i.IsArray()); - // AFL_VERIFY(i.GetArraySafe().size() == 0)("json", i); - // } + std::vector stats; + helper.GetStats(stats, true); + AFL_VERIFY(stats.size() == 3); +// for (auto&& i : stats) { +// AFL_VERIFY(i.IsArray()); +// AFL_VERIFY(i.GetArraySafe().size() == 0)("json", i); +// } } } { @@ -350,15 +357,15 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { std::vector stats; helper.GetStats(stats, true); AFL_VERIFY(stats.size() == 3); - for (auto&& i : stats) { - AFL_VERIFY(i.IsArray()); - AFL_VERIFY(i.GetArraySafe().size() == 1); - AFL_VERIFY(i.GetArraySafe()[0]["chunk_idx"].GetInteger() == 0); - AFL_VERIFY(i.GetArraySafe()[0]["entity_id"].GetInteger() == 5)("json", i); - AFL_VERIFY(i.GetArraySafe()[0]["data"].GetIntegerRobust() >= 799992); - AFL_VERIFY(i.GetArraySafe()[0]["data"].GetIntegerRobust() <= 799999); - AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("json", i); - } +// for (auto&& i : stats) { +// AFL_VERIFY(i.IsArray()); +// AFL_VERIFY(i.GetArraySafe().size() == 1); +// AFL_VERIFY(i.GetArraySafe()[0]["chunk_idx"].GetInteger() == 0); +// AFL_VERIFY(i.GetArraySafe()[0]["entity_id"].GetInteger() == 5)("json", i); +// AFL_VERIFY(i.GetArraySafe()[0]["data"].GetIntegerRobust() >= 799992); +// AFL_VERIFY(i.GetArraySafe()[0]["data"].GetIntegerRobust() <= 799999); +// AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("json", i); +// } } } } @@ -396,6 +403,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { auto selectQuery = TString(R"( SELECT SUM(BlobRangeSize) as Bytes, SUM(Rows) as Rows, PathId, TabletId FROM `/Root/olapStore/.sys/store_primary_index_stats` + WHERE Activity == 1 GROUP BY PathId, TabletId ORDER BY Bytes )"); @@ -409,6 +417,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { auto selectQuery = TString(R"( SELECT Sum(Rows) as Rows, Kind, Sum(ColumnRawBytes) as RawBytes, PathId FROM `/Root/olapStore/.sys/store_primary_index_portion_stats` + WHERE Activity == 1 GROUP BY Kind, PathId ORDER BY PathId, Kind, Rows )"); @@ -529,6 +538,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { auto selectQuery = TString(R"( SELECT PathId, Kind, TabletId, Sum(BlobRangeSize) as Bytes FROM `/Root/olapStore/.sys/store_primary_index_stats` + WHERE Activity == 1 GROUP BY PathId, Kind, TabletId ORDER BY PathId, Kind, TabletId; )"); @@ -542,6 +552,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { auto selectQuery = TString(R"( SELECT PathId, Kind, TabletId, Sum(BlobRangeSize) as Bytes FROM `/Root/olapStore/.sys/store_primary_index_stats` + WHERE Activity == 1 GROUP BY PathId, Kind, TabletId ORDER BY PathId, Kind, TabletId; )"); @@ -569,6 +580,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { SELECT PathId, Kind, TabletId FROM `/Root/olapStore/.sys/store_primary_index_stats` WHERE Kind IN ('SPLIT_COMPACTED', 'INACTIVE', 'EVICTED', 'INSERTED') + AND Activity == 1 GROUP BY PathId, Kind, TabletId ORDER BY PathId, Kind, TabletId; )"); @@ -700,6 +712,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { auto selectQuery = TString(R"( SELECT PathId, TabletId, Kind FROM `/Root/olapStore/.sys/store_primary_index_stats` + WHERE Activity == 1 GROUP BY PathId, TabletId, Kind )"); @@ -715,6 +728,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { count(distinct(Kind)) as KindsCount, count(distinct(TabletId)) as TabletsCount FROM `/Root/olapStore/.sys/store_primary_index_stats` + WHERE Activity == 1 )"); auto rows = ExecuteScanQuery(tableClient, selectQuery); @@ -727,6 +741,7 @@ Y_UNIT_TEST_SUITE(KqpOlapSysView) { auto selectQuery = TString(R"( SELECT PathId, count(*), sum(Rows), sum(BlobRangeSize), sum(RawBytes) FROM `/Root/olapStore/.sys/store_primary_index_stats` + WHERE Activity == 1 GROUP BY PathId ORDER BY PathId )"); diff --git a/ydb/core/kqp/ut/olap/tiering_ut.cpp b/ydb/core/kqp/ut/olap/tiering_ut.cpp index b9cceba93738..847091530c4b 100644 --- a/ydb/core/kqp/ut/olap/tiering_ut.cpp +++ b/ydb/core/kqp/ut/olap/tiering_ut.cpp @@ -12,26 +12,42 @@ namespace NKikimr::NKqp { -Y_UNIT_TEST_SUITE(KqpOlapTiering) { - Y_UNIT_TEST(Eviction) { +class TTestEvictionBase { +protected: + std::optional TestHelper; + TString TieringRule; + +protected: + virtual void UnevictAll() = 0; + +public: + void RunTest() { auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); + csController->SetSkipSpecialCheckForEvict(true); TKikimrSettings runnerSettings; runnerSettings.WithSampleTables = false; - TTestHelper testHelper(runnerSettings); - TLocalHelper localHelper(testHelper.GetKikimr()); - NYdb::NTable::TTableClient tableClient = testHelper.GetKikimr().GetTableClient(); - Tests::NCommon::TLoggerInit(testHelper.GetKikimr()).Initialize(); + TestHelper.emplace(runnerSettings); + TLocalHelper localHelper(TestHelper->GetKikimr()); + // TestHelper->GetRuntime().SetLogPriority(NKikimrServices::FLAT_TX_SCHEMESHARD, NActors::NLog::PRI_DEBUG); + // TestHelper->GetRuntime().SetLogPriority(NKikimrServices::TX_COLUMNSHARD, NActors::NLog::PRI_DEBUG); + TestHelper->GetRuntime().SetLogPriority(NKikimrServices::TX_TIERING, NActors::NLog::PRI_DEBUG); + // TestHelper->GetRuntime().SetLogPriority(NKikimrServices::KQP_GATEWAY, NActors::NLog::PRI_DEBUG); + // TestHelper->GetRuntime().SetLogPriority(NKikimrServices::TX_PROXY_SCHEME_CACHE, NActors::NLog::PRI_DEBUG); + // TestHelper->GetRuntime().SetLogPriority(NKikimrServices::TX_PROXY, NActors::NLog::PRI_DEBUG); + NYdb::NTable::TTableClient tableClient = TestHelper->GetKikimr().GetTableClient(); + Tests::NCommon::TLoggerInit(TestHelper->GetKikimr()).Initialize(); Singleton()->SetSecretKey("fakeSecret"); localHelper.CreateTestOlapTable(); - testHelper.CreateTier("tier1"); - const TString tieringRule = testHelper.CreateTieringRule("tier1", "timestamp"); + TestHelper->CreateTier("tier1"); for (ui64 i = 0; i < 100; ++i) { - WriteTestData(testHelper.GetKikimr(), "/Root/olapStore/olapTable", 0, i * 10000, 1000); + WriteTestData(TestHelper->GetKikimr(), "/Root/olapStore/olapTable", 0, 3600000000 + i * 10000, 1000); + WriteTestData(TestHelper->GetKikimr(), "/Root/olapStore/olapTable", 0, 3600000000 + i * 10000, 1000); } + csController->WaitCompactions(TDuration::Seconds(5)); csController->WaitActualization(TDuration::Seconds(5)); ui64 columnRawBytes = 0; @@ -52,7 +68,7 @@ Y_UNIT_TEST_SUITE(KqpOlapTiering) { UNIT_ASSERT_GT(columnRawBytes, 0); } - testHelper.SetTiering("/Root/olapStore/olapTable", tieringRule); + TestHelper->SetTiering("/Root/olapStore/olapTable", "/Root/tier1", "timestamp"); csController->WaitActualization(TDuration::Seconds(5)); { @@ -66,13 +82,13 @@ Y_UNIT_TEST_SUITE(KqpOlapTiering) { auto rows = ExecuteScanQuery(tableClient, selectQuery); UNIT_ASSERT_VALUES_EQUAL(rows.size(), 1); - UNIT_ASSERT_VALUES_EQUAL(GetUtf8(rows[0].at("TierName")), "tier1"); + UNIT_ASSERT_VALUES_EQUAL(GetUtf8(rows[0].at("TierName")), "/Root/tier1"); UNIT_ASSERT_VALUES_EQUAL_C(GetUint64(rows[0].at("RawBytes")), columnRawBytes, TStringBuilder() << "RawBytes changed after eviction: before=" << columnRawBytes - << " after=" << GetUint64(rows[0].at("RawBytes"))); + << " after=" << GetUint64(rows[0].at("RawBytes"))); } - testHelper.ResetTiering("/Root/olapStore/olapTable"); + UnevictAll(); csController->WaitCompactions(TDuration::Seconds(5)); { @@ -89,72 +105,65 @@ Y_UNIT_TEST_SUITE(KqpOlapTiering) { UNIT_ASSERT_VALUES_EQUAL(GetUtf8(rows[0].at("TierName")), "__DEFAULT"); UNIT_ASSERT_VALUES_EQUAL_C(GetUint64(rows[0].at("RawBytes")), columnRawBytes, TStringBuilder() << "RawBytes changed after resetting tiering: before=" << columnRawBytes - << " after=" << GetUint64(rows[0].at("RawBytes"))); + << " after=" << GetUint64(rows[0].at("RawBytes"))); } + + } +}; + +class TTestEvictionResetTiering : public TTestEvictionBase { + private: + void UnevictAll() { + TestHelper->ResetTiering("/Root/olapStore/olapTable"); + } +}; + +class TTestEvictionIncreaseDuration : public TTestEvictionBase { + private: + void UnevictAll() { + const TString query = R"(ALTER TABLE `/Root/olapStore/olapTable` SET TTL Interval("P30000D") TO EXTERNAL DATA SOURCE `/Root/tier1` ON timestamp)"; + auto result = TestHelper->GetSession().ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), NYdb::EStatus::SUCCESS, result.GetIssues().ToString()); + } +}; + +Y_UNIT_TEST_SUITE(KqpOlapTiering) { + + Y_UNIT_TEST(EvictionResetTiering) { + TTestEvictionResetTiering().RunTest(); } - Y_UNIT_TEST(TieringRuleValidation) { + Y_UNIT_TEST(EvictionIncreaseDuration) { + TTestEvictionIncreaseDuration().RunTest(); + } + + Y_UNIT_TEST(TieringValidation) { auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); TKikimrSettings runnerSettings; runnerSettings.WithSampleTables = false; TTestHelper testHelper(runnerSettings); TLocalHelper localHelper(testHelper.GetKikimr()); + testHelper.GetRuntime().SetLogPriority(NKikimrServices::TX_TIERING, NActors::NLog::PRI_DEBUG); NYdb::NTable::TTableClient tableClient = testHelper.GetKikimr().GetTableClient(); Tests::NCommon::TLoggerInit(testHelper.GetKikimr()).Initialize(); - Singleton()->SetSecretKey("fakeSecret"); localHelper.CreateTestOlapTable(); testHelper.CreateTier("tier1"); { - const TString query = R"( - CREATE OBJECT IF NOT EXISTS empty_tiering_rule (TYPE TIERING_RULE) - WITH (defaultColumn = timestamp, description = `{"rules": []}`))"; - auto result = testHelper.GetSession().ExecuteSchemeQuery(query).GetValueSync(); - UNIT_ASSERT_VALUES_UNEQUAL(result.GetStatus(), NYdb::EStatus::SUCCESS); - } - - { - const TString query = R"( - CREATE OBJECT IF NOT EXISTS empty_default_column (TYPE TIERING_RULE) - WITH (defaultColumn = ``, description = `{"rules": [{ "tierName" : "tier1", "durationForEvict" : "10d" }]}`))"; - auto result = testHelper.GetSession().ExecuteSchemeQuery(query).GetValueSync(); - UNIT_ASSERT_VALUES_UNEQUAL(result.GetStatus(), NYdb::EStatus::SUCCESS); - } - - { - const TString query = R"( - CREATE OBJECT IF NOT EXISTS no_default_column (TYPE TIERING_RULE) - WITH (description = `{"rules": [{ "tierName" : "tier1", "durationForEvict" : "10d" }]}`))"; - auto result = testHelper.GetSession().ExecuteSchemeQuery(query).GetValueSync(); - UNIT_ASSERT_VALUES_UNEQUAL(result.GetStatus(), NYdb::EStatus::SUCCESS); - } - - const TString correctTieringRule = testHelper.CreateTieringRule("tier1", "timestamp"); - { - const TString query = "ALTER OBJECT " + correctTieringRule + R"( (TYPE TIERING_RULE) SET description `{"rules": []}`)"; + const TString query = R"(ALTER TABLE `/Root/olapStore/olapTable` SET TTL Interval("P10D") TO EXTERNAL DATA SOURCE `/Root/tier1` ON unknown_column;)"; auto result = testHelper.GetSession().ExecuteSchemeQuery(query).GetValueSync(); UNIT_ASSERT_VALUES_UNEQUAL(result.GetStatus(), NYdb::EStatus::SUCCESS); } { - const TString query = "ALTER OBJECT " + correctTieringRule + R"( (TYPE TIERING_RULE) SET description `{"rules": []}`)"; + const TString query = R"(ALTER TABLE `/Root/olapStore/olapTable` SET TTL Interval("P10D") TO EXTERNAL DATA SOURCE `/Root/tier1` ON uid;)"; auto result = testHelper.GetSession().ExecuteSchemeQuery(query).GetValueSync(); UNIT_ASSERT_VALUES_UNEQUAL(result.GetStatus(), NYdb::EStatus::SUCCESS); } - { - const TString query = "ALTER OBJECT " + correctTieringRule + R"( (TYPE TIERING_RULE) SET defaultColumn ``)"; - auto result = testHelper.GetSession().ExecuteSchemeQuery(query).GetValueSync(); - UNIT_ASSERT_VALUES_UNEQUAL(result.GetStatus(), NYdb::EStatus::SUCCESS); - } - - { - const TString query = "ALTER OBJECT " + correctTieringRule + R"( (TYPE TIERING_RULE) RESET defaultColumn)"; - auto result = testHelper.GetSession().ExecuteSchemeQuery(query).GetValueSync(); - UNIT_ASSERT_VALUES_UNEQUAL(result.GetStatus(), NYdb::EStatus::SUCCESS); - } + testHelper.SetTiering("/Root/olapStore/olapTable", "/Root/tier1", "timestamp"); } } diff --git a/ydb/core/kqp/ut/olap/write_ut.cpp b/ydb/core/kqp/ut/olap/write_ut.cpp index 8d9751f28193..6d13f7802dcd 100644 --- a/ydb/core/kqp/ut/olap/write_ut.cpp +++ b/ydb/core/kqp/ut/olap/write_ut.cpp @@ -1,45 +1,74 @@ +#include "helpers/get_value.h" #include "helpers/local.h" -#include "helpers/writer.h" -#include "helpers/typed_local.h" #include "helpers/query_executor.h" -#include "helpers/get_value.h" +#include "helpers/typed_local.h" +#include "helpers/writer.h" -#include -#include #include +#include +#include #include +#include + namespace NKikimr::NKqp { Y_UNIT_TEST_SUITE(KqpOlapWrite) { + Y_UNIT_TEST(WriteFails) { + auto csController = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); + csController->SetSmallSizeDetector(1000000); + csController->SetIndexWriteControllerEnabled(false); + csController->SetOverridePeriodicWakeupActivationPeriod(TDuration::Seconds(1)); + csController->SetOverrideBlobPutResultOnWriteValue(NKikimrProto::EReplyStatus::BLOCKED); + Singleton()->ResetWriteCounters(); + + auto settings = TKikimrSettings().SetWithSampleTables(false); + TKikimrRunner kikimr(settings); + kikimr.GetTestServer().GetRuntime()->GetAppData().FeatureFlags.SetEnableWritePortionsOnInsert(true); + TLocalHelper(kikimr).CreateTestOlapTable(); + Tests::NCommon::TLoggerInit(kikimr) + .SetComponents({ NKikimrServices::TX_COLUMNSHARD }, "CS") + .SetPriority(NActors::NLog::PRI_DEBUG) + .Initialize(); + { + auto batch = TLocalHelper(kikimr).TestArrowBatch(30000, 1000000, 11000); + TLocalHelper(kikimr).SendDataViaActorSystem("/Root/olapStore/olapTable", batch, Ydb::StatusIds::INTERNAL_ERROR); + } + } + Y_UNIT_TEST(TierDraftsGC) { - auto csController = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); + auto csController = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); + csController->SetSmallSizeDetector(1000000); csController->SetIndexWriteControllerEnabled(false); csController->SetOverridePeriodicWakeupActivationPeriod(TDuration::Seconds(1)); Singleton()->ResetWriteCounters(); - auto settings = TKikimrSettings() - .SetWithSampleTables(false); + auto settings = TKikimrSettings().SetWithSampleTables(false); TKikimrRunner kikimr(settings); TLocalHelper(kikimr).CreateTestOlapTable(); - Tests::NCommon::TLoggerInit(kikimr).SetComponents({NKikimrServices::TX_COLUMNSHARD}, "CS").SetPriority(NActors::NLog::PRI_DEBUG).Initialize(); + Tests::NCommon::TLoggerInit(kikimr) + .SetComponents({ NKikimrServices::TX_COLUMNSHARD }, "CS") + .SetPriority(NActors::NLog::PRI_DEBUG) + .Initialize(); auto tableClient = kikimr.GetTableClient(); - { - WriteTestData(kikimr, "/Root/olapStore/olapTable", 30000, 1000000, 11000); - } - while (csController->GetInsertStartedCounter().Val() == 0) { + WriteTestData(kikimr, "/Root/olapStore/olapTable", 30000, 1000000, 11000); + WriteTestData(kikimr, "/Root/olapStore/olapTable", 30000, 1000000, 11000); + while (csController->GetCompactionStartedCounter().Val() == 0) { Cout << "Wait indexation..." << Endl; Sleep(TDuration::Seconds(2)); } - while (!Singleton()->GetWritesCount() || !csController->GetIndexWriteControllerBrokeCount().Val()) { - Cout << "Wait errors on write... " << Singleton()->GetWritesCount() << "/" << csController->GetIndexWriteControllerBrokeCount().Val() << Endl; + while (!Singleton()->GetWritesCount() || + !csController->GetIndexWriteControllerBrokeCount().Val()) { + Cout << "Wait errors on write... " << Singleton()->GetWritesCount() << "/" + << csController->GetIndexWriteControllerBrokeCount().Val() << Endl; Sleep(TDuration::Seconds(2)); } csController->DisableBackground(NKikimr::NYDBTest::ICSController::EBackground::Indexation); csController->DisableBackground(NKikimr::NYDBTest::ICSController::EBackground::Compaction); const auto startInstant = TMonotonic::Now(); - while (Singleton()->GetSize() && TMonotonic::Now() - startInstant < TDuration::Seconds(200)) { + while (Singleton()->GetSize() && + TMonotonic::Now() - startInstant < TDuration::Seconds(200)) { Cerr << "Waiting empty... " << Singleton()->GetSize() << Endl; Sleep(TDuration::Seconds(2)); } @@ -57,7 +86,10 @@ Y_UNIT_TEST_SUITE(KqpOlapWrite) { auto settings = TKikimrSettings().SetWithSampleTables(false); TKikimrRunner kikimr(settings); TLocalHelper(kikimr).CreateTestOlapTable(); - Tests::NCommon::TLoggerInit(kikimr).SetComponents({ NKikimrServices::TX_COLUMNSHARD }, "CS").SetPriority(NActors::NLog::PRI_DEBUG).Initialize(); + Tests::NCommon::TLoggerInit(kikimr) + .SetComponents({ NKikimrServices::TX_COLUMNSHARD }, "CS") + .SetPriority(NActors::NLog::PRI_DEBUG) + .Initialize(); auto tableClient = kikimr.GetTableClient(); WriteTestData(kikimr, "/Root/olapStore/olapTable", 30000, 1000000, 11000); @@ -69,41 +101,48 @@ Y_UNIT_TEST_SUITE(KqpOlapWrite) { } Y_UNIT_TEST(TierDraftsGCWithRestart) { - auto csController = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); + auto csController = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); + csController->SetSmallSizeDetector(1000000); csController->SetIndexWriteControllerEnabled(false); csController->SetOverridePeriodicWakeupActivationPeriod(TDuration::Seconds(1000)); csController->DisableBackground(NKikimr::NYDBTest::ICSController::EBackground::GC); Singleton()->ResetWriteCounters(); - auto settings = TKikimrSettings() - .SetWithSampleTables(false); + auto settings = TKikimrSettings().SetWithSampleTables(false); TKikimrRunner kikimr(settings); TLocalHelper(kikimr).CreateTestOlapTable(); - Tests::NCommon::TLoggerInit(kikimr).SetComponents({NKikimrServices::TX_COLUMNSHARD}, "CS").SetPriority(NActors::NLog::PRI_DEBUG).Initialize(); + Tests::NCommon::TLoggerInit(kikimr) + .SetComponents({ NKikimrServices::TX_COLUMNSHARD }, "CS") + .SetPriority(NActors::NLog::PRI_DEBUG) + .Initialize(); auto tableClient = kikimr.GetTableClient(); - { - WriteTestData(kikimr, "/Root/olapStore/olapTable", 30000, 1000000, 11000); - } - while (csController->GetInsertStartedCounter().Val() == 0) { + WriteTestData(kikimr, "/Root/olapStore/olapTable", 30000, 1000000, 11000); + WriteTestData(kikimr, "/Root/olapStore/olapTable", 30000, 1000000, 11000); + + while (csController->GetCompactionStartedCounter().Val() == 0) { Cout << "Wait indexation..." << Endl; Sleep(TDuration::Seconds(2)); } - while (Singleton()->GetWritesCount() < 20 || !csController->GetIndexWriteControllerBrokeCount().Val()) { - Cout << "Wait errors on write... " << Singleton()->GetWritesCount() << "/" << csController->GetIndexWriteControllerBrokeCount().Val() << Endl; + while (Singleton()->GetWritesCount() < 20 || + !csController->GetIndexWriteControllerBrokeCount().Val()) { + Cout << "Wait errors on write... " << Singleton()->GetWritesCount() << "/" + << csController->GetIndexWriteControllerBrokeCount().Val() << Endl; Sleep(TDuration::Seconds(2)); } csController->DisableBackground(NKikimr::NYDBTest::ICSController::EBackground::Indexation); csController->DisableBackground(NKikimr::NYDBTest::ICSController::EBackground::Compaction); + csController->WaitCompactions(TDuration::Seconds(5)); AFL_VERIFY(Singleton()->GetSize()); { const auto startInstant = TMonotonic::Now(); AFL_VERIFY(Singleton()->GetDeletesCount() == 0) ("count", Singleton()->GetDeletesCount()); - while (Singleton()->GetSize() && TMonotonic::Now() - startInstant < TDuration::Seconds(200)) { + while (Singleton()->GetSize() && + TMonotonic::Now() - startInstant < TDuration::Seconds(200)) { for (auto&& i : csController->GetShardActualIds()) { - kikimr.GetTestServer().GetRuntime()->Send(MakePipePerNodeCacheID(false), NActors::TActorId(), new TEvPipeCache::TEvForward( - new TEvents::TEvPoisonPill(), i, false)); + kikimr.GetTestServer().GetRuntime()->Send(MakePipePerNodeCacheID(false), NActors::TActorId(), + new TEvPipeCache::TEvForward(new TEvents::TEvPoisonPill(), i, false)); } csController->EnableBackground(NKikimr::NYDBTest::ICSController::EBackground::GC); Cerr << "Waiting empty... " << Singleton()->GetSize() << Endl; @@ -118,17 +157,18 @@ Y_UNIT_TEST_SUITE(KqpOlapWrite) { const auto startInstant = TMonotonic::Now(); while (TMonotonic::Now() - startInstant < TDuration::Seconds(10)) { for (auto&& i : csController->GetShardActualIds()) { - kikimr.GetTestServer().GetRuntime()->Send(MakePipePerNodeCacheID(false), NActors::TActorId(), new TEvPipeCache::TEvForward( - new TEvents::TEvPoisonPill(), i, false)); + kikimr.GetTestServer().GetRuntime()->Send(MakePipePerNodeCacheID(false), NActors::TActorId(), + new TEvPipeCache::TEvForward(new TEvents::TEvPoisonPill(), i, false)); } - Cerr << "Waiting empty... " << Singleton()->GetWritesCount() << "/" << Singleton()->GetDeletesCount() << Endl; + Cerr << "Waiting empty... " << Singleton()->GetWritesCount() << "/" + << Singleton()->GetDeletesCount() << Endl; Sleep(TDuration::MilliSeconds(500)); } } AFL_VERIFY(writesCountStart == Singleton()->GetWritesCount()) - ("writes", writesCountStart)("count", Singleton()->GetWritesCount()); + ("writes", writesCountStart)("count", Singleton()->GetWritesCount()); AFL_VERIFY(deletesCountStart == Singleton()->GetDeletesCount()) - ("deletes", deletesCountStart)("count", Singleton()->GetDeletesCount()); + ("deletes", deletesCountStart)("count", Singleton()->GetDeletesCount()); } Y_UNIT_TEST(DefaultValues) { @@ -137,7 +177,9 @@ Y_UNIT_TEST_SUITE(KqpOlapWrite) { Tests::NCommon::TLoggerInit(kikimr).Initialize(); TTypedLocalHelper helper("Utf8", kikimr); helper.CreateTestOlapTable(); - helper.ExecuteSchemeQuery("ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=ALTER_COLUMN, NAME=field, `ENCODING.DICTIONARY.ENABLED`=`true`, `DEFAULT_VALUE`=`abcde`);"); + helper.ExecuteSchemeQuery( + "ALTER OBJECT `/Root/olapStore` (TYPE TABLESTORE) SET (ACTION=ALTER_COLUMN, NAME=field, `ENCODING.DICTIONARY.ENABLED`=`true`, " + "`DEFAULT_VALUE`=`abcde`);"); helper.FillPKOnly(0, 800000); auto selectQuery = TString(R"( @@ -153,7 +195,8 @@ Y_UNIT_TEST_SUITE(KqpOlapWrite) { } Y_UNIT_TEST(WriteDeleteCleanGC) { - auto csController = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); + auto csController = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); + csController->SetSmallSizeDetector(1000000); csController->SetOverridePeriodicWakeupActivationPeriod(TDuration::MilliSeconds(100)); csController->DisableBackground(NKikimr::NYDBTest::ICSController::EBackground::GC); Singleton()->ResetWriteCounters(); @@ -164,51 +207,61 @@ Y_UNIT_TEST_SUITE(KqpOlapWrite) { auto settings = TKikimrSettings().SetAppConfig(appConfig).SetWithSampleTables(false); TKikimrRunner kikimr(settings); TLocalHelper(kikimr).CreateTestOlapTable(); - Tests::NCommon::TLoggerInit(kikimr).SetComponents({ NKikimrServices::TX_COLUMNSHARD, NKikimrServices::TX_COLUMNSHARD_BLOBS }, "CS").SetPriority(NActors::NLog::PRI_DEBUG).Initialize(); + Tests::NCommon::TLoggerInit(kikimr) + .SetComponents({ NKikimrServices::TX_COLUMNSHARD, NKikimrServices::TX_COLUMNSHARD_BLOBS }, "CS") + .SetPriority(NActors::NLog::PRI_DEBUG) + .Initialize(); auto tableClient = kikimr.GetTableClient(); auto client = kikimr.GetQueryClient(); { - auto it = client.ExecuteQuery(R"( + auto it = client + .ExecuteQuery(R"( INSERT INTO `/Root/olapStore/olapTable` (timestamp, uid, resource_id) VALUES (Timestamp('1970-01-01T00:00:00Z'), 'a', '0'); INSERT INTO `/Root/olapStore/olapTable` (timestamp, uid, resource_id) VALUES (Timestamp('1970-01-01T00:00:01Z'), 'a', 'test'); INSERT INTO `/Root/olapStore/olapTable` (timestamp, uid, resource_id) VALUES (Timestamp('1970-01-01T00:00:02Z'), 'a', 't'); - )", NYdb::NQuery::TTxControl::BeginTx().CommitTx()).ExtractValueSync(); + )", + NYdb::NQuery::TTxControl::BeginTx().CommitTx()) + .ExtractValueSync(); UNIT_ASSERT_C(it.IsSuccess(), it.GetIssues().ToString()); } - while (csController->GetInsertStartedCounter().Val() == 0) { + while (csController->GetCompactionStartedCounter().Val() == 0) { Cerr << "Wait indexation..." << Endl; Sleep(TDuration::Seconds(2)); } { const TInstant start = TInstant::Now(); - while (!Singleton()->GetSize() && TInstant::Now() - start < TDuration::Seconds(10)) { + while ( + !Singleton()->GetSize() && TInstant::Now() - start < TDuration::Seconds(10)) { Cerr << "Wait size in memory... " << Singleton()->GetSize() << Endl; Sleep(TDuration::Seconds(2)); } AFL_VERIFY(Singleton()->GetSize()); } { - auto it = client.ExecuteQuery(R"( + auto it = client + .ExecuteQuery(R"( DELETE FROM `/Root/olapStore/olapTable` ON SELECT CAST(0u AS Timestamp) AS timestamp, Unwrap(CAST('a' AS Utf8)) AS uid; DELETE FROM `/Root/olapStore/olapTable`; - )", NYdb::NQuery::TTxControl::BeginTx().CommitTx()).ExtractValueSync(); + )", + NYdb::NQuery::TTxControl::BeginTx().CommitTx()) + .ExtractValueSync(); UNIT_ASSERT_C(it.IsSuccess(), it.GetIssues().ToString()); } - csController->SetOverrideReadTimeoutClean(TDuration::Zero()); + csController->SetOverrideMaxReadStaleness(TDuration::Zero()); csController->EnableBackground(NKikimr::NYDBTest::ICSController::EBackground::GC); { const TInstant start = TInstant::Now(); - while (Singleton()->GetSize() && TInstant::Now() - start < TDuration::Seconds(10)) { + while ( + Singleton()->GetSize() && TInstant::Now() - start < TDuration::Seconds(10)) { Cerr << "Wait empty... " << Singleton()->GetSize() << Endl; Sleep(TDuration::Seconds(2)); } AFL_VERIFY(!Singleton()->GetSize()); } } - } -} // namespace +} // namespace NKikimr::NKqp diff --git a/ydb/core/kqp/ut/olap/ya.make b/ydb/core/kqp/ut/olap/ya.make index 23b97651fd48..30df59b8deb0 100644 --- a/ydb/core/kqp/ut/olap/ya.make +++ b/ydb/core/kqp/ut/olap/ya.make @@ -27,6 +27,7 @@ SRCS( sparsed_ut.cpp tiering_ut.cpp decimal_ut.cpp + compression_ut.cpp ) PEERDIR( diff --git a/ydb/core/kqp/ut/scheme/kqp_scheme_ut.cpp b/ydb/core/kqp/ut/scheme/kqp_scheme_ut.cpp index e074ebf0a3cb..cfcd0651a588 100644 --- a/ydb/core/kqp/ut/scheme/kqp_scheme_ut.cpp +++ b/ydb/core/kqp/ut/scheme/kqp_scheme_ut.cpp @@ -2057,6 +2057,94 @@ Y_UNIT_TEST_SUITE(KqpScheme) { } } + Y_UNIT_TEST(CreateFamilyWithCompressionLevel) { + TKikimrRunner kikimr; + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + TString tableName = "/Root/TableWithCompressionLevel"; + auto query = TStringBuilder() << R"( + --!syntax_v1 + CREATE TABLE `)" << tableName + << R"(` ( + Key Uint64, + Value1 String, + Value2 Uint32, + PRIMARY KEY (Key), + FAMILY Family1 ( + DATA = "test", + COMPRESSION = "lz4", + COMPRESSION_LEVEL = 5 + ), + );)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::GENERIC_ERROR, result.GetIssues().ToString()); + UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToString(), "Field `COMPRESSION_LEVEL` is not supported for OLTP tables"); + } + + Y_UNIT_TEST(AlterCompressionLevelInColumnFamily) { + TKikimrRunner kikimr; + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + TString tableName = "/Root/TableWithCompressionLevel"; + auto query = TStringBuilder() << R"( + --!syntax_v1 + CREATE TABLE `)" << tableName + << R"(` ( + Key Uint64, + Value1 String FAMILY Family1, + Value2 Uint32, + PRIMARY KEY (Key), + FAMILY Family1 ( + DATA = "test", + COMPRESSION = "lz4" + ), + );)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + + auto queryAlter = TStringBuilder() << R"( + --!syntax_v1 + ALTER TABLE `)" << tableName << R"(` + ALTER FAMILY Family1 SET COMPRESSION_LEVEL 5;)"; + auto resultAlter = session.ExecuteSchemeQuery(queryAlter).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(resultAlter.GetStatus(), EStatus::BAD_REQUEST, resultAlter.GetIssues().ToString()); + UNIT_ASSERT_STRING_CONTAINS(resultAlter.GetIssues().ToString(), "Field `COMPRESSION_LEVEL` is not supported for OLTP tables"); + } + + Y_UNIT_TEST(AddColumnFamilyWithCompressionLevel) { + TKikimrRunner kikimr; + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + TString tableName = "/Root/TableWithCompressionLevel"; + auto query = TStringBuilder() << R"( + --!syntax_v1 + CREATE TABLE `)" << tableName + << R"(` ( + Key Uint64, + Value1 String FAMILY Family1, + Value2 Uint32, + PRIMARY KEY (Key), + FAMILY Family1 ( + DATA = "test", + COMPRESSION = "lz4" + ), + );)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + + auto queryAlter = TStringBuilder() << R"( + --!syntax_v1 + ALTER TABLE `)" << tableName << R"(` + ADD FAMILY Family2 ( + DATA = "test", + COMPRESSION = "lz4", + COMPRESSION_LEVEL = 5 + );)"; + auto resultAlter = session.ExecuteSchemeQuery(queryAlter).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(resultAlter.GetStatus(), EStatus::BAD_REQUEST, resultAlter.GetIssues().ToString()); + UNIT_ASSERT_STRING_CONTAINS(resultAlter.GetIssues().ToString(), "Field `COMPRESSION_LEVEL` is not supported for OLTP tables"); + } + Y_UNIT_TEST(CreateTableWithDefaultFamily) { TKikimrRunner kikimr; auto db = kikimr.GetTableClient(); @@ -4216,6 +4304,30 @@ Y_UNIT_TEST_SUITE(KqpScheme) { } } + Y_UNIT_TEST(NEG_CreateTableWithUnsupportedStoreType) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TKikimrRunner kikimr(runnerSettings); + auto db = kikimr.GetTableClient(); + auto session = db.CreateSession().GetValueSync().GetSession(); + TString tableStoreName = "/Root/TableStoreTest"; + auto query = TStringBuilder() << R"( + --!syntax_v1 + CREATE TABLE SomeTable ( + Key Timestamp NOT NULL, + Value1 String, + Value2 Int64 NOT NULL, + PRIMARY KEY (Key) + ) + WITH ( + STORE = UNSUPPORTED + ); + )"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::GENERIC_ERROR, result.GetIssues().ToString()); + } + + Y_UNIT_TEST(CreateAlterDropTableStore) { TKikimrSettings runnerSettings; runnerSettings.WithSampleTables = false; @@ -4533,7 +4645,8 @@ Y_UNIT_TEST_SUITE(KqpScheme) { UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::GENERIC_ERROR, result.GetIssues().ToString()); } - { // no partition by + { // nullable pk columns are disabled by default + kikimr.GetTestServer().GetRuntime()->GetAppData().ColumnShardConfig.SetAllowNullableColumnsInPK(false); auto query = TStringBuilder() << R"( --!syntax_v1 CREATE TABLE `)" << tableName << R"(` ( @@ -4591,6 +4704,24 @@ Y_UNIT_TEST_SUITE(KqpScheme) { auto result = session.ExecuteSchemeQuery(query).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + { + auto query2 = TStringBuilder() << R"( + --!syntax_v1 + ALTER OBJECT `)" << tableName << R"(` (TYPE TABLE) SET (ACTION=UPSERT_INDEX, + NAME=max_value1, TYPE=MAX, FEATURES=`{\"column_name\": \"Value1\"}`))"; + result = session.ExecuteSchemeQuery(query2).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + + { + auto query2 = TStringBuilder() << R"( + --!syntax_v1 + ALTER OBJECT `)" << tableName << R"(` (TYPE TABLE) SET (ACTION=UPSERT_INDEX, + NAME=max_value2, TYPE=MAX, FEATURES=`{\"column_name\": \"Value2\"}`))"; + result = session.ExecuteSchemeQuery(query2).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + auto query2 = TStringBuilder() << R"( --!syntax_v1 ALTER TABLE `)" << tableName << R"(` SET(TTL = Interval("P1D") ON Key);)"; @@ -4625,15 +4756,19 @@ Y_UNIT_TEST_SUITE(KqpScheme) { Y_UNIT_TEST(AlterColumnTableTiering) { TKikimrSettings runnerSettings; runnerSettings.WithSampleTables = false; - TKikimrRunner kikimr(runnerSettings); - auto db = kikimr.GetTableClient(); + runnerSettings.SetEnableTieringInColumnShard(true); + TTestHelper testHelper(runnerSettings); + auto db = testHelper.GetKikimr().GetTableClient(); auto session = db.CreateSession().GetValueSync().GetSession(); TString tableName = "/Root/ColumnTableTest"; + testHelper.CreateTier("tier1"); + testHelper.CreateTier("tier2"); + auto query = TStringBuilder() << R"( --!syntax_v1 CREATE TABLE `)" << tableName << R"(` ( - Key Uint64 NOT NULL, + Key Timestamp NOT NULL, Value1 String, Value2 Int64 NOT NULL, PRIMARY KEY (Key) @@ -4642,23 +4777,23 @@ Y_UNIT_TEST_SUITE(KqpScheme) { WITH ( STORE = COLUMN, AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = 10, - TIERING = 'tiering1' + TTL = Interval("PT10S") TO EXTERNAL DATA SOURCE `/Root/tier1` ON Key );)"; auto result = session.ExecuteSchemeQuery(query).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); -#if 0 // TODO { // describe table auto desc = session.DescribeTable(tableName).ExtractValueSync(); UNIT_ASSERT_C(desc.IsSuccess(), desc.GetIssues().ToString()); - auto tiering = desc.GetTableDescription().GetTiering(); - UNIT_ASSERT(tiering); - UNIT_ASSERT_VALUES_EQUAL(*tiering, "tiering1"); + UNIT_ASSERT(desc.GetTableDescription().GetTtlSettings()); + auto ttl = desc.GetTableDescription().GetTtlSettings(); + UNIT_ASSERT_VALUES_EQUAL(ttl->GetTiers().size(), 1); + UNIT_ASSERT_VALUES_EQUAL(std::get(ttl->GetTiers()[0].GetAction()).GetStorage(), "/Root/tier1"); + UNIT_ASSERT_VALUES_EQUAL(std::get(ttl->GetTiers()[0].GetExpression()).GetExpireAfter(), TDuration::Seconds(10)); } -#endif auto query2 = TStringBuilder() << R"( --!syntax_v1 - ALTER TABLE `)" << tableName << R"(` SET(TIERING = 'tiering2');)"; + ALTER TABLE `)" << tableName << R"(` SET (TTL = Interval("PT10S") TO EXTERNAL DATA SOURCE `/Root/tier2` ON Key);)"; result = session.ExecuteSchemeQuery(query2).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); @@ -4666,14 +4801,16 @@ Y_UNIT_TEST_SUITE(KqpScheme) { auto desc = session.DescribeTable(tableName).ExtractValueSync(); UNIT_ASSERT_C(desc.IsSuccess(), desc.GetIssues().ToString()); - auto tiering = desc.GetTableDescription().GetTiering(); - UNIT_ASSERT(tiering); - UNIT_ASSERT_VALUES_EQUAL(*tiering, "tiering2"); + UNIT_ASSERT(desc.GetTableDescription().GetTtlSettings()); + auto ttl = desc.GetTableDescription().GetTtlSettings(); + UNIT_ASSERT_VALUES_EQUAL(ttl->GetTiers().size(), 1); + UNIT_ASSERT_VALUES_EQUAL(std::get(ttl->GetTiers()[0].GetAction()).GetStorage(), "/Root/tier2"); + UNIT_ASSERT_VALUES_EQUAL(std::get(ttl->GetTiers()[0].GetExpression()).GetExpireAfter(), TDuration::Seconds(10)); } auto query3 = TStringBuilder() << R"( --!syntax_v1 - ALTER TABLE `)" << tableName << R"(` RESET (TIERING);)"; + ALTER TABLE `)" << tableName << R"(` RESET (TTL);)"; result = session.ExecuteSchemeQuery(query3).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); @@ -4681,13 +4818,13 @@ Y_UNIT_TEST_SUITE(KqpScheme) { auto desc = session.DescribeTable(tableName).ExtractValueSync(); UNIT_ASSERT_C(desc.IsSuccess(), desc.GetIssues().ToString()); - auto tiering = desc.GetTableDescription().GetTiering(); - UNIT_ASSERT(!tiering); + auto ttl = desc.GetTableDescription().GetTtlSettings(); + UNIT_ASSERT(!ttl); } auto query4 = TStringBuilder() << R"( --!syntax_v1 - ALTER TABLE `)" << tableName << R"(` SET (TIERING = 'tiering1');)"; + ALTER TABLE `)" << tableName << R"(` SET (TTL = Interval("PT10S") TO EXTERNAL DATA SOURCE `/Root/tier1` ON Key);)"; result = session.ExecuteSchemeQuery(query4).GetValueSync(); UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); @@ -4695,9 +4832,11 @@ Y_UNIT_TEST_SUITE(KqpScheme) { auto desc = session.DescribeTable(tableName).ExtractValueSync(); UNIT_ASSERT_C(desc.IsSuccess(), desc.GetIssues().ToString()); - auto tiering = desc.GetTableDescription().GetTiering(); - UNIT_ASSERT(tiering); - UNIT_ASSERT_VALUES_EQUAL(*tiering, "tiering1"); + UNIT_ASSERT(desc.GetTableDescription().GetTtlSettings()); + auto ttl = desc.GetTableDescription().GetTtlSettings(); + UNIT_ASSERT_VALUES_EQUAL(ttl->GetTiers().size(), 1); + UNIT_ASSERT_VALUES_EQUAL(std::get(ttl->GetTiers()[0].GetAction()).GetStorage(), "/Root/tier1"); + UNIT_ASSERT_VALUES_EQUAL(std::get(ttl->GetTiers()[0].GetExpression()).GetExpireAfter(), TDuration::Seconds(10)); } auto query5 = TStringBuilder() << R"( @@ -7145,7 +7284,7 @@ Y_UNIT_TEST_SUITE(KqpOlapScheme) { }; TTestHelper::TColumnTable testTable; - testTable.SetName("/Root/ColumnTableTest").SetPrimaryKey({"id", "id_second"}).SetSharding({"id"}).SetSchema(schema).SetTTL("created_at", "Interval(\"PT1H\")"); + testTable.SetName("/Root/ColumnTableTest").SetPrimaryKey({"created_at", "id_second"}).SetSharding({"created_at"}).SetSchema(schema).SetTTL("created_at", "Interval(\"PT1H\")"); testHelper.CreateTable(testTable); { @@ -7207,7 +7346,7 @@ Y_UNIT_TEST_SUITE(KqpOlapScheme) { }; TTestHelper::TColumnTable testTable; - testTable.SetName(tableName).SetPrimaryKey({"id", "id_second"}).SetSharding({"id"}).SetSchema(schema).SetTTL("created_at", "Interval(\"PT1H\")"); + testTable.SetName(tableName).SetPrimaryKey({"created_at", "id_second"}).SetSharding({"created_at"}).SetSchema(schema).SetTTL("created_at", "Interval(\"PT1H\")"); testHelper.CreateTable(testTable); testHelper.CreateTier("tier1"); @@ -7218,14 +7357,7 @@ Y_UNIT_TEST_SUITE(KqpOlapScheme) { testHelper.BulkUpsert(testTable, tableInserter); } - while (csController->GetInsertFinishedCounter().Val() == 0) { - Cout << "Wait indexation..." << Endl; - Sleep(TDuration::Seconds(2)); - } - - // const auto ruleName = testHelper.CreateTieringRule("tier1", "created_att"); - const auto ruleName = testHelper.CreateTieringRule("tier1", "created_at"); - testHelper.SetTiering(tableName, ruleName); + testHelper.SetTiering(tableName, "/Root/tier1", "created_at"); while (csController->GetTieringUpdates().Val() == 0) { Cout << "Wait tiering..." << Endl; @@ -7250,6 +7382,17 @@ Y_UNIT_TEST_SUITE(KqpOlapScheme) { TTestHelper::TColumnTable testTable; testTable.SetName("/Root/ColumnTableTest").SetPrimaryKey({"id", "id_second"}).SetSharding({"id"}).SetSchema(schema); testHelper.CreateTable(testTable); + testHelper.CreateTier("tier1"); + + { + auto alterQuery = TStringBuilder() << R"( + --!syntax_v1 + ALTER OBJECT `)" << testTable.GetName() << R"(` (TYPE TABLE) SET (ACTION=UPSERT_INDEX, + NAME=max_value1, TYPE=MAX, FEATURES=`{\"column_name\": \"created_at\"}`))"; + auto alterResult = testHelper.GetSession().ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), EStatus::SUCCESS, alterResult.GetIssues().ToString()); + + } { auto alterQuery = TStringBuilder() << "ALTER TABLE `" << testTable.GetName() << "`SET (TTL = Interval(\"PT1H\") ON created_at);"; @@ -7281,16 +7424,27 @@ Y_UNIT_TEST_SUITE(KqpOlapScheme) { UNIT_ASSERT_VALUES_EQUAL(columns.size(), 5); UNIT_ASSERT_VALUES_EQUAL(description.GetTtlSettings()->GetDateTypeColumn().GetExpireAfter(), TDuration::Hours(1)); } - testHelper.SetTiering("/Root/ColumnTableTest", "tiering1"); + { + auto alterQuery = TStringBuilder() << "ALTER TABLE `" << testTable.GetName() << "`SET (TTL = Interval(\"PT10S\") TO EXTERNAL DATA SOURCE `/Root/tier1`, Interval(\"PT1H\") DELETE ON created_at);"; + auto alterResult = testHelper.GetSession().ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), EStatus::SUCCESS, alterResult.GetIssues().ToString()); + } { auto settings = TDescribeTableSettings().WithTableStatistics(true); auto describeResult = testHelper.GetSession().DescribeTable("/Root/ColumnTableTest", settings).GetValueSync(); UNIT_ASSERT_C(describeResult.IsSuccess(), describeResult.GetIssues().ToString()); const auto& description = describeResult.GetTableDescription(); - UNIT_ASSERT(description.GetTiering()); - UNIT_ASSERT_VALUES_EQUAL(*description.GetTiering(), "tiering1"); - UNIT_ASSERT_VALUES_EQUAL(description.GetTtlSettings()->GetDateTypeColumn().GetExpireAfter(), TDuration::Hours(1)); + UNIT_ASSERT(describeResult.GetTableDescription().GetTtlSettings()); + auto ttl = describeResult.GetTableDescription().GetTtlSettings(); + UNIT_ASSERT_VALUES_EQUAL(ttl->GetTiers().size(), 2); + auto evictTier = ttl->GetTiers()[0]; + UNIT_ASSERT(std::holds_alternative(evictTier.GetAction())); + UNIT_ASSERT_VALUES_EQUAL(std::get(evictTier.GetAction()).GetStorage(), "/Root/tier1"); + UNIT_ASSERT_VALUES_EQUAL(std::get(evictTier.GetExpression()).GetExpireAfter(), TDuration::Seconds(10)); + auto deleteTier = ttl->GetTiers()[1]; + UNIT_ASSERT(std::holds_alternative(deleteTier.GetAction())); + UNIT_ASSERT_VALUES_EQUAL(std::get(deleteTier.GetExpression()).GetExpireAfter(), TDuration::Hours(1)); } { auto alterQuery = TStringBuilder() << "ALTER TABLE `" << testTable.GetName() << R"(` RESET (TTL);)"; @@ -7303,18 +7457,6 @@ Y_UNIT_TEST_SUITE(KqpOlapScheme) { UNIT_ASSERT_C(describeResult.IsSuccess(), describeResult.GetIssues().ToString()); const auto& description = describeResult.GetTableDescription(); - UNIT_ASSERT(description.GetTiering()); - UNIT_ASSERT_VALUES_EQUAL(*description.GetTiering(), "tiering1"); - UNIT_ASSERT(!description.GetTtlSettings()); - } - testHelper.ResetTiering("/Root/ColumnTableTest"); - { - auto settings = TDescribeTableSettings().WithTableStatistics(true); - auto describeResult = testHelper.GetSession().DescribeTable("/Root/ColumnTableTest", settings).GetValueSync(); - UNIT_ASSERT_C(describeResult.IsSuccess(), describeResult.GetIssues().ToString()); - - const auto& description = describeResult.GetTableDescription(); - UNIT_ASSERT(!description.GetTiering()); UNIT_ASSERT(!description.GetTtlSettings()); } } @@ -7659,13 +7801,13 @@ Y_UNIT_TEST_SUITE(KqpOlapScheme) { TTestHelper::TUpdatesBuilder tableInserter(testTable.GetArrowSchema(schemaWithNull)); tableInserter.AddRow().Add(1).Add("test_res_1").AddNull(); tableInserter.AddRow().Add(2).Add("test_res_2").Add(123); - testHelper.BulkUpsert(testTable, tableInserter, Ydb::StatusIds::GENERIC_ERROR); + testHelper.BulkUpsert(testTable, tableInserter, Ydb::StatusIds::BAD_REQUEST); } { TTestHelper::TUpdatesBuilder tableInserter(testTable.GetArrowSchema(schemaWithNull)); tableInserter.AddRow().Add(1).Add("test_res_1").AddNull(); tableInserter.AddRow().Add(2).Add("test_res_2").Add(123); - testHelper.BulkUpsert(testTable, tableInserter, Ydb::StatusIds::GENERIC_ERROR); + testHelper.BulkUpsert(testTable, tableInserter, Ydb::StatusIds::BAD_REQUEST); } testHelper.ReadData("SELECT * FROM `/Root/ColumnTableTest` WHERE id=1", "[]"); @@ -7908,6 +8050,14 @@ Y_UNIT_TEST_SUITE(KqpOlapScheme) { TTestHelper::TColumnTable testTable; testTable.SetName("/Root/ColumnTableTest").SetPrimaryKey({"id"}).SetSharding({"id"}).SetSchema(schema); testHelper.CreateTable(testTable); + { + auto alterQuery = TStringBuilder() << R"( + --!syntax_v1 + ALTER OBJECT `)" << testTable.GetName() << R"(` (TYPE TABLE) SET (ACTION=UPSERT_INDEX, + NAME=max_pk_int, TYPE=MAX, FEATURES=`{\"column_name\": \"created_at\"}`))"; + auto alterResult = testHelper.GetSession().ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), EStatus::SUCCESS, alterResult.GetIssues().ToString()); + } { auto alterQuery = TStringBuilder() << "ALTER TABLE `" << testTable.GetName() << "`SET (TTL = Interval(\"PT1H\") ON created_at);"; @@ -8032,6 +8182,1455 @@ Y_UNIT_TEST_SUITE(KqpOlapScheme) { testHelper.CreateTable(testTable, EStatus::SCHEME_ERROR); } + Y_UNIT_TEST(DropColumnAfterInsert) { + using namespace NArrow; + + auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); + csController->DisableBackground(NYDBTest::ICSController::EBackground::Indexation); + + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(runnerSettings); + + TVector schema = { + TTestHelper::TColumnSchema().SetName("id").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema().SetName("int_column").SetType(NScheme::NTypeIds::Int32).SetNullable(true) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName("/Root/ColumnTableTest").SetPrimaryKey({ "id" }).SetSchema(schema); + testHelper.CreateTable(testTable); + + TVector dataBuilders; + dataBuilders.push_back( + NConstruction::TSimpleArrayConstructor>::BuildNotNullable("id", false)); + dataBuilders.push_back( + std::make_shared>>("int_column")); + auto batch = NConstruction::TRecordBatchConstructor(dataBuilders).BuildBatch(100); + testHelper.BulkUpsert(testTable, batch); + + auto alterQueryAdd = TStringBuilder() << "ALTER TABLE `" << testTable.GetName() << "` DROP COLUMN int_column;"; + auto alterAddResult = testHelper.GetSession().ExecuteSchemeQuery(alterQueryAdd).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterAddResult.GetStatus(), EStatus::SUCCESS, alterAddResult.GetIssues().ToString()); + + csController->EnableBackground(NYDBTest::ICSController::EBackground::Indexation); + csController->WaitIndexation(TDuration::Seconds(5)); + } + + void TestInsertAddInsertDrop( + bool autoIndexation, bool indexationAfterInsertAddColumn, bool indexationAfterInsertDropColumn, bool indexationInEnd) { + using namespace NArrow; + + auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); + if (!autoIndexation) { + csController->DisableBackground(NYDBTest::ICSController::EBackground::Indexation); + } + + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(runnerSettings); + + TVector schema = { + TTestHelper::TColumnSchema().SetName("id").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema().SetName("int_column").SetType(NScheme::NTypeIds::Int32).SetNullable(true) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName("/Root/ColumnTableTest").SetPrimaryKey({ "id" }).SetSchema(schema); + testHelper.CreateTable(testTable); + + TVector dataBuilders; + dataBuilders.push_back( + NConstruction::TSimpleArrayConstructor>::BuildNotNullable("id", false)); + dataBuilders.push_back( + std::make_shared>>("int_column")); + auto batch = NConstruction::TRecordBatchConstructor(dataBuilders).BuildBatch(100); + + for (ui32 i = 0; i < 5; i++) { + testHelper.BulkUpsert(testTable, batch); + auto alterQueryAdd = TStringBuilder() << "ALTER TABLE `" << testTable.GetName() << "` ADD COLUMN column" << i << " Uint64;"; + auto alterAddResult = testHelper.GetSession().ExecuteSchemeQuery(alterQueryAdd).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterAddResult.GetStatus(), EStatus::SUCCESS, alterAddResult.GetIssues().ToString()); + + if (!autoIndexation && indexationAfterInsertAddColumn) { + csController->EnableBackground(NYDBTest::ICSController::EBackground::Indexation); + csController->WaitIndexation(TDuration::Seconds(5)); + csController->DisableBackground(NYDBTest::ICSController::EBackground::Indexation); + } + + testHelper.BulkUpsert(testTable, batch); + auto alterQueryDrop = TStringBuilder() << "ALTER TABLE `" << testTable.GetName() << "` DROP COLUMN column" << i << ";"; + auto alterDropResult = testHelper.GetSession().ExecuteSchemeQuery(alterQueryDrop).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterDropResult.GetStatus(), EStatus::SUCCESS, alterDropResult.GetIssues().ToString()); + + if (!autoIndexation && indexationAfterInsertDropColumn) { + csController->EnableBackground(NYDBTest::ICSController::EBackground::Indexation); + csController->WaitIndexation(TDuration::Seconds(5)); + csController->DisableBackground(NYDBTest::ICSController::EBackground::Indexation); + } + } + + if (!autoIndexation && indexationInEnd) { + csController->EnableBackground(NYDBTest::ICSController::EBackground::Indexation); + csController->WaitIndexation(TDuration::Seconds(5)); + } + } + + Y_UNIT_TEST(InsertAddInsertDrop) { + TestInsertAddInsertDrop(true, false, false, false); + for (i32 i = 0; i < 8; i++) { + TestInsertAddInsertDrop(false, i & 1, i & 2, i & 3); + } + } + + Y_UNIT_TEST(DropTableAfterInsert) { + using namespace NArrow; + + auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); + csController->DisableBackground(NYDBTest::ICSController::EBackground::Indexation); + + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(runnerSettings); + + TVector schema = { + TTestHelper::TColumnSchema().SetName("id").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema().SetName("int_column").SetType(NScheme::NTypeIds::Int32).SetNullable(true) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName("/Root/ColumnTableTest").SetPrimaryKey({ "id" }).SetSchema(schema); + testHelper.CreateTable(testTable); + + TVector dataBuilders; + dataBuilders.push_back( + NConstruction::TSimpleArrayConstructor>::BuildNotNullable("id", false)); + dataBuilders.push_back( + std::make_shared>>("int_column")); + auto batch = NConstruction::TRecordBatchConstructor(dataBuilders).BuildBatch(100); + + testHelper.BulkUpsert(testTable, batch); + + auto alterQueryDrop = TStringBuilder() << "DROP TABLE `" << testTable.GetName() << "`;"; + auto alterDropResult = testHelper.GetSession().ExecuteSchemeQuery(alterQueryDrop).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterDropResult.GetStatus(), EStatus::SUCCESS, alterDropResult.GetIssues().ToString()); + + csController->EnableBackground(NYDBTest::ICSController::EBackground::Indexation); + csController->WaitIndexation(TDuration::Seconds(5)); + } + + Y_UNIT_TEST(InsertDropAddColumn) { + using namespace NArrow; + + auto csController = NYDBTest::TControllers::RegisterCSControllerGuard(); + csController->DisableBackground(NYDBTest::ICSController::EBackground::Indexation); + + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(runnerSettings); + + TVector schema = { + TTestHelper::TColumnSchema().SetName("id").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema().SetName("int_column").SetType(NScheme::NTypeIds::Int32).SetNullable(true) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName("/Root/ColumnTableTest").SetPrimaryKey({ "id" }).SetSchema(schema); + testHelper.CreateTable(testTable); + + TVector dataBuilders; + dataBuilders.push_back( + NConstruction::TSimpleArrayConstructor>::BuildNotNullable("id", false)); + dataBuilders.push_back( + std::make_shared>>("int_column")); + auto batch = NConstruction::TRecordBatchConstructor(dataBuilders).BuildBatch(100); + + testHelper.BulkUpsert(testTable, batch); + + auto alterQueryDrop = TStringBuilder() << "ALTER TABLE `" << testTable.GetName() << "` DROP COLUMN int_column;"; + auto alterDropResult = testHelper.GetSession().ExecuteSchemeQuery(alterQueryDrop).GetValueSync(); + + auto alterQueryAdd = TStringBuilder() << "ALTER TABLE `" << testTable.GetName() << "` ADD COLUMN int_column Int32;"; + auto alterAddResult = testHelper.GetSession().ExecuteSchemeQuery(alterQueryAdd).GetValueSync(); + + csController->EnableBackground(NYDBTest::ICSController::EBackground::Indexation); + csController->WaitIndexation(TDuration::Seconds(5)); + } + + Y_UNIT_TEST(CreateWithoutColumnFamily) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + + TString tableName = "/Root/TableWithoutColumnFamily"; + { + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema().SetName("Value1").SetType(NScheme::NTypeIds::String).SetNullable(true), + TTestHelper::TColumnSchema().SetName("Value2").SetType(NScheme::NTypeIds::Uint32).SetNullable(true) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema); + testHelper.CreateTable(testTable); + } + + auto& runner = testHelper.GetKikimr(); + auto tableClient = runner.GetTableClient(); + auto session = tableClient.CreateSession().GetValueSync().GetSession(); + + auto runtime = runner.GetTestServer().GetRuntime(); + TActorId sender = runtime->AllocateEdgeActor(); + + auto describeResult = DescribeTable(&runner.GetTestServer(), sender, tableName); + auto schema = describeResult.GetPathDescription().GetColumnTableDescription().GetSchema(); + TTestHelper::TCompression plainCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + TTestHelper::TColumnFamily defaultFamily = + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(plainCompression); + + UNIT_ASSERT_EQUAL(schema.ColumnFamiliesSize(), 1); + TTestHelper::TColumnFamily defaultFromScheme; + UNIT_ASSERT(defaultFromScheme.DeserializeFromProto(schema.GetColumnFamilies(0))); + { + TString errorMessage; + UNIT_ASSERT_C(defaultFromScheme.IsEqual(defaultFamily, errorMessage), errorMessage); + } + + auto columns = schema.GetColumns(); + for (ui32 i = 0; i < schema.ColumnsSize(); i++) { + auto column = columns[i]; + UNIT_ASSERT(column.HasSerializer()); + UNIT_ASSERT_EQUAL_C( + column.GetColumnFamilyId(), 0, TStringBuilder() << "family for column " << column.GetName() << " is not default"); + TTestHelper::TCompression compression; + UNIT_ASSERT(compression.DeserializeFromProto(schema.GetColumns(i).GetSerializer())); + TString errorMessage; + UNIT_ASSERT_C(compression.IsEqual(defaultFamily.GetCompression(), errorMessage), errorMessage); + } + } + + // Field `Data` is not used in ColumnFamily for ColumnTable + Y_UNIT_TEST(ColumnFamilyWithFieldData) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + TString tableName = "/Root/TableWithoutColumnFamily"; + + { + TTestHelper::TCompression plainCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + TTestHelper::TColumnFamily defaultFamily = + TTestHelper::TColumnFamily().SetId(0).SetData("test").SetFamilyName("default").SetCompression(plainCompression); + + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema().SetName("Value1").SetType(NScheme::NTypeIds::String).SetNullable(true), + TTestHelper::TColumnSchema().SetName("Value2").SetType(NScheme::NTypeIds::Uint32).SetNullable(true) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies({ defaultFamily }); + testHelper.CreateTable(testTable, EStatus::GENERIC_ERROR); + } + + { + TTestHelper::TCompression plainCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + TTestHelper::TColumnFamily defaultFamily = + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(plainCompression); + + TTestHelper::TCompression lz4Compression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecLZ4); + TTestHelper::TColumnFamily family1 = + TTestHelper::TColumnFamily().SetId(1).SetData("test").SetFamilyName("family1").SetCompression(lz4Compression); + + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema() + .SetName("Value1") + .SetType(NScheme::NTypeIds::String) + .SetNullable(true) + .SetColumnFamilyName(family1.GetFamilyName()), + TTestHelper::TColumnSchema().SetName("Value2").SetType(NScheme::NTypeIds::Uint32).SetNullable(true) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies({ defaultFamily, family1 }); + testHelper.CreateTable(testTable, EStatus::GENERIC_ERROR); + } + + { + TTestHelper::TCompression plainCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + TTestHelper::TColumnFamily defaultFamily = + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(plainCompression); + + TTestHelper::TCompression lz4Compression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecLZ4); + TTestHelper::TColumnFamily family1 = TTestHelper::TColumnFamily().SetId(1).SetFamilyName("family1").SetCompression(lz4Compression); + + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema() + .SetName("Value1") + .SetType(NScheme::NTypeIds::String) + .SetNullable(true) + .SetColumnFamilyName(family1.GetFamilyName()), + TTestHelper::TColumnSchema().SetName("Value2").SetType(NScheme::NTypeIds::Uint32).SetNullable(true) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies({ defaultFamily, family1 }); + testHelper.CreateTable(testTable); + + auto query = TStringBuilder() << R"(ALTER TABLE `)" << tableName << R"(` + ALTER FAMILY ")" << family1.GetFamilyName() + << R"( SET COMPRESSION "lz4";)"; + auto session = testHelper.GetSession(); + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::GENERIC_ERROR, result.GetIssues().ToString()); + } + } + + Y_UNIT_TEST(CreateWithDefaultColumnFamily) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + + TString tableName = "/Root/TableWithDefaultColumnFamily"; + TTestHelper::TCompression zstdCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecZSTD).SetCompressionLevel(5); + + TVector families = { + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(zstdCompression), + }; + + { + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema().SetName("Value1").SetType(NScheme::NTypeIds::String).SetNullable(true), + TTestHelper::TColumnSchema().SetName("Value2").SetType(NScheme::NTypeIds::Uint32).SetNullable(true) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable); + } + + auto& runner = testHelper.GetKikimr(); + auto runtime = runner.GetTestServer().GetRuntime(); + TActorId sender = runtime->AllocateEdgeActor(); + auto describeResult = DescribeTable(&runner.GetTestServer(), sender, tableName); + auto schema = describeResult.GetPathDescription().GetColumnTableDescription().GetSchema(); + + UNIT_ASSERT_EQUAL(schema.ColumnFamiliesSize(), families.size()); + TTestHelper::TColumnFamily defaultFromScheme; + UNIT_ASSERT(defaultFromScheme.DeserializeFromProto(schema.GetColumnFamilies(0))); + { + TString errorMessage; + UNIT_ASSERT_C(defaultFromScheme.IsEqual(families[0], errorMessage), errorMessage); + } + + for (const auto& column : schema.GetColumns()) { + UNIT_ASSERT(column.HasSerializer()); + UNIT_ASSERT_EQUAL_C( + column.GetColumnFamilyId(), 0, TStringBuilder() << "family for column " << column.GetName() << " is not default"); + TTestHelper::TCompression compression; + UNIT_ASSERT(compression.DeserializeFromProto(column.GetSerializer())); + TString errorMessage; + UNIT_ASSERT_C(compression.IsEqual(families[0].GetCompression(), errorMessage), errorMessage); + } + } + + Y_UNIT_TEST(CrateWithWrongCodec) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + + TString tableName = "/Root/TableWithWrongCodec"; + TTestHelper::TCompression zstdCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecZSTD).SetCompressionLevel(100); + + TVector families = { + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(zstdCompression), + }; + + { + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema().SetName("Value1").SetType(NScheme::NTypeIds::String).SetNullable(true), + TTestHelper::TColumnSchema().SetName("Value2").SetType(NScheme::NTypeIds::Uint32).SetNullable(true) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable, EStatus::SCHEME_ERROR); + } + + TTestHelper::TCompression lz4Compression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecLZ4).SetCompressionLevel(100); + families[0].SetCompression(lz4Compression); + { + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema().SetName("Value1").SetType(NScheme::NTypeIds::String).SetNullable(true), + TTestHelper::TColumnSchema().SetName("Value2").SetType(NScheme::NTypeIds::Uint32).SetNullable(true) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable, EStatus::SCHEME_ERROR); + } + + { + auto session = testHelper.GetSession(); + auto createQuery = TStringBuilder() << R"(CREATE TABLE `)" << tableName << R"(` ( + Key Uint64 NOT NULL, + Value1 String, + Value2 Uint32, + PRIMARY KEY (Key), + FAMILY default ( + COMPRESSION="snappy" + )) WITH (STORE = COLUMN);)"; + auto result = session.ExecuteSchemeQuery(createQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::GENERIC_ERROR, result.GetIssues().ToString()); + } + } + + Y_UNIT_TEST(AlterCompressionType) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + + TString tableName = "/Root/TableWithColumnFamily"; + TTestHelper::TCompression plainCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + TTestHelper::TCompression zstdCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecZSTD).SetCompressionLevel(3); + TTestHelper::TCompression lz4Compression = TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecLZ4); + + TVector families = { + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(plainCompression), + TTestHelper::TColumnFamily().SetId(1).SetFamilyName("family1").SetCompression(zstdCompression), + TTestHelper::TColumnFamily().SetId(2).SetFamilyName("family2").SetCompression(lz4Compression), + }; + + { + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema() + .SetName("Value1") + .SetType(NScheme::NTypeIds::String) + .SetNullable(true) + .SetColumnFamilyName(families[1].GetFamilyName()), + TTestHelper::TColumnSchema() + .SetName("Value2") + .SetType(NScheme::NTypeIds::Uint32) + .SetNullable(true) + .SetColumnFamilyName(families[2].GetFamilyName()) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable); + } + + auto session = testHelper.GetSession(); + + families[1].MutableCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecLZ4).SetCompressionLevel({}); + auto query = TStringBuilder() << R"(ALTER TABLE `)" << tableName << R"(` + ALTER FAMILY family1 SET COMPRESSION "lz4";)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + + auto& runner = testHelper.GetKikimr(); + auto runtime = runner.GetTestServer().GetRuntime(); + TActorId sender = runtime->AllocateEdgeActor(); + auto describeResult = DescribeTable(&runner.GetTestServer(), sender, tableName); + auto schema = describeResult.GetPathDescription().GetColumnTableDescription().GetSchema(); + + UNIT_ASSERT_EQUAL(schema.ColumnFamiliesSize(), families.size()); + for (ui32 i = 0; i < families.size(); i++) { + TTestHelper::TColumnFamily familyFromScheme; + UNIT_ASSERT(familyFromScheme.DeserializeFromProto(schema.GetColumnFamilies(i))); + TString errorMessage; + UNIT_ASSERT_C(familyFromScheme.IsEqual(families[i], errorMessage), errorMessage); + } + + auto columns = schema.GetColumns(); + for (ui32 i = 0; i < schema.ColumnsSize(); i++) { + UNIT_ASSERT(columns[i].HasSerializer()); + UNIT_ASSERT_EQUAL_C(columns[i].GetColumnFamilyId(), i, + TStringBuilder() << "family for column `" << columns[i].GetName() << "` is not `" << families[i].GetFamilyName() << "`"); + TTestHelper::TCompression compression; + UNIT_ASSERT(compression.DeserializeFromProto(columns[i].GetSerializer())); + TString errorMessage; + UNIT_ASSERT_C(compression.IsEqual(families[i].GetCompression(), errorMessage), errorMessage); + } + } + + Y_UNIT_TEST(AlterCompressionLevel) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + + TString tableName = "/Root/TableWithColumnFamily"; + TTestHelper::TCompression zstdCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecZSTD).SetCompressionLevel(5); + + TVector families = { + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(zstdCompression), + }; + + { + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema().SetName("Value1").SetType(NScheme::NTypeIds::String).SetNullable(true), + TTestHelper::TColumnSchema().SetName("Value2").SetType(NScheme::NTypeIds::Uint32).SetNullable(true) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable); + } + + families[0].MutableCompression().SetCompressionLevel(6); + auto alterFamilyCompressionLevel = TStringBuilder() << R"(ALTER TABLE `)" << tableName << R"(` + ALTER FAMILY default SET COMPRESSION_LEVEL 6;)"; + auto session = testHelper.GetSession(); + auto result = session.ExecuteSchemeQuery(alterFamilyCompressionLevel).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + + auto& runner = testHelper.GetKikimr(); + auto runtime = runner.GetTestServer().GetRuntime(); + TActorId sender = runtime->AllocateEdgeActor(); + auto describeResult = DescribeTable(&runner.GetTestServer(), sender, tableName); + auto schema = describeResult.GetPathDescription().GetColumnTableDescription().GetSchema(); + + UNIT_ASSERT_EQUAL(schema.ColumnFamiliesSize(), families.size()); + TTestHelper::TColumnFamily defaultFromScheme; + UNIT_ASSERT(defaultFromScheme.DeserializeFromProto(schema.GetColumnFamilies(0))); + { + TString errorMessage; + UNIT_ASSERT_C(defaultFromScheme.IsEqual(families[0], errorMessage), errorMessage); + } + + for (const auto& column : schema.GetColumns()) { + UNIT_ASSERT(column.HasSerializer()); + UNIT_ASSERT_EQUAL_C( + column.GetColumnFamilyId(), 0, TStringBuilder() << "family for column " << column.GetName() << " is not default"); + TTestHelper::TCompression compression; + UNIT_ASSERT(compression.DeserializeFromProto(column.GetSerializer())); + TString errorMessage; + UNIT_ASSERT_C(compression.IsEqual(families[0].GetCompression(), errorMessage), errorMessage); + } + } + + Y_UNIT_TEST(AlterCompressionLevelError) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + + TString tableName = "/Root/TableWithColumnFamily"; + TTestHelper::TCompression lz4Compression = TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecLZ4); + + TVector families = { + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(lz4Compression), + }; + + { + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema().SetName("Value1").SetType(NScheme::NTypeIds::String).SetNullable(true), + TTestHelper::TColumnSchema().SetName("Value2").SetType(NScheme::NTypeIds::Uint32).SetNullable(true) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable); + } + + families[0].MutableCompression().SetCompressionLevel(6); + auto alterFamilyCompressionLevel = TStringBuilder() << R"(ALTER TABLE `)" << tableName << R"(` + ALTER FAMILY default SET COMPRESSION_LEVEL 6;)"; + auto session = testHelper.GetSession(); + auto result = session.ExecuteSchemeQuery(alterFamilyCompressionLevel).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SCHEME_ERROR, result.GetIssues().ToString()); + } + + Y_UNIT_TEST(CreateWithColumnFamily) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + + TString tableName = "/Root/TableWithColumnFamily"; + TTestHelper::TCompression plainCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + TTestHelper::TCompression zstdCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecZSTD).SetCompressionLevel(3); + TTestHelper::TCompression lz4Compression = TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecLZ4); + + TVector families = { + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(plainCompression), + TTestHelper::TColumnFamily().SetId(1).SetFamilyName("family1").SetCompression(zstdCompression), + TTestHelper::TColumnFamily().SetId(2).SetFamilyName("family2").SetCompression(lz4Compression), + }; + + { + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema() + .SetName("Value1") + .SetType(NScheme::NTypeIds::String) + .SetNullable(true) + .SetColumnFamilyName(families[1].GetFamilyName()), + TTestHelper::TColumnSchema() + .SetName("Value2") + .SetType(NScheme::NTypeIds::Uint32) + .SetNullable(true) + .SetColumnFamilyName(families[2].GetFamilyName()) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable); + } + + auto& runner = testHelper.GetKikimr(); + auto runtime = runner.GetTestServer().GetRuntime(); + TActorId sender = runtime->AllocateEdgeActor(); + auto describeResult = DescribeTable(&runner.GetTestServer(), sender, tableName); + auto schema = describeResult.GetPathDescription().GetColumnTableDescription().GetSchema(); + + UNIT_ASSERT_EQUAL(schema.ColumnFamiliesSize(), families.size()); + for (ui32 i = 0; i < families.size(); i++) { + TTestHelper::TColumnFamily familyFromScheme; + UNIT_ASSERT(familyFromScheme.DeserializeFromProto(schema.GetColumnFamilies(i))); + TString errorMessage; + UNIT_ASSERT_C(familyFromScheme.IsEqual(families[i], errorMessage), errorMessage); + } + + auto columns = schema.GetColumns(); + for (ui32 i = 0; i < schema.ColumnsSize(); i++) { + UNIT_ASSERT(columns[i].HasSerializer()); + UNIT_ASSERT_EQUAL_C(columns[i].GetColumnFamilyId(), i, + TStringBuilder() << "family for column `" << columns[i].GetName() << "` is not `" << families[i].GetFamilyName() << "`"); + TTestHelper::TCompression compression; + UNIT_ASSERT(compression.DeserializeFromProto(columns[i].GetSerializer())); + TString errorMessage; + UNIT_ASSERT_C(compression.IsEqual(families[i].GetCompression(), errorMessage), errorMessage); + } + } + + Y_UNIT_TEST(AddColumnFamily) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + + TString tableName = "/Root/TableWithColumnFamily"; + TTestHelper::TCompression plainCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + TTestHelper::TCompression zstdCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecZSTD).SetCompressionLevel(1); + TTestHelper::TCompression lz4Compression = TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecLZ4); + + TVector families = { + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(plainCompression), + }; + + { + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema().SetName("Value1").SetType(NScheme::NTypeIds::String).SetNullable(true), + TTestHelper::TColumnSchema().SetName("Value2").SetType(NScheme::NTypeIds::Uint32).SetNullable(true) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable); + } + + auto session = testHelper.GetSession(); + auto& runner = testHelper.GetKikimr(); + auto runtime = runner.GetTestServer().GetRuntime(); + TActorId sender = runtime->AllocateEdgeActor(); + { + families.push_back(TTestHelper::TColumnFamily().SetId(1).SetFamilyName("family1").SetCompression(zstdCompression)); + auto query = TStringBuilder() << R"(ALTER TABLE `)" << tableName << R"(` + ADD FAMILY family1 ( + COMPRESSION = "zstd");)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + + auto describeResult = DescribeTable(&runner.GetTestServer(), sender, tableName); + auto schema = describeResult.GetPathDescription().GetColumnTableDescription().GetSchema(); + UNIT_ASSERT_EQUAL(schema.ColumnFamiliesSize(), families.size()); + for (ui32 i = 0; i < families.size(); i++) { + TTestHelper::TColumnFamily familyFromScheme; + UNIT_ASSERT(familyFromScheme.DeserializeFromProto(schema.GetColumnFamilies(i))); + TString errorMessage; + UNIT_ASSERT_C(familyFromScheme.IsEqual(families[i], errorMessage), errorMessage); + } + } + + { + families.push_back(TTestHelper::TColumnFamily().SetId(2).SetFamilyName("family2").SetCompression(lz4Compression)); + families.push_back(TTestHelper::TColumnFamily().SetId(3).SetFamilyName("family3").SetCompression(zstdCompression)); + auto query = TStringBuilder() << R"(ALTER TABLE `)" << tableName << R"(` + ADD FAMILY family2 ( + COMPRESSION = "lz4"), + ADD FAMILY family3 ( + COMPRESSION = "zstd");)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + + auto describeResult = DescribeTable(&runner.GetTestServer(), sender, tableName); + auto schema = describeResult.GetPathDescription().GetColumnTableDescription().GetSchema(); + UNIT_ASSERT_EQUAL(schema.ColumnFamiliesSize(), families.size()); + for (ui32 i = 0; i < families.size(); i++) { + TTestHelper::TColumnFamily familyFromScheme; + UNIT_ASSERT(familyFromScheme.DeserializeFromProto(schema.GetColumnFamilies(i))); + TString errorMessage; + UNIT_ASSERT_C(familyFromScheme.IsEqual(families[i], errorMessage), errorMessage); + } + } + } + + Y_UNIT_TEST(AddColumnWithoutColumnFamily) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + + TString tableName = "/Root/TableWithColumnFamily"; + TTestHelper::TCompression zstdCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecZSTD).SetCompressionLevel(1); + + TVector families = { + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(zstdCompression), + }; + + { + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema().SetName("Value1").SetType(NScheme::NTypeIds::String).SetNullable(true), + TTestHelper::TColumnSchema().SetName("Value2").SetType(NScheme::NTypeIds::Uint32).SetNullable(true) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable); + } + + auto session = testHelper.GetSession(); + auto& runner = testHelper.GetKikimr(); + auto runtime = runner.GetTestServer().GetRuntime(); + TActorId sender = runtime->AllocateEdgeActor(); + + { + auto query = TStringBuilder() << R"(ALTER TABLE `)" << tableName << R"(` + ADD COLUMN Value3 Uint32;)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + + auto describeResult = DescribeTable(&runner.GetTestServer(), sender, tableName); + auto schema = describeResult.GetPathDescription().GetColumnTableDescription().GetSchema(); + UNIT_ASSERT_EQUAL(schema.ColumnFamiliesSize(), families.size()); + for (ui32 i = 0; i < families.size(); i++) { + TTestHelper::TColumnFamily familyFromScheme; + UNIT_ASSERT(familyFromScheme.DeserializeFromProto(schema.GetColumnFamilies(i))); + TString errorMessage; + UNIT_ASSERT_C(familyFromScheme.IsEqual(families[i], errorMessage), errorMessage); + } + + auto columns = schema.GetColumns(); + for (ui32 i = 0; i < schema.ColumnsSize(); i++) { + UNIT_ASSERT(columns[i].HasSerializer()); + UNIT_ASSERT_EQUAL_C(columns[i].GetColumnFamilyId(), 0, + TStringBuilder() << "family for column `" << columns[i].GetName() << "` is not `" << families[0].GetFamilyName() + << "`"); + TTestHelper::TCompression compression; + UNIT_ASSERT(compression.DeserializeFromProto(columns[i].GetSerializer())); + TString errorMessage; + UNIT_ASSERT_C(compression.IsEqual(families[0].GetCompression(), errorMessage), errorMessage); + } + } + + } + + Y_UNIT_TEST(AddColumnWithColumnFamily) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + + TString tableName = "/Root/TableWithColumnFamily"; + TTestHelper::TCompression plainCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + TTestHelper::TCompression zstdCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecZSTD).SetCompressionLevel(1); + TTestHelper::TCompression lz4Compression = TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecLZ4); + + TVector families = { + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(plainCompression), + }; + + { + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema().SetName("Value1").SetType(NScheme::NTypeIds::String).SetNullable(true), + TTestHelper::TColumnSchema().SetName("Value2").SetType(NScheme::NTypeIds::Uint32).SetNullable(true) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable); + } + + auto session = testHelper.GetSession(); + auto& runner = testHelper.GetKikimr(); + auto runtime = runner.GetTestServer().GetRuntime(); + TActorId sender = runtime->AllocateEdgeActor(); + { + families.push_back(TTestHelper::TColumnFamily().SetId(1).SetFamilyName("family1").SetCompression(zstdCompression)); + auto query = TStringBuilder() << R"(ALTER TABLE `)" << tableName << R"(` + ADD FAMILY family1 ( + COMPRESSION = "zstd");)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + } + + { + auto query = TStringBuilder() << R"(ALTER TABLE `)" << tableName << R"(` + ADD COLUMN Value3 Uint32 FAMILY family1;)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + + auto describeResult = DescribeTable(&runner.GetTestServer(), sender, tableName); + auto schema = describeResult.GetPathDescription().GetColumnTableDescription().GetSchema(); + UNIT_ASSERT_EQUAL(schema.ColumnFamiliesSize(), families.size()); + for (ui32 i = 0; i < families.size(); i++) { + TTestHelper::TColumnFamily familyFromScheme; + UNIT_ASSERT(familyFromScheme.DeserializeFromProto(schema.GetColumnFamilies(i))); + TString errorMessage; + UNIT_ASSERT_C(familyFromScheme.IsEqual(families[i], errorMessage), errorMessage); + } + + auto columns = schema.GetColumns(); + for (ui32 i = 0; i < schema.ColumnsSize(); i++) { + UNIT_ASSERT(columns[i].HasSerializer()); + ui32 indexFamily = 0; + if (columns[i].GetName() == "Value3") { + indexFamily = 1; + } + UNIT_ASSERT_EQUAL_C(columns[i].GetColumnFamilyId(), indexFamily, + TStringBuilder() << "family for column `" << columns[i].GetName() << "` is not `" << families[indexFamily].GetFamilyName() + << "`"); + TTestHelper::TCompression compression; + UNIT_ASSERT(compression.DeserializeFromProto(columns[i].GetSerializer())); + TString errorMessage; + UNIT_ASSERT_C(compression.IsEqual(families[indexFamily].GetCompression(), errorMessage), errorMessage); + } + } + + { + families.push_back(TTestHelper::TColumnFamily().SetId(2).SetFamilyName("family2").SetCompression(lz4Compression)); + auto query = TStringBuilder() << R"(ALTER TABLE `)" << tableName << R"(` + ADD FAMILY family2 ( + COMPRESSION = "lz4"), + ADD COLUMN Value4 Uint32 FAMILY family2, + ADD COLUMN Value5 Uint32 FAMILY family1;)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + + auto describeResult = DescribeTable(&runner.GetTestServer(), sender, tableName); + auto schema = describeResult.GetPathDescription().GetColumnTableDescription().GetSchema(); + UNIT_ASSERT_EQUAL(schema.ColumnFamiliesSize(), families.size()); + for (ui32 i = 0; i < families.size(); i++) { + TTestHelper::TColumnFamily familyFromScheme; + UNIT_ASSERT(familyFromScheme.DeserializeFromProto(schema.GetColumnFamilies(i))); + TString errorMessage; + UNIT_ASSERT_C(familyFromScheme.IsEqual(families[i], errorMessage), errorMessage); + } + + auto columns = schema.GetColumns(); + for (ui32 i = 0; i < schema.ColumnsSize(); i++) { + UNIT_ASSERT(columns[i].HasSerializer()); + ui32 indexFamily = 0; + if (columns[i].GetName() == "Value3" || columns[i].GetName() == "Value5") { + indexFamily = 1; + } else if (columns[i].GetName() == "Value4") { + indexFamily = 2; + } + UNIT_ASSERT_EQUAL_C(columns[i].GetColumnFamilyId(), indexFamily, + TStringBuilder() << "family for column `" << columns[i].GetName() << "` is not `" << families[indexFamily].GetFamilyName() + << "`"); + TTestHelper::TCompression compression; + UNIT_ASSERT(compression.DeserializeFromProto(columns[i].GetSerializer())); + TString errorMessage; + UNIT_ASSERT_C(compression.IsEqual(families[indexFamily].GetCompression(), errorMessage), errorMessage); + } + } + } + + Y_UNIT_TEST(SetColumnFamily) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + + TString tableName = "/Root/TableWithColumnFamily"; + TTestHelper::TCompression plainCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + TTestHelper::TCompression zstdCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecZSTD).SetCompressionLevel(1); + TTestHelper::TCompression lz4Compression = TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecLZ4); + + TVector families = { + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(plainCompression), + TTestHelper::TColumnFamily().SetId(1).SetFamilyName("family1").SetCompression(zstdCompression), + }; + + { + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema().SetName("Value1").SetType(NScheme::NTypeIds::String).SetNullable(true), + TTestHelper::TColumnSchema().SetName("Value2").SetType(NScheme::NTypeIds::Uint32).SetNullable(true) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable); + } + + auto session = testHelper.GetSession(); + auto& runner = testHelper.GetKikimr(); + auto runtime = runner.GetTestServer().GetRuntime(); + TActorId sender = runtime->AllocateEdgeActor(); + + { + auto query = TStringBuilder() << R"(ALTER TABLE `)" << tableName << R"(` + ALTER COLUMN Value1 SET FAMILY family1;)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + + auto describeResult = DescribeTable(&runner.GetTestServer(), sender, tableName); + auto schema = describeResult.GetPathDescription().GetColumnTableDescription().GetSchema(); + + auto columns = schema.GetColumns(); + for (ui32 i = 0; i < schema.ColumnsSize(); i++) { + UNIT_ASSERT(columns[i].HasSerializer()); + ui32 indexFamily = 0; + if (columns[i].GetName() == "Value1") { + indexFamily = 1; + } + + UNIT_ASSERT_EQUAL_C(columns[i].GetColumnFamilyId(), indexFamily, + TStringBuilder() << "family for column `" << columns[i].GetName() << "` is not `" << families[indexFamily].GetFamilyName() + << "`"); + TTestHelper::TCompression compression; + UNIT_ASSERT(compression.DeserializeFromProto(columns[i].GetSerializer())); + TString errorMessage; + UNIT_ASSERT_C(compression.IsEqual(families[indexFamily].GetCompression(), errorMessage), errorMessage); + } + } + + { + families.push_back(TTestHelper::TColumnFamily().SetId(2).SetFamilyName("family2").SetCompression(lz4Compression)); + auto query = TStringBuilder() << R"(ALTER TABLE `)" << tableName << R"(` + ADD FAMILY family2 ( + COMPRESSION = "lz4"), + ALTER COLUMN Value2 SET FAMILY family2;)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + + auto describeResult = DescribeTable(&runner.GetTestServer(), sender, tableName); + auto schema = describeResult.GetPathDescription().GetColumnTableDescription().GetSchema(); + UNIT_ASSERT_EQUAL(schema.ColumnFamiliesSize(), families.size()); + for (ui32 i = 0; i < families.size(); i++) { + TTestHelper::TColumnFamily familyFromScheme; + UNIT_ASSERT(familyFromScheme.DeserializeFromProto(schema.GetColumnFamilies(i))); + TString errorMessage; + UNIT_ASSERT_C(familyFromScheme.IsEqual(families[i], errorMessage), errorMessage); + } + + auto columns = schema.GetColumns(); + for (ui32 i = 0; i < schema.ColumnsSize(); i++) { + UNIT_ASSERT(columns[i].HasSerializer()); + ui32 indexFamily = 0; + if (columns[i].GetName() == "Value1") { + indexFamily = 1; + } else if (columns[i].GetName() == "Value2") { + indexFamily = 2; + } + + UNIT_ASSERT_EQUAL_C(columns[i].GetColumnFamilyId(), indexFamily, + TStringBuilder() << "family for column `" << columns[i].GetName() << "` is not `" << families[indexFamily].GetFamilyName() + << "`"); + TTestHelper::TCompression compression; + UNIT_ASSERT(compression.DeserializeFromProto(columns[i].GetSerializer())); + TString errorMessage; + UNIT_ASSERT_C(compression.IsEqual(families[indexFamily].GetCompression(), errorMessage), errorMessage); + } + } + } + + Y_UNIT_TEST(WithoutDefaultColumnFamily) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + TString tableName = "/Root/TableWithFamily"; + + TTestHelper::TCompression plainCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + + TVector families = { + TTestHelper::TColumnFamily().SetId(1).SetFamilyName("family1").SetCompression(plainCompression), + }; + + { + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema() + .SetName("Value1") + .SetType(NScheme::NTypeIds::String) + .SetNullable(true) + .SetColumnFamilyName(families[0].GetFamilyName()), + TTestHelper::TColumnSchema() + .SetName("Value2") + .SetType(NScheme::NTypeIds::Uint32) + .SetNullable(true) + .SetColumnFamilyName(families[0].GetFamilyName()) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable); + } + + families.push_back(TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(plainCompression)); + auto& runner = testHelper.GetKikimr(); + auto runtime = runner.GetTestServer().GetRuntime(); + TActorId sender = runtime->AllocateEdgeActor(); + auto describeResult = DescribeTable(&runner.GetTestServer(), sender, tableName); + auto schema = describeResult.GetPathDescription().GetColumnTableDescription().GetSchema(); + UNIT_ASSERT_EQUAL(schema.ColumnFamiliesSize(), families.size()); + for (ui32 i = 0; i < families.size(); i++) { + TTestHelper::TColumnFamily familyFromScheme; + UNIT_ASSERT(familyFromScheme.DeserializeFromProto(schema.GetColumnFamilies(i))); + ui32 familyIndex = 0; + if (familyFromScheme.GetFamilyName() == "default") { + familyIndex = 1; + } + TString errorMessage; + UNIT_ASSERT_C(familyFromScheme.IsEqual(families[familyIndex], errorMessage), errorMessage); + } + } + + Y_UNIT_TEST(UnknownColumnFamily) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + TString tableName = "/Root/TableWithFamily"; + + TTestHelper::TCompression plainCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + + TVector families = { + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(plainCompression), + }; + + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema().SetName("Value1").SetType(NScheme::NTypeIds::String).SetNullable(true).SetColumnFamilyName("family1"), + TTestHelper::TColumnSchema().SetName("Value2").SetType(NScheme::NTypeIds::Uint32).SetNullable(true).SetColumnFamilyName("family1") + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable, EStatus::GENERIC_ERROR); + } + + Y_UNIT_TEST(PrimaryKeyNotDefaultColumnFamily) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + TString tableName = "/Root/TableWithFamily"; + + TTestHelper::TCompression plainCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + TTestHelper::TCompression zstdCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecZSTD).SetCompressionLevel(1); + + TVector families = { + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(plainCompression), + TTestHelper::TColumnFamily().SetId(1).SetFamilyName("family1").SetCompression(zstdCompression), + }; + + { + TVector schema = { TTestHelper::TColumnSchema() + .SetName("Key") + .SetType(NScheme::NTypeIds::Uint64) + .SetColumnFamilyName(families[1].GetFamilyName()) + .SetNullable(false), + TTestHelper::TColumnSchema() + .SetName("Value1") + .SetType(NScheme::NTypeIds::String) + .SetNullable(true) + .SetColumnFamilyName(families[1].GetFamilyName()), + TTestHelper::TColumnSchema() + .SetName("Value2") + .SetType(NScheme::NTypeIds::Uint32) + .SetNullable(true) + .SetColumnFamilyName(families[1].GetFamilyName()) }; + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable); + } + + auto& runner = testHelper.GetKikimr(); + auto runtime = runner.GetTestServer().GetRuntime(); + TActorId sender = runtime->AllocateEdgeActor(); + auto describeResult = DescribeTable(&runner.GetTestServer(), sender, tableName); + auto schema = describeResult.GetPathDescription().GetColumnTableDescription().GetSchema(); + + UNIT_ASSERT_EQUAL(schema.ColumnFamiliesSize(), families.size()); + for (ui32 i = 0; i < families.size(); i++) { + TTestHelper::TColumnFamily familyFromScheme; + UNIT_ASSERT(familyFromScheme.DeserializeFromProto(schema.GetColumnFamilies(i))); + TString errorMessage; + UNIT_ASSERT_C(familyFromScheme.IsEqual(families[i], errorMessage), errorMessage); + } + + auto columns = schema.GetColumns(); + for (ui32 i = 0; i < schema.ColumnsSize(); i++) { + UNIT_ASSERT(columns[i].HasSerializer()); + UNIT_ASSERT_EQUAL_C(columns[i].GetColumnFamilyId(), 1, + TStringBuilder() << "family for column `" << columns[i].GetName() << "` is not `" << families[1].GetFamilyName() << "`"); + TTestHelper::TCompression compression; + UNIT_ASSERT(compression.DeserializeFromProto(columns[i].GetSerializer())); + TString errorMessage; + UNIT_ASSERT_C(compression.IsEqual(families[1].GetCompression(), errorMessage), errorMessage); + } + } + + Y_UNIT_TEST(SetNotDefaultColumnFamilyForPrimaryKey) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + TString tableName = "/Root/TableWithFamily"; + + TTestHelper::TCompression plainCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + TTestHelper::TCompression zstdCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecZSTD).SetCompressionLevel(1); + + TVector families = { + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(plainCompression), + TTestHelper::TColumnFamily().SetId(1).SetFamilyName("family1").SetCompression(zstdCompression), + }; + + { + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema() + .SetName("Value1") + .SetType(NScheme::NTypeIds::String) + .SetNullable(true) + .SetColumnFamilyName(families[1].GetFamilyName()), + TTestHelper::TColumnSchema() + .SetName("Value2") + .SetType(NScheme::NTypeIds::Uint32) + .SetNullable(true) + .SetColumnFamilyName(families[1].GetFamilyName()) + }; + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable); + } + + auto session = testHelper.GetSession(); + auto query = TStringBuilder() << R"(ALTER TABLE `)" << tableName << R"(` + ALTER COLUMN Key SET FAMILY family1;)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::SUCCESS, result.GetIssues().ToString()); + + auto& runner = testHelper.GetKikimr(); + auto runtime = runner.GetTestServer().GetRuntime(); + TActorId sender = runtime->AllocateEdgeActor(); + auto describeResult = DescribeTable(&runner.GetTestServer(), sender, tableName); + auto schema = describeResult.GetPathDescription().GetColumnTableDescription().GetSchema(); + + UNIT_ASSERT_EQUAL(schema.ColumnFamiliesSize(), families.size()); + for (ui32 i = 0; i < families.size(); i++) { + TTestHelper::TColumnFamily familyFromScheme; + UNIT_ASSERT(familyFromScheme.DeserializeFromProto(schema.GetColumnFamilies(i))); + TString errorMessage; + UNIT_ASSERT_C(familyFromScheme.IsEqual(families[i], errorMessage), errorMessage); + } + + auto columns = schema.GetColumns(); + for (ui32 i = 0; i < schema.ColumnsSize(); i++) { + UNIT_ASSERT(columns[i].HasSerializer()); + UNIT_ASSERT_EQUAL_C(columns[i].GetColumnFamilyId(), 1, + TStringBuilder() << "family for column `" << columns[i].GetName() << "` is not `" << families[1].GetFamilyName() << "`"); + TTestHelper::TCompression compression; + UNIT_ASSERT(compression.DeserializeFromProto(columns[i].GetSerializer())); + TString errorMessage; + UNIT_ASSERT_C(compression.IsEqual(families[1].GetCompression(), errorMessage), errorMessage); + } + } + + Y_UNIT_TEST(AddExsitsColumnFamily) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + + TString tableName = "/Root/TableWithFamily"; + TTestHelper::TCompression plainCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + TTestHelper::TCompression lz4Compression = TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecLZ4); + + TVector families = { + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(plainCompression), + TTestHelper::TColumnFamily().SetId(1).SetFamilyName("family1").SetCompression(lz4Compression), + TTestHelper::TColumnFamily().SetId(2).SetFamilyName("family2").SetCompression(lz4Compression), + }; + + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema() + .SetName("Value1") + .SetType(NScheme::NTypeIds::String) + .SetNullable(true) + .SetColumnFamilyName(families[1].GetFamilyName()), + TTestHelper::TColumnSchema() + .SetName("Value2") + .SetType(NScheme::NTypeIds::Uint32) + .SetNullable(true) + .SetColumnFamilyName(families[1].GetFamilyName()) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable); + + auto session = testHelper.GetSession(); + { + auto query = TStringBuilder() << R"(ALTER TABLE `)" << tableName << R"(` + ADD FAMILY family1 (COMPRESSION = "lz4")"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::GENERIC_ERROR, result.GetIssues().ToString()); + } + + { + auto query = TStringBuilder() << R"(ALTER TABLE `)" << tableName + << R"(` ADD FAMILY family3 (COMPRESSION = "lz4"), ADD FAMILY family3 (COMPRESSION = "zstd")"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::GENERIC_ERROR, result.GetIssues().ToString()); + } + } + + Y_UNIT_TEST(AddColumnFamilyWithNotSupportedCodec) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + + TString tableName = "/Root/TableWithFamily"; + TTestHelper::TCompression plainCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + TTestHelper::TCompression lz4Compression = TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecLZ4); + + TVector families = { + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(plainCompression), + TTestHelper::TColumnFamily().SetId(1).SetFamilyName("family1").SetCompression(lz4Compression), + TTestHelper::TColumnFamily().SetId(2).SetFamilyName("family2").SetCompression(lz4Compression), + }; + + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema() + .SetName("Value1") + .SetType(NScheme::NTypeIds::String) + .SetNullable(true) + .SetColumnFamilyName(families[1].GetFamilyName()), + TTestHelper::TColumnSchema() + .SetName("Value2") + .SetType(NScheme::NTypeIds::Uint32) + .SetNullable(true) + .SetColumnFamilyName(families[1].GetFamilyName()) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable); + + auto session = testHelper.GetSession(); + { + auto query = TStringBuilder() << R"(ALTER TABLE `)" << tableName << R"(` + ADD FAMILY family1 (COMPRESSION = "snappy")"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::GENERIC_ERROR, result.GetIssues().ToString()); + } + + { + auto query = TStringBuilder() << R"(ALTER TABLE `)" << tableName << R"(` + ADD FAMILY family1 (COMPRESSION = "lz4", COMPRESSION_LEVEL = 5)"; + auto result = session.ExecuteSchemeQuery(query).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.GetStatus(), EStatus::GENERIC_ERROR, result.GetIssues().ToString()); + } + } + + Y_UNIT_TEST(TwoSimilarColumnFamilies) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + + TString tableName = "/Root/TableWithFamily"; + TTestHelper::TCompression plainCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + TTestHelper::TCompression lz4Compression = TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecLZ4); + + TVector families = { + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(plainCompression), + TTestHelper::TColumnFamily().SetId(1).SetFamilyName("family1").SetCompression(lz4Compression), + TTestHelper::TColumnFamily().SetId(2).SetFamilyName("family1").SetCompression(lz4Compression), + }; + + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema() + .SetName("Value1") + .SetType(NScheme::NTypeIds::String) + .SetNullable(true) + .SetColumnFamilyName(families[1].GetFamilyName()), + TTestHelper::TColumnSchema() + .SetName("Value2") + .SetType(NScheme::NTypeIds::Uint32) + .SetNullable(true) + .SetColumnFamilyName(families[1].GetFamilyName()) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable, EStatus::GENERIC_ERROR); + } + + Y_UNIT_TEST(CreateTableStoreWithFamily) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(TKikimrSettings().SetWithSampleTables(false)); + + TString tableName = "/Root/TableStoreWithColumnFamily"; + TTestHelper::TCompression plainCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + TTestHelper::TCompression zstdCompression = + TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecZSTD).SetCompressionLevel(1); + TTestHelper::TCompression lz4Compression = TTestHelper::TCompression().SetCompressionType(NKikimrSchemeOp::EColumnCodec::ColumnCodecLZ4); + + TVector families = { + TTestHelper::TColumnFamily().SetId(0).SetFamilyName("default").SetCompression(plainCompression), + TTestHelper::TColumnFamily().SetId(1).SetFamilyName("family1").SetCompression(zstdCompression), + TTestHelper::TColumnFamily().SetId(2).SetFamilyName("family2").SetCompression(lz4Compression), + }; + + TVector schema = { + TTestHelper::TColumnSchema().SetName("Key").SetType(NScheme::NTypeIds::Uint64).SetNullable(false), + TTestHelper::TColumnSchema() + .SetName("Value1") + .SetType(NScheme::NTypeIds::String) + .SetNullable(true) + .SetColumnFamilyName(families[1].GetFamilyName()), + TTestHelper::TColumnSchema() + .SetName("Value2") + .SetType(NScheme::NTypeIds::Uint32) + .SetNullable(true) + .SetColumnFamilyName(families[2].GetFamilyName()) + }; + + TTestHelper::TColumnTableStore testTable; + testTable.SetName(tableName).SetPrimaryKey({ "Key" }).SetSchema(schema).SetColumnFamilies(families); + testHelper.CreateTable(testTable, EStatus::GENERIC_ERROR); + } + + Y_UNIT_TEST(DropColumnAndResetTtl) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(runnerSettings); + + TVector schema = { + TTestHelper::TColumnSchema().SetName("id").SetType(NScheme::NTypeIds::Int32).SetNullable(false), + TTestHelper::TColumnSchema().SetName("timestamp").SetType(NScheme::NTypeIds::Timestamp).SetNullable(false) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName("/Root/ColumnTableTest").SetPrimaryKey({"id"}).SetSharding({"id"}).SetSchema(schema); + testHelper.CreateTable(testTable); + + { + auto alterQuery = TStringBuilder() << R"( + --!syntax_v1 + ALTER OBJECT `)" << testTable.GetName() << R"(` (TYPE TABLE) SET (ACTION=UPSERT_INDEX, + NAME=max_pk_int, TYPE=MAX, FEATURES=`{\"column_name\": \"timestamp\"}`))"; + auto alterResult = testHelper.GetSession().ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), EStatus::SUCCESS, alterResult.GetIssues().ToString()); + } + + { + auto alterQuery = TStringBuilder() << "ALTER TABLE `" << testTable.GetName() << "`SET (TTL = Interval(\"PT1H\") ON timestamp);"; + auto alterResult = testHelper.GetSession().ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), EStatus::SUCCESS, alterResult.GetIssues().ToString()); + } + + { + auto alterQuery = TStringBuilder() << "ALTER TABLE `" << testTable.GetName() << "` DROP COLUMN timestamp, RESET (TTL);"; + auto alterResult = testHelper.GetSession().ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), EStatus::SUCCESS, alterResult.GetIssues().ToString()); + } + } + + Y_UNIT_TEST(InitTtlSettingsOnShardStart) { + TKikimrSettings runnerSettings; + runnerSettings.WithSampleTables = false; + TTestHelper testHelper(runnerSettings); + + TVector schema = { + TTestHelper::TColumnSchema().SetName("id").SetType(NScheme::NTypeIds::Int32).SetNullable(false), + TTestHelper::TColumnSchema().SetName("timestamp").SetType(NScheme::NTypeIds::Timestamp).SetNullable(false) + }; + + TTestHelper::TColumnTable testTable; + testTable.SetName("/Root/ColumnTableTest").SetPrimaryKey({"id"}).SetSharding({"id"}).SetSchema(schema); + testHelper.CreateTable(testTable); + + { + auto alterQuery = TStringBuilder() << R"( + --!syntax_v1 + ALTER OBJECT `)" << testTable.GetName() << R"(` (TYPE TABLE) SET (ACTION=UPSERT_INDEX, + NAME=max_pk_int, TYPE=MAX, FEATURES=`{\"column_name\": \"timestamp\"}`))"; + auto alterResult = testHelper.GetSession().ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), EStatus::SUCCESS, alterResult.GetIssues().ToString()); + } + + { + auto alterQuery = TStringBuilder() << "ALTER TABLE `" << testTable.GetName() << "`SET (TTL = Interval(\"PT1H\") ON timestamp);"; + auto alterResult = testHelper.GetSession().ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), EStatus::SUCCESS, alterResult.GetIssues().ToString()); + } + + { + auto alterQuery = TStringBuilder() << "ALTER TABLE `" << testTable.GetName() << "` RESET (TTL);"; + auto alterResult = testHelper.GetSession().ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), EStatus::SUCCESS, alterResult.GetIssues().ToString()); + } + + { + auto alterQuery = TStringBuilder() << "ALTER TABLE `" << testTable.GetName() << "` DROP COLUMN timestamp;"; + auto alterResult = testHelper.GetSession().ExecuteSchemeQuery(alterQuery).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(alterResult.GetStatus(), EStatus::SUCCESS, alterResult.GetIssues().ToString()); + } + + testHelper.RebootTablets("/Root/ColumnTableTest"); + } + } Y_UNIT_TEST_SUITE(KqpOlapTypes) { @@ -8247,7 +9846,7 @@ Y_UNIT_TEST_SUITE(KqpOlapTypes) { testHelper.CreateTable(testTable); std::string jsonString = R"({"col1": "val1", "obj": {"obj_col2_int": 16}})"; auto maybeJsonDoc = NBinaryJson::SerializeToBinaryJson(jsonString); - Y_ABORT_UNLESS(maybeJsonDoc.Defined()); + Y_ABORT_UNLESS(maybeJsonDoc.IsSuccess()); const std::string jsonBin(maybeJsonDoc->Data(), maybeJsonDoc->Size()); { TTestHelper::TUpdatesBuilder tableInserter(testTable.GetArrowSchema(schema)); diff --git a/ydb/core/protos/config.proto b/ydb/core/protos/config.proto index a2384c961cc4..bd66f8fb984f 100644 --- a/ydb/core/protos/config.proto +++ b/ydb/core/protos/config.proto @@ -592,6 +592,11 @@ message TConveyorConfig { optional double WorkersCountDouble = 5; } +message TPrioritiesQueueConfig { + optional bool Enabled = 1 [default = true]; + optional uint32 Limit = 2 [default = 32]; +} + message TLimiterConfig { optional bool Enabled = 1 [default = true]; optional uint64 Limit = 2; @@ -601,6 +606,7 @@ message TLimiterConfig { message TGroupedMemoryLimiterConfig { optional bool Enabled = 1 [default = true]; optional uint64 MemoryLimit = 2; + optional uint64 HardMemoryLimit = 3; } message TExternalIndexConfig { @@ -1603,6 +1609,7 @@ message TColumnShardConfig { message TRepairInfo { optional string ClassName = 1; optional string Description = 2; + optional bool DryRun = 3; } repeated TRepairInfo Repairs = 15; @@ -1614,6 +1621,14 @@ message TColumnShardConfig { optional uint32 LagForCompactionBeforeTieringsMs = 22 [default = 3600000]; optional uint32 OptimizerFreshnessCheckDurationMs = 23 [default = 300000]; optional uint32 SmallPortionDetectSizeLimit = 24 [default = 1048576]; // 1 << 20 + optional bool ColumnChunksV0Usage = 25 [default = true]; + optional bool ColumnChunksV1Usage = 26 [default = true]; + optional uint64 MemoryLimitScanPortion = 27 [default = 100000000]; + optional string ReaderClassName = 28; + optional bool AllowNullableColumnsInPK = 29 [default = false]; + optional uint32 RestoreDataOnWriteTimeoutSeconds = 30; + optional bool UseSlicesFilter = 31 [default = true]; + optional uint32 LimitForPortionsMetadataAsk = 32 [default = 1000]; } message TSchemeShardConfig { @@ -2002,10 +2017,11 @@ message TAppConfig { optional TBlobCacheConfig BlobCacheConfig = 78; optional TLimiterConfig CompDiskLimiterConfig = 79; optional TMetadataCacheConfig MetadataCacheConfig = 80; - //optional TMemoryControllerConfig MemoryControllerConfig = 81; NB. exist in main - optional TGroupedMemoryLimiterConfig GroupedMemoryLimiterConfig = 82; + //optional TMemoryControllerConfig MemoryControllerConfig = 81; + optional TGroupedMemoryLimiterConfig GroupedMemoryLimiterConfig = 82; optional NKikimrReplication.TReplicationDefaults ReplicationConfig = 83; optional TShutdownConfig ShutdownConfig = 84; + optional TPrioritiesQueueConfig CompPrioritiesConfig = 85; repeated TNamedConfig NamedConfigs = 100; optional string ClusterYamlConfig = 101; diff --git a/ydb/core/protos/console_config.proto b/ydb/core/protos/console_config.proto index b5309cba1be3..b161c98de09b 100644 --- a/ydb/core/protos/console_config.proto +++ b/ydb/core/protos/console_config.proto @@ -139,7 +139,10 @@ message TConfigItem { S3ProxyResolverConfigItem = 76; BackgroundCleaningConfigItem = 77; MetadataCacheConfigItem = 80; + MemoryControllerConfigItem = 81; + GroupedMemoryLimiterConfig = 82; ReplicationConfigItem = 83; + CompPrioritiesConfig = 85; NamedConfigsItem = 100; ClusterYamlConfigItem = 101; diff --git a/ydb/core/protos/counters_columnshard.proto b/ydb/core/protos/counters_columnshard.proto index 898dac98aad6..327a8c999f5f 100644 --- a/ydb/core/protos/counters_columnshard.proto +++ b/ydb/core/protos/counters_columnshard.proto @@ -201,4 +201,8 @@ enum ETxTypes { TXTYPE_GC_START = 34 [(TxTypeOpts) = {Name: "TxGarbageCollectionStart"}]; TXTYPE_APPLY_NORMALIZER = 35 [(TxTypeOpts) = {Name: "TxApplyNormalizer"}]; TXTYPE_START_INTERNAL_SCAN = 36 [(TxTypeOpts) = {Name: "TxStartInternalScan"}]; + TXTYPE_DATA_SHARING_START_SOURCE_CURSOR = 37 [(TxTypeOpts) = {Name: "TxDataSharingStartSourceCursor"}]; + TXTYPE_ASK_PORTION_METADATA = 38 [(TxTypeOpts) = {Name: "TxAskPortionMetadata"}]; + TXTYPE_WRITE_PORTIONS_FINISHED = 39 [(TxTypeOpts) = {Name: "TxWritePortionsFinished"}]; + TXTYPE_WRITE_PORTIONS_FAILED = 40 [(TxTypeOpts) = {Name: "TxWritePortionsFailed"}]; } diff --git a/ydb/core/protos/data_events.proto b/ydb/core/protos/data_events.proto index 1f0edb1d24ce..804665fd1211 100644 --- a/ydb/core/protos/data_events.proto +++ b/ydb/core/protos/data_events.proto @@ -129,6 +129,7 @@ message TEvWriteResult { STATUS_BAD_REQUEST = 7; STATUS_SCHEME_CHANGED = 8; STATUS_LOCKS_BROKEN = 9; + STATUS_DISK_SPACE_EXHAUSTED = 10; } // Status diff --git a/ydb/core/protos/feature_flags.proto b/ydb/core/protos/feature_flags.proto index 714c426445da..451da4bcd7a4 100644 --- a/ydb/core/protos/feature_flags.proto +++ b/ydb/core/protos/feature_flags.proto @@ -157,8 +157,10 @@ message TFeatureFlags { optional bool EnableExternalDataSourcesOnServerless = 143 [default = true]; optional bool EnableSparsedColumns = 144 [default = false]; optional bool EnableParameterizedDecimal = 145 [default = false]; - optional bool EnableImmediateWritingOnBulkUpsert = 146 [default = false]; + optional bool EnableImmediateWritingOnBulkUpsert = 146 [default = true]; optional bool EnableInsertWriteIdSpecialColumnCompatibility = 147 [default = false]; + optional bool EnableTopicAutopartitioningForCDC = 148 [default = false]; + optional bool EnableWritePortionsOnInsert = 149 [default = false]; optional bool EnableDriveSerialsDiscovery = 152 [default = false]; optional bool EnableScaleRecommender = 157 [default = false]; optional bool DisableLocalDBEraseCache = 161 [default = false]; diff --git a/ydb/core/protos/flat_scheme_op.proto b/ydb/core/protos/flat_scheme_op.proto index a5652056a667..289822c5e4b3 100644 --- a/ydb/core/protos/flat_scheme_op.proto +++ b/ydb/core/protos/flat_scheme_op.proto @@ -126,6 +126,7 @@ message TFamilyDescription { optional EColumnCache ColumnCache = 7; optional EColumnStorage Storage = 8; // DEPRECATED: use StorageConfig optional TStorageConfig StorageConfig = 9; + optional int32 ColumnCodecLevel = 10; } enum ECompactionStrategy { @@ -324,11 +325,24 @@ message TTTLSettings { optional uint32 MaxShardsInFlight = 6 [default = 0]; // zero means no limit } + message TEvictionToExternalStorageSettings { + optional string Storage = 1; + } + + message TTier { + optional uint32 ApplyAfterSeconds = 1; + oneof Action { + google.protobuf.Empty Delete = 2; + TEvictionToExternalStorageSettings EvictToExternalStorage = 3; + } + } + message TEnabled { optional string ColumnName = 1; - optional uint32 ExpireAfterSeconds = 2; + optional uint32 ExpireAfterSeconds = 2 [deprecated = true]; optional EUnit ColumnUnit = 3; optional TSysSettings SysSettings = 4; + repeated TTier Tiers = 5; } message TDisabled { @@ -339,7 +353,7 @@ message TTTLSettings { TDisabled Disabled = 2; } - optional string UseTiering = 3; + reserved 3; } message TTableReplicationConfig { @@ -425,6 +439,7 @@ message TOlapColumnDiff { optional string StorageId = 6; optional string DefaultValue = 7; optional NKikimrArrowAccessorProto.TRequestedConstructor DataAccessorConstructor = 8; + optional string ColumnFamilyName = 9; } message TOlapColumnDescription { @@ -445,6 +460,8 @@ message TOlapColumnDescription { optional string StorageId = 11; optional NKikimrColumnShardColumnDefaults.TColumnDefault DefaultValue = 12; optional NKikimrArrowAccessorProto.TConstructor DataAccessorConstructor = 13; + optional uint32 ColumnFamilyId = 14; + optional string ColumnFamilyName = 15; } message TRequestedBloomFilter { @@ -452,6 +469,14 @@ message TRequestedBloomFilter { repeated string ColumnNames = 3; } +message TRequestedBloomNGrammFilter { + optional uint32 NGrammSize = 1; + optional uint32 FilterSizeBytes = 2; + optional uint32 HashesCount = 3; + optional string ColumnName = 4; + optional uint32 RecordsCount = 5; +} + message TRequestedMaxIndex { optional string ColumnName = 1; } @@ -471,6 +496,7 @@ message TOlapIndexRequested { TRequestedBloomFilter BloomFilter = 40; TRequestedMaxIndex MaxIndex = 41; TRequestedCountMinSketch CountMinSketch = 42; + TRequestedBloomNGrammFilter BloomNGrammFilter = 43; } } @@ -480,6 +506,14 @@ message TBloomFilter { repeated uint32 ColumnIds = 3; } +message TBloomNGrammFilter { + optional uint32 NGrammSize = 1; + optional uint32 FilterSizeBytes = 2; + optional uint32 HashesCount = 3; + optional uint32 ColumnId = 4; + optional uint32 RecordsCount = 5; +} + message TMaxIndex { optional uint32 ColumnId = 1; } @@ -502,6 +536,7 @@ message TOlapIndexDescription { TBloomFilter BloomFilter = 41; TMaxIndex MaxIndex = 42; TCountMinSketch CountMinSketch = 43; + TBloomNGrammFilter BloomNGrammFilter = 44; } } @@ -521,6 +556,20 @@ message TStorageTierConfig { optional TCompressionOptions Compression = 3; } +message TCompactionLevelConstructorContainer { + optional string ClassName = 1; + + message TZeroLevel { + optional uint32 PortionsLiveDurationSeconds = 1; + optional uint64 ExpectedBlobsSize = 2; + } + + oneof Implementation { + TZeroLevel ZeroLevel = 10; + } + +} + message TCompactionPlannerConstructorContainer { optional string ClassName = 1; @@ -533,16 +582,39 @@ message TCompactionPlannerConstructorContainer { optional uint32 FreshnessCheckDurationSeconds = 2 [default = 300]; } + message TLCOptimizer { + repeated TCompactionLevelConstructorContainer Levels = 1; + } + oneof Implementation { TLOptimizer LBuckets = 20; TSOptimizer SBuckets = 21; + TLCOptimizer LCBuckets = 22; + } +} + +message TMetadataManagerConstructorContainer { + optional string ClassName = 1; + + message TInMem { + } + + message TLocalDB { + optional uint64 MemoryCacheSize = 1 [default = 128000000]; + optional bool FetchOnStart = 2 [default = false]; + } + + oneof Implementation { + TInMem InMem = 20; + TLocalDB LocalDB = 21; } } message TColumnTableSchemeOptions { optional bool SchemeNeedActualization = 1 [default = false]; - optional bool ExternalGuaranteeExclusivePK = 2 [default = false]; optional TCompactionPlannerConstructorContainer CompactionPlannerConstructor = 3; + optional TMetadataManagerConstructorContainer MetadataManagerConstructor = 4; + optional string ScanReaderPolicyName = 5; } message TColumnTableSchema { @@ -550,9 +622,6 @@ message TColumnTableSchema { repeated TOlapColumnDescription Columns = 1; repeated string KeyColumnNames = 2; - // A type of engine used by the table - optional EColumnTableEngine Engine = 3; - // Internal fields optional uint32 NextColumnId = 4; @@ -563,15 +632,33 @@ message TColumnTableSchema { //optional int32 DefaultCompressionLevel = 7; // deprecated, not used before replace optional TCompressionOptions DefaultCompression = 8; - optional bool CompositeMarksDeprecated = 9 [ default = false ]; repeated TOlapIndexDescription Indexes = 10; optional TColumnTableSchemeOptions Options = 12; + + repeated TFamilyDescription ColumnFamilies = 13; + // Internal fields + optional uint32 NextColumnFamilyId = 14; +} + +message TColumnTableSchemaDiff { + optional uint64 Version = 1; + + repeated TOlapColumnDescription UpsertColumns = 2; + repeated uint32 DropColumns = 3; + + optional TCompressionOptions DefaultCompression = 4; + + repeated TOlapIndexDescription UpsertIndexes = 5; + repeated uint32 DropIndexes = 6; + + optional TColumnTableSchemeOptions Options = 7; } message TColumnTableRequestedOptions { optional bool SchemeNeedActualization = 1 [default = false]; - optional bool ExternalGuaranteeExclusivePK = 2; optional TCompactionPlannerConstructorContainer CompactionPlannerConstructor = 3; + optional TMetadataManagerConstructorContainer MetadataManagerConstructor = 4; + optional string ScanReaderPolicyName = 5; } message TAlterColumnTableSchema { @@ -582,6 +669,8 @@ message TAlterColumnTableSchema { repeated TOlapIndexRequested UpsertIndexes = 8; repeated string DropIndexes = 9; optional TColumnTableRequestedOptions Options = 12; + repeated TFamilyDescription AddColumnFamily = 13; + repeated TFamilyDescription AlterColumnFamily = 14; } // Schema presets are used to manage multiple tables with the same schema @@ -615,10 +704,11 @@ message TColumnDataLifeCycle { message TTtl { optional string ColumnName = 1; oneof Expire { - uint32 ExpireAfterSeconds = 2; + uint32 ExpireAfterSeconds = 2 [deprecated = true]; // ignored if Tiers are not empty uint64 ExpireAfterBytes = 4; } optional TTTLSettings.EUnit ColumnUnit = 3; + repeated TTTLSettings.TTier Tiers = 5; } message TDisabled { @@ -632,7 +722,7 @@ message TColumnDataLifeCycle { // Incremented on each settings change optional uint64 Version = 3 [default = 1]; - optional string UseTiering = 5; + reserved 5; } message TColumnTableTtlSettingsPreset { diff --git a/ydb/core/protos/kqp.proto b/ydb/core/protos/kqp.proto index c2ccb1bd759f..932f5b669e05 100644 --- a/ydb/core/protos/kqp.proto +++ b/ydb/core/protos/kqp.proto @@ -680,6 +680,19 @@ message TEvScanError { optional uint64 TabletId = 4; } +message TEvKqpScanCursor { + message TColumnShardScanPlain { + } + message TColumnShardScanSimple { + optional uint64 SourceId = 1; + optional uint32 StartRecordIndex = 2; + } + oneof Implementation { + TColumnShardScanPlain ColumnShardPlain = 10; + TColumnShardScanSimple ColumnShardSimple = 11; + } +} + message TEvRemoteScanData { optional uint32 ScanId = 1; optional uint64 CpuTimeUs = 2; @@ -703,6 +716,7 @@ message TEvRemoteScanData { optional bool RequestedBytesLimitReached = 11 [default = false]; optional uint32 AvailablePacks = 12; + optional TEvKqpScanCursor LastCursor = 13; } message TEvRemoteScanDataAck { diff --git a/ydb/core/protos/ssa.proto b/ydb/core/protos/ssa.proto deleted file mode 100644 index 5ffbf067b33d..000000000000 --- a/ydb/core/protos/ssa.proto +++ /dev/null @@ -1,209 +0,0 @@ -package NKikimrSSA; -option java_package = "ru.yandex.kikimr.proto"; - -// Program to pushdown to ColumnShard -// -// > 'SELECT y, z WHERE x > 10' -// PROJECTION x, y, z -// ASSIGN tmp = x > 10 -// FILTER BY tmp -// PROJECTION y, z -// -// > 'SELECT min(x), sum(y) GROUP BY z' -// PROJECTION x, y, z -// ASSIGN agg1 = min(x) -// ASSIGN agg2 = sum(y) -// GROUP BY z -// PROJECTION agg1, agg2 -// -message TProgram { - message TColumn { - optional uint64 Id = 1; - optional string Name = 2; - } - - message TConstant { - oneof value { - bool Bool = 1; - int32 Int32 = 2; - uint32 Uint32 = 3; - int64 Int64 = 4; - uint64 Uint64 = 5; - float Float = 6; - double Double = 7; - bytes Bytes = 8; - string Text = 9; - int32 Int8 = 10; - uint32 Uint8 = 11; - int32 Int16 = 12; - uint32 Uint16 = 13; - uint64 Timestamp = 14; - } - } - - message TBloomFilterChecker { - repeated uint64 HashValues = 1; - } - - message TOlapIndexChecker { - optional uint32 IndexId = 1; - optional string ClassName = 2; - - message TCompositeChecker { - repeated TOlapIndexChecker ChildrenCheckers = 1; - } - - oneof Implementation { - TBloomFilterChecker BloomFilter = 40; - TCompositeChecker Composite = 41; - } - } - - message TParameter { - optional string Name = 1; - } - - enum EFunctionType { - SIMPLE_ARROW = 1; - YQL_KERNEL = 2; - } - - message TAssignment { - enum EFunction { - FUNC_UNSPECIFIED = 0; - FUNC_CMP_EQUAL = 1; - FUNC_CMP_NOT_EQUAL = 2; - FUNC_CMP_LESS = 3; - FUNC_CMP_LESS_EQUAL = 4; - FUNC_CMP_GREATER = 5; - FUNC_CMP_GREATER_EQUAL = 6; - FUNC_IS_NULL = 7; - FUNC_STR_LENGTH = 8; - FUNC_STR_MATCH = 9; - FUNC_BINARY_NOT = 10; - FUNC_BINARY_AND = 11; - FUNC_BINARY_OR = 12; - FUNC_BINARY_XOR = 13; - FUNC_MATH_ADD = 14; - FUNC_MATH_SUBTRACT = 15; - FUNC_MATH_MULTIPLY = 16; - FUNC_MATH_DIVIDE = 17; - FUNC_CAST_TO_BOOLEAN = 18; - FUNC_CAST_TO_INT8 = 19; - FUNC_CAST_TO_INT16 = 20; - FUNC_CAST_TO_INT32 = 21; - FUNC_CAST_TO_INT64 = 22; - FUNC_CAST_TO_UINT8 = 23; - FUNC_CAST_TO_UINT16 = 24; - FUNC_CAST_TO_UINT32 = 25; - FUNC_CAST_TO_UINT64 = 26; - FUNC_CAST_TO_FLOAT = 27; - FUNC_CAST_TO_DOUBLE = 28; - FUNC_CAST_TO_BINARY = 29; - FUNC_CAST_TO_FIXED_SIZE_BINARY = 30; - FUNC_CAST_TO_TIMESTAMP = 31; - FUNC_STR_MATCH_LIKE = 32; - FUNC_STR_STARTS_WITH = 33; - FUNC_STR_ENDS_WITH = 34; - FUNC_STR_MATCH_IGNORE_CASE = 35; - FUNC_STR_STARTS_WITH_IGNORE_CASE = 36; - FUNC_STR_ENDS_WITH_IGNORE_CASE = 37; - } - - message TFunction { - optional uint32 Id = 1; // EFunction - repeated TColumn Arguments = 2; - optional EFunctionType FunctionType = 3 [ default = SIMPLE_ARROW ]; - optional uint32 KernelIdx = 4; - optional uint32 YqlOperationId = 5; // TKernelRequestBuilder::EBinaryOp - } - - message TExternalFunction { - optional string Name = 1; - repeated TColumn Arguments = 2; - } - - optional TColumn Column = 1; - oneof expression { - TFunction Function = 2; - TExternalFunction ExternalFunction = 3; - TConstant Constant = 4; - bool Null = 5; - TParameter Parameter = 6; - } - } - - message TAggregateAssignment { - enum EAggregateFunction { - AGG_UNSPECIFIED = 0; - AGG_SOME = 1; - AGG_COUNT = 2; - AGG_MIN = 3; - AGG_MAX = 4; - AGG_SUM = 5; - //AGG_AVG = 6; - //AGG_VAR = 7; - //AGG_COVAR = 8; - //AGG_STDDEV = 9; - //AGG_CORR = 10; - //AGG_ARG_MIN = 11; - //AGG_ARG_MAX = 12; - //AGG_COUNT_DISTINCT = 13; - //AGG_QUANTILES = 14; - //AGG_TOP_COUNT = 15; - //AGG_TOP_SUM = 16; - } - - message TAggregateFunction { - optional uint32 Id = 1; // EAggregateFunction - repeated TColumn Arguments = 2; - optional string Variant = 3; // i.e. POP/SAMP for AGG_VAR, AGG_COVAR, AGG_STDDEV - optional EFunctionType FunctionType = 4 [ default = SIMPLE_ARROW ]; - optional uint32 KernelIdx = 5; - // TODO: Parameters, i.e. N for topK(N)(arg) - } - - optional TColumn Column = 1; - optional TAggregateFunction Function = 2; - } - - message TProjection { - repeated TColumn Columns = 1; - } - - message TFilter { - // Predicate should be a bool column: - // true - keep the row - // false - remove the row - optional TColumn Predicate = 1; - } - - message TGroupBy { - repeated TAggregateAssignment Aggregates = 1; - repeated TColumn KeyColumns = 2; - } - - message TCommand { - oneof line { - TAssignment Assign = 1; - TProjection Projection = 2; - TFilter Filter = 3; - TGroupBy GroupBy = 4; - // TODO: ORDER BY, LIMIT - } - } - - repeated TCommand Command = 1; - optional uint32 Version = 2; - optional bytes Kernels = 3; -} - -message TOlapProgram { - // Store OLAP program in serialized format in case we do not need to deserialize it in TScanTaskMeta - // Note: when this message exists the program must be present. - optional bytes Program = 1; - // RecordBatch deserialization require arrow::Schema, thus store it here - optional bytes ParametersSchema = 2; - optional bytes Parameters = 3; - optional TProgram.TOlapIndexChecker IndexChecker = 4; -} diff --git a/ydb/core/protos/tx_columnshard.proto b/ydb/core/protos/tx_columnshard.proto index 9cd849fd61be..ff6b8f5087a9 100644 --- a/ydb/core/protos/tx_columnshard.proto +++ b/ydb/core/protos/tx_columnshard.proto @@ -237,6 +237,7 @@ message TSchemaPresetVersionInfo { optional uint64 SinceStep = 2; optional uint64 SinceTxId = 3; optional NKikimrSchemeOp.TColumnTableSchema Schema = 4; + optional NKikimrSchemeOp.TColumnTableSchemaDiff Diff = 5; } message TTtlSettingsPresetVersionInfo { @@ -250,7 +251,6 @@ message TTableVersionInfo { optional uint64 PathId = 1; optional uint64 SinceStep = 2; optional uint64 SinceTxId = 3; - optional NKikimrSchemeOp.TColumnTableSchema Schema = 4; optional uint32 SchemaPresetId = 5; optional NKikimrSchemeOp.TColumnDataLifeCycle TtlSettings = 6; optional uint32 TtlSettingsPresetId = 7; @@ -353,4 +353,6 @@ message TEvReadBlobRangesResult { message TInternalOperationData { repeated uint64 InternalWriteIds = 1; optional uint32 ModificationType = 2; + optional uint64 PathId = 3; + optional bool WritePortions = 4; } diff --git a/ydb/core/protos/tx_datashard.proto b/ydb/core/protos/tx_datashard.proto index 682b2373f736..78280a6ae414 100644 --- a/ydb/core/protos/tx_datashard.proto +++ b/ydb/core/protos/tx_datashard.proto @@ -1525,6 +1525,8 @@ message TEvKqpScan { optional TComputeShardingPolicy ComputeShardingPolicy = 23; optional uint64 LockTxId = 24; optional uint32 LockNodeId = 25; + optional string CSScanPolicy = 26; + optional NKikimrKqp.TEvKqpScanCursor ScanCursor = 27; } message TEvCompactTable { diff --git a/ydb/core/sys_view/common/schema.h b/ydb/core/sys_view/common/schema.h index 81542a645969..24a104bb9682 100644 --- a/ydb/core/sys_view/common/schema.h +++ b/ydb/core/sys_view/common/schema.h @@ -529,6 +529,8 @@ struct Schema : NIceDb::Schema { struct TierName: Column<11, NScheme::NTypeIds::Utf8> {}; struct Stats: Column<12, NScheme::NTypeIds::Utf8> {}; struct Optimized: Column<13, NScheme::NTypeIds::Uint8> {}; + struct CompactionLevel: Column<14, NScheme::NTypeIds::Uint64> {}; + struct Details: Column<15, NScheme::NTypeIds::Utf8> {}; using TKey = TableKey; using TColumns = TableColumns< @@ -544,7 +546,9 @@ struct Schema : NIceDb::Schema { Activity, TierName, Stats, - Optimized + Optimized, + CompactionLevel, + Details >; }; diff --git a/ydb/core/testlib/common_helper.cpp b/ydb/core/testlib/common_helper.cpp index 8e92ccd15c1b..0f329d951717 100644 --- a/ydb/core/testlib/common_helper.cpp +++ b/ydb/core/testlib/common_helper.cpp @@ -53,7 +53,7 @@ void THelper::WaitForSchemeOperation(TActorId sender, ui64 txId) { void THelper::StartScanRequest(const TString& request, const bool expectSuccess, TVector>* result) const { NYdb::NTable::TTableClient tClient(Server.GetDriver(), - NYdb::NTable::TClientSettings().UseQueryCache(false).AuthToken("root@builtin")); + NYdb::NTable::TClientSettings().UseQueryCache(false).AuthToken(AuthToken)); auto expectation = expectSuccess; bool resultReady = false; TVector> rows; @@ -109,7 +109,7 @@ void THelper::StartScanRequest(const TString& request, const bool expectSuccess, void THelper::StartDataRequest(const TString& request, const bool expectSuccess, TString* result) const { NYdb::NTable::TTableClient tClient(Server.GetDriver(), - NYdb::NTable::TClientSettings().UseQueryCache(false).AuthToken("root@builtin")); + NYdb::NTable::TClientSettings().UseQueryCache(false).AuthToken(AuthToken)); auto expectation = expectSuccess; bool resultReady = false; bool* rrPtr = &resultReady; @@ -144,7 +144,7 @@ void THelper::StartDataRequest(const TString& request, const bool expectSuccess, void THelper::StartSchemaRequestTableServiceImpl(const TString& request, const bool expectation, const bool waiting) const { NYdb::NTable::TTableClient tClient(Server.GetDriver(), - NYdb::NTable::TClientSettings().UseQueryCache(false).AuthToken("root@builtin")); + NYdb::NTable::TClientSettings().UseQueryCache(false).AuthToken(AuthToken)); std::shared_ptr rrPtr = std::make_shared(false); tClient.CreateSession().Subscribe([rrPtr, request, expectation](NThreading::TFuture f) { @@ -171,7 +171,7 @@ void THelper::StartSchemaRequestTableServiceImpl(const TString& request, const b void THelper::StartSchemaRequestQueryServiceImpl(const TString& request, const bool expectation, const bool waiting) const { NYdb::NQuery::TQueryClient qClient(Server.GetDriver(), - NYdb::NQuery::TClientSettings().AuthToken("root@builtin")); + NYdb::NQuery::TClientSettings().AuthToken(AuthToken)); std::shared_ptr rrPtr = std::make_shared(false); auto future = qClient.ExecuteQuery(request, NYdb::NQuery::TTxControl::NoTx()); diff --git a/ydb/core/testlib/common_helper.h b/ydb/core/testlib/common_helper.h index 76b3eaf67938..cf1c1d1351d8 100644 --- a/ydb/core/testlib/common_helper.h +++ b/ydb/core/testlib/common_helper.h @@ -54,6 +54,10 @@ class TLoggerInit { }; class THelper { +private: + inline static const TString DefaultAuthToken = "root@builtin"; + YDB_ACCESSOR(TString, AuthToken, DefaultAuthToken); + protected: void WaitForSchemeOperation(TActorId sender, ui64 txId); void PrintResultSet(const NYdb::TResultSet& resultSet, NYson::TYsonWriter& writer) const; @@ -73,6 +77,10 @@ class THelper { UseQueryService = use; } + void ResetAuthToken() { + AuthToken = DefaultAuthToken; + } + void DropTable(const TString& tablePath); void StartScanRequest(const TString& request, const bool expectSuccess, TVector>* result) const; diff --git a/ydb/core/testlib/cs_helper.cpp b/ydb/core/testlib/cs_helper.cpp index dd26da35fa74..20ae84dba14a 100644 --- a/ydb/core/testlib/cs_helper.cpp +++ b/ydb/core/testlib/cs_helper.cpp @@ -193,7 +193,6 @@ TString THelper::GetTestTableSchema() const { sb << R"( KeyColumnNames: "timestamp" KeyColumnNames: "uid" - Engine : COLUMN_ENGINE_REPLACING_TIMESERIES )"; return sb; } @@ -230,6 +229,32 @@ void THelper::CreateOlapTablesWithStore(TVector tableNames /*= {"olapTa CreateSchemaOlapTablesWithStore(GetTestTableSchema(), tableNames, storeName, storeShardsCount, tableShardsCount); } +void THelper::CreateSchemaOlapTables(const TString tableSchema, TVector tableNames, ui32 tableShardsCount) { + TActorId sender = Server.GetRuntime()->AllocateEdgeActor(); + + const TString shardingColumns = "[\"" + JoinSeq("\",\"", GetShardingColumns()) + "\"]"; + + for (const TString& tableName : tableNames) { + TBase::CreateTestOlapTable(sender, "", Sprintf(R"( + Name: "%s" + ColumnShardCount: %d + Sharding { + HashSharding { + Function: %s + Columns: %s + } + } + Schema { + %s + } + )", tableName.c_str(), tableShardsCount, ShardingMethod.data(), shardingColumns.c_str(), tableSchema.data())); + } +} + +void THelper::CreateOlapTables(TVector tableNames /*= {"olapTable"}*/, ui32 tableShardsCount /*= 3*/) { + CreateSchemaOlapTables(GetTestTableSchema(), tableNames, tableShardsCount); +} + // Clickbench table std::shared_ptr TCickBenchHelper::GetArrowSchema() const { diff --git a/ydb/core/testlib/cs_helper.h b/ydb/core/testlib/cs_helper.h index 95c8877b6ba6..b44a5188e3b6 100644 --- a/ydb/core/testlib/cs_helper.h +++ b/ydb/core/testlib/cs_helper.h @@ -15,7 +15,7 @@ class THelperSchemaless : public NCommon::THelper { void CreateTestOlapStore(TActorId sender, TString scheme); void CreateTestOlapTable(TActorId sender, TString storeOrDirName, TString scheme); void SendDataViaActorSystem(TString testTable, ui64 pathIdBegin, ui64 tsBegin, size_t rowCount, const ui32 tsStepUs = 1) const; - void SendDataViaActorSystem(TString testTable, std::shared_ptr batch, const Ydb::StatusIds_StatusCode& expectedStatus = Ydb::StatusIds::SUCCESS) const; + void SendDataViaActorSystem(TString testTable, std::shared_ptr batch, const Ydb::StatusIds_StatusCode& expectedStatus = Ydb::StatusIds::SUCCESS) const; virtual std::shared_ptr TestArrowBatch(ui64 pathIdBegin, ui64 tsBegin, size_t rowCount, const ui32 tsStepUs = 1) const = 0; }; @@ -37,6 +37,10 @@ class THelper: public THelperSchemaless { void CreateOlapTablesWithStore(TVector tableName = {"olapTable"}, TString storeName = "olapStore", ui32 storeShardsCount = 4, ui32 tableShardsCount = 3); + void CreateSchemaOlapTables(const TString tableSchema, TVector tableNames = {"olapTable"}, + ui32 tableShardsCount = 3); + void CreateOlapTables(TVector tableName = {"olapTable"}, ui32 tableShardsCount = 3); + public: using TBase::TBase; @@ -53,7 +57,6 @@ class THelper: public THelperSchemaless { Columns { Name: "level" Type: "Int32" DataAccessorConstructor{ ClassName: "SPARSED" }} Columns { Name: "message" Type: "Utf8" } KeyColumnNames: "timestamp" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES )"; void WithSomeNulls() { diff --git a/ydb/core/testlib/test_client.cpp b/ydb/core/testlib/test_client.cpp index fbc0ed46e7db..1dbf1b31f44c 100644 --- a/ydb/core/testlib/test_client.cpp +++ b/ydb/core/testlib/test_client.cpp @@ -113,6 +113,7 @@ #include #include #include +#include #include #include @@ -773,6 +774,11 @@ namespace Tests { const auto aid = Runtime->Register(actor, nodeIdx, appData.UserPoolId, TMailboxType::Revolving, 0); Runtime->RegisterService(NOlap::NGroupedMemoryManager::TScanMemoryLimiterOperator::MakeServiceId(Runtime->GetNodeId(nodeIdx)), aid, nodeIdx); } + { + auto* actor = NPrioritiesQueue::TCompServiceOperator::CreateService(NPrioritiesQueue::TConfig(), new ::NMonitoring::TDynamicCounters()); + const auto aid = Runtime->Register(actor, nodeIdx, appData.UserPoolId, TMailboxType::Revolving, 0); + Runtime->RegisterService(NPrioritiesQueue::TCompServiceOperator::MakeServiceId(Runtime->GetNodeId(nodeIdx)), aid, nodeIdx); + } { auto* actor = NConveyor::TScanServiceOperator::CreateService(NConveyor::TConfig(), new ::NMonitoring::TDynamicCounters()); const auto aid = Runtime->Register(actor, nodeIdx, appData.UserPoolId, TMailboxType::Revolving, 0); diff --git a/ydb/core/testlib/test_client.h b/ydb/core/testlib/test_client.h index 68b878f4de04..3e6762c2c100 100644 --- a/ydb/core/testlib/test_client.h +++ b/ydb/core/testlib/test_client.h @@ -252,6 +252,7 @@ namespace Tests { AppConfig->MutableHiveConfig()->SetObjectImbalanceToBalance(100); AppConfig->MutableColumnShardConfig()->SetDisabledOnSchemeShard(false); FeatureFlags.SetEnableSeparationComputeActorsFromRead(true); + FeatureFlags.SetEnableWritePortionsOnInsert(true); } TServerSettings(const TServerSettings& settings) = default; diff --git a/ydb/core/testlib/ya.make b/ydb/core/testlib/ya.make index 5a63f36a639f..3acfb09d3c6e 100644 --- a/ydb/core/testlib/ya.make +++ b/ydb/core/testlib/ya.make @@ -102,6 +102,7 @@ PEERDIR( ydb/services/ext_index/service ydb/services/ymq ydb/core/tx/conveyor/service + ydb/core/tx/priorities/service ydb/core/tx/limiter/grouped_memory/usage ydb/services/fq ydb/services/kesus diff --git a/ydb/core/tx/columnshard/background_controller.cpp b/ydb/core/tx/columnshard/background_controller.cpp index 7449e7d31ff4..1a26f8ed32f7 100644 --- a/ydb/core/tx/columnshard/background_controller.cpp +++ b/ydb/core/tx/columnshard/background_controller.cpp @@ -4,14 +4,18 @@ namespace NKikimr::NColumnShard { bool TBackgroundController::StartCompaction(const NOlap::TPlanCompactionInfo& info) { - Y_ABORT_UNLESS(ActiveCompactionInfo.emplace(info.GetPathId(), info).second); + auto it = ActiveCompactionInfo.find(info.GetPathId()); + if (it == ActiveCompactionInfo.end()) { + it = ActiveCompactionInfo.emplace(info.GetPathId(), info.GetPathId()).first; + } + it->second.Start(); return true; } void TBackgroundController::CheckDeadlines() { for (auto&& i : ActiveCompactionInfo) { if (TMonotonic::Now() - i.second.GetStartTime() > NOlap::TCompactionLimits::CompactionTimeout) { - AFL_CRIT(NKikimrServices::TX_COLUMNSHARD)("event", "deadline_compaction"); + AFL_CRIT(NKikimrServices::TX_COLUMNSHARD)("event", "deadline_compaction")("path_id", i.first); Y_DEBUG_ABORT_UNLESS(false); } } diff --git a/ydb/core/tx/columnshard/background_controller.h b/ydb/core/tx/columnshard/background_controller.h index b57a29d5b072..817258b2c2fe 100644 --- a/ydb/core/tx/columnshard/background_controller.h +++ b/ydb/core/tx/columnshard/background_controller.h @@ -15,6 +15,7 @@ class TBackgroundController { using TCurrentCompaction = THashMap; TCurrentCompaction ActiveCompactionInfo; + std::optional WaitingCompactionPriority; std::shared_ptr Counters; bool ActiveCleanupPortions = false; @@ -25,21 +26,35 @@ class TBackgroundController { TBackgroundController(std::shared_ptr counters) : Counters(std::move(counters)) { } - THashSet GetConflictTTLPortions() const; THashSet GetConflictCompactionPortions() const; + void UpdateWaitingPriority(const ui64 priority) { + if (!WaitingCompactionPriority || *WaitingCompactionPriority < priority) { + WaitingCompactionPriority = priority; + } + } + + void ResetWaitingPriority() { + WaitingCompactionPriority.reset(); + } + + std::optional GetWaitingPriorityOptional() { + return WaitingCompactionPriority; + } + void CheckDeadlines(); void CheckDeadlinesIndexation(); bool StartCompaction(const NOlap::TPlanCompactionInfo& info); void FinishCompaction(const NOlap::TPlanCompactionInfo& info) { - Y_ABORT_UNLESS(ActiveCompactionInfo.erase(info.GetPathId())); + auto it = ActiveCompactionInfo.find(info.GetPathId()); + AFL_VERIFY(it != ActiveCompactionInfo.end()); + if (it->second.Finish()) { + ActiveCompactionInfo.erase(it); + } Counters->OnCompactionFinish(info.GetPathId()); } - const TCurrentCompaction& GetActiveCompaction() const { - return ActiveCompactionInfo; - } ui32 GetCompactionsCount() const { return ActiveCompactionInfo.size(); } diff --git a/ydb/core/tx/columnshard/blobs_action/abstract/read.h b/ydb/core/tx/columnshard/blobs_action/abstract/read.h index cd2a272feebb..e533ee9cf85e 100644 --- a/ydb/core/tx/columnshard/blobs_action/abstract/read.h +++ b/ydb/core/tx/columnshard/blobs_action/abstract/read.h @@ -72,7 +72,7 @@ class TActionReadBlobs { TString Extract(const TBlobRange& bRange) { auto it = Blobs.find(bRange); - AFL_VERIFY(it != Blobs.end()); + AFL_VERIFY(it != Blobs.end())("range", bRange.ToString()); TString result = it->second; Blobs.erase(it); return result; diff --git a/ydb/core/tx/columnshard/blobs_action/abstract/storage.cpp b/ydb/core/tx/columnshard/blobs_action/abstract/storage.cpp index e24dd299a23b..19d7bfb03156 100644 --- a/ydb/core/tx/columnshard/blobs_action/abstract/storage.cpp +++ b/ydb/core/tx/columnshard/blobs_action/abstract/storage.cpp @@ -1,5 +1,7 @@ #include "storage.h" +#include + namespace NKikimr::NOlap { bool TCommonBlobsTracker::IsBlobInUsage(const NOlap::TUnifiedBlobId& blobId) const { @@ -42,4 +44,8 @@ void IBlobsStorageOperator::Stop() { Stopped = true; } +const NSplitter::TSplitSettings& IBlobsStorageOperator::GetBlobSplitSettings() const { + return NYDBTest::TControllers::GetColumnShardController()->GetBlobSplitSettings(DoGetBlobSplitSettings()); } + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/blobs_action/abstract/storage.h b/ydb/core/tx/columnshard/blobs_action/abstract/storage.h index 3e392a213985..3497599c1ed2 100644 --- a/ydb/core/tx/columnshard/blobs_action/abstract/storage.h +++ b/ydb/core/tx/columnshard/blobs_action/abstract/storage.h @@ -1,16 +1,16 @@ #pragma once +#include "gc.h" +#include "read.h" #include "remove.h" #include "write.h" -#include "read.h" -#include "gc.h" #include -#include #include +#include #include +#include #include -#include namespace NKikimr::NOlap { @@ -18,9 +18,11 @@ class TCommonBlobsTracker: public IBlobInUseTracker { private: // List of blobs that are used by in-flight requests THashMap BlobsUseCount; + protected: virtual bool DoUseBlob(const TUnifiedBlobId& blobId) override; virtual bool DoFreeBlob(const TUnifiedBlobId& blobId) override; + public: virtual bool IsBlobInUsage(const NOlap::TUnifiedBlobId& blobId) const override; virtual void OnBlobFree(const TUnifiedBlobId& blobId) = 0; @@ -34,8 +36,10 @@ class IBlobsStorageOperator { YDB_READONLY(bool, Stopped, false); std::shared_ptr Counters; YDB_ACCESSOR_DEF(std::shared_ptr, SharedBlobs); + protected: - virtual std::shared_ptr DoStartDeclareRemovingAction(const std::shared_ptr& counters) = 0; + virtual std::shared_ptr DoStartDeclareRemovingAction( + const std::shared_ptr& counters) = 0; virtual std::shared_ptr DoStartWritingAction() = 0; virtual std::shared_ptr DoStartReadingAction() = 0; virtual bool DoLoad(IBlobManagerDb& dbBlobs) = 0; @@ -54,6 +58,7 @@ class IBlobsStorageOperator { virtual void DoStartGCAction(const std::shared_ptr& counters) const = 0; void StartGCAction(const std::shared_ptr& action) const { + AFL_VERIFY(IsReady()); return DoStartGCAction(action); } @@ -66,16 +71,13 @@ class IBlobsStorageOperator { IBlobsStorageOperator(const TString& storageId, const std::shared_ptr& sharedBlobs) : SelfTabletId(sharedBlobs->GetSelfTabletId()) , StorageId(storageId) - , SharedBlobs(sharedBlobs) - { + , SharedBlobs(sharedBlobs) { Counters = std::make_shared(storageId); } void Stop(); - const NSplitter::TSplitSettings& GetBlobSplitSettings() const { - return DoGetBlobSplitSettings(); - } + const NSplitter::TSplitSettings& GetBlobSplitSettings() const; virtual TTabletsByBlob GetBlobsToDelete() const = 0; virtual bool HasToDelete(const TUnifiedBlobId& blobId, const TTabletId initiatorTabletId) const = 0; @@ -96,14 +98,17 @@ class IBlobsStorageOperator { } std::shared_ptr StartDeclareRemovingAction(const NBlobOperations::EConsumer consumerId) { + AFL_VERIFY(IsReady()); return DoStartDeclareRemovingAction(Counters->GetConsumerCounter(consumerId)->GetRemoveDeclareCounters()); } std::shared_ptr StartWritingAction(const NBlobOperations::EConsumer consumerId) { + AFL_VERIFY(IsReady()); auto result = DoStartWritingAction(); result->SetCounters(Counters->GetConsumerCounter(consumerId)->GetWriteCounters()); return result; } std::shared_ptr StartReadingAction(const NBlobOperations::EConsumer consumerId) { + AFL_VERIFY(IsReady()); auto result = DoStartReadingAction(); result->SetCounters(Counters->GetConsumerCounter(consumerId)->GetReadCounters()); return result; @@ -116,7 +121,8 @@ class IBlobsStorageOperator { } [[nodiscard]] std::shared_ptr CreateGC() { - NActors::TLogContextGuard gLogging = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD_BLOBS)("storage_id", GetStorageId())("tablet_id", GetSelfTabletId()); + NActors::TLogContextGuard gLogging = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD_BLOBS)( + "storage_id", GetStorageId())("tablet_id", GetSelfTabletId()); if (CurrentGCAction && CurrentGCAction->IsInProgress()) { AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_BLOBS)("event", "gc_in_progress"); return nullptr; @@ -129,6 +135,8 @@ class IBlobsStorageOperator { CurrentGCAction = task; return CurrentGCAction; } + + virtual bool IsReady() const = 0; }; -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/blobs_action/abstract/ya.make b/ydb/core/tx/columnshard/blobs_action/abstract/ya.make index b3b4c20028c8..1ce1d711629e 100644 --- a/ydb/core/tx/columnshard/blobs_action/abstract/ya.make +++ b/ydb/core/tx/columnshard/blobs_action/abstract/ya.make @@ -18,6 +18,7 @@ PEERDIR( contrib/libs/apache/arrow ydb/core/tablet_flat ydb/core/tx/tiering/abstract + ydb/core/tx/columnshard/hooks/abstract ydb/core/tx/columnshard/blobs_action/common ydb/core/tx/columnshard/data_sharing/protos ydb/core/tx/columnshard/blobs_action/events diff --git a/ydb/core/tx/columnshard/blobs_action/bs/storage.h b/ydb/core/tx/columnshard/blobs_action/bs/storage.h index fd5c21eb309e..8cdc80868e00 100644 --- a/ydb/core/tx/columnshard/blobs_action/bs/storage.h +++ b/ydb/core/tx/columnshard/blobs_action/bs/storage.h @@ -41,6 +41,10 @@ class TOperator: public IBlobsStorageOperator { virtual std::shared_ptr GetBlobsTracker() const override { return Manager; } + + virtual bool IsReady() const override { + return true; + } }; } diff --git a/ydb/core/tx/columnshard/blobs_action/local/storage.h b/ydb/core/tx/columnshard/blobs_action/local/storage.h index beb5c4286cab..85accbe03847 100644 --- a/ydb/core/tx/columnshard/blobs_action/local/storage.h +++ b/ydb/core/tx/columnshard/blobs_action/local/storage.h @@ -48,6 +48,9 @@ class TOperator: public IBlobsStorageOperator { return false; } + virtual bool IsReady() const override { + return true; + } }; -} +} // namespace NKikimr::NOlap::NBlobOperations::NLocal diff --git a/ydb/core/tx/columnshard/blobs_action/tier/storage.cpp b/ydb/core/tx/columnshard/blobs_action/tier/storage.cpp index cf842edbd411..2cb28089eb87 100644 --- a/ydb/core/tx/columnshard/blobs_action/tier/storage.cpp +++ b/ydb/core/tx/columnshard/blobs_action/tier/storage.cpp @@ -54,12 +54,14 @@ void TOperator::DoStartGCAction(const std::shared_ptr& action) c } void TOperator::InitNewExternalOperator(const NColumnShard::NTiers::TManager* tierManager) { - NKikimrSchemeOp::TS3Settings settings; - if (tierManager) { - settings = tierManager->GetS3Settings(); - } else { - settings.SetEndpoint("nowhere"); + if (!tierManager || !tierManager->IsReady()) { + TGuard changeLock(ChangeOperatorLock); + CurrentS3Settings.reset(); + ExternalStorageOperator = nullptr; + return; } + + NKikimrSchemeOp::TS3Settings settings = tierManager->GetS3Settings(); { TGuard changeLock(ChangeOperatorLock); if (CurrentS3Settings && CurrentS3Settings->SerializeAsString() == settings.SerializeAsString()) { @@ -103,12 +105,7 @@ TOperator::TOperator(const TString& storageId, const TActorId& shardActorId, con void TOperator::DoOnTieringModified(const std::shared_ptr& tiers) { auto* tierManager = tiers->GetManagerOptional(TBase::GetStorageId()); - if (tierManager) { - InitNewExternalOperator(tierManager); - } else { - TGuard changeLock(ChangeOperatorLock); - ExternalStorageOperator = nullptr; - } + InitNewExternalOperator(tierManager); } bool TOperator::DoLoad(IBlobManagerDb& dbBlobs) { diff --git a/ydb/core/tx/columnshard/blobs_action/tier/storage.h b/ydb/core/tx/columnshard/blobs_action/tier/storage.h index db188f1be71e..7495014b12a1 100644 --- a/ydb/core/tx/columnshard/blobs_action/tier/storage.h +++ b/ydb/core/tx/columnshard/blobs_action/tier/storage.h @@ -56,6 +56,9 @@ class TOperator: public IBlobsStorageOperator { return GCInfo->HasToDelete(blobId, tabletId); } + virtual bool IsReady() const override { + return !!ExternalStorageOperator; + } }; } diff --git a/ydb/core/tx/columnshard/blobs_action/transaction/tx_blobs_written.cpp b/ydb/core/tx/columnshard/blobs_action/transaction/tx_blobs_written.cpp new file mode 100644 index 000000000000..1e469c09a411 --- /dev/null +++ b/ydb/core/tx/columnshard/blobs_action/transaction/tx_blobs_written.cpp @@ -0,0 +1,159 @@ +#include "tx_blobs_written.h" + +#include +#include +#include +#include + +namespace NKikimr::NColumnShard { + +bool TTxBlobsWritingFinished::DoExecute(TTransactionContext& txc, const TActorContext&) { + TMemoryProfileGuard mpg("TTxBlobsWritingFinished::Execute"); + txc.DB.NoMoreReadsForTx(); + CommitSnapshot = Self->GetCurrentSnapshotForInternalModification(); + NActors::TLogContextGuard logGuard = + NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD_BLOBS)("tablet_id", Self->TabletID())("tx_state", "execute"); + ACFL_DEBUG("event", "start_execute"); + auto& index = Self->MutableIndexAs(); + const auto minReadSnapshot = Self->GetMinReadSnapshot(); + for (auto&& pack : Packs) { + const auto& writeMeta = pack.GetWriteMeta(); + AFL_VERIFY(Self->TablesManager.IsReadyForFinishWrite(writeMeta.GetTableId(), minReadSnapshot)); + AFL_VERIFY(!writeMeta.HasLongTxId()); + auto operation = Self->OperationsManager->GetOperationVerified((TOperationWriteId)writeMeta.GetWriteId()); + Y_ABORT_UNLESS(operation->GetStatus() == EOperationStatus::Started); + auto& granule = index.MutableGranuleVerified(operation->GetPathId()); + for (auto&& portion : pack.MutablePortions()) { + if (operation->GetBehaviour() == EOperationBehaviour::NoTxWrite) { + static TAtomicCounter Counter = 0; + portion.GetPortionInfoConstructor()->MutablePortionConstructor().SetInsertWriteId((TInsertWriteId)Counter.Inc()); + } else { + portion.GetPortionInfoConstructor()->MutablePortionConstructor().SetInsertWriteId(Self->InsertTable->BuildNextWriteId(txc)); + } + pack.AddInsertWriteId(portion.GetPortionInfoConstructor()->GetPortionConstructor().GetInsertWriteIdVerified()); + portion.Finalize(Self, txc); + if (operation->GetBehaviour() == EOperationBehaviour::NoTxWrite) { + granule.CommitImmediateOnExecute(txc, *CommitSnapshot, portion.GetPortionInfo()); + } else { + granule.InsertPortionOnExecute(txc, portion.GetPortionInfo()); + } + } + } + + NOlap::TBlobManagerDb blobManagerDb(txc.DB); + if (WritingActions) { + WritingActions->OnExecuteTxAfterWrite(*Self, blobManagerDb, true); + } + std::set operationIds; + for (auto&& pack : Packs) { + const auto& writeMeta = pack.GetWriteMeta(); + auto operation = Self->OperationsManager->GetOperationVerified((TOperationWriteId)writeMeta.GetWriteId()); + if (!operationIds.emplace(operation->GetWriteId()).second) { + continue; + } + Y_ABORT_UNLESS(operation->GetStatus() == EOperationStatus::Started); + operation->OnWriteFinish(txc, pack.GetInsertWriteIds(), operation->GetBehaviour() == EOperationBehaviour::NoTxWrite); + Self->OperationsManager->LinkInsertWriteIdToOperationWriteId(pack.GetInsertWriteIds(), operation->GetWriteId()); + if (operation->GetBehaviour() == EOperationBehaviour::NoTxWrite) { + auto ev = NEvents::TDataEvents::TEvWriteResult::BuildCompleted(Self->TabletID()); + Results.emplace_back(std::move(ev), writeMeta.GetSource(), operation->GetCookie()); + } else { + auto& info = Self->OperationsManager->GetLockVerified(operation->GetLockId()); + NKikimrDataEvents::TLock lock; + lock.SetLockId(operation->GetLockId()); + lock.SetDataShard(Self->TabletID()); + lock.SetGeneration(info.GetGeneration()); + lock.SetCounter(info.GetInternalGenerationCounter()); + lock.SetPathId(writeMeta.GetTableId()); + auto ev = NEvents::TDataEvents::TEvWriteResult::BuildCompleted(Self->TabletID(), operation->GetLockId(), lock); + Results.emplace_back(std::move(ev), writeMeta.GetSource(), operation->GetCookie()); + } + } + return true; +} + +void TTxBlobsWritingFinished::DoComplete(const TActorContext& ctx) { + TMemoryProfileGuard mpg("TTxBlobsWritingFinished::Complete"); + NActors::TLogContextGuard logGuard = + NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD_BLOBS)("tablet_id", Self->TabletID())("tx_state", "complete"); + const auto now = TMonotonic::Now(); + if (WritingActions) { + WritingActions->OnCompleteTxAfterWrite(*Self, true); + } + + for (auto&& i : Results) { + i.DoSendReply(ctx); + } + auto& index = Self->MutableIndexAs(); + std::set pathIds; + for (auto&& pack : Packs) { + const auto& writeMeta = pack.GetWriteMeta(); + AFL_VERIFY(!writeMeta.HasLongTxId()); + auto op = Self->GetOperationsManager().GetOperationVerified((TOperationWriteId)writeMeta.GetWriteId()); + pathIds.emplace(op->GetPathId()); + auto& granule = index.MutableGranuleVerified(op->GetPathId()); + for (auto&& portion : pack.GetPortions()) { + if (op->GetBehaviour() == EOperationBehaviour::WriteWithLock || op->GetBehaviour() == EOperationBehaviour::NoTxWrite) { + if (op->GetBehaviour() != EOperationBehaviour::NoTxWrite || Self->GetOperationsManager().HasReadLocks(writeMeta.GetTableId())) { + auto evWrite = std::make_shared( + writeMeta.GetTableId(), portion.GetPKBatch(), Self->GetIndexOptional()->GetVersionedIndex().GetPrimaryKey()); + Self->GetOperationsManager().AddEventForLock(*Self, op->GetLockId(), evWrite); + } + } + granule.InsertPortionOnComplete(portion.GetPortionInfo(), index); + } + if (op->GetBehaviour() == EOperationBehaviour::NoTxWrite) { + AFL_VERIFY(CommitSnapshot); + Self->OperationsManager->AddTemporaryTxLink(op->GetLockId()); + Self->OperationsManager->CommitTransactionOnComplete(*Self, op->GetLockId(), *CommitSnapshot); + Self->Counters.GetTabletCounters()->IncCounter(COUNTER_IMMEDIATE_TX_COMPLETED); + } + Self->Counters.GetCSCounters().OnWriteTxComplete(now - writeMeta.GetWriteStartInstant()); + Self->Counters.GetCSCounters().OnSuccessWriteResponse(); + } + Self->SetupCompaction(pathIds); +} + +TTxBlobsWritingFinished::TTxBlobsWritingFinished(TColumnShard* self, const NKikimrProto::EReplyStatus writeStatus, + const std::shared_ptr& writingActions, std::vector&& packs, + const std::vector& noDataWrites) + : TBase(self, "TTxBlobsWritingFinished") + , Packs(std::move(packs)) + , WritingActions(writingActions) { + Y_UNUSED(writeStatus); + for (auto&& i : noDataWrites) { + auto ev = NEvents::TDataEvents::TEvWriteResult::BuildCompleted(Self->TabletID()); + auto op = Self->GetOperationsManager().GetOperationVerified((TOperationWriteId)i.GetWriteMeta().GetWriteId()); + Results.emplace_back(std::move(ev), i.GetWriteMeta().GetSource(), op->GetCookie()); + } +} + +bool TTxBlobsWritingFailed::DoExecute(TTransactionContext& txc, const TActorContext& ctx) { + Y_UNUSED(ctx); + for (auto&& pack : Packs) { + const auto& writeMeta = pack.GetWriteMeta(); + AFL_VERIFY(!writeMeta.HasLongTxId()); + auto op = Self->GetOperationsManager().GetOperationVerified((TOperationWriteId)writeMeta.GetWriteId()); + Self->OperationsManager->AddTemporaryTxLink(op->GetLockId()); + Self->OperationsManager->AbortTransactionOnExecute(*Self, op->GetLockId(), txc); + + auto ev = NEvents::TDataEvents::TEvWriteResult::BuildError(Self->TabletID(), op->GetLockId(), + NKikimrDataEvents::TEvWriteResult::STATUS_INTERNAL_ERROR, "cannot write blob: " + ::ToString(PutBlobResult)); + Results.emplace_back(std::move(ev), writeMeta.GetSource(), op->GetCookie()); + } + return true; +} + +void TTxBlobsWritingFailed::DoComplete(const TActorContext& ctx) { + for (auto&& i : Results) { + i.DoSendReply(ctx); + Self->Counters.GetCSCounters().OnFailedWriteResponse(EWriteFailReason::PutBlob); + } + for (auto&& pack : Packs) { + const auto& writeMeta = pack.GetWriteMeta(); + auto op = Self->GetOperationsManager().GetOperationVerified((TOperationWriteId)writeMeta.GetWriteId()); + Self->OperationsManager->AbortTransactionOnComplete(*Self, op->GetLockId()); + } +} + +} // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/columnshard/blobs_action/transaction/tx_blobs_written.h b/ydb/core/tx/columnshard/blobs_action/transaction/tx_blobs_written.h new file mode 100644 index 000000000000..d758031bd763 --- /dev/null +++ b/ydb/core/tx/columnshard/blobs_action/transaction/tx_blobs_written.h @@ -0,0 +1,94 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +namespace NKikimr::NColumnShard { + +class TColumnShard; + +class TTxBlobsWritingFinished: public NOlap::NDataSharing::TExtendedTransactionBase { +private: + using TBase = NOlap::NDataSharing::TExtendedTransactionBase; + std::vector Packs; + const std::shared_ptr WritingActions; + std::optional CommitSnapshot; + + class TReplyInfo { + private: + std::unique_ptr Event; + TActorId DestinationForReply; + const ui64 Cookie; + + public: + TReplyInfo(std::unique_ptr&& ev, const TActorId& destinationForReply, const ui64 cookie) + : Event(std::move(ev)) + , DestinationForReply(destinationForReply) + , Cookie(cookie) { + } + + void DoSendReply(const TActorContext& ctx) { + ctx.Send(DestinationForReply, Event.release(), 0, Cookie); + } + }; + + std::vector Results; + +public: + TTxBlobsWritingFinished(TColumnShard* self, const NKikimrProto::EReplyStatus writeStatus, + const std::shared_ptr& writingActions, std::vector&& packs, + const std::vector& noDataWrites); + + virtual bool DoExecute(TTransactionContext& txc, const TActorContext& ctx) override; + virtual void DoComplete(const TActorContext& ctx) override; + TTxType GetTxType() const override { + return TXTYPE_WRITE_PORTIONS_FINISHED; + } +}; + +class TTxBlobsWritingFailed: public NOlap::NDataSharing::TExtendedTransactionBase { +private: + using TBase = NOlap::NDataSharing::TExtendedTransactionBase; + const NKikimrProto::EReplyStatus PutBlobResult; + std::vector Packs; + + class TReplyInfo { + private: + std::unique_ptr Event; + TActorId DestinationForReply; + const ui64 Cookie; + + public: + TReplyInfo(std::unique_ptr&& ev, const TActorId& destinationForReply, const ui64 cookie) + : Event(std::move(ev)) + , DestinationForReply(destinationForReply) + , Cookie(cookie) { + } + + void DoSendReply(const TActorContext& ctx) { + ctx.Send(DestinationForReply, Event.release(), 0, Cookie); + } + }; + + std::vector Results; + +public: + TTxBlobsWritingFailed(TColumnShard* self, const NKikimrProto::EReplyStatus writeStatus, std::vector&& packs) + : TBase(self) + , PutBlobResult(writeStatus) + , Packs(std::move(packs)) { + } + + virtual bool DoExecute(TTransactionContext& txc, const TActorContext& ctx) override; + virtual void DoComplete(const TActorContext& ctx) override; + TTxType GetTxType() const override { + return TXTYPE_WRITE_PORTIONS_FAILED; + } +}; + +} // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/columnshard/blobs_action/transaction/tx_write.cpp b/ydb/core/tx/columnshard/blobs_action/transaction/tx_write.cpp index 579a2e5eaa14..52d3bed0a6b5 100644 --- a/ydb/core/tx/columnshard/blobs_action/transaction/tx_write.cpp +++ b/ydb/core/tx/columnshard/blobs_action/transaction/tx_write.cpp @@ -23,23 +23,27 @@ bool TTxWrite::CommitOneBlob(TTransactionContext& txc, const NOlap::TWideSeriali auto userData = batch.BuildInsertionUserData(*Self); TBlobGroupSelector dsGroupSelector(Self->Info()); NOlap::TDbWrapper dbTable(txc.DB, &dsGroupSelector); - NOlap::TCommittedData commitData(userData, Self->GetLastPlannedSnapshot(), Self->Generation(), writeId); + AFL_VERIFY(CommitSnapshot); + NOlap::TCommittedData commitData(userData, *CommitSnapshot, Self->Generation(), writeId); if (Self->TablesManager.HasTable(userData->GetPathId())) { - Self->InsertTable->CommitEphemeral(dbTable, std::move(commitData)); + auto counters = Self->InsertTable->CommitEphemeral(dbTable, std::move(commitData)); + Self->Counters.GetTabletCounters()->OnWriteCommitted(counters); } Self->UpdateInsertTableCounters(); return true; } -bool TTxWrite::Execute(TTransactionContext& txc, const TActorContext&) { +bool TTxWrite::DoExecute(TTransactionContext& txc, const TActorContext&) { + CommitSnapshot = Self->GetCurrentSnapshotForInternalModification(); TMemoryProfileGuard mpg("TTxWrite::Execute"); NActors::TLogContextGuard logGuard = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD_BLOBS)("tablet_id", Self->TabletID())("tx_state", "execute"); ACFL_DEBUG("event", "start_execute"); const NOlap::TWritingBuffer& buffer = PutBlobResult->Get()->MutableWritesBuffer(); + const auto minReadSnapshot = Self->GetMinReadSnapshot(); for (auto&& aggr : buffer.GetAggregations()) { const auto& writeMeta = aggr->GetWriteMeta(); - Y_ABORT_UNLESS(Self->TablesManager.IsReadyForWrite(writeMeta.GetTableId())); + Y_ABORT_UNLESS(Self->TablesManager.IsReadyForFinishWrite(writeMeta.GetTableId(), minReadSnapshot)); txc.DB.NoMoreReadsForTx(); TWriteOperation::TPtr operation; if (writeMeta.HasLongTxId()) { @@ -94,17 +98,6 @@ bool TTxWrite::Execute(TTransactionContext& txc, const TActorContext&) { if (operation->GetBehaviour() == EOperationBehaviour::NoTxWrite) { auto ev = NEvents::TDataEvents::TEvWriteResult::BuildCompleted(Self->TabletID()); Results.emplace_back(std::move(ev), writeMeta.GetSource(), operation->GetCookie()); - } else if (operation->GetBehaviour() == EOperationBehaviour::InTxWrite) { - NKikimrTxColumnShard::TCommitWriteTxBody proto; - proto.SetLockId(operation->GetLockId()); - TString txBody; - Y_ABORT_UNLESS(proto.SerializeToString(&txBody)); - auto op = Self->GetProgressTxController().StartProposeOnExecute( - TTxController::TTxInfo( - NKikimrTxColumnShard::TX_KIND_COMMIT_WRITE, operation->GetLockId(), writeMeta.GetSource(), operation->GetCookie(), {}), - txBody, txc); - AFL_VERIFY(!op->IsFail()); - ResultOperators.emplace_back(op); } else { auto& info = Self->OperationsManager->GetLockVerified(operation->GetLockId()); NKikimrDataEvents::TLock lock; @@ -126,7 +119,7 @@ bool TTxWrite::Execute(TTransactionContext& txc, const TActorContext&) { return true; } -void TTxWrite::Complete(const TActorContext& ctx) { +void TTxWrite::DoComplete(const TActorContext& ctx) { TMemoryProfileGuard mpg("TTxWrite::Complete"); NActors::TLogContextGuard logGuard = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD_BLOBS)("tablet_id", Self->TabletID())("tx_state", "complete"); @@ -139,10 +132,7 @@ void TTxWrite::Complete(const TActorContext& ctx) { i->OnCompleteTxAfterRemoving(true); } - AFL_VERIFY(buffer.GetAggregations().size() == Results.size() + ResultOperators.size()); - for (auto&& i : ResultOperators) { - Self->GetProgressTxController().FinishProposeOnComplete(i->GetTxId(), ctx); - } + AFL_VERIFY(buffer.GetAggregations().size() == Results.size()); for (auto&& i : Results) { i.DoSendReply(ctx); } @@ -151,13 +141,16 @@ void TTxWrite::Complete(const TActorContext& ctx) { if (!writeMeta.HasLongTxId()) { auto op = Self->GetOperationsManager().GetOperationVerified((TOperationWriteId)writeMeta.GetWriteId()); if (op->GetBehaviour() == EOperationBehaviour::WriteWithLock || op->GetBehaviour() == EOperationBehaviour::NoTxWrite) { - auto evWrite = std::make_shared(writeMeta.GetTableId(), - buffer.GetAggregations()[i]->GetRecordBatch(), Self->GetIndexOptional()->GetVersionedIndex().GetPrimaryKey()); - Self->GetOperationsManager().AddEventForLock(*Self, op->GetLockId(), evWrite); + if (op->GetBehaviour() != EOperationBehaviour::NoTxWrite || Self->GetOperationsManager().HasReadLocks(writeMeta.GetTableId())) { + auto evWrite = std::make_shared(writeMeta.GetTableId(), + buffer.GetAggregations()[i]->GetRecordBatch(), Self->GetIndexOptional()->GetVersionedIndex().GetPrimaryKey()); + Self->GetOperationsManager().AddEventForLock(*Self, op->GetLockId(), evWrite); + } } if (op->GetBehaviour() == EOperationBehaviour::NoTxWrite) { Self->OperationsManager->AddTemporaryTxLink(op->GetLockId()); - Self->OperationsManager->CommitTransactionOnComplete(*Self, op->GetLockId(), Self->GetLastTxSnapshot()); + AFL_VERIFY(CommitSnapshot); + Self->OperationsManager->CommitTransactionOnComplete(*Self, op->GetLockId(), *CommitSnapshot); } } Self->Counters.GetCSCounters().OnWriteTxComplete(now - writeMeta.GetWriteStartInstant()); diff --git a/ydb/core/tx/columnshard/blobs_action/transaction/tx_write.h b/ydb/core/tx/columnshard/blobs_action/transaction/tx_write.h index aacaac22384f..aa626c9ea8ed 100644 --- a/ydb/core/tx/columnshard/blobs_action/transaction/tx_write.h +++ b/ydb/core/tx/columnshard/blobs_action/transaction/tx_write.h @@ -1,24 +1,29 @@ #pragma once #include +#include #include namespace NKikimr::NColumnShard { -class TTxWrite : public NTabletFlatExecutor::TTransactionBase { +class TTxWrite: public NOlap::NDataSharing::TExtendedTransactionBase { +private: + using TBase = NOlap::NDataSharing::TExtendedTransactionBase; + public: TTxWrite(TColumnShard* self, const TEvPrivate::TEvWriteBlobsResult::TPtr& putBlobResult) - : NTabletFlatExecutor::TTransactionBase(self) - , PutBlobResult(putBlobResult) - , TabletTxNo(++Self->TabletTxCounter) - {} + : TBase(self, "TTxWrite") + , PutBlobResult(putBlobResult) { + } - bool Execute(TTransactionContext& txc, const TActorContext& ctx) override; - void Complete(const TActorContext& ctx) override; - TTxType GetTxType() const override { return TXTYPE_WRITE; } + bool DoExecute(TTransactionContext& txc, const TActorContext& ctx) override; + void DoComplete(const TActorContext& ctx) override; + TTxType GetTxType() const override { + return TXTYPE_WRITE; + } private: TEvPrivate::TEvWriteBlobsResult::TPtr PutBlobResult; - const ui32 TabletTxNo; + std::optional CommitSnapshot; bool CommitOneBlob(TTransactionContext& txc, const NOlap::TWideSerializedBatch& batch, const TInsertWriteId writeId); bool InsertOneBlob(TTransactionContext& txc, const NOlap::TWideSerializedBatch& batch, const TInsertWriteId writeId); @@ -28,13 +33,12 @@ class TTxWrite : public NTabletFlatExecutor::TTransactionBase { std::unique_ptr Event; TActorId DestinationForReply; const ui64 Cookie; + public: TReplyInfo(std::unique_ptr&& ev, const TActorId& destinationForReply, const ui64 cookie) : Event(std::move(ev)) , DestinationForReply(destinationForReply) - , Cookie(cookie) - { - + , Cookie(cookie) { } void DoSendReply(const TActorContext& ctx) { @@ -43,17 +47,6 @@ class TTxWrite : public NTabletFlatExecutor::TTransactionBase { }; std::vector Results; - std::vector> ResultOperators; - - - TStringBuilder TxPrefix() const { - return TStringBuilder() << "TxWrite[" << ToString(TabletTxNo) << "] "; - } - - TString TxSuffix() const { - return TStringBuilder() << " at tablet " << Self->TabletID(); - } }; - -} +} // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/columnshard/blobs_action/transaction/tx_write_index.cpp b/ydb/core/tx/columnshard/blobs_action/transaction/tx_write_index.cpp index 57a1eee50146..f996b91d242a 100644 --- a/ydb/core/tx/columnshard/blobs_action/transaction/tx_write_index.cpp +++ b/ydb/core/tx/columnshard/blobs_action/transaction/tx_write_index.cpp @@ -16,14 +16,15 @@ bool TTxWriteIndex::Execute(TTransactionContext& txc, const TActorContext& ctx) ACFL_DEBUG("event", "TTxWriteIndex::Execute")("change_type", changes->TypeString())("details", changes->DebugString()); if (Ev->Get()->GetPutStatus() == NKikimrProto::OK) { - NOlap::TSnapshot snapshot(Self->LastPlannedStep, Self->LastPlannedTxId); - Y_ABORT_UNLESS(Ev->Get()->IndexInfo->GetLastSchema()->GetSnapshot() <= snapshot); + AFL_VERIFY(Ev->Get()->IndexInfo->GetLastSchema()->GetSnapshot() <= Self->GetLastTxSnapshot()) + ("schema_last", Ev->Get()->IndexInfo->GetLastSchema()->GetSnapshot().DebugString())( + "planned_last", Self->GetLastTxSnapshot().DebugString()); TBlobGroupSelector dsGroupSelector(Self->Info()); NOlap::TDbWrapper dbWrap(txc.DB, &dsGroupSelector); - AFL_VERIFY(Self->TablesManager.MutablePrimaryIndex().ApplyChangesOnExecute(dbWrap, changes, snapshot)); + AFL_VERIFY(Self->TablesManager.MutablePrimaryIndex().ApplyChangesOnExecute(dbWrap, changes, Self->GetLastTxSnapshot())); LOG_S_DEBUG(TxPrefix() << "(" << changes->TypeString() << ") apply" << TxSuffix()); - NOlap::TWriteIndexContext context(&txc.DB, dbWrap, Self->MutableIndexAs()); + NOlap::TWriteIndexContext context(&txc.DB, dbWrap, Self->MutableIndexAs(), CurrentSnapshot); changes->WriteIndexOnExecute(Self, context); NOlap::TBlobManagerDb blobManagerDb(txc.DB); @@ -48,9 +49,10 @@ bool TTxWriteIndex::Execute(TTransactionContext& txc, const TActorContext& ctx) } void TTxWriteIndex::Complete(const TActorContext& ctx) { - TLogContextGuard gLogging(NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD_BLOBS)("tablet_id", Self->TabletID())); CompleteReady = true; auto changes = Ev->Get()->IndexChanges; + TLogContextGuard gLogging(NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD_BLOBS)("tablet_id", Self->TabletID())( + "task_id", changes->GetTaskIdentifier())); TMemoryProfileGuard mpg("TTxWriteIndex::Complete::" + changes->TypeString()); ACFL_DEBUG("event", "TTxWriteIndex::Complete")("change_type", changes->TypeString())("details", changes->DebugString()); @@ -58,7 +60,8 @@ void TTxWriteIndex::Complete(const TActorContext& ctx) { const ui64 bytesWritten = changes->GetBlobsAction().GetWritingTotalSize(); if (!Ev->Get()->IndexChanges->IsAborted()) { - NOlap::TWriteIndexCompleteContext context(ctx, blobsWritten, bytesWritten, Ev->Get()->Duration, Self->MutableIndexAs()); + NOlap::TWriteIndexCompleteContext context( + ctx, blobsWritten, bytesWritten, Ev->Get()->Duration, Self->MutableIndexAs(), CurrentSnapshot); Ev->Get()->IndexChanges->WriteIndexOnComplete(Self, context); } @@ -80,13 +83,12 @@ TTxWriteIndex::TTxWriteIndex(TColumnShard* self, TEvPrivate::TEvWriteIndex::TPtr : TBase(self) , Ev(ev) , TabletTxNo(++Self->TabletTxCounter) -{ + , CurrentSnapshot(Self->GetCurrentSnapshotForInternalModification()) { AFL_VERIFY(Ev && Ev->Get()->IndexChanges); - NOlap::TSnapshot snapshot(Self->LastPlannedStep, Self->LastPlannedTxId); auto changes = Ev->Get()->IndexChanges; if (Ev->Get()->GetPutStatus() == NKikimrProto::OK) { - AFL_VERIFY(Self->TablesManager.MutablePrimaryIndex().ApplyChangesOnTxCreate(changes, snapshot)); + AFL_VERIFY(Self->TablesManager.MutablePrimaryIndex().ApplyChangesOnTxCreate(changes, CurrentSnapshot)); } } diff --git a/ydb/core/tx/columnshard/blobs_action/transaction/tx_write_index.h b/ydb/core/tx/columnshard/blobs_action/transaction/tx_write_index.h index 3cf5d29a7219..56deafc0672a 100644 --- a/ydb/core/tx/columnshard/blobs_action/transaction/tx_write_index.h +++ b/ydb/core/tx/columnshard/blobs_action/transaction/tx_write_index.h @@ -15,13 +15,15 @@ class TTxWriteIndex: public TTransactionBase { bool Execute(TTransactionContext& txc, const TActorContext& ctx) override; void Complete(const TActorContext& ctx) override; - TTxType GetTxType() const override { return TXTYPE_WRITE_INDEX; } + TTxType GetTxType() const override { + return TXTYPE_WRITE_INDEX; + } virtual void Describe(IOutputStream& out) const noexcept override; private: - TEvPrivate::TEvWriteIndex::TPtr Ev; const ui32 TabletTxNo; + const NOlap::TSnapshot CurrentSnapshot; bool CompleteReady = false; TStringBuilder TxPrefix() const { @@ -33,4 +35,4 @@ class TTxWriteIndex: public TTransactionBase { } }; -} +} // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/columnshard/blobs_action/transaction/ya.make b/ydb/core/tx/columnshard/blobs_action/transaction/ya.make index c78e93ef3b7e..405f0e3f9ebd 100644 --- a/ydb/core/tx/columnshard/blobs_action/transaction/ya.make +++ b/ydb/core/tx/columnshard/blobs_action/transaction/ya.make @@ -7,6 +7,7 @@ SRCS( tx_gc_insert_table.cpp tx_gc_indexed.cpp tx_remove_blobs.cpp + tx_blobs_written.cpp ) PEERDIR( diff --git a/ydb/core/tx/columnshard/blobs_reader/task.cpp b/ydb/core/tx/columnshard/blobs_reader/task.cpp index 8306ccbc6309..bdf7b83f061e 100644 --- a/ydb/core/tx/columnshard/blobs_reader/task.cpp +++ b/ydb/core/tx/columnshard/blobs_reader/task.cpp @@ -89,7 +89,7 @@ ITask::ITask(const TReadActionsCollection& actions, const TString& taskCustomer, , TaskCustomer(taskCustomer) { Agents = actions; - AFL_VERIFY(!Agents.IsEmpty()); +// AFL_VERIFY(!Agents.IsEmpty()); for (auto&& i : Agents) { AFL_VERIFY(i.second->GetExpectedBlobsCount()); } diff --git a/ydb/core/tx/columnshard/blobs_reader/task.h b/ydb/core/tx/columnshard/blobs_reader/task.h index 1f04c963fb9a..509fb0d9ee22 100644 --- a/ydb/core/tx/columnshard/blobs_reader/task.h +++ b/ydb/core/tx/columnshard/blobs_reader/task.h @@ -65,7 +65,7 @@ class TCompositeReadBlobs { } TString Extract(const TString& storageId, const TBlobRange& range) { auto it = BlobsByStorage.find(storageId); - AFL_VERIFY(it != BlobsByStorage.end()); + AFL_VERIFY(it != BlobsByStorage.end())("range", range.ToString())("storage_id", storageId); auto result = it->second.Extract(range); if (it->second.IsEmpty()) { BlobsByStorage.erase(it); diff --git a/ydb/core/tx/columnshard/columnshard.cpp b/ydb/core/tx/columnshard/columnshard.cpp index f3a6b9e99db9..e2ad5f3e0729 100644 --- a/ydb/core/tx/columnshard/columnshard.cpp +++ b/ydb/core/tx/columnshard/columnshard.cpp @@ -3,6 +3,8 @@ #include "bg_tasks/manager/manager.h" #include "blobs_reader/actor.h" #include "counters/aggregation/table_stats.h" +#include "data_accessor/actor.h" +#include "data_accessor/manager.h" #include "engines/column_engine_logs.h" #include "engines/writer/buffer/actor.h" #include "hooks/abstract/abstract.h" @@ -11,6 +13,7 @@ #include #include +#include #include namespace NKikimr { @@ -27,8 +30,16 @@ void TColumnShard::CleanupActors(const TActorContext& ctx) { if (BackgroundSessionsManager) { BackgroundSessionsManager->Stop(); } + InFlightReadsTracker.Stop(this); ctx.Send(ResourceSubscribeActor, new TEvents::TEvPoisonPill); ctx.Send(BufferizationWriteActorId, new TEvents::TEvPoisonPill); + ctx.Send(DataAccessorsControlActorId, new TEvents::TEvPoisonPill); + if (!!OperationsManager) { + OperationsManager->StopWriting(); + } + if (PrioritizationClientId) { + NPrioritiesQueue::TCompServiceOperator::UnregisterClient(PrioritizationClientId); + } for (auto&& i : ActorsToStop) { ctx.Send(i, new TEvents::TEvPoisonPill); } @@ -47,21 +58,26 @@ void TColumnShard::BecomeBroken(const TActorContext& ctx) { CleanupActors(ctx); } -void TColumnShard::SwitchToWork(const TActorContext& ctx) { +void TColumnShard::TrySwitchToWork(const TActorContext& ctx) { + if (!Tiers->AreConfigsComplete()) { + AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("event", "skip_switch_to_work")("reason", "tiering_metadata_not_ready"); + return; + } + if (!IsTxInitFinished) { + AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("event", "skip_switch_to_work")("reason", "db_reading_not_finished"); + return; + } + ProgressTxController->OnTabletInit(); { const TLogContextGuard gLogging = - NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", TabletID())("self_id", SelfId()); + NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", TabletID())("self_id", SelfId())("process", "SwitchToWork"); AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("event", "initialize_shard")("step", "SwitchToWork"); - - for (auto&& i : TablesManager.GetTables()) { - ActivateTiering(i.first, i.second.GetTieringUsage()); - } - Become(&TThis::StateWork); SignalTabletActive(ctx); AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("event", "initialize_shard")("step", "SignalTabletActive"); TryRegisterMediatorTimeCast(); EnqueueProgressTx(ctx, std::nullopt); + OnTieringModified(); } Counters.GetCSCounters().OnIndexMetadataLimit(NOlap::IColumnEngine::GetMetadataLimit()); EnqueueBackgroundActivities(); @@ -72,6 +88,7 @@ void TColumnShard::SwitchToWork(const TActorContext& ctx) { NYDBTest::TControllers::GetColumnShardController()->OnSwitchToWork(TabletID()); AFL_VERIFY(!!StartInstant); Counters.GetCSCounters().Initialization.OnSwitchToWork(TMonotonic::Now() - *StartInstant, TMonotonic::Now() - CreateInstant); + NYDBTest::TControllers::GetColumnShardController()->OnTabletInitCompleted(*this); } void TColumnShard::OnActivateExecutor(const TActorContext& ctx) { @@ -89,8 +106,10 @@ void TColumnShard::OnActivateExecutor(const TActorContext& ctx) { ctx.Send(selfActorId, new TEvPrivate::TEvTieringModified); }); Tiers->Start(Tiers); - if (!NMetadata::NProvider::TServiceOperator::IsEnabled()) { - Tiers->TakeConfigs(NYDBTest::TControllers::GetColumnShardController()->GetFallbackTiersSnapshot(), nullptr); + if (const auto& tiersSnapshot = NYDBTest::TControllers::GetColumnShardController()->GetOverrideTierConfigs(); !tiersSnapshot.empty()) { + for (const auto& [id, tier] : tiersSnapshot) { + Tiers->UpdateTierConfig(tier, CanonizePath(id), false); + } } BackgroundSessionsManager = std::make_shared( std::make_shared(selfActorId, (NOlap::TTabletId)TabletID(), *this)); @@ -101,14 +120,28 @@ void TColumnShard::OnActivateExecutor(const TActorContext& ctx) { Settings.RegisterControls(icb); ResourceSubscribeActor = ctx.Register(new NOlap::NResourceBroker::NSubscribe::TActor(TabletID(), SelfId())); BufferizationWriteActorId = ctx.Register(new NColumnShard::NWriting::TActor(TabletID(), SelfId())); + DataAccessorsControlActorId = ctx.Register(new NOlap::NDataAccessorControl::TActor(TabletID(), SelfId())); + DataAccessorsManager = std::make_shared(DataAccessorsControlActorId, SelfId()), + + PrioritizationClientId = NPrioritiesQueue::TCompServiceOperator::RegisterClient(); Execute(CreateTxInitSchema(), ctx); } void TColumnShard::Handle(TEvPrivate::TEvTieringModified::TPtr& /*ev*/, const TActorContext& /*ctx*/) { + if (const auto& tiersSnapshot = NYDBTest::TControllers::GetColumnShardController()->GetOverrideTierConfigs(); !tiersSnapshot.empty()) { + for (const auto& [id, tier] : tiersSnapshot) { + Tiers->UpdateTierConfig(tier, CanonizePath(id), false); + } + } + OnTieringModified(); NYDBTest::TControllers::GetColumnShardController()->OnTieringModified(Tiers); } +void TColumnShard::HandleInit(TEvPrivate::TEvTieringModified::TPtr& /*ev*/, const TActorContext& ctx) { + TrySwitchToWork(ctx); +} + void TColumnShard::Handle(TEvTabletPipe::TEvClientConnected::TPtr& ev, const TActorContext&) { auto tabletId = ev->Get()->TabletId; auto clientId = ev->Get()->ClientId; @@ -185,11 +218,16 @@ void TColumnShard::Handle(TEvPrivate::TEvReadFinished::TPtr& ev, const TActorCon } void TColumnShard::Handle(TEvPrivate::TEvPingSnapshotsUsage::TPtr& /*ev*/, const TActorContext& ctx) { - if (auto writeTx = - InFlightReadsTracker.Ping(this, NYDBTest::TControllers::GetColumnShardController()->GetPingCheckPeriod(), TInstant::Now())) { + const TDuration stalenessLivetime = NYDBTest::TControllers::GetColumnShardController()->GetMaxReadStaleness(); + const TDuration stalenessInMem = NYDBTest::TControllers::GetColumnShardController()->GetMaxReadStalenessInMem(); + const TDuration usedLivetime = NYDBTest::TControllers::GetColumnShardController()->GetUsedSnapshotLivetime(); + AFL_VERIFY(usedLivetime < stalenessInMem || (stalenessInMem == usedLivetime && usedLivetime == TDuration::Zero()))("used", usedLivetime)( + "staleness", stalenessInMem); + const TDuration ping = 0.3 * std::min(stalenessInMem - usedLivetime, stalenessLivetime - stalenessInMem); + if (auto writeTx = InFlightReadsTracker.Ping(this, stalenessInMem, usedLivetime, TInstant::Now())) { Execute(writeTx.release(), ctx); } - ctx.Schedule(0.3 * GetMaxReadStaleness(), new TEvPrivate::TEvPingSnapshotsUsage()); + ctx.Schedule(NYDBTest::TControllers::GetColumnShardController()->GetStalenessLivetimePing(ping), new TEvPrivate::TEvPingSnapshotsUsage()); } void TColumnShard::Handle(TEvPrivate::TEvPeriodicWakeup::TPtr& ev, const TActorContext& ctx) { @@ -265,7 +303,6 @@ void TColumnShard::UpdateIndexCounters() { auto& stats = TablesManager.MutablePrimaryIndex().GetTotalStats(); const std::shared_ptr& counters = Counters.GetTabletCounters(); counters->SetCounter(COUNTER_INDEX_TABLES, stats.Tables); - counters->SetCounter(COUNTER_INDEX_COLUMN_RECORDS, stats.ColumnRecords); counters->SetCounter(COUNTER_INSERTED_PORTIONS, stats.GetInsertedStats().Portions); counters->SetCounter(COUNTER_INSERTED_BLOBS, stats.GetInsertedStats().Blobs); counters->SetCounter(COUNTER_INSERTED_ROWS, stats.GetInsertedStats().Rows); @@ -295,8 +332,7 @@ void TColumnShard::UpdateIndexCounters() { LOG_S_DEBUG("Index: tables " << stats.Tables << " inserted " << stats.GetInsertedStats().DebugString() << " compacted " << stats.GetCompactedStats().DebugString() << " s-compacted " << stats.GetSplitCompactedStats().DebugString() << " inactive " << stats.GetInactiveStats().DebugString() << " evicted " - << stats.GetEvictedStats().DebugString() << " column records " << stats.ColumnRecords << " at tablet " - << TabletID()); + << stats.GetEvictedStats().DebugString() << " at tablet " << TabletID()); } ui64 TColumnShard::MemoryUsage() const { @@ -393,7 +429,7 @@ void TColumnShard::SendPeriodicStats() { const TInstant now = TAppData::TimeProvider->Now(); if (LastStatsReport + StatsReportInterval > now) { - LOG_S_TRACE("Skip send periodic stats: report interavl = " << StatsReportInterval); + LOG_S_TRACE("Skip send periodic stats: report interval = " << StatsReportInterval); return; } LastStatsReport = now; diff --git a/ydb/core/tx/columnshard/columnshard.h b/ydb/core/tx/columnshard/columnshard.h index 186d665153af..d6424e0678e6 100644 --- a/ydb/core/tx/columnshard/columnshard.h +++ b/ydb/core/tx/columnshard/columnshard.h @@ -108,6 +108,7 @@ struct TEvColumnShard { public: std::optional ReadFromSnapshot; std::optional ReadToSnapshot; + TString TaskIdentifier; std::shared_ptr RangesFilter; public: void AddColumn(const ui32 id, const TString& columnName) { diff --git a/ydb/core/tx/columnshard/columnshard__init.cpp b/ydb/core/tx/columnshard/columnshard__init.cpp index 09cf1f4ef71f..f8743854a384 100644 --- a/ydb/core/tx/columnshard/columnshard__init.cpp +++ b/ydb/core/tx/columnshard/columnshard__init.cpp @@ -1,45 +1,51 @@ #include "columnshard_impl.h" -#include "columnshard_ttl.h" #include "columnshard_private_events.h" #include "columnshard_schema.h" + +#include "bg_tasks/adapter/adapter.h" +#include "bg_tasks/manager/manager.h" #include "blobs_action/storages_manager/manager.h" -#include "hooks/abstract/abstract.h" +#include "data_accessor/manager.h" #include "engines/column_engine_logs.h" -#include "bg_tasks/manager/manager.h" -#include "bg_tasks/adapter/adapter.h" -#include -#include +#include "hooks/abstract/abstract.h" +#include "loading/stages.h" +#include "tx_reader/abstract.h" +#include "tx_reader/composite.h" #include +#include #include - +#include +#include namespace NKikimr::NColumnShard { using namespace NTabletFlatExecutor; -class TTxInit : public TTransactionBase { +class TTxInit: public TTransactionBase { private: const TMonotonic StartInstant = TMonotonic::Now(); public: TTxInit(TColumnShard* self) - : TBase(self) - {} + : TBase(self) { + } bool Execute(TTransactionContext& txc, const TActorContext& ctx) override; void Complete(const TActorContext& ctx) override; - TTxType GetTxType() const override { return TXTYPE_INIT; } + TTxType GetTxType() const override { + return TXTYPE_INIT; + } private: - bool Precharge(TTransactionContext& txc); + std::shared_ptr StartReader; void SetDefaults(); - bool ReadEverything(TTransactionContext& txc, const TActorContext& ctx); + std::shared_ptr BuildReader(); }; void TTxInit::SetDefaults() { Self->CurrentSchemeShardId = 0; - Self->LastSchemaSeqNo = { }; + Self->LastSchemaSeqNo = {}; Self->ProcessingParams.reset(); Self->LastPlannedStep = 0; Self->LastPlannedTxId = 0; @@ -50,201 +56,39 @@ void TTxInit::SetDefaults() { Self->LongTxWritesByUniqueId.clear(); } -bool TTxInit::Precharge(TTransactionContext& txc) { - NIceDb::TNiceDb db(txc.DB); - - bool ready = true; - ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); - ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); - ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); - ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); - ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); - ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); - ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); - ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); - ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); - ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); - ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); - ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); - ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); - - ready = ready && Schema::GetSpecialValueOpt(db, Schema::EValueIds::CurrentSchemeShardId, Self->CurrentSchemeShardId); - ready = ready && Schema::GetSpecialValueOpt(db, Schema::EValueIds::LastSchemaSeqNoGeneration, Self->LastSchemaSeqNo.Generation); - ready = ready && Schema::GetSpecialValueOpt(db, Schema::EValueIds::LastSchemaSeqNoRound, Self->LastSchemaSeqNo.Round); - ready = ready && Schema::GetSpecialProtoValue(db, Schema::EValueIds::ProcessingParams, Self->ProcessingParams); - ready = ready && Schema::GetSpecialValueOpt(db, Schema::EValueIds::LastPlannedStep, Self->LastPlannedStep); - ready = ready && Schema::GetSpecialValueOpt(db, Schema::EValueIds::LastPlannedTxId, Self->LastPlannedTxId); - ready = ready && Schema::GetSpecialValueOpt(db, Schema::EValueIds::LastExportNumber, Self->LastExportNo); - ready = ready && Schema::GetSpecialValueOpt(db, Schema::EValueIds::OwnerPathId, Self->OwnerPathId); - ready = ready && Schema::GetSpecialValueOpt(db, Schema::EValueIds::OwnerPath, Self->OwnerPath); - - - { - ui64 lastCompletedStep = 0; - ui64 lastCompletedTx = 0; - ready = ready && Schema::GetSpecialValueOpt(db, Schema::EValueIds::LastCompletedStep, lastCompletedStep); - ready = ready && Schema::GetSpecialValueOpt(db, Schema::EValueIds::LastCompletedTxId, lastCompletedTx); - Self->LastCompletedTx = NOlap::TSnapshot(lastCompletedStep, lastCompletedTx); - } - - if (!ready) { - return false; - } - return true; -} - -bool TTxInit::ReadEverything(TTransactionContext& txc, const TActorContext& ctx) { - if (!Precharge(txc)) { - return false; - } - - NIceDb::TNiceDb db(txc.DB); - TBlobGroupSelector dsGroupSelector(Self->Info()); - NOlap::TDbWrapper dbTable(txc.DB, &dsGroupSelector); - { - ACFL_DEBUG("step", "TInsertTable::Load_Start"); - TMemoryProfileGuard g("TTxInit/InsertTable"); - auto localInsertTable = std::make_unique(); - if (!localInsertTable->Load(db, dbTable, TAppData::TimeProvider->Now())) { - ACFL_ERROR("step", "TInsertTable::Load_Fails"); - return false; - } - ACFL_DEBUG("step", "TInsertTable::Load_Finish"); - Self->InsertTable.swap(localInsertTable); - } - - { - ACFL_DEBUG("step", "TTxController::Load_Start"); - TMemoryProfileGuard g("TTxInit/TTxController"); - auto localTxController = std::make_unique(*Self); - if (!localTxController->Load(txc)) { - ACFL_ERROR("step", "TTxController::Load_Fails"); - return false; - } - ACFL_DEBUG("step", "TTxController::Load_Finish"); - Self->ProgressTxController.swap(localTxController); - } - - { - ACFL_DEBUG("step", "TOperationsManager::Load_Start"); - TMemoryProfileGuard g("TTxInit/TOperationsManager"); - auto localOperationsManager = std::make_unique(); - if (!localOperationsManager->Load(txc)) { - ACFL_ERROR("step", "TOperationsManager::Load_Fails"); - return false; - } - ACFL_DEBUG("step", "TOperationsManager::Load_Finish"); - Self->OperationsManager.swap(localOperationsManager); - } - - { - ACFL_DEBUG("step", "TStoragesManager::Load_Start"); - AFL_VERIFY(Self->StoragesManager); - TMemoryProfileGuard g("TTxInit/NDataSharing::TStoragesManager"); - if (!Self->StoragesManager->LoadIdempotency(txc.DB)) { - return false; - } - ACFL_DEBUG("step", "TStoragesManager::Load_Finish"); - } - - { - ACFL_DEBUG("step", "TTablesManager::Load_Start"); - TTablesManager tManagerLocal(Self->StoragesManager, Self->TabletID()); - { - TMemoryProfileGuard g("TTxInit/TTablesManager"); - if (!tManagerLocal.InitFromDB(db)) { - ACFL_ERROR("step", "TTablesManager::InitFromDB_Fails"); - return false; - } - } - { - TMemoryProfileGuard g("TTxInit/LoadIndex"); - if (!tManagerLocal.LoadIndex(dbTable)) { - ACFL_ERROR("step", "TTablesManager::LoadIndex_Fails"); - return false; - } - } - Self->TablesManager = std::move(tManagerLocal); - - Self->Counters.GetTabletCounters()->SetCounter(COUNTER_TABLES, Self->TablesManager.GetTables().size()); - Self->Counters.GetTabletCounters()->SetCounter(COUNTER_TABLE_PRESETS, Self->TablesManager.GetSchemaPresets().size()); - Self->Counters.GetTabletCounters()->SetCounter(COUNTER_TABLE_TTLS, Self->TablesManager.GetTtl().PathsCount()); - ACFL_DEBUG("step", "TTablesManager::Load_Finish"); - } - - { - TMemoryProfileGuard g("TTxInit/LongTxWrites"); - auto rowset = db.Table().Select(); - if (!rowset.IsReady()) { - return false; - } - - while (!rowset.EndOfSet()) { - const TInsertWriteId writeId = (TInsertWriteId)rowset.GetValue(); - const ui32 writePartId = rowset.GetValue(); - NKikimrLongTxService::TLongTxId proto; - Y_ABORT_UNLESS(proto.ParseFromString(rowset.GetValue())); - const auto longTxId = NLongTxService::TLongTxId::FromProto(proto); - - std::optional granuleShardingVersion; - if (rowset.HaveValue() && rowset.GetValue()) { - granuleShardingVersion = rowset.GetValue(); - } - - Self->LoadLongTxWrite(writeId, writePartId, longTxId, granuleShardingVersion); - - if (!rowset.Next()) { - return false; - } - } - } - { - TMemoryProfileGuard g("TTxInit/LocksDB"); - if (txc.DB.GetScheme().GetTableInfo(Schema::Locks::TableId)) { - TColumnShardLocksDb locksDb(*Self, txc); - if (!Self->SysLocks.Load(locksDb)) { - return false; - } - } - } - - { - TMemoryProfileGuard g("TTxInit/NDataSharing::TBackgroundSessionsManager"); - if (!Self->BackgroundSessionsManager->LoadIdempotency(txc)) { - return false; - } - } - - { - TMemoryProfileGuard g("TTxInit/NDataSharing::TSessionsManager"); - auto local = std::make_shared(); - if (!local->Load(txc.DB, Self->TablesManager.GetPrimaryIndexAsOptional())) { - return false; - } - Self->SharingSessionsManager = local; - } - { - TMemoryProfileGuard g("TTxInit/TInFlightReadsTracker"); - TInFlightReadsTracker local(Self->StoragesManager, Self->Counters.GetRequestsTracingCounters()); - if (!local.LoadFromDatabase(txc.DB)) { - return false; - } - Self->InFlightReadsTracker = std::move(local); - } - - Self->UpdateInsertTableCounters(); - Self->UpdateIndexCounters(); - Self->UpdateResourceMetrics(ctx, {}); - return true; +std::shared_ptr TTxInit::BuildReader() { + auto result = std::make_shared("composite_init"); + result->AddChildren(std::make_shared("special_values", Self)); + result->AddChildren(std::make_shared("tables_manager", Self)); + result->AddChildren(std::make_shared("insert_table", Self)); + result->AddChildren(std::make_shared("tx_controller", Self)); + result->AddChildren(std::make_shared("operations_manager", Self)); + result->AddChildren(std::make_shared("storages_manager", Self)); + result->AddChildren(std::make_shared("long_tx", Self)); + result->AddChildren(std::make_shared("db_locks", Self)); + result->AddChildren(std::make_shared("bg_sessions", Self)); + result->AddChildren(std::make_shared("sharing_sessions", Self)); + result->AddChildren(std::make_shared("in_flight_reads", Self)); + return result; } bool TTxInit::Execute(TTransactionContext& txc, const TActorContext& ctx) { - NActors::TLogContextGuard gLogging = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", Self->TabletID())("event", "initialize_shard"); + NActors::TLogContextGuard gLogging = + NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", Self->TabletID())("event", "initialize_shard"); LOG_S_DEBUG("TTxInit.Execute at tablet " << Self->TabletID()); try { - SetDefaults(); - return ReadEverything(txc, ctx); + if (!StartReader) { + SetDefaults(); + StartReader = BuildReader(); + } + if (!StartReader->Execute(txc, ctx)) { + return false; + } + StartReader = nullptr; + Self->UpdateInsertTableCounters(); + Self->UpdateIndexCounters(); + Self->UpdateResourceMetrics(ctx, {}); } catch (const TNotReadyTabletException&) { ACFL_ERROR("event", "tablet not ready"); return false; @@ -260,27 +104,35 @@ bool TTxInit::Execute(TTransactionContext& txc, const TActorContext& ctx) { void TTxInit::Complete(const TActorContext& ctx) { Self->Counters.GetCSCounters().Initialization.OnTxInitFinished(TMonotonic::Now() - StartInstant); - Self->ProgressTxController->OnTabletInit(); - Self->SwitchToWork(ctx); - NYDBTest::TControllers::GetColumnShardController()->OnTabletInitCompleted(*Self); + AFL_VERIFY(!Self->IsTxInitFinished); + Self->IsTxInitFinished = true; + + for (const auto& [pathId, tiering] : Self->TablesManager.GetTtl()) { + Self->Tiers->EnablePathId(pathId, tiering.GetUsedTiers()); + } + + Self->TrySwitchToWork(ctx); } -class TTxUpdateSchema : public TTransactionBase { +class TTxUpdateSchema: public TTransactionBase { std::vector NormalizerTasks; const TMonotonic StartInstant = TMonotonic::Now(); public: TTxUpdateSchema(TColumnShard* self) - : TBase(self) - {} + : TBase(self) { + } bool Execute(TTransactionContext& txc, const TActorContext& ctx) override; void Complete(const TActorContext& ctx) override; - TTxType GetTxType() const override { return TXTYPE_UPDATE_SCHEMA; } + TTxType GetTxType() const override { + return TXTYPE_UPDATE_SCHEMA; + } }; bool TTxUpdateSchema::Execute(TTransactionContext& txc, const TActorContext&) { - NActors::TLogContextGuard gLogging = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", Self->TabletID())("event", "initialize_shard"); + NActors::TLogContextGuard gLogging = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", Self->TabletID())( + "process", "TTxUpdateSchema::Execute"); ACFL_INFO("step", "TTxUpdateSchema.Execute_Start")("details", Self->NormalizerController.DebugString()); while (!Self->NormalizerController.IsNormalizationFinished()) { @@ -306,6 +158,8 @@ bool TTxUpdateSchema::Execute(TTransactionContext& txc, const TActorContext&) { } void TTxUpdateSchema::Complete(const TActorContext& ctx) { + NActors::TLogContextGuard gLogging = + NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", Self->TabletID())("process", "TTxUpdateSchema::Complete"); AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("step", "TTxUpdateSchema.Complete"); Self->Counters.GetCSCounters().Initialization.OnTxUpdateSchemaFinished(TMonotonic::Now() - StartInstant); if (NormalizerTasks.empty()) { @@ -324,29 +178,39 @@ void TTxUpdateSchema::Complete(const TActorContext& ctx) { } } -class TTxApplyNormalizer : public TTransactionBase { +class TTxApplyNormalizer: public TTransactionBase { public: TTxApplyNormalizer(TColumnShard* self, NOlap::INormalizerChanges::TPtr changes) : TBase(self) - , Changes(changes) - {} + , IsDryRun(self->NormalizerController.GetNormalizer()->GetIsDryRun()) + , Changes(changes) { + } bool Execute(TTransactionContext& txc, const TActorContext& ctx) override; void Complete(const TActorContext& ctx) override; - TTxType GetTxType() const override { return TXTYPE_APPLY_NORMALIZER; } + TTxType GetTxType() const override { + return TXTYPE_APPLY_NORMALIZER; + } private: + const bool IsDryRun; + bool NormalizerFinished = false; NOlap::INormalizerChanges::TPtr Changes; }; bool TTxApplyNormalizer::Execute(TTransactionContext& txc, const TActorContext&) { - NActors::TLogContextGuard gLogging = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", Self->TabletID())("event", "initialize_shard"); - AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("step", "TTxApplyNormalizer.Execute")("details", Self->NormalizerController.DebugString()); - if (!Changes->ApplyOnExecute(txc, Self->NormalizerController)) { - return false; + NActors::TLogContextGuard gLogging = + NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", Self->TabletID())("event", "TTxApplyNormalizer::Execute"); + AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("step", "TTxApplyNormalizer.Execute")("details", Self->NormalizerController.DebugString())( + "dry_run", IsDryRun); + if (!IsDryRun) { + if (!Changes->ApplyOnExecute(txc, Self->NormalizerController)) { + return false; + } } - if (Self->NormalizerController.GetNormalizer()->GetActiveTasksCount() == 1) { + if (Self->NormalizerController.GetNormalizer()->DecActiveCounters() == 0) { + NormalizerFinished = true; NIceDb::TNiceDb db(txc.DB); Self->NormalizerController.OnNormalizerFinished(db); } @@ -354,12 +218,18 @@ bool TTxApplyNormalizer::Execute(TTransactionContext& txc, const TActorContext&) } void TTxApplyNormalizer::Complete(const TActorContext& ctx) { - AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("step", "TTxApplyNormalizer.Complete")("tablet_id", Self->TabletID())("event", "initialize_shard"); + NActors::TLogContextGuard gLogging = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", Self->TabletID())( + "event", "TTxApplyNormalizer::Complete"); AFL_VERIFY(!Self->NormalizerController.IsNormalizationFinished())("details", Self->NormalizerController.DebugString()); - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("tablet_id", Self->TabletID())("event", "apply_normalizer_changes")("details", Self->NormalizerController.DebugString())("size", Changes->GetSize()); - Changes->ApplyOnComplete(Self->NormalizerController); - Self->NormalizerController.GetNormalizer()->OnResultReady(); - if (Self->NormalizerController.GetNormalizer()->HasActiveTasks()) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "apply_normalizer_changes")("details", Self->NormalizerController.DebugString())( + "size", Changes->GetSize())("dry_run", IsDryRun); + if (IsDryRun) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "normalizer_changes_dry_run")( + "normalizer", Self->NormalizerController.GetNormalizer()->GetClassName())("changes", Changes->DebugString()); + } else { + Changes->ApplyOnComplete(Self->NormalizerController); + } + if (!NormalizerFinished) { return; } @@ -372,21 +242,25 @@ void TTxApplyNormalizer::Complete(const TActorContext& ctx) { } /// Create local database on tablet start if none -class TTxInitSchema : public TTransactionBase { +class TTxInitSchema: public TTransactionBase { private: const TMonotonic StartInstant = TMonotonic::Now(); public: TTxInitSchema(TColumnShard* self) - : TBase(self) - {} + : TBase(self) { + } bool Execute(TTransactionContext& txc, const TActorContext& ctx) override; void Complete(const TActorContext& ctx) override; - TTxType GetTxType() const override { return TXTYPE_INIT_SCHEMA; } + TTxType GetTxType() const override { + return TXTYPE_INIT_SCHEMA; + } }; bool TTxInitSchema::Execute(TTransactionContext& txc, const TActorContext&) { + NActors::TLogContextGuard gLogging = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", Self->TabletID())( + "process", "TTxInitSchema::Execute"); LOG_S_DEBUG("TxInitSchema.Execute at tablet " << Self->TabletID()); const bool isFirstRun = txc.DB.GetScheme().IsEmpty(); @@ -399,7 +273,7 @@ bool TTxInitSchema::Execute(TTransactionContext& txc, const TActorContext&) { } } { - NOlap::TNormalizationController::TInitContext initCtx(Self->Info()); + NOlap::TNormalizationController::TInitContext initCtx(Self->Info(), Self->TabletID(), Self->SelfId()); Self->NormalizerController.InitNormalizers(initCtx); } @@ -416,21 +290,16 @@ bool TTxInitSchema::Execute(TTransactionContext& txc, const TActorContext&) { // Enable compression for the SmallBlobs table const auto* smallBlobsDefaultColumnFamily = txc.DB.GetScheme().DefaultFamilyFor(Schema::SmallBlobs::TableId); - if (!smallBlobsDefaultColumnFamily || - smallBlobsDefaultColumnFamily->Codec != NTable::TAlter::ECodec::LZ4) - { - txc.DB.Alter().SetFamily(Schema::SmallBlobs::TableId, 0, - NTable::TAlter::ECache::None, NTable::TAlter::ECodec::LZ4); + if (!smallBlobsDefaultColumnFamily || smallBlobsDefaultColumnFamily->Codec != NTable::TAlter::ECodec::LZ4) { + txc.DB.Alter().SetFamily(Schema::SmallBlobs::TableId, 0, NTable::TAlter::ECache::None, NTable::TAlter::ECodec::LZ4); } // SmallBlobs table has compaction policy suitable for a big table const auto* smallBlobsTable = txc.DB.GetScheme().GetTableInfo(Schema::SmallBlobs::TableId); NLocalDb::TCompactionPolicyPtr bigTableCompactionPolicy = NLocalDb::CreateDefaultUserTablePolicy(); bigTableCompactionPolicy->MinDataPageSize = 32 * 1024; - if (!smallBlobsTable || - !smallBlobsTable->CompactionPolicy || - smallBlobsTable->CompactionPolicy->Generations.size() != bigTableCompactionPolicy->Generations.size()) - { + if (!smallBlobsTable || !smallBlobsTable->CompactionPolicy || + smallBlobsTable->CompactionPolicy->Generations.size() != bigTableCompactionPolicy->Generations.size()) { txc.DB.Alter().SetCompactionPolicy(Schema::SmallBlobs::TableId, *bigTableCompactionPolicy); } @@ -438,6 +307,8 @@ bool TTxInitSchema::Execute(TTransactionContext& txc, const TActorContext&) { } void TTxInitSchema::Complete(const TActorContext& ctx) { + NActors::TLogContextGuard gLogging = + NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", Self->TabletID())("process", "TTxInitSchema::Complete"); Self->Counters.GetCSCounters().Initialization.OnTxInitSchemaFinished(TMonotonic::Now() - StartInstant); LOG_S_DEBUG("TxInitSchema.Complete at tablet " << Self->TabletID();); Self->Execute(new TTxUpdateSchema(Self), ctx); @@ -451,4 +322,4 @@ void TColumnShard::Handle(TEvPrivate::TEvNormalizerResult::TPtr& ev, const TActo Execute(new TTxApplyNormalizer(this, ev->Get()->GetChanges()), ctx); } -} +} // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/columnshard/columnshard__progress_tx.cpp b/ydb/core/tx/columnshard/columnshard__progress_tx.cpp index 73a4a0200d97..44bb0a27f860 100644 --- a/ydb/core/tx/columnshard/columnshard__progress_tx.cpp +++ b/ydb/core/tx/columnshard/columnshard__progress_tx.cpp @@ -28,9 +28,12 @@ class TColumnShard::TTxProgressTx: public TTransactionBase { } bool Execute(TTransactionContext& txc, const TActorContext& ctx) override { - NActors::TLogContextGuard logGuard = - NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", Self->TabletID())("tx_state", "execute"); - Y_ABORT_UNLESS(Self->ProgressTxInFlight); + NActors::TLogContextGuard logGuard = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD_TX)("tablet_id", Self->TabletID())( + "tx_state", "TTxProgressTx::Execute")("tx_current", Self->ProgressTxInFlight); + if (!Self->ProgressTxInFlight) { + AbortedThroughRemoveExpired = true; + return true; + } Self->Counters.GetTabletCounters()->SetCounter(COUNTER_TX_COMPLETE_LAG, Self->GetTxCompleteLag().MilliSeconds()); const size_t removedCount = Self->ProgressTxController->CleanExpiredTxs(txc); @@ -45,15 +48,24 @@ class TColumnShard::TTxProgressTx: public TTransactionBase { const auto plannedItem = Self->ProgressTxController->GetFirstPlannedTx(); if (!!plannedItem) { PlannedQueueItem.emplace(plannedItem->PlanStep, plannedItem->TxId); - ui64 step = plannedItem->PlanStep; - ui64 txId = plannedItem->TxId; + const ui64 step = plannedItem->PlanStep; + const ui64 txId = plannedItem->TxId; + NActors::TLogContextGuard logGuardTx = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD_TX)("tx_id", txId); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "PlannedItemStart"); TxOperator = Self->ProgressTxController->GetTxOperatorVerified(txId); if (auto txPrepare = TxOperator->BuildTxPrepareForProgress(Self)) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "PlannedItemStart")("details", "BuildTxPrepareForProgress"); AbortedThroughRemoveExpired = true; Self->ProgressTxInFlight = txId; Self->Execute(txPrepare.release(), ctx); return true; + } else if (TxOperator->IsInProgress()) { + AbortedThroughRemoveExpired = true; + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "PlannedItemContinue"); + AFL_VERIFY(Self->ProgressTxInFlight == txId); + return true; } else { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "PlannedItemStart")("details", "PopFirstPlannedTx"); Self->ProgressTxController->PopFirstPlannedTx(); } StartExecution = TMonotonic::Now(); @@ -80,8 +92,9 @@ class TColumnShard::TTxProgressTx: public TTransactionBase { if (AbortedThroughRemoveExpired) { return; } - NActors::TLogContextGuard logGuard = - NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", Self->TabletID())("tx_state", "complete"); + NActors::TLogContextGuard logGuard = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD_TX)( + "tablet_id", Self->TabletID())( + "tx_state", "TTxProgressTx::Complete"); if (TxOperator) { TxOperator->ProgressOnComplete(*Self, ctx); Self->RescheduleWaitingReads(); @@ -104,11 +117,13 @@ class TColumnShard::TTxProgressTx: public TTransactionBase { }; void TColumnShard::EnqueueProgressTx(const TActorContext& ctx, const std::optional continueTxId) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "EnqueueProgressTx")("tablet_id", TabletID()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "EnqueueProgressTx")("tablet_id", TabletID())("tx_id", continueTxId); if (continueTxId) { AFL_VERIFY(!ProgressTxInFlight || ProgressTxInFlight == continueTxId)("current", ProgressTxInFlight)("expected", continueTxId); } if (!ProgressTxInFlight || ProgressTxInFlight == continueTxId) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "EnqueueProgressTxStart")("tablet_id", TabletID())("tx_id", continueTxId)( + "tx_current", ProgressTxInFlight); ProgressTxInFlight = continueTxId.value_or(0); Execute(new TTxProgressTx(this), ctx); } diff --git a/ydb/core/tx/columnshard/columnshard__propose_transaction.cpp b/ydb/core/tx/columnshard/columnshard__propose_transaction.cpp index d4ded82be3d8..55cff6c401f8 100644 --- a/ydb/core/tx/columnshard/columnshard__propose_transaction.cpp +++ b/ydb/core/tx/columnshard/columnshard__propose_transaction.cpp @@ -146,7 +146,6 @@ class TTxProposeTransaction: public NTabletFlatExecutor::TTransactionBaseTablesManager.GetPrimaryIndexSafe().GetVersionedIndex().GetLastSchema(); - auto schema = schemaSnapshot->GetSchema(); auto index = schemaSnapshot->GetColumnIdOptional(columnName); if (!index) { return TTxController::TProposeResult( @@ -164,7 +163,7 @@ class TTxProposeTransaction: public NTabletFlatExecutor::TTransactionBaseSetupTtl(pathTtls)) { return TTxController::TProposeResult(NKikimrTxColumnShard::EResultStatus::SCHEMA_ERROR, "TTL not started"); } - Self->TablesManager.MutablePrimaryIndex().OnTieringModified(Self->Tiers, Self->TablesManager.GetTtl(), {}); + Self->TablesManager.MutablePrimaryIndex().OnTieringModified(Self->TablesManager.GetTtl()); return TTxController::TProposeResult(); } diff --git a/ydb/core/tx/columnshard/columnshard__statistics.cpp b/ydb/core/tx/columnshard/columnshard__statistics.cpp index 84b1a89982b8..46c81c59bbb5 100644 --- a/ydb/core/tx/columnshard/columnshard__statistics.cpp +++ b/ydb/core/tx/columnshard/columnshard__statistics.cpp @@ -26,6 +26,179 @@ void TColumnShard::Handle(NStat::TEvStatistics::TEvAnalyzeTable::TPtr& ev, const Send(ev->Sender, response.release(), 0, ev->Cookie); } +class TResultAccumulator { +private: + TMutex Mutex; + THashMap> Calculated; + TAtomicCounter ResultsCount = 0; + TAtomicCounter WaitingCount = 0; + const NActors::TActorId RequestSenderActorId; + bool Started = false; + const ui64 Cookie; + std::unique_ptr Response; + bool Replied = false; + + void OnResultReady() { + AFL_VERIFY(!Replied); + Replied = true; + auto& respRecord = Response->Record; + respRecord.SetStatus(NKikimrStat::TEvStatisticsResponse::STATUS_SUCCESS); + + for (auto&& [columnTag, sketch] : Calculated) { + if (!sketch) { + continue; + } + + auto* column = respRecord.AddColumns(); + column->SetTag(columnTag); + auto* statistic = column->AddStatistics(); + statistic->SetType(NStat::COUNT_MIN_SKETCH); + statistic->SetData(TString(sketch->AsStringBuf())); + } + + NActors::TActivationContext::Send(RequestSenderActorId, std::move(Response), 0, Cookie); + } + +public: + TResultAccumulator(const std::set& tags, const NActors::TActorId& requestSenderActorId, const ui64 cookie, + std::unique_ptr&& response) + : RequestSenderActorId(requestSenderActorId) + , Cookie(cookie) + , Response(std::move(response)) + { + for (auto&& i : tags) { + AFL_VERIFY(Calculated.emplace(i, nullptr).second); + } + } + + void AddResult(THashMap>&& sketch) { + { + TGuard g(Mutex); + for (auto&& i : sketch) { + auto it = Calculated.find(i.first); + AFL_VERIFY(it != Calculated.end()); + if (!it->second) { + it->second = std::move(i.second); + } else { + *it->second += *i.second; + } + } + } + const i64 count = ResultsCount.Inc(); + if (count == WaitingCount.Val()) { + OnResultReady(); + } else { + AFL_VERIFY(count < WaitingCount.Val()); + } + } + + void AddWaitingTask() { + AFL_VERIFY(!Started); + WaitingCount.Inc(); + } + + void Start() { + AFL_VERIFY(!Started); + Started = true; + if (WaitingCount.Val() == ResultsCount.Val()) { + OnResultReady(); + } + } + +}; + +class TColumnPortionsAccumulator { +private: + const std::set ColumnTagsRequested; + std::vector Portions; + const ui32 PortionsCountLimit = 10000; + std::shared_ptr DataAccessors; + std::shared_ptr Result; + const std::shared_ptr VersionedIndex; + +public: + TColumnPortionsAccumulator(const std::shared_ptr& result, const ui32 portionsCountLimit, + const std::set& originalColumnTags, const std::shared_ptr& vIndex, + const std::shared_ptr& dataAccessorsManager) + : ColumnTagsRequested(originalColumnTags) + , PortionsCountLimit(portionsCountLimit) + , DataAccessors(dataAccessorsManager) + , Result(result) + , VersionedIndex(vIndex) + { + } + + class TMetadataSubscriber: public NOlap::IDataAccessorRequestsSubscriber { + private: + const std::shared_ptr Result; + std::shared_ptr VersionedIndex; + const std::set ColumnTagsRequested; + virtual const std::shared_ptr& DoGetAbortionFlag() const override { + return Default>(); + } + + virtual void DoOnRequestsFinished(NOlap::TDataAccessorsResult&& result) override { + THashMap> sketchesByColumns; + for (auto id : ColumnTagsRequested) { + sketchesByColumns.emplace(id, TCountMinSketch::Create()); + } + + for (const auto& [id, portionInfo] : result.GetPortions()) { + std::shared_ptr portionSchema = portionInfo.GetPortionInfo().GetSchema(*VersionedIndex); + for (const ui32 columnId : ColumnTagsRequested) { + auto indexMeta = portionSchema->GetIndexInfo().GetIndexMetaCountMinSketch({ columnId }); + + if (!indexMeta) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("error", "Missing countMinSketch index for columnId " + ToString(columnId)); + continue; + } + AFL_VERIFY(indexMeta->GetColumnIds().size() == 1); + + const std::vector data = portionInfo.GetIndexInplaceDataVerified(indexMeta->GetIndexId()); + + for (const auto& sketchAsString : data) { + auto sketch = + std::unique_ptr(TCountMinSketch::FromString(sketchAsString.data(), sketchAsString.size())); + *sketchesByColumns[columnId] += *sketch; + } + } + } + Result->AddResult(std::move(sketchesByColumns)); + } + + public: + TMetadataSubscriber( + const std::shared_ptr& result, const std::shared_ptr& vIndex, const std::set& tags) + : Result(result) + , VersionedIndex(vIndex) + , ColumnTagsRequested(tags) + { + + } + }; + + void Flush() { + if (!Portions.size()) { + return; + } + Result->AddWaitingTask(); + std::shared_ptr request = std::make_shared("STATISTICS_FLUSH"); + for (auto&& i : Portions) { + request->AddPortion(i); + } + request->RegisterSubscriber(std::make_shared(Result, VersionedIndex, ColumnTagsRequested)); + Portions.clear(); + DataAccessors->AskData(request); + } + + void AddTask(const NOlap::TPortionInfo::TConstPtr& portion) { + Portions.emplace_back(portion); + if (Portions.size() >= PortionsCountLimit) { + Flush(); + } + } +}; + void TColumnShard::Handle(NStat::TEvStatistics::TEvStatisticsRequest::TPtr& ev, const TActorContext&) { const auto& record = ev->Get()->Record; @@ -57,45 +230,20 @@ void TColumnShard::Handle(NStat::TEvStatistics::TEvStatisticsRequest::TPtr& ev, columnTagsRequested = std::set(allColumnIds.begin(), allColumnIds.end()); } - std::map> sketchesByColumns; - for (auto id : columnTagsRequested) { - sketchesByColumns.emplace(id, TCountMinSketch::Create()); - } + NOlap::TDataAccessorsRequest request("STATISTICS"); + std::shared_ptr resultAccumulator = + std::make_shared(columnTagsRequested, ev->Sender, ev->Cookie, std::move(response)); + auto versionedIndex = std::make_shared(index.GetVersionedIndex()); + TColumnPortionsAccumulator portionsPack(resultAccumulator, 1000, columnTagsRequested, versionedIndex, DataAccessorsManager.GetObjectPtrVerified()); for (const auto& [_, portionInfo] : spg->GetPortions()) { - if (portionInfo->IsVisible(GetMaxReadVersion())) { - std::shared_ptr portionSchema = portionInfo->GetSchema(index.GetVersionedIndex()); - for (ui32 columnId : columnTagsRequested) { - auto indexMeta = portionSchema->GetIndexInfo().GetIndexMetaCountMinSketch({columnId}); - - if (!indexMeta) { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("error", "Missing countMinSketch index for columnId " + ToString(columnId)); - continue; - } - AFL_VERIFY(indexMeta->GetColumnIds().size() == 1); - - const std::vector data = portionInfo->GetIndexInplaceDataVerified(indexMeta->GetIndexId()); - - for (const auto& sketchAsString : data) { - auto sketch = std::unique_ptr(TCountMinSketch::FromString(sketchAsString.data(), sketchAsString.size())); - *sketchesByColumns[columnId] += *sketch; - } - } + if (!portionInfo->IsVisible(GetMaxReadVersion())) { + continue; } + portionsPack.AddTask(portionInfo); } - - respRecord.SetStatus(NKikimrStat::TEvStatisticsResponse::STATUS_SUCCESS); - - for (ui32 columnTag : columnTagsRequested) { - auto* column = respRecord.AddColumns(); - column->SetTag(columnTag); - - auto* statistic = column->AddStatistics(); - statistic->SetType(NStat::COUNT_MIN_SKETCH); - statistic->SetData(TString(sketchesByColumns[columnTag]->AsStringBuf())); - } - - Send(ev->Sender, response.release(), 0, ev->Cookie); + portionsPack.Flush(); + resultAccumulator->Start(); } } diff --git a/ydb/core/tx/columnshard/columnshard__write.cpp b/ydb/core/tx/columnshard/columnshard__write.cpp index bab3ca229fdd..c8cb71b372a5 100644 --- a/ydb/core/tx/columnshard/columnshard__write.cpp +++ b/ydb/core/tx/columnshard/columnshard__write.cpp @@ -1,5 +1,6 @@ #include "columnshard_impl.h" +#include "blobs_action/transaction/tx_blobs_written.h" #include "blobs_action/transaction/tx_draft.h" #include "blobs_action/transaction/tx_write.h" #include "common/limits.h" @@ -45,18 +46,18 @@ void TColumnShard::OverloadWriteFail(const EOverloadStatus overloadReason, const Y_ABORT("invalid function usage"); } - AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "write_overload")("size", writeSize)("path_id", writeMeta.GetTableId())( + AFL_TRACE(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "write_overload")("size", writeSize)("path_id", writeMeta.GetTableId())( "reason", overloadReason); ctx.Send(writeMeta.GetSource(), event.release(), 0, cookie); } -TColumnShard::EOverloadStatus TColumnShard::CheckOverloaded(const ui64 tableId) const { +TColumnShard::EOverloadStatus TColumnShard::CheckOverloaded(const ui64 pathId) const { if (IsAnyChannelYellowStop()) { return EOverloadStatus::Disk; } - if (InsertTable && InsertTable->IsOverloadedByCommitted(tableId)) { + if (InsertTable && InsertTable->IsOverloadedByCommitted(pathId)) { return EOverloadStatus::InsertTable; } @@ -69,26 +70,65 @@ TColumnShard::EOverloadStatus TColumnShard::CheckOverloaded(const ui64 tableId) ui64 writesLimit = Settings.OverloadWritesInFlight; ui64 writesSizeLimit = Settings.OverloadWritesSizeInFlight; if (txLimit && Executor()->GetStats().TxInFly > txLimit) { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "shard_overload")("reason", "tx_in_fly")("sum", Executor()->GetStats().TxInFly)( + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "shard_overload")("reason", "tx_in_fly")("sum", Executor()->GetStats().TxInFly)( "limit", txLimit); return EOverloadStatus::ShardTxInFly; } if (writesLimit && Counters.GetWritesMonitor()->GetWritesInFlight() > writesLimit) { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "shard_overload")("reason", "writes_in_fly")( + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "shard_overload")("reason", "writes_in_fly")( "sum", Counters.GetWritesMonitor()->GetWritesInFlight())("limit", writesLimit); return EOverloadStatus::ShardWritesInFly; } if (writesSizeLimit && Counters.GetWritesMonitor()->GetWritesSizeInFlight() > writesSizeLimit) { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "shard_overload")("reason", "writes_size_in_fly")( + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "shard_overload")("reason", "writes_size_in_fly")( "sum", Counters.GetWritesMonitor()->GetWritesSizeInFlight())("limit", writesSizeLimit); return EOverloadStatus::ShardWritesSizeInFly; } return EOverloadStatus::None; } +void TColumnShard::Handle(NPrivateEvents::NWrite::TEvWritePortionResult::TPtr& ev, const TActorContext& ctx) { + TMemoryProfileGuard mpg("TEvWritePortionResult"); + NActors::TLogContextGuard gLogging = + NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD_WRITE)("tablet_id", TabletID())("event", "TEvWritePortionResult"); + std::vector noDataWrites = ev->Get()->DetachNoDataWrites(); + for (auto&& i : noDataWrites) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "no_data_write_finished")("writing_size", i.GetDataSize())("writing_id", i.GetWriteMeta().GetId()); + Counters.GetWritesMonitor()->OnFinishWrite(i.GetDataSize(), 1); + } + if (ev->Get()->GetWriteStatus() == NKikimrProto::OK) { + std::vector writtenPacks = ev->Get()->DetachInsertedPacks(); + const TMonotonic now = TMonotonic::Now(); + for (auto&& i : writtenPacks) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_WRITE)("writing_size", i.GetDataSize())("event", "data_write_finished")( + "writing_id", i.GetWriteMeta().GetId()); + Counters.OnWritePutBlobsSuccess(now - i.GetWriteMeta().GetWriteStartInstant(), i.GetRecordsCount()); + Counters.GetWritesMonitor()->OnFinishWrite(i.GetDataSize(), 1); + } + Execute(new TTxBlobsWritingFinished( + this, ev->Get()->GetWriteStatus(), ev->Get()->GetWriteAction(), std::move(writtenPacks), std::move(noDataWrites)), + ctx); + } else { + if (noDataWrites.size()) { + Execute(new TTxBlobsWritingFinished(this, NKikimrProto::OK, ev->Get()->GetWriteAction(), {}, std::move(noDataWrites)), ctx); + } + + std::vector writtenPacks = ev->Get()->DetachInsertedPacks(); + const TMonotonic now = TMonotonic::Now(); + for (auto&& i : writtenPacks) { + Counters.OnWritePutBlobsFailed(now - i.GetWriteMeta().GetWriteStartInstant(), i.GetRecordsCount()); + Counters.GetCSCounters().OnWritePutBlobsFail(now - i.GetWriteMeta().GetWriteStartInstant()); + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_WRITE)("writing_size", i.GetDataSize())("event", "data_write_error")( + "writing_id", i.GetWriteMeta().GetId()); + Counters.GetWritesMonitor()->OnFinishWrite(i.GetDataSize(), 1); + } + Execute(new TTxBlobsWritingFailed(this, ev->Get()->GetWriteStatus(), std::move(writtenPacks)), ctx); + } +} + void TColumnShard::Handle(TEvPrivate::TEvWriteBlobsResult::TPtr& ev, const TActorContext& ctx) { NActors::TLogContextGuard gLogging = - NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", TabletID())("event", "TEvWriteBlobsResult"); + NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD_WRITE)("tablet_id", TabletID())("event", "TEvWriteBlobsResult"); auto& putResult = ev->Get()->GetPutResult(); OnYellowChannels(putResult); @@ -96,21 +136,11 @@ void TColumnShard::Handle(TEvPrivate::TEvWriteBlobsResult::TPtr& ev, const TActo auto baseAggregations = wBuffer.GetAggregations(); wBuffer.InitReplyReceived(TMonotonic::Now()); - Counters.GetWritesMonitor()->OnFinishWrite(wBuffer.GetSumSize(), wBuffer.GetAggregations().size()); - for (auto&& aggr : baseAggregations) { const auto& writeMeta = aggr->GetWriteMeta(); - - if (!TablesManager.IsReadyForWrite(writeMeta.GetTableId())) { - ACFL_ERROR("event", "absent_pathId")("path_id", writeMeta.GetTableId())("has_index", TablesManager.HasPrimaryIndex()); - Counters.GetTabletCounters()->IncCounter(COUNTER_WRITE_FAIL); - - auto result = std::make_unique(TabletID(), writeMeta, NKikimrTxColumnShard::EResultStatus::ERROR); - ctx.Send(writeMeta.GetSource(), result.release()); - Counters.GetCSCounters().OnFailedWriteResponse(EWriteFailReason::NoTable); - wBuffer.RemoveData(aggr, StoragesManager->GetInsertOperator()); - continue; - } + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "blobs_write_finished")("writing_size", aggr->GetSize())( + "writing_id", writeMeta.GetId())("status", putResult.GetPutStatus()); + Counters.GetWritesMonitor()->OnFinishWrite(aggr->GetSize(), 1); if (putResult.GetPutStatus() != NKikimrProto::OK) { Counters.GetCSCounters().OnWritePutBlobsFail(TMonotonic::Now() - writeMeta.GetWriteStartInstant()); @@ -162,20 +192,20 @@ void TColumnShard::Handle(TEvColumnShard::TEvWrite::TPtr& ev, const TActorContex Counters.GetCSCounters().OnStartWriteRequest(); const auto& record = Proto(ev->Get()); - const ui64 tableId = record.GetTableId(); + const ui64 pathId = record.GetTableId(); const ui64 writeId = record.GetWriteId(); const ui64 cookie = ev->Cookie; const TString dedupId = record.GetDedupId(); const auto source = ev->Sender; - Counters.GetColumnTablesCounters()->GetPathIdCounter(tableId)->OnWriteEvent(); + Counters.GetColumnTablesCounters()->GetPathIdCounter(pathId)->OnWriteEvent(); std::optional granuleShardingVersion; if (record.HasGranuleShardingVersion()) { granuleShardingVersion = record.GetGranuleShardingVersion(); } - NEvWrite::TWriteMeta writeMeta(writeId, tableId, source, granuleShardingVersion); + NEvWrite::TWriteMeta writeMeta(writeId, pathId, source, granuleShardingVersion, TGUID::CreateTimebased().AsGuidString()); if (record.HasModificationType()) { writeMeta.SetModificationType(TEnumOperator::DeserializeFromProto(record.GetModificationType())); } @@ -193,11 +223,11 @@ void TColumnShard::Handle(TEvColumnShard::TEvWrite::TPtr& ev, const TActorContex }; if (!AppDataVerified().ColumnShardConfig.GetWritingEnabled()) { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "skip_writing")("reason", "disabled"); + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "skip_writing")("reason", "disabled"); return returnFail(COUNTER_WRITE_FAIL, EWriteFailReason::Disabled); } - if (!TablesManager.IsReadyForWrite(tableId)) { + if (!TablesManager.IsReadyForStartWrite(pathId, false)) { LOG_S_NOTICE("Write (fail) into pathId:" << writeMeta.GetTableId() << (TablesManager.HasPrimaryIndex() ? "" : " no index") << " at tablet " << TabletID()); @@ -205,24 +235,13 @@ void TColumnShard::Handle(TEvColumnShard::TEvWrite::TPtr& ev, const TActorContex } { - auto& portionsIndex = - TablesManager.GetPrimaryIndexAsVerified().GetGranuleVerified(writeMeta.GetTableId()).GetPortionsIndex(); - { - const ui64 minMemoryRead = portionsIndex.GetMinRawMemoryRead(); - if (NOlap::TGlobalLimits::DefaultReduceMemoryIntervalLimit < minMemoryRead) { - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("event", "overlimit")("reason", "read_raw_memory")("current", minMemoryRead)( - "limit", NOlap::TGlobalLimits::DefaultReduceMemoryIntervalLimit)("table_id", writeMeta.GetTableId()); - return returnFail(COUNTER_WRITE_FAIL, EWriteFailReason::OverlimitReadRawMemory); - } - } - - { - const ui64 minMemoryRead = portionsIndex.GetMinBlobMemoryRead(); - if (NOlap::TGlobalLimits::DefaultBlobsMemoryIntervalLimit < minMemoryRead) { - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("event", "overlimit")("reason", "read_blob_memory")("current", minMemoryRead)( - "limit", NOlap::TGlobalLimits::DefaultBlobsMemoryIntervalLimit)("table_id", writeMeta.GetTableId()); - return returnFail(COUNTER_WRITE_FAIL, EWriteFailReason::OverlimitReadBlobMemory); - } + auto status = TablesManager.GetPrimaryIndexAsVerified() + .GetGranuleVerified(writeMeta.GetTableId()) + .GetOptimizerPlanner() + .CheckWriteData(); + if (status.IsFail()) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "writing_fail_through_compaction")("reason", status.GetErrorMessage()); + return returnFail(COUNTER_WRITE_FAIL, EWriteFailReason::CompactionCriteria); } } @@ -235,8 +254,8 @@ void TColumnShard::Handle(TEvColumnShard::TEvWrite::TPtr& ev, const TActorContex } NEvWrite::TWriteData writeData(writeMeta, arrowData, snapshotSchema->GetIndexInfo().GetReplaceKey(), - StoragesManager->GetInsertOperator()->StartWritingAction(NOlap::NBlobOperations::EConsumer::WRITING)); - auto overloadStatus = CheckOverloaded(tableId); + StoragesManager->GetInsertOperator()->StartWritingAction(NOlap::NBlobOperations::EConsumer::WRITING), false); + auto overloadStatus = CheckOverloaded(pathId); if (overloadStatus != EOverloadStatus::None) { std::unique_ptr result = std::make_unique( TabletID(), writeData.GetWriteMeta(), NKikimrTxColumnShard::EResultStatus::OVERLOADED); @@ -262,8 +281,11 @@ void TColumnShard::Handle(TEvColumnShard::TEvWrite::TPtr& ev, const TActorContex << (writeMeta.GetWriteId() ? (" writeId " + ToString(writeMeta.GetWriteId())).c_str() : " ") << Counters.GetWritesMonitor()->DebugString() << " at tablet " << TabletID()); writeData.MutableWriteMeta().SetWriteMiddle1StartInstant(TMonotonic::Now()); - std::shared_ptr task = std::make_shared(TabletID(), SelfId(), BufferizationWriteActorId, - std::move(writeData), snapshotSchema, GetLastTxSnapshot(), Counters.GetCSCounters().WritingCounters); + + NOlap::TWritingContext context(TabletID(), SelfId(), snapshotSchema, StoragesManager, Counters.GetIndexationCounters().SplitterCounters, + Counters.GetCSCounters().WritingCounters, GetLastTxSnapshot(), std::make_shared(1)); + std::shared_ptr task = + std::make_shared(BufferizationWriteActorId, std::move(writeData), context); NConveyor::TInsertServiceOperator::AsyncTaskToExecute(task); } } @@ -334,8 +356,8 @@ class TCommitOperation { return std::make_unique( TFullTxInfo::BuildFake(kind), LockId, ReceivingShards, SendingShards); } else { - return std::make_unique(TFullTxInfo::BuildFake(kind), LockId, - ArbiterColumnShard, ReceivingShards.contains(TabletId)); + return std::make_unique( + TFullTxInfo::BuildFake(kind), LockId, ArbiterColumnShard, ReceivingShards.contains(TabletId)); } } @@ -429,14 +451,24 @@ class TAbortWriteTransaction: public NTabletFlatExecutor::TTransactionBaseGet()->Record; const auto source = ev->Sender; const auto cookie = ev->Cookie; + + if (!TablesManager.GetPrimaryIndex()) { + Counters.GetTabletCounters()->IncCounter(COUNTER_WRITE_FAIL); + auto result = NEvents::TDataEvents::TEvWriteResult::BuildError( + TabletID(), 0, NKikimrDataEvents::TEvWriteResult::STATUS_BAD_REQUEST, "schema not ready for writing"); + ctx.Send(source, result.release(), 0, cookie); + return; + } + const auto behaviourConclusion = TOperationsManager::GetBehaviour(*ev->Get()); -// AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("ev_write", record.DebugString()); + AFL_TRACE(NKikimrServices::TX_COLUMNSHARD_WRITE)("ev_write", record.DebugString()); if (behaviourConclusion.IsFail()) { Counters.GetTabletCounters()->IncCounter(COUNTER_WRITE_FAIL); auto result = NEvents::TDataEvents::TEvWriteResult::BuildError(TabletID(), 0, NKikimrDataEvents::TEvWriteResult::STATUS_BAD_REQUEST, @@ -451,23 +483,23 @@ void TColumnShard::Handle(NEvents::TDataEvents::TEvWrite::TPtr& ev, const TActor return; } + const auto sendError = [&](const TString& message, const NKikimrDataEvents::TEvWriteResult::EStatus status) { + Counters.GetTabletCounters()->IncCounter(COUNTER_WRITE_FAIL); + auto result = NEvents::TDataEvents::TEvWriteResult::BuildError(TabletID(), 0, status, message); + ctx.Send(source, result.release(), 0, cookie); + }; if (behaviour == EOperationBehaviour::CommitWriteLock) { auto commitOperation = std::make_shared(TabletID()); - const auto sendError = [&](const TString& message, const NKikimrDataEvents::TEvWriteResult::EStatus status) { - Counters.GetTabletCounters()->IncCounter(COUNTER_WRITE_FAIL); - auto result = NEvents::TDataEvents::TEvWriteResult::BuildError(TabletID(), 0, status, message); - ctx.Send(source, result.release(), 0, cookie); - }; auto conclusionParse = commitOperation->Parse(*ev->Get()); if (conclusionParse.IsFail()) { sendError(conclusionParse.GetErrorMessage(), NKikimrDataEvents::TEvWriteResult::STATUS_BAD_REQUEST); } else { - if (commitOperation->NeedSyncLocks()) { - auto* lockInfo = OperationsManager->GetLockOptional(commitOperation->GetLockId()); - if (!lockInfo) { - sendError("haven't lock for commit: " + ::ToString(commitOperation->GetLockId()), - NKikimrDataEvents::TEvWriteResult::STATUS_ABORTED); - } else { + auto* lockInfo = OperationsManager->GetLockOptional(commitOperation->GetLockId()); + if (!lockInfo) { + sendError("haven't lock for commit: " + ::ToString(commitOperation->GetLockId()), + NKikimrDataEvents::TEvWriteResult::STATUS_BAD_REQUEST); + } else { + if (commitOperation->NeedSyncLocks()) { if (lockInfo->GetGeneration() != commitOperation->GetGeneration()) { sendError("tablet lock have another generation: " + ::ToString(lockInfo->GetGeneration()) + " != " + ::ToString(commitOperation->GetGeneration()), NKikimrDataEvents::TEvWriteResult::STATUS_LOCKS_BROKEN); @@ -479,9 +511,9 @@ void TColumnShard::Handle(NEvents::TDataEvents::TEvWrite::TPtr& ev, const TActor } else { Execute(new TProposeWriteTransaction(this, commitOperation, source, cookie), ctx); } + } else { + Execute(new TProposeWriteTransaction(this, commitOperation, source, cookie), ctx); } - } else { - Execute(new TProposeWriteTransaction(this, commitOperation, source, cookie), ctx); } } return; @@ -499,58 +531,45 @@ void TColumnShard::Handle(NEvents::TDataEvents::TEvWrite::TPtr& ev, const TActor const std::optional mType = TEnumOperator::DeserializeFromProto(operation.GetType()); if (!mType) { - Counters.GetTabletCounters()->IncCounter(COUNTER_WRITE_FAIL); - auto result = NEvents::TDataEvents::TEvWriteResult::BuildError(TabletID(), 0, NKikimrDataEvents::TEvWriteResult::STATUS_BAD_REQUEST, - "operation " + NKikimrDataEvents::TEvWrite::TOperation::EOperationType_Name(operation.GetType()) + " is not supported"); - ctx.Send(source, result.release(), 0, cookie); + sendError("operation " + NKikimrDataEvents::TEvWrite::TOperation::EOperationType_Name(operation.GetType()) + " is not supported", + NKikimrDataEvents::TEvWriteResult::STATUS_BAD_REQUEST); return; } if (!operation.GetTableId().HasSchemaVersion()) { - Counters.GetTabletCounters()->IncCounter(COUNTER_WRITE_FAIL); - auto result = NEvents::TDataEvents::TEvWriteResult::BuildError( - TabletID(), 0, NKikimrDataEvents::TEvWriteResult::STATUS_BAD_REQUEST, "schema version not set"); - ctx.Send(source, result.release(), 0, cookie); + sendError("schema version not set", NKikimrDataEvents::TEvWriteResult::STATUS_BAD_REQUEST); return; } - auto schema = TablesManager.GetPrimaryIndex()->GetVersionedIndex().GetSchema(operation.GetTableId().GetSchemaVersion()); + auto schema = TablesManager.GetPrimaryIndex()->GetVersionedIndex().GetSchemaOptional(operation.GetTableId().GetSchemaVersion()); if (!schema) { - Counters.GetTabletCounters()->IncCounter(COUNTER_WRITE_FAIL); - auto result = NEvents::TDataEvents::TEvWriteResult::BuildError( - TabletID(), 0, NKikimrDataEvents::TEvWriteResult::STATUS_BAD_REQUEST, "unknown schema version"); - ctx.Send(source, result.release(), 0, cookie); + sendError("unknown schema version", NKikimrDataEvents::TEvWriteResult::STATUS_BAD_REQUEST); return; } - const auto tableId = operation.GetTableId().GetTableId(); + const auto pathId = operation.GetTableId().GetTableId(); - if (!TablesManager.IsReadyForWrite(tableId)) { - Counters.GetTabletCounters()->IncCounter(COUNTER_WRITE_FAIL); - auto result = NEvents::TDataEvents::TEvWriteResult::BuildError( - TabletID(), 0, NKikimrDataEvents::TEvWriteResult::STATUS_INTERNAL_ERROR, "table not writable"); - ctx.Send(source, result.release(), 0, cookie); + if (!TablesManager.IsReadyForStartWrite(pathId, false)) { + sendError("table not writable", NKikimrDataEvents::TEvWriteResult::STATUS_INTERNAL_ERROR); return; } + Counters.GetColumnTablesCounters()->GetPathIdCounter(pathId)->OnWriteEvent(); + auto arrowData = std::make_shared(schema); if (!arrowData->Parse(operation, NEvWrite::TPayloadReader(*ev->Get()))) { - Counters.GetTabletCounters()->IncCounter(COUNTER_WRITE_FAIL); - auto result = NEvents::TDataEvents::TEvWriteResult::BuildError( - TabletID(), 0, NKikimrDataEvents::TEvWriteResult::STATUS_BAD_REQUEST, "parsing data error"); - ctx.Send(source, result.release(), 0, cookie); + sendError("parsing data error", NKikimrDataEvents::TEvWriteResult::STATUS_BAD_REQUEST); + return; } - auto overloadStatus = CheckOverloaded(tableId); + auto overloadStatus = CheckOverloaded(pathId); if (overloadStatus != EOverloadStatus::None) { std::unique_ptr result = NEvents::TDataEvents::TEvWriteResult::BuildError( TabletID(), 0, NKikimrDataEvents::TEvWriteResult::STATUS_OVERLOADED, "overload data error"); - OverloadWriteFail(overloadStatus, NEvWrite::TWriteMeta(0, tableId, source, {}), arrowData->GetSize(), cookie, std::move(result), ctx); + OverloadWriteFail(overloadStatus, NEvWrite::TWriteMeta(0, pathId, source, {}, TGUID::CreateTimebased().AsGuidString()), arrowData->GetSize(), cookie, std::move(result), ctx); return; } - Counters.GetWritesMonitor()->OnStartWrite(arrowData->GetSize()); - std::optional granuleShardingVersionId; if (record.HasGranuleShardingVersionId()) { granuleShardingVersionId = record.GetGranuleShardingVersionId(); @@ -559,17 +578,29 @@ void TColumnShard::Handle(NEvents::TDataEvents::TEvWrite::TPtr& ev, const TActor ui64 lockId = 0; if (behaviour == EOperationBehaviour::NoTxWrite) { lockId = BuildEphemeralTxId(); - } else if (behaviour == EOperationBehaviour::InTxWrite) { - lockId = record.GetTxId(); } else { lockId = record.GetLockTxId(); } + if (!AppDataVerified().ColumnShardConfig.GetWritingEnabled()) { + sendError("writing disabled", NKikimrDataEvents::TEvWriteResult::STATUS_OVERLOADED); + return; + } + OperationsManager->RegisterLock(lockId, Generation()); - auto writeOperation = OperationsManager->RegisterOperation(lockId, cookie, granuleShardingVersionId, *mType); + auto writeOperation = OperationsManager->RegisterOperation( + pathId, lockId, cookie, granuleShardingVersionId, *mType, AppDataVerified().FeatureFlags.GetEnableWritePortionsOnInsert()); + + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_WRITE)("writing_size", arrowData->GetSize())("operation_id", writeOperation->GetIdentifier())( + "in_flight", Counters.GetWritesMonitor()->GetWritesInFlight())("size_in_flight", Counters.GetWritesMonitor()->GetWritesSizeInFlight()); + Counters.GetWritesMonitor()->OnStartWrite(arrowData->GetSize()); + Y_ABORT_UNLESS(writeOperation); writeOperation->SetBehaviour(behaviour); - writeOperation->Start(*this, tableId, arrowData, source, schema, ctx, NOlap::TSnapshot::Max()); + NOlap::TWritingContext wContext(TabletID(), SelfId(), schema, StoragesManager, Counters.GetIndexationCounters().SplitterCounters, + Counters.GetCSCounters().WritingCounters, NOlap::TSnapshot::Max(), writeOperation->GetActivityChecker()); + arrowData->SetSeparationPoints(GetIndexAs().GetGranulePtrVerified(pathId)->GetBucketPositions()); + writeOperation->Start(*this, arrowData, source, wContext); } } // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/columnshard/columnshard_impl.cpp b/ydb/core/tx/columnshard/columnshard_impl.cpp index 9f1c7a10859d..fde0bba29cad 100644 --- a/ydb/core/tx/columnshard/columnshard_impl.cpp +++ b/ydb/core/tx/columnshard/columnshard_impl.cpp @@ -1,48 +1,56 @@ -#include "columnshard_impl.h" #include "blob.h" +#include "columnshard_impl.h" #include "columnshard_schema.h" -#include "common/tablet_id.h" -#include "blobs_reader/task.h" -#include "blobs_reader/events.h" + #include "blobs_action/bs/storage.h" +#include "blobs_reader/events.h" +#include "blobs_reader/task.h" +#include "common/tablet_id.h" #include "resource_subscriber/task.h" #ifndef KIKIMR_DISABLE_S3_OPS #include "blobs_action/tier/storage.h" #endif -#include "blobs_reader/actor.h" +#include "bg_tasks/adapter/adapter.h" +#include "bg_tasks/events/events.h" +#include "bg_tasks/manager/manager.h" #include "blobs_action/storages_manager/manager.h" +#include "blobs_action/transaction/tx_gc_indexed.h" +#include "blobs_action/transaction/tx_gc_insert_table.h" #include "blobs_action/transaction/tx_remove_blobs.h" #include "blobs_action/transaction/tx_gc_insert_table.h" #include "blobs_action/transaction/tx_gc_indexed.h" +#include "blobs_reader/actor.h" #include "bg_tasks/events/events.h" +#include "data_accessor/manager.h" #include "data_sharing/destination/session/destination.h" #include "data_sharing/source/session/source.h" -#include "data_sharing/common/transactions/tx_extension.h" - -#include "engines/changes/indexation.h" #include "engines/changes/cleanup_portions.h" #include "engines/changes/cleanup_tables.h" +#include "engines/changes/general_compaction.h" +#include "engines/changes/indexation.h" #include "engines/changes/ttl.h" - +#include "hooks/abstract/abstract.h" #include "resource_subscriber/counters.h" #include "transactions/operators/ev_write/sync.h" -#include "bg_tasks/adapter/adapter.h" -#include "bg_tasks/manager/manager.h" -#include "hooks/abstract/abstract.h" - +#include #include #include -#include -#include #include -#include -#include +#include +#include #include -#include +#include +#include +#include +#include + +#include + +#include namespace NKikimr::NColumnShard { @@ -50,8 +58,7 @@ namespace NKikimr::NColumnShard { // But in unittests we want to test both scenarios bool gAllowLogBatchingDefaultValue = true; -namespace -{ +namespace { NTabletPipe::TClientConfig GetPipeClientConfig() { NTabletPipe::TClientConfig config; @@ -62,7 +69,7 @@ NTabletPipe::TClientConfig GetPipeClientConfig() { return config; } -} +} // namespace TColumnShard::TColumnShard(TTabletStorageInfo* info, const TActorId& tablet) : TActor(&TThis::StateInit) @@ -76,7 +83,8 @@ TColumnShard::TColumnShard(TTabletStorageInfo* info, const TActorId& tablet) , PeriodicWakeupActivationPeriod(NYDBTest::TControllers::GetColumnShardController()->GetPeriodicWakeupActivationPeriod()) , StatsReportInterval(NYDBTest::TControllers::GetColumnShardController()->GetStatsReportInterval()) , InFlightReadsTracker(StoragesManager, Counters.GetRequestsTracingCounters()) - , TablesManager(StoragesManager, info->TabletID) + , TablesManager(StoragesManager, std::make_shared(nullptr), + std::make_shared(), info->TabletID) , Subscribers(std::make_shared(*this)) , PipeClientCache(NTabletPipe::CreateBoundedClientCache(new NTabletPipe::TBoundedClientCacheConfig(), GetPipeClientConfig())) , InsertTable(std::make_unique()) @@ -86,6 +94,7 @@ TColumnShard::TColumnShard(TTabletStorageInfo* info, const TActorId& tablet) , BackgroundController(Counters.GetBackgroundControllerCounters()) , NormalizerController(StoragesManager, Counters.GetSubscribeCounters()) , SysLocks(this) { + AFL_VERIFY(TabletActivityImpl->Inc() == 1); } void TColumnShard::OnDetach(const TActorContext& ctx) { @@ -130,8 +139,7 @@ bool TColumnShard::WaitPlanStep(ui64 step) { } if (MediatorTimeCastRegistered) { if (MediatorTimeCastWaitingSteps.empty() || - step < *MediatorTimeCastWaitingSteps.begin()) - { + step < *MediatorTimeCastWaitingSteps.begin()) { MediatorTimeCastWaitingSteps.insert(step); SendWaitPlanStep(step); LOG_S_DEBUG("Waiting for PlanStep# " << step << " from mediator time cast"); @@ -187,7 +195,7 @@ ui64 TColumnShard::GetOutdatedStep() const { } NOlap::TSnapshot TColumnShard::GetMinReadSnapshot() const { - ui64 delayMillisec = GetMaxReadStaleness().MilliSeconds(); + ui64 delayMillisec = NYDBTest::TControllers::GetColumnShardController()->GetMaxReadStaleness().MilliSeconds(); ui64 passedStep = GetOutdatedStep(); ui64 minReadStep = (passedStep > delayMillisec ? passedStep - delayMillisec : 0); @@ -302,7 +310,7 @@ void TColumnShard::UpdateSchemaSeqNo(const TMessageSeqNo& seqNo, NTabletFlatExec } void TColumnShard::ProtectSchemaSeqNo(const NKikimrTxColumnShard::TSchemaSeqNo& seqNoProto, - NTabletFlatExecutor::TTransactionContext& txc) { + NTabletFlatExecutor::TTransactionContext& txc) { auto seqNo = SeqNoFromProto(seqNoProto); if (LastSchemaSeqNo <= seqNo) { UpdateSchemaSeqNo(++seqNo, txc); @@ -310,7 +318,7 @@ void TColumnShard::ProtectSchemaSeqNo(const NKikimrTxColumnShard::TSchemaSeqNo& } void TColumnShard::RunSchemaTx(const NKikimrTxColumnShard::TSchemaTxBody& body, const NOlap::TSnapshot& version, - NTabletFlatExecutor::TTransactionContext& txc) { + NTabletFlatExecutor::TTransactionContext& txc) { switch (body.TxBody_case()) { case NKikimrTxColumnShard::TSchemaTxBody::kInitShard: { RunInit(body.GetInitShard(), version, txc); @@ -342,7 +350,7 @@ void TColumnShard::RunSchemaTx(const NKikimrTxColumnShard::TSchemaTxBody& body, } void TColumnShard::RunInit(const NKikimrTxColumnShard::TInitShard& proto, const NOlap::TSnapshot& version, - NTabletFlatExecutor::TTransactionContext& txc) { + NTabletFlatExecutor::TTransactionContext& txc) { Y_UNUSED(version); NIceDb::TNiceDb db(txc.DB); @@ -363,7 +371,7 @@ void TColumnShard::RunInit(const NKikimrTxColumnShard::TInitShard& proto, const } void TColumnShard::RunEnsureTable(const NKikimrTxColumnShard::TCreateTable& tableProto, const NOlap::TSnapshot& version, - NTabletFlatExecutor::TTransactionContext& txc) { + NTabletFlatExecutor::TTransactionContext& txc) { NIceDb::TNiceDb db(txc.DB); const ui64 pathId = tableProto.GetPathId(); @@ -373,14 +381,15 @@ void TColumnShard::RunEnsureTable(const NKikimrTxColumnShard::TCreateTable& tabl } LOG_S_DEBUG("EnsureTable for pathId: " << pathId - << " ttl settings: " << tableProto.GetTtlSettings() - << " at tablet " << TabletID()); + << " ttl settings: " << tableProto.GetTtlSettings() + << " at tablet " << TabletID()); NKikimrTxColumnShard::TTableVersionInfo tableVerProto; tableVerProto.SetPathId(pathId); // check schema changed + std::optional schema; if (tableProto.HasSchemaPreset()) { Y_ABORT_UNLESS(!tableProto.HasSchema(), "Tables has either schema or preset"); @@ -390,75 +399,77 @@ void TColumnShard::RunEnsureTable(const NKikimrTxColumnShard::TCreateTable& tabl tableVerProto.SetSchemaPresetId(preset.GetId()); if (TablesManager.RegisterSchemaPreset(preset, db)) { - TablesManager.AddSchemaVersion(tableProto.GetSchemaPreset().GetId(), version, tableProto.GetSchemaPreset().GetSchema(), db, Tiers); + TablesManager.AddSchemaVersion(tableProto.GetSchemaPreset().GetId(), version, tableProto.GetSchemaPreset().GetSchema(), db); } } else { Y_ABORT_UNLESS(tableProto.HasSchema(), "Tables has either schema or preset"); - *tableVerProto.MutableSchema() = tableProto.GetSchema(); + schema = tableProto.GetSchema(); } { - bool needTieringActivation = false; + THashSet usedTiers; TTableInfo table(pathId); if (tableProto.HasTtlSettings()) { const auto& ttlSettings = tableProto.GetTtlSettings(); *tableVerProto.MutableTtlSettings() = ttlSettings; - if (ttlSettings.HasUseTiering()) { - table.SetTieringUsage(ttlSettings.GetUseTiering()); - needTieringActivation = true; + if (ttlSettings.HasEnabled()) { + usedTiers = NOlap::TTiering::GetUsedTiers(ttlSettings.GetEnabled()); } } - const TString tieringName = table.GetTieringUsage(); TablesManager.RegisterTable(std::move(table), db); - if (needTieringActivation) { - ActivateTiering(pathId, tieringName); + if (!usedTiers.empty()) { + ActivateTiering(pathId, usedTiers); } } tableVerProto.SetSchemaPresetVersionAdj(tableProto.GetSchemaPresetVersionAdj()); - TablesManager.AddTableVersion(pathId, version, tableVerProto, db, Tiers); + TablesManager.AddTableVersion(pathId, version, tableVerProto, schema, db); + InsertTable->RegisterPathInfo(pathId); Counters.GetTabletCounters()->SetCounter(COUNTER_TABLES, TablesManager.GetTables().size()); Counters.GetTabletCounters()->SetCounter(COUNTER_TABLE_PRESETS, TablesManager.GetSchemaPresets().size()); - Counters.GetTabletCounters()->SetCounter(COUNTER_TABLE_TTLS, TablesManager.GetTtl().PathsCount()); + Counters.GetTabletCounters()->SetCounter(COUNTER_TABLE_TTLS, TablesManager.GetTtl().size()); } void TColumnShard::RunAlterTable(const NKikimrTxColumnShard::TAlterTable& alterProto, const NOlap::TSnapshot& version, - NTabletFlatExecutor::TTransactionContext& txc) { + NTabletFlatExecutor::TTransactionContext& txc) { NIceDb::TNiceDb db(txc.DB); const ui64 pathId = alterProto.GetPathId(); Y_ABORT_UNLESS(TablesManager.HasTable(pathId), "AlterTable on a dropped or non-existent table"); LOG_S_DEBUG("AlterTable for pathId: " << pathId - << " schema: " << alterProto.GetSchema() - << " ttl settings: " << alterProto.GetTtlSettings() - << " at tablet " << TabletID()); + << " schema: " << alterProto.GetSchema() + << " ttl settings: " << alterProto.GetTtlSettings() + << " at tablet " << TabletID()); NKikimrTxColumnShard::TTableVersionInfo tableVerProto; + std::optional schema; if (alterProto.HasSchemaPreset()) { tableVerProto.SetSchemaPresetId(alterProto.GetSchemaPreset().GetId()); - TablesManager.AddSchemaVersion(alterProto.GetSchemaPreset().GetId(), version, alterProto.GetSchemaPreset().GetSchema(), db, Tiers); + TablesManager.AddSchemaVersion(alterProto.GetSchemaPreset().GetId(), version, alterProto.GetSchemaPreset().GetSchema(), db); } else if (alterProto.HasSchema()) { - *tableVerProto.MutableSchema() = alterProto.GetSchema(); + schema = alterProto.GetSchema(); } - const auto& ttlSettings = alterProto.GetTtlSettings(); // Note: Not valid behaviour for full alter implementation - const TString& tieringUsage = ttlSettings.GetUseTiering(); + THashSet usedTiers; if (alterProto.HasTtlSettings()) { const auto& ttlSettings = alterProto.GetTtlSettings(); *tableVerProto.MutableTtlSettings() = ttlSettings; + + if (ttlSettings.HasEnabled()) { + usedTiers = NOlap::TTiering::GetUsedTiers(ttlSettings.GetEnabled()); + } } - ActivateTiering(pathId, tieringUsage); - Schema::SaveTableInfo(db, pathId, tieringUsage); + ActivateTiering(pathId, usedTiers); tableVerProto.SetSchemaPresetVersionAdj(alterProto.GetSchemaPresetVersionAdj()); - TablesManager.AddTableVersion(pathId, version, tableVerProto, db, Tiers); + TablesManager.AddTableVersion(pathId, version, tableVerProto, schema, db); } void TColumnShard::RunDropTable(const NKikimrTxColumnShard::TDropTable& dropProto, const NOlap::TSnapshot& version, - NTabletFlatExecutor::TTransactionContext& txc) { + NTabletFlatExecutor::TTransactionContext& txc) { NIceDb::TNiceDb db(txc.DB); const ui64 pathId = dropProto.GetPathId(); @@ -472,7 +483,7 @@ void TColumnShard::RunDropTable(const NKikimrTxColumnShard::TDropTable& dropProt } void TColumnShard::RunAlterStore(const NKikimrTxColumnShard::TAlterStore& proto, const NOlap::TSnapshot& version, - NTabletFlatExecutor::TTransactionContext& txc) { + NTabletFlatExecutor::TTransactionContext& txc) { NIceDb::TNiceDb db(txc.DB); if (proto.HasStorePathId()) { @@ -491,7 +502,7 @@ void TColumnShard::RunAlterStore(const NKikimrTxColumnShard::TAlterStore& proto, if (!TablesManager.HasPreset(presetProto.GetId())) { continue; // we don't update presets that we don't use } - TablesManager.AddSchemaVersion(presetProto.GetId(), version, presetProto.GetSchema(), db, Tiers); + TablesManager.AddSchemaVersion(presetProto.GetId(), version, presetProto.GetSchema(), db); } } @@ -506,13 +517,14 @@ void TColumnShard::EnqueueBackgroundActivities(const bool periodic) { AFL_NOTICE(NKikimrServices::TX_COLUMNSHARD)("problem", "Background activities cannot be started: no index at tablet"); return; } -// !!!!!! MUST BE FIRST THROUGH DATA HAVE TO BE SAME IN SESSIONS AFTER TABLET RESTART + // !!!!!! MUST BE FIRST THROUGH DATA HAVE TO BE SAME IN SESSIONS AFTER TABLET RESTART SharingSessionsManager->Start(*this); SetupIndexation(); - SetupCompaction(); + SetupCompaction({}); SetupCleanupPortions(); SetupCleanupTables(); + SetupMetadata(); SetupTtl(); SetupGC(); SetupCleanupInsertTable(); @@ -526,6 +538,7 @@ class TChangesTask: public NConveyor::ITask { const TActorId ParentActorId; TString ClassId; NOlap::TSnapshot LastCompletedTx; + protected: virtual TConclusionStatus DoExecute(const std::shared_ptr& /*taskPtr*/) override { NActors::TLogContextGuard g(NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", TabletId)("parent_id", ParentActorId)); @@ -539,6 +552,7 @@ class TChangesTask: public NConveyor::ITask { TActorContext::AsActorContext().Send(ParentActorId, std::move(TxEvent)); return TConclusionStatus::Success(); } + public: virtual TString GetTaskClassIdentifier() const override { return ClassId; @@ -549,8 +563,7 @@ class TChangesTask: public NConveyor::ITask { , Counters(counters) , TabletId(tabletId) , ParentActorId(parentActorId) - , LastCompletedTx(lastCompletedTx) - { + , LastCompletedTx(lastCompletedTx) { Y_ABORT_UNLESS(TxEvent); Y_ABORT_UNLESS(TxEvent->IndexChanges); ClassId = "Changes::ConstructBlobs::" + TxEvent->IndexChanges->TypeString(); @@ -565,10 +578,16 @@ class TChangesReadTask: public NOlap::NBlobOperations::NRead::ITask { std::unique_ptr TxEvent; TIndexationCounters Counters; NOlap::TSnapshot LastCompletedTx; + protected: virtual void DoOnDataReady(const std::shared_ptr& resourcesGuard) override { + if (!!resourcesGuard) { + AFL_VERIFY(!TxEvent->IndexChanges->ResourcesGuard); + TxEvent->IndexChanges->ResourcesGuard = resourcesGuard; + } else { + AFL_VERIFY(TxEvent->IndexChanges->ResourcesGuard); + } TxEvent->IndexChanges->Blobs = ExtractBlobsData(); - TxEvent->IndexChanges->ResourcesGuard = resourcesGuard; const bool isInsert = !!dynamic_pointer_cast(TxEvent->IndexChanges); std::shared_ptr task = std::make_shared(std::move(TxEvent), Counters, TabletId, ParentActorId, LastCompletedTx); if (isInsert) { @@ -579,14 +598,13 @@ class TChangesReadTask: public NOlap::NBlobOperations::NRead::ITask { } virtual bool DoOnError(const TString& storageId, const NOlap::TBlobRange& range, const NOlap::IBlobsReadingAction::TErrorStatus& status) override { AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("event", "DoOnError")("storage_id", storageId)("blob_id", range)("status", status.GetErrorMessage())("status_code", status.GetStatus()); - AFL_VERIFY(status.GetStatus() != NKikimrProto::EReplyStatus::NODATA)("blob_id", range)("status", status.GetStatus()) - ("error", status.GetErrorMessage())("type", TxEvent->IndexChanges->TypeString())("task_id", TxEvent->IndexChanges->GetTaskIdentifier()) - ("debug", TxEvent->IndexChanges->DebugString()); + AFL_VERIFY(status.GetStatus() != NKikimrProto::EReplyStatus::NODATA)("blob_id", range)("status", status.GetStatus())("error", status.GetErrorMessage())("type", TxEvent->IndexChanges->TypeString())("task_id", TxEvent->IndexChanges->GetTaskIdentifier())("debug", TxEvent->IndexChanges->DebugString()); TxEvent->SetPutStatus(NKikimrProto::ERROR); Counters.ReadErrors->Add(1); TActorContext::AsActorContext().Send(ParentActorId, std::move(TxEvent)); return false; } + public: TChangesReadTask(std::unique_ptr&& event, const TActorId parentActorId, const ui64 tabletId, const TIndexationCounters& counters, NOlap::TSnapshot lastCompletedTx) : TBase(event->IndexChanges->GetReadingActions(), event->IndexChanges->TypeString(), event->IndexChanges->GetTaskIdentifier()) @@ -594,15 +612,100 @@ class TChangesReadTask: public NOlap::NBlobOperations::NRead::ITask { , TabletId(tabletId) , TxEvent(std::move(event)) , Counters(counters) - , LastCompletedTx(lastCompletedTx) - { + , LastCompletedTx(lastCompletedTx) { AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "start_changes")("type", TxEvent->IndexChanges->TypeString())("task_id", TxEvent->IndexChanges->GetTaskIdentifier()); } }; +class TDataAccessorsSubscriberBase: public NOlap::IDataAccessorRequestsSubscriber { +private: + std::shared_ptr ResourcesGuard; + virtual const std::shared_ptr& DoGetAbortionFlag() const override { + return Default>(); + } + + virtual void DoOnRequestsFinished(NOlap::TDataAccessorsResult&& result) override final { + AFL_VERIFY(ResourcesGuard); + DoOnRequestsFinished(std::move(result), std::move(ResourcesGuard)); + } + +protected: + virtual void DoOnRequestsFinished(NOlap::TDataAccessorsResult&& result, std::shared_ptr&& guard) = 0; + +public: + void SetResourcesGuard(const std::shared_ptr& guard) { + AFL_VERIFY(!ResourcesGuard); + AFL_VERIFY(guard); + ResourcesGuard = guard; + } +}; + +class TDataAccessorsSubscriber: public TDataAccessorsSubscriberBase { +protected: + const NActors::TActorId ShardActorId; + std::shared_ptr Changes; + std::shared_ptr VersionedIndex; + std::shared_ptr ResourcesGuard; + + virtual void DoOnRequestsFinishedImpl() = 0; + + virtual void DoOnRequestsFinished(NOlap::TDataAccessorsResult&& result, std::shared_ptr&& guard) override final { + Changes->SetFetchedDataAccessors(std::move(result), NOlap::TDataAccessorsInitializationContext(VersionedIndex)); + Changes->ResourcesGuard = std::move(guard); + DoOnRequestsFinishedImpl(); + } + +public: + void SetResourcesGuard(const std::shared_ptr& guard) { + AFL_VERIFY(!ResourcesGuard); + ResourcesGuard = guard; + } + + std::shared_ptr&& ExtractResourcesGuard() { + AFL_VERIFY(ResourcesGuard); + return std::move(ResourcesGuard); + } + + TDataAccessorsSubscriber(const NActors::TActorId& shardActorId, const std::shared_ptr& changes, + const std::shared_ptr& versionedIndex) + : ShardActorId(shardActorId) + , Changes(changes) + , VersionedIndex(versionedIndex) { + } +}; + +class TDataAccessorsSubscriberWithRead: public TDataAccessorsSubscriber { +private: + using TBase = TDataAccessorsSubscriber; + +protected: + const bool CacheDataAfterWrite = false; + const ui64 ShardTabletId; + TIndexationCounters Counters; + NOlap::TSnapshot SnapshotModification; + const NActors::TActorId ResourceSubscribeActor; + const NOlap::NResourceBroker::NSubscribe::TTaskContext TaskSubscriptionContext; + +public: + TDataAccessorsSubscriberWithRead(const NActors::TActorId& resourceSubscribeActor, const std::shared_ptr& changes, + const std::shared_ptr& versionedIndex, const bool cacheAfterWrite, const NActors::TActorId& shardActorId, + const ui64 shardTabletId, const TIndexationCounters& counters, const NOlap::TSnapshot& snapshotModification, + const NOlap::NResourceBroker::NSubscribe::TTaskContext& taskSubscriptionContext) + : TBase(shardActorId, changes, versionedIndex) + , CacheDataAfterWrite(cacheAfterWrite) + , ShardTabletId(shardTabletId) + , Counters(counters) + , SnapshotModification(snapshotModification) + , ResourceSubscribeActor(resourceSubscribeActor) + , TaskSubscriptionContext(taskSubscriptionContext) + { + } +}; + class TInsertChangesReadTask: public TChangesReadTask, public TMonitoringObjectsCounter { private: using TBase = TChangesReadTask; + public: using TBase::TBase; }; @@ -610,6 +713,7 @@ class TInsertChangesReadTask: public TChangesReadTask, public TMonitoringObjects class TCompactChangesReadTask: public TChangesReadTask, public TMonitoringObjectsCounter { private: using TBase = TChangesReadTask; + public: using TBase::TBase; }; @@ -617,6 +721,7 @@ class TCompactChangesReadTask: public TChangesReadTask, public TMonitoringObject class TTTLChangesReadTask: public TChangesReadTask, public TMonitoringObjectsCounter { private: using TBase = TChangesReadTask; + public: using TBase::TBase; }; @@ -637,18 +742,17 @@ void TColumnShard::StartIndexTask(std::vector&& da auto indexChanges = TablesManager.MutablePrimaryIndex().StartInsert(std::move(data)); Y_ABORT_UNLESS(indexChanges); - auto actualIndexInfo = std::make_shared(TablesManager.GetPrimaryIndex()->GetVersionedIndex()); + auto actualIndexInfo = TablesManager.GetPrimaryIndex()->GetVersionedIndexReadonlyCopy(); indexChanges->Start(*this); auto ev = std::make_unique(actualIndexInfo, indexChanges, Settings.CacheDataAfterIndexing); const TString externalTaskId = indexChanges->GetTaskIdentifier(); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "indexation")("bytes", bytesToIndex)("blobs_count", dataToIndex.size())("max_limit", (i64)Limits.MaxInsertBytes) - ("has_more", bytesToIndex >= Limits.MaxInsertBytes)("external_task_id", externalTaskId); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "indexation")("bytes", bytesToIndex)("blobs_count", dataToIndex.size())("max_limit", (i64)Limits.MaxInsertBytes)("has_more", bytesToIndex >= Limits.MaxInsertBytes)("external_task_id", externalTaskId); NOlap::NResourceBroker::NSubscribe::ITask::StartResourceSubscription( ResourceSubscribeActor, std::make_shared( - std::make_shared(std::move(ev), SelfId(), TabletID(), Counters.GetIndexationCounters(), GetLastCompletedTx()), - 0, indexChanges->CalcMemoryForUsage(), externalTaskId, InsertTaskSubscription)); + std::make_shared(std::move(ev), SelfId(), TabletID(), Counters.GetIndexationCounters(), GetLastCompletedTx()), + 0, indexChanges->CalcMemoryForUsage(), externalTaskId, InsertTaskSubscription)); } void TColumnShard::SetupIndexation() { @@ -658,9 +762,7 @@ void TColumnShard::SetupIndexation() { } BackgroundController.CheckDeadlinesIndexation(); if (BackgroundController.GetIndexingActiveCount()) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "skip_indexation")("reason", "in_progress") - ("count", BackgroundController.GetIndexingActiveCount())("insert_overload_size", InsertTable->GetCountersCommitted().Bytes) - ("indexing_debug", BackgroundController.DebugStringIndexation()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "skip_indexation")("reason", "in_progress")("count", BackgroundController.GetIndexingActiveCount())("insert_overload_size", InsertTable->GetCountersCommitted().Bytes)("indexing_debug", BackgroundController.DebugStringIndexation()); return; } @@ -672,8 +774,7 @@ void TColumnShard::SetupIndexation() { const TDuration durationLimit = NYDBTest::TControllers::GetColumnShardController()->GetGuaranteeIndexationInterval(); if (!force && InsertTable->GetCountersCommitted().Bytes < bytesLimit && TMonotonic::Now() < BackgroundController.GetLastIndexationInstant() + durationLimit) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "skip_indexation")("reason", "not_enough_data_and_too_frequency") - ("insert_size", InsertTable->GetCountersCommitted().Bytes); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "skip_indexation")("reason", "not_enough_data_and_too_frequency")("insert_size", InsertTable->GetCountersCommitted().Bytes); return; } @@ -690,7 +791,7 @@ void TColumnShard::SetupIndexation() { bytesToIndex += data.BlobSize(); txBytesWrite += data.GetTxVolume(); dataToIndex.push_back(&data); - if (bytesToIndex >= (ui64)Limits.MaxInsertBytes || txBytesWrite >= NOlap::TGlobalLimits::TxWriteLimitBytes) { + if (bytesToIndex >= (ui64)Limits.MaxInsertBytes || txBytesWrite >= NOlap::TGlobalLimits::TxWriteLimitBytes || dataToIndex.size() > 500) { StartIndexTask(std::move(dataToIndex), bytesToIndex); dataToIndex.clear(); bytesToIndex = 0; @@ -704,38 +805,186 @@ void TColumnShard::SetupIndexation() { } } -void TColumnShard::SetupCompaction() { - if (!AppDataVerified().ColumnShardConfig.GetCompactionEnabled() || !NYDBTest::TControllers::GetColumnShardController()->IsBackgroundEnabled(NYDBTest::ICSController::EBackground::Compaction)) { +namespace { +class TCompactionAllocated: public NPrioritiesQueue::IRequest { +private: + const NActors::TActorId TabletActorId; + virtual void DoOnAllocated(const std::shared_ptr& guard) override { + NActors::TActorContext::AsActorContext().Send(TabletActorId, new TEvPrivate::TEvStartCompaction(guard)); + } + +public: + TCompactionAllocated(const NActors::TActorId& tabletActorId) + : TabletActorId(tabletActorId) { + } +}; + +} // namespace + +void TColumnShard::SetupCompaction(const std::set& pathIds) { + if (!AppDataVerified().ColumnShardConfig.GetCompactionEnabled() || + !NYDBTest::TControllers::GetColumnShardController()->IsBackgroundEnabled(NYDBTest::ICSController::EBackground::Compaction)) { AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "skip_compaction")("reason", "disabled"); return; } - Counters.GetCSCounters().OnSetupCompaction(); BackgroundController.CheckDeadlines(); - while (BackgroundController.GetCompactionsCount() < TSettings::MAX_ACTIVE_COMPACTIONS) { - auto indexChanges = TablesManager.MutablePrimaryIndex().StartCompaction(DataLocksManager); - if (!indexChanges) { - LOG_S_DEBUG("Compaction not started: cannot prepare compaction at tablet " << TabletID()); - break; + if (BackgroundController.GetCompactionsCount()) { + return; + } + const ui64 priority = TablesManager.MutablePrimaryIndex().GetCompactionPriority(DataLocksManager, pathIds, BackgroundController.GetWaitingPriorityOptional()); + if (priority) { + BackgroundController.UpdateWaitingPriority(priority); + if (pathIds.size()) { + NPrioritiesQueue::TCompServiceOperator::AskMax(PrioritizationClientId, priority, std::make_shared(SelfId())); + } else { + NPrioritiesQueue::TCompServiceOperator::Ask(PrioritizationClientId, priority, std::make_shared(SelfId())); } + } +} + +class TAccessorsMemorySubscriber: public NOlap::NResourceBroker::NSubscribe::ITask { +private: + using TBase = NOlap::NResourceBroker::NSubscribe::ITask; + std::shared_ptr Request; + std::shared_ptr Subscriber; + std::shared_ptr DataAccessorsManager; - indexChanges->Start(*this); + virtual void DoOnAllocationSuccess(const std::shared_ptr& guard) override { + Subscriber->SetResourcesGuard(guard); + Request->RegisterSubscriber(Subscriber); + DataAccessorsManager->AskData(Request); + } + +public: + TAccessorsMemorySubscriber(const ui64 memory, const TString& externalTaskId, const NOlap::NResourceBroker::NSubscribe::TTaskContext& context, + std::shared_ptr&& request, const std::shared_ptr& subscriber, + const std::shared_ptr& dataAccessorsManager) + : TBase(0, memory, externalTaskId, context) + , Request(std::move(request)) + , Subscriber(subscriber) + , DataAccessorsManager(dataAccessorsManager) { + } +}; + +class TCompactionDataAccessorsSubscriber: public TDataAccessorsSubscriberWithRead { +private: + using TBase = TDataAccessorsSubscriberWithRead; - auto actualIndexInfo = std::make_shared(TablesManager.GetPrimaryIndex()->GetVersionedIndex()); - auto ev = std::make_unique(actualIndexInfo, indexChanges, Settings.CacheDataAfterCompaction); - const TString externalTaskId = indexChanges->GetTaskIdentifier(); +protected: + virtual void DoOnRequestsFinishedImpl() override { + const TString externalTaskId = Changes->GetTaskIdentifier(); AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "compaction")("external_task_id", externalTaskId); - NOlap::NResourceBroker::NSubscribe::ITask::StartResourceSubscription( - ResourceSubscribeActor, std::make_shared( - std::make_shared(std::move(ev), SelfId(), TabletID(), Counters.GetCompactionCounters(), GetLastCompletedTx()), 0, indexChanges->CalcMemoryForUsage(), externalTaskId, CompactTaskSubscription)); + auto ev = std::make_unique(VersionedIndex, Changes, CacheDataAfterWrite); + TActorContext::AsActorContext().Register(new NOlap::NBlobOperations::NRead::TActor( + std::make_shared(std::move(ev), ShardActorId, ShardTabletId, Counters, SnapshotModification))); + } + +public: + using TBase::TBase; +}; + +void TColumnShard::StartCompaction(const std::shared_ptr& guard) { + Counters.GetCSCounters().OnSetupCompaction(); + BackgroundController.ResetWaitingPriority(); + + auto indexChanges = TablesManager.MutablePrimaryIndex().StartCompaction(DataLocksManager); + if (!indexChanges) { + LOG_S_DEBUG("Compaction not started: cannot prepare compaction at tablet " << TabletID()); + return; + } + + auto compaction = dynamic_pointer_cast(indexChanges); + compaction->SetActivityFlag(GetTabletActivity()); + compaction->SetQueueGuard(guard); + compaction->Start(*this); + + auto actualIndexInfo = TablesManager.GetPrimaryIndex()->GetVersionedIndexReadonlyCopy(); + auto request = compaction->ExtractDataAccessorsRequest(); + const ui64 accessorsMemory = request->PredictAccessorsMemory(TablesManager.GetPrimaryIndex()->GetVersionedIndex().GetLastSchema()) + + indexChanges->CalcMemoryForUsage(); + const auto subscriber = std::make_shared(ResourceSubscribeActor, indexChanges, actualIndexInfo, + Settings.CacheDataAfterCompaction, SelfId(), TabletID(), Counters.GetCompactionCounters(), GetLastCompletedTx(), + CompactTaskSubscription); + NOlap::NResourceBroker::NSubscribe::ITask::StartResourceSubscription( + ResourceSubscribeActor, std::make_shared(accessorsMemory, indexChanges->GetTaskIdentifier(), + CompactTaskSubscription, std::move(request), subscriber, DataAccessorsManager.GetObjectPtrVerified())); +} + +class TWriteEvictPortionsDataAccessorsSubscriber: public TDataAccessorsSubscriberWithRead { +private: + using TBase = TDataAccessorsSubscriberWithRead; + +protected: + virtual void DoOnRequestsFinishedImpl() override { + ACFL_DEBUG("background", "ttl")("need_writes", true); + auto ev = std::make_unique(VersionedIndex, Changes, false); + TActorContext::AsActorContext().Register(new NOlap::NBlobOperations::NRead::TActor( + std::make_shared(std::move(ev), ShardActorId, ShardTabletId, Counters, SnapshotModification))); + } + +public: + using TBase::TBase; +}; + +class TNoWriteEvictPortionsDataAccessorsSubscriber: public TDataAccessorsSubscriber { +private: + using TBase = TDataAccessorsSubscriber; + +protected: + virtual void DoOnRequestsFinishedImpl() override { + ACFL_DEBUG("background", "ttl")("need_writes", false); + auto ev = std::make_unique(VersionedIndex, Changes, false); + ev->SetPutStatus(NKikimrProto::OK); + NActors::TActivationContext::Send(ShardActorId, std::move(ev)); + } + +public: + using TBase::TBase; +}; + +class TCSMetadataSubscriber: public TDataAccessorsSubscriberBase, public TObjectCounter { +private: + NActors::TActorId TabletActorId; + const std::shared_ptr Processor; + const ui64 Generation; + virtual void DoOnRequestsFinished( + NOlap::TDataAccessorsResult&& result, std::shared_ptr&& guard) override { + NActors::TActivationContext::Send( + TabletActorId, std::make_unique(Processor, Generation, + NOlap::NResourceBroker::NSubscribe::TResourceContainer(std::move(result), std::move(guard)))); + } + +public: + TCSMetadataSubscriber( + const NActors::TActorId& tabletActorId, const std::shared_ptr& processor, const ui64 gen) + : TabletActorId(tabletActorId) + , Processor(processor) + , Generation(gen) + { + } +}; - LOG_S_DEBUG("ActiveCompactions: " << BackgroundController.GetCompactionsCount() << " at tablet " << TabletID()); +void TColumnShard::SetupMetadata() { + if (TObjectCounter::ObjectCount()) { + return; + } + std::vector requests = TablesManager.MutablePrimaryIndex().CollectMetadataRequests(); + for (auto&& i : requests) { + const ui64 accessorsMemory = + i.GetRequest()->PredictAccessorsMemory(TablesManager.GetPrimaryIndex()->GetVersionedIndex().GetLastSchema()); + NOlap::NResourceBroker::NSubscribe::ITask::StartResourceSubscription(ResourceSubscribeActor, + std::make_shared(accessorsMemory, i.GetRequest()->GetTaskId(), TTLTaskSubscription, + std::shared_ptr(i.GetRequest()), + std::make_shared(SelfId(), i.GetProcessor(), Generation()), DataAccessorsManager.GetObjectPtrVerified())); + } } bool TColumnShard::SetupTtl(const THashMap& pathTtls) { - if (!AppDataVerified().ColumnShardConfig.GetTTLEnabled() || !NYDBTest::TControllers::GetColumnShardController()->IsBackgroundEnabled(NYDBTest::ICSController::EBackground::TTL)) { + if (!AppDataVerified().ColumnShardConfig.GetTTLEnabled() || + !NYDBTest::TControllers::GetColumnShardController()->IsBackgroundEnabled(NYDBTest::ICSController::EBackground::TTL)) { AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "skip_ttl")("reason", "disabled"); return false; } @@ -746,33 +995,53 @@ bool TColumnShard::SetupTtl(const THashMap& pathTtls) { } const ui64 memoryUsageLimit = HasAppData() ? AppDataVerified().ColumnShardConfig.GetTieringsMemoryLimit() : ((ui64)512 * 1024 * 1024); - std::vector> indexChanges = TablesManager.MutablePrimaryIndex().StartTtl(eviction, DataLocksManager, memoryUsageLimit); + std::vector> indexChanges = + TablesManager.MutablePrimaryIndex().StartTtl(eviction, DataLocksManager, memoryUsageLimit); if (indexChanges.empty()) { ACFL_DEBUG("background", "ttl")("skip_reason", "no_changes"); return false; } - auto actualIndexInfo = std::make_shared(TablesManager.GetPrimaryIndex()->GetVersionedIndex()); + auto actualIndexInfo = TablesManager.GetPrimaryIndex()->GetVersionedIndexReadonlyCopy(); for (auto&& i : indexChanges) { - const TString externalTaskId = i->GetTaskIdentifier(); - const bool needWrites = i->NeedConstruction(); - ACFL_DEBUG("background", "ttl")("need_writes", needWrites); i->Start(*this); - auto ev = std::make_unique(actualIndexInfo, i, false); - if (needWrites) { - NOlap::NResourceBroker::NSubscribe::ITask::StartResourceSubscription( - ResourceSubscribeActor, std::make_shared( - std::make_shared(std::move(ev), SelfId(), TabletID(), Counters.GetCompactionCounters(), GetLastCompletedTx()), - 0, i->CalcMemoryForUsage(), externalTaskId, TTLTaskSubscription)); + auto request = i->ExtractDataAccessorsRequest(); + ui64 memoryUsage = 0; + std::shared_ptr subscriber; + if (i->NeedConstruction()) { + subscriber = std::make_shared(ResourceSubscribeActor, i, actualIndexInfo, + Settings.CacheDataAfterCompaction, SelfId(), TabletID(), Counters.GetEvictionCounters(), GetLastCompletedTx(), + TTLTaskSubscription); + memoryUsage = i->CalcMemoryForUsage(); } else { - ev->SetPutStatus(NKikimrProto::OK); - ActorContext().Send(SelfId(), std::move(ev)); + subscriber = std::make_shared(SelfId(), i, actualIndexInfo); } + const ui64 accessorsMemory = + request->PredictAccessorsMemory(TablesManager.GetPrimaryIndex()->GetVersionedIndex().GetLastSchema()) + memoryUsage; + NOlap::NResourceBroker::NSubscribe::ITask::StartResourceSubscription( + ResourceSubscribeActor, std::make_shared(accessorsMemory, i->GetTaskIdentifier(), TTLTaskSubscription, + std::move(request), subscriber, DataAccessorsManager.GetObjectPtrVerified())); } return true; } +class TCleanupPortionsDataAccessorsSubscriber: public TDataAccessorsSubscriber { +private: + using TBase = TDataAccessorsSubscriber; + +protected: + virtual void DoOnRequestsFinishedImpl() override { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("background", "cleanup")("changes_info", Changes->DebugString()); + auto ev = std::make_unique(VersionedIndex, Changes, false); + ev->SetPutStatus(NKikimrProto::OK); // No new blobs to write + NActors::TActivationContext::Send(ShardActorId, std::move(ev)); + } + +public: + using TBase::TBase; +}; + void TColumnShard::SetupCleanupPortions() { Counters.GetCSCounters().OnSetupCleanup(); if (!AppDataVerified().ColumnShardConfig.GetCleanupEnabled() || !NYDBTest::TControllers::GetColumnShardController()->IsBackgroundEnabled(NYDBTest::ICSController::EBackground::Cleanup)) { @@ -784,21 +1053,24 @@ void TColumnShard::SetupCleanupPortions() { return; } - auto changes = - TablesManager.MutablePrimaryIndex().StartCleanupPortions(GetMinReadSnapshot(), TablesManager.GetPathsToDrop(), DataLocksManager); + const NOlap::TSnapshot minReadSnapshot = GetMinReadSnapshot(); + THashSet pathsToDrop = TablesManager.GetPathsToDrop(minReadSnapshot); + + auto changes = TablesManager.MutablePrimaryIndex().StartCleanupPortions(minReadSnapshot, pathsToDrop, DataLocksManager); if (!changes) { ACFL_DEBUG("background", "cleanup")("skip_reason", "no_changes"); return; } - - ACFL_DEBUG("background", "cleanup")("changes_info", changes->DebugString()); - auto actualIndexInfo = std::make_shared(TablesManager.GetPrimaryIndex()->GetVersionedIndex()); - auto ev = std::make_unique(actualIndexInfo, changes, false); - ev->SetPutStatus(NKikimrProto::OK); // No new blobs to write - changes->Start(*this); - Send(SelfId(), ev.release()); + auto request = changes->ExtractDataAccessorsRequest(); + auto actualIndexInfo = TablesManager.GetPrimaryIndex()->GetVersionedIndexReadonlyCopy(); + const ui64 accessorsMemory = request->PredictAccessorsMemory(TablesManager.GetPrimaryIndex()->GetVersionedIndex().GetLastSchema()); + const auto subscriber = std::make_shared(SelfId(), changes, actualIndexInfo); + + NOlap::NResourceBroker::NSubscribe::ITask::StartResourceSubscription( + ResourceSubscribeActor, std::make_shared(accessorsMemory, changes->GetTaskIdentifier(), TTLTaskSubscription, + std::move(request), subscriber, DataAccessorsManager.GetObjectPtrVerified())); } void TColumnShard::SetupCleanupTables() { @@ -809,7 +1081,7 @@ void TColumnShard::SetupCleanupTables() { } THashSet pathIdsEmptyInInsertTable; - for (auto&& i : TablesManager.GetPathsToDrop()) { + for (auto&& i : TablesManager.GetPathsToDrop(GetMinReadSnapshot())) { if (InsertTable->HasPathIdData(i)) { continue; } @@ -823,7 +1095,7 @@ void TColumnShard::SetupCleanupTables() { } ACFL_DEBUG("background", "cleanup")("changes_info", changes->DebugString()); - auto actualIndexInfo = std::make_shared(TablesManager.GetPrimaryIndex()->GetVersionedIndex()); + auto actualIndexInfo = TablesManager.GetPrimaryIndex()->GetVersionedIndexReadonlyCopy(); auto ev = std::make_unique(actualIndexInfo, changes, false); ev->SetPutStatus(NKikimrProto::OK); // No new blobs to write @@ -846,6 +1118,17 @@ void TColumnShard::SetupGC() { } } +void TColumnShard::Handle(TEvPrivate::TEvStartCompaction::TPtr& ev, const TActorContext& /*ctx*/) { + StartCompaction(ev->Get()->GetGuard()); +} + +void TColumnShard::Handle(TEvPrivate::TEvMetadataAccessorsInfo::TPtr& ev, const TActorContext& /*ctx*/) { + AFL_VERIFY(ev->Get()->GetGeneration() == Generation())("ev", ev->Get()->GetGeneration())("tablet", Generation()); + ev->Get()->GetProcessor()->ApplyResult( + ev->Get()->ExtractResult(), TablesManager.MutablePrimaryIndexAsVerified()); + SetupMetadata(); +} + void TColumnShard::Handle(TEvPrivate::TEvGarbageCollectionFinished::TPtr& ev, const TActorContext& ctx) { Execute(new TTxGarbageCollectionFinished(this, ev->Get()->Action), ctx); } @@ -867,11 +1150,12 @@ void TColumnShard::SetupCleanupInsertTable() { } void TColumnShard::Die(const TActorContext& ctx) { + AFL_VERIFY(TabletActivityImpl->Dec() == 0); CleanupActors(ctx); NTabletPipe::CloseAndForgetClient(SelfId(), StatsReportPipe); UnregisterMediatorTimeCast(); NYDBTest::TControllers::GetColumnShardController()->OnTabletStopped(*this); - return IActor::Die(ctx); + IActor::Die(ctx); } void TColumnShard::Handle(NActors::TEvents::TEvUndelivered::TPtr& ev, const TActorContext&) { @@ -954,7 +1238,6 @@ void TColumnShard::Handle(NOlap::NDataSharing::NEvents::TEvConfirmFromInitiator: if (currentSession->IsConfirmed()) { currentSession->GetInitiatorController().ConfirmSuccess(ev->Get()->Record.GetSessionId()); } else { - auto txConclusion = SharingSessionsManager->ConfirmDestSession(this, currentSession); Execute(txConclusion.release(), ctx); } @@ -983,16 +1266,26 @@ void TColumnShard::Handle(NOlap::NDataSharing::NEvents::TEvSendDataFromSource::T return; } + // in current implementation the next loop will crash if schemas will be sent in the same package with the data, so adding this verify to ensure consistent behaviour + AFL_VERIFY(ev->Get()->Record.GetPathIdData().empty() || ev->Get()->Record.GetSchemeHistory().empty())("reason", "can not send schemas and data in the same package"); + THashMap dataByPathId; + TBlobGroupSelector dsGroupSelector(Info()); for (auto&& i : ev->Get()->Record.GetPathIdData()) { - auto schema = TablesManager.GetPrimaryIndexAsVerified().GetVersionedIndex().GetLastSchema(); - AFL_VERIFY(schema); - auto data = NOlap::NDataSharing::NEvents::TPathIdData::BuildFromProto(i, schema->GetIndexInfo()); + auto data = NOlap::NDataSharing::NEvents::TPathIdData::BuildFromProto(i, TablesManager.GetPrimaryIndexAsVerified().GetVersionedIndex(), dsGroupSelector); AFL_VERIFY(data.IsSuccess())("error", data.GetErrorMessage()); AFL_VERIFY(dataByPathId.emplace(i.GetPathId(), data.DetachResult()).second); } - auto txConclusion = currentSession->ReceiveData(this, dataByPathId, ev->Get()->Record.GetPackIdx(), (NOlap::TTabletId)ev->Get()->Record.GetSourceTabletId(), currentSession); + const auto& schemeHistoryProto = ev->Get()->Record.GetSchemeHistory(); + + std::vector schemas; + + for (auto&& i : schemeHistoryProto) { + schemas.emplace_back(i); + } + + auto txConclusion = currentSession->ReceiveData(this, std::move(dataByPathId), std::move(schemas), ev->Get()->Record.GetPackIdx(), (NOlap::TTabletId)ev->Get()->Record.GetSourceTabletId(), currentSession); if (!txConclusion) { AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "skip_received_data"); } else { @@ -1052,6 +1345,174 @@ void TColumnShard::Handle(NOlap::NDataSharing::NEvents::TEvFinishedFromSource::T } }; +class TPortionConstructorV2 { +private: + NOlap::TPortionInfo::TConstPtr PortionInfo; + std::optional Records; + std::optional> Indexes; + +public: + TPortionConstructorV2(const NOlap::TPortionInfo::TConstPtr& portionInfo) + : PortionInfo(portionInfo) { + } + + bool IsReady() const { + return HasRecords() && HasIndexes(); + } + + bool HasRecords() const { + return !!Records; + } + + bool HasIndexes() const { + return !!Indexes; + } + + void SetRecords(NOlap::TColumnChunkLoadContextV2&& records) { + AFL_VERIFY(!Records); + Records = std::move(records); + } + + void SetIndexes(std::vector&& indexes) { + AFL_VERIFY(!Indexes); + Indexes = std::move(indexes); + } + + NOlap::TPortionDataAccessor BuildAccessor() { + AFL_VERIFY(PortionInfo); + AFL_VERIFY(Records)("portion_id", PortionInfo->GetPortionId())("path_id", PortionInfo->GetPathId()); + AFL_VERIFY(Indexes)("portion_id", PortionInfo->GetPortionId())("path_id", PortionInfo->GetPathId()); + std::vector records = Records->BuildRecordsV1(); + return NOlap::TPortionAccessorConstructor::BuildForLoading(std::move(PortionInfo), std::move(records), std::move(*Indexes)); + } +}; + +class TAccessorsParsingTask: public NConveyor::ITask { +private: + std::shared_ptr FetchCallback; + std::vector Portions; + + virtual TConclusionStatus DoExecute(const std::shared_ptr& /*taskPtr*/) override { + std::vector accessors; + accessors.reserve(Portions.size()); + for (auto&& i : Portions) { + accessors.emplace_back(i.BuildAccessor()); + } + FetchCallback->OnAccessorsFetched(std::move(accessors)); + return TConclusionStatus::Success(); + } + virtual void DoOnCannotExecute(const TString& reason) override { + AFL_VERIFY(false)("cannot parse metadata", reason); + } + +public: + virtual TString GetTaskClassIdentifier() const override { + return "ASKED_METADATA_PARSER"; + } + + TAccessorsParsingTask( + const std::shared_ptr& callback, std::vector&& portions) + : FetchCallback(callback) + , Portions(std::move(portions)) + { + + } +}; + +class TTxAskPortionChunks: public TTransactionBase { +private: + using TBase = TTransactionBase; + std::shared_ptr FetchCallback; + THashMap> PortionsByPath; + std::vector FetchedAccessors; + const TString Consumer; + THashMap Constructors; + +public: + TTxAskPortionChunks(TColumnShard* self, const std::shared_ptr& fetchCallback, + std::vector&& portions, const TString& consumer) + : TBase(self) + , FetchCallback(fetchCallback) + , Consumer(consumer) + { + for (auto&& i : portions) { + PortionsByPath[i->GetPathId()].emplace_back(i); + } + } + + bool Execute(TTransactionContext& txc, const TActorContext& /*ctx*/) override { + NIceDb::TNiceDb db(txc.DB); + + TBlobGroupSelector selector(Self->Info()); + bool reask = false; + NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("consumer", Consumer)("event", "TTxAskPortionChunks::Execute"); + for (auto&& i : PortionsByPath) { + AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("size", i.second.size())("path_id", i.first); + for (auto&& p : i.second) { + auto itPortionConstructor = Constructors.find(p->GetAddress()); + if (itPortionConstructor == Constructors.end()) { + TPortionConstructorV2 constructor(p); + itPortionConstructor = Constructors.emplace(p->GetAddress(), std::move(constructor)).first; + } else if (itPortionConstructor->second.IsReady()) { + continue; + } + if (!itPortionConstructor->second.HasRecords()) { + auto rowset = db.Table().Key(p->GetPathId(), p->GetPortionId()).Select(); + if (!rowset.IsReady()) { + reask = true; + } else { + AFL_VERIFY(!rowset.EndOfSet())("path_id", p->GetPathId())("portion_id", p->GetPortionId())( + "debug", p->DebugString(true)); + NOlap::TColumnChunkLoadContextV2 info(rowset); + itPortionConstructor->second.SetRecords(std::move(info)); + } + } + if (!itPortionConstructor->second.HasIndexes()) { + if (!p->GetSchema(Self->GetIndexAs().GetVersionedIndex())->GetIndexesCount()) { + itPortionConstructor->second.SetIndexes({}); + } else { + auto rowset = db.Table().Prefix(p->GetPathId(), p->GetPortionId()).Select(); + if (!rowset.IsReady()) { + reask = true; + } else { + std::vector indexes; + bool localReask = false; + while (!localReask && !rowset.EndOfSet()) { + indexes.emplace_back(NOlap::TIndexChunkLoadContext(rowset, &selector)); + if (!rowset.Next()) { + reask = true; + localReask = true; + } + } + itPortionConstructor->second.SetIndexes(std::move(indexes)); + } + } + } + } + } + if (reask) { + return false; + } + + for (auto&& i : Constructors) { + FetchedAccessors.emplace_back(std::move(i.second)); + } + + AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("stage", "finished"); + NConveyor::TInsertServiceOperator::AsyncTaskToExecute(std::make_shared(FetchCallback, std::move(FetchedAccessors))); + return true; + } + void Complete(const TActorContext& /*ctx*/) override { + } + TTxType GetTxType() const override { + return TXTYPE_ASK_PORTION_METADATA; + } +}; + +void TColumnShard::Handle(NOlap::NDataAccessorControl::TEvAskTabletDataAccessors::TPtr& ev, const TActorContext& /*ctx*/) { + Execute(new TTxAskPortionChunks(this, ev->Get()->GetCallback(), std::move(ev->Get()->MutablePortions()), ev->Get()->GetConsumer())); +} + void TColumnShard::Handle(NOlap::NDataSharing::NEvents::TEvAckFinishFromInitiator::TPtr& ev, const TActorContext& ctx) { AFL_NOTICE(NKikimrServices::TX_COLUMNSHARD)("process", "BlobsSharing")("event", "TEvAckFinishFromInitiator"); auto currentSession = SharingSessionsManager->GetDestinationSession(ev->Get()->Record.GetSessionId()); @@ -1129,19 +1590,11 @@ void TColumnShard::Handle(NOlap::NBlobOperations::NEvents::TEvDeleteSharedBlobs: Execute(new TTxRemoveSharedBlobs(this, blobs, NActors::ActorIdFromProto(ev->Get()->Record.GetSourceActorId()), ev->Get()->Record.GetStorageId()), ctx); } -void TColumnShard::Handle(NMetadata::NProvider::TEvRefreshSubscriberData::TPtr& ev) { - Y_ABORT_UNLESS(Tiers); - AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("event", "TEvRefreshSubscriberData")("snapshot", ev->Get()->GetSnapshot()->SerializeToString()); - Tiers->TakeConfigs(ev->Get()->GetSnapshot(), nullptr); -} - -void TColumnShard::ActivateTiering(const ui64 pathId, const TString& useTiering) { +void TColumnShard::ActivateTiering(const ui64 pathId, const THashSet& usedTiers) { AFL_VERIFY(Tiers); - if (useTiering) { - AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("event", "activate_tiering")("path_id", pathId)("tiering", useTiering); - } - if (useTiering) { - Tiers->EnablePathId(pathId, useTiering); + if (!usedTiers.empty()) { + AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("event", "activate_tiering")("path_id", pathId)("tiers", JoinStrings(usedTiers.begin(), usedTiers.end(), ",")); + Tiers->EnablePathId(pathId, usedTiers); } else { Tiers->DisablePathId(pathId); } @@ -1149,21 +1602,30 @@ void TColumnShard::ActivateTiering(const ui64 pathId, const TString& useTiering) } void TColumnShard::Enqueue(STFUNC_SIG) { - const TLogContextGuard gLogging = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", TabletID())("self_id", SelfId()); + const TLogContextGuard gLogging = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", TabletID())( + "self_id", SelfId())("process", "Enqueue")("ev", ev->GetTypeName()); switch (ev->GetTypeRewrite()) { - HFunc(TEvPrivate::TEvTieringModified, Handle); + HFunc(TEvPrivate::TEvTieringModified, HandleInit); HFunc(TEvPrivate::TEvNormalizerResult, Handle); + HFunc(NOlap::NDataAccessorControl::TEvAskTabletDataAccessors, Handle); default: + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "unexpected event in enqueue"); return NTabletFlatExecutor::TTabletExecutedFlat::Enqueue(ev); } } void TColumnShard::OnTieringModified(const std::optional pathId) { AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "OnTieringModified")("path_id", pathId); - if (Tiers->IsReady()) { - StoragesManager->OnTieringModified(Tiers); - if (TablesManager.HasPrimaryIndex()) { - TablesManager.MutablePrimaryIndex().OnTieringModified(Tiers, TablesManager.GetTtl(), pathId); + StoragesManager->OnTieringModified(Tiers); + if (TablesManager.HasPrimaryIndex()) { + if (pathId) { + std::optional tableTtl; + if (auto* findTtl = TablesManager.GetTtl().FindPtr(*pathId)) { + tableTtl = *findTtl; + } + TablesManager.MutablePrimaryIndex().OnTieringModified(tableTtl, *pathId); + } else { + TablesManager.MutablePrimaryIndex().OnTieringModified(TablesManager.GetTtl()); } } } @@ -1173,8 +1635,4 @@ const NKikimr::NColumnShard::NTiers::TManager* TColumnShard::GetTierManagerPoint return Tiers->GetManagerOptional(tierId); } -TDuration TColumnShard::GetMaxReadStaleness() { - return NYDBTest::TControllers::GetColumnShardController()->GetReadTimeoutClean(); -} - -} +} // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/columnshard/columnshard_impl.h b/ydb/core/tx/columnshard/columnshard_impl.h index 1af1f3bbd08b..471a08c951ed 100644 --- a/ydb/core/tx/columnshard/columnshard_impl.h +++ b/ydb/core/tx/columnshard/columnshard_impl.h @@ -1,35 +1,32 @@ #pragma once -#include "defs.h" #include "background_controller.h" -#include "counters.h" #include "columnshard.h" -#include "columnshard_ttl.h" #include "columnshard_private_events.h" +#include "counters.h" +#include "defs.h" +#include "inflight_request_tracker.h" #include "tables_manager.h" -#include "blobs_action/events/delete_blobs.h" #include "bg_tasks/events/local.h" -#include "transactions/tx_controller.h" -#include "inflight_request_tracker.h" +#include "blobs_action/events/delete_blobs.h" #include "counters/columnshard.h" #include "counters/counters_manager.h" -#include "resource_subscriber/counters.h" -#include "resource_subscriber/task.h" -#include "normalizer/abstract/abstract.h" -#include "operations/manager.h" - -#include "export/events/events.h" - +#include "data_sharing/common/transactions/tx_extension.h" #include "data_sharing/destination/events/control.h" -#include "data_sharing/source/events/control.h" #include "data_sharing/destination/events/transfer.h" -#include "data_sharing/source/events/transfer.h" #include "data_sharing/manager/sessions.h" #include "data_sharing/manager/shared_blobs.h" -#include "data_sharing/common/transactions/tx_extension.h" #include "data_sharing/modification/events/change_owning.h" - +#include "data_sharing/source/events/control.h" +#include "data_sharing/source/events/transfer.h" +#include "export/events/events.h" +#include "normalizer/abstract/abstract.h" +#include "operations/events.h" +#include "operations/manager.h" +#include "resource_subscriber/counters.h" +#include "resource_subscriber/task.h" #include "subscriber/abstract/manager/manager.h" +#include "transactions/tx_controller.h" #include #include @@ -38,12 +35,13 @@ #include #include #include +#include #include #include #include -#include -#include + #include +#include namespace NKikimr::NOlap { class TCleanupPortionsColumnEngineChanges; @@ -60,14 +58,17 @@ class TTxInternalScan; namespace NPlain { class TIndexScannerConstructor; } +namespace NSimple { +class TIndexScannerConstructor; } +} // namespace NReader namespace NDataSharing { class TTxDataFromSource; class TTxDataAckToSource; class TTxFinishAckToSource; class TTxFinishAckFromInitiator; -} +} // namespace NDataSharing namespace NBackground { class TSessionsManager; @@ -77,15 +78,15 @@ namespace NBlobOperations { namespace NBlobStorage { class TWriteAction; class TOperator; -} +} // namespace NBlobStorage namespace NTier { class TOperator; } -} +} // namespace NBlobOperations namespace NCompaction { class TGeneralCompactColumnEngineChanges; } -} +} // namespace NKikimr::NOlap namespace NKikimr::NColumnShard { @@ -96,6 +97,22 @@ class TTxInsertTableCleanup; class TTxRemoveSharedBlobs; class TOperationsManager; class TWaitEraseTablesTxSubscriber; +class TTxBlobsWritingFinished; +class TTxBlobsWritingFailed; + +namespace NLoading { +class TInsertTableInitializer; +class TTxControllerInitializer; +class TOperationsManagerInitializer; +class TStoragesManagerInitializer; +class TLongTxInitializer; +class TDBLocksInitializer; +class TBackgroundSessionsInitializer; +class TSharingSessionsInitializer; +class TInFlightReadsInitializer; +class TSpecialValuesInitializer; +class TTablesManagerInitializer; +} // namespace NLoading extern bool gAllowLogBatchingDefaultValue; @@ -121,8 +138,8 @@ struct TSettings { TSettings() : BlobWriteGrouppingEnabled(1, 0, 1) , CacheDataAfterIndexing(1, 0, 1) - , CacheDataAfterCompaction(1, 0, 1) - {} + , CacheDataAfterCompaction(1, 0, 1) { + } void RegisterControls(TControlBoard& icb) { icb.RegisterSharedControl(BlobWriteGrouppingEnabled, "ColumnShardControls.BlobWriteGrouppingEnabled"); @@ -136,10 +153,7 @@ using ITransaction = NTabletFlatExecutor::ITransaction; template using TTransactionBase = NTabletFlatExecutor::TTransactionBase; -class TColumnShard - : public TActor - , public NTabletFlatExecutor::TTabletExecutedFlat -{ +class TColumnShard: public TActor, public NTabletFlatExecutor::TTabletExecutedFlat { friend class TEvWriteCommitSecondaryTransactionOperator; friend class TEvWriteCommitPrimaryTransactionOperator; friend class TTxInsertTableCleanup; @@ -150,6 +164,8 @@ class TColumnShard friend class TTxNotifyTxCompletion; friend class TTxPlanStep; friend class TTxWrite; + friend class TTxBlobsWritingFinished; + friend class TTxBlobsWritingFailed; friend class TTxReadBase; friend class TTxRead; friend class TTxWriteIndex; @@ -179,12 +195,14 @@ class TColumnShard friend class NOlap::NDataSharing::TTxDataAckToSource; friend class NOlap::NDataSharing::TTxFinishAckToSource; friend class NOlap::NDataSharing::TTxFinishAckFromInitiator; + friend class NOlap::NDataSharing::TSourceSession; friend class NOlap::TStoragesManager; friend class NOlap::NReader::TTxScan; friend class NOlap::NReader::TTxInternalScan; friend class NOlap::NReader::NPlain::TIndexScannerConstructor; + friend class NOlap::NReader::NSimple::TIndexScannerConstructor; class TStoragesManager; friend class TTxController; @@ -199,6 +217,17 @@ class TColumnShard friend class IProposeTxOperator; friend class TSharingTransactionOperator; + friend class NLoading::TInsertTableInitializer; + friend class NLoading::TTxControllerInitializer; + friend class NLoading::TOperationsManagerInitializer; + friend class NLoading::TStoragesManagerInitializer; + friend class NLoading::TLongTxInitializer; + friend class NLoading::TDBLocksInitializer; + friend class NLoading::TBackgroundSessionsInitializer; + friend class NLoading::TSharingSessionsInitializer; + friend class NLoading::TInFlightReadsInitializer; + friend class NLoading::TSpecialValuesInitializer; + friend class NLoading::TTablesManagerInitializer; class TTxProgressTx; class TTxProposeCancel; @@ -220,14 +249,18 @@ class TColumnShard void Handle(TEvMediatorTimecast::TEvRegisterTabletResult::TPtr& ev, const TActorContext& ctx); void Handle(TEvMediatorTimecast::TEvNotifyPlanStep::TPtr& ev, const TActorContext& ctx); void Handle(TEvPrivate::TEvWriteBlobsResult::TPtr& ev, const TActorContext& ctx); - void Handle(TEvPrivate::TEvScanStats::TPtr &ev, const TActorContext &ctx); - void Handle(TEvPrivate::TEvReadFinished::TPtr &ev, const TActorContext &ctx); + void Handle(TEvPrivate::TEvStartCompaction::TPtr& ev, const TActorContext& ctx); + void Handle(TEvPrivate::TEvMetadataAccessorsInfo::TPtr& ev, const TActorContext& ctx); + + void Handle(NPrivateEvents::NWrite::TEvWritePortionResult::TPtr& ev, const TActorContext& ctx); + + void Handle(TEvPrivate::TEvScanStats::TPtr& ev, const TActorContext& ctx); + void Handle(TEvPrivate::TEvReadFinished::TPtr& ev, const TActorContext& ctx); void Handle(TEvPrivate::TEvPeriodicWakeup::TPtr& ev, const TActorContext& ctx); void Handle(NActors::TEvents::TEvWakeup::TPtr& ev, const TActorContext& ctx); void Handle(TEvPrivate::TEvPingSnapshotsUsage::TPtr& ev, const TActorContext& ctx); void Handle(TEvPrivate::TEvWriteIndex::TPtr& ev, const TActorContext& ctx); - void Handle(NMetadata::NProvider::TEvRefreshSubscriberData::TPtr& ev); void Handle(NEvents::TDataEvents::TEvWrite::TPtr& ev, const TActorContext& ctx); void Handle(TEvPrivate::TEvWriteDraft::TPtr& ev, const TActorContext& ctx); void Handle(TEvPrivate::TEvGarbageCollectionFinished::TPtr& ev, const TActorContext& ctx); @@ -254,6 +287,10 @@ class TColumnShard void Handle(NOlap::NDataSharing::NEvents::TEvAckFinishToSource::TPtr& ev, const TActorContext& ctx); void Handle(NOlap::NDataSharing::NEvents::TEvAckFinishFromInitiator::TPtr& ev, const TActorContext& ctx); + void Handle(NOlap::NDataAccessorControl::TEvAskTabletDataAccessors::TPtr& ev, const TActorContext& ctx); + + void HandleInit(TEvPrivate::TEvTieringModified::TPtr& ev, const TActorContext&); + ITransaction* CreateTxInitSchema(); void OnActivateExecutor(const TActorContext& ctx) override; @@ -272,7 +309,7 @@ class TColumnShard void CleanupActors(const TActorContext& ctx); void BecomeBroken(const TActorContext& ctx); - void SwitchToWork(const TActorContext& ctx); + void TrySwitchToWork(const TActorContext& ctx); bool IsAnyChannelYellowStop() const { return Executor()->GetStats().IsAnyChannelYellowStop; @@ -286,9 +323,11 @@ class TColumnShard putStatus.OnYellowChannels(Executor()); } - void ActivateTiering(const ui64 pathId, const TString& useTiering); + void ActivateTiering(const ui64 pathId, const THashSet& usedTiers); void OnTieringModified(const std::optional pathId = {}); + std::shared_ptr TabletActivityImpl = std::make_shared(0); + public: ui64 BuildEphemeralTxId() { static TAtomicCounter Counter = 0; @@ -323,14 +362,17 @@ class TColumnShard return TRowVersion(LastCompletedTx.GetPlanStep(), LastCompletedTx.GetTxId()); } - ui32 Generation() const { return Executor()->Generation(); } + ui32 Generation() const { + return Executor()->Generation(); + } bool IsUserTable(const TTableId&) const { return true; } private: - void OverloadWriteFail(const EOverloadStatus overloadReason, const NEvWrite::TWriteMeta& writeMeta, const ui64 writeSize, const ui64 cookie, std::unique_ptr&& event, const TActorContext& ctx); + void OverloadWriteFail(const EOverloadStatus overloadReason, const NEvWrite::TWriteMeta& writeMeta, const ui64 writeSize, const ui64 cookie, + std::unique_ptr&& event, const TActorContext& ctx); EOverloadStatus CheckOverloaded(const ui64 tableId) const; protected: @@ -343,21 +385,19 @@ class TColumnShard TRACE_EVENT(NKikimrServices::TX_COLUMNSHARD); switch (ev->GetTypeRewrite()) { HFunc(TEvTablet::TEvTabletDead, HandleTabletDead); - default: - LOG_S_WARN("TColumnShard.StateBroken at " << TabletID() - << " unhandled event type: " << ev->GetTypeRewrite() - << " event: " << ev->ToString()); - Send(IEventHandle::ForwardOnNondelivery(std::move(ev), NActors::TEvents::TEvUndelivered::ReasonActorUnknown)); - break; + default: + LOG_S_WARN("TColumnShard.StateBroken at " << TabletID() << " unhandled event type: " << ev->GetTypeName() + << " event: " << ev->ToString()); + Send(IEventHandle::ForwardOnNondelivery(std::move(ev), NActors::TEvents::TEvUndelivered::ReasonActorUnknown)); + break; } } STFUNC(StateWork) { - const TLogContextGuard gLogging = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", TabletID())("self_id", SelfId()); + const TLogContextGuard gLogging = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", TabletID())( + "self_id", SelfId())("ev", ev->GetTypeName()); TRACE_EVENT(NKikimrServices::TX_COLUMNSHARD); switch (ev->GetTypeRewrite()) { - hFunc(NMetadata::NProvider::TEvRefreshSubscriberData, Handle); - HFunc(TEvTxProcessing::TEvReadSet, Handle); HFunc(TEvTxProcessing::TEvReadSetAck, Handle); @@ -374,6 +414,10 @@ class TColumnShard HFunc(TEvTxProcessing::TEvPlanStep, Handle); HFunc(TEvColumnShard::TEvWrite, Handle); HFunc(TEvPrivate::TEvWriteBlobsResult, Handle); + HFunc(TEvPrivate::TEvStartCompaction, Handle); + HFunc(TEvPrivate::TEvMetadataAccessorsInfo, Handle); + HFunc(NPrivateEvents::NWrite::TEvWritePortionResult, Handle); + HFunc(TEvMediatorTimecast::TEvRegisterTabletResult, Handle); HFunc(TEvMediatorTimecast::TEvNotifyPlanStep, Handle); HFunc(TEvPrivate::TEvWriteIndex, Handle); @@ -382,7 +426,7 @@ class TColumnShard HFunc(TEvPrivate::TEvPeriodicWakeup, Handle); HFunc(NActors::TEvents::TEvWakeup, Handle); HFunc(TEvPrivate::TEvPingSnapshotsUsage, Handle); - + HFunc(NEvents::TDataEvents::TEvWrite, Handle); HFunc(TEvPrivate::TEvWriteDraft, Handle); HFunc(TEvPrivate::TEvGarbageCollectionFinished, Handle); @@ -406,13 +450,14 @@ class TColumnShard HFunc(NOlap::NDataSharing::NEvents::TEvFinishedFromSource, Handle); HFunc(NOlap::NDataSharing::NEvents::TEvAckFinishToSource, Handle); HFunc(NOlap::NDataSharing::NEvents::TEvAckFinishFromInitiator, Handle); - default: - if (!HandleDefaultEvents(ev, SelfId())) { - LOG_S_WARN("TColumnShard.StateWork at " << TabletID() - << " unhandled event type: "<< ev->GetTypeRewrite() - << " event: " << ev->ToString()); - } - break; + HFunc(NOlap::NDataAccessorControl::TEvAskTabletDataAccessors, Handle); + + default: + if (!HandleDefaultEvents(ev, SelfId())) { + LOG_S_WARN("TColumnShard.StateWork at " << TabletID() << " unhandled event type: " << ev->GetTypeName() + << " event: " << ev->ToString()); + } + break; } } @@ -427,11 +472,14 @@ class TColumnShard std::shared_ptr BackgroundSessionsManager; std::shared_ptr DataLocksManager; + ui64 PrioritizationClientId = 0; + using TSchemaPreset = TSchemaPreset; using TTableInfo = TTableInfo; const TMonotonic CreateInstant = TMonotonic::Now(); std::optional StartInstant; + bool IsTxInitFinished = false; struct TLongTxWriteInfo { TInsertWriteId InsertWriteId; @@ -463,6 +511,9 @@ class TColumnShard TActorId ResourceSubscribeActor; TActorId BufferizationWriteActorId; + TActorId DataAccessorsControlActorId; + NOlap::NDataAccessorControl::TDataAccessorsManagerContainer DataAccessorsManager; + TActorId StatsReportPipe; std::vector ActorsToStop; @@ -487,7 +538,6 @@ class TColumnShard TLimits Limits; NOlap::TNormalizationController NormalizerController; NDataShard::TSysLocks SysLocks; - static TDuration GetMaxReadStaleness(); void TryRegisterMediatorTimeCast(); void UnregisterMediatorTimeCast(); @@ -505,9 +555,11 @@ class TColumnShard } TInsertWriteId HasLongTxWrite(const NLongTxService::TLongTxId& longTxId, const ui32 partId) const; - TInsertWriteId GetLongTxWrite(NIceDb::TNiceDb& db, const NLongTxService::TLongTxId& longTxId, const ui32 partId, const std::optional granuleShardingVersionId); + TInsertWriteId GetLongTxWrite( + NIceDb::TNiceDb& db, const NLongTxService::TLongTxId& longTxId, const ui32 partId, const std::optional granuleShardingVersionId); void AddLongTxWrite(const TInsertWriteId writeId, ui64 txId); - void LoadLongTxWrite(const TInsertWriteId writeId, const ui32 writePartId, const NLongTxService::TLongTxId& longTxId, const std::optional granuleShardingVersion); + void LoadLongTxWrite(const TInsertWriteId writeId, const ui32 writePartId, const NLongTxService::TLongTxId& longTxId, + const std::optional granuleShardingVersion); bool RemoveLongTxWrite(NIceDb::TNiceDb& db, const TInsertWriteId writeId, const ui64 txId); void EnqueueBackgroundActivities(const bool periodic = false); @@ -516,16 +568,24 @@ class TColumnShard void UpdateSchemaSeqNo(const TMessageSeqNo& seqNo, NTabletFlatExecutor::TTransactionContext& txc); void ProtectSchemaSeqNo(const NKikimrTxColumnShard::TSchemaSeqNo& seqNoProto, NTabletFlatExecutor::TTransactionContext& txc); - void RunSchemaTx(const NKikimrTxColumnShard::TSchemaTxBody& body, const NOlap::TSnapshot& version, NTabletFlatExecutor::TTransactionContext& txc); + void RunSchemaTx( + const NKikimrTxColumnShard::TSchemaTxBody& body, const NOlap::TSnapshot& version, NTabletFlatExecutor::TTransactionContext& txc); void RunInit(const NKikimrTxColumnShard::TInitShard& body, const NOlap::TSnapshot& version, NTabletFlatExecutor::TTransactionContext& txc); - void RunEnsureTable(const NKikimrTxColumnShard::TCreateTable& body, const NOlap::TSnapshot& version, NTabletFlatExecutor::TTransactionContext& txc); - void RunAlterTable(const NKikimrTxColumnShard::TAlterTable& body, const NOlap::TSnapshot& version, NTabletFlatExecutor::TTransactionContext& txc); - void RunDropTable(const NKikimrTxColumnShard::TDropTable& body, const NOlap::TSnapshot& version, NTabletFlatExecutor::TTransactionContext& txc); - void RunAlterStore(const NKikimrTxColumnShard::TAlterStore& body, const NOlap::TSnapshot& version, NTabletFlatExecutor::TTransactionContext& txc); + void RunEnsureTable( + const NKikimrTxColumnShard::TCreateTable& body, const NOlap::TSnapshot& version, NTabletFlatExecutor::TTransactionContext& txc); + void RunAlterTable( + const NKikimrTxColumnShard::TAlterTable& body, const NOlap::TSnapshot& version, NTabletFlatExecutor::TTransactionContext& txc); + void RunDropTable( + const NKikimrTxColumnShard::TDropTable& body, const NOlap::TSnapshot& version, NTabletFlatExecutor::TTransactionContext& txc); + void RunAlterStore( + const NKikimrTxColumnShard::TAlterStore& body, const NOlap::TSnapshot& version, NTabletFlatExecutor::TTransactionContext& txc); void StartIndexTask(std::vector&& dataToIndex, const i64 bytesToIndex); void SetupIndexation(); - void SetupCompaction(); + void SetupCompaction(const std::set& pathIds); + void StartCompaction(const std::shared_ptr& guard); + + void SetupMetadata(); bool SetupTtl(const THashMap& pathTtls = {}); void SetupCleanupPortions(); void SetupCleanupTables(); @@ -544,6 +604,10 @@ class TColumnShard public: ui64 TabletTxCounter = 0; + std::shared_ptr GetTabletActivity() const { + return TabletActivityImpl; + } + const TTablesManager& GetTablesManager() const { return TablesManager; } @@ -556,6 +620,10 @@ class TColumnShard return NOlap::TSnapshot(LastPlannedStep, LastPlannedTxId); } + NOlap::TSnapshot GetCurrentSnapshotForInternalModification() const { + return NOlap::TSnapshot::MaxForPlanStep(GetOutdatedStep()); + } + const std::shared_ptr& GetSharingSessionsManager() const { return SharingSessionsManager; } @@ -622,4 +690,4 @@ class TColumnShard TColumnShard(TTabletStorageInfo* info, const TActorId& tablet); }; -} +} // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/columnshard/columnshard_private_events.h b/ydb/core/tx/columnshard/columnshard_private_events.h index cb0e8cd97150..c8f6c29cb961 100644 --- a/ydb/core/tx/columnshard/columnshard_private_events.h +++ b/ydb/core/tx/columnshard/columnshard_private_events.h @@ -1,20 +1,29 @@ #pragma once -#include "blobs_action/abstract/gc.h" #include "defs.h" +#include "blobs_action/abstract/gc.h" + +#include #include #include -#include -#include #include +#include +#include +#include #include -#include +#include namespace NKikimr::NOlap::NReader { class IApplyAction; } +namespace NKikimr::NOlap { +class IBlobsWritingAction; +class TPortionInfo; +class TPortionInfoConstructor; +} // namespace NKikimr::NOlap + namespace NKikimr::NColumnShard { struct TEvPrivate { @@ -47,12 +56,60 @@ struct TEvPrivate { EvTaskProcessedResult, EvPingSnapshotsUsage, + EvWritePortionResult, + EvStartCompaction, + + EvRegisterGranuleDataAccessor, + EvUnregisterGranuleDataAccessor, + EvAskTabletDataAccessors, + EvAskServiceDataAccessors, + EvAddPortionDataAccessor, + EvRemovePortionDataAccessor, + EvMetadataAccessorsInfo, EvEnd }; static_assert(EvEnd < EventSpaceEnd(TEvents::ES_PRIVATE), "expect EvEnd < EventSpaceEnd(TEvents::ES_PRIVATE)"); + class TEvMetadataAccessorsInfo: public NActors::TEventLocal { + private: + const std::shared_ptr Processor; + const ui64 Generation; + std::optional> Result; + + public: + const std::shared_ptr& GetProcessor() const { + return Processor; + } + ui64 GetGeneration() const { + return Generation; + } + NOlap::NResourceBroker::NSubscribe::TResourceContainer ExtractResult() { + AFL_VERIFY(Result); + auto result = std::move(*Result); + Result.reset(); + return result; + } + + TEvMetadataAccessorsInfo(const std::shared_ptr& processor, const ui64 gen, + NOlap::NResourceBroker::NSubscribe::TResourceContainer&& result) + : Processor(processor) + , Generation(gen) + , Result(std::move(result)) { + } + }; + + class TEvStartCompaction: public NActors::TEventLocal { + private: + YDB_READONLY_DEF(std::shared_ptr, Guard); + + public: + TEvStartCompaction(const std::shared_ptr& g) + : Guard(g) { + } + }; + class TEvTaskProcessedResult: public NActors::TEventLocal { private: TConclusion> Result; @@ -67,23 +124,22 @@ struct TEvPrivate { } }; - struct TEvTieringModified: public TEventLocal { - }; + struct TEvTieringModified: public TEventLocal {}; struct TEvWriteDraft: public TEventLocal { const std::shared_ptr WriteController; TEvWriteDraft(std::shared_ptr controller) : WriteController(controller) { - } }; class TEvNormalizerResult: public TEventLocal { NOlap::INormalizerChanges::TPtr Changes; + public: TEvNormalizerResult(NOlap::INormalizerChanges::TPtr changes) - : Changes(changes) - {} + : Changes(changes) { + } NOlap::INormalizerChanges::TPtr GetChanges() const { Y_ABORT_UNLESS(!!Changes); @@ -95,27 +151,24 @@ struct TEvPrivate { const std::shared_ptr Action; TEvGarbageCollectionFinished(const std::shared_ptr& action) : Action(action) { - } }; /// Common event for Indexing and GranuleCompaction: write index data in TTxWriteIndex transaction. - struct TEvWriteIndex : public TEventLocal { + struct TEvWriteIndex: public TEventLocal { std::shared_ptr IndexInfo; std::shared_ptr IndexChanges; - bool GranuleCompaction{false}; + bool GranuleCompaction{ false }; TUsage ResourceUsage; - bool CacheData{false}; + bool CacheData{ false }; TDuration Duration; TBlobPutResult::TPtr PutResult; - TEvWriteIndex(const std::shared_ptr& indexInfo, - std::shared_ptr indexChanges, - bool cacheData) + TEvWriteIndex( + const std::shared_ptr& indexInfo, std::shared_ptr indexChanges, bool cacheData) : IndexInfo(indexInfo) , IndexChanges(indexChanges) - , CacheData(cacheData) - { + , CacheData(cacheData) { PutResult = std::make_shared(NKikimrProto::UNKNOWN); } @@ -135,13 +188,16 @@ struct TEvPrivate { } }; - struct TEvScanStats : public TEventLocal { - TEvScanStats(ui64 rows, ui64 bytes) : Rows(rows), Bytes(bytes) {} + struct TEvScanStats: public TEventLocal { + TEvScanStats(ui64 rows, ui64 bytes) + : Rows(rows) + , Bytes(bytes) { + } ui64 Rows; ui64 Bytes; }; - struct TEvReadFinished : public TEventLocal { + struct TEvReadFinished: public TEventLocal { explicit TEvReadFinished(ui64 requestCookie, ui64 txId = 0) : RequestCookie(requestCookie) , TxId(txId) { @@ -151,10 +207,10 @@ struct TEvPrivate { ui64 TxId; }; - struct TEvPeriodicWakeup : public TEventLocal { + struct TEvPeriodicWakeup: public TEventLocal { TEvPeriodicWakeup(bool manual = false) - : Manual(manual) - {} + : Manual(manual) { + } bool Manual; }; @@ -169,6 +225,7 @@ struct TEvPrivate { Internal, Request }; + private: NColumnShard::TBlobPutResult::TPtr PutResult; NOlap::TWritingBuffer WritesBuffer; @@ -176,7 +233,6 @@ struct TEvPrivate { YDB_ACCESSOR(EErrorClass, ErrorClass, EErrorClass::Internal); public: - NKikimrDataEvents::TEvWriteResult::EStatus GetWriteResultStatus() const { switch (ErrorClass) { case EErrorClass::Internal: @@ -185,7 +241,7 @@ struct TEvPrivate { return NKikimrDataEvents::TEvWriteResult::STATUS_BAD_REQUEST; } } - + static std::unique_ptr Error( const NKikimrProto::EReplyStatus status, NOlap::TWritingBuffer&& writesBuffer, const TString& error, const EErrorClass errorClass) { std::unique_ptr result = @@ -197,8 +253,7 @@ struct TEvPrivate { TEvWriteBlobsResult(const NColumnShard::TBlobPutResult::TPtr& putResult, NOlap::TWritingBuffer&& writesBuffer) : PutResult(putResult) - , WritesBuffer(std::move(writesBuffer)) - { + , WritesBuffer(std::move(writesBuffer)) { Y_ABORT_UNLESS(PutResult); } @@ -216,4 +271,4 @@ struct TEvPrivate { }; }; -} +} // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/columnshard/columnshard_schema.h b/ydb/core/tx/columnshard/columnshard_schema.h index 4f08426ddd70..1837c16501b0 100644 --- a/ydb/core/tx/columnshard/columnshard_schema.h +++ b/ydb/core/tx/columnshard/columnshard_schema.h @@ -11,10 +11,6 @@ #include -namespace NKikimr::NOlap { -class TColumnChunkLoadContext; -} - namespace NKikimr::NColumnShard { using NOlap::TInsertWriteId; @@ -59,7 +55,9 @@ struct Schema : NIceDb::Schema { ShardingInfoTableId, RepairsTableId, NormalizersTableId, - NormalizerEventsTableId + NormalizerEventsTableId, + ColumnsV1TableId, + ColumnsV2TableId }; enum class ETierTables: ui32 { @@ -503,9 +501,15 @@ struct Schema : NIceDb::Schema { struct XTxId: Column<5, NScheme::NTypeIds::Uint64> {}; struct Metadata: Column<6, NScheme::NTypeIds::String> {}; // NKikimrTxColumnShard.TIndexColumnMeta struct ShardingVersion: Column<7, NScheme::NTypeIds::Uint64> {}; + struct MinSnapshotPlanStep: Column<8, NScheme::NTypeIds::Uint64> {}; + struct MinSnapshotTxId: Column<9, NScheme::NTypeIds::Uint64> {}; + struct CommitPlanStep: Column<10, NScheme::NTypeIds::Uint64> {}; + struct CommitTxId: Column<11, NScheme::NTypeIds::Uint64> {}; + struct InsertWriteId: Column<12, NScheme::NTypeIds::Uint64> {}; using TKey = TableKey; - using TColumns = TableColumns; + using TColumns = TableColumns; }; struct BackgroundSessions: Table { @@ -552,6 +556,29 @@ struct Schema : NIceDb::Schema { using TColumns = TableColumns; }; + struct IndexColumnsV1: Table { + struct PathId: Column<1, NScheme::NTypeIds::Uint64> {}; + struct PortionId: Column<2, NScheme::NTypeIds::Uint64> {}; + struct SSColumnId: Column<3, NScheme::NTypeIds::Uint32> {}; + struct ChunkIdx: Column<4, NScheme::NTypeIds::Uint32> {}; + struct Metadata: Column<5, NScheme::NTypeIds::String> {}; // NKikimrTxColumnShard.TIndexColumnMeta + struct BlobIdx: Column<6, NScheme::NTypeIds::Uint32> {}; + struct Offset: Column<7, NScheme::NTypeIds::Uint32> {}; + struct Size: Column<8, NScheme::NTypeIds::Uint32> {}; + + using TKey = TableKey; + using TColumns = TableColumns; + }; + + struct IndexColumnsV2: Table { + struct PathId: Column<1, NScheme::NTypeIds::Uint64> {}; + struct PortionId: Column<2, NScheme::NTypeIds::Uint64> {}; + struct Metadata: Column<3, NScheme::NTypeIds::String> {}; + + using TKey = TableKey; + using TColumns = TableColumns; + }; + using TTables = SchemaTables< Value, TxInfo, @@ -589,7 +616,9 @@ struct Schema : NIceDb::Schema { InFlightSnapshots, TxDependencies, TxStates, - TxEvents + TxEvents, + IndexColumnsV1, + IndexColumnsV2 >; // @@ -752,10 +781,8 @@ struct Schema : NIceDb::Schema { db.Table().Key(id).Delete(); } - static void SaveTableInfo(NIceDb::TNiceDb& db, const ui64 pathId, const TString tieringUsage) { - db.Table().Key(pathId).Update( - NIceDb::TUpdate(tieringUsage) - ); + static void SaveTableInfo(NIceDb::TNiceDb& db, const ui64 pathId) { + db.Table().Key(pathId).Update(); } @@ -895,27 +922,69 @@ struct Schema : NIceDb::Schema { } namespace NKikimr::NOlap { +class TPortionLoadContext { +private: + YDB_READONLY(ui64, PathId, 0); + YDB_READONLY(ui64, PortionId, 0); + YDB_READONLY_DEF(NKikimrTxColumnShard::TIndexPortionMeta, MetaProto); + YDB_READONLY_DEF(std::optional, DeprecatedMinSnapshot); + +public: + template + TPortionLoadContext(const TSource& rowset) { + PathId = rowset.template GetValue(); + PortionId = rowset.template GetValue(); + const TString metadata = rowset.template GetValue(); + AFL_VERIFY(rowset.template HaveValue() == rowset.template HaveValue()); + if (rowset.template HaveValue()) { + DeprecatedMinSnapshot = NOlap::TSnapshot(rowset.template GetValue(), + rowset.template GetValue()); + } + AFL_VERIFY(MetaProto.ParseFromArray(metadata.data(), metadata.size()))("event", "cannot parse metadata as protobuf"); + } +}; + class TColumnChunkLoadContext { private: YDB_READONLY_DEF(TBlobRange, BlobRange); TChunkAddress Address; + YDB_READONLY(ui64, PathId, 0); + YDB_READONLY(ui64, PortionId, 0); YDB_READONLY_DEF(NKikimrTxColumnShard::TIndexColumnMeta, MetaProto); + YDB_READONLY(TSnapshot, RemoveSnapshot, TSnapshot::Zero()); + YDB_READONLY(TSnapshot, MinSnapshotDeprecated, TSnapshot::Zero()); + public: + TPortionAddress GetPortionAddress() const { + return TPortionAddress(PathId, PortionId); + } + const TChunkAddress& GetAddress() const { return Address; } - TColumnChunkLoadContext(const TChunkAddress& address, const TBlobRange& bRange, const NKikimrTxColumnShard::TIndexColumnMeta& metaProto) + TFullChunkAddress GetFullChunkAddress() const { + return TFullChunkAddress(PathId, PortionId, Address.GetEntityId(), Address.GetChunkIdx()); + } + + TColumnChunkLoadContext(const ui64 pathId, const ui64 portionId, const TChunkAddress& address, const TBlobRange& bRange, + const NKikimrTxColumnShard::TIndexColumnMeta& metaProto) : BlobRange(bRange) , Address(address) - , MetaProto(metaProto) - { - + , PathId(pathId) + , PortionId(portionId) + , MetaProto(metaProto) { } template TColumnChunkLoadContext(const TSource& rowset, const IBlobGroupSelector* dsGroupSelector) - : Address(rowset.template GetValue(), rowset.template GetValue()) { + : Address(rowset.template GetValue(), + rowset.template GetValue()) + , RemoveSnapshot(rowset.template GetValue(), + rowset.template GetValue()) + , MinSnapshotDeprecated(rowset.template GetValue(), + rowset.template GetValue()) + { AFL_VERIFY(Address.GetColumnId())("event", "incorrect address")("address", Address.DebugString()); TString strBlobId = rowset.template GetValue(); Y_ABORT_UNLESS(strBlobId.size() == sizeof(TLogoBlobID), "Size %" PRISZT " doesn't match TLogoBlobID", strBlobId.size()); @@ -923,18 +992,100 @@ class TColumnChunkLoadContext { BlobRange.BlobId = NOlap::TUnifiedBlobId(dsGroupSelector->GetGroup(logoBlobId), logoBlobId); BlobRange.Offset = rowset.template GetValue(); BlobRange.Size = rowset.template GetValue(); + PathId = rowset.template GetValue(); + PortionId = rowset.template GetValue(); AFL_VERIFY(BlobRange.BlobId.IsValid() && BlobRange.Size)("event", "incorrect blob")("blob", BlobRange.ToString()); const TString metadata = rowset.template GetValue(); AFL_VERIFY(MetaProto.ParseFromArray(metadata.data(), metadata.size()))("event", "cannot parse metadata as protobuf"); } +}; - const NKikimrTxColumnShard::TIndexPortionMeta* GetPortionMeta() const { - if (MetaProto.HasPortionMeta()) { - return &MetaProto.GetPortionMeta(); - } else { - return nullptr; +class TColumnChunkLoadContextV1 { +private: + TChunkAddress Address; + YDB_READONLY_DEF(TBlobRangeLink16, BlobRange); + YDB_READONLY(ui64, PathId, 0); + YDB_READONLY(ui64, PortionId, 0); + YDB_READONLY_DEF(NKikimrTxColumnShard::TIndexColumnMeta, MetaProto); + +public: + TPortionAddress GetPortionAddress() const { + return TPortionAddress(PathId, PortionId); + } + + NKikimrTxColumnShard::TColumnChunkInfo SerializeToDBProto() const { + NKikimrTxColumnShard::TColumnChunkInfo proto; + proto.SetSSColumnId(Address.GetColumnId()); + proto.SetChunkIdx(Address.GetChunkIdx()); + *proto.MutableChunkMetadata() = MetaProto; + *proto.MutableBlobRangeLink() = BlobRange.SerializeToProto(); + return proto; + } + + TFullChunkAddress GetFullChunkAddress() const { + return TFullChunkAddress(PathId, PortionId, Address.GetEntityId(), Address.GetChunkIdx()); + } + + const TChunkAddress& GetAddress() const { + return Address; + } + + TColumnChunkLoadContextV1(const ui64 pathId, const ui64 portionId, const TChunkAddress& address, const TBlobRangeLink16& bRange, + const NKikimrTxColumnShard::TIndexColumnMeta& metaProto) + : Address(address) + , BlobRange(bRange) + , PathId(pathId) + , PortionId(portionId) + , MetaProto(metaProto) { + } + + template + TColumnChunkLoadContextV1(const TSource& rowset) + : Address(rowset.template GetValue(), + rowset.template GetValue()) + , BlobRange(rowset.template GetValue(), + rowset.template GetValue(), + rowset.template GetValue()) + { + AFL_VERIFY(Address.GetColumnId())("event", "incorrect address")("address", Address.DebugString()); + PathId = rowset.template GetValue(); + PortionId = rowset.template GetValue(); + const TString metadata = rowset.template GetValue(); + AFL_VERIFY(MetaProto.ParseFromArray(metadata.data(), metadata.size()))("event", "cannot parse metadata as protobuf"); + } +}; + +class TColumnChunkLoadContextV2 { +private: + YDB_READONLY(ui64, PathId, 0); + YDB_READONLY(ui64, PortionId, 0); + YDB_READONLY_DEF(TString, MetadataProto); + +public: + template + TColumnChunkLoadContextV2(const TSource& rowset) { + PathId = rowset.template GetValue(); + PortionId = rowset.template GetValue(); + MetadataProto = rowset.template GetValue(); + } + + TColumnChunkLoadContextV2(const ui64 pathId, const ui64 portionId, const NKikimrTxColumnShard::TIndexPortionAccessor& proto) + : PathId(pathId) + , PortionId(portionId) + , MetadataProto(proto.SerializeAsString()) { + } + + std::vector BuildRecordsV1() const { + std::vector records; + NKikimrTxColumnShard::TIndexPortionAccessor metaProto; + AFL_VERIFY(metaProto.ParseFromArray(MetadataProto.data(), MetadataProto.size()))("event", "cannot parse metadata as protobuf"); + for (auto&& i : metaProto.GetChunks()) { + TColumnChunkLoadContextV1 result(PathId, PortionId, TChunkAddress(i.GetSSColumnId(), i.GetChunkIdx()), + TBlobRangeLink16::BuildFromProto(i.GetBlobRangeLink()).DetachResult(), i.GetChunkMetadata()); + records.emplace_back(std::move(result)); } + return records; } }; @@ -942,10 +1093,25 @@ class TIndexChunkLoadContext { private: YDB_READONLY_DEF(std::optional, BlobRange); YDB_READONLY_DEF(std::optional, BlobData); + YDB_READONLY(ui64, PathId, 0); + YDB_READONLY(ui64, PortionId, 0); TChunkAddress Address; const ui32 RecordsCount; const ui32 RawBytes; public: + ui32 GetRawBytes() const { + return RawBytes; + } + + ui32 GetDataSize() const { + if (BlobRange) { + return BlobRange->GetSize(); + } else { + AFL_VERIFY(!!BlobData); + return BlobData->size(); + } + } + TIndexChunk BuildIndexChunk(const TBlobRangeLink16::TLinkId blobLinkId) const { AFL_VERIFY(BlobRange); return TIndexChunk(Address.GetColumnId(), Address.GetChunkIdx(), RecordsCount, RawBytes, BlobRange->BuildLink(blobLinkId)); @@ -956,9 +1122,20 @@ class TIndexChunkLoadContext { return TIndexChunk(Address.GetColumnId(), Address.GetChunkIdx(), RecordsCount, RawBytes, *BlobData); } + TIndexChunk BuildIndexChunk(const TPortionInfo& portionInfo) const { + if (BlobData) { + return BuildIndexChunk(); + } else { + AFL_VERIFY(!!BlobRange); + return BuildIndexChunk(portionInfo.GetMeta().GetBlobIdxVerified(BlobRange->BlobId)); + } + } + template TIndexChunkLoadContext(const TSource& rowset, const IBlobGroupSelector* dsGroupSelector) - : Address(rowset.template GetValue(), rowset.template GetValue()) + : PathId(rowset.template GetValue()) + , PortionId(rowset.template GetValue()) + , Address(rowset.template GetValue(), rowset.template GetValue()) , RecordsCount(rowset.template GetValue()) , RawBytes(rowset.template GetValue()) { diff --git a/ydb/core/tx/columnshard/columnshard_ttl.h b/ydb/core/tx/columnshard/columnshard_ttl.h deleted file mode 100644 index de2378737e95..000000000000 --- a/ydb/core/tx/columnshard/columnshard_ttl.h +++ /dev/null @@ -1,93 +0,0 @@ -#pragma once -#include "defs.h" - -namespace NKikimr::NColumnShard { - -class TTtl { -public: - struct TEviction { - TDuration EvictAfter; - TString ColumnName; - ui32 UnitsInSecond = 0; // 0 means auto (data type specific) - }; - - struct TDescription { - std::optional Eviction; - - TDescription() = default; - - TDescription(const NKikimrSchemeOp::TColumnDataLifeCycle::TTtl& ttl) { - auto expireSec = TDuration::Seconds(ttl.GetExpireAfterSeconds()); - - Eviction = TEviction{expireSec, ttl.GetColumnName()}; - Y_ABORT_UNLESS(!Eviction->ColumnName.empty()); - - switch (ttl.GetColumnUnit()) { - case NKikimrSchemeOp::TTTLSettings::UNIT_SECONDS: - Eviction->UnitsInSecond = 1; - break; - case NKikimrSchemeOp::TTTLSettings::UNIT_MILLISECONDS: - Eviction->UnitsInSecond = 1000; - break; - case NKikimrSchemeOp::TTTLSettings::UNIT_MICROSECONDS: - Eviction->UnitsInSecond = 1000 * 1000; - break; - case NKikimrSchemeOp::TTTLSettings::UNIT_NANOSECONDS: - Eviction->UnitsInSecond = 1000 * 1000 * 1000; - break; - case NKikimrSchemeOp::TTTLSettings::UNIT_AUTO: - default: - break; - } - } - }; - - ui64 PathsCount() const { - return PathTtls.size(); - } - - void SetPathTtl(ui64 pathId, TDescription&& descr) { - if (descr.Eviction) { - auto& evict = descr.Eviction; - auto it = Columns.find(evict->ColumnName); - if (it != Columns.end()) { - evict->ColumnName = *it; // replace string dups (memory efficiency) - } else { - Columns.insert(evict->ColumnName); - } - PathTtls[pathId] = descr; - } else { - PathTtls.erase(pathId); - } - } - - void DropPathTtl(ui64 pathId) { - PathTtls.erase(pathId); - } - - bool AddTtls(THashMap& eviction) const { - for (auto& [pathId, descr] : PathTtls) { - if (!eviction[pathId].Add(Convert(descr))) { - return false; - } - } - return true; - } - - const THashSet& TtlColumns() const { return Columns; } - -private: - THashMap PathTtls; // pathId -> ttl - THashSet Columns; - - std::shared_ptr Convert(const TDescription& descr) const - { - if (descr.Eviction) { - auto& evict = descr.Eviction; - return NOlap::TTierInfo::MakeTtl(evict->EvictAfter, evict->ColumnName, evict->UnitsInSecond); - } - return {}; - } -}; - -} diff --git a/ydb/core/tx/columnshard/common/blob.cpp b/ydb/core/tx/columnshard/common/blob.cpp index 6bcf397e339a..192826965e1a 100644 --- a/ydb/core/tx/columnshard/common/blob.cpp +++ b/ydb/core/tx/columnshard/common/blob.cpp @@ -136,6 +136,21 @@ NKikimrColumnShardProto::TBlobRange TBlobRange::SerializeToProto() const { return result; } +TString TBlobRange::GetData(const TString& blobData) const { + AFL_VERIFY(Offset + Size <= blobData.size())("offset", Offset)("size", Size)("blobDataSize", blobData.size()); + return blobData.substr(Offset, Size); +} + +TBlobRange::TBlobRange(const TUnifiedBlobId& blobId /*= TUnifiedBlobId()*/, ui32 offset /*= 0*/, ui32 size /*= 0*/) + : BlobId(blobId) + , Offset(offset) + , Size(size) { + if (Size > 0) { + AFL_VERIFY(Offset < BlobId.BlobSize())("offset", Offset)("size", Size)("blob", BlobId.ToStringNew()); + AFL_VERIFY(Offset + Size <= BlobId.BlobSize())("offset", Offset)("size", Size)("blob", BlobId.ToStringNew()); + } +} + NKikimr::TConclusionStatus TBlobRangeLink16::DeserializeFromProto(const NKikimrColumnShardProto::TBlobRangeLink16& proto) { BlobIdx = proto.GetBlobIdx(); Offset = proto.GetOffset(); @@ -166,8 +181,12 @@ ui16 TBlobRangeLink16::GetBlobIdxVerified() const { return *BlobIdx; } -NKikimr::NOlap::TBlobRange TBlobRangeLink16::RestoreRange(const TUnifiedBlobId& blobId) const { +TBlobRange TBlobRangeLink16::RestoreRange(const TUnifiedBlobId& blobId) const { return TBlobRange(blobId, Offset, Size); } +bool TBlobRangeLink16::CheckBlob(const TUnifiedBlobId& blobId) const { + return Offset + Size <= blobId.BlobSize(); } + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/common/blob.h b/ydb/core/tx/columnshard/common/blob.h index 9aa06bd3d558..bf3fbf12417f 100644 --- a/ydb/core/tx/columnshard/common/blob.h +++ b/ydb/core/tx/columnshard/common/blob.h @@ -21,7 +21,12 @@ class IBlobGroupSelector { virtual ui32 GetGroup(const TLogoBlobID& blobId) const = 0; }; -class TUnifiedBlobId; +class TFakeGroupSelector: public IBlobGroupSelector { +public: + virtual ui32 GetGroup(const TLogoBlobID& /*blobId*/) const override { + return 1; + } +}; class TUnifiedBlobId { // Id of a blob in YDB distributed storage @@ -100,6 +105,10 @@ class TUnifiedBlobId { return Id.BlobId.BlobSize(); } + ui32 Channel() const { + return Id.BlobId.Channel(); + } + TLogoBlobID GetLogoBlobId() const { return Id.BlobId; } @@ -184,6 +193,7 @@ class TBlobRangeLink16 { } TBlobRange RestoreRange(const TUnifiedBlobId& blobId) const; + bool CheckBlob(const TUnifiedBlobId& blobId) const; }; struct TBlobRange { @@ -191,6 +201,16 @@ struct TBlobRange { ui32 Offset; ui32 Size; + ui32 GetSize() const { + return Size; + } + + ui32 GetOffset() const { + return Offset; + } + + TString GetData(const TString& blobData) const; + bool operator<(const TBlobRange& br) const { if (BlobId != br.BlobId) { return BlobId.GetLogoBlobId().Compare(br.BlobId.GetLogoBlobId()) < 0; @@ -252,15 +272,28 @@ struct TBlobRange { return Size == BlobId.BlobSize(); } - explicit TBlobRange(const TUnifiedBlobId& blobId = TUnifiedBlobId(), ui32 offset = 0, ui32 size = 0) - : BlobId(blobId) - , Offset(offset) - , Size(size) - { - if (Size > 0) { - Y_ABORT_UNLESS(Offset < BlobId.BlobSize()); - Y_ABORT_UNLESS(Offset + Size <= BlobId.BlobSize()); + explicit TBlobRange(const TUnifiedBlobId& blobId = TUnifiedBlobId(), ui32 offset = 0, ui32 size = 0); + + static TConclusionStatus Validate(const std::vector& blobIds, const TBlobRangeLink16& range) { + if (blobIds.size() <= range.GetBlobIdxVerified()) { + return TConclusionStatus::Fail( + "incorrect blob index: " + ::ToString(range.GetBlobIdxVerified()) + " in " + ::ToString(blobIds.size()) + " elements"); + } + return Validate(blobIds[range.GetBlobIdxVerified()], range); + } + + static TConclusionStatus Validate(const TUnifiedBlobId& blobId, const TBlobRangeLink16& range) { + if (!range.GetSize()) { + return TConclusionStatus::Fail("zero range size"); + } + if (blobId.BlobSize() <= range.GetOffset()) { + return TConclusionStatus::Fail("too big offset for blob: " + ::ToString(range.GetOffset()) + " in " + ::ToString(blobId.BlobSize())); + } + if (blobId.BlobSize() < range.GetOffset() + range.GetSize()) { + return TConclusionStatus::Fail("too big right border for blob: " + ::ToString(range.GetOffset()) + " + " + + ::ToString(range.GetSize()) + " in " + ::ToString(blobId.BlobSize())); } + return TConclusionStatus::Success(); } static TBlobRange FromBlobId(const TUnifiedBlobId& blobId) { diff --git a/ydb/core/tx/columnshard/common/portion.h b/ydb/core/tx/columnshard/common/portion.h index 311cfa23269c..444a1559d580 100644 --- a/ydb/core/tx/columnshard/common/portion.h +++ b/ydb/core/tx/columnshard/common/portion.h @@ -19,10 +19,10 @@ class TSpecialColumns { static constexpr const char* SPEC_COL_TX_ID = "_yql_tx_id"; static constexpr const char* SPEC_COL_WRITE_ID = "_yql_write_id"; static constexpr const char* SPEC_COL_DELETE_FLAG = "_yql_delete_flag"; - static const ui32 SPEC_COL_PLAN_STEP_INDEX = 0xffffff00; - static const ui32 SPEC_COL_TX_ID_INDEX = SPEC_COL_PLAN_STEP_INDEX + 1; - static const ui32 SPEC_COL_WRITE_ID_INDEX = SPEC_COL_PLAN_STEP_INDEX + 2; - static const ui32 SPEC_COL_DELETE_FLAG_INDEX = SPEC_COL_PLAN_STEP_INDEX + 3; + static constexpr const ui32 SPEC_COL_PLAN_STEP_INDEX = 0xffffff00; + static constexpr const ui32 SPEC_COL_TX_ID_INDEX = SPEC_COL_PLAN_STEP_INDEX + 1; + static constexpr const ui32 SPEC_COL_WRITE_ID_INDEX = SPEC_COL_PLAN_STEP_INDEX + 2; + static constexpr const ui32 SPEC_COL_DELETE_FLAG_INDEX = SPEC_COL_PLAN_STEP_INDEX + 3; }; } diff --git a/ydb/core/tx/columnshard/common/snapshot.cpp b/ydb/core/tx/columnshard/common/snapshot.cpp index eb6e62ccac0c..e0e873488985 100644 --- a/ydb/core/tx/columnshard/common/snapshot.cpp +++ b/ydb/core/tx/columnshard/common/snapshot.cpp @@ -43,4 +43,8 @@ NKikimr::NOlap::TSnapshot TSnapshot::MaxForPlanInstant(const TInstant planInstan return TSnapshot(planInstant.MilliSeconds(), ::Max()); } +NJson::TJsonValue TSnapshot::SerializeToJson() const { + return DebugJson(); +} + }; diff --git a/ydb/core/tx/columnshard/common/snapshot.h b/ydb/core/tx/columnshard/common/snapshot.h index 4bc99d268420..7f04203eea8a 100644 --- a/ydb/core/tx/columnshard/common/snapshot.h +++ b/ydb/core/tx/columnshard/common/snapshot.h @@ -26,6 +26,8 @@ class TSnapshot { , TxId(txId) { } + NJson::TJsonValue SerializeToJson() const; + constexpr TInstant GetPlanInstant() const noexcept { return TInstant::MilliSeconds(PlanStep); } diff --git a/ydb/core/tx/columnshard/common/volume.cpp b/ydb/core/tx/columnshard/common/volume.cpp new file mode 100644 index 000000000000..aecdaf1a5b42 --- /dev/null +++ b/ydb/core/tx/columnshard/common/volume.cpp @@ -0,0 +1,19 @@ +#include "volume.h" +#include + +namespace NKikimr::NOlap { + +TBlobsVolume TBlobsVolume::operator-(const TBlobsVolume& item) const { + AFL_VERIFY(item.BlobBytes <= BlobBytes); + AFL_VERIFY(item.RawBytes <= RawBytes); + return TBlobsVolume(BlobBytes - item.BlobBytes, RawBytes - item.RawBytes); +} + +void TBlobsVolume::operator-=(const TBlobsVolume& item) { + AFL_VERIFY(item.BlobBytes <= BlobBytes); + AFL_VERIFY(item.RawBytes <= RawBytes); + BlobBytes -= item.BlobBytes; + RawBytes -= item.RawBytes; +} + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/common/volume.h b/ydb/core/tx/columnshard/common/volume.h new file mode 100644 index 000000000000..dae887a480cb --- /dev/null +++ b/ydb/core/tx/columnshard/common/volume.h @@ -0,0 +1,40 @@ +#pragma once +#include + +#include + +namespace NKikimr::NOlap { +class TBlobsVolume { +private: + YDB_READONLY(ui64, BlobBytes, 0); + YDB_READONLY(ui64, RawBytes, 0); + +public: + TBlobsVolume(const ui64 blob, const ui64 raw) + : BlobBytes(blob) + , RawBytes(raw) { + } + + TBlobsVolume operator+(const TBlobsVolume& item) const { + return TBlobsVolume(BlobBytes + item.BlobBytes, RawBytes + item.RawBytes); + } + + void Clear() { + BlobBytes = 0; + RawBytes = 0; + } + + bool CheckWithMax(const TBlobsVolume& maxLimit) const { + return BlobBytes < maxLimit.BlobBytes && RawBytes < maxLimit.RawBytes; + } + + void operator+=(const TBlobsVolume& item) { + BlobBytes += item.BlobBytes; + RawBytes += item.RawBytes; + } + + TBlobsVolume operator-(const TBlobsVolume& item) const; + + void operator-=(const TBlobsVolume& item); +}; +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/common/ya.make b/ydb/core/tx/columnshard/common/ya.make index c7d8a27bf3ee..049f150dc259 100644 --- a/ydb/core/tx/columnshard/common/ya.make +++ b/ydb/core/tx/columnshard/common/ya.make @@ -8,6 +8,7 @@ SRCS( portion.cpp tablet_id.cpp blob.cpp + volume.cpp ) PEERDIR( diff --git a/ydb/core/tx/columnshard/counters/aggregation/table_stats.h b/ydb/core/tx/columnshard/counters/aggregation/table_stats.h index 68f39a4191de..2078e3b640fa 100644 --- a/ydb/core/tx/columnshard/counters/aggregation/table_stats.h +++ b/ydb/core/tx/columnshard/counters/aggregation/table_stats.h @@ -41,6 +41,14 @@ class TTableStatsBuilder { auto activeStats = ColumnEngine.GetTotalStats().Active(); tableStats.SetRowCount(activeStats.Rows); tableStats.SetDataSize(activeStats.Bytes); + for (ui32 ch = 0; ch < activeStats.ByChannel.size(); ch++) { + ui64 dataSize = activeStats.ByChannel[ch]; + if (dataSize > 0) { + auto item = tableStats.AddChannels(); + item->SetChannel(ch); + item->SetDataSize(dataSize); + } + } } }; diff --git a/ydb/core/tx/columnshard/counters/columnshard.h b/ydb/core/tx/columnshard/counters/columnshard.h index 81df8b300eb8..e5b55f713690 100644 --- a/ydb/core/tx/columnshard/counters/columnshard.h +++ b/ydb/core/tx/columnshard/counters/columnshard.h @@ -17,8 +17,7 @@ enum class EWriteFailReason { NoTable /* "no_table" */, IncorrectSchema /* "incorrect_schema" */, Overload /* "overload" */, - OverlimitReadRawMemory /* "overlimit_read_raw_memory" */, - OverlimitReadBlobMemory /* "overlimit_read_blob_memory" */ + CompactionCriteria /* "compaction_criteria" */ }; class TWriteCounters: public TCommonCountersOwner { diff --git a/ydb/core/tx/columnshard/counters/common/owner.h b/ydb/core/tx/columnshard/counters/common/owner.h index 456699c80dc0..aa2920b63c48 100644 --- a/ydb/core/tx/columnshard/counters/common/owner.h +++ b/ydb/core/tx/columnshard/counters/common/owner.h @@ -56,6 +56,10 @@ class TCommonCountersOwner { NMonitoring::THistogramPtr GetHistogram(const TString& name, NMonitoring::IHistogramCollectorPtr&& hCollector) const; TCommonCountersOwner(const TString& module, TIntrusivePtr<::NMonitoring::TDynamicCounters> baseSignals = nullptr); + + TCommonCountersOwner(TCommonCountersOwner&& other) + : TCommonCountersOwner(other) { + } }; class TValueGuard { diff --git a/ydb/core/tx/columnshard/counters/common/private.cpp b/ydb/core/tx/columnshard/counters/common/private.cpp index 560ba1ec11e5..db93c893e13d 100644 --- a/ydb/core/tx/columnshard/counters/common/private.cpp +++ b/ydb/core/tx/columnshard/counters/common/private.cpp @@ -12,7 +12,7 @@ class TRegularSignalBuilderActor: public NActors::TActorBootstrappedResendStatus(); - Schedule(TDuration::Seconds(5), new NActors::TEvents::TEvWakeup); + Schedule(TDuration::Seconds(13), new NActors::TEvents::TEvWakeup); } public: TRegularSignalBuilderActor(std::shared_ptr agent) @@ -23,7 +23,7 @@ class TRegularSignalBuilderActor: public NActors::TActorBootstrappedResendStatus(); - Schedule(TDuration::Seconds(5), new NActors::TEvents::TEvWakeup); + Schedule(TDuration::Seconds(13), new NActors::TEvents::TEvWakeup); Become(&TRegularSignalBuilderActor::StateMain); } diff --git a/ydb/core/tx/columnshard/counters/common_data.cpp b/ydb/core/tx/columnshard/counters/common_data.cpp index 121a9b183c16..ebdae779e8a2 100644 --- a/ydb/core/tx/columnshard/counters/common_data.cpp +++ b/ydb/core/tx/columnshard/counters/common_data.cpp @@ -19,4 +19,21 @@ TDataOwnerSignals::TDataOwnerSignals(const TString& module, const TString dataNa SkipEraseBytes = GetDeriviative(DataName + "/SkipErase/Bytes"); } +TLoadTimeSignals::TLoadTimer::~TLoadTimer() { + ui64 duration = (TInstant::Now() - Start).MicroSeconds(); + if (Failed) { + Signals.AddFailedLoadingTime(duration); + } else { + Signals.AddLoadingTime(duration); + } + AFL_INFO(NKikimrServices::TX_COLUMNSHARD)(Name, duration); +} + +void TLoadTimeSignals::TLoadTimer::AddLoadingFail() { + if (!Failed) { + Failed = true; + Signals.AddLoadingFail(); + } +} + } diff --git a/ydb/core/tx/columnshard/counters/common_data.h b/ydb/core/tx/columnshard/counters/common_data.h index 13c793d36d53..6c92a4fed41e 100644 --- a/ydb/core/tx/columnshard/counters/common_data.h +++ b/ydb/core/tx/columnshard/counters/common_data.h @@ -1,6 +1,8 @@ #pragma once #include "common/owner.h" +#include + namespace NKikimr::NColumnShard { class TDataOwnerSignals: public TCommonCountersOwner { @@ -53,4 +55,101 @@ class TDataOwnerSignals: public TCommonCountersOwner { }; +class TLoadTimeSignals: public TCommonCountersOwner { +public: + class TLoadTimer : public TNonCopyable { + private: + const TLoadTimeSignals& Signals; + TInstant Start; + TString Name; + bool Failed = false; + + public: + TLoadTimer(const TLoadTimeSignals& signals, const TString& name) + : Signals(signals) + , Name(name) + { + Start = TInstant::Now(); + } + + void AddLoadingFail(); + + ~TLoadTimer(); + }; + + class TSignalsRegistry { + private: + TRWMutex Mutex; + THashMap Signals; + TLoadTimeSignals GetSignalImpl(const TString& name) { + TReadGuard rg(Mutex); + auto it = Signals.find(name); + if (it == Signals.end()) { + rg.Release(); + TWriteGuard wg(Mutex); + it = Signals.emplace(name, TLoadTimeSignals(name)).first; + return it->second; + } else { + return it->second; + } + } + + public: + static TLoadTimeSignals GetSignal(const TString& name) { + return Singleton()->GetSignalImpl(name); + } + }; + +private: + using TBase = TCommonCountersOwner; + NMonitoring::TDynamicCounters::TCounterPtr LoadingTimeCounter; + NMonitoring::TDynamicCounters::TCounterPtr FailedLoadingTimeCounter; + NMonitoring::TDynamicCounters::TCounterPtr LoadingFailCounter; + TString Type; + + TLoadTimeSignals(const TString& type) + : TBase("Startup") + , Type(type) { + LoadingTimeCounter = TBase::GetValue("Startup/" + type + "/LoadingTime"); + FailedLoadingTimeCounter = TBase::GetValue("Startup/" + type + "/FailedLoadingTime"); + LoadingFailCounter = TBase::GetValue("Startup/" + type + "/LoadingFailCount"); + } + +public: + TLoadTimer StartGuard() const { + return TLoadTimer(*this, Type + "LoadingTime"); + } + +private: + void AddLoadingTime(ui64 microSeconds) const { + LoadingTimeCounter->Add(microSeconds); + } + + void AddFailedLoadingTime(ui64 microSeconds) const { + FailedLoadingTimeCounter->Add(microSeconds); + } + + void AddLoadingFail() const { + LoadingFailCounter->Add(1); + } +}; + +class TTableLoadTimeCounters { +public: + NColumnShard::TLoadTimeSignals TableLoadTimeCounters; + NColumnShard::TLoadTimeSignals SchemaPresetLoadTimeCounters; + NColumnShard::TLoadTimeSignals TableVersionsLoadTimeCounters; + NColumnShard::TLoadTimeSignals SchemaPresetVersionsLoadTimeCounters; + NColumnShard::TLoadTimeSignals PrechargeTimeCounters; + +public: + TTableLoadTimeCounters() + : TableLoadTimeCounters(NColumnShard::TLoadTimeSignals::TSignalsRegistry::GetSignal("Tables")) + , SchemaPresetLoadTimeCounters(NColumnShard::TLoadTimeSignals::TSignalsRegistry::GetSignal("SchemaPreset")) + , TableVersionsLoadTimeCounters(NColumnShard::TLoadTimeSignals::TSignalsRegistry::GetSignal("TableVersionss")) + , SchemaPresetVersionsLoadTimeCounters(NColumnShard::TLoadTimeSignals::TSignalsRegistry::GetSignal("SchemaPresetVersions")) + , PrechargeTimeCounters(NColumnShard::TLoadTimeSignals::TSignalsRegistry::GetSignal("Precharge")) { + } +}; + } diff --git a/ydb/core/tx/columnshard/counters/counters_manager.h b/ydb/core/tx/columnshard/counters/counters_manager.h index 17336ca3410d..9939191104f5 100644 --- a/ydb/core/tx/columnshard/counters/counters_manager.h +++ b/ydb/core/tx/columnshard/counters/counters_manager.h @@ -92,6 +92,11 @@ class TCountersManager { TabletCounters->OnWritePutBlobsSuccess(rowsWritten); CSCounters.OnWritePutBlobsSuccess(d); } + + void OnWritePutBlobsFailed(const TDuration d, const ui64 /*rowsWritten*/) const { + TabletCounters->OnWriteFailure(); + CSCounters.OnWritePutBlobsFail(d); + } }; } // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/columnshard/counters/engine_logs.cpp b/ydb/core/tx/columnshard/counters/engine_logs.cpp index 7a38e052c5ed..fcd68f5ff6ce 100644 --- a/ydb/core/tx/columnshard/counters/engine_logs.cpp +++ b/ydb/core/tx/columnshard/counters/engine_logs.cpp @@ -83,20 +83,8 @@ void TEngineLogsCounters::OnActualizationTask(const ui32 evictCount, const ui32 void TEngineLogsCounters::TPortionsInfoGuard::OnNewPortion(const std::shared_ptr& portion) const { const ui32 producedId = (ui32)(portion->HasRemoveSnapshot() ? NOlap::NPortion::EProduced::INACTIVE : portion->GetMeta().Produced); Y_ABORT_UNLESS(producedId < BlobGuards.size()); - THashSet blobIds; - for (auto&& i : portion->GetRecords()) { - const auto blobId = portion->GetBlobId(i.GetBlobRange().GetBlobIdxVerified()); - if (blobIds.emplace(blobId).second) { - BlobGuards[producedId]->Add(blobId.BlobSize(), blobId.BlobSize()); - } - } - for (auto&& i : portion->GetIndexes()) { - if (i.HasBlobRange()) { - const auto blobId = portion->GetBlobId(i.GetBlobRangeVerified().GetBlobIdxVerified()); - if (blobIds.emplace(blobId).second) { - BlobGuards[producedId]->Add(blobId.BlobSize(), blobId.BlobSize()); - } - } + for (auto&& blobId : portion->GetBlobIds()) { + BlobGuards[producedId]->Add(blobId.BlobSize(), blobId.BlobSize()); } PortionRecordCountGuards[producedId]->Add(portion->GetRecordsCount(), 1); PortionSizeGuards[producedId]->Add(portion->GetTotalBlobBytes(), 1); @@ -106,19 +94,8 @@ void TEngineLogsCounters::TPortionsInfoGuard::OnDropPortion(const std::shared_pt const ui32 producedId = (ui32)(portion->HasRemoveSnapshot() ? NOlap::NPortion::EProduced::INACTIVE : portion->GetMeta().Produced); Y_ABORT_UNLESS(producedId < BlobGuards.size()); THashSet blobIds; - for (auto&& i : portion->GetRecords()) { - const auto blobId = portion->GetBlobId(i.GetBlobRange().GetBlobIdxVerified()); - if (blobIds.emplace(blobId).second) { - BlobGuards[producedId]->Sub(blobId.BlobSize(), blobId.BlobSize()); - } - } - for (auto&& i : portion->GetIndexes()) { - if (i.HasBlobRange()) { - const auto blobId = portion->GetBlobId(i.GetBlobRangeVerified().GetBlobIdxVerified()); - if (blobIds.emplace(blobId).second) { - BlobGuards[producedId]->Sub(blobId.BlobSize(), blobId.BlobSize()); - } - } + for (auto&& blobId : portion->GetBlobIds()) { + BlobGuards[producedId]->Sub(blobId.BlobSize(), blobId.BlobSize()); } PortionRecordCountGuards[producedId]->Sub(portion->GetRecordsCount(), 1); PortionSizeGuards[producedId]->Sub(portion->GetTotalBlobBytes(), 1); diff --git a/ydb/core/tx/columnshard/counters/engine_logs.h b/ydb/core/tx/columnshard/counters/engine_logs.h index 2cbaf7fa234e..7461981363b6 100644 --- a/ydb/core/tx/columnshard/counters/engine_logs.h +++ b/ydb/core/tx/columnshard/counters/engine_logs.h @@ -1,4 +1,6 @@ #pragma once + +#include "common_data.h" #include "common/owner.h" #include "common/histogram.h" #include @@ -8,6 +10,7 @@ namespace NKikimr::NOlap { class TPortionInfo; +class TColumnEngineForLogs; } namespace NKikimr::NColumnShard { @@ -229,6 +232,7 @@ class TEngineLogsCounters: public TCommonCountersOwner { std::vector> PortionRecordsDistribution; public: + friend class NKikimr::NOlap::TColumnEngineForLogs; class TPortionsInfoGuard { private: diff --git a/ydb/core/tx/columnshard/counters/insert_table.cpp b/ydb/core/tx/columnshard/counters/insert_table.cpp index f7c5446dabea..4d9e9d9f734e 100644 --- a/ydb/core/tx/columnshard/counters/insert_table.cpp +++ b/ydb/core/tx/columnshard/counters/insert_table.cpp @@ -9,6 +9,7 @@ TInsertTableCounters::TInsertTableCounters() , Inserted("InsertTable", "Inserted") , Committed("InsertTable", "Committed") , Aborted("InsertTable", "Aborted") + , LoadCounters(NColumnShard::TLoadTimeSignals::TSignalsRegistry::GetSignal("InsertTable")) { } diff --git a/ydb/core/tx/columnshard/counters/insert_table.h b/ydb/core/tx/columnshard/counters/insert_table.h index 52996e190165..0c20660d609e 100644 --- a/ydb/core/tx/columnshard/counters/insert_table.h +++ b/ydb/core/tx/columnshard/counters/insert_table.h @@ -60,6 +60,7 @@ class TInsertTableCounters: public TCommonCountersOwner { const TDataOwnerSignals Inserted; const TDataOwnerSignals Committed; const TDataOwnerSignals Aborted; + const TLoadTimeSignals LoadCounters; TInsertTableCounters(); diff --git a/ydb/core/tx/columnshard/counters/portions.cpp b/ydb/core/tx/columnshard/counters/portions.cpp new file mode 100644 index 000000000000..ab54f3339aaa --- /dev/null +++ b/ydb/core/tx/columnshard/counters/portions.cpp @@ -0,0 +1,50 @@ +#include "portions.h" +#include + +namespace NKikimr::NColumnShard { + +void TPortionCategoryCounters::AddPortion(const std::shared_ptr& p) { + RecordsCount->Add(p->GetRecordsCount()); + Count->Add(1); + BlobBytes->Add(p->GetTotalBlobBytes()); + RawBytes->Add(p->GetTotalRawBytes()); +} + +void TPortionCategoryCounters::RemovePortion(const std::shared_ptr& p) { + RecordsCount->Remove(p->GetRecordsCount()); + Count->Remove(1); + BlobBytes->Remove(p->GetTotalBlobBytes()); + RawBytes->Remove(p->GetTotalRawBytes()); +} + +} // namespace NKikimr::NColumnShard + +namespace NKikimr::NOlap { + +void TSimplePortionsGroupInfo::AddPortion(const std::shared_ptr& p) { + AFL_VERIFY(p); + AddPortion(*p); +} +void TSimplePortionsGroupInfo::AddPortion(const TPortionInfo& p) { + BlobBytes += p.GetTotalBlobBytes(); + RawBytes += p.GetTotalRawBytes(); + Count += 1; + RecordsCount += p.GetRecordsCount(); +} + +void TSimplePortionsGroupInfo::RemovePortion(const std::shared_ptr& p) { + AFL_VERIFY(p); + RemovePortion(*p); +} +void TSimplePortionsGroupInfo::RemovePortion(const TPortionInfo& p) { + BlobBytes -= p.GetTotalBlobBytes(); + RawBytes -= p.GetTotalRawBytes(); + Count -= 1; + RecordsCount -= p.GetRecordsCount(); + AFL_VERIFY(RawBytes >= 0); + AFL_VERIFY(BlobBytes >= 0); + AFL_VERIFY(Count >= 0); + AFL_VERIFY(RecordsCount >= 0); +} + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/counters/portions.h b/ydb/core/tx/columnshard/counters/portions.h new file mode 100644 index 000000000000..d54fe6094221 --- /dev/null +++ b/ydb/core/tx/columnshard/counters/portions.h @@ -0,0 +1,127 @@ +#pragma once +#include "common/agent.h" +#include "common/client.h" +#include "common/owner.h" + +#include +#include + +namespace NKikimr::NOlap { +class TPortionInfo; + +class TSimplePortionsGroupInfo { +private: + YDB_READONLY(i64, BlobBytes, 0); + YDB_READONLY(i64, RawBytes, 0); + YDB_READONLY(i64, Count, 0); + YDB_READONLY(i64, RecordsCount, 0); + +public: + NJson::TJsonValue SerializeToJson() const { + NJson::TJsonValue result = NJson::JSON_MAP; + result.InsertValue("blob_bytes", BlobBytes); + result.InsertValue("raw_bytes", RawBytes); + result.InsertValue("count", Count); + result.InsertValue("records_count", RecordsCount); + return result; + } + + ui64 PredictPackedBlobBytes(const std::optional kff) const { + if (kff) { + return RawBytes * *kff; + } else { + return BlobBytes; + } + } + + TString DebugString() const { + return TStringBuilder() << "{blob_bytes=" << BlobBytes << ";raw_bytes=" << RawBytes << ";count=" << Count << ";records=" << RecordsCount + << "}"; + } + + TSimplePortionsGroupInfo operator+(const TSimplePortionsGroupInfo& item) const { + TSimplePortionsGroupInfo result; + result.BlobBytes = BlobBytes + item.BlobBytes; + result.RawBytes = RawBytes + item.RawBytes; + result.Count = Count + item.Count; + result.RecordsCount = RecordsCount + item.RecordsCount; + return result; + } + + void AddPortion(const std::shared_ptr& p); + void RemovePortion(const std::shared_ptr& p); + + void AddPortion(const TPortionInfo& p); + void RemovePortion(const TPortionInfo& p); +}; + +class TPortionGroupCounters: public NColumnShard::TCommonCountersOwner { +private: + using TBase = NColumnShard::TCommonCountersOwner; + NMonitoring::TDynamicCounters::TCounterPtr Count; + NMonitoring::TDynamicCounters::TCounterPtr RawBytes; + NMonitoring::TDynamicCounters::TCounterPtr BlobBytes; + +public: + TPortionGroupCounters(const TString& kind, const NColumnShard::TCommonCountersOwner& baseOwner) + : TBase(baseOwner, "kind", kind) { + Count = TBase::GetDeriviative("Portions/Count"); + RawBytes = TBase::GetDeriviative("Portions/Raw/Bytes"); + BlobBytes = TBase::GetDeriviative("Portions/Blob/Bytes"); + } + + void OnData(const i64 portionsCount, const i64 portionBlobBytes, const i64 portionRawBytes) { + Count->Add(portionsCount); + RawBytes->Add(portionRawBytes); + BlobBytes->Add(portionBlobBytes); + } + + void OnData(const TSimplePortionsGroupInfo& group) { + Count->Add(group.GetCount()); + RawBytes->Add(group.GetRawBytes()); + BlobBytes->Add(group.GetBlobBytes()); + } +}; + +} // namespace NKikimr::NOlap + +namespace NKikimr::NColumnShard { + +class TPortionCategoryCounterAgents: public TCommonCountersOwner { +private: + using TBase = TCommonCountersOwner; + +public: + const std::shared_ptr RecordsCount; + const std::shared_ptr Count; + const std::shared_ptr BlobBytes; + const std::shared_ptr RawBytes; + TPortionCategoryCounterAgents(TCommonCountersOwner& base, const TString& categoryName) + : TBase(base, "category", categoryName) + , RecordsCount(TBase::GetValueAutoAggregations("ByGranule/Portions/RecordsCount")) + , Count(TBase::GetValueAutoAggregations("ByGranule/Portions/Count")) + , BlobBytes(TBase::GetValueAutoAggregations("ByGranule/Portions/Blob/Bytes")) + , RawBytes(TBase::GetValueAutoAggregations("ByGranule/Portions/Raw/Bytes")) { + } +}; + +class TPortionCategoryCounters { +private: + std::shared_ptr RecordsCount; + std::shared_ptr Count; + std::shared_ptr BlobBytes; + std::shared_ptr RawBytes; + +public: + TPortionCategoryCounters(TPortionCategoryCounterAgents& agents) { + RecordsCount = agents.RecordsCount->GetClient(); + Count = agents.Count->GetClient(); + BlobBytes = agents.BlobBytes->GetClient(); + RawBytes = agents.RawBytes->GetClient(); + } + + void AddPortion(const std::shared_ptr& p); + void RemovePortion(const std::shared_ptr& p); +}; + +} // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/columnshard/counters/scan.cpp b/ydb/core/tx/columnshard/counters/scan.cpp index cdfd42aa9bc4..2fb37d048494 100644 --- a/ydb/core/tx/columnshard/counters/scan.cpp +++ b/ydb/core/tx/columnshard/counters/scan.cpp @@ -1,4 +1,5 @@ #include "scan.h" + #include #include @@ -58,11 +59,16 @@ TScanCounters::TScanCounters(const TString& module) , BlobsReceivedCount(TBase::GetDeriviative("BlobsReceivedCount")) , BlobsReceivedBytes(TBase::GetDeriviative("BlobsReceivedBytes")) -{ + , ProcessedSourceCount(TBase::GetDeriviative("ProcessedSource/Count")) + , ProcessedSourceRawBytes(TBase::GetDeriviative("ProcessedSource/RawBytes")) + , ProcessedSourceRecords(TBase::GetDeriviative("ProcessedSource/Records")) + , ProcessedSourceEmptyCount(TBase::GetDeriviative("ProcessedSource/Empty/Count")) + , HistogramFilteredResultCount(TBase::GetHistogram("ProcessedSource/Filtered/Count", NMonitoring::ExponentialHistogram(20, 2))) { HistogramIntervalMemoryRequiredOnFail = TBase::GetHistogram("IntervalMemory/RequiredOnFail/Gb", NMonitoring::LinearHistogram(10, 1, 1)); HistogramIntervalMemoryReduceSize = TBase::GetHistogram("IntervalMemory/Reduce/Gb", NMonitoring::ExponentialHistogram(8, 2, 1)); - HistogramIntervalMemoryRequiredAfterReduce = TBase::GetHistogram("IntervalMemory/RequiredAfterReduce/Mb", NMonitoring::ExponentialHistogram(10, 2, 64)); -/* + HistogramIntervalMemoryRequiredAfterReduce = + TBase::GetHistogram("IntervalMemory/RequiredAfterReduce/Mb", NMonitoring::ExponentialHistogram(10, 2, 64)); + /* { const std::map borders = {{0, "0"}, {512LLU * 1024 * 1024, "0.5Gb"}, {1LLU * 1024 * 1024 * 1024, "1Gb"}, {2LLU * 1024 * 1024 * 1024, "2Gb"}, {3LLU * 1024 * 1024 * 1024, "3Gb"}, @@ -94,7 +100,8 @@ TScanCounters::TScanCounters(const TString& module) if (i == EStatusFinish::COUNT) { continue; } - ScanDurationByStatus[(ui32)i] = TBase::GetHistogram("ScanDuration/" + ::ToString(i) + "/Milliseconds", NMonitoring::ExponentialHistogram(18, 2, 1)); + ScanDurationByStatus[(ui32)i] = + TBase::GetHistogram("ScanDuration/" + ::ToString(i) + "/Milliseconds", NMonitoring::ExponentialHistogram(18, 2, 1)); ScansFinishedByStatus[(ui32)i] = TBase::GetDeriviative("ScansFinished/" + ::ToString(i)); AFL_VERIFY(idx == (ui32)i); ++idx; @@ -109,4 +116,4 @@ void TScanCounters::FillStats(::NKikimrTableStats::TTableStats& output) const { output.SetRangeReads(ScansFinishedByStatus[(ui32)EStatusFinish::Success]->Val()); } -} +} // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/columnshard/counters/scan.h b/ydb/core/tx/columnshard/counters/scan.h index 6d1202288514..712afe67d72e 100644 --- a/ydb/core/tx/columnshard/counters/scan.h +++ b/ydb/core/tx/columnshard/counters/scan.h @@ -185,8 +185,24 @@ class TScanCounters: public TCommonCountersOwner { NMonitoring::TDynamicCounters::TCounterPtr BlobsReceivedCount; NMonitoring::TDynamicCounters::TCounterPtr BlobsReceivedBytes; + NMonitoring::TDynamicCounters::TCounterPtr ProcessedSourceCount; + NMonitoring::TDynamicCounters::TCounterPtr ProcessedSourceRawBytes; + NMonitoring::TDynamicCounters::TCounterPtr ProcessedSourceRecords; + NMonitoring::TDynamicCounters::TCounterPtr ProcessedSourceEmptyCount; + NMonitoring::THistogramPtr HistogramFilteredResultCount; + TScanCounters(const TString& module = "Scan"); + void OnSourceFinished(const ui32 recordsCount, const ui64 rawBytes, const ui32 filteredRecordsCount) const { + ProcessedSourceCount->Add(1); + ProcessedSourceRawBytes->Add(rawBytes); + ProcessedSourceRecords->Add(recordsCount); + HistogramFilteredResultCount->Collect(filteredRecordsCount); + if (!filteredRecordsCount) { + ProcessedSourceEmptyCount->Add(1); + } + } + void OnOptimizedIntervalMemoryFailed(const ui64 memoryRequired) const { HistogramIntervalMemoryRequiredOnFail->Collect(memoryRequired / (1024.0 * 1024.0 * 1024.0)); } @@ -264,7 +280,7 @@ class TScanCounters: public TCommonCountersOwner { void FillStats(::NKikimrTableStats::TTableStats& output) const; }; -class TCounterGuard: TNonCopyable { +class TCounterGuard: TMoveOnly { private: std::shared_ptr Counter; public: @@ -290,13 +306,45 @@ class TCounterGuard: TNonCopyable { class TConcreteScanCounters: public TScanCounters { private: using TBase = TScanCounters; + std::shared_ptr FetchAccessorsCount; + std::shared_ptr FetchBlobsCount; std::shared_ptr MergeTasksCount; std::shared_ptr AssembleTasksCount; std::shared_ptr ReadTasksCount; std::shared_ptr ResourcesAllocationTasksCount; + std::shared_ptr ResultsForSourceCount; + std::shared_ptr ResultsForReplyGuard; + public: TScanAggregations Aggregations; + TString DebugString() const { + return TStringBuilder() << "FetchAccessorsCount:" << FetchAccessorsCount->Val() << ";" + << "FetchBlobsCount:" << FetchBlobsCount->Val() << ";" + << "MergeTasksCount:" << MergeTasksCount->Val() << ";" + << "AssembleTasksCount:" << AssembleTasksCount->Val() << ";" + << "ReadTasksCount:" << ReadTasksCount->Val() << ";" + << "ResourcesAllocationTasksCount:" << ResourcesAllocationTasksCount->Val() << ";" + << "ResultsForSourceCount:" << ResultsForSourceCount->Val() << ";" + << "ResultsForReplyGuard:" << ResultsForReplyGuard->Val() << ";"; + } + + TCounterGuard GetResultsForReplyGuard() const { + return TCounterGuard(ResultsForReplyGuard); + } + + TCounterGuard GetFetcherAcessorsGuard() const { + return TCounterGuard(FetchAccessorsCount); + } + + TCounterGuard GetFetchBlobsGuard() const { + return TCounterGuard(FetchBlobsCount); + } + + TCounterGuard GetResultsForSourceGuard() const { + return TCounterGuard(ResultsForSourceCount); + } + TCounterGuard GetMergeTasksGuard() const { return TCounterGuard(MergeTasksCount); } @@ -314,7 +362,8 @@ class TConcreteScanCounters: public TScanCounters { } bool InWaiting() const { - return MergeTasksCount->Val() || AssembleTasksCount->Val() || ReadTasksCount->Val() || ResourcesAllocationTasksCount->Val(); + return MergeTasksCount->Val() || AssembleTasksCount->Val() || ReadTasksCount->Val() || ResourcesAllocationTasksCount->Val() || + FetchAccessorsCount->Val() || ResultsForSourceCount->Val() || FetchBlobsCount->Val() || ResultsForReplyGuard->Val(); } void OnBlobsWaitDuration(const TDuration d, const TDuration fullScanDuration) const { @@ -324,10 +373,14 @@ class TConcreteScanCounters: public TScanCounters { TConcreteScanCounters(const TScanCounters& counters) : TBase(counters) + , FetchAccessorsCount(std::make_shared()) + , FetchBlobsCount(std::make_shared()) , MergeTasksCount(std::make_shared()) , AssembleTasksCount(std::make_shared()) , ReadTasksCount(std::make_shared()) , ResourcesAllocationTasksCount(std::make_shared()) + , ResultsForSourceCount(std::make_shared()) + , ResultsForReplyGuard(std::make_shared()) , Aggregations(TBase::BuildAggregations()) { diff --git a/ydb/core/tx/columnshard/counters/writes_monitor.cpp b/ydb/core/tx/columnshard/counters/writes_monitor.cpp new file mode 100644 index 000000000000..380b6bb719fc --- /dev/null +++ b/ydb/core/tx/columnshard/counters/writes_monitor.cpp @@ -0,0 +1,35 @@ +#include "writes_monitor.h" + +#include + +namespace NKikimr::NColumnShard { + +TAtomicCounter TWritesMonitor::WritesInFlight = 0; +TAtomicCounter TWritesMonitor::WritesSizeInFlight = 0; + +void TWritesMonitor::OnStartWrite(const ui64 dataSize) { + ++WritesInFlightLocal; + WritesSizeInFlightLocal += dataSize; + WritesInFlight.Inc(); + WritesSizeInFlight.Add(dataSize); + UpdateTabletCounters(); +} + +void TWritesMonitor::OnFinishWrite(const ui64 dataSize, const ui32 writesCount /*= 1*/, const bool onDestroy /*= false*/) { + AFL_VERIFY(writesCount <= WritesInFlightLocal); + AFL_VERIFY(dataSize <= WritesSizeInFlightLocal); + WritesSizeInFlightLocal -= dataSize; + WritesInFlightLocal -= writesCount; + AFL_VERIFY(0 <= WritesInFlight.Sub(writesCount)); + AFL_VERIFY(0 <= WritesSizeInFlight.Sub(dataSize)); + if (!onDestroy) { + UpdateTabletCounters(); + } +} + +TString TWritesMonitor::DebugString() const { + return TStringBuilder() << "{object=write_monitor;count_local=" << WritesInFlightLocal << ";size_local=" << WritesSizeInFlightLocal << ";" + << "count_node=" << WritesInFlight.Val() << ";size_node=" << WritesSizeInFlight.Val() << "}"; +} + +} // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/columnshard/counters/writes_monitor.h b/ydb/core/tx/columnshard/counters/writes_monitor.h index ad8ad6e474cc..d66bd010f69e 100644 --- a/ydb/core/tx/columnshard/counters/writes_monitor.h +++ b/ydb/core/tx/columnshard/counters/writes_monitor.h @@ -6,40 +6,40 @@ namespace NKikimr::NColumnShard { -class TWritesMonitor { +class TWritesMonitor: TNonCopyable { private: TTabletCountersBase& Stats; - YDB_READONLY(ui64, WritesInFlight, 0); - YDB_READONLY(ui64, WritesSizeInFlight, 0); + static TAtomicCounter WritesInFlight; + static TAtomicCounter WritesSizeInFlight; + ui64 WritesInFlightLocal = 0; + ui64 WritesSizeInFlightLocal = 0; public: TWritesMonitor(TTabletCountersBase& stats) : Stats(stats) { } - void OnStartWrite(const ui64 dataSize) { - ++WritesInFlight; - WritesSizeInFlight += dataSize; - UpdateTabletCounters(); + ~TWritesMonitor() { + OnFinishWrite(WritesSizeInFlightLocal, WritesInFlightLocal, true); } - void OnFinishWrite(const ui64 dataSize, const ui32 writesCount = 1) { - Y_ABORT_UNLESS(WritesInFlight > 0); - Y_ABORT_UNLESS(WritesSizeInFlight >= dataSize); - WritesInFlight -= writesCount; - WritesSizeInFlight -= dataSize; - UpdateTabletCounters(); - } + void OnStartWrite(const ui64 dataSize); + + void OnFinishWrite(const ui64 dataSize, const ui32 writesCount = 1, const bool onDestroy = false); - TString DebugString() const { - return TStringBuilder() << "{object=write_monitor;count=" << WritesInFlight << ";size=" << WritesSizeInFlight - << "}"; + TString DebugString() const; + + ui64 GetWritesInFlight() const { + return WritesInFlight.Val(); + } + ui64 GetWritesSizeInFlight() const { + return WritesSizeInFlight.Val(); } private: void UpdateTabletCounters() { - Stats.Simple()[COUNTER_WRITES_IN_FLY].Set(WritesInFlight); + Stats.Simple()[COUNTER_WRITES_IN_FLY].Set(WritesInFlightLocal); } }; diff --git a/ydb/core/tx/columnshard/counters/ya.make b/ydb/core/tx/columnshard/counters/ya.make index 8707d6080e30..da5aa6f2abdd 100644 --- a/ydb/core/tx/columnshard/counters/ya.make +++ b/ydb/core/tx/columnshard/counters/ya.make @@ -13,6 +13,8 @@ SRCS( req_tracer.cpp scan.cpp splitter.cpp + portions.cpp + writes_monitor.cpp ) PEERDIR( @@ -20,6 +22,7 @@ PEERDIR( ydb/core/tx/columnshard/counters/aggregation ydb/core/tx/columnshard/counters/common ydb/core/base + ydb/library/actors/core ) GENERATE_ENUM_SERIALIZATION(columnshard.h) diff --git a/ydb/core/tx/columnshard/data_accessor/abstract/collector.cpp b/ydb/core/tx/columnshard/data_accessor/abstract/collector.cpp new file mode 100644 index 000000000000..18b138b607d7 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/abstract/collector.cpp @@ -0,0 +1,23 @@ +#include "collector.h" + +#include +#include + +namespace NKikimr::NOlap::NDataAccessorControl { + +void IGranuleDataAccessor::AskData( + const std::vector& portions, const std::shared_ptr& callback, const TString& consumer) { + AFL_VERIFY(portions.size()); + DoAskData(portions, callback, consumer); +} + +TDataCategorized IGranuleDataAccessor::AnalyzeData( + const std::vector& portions, const TString& consumer) { + return DoAnalyzeData(portions, consumer); +} + +void TActorAccessorsCallback::OnAccessorsFetched(std::vector&& accessors) { + NActors::TActivationContext::Send(ActorId, std::make_unique(std::move(accessors))); +} + +} // namespace NKikimr::NOlap::NDataAccessorControl diff --git a/ydb/core/tx/columnshard/data_accessor/abstract/collector.h b/ydb/core/tx/columnshard/data_accessor/abstract/collector.h new file mode 100644 index 000000000000..1d0bdf041520 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/abstract/collector.h @@ -0,0 +1,65 @@ +#pragma once +#include +#include + +namespace NKikimr::NOlap::NDataAccessorControl { +class IAccessorCallback { +public: + virtual void OnAccessorsFetched(std::vector&& accessors) = 0; + virtual ~IAccessorCallback() = default; +}; + +class TActorAccessorsCallback: public IAccessorCallback { +private: + const NActors::TActorId ActorId; + +public: + virtual void OnAccessorsFetched(std::vector&& accessors) override; + TActorAccessorsCallback(const NActors::TActorId& actorId) + : ActorId(actorId) { + } +}; + +class TDataCategorized { +private: + YDB_READONLY_DEF(std::vector, PortionsToAsk); + YDB_READONLY_DEF(std::vector, CachedAccessors); + +public: + void AddToAsk(const TPortionInfo::TConstPtr& p) { + PortionsToAsk.emplace_back(p); + } + void AddFromCache(const TPortionDataAccessor& accessor) { + CachedAccessors.emplace_back(accessor); + } +}; + +class IGranuleDataAccessor { +private: + const ui64 PathId; + + virtual void DoAskData( + const std::vector& portions, const std::shared_ptr& callback, const TString& consumer) = 0; + virtual TDataCategorized DoAnalyzeData(const std::vector& portions, const TString& consumer) = 0; + virtual void DoModifyPortions(const std::vector& add, const std::vector& remove) = 0; + +public: + virtual ~IGranuleDataAccessor() = default; + + ui64 GetPathId() const { + return PathId; + } + + IGranuleDataAccessor(const ui64 pathId) + : PathId(pathId) { + } + + void AskData( + const std::vector& portions, const std::shared_ptr& callback, const TString& consumer); + TDataCategorized AnalyzeData(const std::vector& portions, const TString& consumer); + void ModifyPortions(const std::vector& add, const std::vector& remove) { + return DoModifyPortions(add, remove); + } +}; + +} // namespace NKikimr::NOlap::NDataAccessorControl diff --git a/ydb/core/tx/columnshard/data_accessor/abstract/constructor.cpp b/ydb/core/tx/columnshard/data_accessor/abstract/constructor.cpp new file mode 100644 index 000000000000..7c47d468fcb9 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/abstract/constructor.cpp @@ -0,0 +1,11 @@ +#include "constructor.h" + +#include + +namespace NKikimr::NOlap::NDataAccessorControl { + +std::shared_ptr IManagerConstructor::BuildDefault() { + return NLocalDB::TManagerConstructor::BuildDefault(); +} + +} // namespace NKikimr::NOlap::NDataAccessorControl diff --git a/ydb/core/tx/columnshard/data_accessor/abstract/constructor.h b/ydb/core/tx/columnshard/data_accessor/abstract/constructor.h new file mode 100644 index 000000000000..add00ed0c28a --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/abstract/constructor.h @@ -0,0 +1,94 @@ +#pragma once +#include "manager.h" + +#include + +namespace NKikimr::NOlap::NDataAccessorControl { + +class TManagerConstructionContext { +private: + YDB_READONLY_DEF(NActors::TActorId, TabletActorId); + const bool IsUpdateFlag = false; + +public: + bool IsUpdate() const { + return IsUpdateFlag; + } + + TManagerConstructionContext(const NActors::TActorId& tabletActorId, const bool isUpdate) + : TabletActorId(tabletActorId) + , IsUpdateFlag(isUpdate) + { + } +}; + +class IManagerConstructor { +public: + using TFactory = NObjectFactory::TObjectFactory; + using TProto = NKikimrSchemeOp::TMetadataManagerConstructorContainer; + +private: + virtual TConclusion> DoBuild(const TManagerConstructionContext& context) const = 0; + virtual bool DoDeserializeFromProto(const TProto& proto) = 0; + virtual void DoSerializeToProto(TProto& proto) const = 0; + virtual TConclusionStatus DoDeserializeFromJson(const NJson::TJsonValue& jsonInfo) = 0; + virtual bool IsEqualToWithSameClassName(const IManagerConstructor& /*item*/) const { + return false; + } + +public: + static std::shared_ptr BuildDefault(); + + virtual ~IManagerConstructor() = default; + + bool IsEqualTo(const IManagerConstructor& item) const { + if (GetClassName() != item.GetClassName()) { + return false; + } + return IsEqualToWithSameClassName(item); + } + virtual TString GetClassName() const = 0; + + TConclusionStatus DeserializeFromJson(const NJson::TJsonValue& jsonInfo) { + return DoDeserializeFromJson(jsonInfo); + } + + bool DeserializeFromProto(const TProto& proto) { + return DoDeserializeFromProto(proto); + } + void SerializeToProto(TProto& proto) const { + DoSerializeToProto(proto); + } + + TConclusion> Build(const TManagerConstructionContext& context) { + return DoBuild(context); + } +}; + +class TMetadataManagerConstructorContainer: public NBackgroundTasks::TInterfaceProtoContainer { +private: + using TBase = NBackgroundTasks::TInterfaceProtoContainer; + +public: + using TBase::TBase; + + bool IsEqualTo(const TMetadataManagerConstructorContainer& item) { + if (TBase::HasObject() != item.HasObject()) { + return false; + } + if (!TBase::HasObject()) { + return true; + } + return TBase::GetObjectPtr()->IsEqualTo(*item.GetObjectPtr()); + } + + static TConclusion BuildFromProto(const NKikimrSchemeOp::TMetadataManagerConstructorContainer& proto) { + TMetadataManagerConstructorContainer result; + if (!result.DeserializeFromProto(proto)) { + return TConclusionStatus::Fail("cannot parse interface from proto: " + proto.DebugString()); + } + return result; + } +}; + +} // namespace NKikimr::NOlap::NDataAccessorControl diff --git a/ydb/core/tx/columnshard/data_accessor/abstract/manager.cpp b/ydb/core/tx/columnshard/data_accessor/abstract/manager.cpp new file mode 100644 index 000000000000..c69c77c269b0 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/abstract/manager.cpp @@ -0,0 +1,5 @@ +#include "manager.h" + +namespace NKikimr::NOlap::NDataAccessorControl { + +} \ No newline at end of file diff --git a/ydb/core/tx/columnshard/data_accessor/abstract/manager.h b/ydb/core/tx/columnshard/data_accessor/abstract/manager.h new file mode 100644 index 000000000000..1d47c21f99fa --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/abstract/manager.h @@ -0,0 +1,33 @@ +#pragma once +#include "collector.h" + +#include +#include + +namespace NKikimr::NOlap { +class TGranuleMeta; +} + +namespace NKikimr::NOlap::NDataAccessorControl { +class IMetadataMemoryManager { +private: + virtual std::unique_ptr DoBuildCollector(const ui64 pathId) = 0; + virtual std::shared_ptr DoBuildLoader( + const TVersionedIndex& versionedIndex, TGranuleMeta* granule, const std::shared_ptr& dsGroupSelector) = 0; + +public: + virtual ~IMetadataMemoryManager() = default; + virtual bool NeedPrefetch() const { + return false; + } + + std::unique_ptr BuildCollector(const ui64 pathId) { + return DoBuildCollector(pathId); + } + + std::shared_ptr BuildLoader( + const TVersionedIndex& versionedIndex, TGranuleMeta* granule, const std::shared_ptr& dsGroupSelector) { + return DoBuildLoader(versionedIndex, granule, dsGroupSelector); + } +}; +} // namespace NKikimr::NOlap::NDataAccessorControl diff --git a/ydb/core/tx/columnshard/data_accessor/abstract/ya.make b/ydb/core/tx/columnshard/data_accessor/abstract/ya.make new file mode 100644 index 000000000000..524cc6380edd --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/abstract/ya.make @@ -0,0 +1,14 @@ +LIBRARY() + +SRCS( + manager.cpp + collector.cpp + constructor.cpp +) + +PEERDIR( + ydb/core/tx/columnshard/engines/portions + ydb/core/protos +) + +END() diff --git a/ydb/core/tx/columnshard/data_accessor/actor.cpp b/ydb/core/tx/columnshard/data_accessor/actor.cpp new file mode 100644 index 000000000000..65680779d18e --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/actor.cpp @@ -0,0 +1,15 @@ +#include "actor.h" + +namespace NKikimr::NOlap::NDataAccessorControl { + +void TActor::Handle(TEvAskServiceDataAccessors::TPtr& ev) { + Manager->AskData(ev->Get()->GetRequest()); +} + +void TActor::Bootstrap() { + AccessorsCallback = std::make_shared(SelfId()); + Manager = std::make_shared(AccessorsCallback); + Become(&TThis::StateWait); +} + +} diff --git a/ydb/core/tx/columnshard/data_accessor/actor.h b/ydb/core/tx/columnshard/data_accessor/actor.h new file mode 100644 index 000000000000..e21b7af85205 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/actor.h @@ -0,0 +1,65 @@ +#pragma once +#include "events.h" +#include "manager.h" + +#include "abstract/collector.h" + +#include +#include + +namespace NKikimr::NOlap::NDataAccessorControl { + +class TActor: public TActorBootstrapped { +private: + const ui64 TabletId; + const NActors::TActorId Parent; + std::shared_ptr Manager; + + std::shared_ptr AccessorsCallback; + + void StartStopping() { + PassAway(); + } + + void Handle(TEvRegisterController::TPtr& ev) { + Manager->RegisterController(ev->Get()->ExtractController(), ev->Get()->IsUpdate()); + } + void Handle(TEvUnregisterController::TPtr& ev) { + Manager->UnregisterController(ev->Get()->GetPathId()); + } + void Handle(TEvAddPortion::TPtr& ev) { + for (auto&& a : ev->Get()->ExtractAccessors()) { + Manager->AddPortion(std::move(a)); + } + } + void Handle(TEvRemovePortion::TPtr& ev) { + Manager->RemovePortion(ev->Get()->GetPortion()); + } + void Handle(TEvAskServiceDataAccessors::TPtr& ev); + +public: + TActor(const ui64 tabletId, const TActorId& parent) + : TabletId(tabletId) + , Parent(parent) { + Y_UNUSED(TabletId); + } + ~TActor() = default; + + void Bootstrap(); + + STFUNC(StateWait) { + const NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("self_id", SelfId())("tablet_id", TabletId)("parent", Parent); + switch (ev->GetTypeRewrite()) { + cFunc(NActors::TEvents::TEvPoison::EventType, StartStopping); + hFunc(TEvRegisterController, Handle); + hFunc(TEvUnregisterController, Handle); + hFunc(TEvAskServiceDataAccessors, Handle); + hFunc(TEvRemovePortion, Handle); + hFunc(TEvAddPortion, Handle); + default: + AFL_VERIFY(false); + } + } +}; + +} // namespace NKikimr::NOlap::NDataAccessorControl diff --git a/ydb/core/tx/columnshard/data_accessor/events.cpp b/ydb/core/tx/columnshard/data_accessor/events.cpp new file mode 100644 index 000000000000..a1ec74cd603d --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/events.cpp @@ -0,0 +1,5 @@ +#include "events.h" + +namespace NKikimr::NOlap::NDataAccessorControl { + +} diff --git a/ydb/core/tx/columnshard/data_accessor/events.h b/ydb/core/tx/columnshard/data_accessor/events.h new file mode 100644 index 000000000000..d5fb45aa42be --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/events.h @@ -0,0 +1,104 @@ +#pragma once + +#include "abstract/collector.h" + +#include +#include + +#include +#include + +namespace NKikimr::NOlap { +class IGranuleDataAccessor; +class TDataAccessorsRequest; +} // namespace NKikimr::NOlap + +namespace NKikimr::NOlap::NDataAccessorControl { + +class TEvAddPortion: public NActors::TEventLocal { +private: + std::vector Accessors; + +public: + std::vector ExtractAccessors() { + return std::move(Accessors); + } + + explicit TEvAddPortion(const TPortionDataAccessor& accessor) { + Accessors.emplace_back(accessor); + } + + explicit TEvAddPortion(const std::vector& accessors) { + Accessors = accessors; + } +}; + +class TEvRemovePortion: public NActors::TEventLocal { +private: + YDB_READONLY_DEF(TPortionInfo::TConstPtr, Portion); + +public: + explicit TEvRemovePortion(const TPortionInfo::TConstPtr& portion) + : Portion(portion) { + } +}; + +class TEvRegisterController: public NActors::TEventLocal { +private: + std::unique_ptr Controller; + bool IsUpdateFlag = false; + +public: + bool IsUpdate() const { + return IsUpdateFlag; + } + + std::unique_ptr ExtractController() { + return std::move(Controller); + } + + explicit TEvRegisterController(std::unique_ptr&& accessor, const bool isUpdate) + : Controller(std::move(accessor)) + , IsUpdateFlag(isUpdate) + { + } +}; + +class TEvUnregisterController + : public NActors::TEventLocal { +private: + YDB_READONLY(ui64, PathId, 0); + +public: + explicit TEvUnregisterController(const ui64 pathId) + : PathId(pathId) { + } +}; + +class TEvAskTabletDataAccessors: public NActors::TEventLocal { +private: + YDB_ACCESSOR_DEF(std::vector, Portions); + YDB_READONLY_DEF(std::shared_ptr, Callback); + YDB_READONLY_DEF(TString, Consumer); + +public: + explicit TEvAskTabletDataAccessors(const std::vector& portions, + const std::shared_ptr& callback, const TString& consumer) + : Portions(portions) + , Callback(callback) + , Consumer(consumer) { + } +}; + +class TEvAskServiceDataAccessors + : public NActors::TEventLocal { +private: + YDB_READONLY_DEF(std::shared_ptr, Request); + +public: + explicit TEvAskServiceDataAccessors(const std::shared_ptr& request) + : Request(request) { + } +}; + +} // namespace NKikimr::NOlap::NDataAccessorControl diff --git a/ydb/core/tx/columnshard/data_accessor/in_mem/collector.cpp b/ydb/core/tx/columnshard/data_accessor/in_mem/collector.cpp new file mode 100644 index 000000000000..a8a1003a045b --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/in_mem/collector.cpp @@ -0,0 +1,29 @@ +#include "collector.h" + +namespace NKikimr::NOlap::NDataAccessorControl::NInMem { + +void TCollector::DoAskData( + const std::vector& portions, const std::shared_ptr& /*callback*/, const TString& /*consumer*/) { + AFL_VERIFY(portions.empty()); +} + +TDataCategorized TCollector::DoAnalyzeData(const std::vector& portions, const TString& /*consumer*/) { + TDataCategorized result; + for (auto&& i : portions) { + auto it = Accessors.find(i->GetPortionId()); + AFL_VERIFY(it != Accessors.end()); + result.AddFromCache(it->second); + } + return result; +} + +void TCollector::DoModifyPortions(const std::vector& add, const std::vector& remove) { + for (auto&& i : remove) { + AFL_VERIFY(Accessors.erase(i)); + } + for (auto&& i : add) { + AFL_VERIFY(Accessors.emplace(i.GetPortionInfo().GetPortionId(), i).second); + } +} + +} // namespace NKikimr::NOlap::NDataAccessorControl::NInMem diff --git a/ydb/core/tx/columnshard/data_accessor/in_mem/collector.h b/ydb/core/tx/columnshard/data_accessor/in_mem/collector.h new file mode 100644 index 000000000000..41ab570d7f25 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/in_mem/collector.h @@ -0,0 +1,21 @@ +#pragma once +#include + +namespace NKikimr::NOlap::NDataAccessorControl::NInMem { +class TCollector: public IGranuleDataAccessor { +private: + using TBase = IGranuleDataAccessor; + THashMap Accessors; + virtual void DoAskData(const std::vector& portions, const std::shared_ptr& callback, + const TString& consumer) override; + virtual TDataCategorized DoAnalyzeData(const std::vector& portions, const TString& consumer) override; + virtual void DoModifyPortions(const std::vector& add, + const std::vector& remove) override; + +public: + TCollector(const ui64 pathId) + : TBase(pathId) { + } +}; + +} // namespace NKikimr::NOlap::NDataAccessorControl::NInMem diff --git a/ydb/core/tx/columnshard/data_accessor/in_mem/constructor.cpp b/ydb/core/tx/columnshard/data_accessor/in_mem/constructor.cpp new file mode 100644 index 000000000000..b004602d6d00 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/in_mem/constructor.cpp @@ -0,0 +1,10 @@ +#include "constructor.h" +#include "manager.h" + +namespace NKikimr::NOlap::NDataAccessorControl::NInMem { + +TConclusion> TManagerConstructor::DoBuild(const TManagerConstructionContext& /*context*/) const { + return std::make_shared(); +} + +} // namespace NKikimr::NOlap::NDataAccessorControl::NInMem diff --git a/ydb/core/tx/columnshard/data_accessor/in_mem/constructor.h b/ydb/core/tx/columnshard/data_accessor/in_mem/constructor.h new file mode 100644 index 000000000000..a7d56f22fb68 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/in_mem/constructor.h @@ -0,0 +1,36 @@ +#pragma once +#include + +namespace NKikimr::NOlap::NDataAccessorControl::NInMem { + +class TManagerConstructor: public IManagerConstructor { +public: + static TString GetClassNameStatic() { + return "in_mem"; + } + +private: + virtual bool IsEqualToWithSameClassName(const IManagerConstructor& /*item*/) const override { + return true; + } + virtual TConclusion> DoBuild(const TManagerConstructionContext& context) const override; + virtual TConclusionStatus DoDeserializeFromJson(const NJson::TJsonValue& /*jsonValue*/) override { + return TConclusionStatus::Success(); + } + virtual bool DoDeserializeFromProto(const TProto& /*proto*/) override { + return true; + } + virtual void DoSerializeToProto(TProto& /*proto*/) const override { + return; + } + + static const inline TFactory::TRegistrator Registrator = + TFactory::TRegistrator(GetClassNameStatic()); + +public: + virtual TString GetClassName() const override { + return GetClassNameStatic(); + } +}; + +} // namespace NKikimr::NOlap::NDataAccessorControl::NInMem diff --git a/ydb/core/tx/columnshard/data_accessor/in_mem/manager.cpp b/ydb/core/tx/columnshard/data_accessor/in_mem/manager.cpp new file mode 100644 index 000000000000..9fc38906012c --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/in_mem/manager.cpp @@ -0,0 +1,26 @@ +#include "collector.h" +#include "manager.h" + +#include +#include + +namespace NKikimr::NOlap::NDataAccessorControl::NInMem { + +std::shared_ptr TManager::DoBuildLoader( + const TVersionedIndex& versionedIndex, TGranuleMeta* granule, const std::shared_ptr& dsGroupSelector) { + auto result = std::make_shared("granule"); + auto portionsLoadContext = std::make_shared(); + result->AddChildren( + std::make_shared("columns", &versionedIndex, granule, dsGroupSelector, portionsLoadContext)); + result->AddChildren( + std::make_shared("indexes", &versionedIndex, granule, dsGroupSelector, portionsLoadContext)); + result->AddChildren( + std::make_shared("finish", &versionedIndex, granule, dsGroupSelector, portionsLoadContext)); + return result; +} + +std::unique_ptr TManager::DoBuildCollector(const ui64 pathId) { + return std::make_unique(pathId); +} + +} // namespace NKikimr::NOlap::NDataAccessorControl::NInMem diff --git a/ydb/core/tx/columnshard/data_accessor/in_mem/manager.h b/ydb/core/tx/columnshard/data_accessor/in_mem/manager.h new file mode 100644 index 000000000000..5ae82e8972eb --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/in_mem/manager.h @@ -0,0 +1,15 @@ +#pragma once +#include +#include + +namespace NKikimr::NOlap::NDataAccessorControl::NInMem { +class TManager: public IMetadataMemoryManager { +private: + virtual std::unique_ptr DoBuildCollector(const ui64 pathId) override; + + virtual std::shared_ptr DoBuildLoader( + const TVersionedIndex& versionedIndex, TGranuleMeta* granule, const std::shared_ptr& dsGroupSelector) override; + +public: +}; +} // namespace NKikimr::NOlap::NDataAccessorControl::NInMem diff --git a/ydb/core/tx/columnshard/data_accessor/in_mem/ya.make b/ydb/core/tx/columnshard/data_accessor/in_mem/ya.make new file mode 100644 index 000000000000..d6218eca2880 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/in_mem/ya.make @@ -0,0 +1,13 @@ +LIBRARY() + +SRCS( + manager.cpp + collector.cpp + GLOBAL constructor.cpp +) + +PEERDIR( + ydb/core/tx/columnshard/data_accessor/abstract +) + +END() diff --git a/ydb/core/tx/columnshard/data_accessor/local_db/collector.cpp b/ydb/core/tx/columnshard/data_accessor/local_db/collector.cpp new file mode 100644 index 000000000000..dfbc9eb74ac7 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/local_db/collector.cpp @@ -0,0 +1,38 @@ +#include "collector.h" + +#include +namespace NKikimr::NOlap::NDataAccessorControl::NLocalDB { + +void TCollector::DoAskData( + const std::vector& portions, const std::shared_ptr& callback, const TString& consumer) { + if (portions.size()) { + NActors::TActivationContext::Send( + TabletActorId, std::make_unique(portions, callback, consumer)); + } +} + +TDataCategorized TCollector::DoAnalyzeData(const std::vector& portions, const TString& consumer) { + Y_UNUSED(consumer); + TDataCategorized result; + for (auto&& p : portions) { + auto it = AccessorsCache.Find(p->GetPortionId()); + if (it != AccessorsCache.End() && it.Key() == p->GetPortionId()) { + result.AddFromCache(it.Value()); + } else { + result.AddToAsk(p); + } + } + return result; +} + +void TCollector::DoModifyPortions(const std::vector& add, const std::vector& remove) { + for (auto&& i : remove) { + TPortionDataAccessor result = TPortionDataAccessor::BuildEmpty(); + AccessorsCache.PickOut(i, &result); + } + for (auto&& i : add) { + AccessorsCache.Insert(i.GetPortionInfo().GetPortionId(), i); + } +} + +} // namespace NKikimr::NOlap::NDataAccessorControl::NLocalDB diff --git a/ydb/core/tx/columnshard/data_accessor/local_db/collector.h b/ydb/core/tx/columnshard/data_accessor/local_db/collector.h new file mode 100644 index 000000000000..c879224f97fe --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/local_db/collector.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include + +#include + +namespace NKikimr::NOlap::NDataAccessorControl::NLocalDB { + +class TCollector: public IGranuleDataAccessor { +private: + const NActors::TActorId TabletActorId; + struct TMetadataSizeProvider { + size_t operator()(const TPortionDataAccessor& data) { + return data.GetMetadataSize(); + } + }; + + TLRUCache AccessorsCache; + using TBase = IGranuleDataAccessor; + virtual void DoAskData(const std::vector& portions, + const std::shared_ptr& callback, const TString& consumer) override; + virtual TDataCategorized DoAnalyzeData(const std::vector& portions, const TString& consumer) override; + virtual void DoModifyPortions(const std::vector& add, const std::vector& remove) override; + +public: + TCollector(const ui64 pathId, const ui64 maxSize, const NActors::TActorId& actorId) + : TBase(pathId) + , TabletActorId(actorId) + , AccessorsCache(maxSize) { + } +}; + +} // namespace NKikimr::NOlap::NDataAccessorControl::NLocalDB diff --git a/ydb/core/tx/columnshard/data_accessor/local_db/constructor.cpp b/ydb/core/tx/columnshard/data_accessor/local_db/constructor.cpp new file mode 100644 index 000000000000..d640cba54813 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/local_db/constructor.cpp @@ -0,0 +1,10 @@ +#include "constructor.h" +#include "manager.h" + +namespace NKikimr::NOlap::NDataAccessorControl::NLocalDB { + +TConclusion> TManagerConstructor::DoBuild(const TManagerConstructionContext& context) const { + return std::make_shared(context.GetTabletActorId(), MemoryCacheSize, FetchOnStart); +} + +} // namespace NKikimr::NOlap::NDataAccessorControl::NLocalDB diff --git a/ydb/core/tx/columnshard/data_accessor/local_db/constructor.h b/ydb/core/tx/columnshard/data_accessor/local_db/constructor.h new file mode 100644 index 000000000000..b5baeab6bfc4 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/local_db/constructor.h @@ -0,0 +1,68 @@ +#pragma once +#include + +namespace NKikimr::NOlap::NDataAccessorControl::NLocalDB { + +class TManagerConstructor: public IManagerConstructor { +public: + static TString GetClassNameStatic() { + return "local_db"; + } + +private: + ui64 MemoryCacheSize = (ui64)128 << 20; + bool FetchOnStart = false; + + virtual TConclusion> DoBuild(const TManagerConstructionContext& context) const override; + virtual TConclusionStatus DoDeserializeFromJson(const NJson::TJsonValue& jsonValue) override { + if (jsonValue.Has("memory_cache_size")) { + if (!jsonValue["memory_cache_size"].IsUInteger()) { + return TConclusionStatus::Fail("'memory_cache_size' have to been uint64"); + } + MemoryCacheSize = jsonValue["memory_cache_size"].GetUIntegerRobust(); + } + + if (jsonValue.Has("fetch_on_start")) { + if (!jsonValue["fetch_on_start"].IsBoolean()) { + return TConclusionStatus::Fail("'fetch_on_start' have to been boolean"); + } + FetchOnStart = jsonValue["fetch_on_start"].GetBoolean(); + } + + return TConclusionStatus::Success(); + } + virtual bool DoDeserializeFromProto(const TProto& proto) override { + if (!proto.HasLocalDB()) { + return true; + } + if (proto.GetLocalDB().HasMemoryCacheSize()) { + MemoryCacheSize = proto.GetLocalDB().GetMemoryCacheSize(); + } + if (proto.GetLocalDB().HasFetchOnStart()) { + FetchOnStart = proto.GetLocalDB().GetFetchOnStart(); + } + + return true; + } + virtual void DoSerializeToProto(TProto& proto) const override { + proto.MutableLocalDB()->SetMemoryCacheSize(MemoryCacheSize); + proto.MutableLocalDB()->SetFetchOnStart(FetchOnStart); + return; + } + static const inline TFactory::TRegistrator Registrator = + TFactory::TRegistrator(GetClassNameStatic()); + +public: + static std::shared_ptr BuildDefault() { + auto result = std::make_shared(); + result->MemoryCacheSize = Max(); + result->FetchOnStart = true; + return result; + } + + virtual TString GetClassName() const override { + return GetClassNameStatic(); + } +}; + +} // namespace NKikimr::NOlap::NDataAccessorControl::NLocalDB diff --git a/ydb/core/tx/columnshard/data_accessor/local_db/manager.cpp b/ydb/core/tx/columnshard/data_accessor/local_db/manager.cpp new file mode 100644 index 000000000000..2c0a97829e02 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/local_db/manager.cpp @@ -0,0 +1,17 @@ +#include "collector.h" +#include "manager.h" + +#include + +namespace NKikimr::NOlap::NDataAccessorControl::NLocalDB { + +std::shared_ptr TManager::DoBuildLoader( + const TVersionedIndex& /*versionedIndex*/, TGranuleMeta* /*granule*/, const std::shared_ptr& /*dsGroupSelector*/) { + return nullptr; +} + +std::unique_ptr TManager::DoBuildCollector(const ui64 pathId) { + return std::make_unique(pathId, MemoryCacheSize, TabletActorId); +} + +} // namespace NKikimr::NOlap::NDataAccessorControl::NLocalDB diff --git a/ydb/core/tx/columnshard/data_accessor/local_db/manager.h b/ydb/core/tx/columnshard/data_accessor/local_db/manager.h new file mode 100644 index 000000000000..8572c1ed81c2 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/local_db/manager.h @@ -0,0 +1,28 @@ +#pragma once +#include + +namespace NKikimr::NOlap::NDataAccessorControl::NLocalDB { +class TManager: public IMetadataMemoryManager { +private: + const NActors::TActorId TabletActorId; + const ui64 MemoryCacheSize; + const bool FetchOnStart = true; + virtual std::unique_ptr DoBuildCollector(const ui64 pathId) override; + + virtual std::shared_ptr DoBuildLoader( + const TVersionedIndex& versionedIndex, TGranuleMeta* granule, const std::shared_ptr& dsGroupSelector) override; + +public: + virtual bool NeedPrefetch() const override { + return FetchOnStart; + } + + TManager(const NActors::TActorId& actorId, const ui64 memoryCacheSize, const bool fetchOnStart) + : TabletActorId(actorId) + , MemoryCacheSize(memoryCacheSize) + , FetchOnStart(fetchOnStart) + { + + } +}; +} // namespace NKikimr::NOlap::NDataAccessorControl::NLocalDB diff --git a/ydb/core/tx/columnshard/data_accessor/local_db/ya.make b/ydb/core/tx/columnshard/data_accessor/local_db/ya.make new file mode 100644 index 000000000000..d6218eca2880 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/local_db/ya.make @@ -0,0 +1,13 @@ +LIBRARY() + +SRCS( + manager.cpp + collector.cpp + GLOBAL constructor.cpp +) + +PEERDIR( + ydb/core/tx/columnshard/data_accessor/abstract +) + +END() diff --git a/ydb/core/tx/columnshard/data_accessor/manager.cpp b/ydb/core/tx/columnshard/data_accessor/manager.cpp new file mode 100644 index 000000000000..906a86a680ef --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/manager.cpp @@ -0,0 +1,130 @@ +#include "manager.h" + +#include + +namespace NKikimr::NOlap::NDataAccessorControl { + +void TLocalManager::DrainQueue() { + std::optional lastPathId; + IGranuleDataAccessor* lastDataAccessor = nullptr; + ui32 countToFlight = 0; + while (PortionsAskInFlight + countToFlight < NYDBTest::TControllers::GetColumnShardController()->GetLimitForPortionsMetadataAsk() && + PortionsAsk.size()) { + THashMap> portionsToAsk; + while (PortionsAskInFlight + countToFlight < 1000 && PortionsAsk.size()) { + auto p = PortionsAsk.front().ExtractPortion(); + PortionsAsk.pop_front(); + if (!lastPathId || *lastPathId != p->GetPathId()) { + lastPathId = p->GetPathId(); + auto it = Managers.find(p->GetPathId()); + if (it == Managers.end()) { + lastDataAccessor = nullptr; + } else { + lastDataAccessor = it->second.get(); + } + } + auto it = RequestsByPortion.find(p->GetPortionId()); + if (it == RequestsByPortion.end()) { + continue; + } + if (!lastDataAccessor) { + for (auto&& i : it->second) { + if (!i->IsFetched() && !i->IsAborted()) { + i->AddError(p->GetPathId(), "path id absent"); + } + } + RequestsByPortion.erase(it); + } else { + bool toAsk = false; + for (auto&& i : it->second) { + if (!i->IsFetched() && !i->IsAborted()) { + toAsk = true; + } + } + if (!toAsk) { + RequestsByPortion.erase(it); + } else { + portionsToAsk[p->GetPathId()].emplace_back(p); + ++countToFlight; + } + } + } + for (auto&& i : portionsToAsk) { + auto it = Managers.find(i.first); + AFL_VERIFY(it != Managers.end()); + auto dataAnalyzed = it->second->AnalyzeData(i.second, "ANALYZE"); + for (auto&& accessor : dataAnalyzed.GetCachedAccessors()) { + auto it = RequestsByPortion.find(accessor.GetPortionInfo().GetPortionId()); + AFL_VERIFY(it != RequestsByPortion.end()); + for (auto&& i : it->second) { + Counters.ResultFromCache->Add(1); + if (!i->IsFetched() && !i->IsAborted()) { + i->AddAccessor(accessor); + } + } + RequestsByPortion.erase(it); + AFL_VERIFY(countToFlight); + --countToFlight; + } + if (dataAnalyzed.GetPortionsToAsk().size()) { + Counters.ResultAskDirectly->Add(dataAnalyzed.GetPortionsToAsk().size()); + it->second->AskData(dataAnalyzed.GetPortionsToAsk(), AccessorCallback, "ANALYZE"); + } + } + } + PortionsAskInFlight += countToFlight; + Counters.FetchingCount->Set(PortionsAskInFlight); + Counters.QueueSize->Set(PortionsAsk.size()); +} + +void TLocalManager::DoAskData(const std::shared_ptr& request) { + AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("event", "ask_data")("request", request->DebugString()); + for (auto&& pathId : request->GetPathIds()) { + auto portions = request->StartFetching(pathId); + for (auto&& [_, i] : portions) { + auto itRequest = RequestsByPortion.find(i->GetPortionId()); + if (itRequest == RequestsByPortion.end()) { + AFL_VERIFY(RequestsByPortion.emplace(i->GetPortionId(), std::vector>({request})).second); + PortionsAsk.emplace_back(i, request->GetAbortionFlag()); + Counters.AskNew->Add(1); + } else { + itRequest->second.emplace_back(request); + Counters.AskDuplication->Add(1); + } + } + } + DrainQueue(); +} + +void TLocalManager::DoRegisterController(std::unique_ptr&& controller, const bool update) { + if (update) { + auto it = Managers.find(controller->GetPathId()); + if (it != Managers.end()) { + it->second = std::move(controller); + } + } else { + AFL_VERIFY(Managers.emplace(controller->GetPathId(), std::move(controller)).second); + } +} + +void TLocalManager::DoAddPortion(const TPortionDataAccessor& accessor) { + { + auto it = Managers.find(accessor.GetPortionInfo().GetPathId()); + AFL_VERIFY(it != Managers.end()); + it->second->ModifyPortions({ accessor }, {}); + } + { + auto it = RequestsByPortion.find(accessor.GetPortionInfo().GetPortionId()); + if (it != RequestsByPortion.end()) { + for (auto&& i : it->second) { + i->AddAccessor(accessor); + } + AFL_VERIFY(PortionsAskInFlight); + --PortionsAskInFlight; + } + RequestsByPortion.erase(it); + } + DrainQueue(); +} + +} // namespace NKikimr::NOlap::NDataAccessorControl diff --git a/ydb/core/tx/columnshard/data_accessor/manager.h b/ydb/core/tx/columnshard/data_accessor/manager.h new file mode 100644 index 000000000000..c03e7a6c6c33 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/manager.h @@ -0,0 +1,186 @@ +#pragma once +#include "events.h" +#include "request.h" + +#include "abstract/collector.h" + +#include + +namespace NKikimr::NOlap::NDataAccessorControl { + +class TAccessorSignals: public NColumnShard::TCommonCountersOwner { +private: + using TBase = NColumnShard::TCommonCountersOwner; + +public: + const NMonitoring::TDynamicCounters::TCounterPtr QueueSize; + const NMonitoring::TDynamicCounters::TCounterPtr FetchingCount; + const NMonitoring::TDynamicCounters::TCounterPtr AskNew; + const NMonitoring::TDynamicCounters::TCounterPtr AskDuplication; + const NMonitoring::TDynamicCounters::TCounterPtr ResultFromCache; + const NMonitoring::TDynamicCounters::TCounterPtr ResultAskDirectly; + + TAccessorSignals() + : TBase("AccessorsFetching") + , QueueSize(TBase::GetValue("Queue/Count")) + , FetchingCount(TBase::GetValue("Fetching/Count")) + , AskNew(TBase::GetDeriviative("Ask/Fault/Count")) + , AskDuplication(TBase::GetDeriviative("Ask/Duplication/Count")) + , ResultFromCache(TBase::GetDeriviative("ResultFromCache/Count")) + , ResultAskDirectly(TBase::GetDeriviative("ResultAskDirectly/Count")) { + } +}; + +class IDataAccessorsManager { +private: + virtual void DoAskData(const std::shared_ptr& request) = 0; + virtual void DoRegisterController(std::unique_ptr&& controller, const bool update) = 0; + virtual void DoUnregisterController(const ui64 pathId) = 0; + virtual void DoAddPortion(const TPortionDataAccessor& accessor) = 0; + virtual void DoRemovePortion(const TPortionInfo::TConstPtr& portion) = 0; + const NActors::TActorId TabletActorId; + +public: + const NActors::TActorId& GetTabletActorId() const { + return TabletActorId; + } + + IDataAccessorsManager(const NActors::TActorId& tabletActorId) + : TabletActorId(tabletActorId) { + } + + virtual ~IDataAccessorsManager() = default; + + void AddPortion(const TPortionDataAccessor& accessor) { + DoAddPortion(accessor); + } + void RemovePortion(const TPortionInfo::TConstPtr& portion) { + DoRemovePortion(portion); + } + void AskData(const std::shared_ptr& request) { + AFL_VERIFY(request); + AFL_VERIFY(request->HasSubscriber()); + return DoAskData(request); + } + void RegisterController(std::unique_ptr&& controller, const bool update) { + AFL_VERIFY(controller); + return DoRegisterController(std::move(controller), update); + } + void UnregisterController(const ui64 pathId) { + return DoUnregisterController(pathId); + } +}; + +class TDataAccessorsManagerContainer: public NBackgroundTasks::TControlInterfaceContainer { +private: + using TBase = NBackgroundTasks::TControlInterfaceContainer; + +public: + using TBase::TBase; +}; + +class TActorAccessorsManager: public IDataAccessorsManager { +private: + using TBase = IDataAccessorsManager; + const NActors::TActorId ActorId; + std::shared_ptr AccessorsCallback; + virtual void DoAskData(const std::shared_ptr& request) override { + NActors::TActivationContext::Send(ActorId, std::make_unique(request)); + } + virtual void DoRegisterController(std::unique_ptr&& controller, const bool update) override { + NActors::TActivationContext::Send(ActorId, std::make_unique(std::move(controller), update)); + } + virtual void DoUnregisterController(const ui64 pathId) override { + NActors::TActivationContext::Send(ActorId, std::make_unique(pathId)); + } + virtual void DoAddPortion(const TPortionDataAccessor& accessor) override { + NActors::TActivationContext::Send(ActorId, std::make_unique(accessor)); + } + virtual void DoRemovePortion(const TPortionInfo::TConstPtr& portion) override { + NActors::TActivationContext::Send(ActorId, std::make_unique(portion)); + } + +public: + TActorAccessorsManager(const NActors::TActorId& actorId, const NActors::TActorId& tabletActorId) + : TBase(tabletActorId) + , ActorId(actorId) + , AccessorsCallback(std::make_shared(ActorId)) { + AFL_VERIFY(!!tabletActorId); + } +}; + +class TLocalManager: public IDataAccessorsManager { +private: + using TBase = IDataAccessorsManager; + THashMap> Managers; + THashMap>> RequestsByPortion; + TAccessorSignals Counters; + const std::shared_ptr AccessorCallback; + + class TPortionToAsk { + private: + TPortionInfo::TConstPtr Portion; + YDB_READONLY_DEF(std::shared_ptr, AbortionFlag); + + public: + TPortionToAsk(const TPortionInfo::TConstPtr& portion, const std::shared_ptr& abortionFlag) + : Portion(portion) + , AbortionFlag(abortionFlag) { + } + + TPortionInfo::TConstPtr ExtractPortion() { + return std::move(Portion); + } + }; + + std::deque PortionsAsk; + ui64 PortionsAskInFlight = 0; + + void DrainQueue(); + + virtual void DoAskData(const std::shared_ptr& request) override; + virtual void DoRegisterController(std::unique_ptr&& controller, const bool update) override; + virtual void DoUnregisterController(const ui64 pathId) override { + AFL_VERIFY(Managers.erase(pathId)); + } + virtual void DoAddPortion(const TPortionDataAccessor& accessor) override; + virtual void DoRemovePortion(const TPortionInfo::TConstPtr& portionInfo) override { + auto it = Managers.find(portionInfo->GetPathId()); + AFL_VERIFY(it != Managers.end()); + it->second->ModifyPortions({}, { portionInfo->GetPortionId() }); + } + +public: + class TTestingCallback: public IAccessorCallback { + private: + std::weak_ptr Manager; + virtual void OnAccessorsFetched(std::vector&& accessors) override { + auto mImpl = Manager.lock(); + if (!mImpl) { + return; + } + for (auto&& i : accessors) { + mImpl->AddPortion(i); + } + } + + public: + void InitManager(const std::weak_ptr& manager) { + Manager = manager; + } + }; + + static std::shared_ptr BuildForTests() { + auto callback = std::make_shared(); + std::shared_ptr result = std::make_shared(callback); + callback->InitManager(result); + return result; + } + + TLocalManager(const std::shared_ptr& callback) + : TBase(NActors::TActorId()) + , AccessorCallback(callback) { + } +}; + +} // namespace NKikimr::NOlap::NDataAccessorControl diff --git a/ydb/core/tx/columnshard/data_accessor/request.cpp b/ydb/core/tx/columnshard/data_accessor/request.cpp new file mode 100644 index 000000000000..bdf3f6cf2af8 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/request.cpp @@ -0,0 +1,10 @@ +#include "request.h" + +namespace NKikimr::NOlap { + +void IDataAccessorRequestsSubscriber::RegisterRequestId(const TDataAccessorsRequest& request) { + AFL_VERIFY(!request.IsFetched()); + AFL_VERIFY(RequestIds.emplace(request.GetRequestId()).second); +} + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/data_accessor/request.h b/ydb/core/tx/columnshard/data_accessor/request.h new file mode 100644 index 000000000000..6be2665e8be1 --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/request.h @@ -0,0 +1,344 @@ +#pragma once +#include +#include +#include +#include + +namespace NKikimr::NOlap { + +class TDataAccessorsRequest; + +class TDataAccessorsResult: private NNonCopyable::TMoveOnly { +private: + THashMap ErrorsByPathId; + THashMap PortionsById; + +public: + const THashMap& GetPortions() const { + return PortionsById; + } + + std::vector ExtractPortionsVector() { + std::vector portions; + portions.reserve(PortionsById.size()); + for (auto&& [_, portionInfo] : PortionsById) { + portions.emplace_back(std::move(portionInfo)); + } + return portions; + } + + void Merge(TDataAccessorsResult&& result) { + for (auto&& i : result.ErrorsByPathId) { + AFL_VERIFY(ErrorsByPathId.emplace(i.first, i.second).second); + } + for (auto&& i : result.PortionsById) { + AFL_VERIFY(PortionsById.emplace(i.first, std::move(i.second)).second); + } + } + + const TPortionDataAccessor& GetPortionAccessorVerified(const ui64 portionId) const { + auto it = PortionsById.find(portionId); + AFL_VERIFY(it != PortionsById.end()); + return it->second; + } + + void AddData(THashMap&& accessors) { + std::deque v; + for (auto&& [portionId, i] : accessors) { + AFL_VERIFY(PortionsById.emplace(portionId, i).second); + } + } + + void AddError(const ui64 pathId, const TString& errorMessage) { + ErrorsByPathId.emplace(pathId, errorMessage); + } + + bool HasErrors() const { + return ErrorsByPathId.size(); + } +}; + +class IDataAccessorRequestsSubscriber: public NColumnShard::TMonitoringObjectsCounter { +private: + THashSet RequestIds; + + virtual void DoOnRequestsFinished(TDataAccessorsResult&& result) = 0; + virtual const std::shared_ptr& DoGetAbortionFlag() const = 0; + + void OnRequestsFinished(TDataAccessorsResult&& result) { + DoOnRequestsFinished(std::move(result)); + } + + void RegisterRequestId(const TDataAccessorsRequest& request); + + friend class TDataAccessorsRequest; + std::optional Result; + +public: + void OnResult(const ui32 requestId, TDataAccessorsResult&& result) { + AFL_VERIFY(RequestIds.erase(requestId)); + if (!Result) { + Result = std::move(result); + } else { + Result->Merge(std::move(result)); + } + if (RequestIds.empty()) { + OnRequestsFinished(std::move(*Result)); + } + } + const std::shared_ptr& GetAbortionFlag() const { + return DoGetAbortionFlag(); + } + + virtual ~IDataAccessorRequestsSubscriber() = default; +}; + +class TFakeDataAccessorsSubscriber: public IDataAccessorRequestsSubscriber { +private: + virtual const std::shared_ptr& DoGetAbortionFlag() const override { + return Default>(); + } + virtual void DoOnRequestsFinished(TDataAccessorsResult&& /*result*/) override { + } +}; + +class TPathFetchingState { +public: + enum class EFetchStage { + Preparing, + Fetching, + Error, + Fetched + }; + +private: + const ui64 PathId; + + YDB_READONLY(EFetchStage, Stage, EFetchStage::Preparing); + YDB_READONLY_DEF(TString, ErrorMessage); + THashMap Portions; + THashMap PortionAccessors; + +public: + TString DebugString() const { + TStringBuilder sb; + sb << "portions_count=" << Portions.size(); + return sb; + } + + TPathFetchingState(const ui64 pathId) + : PathId(pathId) { + } + + const THashMap& GetPortions() const { + return Portions; + } + + bool IsFinished() { + return Portions.empty() || Stage == EFetchStage::Error; + } + + THashMap&& DetachAccessors() { + return std::move(PortionAccessors); + } + + void AddPortion(const TPortionInfo::TConstPtr& portion) { + AFL_VERIFY(Stage == EFetchStage::Preparing); + AFL_VERIFY(portion->GetPathId() == PathId); + AFL_VERIFY(Portions.emplace(portion->GetPortionId(), portion).second); + } + + void AddAccessor( + const TPortionDataAccessor& accessor, const std::optional>& columnIds, const std::optional>& indexIds) { + AFL_VERIFY(Stage == EFetchStage::Fetching); + AFL_VERIFY(Portions.erase(accessor.GetPortionInfo().GetPortionId())); + AFL_VERIFY(PortionAccessors.emplace(accessor.GetPortionInfo().GetPortionId(), accessor.Extract(columnIds, indexIds)).second); + if (Portions.empty()) { + AFL_VERIFY(Stage == EFetchStage::Fetching); + Stage = EFetchStage::Fetched; + } + } + void StartFetch() { + AFL_VERIFY(Stage == EFetchStage::Preparing); + Stage = EFetchStage::Fetching; + AFL_VERIFY(Portions.size()); + } + + void OnError(const TString& errorMessage) { + AFL_VERIFY(Stage == EFetchStage::Fetching); + Stage = EFetchStage::Error; + ErrorMessage = errorMessage; + } +}; + +class TDataAccessorsRequest: public NColumnShard::TMonitoringObjectsCounter { +private: + static inline TAtomicCounter Counter = 0; + ui32 FetchStage = 0; + YDB_READONLY(ui64, RequestId, Counter.Inc()); + YDB_READONLY_DEF(TString, Consumer); + THashSet PortionIds; + THashMap PathIdStatus; + THashSet PathIds; + TDataAccessorsResult AccessorsByPathId; + YDB_READONLY_DEF(std::optional>, ColumnIds); + std::optional> IndexIds; + + TAtomicCounter PreparingCount = 0; + TAtomicCounter FetchingCount = 0; + TAtomicCounter ReadyCount = 0; + + std::shared_ptr Subscriber; + + void CheckReady() { + if (PathIdStatus.size()) { + return; + } + AFL_VERIFY(!PreparingCount.Val()); + AFL_VERIFY(!FetchingCount.Val()); + FetchStage = 2; + Subscriber->OnResult(RequestId, std::move(AccessorsByPathId)); + Subscriber = nullptr; + } + +public: + void SetColumnIds(const std::set& columnIds) { + AFL_VERIFY(!ColumnIds); + ColumnIds = columnIds; + } + + TString DebugString() const { + TStringBuilder sb; + sb << "request_id=" << RequestId << ";"; + for (auto&& i : PathIdStatus) { + sb << i.first << "={" << i.second.DebugString() << "};"; + } + return sb; + } + + TDataAccessorsRequest(const TString& consumer) + : Consumer(consumer) + { + + } + + ui64 PredictAccessorsMemory(const ISnapshotSchema::TPtr& schema) const { + ui64 result = 0; + for (auto&& i : PathIdStatus) { + for (auto&& [_, p] : i.second.GetPortions()) { + result += p->PredictAccessorsMemory(schema); + } + } + return result; + } + + bool IsAborted() const { + AFL_VERIFY(HasSubscriber()); + auto flag = Subscriber->GetAbortionFlag(); + return flag && flag->Val(); + } + + const std::shared_ptr& GetAbortionFlag() const { + AFL_VERIFY(HasSubscriber()); + return Subscriber->GetAbortionFlag(); + } + + bool HasSubscriber() const { + return !!Subscriber; + } + + ui32 GetSize() const { + return PortionIds.size(); + } + + const THashSet& GetPathIds() const { + return PathIds; + } + + bool IsEmpty() const { + return PortionIds.empty(); + } + + void RegisterSubscriber(const std::shared_ptr& subscriber) { + AFL_VERIFY(!Subscriber); + AFL_VERIFY(FetchStage == 0); + Subscriber = subscriber; + Subscriber->RegisterRequestId(*this); + } + + const THashMap& StartFetching(const ui64 pathId) { + AFL_VERIFY(!!Subscriber); + AFL_VERIFY(FetchStage <= 1); + FetchStage = 1; + + auto it = PathIdStatus.find(pathId); + AFL_VERIFY(it != PathIdStatus.end()); + it->second.StartFetch(); + + FetchingCount.Inc(); + AFL_VERIFY(PreparingCount.Dec() >= 0); + + return it->second.GetPortions(); + } + + void AddPortion(const TPortionInfo::TConstPtr& portion) { + AFL_VERIFY(portion); + AFL_VERIFY(FetchStage <= 1); + AFL_VERIFY(PortionIds.emplace(portion->GetPortionId()).second); + PathIds.emplace(portion->GetPathId()); + auto it = PathIdStatus.find(portion->GetPathId()); + if (it == PathIdStatus.end()) { + PreparingCount.Inc(); + it = PathIdStatus.emplace(portion->GetPathId(), portion->GetPathId()).first; + } + it->second.AddPortion(portion); + } + + bool IsFetched() const { + return FetchStage == 2; + } + + void AddError(const ui64 pathId, const TString& errorMessage) { + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("error", errorMessage)("event", "ErrorOnFetching")("path_id", pathId); + AFL_VERIFY(FetchStage <= 1); + auto itStatus = PathIdStatus.find(pathId); + AFL_VERIFY(itStatus != PathIdStatus.end()); + itStatus->second.OnError(errorMessage); + PathIdStatus.erase(itStatus); + AFL_VERIFY(FetchingCount.Dec() >= 0); + ReadyCount.Inc(); + AccessorsByPathId.AddError(pathId, errorMessage); + CheckReady(); + } + + void AddAccessor(const TPortionDataAccessor& accessor) { + AFL_VERIFY(FetchStage == 1); + auto pathId = accessor.GetPortionInfo().GetPathId(); + { + auto itStatus = PathIdStatus.find(pathId); + AFL_VERIFY(itStatus != PathIdStatus.end()); + itStatus->second.AddAccessor(accessor, ColumnIds, IndexIds); + if (itStatus->second.IsFinished()) { + AFL_VERIFY(FetchingCount.Dec() >= 0); + ReadyCount.Inc(); + AccessorsByPathId.AddData(itStatus->second.DetachAccessors()); + PathIdStatus.erase(itStatus); + } + } + CheckReady(); + } + + void AddData(THashMap>&& accessors) { + for (auto&& i : accessors) { + for (auto&& a : i.second) { + AddAccessor(std::move(a)); + } + } + } + + TString GetTaskId() const { + return TStringBuilder() << "data-accessor-request-" << RequestId; + } +}; + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/data_accessor/ya.make b/ydb/core/tx/columnshard/data_accessor/ya.make new file mode 100644 index 000000000000..f3212e91e74e --- /dev/null +++ b/ydb/core/tx/columnshard/data_accessor/ya.make @@ -0,0 +1,18 @@ +LIBRARY() + +SRCS( + actor.cpp + events.cpp + request.cpp + manager.cpp +) + +PEERDIR( + ydb/library/actors/core + ydb/core/tx/columnshard/engines/portions + ydb/core/tx/columnshard/data_accessor/abstract + ydb/core/tx/columnshard/data_accessor/local_db + ydb/core/tx/columnshard/resource_subscriber +) + +END() diff --git a/ydb/core/tx/columnshard/data_locks/locks/abstract.h b/ydb/core/tx/columnshard/data_locks/locks/abstract.h index 826d363ad850..0f4bfb7c0fb3 100644 --- a/ydb/core/tx/columnshard/data_locks/locks/abstract.h +++ b/ydb/core/tx/columnshard/data_locks/locks/abstract.h @@ -1,53 +1,93 @@ #pragma once #include -#include #include +#include -#include #include +#include +#include #include namespace NKikimr::NOlap { class TPortionInfo; class TGranuleMeta; -} +} // namespace NKikimr::NOlap namespace NKikimr::NOlap::NDataLocks { +enum class ELockCategory : ui32 { + Compaction = 0, + Cleanup, + Sharing, + Actualization, + Tables, + Any, + MAX +}; + +static const inline std::array, (ui32)ELockCategory::MAX> LockCategoriesInteraction = { + //Compaction + std::set({ ELockCategory::Compaction, ELockCategory::Actualization, ELockCategory::Tables, ELockCategory::Any}), + //Cleanup + std::set({ ELockCategory::Cleanup, ELockCategory::Sharing, ELockCategory::Tables, ELockCategory::Any }), + //Sharing + std::set({ ELockCategory::Sharing, ELockCategory::Cleanup, ELockCategory::Tables, ELockCategory::Any }), + //Actualization + std::set({ ELockCategory::Actualization, ELockCategory::Compaction, ELockCategory::Tables, ELockCategory::Any }), + //Tables + std::set({ ELockCategory::Cleanup, ELockCategory::Sharing, ELockCategory::Actualization, ELockCategory::Compaction, + ELockCategory::Tables, ELockCategory::Any }), + //Any + std::set({ ELockCategory::Cleanup, ELockCategory::Sharing, ELockCategory::Actualization, ELockCategory::Compaction, + ELockCategory::Tables, ELockCategory::Any }), +}; + class ILock { private: YDB_READONLY_DEF(TString, LockName); YDB_READONLY_FLAG(ReadOnly, false); + const ELockCategory Category; + protected: - virtual std::optional DoIsLocked(const TPortionInfo& portion, const THashSet& excludedLocks = {}) const = 0; - virtual std::optional DoIsLocked(const TGranuleMeta& granule, const THashSet& excludedLocks = {}) const = 0; + virtual std::optional DoIsLocked( + const TPortionInfo& portion, const ELockCategory category, const THashSet& excludedLocks = {}) const = 0; + virtual std::optional DoIsLocked( + const TGranuleMeta& granule, const ELockCategory category, const THashSet& excludedLocks = {}) const = 0; virtual bool DoIsEmpty() const = 0; + public: - ILock(const TString& lockName, const bool isReadOnly = false) + ILock(const TString& lockName, const ELockCategory category, const bool isReadOnly = false) : LockName(lockName) , ReadOnlyFlag(isReadOnly) - { - + , Category(category) { } virtual ~ILock() = default; - std::optional IsLocked(const TPortionInfo& portion, const THashSet& excludedLocks = {}, const bool readOnly = false) const { + std::optional IsLocked(const TPortionInfo& portion, const ELockCategory portionForLock, const THashSet& excludedLocks = {}, + const bool readOnly = false) const { if (IsReadOnly() && readOnly) { return {}; } - return DoIsLocked(portion, excludedLocks); + if (!LockCategoriesInteraction[(ui32)Category].contains(portionForLock)) { + return {}; + } + return DoIsLocked(portion, portionForLock, excludedLocks); } - std::optional IsLocked(const TGranuleMeta& g, const THashSet& excludedLocks = {}, const bool readOnly = false) const { + std::optional IsLocked(const TGranuleMeta& g, const ELockCategory portionForLock, const THashSet& excludedLocks = {}, + const bool readOnly = false) const { if (IsReadOnly() && readOnly) { return {}; } - return DoIsLocked(g, excludedLocks); + if (!LockCategoriesInteraction[(ui32)Category].contains(portionForLock)) { + return {}; + } + return DoIsLocked(g, portionForLock, excludedLocks); } bool IsEmpty() const { return DoIsEmpty(); } }; -} \ No newline at end of file +} // namespace NKikimr::NOlap::NDataLocks diff --git a/ydb/core/tx/columnshard/data_locks/locks/composite.h b/ydb/core/tx/columnshard/data_locks/locks/composite.h index 3c57da6047b8..819239bb5d38 100644 --- a/ydb/core/tx/columnshard/data_locks/locks/composite.h +++ b/ydb/core/tx/columnshard/data_locks/locks/composite.h @@ -8,23 +8,24 @@ class TCompositeLock: public ILock { using TBase = ILock; std::vector> Locks; protected: - virtual std::optional DoIsLocked(const TPortionInfo& portion, const THashSet& excludedLocks) const override { + virtual std::optional DoIsLocked(const TPortionInfo& portion, const ELockCategory category, const THashSet& excludedLocks) const override { for (auto&& i : Locks) { if (excludedLocks.contains(i->GetLockName())) { continue; } - if (auto lockName = i->IsLocked(portion)) { + if (auto lockName = i->IsLocked(portion, category)) { return lockName; } } return {}; } - virtual std::optional DoIsLocked(const TGranuleMeta& granule, const THashSet& excludedLocks) const override { + virtual std::optional DoIsLocked( + const TGranuleMeta& granule, const ELockCategory category, const THashSet& excludedLocks) const override { for (auto&& i : Locks) { if (excludedLocks.contains(i->GetLockName())) { continue; } - if (auto lockName = i->IsLocked(granule)) { + if (auto lockName = i->IsLocked(granule, category)) { return lockName; } } @@ -34,8 +35,23 @@ class TCompositeLock: public ILock { return Locks.empty(); } public: - TCompositeLock(const TString& lockName, const std::vector>& locks, const bool readOnly = false) - : TBase(lockName, readOnly) + static std::shared_ptr Build(const TString& lockName, const std::initializer_list>& locks) { + std::vector> locksUseful; + for (auto&& i : locks) { + if (i && !i->IsEmpty()) { + locksUseful.emplace_back(i); + } + } + if (locksUseful.size() == 1) { + return locksUseful.front(); + } else { + return std::make_shared(lockName, locksUseful); + } + } + + TCompositeLock(const TString& lockName, const std::vector>& locks, + const ELockCategory category = NDataLocks::ELockCategory::Any, const bool readOnly = false) + : TBase(lockName, category, readOnly) { for (auto&& l : locks) { if (!l || l->IsEmpty()) { @@ -45,8 +61,9 @@ class TCompositeLock: public ILock { } } - TCompositeLock(const TString& lockName, std::initializer_list> locks, const bool readOnly = false) - : TBase(lockName, readOnly) + TCompositeLock(const TString& lockName, std::initializer_list> locks, + const ELockCategory category = NDataLocks::ELockCategory::Any, const bool readOnly = false) + : TBase(lockName, category, readOnly) { for (auto&& l : locks) { if (!l || l->IsEmpty()) { diff --git a/ydb/core/tx/columnshard/data_locks/locks/list.h b/ydb/core/tx/columnshard/data_locks/locks/list.h index 512386e985b1..b206d334420f 100644 --- a/ydb/core/tx/columnshard/data_locks/locks/list.h +++ b/ydb/core/tx/columnshard/data_locks/locks/list.h @@ -1,7 +1,7 @@ #pragma once #include "abstract.h" #include -#include +#include namespace NKikimr::NOlap::NDataLocks { @@ -11,13 +11,15 @@ class TListPortionsLock: public ILock { THashSet Portions; THashSet Granules; protected: - virtual std::optional DoIsLocked(const TPortionInfo& portion, const THashSet& /*excludedLocks*/) const override { + virtual std::optional DoIsLocked( + const TPortionInfo& portion, const ELockCategory /*category*/, const THashSet& /*excludedLocks*/) const override { if (Portions.contains(portion.GetAddress())) { return GetLockName(); } return {}; } - virtual std::optional DoIsLocked(const TGranuleMeta& granule, const THashSet& /*excludedLocks*/) const override { + virtual std::optional DoIsLocked( + const TGranuleMeta& granule, const ELockCategory /*category*/, const THashSet& /*excludedLocks*/) const override { if (Granules.contains(granule.GetPathId())) { return GetLockName(); } @@ -27,17 +29,36 @@ class TListPortionsLock: public ILock { return Portions.empty(); } public: - TListPortionsLock(const TString& lockName, const std::vector>& portions, const bool readOnly = false) - : TBase(lockName, readOnly) + TListPortionsLock(const TString& lockName, const std::vector& portions, const ELockCategory category, const bool readOnly = false) + : TBase(lockName, category, readOnly) { + for (auto&& p : portions) { + Portions.emplace(p.GetPortionInfo().GetAddress()); + Granules.emplace(p.GetPortionInfo().GetPathId()); + } + } + + TListPortionsLock(const TString& lockName, const std::vector>& portions, const ELockCategory category, + const bool readOnly = false) + : TBase(lockName, category, readOnly) { for (auto&& p : portions) { Portions.emplace(p->GetAddress()); Granules.emplace(p->GetPathId()); } } - TListPortionsLock(const TString& lockName, const std::vector& portions, const bool readOnly = false) - : TBase(lockName, readOnly) { + TListPortionsLock( + const TString& lockName, const std::vector& portions, const ELockCategory category, const bool readOnly = false) + : TBase(lockName, category, readOnly) { + for (auto&& p : portions) { + Portions.emplace(p->GetAddress()); + Granules.emplace(p->GetPathId()); + } + } + + TListPortionsLock( + const TString& lockName, const std::vector& portions, const ELockCategory category, const bool readOnly = false) + : TBase(lockName, category, readOnly) { for (auto&& p : portions) { Portions.emplace(p.GetAddress()); Granules.emplace(p.GetPathId()); @@ -45,8 +66,9 @@ class TListPortionsLock: public ILock { } template - TListPortionsLock(const TString& lockName, const std::vector& portions, const TGetter& g, const bool readOnly = false) - : TBase(lockName, readOnly) { + TListPortionsLock( + const TString& lockName, const std::vector& portions, const TGetter& g, const ELockCategory category, const bool readOnly = false) + : TBase(lockName, category, readOnly) { for (auto&& p : portions) { const auto address = g(p); Portions.emplace(address); @@ -55,14 +77,24 @@ class TListPortionsLock: public ILock { } template - TListPortionsLock(const TString& lockName, const THashMap& portions, const bool readOnly = false) - : TBase(lockName, readOnly) { + TListPortionsLock( + const TString& lockName, const THashMap& portions, const ELockCategory category, const bool readOnly = false) + : TBase(lockName, category, readOnly) { for (auto&& p : portions) { const auto address = p.first; Portions.emplace(address); Granules.emplace(address.GetPathId()); } } + + TListPortionsLock( + const TString& lockName, const THashSet& portions, const ELockCategory category, const bool readOnly = false) + : TBase(lockName, category, readOnly) { + for (auto&& address : portions) { + Portions.emplace(address); + Granules.emplace(address.GetPathId()); + } + } }; class TListTablesLock: public ILock { @@ -70,13 +102,15 @@ class TListTablesLock: public ILock { using TBase = ILock; THashSet Tables; protected: - virtual std::optional DoIsLocked(const TPortionInfo& portion, const THashSet& /*excludedLocks*/) const override { + virtual std::optional DoIsLocked( + const TPortionInfo& portion, const ELockCategory /*category*/, const THashSet& /*excludedLocks*/) const override { if (Tables.contains(portion.GetPathId())) { return GetLockName(); } return {}; } - virtual std::optional DoIsLocked(const TGranuleMeta& granule, const THashSet& /*excludedLocks*/) const override { + virtual std::optional DoIsLocked( + const TGranuleMeta& granule, const ELockCategory /*category*/, const THashSet& /*excludedLocks*/) const override { if (Tables.contains(granule.GetPathId())) { return GetLockName(); } @@ -86,8 +120,8 @@ class TListTablesLock: public ILock { return Tables.empty(); } public: - TListTablesLock(const TString& lockName, const THashSet& tables, const bool readOnly = false) - : TBase(lockName, readOnly) + TListTablesLock(const TString& lockName, const THashSet& tables, const ELockCategory category, const bool readOnly = false) + : TBase(lockName, category, readOnly) , Tables(tables) { } diff --git a/ydb/core/tx/columnshard/data_locks/locks/snapshot.h b/ydb/core/tx/columnshard/data_locks/locks/snapshot.h index 78edc72599f0..d39d7189671a 100644 --- a/ydb/core/tx/columnshard/data_locks/locks/snapshot.h +++ b/ydb/core/tx/columnshard/data_locks/locks/snapshot.h @@ -1,7 +1,7 @@ #pragma once #include "abstract.h" #include -#include +#include namespace NKikimr::NOlap::NDataLocks { @@ -11,7 +11,8 @@ class TSnapshotLock: public ILock { const TSnapshot SnapshotBarrier; const THashSet PathIds; protected: - virtual std::optional DoIsLocked(const TPortionInfo& portion, const THashSet& /*excludedLocks*/) const override { + virtual std::optional DoIsLocked( + const TPortionInfo& portion, const ELockCategory /*category*/, const THashSet& /*excludedLocks*/) const override { if (PathIds.contains(portion.GetPathId()) && portion.RecordSnapshotMin() <= SnapshotBarrier) { return GetLockName(); } @@ -20,15 +21,16 @@ class TSnapshotLock: public ILock { virtual bool DoIsEmpty() const override { return PathIds.empty(); } - virtual std::optional DoIsLocked(const TGranuleMeta& granule, const THashSet& /*excludedLocks*/) const override { + virtual std::optional DoIsLocked( + const TGranuleMeta& granule, const ELockCategory /*category*/, const THashSet& /*excludedLocks*/) const override { if (PathIds.contains(granule.GetPathId())) { return GetLockName(); } return {}; } public: - TSnapshotLock(const TString& lockName, const TSnapshot& snapshotBarrier, const THashSet& pathIds, const bool readOnly = false) - : TBase(lockName, readOnly) + TSnapshotLock(const TString& lockName, const TSnapshot& snapshotBarrier, const THashSet& pathIds, const ELockCategory category, const bool readOnly = false) + : TBase(lockName, category, readOnly) , SnapshotBarrier(snapshotBarrier) , PathIds(pathIds) { diff --git a/ydb/core/tx/columnshard/data_locks/manager/manager.cpp b/ydb/core/tx/columnshard/data_locks/manager/manager.cpp index 8de6300b85a1..0ded5dc7f4dd 100644 --- a/ydb/core/tx/columnshard/data_locks/manager/manager.cpp +++ b/ydb/core/tx/columnshard/data_locks/manager/manager.cpp @@ -15,30 +15,38 @@ void TManager::UnregisterLock(const TString& processId) { AFL_VERIFY(ProcessLocks.erase(processId))("process_id", processId); } -std::optional TManager::IsLocked(const TPortionInfo& portion, const THashSet& excludedLocks) const { +std::optional TManager::IsLocked( + const TPortionInfo& portion, const ELockCategory lockCategory, const THashSet& excludedLocks) const { for (auto&& i : ProcessLocks) { if (excludedLocks.contains(i.first)) { continue; } - if (auto lockName = i.second->IsLocked(portion, excludedLocks)) { + if (auto lockName = i.second->IsLocked(portion, lockCategory, excludedLocks)) { return lockName; } } return {}; } -std::optional TManager::IsLocked(const TGranuleMeta& granule, const THashSet& excludedLocks) const { +std::optional TManager::IsLocked( + const TGranuleMeta& granule, const ELockCategory lockCategory, const THashSet& excludedLocks) const { for (auto&& i : ProcessLocks) { if (excludedLocks.contains(i.first)) { continue; } - if (auto lockName = i.second->IsLocked(granule, excludedLocks)) { + if (auto lockName = i.second->IsLocked(granule, lockCategory, excludedLocks)) { return lockName; } } return {}; } +std::optional TManager::IsLocked(const std::shared_ptr& portion, const ELockCategory lockCategory, + const THashSet& excludedLocks /*= {}*/) const { + AFL_VERIFY(!!portion); + return IsLocked(*portion, lockCategory, excludedLocks); +} + void TManager::Stop() { AFL_VERIFY(StopFlag->Inc() == 1); } diff --git a/ydb/core/tx/columnshard/data_locks/manager/manager.h b/ydb/core/tx/columnshard/data_locks/manager/manager.h index b59a0bdb1e83..7d7d43f57327 100644 --- a/ydb/core/tx/columnshard/data_locks/manager/manager.h +++ b/ydb/core/tx/columnshard/data_locks/manager/manager.h @@ -41,8 +41,12 @@ class TManager { [[nodiscard]] std::shared_ptr RegisterLock(Args&&... args) { return RegisterLock(std::make_shared(args...)); } - std::optional IsLocked(const TPortionInfo& portion, const THashSet& excludedLocks = {}) const; - std::optional IsLocked(const TGranuleMeta& granule, const THashSet& excludedLocks = {}) const; + std::optional IsLocked( + const TPortionInfo& portion, const ELockCategory lockCategory, const THashSet& excludedLocks = {}) const; + std::optional IsLocked( + const std::shared_ptr& portion, const ELockCategory lockCategory, const THashSet& excludedLocks = {}) const; + std::optional IsLocked( + const TGranuleMeta& granule, const ELockCategory lockCategory, const THashSet& excludedLocks = {}) const; }; diff --git a/ydb/core/tx/columnshard/data_reader/actor.cpp b/ydb/core/tx/columnshard/data_reader/actor.cpp index a1fb223f78a6..32924b143395 100644 --- a/ydb/core/tx/columnshard/data_reader/actor.cpp +++ b/ydb/core/tx/columnshard/data_reader/actor.cpp @@ -3,6 +3,12 @@ namespace NKikimr::NOlap::NDataReader { void TActor::HandleExecute(NKqp::TEvKqpCompute::TEvScanData::TPtr& ev) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_RESTORE)("event", "scan_data"); + LastAck = std::nullopt; + if (!CheckActivity()) { + TBase::Send(*ScanActorId, new NKqp::TEvKqp::TEvAbortExecution(NYql::NDqProto::StatusIds::ABORTED, "external task aborted")); + return; + } SwitchStage(EStage::WaitData, EStage::WaitData); auto data = ev->Get()->ArrowBatch; AFL_VERIFY(!!data || ev->Get()->Finished); @@ -10,43 +16,99 @@ void TActor::HandleExecute(NKqp::TEvKqpCompute::TEvScanData::TPtr& ev) { AFL_VERIFY(ScanActorId); const auto status = RestoreTask->OnDataChunk(data); if (status.IsSuccess()) { - TBase::Send(*ScanActorId, new NKqp::TEvKqpCompute::TEvScanDataAck(FreeSpace, 1, 1)); + TBase::Send(*ScanActorId, new NKqp::TEvKqpCompute::TEvScanDataAck(FreeSpace, 1, 1), NActors::IEventHandle::FlagTrackDelivery); + LastAck = TMonotonic::Now(); } else { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_RESTORE)("event", "scan_data_restore_fail")("message", status.GetErrorMessage()); SwitchStage(EStage::WaitData, EStage::Finished); TBase::Send(*ScanActorId, NKqp::TEvKqp::TEvAbortExecution::Aborted("task finished: " + status.GetErrorMessage()).Release()); + PassAway(); } } else { SwitchStage(EStage::WaitData, EStage::Finished); auto status = RestoreTask->OnFinished(); if (status.IsFail()) { - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("event", "restore_task_finished_error")("reason", status.GetErrorMessage()); + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD_RESTORE)("event", "restore_task_finished_error")("reason", status.GetErrorMessage()); } else { - AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("event", "restore_task_finished")("reason", status.GetErrorMessage()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_RESTORE)("event", "restore_task_finished"); } PassAway(); } } void TActor::HandleExecute(NKqp::TEvKqpCompute::TEvScanInitActor::TPtr& ev) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_RESTORE)("event", "init_actor"); + LastAck = std::nullopt; + if (!CheckActivity()) { + TBase::Send(*ScanActorId, new NKqp::TEvKqp::TEvAbortExecution(NYql::NDqProto::StatusIds::ABORTED, "external task aborted")); + return; + } SwitchStage(EStage::Initialization, EStage::WaitData); AFL_VERIFY(!ScanActorId); auto& msg = ev->Get()->Record; ScanActorId = ActorIdFromProto(msg.GetScanActorId()); - TBase::Send(*ScanActorId, new NKqp::TEvKqpCompute::TEvScanDataAck(FreeSpace, 1, 1)); + TBase::Send(*ScanActorId, new NKqp::TEvKqpCompute::TEvScanDataAck(FreeSpace, 1, 1), NActors::IEventHandle::FlagTrackDelivery); + LastAck = TMonotonic::Now(); } void TActor::HandleExecute(NKqp::TEvKqpCompute::TEvScanError::TPtr& ev) { SwitchStage(std::nullopt, EStage::Finished); - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("event", "problem_on_restore_data")( + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD_RESTORE)("event", "problem_on_restore_data")( "reason", NYql::IssuesFromMessageAsString(ev->Get()->Record.GetIssues())); RestoreTask->OnError(NYql::IssuesFromMessageAsString(ev->Get()->Record.GetIssues())); PassAway(); } +void TActor::HandleExecute(NActors::TEvents::TEvUndelivered::TPtr& ev) { + SwitchStage(std::nullopt, EStage::Finished); + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD_RESTORE)("event", "problem_on_event_undelivered")("reason", ev->Get()->Reason); + RestoreTask->OnError("cannot delivery event: " + ::ToString(ev->Get()->Reason)); + PassAway(); +} + +void TActor::HandleExecute(NActors::TEvents::TEvWakeup::TPtr& /*ev*/) { + if (!CheckActivity()) { + TBase::Send(*ScanActorId, new NKqp::TEvKqp::TEvAbortExecution(NYql::NDqProto::StatusIds::ABORTED, "external task aborted")); + return; + } + + if (LastAck && TMonotonic::Now() - *LastAck > RestoreTask->GetTimeout()) { + SwitchStage(std::nullopt, EStage::Finished); + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD_RESTORE)("event", "problem_timeout"); + RestoreTask->OnError("timeout on restore data"); + TBase::Send(*ScanActorId, new NKqp::TEvKqp::TEvAbortExecution(NYql::NDqProto::StatusIds::ABORTED, "external task aborted")); + PassAway(); + return; + } + Schedule(TDuration::Seconds(1), new NActors::TEvents::TEvWakeup()); +} + void TActor::Bootstrap(const TActorContext& /*ctx*/) { + if (!CheckActivity()) { + return; + } + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_RESTORE)("event", "start_restore")("tablet_actor_id", RestoreTask->GetTabletActorId())( + "this", (ui64)this); auto evStart = RestoreTask->BuildRequestInitiator(); - Send(RestoreTask->GetTabletActorId(), evStart.release()); + Send(RestoreTask->GetTabletActorId(), evStart.release(), NActors::IEventHandle::FlagTrackDelivery); + LastAck = TMonotonic::Now(); Become(&TActor::StateFunc); + Schedule(TDuration::Seconds(1), new NActors::TEvents::TEvWakeup()); +} + +bool TActor::CheckActivity() { + if (AbortedFlag) { + return false; + } + if (RestoreTask->IsActive()) { + return true; + } + AbortedFlag = true; + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "restoring_cancelled_from_operation"); + SwitchStage(std::nullopt, EStage::Finished); + RestoreTask->OnError("restore task aborted through operation cancelled"); + PassAway(); + return false; } -} \ No newline at end of file +} // namespace NKikimr::NOlap::NDataReader diff --git a/ydb/core/tx/columnshard/data_reader/actor.h b/ydb/core/tx/columnshard/data_reader/actor.h index 048ee314922f..d395aa2bc074 100644 --- a/ydb/core/tx/columnshard/data_reader/actor.h +++ b/ydb/core/tx/columnshard/data_reader/actor.h @@ -8,6 +8,7 @@ namespace NKikimr::NOlap::NDataReader { class IRestoreTask { private: + YDB_READONLY_DEF(TString, TaskId); YDB_READONLY(ui64, TabletId, 0); YDB_READONLY_DEF(NActors::TActorId, TabletActorId); virtual TConclusionStatus DoOnDataChunk(const std::shared_ptr& data) = 0; @@ -16,6 +17,9 @@ class IRestoreTask { virtual std::unique_ptr DoBuildRequestInitiator() const = 0; public: + virtual bool IsActive() const = 0; + virtual TDuration GetTimeout() const = 0; + TConclusionStatus OnDataChunk(const std::shared_ptr& data) { AFL_VERIFY(data->num_rows()); return DoOnDataChunk(data); @@ -33,8 +37,9 @@ class IRestoreTask { return DoBuildRequestInitiator(); } - IRestoreTask(const ui64 tabletId, const NActors::TActorId& tabletActorId) - : TabletId(tabletId) + IRestoreTask(const ui64 tabletId, const NActors::TActorId& tabletActorId, const TString& taskId) + : TaskId(taskId) + , TabletId(tabletId) , TabletActorId(tabletActorId) { @@ -65,11 +70,16 @@ class TActor: public NActors::TActorBootstrapped { } Stage = to; } + std::optional LastAck; + bool AbortedFlag = false; + bool CheckActivity(); protected: void HandleExecute(NKqp::TEvKqpCompute::TEvScanInitActor::TPtr& ev); void HandleExecute(NKqp::TEvKqpCompute::TEvScanData::TPtr& ev); void HandleExecute(NKqp::TEvKqpCompute::TEvScanError::TPtr& ev); + void HandleExecute(NActors::TEvents::TEvUndelivered::TPtr& ev); + void HandleExecute(NActors::TEvents::TEvWakeup::TPtr& ev); public: TActor(const std::shared_ptr& rTask) @@ -79,14 +89,17 @@ class TActor: public NActors::TActorBootstrapped { } STATEFN(StateFunc) { - NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("tablet_id", RestoreTask->GetTabletId()); + NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("tablet_id", RestoreTask->GetTabletId())("tablet_actor_id", + RestoreTask->GetTabletActorId())("this", (ui64)this)("activity", RestoreTask->IsActive())("task_id", RestoreTask->GetTaskId()); try { switch (ev->GetTypeRewrite()) { hFunc(NKqp::TEvKqpCompute::TEvScanInitActor, HandleExecute); hFunc(NKqp::TEvKqpCompute::TEvScanData, HandleExecute); hFunc(NKqp::TEvKqpCompute::TEvScanError, HandleExecute); + hFunc(NActors::TEvents::TEvUndelivered, HandleExecute); + hFunc(NActors::TEvents::TEvWakeup, HandleExecute); default: - AFL_VERIFY(false); + AFL_VERIFY(false)("type", ev->GetTypeName()); } } catch (...) { AFL_VERIFY(false); diff --git a/ydb/core/tx/columnshard/data_sharing/common/session/common.cpp b/ydb/core/tx/columnshard/data_sharing/common/session/common.cpp index eb95cc36711a..6693b984a6e9 100644 --- a/ydb/core/tx/columnshard/data_sharing/common/session/common.cpp +++ b/ydb/core/tx/columnshard/data_sharing/common/session/common.cpp @@ -12,33 +12,35 @@ TString TCommonSession::DebugString() const { return TStringBuilder() << "{id=" << SessionId << ";context=" << TransferContext.DebugString() << ";state=" << State << ";}"; } -bool TCommonSession::TryStart(const NColumnShard::TColumnShard& shard) { +TConclusionStatus TCommonSession::TryStart(NColumnShard::TColumnShard& shard) { const NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("info", Info); AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("info", "Start"); AFL_VERIFY(State == EState::Prepared); AFL_VERIFY(!!LockGuard); const auto& index = shard.GetIndexAs(); - THashMap>> portionsByPath; + THashMap> portionsByPath; THashSet StoragesIds; for (auto&& i : GetPathIdsForStart()) { - auto& portionsVector = portionsByPath[i]; const auto& g = index.GetGranuleVerified(i); for (auto&& p : g.GetPortionsOlderThenSnapshot(GetSnapshotBarrier())) { - if (shard.GetDataLocksManager()->IsLocked(*p.second, { "sharing_session:" + GetSessionId() })) { - return false; + if (shard.GetDataLocksManager()->IsLocked( + *p.second, NDataLocks::ELockCategory::Sharing, { "sharing_session:" + GetSessionId() })) { + return TConclusionStatus::Fail("failed to start cursor: portion is locked"); } - portionsVector.emplace_back(p.second); +// portionsByPath[i].emplace_back(p.second); } } if (shard.GetStoragesManager()->GetSharedBlobsManager()->HasExternalModifications()) { - return false; + return TConclusionStatus::Fail("failed to start cursor: has external modifications"); } - AFL_VERIFY(DoStart(shard, portionsByPath)); - State = EState::InProgress; - return true; + TConclusionStatus status = DoStart(shard, std::move(portionsByPath)); + if (status.Ok()) { + State = EState::InProgress; + } + return status; } void TCommonSession::PrepareToStart(const NColumnShard::TColumnShard& shard) { @@ -47,12 +49,12 @@ void TCommonSession::PrepareToStart(const NColumnShard::TColumnShard& shard) { State = EState::Prepared; AFL_VERIFY(!LockGuard); LockGuard = shard.GetDataLocksManager()->RegisterLock("sharing_session:" + GetSessionId(), - TransferContext.GetSnapshotBarrierVerified(), GetPathIdsForStart(), true); + TransferContext.GetSnapshotBarrierVerified(), GetPathIdsForStart(), NDataLocks::ELockCategory::Sharing, true); shard.GetSharingSessionsManager()->StartSharingSession(); } void TCommonSession::Finish(const NColumnShard::TColumnShard& shard, const std::shared_ptr& dataLocksManager) { - AFL_VERIFY(State == EState::InProgress); + AFL_VERIFY(State == EState::InProgress || State == EState::Prepared); State = EState::Finished; shard.GetSharingSessionsManager()->FinishSharingSession(); AFL_VERIFY(LockGuard); diff --git a/ydb/core/tx/columnshard/data_sharing/common/session/common.h b/ydb/core/tx/columnshard/data_sharing/common/session/common.h index 289480501e60..9e78d6f4288b 100644 --- a/ydb/core/tx/columnshard/data_sharing/common/session/common.h +++ b/ydb/core/tx/columnshard/data_sharing/common/session/common.h @@ -1,8 +1,8 @@ #pragma once +#include #include -#include #include -#include +#include #include #include @@ -13,10 +13,11 @@ class TColumnShard; namespace NKikimr::NOlap { class TPortionInfo; +class TPortionDataAccessor; namespace NDataLocks { class TManager; } -} +} // namespace NKikimr::NOlap namespace NKikimr::NOlap::NDataSharing { @@ -40,17 +41,17 @@ class TCommonSession { YDB_READONLY(ui64, RuntimeId, GetNextRuntimeId()); std::shared_ptr LockGuard; EState State = EState::Created; + protected: TTransferContext TransferContext; - virtual bool DoStart(const NColumnShard::TColumnShard& shard, const THashMap>>& portions) = 0; + virtual TConclusionStatus DoStart(NColumnShard::TColumnShard& shard, THashMap>&& portions) = 0; virtual THashSet GetPathIdsForStart() const = 0; + public: virtual ~TCommonSession() = default; TCommonSession(const TString& info) - : Info(info) - { - + : Info(info) { } TCommonSession(const TString& sessionId, const TString& info, const TTransferContext& transferContext) @@ -85,7 +86,7 @@ class TCommonSession { } void PrepareToStart(const NColumnShard::TColumnShard& shard); - bool TryStart(const NColumnShard::TColumnShard& shard); + TConclusionStatus TryStart(NColumnShard::TColumnShard& shard); void Finish(const NColumnShard::TColumnShard& shard, const std::shared_ptr& dataLocksManager); const TSnapshot& GetSnapshotBarrier() const { @@ -120,7 +121,6 @@ class TCommonSession { } return TConclusionStatus::Success(); } - }; -} \ No newline at end of file +} // namespace NKikimr::NOlap::NDataSharing diff --git a/ydb/core/tx/columnshard/data_sharing/destination/events/transfer.cpp b/ydb/core/tx/columnshard/data_sharing/destination/events/transfer.cpp index cd90a7353322..2fae651fcb34 100644 --- a/ydb/core/tx/columnshard/data_sharing/destination/events/transfer.cpp +++ b/ydb/core/tx/columnshard/data_sharing/destination/events/transfer.cpp @@ -1,15 +1,18 @@ #include "transfer.h" -#include + #include +#include #include +#include namespace NKikimr::NOlap::NDataSharing::NEvents { -THashMap TPathIdData::BuildLinkTabletTasks( - const std::shared_ptr& storages, const TTabletId selfTabletId, const TTransferContext& context, const TVersionedIndex& index) { +THashMap TPathIdData::BuildLinkTabletTasks( + const std::shared_ptr& storages, const TTabletId selfTabletId, const TTransferContext& context, + const TVersionedIndex& index) { THashMap> blobIds; for (auto&& i : Portions) { - auto schema = i.GetSchema(index); + auto schema = i.GetPortionInfo().GetSchema(index); i.FillBlobIdsByStorage(blobIds, schema->GetIndexInfo()); } @@ -51,7 +54,9 @@ THashMap storageTabletTasks; for (auto&& [_, blobInfo] : blobs) { - THashMap blobTabletTasks = context.GetMoving() ? blobInfo.BuildTabletTasksOnMove(context, selfTabletId, storageId) : blobInfo.BuildTabletTasksOnCopy(context, selfTabletId, storageId); + THashMap blobTabletTasks = context.GetMoving() + ? blobInfo.BuildTabletTasksOnMove(context, selfTabletId, storageId) + : blobInfo.BuildTabletTasksOnCopy(context, selfTabletId, storageId); for (auto&& [tId, tInfo] : blobTabletTasks) { auto itTablet = storageTabletTasks.find(tId); if (itTablet == storageTabletTasks.end()) { @@ -71,4 +76,4 @@ THashMap +#include #include #include -#include +#include +#include +#include +#include #include - -namespace NKikimr::NOlap { -class TVersionedIndex; -} - namespace NKikimr::NOlap::NDataSharing { class TSharedBlobsManager; class TTaskForTablet; -} +} // namespace NKikimr::NOlap::NDataSharing namespace NKikimr::NOlap::NDataSharing::NEvents { class TPathIdData { private: YDB_READONLY(ui64, PathId, 0); - YDB_ACCESSOR_DEF(std::vector, Portions); + YDB_ACCESSOR_DEF(std::vector, Portions); TPathIdData() = default; - TConclusionStatus DeserializeFromProto(const NKikimrColumnShardDataSharingProto::TPathIdData& proto, const TIndexInfo& indexInfo) { + TConclusionStatus DeserializeFromProto( + const NKikimrColumnShardDataSharingProto::TPathIdData& proto, const TVersionedIndex& versionedIndex, const IBlobGroupSelector& groupSelector) { if (!proto.HasPathId()) { return TConclusionStatus::Fail("no path id in proto"); } PathId = proto.GetPathId(); for (auto&& portionProto : proto.GetPortions()) { - TConclusion portion = TPortionInfo::BuildFromProto(portionProto, indexInfo); + const auto schema = versionedIndex.GetSchemaVerified(portionProto.GetSchemaVersion()); + TConclusion portion = TPortionDataAccessor::BuildFromProto(portionProto, schema->GetIndexInfo(), groupSelector); if (!portion) { return portion.GetError(); } @@ -38,15 +38,14 @@ class TPathIdData { } return TConclusionStatus::Success(); } + public: - TPathIdData(const ui64 pathId, const std::vector& portions) + TPathIdData(const ui64 pathId, const std::vector& portions) : PathId(pathId) - , Portions(portions) - { - + , Portions(portions) { } - std::vector DetachPortions() { + std::vector DetachPortions() { return std::move(Portions); } THashMap BuildLinkTabletTasks(const std::shared_ptr& storages, const TTabletId selfTabletId, @@ -55,9 +54,9 @@ class TPathIdData { void InitPortionIds(ui64* lastPortionId, const std::optional pathId = {}) { AFL_VERIFY(lastPortionId); for (auto&& i : Portions) { - i.SetPortion(++*lastPortionId); + i.MutablePortionInfo().SetPortionId(++*lastPortionId); if (pathId) { - i.SetPathId(*pathId); + i.MutablePortionInfo().SetPathId(*pathId); } } } @@ -69,33 +68,39 @@ class TPathIdData { } }; - - static TConclusion BuildFromProto(const NKikimrColumnShardDataSharingProto::TPathIdData& proto, const TIndexInfo& indexInfo) { + static TConclusion BuildFromProto( + const NKikimrColumnShardDataSharingProto::TPathIdData& proto, const TVersionedIndex& versionedIndex, const IBlobGroupSelector& groupSelector) { TPathIdData result; - auto resultParsing = result.DeserializeFromProto(proto, indexInfo); + auto resultParsing = result.DeserializeFromProto(proto, versionedIndex, groupSelector); if (!resultParsing) { return resultParsing; } else { return result; } } - }; -struct TEvSendDataFromSource: public NActors::TEventPB { +struct TEvSendDataFromSource: public NActors::TEventPB { TEvSendDataFromSource() = default; - TEvSendDataFromSource(const TString& sessionId, const ui32 packIdx, const TTabletId sourceTabletId, const THashMap& pathIdData) { + TEvSendDataFromSource( + const TString& sessionId, const ui32 packIdx, const TTabletId sourceTabletId, const THashMap& pathIdData, TArrayRef schemas) { Record.SetSessionId(sessionId); Record.SetPackIdx(packIdx); Record.SetSourceTabletId((ui64)sourceTabletId); for (auto&& i : pathIdData) { i.second.SerializeToProto(*Record.AddPathIdData()); } + + for (auto&& i : schemas) { + *Record.AddSchemeHistory() = i.GetProto(); + } } }; -struct TEvFinishedFromSource: public NActors::TEventPB { +struct TEvFinishedFromSource: public NActors::TEventPB { TEvFinishedFromSource() = default; TEvFinishedFromSource(const TString& sessionId, const TTabletId sourceTabletId) { @@ -104,4 +109,4 @@ struct TEvFinishedFromSource: public NActors::TEventPB #include #include +#include #include namespace NKikimr::NOlap::NDataSharing { -NKikimr::TConclusionStatus TDestinationSession::DataReceived(THashMap&& data, TColumnEngineForLogs& index, const std::shared_ptr& /*manager*/) { +NKikimr::TConclusionStatus TDestinationSession::DataReceived( + THashMap&& data, TColumnEngineForLogs& index, const std::shared_ptr& /*manager*/) { auto guard = index.GranulesStorage->GetStats()->StartPackModification(); for (auto&& i : data) { auto it = PathIds.find(i.first); AFL_VERIFY(it != PathIds.end())("path_id_undefined", i.first); for (auto&& portion : i.second.DetachPortions()) { - portion.SetPathId(it->second); - index.UpsertPortion(std::move(portion)); + portion.MutablePortionInfo().SetPathId(it->second); + index.AppendPortion(portion); } } return TConclusionStatus::Success(); @@ -66,28 +68,31 @@ void TDestinationSession::SendCurrentCursorAck(const NColumnShard::TColumnShard& AFL_VERIFY(found); } -NKikimr::TConclusion> TDestinationSession::ReceiveData( - NColumnShard::TColumnShard* self, const THashMap& data, const ui32 receivedPackIdx, const TTabletId sourceTabletId, +NKikimr::TConclusion> TDestinationSession::ReceiveData(NColumnShard::TColumnShard* self, + THashMap&& data, std::vector&& schemas, const ui32 receivedPackIdx, const TTabletId sourceTabletId, const std::shared_ptr& selfPtr) { auto result = GetCursorVerified(sourceTabletId).ReceiveData(receivedPackIdx); if (!result) { return result; } - return std::unique_ptr(new TTxDataFromSource(self, selfPtr, data, sourceTabletId)); + return std::unique_ptr(new TTxDataFromSource(self, selfPtr, std::move(data), std::move(schemas), sourceTabletId)); } -NKikimr::TConclusion> TDestinationSession::ReceiveFinished(NColumnShard::TColumnShard* self, const TTabletId sourceTabletId, const std::shared_ptr& selfPtr) { +NKikimr::TConclusion> TDestinationSession::ReceiveFinished( + NColumnShard::TColumnShard* self, const TTabletId sourceTabletId, const std::shared_ptr& selfPtr) { if (GetCursorVerified(sourceTabletId).GetDataFinished()) { return TConclusionStatus::Fail("session finished already"); } return std::unique_ptr(new TTxFinishFromSource(self, sourceTabletId, selfPtr)); } -NKikimr::TConclusion> TDestinationSession::AckInitiatorFinished(NColumnShard::TColumnShard* self, const std::shared_ptr& selfPtr) { +NKikimr::TConclusion> TDestinationSession::AckInitiatorFinished( + NColumnShard::TColumnShard* self, const std::shared_ptr& selfPtr) { return std::unique_ptr(new TTxFinishAckFromInitiator(self, selfPtr)); } -NKikimr::TConclusionStatus TDestinationSession::DeserializeDataFromProto(const NKikimrColumnShardDataSharingProto::TDestinationSession& proto, const TColumnEngineForLogs& index) { +NKikimr::TConclusionStatus TDestinationSession::DeserializeDataFromProto( + const NKikimrColumnShardDataSharingProto::TDestinationSession& proto, const TColumnEngineForLogs& index) { if (!InitiatorController.DeserializeFromProto(proto.GetInitiatorController())) { return TConclusionStatus::Fail("cannot parse initiator controller: " + proto.GetInitiatorController().DebugString()); } @@ -139,7 +144,8 @@ NKikimrColumnShardDataSharingProto::TDestinationSession::TFullCursor TDestinatio return result; } -NKikimr::TConclusionStatus TDestinationSession::DeserializeCursorFromProto(const NKikimrColumnShardDataSharingProto::TDestinationSession::TFullCursor& proto) { +NKikimr::TConclusionStatus TDestinationSession::DeserializeCursorFromProto( + const NKikimrColumnShardDataSharingProto::TDestinationSession::TFullCursor& proto) { ConfirmedFlag = proto.GetConfirmedFlag(); for (auto&& i : proto.GetSourceCursors()) { TSourceCursorForDestination cursor; @@ -154,21 +160,22 @@ NKikimr::TConclusionStatus TDestinationSession::DeserializeCursorFromProto(const return TConclusionStatus::Success(); } -bool TDestinationSession::DoStart(const NColumnShard::TColumnShard& shard, const THashMap>>& portions) { +TConclusionStatus TDestinationSession::DoStart( + NColumnShard::TColumnShard& shard, THashMap>&& portions) { AFL_VERIFY(IsConfirmed()); NYDBTest::TControllers::GetColumnShardController()->OnDataSharingStarted(shard.TabletID(), GetSessionId()); THashMap> local; for (auto&& i : portions) { for (auto&& p : i.second) { - p->FillBlobIdsByStorage(local, shard.GetIndexAs().GetVersionedIndex()); + p.FillBlobIdsByStorage(local, shard.GetIndexAs().GetVersionedIndex()); } } std::swap(CurrentBlobIds, local); SendCurrentCursorAck(shard, {}); - return true; + return TConclusionStatus::Success(); } -bool TDestinationSession::TryTakePortionBlobs(const TVersionedIndex& vIndex, const TPortionInfo& portion) { +bool TDestinationSession::TryTakePortionBlobs(const TVersionedIndex& vIndex, const TPortionDataAccessor& portion) { THashMap> blobIds; portion.FillBlobIdsByStorage(blobIds, vIndex); ui32 containsCounter = 0; @@ -187,4 +194,4 @@ bool TDestinationSession::TryTakePortionBlobs(const TVersionedIndex& vIndex, con return newCounter; } -} // namespace NKikimr::NOlap::NDataSharing +} // namespace NKikimr::NOlap::NDataSharing diff --git a/ydb/core/tx/columnshard/data_sharing/destination/session/destination.h b/ydb/core/tx/columnshard/data_sharing/destination/session/destination.h index 3106415c4daa..7b47a013b008 100644 --- a/ydb/core/tx/columnshard/data_sharing/destination/session/destination.h +++ b/ydb/core/tx/columnshard/data_sharing/destination/session/destination.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -15,7 +16,7 @@ class TColumnShard; namespace NKikimr::NOlap { class TColumnEngineForLogs; class IStoragesManager; -} // namespace NKikimr::NOlap +} // namespace NKikimr::NOlap namespace NKikimr::NOlap::NDataSharing { @@ -78,7 +79,7 @@ class TDestinationSession: public TCommonSession { THashMap> CurrentBlobIds; protected: - virtual bool DoStart(const NColumnShard::TColumnShard& shard, const THashMap>>& portions) override; + virtual TConclusionStatus DoStart(NColumnShard::TColumnShard& shard, THashMap>&& portions) override; virtual THashSet GetPathIdsForStart() const override { THashSet result; for (auto&& i : PathIds) { @@ -88,7 +89,7 @@ class TDestinationSession: public TCommonSession { } public: - bool TryTakePortionBlobs(const TVersionedIndex& vIndex, const TPortionInfo& portion); + bool TryTakePortionBlobs(const TVersionedIndex& vIndex, const TPortionDataAccessor& portion); TSourceCursorForDestination& GetCursorVerified(const TTabletId& tabletId) { auto it = Cursors.find(tabletId); @@ -122,8 +123,8 @@ class TDestinationSession: public TCommonSession { [[nodiscard]] TConclusion> AckInitiatorFinished(NColumnShard::TColumnShard* self, const std::shared_ptr& selfPtr); - [[nodiscard]] TConclusion> ReceiveData(NColumnShard::TColumnShard* self, const THashMap& data, - const ui32 receivedPackIdx, const TTabletId sourceTabletId, const std::shared_ptr& selfPtr); + [[nodiscard]] TConclusion> ReceiveData(NColumnShard::TColumnShard* self, THashMap&& data, + std::vector&& schemas, const ui32 receivedPackIdx, const TTabletId sourceTabletId, const std::shared_ptr& selfPtr); NKikimrColumnShardDataSharingProto::TDestinationSession::TFullCursor SerializeCursorToProto() const; [[nodiscard]] TConclusionStatus DeserializeCursorFromProto(const NKikimrColumnShardDataSharingProto::TDestinationSession::TFullCursor& proto); @@ -131,4 +132,4 @@ class TDestinationSession: public TCommonSession { [[nodiscard]] TConclusionStatus DeserializeDataFromProto(const NKikimrColumnShardDataSharingProto::TDestinationSession& proto, const TColumnEngineForLogs& index); }; -} // namespace NKikimr::NOlap::NDataSharing +} // namespace NKikimr::NOlap::NDataSharing diff --git a/ydb/core/tx/columnshard/data_sharing/destination/transactions/tx_data_from_source.cpp b/ydb/core/tx/columnshard/data_sharing/destination/transactions/tx_data_from_source.cpp index 0cde35b73dec..28b13a7eebd0 100644 --- a/ydb/core/tx/columnshard/data_sharing/destination/transactions/tx_data_from_source.cpp +++ b/ydb/core/tx/columnshard/data_sharing/destination/transactions/tx_data_from_source.cpp @@ -6,8 +6,20 @@ namespace NKikimr::NOlap::NDataSharing { bool TTxDataFromSource::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { using namespace NKikimr::NColumnShard; - TDbWrapper dbWrapper(txc.DB, nullptr); + + NIceDb::TNiceDb db(txc.DB); + for (auto info : SchemeHistory) { + info.SaveToLocalDb(db); + } + auto& index = Self->TablesManager.MutablePrimaryIndexAsVerified(); + + for (auto& info : SchemeHistory) { + index.RegisterOldSchemaVersion(info.GetSnapshot(), info.GetProto().GetId(), info.GetSchema()); + } + + TDbWrapper dbWrapper(txc.DB, nullptr); + { ui64* lastPortionPtr = index.GetLastPortionPointer(); for (auto&& i : PortionsByPathId) { @@ -24,7 +36,6 @@ bool TTxDataFromSource::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, p.SaveToDatabase(dbWrapper, schemaPtr->GetIndexInfo().GetPKFirstColumnId(), false); } } - NIceDb::TNiceDb db(txc.DB); db.Table().Key(Session->GetSessionId()) .Update(NIceDb::TUpdate(Session->SerializeCursorToProto().SerializeAsString())); return true; @@ -35,23 +46,22 @@ void TTxDataFromSource::DoComplete(const TActorContext& /*ctx*/) { Session->SendCurrentCursorAck(*Self, SourceTabletId); } -TTxDataFromSource::TTxDataFromSource(NColumnShard::TColumnShard* self, const std::shared_ptr& session, const THashMap& portionsByPathId, const TTabletId sourceTabletId) +TTxDataFromSource::TTxDataFromSource(NColumnShard::TColumnShard* self, const std::shared_ptr& session, THashMap&& portionsByPathId, std::vector&& schemas, const TTabletId sourceTabletId) : TBase(self) , Session(session) - , PortionsByPathId(portionsByPathId) - , SourceTabletId(sourceTabletId) -{ + , PortionsByPathId(std::move(portionsByPathId)) + , SchemeHistory(std::move(schemas)) + , SourceTabletId(sourceTabletId) { for (auto&& i : PortionsByPathId) { for (ui32 p = 0; p < i.second.GetPortions().size();) { if (Session->TryTakePortionBlobs(Self->GetIndexAs().GetVersionedIndex(), i.second.GetPortions()[p])) { ++p; } else { i.second.MutablePortions()[p] = std::move(i.second.MutablePortions().back()); - i.second.MutablePortions()[p].ResetShardingVersion(); + i.second.MutablePortions()[p].MutablePortionInfo().ResetShardingVersion(); i.second.MutablePortions().pop_back(); } } } } - } \ No newline at end of file diff --git a/ydb/core/tx/columnshard/data_sharing/destination/transactions/tx_data_from_source.h b/ydb/core/tx/columnshard/data_sharing/destination/transactions/tx_data_from_source.h index 82b69ac41fb6..91d48eb76346 100644 --- a/ydb/core/tx/columnshard/data_sharing/destination/transactions/tx_data_from_source.h +++ b/ydb/core/tx/columnshard/data_sharing/destination/transactions/tx_data_from_source.h @@ -1,10 +1,11 @@ #pragma once +#include #include #include -#include -#include #include -#include +#include +#include +#include namespace NKikimr::NOlap::NDataSharing { @@ -14,12 +15,13 @@ class TTxDataFromSource: public TExtendedTransactionBase Session; THashMap PortionsByPathId; THashMap> SharedBlobIds; + std::vector SchemeHistory; const TTabletId SourceTabletId; protected: virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& ctx) override; virtual void DoComplete(const TActorContext& ctx) override; public: - TTxDataFromSource(NColumnShard::TColumnShard* self, const std::shared_ptr& session, const THashMap& portionsByPathId, const TTabletId sourceTabletId); + TTxDataFromSource(NColumnShard::TColumnShard* self, const std::shared_ptr& session, THashMap&& portionsByPathId, std::vector&& schemas, const TTabletId sourceTabletId); TTxType GetTxType() const override { return NColumnShard::TXTYPE_DATA_SHARING_DATA_FROM_SOURCE; } }; diff --git a/ydb/core/tx/columnshard/data_sharing/manager/sessions.cpp b/ydb/core/tx/columnshard/data_sharing/manager/sessions.cpp index 18a30ac76061..a1bc57a37433 100644 --- a/ydb/core/tx/columnshard/data_sharing/manager/sessions.cpp +++ b/ydb/core/tx/columnshard/data_sharing/manager/sessions.cpp @@ -7,7 +7,7 @@ namespace NKikimr::NOlap::NDataSharing { -void TSessionsManager::Start(const NColumnShard::TColumnShard& shard) const { +void TSessionsManager::Start(NColumnShard::TColumnShard& shard) const { NActors::TLogContextGuard logGuard = NActors::TLogContextBuilder::Build()("sessions", "start")("tablet_id", shard.TabletID()); for (auto&& i : SourceSessions) { if (i.second->IsReadyForStarting()) { @@ -22,12 +22,15 @@ void TSessionsManager::Start(const NColumnShard::TColumnShard& shard) const { for (auto&& i : SourceSessions) { if (i.second->IsPrepared()) { - i.second->TryStart(shard); + TConclusionStatus status = i.second->TryStart(shard); + AFL_VERIFY(status.Ok())("failed to start source session", status.GetErrorMessage()); } } for (auto&& i : DestSessions) { if (i.second->IsPrepared() && i.second->IsConfirmed()) { - i.second->TryStart(shard); + TConclusionStatus status = i.second->TryStart(shard); + AFL_VERIFY(status.Ok())("failed to start dest session", status.GetErrorMessage()); + if (!i.second->GetSourcesInProgressCount()) { i.second->Finish(shard, shard.GetDataLocksManager()); } @@ -67,11 +70,21 @@ bool TSessionsManager::Load(NTable::TDatabase& database, const TColumnEngineForL NKikimrColumnShardDataSharingProto::TSourceSession protoSession; AFL_VERIFY(protoSession.ParseFromString(rowset.GetValue())); - NKikimrColumnShardDataSharingProto::TSourceSession::TCursorDynamic protoSessionCursorDynamic; - AFL_VERIFY(protoSessionCursorDynamic.ParseFromString(rowset.GetValue())); + std::optional protoSessionCursorDynamic; + if (rowset.HaveValue()) { + protoSessionCursorDynamic = NKikimrColumnShardDataSharingProto::TSourceSession::TCursorDynamic{}; + AFL_VERIFY(protoSessionCursorDynamic->ParseFromString(rowset.GetValue())); + } - NKikimrColumnShardDataSharingProto::TSourceSession::TCursorStatic protoSessionCursorStatic; - AFL_VERIFY(protoSessionCursorStatic.ParseFromString(rowset.GetValue())); + std::optional protoSessionCursorStatic; + if (rowset.HaveValue()) { + protoSessionCursorStatic = NKikimrColumnShardDataSharingProto::TSourceSession::TCursorStatic{}; + AFL_VERIFY(protoSessionCursorStatic->ParseFromString(rowset.GetValue())); + } + + if (protoSessionCursorDynamic && !protoSessionCursorStatic) { + protoSessionCursorStatic = NKikimrColumnShardDataSharingProto::TSourceSession::TCursorStatic{}; + } AFL_VERIFY(index); session->DeserializeFromProto(protoSession, protoSessionCursorDynamic, protoSessionCursorStatic).Validate(); diff --git a/ydb/core/tx/columnshard/data_sharing/manager/sessions.h b/ydb/core/tx/columnshard/data_sharing/manager/sessions.h index a2e5efdaa60b..8c49b64ece85 100644 --- a/ydb/core/tx/columnshard/data_sharing/manager/sessions.h +++ b/ydb/core/tx/columnshard/data_sharing/manager/sessions.h @@ -28,7 +28,7 @@ class TSessionsManager { return SharingSessions.Val(); } - void Start(const NColumnShard::TColumnShard& shard) const; + void Start(NColumnShard::TColumnShard& shard) const; std::shared_ptr GetSourceSession(const TString& sessionId) const { auto it = SourceSessions.find(sessionId); diff --git a/ydb/core/tx/columnshard/data_sharing/protos/data.proto b/ydb/core/tx/columnshard/data_sharing/protos/data.proto index 8b376e919946..6ead2d5241e0 100644 --- a/ydb/core/tx/columnshard/data_sharing/protos/data.proto +++ b/ydb/core/tx/columnshard/data_sharing/protos/data.proto @@ -36,6 +36,8 @@ message TPortionInfo { repeated TIndexChunk Indexes = 7; repeated NKikimrColumnShardProto.TUnifiedBlobId BlobIds = 8; optional uint64 SchemaVersion = 9; + optional uint64 InsertWriteId = 10; + optional NKikimrColumnShardProto.TSnapshot CommitSnapshot = 11; } message TPathIdData { diff --git a/ydb/core/tx/columnshard/data_sharing/protos/events.proto b/ydb/core/tx/columnshard/data_sharing/protos/events.proto index 39d329030197..bc48fa44dde6 100644 --- a/ydb/core/tx/columnshard/data_sharing/protos/events.proto +++ b/ydb/core/tx/columnshard/data_sharing/protos/events.proto @@ -3,6 +3,7 @@ import "ydb/core/tx/columnshard/data_sharing/protos/links.proto"; import "ydb/core/tx/columnshard/data_sharing/protos/data.proto"; import "ydb/core/tx/columnshard/data_sharing/protos/sessions.proto"; import "ydb/core/tx/columnshard/data_sharing/protos/initiator.proto"; +import "ydb/core/protos/tx_columnshard.proto"; package NKikimrColumnShardDataSharingProto; @@ -23,6 +24,7 @@ message TEvSendDataFromSource { optional uint64 PackIdx = 2; repeated TPathIdData PathIdData = 3; optional uint64 SourceTabletId = 4; + repeated NKikimrTxColumnShard.TSchemaPresetVersionInfo SchemeHistory = 5; } message TEvAckDataToSource { diff --git a/ydb/core/tx/columnshard/data_sharing/protos/sessions.proto b/ydb/core/tx/columnshard/data_sharing/protos/sessions.proto index cd0397a9a21d..10c9a1903ed0 100644 --- a/ydb/core/tx/columnshard/data_sharing/protos/sessions.proto +++ b/ydb/core/tx/columnshard/data_sharing/protos/sessions.proto @@ -2,6 +2,7 @@ package NKikimrColumnShardDataSharingProto; import "ydb/core/tx/columnshard/common/protos/snapshot.proto"; import "ydb/core/tx/columnshard/data_sharing/protos/initiator.proto"; +import "ydb/core/protos/tx_columnshard.proto"; message TDestinationRemapIds { optional uint64 SourcePathId = 1; @@ -47,6 +48,8 @@ message TSourceSession { optional uint32 PackIdx = 5; optional uint32 AckReceivedForPackIdx = 6[default = 0]; repeated uint64 LinksModifiedTablets = 7; + optional uint32 NextSchemasIntervalBegin = 8; + optional uint32 NextSchemasIntervalEnd = 9; } message TPathPortionsHash { @@ -56,6 +59,7 @@ message TSourceSession { message TCursorStatic { repeated TPathPortionsHash PathHashes = 7; + repeated NKikimrTxColumnShard.TSchemaPresetVersionInfo SchemeHistory = 8; } } diff --git a/ydb/core/tx/columnshard/data_sharing/protos/transfer.proto b/ydb/core/tx/columnshard/data_sharing/protos/transfer.proto index 8d40ba1abfc1..20a9c3f40ae7 100644 --- a/ydb/core/tx/columnshard/data_sharing/protos/transfer.proto +++ b/ydb/core/tx/columnshard/data_sharing/protos/transfer.proto @@ -6,6 +6,7 @@ message TEvSendDataFromSource { optional NActorsProto.TActorId SourceActorId = 1; optional string SharingId = 2; repeated TPathIdData DataByPathId = 3; + repeated NKikimrTxColumnShard.TSchemaPresetVersionInfo SchemeHistory = 4; } message TEvAckDataToSource { diff --git a/ydb/core/tx/columnshard/data_sharing/protos/ya.make b/ydb/core/tx/columnshard/data_sharing/protos/ya.make index 3b50d7c2303c..445f5ca00f7a 100644 --- a/ydb/core/tx/columnshard/data_sharing/protos/ya.make +++ b/ydb/core/tx/columnshard/data_sharing/protos/ya.make @@ -9,11 +9,11 @@ SRCS( ) PEERDIR( + ydb/core/protos ydb/core/tx/columnshard/engines/protos ydb/core/tx/columnshard/common/protos ydb/library/actors/protos ydb/core/tx/columnshard/blobs_action/protos - ) END() diff --git a/ydb/core/tx/columnshard/data_sharing/source/session/cursor.cpp b/ydb/core/tx/columnshard/data_sharing/source/session/cursor.cpp index 5bc37cd29122..eed46498ed83 100644 --- a/ydb/core/tx/columnshard/data_sharing/source/session/cursor.cpp +++ b/ydb/core/tx/columnshard/data_sharing/source/session/cursor.cpp @@ -1,6 +1,9 @@ #include "source.h" -#include + +#include #include +#include + #include namespace NKikimr::NOlap::NDataSharing { @@ -15,16 +18,16 @@ void TSourceCursor::BuildSelection(const std::shared_ptr& stor ui32 chunksCount = 0; bool selectMore = true; for (; itCurrentPath != PortionsForSend.end() && selectMore; ++itCurrentPath) { - std::vector portions; + std::vector portions; for (; itPortion != itCurrentPath->second.end(); ++itPortion) { selectMore = (count < 10000 && chunksCount < 1000000); if (!selectMore) { NextPathId = itCurrentPath->first; NextPortionId = itPortion->first; } else { - portions.emplace_back(*itPortion->second); - chunksCount += portions.back().GetRecords().size(); - chunksCount += portions.back().GetIndexes().size(); + portions.emplace_back(itPortion->second); + chunksCount += portions.back().GetRecordsVerified().size(); + chunksCount += portions.back().GetIndexesVerified().size(); ++count; } } @@ -56,7 +59,31 @@ void TSourceCursor::BuildSelection(const std::shared_ptr& stor std::swap(Selected, result); } +bool TSourceCursor::NextSchemas() { + NextSchemasIntervalBegin = NextSchemasIntervalEnd; + + if (NextSchemasIntervalEnd == SchemeHistory.size()) { + return false; + } + + i32 columnsToSend = 0; + const i32 maxColumnsToSend = 10000; + + // limit the count of schemas to send based on their size in columns + // maxColumnsToSend is pretty random value, so I don't care if columnsToSend would be greater then this value + for (; NextSchemasIntervalEnd < SchemeHistory.size() && columnsToSend < maxColumnsToSend; ++NextSchemasIntervalEnd) { + columnsToSend += SchemeHistory[NextSchemasIntervalEnd].ColumnsSize(); + } + + ++PackIdx; + + return true; +} + bool TSourceCursor::Next(const std::shared_ptr& storagesManager, const TVersionedIndex& index) { + if (NextSchemas()) { + return true; + } PreviousSelected = std::move(Selected); LinksModifiedTablets.clear(); Selected.clear(); @@ -80,6 +107,8 @@ NKikimrColumnShardDataSharingProto::TSourceSession::TCursorDynamic TSourceCursor NKikimrColumnShardDataSharingProto::TSourceSession::TCursorDynamic result; result.SetStartPathId(StartPathId); result.SetStartPortionId(StartPortionId); + result.SetNextSchemasIntervalBegin(NextSchemasIntervalBegin); + result.SetNextSchemasIntervalEnd(NextSchemasIntervalEnd); if (NextPathId) { result.SetNextPathId(*NextPathId); } @@ -101,6 +130,10 @@ NKikimrColumnShardDataSharingProto::TSourceSession::TCursorStatic TSourceCursor: pathHash->SetPathId(i.first); pathHash->SetHash(i.second); } + + for (auto&& i : SchemeHistory) { + *result.AddSchemeHistory() = i.GetProto(); + } return result; } @@ -109,6 +142,8 @@ NKikimr::TConclusionStatus TSourceCursor::DeserializeFromProto(const NKikimrColu StartPathId = proto.GetStartPathId(); StartPortionId = proto.GetStartPortionId(); PackIdx = proto.GetPackIdx(); + NextSchemasIntervalBegin = proto.GetNextSchemasIntervalBegin(); + NextSchemasIntervalEnd = proto.GetNextSchemasIntervalEnd(); if (!PackIdx) { return TConclusionStatus::Fail("Incorrect proto cursor PackIdx value: " + proto.DebugString()); } @@ -129,28 +164,46 @@ NKikimr::TConclusionStatus TSourceCursor::DeserializeFromProto(const NKikimrColu for (auto&& i : protoStatic.GetPathHashes()) { PathPortionHashes.emplace(i.GetPathId(), i.GetHash()); } - AFL_VERIFY(PathPortionHashes.size()); - StaticSaved = true; + + for (auto&& i : protoStatic.GetSchemeHistory()) { + SchemeHistory.emplace_back(i); + } + if (PathPortionHashes.empty()) { + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("problem", "empty static cursor"); + } else { + IsStaticSaved = true; + } return TConclusionStatus::Success(); } TSourceCursor::TSourceCursor(const TTabletId selfTabletId, const std::set& pathIds, const TTransferContext transferContext) : SelfTabletId(selfTabletId) , TransferContext(transferContext) - , PathIds(pathIds) -{ + , PathIds(pathIds) { +} + +void TSourceCursor::SaveToDatabase(NIceDb::TNiceDb& db, const TString& sessionId) { + using SourceSessions = NKikimr::NColumnShard::Schema::SourceSessions; + db.Table().Key(sessionId).Update( + NIceDb::TUpdate(SerializeDynamicToProto().SerializeAsString())); + if (!IsStaticSaved) { + db.Table().Key(sessionId).Update( + NIceDb::TUpdate(SerializeStaticToProto().SerializeAsString())); + IsStaticSaved = true; + } } -bool TSourceCursor::Start(const std::shared_ptr& storagesManager, const THashMap>>& portions, const TVersionedIndex& index) { +bool TSourceCursor::Start(const std::shared_ptr& storagesManager, + THashMap>&& portions, std::vector&& schemeHistory, const TVersionedIndex& index) { + SchemeHistory = std::move(schemeHistory); AFL_VERIFY(!IsStartedFlag); - std::map>> local; - std::vector> portionsLock; + std::map> local; NArrow::NHash::NXX64::TStreamStringHashCalcer hashCalcer(0); for (auto&& i : portions) { hashCalcer.Start(); - std::map> portionsMap; + std::map portionsMap; for (auto&& p : i.second) { - const ui64 portionId = p->GetPortionId(); + const ui64 portionId = p.GetPortionInfo().GetPortionId(); hashCalcer.Update((ui8*)&portionId, sizeof(portionId)); AFL_VERIFY(portionsMap.emplace(portionId, p).second); } @@ -164,10 +217,17 @@ bool TSourceCursor::Start(const std::shared_ptr& storagesManag local.emplace(i.first, std::move(portionsMap)); } std::swap(PortionsForSend, local); - if (!StartPathId) { - AFL_VERIFY(PortionsForSend.size()); - AFL_VERIFY(PortionsForSend.begin()->second.size()); + if (PortionsForSend.empty()) { + AFL_VERIFY(!StartPortionId); + NextPathId = std::nullopt; + NextPortionId = std::nullopt; + // we don't need to send scheme history if we don't have data + // this also invalidates cursor in this case + SchemeHistory.clear(); + return true; + } else if (!StartPathId) { + AFL_VERIFY(PortionsForSend.begin()->second.size()); NextPathId = PortionsForSend.begin()->first; NextPortionId = PortionsForSend.begin()->second.begin()->first; AFL_VERIFY(Next(storagesManager, index)); @@ -177,5 +237,4 @@ bool TSourceCursor::Start(const std::shared_ptr& storagesManag IsStartedFlag = true; return true; } - -} \ No newline at end of file +} // namespace NKikimr::NOlap::NDataSharing diff --git a/ydb/core/tx/columnshard/data_sharing/source/session/cursor.h b/ydb/core/tx/columnshard/data_sharing/source/session/cursor.h index 3f4cdba86c15..44dcdc33c99f 100644 --- a/ydb/core/tx/columnshard/data_sharing/source/session/cursor.h +++ b/ydb/core/tx/columnshard/data_sharing/source/session/cursor.h @@ -1,11 +1,16 @@ #pragma once +#include #include #include -#include +#include namespace NKikimr::NOlap { class TColumnEngineForLogs; class TVersionedIndex; +} // namespace NKikimr::NOlap + +namespace NKikimr::NIceDb { +class TNiceDb; } namespace NKikimr::NOlap::NDataSharing { @@ -14,10 +19,11 @@ class TSharedBlobsManager; class TSourceCursor { private: - std::map>> PortionsForSend; + std::map> PortionsForSend; THashMap PreviousSelected; THashMap Selected; THashMap Links; + std::vector SchemeHistory; YDB_READONLY(ui64, StartPathId, 0); YDB_READONLY(ui64, StartPortionId, 0); YDB_READONLY(ui64, PackIdx, 0); @@ -25,13 +31,23 @@ class TSourceCursor { TTransferContext TransferContext; std::optional NextPathId = 0; std::optional NextPortionId = 0; + + // Begin/End of the next slice of SchemeHistory + ui64 NextSchemasIntervalBegin = 0; + ui64 NextSchemasIntervalEnd = 0; + THashSet LinksModifiedTablets; ui64 AckReceivedForPackIdx = 0; std::set PathIds; THashMap PathPortionHashes; bool IsStartedFlag = false; - YDB_ACCESSOR(bool, StaticSaved, false); + bool IsStaticSaved = false; void BuildSelection(const std::shared_ptr& storagesManager, const TVersionedIndex& index); + NKikimrColumnShardDataSharingProto::TSourceSession::TCursorDynamic SerializeDynamicToProto() const; + NKikimrColumnShardDataSharingProto::TSourceSession::TCursorStatic SerializeStaticToProto() const; + + bool NextSchemas(); + public: bool IsAckDataReceived() const { return AckReceivedForPackIdx == PackIdx; @@ -80,6 +96,10 @@ class TSourceCursor { return PreviousSelected; } + TArrayRef GetSelectedSchemas() const { + return TArrayRef(SchemeHistory.data() + NextSchemasIntervalBegin, NextSchemasIntervalEnd - NextSchemasIntervalBegin); + } + const THashMap& GetSelected() const { return Selected; } @@ -91,18 +111,18 @@ class TSourceCursor { bool Next(const std::shared_ptr& storagesManager, const TVersionedIndex& index); bool IsValid() { - return Selected.size(); + AFL_VERIFY(NextSchemasIntervalBegin <= SchemeHistory.size()); + return NextSchemasIntervalBegin < SchemeHistory.size() || Selected.size(); } TSourceCursor(const TTabletId selfTabletId, const std::set& pathIds, const TTransferContext transferContext); - bool Start(const std::shared_ptr& storagesManager, const THashMap>>& portions, const TVersionedIndex& index); - - NKikimrColumnShardDataSharingProto::TSourceSession::TCursorDynamic SerializeDynamicToProto() const; - NKikimrColumnShardDataSharingProto::TSourceSession::TCursorStatic SerializeStaticToProto() const; + void SaveToDatabase(class NIceDb::TNiceDb& db, const TString& sessionId); + bool Start(const std::shared_ptr& storagesManager, THashMap>&& portions, + std::vector&& schemeHistory, const TVersionedIndex& index); [[nodiscard]] TConclusionStatus DeserializeFromProto(const NKikimrColumnShardDataSharingProto::TSourceSession::TCursorDynamic& proto, const NKikimrColumnShardDataSharingProto::TSourceSession::TCursorStatic& protoStatic); }; -} \ No newline at end of file +} // namespace NKikimr::NOlap::NDataSharing diff --git a/ydb/core/tx/columnshard/data_sharing/source/session/source.cpp b/ydb/core/tx/columnshard/data_sharing/source/session/source.cpp index 7c3e244ade19..40228cb05dd3 100644 --- a/ydb/core/tx/columnshard/data_sharing/source/session/source.cpp +++ b/ydb/core/tx/columnshard/data_sharing/source/session/source.cpp @@ -1,13 +1,15 @@ #include "source.h" -#include + +#include #include +#include +#include #include -#include #include namespace NKikimr::NOlap::NDataSharing { -NKikimr::TConclusionStatus TSourceSession::DeserializeFromProto(const NKikimrColumnShardDataSharingProto::TSourceSession& proto, +NKikimr::TConclusionStatus TSourceSession::DeserializeFromProto(const NKikimrColumnShardDataSharingProto::TSourceSession& proto, const std::optional& protoCursor, const std::optional& protoCursorStatic) { auto parseBase = TBase::DeserializeFromProto(proto); @@ -68,13 +70,18 @@ TConclusion> TSourceSession:: } } +void TSourceSession::SaveCursorToDatabase(NIceDb::TNiceDb& db) { + GetCursorVerified()->SaveToDatabase(db, GetSessionId()); +} + void TSourceSession::ActualizeDestination(const NColumnShard::TColumnShard& shard, const std::shared_ptr& dataLocksManager) { AFL_VERIFY(IsInProgress() || IsPrepared()); AFL_VERIFY(Cursor); if (Cursor->IsValid()) { if (!Cursor->IsAckDataReceived()) { const THashMap& packPortions = Cursor->GetSelected(); - auto ev = std::make_unique(GetSessionId(), Cursor->GetPackIdx(), SelfTabletId, packPortions); + + auto ev = std::make_unique(GetSessionId(), Cursor->GetPackIdx(), SelfTabletId, packPortions, Cursor->GetSelectedSchemas()); NActors::TActivationContext::AsActorContext().Send(MakePipePerNodeCacheID(false), new TEvPipeCache::TEvForward(ev.release(), (ui64)DestinationTabletId, true), IEventHandle::FlagTrackDelivery, GetRuntimeId()); } @@ -97,14 +104,14 @@ void TSourceSession::ActualizeDestination(const NColumnShard::TColumnShard& shar } } -bool TSourceSession::DoStart(const NColumnShard::TColumnShard& shard, const THashMap>>& portions) { +void TSourceSession::StartCursor(const NColumnShard::TColumnShard& shard, THashMap>&& portions, std::vector&& schemeHistory) { AFL_VERIFY(Cursor); - if (Cursor->Start(shard.GetStoragesManager(), portions, shard.GetIndexAs().GetVersionedIndex())) { - ActualizeDestination(shard, shard.GetDataLocksManager()); - return true; - } else { - return false; - } + AFL_VERIFY(Cursor->Start(shard.GetStoragesManager(), std::move(portions), std::move(schemeHistory), shard.GetIndexAs().GetVersionedIndex())); + ActualizeDestination(shard, shard.GetDataLocksManager()); } -} \ No newline at end of file +TConclusionStatus TSourceSession::DoStart(NColumnShard::TColumnShard& shard, THashMap>&& portions) { + shard.Execute(new TTxStartSourceCursor(this, &shard, std::move(portions), "start_source_cursor")); + return TConclusionStatus::Success(); +} +} // namespace NKikimr::NOlap::NDataSharing diff --git a/ydb/core/tx/columnshard/data_sharing/source/session/source.h b/ydb/core/tx/columnshard/data_sharing/source/session/source.h index 903fc61c1783..926d137b9a37 100644 --- a/ydb/core/tx/columnshard/data_sharing/source/session/source.h +++ b/ydb/core/tx/columnshard/data_sharing/source/session/source.h @@ -1,7 +1,13 @@ #pragma once #include "cursor.h" -#include + #include +#include +#include + +namespace NKikimr::NIceDb { +class TNiceDb; +} namespace NKikimr::NOlap::NDataSharing { @@ -14,8 +20,9 @@ class TSourceSession: public TCommonSession { std::shared_ptr Cursor; YDB_READONLY_DEF(std::set, PathIds); TTabletId DestinationTabletId = TTabletId(0); + protected: - virtual bool DoStart(const NColumnShard::TColumnShard& shard, const THashMap>>& portions) override; + virtual TConclusionStatus DoStart(NColumnShard::TColumnShard& shard, THashMap>&& portions) override; virtual THashSet GetPathIdsForStart() const override { THashSet result; for (auto&& i : PathIds) { @@ -23,20 +30,18 @@ class TSourceSession: public TCommonSession { } return result; } + public: TSourceSession(const TTabletId selfTabletId) : TBase("source_proto") - , SelfTabletId(selfTabletId) - { - + , SelfTabletId(selfTabletId) { } TSourceSession(const TString& sessionId, const TTransferContext& transfer, const TTabletId selfTabletId, const std::set& pathIds, const TTabletId destTabletId) : TBase(sessionId, "source_base", transfer) , SelfTabletId(selfTabletId) , PathIds(pathIds) - , DestinationTabletId(destTabletId) - { + , DestinationTabletId(destTabletId) { } TTabletId GetDestinationTabletId() const { @@ -48,26 +53,20 @@ class TSourceSession: public TCommonSession { } bool IsEqualTo(const TSourceSession& item) const { - return - TBase::IsEqualTo(item) && - DestinationTabletId == item.DestinationTabletId && - PathIds == item.PathIds; + return TBase::IsEqualTo(item) && + DestinationTabletId == item.DestinationTabletId && + PathIds == item.PathIds; } std::shared_ptr GetCursorVerified() const { AFL_VERIFY(!!Cursor); return Cursor; } -/* - bool TryNextCursor(const ui32 packIdx, const std::shared_ptr& storagesManager, const TVersionedIndex& index) { - AFL_VERIFY(Cursor); - if (packIdx != Cursor->GetPackIdx()) { - return false; - } - Cursor->Next(storagesManager, index); - return true; - } -*/ + + void SaveCursorToDatabase(NIceDb::TNiceDb& db); + + void StartCursor(const NColumnShard::TColumnShard& shard, THashMap>&& portions, std::vector&& schemeHistory); + [[nodiscard]] TConclusion> AckFinished(NColumnShard::TColumnShard* self, const std::shared_ptr& selfPtr); [[nodiscard]] TConclusion> AckData(NColumnShard::TColumnShard* self, const ui32 receivedPackIdx, const std::shared_ptr& selfPtr); [[nodiscard]] TConclusion> AckLinks(NColumnShard::TColumnShard* self, const TTabletId tabletId, const ui32 packIdx, const std::shared_ptr& selfPtr); @@ -85,7 +84,7 @@ class TSourceSession: public TCommonSession { } [[nodiscard]] TConclusionStatus DeserializeFromProto(const NKikimrColumnShardDataSharingProto::TSourceSession& proto, - const std::optional& protoCursor, + const std::optional& protoCursor, const std::optional& protoCursorStatic); }; -} \ No newline at end of file +} // namespace NKikimr::NOlap::NDataSharing diff --git a/ydb/core/tx/columnshard/data_sharing/source/transactions/tx_data_ack_to_source.cpp b/ydb/core/tx/columnshard/data_sharing/source/transactions/tx_data_ack_to_source.cpp index 5a9bb1cf1274..438271ef5f7c 100644 --- a/ydb/core/tx/columnshard/data_sharing/source/transactions/tx_data_ack_to_source.cpp +++ b/ydb/core/tx/columnshard/data_sharing/source/transactions/tx_data_ack_to_source.cpp @@ -1,5 +1,7 @@ #include "tx_data_ack_to_source.h" + #include +#include namespace NKikimr::NOlap::NDataSharing { @@ -22,19 +24,15 @@ bool TTxDataAckToSource::DoExecute(NTabletFlatExecutor::TTransactionContext& txc } NIceDb::TNiceDb db(txc.DB); - db.Table().Key(Session->GetSessionId()) - .Update(NIceDb::TUpdate(Session->GetCursorVerified()->SerializeDynamicToProto().SerializeAsString())); - if (!Session->GetCursorVerified()->GetStaticSaved()) { - db.Table().Key(Session->GetSessionId()) - .Update(NIceDb::TUpdate(Session->GetCursorVerified()->SerializeStaticToProto().SerializeAsString())); - Session->GetCursorVerified()->SetStaticSaved(true); - } + Session->SaveCursorToDatabase(db); std::swap(SharedBlobIds, sharedTabletBlobIds); return true; } void TTxDataAckToSource::DoComplete(const TActorContext& /*ctx*/) { + AFL_NOTICE(NKikimrServices::TX_COLUMNSHARD)("TTxDataAckToSource::DoComplete", "1"); + Session->ActualizeDestination(*Self, Self->GetDataLocksManager()); } -} \ No newline at end of file +} // namespace NKikimr::NOlap::NDataSharing diff --git a/ydb/core/tx/columnshard/data_sharing/source/transactions/tx_start_source_cursor.cpp b/ydb/core/tx/columnshard/data_sharing/source/transactions/tx_start_source_cursor.cpp new file mode 100644 index 000000000000..e88d270b3a63 --- /dev/null +++ b/ydb/core/tx/columnshard/data_sharing/source/transactions/tx_start_source_cursor.cpp @@ -0,0 +1,40 @@ +#include "tx_start_source_cursor.h" + +#include +#include + +namespace NKikimr::NOlap::NDataSharing { + +bool TTxStartSourceCursor::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + using namespace NColumnShard; + + std::vector schemeHistory; + + NIceDb::TNiceDb db(txc.DB); + + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return false; + } + + while (!rowset.EndOfSet()) { + TSchemaPreset::TSchemaPresetVersionInfo info; + Y_ABORT_UNLESS(info.ParseFromString(rowset.GetValue())); + + schemeHistory.push_back(info); + + if (!rowset.Next()) { + return false; + } + } + + std::sort(schemeHistory.begin(), schemeHistory.end()); + + Session->StartCursor(*Self, std::move(Portions), std::move(schemeHistory)); + return true; +} + +void TTxStartSourceCursor::DoComplete(const TActorContext& /*ctx*/) { +} + +} // namespace NKikimr::NOlap::NDataSharing diff --git a/ydb/core/tx/columnshard/data_sharing/source/transactions/tx_start_source_cursor.h b/ydb/core/tx/columnshard/data_sharing/source/transactions/tx_start_source_cursor.h new file mode 100644 index 000000000000..b558ab4eabd4 --- /dev/null +++ b/ydb/core/tx/columnshard/data_sharing/source/transactions/tx_start_source_cursor.h @@ -0,0 +1,31 @@ +#pragma once +#include +#include +#include + +namespace NKikimr::NOlap::NDataSharing { + +class TTxStartSourceCursor: public TExtendedTransactionBase { +private: + using TBase = TExtendedTransactionBase; + + TSourceSession* Session; + THashMap> Portions; + +protected: + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& ctx) override; + virtual void DoComplete(const TActorContext& ctx) override; + +public: + TTxStartSourceCursor(TSourceSession* session, NColumnShard::TColumnShard* self, THashMap>&& portions, const TString& info) + : TBase(self, info) + , Session(session) + , Portions(std::move(portions)) { + } + + TTxType GetTxType() const override { + return NColumnShard::TXTYPE_DATA_SHARING_START_SOURCE_CURSOR; + } +}; + +} // namespace NKikimr::NOlap::NDataSharing diff --git a/ydb/core/tx/columnshard/data_sharing/source/transactions/tx_write_source_cursor.cpp b/ydb/core/tx/columnshard/data_sharing/source/transactions/tx_write_source_cursor.cpp index 4af96622de2b..a4c67ed121cf 100644 --- a/ydb/core/tx/columnshard/data_sharing/source/transactions/tx_write_source_cursor.cpp +++ b/ydb/core/tx/columnshard/data_sharing/source/transactions/tx_write_source_cursor.cpp @@ -6,8 +6,7 @@ namespace NKikimr::NOlap::NDataSharing { bool TTxWriteSourceCursor::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { using namespace NColumnShard; NIceDb::TNiceDb db(txc.DB); - db.Table().Key(Session->GetSessionId()) - .Update(NIceDb::TUpdate(Session->GetCursorVerified()->SerializeDynamicToProto().SerializeAsString())); + Session->SaveCursorToDatabase(db); return true; } diff --git a/ydb/core/tx/columnshard/data_sharing/source/transactions/ya.make b/ydb/core/tx/columnshard/data_sharing/source/transactions/ya.make index 90269b952ed4..824427fed1a8 100644 --- a/ydb/core/tx/columnshard/data_sharing/source/transactions/ya.make +++ b/ydb/core/tx/columnshard/data_sharing/source/transactions/ya.make @@ -5,6 +5,7 @@ SRCS( tx_data_ack_to_source.cpp tx_finish_ack_to_source.cpp tx_write_source_cursor.cpp + tx_start_source_cursor.cpp ) PEERDIR( diff --git a/ydb/core/tx/columnshard/engines/changes/abstract/abstract.cpp b/ydb/core/tx/columnshard/engines/changes/abstract/abstract.cpp index 7d37981a9039..3effba1d665c 100644 --- a/ydb/core/tx/columnshard/engines/changes/abstract/abstract.cpp +++ b/ydb/core/tx/columnshard/engines/changes/abstract/abstract.cpp @@ -114,11 +114,11 @@ void TColumnEngineChanges::OnFinish(NColumnShard::TColumnShard& self, TChangesFi DoOnFinish(self, context); } -TWriteIndexContext::TWriteIndexContext(NTable::TDatabase* db, IDbWrapper& dbWrapper, TColumnEngineForLogs& engineLogs) +TWriteIndexContext::TWriteIndexContext(NTable::TDatabase* db, IDbWrapper& dbWrapper, TColumnEngineForLogs& engineLogs, const TSnapshot& snapshot) : DB(db) , DBWrapper(dbWrapper) , EngineLogs(engineLogs) -{ + , Snapshot(snapshot) { } diff --git a/ydb/core/tx/columnshard/engines/changes/abstract/abstract.h b/ydb/core/tx/columnshard/engines/changes/abstract/abstract.h index 721270ea63f3..515ccd82b06e 100644 --- a/ydb/core/tx/columnshard/engines/changes/abstract/abstract.h +++ b/ydb/core/tx/columnshard/engines/changes/abstract/abstract.h @@ -130,7 +130,8 @@ class TWriteIndexContext: TNonCopyable { NTable::TDatabase* DB; IDbWrapper& DBWrapper; TColumnEngineForLogs& EngineLogs; - TWriteIndexContext(NTable::TDatabase* db, IDbWrapper& dbWrapper, TColumnEngineForLogs& engineLogs); + const TSnapshot Snapshot; + TWriteIndexContext(NTable::TDatabase* db, IDbWrapper& dbWrapper, TColumnEngineForLogs& engineLogs, const TSnapshot& snapshot); }; class TChangesFinishContext { @@ -155,13 +156,15 @@ class TWriteIndexCompleteContext: TNonCopyable, public TChangesFinishContext { const ui64 BytesWritten; const TDuration Duration; TColumnEngineForLogs& EngineLogs; - TWriteIndexCompleteContext(const TActorContext& actorContext, const ui32 blobsWritten, const ui64 bytesWritten - , const TDuration d, TColumnEngineForLogs& engineLogs) + const TSnapshot Snapshot; + TWriteIndexCompleteContext(const TActorContext& actorContext, const ui32 blobsWritten, const ui64 bytesWritten, const TDuration d, + TColumnEngineForLogs& engineLogs, const TSnapshot& snapshot) : ActorContext(actorContext) , BlobsWritten(blobsWritten) , BytesWritten(bytesWritten) , Duration(d) , EngineLogs(engineLogs) + , Snapshot(snapshot) { } @@ -184,6 +187,17 @@ class TConstructionContext: TNonCopyable { class TGranuleMeta; +class TDataAccessorsInitializationContext { +private: + YDB_READONLY_DEF(std::shared_ptr, VersionedIndex); + +public: + TDataAccessorsInitializationContext(const std::shared_ptr& versionedIndex) + : VersionedIndex(versionedIndex) { + AFL_VERIFY(VersionedIndex); + } +}; + class TColumnEngineChanges { public: enum class EStage: ui32 { @@ -199,8 +213,12 @@ class TColumnEngineChanges { EStage Stage = EStage::Created; std::shared_ptr LockGuard; TString AbortedReason; + const TString TaskIdentifier = TGUID::CreateTimebased().AsGuidString(); + std::shared_ptr ActivityFlag; protected: + std::optional FetchedDataAccessors; + virtual NDataLocks::ELockCategory GetLockCategory() const = 0; virtual void DoDebugString(TStringOutput& out) const = 0; virtual void DoCompile(TFinalizationContext& context) = 0; virtual void DoOnAfterCompile() {} @@ -210,7 +228,7 @@ class TColumnEngineChanges { virtual bool NeedConstruction() const { return true; } - virtual void DoStart(NColumnShard::TColumnShard& self) = 0; + virtual void DoStart(NColumnShard::TColumnShard& context) = 0; virtual TConclusionStatus DoConstructBlobs(TConstructionContext& context) noexcept = 0; virtual void OnAbortEmergency() { } @@ -219,17 +237,53 @@ class TColumnEngineChanges { virtual NColumnShard::ECumulativeCounters GetCounterIndex(const bool isSuccess) const = 0; - const TString TaskIdentifier = TGUID::Create().AsGuidString(); virtual ui64 DoCalcMemoryForUsage() const = 0; virtual std::shared_ptr DoBuildDataLock() const = 0; std::shared_ptr BuildDataLock() const { return DoBuildDataLock(); } + std::shared_ptr PortionsToAccess = std::make_shared(TaskIdentifier); + virtual void OnDataAccessorsInitialized(const TDataAccessorsInitializationContext& context) = 0; + public: + bool IsActive() const { + return !ActivityFlag || ActivityFlag->Val(); + } + + void SetActivityFlag(const std::shared_ptr& flag) { + AFL_VERIFY(!ActivityFlag); + ActivityFlag = flag; + } + + std::shared_ptr ExtractDataAccessorsRequest() const { + AFL_VERIFY(!!PortionsToAccess); + return std::move(PortionsToAccess); + } + + const TPortionDataAccessor& GetPortionDataAccessor(const ui64 portionId) const { + AFL_VERIFY(FetchedDataAccessors); + return FetchedDataAccessors->GetPortionAccessorVerified(portionId); + } + + std::vector GetPortionDataAccessors(const std::vector& portions) const { + AFL_VERIFY(FetchedDataAccessors); + std::vector result; + for (auto&& i : portions) { + result.emplace_back(GetPortionDataAccessor(i->GetPortionId())); + } + return result; + } + + void SetFetchedDataAccessors(TDataAccessorsResult&& result, const TDataAccessorsInitializationContext& context) { + AFL_VERIFY(!FetchedDataAccessors); + FetchedDataAccessors = std::move(result); + OnDataAccessorsInitialized(context); + } + class IMemoryPredictor { public: - virtual ui64 AddPortion(const TPortionInfo& portionInfo) = 0; + virtual ui64 AddPortion(const TPortionInfo::TConstPtr& portionInfo) = 0; virtual ~IMemoryPredictor() = default; }; @@ -288,7 +342,7 @@ class TColumnEngineChanges { std::vector> GetReadingActions() const { auto result = BlobsAction.GetReadingActions(); - Y_ABORT_UNLESS(result.size()); +// Y_ABORT_UNLESS(result.size()); return result; } virtual TString TypeString() const = 0; diff --git a/ydb/core/tx/columnshard/engines/changes/abstract/compaction_info.cpp b/ydb/core/tx/columnshard/engines/changes/abstract/compaction_info.cpp index e3e2a73955d6..334fab3e9887 100644 --- a/ydb/core/tx/columnshard/engines/changes/abstract/compaction_info.cpp +++ b/ydb/core/tx/columnshard/engines/changes/abstract/compaction_info.cpp @@ -1,5 +1,16 @@ #include "compaction_info.h" +#include + namespace NKikimr::NOlap { +bool TPlanCompactionInfo::Finish() { + if (Count > 0) { + return --Count == 0; + } else { + AFL_VERIFY(false); + return false; + } } + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/changes/abstract/compaction_info.h b/ydb/core/tx/columnshard/engines/changes/abstract/compaction_info.h index 0e1ee6b72325..1020f2580ad8 100644 --- a/ydb/core/tx/columnshard/engines/changes/abstract/compaction_info.h +++ b/ydb/core/tx/columnshard/engines/changes/abstract/compaction_info.h @@ -11,8 +11,17 @@ class TGranuleMeta; class TPlanCompactionInfo { private: ui64 PathId = 0; - const TMonotonic StartTime = TMonotonic::Now(); + TMonotonic StartTime = TMonotonic::Now(); + ui32 Count = 0; + public: + void Start() { + StartTime = TMonotonic::Now(); + ++Count; + } + + bool Finish(); + TMonotonic GetStartTime() const { return StartTime; } diff --git a/ydb/core/tx/columnshard/engines/changes/actualization/construction/context.cpp b/ydb/core/tx/columnshard/engines/changes/actualization/construction/context.cpp index 97dbee070534..b5f1aadfb680 100644 --- a/ydb/core/tx/columnshard/engines/changes/actualization/construction/context.cpp +++ b/ydb/core/tx/columnshard/engines/changes/actualization/construction/context.cpp @@ -6,8 +6,10 @@ namespace NKikimr::NOlap::NActualizer { TTieringProcessContext::TTieringProcessContext(const ui64 memoryUsageLimit, const TSaverContext& saverContext, - const std::shared_ptr& dataLocksManager, const NColumnShard::TEngineLogsCounters& counters, const std::shared_ptr& controller) - : MemoryUsageLimit(memoryUsageLimit) + const std::shared_ptr& dataLocksManager, const TVersionedIndex& versionedIndex, + const NColumnShard::TEngineLogsCounters& counters, const std::shared_ptr& controller) + : VersionedIndex(versionedIndex) + , MemoryUsageLimit(memoryUsageLimit) , SaverContext(saverContext) , Counters(counters) , Controller(controller) @@ -17,11 +19,12 @@ TTieringProcessContext::TTieringProcessContext(const ui64 memoryUsageLimit, cons } -bool TTieringProcessContext::AddPortion(const TPortionInfo& info, TPortionEvictionFeatures&& features, const std::optional dWait) { - if (!UsedPortions.emplace(info.GetAddress()).second) { +bool TTieringProcessContext::AddPortion( + const std::shared_ptr& info, TPortionEvictionFeatures&& features, const std::optional dWait) { + if (!UsedPortions.emplace(info->GetAddress()).second) { return true; } - if (DataLocksManager->IsLocked(info)) { + if (DataLocksManager->IsLocked(*info, NDataLocks::ELockCategory::Actualization)) { return true; } @@ -30,10 +33,10 @@ bool TTieringProcessContext::AddPortion(const TPortionInfo& info, TPortionEvicti }; auto it = Tasks.find(features.GetRWAddress()); if (it == Tasks.end()) { - std::vector tasks = {buildNewTask()}; + std::vector tasks = { buildNewTask() }; it = Tasks.emplace(features.GetRWAddress(), std::move(tasks)).first; } - if (it->second.back().GetTxWriteVolume() + info.GetTxVolume() > TGlobalLimits::TxWriteLimitBytes / 2 && it->second.back().GetTxWriteVolume()) { + if (!it->second.back().CanTakePortionInTx(info, VersionedIndex)) { if (Controller->IsNewTaskAvailable(it->first, it->second.size())) { it->second.emplace_back(buildNewTask()); } else { @@ -52,17 +55,17 @@ bool TTieringProcessContext::AddPortion(const TPortionInfo& info, TPortionEvicti } it->second.back().MutableMemoryUsage() = it->second.back().GetMemoryPredictor()->AddPortion(info); } - it->second.back().MutableTxWriteVolume() += info.GetTxVolume(); + it->second.back().TakePortionInTx(info, VersionedIndex); if (features.GetTargetTierName() == NTiering::NCommon::DeleteTierName) { AFL_VERIFY(dWait); - Counters.OnPortionToDrop(info.GetTotalBlobBytes(), *dWait); + Counters.OnPortionToDrop(info->GetTotalBlobBytes(), *dWait); it->second.back().GetTask()->AddPortionToRemove(info); AFL_VERIFY(!it->second.back().GetTask()->GetPortionsToEvictCount())("rw", features.GetRWAddress().DebugString())("f", it->first.DebugString()); } else { if (!dWait) { AFL_VERIFY(features.GetCurrentScheme()->GetVersion() < features.GetTargetScheme()->GetVersion()); } else { - Counters.OnPortionToEvict(info.GetTotalBlobBytes(), *dWait); + Counters.OnPortionToEvict(info->GetTotalBlobBytes(), *dWait); } it->second.back().GetTask()->AddPortionToEvict(info, std::move(features)); AFL_VERIFY(!it->second.back().GetTask()->HasPortionsToRemove())("rw", features.GetRWAddress().DebugString())("f", it->first.DebugString()); diff --git a/ydb/core/tx/columnshard/engines/changes/actualization/construction/context.h b/ydb/core/tx/columnshard/engines/changes/actualization/construction/context.h index b670b8fe25b0..99dedc4c106a 100644 --- a/ydb/core/tx/columnshard/engines/changes/actualization/construction/context.h +++ b/ydb/core/tx/columnshard/engines/changes/actualization/construction/context.h @@ -15,17 +15,34 @@ class TTaskConstructor { YDB_READONLY_DEF(std::shared_ptr, MemoryPredictor); YDB_READONLY_DEF(std::shared_ptr, Task); YDB_ACCESSOR(ui64, MemoryUsage, 0); - YDB_ACCESSOR(ui64, TxWriteVolume, 0); + YDB_READONLY(ui64, PortionsCount, 0); + YDB_READONLY(ui64, ChunksCount, 0); + public: TTaskConstructor(const std::shared_ptr& predictor, const std::shared_ptr& task) : MemoryPredictor(predictor) , Task(task) { } + + bool CanTakePortionInTx(const TPortionInfo::TConstPtr& portion, const TVersionedIndex& index) { + if (!PortionsCount) { + return true; + } + return + (PortionsCount + 1 < 1000) && + (ChunksCount + portion->GetApproxChunksCount(portion->GetSchema(index)->GetColumnsCount()) < 100000); + } + + void TakePortionInTx(const TPortionInfo::TConstPtr& portion, const TVersionedIndex& index) { + ++PortionsCount; + ChunksCount += portion->GetApproxChunksCount(portion->GetSchema(index)->GetColumnsCount()); + } }; class TTieringProcessContext { private: + const TVersionedIndex& VersionedIndex; THashSet UsedPortions; const ui64 MemoryUsageLimit; TSaverContext SaverContext; @@ -52,7 +69,17 @@ class TTieringProcessContext { return Tasks; } - bool AddPortion(const TPortionInfo& info, TPortionEvictionFeatures&& features, const std::optional dWait); + TString DebugString() const { + TStringBuilder result; + result << "{"; + for (auto&& i : Tasks) { + result << i.first.DebugString() << ":" << i.second.size() << ";"; + } + result << "}"; + return result; + } + + bool AddPortion(const std::shared_ptr& info, TPortionEvictionFeatures&& features, const std::optional dWait); bool IsRWAddressAvailable(const TRWAddress& address) const { auto it = Tasks.find(address); @@ -63,7 +90,8 @@ class TTieringProcessContext { } } - TTieringProcessContext(const ui64 memoryUsageLimit, const TSaverContext& saverContext, const std::shared_ptr& dataLocksManager, + TTieringProcessContext(const ui64 memoryUsageLimit, const TSaverContext& saverContext, + const std::shared_ptr& dataLocksManager, const TVersionedIndex& versionedIndex, const NColumnShard::TEngineLogsCounters& counters, const std::shared_ptr& controller); }; diff --git a/ydb/core/tx/columnshard/engines/changes/actualization/controller/controller.h b/ydb/core/tx/columnshard/engines/changes/actualization/controller/controller.h index bae5930bec6a..e5b81be7675a 100644 --- a/ydb/core/tx/columnshard/engines/changes/actualization/controller/controller.h +++ b/ydb/core/tx/columnshard/engines/changes/actualization/controller/controller.h @@ -11,10 +11,14 @@ class TController { public: void StartActualization(const NActualizer::TRWAddress& address) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_ACTUALIZATION)("event", "actualization_start")("count", ActualizationsInProgress[address])( + "limit", GetLimitForAddress(address))("rw", address.DebugString()); AFL_VERIFY(++ActualizationsInProgress[address] <= (i32)GetLimitForAddress(address)); } void FinishActualization(const NActualizer::TRWAddress& address) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_ACTUALIZATION)("event", "actualization_finished")("count", ActualizationsInProgress[address])( + "limit", GetLimitForAddress(address))("rw", address.DebugString()); AFL_VERIFY(--ActualizationsInProgress[address] >= 0); } diff --git a/ydb/core/tx/columnshard/engines/changes/cleanup_portions.cpp b/ydb/core/tx/columnshard/engines/changes/cleanup_portions.cpp index 7917b77682b9..f094c0af7f06 100644 --- a/ydb/core/tx/columnshard/engines/changes/cleanup_portions.cpp +++ b/ydb/core/tx/columnshard/engines/changes/cleanup_portions.cpp @@ -1,8 +1,10 @@ #include "cleanup_portions.h" -#include -#include + #include +#include #include +#include +#include namespace NKikimr::NOlap { @@ -10,7 +12,7 @@ void TCleanupPortionsColumnEngineChanges::DoDebugString(TStringOutput& out) cons if (ui32 dropped = PortionsToDrop.size()) { out << "drop " << dropped << " portions"; for (auto& portionInfo : PortionsToDrop) { - out << portionInfo.DebugString(); + out << portionInfo->DebugString(); } } } @@ -20,11 +22,24 @@ void TCleanupPortionsColumnEngineChanges::DoWriteIndexOnExecute(NColumnShard::TC if (!self) { return; } + THashSet usedPortionIds; + auto schemaPtr = context.EngineLogs.GetVersionedIndex().GetLastSchema(); + for (auto&& i : PortionsToRemove) { + Y_ABORT_UNLESS(!i->HasRemoveSnapshot()); + AFL_VERIFY(usedPortionIds.emplace(i->GetPortionId()).second)("portion_info", i->DebugString(true)); + const auto pred = [&](TPortionInfo& portionCopy) { + portionCopy.SetRemoveSnapshot(context.Snapshot); + }; + context.EngineLogs.GetGranuleVerified(i->GetPathId()) + .ModifyPortionOnExecute( + context.DBWrapper, GetPortionDataAccessor(i->GetPortionId()), pred, schemaPtr->GetIndexInfo().GetPKFirstColumnId()); + } + THashMap> blobIdsByStorage; - for (auto&& p : PortionsToDrop) { + for (auto&& [_, p] : FetchedDataAccessors->GetPortions()) { p.RemoveFromDatabase(context.DBWrapper); p.FillBlobIdsByStorage(blobIdsByStorage, context.EngineLogs.GetVersionedIndex()); - pathIds.emplace(p.GetPathId()); + pathIds.emplace(p.GetPortionInfo().GetPathId()); } for (auto&& i : blobIdsByStorage) { auto action = BlobsAction.GetRemoving(i.first); @@ -35,15 +50,35 @@ void TCleanupPortionsColumnEngineChanges::DoWriteIndexOnExecute(NColumnShard::TC } void TCleanupPortionsColumnEngineChanges::DoWriteIndexOnComplete(NColumnShard::TColumnShard* self, TWriteIndexCompleteContext& context) { + { + auto g = context.EngineLogs.GranulesStorage->GetStats()->StartPackModification(); + for (auto&& i : PortionsToRemove) { + Y_ABORT_UNLESS(!i->HasRemoveSnapshot()); + const auto pred = [&](const std::shared_ptr& portion) { + portion->SetRemoveSnapshot(context.Snapshot); + }; + context.EngineLogs.ModifyPortionOnComplete(i, pred); + context.EngineLogs.AddCleanupPortion(i); + } + } for (auto& portionInfo : PortionsToDrop) { - if (!context.EngineLogs.ErasePortion(portionInfo)) { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "Cannot erase portion")("portion", portionInfo.DebugString()); + if (!context.EngineLogs.ErasePortion(*portionInfo)) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "Cannot erase portion")("portion", portionInfo->DebugString()); } } if (self) { + self->Counters.GetTabletCounters()->IncCounter(NColumnShard::COUNTER_PORTIONS_DEACTIVATED, PortionsToRemove.size()); + for (auto& portionInfo : PortionsToRemove) { + self->Counters.GetTabletCounters()->IncCounter(NColumnShard::COUNTER_BLOBS_DEACTIVATED, portionInfo->GetBlobIdsCount()); + for (auto& blobId : portionInfo->GetBlobIds()) { + self->Counters.GetTabletCounters()->IncCounter(NColumnShard::COUNTER_BYTES_DEACTIVATED, blobId.BlobSize()); + } + self->Counters.GetTabletCounters()->IncCounter(NColumnShard::COUNTER_RAW_BYTES_DEACTIVATED, portionInfo->GetTotalRawBytes()); + } + self->Counters.GetTabletCounters()->IncCounter(NColumnShard::COUNTER_PORTIONS_ERASED, PortionsToDrop.size()); for (auto&& p : PortionsToDrop) { - self->Counters.GetTabletCounters()->OnDropPortionEvent(p.GetTotalRawBytes(), p.GetTotalBlobBytes(), p.NumRows()); + self->Counters.GetTabletCounters()->OnDropPortionEvent(p->GetTotalRawBytes(), p->GetTotalBlobBytes(), p->GetRecordsCount()); } } } @@ -60,4 +95,4 @@ NColumnShard::ECumulativeCounters TCleanupPortionsColumnEngineChanges::GetCounte return isSuccess ? NColumnShard::COUNTER_CLEANUP_SUCCESS : NColumnShard::COUNTER_CLEANUP_FAIL; } -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/changes/cleanup_portions.h b/ydb/core/tx/columnshard/engines/changes/cleanup_portions.h index a77d172be9e9..e93f1916ff61 100644 --- a/ydb/core/tx/columnshard/engines/changes/cleanup_portions.h +++ b/ydb/core/tx/columnshard/engines/changes/cleanup_portions.h @@ -3,12 +3,19 @@ namespace NKikimr::NOlap { -class TCleanupPortionsColumnEngineChanges: public TColumnEngineChanges { +class TCleanupPortionsColumnEngineChanges: public TColumnEngineChanges, + public NColumnShard::TMonitoringObjectsCounter { private: using TBase = TColumnEngineChanges; - THashMap> BlobsToForget; THashMap>> StoragePortions; + std::vector PortionsToDrop; + std::vector PortionsToRemove; + THashSet TablesToDrop; + protected: + virtual void OnDataAccessorsInitialized(const TDataAccessorsInitializationContext& /*context*/) override { + } + virtual void DoWriteIndexOnComplete(NColumnShard::TColumnShard* self, TWriteIndexCompleteContext& context) override; virtual void DoWriteIndexOnExecute(NColumnShard::TColumnShard* self, TWriteIndexContext& context) override; @@ -27,8 +34,17 @@ class TCleanupPortionsColumnEngineChanges: public TColumnEngineChanges { virtual ui64 DoCalcMemoryForUsage() const override { return 0; } + virtual NDataLocks::ELockCategory GetLockCategory() const override { + return NDataLocks::ELockCategory::Cleanup; + } virtual std::shared_ptr DoBuildDataLock() const override { - return std::make_shared(TypeString() + "::" + GetTaskIdentifier(), PortionsToDrop); + auto portionsDropLock = std::make_shared( + TypeString() + "::PORTIONS_DROP::" + GetTaskIdentifier(), PortionsToDrop, NDataLocks::ELockCategory::Cleanup); + auto portionsRemoveLock = std::make_shared( + TypeString() + "::PORTIONS_REMOVE::" + GetTaskIdentifier(), PortionsToRemove, NDataLocks::ELockCategory::Compaction); + auto tablesLock = std::make_shared( + TypeString() + "::TABLES::" + GetTaskIdentifier(), TablesToDrop, NDataLocks::ELockCategory::Tables); + return NDataLocks::TCompositeLock::Build(TypeString() + "::COMPOSITE::" + GetTaskIdentifier(), {portionsDropLock, portionsRemoveLock, tablesLock}); } public: @@ -37,7 +53,23 @@ class TCleanupPortionsColumnEngineChanges: public TColumnEngineChanges { } - std::vector PortionsToDrop; + void AddTableToDrop(const ui64 pathId) { + TablesToDrop.emplace(pathId); + } + + const std::vector& GetPortionsToDrop() const { + return PortionsToDrop; + } + + void AddPortionToDrop(const TPortionInfo::TConstPtr& portion) { + PortionsToDrop.emplace_back(portion); + PortionsToAccess->AddPortion(portion); + } + + void AddPortionToRemove(const TPortionInfo::TConstPtr& portion) { + PortionsToRemove.emplace_back(portion); + PortionsToAccess->AddPortion(portion); + } virtual ui32 GetWritePortionsCount() const override { return 0; diff --git a/ydb/core/tx/columnshard/engines/changes/cleanup_tables.h b/ydb/core/tx/columnshard/engines/changes/cleanup_tables.h index 33c7fe34cb1d..69222eb04bbc 100644 --- a/ydb/core/tx/columnshard/engines/changes/cleanup_tables.h +++ b/ydb/core/tx/columnshard/engines/changes/cleanup_tables.h @@ -3,13 +3,18 @@ namespace NKikimr::NOlap { -class TCleanupTablesColumnEngineChanges: public TColumnEngineChanges { +class TCleanupTablesColumnEngineChanges: public TColumnEngineChanges, + public NColumnShard::TMonitoringObjectsCounter { private: using TBase = TColumnEngineChanges; protected: virtual void DoWriteIndexOnComplete(NColumnShard::TColumnShard* self, TWriteIndexCompleteContext& context) override; virtual void DoWriteIndexOnExecute(NColumnShard::TColumnShard* self, TWriteIndexContext& context) override; + virtual void OnDataAccessorsInitialized(const TDataAccessorsInitializationContext& /*context*/) override { + + } + virtual void DoStart(NColumnShard::TColumnShard& self) override; virtual void DoOnFinish(NColumnShard::TColumnShard& self, TChangesFinishContext& context) override; virtual void DoDebugString(TStringOutput& out) const override; @@ -25,8 +30,11 @@ class TCleanupTablesColumnEngineChanges: public TColumnEngineChanges { virtual ui64 DoCalcMemoryForUsage() const override { return 0; } + virtual NDataLocks::ELockCategory GetLockCategory() const override { + return NDataLocks::ELockCategory::Tables; + } virtual std::shared_ptr DoBuildDataLock() const override { - return std::make_shared(TypeString() + "::" + GetTaskIdentifier(), TablesToDrop); + return std::make_shared(TypeString() + "::" + GetTaskIdentifier(), TablesToDrop, GetLockCategory()); } public: diff --git a/ydb/core/tx/columnshard/engines/changes/compaction.cpp b/ydb/core/tx/columnshard/engines/changes/compaction.cpp index 2441ce4248b8..40f08e502b8b 100644 --- a/ydb/core/tx/columnshard/engines/changes/compaction.cpp +++ b/ydb/core/tx/columnshard/engines/changes/compaction.cpp @@ -1,8 +1,10 @@ #include "compaction.h" -#include -#include -#include + #include +#include +#include +#include +#include namespace NKikimr::NOlap { @@ -12,7 +14,7 @@ void TCompactColumnEngineChanges::DoDebugString(TStringOutput& out) const { if (ui32 switched = SwitchedPortions.size()) { out << "switch " << switched << " portions:("; for (auto& portionInfo : SwitchedPortions) { - out << portionInfo; + out << portionInfo->DebugString(false); } out << "); "; } @@ -23,28 +25,13 @@ void TCompactColumnEngineChanges::DoCompile(TFinalizationContext& context) { const TPortionMeta::EProduced producedClassResultCompaction = GetResultProducedClass(); for (auto& portionInfo : AppendedPortions) { - portionInfo.GetPortionConstructor().MutableMeta().UpdateRecordsMeta(producedClassResultCompaction); + portionInfo.GetPortionConstructor().MutablePortionConstructor().MutableMeta().UpdateRecordsMeta(producedClassResultCompaction); } } void TCompactColumnEngineChanges::DoStart(NColumnShard::TColumnShard& self) { TBase::DoStart(self); - Y_ABORT_UNLESS(SwitchedPortions.size()); - THashMap> blobRanges; - auto& index = self.GetIndexAs().GetVersionedIndex(); - for (const auto& p : SwitchedPortions) { - Y_ABORT_UNLESS(!p.Empty()); - p.FillBlobRangesByStorage(blobRanges, index); - } - - for (const auto& p : blobRanges) { - auto action = BlobsAction.GetReading(p.first); - for (auto&& b: p.second) { - action->AddRange(b); - } - } - self.BackgroundController.StartCompaction(NKikimr::NOlap::TPlanCompactionInfo(GranuleMeta->GetPathId())); NeedGranuleStatusProvide = true; GranuleMeta->OnCompactionStarted(); @@ -68,23 +55,23 @@ void TCompactColumnEngineChanges::DoOnFinish(NColumnShard::TColumnShard& self, T NeedGranuleStatusProvide = false; } -TCompactColumnEngineChanges::TCompactColumnEngineChanges(std::shared_ptr granule, const std::vector>& portions, const TSaverContext& saverContext) +TCompactColumnEngineChanges::TCompactColumnEngineChanges( + std::shared_ptr granule, const std::vector& portions, const TSaverContext& saverContext) : TBase(saverContext, NBlobOperations::EConsumer::GENERAL_COMPACTION) , GranuleMeta(granule) { Y_ABORT_UNLESS(GranuleMeta); - SwitchedPortions.reserve(portions.size()); for (const auto& portionInfo : portions) { Y_ABORT_UNLESS(!portionInfo->HasRemoveSnapshot()); - SwitchedPortions.emplace_back(*portionInfo); - AddPortionToRemove(*portionInfo); + SwitchedPortions.emplace_back(portionInfo); + AddPortionToRemove(portionInfo); Y_ABORT_UNLESS(portionInfo->GetPathId() == GranuleMeta->GetPathId()); } - Y_ABORT_UNLESS(SwitchedPortions.size()); + // Y_ABORT_UNLESS(SwitchedPortions.size()); } TCompactColumnEngineChanges::~TCompactColumnEngineChanges() { - Y_DEBUG_ABORT_UNLESS(!NActors::TlsActivationContext || !NeedGranuleStatusProvide); + Y_DEBUG_ABORT_UNLESS(!NActors::TlsActivationContext || !NeedGranuleStatusProvide || !IsActive()); } -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/changes/compaction.h b/ydb/core/tx/columnshard/engines/changes/compaction.h index fc449e341459..4ed7e663ec04 100644 --- a/ydb/core/tx/columnshard/engines/changes/compaction.h +++ b/ydb/core/tx/columnshard/engines/changes/compaction.h @@ -12,6 +12,7 @@ class TCompactColumnEngineChanges: public TChangesWithAppend { using TBase = TChangesWithAppend; bool NeedGranuleStatusProvide = false; protected: + std::vector SwitchedPortions; // Portions that would be replaced by new ones std::shared_ptr GranuleMeta; virtual void DoWriteIndexOnComplete(NColumnShard::TColumnShard* self, TWriteIndexCompleteContext& context) override; @@ -24,16 +25,42 @@ class TCompactColumnEngineChanges: public TChangesWithAppend { virtual void OnAbortEmergency() override { NeedGranuleStatusProvide = false; } + virtual NDataLocks::ELockCategory GetLockCategory() const override { + return NDataLocks::ELockCategory::Compaction; + } virtual std::shared_ptr DoBuildDataLockImpl() const override { - return std::make_shared(TypeString() + "::" + GetTaskIdentifier(), SwitchedPortions); + const THashSet pathIds = { GranuleMeta->GetPathId() }; + return std::make_shared(TypeString() + "::" + GetTaskIdentifier(), pathIds, GetLockCategory()); } -public: - std::vector SwitchedPortions; // Portions that would be replaced by new ones + virtual void OnDataAccessorsInitialized(const TDataAccessorsInitializationContext& context) override { + TBase::OnDataAccessorsInitialized(context); + THashMap> blobRanges; + for (const auto& p : SwitchedPortions) { + GetPortionDataAccessor(p->GetPortionId()).FillBlobRangesByStorage(blobRanges, *context.GetVersionedIndex()); + } - TCompactColumnEngineChanges(std::shared_ptr granule, const std::vector>& portions, const TSaverContext& saverContext); + for (const auto& p : blobRanges) { + auto action = BlobsAction.GetReading(p.first); + for (auto&& b : p.second) { + action->AddRange(b); + } + } + } + +public: + TCompactColumnEngineChanges(std::shared_ptr granule, const std::vector& portions, const TSaverContext& saverContext); ~TCompactColumnEngineChanges(); + const std::vector& GetSwitchedPortions() const { + return SwitchedPortions; + } + + void AddSwitchedPortion(const TPortionInfo::TConstPtr& portion) { + SwitchedPortions.emplace_back(portion); + PortionsToAccess->AddPortion(portion); + } + static TString StaticTypeName() { return "CS::GENERAL"; } diff --git a/ydb/core/tx/columnshard/engines/changes/compaction/merger.cpp b/ydb/core/tx/columnshard/engines/changes/compaction/merger.cpp index 825f65f80106..9df83a6cb2d4 100644 --- a/ydb/core/tx/columnshard/engines/changes/compaction/merger.cpp +++ b/ydb/core/tx/columnshard/engines/changes/compaction/merger.cpp @@ -6,9 +6,10 @@ #include #include +#include + #include #include -#include namespace NKikimr::NOlap::NCompaction { @@ -139,14 +140,14 @@ std::vector TMerger::Execute(const std::shared } batchSlices.emplace_back(portionColumns, schemaDetails, Context.Counters.SplitterCounters); } - NArrow::NSplitter::TSimilarPacker slicer(NSplitter::TSplitSettings().GetExpectedPortionSize()); + NArrow::NSplitter::TSimilarPacker slicer(PortionExpectedSize); auto packs = slicer.Split(batchSlices); ui32 recordIdx = 0; for (auto&& i : packs) { TGeneralSerializedSlice slicePrimary(std::move(i)); auto dataWithSecondary = resultFiltered->GetIndexInfo() - .AppendIndexes(slicePrimary.GetPortionChunksToHash(), SaverContext.GetStoragesManager()) + .AppendIndexes(slicePrimary.GetPortionChunksToHash(), SaverContext.GetStoragesManager(), slicePrimary.GetRecordsCount()) .DetachResult(); TGeneralSerializedSlice slice(dataWithSecondary.GetExternalData(), schemaDetails, Context.Counters.SplitterCounters); @@ -158,10 +159,10 @@ std::vector TMerger::Execute(const std::shared NArrow::TFirstLastSpecialKeys primaryKeys(slice.GetFirstLastPKBatch(resultFiltered->GetIndexInfo().GetReplaceKey())); NArrow::TMinMaxSpecialKeys snapshotKeys(b, TIndexInfo::ArrowSchemaSnapshot()); - constructor.GetPortionConstructor().AddMetadata(*resultFiltered, deletionsCount, primaryKeys, snapshotKeys); - constructor.GetPortionConstructor().MutableMeta().SetTierName(IStoragesManager::DefaultStorageId); + constructor.GetPortionConstructor().MutablePortionConstructor().AddMetadata(*resultFiltered, deletionsCount, primaryKeys, snapshotKeys); + constructor.GetPortionConstructor().MutablePortionConstructor().MutableMeta().SetTierName(IStoragesManager::DefaultStorageId); if (shardingActualVersion) { - constructor.GetPortionConstructor().SetShardingVersion(*shardingActualVersion); + constructor.GetPortionConstructor().MutablePortionConstructor().SetShardingVersion(*shardingActualVersion); } result.emplace_back(std::move(constructor)); recordIdx += slice.GetRecordsCount(); diff --git a/ydb/core/tx/columnshard/engines/changes/compaction/merger.h b/ydb/core/tx/columnshard/engines/changes/compaction/merger.h index 9c84799fe8ad..6b7de3217638 100644 --- a/ydb/core/tx/columnshard/engines/changes/compaction/merger.h +++ b/ydb/core/tx/columnshard/engines/changes/compaction/merger.h @@ -11,6 +11,7 @@ namespace NKikimr::NOlap::NCompaction { class TMerger { private: YDB_ACCESSOR(bool, OptimizationWritingPackMode, false); + YDB_ACCESSOR(ui64, PortionExpectedSize, 1.5 * (1 << 20)); std::vector> Batches; std::vector> Filters; const TConstructionContext& Context; diff --git a/ydb/core/tx/columnshard/engines/changes/compaction/sparsed/logic.cpp b/ydb/core/tx/columnshard/engines/changes/compaction/sparsed/logic.cpp index d2c4e14f0664..ddcb51e4104c 100644 --- a/ydb/core/tx/columnshard/engines/changes/compaction/sparsed/logic.cpp +++ b/ydb/core/tx/columnshard/engines/changes/compaction/sparsed/logic.cpp @@ -160,6 +160,7 @@ void TSparsedMerger::TCursor::InitArrays(const ui32 position) { SparsedCursor = std::make_shared(sparsedArray, &*CurrentOwnedArray); PlainCursor = nullptr; } else { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_COMPACTION)("event", "plain_merger"); PlainCursor = make_shared(CurrentOwnedArray->GetArray(), &*CurrentOwnedArray); SparsedCursor = nullptr; } diff --git a/ydb/core/tx/columnshard/engines/changes/counters/general.h b/ydb/core/tx/columnshard/engines/changes/counters/general.h index 11c038122e93..98deee42a6af 100644 --- a/ydb/core/tx/columnshard/engines/changes/counters/general.h +++ b/ydb/core/tx/columnshard/engines/changes/counters/general.h @@ -1,5 +1,9 @@ #pragma once #include +#include + +#include + #include #include @@ -12,42 +16,77 @@ class TGeneralCompactionCounters: public NColumnShard::TCommonCountersOwner { NMonitoring::TDynamicCounters::TCounterPtr FullBlobsAppendBytes; NMonitoring::TDynamicCounters::TCounterPtr SplittedBlobsAppendCount; NMonitoring::TDynamicCounters::TCounterPtr SplittedBlobsAppendBytes; - NMonitoring::TDynamicCounters::TCounterPtr RepackPortionsCount; - NMonitoring::TDynamicCounters::TCounterPtr RepackPortionsBytes; - NMonitoring::TDynamicCounters::TCounterPtr RepackInsertedPortionsBytes; - NMonitoring::TDynamicCounters::TCounterPtr RepackCompactedPortionsBytes; - NMonitoring::TDynamicCounters::TCounterPtr RepackOtherPortionsBytes; - NMonitoring::THistogramPtr HistogramRepackPortionsBytes; + + TPortionGroupCounters RepackPortions; + TPortionGroupCounters RepackInsertedPortions; + TPortionGroupCounters RepackCompactedPortions; + THashMap RepackPortionsFromLevel; + THashMap RepackPortionsToLevel; + THashMap MovePortionsFromLevel; + THashMap MovePortionsToLevel; + NMonitoring::THistogramPtr HistogramRepackPortionsRawBytes; + NMonitoring::THistogramPtr HistogramRepackPortionsBlobBytes; NMonitoring::THistogramPtr HistogramRepackPortionsCount; + public: TGeneralCompactionCounters() : TBase("GeneralCompaction") - { + , RepackPortions("ALL", CreateSubGroup("action", "repack")) + , RepackInsertedPortions("INSERTED", CreateSubGroup("action", "repack")) + , RepackCompactedPortions("COMPACTED", CreateSubGroup("action", "repack")) { + for (ui32 i = 0; i < 10; ++i) { + RepackPortionsFromLevel.emplace( + i, TPortionGroupCounters("level=" + ::ToString(i), CreateSubGroup("action", "repack").CreateSubGroup("direction", "from"))); + RepackPortionsToLevel.emplace( + i, TPortionGroupCounters("level=" + ::ToString(i), CreateSubGroup("action", "repack").CreateSubGroup("direction", "to"))); + MovePortionsFromLevel.emplace( + i, TPortionGroupCounters("level=" + ::ToString(i), CreateSubGroup("action", "move").CreateSubGroup("direction", "from"))); + MovePortionsToLevel.emplace( + i, TPortionGroupCounters("level=" + ::ToString(i), CreateSubGroup("action", "move").CreateSubGroup("direction", "to"))); + } FullBlobsAppendCount = TBase::GetDeriviative("FullBlobsAppend/Count"); FullBlobsAppendBytes = TBase::GetDeriviative("FullBlobsAppend/Bytes"); SplittedBlobsAppendCount = TBase::GetDeriviative("SplittedBlobsAppend/Count"); SplittedBlobsAppendBytes = TBase::GetDeriviative("SplittedBlobsAppend/Bytes"); - RepackPortionsCount = TBase::GetDeriviative("RepackPortions/Count"); - RepackPortionsBytes = TBase::GetDeriviative("RepackPortions/Bytes"); - HistogramRepackPortionsBytes = TBase::GetHistogram("RepackPortions/Bytes", NMonitoring::ExponentialHistogram(18, 2, 256 * 1024)); - HistogramRepackPortionsCount = TBase::GetHistogram("RepackPortions/Count", NMonitoring::ExponentialHistogram(15, 2, 4)); + HistogramRepackPortionsRawBytes = TBase::GetHistogram("RepackPortions/Raw/Bytes", NMonitoring::ExponentialHistogram(18, 2, 256 * 1024)); + HistogramRepackPortionsBlobBytes = + TBase::GetHistogram("RepackPortions/Blob/Bytes", NMonitoring::ExponentialHistogram(18, 2, 256 * 1024)); + HistogramRepackPortionsCount = TBase::GetHistogram("RepackPortions/Count", NMonitoring::LinearHistogram(15, 10, 16)); + } + + static void OnRepackPortions(const TSimplePortionsGroupInfo& portions) { + Singleton()->RepackPortions.OnData(portions); + Singleton()->HistogramRepackPortionsCount->Collect(portions.GetCount()); + Singleton()->HistogramRepackPortionsBlobBytes->Collect(portions.GetBlobBytes()); + Singleton()->HistogramRepackPortionsRawBytes->Collect(portions.GetRawBytes()); + } + + static void OnRepackPortionsByLevel(const THashMap& portions, const ui32 targetLevelIdx) { + for (auto&& i : portions) { + auto& counters = (i.first == targetLevelIdx) ? Singleton()->RepackPortionsToLevel + : Singleton()->RepackPortionsFromLevel; + auto it = counters.find(i.first); + AFL_VERIFY(it != counters.end()); + it->second.OnData(i.second); + } + } - RepackInsertedPortionsBytes = TBase::GetDeriviative("RepackInsertedPortions/Bytes"); - RepackCompactedPortionsBytes = TBase::GetDeriviative("RepackCompactedPortions/Bytes"); - RepackOtherPortionsBytes = TBase::GetDeriviative("RepackOtherPortions/Bytes"); + static void OnMovePortionsByLevel(const THashMap& portions, const ui32 targetLevelIdx) { + for (auto&& i : portions) { + auto& counters = (i.first == targetLevelIdx) ? Singleton()->MovePortionsToLevel + : Singleton()->MovePortionsFromLevel; + auto it = counters.find(i.first); + AFL_VERIFY(it != counters.end()); + it->second.OnData(i.second); + } } - static void OnRepackPortions(const i64 portionsCount, const i64 portionBytes) { - Singleton()->RepackPortionsCount->Add(portionsCount); - Singleton()->RepackPortionsBytes->Add(portionBytes); - Singleton()->HistogramRepackPortionsCount->Collect(portionsCount); - Singleton()->HistogramRepackPortionsBytes->Collect(portionBytes); + static void OnRepackInsertedPortions(const TSimplePortionsGroupInfo& portions) { + Singleton()->RepackInsertedPortions.OnData(portions); } - static void OnPortionsKind(const i64 insertedBytes, const i64 compactedBytes, const i64 otherBytes) { - Singleton()->RepackInsertedPortionsBytes->Add(insertedBytes); - Singleton()->RepackCompactedPortionsBytes->Add(compactedBytes); - Singleton()->RepackOtherPortionsBytes->Add(otherBytes); + static void OnRepackCompactedPortions(const TSimplePortionsGroupInfo& portions) { + Singleton()->RepackCompactedPortions.OnData(portions); } static void OnSplittedBlobAppend(const i64 bytes) { @@ -61,4 +100,4 @@ class TGeneralCompactionCounters: public NColumnShard::TCommonCountersOwner { } }; -} +} // namespace NKikimr::NOlap::NChanges diff --git a/ydb/core/tx/columnshard/engines/changes/general_compaction.cpp b/ydb/core/tx/columnshard/engines/changes/general_compaction.cpp index 2f76ab4b1772..88e1d5ea4610 100644 --- a/ydb/core/tx/columnshard/engines/changes/general_compaction.cpp +++ b/ydb/core/tx/columnshard/engines/changes/general_compaction.cpp @@ -1,17 +1,19 @@ - #include "general_compaction.h" -#include "counters/general.h" #include "compaction/merger.h" +#include "counters/general.h" #include #include +#include +#include namespace NKikimr::NOlap::NCompaction { std::shared_ptr TGeneralCompactColumnEngineChanges::BuildPortionFilter( const std::optional& shardingActual, const std::shared_ptr& batch, const TPortionInfo& pInfo, const THashSet& portionsInUsage, const ISnapshotSchema::TPtr& resultSchema) const { + Y_UNUSED(resultSchema); std::shared_ptr filter; if (shardingActual && pInfo.NeedShardingFilter(*shardingActual)) { std::set fieldNames; @@ -24,49 +26,25 @@ std::shared_ptr TGeneralCompactColumnEngineChanges::Build } NArrow::TColumnFilter filterDeleted = NArrow::TColumnFilter::BuildAllowFilter(); if (pInfo.GetMeta().GetDeletionsCount()) { - auto table = batch->BuildTableVerified(std::set({ TIndexInfo::SPEC_COL_DELETE_FLAG })); - AFL_VERIFY(table); - auto col = table->GetColumnByName(TIndexInfo::SPEC_COL_DELETE_FLAG); - AFL_VERIFY(col); - AFL_VERIFY(col->type()->id() == arrow::Type::BOOL); - for (auto&& c : col->chunks()) { - auto bCol = static_pointer_cast(c); - for (ui32 i = 0; i < bCol->length(); ++i) { - filterDeleted.Add(!bCol->GetView(i)); + if (pInfo.HasInsertWriteId()) { + AFL_VERIFY(pInfo.GetMeta().GetDeletionsCount() == pInfo.GetRecordsCount()); + filterDeleted = NArrow::TColumnFilter::BuildDenyFilter(); + } else { + auto table = batch->BuildTableVerified(std::set({ TIndexInfo::SPEC_COL_DELETE_FLAG })); + AFL_VERIFY(table); + auto col = table->GetColumnByName(TIndexInfo::SPEC_COL_DELETE_FLAG); + AFL_VERIFY(col); + AFL_VERIFY(col->type()->id() == arrow::Type::BOOL); + for (auto&& c : col->chunks()) { + auto bCol = static_pointer_cast(c); + for (ui32 i = 0; i < bCol->length(); ++i) { + filterDeleted.Add(!bCol->GetView(i)); + } } } - NArrow::TColumnFilter filterCorrection = NArrow::TColumnFilter::BuildDenyFilter(); - auto pkSchema = resultSchema->GetIndexInfo().GetReplaceKey(); - NArrow::NMerger::TRWSortableBatchPosition pos(batch, 0, pkSchema->field_names(), {}, false); - ui32 posCurrent = 0; - auto excludedIntervalsInfo = GranuleMeta->GetPortionsIndex().GetIntervalFeatures(pInfo, portionsInUsage); - for (auto&& i : excludedIntervalsInfo.GetExcludedIntervals()) { - NArrow::NMerger::TSortableBatchPosition startForFound(i.GetStart().ToBatch(pkSchema), 0, pkSchema->field_names(), {}, false); - NArrow::NMerger::TSortableBatchPosition finishForFound(i.GetFinish().ToBatch(pkSchema), 0, pkSchema->field_names(), {}, false); - auto foundStart = - NArrow::NMerger::TSortableBatchPosition::FindPosition(pos, pos.GetPosition(), batch->num_rows() - 1, startForFound, true); - AFL_VERIFY(foundStart); - AFL_VERIFY(!foundStart->IsLess())("pos", pos.DebugJson())("start", startForFound.DebugJson())("found", foundStart->DebugString()); - auto foundFinish = - NArrow::NMerger::TSortableBatchPosition::FindPosition(pos, pos.GetPosition(), batch->num_rows() - 1, finishForFound, false); - AFL_VERIFY(foundFinish); - AFL_VERIFY(foundFinish->GetPosition() >= foundStart->GetPosition()); - if (foundFinish->GetPosition() > foundStart->GetPosition()) { - AFL_VERIFY(!foundFinish->IsGreater())("pos", pos.DebugJson())("finish", finishForFound.DebugJson())( - "found", foundFinish->DebugString()); - } - filterCorrection.Add(foundStart->GetPosition() - posCurrent, false); - if (foundFinish->IsGreater()) { - filterCorrection.Add(foundFinish->GetPosition() - foundStart->GetPosition(), true); - posCurrent = foundFinish->GetPosition(); - } else { - filterCorrection.Add(foundFinish->GetPosition() - foundStart->GetPosition() + 1, true); - posCurrent = foundFinish->GetPosition() + 1; - } + if (GranuleMeta->GetPortionsIndex().HasOlderIntervals(pInfo, portionsInUsage)) { + filterDeleted = NArrow::TColumnFilter::BuildAllowFilter(); } - AFL_VERIFY(filterCorrection.Size() <= batch->num_rows()); - filterCorrection.Add(false, batch->num_rows() - filterCorrection.Size()); - filterDeleted = filterDeleted.Or(filterCorrection); } if (filter) { *filter = filter->And(filterDeleted); @@ -80,10 +58,13 @@ void TGeneralCompactColumnEngineChanges::BuildAppendedPortionsByChunks( TConstructionContext& context, std::vector&& portions) noexcept { auto resultSchema = context.SchemaVersions.GetLastSchema(); auto shardingActual = context.SchemaVersions.GetShardingInfoActual(GranuleMeta->GetPathId()); - + if (portions.empty()) { + return; + } std::shared_ptr stats = std::make_shared(); std::shared_ptr resultFiltered; NCompaction::TMerger merger(context, SaverContext); + merger.SetPortionExpectedSize(PortionExpectedSize); { std::set pkColumnIds; { @@ -95,15 +76,19 @@ void TGeneralCompactColumnEngineChanges::BuildAppendedPortionsByChunks( { THashMap schemas; for (auto& portion : SwitchedPortions) { - auto dataSchema = portion.GetSchema(context.SchemaVersions); + auto dataSchema = portion->GetSchema(context.SchemaVersions); schemas.emplace(dataSchema->GetVersion(), dataSchema); } dataColumnIds = ISnapshotSchema::GetColumnsWithDifferentDefaults(schemas, resultSchema); } for (auto&& i : SwitchedPortions) { - stats->Merge(i.GetSerializationStat(*resultSchema)); + const auto& accessor = GetPortionDataAccessor(i->GetPortionId()); + stats->Merge(accessor.GetSerializationStat(*resultSchema)); + if (i->GetMeta().GetDeletionsCount()) { + dataColumnIds.emplace((ui32)IIndexInfo::ESpecialColumn::DELETE_FLAG); + } if (dataColumnIds.size() != resultSchema->GetColumnsCount()) { - for (auto id : i.GetColumnIds()) { + for (auto id : accessor.GetColumnIds()) { if (resultSchema->HasColumnId(id)) { dataColumnIds.emplace(id); } @@ -116,6 +101,7 @@ void TGeneralCompactColumnEngineChanges::BuildAppendedPortionsByChunks( } dataColumnIds.emplace((ui32)IIndexInfo::ESpecialColumn::WRITE_ID); } + dataColumnIds.insert(IIndexInfo::GetSnapshotColumnIds().begin(), IIndexInfo::GetSnapshotColumnIds().end()); resultFiltered = std::make_shared(resultSchema, dataColumnIds); { auto seqDataColumnIds = dataColumnIds; @@ -129,7 +115,7 @@ void TGeneralCompactColumnEngineChanges::BuildAppendedPortionsByChunks( for (auto&& i : portions) { auto blobsSchema = i.GetPortionInfo().GetSchema(context.SchemaVersions); - auto batch = i.RestoreBatch(*blobsSchema, *resultFiltered, seqDataColumnIds); + auto batch = i.RestoreBatch(*blobsSchema, *resultFiltered, seqDataColumnIds, false).DetachResult(); std::shared_ptr filter = BuildPortionFilter(shardingActual, batch, i.GetPortionInfo(), usedPortionIds, resultFiltered); merger.AddBatch(batch, filter); @@ -143,33 +129,34 @@ void TGeneralCompactColumnEngineChanges::BuildAppendedPortionsByChunks( } AppendedPortions = merger.Execute(stats, CheckPoints, resultFiltered, GranuleMeta->GetPathId(), shardingActualVersion); for (auto&& p : AppendedPortions) { - p.GetPortionConstructor().MutableMeta().UpdateRecordsMeta(NPortion::EProduced::SPLIT_COMPACTED); + p.GetPortionConstructor().MutablePortionConstructor().MutableMeta().UpdateRecordsMeta(NPortion::EProduced::SPLIT_COMPACTED); } } TConclusionStatus TGeneralCompactColumnEngineChanges::DoConstructBlobs(TConstructionContext& context) noexcept { - i64 portionsSize = 0; - i64 portionsCount = 0; - i64 insertedPortionsSize = 0; - i64 compactedPortionsSize = 0; - i64 otherPortionsSize = 0; + TSimplePortionsGroupInfo insertedPortions; + TSimplePortionsGroupInfo compactedPortions; + THashMap portionGroups; for (auto&& i : SwitchedPortions) { - if (i.GetMeta().GetProduced() == TPortionMeta::EProduced::INSERTED) { - insertedPortionsSize += i.GetTotalBlobBytes(); - } else if (i.GetMeta().GetProduced() == TPortionMeta::EProduced::SPLIT_COMPACTED) { - compactedPortionsSize += i.GetTotalBlobBytes(); + portionGroups[i->GetMeta().GetCompactionLevel()].AddPortion(i); + if (i->GetMeta().GetProduced() == TPortionMeta::EProduced::INSERTED) { + insertedPortions.AddPortion(i); + } else if (i->GetMeta().GetProduced() == TPortionMeta::EProduced::SPLIT_COMPACTED) { + compactedPortions.AddPortion(i); } else { - otherPortionsSize += i.GetTotalBlobBytes(); + AFL_VERIFY(false); } - portionsSize += i.GetTotalBlobBytes(); - ++portionsCount; } - NChanges::TGeneralCompactionCounters::OnPortionsKind(insertedPortionsSize, compactedPortionsSize, otherPortionsSize); - NChanges::TGeneralCompactionCounters::OnRepackPortions(portionsCount, portionsSize); + NChanges::TGeneralCompactionCounters::OnRepackPortions(insertedPortions + compactedPortions); + NChanges::TGeneralCompactionCounters::OnRepackInsertedPortions(insertedPortions); + NChanges::TGeneralCompactionCounters::OnRepackCompactedPortions(compactedPortions); + if (TargetCompactionLevel) { + NChanges::TGeneralCompactionCounters::OnRepackPortionsByLevel(portionGroups, *TargetCompactionLevel); + } { std::vector portions = - TReadPortionInfoWithBlobs::RestorePortions(SwitchedPortions, Blobs, context.SchemaVersions); + TReadPortionInfoWithBlobs::RestorePortions(GetPortionDataAccessors(SwitchedPortions), Blobs, context.SchemaVersions); BuildAppendedPortionsByChunks(context, std::move(portions)); } @@ -177,7 +164,7 @@ TConclusionStatus TGeneralCompactColumnEngineChanges::DoConstructBlobs(TConstruc TStringBuilder sbSwitched; sbSwitched << ""; for (auto&& p : SwitchedPortions) { - sbSwitched << p.DebugString() << ";"; + sbSwitched << p->DebugString() << ";"; } sbSwitched << ""; @@ -202,6 +189,7 @@ void TGeneralCompactColumnEngineChanges::DoWriteIndexOnComplete(NColumnShard::TC } void TGeneralCompactColumnEngineChanges::DoStart(NColumnShard::TColumnShard& self) { + AFL_VERIFY(PrioritiesAllocationGuard); TBase::DoStart(self); auto& g = *GranuleMeta; self.Counters.GetCSCounters().OnSplitCompactionInfo( @@ -212,8 +200,7 @@ NColumnShard::ECumulativeCounters TGeneralCompactColumnEngineChanges::GetCounter return isSuccess ? NColumnShard::COUNTER_COMPACTION_SUCCESS : NColumnShard::COUNTER_COMPACTION_FAIL; } -void TGeneralCompactColumnEngineChanges::AddCheckPoint( - const NArrow::NMerger::TSortableBatchPosition& position, const bool include) { +void TGeneralCompactColumnEngineChanges::AddCheckPoint(const NArrow::NMerger::TSortableBatchPosition& position, const bool include) { CheckPoints.InsertPosition(position, include); } @@ -221,31 +208,10 @@ std::shared_ptr TGeneralCo return std::make_shared(); } -ui64 TGeneralCompactColumnEngineChanges::TMemoryPredictorChunkedPolicy::AddPortion(const TPortionInfo& portionInfo) { - SumMemoryFix += portionInfo.GetRecordsCount() * (2 * sizeof(ui64) + sizeof(ui32) + sizeof(ui16)); - ++PortionsCount; - THashMap maxChunkSizeByColumn; - for (auto&& i : portionInfo.GetRecords()) { - SumMemoryFix += i.BlobRange.Size; - auto it = maxChunkSizeByColumn.find(i.GetColumnId()); - if (it == maxChunkSizeByColumn.end()) { - maxChunkSizeByColumn.emplace(i.GetColumnId(), i.GetMeta().GetRawBytes()); - } else { - if (it->second < i.GetMeta().GetRawBytes()) { - it->second = i.GetMeta().GetRawBytes(); - } - } - } - - SumMemoryDelta = 0; - for (auto&& i : maxChunkSizeByColumn) { - MaxMemoryByColumnChunk[i.first] += i.second; - SumMemoryDelta = std::max(SumMemoryDelta, MaxMemoryByColumnChunk[i.first]); - } - - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("memory_prediction_after", SumMemoryFix + SumMemoryDelta)( - "portion_info", portionInfo.DebugString()); - return SumMemoryFix + SumMemoryDelta; +ui64 TGeneralCompactColumnEngineChanges::TMemoryPredictorChunkedPolicy::AddPortion(const TPortionInfo::TConstPtr& portionInfo) { + SumMemoryFix += portionInfo->GetRecordsCount() * (2 * sizeof(ui64) + sizeof(ui32) + sizeof(ui16)) + portionInfo->GetTotalBlobBytes(); + SumMemoryRaw += portionInfo->GetTotalRawBytes(); + return SumMemoryFix + std::min(SumMemoryRaw, ((ui64)500 << 20)); } } // namespace NKikimr::NOlap::NCompaction diff --git a/ydb/core/tx/columnshard/engines/changes/general_compaction.h b/ydb/core/tx/columnshard/engines/changes/general_compaction.h index ab6f1e18684e..a8a7547af2fb 100644 --- a/ydb/core/tx/columnshard/engines/changes/general_compaction.h +++ b/ydb/core/tx/columnshard/engines/changes/general_compaction.h @@ -1,13 +1,18 @@ #pragma once #include "compaction.h" + #include #include +#include namespace NKikimr::NOlap::NCompaction { -class TGeneralCompactColumnEngineChanges: public TCompactColumnEngineChanges { +class TGeneralCompactColumnEngineChanges: public TCompactColumnEngineChanges, + public NColumnShard::TMonitoringObjectsCounter { private: + YDB_ACCESSOR(ui64, PortionExpectedSize, 1.5 * (1 << 20)); using TBase = TCompactColumnEngineChanges; + std::shared_ptr PrioritiesAllocationGuard; virtual void DoWriteIndexOnComplete(NColumnShard::TColumnShard* self, TWriteIndexCompleteContext& context) override; NArrow::NMerger::TIntervalPositions CheckPoints; void BuildAppendedPortionsByChunks(TConstructionContext& context, std::vector&& portions) noexcept; @@ -15,6 +20,7 @@ class TGeneralCompactColumnEngineChanges: public TCompactColumnEngineChanges { std::shared_ptr BuildPortionFilter(const std::optional& shardingActual, const std::shared_ptr& batch, const TPortionInfo& pInfo, const THashSet& portionsInUsage, const ISnapshotSchema::TPtr& resultSchema) const; + protected: virtual TConclusionStatus DoConstructBlobs(TConstructionContext& context) noexcept override; @@ -35,39 +41,31 @@ class TGeneralCompactColumnEngineChanges: public TCompactColumnEngineChanges { } return result; } + public: + void SetQueueGuard(const std::shared_ptr& g) { + PrioritiesAllocationGuard = g; + } using TBase::TBase; - class TMemoryPredictorSimplePolicy: public IMemoryPredictor { - private: - ui64 SumMemory = 0; - public: - virtual ui64 AddPortion(const TPortionInfo& portionInfo) override { - for (auto&& i : portionInfo.GetRecords()) { - SumMemory += i.BlobRange.Size; - SumMemory += 2 * i.GetMeta().GetRawBytes(); - } - return SumMemory; - } - }; - class TMemoryPredictorChunkedPolicy: public IMemoryPredictor { private: - ui64 SumMemoryDelta = 0; ui64 SumMemoryFix = 0; - ui32 PortionsCount = 0; - THashMap MaxMemoryByColumnChunk; + ui64 SumMemoryRaw = 0; public: - virtual ui64 AddPortion(const TPortionInfo& portionInfo) override; + virtual ui64 AddPortion(const TPortionInfo::TConstPtr& portionInfo) override; }; static std::shared_ptr BuildMemoryPredictor(); void AddCheckPoint(const NArrow::NMerger::TSortableBatchPosition& position, const bool include); + void SetCheckPoints(NArrow::NMerger::TIntervalPositions&& positions) { + CheckPoints = std::move(positions); + } virtual TString TypeString() const override { return StaticTypeName(); } }; -} +} // namespace NKikimr::NOlap::NCompaction diff --git a/ydb/core/tx/columnshard/engines/changes/indexation.cpp b/ydb/core/tx/columnshard/engines/changes/indexation.cpp index edce92470ad9..3d3d5fb8c6c9 100644 --- a/ydb/core/tx/columnshard/engines/changes/indexation.cpp +++ b/ydb/core/tx/columnshard/engines/changes/indexation.cpp @@ -104,14 +104,20 @@ class TPathFieldsInfo { return; } auto blobSchema = context.SchemaVersions.GetSchemaVerified(data.GetSchemaVersion()); + std::set columnIdsToDelete = blobSchema->GetColumnIdsToDelete(ResultSchema); if (!Schemas.contains(data.GetSchemaVersion())) { Schemas.emplace(data.GetSchemaVersion(), blobSchema); } - std::vector filteredIds = data.GetMeta().GetSchemaSubset().Apply(blobSchema->GetIndexInfo().GetColumnIds(false)); + TColumnIdsView columnIds = blobSchema->GetIndexInfo().GetColumnIds(false); + std::vector filteredIds = data.GetMeta().GetSchemaSubset().Apply(columnIds.begin(), columnIds.end()); if (data.GetMeta().GetModificationType() == NEvWrite::EModificationType::Delete) { filteredIds.emplace_back((ui32)IIndexInfo::ESpecialColumn::DELETE_FLAG); } - UsageColumnIds.insert(filteredIds.begin(), filteredIds.end()); + for (const auto& filteredId : filteredIds) { + if (!columnIdsToDelete.contains(filteredId)) { + UsageColumnIds.insert(filteredId); + } + } } }; @@ -225,6 +231,7 @@ TConclusionStatus TInsertColumnEngineChanges::DoConstructBlobs(TConstructionCont } pathBatches.AddChunkInfo(inserted, context); } + NoAppendIsCorrect = pathBatches.GetData().empty(); pathBatches.FinishChunksInfo(); @@ -239,10 +246,15 @@ TConclusionStatus TInsertColumnEngineChanges::DoConstructBlobs(TConstructionCont std::shared_ptr batch; { const auto blobData = Blobs.Extract(IStoragesManager::DefaultStorageId, blobRange); + + NArrow::TSchemaLiteView blobSchemaView = blobSchema->GetIndexInfo().ArrowSchema(); auto batchSchema = - std::make_shared(inserted.GetMeta().GetSchemaSubset().Apply(blobSchema->GetIndexInfo().ArrowSchema()->fields())); + std::make_shared(inserted.GetMeta().GetSchemaSubset().Apply(blobSchemaView.begin(), blobSchemaView.end())); batch = std::make_shared(NArrow::DeserializeBatch(blobData, batchSchema)); - blobSchema->AdaptBatchToSchema(*batch, resultSchema); + std::set columnIdsToDelete = blobSchema->GetColumnIdsToDelete(resultSchema); + if (!columnIdsToDelete.empty()) { + batch->DeleteFieldsByIndex(blobSchema->ConvertColumnIdsToIndexes(columnIdsToDelete)); + } } IIndexInfo::AddSnapshotColumns(*batch, inserted.GetSnapshot(), (ui64)inserted.GetInsertWriteId()); @@ -273,7 +285,7 @@ TConclusionStatus TInsertColumnEngineChanges::DoConstructBlobs(TConstructionCont merger.SetOptimizationWritingPackMode(true); auto localAppended = merger.Execute(stats, itGranule->second, filteredSnapshot, pathId, shardingVersion); for (auto&& i : localAppended) { - i.GetPortionConstructor().MutableMeta().UpdateRecordsMeta(NPortion::EProduced::INSERTED); + i.GetPortionConstructor().MutablePortionConstructor().MutableMeta().UpdateRecordsMeta(NPortion::EProduced::INSERTED); AppendedPortions.emplace_back(std::move(i)); } } diff --git a/ydb/core/tx/columnshard/engines/changes/indexation.h b/ydb/core/tx/columnshard/engines/changes/indexation.h index 4c7f8602a6f5..fc27a685b3fe 100644 --- a/ydb/core/tx/columnshard/engines/changes/indexation.h +++ b/ydb/core/tx/columnshard/engines/changes/indexation.h @@ -11,7 +11,7 @@ namespace NKikimr::NOlap { -class TInsertColumnEngineChanges: public TChangesWithAppend { +class TInsertColumnEngineChanges: public TChangesWithAppend, public NColumnShard::TMonitoringObjectsCounter { private: using TBase = TChangesWithAppend; std::vector DataToIndex; @@ -35,13 +35,16 @@ class TInsertColumnEngineChanges: public TChangesWithAppend { virtual std::shared_ptr DoBuildDataLockImpl() const override { return nullptr; } - + virtual NDataLocks::ELockCategory GetLockCategory() const override { + return NDataLocks::ELockCategory::Compaction; + } public: THashMap PathToGranule; // pathId -> positions (sorted by pk) public: TInsertColumnEngineChanges(std::vector&& dataToIndex, const TSaverContext& saverContext) : TBase(saverContext, NBlobOperations::EConsumer::INDEXATION) , DataToIndex(std::move(dataToIndex)) { + SetTargetCompactionLevel(0); } const std::vector& GetDataToIndex() const { diff --git a/ydb/core/tx/columnshard/engines/changes/ttl.cpp b/ydb/core/tx/columnshard/engines/changes/ttl.cpp index fc74dbea0454..72f49e67e4f4 100644 --- a/ydb/core/tx/columnshard/engines/changes/ttl.cpp +++ b/ydb/core/tx/columnshard/engines/changes/ttl.cpp @@ -1,10 +1,12 @@ #include "ttl.h" -#include + +#include #include -#include -#include #include -#include +#include +#include +#include +#include namespace NKikimr::NOlap { @@ -15,20 +17,7 @@ void TTTLColumnEngineChanges::DoDebugString(TStringOutput& out) const { void TTTLColumnEngineChanges::DoStart(NColumnShard::TColumnShard& self) { Y_ABORT_UNLESS(PortionsToEvict.size() || HasPortionsToRemove()); - THashMap> blobRanges; - auto& engine = self.MutableIndexAs(); - auto& index = engine.GetVersionedIndex(); - for (const auto& p : PortionsToEvict) { - Y_ABORT_UNLESS(!p.GetPortionInfo().Empty()); - p.GetPortionInfo().FillBlobRangesByStorage(blobRanges, index); - } - for (auto&& i : blobRanges) { - auto action = BlobsAction.GetReading(i.first); - for (auto&& b : i.second) { - action->AddRange(b); - } - } - engine.GetActualizationController()->StartActualization(RWAddress); + self.GetIndexAs().GetActualizationController()->StartActualization(RWAddress); } void TTTLColumnEngineChanges::DoOnFinish(NColumnShard::TColumnShard& self, TChangesFinishContext& /*context*/) { @@ -37,7 +26,7 @@ void TTTLColumnEngineChanges::DoOnFinish(NColumnShard::TColumnShard& self, TChan if (IsAborted()) { THashMap> restoreIndexAddresses; for (auto&& i : PortionsToEvict) { - AFL_VERIFY(restoreIndexAddresses[i.GetPortionInfo().GetPathId()].emplace(i.GetPortionInfo().GetPortionId()).second); + AFL_VERIFY(restoreIndexAddresses[i.GetPortionInfo()->GetPathId()].emplace(i.GetPortionInfo()->GetPortionId()).second); } for (auto&& i : GetPortionsToRemove()) { AFL_VERIFY(restoreIndexAddresses[i.first.GetPathId()].emplace(i.first.GetPortionId()).second); @@ -46,17 +35,19 @@ void TTTLColumnEngineChanges::DoOnFinish(NColumnShard::TColumnShard& self, TChan } } -std::optional TTTLColumnEngineChanges::UpdateEvictedPortion(TPortionForEviction& info, NBlobOperations::NRead::TCompositeReadBlobs& srcBlobs, - TConstructionContext& context) const -{ - const TPortionInfo& portionInfo = info.GetPortionInfo(); +std::optional TTTLColumnEngineChanges::UpdateEvictedPortion( + TPortionForEviction& info, NBlobOperations::NRead::TCompositeReadBlobs& srcBlobs, TConstructionContext& context) const { + const TPortionInfo& portionInfo = *info.GetPortionInfo(); auto& evictFeatures = info.GetFeatures(); auto blobSchema = portionInfo.GetSchema(context.SchemaVersions); - Y_ABORT_UNLESS(portionInfo.GetMeta().GetTierName() != evictFeatures.GetTargetTierName() || blobSchema->GetVersion() < evictFeatures.GetTargetScheme()->GetVersion()); + Y_ABORT_UNLESS(portionInfo.GetMeta().GetTierName() != evictFeatures.GetTargetTierName() || + blobSchema->GetVersion() < evictFeatures.GetTargetScheme()->GetVersion()); - auto portionWithBlobs = TReadPortionInfoWithBlobs::RestorePortion(portionInfo, srcBlobs, blobSchema->GetIndexInfo()); - std::optional result = TReadPortionInfoWithBlobs::SyncPortion( - std::move(portionWithBlobs), blobSchema, evictFeatures.GetTargetScheme(), evictFeatures.GetTargetTierName(), SaverContext.GetStoragesManager(), context.Counters.SplitterCounters); + auto portionWithBlobs = TReadPortionInfoWithBlobs::RestorePortion( + GetPortionDataAccessor(info.GetPortionInfo()->GetPortionId()), srcBlobs, blobSchema->GetIndexInfo()); + std::optional result = + TReadPortionInfoWithBlobs::SyncPortion(std::move(portionWithBlobs), blobSchema, evictFeatures.GetTargetScheme(), + evictFeatures.GetTargetTierName(), SaverContext.GetStoragesManager(), context.Counters.SplitterCounters); return std::move(result); } @@ -66,7 +57,7 @@ NKikimr::TConclusionStatus TTTLColumnEngineChanges::DoConstructBlobs(TConstructi for (auto&& info : PortionsToEvict) { if (auto pwb = UpdateEvictedPortion(info, Blobs, context)) { - AddPortionToRemove(info.GetPortionInfo()); + AddPortionToRemove(info.GetPortionInfo(), false); AppendedPortions.emplace_back(std::move(*pwb)); } } @@ -79,4 +70,4 @@ NColumnShard::ECumulativeCounters TTTLColumnEngineChanges::GetCounterIndex(const return isSuccess ? NColumnShard::COUNTER_TTL_SUCCESS : NColumnShard::COUNTER_TTL_FAIL; } -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/changes/ttl.h b/ydb/core/tx/columnshard/engines/changes/ttl.h index b75795e16fe4..5c4b515cf8db 100644 --- a/ydb/core/tx/columnshard/engines/changes/ttl.h +++ b/ydb/core/tx/columnshard/engines/changes/ttl.h @@ -7,21 +7,19 @@ namespace NKikimr::NOlap { -class TTTLColumnEngineChanges: public TChangesWithAppend { +class TTTLColumnEngineChanges: public TChangesWithAppend, public NColumnShard::TMonitoringObjectsCounter { private: using TBase = TChangesWithAppend; class TPortionForEviction { private: - TPortionInfo PortionInfo; + TPortionInfo::TConstPtr PortionInfo; TPortionEvictionFeatures Features; public: - TPortionForEviction(const TPortionInfo& portion, TPortionEvictionFeatures&& features) + TPortionForEviction(const TPortionInfo::TConstPtr& portion, TPortionEvictionFeatures&& features) : PortionInfo(portion) - , Features(std::move(features)) - { - - } + , Features(std::move(features)) { + }; TPortionEvictionFeatures& GetFeatures() { return Features; @@ -31,11 +29,7 @@ class TTTLColumnEngineChanges: public TChangesWithAppend { return Features; } - const TPortionInfo& GetPortionInfo() const { - return PortionInfo; - } - - TPortionInfo& MutablePortionInfo() { + const TPortionInfo::TConstPtr& GetPortionInfo() const { return PortionInfo; } }; @@ -59,23 +53,41 @@ class TTTLColumnEngineChanges: public TChangesWithAppend { } return result; } + virtual NDataLocks::ELockCategory GetLockCategory() const override { + return NDataLocks::ELockCategory::Actualization; + } virtual std::shared_ptr DoBuildDataLockImpl() const override { const auto pred = [](const TPortionForEviction& p) { - return p.GetPortionInfo().GetAddress(); + return p.GetPortionInfo()->GetAddress(); }; - return std::make_shared(TypeString() + "::" + RWAddress.DebugString() + "::" + GetTaskIdentifier(), PortionsToEvict, pred); + return std::make_shared(TypeString() + "::" + RWAddress.DebugString() + "::" + GetTaskIdentifier(), + PortionsToEvict, pred, GetLockCategory()); + } + virtual void OnDataAccessorsInitialized(const TDataAccessorsInitializationContext& context) override { + TBase::OnDataAccessorsInitialized(context); + THashMap> blobRanges; + for (const auto& p : PortionsToEvict) { + GetPortionDataAccessor(p.GetPortionInfo()->GetPortionId()).FillBlobRangesByStorage(blobRanges, *context.GetVersionedIndex()); + } + for (auto&& i : blobRanges) { + auto action = BlobsAction.GetReading(i.first); + for (auto&& b : i.second) { + action->AddRange(b); + } + } } + public: class TMemoryPredictorSimplePolicy: public IMemoryPredictor { private: ui64 SumBlobsMemory = 0; ui64 MaxRawMemory = 0; public: - virtual ui64 AddPortion(const TPortionInfo& portionInfo) override { - if (MaxRawMemory < portionInfo.GetTotalRawBytes()) { - MaxRawMemory = portionInfo.GetTotalRawBytes(); + virtual ui64 AddPortion(const TPortionInfo::TConstPtr& portionInfo) override { + if (MaxRawMemory < portionInfo->GetTotalRawBytes()) { + MaxRawMemory = portionInfo->GetTotalRawBytes(); } - SumBlobsMemory += portionInfo.GetTotalBlobBytes(); + SumBlobsMemory += portionInfo->GetTotalBlobBytes(); return SumBlobsMemory + MaxRawMemory; } }; @@ -94,10 +106,10 @@ class TTTLColumnEngineChanges: public TChangesWithAppend { ui32 GetPortionsToEvictCount() const { return PortionsToEvict.size(); } - void AddPortionToEvict(const TPortionInfo& info, TPortionEvictionFeatures&& features) { - Y_ABORT_UNLESS(!info.Empty()); - Y_ABORT_UNLESS(!info.HasRemoveSnapshot()); + void AddPortionToEvict(const TPortionInfo::TConstPtr& info, TPortionEvictionFeatures&& features) { + AFL_VERIFY(!info->HasRemoveSnapshot()); PortionsToEvict.emplace_back(info, std::move(features)); + PortionsToAccess->AddPortion(info); } static TString StaticTypeName() { diff --git a/ydb/core/tx/columnshard/engines/changes/with_appended.cpp b/ydb/core/tx/columnshard/engines/changes/with_appended.cpp index 24d44eb34587..eb83e5a23eab 100644 --- a/ydb/core/tx/columnshard/engines/changes/with_appended.cpp +++ b/ydb/core/tx/columnshard/engines/changes/with_appended.cpp @@ -1,5 +1,7 @@ #include "with_appended.h" +#include "counters/general.h" + #include #include #include @@ -11,16 +13,23 @@ namespace NKikimr::NOlap { void TChangesWithAppend::DoWriteIndexOnExecute(NColumnShard::TColumnShard* self, TWriteIndexContext& context) { THashSet usedPortionIds; auto schemaPtr = context.EngineLogs.GetVersionedIndex().GetLastSchema(); - for (auto& [_, portionInfo] : PortionsToRemove) { - Y_ABORT_UNLESS(!portionInfo.Empty()); - Y_ABORT_UNLESS(portionInfo.HasRemoveSnapshot()); - AFL_VERIFY(usedPortionIds.emplace(portionInfo.GetPortionId()).second)("portion_info", portionInfo.DebugString(true)); - portionInfo.SaveToDatabase(context.DBWrapper, schemaPtr->GetIndexInfo().GetPKFirstColumnId(), false); + + for (auto&& [_, i] : PortionsToRemove) { + Y_ABORT_UNLESS(!i->HasRemoveSnapshot()); + AFL_VERIFY(usedPortionIds.emplace(i->GetPortionId()).second)("portion_info", i->DebugString(true)); + const auto pred = [&](TPortionInfo& portionCopy) { + portionCopy.SetRemoveSnapshot(context.Snapshot); + }; + context.EngineLogs.GetGranuleVerified(i->GetPathId()) + .ModifyPortionOnExecute( + context.DBWrapper, GetPortionDataAccessor(i->GetPortionId()), pred, schemaPtr->GetIndexInfo().GetPKFirstColumnId()); } + const auto predRemoveDroppedTable = [self](const TWritePortionInfoWithBlobsResult& item) { auto& portionInfo = item.GetPortionResult(); - if (!!self && !self->TablesManager.HasTable(portionInfo.GetPathId(), false)) { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "skip_inserted_data")("reason", "table_removed")("path_id", portionInfo.GetPathId()); + if (!!self && !self->TablesManager.HasTable(portionInfo.GetPortionInfo().GetPathId(), false)) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "skip_inserted_data")("reason", "table_removed")( + "path_id", portionInfo.GetPortionInfo().GetPathId()); return true; } else { return false; @@ -28,17 +37,27 @@ void TChangesWithAppend::DoWriteIndexOnExecute(NColumnShard::TColumnShard* self, }; AppendedPortions.erase(std::remove_if(AppendedPortions.begin(), AppendedPortions.end(), predRemoveDroppedTable), AppendedPortions.end()); for (auto& portionInfoWithBlobs : AppendedPortions) { - auto& portionInfo = portionInfoWithBlobs.GetPortionResult(); - AFL_VERIFY(usedPortionIds.emplace(portionInfo.GetPortionId()).second)("portion_info", portionInfo.DebugString(true)); - portionInfo.SaveToDatabase(context.DBWrapper, schemaPtr->GetIndexInfo().GetPKFirstColumnId(), false); + const auto& portionInfo = portionInfoWithBlobs.GetPortionResult().GetPortionInfoPtr(); + AFL_VERIFY(usedPortionIds.emplace(portionInfo->GetPortionId()).second)("portion_info", portionInfo->DebugString(true)); + portionInfoWithBlobs.GetPortionResult().SaveToDatabase(context.DBWrapper, schemaPtr->GetIndexInfo().GetPKFirstColumnId(), false); + } + for (auto&& [_, i] : PortionsToMove) { + const auto pred = [&](TPortionInfo& portionCopy) { + portionCopy.MutableMeta().ResetCompactionLevel(TargetCompactionLevel.value_or(0)); + }; + context.EngineLogs.GetGranuleVerified(i->GetPathId()) + .ModifyPortionOnExecute( + context.DBWrapper, GetPortionDataAccessor(i->GetPortionId()), pred, schemaPtr->GetIndexInfo().GetPKFirstColumnId()); } } void TChangesWithAppend::DoWriteIndexOnComplete(NColumnShard::TColumnShard* self, TWriteIndexCompleteContext& context) { if (self) { + TStringBuilder sb; for (auto& portionBuilder : AppendedPortions) { auto& portionInfo = portionBuilder.GetPortionResult(); - switch (portionInfo.GetMeta().Produced) { + sb << portionInfo.GetPortionInfo().GetPortionId() << ","; + switch (portionInfo.GetPortionInfo().GetMeta().Produced) { case NOlap::TPortionMeta::EProduced::UNSPECIFIED: Y_ABORT_UNLESS(false); // unexpected case NOlap::TPortionMeta::EProduced::INSERTED: @@ -58,46 +77,61 @@ void TChangesWithAppend::DoWriteIndexOnComplete(NColumnShard::TColumnShard* self break; } } + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("portions", sb)("task_id", GetTaskIdentifier()); self->Counters.GetTabletCounters()->IncCounter(NColumnShard::COUNTER_PORTIONS_DEACTIVATED, PortionsToRemove.size()); - THashSet blobsDeactivated; for (auto& [_, portionInfo] : PortionsToRemove) { - for (auto& rec : portionInfo.Records) { - blobsDeactivated.emplace(portionInfo.GetBlobId(rec.BlobRange.GetBlobIdxVerified())); + self->Counters.GetTabletCounters()->IncCounter(NColumnShard::COUNTER_BLOBS_DEACTIVATED, portionInfo->GetBlobIdsCount()); + for (auto& blobId : portionInfo->GetBlobIds()) { + self->Counters.GetTabletCounters()->IncCounter(NColumnShard::COUNTER_BYTES_DEACTIVATED, blobId.BlobSize()); } - self->Counters.GetTabletCounters()->IncCounter(NColumnShard::COUNTER_RAW_BYTES_DEACTIVATED, portionInfo.GetTotalRawBytes()); + self->Counters.GetTabletCounters()->IncCounter(NColumnShard::COUNTER_RAW_BYTES_DEACTIVATED, portionInfo->GetTotalRawBytes()); } - self->Counters.GetTabletCounters()->IncCounter(NColumnShard::COUNTER_BLOBS_DEACTIVATED, blobsDeactivated.size()); - for (auto& blobId : blobsDeactivated) { - self->Counters.GetTabletCounters()->IncCounter(NColumnShard::COUNTER_BYTES_DEACTIVATED, blobId.BlobSize()); + } + if (PortionsToMove.size()) { + THashMap portionGroups; + for (auto&& [_, i] : PortionsToMove) { + portionGroups[i->GetMeta().GetCompactionLevel()].AddPortion(i); + } + NChanges::TGeneralCompactionCounters::OnMovePortionsByLevel(portionGroups, TargetCompactionLevel.value_or(0)); + for (auto&& [_, i] : PortionsToMove) { + const auto pred = [&](const std::shared_ptr& portion) { + portion->MutableMeta().ResetCompactionLevel(TargetCompactionLevel.value_or(0)); + }; + context.EngineLogs.ModifyPortionOnComplete(i, pred); } } { auto g = context.EngineLogs.GranulesStorage->GetStats()->StartPackModification(); - for (auto& [_, portionInfo] : PortionsToRemove) { - context.EngineLogs.AddCleanupPortion(portionInfo); - const TPortionInfo& oldInfo = context.EngineLogs.GetGranuleVerified(portionInfo.GetPathId()).GetPortionVerified(portionInfo.GetPortion()); - context.EngineLogs.UpsertPortion(portionInfo, &oldInfo); + for (auto&& [_, i] : PortionsToRemove) { + Y_ABORT_UNLESS(!i->HasRemoveSnapshot()); + const auto pred = [&](const std::shared_ptr& portion) { + portion->SetRemoveSnapshot(context.Snapshot); + }; + context.EngineLogs.ModifyPortionOnComplete(i, pred); + context.EngineLogs.AddCleanupPortion(i); } for (auto& portionBuilder : AppendedPortions) { - context.EngineLogs.UpsertPortion(portionBuilder.GetPortionResult()); + context.EngineLogs.AppendPortion(portionBuilder.GetPortionResult()); } } } void TChangesWithAppend::DoCompile(TFinalizationContext& context) { + AFL_VERIFY(PortionsToRemove.size() + PortionsToMove.size() + AppendedPortions.size() || NoAppendIsCorrect); for (auto&& i : AppendedPortions) { - i.GetPortionConstructor().SetPortionId(context.NextPortionId()); - } - for (auto& [_, portionInfo] : PortionsToRemove) { - portionInfo.SetRemoveSnapshot(context.GetSnapshot()); + i.GetPortionConstructor().MutablePortionConstructor().SetPortionId(context.NextPortionId()); + i.GetPortionConstructor().MutablePortionConstructor().MutableMeta().SetCompactionLevel(TargetCompactionLevel.value_or(0)); } } void TChangesWithAppend::DoOnAfterCompile() { - for (auto&& i : AppendedPortions) { - i.FinalizePortionConstructor(); + if (AppendedPortions.size()) { + for (auto&& i : AppendedPortions) { + i.GetPortionConstructor().MutablePortionConstructor().MutableMeta().SetCompactionLevel(TargetCompactionLevel.value_or(0)); + i.FinalizePortionConstructor(); + } } } diff --git a/ydb/core/tx/columnshard/engines/changes/with_appended.h b/ydb/core/tx/columnshard/engines/changes/with_appended.h index e35dfbbe4acc..522f77ca260f 100644 --- a/ydb/core/tx/columnshard/engines/changes/with_appended.h +++ b/ydb/core/tx/columnshard/engines/changes/with_appended.h @@ -9,9 +9,19 @@ namespace NKikimr::NOlap { class TChangesWithAppend: public TColumnEngineChanges { private: using TBase = TColumnEngineChanges; - THashMap PortionsToRemove; + THashMap> PortionsToRemove; + THashMap> PortionsToMove; + protected: + std::vector AppendedPortions; + std::optional TargetCompactionLevel; TSaverContext SaverContext; + bool NoAppendIsCorrect = false; + + virtual void OnDataAccessorsInitialized(const TDataAccessorsInitializationContext& /*context*/) override { + + } + virtual void DoCompile(TFinalizationContext& context) override; virtual void DoOnAfterCompile() override; virtual void DoWriteIndexOnExecute(NColumnShard::TColumnShard* self, TWriteIndexContext& context) override; @@ -19,18 +29,27 @@ class TChangesWithAppend: public TColumnEngineChanges { virtual void DoStart(NColumnShard::TColumnShard& self) override; virtual void DoDebugString(TStringOutput& out) const override { - out << "remove=" << PortionsToRemove.size() << ";append=" << AppendedPortions.size() << ";"; + out << "remove=" << PortionsToRemove.size() << ";append=" << AppendedPortions.size() << ";move=" << PortionsToMove.size(); } virtual std::shared_ptr DoBuildDataLockImpl() const = 0; virtual std::shared_ptr DoBuildDataLock() const override final { auto actLock = DoBuildDataLockImpl(); + THashSet portions; + for (auto&& i : PortionsToRemove) { + AFL_VERIFY(portions.emplace(i.first).second); + } + for (auto&& i : PortionsToMove) { + AFL_VERIFY(portions.emplace(i.first).second); + } if (actLock) { - auto selfLock = std::make_shared(TypeString() + "::" + GetTaskIdentifier() + "::REMOVE", PortionsToRemove); - return std::make_shared(TypeString() + "::" + GetTaskIdentifier(), std::vector>({actLock, selfLock})); + auto selfLock = std::make_shared(TypeString() + "::" + GetTaskIdentifier() + "::REMOVE/MOVE", portions, GetLockCategory()); + return std::make_shared( + TypeString() + "::" + GetTaskIdentifier(), std::vector>({ actLock, selfLock })); } else { - auto selfLock = std::make_shared(TypeString() + "::" + GetTaskIdentifier(), PortionsToRemove); + auto selfLock = + std::make_shared(TypeString() + "::" + GetTaskIdentifier(), portions, GetLockCategory()); return selfLock; } } @@ -42,7 +61,23 @@ class TChangesWithAppend: public TColumnEngineChanges { } - const THashMap& GetPortionsToRemove() const { + const std::vector& GetAppendedPortions() const { + return AppendedPortions; + } + + std::vector& MutableAppendedPortions() { + return AppendedPortions; + } + + void AddMovePortions(const std::vector>& portions) { + for (auto&& i : portions) { + AFL_VERIFY(i); + AFL_VERIFY(PortionsToMove.emplace(i->GetAddress(), i).second)("portion_id", i->GetPortionId()); + PortionsToAccess->AddPortion(i); + } + } + + const THashMap& GetPortionsToRemove() const { return PortionsToRemove; } @@ -54,12 +89,18 @@ class TChangesWithAppend: public TColumnEngineChanges { return PortionsToRemove.size(); } - void AddPortionToRemove(const TPortionInfo& info) { - AFL_VERIFY(!info.HasRemoveSnapshot()); - AFL_VERIFY(PortionsToRemove.emplace(info.GetAddress(), info).second); + void SetTargetCompactionLevel(const ui64 level) { + TargetCompactionLevel = level; + } + + void AddPortionToRemove(const TPortionInfo::TConstPtr& info, const bool addIntoDataAccessRequest = true) { + AFL_VERIFY(!info->HasRemoveSnapshot()); + AFL_VERIFY(PortionsToRemove.emplace(info->GetAddress(), info).second); + if (addIntoDataAccessRequest) { + PortionsToAccess->AddPortion(info); + } } - std::vector AppendedPortions; virtual ui32 GetWritePortionsCount() const override { return AppendedPortions.size(); } @@ -72,4 +113,4 @@ class TChangesWithAppend: public TColumnEngineChanges { } }; -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/column_engine.cpp b/ydb/core/tx/columnshard/engines/column_engine.cpp index 0771ecaeec1d..bd6cf4925a4e 100644 --- a/ydb/core/tx/columnshard/engines/column_engine.cpp +++ b/ydb/core/tx/columnshard/engines/column_engine.cpp @@ -1,9 +1,12 @@ #include "column_engine.h" + #include "portions/portion_info.h" -#include -#include +#include #include +#include + +#include namespace NKikimr::NOlap { @@ -14,48 +17,50 @@ const std::shared_ptr& IColumnEngine::GetReplaceKey() const { ui64 IColumnEngine::GetMetadataLimit() { static const ui64 MemoryTotal = NSystemInfo::TotalMemorySize(); if (!HasAppData()) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("total", MemoryTotal); + AFL_TRACE(NKikimrServices::TX_COLUMNSHARD_WRITE)("total", MemoryTotal); return MemoryTotal * 0.3; } else if (AppDataVerified().ColumnShardConfig.GetIndexMetadataMemoryLimit().HasAbsoluteValue()) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("value", AppDataVerified().ColumnShardConfig.GetIndexMetadataMemoryLimit().GetAbsoluteValue()); + AFL_TRACE(NKikimrServices::TX_COLUMNSHARD_WRITE)( + "value", AppDataVerified().ColumnShardConfig.GetIndexMetadataMemoryLimit().GetAbsoluteValue()); return AppDataVerified().ColumnShardConfig.GetIndexMetadataMemoryLimit().GetAbsoluteValue(); } else { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("total", MemoryTotal)("kff", AppDataVerified().ColumnShardConfig.GetIndexMetadataMemoryLimit().GetTotalRatio()); + AFL_TRACE(NKikimrServices::TX_COLUMNSHARD_WRITE)("total", MemoryTotal)( + "kff", AppDataVerified().ColumnShardConfig.GetIndexMetadataMemoryLimit().GetTotalRatio()); return MemoryTotal * AppDataVerified().ColumnShardConfig.GetIndexMetadataMemoryLimit().GetTotalRatio(); } } -size_t TSelectInfo::NumChunks() const { - size_t records = 0; - for (auto& portionInfo : PortionsOrderedPK) { - records += portionInfo->NumChunks(); - } - return records; +void IColumnEngine::FetchDataAccessors(const std::shared_ptr& request) const { + AFL_VERIFY(!!request); + AFL_VERIFY(!request->IsEmpty()); + DoFetchDataAccessors(request); } TSelectInfo::TStats TSelectInfo::Stats() const { TStats out; - out.Portions = PortionsOrderedPK.size(); + out.Portions = Portions.size(); THashSet uniqBlob; - for (auto& portionInfo : PortionsOrderedPK) { - out.Records += portionInfo->NumChunks(); - out.Rows += portionInfo->NumRows(); - for (auto& rec : portionInfo->Records) { - out.Bytes += rec.BlobRange.Size; + for (auto& portionInfo : Portions) { + out.Rows += portionInfo->GetRecordsCount(); + for (auto& blobId : portionInfo->GetBlobIds()) { + out.Bytes += blobId.BlobSize(); } out.Blobs += portionInfo->GetBlobIdsCount(); } return out; } -void TSelectInfo::DebugStream(IOutputStream& out) { - if (PortionsOrderedPK.size()) { - out << "portions:"; - for (auto& portionInfo : PortionsOrderedPK) { - out << portionInfo->DebugString(); +TString TSelectInfo::DebugString() const { + TStringBuilder result; + result << "count:" << Portions.size() << ";"; + if (Portions.size()) { + result << "portions:"; + for (auto& portionInfo : Portions) { + result << portionInfo->DebugString(); } } + return result; } -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/column_engine.h b/ydb/core/tx/columnshard/engines/column_engine.h index 2c616c06e32d..b3beefc25aa9 100644 --- a/ydb/core/tx/columnshard/engines/column_engine.h +++ b/ydb/core/tx/columnshard/engines/column_engine.h @@ -8,20 +8,24 @@ #include "scheme/versions/versioned_index.h" #include +#include +#include +#include namespace NKikimr::NColumnShard { class TTiersManager; -class TTtl; } // namespace NKikimr::NColumnShard namespace NKikimr::NOlap { class TInsertColumnEngineChanges; +class TDataAccessorsRequest; class TCompactColumnEngineChanges; class TColumnEngineChanges; class TTTLColumnEngineChanges; class TCleanupPortionsColumnEngineChanges; class TCleanupTablesColumnEngineChanges; class TPortionInfo; +class TDataAccessorsRequest; namespace NDataLocks { class TManager; } @@ -29,14 +33,12 @@ class TManager; struct TSelectInfo { struct TStats { size_t Portions{}; - size_t Records{}; size_t Blobs{}; size_t Rows{}; size_t Bytes{}; const TStats& operator+=(const TStats& stats) { Portions += stats.Portions; - Records += stats.Records; Blobs += stats.Blobs; Rows += stats.Rows; Bytes += stats.Bytes; @@ -44,13 +46,11 @@ struct TSelectInfo { } }; - std::vector> PortionsOrderedPK; - - size_t NumChunks() const; + std::vector> Portions; TStats Stats() const; - void DebugStream(IOutputStream& out); + TString DebugString() const; }; class TColumnEngineStats { @@ -74,11 +74,11 @@ class TColumnEngineStats { i64 Rows = 0; i64 Bytes = 0; i64 RawBytes = 0; - THashMap BytesByColumn; - THashMap RawBytesByColumn; + std::vector ByChannel; TString DebugString() const { - return TStringBuilder() << "portions=" << Portions << ";blobs=" << Blobs << ";rows=" << Rows << ";bytes=" << Bytes << ";raw_bytes=" << RawBytes << ";"; + return TStringBuilder() << "portions=" << Portions << ";blobs=" << Blobs << ";rows=" << Rows << ";bytes=" << Bytes + << ";raw_bytes=" << RawBytes << ";"; } TPortionsStats operator+(const TPortionsStats& item) const { @@ -93,13 +93,9 @@ class TColumnEngineStats { result.Rows = kff * Rows; result.Bytes = kff * Bytes; result.RawBytes = kff * RawBytes; - - for (auto&& i : BytesByColumn) { - result.BytesByColumn[i.first] = kff * i.second; - } - - for (auto&& i : RawBytesByColumn) { - result.RawBytesByColumn[i.first] = kff * i.second; + result.ByChannel.reserve(ByChannel.size()); + for (ui64 channelBytes: ByChannel) { + result.ByChannel.push_back(channelBytes * kff); } return result; } @@ -114,21 +110,17 @@ class TColumnEngineStats { Rows = SumVerifiedPositive(Rows, item.Rows); Bytes = SumVerifiedPositive(Bytes, item.Bytes); RawBytes = SumVerifiedPositive(RawBytes, item.RawBytes); - for (auto&& i : item.BytesByColumn) { - auto& v = BytesByColumn[i.first]; - v = SumVerifiedPositive(v, i.second); + if (ByChannel.size() < item.ByChannel.size()) { + ByChannel.resize(item.ByChannel.size()); } - - for (auto&& i : item.RawBytesByColumn) { - auto& v = RawBytesByColumn[i.first]; - v = SumVerifiedPositive(v, i.second); + for (ui32 ch = 0; ch < item.ByChannel.size(); ch++) { + ByChannel[ch] = SumVerifiedPositive(ByChannel[ch], item.ByChannel[ch]); } return *this; } }; i64 Tables{}; - i64 ColumnRecords{}; THashMap StatsByType; std::vector GetKinds() const { @@ -258,37 +250,114 @@ class TColumnEngineStats { } }; +class TColumnEngineForLogs; +class IMetadataAccessorResultProcessor { +private: + virtual void DoApplyResult(NResourceBroker::NSubscribe::TResourceContainer&& result, TColumnEngineForLogs& engine) = 0; + +public: + virtual ~IMetadataAccessorResultProcessor() = default; + + void ApplyResult(NResourceBroker::NSubscribe::TResourceContainer&& result, TColumnEngineForLogs& engine) { + return DoApplyResult(std::move(result), engine); + } + + IMetadataAccessorResultProcessor() = default; +}; + +class TCSMetadataRequest { +private: + YDB_READONLY_DEF(std::shared_ptr, Request); + YDB_READONLY_DEF(std::shared_ptr, Processor); + +public: + TCSMetadataRequest(const std::shared_ptr& request, const std::shared_ptr& processor) + : Request(request) + , Processor(processor) { + AFL_VERIFY(Request); + AFL_VERIFY(Processor); + } +}; + class IColumnEngine { protected: virtual void DoRegisterTable(const ui64 pathId) = 0; + virtual void DoFetchDataAccessors(const std::shared_ptr& request) const = 0; public: + class TSchemaInitializationData { + private: + YDB_READONLY_DEF(std::optional, Schema); + YDB_READONLY_DEF(std::optional, Diff); + + public: + const NKikimrSchemeOp::TColumnTableSchema& GetSchemaVerified() const { + AFL_VERIFY(Schema); + return *Schema; + } + + TSchemaInitializationData( + const std::optional& schema, const std::optional& diff) + : Schema(schema) + , Diff(diff) { + AFL_VERIFY(Schema || Diff); + } + + TSchemaInitializationData(const NKikimrTxColumnShard::TSchemaPresetVersionInfo& info) { + if (info.HasSchema()) { + Schema = info.GetSchema(); + } + if (info.HasDiff()) { + Diff = info.GetDiff(); + } + } + + ui64 GetVersion() const { + if (Schema) { + return Schema->GetVersion(); + } + AFL_VERIFY(Diff); + return Diff->GetVersion(); + } + }; + + void FetchDataAccessors(const std::shared_ptr& request) const; + static ui64 GetMetadataLimit(); virtual ~IColumnEngine() = default; + virtual std::vector CollectMetadataRequests() const = 0; virtual const TVersionedIndex& GetVersionedIndex() const = 0; + virtual const std::shared_ptr& GetVersionedIndexReadonlyCopy() = 0; virtual std::shared_ptr CopyVersionedIndexPtr() const = 0; virtual const std::shared_ptr& GetReplaceKey() const; virtual bool HasDataInPathId(const ui64 pathId) const = 0; virtual bool ErasePathId(const ui64 pathId) = 0; - virtual bool Load(IDbWrapper& db) = 0; + virtual std::shared_ptr BuildLoader(const std::shared_ptr& dsGroupSelector) = 0; void RegisterTable(const ui64 pathId) { AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "RegisterTable")("path_id", pathId); return DoRegisterTable(pathId); } virtual bool IsOverloadedByMetadata(const ui64 limit) const = 0; - virtual std::shared_ptr Select(ui64 pathId, TSnapshot snapshot, const TPKRangesFilter& pkRangesFilter) const = 0; + virtual std::shared_ptr Select( + ui64 pathId, TSnapshot snapshot, const TPKRangesFilter& pkRangesFilter, const bool withUncommitted) const = 0; virtual std::shared_ptr StartInsert(std::vector&& dataToIndex) noexcept = 0; virtual std::shared_ptr StartCompaction(const std::shared_ptr& dataLocksManager) noexcept = 0; - virtual std::shared_ptr StartCleanupPortions(const TSnapshot& snapshot, const THashSet& pathsToDrop, const std::shared_ptr& dataLocksManager) noexcept = 0; + virtual ui64 GetCompactionPriority(const std::shared_ptr& dataLocksManager, const std::set& pathIds, + const std::optional waitingPriority) noexcept = 0; + virtual std::shared_ptr StartCleanupPortions(const TSnapshot& snapshot, + const THashSet& pathsToDrop, const std::shared_ptr& dataLocksManager) noexcept = 0; virtual std::shared_ptr StartCleanupTables(const THashSet& pathsToDrop) noexcept = 0; - virtual std::vector> StartTtl(const THashMap& pathEviction, const std::shared_ptr& dataLocksManager, const ui64 memoryUsageLimit) noexcept = 0; + virtual std::vector> StartTtl(const THashMap& pathEviction, + const std::shared_ptr& dataLocksManager, const ui64 memoryUsageLimit) noexcept = 0; virtual bool ApplyChangesOnTxCreate(std::shared_ptr changes, const TSnapshot& snapshot) noexcept = 0; virtual bool ApplyChangesOnExecute(IDbWrapper& db, std::shared_ptr changes, const TSnapshot& snapshot) noexcept = 0; - virtual void RegisterSchemaVersion(const TSnapshot& snapshot, TIndexInfo&& info) = 0; - virtual void RegisterSchemaVersion(const TSnapshot& snapshot, const NKikimrSchemeOp::TColumnTableSchema& schema) = 0; + virtual void RegisterSchemaVersion(const TSnapshot& snapshot, const ui64 presetId, TIndexInfo&& info) = 0; + virtual void RegisterSchemaVersion(const TSnapshot& snapshot, const ui64 presetId, const TSchemaInitializationData& schema) = 0; + virtual void RegisterOldSchemaVersion(const TSnapshot& snapshot, const ui64 presetId, const TSchemaInitializationData& schema) = 0; + virtual const TMap>& GetStats() const = 0; virtual const TColumnEngineStats& GetTotalStats() = 0; virtual ui64 MemoryUsage() const { @@ -297,7 +366,8 @@ class IColumnEngine { virtual TSnapshot LastUpdate() const { return TSnapshot::Zero(); } - virtual void OnTieringModified(const std::shared_ptr& manager, const NColumnShard::TTtl& ttl, const std::optional pathId) = 0; + virtual void OnTieringModified(const std::optional& ttl, const ui64 pathId) = 0; + virtual void OnTieringModified(const THashMap& ttl) = 0; }; } // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/column_engine_logs.cpp b/ydb/core/tx/columnshard/engines/column_engine_logs.cpp index 95f9a41aa050..aa588965e964 100644 --- a/ydb/core/tx/columnshard/engines/column_engine_logs.cpp +++ b/ydb/core/tx/columnshard/engines/column_engine_logs.cpp @@ -2,51 +2,56 @@ #include "filter.h" #include "changes/actualization/construction/context.h" -#include "changes/indexation.h" -#include "changes/general_compaction.h" #include "changes/cleanup_portions.h" #include "changes/cleanup_tables.h" +#include "changes/general_compaction.h" +#include "changes/indexation.h" #include "changes/ttl.h" -#include "portions/constructor.h" +#include "loading/stages.h" #include -#include -#include -#include #include +#include #include +#include +#include #include +#include #include #include -#include #include namespace NKikimr::NOlap { -TColumnEngineForLogs::TColumnEngineForLogs(ui64 tabletId, const std::shared_ptr& storagesManager, - const TSnapshot& snapshot, const NKikimrSchemeOp::TColumnTableSchema& schema) - : GranulesStorage(std::make_shared(SignalCounters, storagesManager)) +TColumnEngineForLogs::TColumnEngineForLogs(const ui64 tabletId, const std::shared_ptr& schemaCache, + const std::shared_ptr& dataAccessorsManager, + const std::shared_ptr& storagesManager, const TSnapshot& snapshot, const ui64 presetId, const TSchemaInitializationData& schema) + : GranulesStorage(std::make_shared(SignalCounters, dataAccessorsManager, storagesManager)) + , DataAccessorsManager(dataAccessorsManager) , StoragesManager(storagesManager) + , SchemaObjectsCache(schemaCache) , TabletId(tabletId) , LastPortion(0) - , LastGranule(0) -{ + , LastGranule(0) { ActualizationController = std::make_shared(); - RegisterSchemaVersion(snapshot, schema); + RegisterSchemaVersion(snapshot, presetId, schema); } -TColumnEngineForLogs::TColumnEngineForLogs(ui64 tabletId, const std::shared_ptr& storagesManager, - const TSnapshot& snapshot, TIndexInfo&& schema) - : GranulesStorage(std::make_shared(SignalCounters, storagesManager)) +TColumnEngineForLogs::TColumnEngineForLogs(const ui64 tabletId, const std::shared_ptr& schemaCache, + const std::shared_ptr& dataAccessorsManager, + const std::shared_ptr& storagesManager, const TSnapshot& snapshot, const ui64 presetId, TIndexInfo&& schema) + : GranulesStorage(std::make_shared(SignalCounters, dataAccessorsManager, storagesManager)) + , DataAccessorsManager(dataAccessorsManager) , StoragesManager(storagesManager) + , SchemaObjectsCache(schemaCache) , TabletId(tabletId) , LastPortion(0) , LastGranule(0) { ActualizationController = std::make_shared(); - RegisterSchemaVersion(snapshot, std::move(schema)); + RegisterSchemaVersion(snapshot, presetId, std::move(schema)); } const TMap>& TColumnEngineForLogs::GetStats() const { @@ -58,13 +63,14 @@ const TColumnEngineStats& TColumnEngineForLogs::GetTotalStats() { return Counters; } -void TColumnEngineForLogs::UpdatePortionStats(const TPortionInfo& portionInfo, EStatsUpdateType updateType, - const TPortionInfo* exPortionInfo) { +void TColumnEngineForLogs::UpdatePortionStats(const TPortionInfo& portionInfo, EStatsUpdateType updateType, const TPortionInfo* exPortionInfo) { if (IS_LOG_PRIORITY_ENABLED(NActors::NLog::PRI_DEBUG, NKikimrServices::TX_COLUMNSHARD)) { auto before = Counters.Active(); UpdatePortionStats(Counters, portionInfo, updateType, exPortionInfo); auto after = Counters.Active(); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "portion_stats_updated")("type", updateType)("path_id", portionInfo.GetPathId())("portion", portionInfo.GetPortionId())("before_size", before.Bytes)("after_size", after.Bytes)("before_rows", before.Rows)("after_rows", after.Rows); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "portion_stats_updated")("type", updateType)("path_id", portionInfo.GetPathId())( + "portion", portionInfo.GetPortionId())("before_size", before.Bytes)("after_size", after.Bytes)("before_rows", before.Rows)( + "after_rows", after.Rows); } else { UpdatePortionStats(Counters, portionInfo, updateType, exPortionInfo); } @@ -81,11 +87,7 @@ void TColumnEngineForLogs::UpdatePortionStats(const TPortionInfo& portionInfo, E TColumnEngineStats::TPortionsStats DeltaStats(const TPortionInfo& portionInfo) { TColumnEngineStats::TPortionsStats deltaStats; deltaStats.Bytes = 0; - for (auto& rec : portionInfo.Records) { - deltaStats.BytesByColumn[rec.ColumnId] += rec.BlobRange.Size; - deltaStats.RawBytesByColumn[rec.ColumnId] += rec.GetMeta().GetRawBytes(); - } - deltaStats.Rows = portionInfo.NumRows(); + deltaStats.Rows = portionInfo.GetRecordsCount(); deltaStats.Bytes = portionInfo.GetTotalBlobBytes(); deltaStats.RawBytes = portionInfo.GetTotalRawBytes(); deltaStats.Blobs = portionInfo.GetBlobIdsCount(); @@ -93,68 +95,76 @@ TColumnEngineStats::TPortionsStats DeltaStats(const TPortionInfo& portionInfo) { return deltaStats; } -void TColumnEngineForLogs::UpdatePortionStats(TColumnEngineStats& engineStats, const TPortionInfo& portionInfo, - EStatsUpdateType updateType, - const TPortionInfo* exPortionInfo) const { - ui64 columnRecords = portionInfo.Records.size(); +void TColumnEngineForLogs::UpdatePortionStats( + TColumnEngineStats& engineStats, const TPortionInfo& portionInfo, EStatsUpdateType updateType, const TPortionInfo* exPortionInfo) const { TColumnEngineStats::TPortionsStats deltaStats = DeltaStats(portionInfo); + ui64 totalBlobsSize = 0; + ui32 blobCount = portionInfo.GetBlobIdsCount(); + for (ui32 i = 0; i < blobCount; i++) { + const auto& blob = portionInfo.GetBlobId(i); + ui32 channel = blob.Channel(); + if (deltaStats.ByChannel.size() <= channel) { + deltaStats.ByChannel.resize(channel + 1); + } + deltaStats.ByChannel[channel] += blob.BlobSize(); + totalBlobsSize += blob.BlobSize(); + } + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "update_portion")("blobs_size", totalBlobsSize)("portion_bytes", deltaStats.Bytes)("portion_raw_bytes", deltaStats.RawBytes); + Y_ABORT_UNLESS(!exPortionInfo || exPortionInfo->GetMeta().Produced != TPortionMeta::EProduced::UNSPECIFIED); Y_ABORT_UNLESS(portionInfo.GetMeta().Produced != TPortionMeta::EProduced::UNSPECIFIED); - TColumnEngineStats::TPortionsStats& srcStats = exPortionInfo - ? (exPortionInfo->HasRemoveSnapshot() - ? engineStats.StatsByType[TPortionMeta::EProduced::INACTIVE] - : engineStats.StatsByType[exPortionInfo->GetMeta().Produced]) - : engineStats.StatsByType[portionInfo.GetMeta().Produced]; - TColumnEngineStats::TPortionsStats& stats = portionInfo.HasRemoveSnapshot() - ? engineStats.StatsByType[TPortionMeta::EProduced::INACTIVE] - : engineStats.StatsByType[portionInfo.GetMeta().Produced]; + TColumnEngineStats::TPortionsStats& srcStats = + exPortionInfo ? (exPortionInfo->HasRemoveSnapshot() ? engineStats.StatsByType[TPortionMeta::EProduced::INACTIVE] + : engineStats.StatsByType[exPortionInfo->GetMeta().Produced]) + : engineStats.StatsByType[portionInfo.GetMeta().Produced]; + TColumnEngineStats::TPortionsStats& stats = portionInfo.HasRemoveSnapshot() ? engineStats.StatsByType[TPortionMeta::EProduced::INACTIVE] + : engineStats.StatsByType[portionInfo.GetMeta().Produced]; const bool isErase = updateType == EStatsUpdateType::ERASE; const bool isAdd = updateType == EStatsUpdateType::ADD; - if (isErase) { // PortionsToDrop - engineStats.ColumnRecords -= columnRecords; - + if (isErase) { // PortionsToDrop stats -= deltaStats; - } else if (isAdd) { // Load || AppendedPortions - engineStats.ColumnRecords += columnRecords; - + } else if (isAdd) { // Load || AppendedPortions stats += deltaStats; - } else if (&srcStats != &stats || exPortionInfo) { // SwitchedPortions || PortionsToEvict + } else if (&srcStats != &stats || exPortionInfo) { // SwitchedPortions || PortionsToEvict stats += deltaStats; if (exPortionInfo) { srcStats -= DeltaStats(*exPortionInfo); - - engineStats.ColumnRecords += columnRecords - exPortionInfo->Records.size(); } else { srcStats -= deltaStats; } } } -void TColumnEngineForLogs::RegisterSchemaVersion(const TSnapshot& snapshot, TIndexInfo&& indexInfo) { +void TColumnEngineForLogs::RegisterSchemaVersion(const TSnapshot& snapshot, const ui64 presetId, TIndexInfo&& indexInfo) { + AFL_VERIFY(DataAccessorsManager); bool switchOptimizer = false; + bool switchAccessorsManager = false; if (!VersionedIndex.IsEmpty()) { const NOlap::TIndexInfo& lastIndexInfo = VersionedIndex.GetLastSchema()->GetIndexInfo(); Y_ABORT_UNLESS(lastIndexInfo.CheckCompatible(indexInfo)); switchOptimizer = !indexInfo.GetCompactionPlannerConstructor()->IsEqualTo(lastIndexInfo.GetCompactionPlannerConstructor()); + switchAccessorsManager = !indexInfo.GetMetadataManagerConstructor()->IsEqualTo(*lastIndexInfo.GetMetadataManagerConstructor()); } + const bool isCriticalScheme = indexInfo.GetSchemeNeedActualization(); - auto* indexInfoActual = VersionedIndex.AddIndex(snapshot, std::move(indexInfo)); + auto* indexInfoActual = VersionedIndex.AddIndex(snapshot, SchemaObjectsCache->UpsertIndexInfo(presetId, std::move(indexInfo))); if (isCriticalScheme) { - if (!ActualizationStarted) { - ActualizationStarted = true; - for (auto&& i : GranulesStorage->GetTables()) { - i.second->StartActualizationIndex(); - } - } + StartActualization({}); for (auto&& i : GranulesStorage->GetTables()) { i.second->RefreshScheme(); } } + if (switchAccessorsManager) { + NDataAccessorControl::TManagerConstructionContext context(DataAccessorsManager->GetTabletActorId(), true); + for (auto&& i : GranulesStorage->GetTables()) { + i.second->ResetAccessorsManager(indexInfoActual->GetMetadataManagerConstructor(), context); + } + } if (switchOptimizer) { for (auto&& i : GranulesStorage->GetTables()) { i.second->ResetOptimizer(indexInfoActual->GetCompactionPlannerConstructor(), StoragesManager, indexInfoActual->GetPrimaryKey()); @@ -162,40 +172,75 @@ void TColumnEngineForLogs::RegisterSchemaVersion(const TSnapshot& snapshot, TInd } } -void TColumnEngineForLogs::RegisterSchemaVersion(const TSnapshot& snapshot, const NKikimrSchemeOp::TColumnTableSchema& schema) { - std::optional indexInfoOptional = NOlap::TIndexInfo::BuildFromProto(schema, StoragesManager, SchemaObjectsCache); +void TColumnEngineForLogs::RegisterSchemaVersion(const TSnapshot& snapshot, const ui64 presetId, const TSchemaInitializationData& schema) { + AFL_VERIFY(VersionedIndex.IsEmpty() || schema.GetVersion() >= VersionedIndex.GetLastSchema()->GetVersion())("empty", VersionedIndex.IsEmpty())("current", schema.GetVersion())( + "last", VersionedIndex.GetLastSchema()->GetVersion()); + + std::optional indexInfoOptional; + if (schema.GetDiff()) { + AFL_VERIFY(!VersionedIndex.IsEmpty()); + + indexInfoOptional = NOlap::TIndexInfo::BuildFromProto( + *schema.GetDiff(), VersionedIndex.GetLastSchema()->GetIndexInfo(), StoragesManager, SchemaObjectsCache); + } else { + indexInfoOptional = NOlap::TIndexInfo::BuildFromProto(schema.GetSchemaVerified(), StoragesManager, SchemaObjectsCache); + } AFL_VERIFY(indexInfoOptional); - NOlap::TIndexInfo indexInfo = std::move(*indexInfoOptional); - RegisterSchemaVersion(snapshot, std::move(indexInfo)); + RegisterSchemaVersion(snapshot, presetId, std::move(*indexInfoOptional)); } -bool TColumnEngineForLogs::Load(IDbWrapper& db) { - Y_ABORT_UNLESS(!Loaded); - Loaded = true; - THashMap granuleToPathIdDecoder; - { - TMemoryProfileGuard g("TTxInit/LoadShardingInfo"); - if (!VersionedIndex.LoadShardingInfo(db)) { - return false; - } +void TColumnEngineForLogs::RegisterOldSchemaVersion(const TSnapshot& snapshot, const ui64 presetId, const TSchemaInitializationData& schema) { + AFL_VERIFY(!VersionedIndex.IsEmpty()); + + ui64 version = schema.GetVersion(); + + ISnapshotSchema::TPtr prevSchema = VersionedIndex.GetLastSchemaBeforeOrEqualSnapshotOptional(version); + + if (prevSchema && version == prevSchema->GetVersion()) { + // skip already registered version + return; } - { - auto guard = GranulesStorage->GetStats()->StartPackModification(); - if (!LoadColumns(db)) { - return false; - } - TMemoryProfileGuard g("TTxInit/LoadCounters"); - if (!LoadCounters(db)) { - return false; + ISnapshotSchema::TPtr secondLast = + VersionedIndex.GetLastSchemaBeforeOrEqualSnapshotOptional(VersionedIndex.GetLastSchema()->GetVersion() - 1); + + AFL_VERIFY(!secondLast || secondLast->GetVersion() <= version)("reason", "incorrect schema registration order"); + + std::optional indexInfoOptional; + if (schema.GetDiff()) { + AFL_VERIFY(prevSchema)("reason", "no base schema to apply diff for"); + + indexInfoOptional = + NOlap::TIndexInfo::BuildFromProto(*schema.GetDiff(), prevSchema->GetIndexInfo(), StoragesManager, SchemaObjectsCache); + } else { + indexInfoOptional = NOlap::TIndexInfo::BuildFromProto(schema.GetSchemaVerified(), StoragesManager, SchemaObjectsCache); + } + + AFL_VERIFY(indexInfoOptional); + VersionedIndex.AddIndex(snapshot, SchemaObjectsCache->UpsertIndexInfo(presetId, std::move(*indexInfoOptional))); +} + +std::shared_ptr TColumnEngineForLogs::BuildLoader(const std::shared_ptr& dsGroupSelector) { + auto result = std::make_shared("column_engines"); + result->AddChildren(std::make_shared("counters", this, dsGroupSelector)); + result->AddChildren(std::make_shared("sharding_info", this, dsGroupSelector)); + if (GranulesStorage->GetTables().size()) { + auto granules = std::make_shared("granules"); + for (auto&& i : GranulesStorage->GetTables()) { + granules->AddChildren(i.second->BuildLoader(dsGroupSelector, VersionedIndex)); } + result->AddChildren(granules); } + result->AddChildren(std::make_shared("finish", this)); + return result; +} +bool TColumnEngineForLogs::FinishLoading() { for (const auto& [pathId, spg] : GranulesStorage->GetTables()) { for (const auto& [_, portionInfo] : spg->GetPortions()) { UpdatePortionStats(*portionInfo, EStatsUpdateType::ADD); if (portionInfo->CheckForCleanup()) { - AddCleanupPortion(*portionInfo); + AddCleanupPortion(portionInfo); } } } @@ -205,78 +250,6 @@ bool TColumnEngineForLogs::Load(IDbWrapper& db) { return true; } -bool TColumnEngineForLogs::LoadColumns(IDbWrapper& db) { - TPortionConstructors constructors; - { - TMemoryProfileGuard g("TTxInit/LoadColumns/Portions"); - if (!db.LoadPortions([&](TPortionInfoConstructor&& portion, const NKikimrTxColumnShard::TIndexPortionMeta& metaProto) { - const TIndexInfo& indexInfo = portion.GetSchema(VersionedIndex)->GetIndexInfo(); - AFL_VERIFY(portion.MutableMeta().LoadMetadata(metaProto, indexInfo)); - AFL_VERIFY(constructors.AddConstructorVerified(std::move(portion))); - })) { - return false; - } - } - - { - TMemoryProfileGuard g("TTxInit/LoadColumns/Records"); - TPortionInfo::TSchemaCursor schema(VersionedIndex); - if (!db.LoadColumns([&](TPortionInfoConstructor&& portion, const TColumnChunkLoadContext& loadContext) { - auto currentSchema = schema.GetSchema(portion); - auto* constructor = constructors.MergeConstructor(std::move(portion)); - constructor->LoadRecord(currentSchema->GetIndexInfo(), loadContext); - })) { - return false; - } - } - { - TMemoryProfileGuard g("TTxInit/LoadColumns/Indexes"); - if (!db.LoadIndexes([&](const ui64 pathId, const ui64 portionId, const TIndexChunkLoadContext& loadContext) { - auto* constructor = constructors.GetConstructorVerified(pathId, portionId); - constructor->LoadIndex(loadContext); - })) { - return false; - }; - } - { - TMemoryProfileGuard g("TTxInit/LoadColumns/Constructors"); - for (auto&& [granuleId, pathConstructors] : constructors) { - auto g = GetGranulePtrVerified(granuleId); - for (auto&& [portionId, constructor] : pathConstructors) { - g->UpsertPortionOnLoad(constructor.Build(false)); - } - } - } - { - TMemoryProfileGuard g("TTxInit/LoadColumns/After"); - for (auto&& i : GranulesStorage->GetTables()) { - i.second->OnAfterPortionsLoad(); - } - } - return true; -} - -bool TColumnEngineForLogs::LoadCounters(IDbWrapper& db) { - auto callback = [&](ui32 id, ui64 value) { - switch (id) { - case LAST_PORTION: - LastPortion = value; - break; - case LAST_GRANULE: - LastGranule = value; - break; - case LAST_PLAN_STEP: - LastSnapshot = TSnapshot(value, LastSnapshot.GetTxId()); - break; - case LAST_TX_ID: - LastSnapshot = TSnapshot(LastSnapshot.GetPlanStep(), value); - break; - } - }; - - return db.LoadCounters(callback); -} - std::shared_ptr TColumnEngineForLogs::StartInsert(std::vector&& dataToIndex) noexcept { Y_ABORT_UNLESS(dataToIndex.size()); @@ -293,13 +266,23 @@ std::shared_ptr TColumnEngineForLogs::StartInsert(st if (!data.GetRemove()) { AFL_VERIFY(changes->PathToGranule.emplace(pathId, GetGranulePtrVerified(pathId)->GetBucketPositions()).second); } - } return changes; } -std::shared_ptr TColumnEngineForLogs::StartCompaction(const std::shared_ptr& dataLocksManager) noexcept { +ui64 TColumnEngineForLogs::GetCompactionPriority(const std::shared_ptr& dataLocksManager, const std::set& pathIds, + const std::optional waitingPriority) noexcept { + auto priority = GranulesStorage->GetCompactionPriority(dataLocksManager, pathIds, waitingPriority); + if (!priority) { + return 0; + } else { + return priority->GetGeneralPriority(); + } +} + +std::shared_ptr TColumnEngineForLogs::StartCompaction( + const std::shared_ptr& dataLocksManager) noexcept { AFL_VERIFY(dataLocksManager); auto granule = GranulesStorage->GetGranuleForCompaction(dataLocksManager); if (!granule) { @@ -309,7 +292,8 @@ std::shared_ptr TColumnEngineForLogs::StartCompaction(cons granule->OnStartCompaction(); auto changes = granule->GetOptimizationTask(granule, dataLocksManager); if (!changes) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "cannot build optimization task for granule that need compaction")("weight", granule->GetCompactionPriority().DebugString()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "cannot build optimization task for granule that need compaction")( + "weight", granule->GetCompactionPriority().DebugString()); } return changes; } @@ -337,41 +321,46 @@ std::shared_ptr TColumnEngineForLogs::StartCl return changes; } -std::shared_ptr TColumnEngineForLogs::StartCleanupPortions(const TSnapshot& snapshot, - const THashSet& pathsToDrop, const std::shared_ptr& dataLocksManager) noexcept { +std::shared_ptr TColumnEngineForLogs::StartCleanupPortions( + const TSnapshot& snapshot, const THashSet& pathsToDrop, const std::shared_ptr& dataLocksManager) noexcept { AFL_VERIFY(dataLocksManager); AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "StartCleanup")("portions_count", CleanupPortions.size()); - auto changes = std::make_shared(StoragesManager); - + std::shared_ptr changes = std::make_shared(StoragesManager); // Add all portions from dropped paths - ui64 txSize = 0; - const ui64 txSizeLimit = TGlobalLimits::TxWriteLimitBytes / 4; + ui64 portionsCount = 0; + ui64 chunksCount = 0; ui32 skipLocked = 0; ui32 portionsFromDrop = 0; bool limitExceeded = false; + const ui32 maxChunksCount = 500000; + const ui32 maxPortionsCount = 1000; for (ui64 pathId : pathsToDrop) { auto g = GranulesStorage->GetGranuleOptional(pathId); if (!g) { continue; } - + if (dataLocksManager->IsLocked(*g, NDataLocks::ELockCategory::Tables)) { + continue; + } for (auto& [portion, info] : g->GetPortions()) { if (info->CheckForCleanup()) { continue; } - if (dataLocksManager->IsLocked(*info)) { + if (dataLocksManager->IsLocked(*info, NDataLocks::ELockCategory::Cleanup)) { ++skipLocked; continue; } - if (txSize + info->GetTxVolume() < txSizeLimit || changes->PortionsToDrop.empty()) { - txSize += info->GetTxVolume(); + ++portionsCount; + chunksCount += info->GetApproxChunksCount(info->GetSchema(VersionedIndex)->GetColumnsCount()); + if ((portionsCount < maxPortionsCount && chunksCount < maxChunksCount) || changes->GetPortionsToDrop().empty()) { } else { limitExceeded = true; break; } - changes->PortionsToDrop.push_back(*info); + changes->AddPortionToRemove(info); ++portionsFromDrop; } + changes->AddTableToDrop(pathId); } const TInstant snapshotInstant = snapshot.GetPlanInstant(); @@ -382,19 +371,20 @@ std::shared_ptr TColumnEngineForLogs::Start break; } for (ui32 i = 0; i < it->second.size();) { - if (dataLocksManager->IsLocked(it->second[i])) { + if (dataLocksManager->IsLocked(it->second[i], NDataLocks::ELockCategory::Cleanup)) { ++skipLocked; ++i; continue; } - AFL_VERIFY(it->second[i].CheckForCleanup(snapshot))("p_snapshot", it->second[i].GetRemoveSnapshotOptional())("snapshot", snapshot); - if (txSize + it->second[i].GetTxVolume() < txSizeLimit || changes->PortionsToDrop.empty()) { - txSize += it->second[i].GetTxVolume(); + AFL_VERIFY(it->second[i]->CheckForCleanup(snapshot))("p_snapshot", it->second[i]->GetRemoveSnapshotOptional())("snapshot", snapshot); + ++portionsCount; + chunksCount += it->second[i]->GetApproxChunksCount(it->second[i]->GetSchema(VersionedIndex)->GetColumnsCount()); + if ((portionsCount < maxPortionsCount && chunksCount < maxChunksCount) || changes->GetPortionsToDrop().empty()) { } else { limitExceeded = true; break; } - changes->PortionsToDrop.push_back(std::move(it->second[i])); + changes->AddPortionToDrop(it->second[i]); if (i + 1 < it->second.size()) { it->second[i] = std::move(it->second.back()); } @@ -409,23 +399,24 @@ std::shared_ptr TColumnEngineForLogs::Start ++it; } } - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "StartCleanup") - ("portions_count", CleanupPortions.size())("portions_prepared", changes->PortionsToDrop.size())("drop", portionsFromDrop)("skip", skipLocked); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "StartCleanup")("portions_count", CleanupPortions.size())( + "portions_prepared", changes->GetPortionsToDrop().size())("drop", portionsFromDrop)("skip", skipLocked)("portions_counter", portionsCount)( + "chunks", chunksCount)("limit", limitExceeded)("max_portions", maxPortionsCount)("max_chunks", maxChunksCount); - if (changes->PortionsToDrop.empty()) { + if (changes->GetPortionsToDrop().empty()) { return nullptr; } return changes; } -std::vector> TColumnEngineForLogs::StartTtl(const THashMap& pathEviction, const std::shared_ptr& dataLocksManager, - const ui64 memoryUsageLimit) noexcept { +std::vector> TColumnEngineForLogs::StartTtl(const THashMap& pathEviction, + const std::shared_ptr& dataLocksManager, const ui64 memoryUsageLimit) noexcept { AFL_VERIFY(dataLocksManager); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "StartTtl")("external", pathEviction.size()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_ACTUALIZATION)("event", "StartTtl")("external", pathEviction.size()); TSaverContext saverContext(StoragesManager); - NActualizer::TTieringProcessContext context(memoryUsageLimit, saverContext, dataLocksManager, SignalCounters, ActualizationController); + NActualizer::TTieringProcessContext context(memoryUsageLimit, saverContext, dataLocksManager, VersionedIndex, SignalCounters, ActualizationController); const TDuration actualizationLag = NYDBTest::TControllers::GetColumnShardController()->GetActualizationTasksLag(); for (auto&& i : pathEviction) { auto g = GetGranuleOptional(i.first); @@ -448,10 +439,12 @@ std::vector> TColumnEngineForLogs::Star i.second->BuildActualizationTasks(context, actualizationLag); } } else { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "StartTtl")("skip", "not_ready_tiers"); + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_ACTUALIZATION)("event", "StartTtl")("skip", "not_ready_tiers"); } std::vector> result; + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_ACTUALIZATION)("event", "StartTtl")("rw_tasks_count", context.GetTasks().size()); for (auto&& i : context.GetTasks()) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_ACTUALIZATION)("event", "StartTtl")("rw", i.first.DebugString())("count", i.second.size()); for (auto&& t : i.second) { SignalCounters.OnActualizationTask(t.GetTask()->GetPortionsToEvictCount(), t.GetTask()->GetPortionsToRemoveSize()); result.emplace_back(t.GetTask()); @@ -466,7 +459,8 @@ bool TColumnEngineForLogs::ApplyChangesOnTxCreate(std::shared_ptr /*indexChanges*/, const TSnapshot& snapshot) noexcept { +bool TColumnEngineForLogs::ApplyChangesOnExecute( + IDbWrapper& db, std::shared_ptr /*indexChanges*/, const TSnapshot& snapshot) noexcept { db.WriteCounter(LAST_PORTION, LastPortion); db.WriteCounter(LAST_GRANULE, LastGranule); @@ -478,19 +472,18 @@ bool TColumnEngineForLogs::ApplyChangesOnExecute(IDbWrapper& db, std::shared_ptr return true; } -void TColumnEngineForLogs::UpsertPortion(const TPortionInfo& portionInfo, const TPortionInfo* exInfo) { - if (exInfo) { - UpdatePortionStats(portionInfo, EStatsUpdateType::DEFAULT, exInfo); - } else { - UpdatePortionStats(portionInfo, EStatsUpdateType::ADD); +void TColumnEngineForLogs::AppendPortion(const TPortionDataAccessor& portionInfo, const bool addAsAccessor) { + auto granule = GetGranulePtrVerified(portionInfo.GetPortionInfo().GetPathId()); + AFL_VERIFY(!granule->GetPortionOptional(portionInfo.GetPortionInfo().GetPortionId())); + UpdatePortionStats(portionInfo.GetPortionInfo(), EStatsUpdateType::ADD); + granule->AppendPortion(portionInfo, addAsAccessor); + if (portionInfo.GetPortionInfo().HasRemoveSnapshot()) { + AddCleanupPortion(portionInfo.GetPortionInfoPtr()); } - - GetGranulePtrVerified(portionInfo.GetPathId())->UpsertPortion(portionInfo); } bool TColumnEngineForLogs::ErasePortion(const TPortionInfo& portionInfo, bool updateStats) { - Y_ABORT_UNLESS(!portionInfo.Empty()); - const ui64 portion = portionInfo.GetPortion(); + const ui64 portion = portionInfo.GetPortionId(); auto& spg = MutableGranuleVerified(portionInfo.GetPathId()); auto p = spg.GetPortionOptional(portion); @@ -506,66 +499,82 @@ bool TColumnEngineForLogs::ErasePortion(const TPortionInfo& portionInfo, bool up } } -std::shared_ptr TColumnEngineForLogs::Select(ui64 pathId, TSnapshot snapshot, - const TPKRangesFilter& pkRangesFilter) const { +std::shared_ptr TColumnEngineForLogs::Select( + ui64 pathId, TSnapshot snapshot, const TPKRangesFilter& pkRangesFilter, const bool withUncommitted) const { auto out = std::make_shared(); auto spg = GranulesStorage->GetGranuleOptional(pathId); if (!spg) { return out; } + for (const auto& [_, portionInfo] : spg->GetInsertedPortions()) { + AFL_VERIFY(portionInfo->HasInsertWriteId()); + if (withUncommitted) { + if (!portionInfo->IsVisible(snapshot, !withUncommitted)) { + continue; + } + } else if (!portionInfo->HasCommitSnapshot()) { + continue; + } + const bool skipPortion = !pkRangesFilter.IsPortionInUsage(*portionInfo); + AFL_TRACE(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", skipPortion ? "portion_skipped" : "portion_selected")("pathId", pathId)( + "portion", portionInfo->DebugString()); + if (skipPortion) { + continue; + } + out->Portions.emplace_back(portionInfo); + } for (const auto& [_, portionInfo] : spg->GetPortions()) { - if (!portionInfo->IsVisible(snapshot)) { + if (!portionInfo->IsVisible(snapshot, !withUncommitted)) { continue; } - Y_ABORT_UNLESS(portionInfo->Produced()); const bool skipPortion = !pkRangesFilter.IsPortionInUsage(*portionInfo); AFL_TRACE(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", skipPortion ? "portion_skipped" : "portion_selected")("pathId", pathId)( "portion", portionInfo->DebugString()); if (skipPortion) { continue; } - out->PortionsOrderedPK.emplace_back(portionInfo); + out->Portions.emplace_back(portionInfo); } return out; } -void TColumnEngineForLogs::OnTieringModified(const std::shared_ptr& manager, const NColumnShard::TTtl& ttl, const std::optional pathId) { - if (!ActualizationStarted) { - for (auto&& i : GranulesStorage->GetTables()) { - i.second->StartActualizationIndex(); +bool TColumnEngineForLogs::StartActualization(const THashMap& specialPathEviction) { + if (ActualizationStarted) { + return false; + } + for (auto&& i : GranulesStorage->GetTables()) { + i.second->StartActualizationIndex(); + } + for (auto&& i : specialPathEviction) { + auto g = GetGranuleOptional(i.first); + if (g) { + g->RefreshTiering(i.second); } } - ActualizationStarted = true; - AFL_VERIFY(manager); - THashMap tierings = manager->GetTiering(); - ttl.AddTtls(tierings); + return true; +} +void TColumnEngineForLogs::OnTieringModified(const std::optional& ttl, const ui64 pathId) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "OnTieringModified")("path_id", pathId); + StartActualization({}); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "OnTieringModified") - ("new_count_tierings", tierings.size()) - ("new_count_ttls", ttl.PathsCount()); - // some string + auto g = GetGranulePtrVerified(pathId); + g->RefreshTiering(ttl); +} +void TColumnEngineForLogs::OnTieringModified(const THashMap& ttl) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "OnTieringModified")("new_count_tierings", ttl.size()); + StartActualization({}); - if (pathId) { - auto g = GetGranulePtrVerified(*pathId); - auto it = tierings.find(*pathId); - if (it == tierings.end()) { + for (auto&& [gPathId, g] : GranulesStorage->GetTables()) { + auto it = ttl.find(gPathId); + if (it == ttl.end()) { g->RefreshTiering({}); } else { g->RefreshTiering(it->second); } - } else { - for (auto&& [gPathId, g] : GranulesStorage->GetTables()) { - auto it = tierings.find(gPathId); - if (it == tierings.end()) { - g->RefreshTiering({}); - } else { - g->RefreshTiering(it->second); - } - } } } @@ -577,4 +586,46 @@ void TColumnEngineForLogs::DoRegisterTable(const ui64 pathId) { } } -} // namespace NKikimr::NOlap +bool TColumnEngineForLogs::TestingLoad(IDbWrapper& db) { + { + TMemoryProfileGuard g("TTxInit/LoadShardingInfo"); + if (!VersionedIndex.LoadShardingInfo(db)) { + return false; + } + } + + { + auto guard = GranulesStorage->GetStats()->StartPackModification(); + for (auto&& [_, i] : GranulesStorage->GetTables()) { + i->TestingLoad(db, VersionedIndex); + } + if (!LoadCounters(db)) { + return false; + } + FinishLoading(); + } + return true; +} + +bool TColumnEngineForLogs::LoadCounters(IDbWrapper& db) { + auto callback = [&](ui32 id, ui64 value) { + switch (id) { + case LAST_PORTION: + LastPortion = value; + break; + case LAST_GRANULE: + LastGranule = value; + break; + case LAST_PLAN_STEP: + LastSnapshot = TSnapshot(value, LastSnapshot.GetTxId()); + break; + case LAST_TX_ID: + LastSnapshot = TSnapshot(LastSnapshot.GetPlanStep(), value); + break; + } + }; + + return db.LoadCounters(callback); +} + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/column_engine_logs.h b/ydb/core/tx/columnshard/engines/column_engine_logs.h index 7b515c26f40c..b62ce870dc30 100644 --- a/ydb/core/tx/columnshard/engines/column_engine_logs.h +++ b/ydb/core/tx/columnshard/engines/column_engine_logs.h @@ -1,17 +1,18 @@ #pragma once -#include "defs.h" #include "column_engine.h" -#include -#include -#include -#include +#include "defs.h" #include "changes/actualization/controller/controller.h" - #include "scheme/tier_info.h" -#include "storage/granule.h" -#include "storage/storage.h" +#include "storage/granule/granule.h" +#include "storage/granule/storage.h" + +#include +#include +#include +#include +#include namespace NKikimr::NArrow { struct TSortDescription; @@ -29,6 +30,10 @@ class TCleanupTablesColumnEngineChanges; namespace NDataSharing { class TDestinationSession; } +namespace NEngineLoading { +class TEngineShardingInfoReader; +class TEngineCountersReader; +} struct TReadMetadata; @@ -37,7 +42,7 @@ struct TReadMetadata; /// - Columns: granule -> blobs /// /// @note One instance per tablet. -class TColumnEngineForLogs : public IColumnEngine { +class TColumnEngineForLogs: public IColumnEngine { friend class TCompactColumnEngineChanges; friend class TTTLColumnEngineChanges; friend class TChangesWithAppend; @@ -45,17 +50,29 @@ class TColumnEngineForLogs : public IColumnEngine { friend class TCleanupPortionsColumnEngineChanges; friend class TCleanupTablesColumnEngineChanges; friend class NDataSharing::TDestinationSession; + friend class NEngineLoading::TEngineShardingInfoReader; + friend class NEngineLoading::TEngineCountersReader; private: bool ActualizationStarted = false; const NColumnShard::TEngineLogsCounters SignalCounters; std::shared_ptr GranulesStorage; + std::shared_ptr DataAccessorsManager; std::shared_ptr StoragesManager; std::shared_ptr ActualizationController; - std::shared_ptr SchemaObjectsCache = std::make_shared(); + std::shared_ptr SchemaObjectsCache; + TVersionedIndex VersionedIndex; + std::shared_ptr VersionedIndexCopy; public: + virtual const std::shared_ptr& GetVersionedIndexReadonlyCopy() override { + if (!VersionedIndexCopy || !VersionedIndexCopy->IsEqualTo(VersionedIndex)) { + VersionedIndexCopy = std::make_shared(VersionedIndex); + } + return VersionedIndexCopy; + } + const std::shared_ptr& GetActualizationController() const { return ActualizationController; } @@ -81,10 +98,16 @@ class TColumnEngineForLogs : public IColumnEngine { ADD, }; - TColumnEngineForLogs(ui64 tabletId, const std::shared_ptr& storagesManager, const TSnapshot& snapshot, const NKikimrSchemeOp::TColumnTableSchema& schema); - TColumnEngineForLogs(ui64 tabletId, const std::shared_ptr& storagesManager, const TSnapshot& snapshot, TIndexInfo&& schema); + TColumnEngineForLogs(const ui64 tabletId, const std::shared_ptr& schemaCache, + const std::shared_ptr& dataAccessorsManager, + const std::shared_ptr& storagesManager, const TSnapshot& snapshot, const ui64 presetId, + const TSchemaInitializationData& schema); + TColumnEngineForLogs(const ui64 tabletId, const std::shared_ptr& schemaCache, + const std::shared_ptr& dataAccessorsManager, + const std::shared_ptr& storagesManager, const TSnapshot& snapshot, const ui64 presetId, TIndexInfo&& schema); - virtual void OnTieringModified(const std::shared_ptr& manager, const NColumnShard::TTtl& ttl, const std::optional pathId) override; + void OnTieringModified(const std::optional& ttl, const ui64 pathId) override; + void OnTieringModified(const THashMap& ttl) override; virtual std::shared_ptr CopyVersionedIndexPtr() const override { return std::make_shared(VersionedIndex); @@ -101,30 +124,48 @@ class TColumnEngineForLogs : public IColumnEngine { } virtual void DoRegisterTable(const ui64 pathId) override; + void DoFetchDataAccessors(const std::shared_ptr& request) const override { + GranulesStorage->FetchDataAccessors(request); + } + + bool TestingLoadColumns(IDbWrapper& db); + bool LoadCounters(IDbWrapper& db); public: - bool Load(IDbWrapper& db) override; + virtual std::shared_ptr BuildLoader(const std::shared_ptr& dsGroupSelector) override; + bool FinishLoading(); + bool StartActualization(const THashMap& specialPathEviction); virtual bool IsOverloadedByMetadata(const ui64 limit) const override { return limit < TGranulesStat::GetSumMetadataMemoryPortionsSize(); } + virtual std::vector CollectMetadataRequests() const override { + return GranulesStorage->CollectMetadataRequests(); + } std::shared_ptr StartInsert(std::vector&& dataToIndex) noexcept override; + ui64 GetCompactionPriority(const std::shared_ptr& dataLocksManager, const std::set& pathIds, + const std::optional waitingPriority) noexcept override; std::shared_ptr StartCompaction(const std::shared_ptr& dataLocksManager) noexcept override; - std::shared_ptr StartCleanupPortions(const TSnapshot& snapshot, const THashSet& pathsToDrop, const std::shared_ptr& dataLocksManager) noexcept override; + std::shared_ptr StartCleanupPortions(const TSnapshot& snapshot, const THashSet& pathsToDrop, + const std::shared_ptr& dataLocksManager) noexcept override; std::shared_ptr StartCleanupTables(const THashSet& pathsToDrop) noexcept override; - std::vector> StartTtl(const THashMap& pathEviction, const std::shared_ptr& locksManager, const ui64 memoryUsageLimit) noexcept override; + std::vector> StartTtl(const THashMap& pathEviction, + const std::shared_ptr& locksManager, const ui64 memoryUsageLimit) noexcept override; void ReturnToIndexes(const THashMap>& portions) const { return GranulesStorage->ReturnToIndexes(portions); } virtual bool ApplyChangesOnTxCreate(std::shared_ptr indexChanges, const TSnapshot& snapshot) noexcept override; - virtual bool ApplyChangesOnExecute(IDbWrapper& db, std::shared_ptr indexChanges, const TSnapshot& snapshot) noexcept override; + virtual bool ApplyChangesOnExecute( + IDbWrapper& db, std::shared_ptr indexChanges, const TSnapshot& snapshot) noexcept override; - void RegisterSchemaVersion(const TSnapshot& snapshot, TIndexInfo&& info) override; - void RegisterSchemaVersion(const TSnapshot& snapshot, const NKikimrSchemeOp::TColumnTableSchema& schema) override; + void RegisterSchemaVersion(const TSnapshot& snapshot, const ui64 presetId, TIndexInfo&& info) override; + void RegisterSchemaVersion(const TSnapshot& snapshot, const ui64 presetId, const TSchemaInitializationData& schema) override; + void RegisterOldSchemaVersion(const TSnapshot& snapshot, const ui64 presetId, const TSchemaInitializationData& schema) override; - std::shared_ptr Select(ui64 pathId, TSnapshot snapshot, const TPKRangesFilter& pkRangesFilter) const override; + std::shared_ptr Select( + ui64 pathId, TSnapshot snapshot, const TPKRangesFilter& pkRangesFilter, const bool withUncommitted) const override; bool IsPortionExists(const ui64 pathId, const ui64 portionId) const { return !!GranulesStorage->GetPortionOptional(pathId, portionId); @@ -137,7 +178,6 @@ class TColumnEngineForLogs : public IColumnEngine { return GranulesStorage->EraseTable(pathId); } - virtual bool HasDataInPathId(const ui64 pathId) const override { auto g = GetGranuleOptional(pathId); return g && g->GetPortions().size(); @@ -173,18 +213,31 @@ class TColumnEngineForLogs : public IColumnEngine { return TabletId; } - void AddCleanupPortion(const TPortionInfo& info) { - CleanupPortions[info.GetRemoveSnapshotVerified().GetPlanInstant()].emplace_back(info); + void AddCleanupPortion(const TPortionInfo::TConstPtr& info) { + AFL_VERIFY(info->HasRemoveSnapshot()); + CleanupPortions[info->GetRemoveSnapshotVerified().GetPlanInstant()].emplace_back(info); } void AddShardingInfo(const TGranuleShardingInfo& shardingInfo) { VersionedIndex.AddShardingInfo(shardingInfo); } + bool TestingLoad(IDbWrapper& db); + + template + void ModifyPortionOnComplete(const TPortionInfo::TConstPtr& portion, const TModifier& modifier) { + auto exPortion = portion->MakeCopy(); + AFL_VERIFY(portion); + auto granule = GetGranulePtrVerified(portion->GetPathId()); + granule->ModifyPortionOnComplete(portion, modifier); + UpdatePortionStats(*portion, EStatsUpdateType::DEFAULT, &exPortion); + } + + void AppendPortion(const TPortionDataAccessor& portionInfo, const bool addAsAccessor = true); + private: - TVersionedIndex VersionedIndex; ui64 TabletId; TMap> PathStats; // per path_id stats sorted by path_id - std::map> CleanupPortions; + std::map> CleanupPortions; TColumnEngineStats Counters; ui64 LastPortion; ui64 LastGranule; @@ -192,14 +245,11 @@ class TColumnEngineForLogs : public IColumnEngine { bool Loaded = false; private: - bool LoadColumns(IDbWrapper& db); - bool LoadShardingInfo(IDbWrapper& db); - bool LoadCounters(IDbWrapper& db); - - void UpsertPortion(const TPortionInfo& portionInfo, const TPortionInfo* exInfo = nullptr); bool ErasePortion(const TPortionInfo& portionInfo, bool updateStats = true); - void UpdatePortionStats(const TPortionInfo& portionInfo, EStatsUpdateType updateType = EStatsUpdateType::DEFAULT, const TPortionInfo* exPortionInfo = nullptr); - void UpdatePortionStats(TColumnEngineStats& engineStats, const TPortionInfo& portionInfo, EStatsUpdateType updateType, const TPortionInfo* exPortionInfo = nullptr) const; + void UpdatePortionStats( + const TPortionInfo& portionInfo, EStatsUpdateType updateType = EStatsUpdateType::DEFAULT, const TPortionInfo* exPortionInfo = nullptr); + void UpdatePortionStats(TColumnEngineStats& engineStats, const TPortionInfo& portionInfo, EStatsUpdateType updateType, + const TPortionInfo* exPortionInfo = nullptr) const; }; } // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/column_features.cpp b/ydb/core/tx/columnshard/engines/column_features.cpp deleted file mode 100644 index 44faec99a703..000000000000 --- a/ydb/core/tx/columnshard/engines/column_features.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "column_features.h" diff --git a/ydb/core/tx/columnshard/engines/column_features.h b/ydb/core/tx/columnshard/engines/column_features.h deleted file mode 100644 index 053578affb0f..000000000000 --- a/ydb/core/tx/columnshard/engines/column_features.h +++ /dev/null @@ -1,2 +0,0 @@ -#pragma once -#include "scheme/column_features.h" diff --git a/ydb/core/tx/columnshard/engines/columns_table.h b/ydb/core/tx/columnshard/engines/columns_table.h deleted file mode 100644 index 0b1cd1bdc806..000000000000 --- a/ydb/core/tx/columnshard/engines/columns_table.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "db_wrapper.h" -#include "portions/column_record.h" - -#include - -namespace NKikimr::NOlap { - -} diff --git a/ydb/core/tx/columnshard/engines/db_wrapper.cpp b/ydb/core/tx/columnshard/engines/db_wrapper.cpp index b5c8e5e4ea58..60544f24190f 100644 --- a/ydb/core/tx/columnshard/engines/db_wrapper.cpp +++ b/ydb/core/tx/columnshard/engines/db_wrapper.cpp @@ -1,6 +1,9 @@ -#include "defs.h" #include "db_wrapper.h" -#include "portions/constructor.h" +#include "defs.h" + +#include "portions/constructor_portion.h" + +#include #include #include #include @@ -37,110 +40,164 @@ void TDbWrapper::EraseAborted(const TInsertedData& data) { NColumnShard::Schema::InsertTable_EraseAborted(db, data); } -bool TDbWrapper::Load(TInsertTableAccessor& insertTable, - const TInstant& loadTime) { +bool TDbWrapper::Load(TInsertTableAccessor& insertTable, const TInstant& loadTime) { NIceDb::TNiceDb db(Database); return NColumnShard::Schema::InsertTable_Load(db, DsGroupSelector, insertTable, loadTime); } void TDbWrapper::WriteColumn(const NOlap::TPortionInfo& portion, const TColumnRecord& row, const ui32 firstPKColumnId) { + if (!AppDataVerified().ColumnShardConfig.GetColumnChunksV1Usage() && !AppDataVerified().ColumnShardConfig.GetColumnChunksV0Usage()) { + return; + } NIceDb::TNiceDb db(Database); + using IndexColumnsV1 = NColumnShard::Schema::IndexColumnsV1; auto rowProto = row.GetMeta().SerializeToProto(); - if (row.GetChunkIdx() == 0 && row.GetColumnId() == firstPKColumnId) { - *rowProto.MutablePortionMeta() = portion.GetMeta().SerializeToProto(); + if (AppDataVerified().ColumnShardConfig.GetColumnChunksV1Usage()) { + db.Table() + .Key(portion.GetPathId(), portion.GetPortionId(), row.ColumnId, row.Chunk) + .Update(NIceDb::TUpdate(row.GetBlobRange().GetBlobIdxVerified()), + NIceDb::TUpdate(rowProto.SerializeAsString()), + NIceDb::TUpdate(row.BlobRange.Offset), NIceDb::TUpdate(row.BlobRange.Size)); + } + if (AppDataVerified().ColumnShardConfig.GetColumnChunksV0Usage()) { + if (row.GetChunkIdx() == 0 && row.GetColumnId() == firstPKColumnId) { + *rowProto.MutablePortionMeta() = portion.GetMeta().SerializeToProto(); + } + using IndexColumns = NColumnShard::Schema::IndexColumns; + auto removeSnapshot = portion.GetRemoveSnapshotOptional(); + db.Table() + .Key(0, 0, row.ColumnId, portion.GetMinSnapshotDeprecated().GetPlanStep(), portion.GetMinSnapshotDeprecated().GetTxId(), + portion.GetPortionId(), row.Chunk) + .Update(NIceDb::TUpdate(removeSnapshot ? removeSnapshot->GetPlanStep() : 0), + NIceDb::TUpdate(removeSnapshot ? removeSnapshot->GetTxId() : 0), + NIceDb::TUpdate(portion.GetBlobId(row.GetBlobRange().GetBlobIdxVerified()).SerializeBinary()), + NIceDb::TUpdate(rowProto.SerializeAsString()), + NIceDb::TUpdate(row.BlobRange.Offset), NIceDb::TUpdate(row.BlobRange.Size), + NIceDb::TUpdate(portion.GetPathId())); } - using IndexColumns = NColumnShard::Schema::IndexColumns; - auto removeSnapshot = portion.GetRemoveSnapshotOptional(); - db.Table().Key(0, 0, row.ColumnId, - portion.GetMinSnapshotDeprecated().GetPlanStep(), portion.GetMinSnapshotDeprecated().GetTxId(), portion.GetPortion(), row.Chunk).Update( - NIceDb::TUpdate(removeSnapshot ? removeSnapshot->GetPlanStep() : 0), - NIceDb::TUpdate(removeSnapshot ? removeSnapshot->GetTxId() : 0), - NIceDb::TUpdate(portion.GetBlobId(row.GetBlobRange().GetBlobIdxVerified()).SerializeBinary()), - NIceDb::TUpdate(rowProto.SerializeAsString()), - NIceDb::TUpdate(row.BlobRange.Offset), - NIceDb::TUpdate(row.BlobRange.Size), - NIceDb::TUpdate(portion.GetPathId()) - ); } void TDbWrapper::WritePortion(const NOlap::TPortionInfo& portion) { NIceDb::TNiceDb db(Database); auto metaProto = portion.GetMeta().SerializeToProto(); using IndexPortions = NColumnShard::Schema::IndexPortions; - auto removeSnapshot = portion.GetRemoveSnapshotOptional(); - db.Table().Key(portion.GetPathId(), portion.GetPortion()).Update( - NIceDb::TUpdate(portion.GetSchemaVersionVerified()), - NIceDb::TUpdate(portion.GetShardingVersionDef(0)), - NIceDb::TUpdate(removeSnapshot ? removeSnapshot->GetPlanStep() : 0), - NIceDb::TUpdate(removeSnapshot ? removeSnapshot->GetTxId() : 0), - NIceDb::TUpdate(metaProto.SerializeAsString())); + const auto removeSnapshot = portion.GetRemoveSnapshotOptional(); + const auto commitSnapshot = portion.GetCommitSnapshotOptional(); + const auto insertWriteId = portion.GetInsertWriteIdOptional(); + const auto minSnapshotDeprecated = portion.GetMinSnapshotDeprecated(); + db.Table() + .Key(portion.GetPathId(), portion.GetPortionId()) + .Update(NIceDb::TUpdate(portion.GetSchemaVersionVerified()), + NIceDb::TUpdate(portion.GetShardingVersionDef(0)), + NIceDb::TUpdate(commitSnapshot ? commitSnapshot->GetPlanStep() : 0), + NIceDb::TUpdate(commitSnapshot ? commitSnapshot->GetTxId() : 0), + NIceDb::TUpdate((ui64)insertWriteId.value_or(TInsertWriteId(0))), + NIceDb::TUpdate(removeSnapshot ? removeSnapshot->GetPlanStep() : 0), + NIceDb::TUpdate(removeSnapshot ? removeSnapshot->GetTxId() : 0), + NIceDb::TUpdate(minSnapshotDeprecated.GetPlanStep()), + NIceDb::TUpdate(minSnapshotDeprecated.GetTxId()), + NIceDb::TUpdate(metaProto.SerializeAsString())); } void TDbWrapper::ErasePortion(const NOlap::TPortionInfo& portion) { NIceDb::TNiceDb db(Database); - using IndexPortions = NColumnShard::Schema::IndexPortions; - db.Table().Key(portion.GetPathId(), portion.GetPortion()).Delete(); + db.Table().Key(portion.GetPathId(), portion.GetPortionId()).Delete(); + db.Table().Key(portion.GetPathId(), portion.GetPortionId()).Delete(); } void TDbWrapper::EraseColumn(const NOlap::TPortionInfo& portion, const TColumnRecord& row) { NIceDb::TNiceDb db(Database); - using IndexColumns = NColumnShard::Schema::IndexColumns; - db.Table().Key(0, 0, row.ColumnId, - portion.GetMinSnapshotDeprecated().GetPlanStep(), portion.GetMinSnapshotDeprecated().GetTxId(), portion.GetPortion(), row.Chunk).Delete(); + if (AppDataVerified().ColumnShardConfig.GetColumnChunksV1Usage()) { + using IndexColumnsV1 = NColumnShard::Schema::IndexColumnsV1; + db.Table().Key(portion.GetPathId(), portion.GetPortionId(), row.ColumnId, row.Chunk).Delete(); + } + if (AppDataVerified().ColumnShardConfig.GetColumnChunksV0Usage()) { + using IndexColumns = NColumnShard::Schema::IndexColumns; + db.Table() + .Key(0, 0, row.ColumnId, portion.GetMinSnapshotDeprecated().GetPlanStep(), portion.GetMinSnapshotDeprecated().GetTxId(), + portion.GetPortionId(), row.Chunk) + .Delete(); + } } -bool TDbWrapper::LoadColumns(const std::function& callback) { +bool TDbWrapper::LoadColumns(const std::optional pathId, const std::function& callback) { NIceDb::TNiceDb db(Database); - using IndexColumns = NColumnShard::Schema::IndexColumns; - auto rowset = db.Table().Prefix(0).Select(); - if (!rowset.IsReady()) { - return false; - } - - while (!rowset.EndOfSet()) { - NOlap::TSnapshot minSnapshot(rowset.GetValue(), rowset.GetValue()); - NOlap::TSnapshot removeSnapshot(rowset.GetValue(), rowset.GetValue()); - - NOlap::TPortionInfoConstructor constructor(rowset.GetValue(), rowset.GetValue()); - constructor.SetMinSnapshotDeprecated(minSnapshot); - constructor.SetRemoveSnapshot(removeSnapshot); + using IndexColumnsV2 = NColumnShard::Schema::IndexColumnsV2; + const auto pred = [&](auto& rowset) { + if (!rowset.IsReady()) { + return false; + } - NOlap::TColumnChunkLoadContext chunkLoadContext(rowset, DsGroupSelector); - callback(std::move(constructor), chunkLoadContext); + while (!rowset.EndOfSet()) { + NOlap::TColumnChunkLoadContextV2 chunkLoadContext(rowset); + callback(std::move(chunkLoadContext)); - if (!rowset.Next()) { - return false; + if (!rowset.Next()) { + return false; + } } + return true; + }; + if (pathId) { + auto rowset = db.Table().Prefix(*pathId).Select(); + return pred(rowset); + } else { + auto rowset = db.Table().Select(); + return pred(rowset); } - return true; } -bool TDbWrapper::LoadPortions(const std::function& callback) { +bool TDbWrapper::LoadPortions(const std::optional pathId, + const std::function& callback) { NIceDb::TNiceDb db(Database); using IndexPortions = NColumnShard::Schema::IndexPortions; - auto rowset = db.Table().Select(); - if (!rowset.IsReady()) { - return false; - } - - while (!rowset.EndOfSet()) { - NOlap::TPortionInfoConstructor portion(rowset.GetValue(), rowset.GetValue()); - portion.SetSchemaVersion(rowset.GetValue()); - if (rowset.HaveValue() && rowset.GetValue()) { - portion.SetShardingVersion(rowset.GetValue()); + const auto pred = [&](auto& rowset) { + if (!rowset.IsReady()) { + return false; } - portion.SetRemoveSnapshot(rowset.GetValue(), rowset.GetValue()); - NKikimrTxColumnShard::TIndexPortionMeta metaProto; - const TString metadata = rowset.template GetValue(); - AFL_VERIFY(metaProto.ParseFromArray(metadata.data(), metadata.size()))("event", "cannot parse metadata as protobuf"); - callback(std::move(portion), metaProto); + while (!rowset.EndOfSet()) { + NOlap::TPortionInfoConstructor portion( + rowset.template GetValue(), rowset.template GetValue()); + portion.SetSchemaVersion(rowset.template GetValue()); + if (rowset.template HaveValue() && rowset.template GetValue()) { + portion.SetShardingVersion(rowset.template GetValue()); + } + portion.SetRemoveSnapshot(rowset.template GetValue(), rowset.template GetValue()); + if (rowset.template GetValue()) { + portion.SetMinSnapshotDeprecated(TSnapshot( + rowset.template GetValue(), rowset.template GetValue())); + } - if (!rowset.Next()) { - return false; + if (rowset.template GetValueOrDefault(0)) { + portion.SetInsertWriteId((TInsertWriteId)rowset.template GetValue()); + } + if (rowset.template GetValueOrDefault(0)) { + AFL_VERIFY(rowset.template GetValueOrDefault(0)); + portion.SetCommitSnapshot( + TSnapshot(rowset.template GetValue(), rowset.template GetValue())); + } else { + AFL_VERIFY(!rowset.template GetValueOrDefault(0)); + } + + NKikimrTxColumnShard::TIndexPortionMeta metaProto; + const TString metadata = rowset.template GetValue(); + AFL_VERIFY(metaProto.ParseFromArray(metadata.data(), metadata.size()))("event", "cannot parse metadata as protobuf"); + callback(std::move(portion), metaProto); + + if (!rowset.Next()) { + return false; + } } + return true; + }; + if (pathId) { + auto rowset = db.Table().Prefix(*pathId).Select(); + return pred(rowset); + } else { + auto rowset = db.Table().Select(); + return pred(rowset); } - return true; } void TDbWrapper::WriteIndex(const TPortionInfo& portion, const TIndexChunk& row) { @@ -156,8 +213,8 @@ void TDbWrapper::WriteIndex(const TPortionInfo& portion, const TIndexChunk& row) } else if (auto bData = row.GetBlobDataOptional()) { db.Table() .Key(portion.GetPathId(), portion.GetPortionId(), row.GetIndexId(), row.GetChunkIdx()) - .Update(NIceDb::TUpdate(*bData), - NIceDb::TUpdate(row.GetRecordsCount()), NIceDb::TUpdate(row.GetRawBytes())); + .Update(NIceDb::TUpdate(*bData), NIceDb::TUpdate(row.GetRecordsCount()), + NIceDb::TUpdate(row.GetRawBytes())); } else { AFL_VERIFY(false); } @@ -169,23 +226,33 @@ void TDbWrapper::EraseIndex(const TPortionInfo& portion, const TIndexChunk& row) db.Table().Key(portion.GetPathId(), portion.GetPortionId(), row.GetIndexId(), 0).Delete(); } -bool TDbWrapper::LoadIndexes(const std::function& callback) { +bool TDbWrapper::LoadIndexes( + const std::optional pathId, const std::function& callback) { NIceDb::TNiceDb db(Database); using IndexIndexes = NColumnShard::Schema::IndexIndexes; - auto rowset = db.Table().Select(); - if (!rowset.IsReady()) { - return false; - } + const auto pred = [&](auto& rowset) { + if (!rowset.IsReady()) { + return false; + } - while (!rowset.EndOfSet()) { - NOlap::TIndexChunkLoadContext chunkLoadContext(rowset, DsGroupSelector); - callback(rowset.GetValue(), rowset.GetValue(), chunkLoadContext); + while (!rowset.EndOfSet()) { + NOlap::TIndexChunkLoadContext chunkLoadContext(rowset, DsGroupSelector); + callback(rowset.template GetValue(), rowset.template GetValue(), + std::move(chunkLoadContext)); - if (!rowset.Next()) { - return false; + if (!rowset.Next()) { + return false; + } } + return true; + }; + if (pathId) { + auto rowset = db.Table().Prefix(*pathId).Select(); + return pred(rowset); + } else { + auto rowset = db.Table().Select(); + return pred(rowset); } - return true; } void TDbWrapper::WriteCounter(ui32 counterId, ui64 value) { @@ -211,7 +278,8 @@ TConclusion>> TD snapshot.DeserializeFromString(rowset.GetValue()).Validate(); NSharding::TGranuleShardingLogicContainer logic; logic.DeserializeFromString(rowset.GetValue()).Validate(); - TGranuleShardingInfo gShardingInfo(logic, snapshot, rowset.GetValue(), rowset.GetValue()); + TGranuleShardingInfo gShardingInfo( + logic, snapshot, rowset.GetValue(), rowset.GetValue()); AFL_VERIFY(result[gShardingInfo.GetPathId()].emplace(gShardingInfo.GetSinceSnapshot(), gShardingInfo).second); if (!rowset.Next()) { @@ -221,4 +289,12 @@ TConclusion>> TD return result; } +void TDbWrapper::WriteColumns(const NOlap::TPortionInfo& portion, const NKikimrTxColumnShard::TIndexPortionAccessor& proto) { + NIceDb::TNiceDb db(Database); + using IndexColumnsV2 = NColumnShard::Schema::IndexColumnsV2; + db.Table() + .Key(portion.GetPathId(), portion.GetPortionId()) + .Update(NIceDb::TUpdate(proto.SerializeAsString())); } + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/db_wrapper.h b/ydb/core/tx/columnshard/engines/db_wrapper.h index 50958b6fca29..dacf770e78ea 100644 --- a/ydb/core/tx/columnshard/engines/db_wrapper.h +++ b/ydb/core/tx/columnshard/engines/db_wrapper.h @@ -14,7 +14,7 @@ class TDatabase; namespace NKikimr::NOlap { -class TColumnChunkLoadContext; +class TColumnChunkLoadContextV2; class TIndexChunkLoadContext; class TInsertedData; class TCommittedData; @@ -30,26 +30,36 @@ class IDbWrapper { public: virtual ~IDbWrapper() = default; + virtual const IBlobGroupSelector* GetDsGroupSelector() const = 0; + const IBlobGroupSelector& GetDsGroupSelectorVerified() const { + const auto* result = GetDsGroupSelector(); + AFL_VERIFY(result); + return *result; + } + virtual void Insert(const TInsertedData& data) = 0; virtual void Commit(const TCommittedData& data) = 0; virtual void Abort(const TInsertedData& data) = 0; virtual void EraseInserted(const TInsertedData& data) = 0; virtual void EraseCommitted(const TCommittedData& data) = 0; virtual void EraseAborted(const TInsertedData& data) = 0; + virtual void WriteColumns(const NOlap::TPortionInfo& portion, const NKikimrTxColumnShard::TIndexPortionAccessor& proto) = 0; virtual bool Load(TInsertTableAccessor& insertTable, const TInstant& loadTime) = 0; virtual void WriteColumn(const TPortionInfo& portion, const TColumnRecord& row, const ui32 firstPKColumnId) = 0; virtual void EraseColumn(const TPortionInfo& portion, const TColumnRecord& row) = 0; - virtual bool LoadColumns(const std::function& callback) = 0; + virtual bool LoadColumns(const std::optional pathId, const std::function& callback) = 0; virtual void WritePortion(const NOlap::TPortionInfo& portion) = 0; virtual void ErasePortion(const NOlap::TPortionInfo& portion) = 0; - virtual bool LoadPortions(const std::function& callback) = 0; + virtual bool LoadPortions(const std::optional pathId, + const std::function& callback) = 0; virtual void WriteIndex(const TPortionInfo& portion, const TIndexChunk& row) = 0; virtual void EraseIndex(const TPortionInfo& portion, const TIndexChunk& row) = 0; - virtual bool LoadIndexes(const std::function& callback) = 0; + virtual bool LoadIndexes(const std::optional pathId, + const std::function& callback) = 0; virtual void WriteCounter(ui32 counterId, ui64 value) = 0; virtual bool LoadCounters(const std::function& callback) = 0; @@ -74,21 +84,27 @@ class TDbWrapper : public IDbWrapper { void WritePortion(const NOlap::TPortionInfo& portion) override; void ErasePortion(const NOlap::TPortionInfo& portion) override; - bool LoadPortions(const std::function& callback) override; + bool LoadPortions(const std::optional pathId, const std::function& callback) override; void WriteColumn(const NOlap::TPortionInfo& portion, const TColumnRecord& row, const ui32 firstPKColumnId) override; + void WriteColumns(const NOlap::TPortionInfo& portion, const NKikimrTxColumnShard::TIndexPortionAccessor& proto) override; void EraseColumn(const NOlap::TPortionInfo& portion, const TColumnRecord& row) override; - bool LoadColumns(const std::function& callback) override; + bool LoadColumns(const std::optional pathId, const std::function& callback) override; virtual void WriteIndex(const TPortionInfo& portion, const TIndexChunk& row) override; virtual void EraseIndex(const TPortionInfo& portion, const TIndexChunk& row) override; - virtual bool LoadIndexes(const std::function& callback) override; + virtual bool LoadIndexes(const std::optional pathId, + const std::function& callback) override; void WriteCounter(ui32 counterId, ui64 value) override; bool LoadCounters(const std::function& callback) override; virtual TConclusion>> LoadGranulesShardingInfo() override; + virtual const IBlobGroupSelector* GetDsGroupSelector() const override { + return DsGroupSelector; + } + private: NTable::TDatabase& Database; const IBlobGroupSelector* DsGroupSelector; diff --git a/ydb/core/tx/columnshard/engines/index_info.cpp b/ydb/core/tx/columnshard/engines/index_info.cpp deleted file mode 100644 index 80fbea3cee7d..000000000000 --- a/ydb/core/tx/columnshard/engines/index_info.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "index_info.h" diff --git a/ydb/core/tx/columnshard/engines/index_info.h b/ydb/core/tx/columnshard/engines/index_info.h deleted file mode 100644 index 382c410d923a..000000000000 --- a/ydb/core/tx/columnshard/engines/index_info.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -#include "scheme/index_info.h" diff --git a/ydb/core/tx/columnshard/engines/insert_table/insert_table.cpp b/ydb/core/tx/columnshard/engines/insert_table/insert_table.cpp index 56a4f730a422..d4d1213eee84 100644 --- a/ydb/core/tx/columnshard/engines/insert_table/insert_table.cpp +++ b/ydb/core/tx/columnshard/engines/insert_table/insert_table.cpp @@ -30,7 +30,7 @@ TInsertionSummary::TCounters TInsertTable::Commit( continue; } - counters.Rows += data->GetMeta().GetNumRows(); + counters.Rows += data->GetMeta().GetRecordsCount(); counters.RawBytes += data->GetMeta().GetRawBytes(); counters.Bytes += data->BlobSize(); @@ -59,13 +59,13 @@ TInsertionSummary::TCounters TInsertTable::Commit( TInsertionSummary::TCounters TInsertTable::CommitEphemeral(IDbWrapper& dbTable, TCommittedData&& data) { TInsertionSummary::TCounters counters; - counters.Rows += data.GetMeta().GetNumRows(); + counters.Rows += data.GetMeta().GetRecordsCount(); counters.RawBytes += data.GetMeta().GetRawBytes(); counters.Bytes += data.BlobSize(); AddBlobLink(data.GetBlobRange().BlobId); const ui64 pathId = data.GetPathId(); - auto& pathInfo = Summary.GetPathInfo(pathId); + auto& pathInfo = Summary.GetPathInfoVerified(pathId); AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "commit_insertion")("path_id", pathId)("blob_range", data.GetBlobRange().ToString()); dbTable.Commit(data); pathInfo.AddCommitted(std::move(data)); @@ -124,11 +124,19 @@ bool TInsertTable::Load(NIceDb::TNiceDb& db, IDbWrapper& dbTable, const TInstant Y_ABORT_UNLESS(!Loaded); Loaded = true; LastWriteId = (TInsertWriteId)0; - if (!NColumnShard::Schema::GetSpecialValueOpt(db, NColumnShard::Schema::EValueIds::LastWriteId, LastWriteId)) { - return false; - } + { + NColumnShard::TLoadTimeSignals::TLoadTimer timer = Summary.GetCounters().LoadCounters.StartGuard(); + if (!NColumnShard::Schema::GetSpecialValueOpt(db, NColumnShard::Schema::EValueIds::LastWriteId, LastWriteId)) { + timer.AddLoadingFail(); + return false; + } - return dbTable.Load(*this, loadTime); + if (!dbTable.Load(*this, loadTime)) { + timer.AddLoadingFail(); + return false; + } + return true; + } } std::vector TInsertTable::Read(ui64 pathId, const std::optional lockId, const TSnapshot& reqSnapshot, @@ -148,7 +156,7 @@ std::vector TInsertTable::Read(ui64 pathId, const std::optional< if (pkRangesFilter && pkRangesFilter->IsPortionInPartialUsage(start, finish) == TPKRangeFilter::EUsageClass::DontUsage) { continue; } - result.emplace_back(TCommittedBlob(data.GetBlobRange(), data.GetSnapshot(), data.GetInsertWriteId(), data.GetSchemaVersion(), data.GetMeta().GetNumRows(), + result.emplace_back(TCommittedBlob(data.GetBlobRange(), data.GetSnapshot(), data.GetInsertWriteId(), data.GetSchemaVersion(), data.GetMeta().GetRecordsCount(), start, finish, data.GetMeta().GetModificationType() == NEvWrite::EModificationType::Delete, data.GetMeta().GetSchemaSubset())); } } @@ -162,7 +170,7 @@ std::vector TInsertTable::Read(ui64 pathId, const std::optional< if (pkRangesFilter && pkRangesFilter->IsPortionInPartialUsage(start, finish) == TPKRangeFilter::EUsageClass::DontUsage) { continue; } - result.emplace_back(TCommittedBlob(data.GetBlobRange(), writeId, data.GetSchemaVersion(), data.GetMeta().GetNumRows(), start, finish, + result.emplace_back(TCommittedBlob(data.GetBlobRange(), writeId, data.GetSchemaVersion(), data.GetMeta().GetRecordsCount(), start, finish, data.GetMeta().GetModificationType() == NEvWrite::EModificationType::Delete, data.GetMeta().GetSchemaSubset())); } } diff --git a/ydb/core/tx/columnshard/engines/insert_table/insert_table.h b/ydb/core/tx/columnshard/engines/insert_table/insert_table.h index 1eb6835053b7..33bf53bfa380 100644 --- a/ydb/core/tx/columnshard/engines/insert_table/insert_table.h +++ b/ydb/core/tx/columnshard/engines/insert_table/insert_table.h @@ -6,6 +6,7 @@ #include #include +#include #include namespace NKikimr::NOlap { @@ -25,6 +26,10 @@ class TInsertTableAccessor { bool RemoveBlobLinkOnComplete(const TUnifiedBlobId& blobId); public: + TPathInfo& RegisterPathInfo(const ui64 pathId) { + return Summary.RegisterPathInfo(pathId); + } + void ErasePath(const ui64 pathId) { Summary.ErasePath(pathId); } @@ -64,7 +69,7 @@ class TInsertTableAccessor { AddBlobLink(data.GetBlobRange().BlobId); } const ui64 pathId = data.GetPathId(); - return Summary.GetPathInfo(pathId).AddCommitted(std::move(data), load); + return Summary.GetPathInfoVerified(pathId).AddCommitted(std::move(data), load); } bool HasPathIdData(const ui64 pathId) const { return Summary.HasPathIdData(pathId); diff --git a/ydb/core/tx/columnshard/engines/insert_table/meta.h b/ydb/core/tx/columnshard/engines/insert_table/meta.h index a913e88c973a..a7121e46d32f 100644 --- a/ydb/core/tx/columnshard/engines/insert_table/meta.h +++ b/ydb/core/tx/columnshard/engines/insert_table/meta.h @@ -12,7 +12,7 @@ namespace NKikimr::NOlap { class TInsertedDataMeta { private: YDB_READONLY_DEF(TInstant, DirtyWriteTime); - YDB_READONLY(ui32, NumRows, 0); + YDB_READONLY(ui32, RecordsCount, 0); YDB_READONLY(ui64, RawBytes, 0); YDB_READONLY(NEvWrite::EModificationType, ModificationType, NEvWrite::EModificationType::Upsert); YDB_READONLY_DEF(NArrow::TSchemaSubset, SchemaSubset); @@ -25,7 +25,8 @@ class TInsertedDataMeta { public: ui64 GetTxVolume() const { - return 2 * sizeof(ui64) + sizeof(ui32) + sizeof(OriginalProto) + (SpecialKeysParsed ? SpecialKeysParsed->GetMemoryBytes() : 0); + return 512 + 2 * sizeof(ui64) + sizeof(ui32) + sizeof(OriginalProto) + (SpecialKeysParsed ? SpecialKeysParsed->GetMemoryBytes() : 0) + + SchemaSubset.GetTxVolume(); } TInsertedDataMeta(const NKikimrTxColumnShard::TLogicalMetadata& proto) @@ -33,7 +34,7 @@ class TInsertedDataMeta { { AFL_VERIFY(proto.HasDirtyWriteTimeSeconds())("data", proto.DebugString()); DirtyWriteTime = TInstant::Seconds(proto.GetDirtyWriteTimeSeconds()); - NumRows = proto.GetNumRows(); + RecordsCount = proto.GetNumRows(); RawBytes = proto.GetRawBytes(); if (proto.HasModificationType()) { ModificationType = TEnumOperator::DeserializeFromProto(proto.GetModificationType()); diff --git a/ydb/core/tx/columnshard/engines/insert_table/rt_insertion.cpp b/ydb/core/tx/columnshard/engines/insert_table/rt_insertion.cpp index 6cc6e4872da3..89b317bb5e85 100644 --- a/ydb/core/tx/columnshard/engines/insert_table/rt_insertion.cpp +++ b/ydb/core/tx/columnshard/engines/insert_table/rt_insertion.cpp @@ -39,7 +39,7 @@ void TInsertionSummary::AddPriority(const TPathInfo& pathInfo) noexcept { } } -NKikimr::NOlap::TPathInfo& TInsertionSummary::GetPathInfo(const ui64 pathId) { +TPathInfo& TInsertionSummary::RegisterPathInfo(const ui64 pathId) { auto it = PathInfo.find(pathId); if (it == PathInfo.end()) { it = PathInfo.emplace(pathId, TPathInfo(*this, pathId)).first; @@ -47,7 +47,7 @@ NKikimr::NOlap::TPathInfo& TInsertionSummary::GetPathInfo(const ui64 pathId) { return it->second; } -NKikimr::NOlap::TPathInfo* TInsertionSummary::GetPathInfoOptional(const ui64 pathId) { +TPathInfo* TInsertionSummary::GetPathInfoOptional(const ui64 pathId) { auto it = PathInfo.find(pathId); if (it == PathInfo.end()) { return nullptr; @@ -55,7 +55,7 @@ NKikimr::NOlap::TPathInfo* TInsertionSummary::GetPathInfoOptional(const ui64 pat return &it->second; } -const NKikimr::NOlap::TPathInfo* TInsertionSummary::GetPathInfoOptional(const ui64 pathId) const { +const TPathInfo* TInsertionSummary::GetPathInfoOptional(const ui64 pathId) const { auto it = PathInfo.find(pathId); if (it == PathInfo.end()) { return nullptr; @@ -134,7 +134,7 @@ bool TInsertionSummary::HasCommitted(const TCommittedData& data) { return pathInfo->HasCommitted(data); } -const NKikimr::NOlap::TInsertedData* TInsertionSummary::AddAborted(TInsertedData&& data, const bool load /*= false*/) { +const TInsertedData* TInsertionSummary::AddAborted(TInsertedData&& data, const bool load /*= false*/) { const TInsertWriteId writeId = data.GetInsertWriteId(); Counters.Aborted.Add(data.BlobSize(), load); AFL_VERIFY_DEBUG(!Inserted.contains(writeId)); @@ -143,7 +143,7 @@ const NKikimr::NOlap::TInsertedData* TInsertionSummary::AddAborted(TInsertedData return &insertInfo.first->second; } -std::optional TInsertionSummary::ExtractInserted(const TInsertWriteId id) { +std::optional TInsertionSummary::ExtractInserted(const TInsertWriteId id) { auto result = Inserted.ExtractOptional(id); if (result) { auto pathInfo = GetPathInfoOptional(result->GetPathId()); @@ -154,10 +154,10 @@ std::optional TInsertionSummary::ExtractInserted( return result; } -const NKikimr::NOlap::TInsertedData* TInsertionSummary::AddInserted(TInsertedData&& data, const bool load /*= false*/) { +const TInsertedData* TInsertionSummary::AddInserted(TInsertedData&& data, const bool load /*= false*/) { auto* insertInfo = Inserted.AddVerified(std::move(data)); AFL_VERIFY_DEBUG(!Aborted.contains(insertInfo->GetInsertWriteId())); - OnNewInserted(GetPathInfo(insertInfo->GetPathId()), insertInfo->BlobSize(), load); + OnNewInserted(GetPathInfoVerified(insertInfo->GetPathId()), insertInfo->BlobSize(), load); return insertInfo; } diff --git a/ydb/core/tx/columnshard/engines/insert_table/rt_insertion.h b/ydb/core/tx/columnshard/engines/insert_table/rt_insertion.h index 67e8034628c8..0724fd8f66b3 100644 --- a/ydb/core/tx/columnshard/engines/insert_table/rt_insertion.h +++ b/ydb/core/tx/columnshard/engines/insert_table/rt_insertion.h @@ -199,9 +199,19 @@ class TInsertionSummary { const NColumnShard::TInsertTableCounters& GetCounters() const { return Counters; } - NKikimr::NOlap::TPathInfo& GetPathInfo(const ui64 pathId); + NKikimr::NOlap::TPathInfo& RegisterPathInfo(const ui64 pathId); TPathInfo* GetPathInfoOptional(const ui64 pathId); const TPathInfo* GetPathInfoOptional(const ui64 pathId) const; + TPathInfo& GetPathInfoVerified(const ui64 pathId) { + auto* result = GetPathInfoOptional(pathId); + AFL_VERIFY(result); + return *result; + } + const TPathInfo& GetPathInfoVerified(const ui64 pathId) const { + auto* result = GetPathInfoOptional(pathId); + AFL_VERIFY(result); + return *result; + } const THashMap& GetPathInfo() const { return PathInfo; diff --git a/ydb/core/tx/columnshard/engines/loading/stages.cpp b/ydb/core/tx/columnshard/engines/loading/stages.cpp new file mode 100644 index 000000000000..5a12216f67ed --- /dev/null +++ b/ydb/core/tx/columnshard/engines/loading/stages.cpp @@ -0,0 +1,35 @@ +#include "stages.h" + +#include +#include +#include +#include + +namespace NKikimr::NOlap::NEngineLoading { + +bool TEngineShardingInfoReader::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + TDbWrapper db(txc.DB, &*DsGroupSelector); + return Self->VersionedIndex.LoadShardingInfo(db); +} + +bool TEngineShardingInfoReader::DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + NIceDb::TNiceDb db(txc.DB); + return NColumnShard::Schema::Precharge(db, txc.DB.GetScheme()); +} + +bool TEngineLoadingFinish::DoExecute(NTabletFlatExecutor::TTransactionContext& /*txc*/, const TActorContext& /*ctx*/) { + Self->FinishLoading(); + return true; +} + +bool TEngineCountersReader::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + TDbWrapper db(txc.DB, &*DsGroupSelector); + return Self->LoadCounters(db); +} + +bool TEngineCountersReader::DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + NIceDb::TNiceDb db(txc.DB); + return NColumnShard::Schema::Precharge(db, txc.DB.GetScheme()); +} + +} // namespace NKikimr::NOlap::NEngineLoading diff --git a/ydb/core/tx/columnshard/engines/loading/stages.h b/ydb/core/tx/columnshard/engines/loading/stages.h new file mode 100644 index 000000000000..748176f0e81e --- /dev/null +++ b/ydb/core/tx/columnshard/engines/loading/stages.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +namespace NKikimr::NOlap { +class TColumnEngineForLogs; +} + +namespace NKikimr::NOlap::NEngineLoading { + +class IEngineTxReader: public ITxReader { +private: + using TBase = ITxReader; + +protected: + const std::shared_ptr DsGroupSelector; + TColumnEngineForLogs* Self = nullptr; + +public: + IEngineTxReader(const TString& name, TColumnEngineForLogs* self, const std::shared_ptr& dsGroupSelector) + : TBase(name) + , DsGroupSelector(dsGroupSelector) + , Self(self) { + } +}; + +class TEngineCountersReader: public IEngineTxReader { +private: + using TBase = IEngineTxReader; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + +public: + using TBase::TBase; +}; + +class TEngineShardingInfoReader: public IEngineTxReader { +private: + using TBase = IEngineTxReader; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + +public: + using TBase::TBase; +}; + +class TEngineLoadingFinish: public ITxReader { +private: + using TBase = ITxReader; + TColumnEngineForLogs* Self = nullptr; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& /*txc*/, const TActorContext& /*ctx*/) override { + return true; + } + +public: + TEngineLoadingFinish(const TString& name, TColumnEngineForLogs* self) + : TBase(name) + , Self(self) { + } +}; + +} // namespace NKikimr::NOlap::NEngineLoading diff --git a/ydb/core/tx/columnshard/engines/loading/ya.make b/ydb/core/tx/columnshard/engines/loading/ya.make new file mode 100644 index 000000000000..c50c4958e9d0 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/loading/ya.make @@ -0,0 +1,12 @@ +LIBRARY() + +SRCS( + stages.cpp +) + +PEERDIR( + ydb/core/tx/columnshard/common + ydb/core/tx/columnshard/tx_reader +) + +END() diff --git a/ydb/core/tx/columnshard/engines/portion_info.h b/ydb/core/tx/columnshard/engines/portion_info.h deleted file mode 100644 index 673e4f6c0b16..000000000000 --- a/ydb/core/tx/columnshard/engines/portion_info.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "portions/portion_info.h" - -namespace NKikimr::NOlap { - -} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portions/column_record.cpp b/ydb/core/tx/columnshard/engines/portions/column_record.cpp index 6127ad439326..30c2b98945d3 100644 --- a/ydb/core/tx/columnshard/engines/portions/column_record.cpp +++ b/ydb/core/tx/columnshard/engines/portions/column_record.cpp @@ -1,63 +1,49 @@ #include "column_record.h" #include - -#include +#include #include +#include #include -#include namespace NKikimr::NOlap { -TConclusionStatus TChunkMeta::DeserializeFromProto(const TChunkAddress& address, const NKikimrTxColumnShard::TIndexColumnMeta& proto, const TSimpleColumnInfo& columnInfo) { - auto field = columnInfo.GetArrowField(); +TConclusionStatus TChunkMeta::DeserializeFromProto(const NKikimrTxColumnShard::TIndexColumnMeta& proto) { if (proto.HasNumRows()) { - NumRows = proto.GetNumRows(); + RecordsCount = proto.GetNumRows(); } if (proto.HasRawBytes()) { RawBytes = proto.GetRawBytes(); } - if (proto.HasMaxValue()) { - AFL_VERIFY(field)("field_id", address.GetColumnId())("field_name", columnInfo.GetColumnName()); - Max = ConstantToScalar(proto.GetMaxValue(), field->type()); - } return TConclusionStatus::Success(); } -TChunkMeta::TChunkMeta(const TColumnChunkLoadContext& context, const TSimpleColumnInfo& columnInfo) { - DeserializeFromProto(context.GetAddress(), context.GetMetaProto(), columnInfo).Validate(); +TChunkMeta::TChunkMeta(const TColumnChunkLoadContextV1& context) { + DeserializeFromProto(context.GetMetaProto()).Validate(); } -TChunkMeta::TChunkMeta(const std::shared_ptr& column, const TSimpleColumnInfo& columnInfo) - : TBase(column, columnInfo.GetNeedMinMax(), columnInfo.GetIsSorted()) -{ +TChunkMeta::TChunkMeta(const std::shared_ptr& column) + : TBase(column) { } NKikimrTxColumnShard::TIndexColumnMeta TChunkMeta::SerializeToProto() const { NKikimrTxColumnShard::TIndexColumnMeta meta; - meta.SetNumRows(NumRows); + meta.SetNumRows(RecordsCount); meta.SetRawBytes(RawBytes); - if (HasMax()) { - ScalarToConstant(*Max, *meta.MutableMaxValue()); - ScalarToConstant(*Max, *meta.MutableMinValue()); - } return meta; } -TColumnRecord::TColumnRecord(const TBlobRangeLink16::TLinkId blobLinkId, const TColumnChunkLoadContext& loadContext, const TSimpleColumnInfo& columnInfo) - : Meta(loadContext, columnInfo) +TColumnRecord::TColumnRecord(const TColumnChunkLoadContextV1& loadContext) + : Meta(loadContext) , ColumnId(loadContext.GetAddress().GetColumnId()) , Chunk(loadContext.GetAddress().GetChunk()) - , BlobRange(loadContext.GetBlobRange().BuildLink(blobLinkId)) -{ + , BlobRange(loadContext.GetBlobRange()) { } -TColumnRecord::TColumnRecord( - const TChunkAddress& address, const std::shared_ptr& column, const TSimpleColumnInfo& columnInfo) - : Meta(column, columnInfo) +TColumnRecord::TColumnRecord(const TChunkAddress& address, const std::shared_ptr& column) + : Meta(column) , ColumnId(address.GetColumnId()) - , Chunk(address.GetChunk()) -{ + , Chunk(address.GetChunk()) { } NKikimrColumnShardDataSharingProto::TColumnRecord TColumnRecord::SerializeToProto() const { @@ -69,11 +55,11 @@ NKikimrColumnShardDataSharingProto::TColumnRecord TColumnRecord::SerializeToProt return result; } -NKikimr::TConclusionStatus TColumnRecord::DeserializeFromProto(const NKikimrColumnShardDataSharingProto::TColumnRecord& proto, const TSimpleColumnInfo& columnInfo) { +NKikimr::TConclusionStatus TColumnRecord::DeserializeFromProto(const NKikimrColumnShardDataSharingProto::TColumnRecord& proto) { ColumnId = proto.GetColumnId(); Chunk = proto.GetChunkIdx(); { - auto parse = Meta.DeserializeFromProto(GetAddress(), proto.GetMeta(), columnInfo); + auto parse = Meta.DeserializeFromProto(proto.GetMeta()); if (!parse) { return parse; } @@ -88,4 +74,4 @@ NKikimr::TConclusionStatus TColumnRecord::DeserializeFromProto(const NKikimrColu return TConclusionStatus::Success(); } -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portions/column_record.h b/ydb/core/tx/columnshard/engines/portions/column_record.h index 18fd0984d61b..a063d8b621cc 100644 --- a/ydb/core/tx/columnshard/engines/portions/column_record.h +++ b/ydb/core/tx/columnshard/engines/portions/column_record.h @@ -2,8 +2,6 @@ #include "common.h" -#include -#include #include #include #include @@ -11,6 +9,8 @@ #include #include +#include +#include #include #include @@ -22,7 +22,7 @@ class TColumnRecord; } namespace NKikimr::NOlap { -class TColumnChunkLoadContext; +class TColumnChunkLoadContextV1; struct TIndexInfo; class TColumnRecord; @@ -30,8 +30,7 @@ struct TChunkMeta: public TSimpleChunkMeta { private: using TBase = TSimpleChunkMeta; TChunkMeta() = default; - [[nodiscard]] TConclusionStatus DeserializeFromProto( - const TChunkAddress& address, const NKikimrTxColumnShard::TIndexColumnMeta& proto, const TSimpleColumnInfo& columnInfo); + [[nodiscard]] TConclusionStatus DeserializeFromProto(const NKikimrTxColumnShard::TIndexColumnMeta& proto); friend class TColumnRecord; public: @@ -39,10 +38,9 @@ struct TChunkMeta: public TSimpleChunkMeta { : TBase(baseMeta) { } - [[nodiscard]] static TConclusion BuildFromProto( - const TChunkAddress& address, const NKikimrTxColumnShard::TIndexColumnMeta& proto, const TSimpleColumnInfo& columnInfo) { + [[nodiscard]] static TConclusion BuildFromProto(const NKikimrTxColumnShard::TIndexColumnMeta& proto) { TChunkMeta result; - auto parse = result.DeserializeFromProto(address, proto, columnInfo); + auto parse = result.DeserializeFromProto(proto); if (!parse) { return parse; } @@ -53,17 +51,17 @@ struct TChunkMeta: public TSimpleChunkMeta { class TTestInstanceBuilder { public: - static TChunkMeta Build(const ui64 numRows, const ui64 rawBytes) { + static TChunkMeta Build(const ui64 recordsCount, const ui64 rawBytes) { TChunkMeta result; - result.NumRows = numRows; + result.RecordsCount = recordsCount; result.RawBytes = rawBytes; return result; } }; - TChunkMeta(const TColumnChunkLoadContext& context, const TSimpleColumnInfo& columnInfo); + TChunkMeta(const TColumnChunkLoadContextV1& context); - TChunkMeta(const std::shared_ptr& column, const TSimpleColumnInfo& columnInfo); + TChunkMeta(const std::shared_ptr& column); }; class TColumnRecord { @@ -74,7 +72,7 @@ class TColumnRecord { } TColumnRecord() = default; - TConclusionStatus DeserializeFromProto(const NKikimrColumnShardDataSharingProto::TColumnRecord& proto, const TSimpleColumnInfo& columnInfo); + TConclusionStatus DeserializeFromProto(const NKikimrColumnShardDataSharingProto::TColumnRecord& proto); public: ui32 ColumnId = 0; @@ -103,8 +101,8 @@ class TColumnRecord { class TTestInstanceBuilder { public: - static TColumnRecord Build(const ui32 columnId, const ui16 chunkId, const ui64 offset, const ui64 size, const ui64 numRows, const ui64 rawBytes) { - TColumnRecord result(TChunkMeta::TTestInstanceBuilder::Build(numRows, rawBytes)); + static TColumnRecord Build(const ui32 columnId, const ui16 chunkId, const ui64 offset, const ui64 size, const ui64 recordsCount, const ui64 rawBytes) { + TColumnRecord result(TChunkMeta::TTestInstanceBuilder::Build(recordsCount, rawBytes)); result.ColumnId = columnId; result.Chunk = chunkId; result.BlobRange.Offset = offset; @@ -123,11 +121,18 @@ class TColumnRecord { return BlobRange; } + NKikimrTxColumnShard::TColumnChunkInfo SerializeToDBProto() const { + NKikimrTxColumnShard::TColumnChunkInfo result; + result.SetSSColumnId(GetEntityId()); + result.SetChunkIdx(GetChunkIdx()); + *result.MutableChunkMetadata() = Meta.SerializeToProto(); + *result.MutableBlobRangeLink() = BlobRange.SerializeToProto(); + return result; + } NKikimrColumnShardDataSharingProto::TColumnRecord SerializeToProto() const; - static TConclusion BuildFromProto( - const NKikimrColumnShardDataSharingProto::TColumnRecord& proto, const TSimpleColumnInfo& columnInfo) { + static TConclusion BuildFromProto(const NKikimrColumnShardDataSharingProto::TColumnRecord& proto) { TColumnRecord result; - auto parse = result.DeserializeFromProto(proto, columnInfo); + auto parse = result.DeserializeFromProto(proto); if (!parse) { return parse; } @@ -141,7 +146,7 @@ class TColumnRecord { } NArrow::NSplitter::TSimpleSerializationStat GetSerializationStat() const { - return NArrow::NSplitter::TSimpleSerializationStat(BlobRange.Size, Meta.GetNumRows(), Meta.GetRawBytes()); + return NArrow::NSplitter::TSimpleSerializationStat(BlobRange.Size, Meta.GetRecordsCount(), Meta.GetRawBytes()); } const TChunkMeta& GetMeta() const { @@ -156,20 +161,14 @@ class TColumnRecord { return ColumnId == item.ColumnId && Chunk == item.Chunk; } - bool Valid() const { - return ColumnId && BlobRange.IsValid(); - } - TString DebugString() const { return TStringBuilder() << "column_id:" << ColumnId << ";" << "chunk_idx:" << Chunk << ";" << "blob_range:" << BlobRange.ToString() << ";"; } - TColumnRecord( - const TChunkAddress& address, const std::shared_ptr& column, const TSimpleColumnInfo& columnInfo); - - TColumnRecord(const TBlobRangeLink16::TLinkId blobLinkId, const TColumnChunkLoadContext& loadContext, const TSimpleColumnInfo& columnInfo); + TColumnRecord(const TChunkAddress& address, const std::shared_ptr& column); + TColumnRecord(const TColumnChunkLoadContextV1& loadContext); friend IOutputStream& operator<<(IOutputStream& out, const TColumnRecord& rec) { out << '{'; diff --git a/ydb/core/tx/columnshard/engines/portions/common.cpp b/ydb/core/tx/columnshard/engines/portions/common.cpp index e18ca98033c2..4b8efcf42bbb 100644 --- a/ydb/core/tx/columnshard/engines/portions/common.cpp +++ b/ydb/core/tx/columnshard/engines/portions/common.cpp @@ -7,4 +7,8 @@ TString TChunkAddress::DebugString() const { return TStringBuilder() << "(column_id=" << ColumnId << ";chunk=" << Chunk << ";)"; } +TString TFullChunkAddress::DebugString() const { + return TStringBuilder() << "(path_id=" << PathId << ";portion_id=" << PortionId << ";column_id=" << ColumnId << ";chunk=" << Chunk << ";)"; +} + } diff --git a/ydb/core/tx/columnshard/engines/portions/common.h b/ydb/core/tx/columnshard/engines/portions/common.h index 3702887ccc81..8dba84c96c98 100644 --- a/ydb/core/tx/columnshard/engines/portions/common.h +++ b/ydb/core/tx/columnshard/engines/portions/common.h @@ -35,11 +35,52 @@ class TChunkAddress { TString DebugString() const; }; -} +class TFullChunkAddress { +private: + YDB_READONLY(ui64, PathId, 0); + YDB_READONLY(ui64, PortionId, 0); + YDB_READONLY(ui32, ColumnId, 0); + YDB_READONLY(ui16, Chunk, 0); + +public: + ui32 GetEntityId() const { + return ColumnId; + } + + ui32 GetChunkIdx() const { + return Chunk; + } + + TFullChunkAddress(const ui64 pathId, const ui64 portionId, const ui32 columnId, const ui16 chunk) + : PathId(pathId) + , PortionId(portionId) + , ColumnId(columnId) + , Chunk(chunk) { + } + + bool operator<(const TFullChunkAddress& address) const { + return std::tie(PathId, PortionId, ColumnId, Chunk) < std::tie(address.PathId, address.PortionId, address.ColumnId, address.Chunk); + } -template<> + bool operator==(const TFullChunkAddress& address) const { + return std::tie(PathId, PortionId, ColumnId, Chunk) == std::tie(address.PathId, address.PortionId, address.ColumnId, address.Chunk); + } + + TString DebugString() const; +}; + +} // namespace NKikimr::NOlap + +template <> struct ::THash { inline ui64 operator()(const NKikimr::NOlap::TChunkAddress& a) const { return ((ui64)a.GetEntityId()) << 16 + a.GetChunkIdx(); } }; + +template <> +struct ::THash { + inline ui64 operator()(const NKikimr::NOlap::TFullChunkAddress& a) const { + return CombineHashes(CombineHashes(((ui64)a.GetEntityId()) << 16 + a.GetChunkIdx(), a.GetPathId()), a.GetPortionId()); + } +}; diff --git a/ydb/core/tx/columnshard/engines/portions/constructor.cpp b/ydb/core/tx/columnshard/engines/portions/constructor.cpp deleted file mode 100644 index 39cd0fe983dc..000000000000 --- a/ydb/core/tx/columnshard/engines/portions/constructor.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "constructor.h" - -#include -#include -#include -#include -#include -#include - -namespace NKikimr::NOlap { - -TPortionInfo TPortionInfoConstructor::Build(const bool needChunksNormalization) { - TPortionInfo result(MetaConstructor.Build()); - AFL_VERIFY(PathId); - result.PathId = PathId; - result.Portion = GetPortionIdVerified(); - - AFL_VERIFY(MinSnapshotDeprecated); - AFL_VERIFY(MinSnapshotDeprecated->Valid()); - result.MinSnapshotDeprecated = *MinSnapshotDeprecated; - if (RemoveSnapshot) { - AFL_VERIFY(RemoveSnapshot->Valid()); - result.RemoveSnapshot = *RemoveSnapshot; - } - result.SchemaVersion = SchemaVersion; - result.ShardingVersion = ShardingVersion; - - if (needChunksNormalization) { - ReorderChunks(); - } - NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("portion_id", GetPortionIdVerified()); - FullValidation(); - - result.Indexes = Indexes; - result.Records = Records; - result.BlobIds = BlobIds; - return result; -} - -ISnapshotSchema::TPtr TPortionInfoConstructor::GetSchema(const TVersionedIndex& index) const { - if (SchemaVersion) { - auto schema = index.GetSchema(SchemaVersion.value()); - AFL_VERIFY(!!schema)("details", TStringBuilder() << "cannot find schema for version " << SchemaVersion.value()); - return schema; - } - AFL_VERIFY(MinSnapshotDeprecated); - return index.GetSchema(*MinSnapshotDeprecated); -} - -void TPortionInfoConstructor::LoadRecord(const TIndexInfo& indexInfo, const TColumnChunkLoadContext& loadContext) { - TColumnRecord rec(RegisterBlobId(loadContext.GetBlobRange().GetBlobId()), loadContext, indexInfo.GetColumnFeaturesVerified(loadContext.GetAddress().GetColumnId())); - Records.push_back(std::move(rec)); - - if (loadContext.GetPortionMeta()) { - AFL_VERIFY(MetaConstructor.LoadMetadata(*loadContext.GetPortionMeta(), indexInfo)); - } -} - -void TPortionInfoConstructor::LoadIndex(const TIndexChunkLoadContext& loadContext) { - if (loadContext.GetBlobRange()) { - const TBlobRangeLink16::TLinkId linkBlobId = RegisterBlobId(loadContext.GetBlobRange()->GetBlobId()); - AddIndex(loadContext.BuildIndexChunk(linkBlobId)); - } else { - AddIndex(loadContext.BuildIndexChunk()); - } -} - -const NKikimr::NOlap::TColumnRecord& TPortionInfoConstructor::AppendOneChunkColumn(TColumnRecord&& record) { - Y_ABORT_UNLESS(record.ColumnId); - Records.emplace_back(std::move(record)); - return Records.back(); -} - -void TPortionInfoConstructor::AddMetadata(const ISnapshotSchema& snapshotSchema, const std::shared_ptr& batch) { - Y_ABORT_UNLESS(batch->num_rows() == GetRecordsCount()); - MetaConstructor.FillMetaInfo(NArrow::TFirstLastSpecialKeys(batch), IIndexInfo::CalcDeletions(batch, false), - NArrow::TMinMaxSpecialKeys(batch, TIndexInfo::ArrowSchemaSnapshot()), snapshotSchema.GetIndexInfo()); -} - -} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portions/constructor.h b/ydb/core/tx/columnshard/engines/portions/constructor.h deleted file mode 100644 index 4146c80fbc19..000000000000 --- a/ydb/core/tx/columnshard/engines/portions/constructor.h +++ /dev/null @@ -1,355 +0,0 @@ -#pragma once -#include "column_record.h" -#include "constructor_meta.h" -#include "index_chunk.h" -#include "portion_info.h" - -#include - -namespace NKikimr::NOlap { -class TPortionInfo; -class TVersionedIndex; -class ISnapshotSchema; -class TIndexChunkLoadContext; -class TGranuleShardingInfo; - -class TPortionInfoConstructor { -private: - YDB_ACCESSOR(ui64, PathId, 0); - std::optional PortionId; - - TPortionMetaConstructor MetaConstructor; - - std::optional MinSnapshotDeprecated; - std::optional RemoveSnapshot; - std::optional SchemaVersion; - std::optional ShardingVersion; - - std::vector Indexes; - YDB_ACCESSOR_DEF(std::vector, Records); - std::vector BlobIds; - -public: - void SetPortionId(const ui64 value) { - AFL_VERIFY(value); - PortionId = value; - } - - void AddMetadata(const ISnapshotSchema& snapshotSchema, const std::shared_ptr& batch); - - void AddMetadata(const ISnapshotSchema& snapshotSchema, const ui32 deletionsCount, const NArrow::TFirstLastSpecialKeys& firstLastRecords, const NArrow::TMinMaxSpecialKeys& minMaxSpecial) { - MetaConstructor.FillMetaInfo(firstLastRecords, deletionsCount, minMaxSpecial, snapshotSchema.GetIndexInfo()); - } - - ui64 GetPortionIdVerified() const { - AFL_VERIFY(PortionId); - AFL_VERIFY(*PortionId); - return *PortionId; - } - - TPortionMetaConstructor& MutableMeta() { - return MetaConstructor; - } - - const TPortionMetaConstructor& GetMeta() const { - return MetaConstructor; - } - - TPortionInfoConstructor(const TPortionInfo& portion, const bool withBlobs, const bool withMetadata) - : PathId(portion.GetPathId()) - , PortionId(portion.GetPortionId()) - , MinSnapshotDeprecated(portion.GetMinSnapshotDeprecated()) - , RemoveSnapshot(portion.GetRemoveSnapshotOptional()) - , SchemaVersion(portion.GetSchemaVersionOptional()) - , ShardingVersion(portion.GetShardingVersionOptional()) { - if (withMetadata) { - MetaConstructor = TPortionMetaConstructor(portion.Meta); - } - if (withBlobs) { - Indexes = portion.GetIndexes(); - Records = portion.GetRecords(); - BlobIds = portion.BlobIds; - } - } - - TPortionInfoConstructor(TPortionInfo&& portion) - : PathId(portion.GetPathId()) - , PortionId(portion.GetPortionId()) - , MinSnapshotDeprecated(portion.GetMinSnapshotDeprecated()) - , RemoveSnapshot(portion.GetRemoveSnapshotOptional()) - , SchemaVersion(portion.GetSchemaVersionOptional()) - , ShardingVersion(portion.GetShardingVersionOptional()) { - MetaConstructor = TPortionMetaConstructor(portion.Meta); - Indexes = std::move(portion.Indexes); - Records = std::move(portion.Records); - BlobIds = std::move(portion.BlobIds); - } - - TPortionAddress GetAddress() const { - return TPortionAddress(PathId, GetPortionIdVerified()); - } - - bool HasRemoveSnapshot() const { - return !!RemoveSnapshot; - } - - template - static void CheckChunksOrder(const std::vector& chunks) { - ui32 entityId = 0; - ui32 chunkIdx = 0; - - const auto debugString = [&]() { - TStringBuilder sb; - for (auto&& i : chunks) { - sb << i.GetAddress().DebugString() << ";"; - } - return sb; - }; - - for (auto&& i : chunks) { - if (entityId != i.GetEntityId()) { - AFL_VERIFY(entityId < i.GetEntityId())("entity", entityId)("next", i.GetEntityId())("details", debugString()); - AFL_VERIFY(i.GetChunkIdx() == 0); - entityId = i.GetEntityId(); - chunkIdx = 0; - } else { - AFL_VERIFY(i.GetChunkIdx() == chunkIdx + 1)("chunkIdx", chunkIdx)("i.GetChunkIdx()", i.GetChunkIdx())("entity", entityId)("details", debugString()); - chunkIdx = i.GetChunkIdx(); - } - AFL_VERIFY(i.GetEntityId()); - } - } - - void Merge(TPortionInfoConstructor&& item) { - AFL_VERIFY(item.PathId == PathId); - AFL_VERIFY(item.PortionId == PortionId); - if (item.MinSnapshotDeprecated) { - if (MinSnapshotDeprecated) { - AFL_VERIFY(*MinSnapshotDeprecated == *item.MinSnapshotDeprecated); - } else { - MinSnapshotDeprecated = item.MinSnapshotDeprecated; - } - } - if (item.RemoveSnapshot) { - if (RemoveSnapshot) { - AFL_VERIFY(*RemoveSnapshot == *item.RemoveSnapshot); - } else { - RemoveSnapshot = item.RemoveSnapshot; - } - } - } - - TPortionInfoConstructor(const ui64 pathId, const ui64 portionId) - : PathId(pathId) - , PortionId(portionId) { - AFL_VERIFY(PathId); - AFL_VERIFY(PortionId); - } - - TPortionInfoConstructor(const ui64 pathId) - : PathId(pathId) { - AFL_VERIFY(PathId); - } - - const TSnapshot& GetMinSnapshotDeprecatedVerified() const { - AFL_VERIFY(!!MinSnapshotDeprecated); - return *MinSnapshotDeprecated; - } - - std::shared_ptr GetSchema(const TVersionedIndex& index) const; - - void SetMinSnapshotDeprecated(const TSnapshot& snap) { - Y_ABORT_UNLESS(snap.Valid()); - MinSnapshotDeprecated = snap; - } - - void SetSchemaVersion(const ui64 version) { -// AFL_VERIFY(version); - SchemaVersion = version; - } - - void SetShardingVersion(const ui64 version) { -// AFL_VERIFY(version); - ShardingVersion = version; - } - - void SetRemoveSnapshot(const TSnapshot& snap) { - AFL_VERIFY(!RemoveSnapshot); - if (snap.Valid()) { - RemoveSnapshot = snap; - } - } - - void SetRemoveSnapshot(const ui64 planStep, const ui64 txId) { - SetRemoveSnapshot(TSnapshot(planStep, txId)); - } - - void LoadRecord(const TIndexInfo& indexInfo, const TColumnChunkLoadContext& loadContext); - - ui32 GetRecordsCount() const { - ui32 result = 0; - std::optional columnIdFirst; - for (auto&& i : Records) { - if (!columnIdFirst || *columnIdFirst == i.ColumnId) { - result += i.GetMeta().GetNumRows(); - columnIdFirst = i.ColumnId; - } - } - AFL_VERIFY(columnIdFirst); - return result; - } - - TBlobRangeLink16::TLinkId RegisterBlobId(const TUnifiedBlobId& blobId) { - AFL_VERIFY(blobId.IsValid()); - TBlobRangeLink16::TLinkId idx = 0; - for (auto&& i : BlobIds) { - if (i == blobId) { - return idx; - } - ++idx; - } - BlobIds.emplace_back(blobId); - return idx; - } - - const TBlobRange RestoreBlobRange(const TBlobRangeLink16& linkRange) const { - return linkRange.RestoreRange(GetBlobId(linkRange.GetBlobIdxVerified())); - } - - const TUnifiedBlobId& GetBlobId(const TBlobRangeLink16::TLinkId linkId) const { - AFL_VERIFY(linkId < BlobIds.size()); - return BlobIds[linkId]; - } - - ui32 GetBlobIdsCount() const { - return BlobIds.size(); - } - - void RegisterBlobIdx(const TChunkAddress& address, const TBlobRangeLink16::TLinkId blobIdx) { - for (auto&& i : Records) { - if (i.GetColumnId() == address.GetEntityId() && i.GetChunkIdx() == address.GetChunkIdx()) { - i.RegisterBlobIdx(blobIdx); - return; - } - } - for (auto&& i : Indexes) { - if (i.GetIndexId() == address.GetEntityId() && i.GetChunkIdx() == address.GetChunkIdx()) { - i.RegisterBlobIdx(blobIdx); - return; - } - } - AFL_VERIFY(false)("problem", "portion haven't address for blob registration")("address", address.DebugString()); - } - - TString DebugString() const { - TStringBuilder sb; - sb << (PortionId ? *PortionId : 0) << ";"; - for (auto&& i : Records) { - sb << i.DebugString() << ";"; - } - return sb; - } - - void ReorderChunks() { - { - auto pred = [](const TColumnRecord& l, const TColumnRecord& r) { - return l.GetAddress() < r.GetAddress(); - }; - std::sort(Records.begin(), Records.end(), pred); - CheckChunksOrder(Records); - } - { - auto pred = [](const TIndexChunk& l, const TIndexChunk& r) { - return l.GetAddress() < r.GetAddress(); - }; - std::sort(Indexes.begin(), Indexes.end(), pred); - CheckChunksOrder(Indexes); - } - } - - void FullValidation() const { - AFL_VERIFY(Records.size()); - CheckChunksOrder(Records); - CheckChunksOrder(Indexes); - std::set blobIdxs; - for (auto&& i : Records) { - blobIdxs.emplace(i.GetBlobRange().GetBlobIdxVerified()); - } - for (auto&& i : Indexes) { - if (i.HasBlobRange()) { - blobIdxs.emplace(i.GetBlobRangeVerified().GetBlobIdxVerified()); - } - } - if (BlobIds.size()) { - AFL_VERIFY(BlobIds.size() == blobIdxs.size()); - AFL_VERIFY(BlobIds.size() == *blobIdxs.rbegin() + 1); - } else { - AFL_VERIFY(blobIdxs.empty()); - } - } - - void LoadIndex(const TIndexChunkLoadContext& loadContext); - - const TColumnRecord& AppendOneChunkColumn(TColumnRecord&& record); - - void AddIndex(const TIndexChunk& chunk) { - ui32 chunkIdx = 0; - for (auto&& i : Indexes) { - if (i.GetIndexId() == chunk.GetIndexId()) { - AFL_VERIFY(chunkIdx == i.GetChunkIdx())("index_id", chunk.GetIndexId())("expected", chunkIdx)("real", i.GetChunkIdx()); - ++chunkIdx; - } - } - AFL_VERIFY(chunkIdx == chunk.GetChunkIdx())("index_id", chunk.GetIndexId())("expected", chunkIdx)("real", chunk.GetChunkIdx()); - Indexes.emplace_back(chunk); - } - - TPortionInfo Build(const bool needChunksNormalization); -}; - -class TPortionConstructors { -private: - THashMap> Constructors; - -public: - THashMap>::iterator begin() { - return Constructors.begin(); - } - - THashMap>::iterator end() { - return Constructors.end(); - } - - TPortionInfoConstructor* GetConstructorVerified(const ui64 pathId, const ui64 portionId) { - auto itPathId = Constructors.find(pathId); - AFL_VERIFY(itPathId != Constructors.end()); - auto itPortionId = itPathId->second.find(portionId); - AFL_VERIFY(itPortionId != itPathId->second.end()); - return &itPortionId->second; - } - - TPortionInfoConstructor* AddConstructorVerified(TPortionInfoConstructor&& constructor) { - const ui64 pathId = constructor.GetPathId(); - const ui64 portionId = constructor.GetPortionIdVerified(); - auto info = Constructors[pathId].emplace(portionId, std::move(constructor)); - AFL_VERIFY(info.second); - return &info.first->second; - } - - TPortionInfoConstructor* MergeConstructor(TPortionInfoConstructor&& constructor) { - const ui64 pathId = constructor.GetPathId(); - const ui64 portionId = constructor.GetPortionIdVerified(); - auto itPathId = Constructors.find(pathId); - if (itPathId == Constructors.end()) { - return AddConstructorVerified(std::move(constructor)); - } - auto itPortionId = itPathId->second.find(portionId); - if (itPortionId == itPathId->second.end()) { - return AddConstructorVerified(std::move(constructor)); - } - itPortionId->second.Merge(std::move(constructor)); - return &itPortionId->second; - } -}; - -} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portions/constructor_accessor.cpp b/ydb/core/tx/columnshard/engines/portions/constructor_accessor.cpp new file mode 100644 index 000000000000..b3d6b778f11c --- /dev/null +++ b/ydb/core/tx/columnshard/engines/portions/constructor_accessor.cpp @@ -0,0 +1,172 @@ +#include "constructor_accessor.h" + +#include + +namespace NKikimr::NOlap { + +void TPortionAccessorConstructor::ChunksValidation() const { + AFL_VERIFY(Records.size()); + CheckChunksOrder(Records); + CheckChunksOrder(Indexes); + if (BlobIdxs.size()) { + AFL_VERIFY(BlobIdxs.size() <= Records.size() + Indexes.size())("blobs", BlobIdxs.size())("records", Records.size())( + "indexes", Indexes.size()); + } else { + std::set blobIdxs; + for (auto&& i : Records) { + TBlobRange::Validate(PortionInfo.MetaConstructor.BlobIds, i.GetBlobRange()).Validate(); + blobIdxs.emplace(i.GetBlobRange().GetBlobIdxVerified()); + } + for (auto&& i : Indexes) { + if (i.HasBlobRange()) { + TBlobRange::Validate(PortionInfo.MetaConstructor.BlobIds, i.GetBlobRangeVerified()).Validate(); + blobIdxs.emplace(i.GetBlobRangeVerified().GetBlobIdxVerified()); + } + } + if (PortionInfo.MetaConstructor.BlobIds.size()) { + AFL_VERIFY(PortionInfo.MetaConstructor.BlobIds.size() == blobIdxs.size()); + AFL_VERIFY(PortionInfo.MetaConstructor.BlobIds.size() == *blobIdxs.rbegin() + 1); + } else { + AFL_VERIFY(blobIdxs.empty()); + } + } +} + +TPortionDataAccessor TPortionAccessorConstructor::Build(const bool needChunksNormalization) { + AFL_VERIFY(!Constructed); + Constructed = true; + + AFL_VERIFY(Records.size()); + + PortionInfo.MetaConstructor.ColumnRawBytes = 0; + PortionInfo.MetaConstructor.ColumnBlobBytes = 0; + PortionInfo.MetaConstructor.IndexRawBytes = 0; + PortionInfo.MetaConstructor.IndexBlobBytes = 0; + + PortionInfo.MetaConstructor.RecordsCount = CalcRecordsCount(); + for (auto&& r : Records) { + *PortionInfo.MetaConstructor.ColumnRawBytes += r.GetMeta().GetRawBytes(); + *PortionInfo.MetaConstructor.ColumnBlobBytes += r.GetBlobRange().GetSize(); + } + for (auto&& r : Indexes) { + *PortionInfo.MetaConstructor.IndexRawBytes += r.GetRawBytes(); + *PortionInfo.MetaConstructor.IndexBlobBytes += r.GetDataSize(); + } + + std::shared_ptr result = PortionInfo.Build(); + + if (needChunksNormalization) { + ReorderChunks(); + } + NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("portion_id", PortionInfo.GetPortionIdVerified()); + if (BlobIdxs.size()) { + auto itRecord = Records.begin(); + auto itIndex = Indexes.begin(); + auto itBlobIdx = BlobIdxs.begin(); + while (itRecord != Records.end() && itIndex != Indexes.end() && itBlobIdx != BlobIdxs.end()) { + if (itRecord->GetAddress() < itIndex->GetAddress()) { + AFL_VERIFY(itRecord->GetAddress() == itBlobIdx->GetAddress()); + itRecord->RegisterBlobIdx(itBlobIdx->GetBlobIdx()); + ++itRecord; + ++itBlobIdx; + } else if (itIndex->GetAddress() < itRecord->GetAddress()) { + if (itIndex->HasBlobData()) { + ++itIndex; + continue; + } + AFL_VERIFY(itIndex->GetAddress() == itBlobIdx->GetAddress()); + itIndex->RegisterBlobIdx(itBlobIdx->GetBlobIdx()); + ++itIndex; + ++itBlobIdx; + } else { + AFL_VERIFY(false); + } + } + for (; itRecord != Records.end() && itBlobIdx != BlobIdxs.end(); ++itRecord, ++itBlobIdx) { + AFL_VERIFY(itRecord->GetAddress() == itBlobIdx->GetAddress()); + itRecord->RegisterBlobIdx(itBlobIdx->GetBlobIdx()); + } + for (; itIndex != Indexes.end() && itBlobIdx != BlobIdxs.end(); ++itIndex) { + if (itIndex->HasBlobData()) { + continue; + } + AFL_VERIFY(itIndex->GetAddress() == itBlobIdx->GetAddress()); + itIndex->RegisterBlobIdx(itBlobIdx->GetBlobIdx()); + ++itBlobIdx; + } + AFL_VERIFY(itRecord == Records.end()); + AFL_VERIFY(itBlobIdx == BlobIdxs.end()); + } else { + for (auto&& i : Records) { + AFL_VERIFY(i.BlobRange.GetBlobIdxVerified() < PortionInfo.MetaConstructor.BlobIds.size()); + } + for (auto&& i : Indexes) { + if (auto* blobId = i.GetBlobRangeOptional()) { + AFL_VERIFY(blobId->GetBlobIdxVerified() < PortionInfo.MetaConstructor.BlobIds.size()); + } + } + } + ChunksValidation(); + + return TPortionDataAccessor(result, std::move(Records), std::move(Indexes), false); +} + +void TPortionAccessorConstructor::LoadRecord(TColumnChunkLoadContextV1&& loadContext) { + AFL_VERIFY(loadContext.GetBlobRange().GetBlobIdxVerified() < PortionInfo.MetaConstructor.BlobIds.size()); + AFL_VERIFY(loadContext.GetBlobRange().CheckBlob(PortionInfo.MetaConstructor.BlobIds[loadContext.GetBlobRange().GetBlobIdxVerified()]))( + "blobs", JoinSeq(",", PortionInfo.MetaConstructor.BlobIds))("range", loadContext.GetBlobRange().ToString()); + TColumnRecord rec(loadContext); + Records.push_back(std::move(rec)); +} + +void TPortionAccessorConstructor::LoadIndex(TIndexChunkLoadContext&& loadContext) { + if (loadContext.GetBlobRange()) { + const TBlobRangeLink16::TLinkId linkBlobId = PortionInfo.GetMeta().GetBlobIdxVerified(loadContext.GetBlobRange()->GetBlobId()); + AddIndex(loadContext.BuildIndexChunk(linkBlobId)); + } else { + AddIndex(loadContext.BuildIndexChunk()); + } +} + +TPortionDataAccessor TPortionAccessorConstructor::BuildForLoading( + const TPortionInfo::TConstPtr& portion, std::vector&& records, std::vector&& indexes) { + AFL_VERIFY(portion); + std::vector recordChunks; + { + const auto pred = [](const TColumnRecord& l, const TColumnRecord& r) -> bool { + return l.GetAddress() < r.GetAddress(); + }; + bool needSort = false; + for (auto&& i : records) { + TColumnRecord chunk(i); + if (recordChunks.size() && !pred(recordChunks.back(), chunk)) { + needSort = true; + } + recordChunks.emplace_back(std::move(chunk)); + } + if (needSort) { + std::sort(recordChunks.begin(), recordChunks.end(), pred); + } + } + std::vector indexChunks; + { + + const auto pred = [](const TIndexChunk& l, const TIndexChunk& r) ->bool { + return l.GetAddress() < r.GetAddress(); + }; + bool needSort = false; + for (auto&& i : indexes) { + auto chunk = i.BuildIndexChunk(*portion); + if (indexChunks.size() && !pred(indexChunks.back(), chunk)) { + needSort = true; + } + indexChunks.emplace_back(std::move(chunk)); + } + if (needSort) { + std::sort(indexChunks.begin(), indexChunks.end(), pred); + } + } + return TPortionDataAccessor(portion, std::move(recordChunks), std::move(indexChunks), true); +} + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portions/constructor_accessor.h b/ydb/core/tx/columnshard/engines/portions/constructor_accessor.h new file mode 100644 index 000000000000..e9d219440802 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/portions/constructor_accessor.h @@ -0,0 +1,281 @@ +#pragma once +#include "column_record.h" +#include "constructor_portion.h" +#include "data_accessor.h" +#include "index_chunk.h" + +#include + +namespace NKikimr::NOlap { + +class TPortionAccessorConstructor { +private: + bool Constructed = false; + TPortionInfoConstructor PortionInfo; + std::vector Indexes; + std::vector Records; + + class TAddressBlobId { + private: + TChunkAddress Address; + YDB_READONLY(TBlobRangeLink16::TLinkId, BlobIdx, 0); + + public: + const TChunkAddress& GetAddress() const { + return Address; + } + + TAddressBlobId(const TChunkAddress& address, const TBlobRangeLink16::TLinkId blobIdx) + : Address(address) + , BlobIdx(blobIdx) { + } + }; + std::vector BlobIdxs; + bool NeedBlobIdxsSort = false; + + TPortionAccessorConstructor(const TPortionAccessorConstructor&) = default; + TPortionAccessorConstructor& operator=(const TPortionAccessorConstructor&) = default; + + TPortionAccessorConstructor(TPortionDataAccessor&& accessor) + : PortionInfo(accessor.GetPortionInfo(), true, true) { + Indexes = accessor.ExtractIndexes(); + Records = accessor.ExtractRecords(); + } + + TPortionAccessorConstructor( + const TPortionDataAccessor& accessor, const bool withBlobs, const bool withMetadata, const bool withMetadataBlobs) + : PortionInfo(accessor.GetPortionInfo(), withMetadata, withMetadataBlobs) { + if (withBlobs) { + AFL_VERIFY(withMetadataBlobs && withMetadata); + Indexes = accessor.GetIndexesVerified(); + Records = accessor.GetRecordsVerified(); + } + } + + void ChunksValidation() const; + + static void Validate(const TColumnRecord& rec) { + AFL_VERIFY(rec.GetColumnId()); + } + + static ui32 GetRecordsCount(const TColumnRecord& rec) { + return rec.GetMeta().GetRecordsCount(); + } + + static void Validate(const TIndexChunk& rec) { + AFL_VERIFY(rec.GetIndexId()); + if (const auto* blobData = rec.GetBlobDataOptional()) { + AFL_VERIFY(blobData->size()); + } + } + + static ui32 GetRecordsCount(const TIndexChunk& rec) { + return rec.GetRecordsCount(); + } + + template + static void CheckChunksOrder(const std::vector& chunks) { + ui32 entityId = 0; + ui32 chunkIdx = 0; + + const auto debugString = [&]() { + TStringBuilder sb; + for (auto&& i : chunks) { + sb << i.GetAddress().DebugString() << ";"; + } + return sb; + }; + + std::optional recordsCount; + ui32 recordsCountCurrent = 0; + for (auto&& i : chunks) { + Validate(i); + if (entityId != i.GetEntityId()) { + if (entityId) { + if (recordsCount) { + AFL_VERIFY(recordsCountCurrent == *recordsCount); + } else { + recordsCount = recordsCountCurrent; + } + } + AFL_VERIFY(entityId < i.GetEntityId())("entity", entityId)("next", i.GetEntityId())("details", debugString()); + AFL_VERIFY(i.GetChunkIdx() == 0); + entityId = i.GetEntityId(); + chunkIdx = 0; + recordsCountCurrent = 0; + } else { + AFL_VERIFY(i.GetChunkIdx() == chunkIdx + 1)("chunkIdx", chunkIdx)("i.GetChunkIdx()", i.GetChunkIdx())("entity", entityId)( + "details", debugString()); + chunkIdx = i.GetChunkIdx(); + } + recordsCountCurrent += GetRecordsCount(i); + AFL_VERIFY(i.GetEntityId()); + } + if (recordsCount) { + AFL_VERIFY(recordsCountCurrent == *recordsCount); + } + } + + void ReorderChunks() { + { + auto pred = [](const TColumnRecord& l, const TColumnRecord& r) { + return l.GetAddress() < r.GetAddress(); + }; + std::sort(Records.begin(), Records.end(), pred); + CheckChunksOrder(Records); + } + { + auto pred = [](const TIndexChunk& l, const TIndexChunk& r) { + return l.GetAddress() < r.GetAddress(); + }; + std::sort(Indexes.begin(), Indexes.end(), pred); + CheckChunksOrder(Indexes); + } + if (NeedBlobIdxsSort) { + auto pred = [](const TAddressBlobId& l, const TAddressBlobId& r) { + return l.GetAddress() < r.GetAddress(); + }; + std::sort(BlobIdxs.begin(), BlobIdxs.end(), pred); + } + } + +public: + TPortionAccessorConstructor(const ui64 pathId) + : PortionInfo(pathId) + { + + } + + TPortionAccessorConstructor(TPortionInfoConstructor&& portionInfo) + : PortionInfo(std::move(portionInfo)) + { + + } + + TPortionAccessorConstructor MakeCopy() const { + return TPortionAccessorConstructor(*this); + } + + static TPortionAccessorConstructor BuildForRewriteBlobs(const TPortionInfo& portion) { + return TPortionAccessorConstructor(TPortionInfoConstructor(portion, true, false)); + } + + static TPortionDataAccessor BuildForLoading( + const TPortionInfo::TConstPtr& portion, std::vector&& records, std::vector&& indexes); + + const std::vector& GetRecords() const { + return Records; + } + + TPortionInfoConstructor& MutablePortionConstructor() { + return PortionInfo; + } + + std::vector& TestMutableRecords() { + return Records; + } + + const std::vector& TestGetRecords() const { + return Records; + } + + const TPortionInfoConstructor& GetPortionConstructor() const { + return PortionInfo; + } + + void RegisterBlobIdx(const TChunkAddress& address, const TBlobRangeLink16::TLinkId blobIdx) { + if (BlobIdxs.size() && address < BlobIdxs.back().GetAddress()) { + NeedBlobIdxsSort = true; + } + BlobIdxs.emplace_back(address, blobIdx); + } + + TString DebugString() const { + TStringBuilder sb; + sb << PortionInfo.DebugString() << ";"; + for (auto&& i : Records) { + sb << i.DebugString() << ";"; + } + return sb; + } + + const TBlobRange RestoreBlobRangeSlow(const TBlobRangeLink16& linkRange, const TChunkAddress& address) const { + for (auto&& i : BlobIdxs) { + if (i.GetAddress() == address) { + return linkRange.RestoreRange(GetBlobId(i.GetBlobIdx())); + } + } + AFL_VERIFY(false); + return TBlobRange(); + } + + TPortionDataAccessor Build(const bool needChunksNormalization); + + TBlobRangeLink16::TLinkId RegisterBlobId(const TUnifiedBlobId& blobId) { + return PortionInfo.MetaConstructor.RegisterBlobId(blobId); + } + + const TBlobRange RestoreBlobRange(const TBlobRangeLink16& linkRange) const { + return PortionInfo.MetaConstructor.RestoreBlobRange(linkRange); + } + + const TUnifiedBlobId& GetBlobId(const TBlobRangeLink16::TLinkId linkId) const { + return PortionInfo.MetaConstructor.GetBlobId(linkId); + } + + ui32 GetBlobIdsCount() const { + return PortionInfo.MetaConstructor.GetBlobIdsCount(); + } + + TPortionAccessorConstructor(TPortionAccessorConstructor&&) noexcept = default; + TPortionAccessorConstructor& operator=(TPortionAccessorConstructor&&) noexcept = default; + + void LoadRecord(TColumnChunkLoadContextV1&& loadContext); + void LoadIndex(TIndexChunkLoadContext&& loadContext); + + const TColumnRecord& AppendOneChunkColumn(TColumnRecord&& record) { + Y_ABORT_UNLESS(record.ColumnId); + Records.emplace_back(std::move(record)); + return Records.back(); + } + + ui32 CalcRecordsCount() const { + AFL_VERIFY(Records.size()); + ui32 result = 0; + std::optional columnIdFirst; + for (auto&& i : Records) { + if (!columnIdFirst || *columnIdFirst == i.ColumnId) { + result += i.GetMeta().GetRecordsCount(); + columnIdFirst = i.ColumnId; + } + } + AFL_VERIFY(columnIdFirst); + return result; + } + + bool HaveBlobsData() { + return PortionInfo.HaveBlobsData() || Records.size() || Indexes.size(); + } + + void ClearRecords() { + Records.clear(); + } + + void ClearIndexes() { + Indexes.clear(); + } + + void AddIndex(const TIndexChunk& chunk) { + ui32 chunkIdx = 0; + for (auto&& i : Indexes) { + if (i.GetIndexId() == chunk.GetIndexId()) { + AFL_VERIFY(chunkIdx == i.GetChunkIdx())("index_id", chunk.GetIndexId())("expected", chunkIdx)("real", i.GetChunkIdx()); + ++chunkIdx; + } + } + AFL_VERIFY(chunkIdx == chunk.GetChunkIdx())("index_id", chunk.GetIndexId())("expected", chunkIdx)("real", chunk.GetChunkIdx()); + Indexes.emplace_back(chunk); + } +}; + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portions/constructor_meta.cpp b/ydb/core/tx/columnshard/engines/portions/constructor_meta.cpp index fa21c6feeeb6..3667049a77df 100644 --- a/ydb/core/tx/columnshard/engines/portions/constructor_meta.cpp +++ b/ydb/core/tx/columnshard/engines/portions/constructor_meta.cpp @@ -4,31 +4,38 @@ namespace NKikimr::NOlap { -void TPortionMetaConstructor::FillMetaInfo(const NArrow::TFirstLastSpecialKeys& primaryKeys, const ui32 deletionsCount, const NArrow::TMinMaxSpecialKeys& snapshotKeys, const TIndexInfo& indexInfo) { +void TPortionMetaConstructor::FillMetaInfo(const NArrow::TFirstLastSpecialKeys& primaryKeys, const ui32 deletionsCount, const std::optional& snapshotKeys, const TIndexInfo& indexInfo) { AFL_VERIFY(!FirstAndLastPK); FirstAndLastPK = *primaryKeys.BuildAccordingToSchemaVerified(indexInfo.GetReplaceKey()); AFL_VERIFY(!RecordSnapshotMin); AFL_VERIFY(!RecordSnapshotMax); DeletionsCount = deletionsCount; - { - auto cPlanStep = snapshotKeys.GetBatch()->GetColumnByName(TIndexInfo::SPEC_COL_PLAN_STEP); - auto cTxId = snapshotKeys.GetBatch()->GetColumnByName(TIndexInfo::SPEC_COL_TX_ID); + if (snapshotKeys) { + auto cPlanStep = snapshotKeys->GetBatch()->GetColumnByName(TIndexInfo::SPEC_COL_PLAN_STEP); + auto cTxId = snapshotKeys->GetBatch()->GetColumnByName(TIndexInfo::SPEC_COL_TX_ID); Y_ABORT_UNLESS(cPlanStep && cTxId); Y_ABORT_UNLESS(cPlanStep->type_id() == arrow::UInt64Type::type_id); Y_ABORT_UNLESS(cTxId->type_id() == arrow::UInt64Type::type_id); const arrow::UInt64Array& cPlanStepArray = static_cast(*cPlanStep); const arrow::UInt64Array& cTxIdArray = static_cast(*cTxId); RecordSnapshotMin = TSnapshot(cPlanStepArray.GetView(0), cTxIdArray.GetView(0)); - RecordSnapshotMax = TSnapshot(cPlanStepArray.GetView(snapshotKeys.GetBatch()->num_rows() - 1), cTxIdArray.GetView(snapshotKeys.GetBatch()->num_rows() - 1)); + RecordSnapshotMax = TSnapshot(cPlanStepArray.GetView(snapshotKeys->GetBatch()->num_rows() - 1), cTxIdArray.GetView(snapshotKeys->GetBatch()->num_rows() - 1)); + } else { + RecordSnapshotMin = TSnapshot::Zero(); + RecordSnapshotMax = TSnapshot::Zero(); } } -TPortionMetaConstructor::TPortionMetaConstructor(const TPortionMeta& meta) { +TPortionMetaConstructor::TPortionMetaConstructor(const TPortionMeta& meta, const bool withBlobs) { FirstAndLastPK = meta.ReplaceKeyEdges; RecordSnapshotMin = meta.RecordSnapshotMin; RecordSnapshotMax = meta.RecordSnapshotMax; + CompactionLevel = meta.GetCompactionLevel(); DeletionsCount = meta.GetDeletionsCount(); TierName = meta.GetTierNameOptional(); + if (withBlobs) { + BlobIds = meta.BlobIds; + } if (meta.Produced != NPortion::EProduced::UNSPECIFIED) { Produced = meta.Produced; } @@ -42,18 +49,24 @@ TPortionMeta TPortionMetaConstructor::Build() { if (TierName) { result.TierName = *TierName; } - AFL_VERIFY(DeletionsCount); - result.DeletionsCount = *DeletionsCount; - AFL_VERIFY(Produced); - result.Produced = *Produced; + TBase::FullValidation(); + result.BlobIds = BlobIds; + result.BlobIds.shrink_to_fit(); + result.CompactionLevel = *TValidator::CheckNotNull(CompactionLevel); + result.DeletionsCount = *TValidator::CheckNotNull(DeletionsCount); + result.Produced = *TValidator::CheckNotNull(Produced); + + result.RecordsCount = *TValidator::CheckNotNull(RecordsCount); + result.ColumnRawBytes = *TValidator::CheckNotNull(ColumnRawBytes); + result.ColumnBlobBytes = *TValidator::CheckNotNull(ColumnBlobBytes); + result.IndexRawBytes = *TValidator::CheckNotNull(IndexRawBytes); + result.IndexBlobBytes = *TValidator::CheckNotNull(IndexBlobBytes); + return result; } -bool TPortionMetaConstructor::LoadMetadata(const NKikimrTxColumnShard::TIndexPortionMeta& portionMeta, const TIndexInfo& indexInfo) { - if (!!Produced) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "DeserializeFromProto")("error", "parsing duplication"); - return true; - } +bool TPortionMetaConstructor::LoadMetadata(const NKikimrTxColumnShard::TIndexPortionMeta& portionMeta, const TIndexInfo& indexInfo, const IBlobGroupSelector& groupSelector) { + AFL_VERIFY(!Produced)("produced", Produced); if (portionMeta.GetTierName()) { TierName = portionMeta.GetTierName(); } @@ -62,6 +75,16 @@ bool TPortionMetaConstructor::LoadMetadata(const NKikimrTxColumnShard::TIndexPor } else { DeletionsCount = 0; } + for (auto&& i : portionMeta.GetBlobIds()) { + TLogoBlobID logo = TLogoBlobID::FromBinary(i); + BlobIds.emplace_back(TUnifiedBlobId(groupSelector.GetGroup(logo), logo)); + } + CompactionLevel = portionMeta.GetCompactionLevel(); + RecordsCount = TValidator::CheckNotNull(portionMeta.GetRecordsCount()); + ColumnRawBytes = TValidator::CheckNotNull(portionMeta.GetColumnRawBytes()); + ColumnBlobBytes = TValidator::CheckNotNull(portionMeta.GetColumnBlobBytes()); + IndexRawBytes = portionMeta.GetIndexRawBytes(); + IndexBlobBytes = portionMeta.GetIndexBlobBytes(); if (portionMeta.GetIsInserted()) { Produced = TPortionMeta::EProduced::INSERTED; } else if (portionMeta.GetIsCompacted()) { diff --git a/ydb/core/tx/columnshard/engines/portions/constructor_meta.h b/ydb/core/tx/columnshard/engines/portions/constructor_meta.h index 87b808a282fd..ab222ed7e964 100644 --- a/ydb/core/tx/columnshard/engines/portions/constructor_meta.h +++ b/ydb/core/tx/columnshard/engines/portions/constructor_meta.h @@ -1,5 +1,7 @@ #pragma once +#include "common.h" #include "meta.h" + #include #include #include @@ -8,20 +10,53 @@ namespace NKikimr::NOlap { class TPortionInfoConstructor; struct TIndexInfo; -class TPortionMetaConstructor { +class TPortionMetaConstructor: public TPortionMetaBase { private: + using TBase = TPortionMetaBase; std::optional FirstAndLastPK; std::optional TierName; std::optional RecordSnapshotMin; std::optional RecordSnapshotMax; std::optional Produced; + std::optional CompactionLevel; + + std::optional RecordsCount; + std::optional ColumnRawBytes; + std::optional ColumnBlobBytes; + std::optional IndexRawBytes; + std::optional IndexBlobBytes; + std::optional DeletionsCount; + friend class TPortionInfoConstructor; - void FillMetaInfo(const NArrow::TFirstLastSpecialKeys& primaryKeys, const ui32 deletionsCount, const NArrow::TMinMaxSpecialKeys& snapshotKeys, const TIndexInfo& indexInfo); + friend class TPortionAccessorConstructor; + void FillMetaInfo(const NArrow::TFirstLastSpecialKeys& primaryKeys, const ui32 deletionsCount, + const std::optional& snapshotKeys, const TIndexInfo& indexInfo); public: TPortionMetaConstructor() = default; - TPortionMetaConstructor(const TPortionMeta& meta); + TPortionMetaConstructor(const TPortionMeta& meta, const bool withBlobs); + + const TBlobRange RestoreBlobRange(const TBlobRangeLink16& linkRange) const { + return linkRange.RestoreRange(GetBlobId(linkRange.GetBlobIdxVerified())); + } + + TBlobRangeLink16::TLinkId RegisterBlobId(const TUnifiedBlobId& blobId) { + AFL_VERIFY(blobId.IsValid()); + TBlobRangeLink16::TLinkId idx = 0; + for (auto&& i : BlobIds) { + if (i == blobId) { + return idx; + } + ++idx; + } + BlobIds.emplace_back(blobId); + return idx; + } + + void SetCompactionLevel(const ui64 level) { + CompactionLevel = level; + } void SetTierName(const TString& tierName); void ResetTierName(const TString& tierName) { @@ -35,8 +70,8 @@ class TPortionMetaConstructor { TPortionMeta Build(); - [[nodiscard]] bool LoadMetadata(const NKikimrTxColumnShard::TIndexPortionMeta& portionMeta, const TIndexInfo& indexInfo); - + [[nodiscard]] bool LoadMetadata( + const NKikimrTxColumnShard::TIndexPortionMeta& portionMeta, const TIndexInfo& indexInfo, const IBlobGroupSelector& groupSelector); }; -} \ No newline at end of file +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portions/constructor_portion.cpp b/ydb/core/tx/columnshard/engines/portions/constructor_portion.cpp new file mode 100644 index 000000000000..7092b5f46543 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/portions/constructor_portion.cpp @@ -0,0 +1,60 @@ +#include "constructor_portion.h" + +#include +#include +#include +#include +#include +#include + +namespace NKikimr::NOlap { + +std::shared_ptr TPortionInfoConstructor::Build() { + AFL_VERIFY(!Constructed); + Constructed = true; + + std::shared_ptr result(new TPortionInfo(MetaConstructor.Build())); + AFL_VERIFY(PathId); + result->PathId = PathId; + result->PortionId = GetPortionIdVerified(); + + AFL_VERIFY(MinSnapshotDeprecated); + AFL_VERIFY(MinSnapshotDeprecated->Valid()); + result->MinSnapshotDeprecated = *MinSnapshotDeprecated; + if (RemoveSnapshot) { + AFL_VERIFY(RemoveSnapshot->Valid()); + result->RemoveSnapshot = *RemoveSnapshot; + } + result->SchemaVersion = SchemaVersion; + result->ShardingVersion = ShardingVersion; + result->CommitSnapshot = CommitSnapshot; + result->InsertWriteId = InsertWriteId; + AFL_VERIFY(!CommitSnapshot || !!InsertWriteId); + + if (result->GetMeta().GetProduced() == NPortion::EProduced::INSERTED) { +// AFL_VERIFY(!!InsertWriteId); + } else { + AFL_VERIFY(!CommitSnapshot); + AFL_VERIFY(!InsertWriteId); + } + + return result; +} + +ISnapshotSchema::TPtr TPortionInfoConstructor::GetSchema(const TVersionedIndex& index) const { + if (SchemaVersion) { + auto schema = index.GetSchemaVerified(SchemaVersion.value()); + AFL_VERIFY(!!schema)("details", TStringBuilder() << "cannot find schema for version " << SchemaVersion.value()); + return schema; + } else { + AFL_VERIFY(MinSnapshotDeprecated); + return index.GetSchemaVerified(*MinSnapshotDeprecated); + } +} + +void TPortionInfoConstructor::AddMetadata(const ISnapshotSchema& snapshotSchema, const std::shared_ptr& batch) { + MetaConstructor.FillMetaInfo(NArrow::TFirstLastSpecialKeys(batch), IIndexInfo::CalcDeletions(batch, false), + NArrow::TMinMaxSpecialKeys(batch, TIndexInfo::ArrowSchemaSnapshot()), snapshotSchema.GetIndexInfo()); +} + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portions/constructor_portion.h b/ydb/core/tx/columnshard/engines/portions/constructor_portion.h new file mode 100644 index 000000000000..a11e3fcf1ac1 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/portions/constructor_portion.h @@ -0,0 +1,175 @@ +#pragma once +#include "constructor_meta.h" +#include "portion_info.h" + +#include + +#include + +namespace NKikimr::NOlap { + +class TColumnChunkLoadContextV1; +class TIndexChunkLoadContext; +class TPortionAccessorConstructor; + +class TPortionInfoConstructor { +private: + bool Constructed = false; + YDB_ACCESSOR(ui64, PathId, 0); + std::optional PortionId; + + TPortionMetaConstructor MetaConstructor; + + std::optional MinSnapshotDeprecated; + std::optional RemoveSnapshot; + std::optional SchemaVersion; + std::optional ShardingVersion; + + std::optional CommitSnapshot; + std::optional InsertWriteId; + + TPortionInfoConstructor(const TPortionInfoConstructor&) = default; + TPortionInfoConstructor& operator=(const TPortionInfoConstructor&) = default; + + TPortionInfoConstructor(TPortionInfo&& portion) + : PathId(portion.GetPathId()) + , PortionId(portion.GetPortionId()) + , MinSnapshotDeprecated(portion.GetMinSnapshotDeprecated()) + , RemoveSnapshot(portion.GetRemoveSnapshotOptional()) + , SchemaVersion(portion.GetSchemaVersionOptional()) + , ShardingVersion(portion.GetShardingVersionOptional()) { + MetaConstructor = TPortionMetaConstructor(std::move(portion.Meta), true); + } + friend class TPortionAccessorConstructor; + +public: + TPortionInfoConstructor(TPortionInfoConstructor&&) noexcept = default; + TPortionInfoConstructor& operator=(TPortionInfoConstructor&&) noexcept = default; + + TPortionInfoConstructor(const TPortionInfo& portion, const bool withMetadata, const bool withMetadataBlobs) + : PathId(portion.GetPathId()) + , PortionId(portion.GetPortionId()) + , MinSnapshotDeprecated(portion.GetMinSnapshotDeprecated()) + , RemoveSnapshot(portion.GetRemoveSnapshotOptional()) + , SchemaVersion(portion.GetSchemaVersionOptional()) + , ShardingVersion(portion.GetShardingVersionOptional()) + , CommitSnapshot(portion.GetCommitSnapshotOptional()) + , InsertWriteId(portion.GetInsertWriteIdOptional()) { + if (withMetadata) { + MetaConstructor = TPortionMetaConstructor(portion.Meta, withMetadataBlobs); + } else { + AFL_VERIFY(!withMetadataBlobs); + } + } + + bool HaveBlobsData() { + return MetaConstructor.GetBlobIdsCount(); + } + + void SetPortionId(const ui64 value) { + AFL_VERIFY(value); + PortionId = value; + } + + void AddMetadata(const ISnapshotSchema& snapshotSchema, const std::shared_ptr& batch); + + void AddMetadata(const ISnapshotSchema& snapshotSchema, const ui32 deletionsCount, const NArrow::TFirstLastSpecialKeys& firstLastRecords, + const std::optional& minMaxSpecial) { + MetaConstructor.FillMetaInfo(firstLastRecords, deletionsCount, minMaxSpecial, snapshotSchema.GetIndexInfo()); + } + + ui64 GetPortionIdVerified() const { + AFL_VERIFY(PortionId); + AFL_VERIFY(*PortionId); + return *PortionId; + } + + TPortionMetaConstructor& MutableMeta() { + return MetaConstructor; + } + + const TPortionMetaConstructor& GetMeta() const { + return MetaConstructor; + } + + TInsertWriteId GetInsertWriteIdVerified() const { + AFL_VERIFY(InsertWriteId); + return *InsertWriteId; + } + + TPortionAddress GetAddress() const { + return TPortionAddress(PathId, GetPortionIdVerified()); + } + + bool HasRemoveSnapshot() const { + return !!RemoveSnapshot; + } + + TPortionInfoConstructor(const ui64 pathId, const ui64 portionId) + : PathId(pathId) + , PortionId(portionId) { + AFL_VERIFY(PathId); + AFL_VERIFY(PortionId); + } + + TPortionInfoConstructor(const ui64 pathId) + : PathId(pathId) { + AFL_VERIFY(PathId); + } + + const TSnapshot& GetMinSnapshotDeprecatedVerified() const { + AFL_VERIFY(!!MinSnapshotDeprecated); + return *MinSnapshotDeprecated; + } + + std::shared_ptr GetSchema(const TVersionedIndex& index) const; + + void SetCommitSnapshot(const TSnapshot& snap) { + AFL_VERIFY(!!InsertWriteId); + AFL_VERIFY(!CommitSnapshot); + AFL_VERIFY(snap.Valid()); + CommitSnapshot = snap; + } + + void SetInsertWriteId(const TInsertWriteId value) { + AFL_VERIFY(!InsertWriteId); + AFL_VERIFY((ui64)value); + InsertWriteId = value; + } + + void SetMinSnapshotDeprecated(const TSnapshot& snap) { + Y_ABORT_UNLESS(snap.Valid()); + MinSnapshotDeprecated = snap; + } + + void SetSchemaVersion(const ui64 version) { + // AFL_VERIFY(version); + SchemaVersion = version; + } + + void SetShardingVersion(const ui64 version) { + // AFL_VERIFY(version); + ShardingVersion = version; + } + + void SetRemoveSnapshot(const TSnapshot& snap) { + AFL_VERIFY(!RemoveSnapshot); + if (snap.Valid()) { + RemoveSnapshot = snap; + } + } + + void SetRemoveSnapshot(const ui64 planStep, const ui64 txId) { + SetRemoveSnapshot(TSnapshot(planStep, txId)); + } + + TString DebugString() const { + TStringBuilder sb; + sb << (PortionId ? *PortionId : 0) << ";"; + return sb; + } + + std::shared_ptr Build(); +}; + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portions/constructors.cpp b/ydb/core/tx/columnshard/engines/portions/constructors.cpp new file mode 100644 index 000000000000..11b951a871e9 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/portions/constructors.cpp @@ -0,0 +1,5 @@ +#include "constructors.h" + +namespace NKikimr::NOlap { + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portions/constructors.h b/ydb/core/tx/columnshard/engines/portions/constructors.h new file mode 100644 index 000000000000..f3f1d18f614e --- /dev/null +++ b/ydb/core/tx/columnshard/engines/portions/constructors.h @@ -0,0 +1,86 @@ +#pragma once +#include "constructor_accessor.h" + +namespace NKikimr::NOlap { + +class TPortionConstructors { +private: + THashMap> Constructors; + +public: + THashMap>::iterator begin() { + return Constructors.begin(); + } + + THashMap>::iterator end() { + return Constructors.end(); + } + + TPortionAccessorConstructor* GetConstructorVerified(const ui64 pathId, const ui64 portionId) { + auto itPathId = Constructors.find(pathId); + AFL_VERIFY(itPathId != Constructors.end()); + auto itPortionId = itPathId->second.find(portionId); + AFL_VERIFY(itPortionId != itPathId->second.end()); + return &itPortionId->second; + } + + TPortionAccessorConstructor* AddConstructorVerified(TPortionAccessorConstructor&& constructor) { + const ui64 pathId = constructor.GetPortionConstructor().GetPathId(); + const ui64 portionId = constructor.GetPortionConstructor().GetPortionIdVerified(); + auto info = Constructors[pathId].emplace(portionId, std::move(constructor)); + AFL_VERIFY(info.second); + return &info.first->second; + } +}; + +class TInGranuleConstructors { +private: + THashMap Constructors; + +public: + THashMap::iterator begin() { + return Constructors.begin(); + } + + THashMap::iterator end() { + return Constructors.end(); + } + + void ClearPortions() { + Constructors.clear(); + } + + void ClearColumns() { + for (auto&& i : Constructors) { + i.second.ClearRecords(); + } + } + + void ClearIndexes() { + for (auto&& i : Constructors) { + i.second.ClearIndexes(); + } + } + + TPortionAccessorConstructor* GetConstructorVerified(const ui64 portionId) { + auto itPortionId = Constructors.find(portionId); + AFL_VERIFY(itPortionId != Constructors.end()); + return &itPortionId->second; + } + + TPortionAccessorConstructor* AddConstructorVerified(TPortionAccessorConstructor&& constructor) { + const ui64 portionId = constructor.GetPortionConstructor().GetPortionIdVerified(); + auto info = Constructors.emplace(portionId, std::move(constructor)); + AFL_VERIFY(info.second); + return &info.first->second; + } + + TPortionAccessorConstructor* AddConstructorVerified(TPortionInfoConstructor&& constructor) { + const ui64 portionId = constructor.GetPortionIdVerified(); + auto info = Constructors.emplace(portionId, TPortionAccessorConstructor(std::move(constructor))); + AFL_VERIFY(info.second); + return &info.first->second; + } +}; + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portions/data_accessor.cpp b/ydb/core/tx/columnshard/engines/portions/data_accessor.cpp new file mode 100644 index 000000000000..61209dcee4de --- /dev/null +++ b/ydb/core/tx/columnshard/engines/portions/data_accessor.cpp @@ -0,0 +1,800 @@ +#include "constructor_meta.h" +#include "data_accessor.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace NKikimr::NOlap { + +namespace { + +void FillDefaultColumn( + TPortionDataAccessor::TColumnAssemblingInfo& column, const TPortionInfo& portionInfo, const TSnapshot& defaultSnapshot) { + if (column.GetColumnId() == (ui32)IIndexInfo::ESpecialColumn::PLAN_STEP) { + column.AddBlobInfo(0, portionInfo.GetRecordsCount(), + TPortionDataAccessor::TAssembleBlobInfo( + portionInfo.GetRecordsCount(), std::make_shared(defaultSnapshot.GetPlanStep()), false)); + } + if (column.GetColumnId() == (ui32)IIndexInfo::ESpecialColumn::TX_ID) { + column.AddBlobInfo(0, portionInfo.GetRecordsCount(), + TPortionDataAccessor::TAssembleBlobInfo( + portionInfo.GetRecordsCount(), std::make_shared(defaultSnapshot.GetTxId()), false)); + } + if (column.GetColumnId() == (ui32)IIndexInfo::ESpecialColumn::WRITE_ID) { + column.AddBlobInfo(0, portionInfo.GetRecordsCount(), + TPortionDataAccessor::TAssembleBlobInfo( + portionInfo.GetRecordsCount(), std::make_shared((ui64)portionInfo.GetInsertWriteIdVerified()), false)); + } + if (column.GetColumnId() == (ui32)IIndexInfo::ESpecialColumn::DELETE_FLAG) { + AFL_VERIFY(portionInfo.GetRecordsCount() == portionInfo.GetMeta().GetDeletionsCount() || portionInfo.GetMeta().GetDeletionsCount() == 0)("deletes", + portionInfo.GetMeta().GetDeletionsCount())("count", portionInfo.GetRecordsCount()); + column.AddBlobInfo(0, portionInfo.GetRecordsCount(), + TPortionDataAccessor::TAssembleBlobInfo( + portionInfo.GetRecordsCount(), std::make_shared((bool)portionInfo.GetMeta().GetDeletionsCount()), true)); + } +} + +template +TPortionDataAccessor::TPreparedBatchData PrepareForAssembleImpl(const TPortionDataAccessor& portionData, const TPortionInfo& portionInfo, + const ISnapshotSchema& dataSchema, const ISnapshotSchema& resultSchema, THashMap& blobsData, + const std::optional& defaultSnapshot, const bool restoreAbsent) { + std::vector columns; + columns.reserve(resultSchema.GetColumnIds().size()); + const ui32 rowsCount = portionInfo.GetRecordsCount(); + auto it = portionData.GetRecordsVerified().begin(); + + TSnapshot defaultSnapshotLocal = TSnapshot::Zero(); + if (portionInfo.HasCommitSnapshot()) { + defaultSnapshotLocal = portionInfo.GetCommitSnapshotVerified(); + } else if (defaultSnapshot) { + defaultSnapshotLocal = *defaultSnapshot; + } + + for (auto&& i : resultSchema.GetColumnIds()) { + while (it != portionData.GetRecordsVerified().end() && it->GetColumnId() < i) { + ++it; + continue; + } + if ((it == portionData.GetRecordsVerified().end() || i < it->GetColumnId())) { + if (restoreAbsent || IIndexInfo::IsSpecialColumn(i)) { + columns.emplace_back(rowsCount, dataSchema.GetColumnLoaderOptional(i), resultSchema.GetColumnLoaderVerified(i)); + } + if (!portionInfo.HasInsertWriteId()) { + continue; + } + FillDefaultColumn(columns.back(), portionInfo, defaultSnapshotLocal); + } + if (it == portionData.GetRecordsVerified().end()) { + continue; + } else if (it->GetColumnId() != i) { + AFL_VERIFY(i < it->GetColumnId()); + continue; + } + columns.emplace_back(rowsCount, dataSchema.GetColumnLoaderOptional(i), resultSchema.GetColumnLoaderVerified(i)); + while (it != portionData.GetRecordsVerified().end() && it->GetColumnId() == i) { + auto itBlobs = blobsData.find(it->GetAddress()); + AFL_VERIFY(itBlobs != blobsData.end())("size", blobsData.size())("address", it->GetAddress().DebugString()); + columns.back().AddBlobInfo(it->Chunk, it->GetMeta().GetRecordsCount(), std::move(itBlobs->second)); + blobsData.erase(itBlobs); + + ++it; + continue; + } + } + + // Make chunked arrays for columns + std::vector preparedColumns; + preparedColumns.reserve(columns.size()); + for (auto& c : columns) { + preparedColumns.emplace_back(c.Compile()); + } + + return TPortionDataAccessor::TPreparedBatchData(std::move(preparedColumns), rowsCount); +} + +} // namespace + +TPortionDataAccessor::TPreparedBatchData TPortionDataAccessor::PrepareForAssemble(const ISnapshotSchema& dataSchema, + const ISnapshotSchema& resultSchema, THashMap& blobsData, const std::optional& defaultSnapshot, + const bool restoreAbsent) const { + return PrepareForAssembleImpl(*this, *PortionInfo, dataSchema, resultSchema, blobsData, defaultSnapshot, restoreAbsent); +} + +TPortionDataAccessor::TPreparedBatchData TPortionDataAccessor::PrepareForAssemble(const ISnapshotSchema& dataSchema, + const ISnapshotSchema& resultSchema, THashMap& blobsData, const std::optional& defaultSnapshot, + const bool restoreAbsent) const { + return PrepareForAssembleImpl(*this, *PortionInfo, dataSchema, resultSchema, blobsData, defaultSnapshot, restoreAbsent); +} + +void TPortionDataAccessor::FillBlobRangesByStorage(THashMap>& result, const TVersionedIndex& index) const { + auto schema = PortionInfo->GetSchema(index); + return FillBlobRangesByStorage(result, schema->GetIndexInfo()); +} + +void TPortionDataAccessor::FillBlobRangesByStorage(THashMap>& result, const TIndexInfo& indexInfo) const { + for (auto&& i : GetRecordsVerified()) { + const TString& storageId = PortionInfo->GetColumnStorageId(i.GetColumnId(), indexInfo); + AFL_VERIFY(result[storageId].emplace(PortionInfo->RestoreBlobRange(i.GetBlobRange())).second)( + "blob_id", PortionInfo->RestoreBlobRange(i.GetBlobRange()).ToString()); + } + for (auto&& i : GetIndexesVerified()) { + const TString& storageId = PortionInfo->GetIndexStorageId(i.GetIndexId(), indexInfo); + if (auto bRange = i.GetBlobRangeOptional()) { + AFL_VERIFY(result[storageId].emplace(PortionInfo->RestoreBlobRange(*bRange)).second)( + "blob_id", PortionInfo->RestoreBlobRange(*bRange).ToString()); + } + } +} + +void TPortionDataAccessor::FillBlobIdsByStorage(THashMap>& result, const TIndexInfo& indexInfo) const { + THashMap> local; + THashSet* currentHashLocal = nullptr; + THashSet* currentHashResult = nullptr; + std::optional lastEntityId; + TString lastStorageId; + ui32 lastBlobIdx = PortionInfo->GetBlobIdsCount(); + for (auto&& i : GetRecordsVerified()) { + if (!lastEntityId || *lastEntityId != i.GetEntityId()) { + const TString& storageId = PortionInfo->GetColumnStorageId(i.GetEntityId(), indexInfo); + lastEntityId = i.GetEntityId(); + if (storageId != lastStorageId) { + currentHashResult = &result[storageId]; + currentHashLocal = &local[storageId]; + lastStorageId = storageId; + lastBlobIdx = PortionInfo->GetBlobIdsCount(); + } + } + if (lastBlobIdx != i.GetBlobRange().GetBlobIdxVerified() && currentHashLocal->emplace(i.GetBlobRange().GetBlobIdxVerified()).second) { + auto blobId = PortionInfo->GetBlobId(i.GetBlobRange().GetBlobIdxVerified()); + AFL_VERIFY(currentHashResult); + AFL_VERIFY(currentHashResult->emplace(blobId).second)("blob_id", blobId.ToStringNew()); + lastBlobIdx = i.GetBlobRange().GetBlobIdxVerified(); + } + } + for (auto&& i : GetIndexesVerified()) { + if (!lastEntityId || *lastEntityId != i.GetEntityId()) { + const TString& storageId = PortionInfo->GetIndexStorageId(i.GetEntityId(), indexInfo); + lastEntityId = i.GetEntityId(); + if (storageId != lastStorageId) { + currentHashResult = &result[storageId]; + currentHashLocal = &local[storageId]; + lastStorageId = storageId; + lastBlobIdx = PortionInfo->GetBlobIdsCount(); + } + } + if (auto bRange = i.GetBlobRangeOptional()) { + if (lastBlobIdx != bRange->GetBlobIdxVerified() && currentHashLocal->emplace(bRange->GetBlobIdxVerified()).second) { + auto blobId = PortionInfo->GetBlobId(bRange->GetBlobIdxVerified()); + AFL_VERIFY(currentHashResult); + AFL_VERIFY(currentHashResult->emplace(blobId).second)("blob_id", blobId.ToStringNew()); + lastBlobIdx = bRange->GetBlobIdxVerified(); + } + } + } +} + +void TPortionDataAccessor::FillBlobIdsByStorage(THashMap>& result, const TVersionedIndex& index) const { + auto schema = PortionInfo->GetSchema(index); + return FillBlobIdsByStorage(result, schema->GetIndexInfo()); +} + +THashMap>> +TPortionDataAccessor::RestoreEntityChunks(NBlobOperations::NRead::TCompositeReadBlobs& blobs, const TIndexInfo& indexInfo) const { + THashMap>> result; + for (auto&& c : GetRecordsVerified()) { + const TString& storageId = PortionInfo->GetColumnStorageId(c.GetColumnId(), indexInfo); + auto chunk = std::make_shared( + blobs.Extract(storageId, PortionInfo->RestoreBlobRange(c.GetBlobRange())), c, indexInfo.GetColumnFeaturesVerified(c.GetColumnId())); + chunk->SetChunkIdx(c.GetChunkIdx()); + AFL_VERIFY(result[storageId].emplace(c.GetAddress(), chunk).second); + } + for (auto&& c : GetIndexesVerified()) { + const TString& storageId = indexInfo.GetIndexStorageId(c.GetIndexId()); + const TString blobData = [&]() -> TString { + if (auto bRange = c.GetBlobRangeOptional()) { + return blobs.Extract(storageId, PortionInfo->RestoreBlobRange(*bRange)); + } else if (auto data = c.GetBlobDataOptional()) { + return *data; + } else { + AFL_VERIFY(false); + Y_UNREACHABLE(); + } + }(); + auto chunk = std::make_shared(c.GetAddress(), c.GetRecordsCount(), c.GetRawBytes(), blobData); + chunk->SetChunkIdx(c.GetChunkIdx()); + + AFL_VERIFY(result[storageId].emplace(c.GetAddress(), chunk).second); + } + return result; +} + +THashMap TPortionDataAccessor::DecodeBlobAddresses( + NBlobOperations::NRead::TCompositeReadBlobs&& blobs, const TIndexInfo& indexInfo) const { + THashMap result; + for (auto&& i : blobs) { + for (auto&& b : i.second) { + bool found = false; + TString columnStorageId; + ui32 columnId = 0; + for (auto&& record : GetRecordsVerified()) { + if (PortionInfo->RestoreBlobRange(record.GetBlobRange()) == b.first) { + if (columnId != record.GetColumnId()) { + columnStorageId = PortionInfo->GetColumnStorageId(record.GetColumnId(), indexInfo); + } + if (columnStorageId != i.first) { + continue; + } + result.emplace(record.GetAddress(), std::move(b.second)); + found = true; + break; + } + } + if (found) { + continue; + } + for (auto&& record : GetIndexesVerified()) { + if (!record.HasBlobRange()) { + continue; + } + if (PortionInfo->RestoreBlobRange(record.GetBlobRangeVerified()) == b.first) { + if (columnId != record.GetIndexId()) { + columnStorageId = indexInfo.GetIndexStorageId(record.GetIndexId()); + } + if (columnStorageId != i.first) { + continue; + } + result.emplace(record.GetAddress(), std::move(b.second)); + found = true; + break; + } + } + AFL_VERIFY(found)("blobs", blobs.DebugString())("records", DebugString())("problem", b.first); + } + } + return result; +} + +bool TPortionDataAccessor::HasEntityAddress(const TChunkAddress& address) const { + { + auto it = std::lower_bound( + GetRecordsVerified().begin(), GetRecordsVerified().end(), address, [](const TColumnRecord& item, const TChunkAddress& address) { + return item.GetAddress() < address; + }); + if (it != GetRecordsVerified().end() && it->GetAddress() == address) { + return true; + } + } + { + auto it = std::lower_bound( + GetIndexesVerified().begin(), GetIndexesVerified().end(), address, [](const TIndexChunk& item, const TChunkAddress& address) { + return item.GetAddress() < address; + }); + if (it != GetIndexesVerified().end() && it->GetAddress() == address) { + return true; + } + } + return false; +} + +const NKikimr::NOlap::TColumnRecord* TPortionDataAccessor::GetRecordPointer(const TChunkAddress& address) const { + auto it = std::lower_bound( + GetRecordsVerified().begin(), GetRecordsVerified().end(), address, [](const TColumnRecord& item, const TChunkAddress& address) { + return item.GetAddress() < address; + }); + if (it != GetRecordsVerified().end() && it->GetAddress() == address) { + return &*it; + } + return nullptr; +} + +TString TPortionDataAccessor::DebugString() const { + TStringBuilder sb; + sb << "chunks:(" << GetRecordsVerified().size() << ");"; + if (IS_TRACE_LOG_ENABLED(NKikimrServices::TX_COLUMNSHARD)) { + std::vector blobRanges; + for (auto&& i : GetRecordsVerified()) { + blobRanges.emplace_back(PortionInfo->RestoreBlobRange(i.BlobRange)); + } + sb << "blobs:" << JoinSeq(",", blobRanges) << ";ranges_count:" << blobRanges.size() << ";"; + } + return sb << ")"; +} + +ui64 TPortionDataAccessor::GetColumnRawBytes(const std::set& entityIds, const bool validation /*= true*/) const { + ui64 sum = 0; + const auto aggr = [&](const TColumnRecord& r) { + sum += r.GetMeta().GetRawBytes(); + }; + AggregateIndexChunksData(aggr, GetRecordsVerified(), &entityIds, validation); + return sum; +} + +ui64 TPortionDataAccessor::GetColumnBlobBytes(const std::set& entityIds, const bool validation /*= true*/) const { + ui64 sum = 0; + const auto aggr = [&](const TColumnRecord& r) { + sum += r.GetBlobRange().GetSize(); + }; + AggregateIndexChunksData(aggr, GetRecordsVerified(), &entityIds, validation); + return sum; +} + +ui64 TPortionDataAccessor::GetIndexRawBytes(const std::set& entityIds, const bool validation /*= true*/) const { + ui64 sum = 0; + const auto aggr = [&](const TIndexChunk& r) { + sum += r.GetRawBytes(); + }; + AggregateIndexChunksData(aggr, GetIndexesVerified(), &entityIds, validation); + return sum; +} + +ui64 TPortionDataAccessor::GetIndexRawBytes(const bool validation /*= true*/) const { + ui64 sum = 0; + const auto aggr = [&](const TIndexChunk& r) { + sum += r.GetRawBytes(); + }; + AggregateIndexChunksData(aggr, GetIndexesVerified(), nullptr, validation); + return sum; +} + +std::vector TPortionDataAccessor::GetColumnChunksPointers(const ui32 columnId) const { + std::vector result; + for (auto&& c : GetRecordsVerified()) { + if (c.ColumnId == columnId) { + Y_ABORT_UNLESS(c.Chunk == result.size()); + Y_ABORT_UNLESS(c.GetMeta().GetRecordsCount()); + result.emplace_back(&c); + } + } + return result; +} + +std::vector TPortionDataAccessor::BuildReadPages(const ui64 memoryLimit, const std::set& entityIds) const { + class TEntityDelimiter { + private: + YDB_READONLY(ui32, IndexStart, 0); + YDB_READONLY(ui32, EntityId, 0); + YDB_READONLY(ui32, ChunkIdx, 0); + YDB_READONLY(ui64, MemoryStartChunk, 0); + YDB_READONLY(ui64, MemoryFinishChunk, 0); + + public: + TEntityDelimiter(const ui32 indexStart, const ui32 entityId, const ui32 chunkIdx, const ui64 memStartChunk, const ui64 memFinishChunk) + : IndexStart(indexStart) + , EntityId(entityId) + , ChunkIdx(chunkIdx) + , MemoryStartChunk(memStartChunk) + , MemoryFinishChunk(memFinishChunk) { + } + + bool operator<(const TEntityDelimiter& item) const { + return std::tie(IndexStart, EntityId, ChunkIdx) < std::tie(item.IndexStart, item.EntityId, item.ChunkIdx); + } + }; + + class TGlobalDelimiter { + private: + YDB_READONLY(ui32, IndexStart, 0); + YDB_ACCESSOR(ui64, UsedMemory, 0); + YDB_ACCESSOR(ui64, WholeChunksMemory, 0); + + public: + TGlobalDelimiter(const ui32 indexStart) + : IndexStart(indexStart) { + } + }; + + std::vector delimiters; + + ui32 lastAppliedId = 0; + ui32 currentRecordIdx = 0; + bool needOne = false; + const TColumnRecord* lastRecord = nullptr; + for (auto&& i : GetRecordsVerified()) { + if (lastAppliedId != i.GetEntityId()) { + if (delimiters.size()) { + AFL_VERIFY(delimiters.back().GetIndexStart() == PortionInfo->GetRecordsCount()); + } + needOne = entityIds.contains(i.GetEntityId()); + currentRecordIdx = 0; + lastAppliedId = i.GetEntityId(); + lastRecord = nullptr; + } + if (!needOne) { + continue; + } + delimiters.emplace_back( + currentRecordIdx, i.GetEntityId(), i.GetChunkIdx(), i.GetMeta().GetRawBytes(), lastRecord ? lastRecord->GetMeta().GetRawBytes() : 0); + currentRecordIdx += i.GetMeta().GetRecordsCount(); + if (currentRecordIdx == PortionInfo->GetRecordsCount()) { + delimiters.emplace_back(currentRecordIdx, i.GetEntityId(), i.GetChunkIdx() + 1, 0, i.GetMeta().GetRawBytes()); + } + lastRecord = &i; + } + if (delimiters.empty()) { + return { TPortionDataAccessor::TReadPage(0, PortionInfo->GetRecordsCount(), 0) }; + } + std::sort(delimiters.begin(), delimiters.end()); + std::vector sumDelimiters; + for (auto&& i : delimiters) { + if (sumDelimiters.empty()) { + sumDelimiters.emplace_back(i.GetIndexStart()); + } else if (sumDelimiters.back().GetIndexStart() != i.GetIndexStart()) { + AFL_VERIFY(sumDelimiters.back().GetIndexStart() < i.GetIndexStart()); + TGlobalDelimiter backDelimiter(i.GetIndexStart()); + backDelimiter.MutableWholeChunksMemory() = sumDelimiters.back().GetWholeChunksMemory(); + backDelimiter.MutableUsedMemory() = sumDelimiters.back().GetUsedMemory(); + sumDelimiters.emplace_back(std::move(backDelimiter)); + } + sumDelimiters.back().MutableWholeChunksMemory() += i.GetMemoryFinishChunk(); + sumDelimiters.back().MutableUsedMemory() += i.GetMemoryStartChunk(); + } + std::vector recordIdx = { 0 }; + std::vector packMemorySize; + const TGlobalDelimiter* lastBorder = &sumDelimiters.front(); + for (auto&& i : sumDelimiters) { + const i64 sumMemory = (i64)i.GetUsedMemory() - (i64)lastBorder->GetWholeChunksMemory(); + AFL_VERIFY(sumMemory > 0); + if (((ui64)sumMemory >= memoryLimit || i.GetIndexStart() == PortionInfo->GetRecordsCount()) && i.GetIndexStart()) { + AFL_VERIFY(lastBorder->GetIndexStart() < i.GetIndexStart()); + recordIdx.emplace_back(i.GetIndexStart()); + packMemorySize.emplace_back(sumMemory); + lastBorder = &i; + } + } + AFL_VERIFY(recordIdx.front() == 0); + AFL_VERIFY(recordIdx.back() == PortionInfo->GetRecordsCount())("real", JoinSeq(",", recordIdx))("expected", PortionInfo->GetRecordsCount()); + AFL_VERIFY(recordIdx.size() == packMemorySize.size() + 1); + std::vector pages; + for (ui32 i = 0; i < packMemorySize.size(); ++i) { + pages.emplace_back(recordIdx[i], recordIdx[i + 1] - recordIdx[i], packMemorySize[i]); + } + return pages; +} + +std::vector TPortionDataAccessor::BuildPages() const { + std::vector pages; + struct TPart { + public: + const TColumnRecord* Record = nullptr; + const TIndexChunk* Index = nullptr; + const ui32 RecordsCount; + TPart(const TColumnRecord* record, const ui32 recordsCount) + : Record(record) + , RecordsCount(recordsCount) { + } + TPart(const TIndexChunk* record, const ui32 recordsCount) + : Index(record) + , RecordsCount(recordsCount) { + } + }; + std::map> entities; + std::map currentCursor; + ui32 currentSize = 0; + ui32 currentId = 0; + for (auto&& i : GetRecordsVerified()) { + if (currentId != i.GetColumnId()) { + currentSize = 0; + currentId = i.GetColumnId(); + } + currentSize += i.GetMeta().GetRecordsCount(); + ++currentCursor[currentSize]; + entities[i.GetColumnId()].emplace_back(&i, i.GetMeta().GetRecordsCount()); + } + for (auto&& i : GetIndexesVerified()) { + if (currentId != i.GetIndexId()) { + currentSize = 0; + currentId = i.GetIndexId(); + } + currentSize += i.GetRecordsCount(); + ++currentCursor[currentSize]; + entities[i.GetIndexId()].emplace_back(&i, i.GetRecordsCount()); + } + const ui32 entitiesCount = entities.size(); + ui32 predCount = 0; + for (auto&& i : currentCursor) { + if (i.second != entitiesCount) { + continue; + } + std::vector records; + std::vector indexes; + for (auto&& c : entities) { + ui32 readyCount = 0; + while (readyCount < i.first - predCount && c.second.size()) { + if (c.second.front().Record) { + records.emplace_back(c.second.front().Record); + } else { + AFL_VERIFY(c.second.front().Index); + indexes.emplace_back(c.second.front().Index); + } + readyCount += c.second.front().RecordsCount; + c.second.pop_front(); + } + AFL_VERIFY(readyCount == i.first - predCount)("ready", readyCount)("cursor", i.first)("pred_cursor", predCount); + } + pages.emplace_back(std::move(records), std::move(indexes), i.first - predCount); + predCount = i.first; + } + for (auto&& i : entities) { + AFL_VERIFY(i.second.empty()); + } + return pages; +} + +ui64 TPortionDataAccessor::GetMinMemoryForReadColumns(const std::optional>& columnIds) const { + ui32 columnId = 0; + ui32 chunkIdx = 0; + + struct TDelta { + i64 BlobBytes = 0; + i64 RawBytes = 0; + void operator+=(const TDelta& add) { + BlobBytes += add.BlobBytes; + RawBytes += add.RawBytes; + } + }; + + std::map diffByPositions; + ui64 position = 0; + ui64 RawBytesCurrent = 0; + ui64 BlobBytesCurrent = 0; + std::optional recordsCount; + + const auto doFlushColumn = [&]() { + if (!recordsCount && position) { + recordsCount = position; + } else { + AFL_VERIFY(*recordsCount == position); + } + if (position) { + TDelta delta; + delta.RawBytes = -1 * RawBytesCurrent; + delta.BlobBytes = -1 * BlobBytesCurrent; + diffByPositions[position] += delta; + } + position = 0; + chunkIdx = 0; + RawBytesCurrent = 0; + BlobBytesCurrent = 0; + }; + + for (auto&& i : GetRecordsVerified()) { + if (columnIds && !columnIds->contains(i.GetColumnId())) { + continue; + } + if (columnId != i.GetColumnId()) { + if (columnId) { + doFlushColumn(); + } + AFL_VERIFY(i.GetColumnId() > columnId); + AFL_VERIFY(i.GetChunkIdx() == 0); + columnId = i.GetColumnId(); + } else { + AFL_VERIFY(i.GetChunkIdx() == chunkIdx + 1); + } + chunkIdx = i.GetChunkIdx(); + TDelta delta; + delta.RawBytes = -1 * RawBytesCurrent + i.GetMeta().GetRawBytes(); + delta.BlobBytes = -1 * BlobBytesCurrent + i.GetBlobRange().Size; + diffByPositions[position] += delta; + position += i.GetMeta().GetRecordsCount(); + RawBytesCurrent = i.GetMeta().GetRawBytes(); + BlobBytesCurrent = i.GetBlobRange().Size; + } + if (columnId) { + doFlushColumn(); + } + i64 maxRawBytes = 0; + TDelta current; + for (auto&& i : diffByPositions) { + current += i.second; + AFL_VERIFY(current.BlobBytes >= 0); + AFL_VERIFY(current.RawBytes >= 0); + if (maxRawBytes < current.RawBytes) { + maxRawBytes = current.RawBytes; + } + } + AFL_VERIFY(current.BlobBytes == 0)("real", current.BlobBytes); + AFL_VERIFY(current.RawBytes == 0)("real", current.RawBytes); + return maxRawBytes; +} + +void TPortionDataAccessor::SaveToDatabase(IDbWrapper& db, const ui32 firstPKColumnId, const bool saveOnlyMeta) const { + FullValidation(); + db.WritePortion(*PortionInfo); + if (!saveOnlyMeta) { + NKikimrTxColumnShard::TIndexPortionAccessor protoData; + for (auto& record : GetRecordsVerified()) { + *protoData.AddChunks() = record.SerializeToDBProto(); + } + db.WriteColumns(*PortionInfo, std::move(protoData)); + + for (auto& record : GetRecordsVerified()) { + db.WriteColumn(*PortionInfo, record, firstPKColumnId); + } + for (auto& record : GetIndexesVerified()) { + db.WriteIndex(*PortionInfo, record); + } + } +} + +void TPortionDataAccessor::RemoveFromDatabase(IDbWrapper& db) const { + db.ErasePortion(*PortionInfo); + for (auto& record : GetRecordsVerified()) { + db.EraseColumn(*PortionInfo, record); + } + for (auto& record : GetIndexesVerified()) { + db.EraseIndex(*PortionInfo, record); + } +} + +void TPortionDataAccessor::FullValidation() const { + CheckChunksOrder(GetRecordsVerified()); + CheckChunksOrder(GetIndexesVerified()); + PortionInfo->FullValidation(); + std::set blobIdxs; + for (auto&& i : GetRecordsVerified()) { + TBlobRange::Validate(PortionInfo->GetMeta().GetBlobIds(), i.GetBlobRange()).Validate(); + blobIdxs.emplace(i.GetBlobRange().GetBlobIdxVerified()); + } + for (auto&& i : GetIndexesVerified()) { + if (auto bRange = i.GetBlobRangeOptional()) { + TBlobRange::Validate(PortionInfo->GetMeta().GetBlobIds(), *bRange).Validate(); + blobIdxs.emplace(bRange->GetBlobIdxVerified()); + } + } + AFL_VERIFY(blobIdxs.size())("portion_info", PortionInfo->DebugString()); + AFL_VERIFY(PortionInfo->GetBlobIdsCount() == blobIdxs.size()); + AFL_VERIFY(PortionInfo->GetBlobIdsCount() == *blobIdxs.rbegin() + 1); +} + +void TPortionDataAccessor::SerializeToProto(NKikimrColumnShardDataSharingProto::TPortionInfo& proto) const { + PortionInfo->SerializeToProto(proto); + AFL_VERIFY(GetRecordsVerified().size()); + for (auto&& r : GetRecordsVerified()) { + *proto.AddRecords() = r.SerializeToProto(); + } + + for (auto&& r : GetIndexesVerified()) { + *proto.AddIndexes() = r.SerializeToProto(); + } +} + +TConclusionStatus TPortionDataAccessor::DeserializeFromProto(const NKikimrColumnShardDataSharingProto::TPortionInfo& proto) { + Records = std::vector(); + Indexes = std::vector(); + for (auto&& i : proto.GetRecords()) { + auto parse = TColumnRecord::BuildFromProto(i); + if (!parse) { + return parse; + } + Records->emplace_back(std::move(parse.DetachResult())); + } + for (auto&& i : proto.GetIndexes()) { + auto parse = TIndexChunk::BuildFromProto(i); + if (!parse) { + return parse; + } + Indexes->emplace_back(std::move(parse.DetachResult())); + } + return TConclusionStatus::Success(); +} + +TConclusion TPortionDataAccessor::BuildFromProto( + const NKikimrColumnShardDataSharingProto::TPortionInfo& proto, const TIndexInfo& indexInfo, const IBlobGroupSelector& groupSelector) { + TPortionMetaConstructor constructor; + if (!constructor.LoadMetadata(proto.GetMeta(), indexInfo, groupSelector)) { + return TConclusionStatus::Fail("cannot parse meta"); + } + std::shared_ptr resultPortion(new TPortionInfo(constructor.Build())); + { + auto parse = resultPortion->DeserializeFromProto(proto); + if (!parse) { + return parse; + } + } + { + TPortionDataAccessor result; + result.PortionInfo = resultPortion; + auto parse = result.DeserializeFromProto(proto); + if (!parse) { + return parse; + } + return result; + } +} + +TConclusion> TPortionDataAccessor::TPreparedColumn::AssembleAccessor() const { + Y_ABORT_UNLESS(!Blobs.empty()); + + NArrow::NAccessor::TCompositeChunkedArray::TBuilder builder(GetField()->type()); + for (auto& blob : Blobs) { + auto chunkedArray = blob.BuildRecordBatch(*Loader); + if (chunkedArray.IsFail()) { + return chunkedArray; + } + builder.AddChunk(chunkedArray.DetachResult()); + } + return builder.Finish(); +} + +std::shared_ptr TPortionDataAccessor::TPreparedColumn::AssembleForSeqAccess() const { + Y_ABORT_UNLESS(!Blobs.empty()); + + std::vector chunks; + chunks.reserve(Blobs.size()); + ui64 recordsCount = 0; + for (auto& blob : Blobs) { + chunks.push_back(blob.BuildDeserializeChunk(Loader)); + if (!!blob.GetData()) { + recordsCount += blob.GetExpectedRowsCountVerified(); + } else { + recordsCount += blob.GetDefaultRowsCount(); + } + } + + return std::make_shared(recordsCount, Loader, std::move(chunks)); +} + +NArrow::NAccessor::TDeserializeChunkedArray::TChunk TPortionDataAccessor::TAssembleBlobInfo::BuildDeserializeChunk( + const std::shared_ptr& loader) const { + if (DefaultRowsCount) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "build_trivial"); + Y_ABORT_UNLESS(!Data); + auto col = std::make_shared( + NArrow::TThreadSimpleArraysCache::Get(loader->GetField()->type(), DefaultValue, DefaultRowsCount)); + return NArrow::NAccessor::TDeserializeChunkedArray::TChunk(col); + } else { + AFL_VERIFY(ExpectedRowsCount); + return NArrow::NAccessor::TDeserializeChunkedArray::TChunk(*ExpectedRowsCount, Data); + } +} + +TConclusion> TPortionDataAccessor::TAssembleBlobInfo::BuildRecordBatch( + const TColumnLoader& loader) const { + if (DefaultRowsCount) { + Y_ABORT_UNLESS(!Data); + if (NeedCache) { + return std::make_shared( + NArrow::TThreadSimpleArraysCache::Get(loader.GetField()->type(), DefaultValue, DefaultRowsCount)); + } else { + return std::make_shared( + NArrow::TStatusValidator::GetValid(arrow::MakeArrayFromScalar(*DefaultValue, DefaultRowsCount))); + } + } else { + AFL_VERIFY(ExpectedRowsCount); + return loader.ApplyConclusion(Data, *ExpectedRowsCount); + } +} + +TConclusion> TPortionDataAccessor::TPreparedBatchData::AssembleToGeneralContainer( + const std::set& sequentialColumnIds) const { + std::vector> columns; + std::vector> fields; + for (auto&& i : Columns) { +// NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("column", i.GetField()->ToString())("column_id", i.GetColumnId()); + if (sequentialColumnIds.contains(i.GetColumnId())) { + columns.emplace_back(i.AssembleForSeqAccess()); + } else { + auto conclusion = i.AssembleAccessor(); + if (conclusion.IsFail()) { + return conclusion; + } + columns.emplace_back(conclusion.DetachResult()); + } + fields.emplace_back(i.GetField()); + } + + return std::make_shared(fields, std::move(columns)); +} + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portions/data_accessor.h b/ydb/core/tx/columnshard/engines/portions/data_accessor.h new file mode 100644 index 000000000000..fadbf8f8cc30 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/portions/data_accessor.h @@ -0,0 +1,514 @@ +#pragma once +#include "portion_info.h" + +#include + +#include + +#include + +namespace NKikimr::NOlap { + +namespace NBlobOperations::NRead { +class TCompositeReadBlobs; +} + +class TPortionDataAccessor { +private: + TPortionInfo::TConstPtr PortionInfo; + std::optional> Records; + std::optional> Indexes; + + template + static void CheckChunksOrder(const std::vector& chunks) { + ui32 entityId = 0; + ui32 chunkIdx = 0; + for (auto&& i : chunks) { + if (entityId != i.GetEntityId()) { + AFL_VERIFY(entityId < i.GetEntityId()); + AFL_VERIFY(i.GetChunkIdx() == 0); + entityId = i.GetEntityId(); + chunkIdx = 0; + } else { + AFL_VERIFY(i.GetChunkIdx() == chunkIdx + 1)("chunk", i.GetChunkIdx())("idx", chunkIdx); + chunkIdx = i.GetChunkIdx(); + } + } + } + + void FullValidation() const; + TPortionDataAccessor() = default; + +public: + ui64 GetMetadataSize() const { + return (Records ? (Records->size() * sizeof(TColumnRecord)) : 0) + + (Indexes ? (Indexes->size() * sizeof(TIndexChunk)) : 0); + } + + class TExtractContext { + private: + YDB_ACCESSOR_DEF(std::optional>, ColumnIds); + YDB_ACCESSOR_DEF(std::optional>, IndexIds); + + public: + TExtractContext() = default; + }; + + TPortionDataAccessor Extract(const std::optional>& columnIds, const std::optional>& indexIds) const { + return Extract(TExtractContext().SetColumnIds(columnIds).SetIndexIds(indexIds)); + } + + TPortionDataAccessor Extract(const TExtractContext& context) const { + AFL_VERIFY(Records); + std::vector extractedRecords; + if (context.GetColumnIds()) { + auto itRec = Records->begin(); + auto itExt = context.GetColumnIds()->begin(); + while (itRec != Records->end() && itExt != context.GetColumnIds()->end()) { + if (itRec->GetEntityId() == *itExt) { + extractedRecords.emplace_back(*itRec); + ++itRec; + } else if (itRec->GetEntityId() < *itExt) { + ++itRec; + } else { + ++itExt; + } + } + } else { + extractedRecords = *Records; + } + + AFL_VERIFY(Indexes); + std::vector extractedIndexes; + if (context.GetIndexIds()) { + auto itIdx = Indexes->begin(); + auto itExt = context.GetIndexIds()->begin(); + while (itIdx != Indexes->end() && itExt != context.GetIndexIds()->end()) { + if (itIdx->GetEntityId() == *itExt) { + extractedIndexes.emplace_back(*itIdx); + ++itIdx; + } else if (itIdx->GetEntityId() < *itExt) { + ++itIdx; + } else { + ++itExt; + } + } + } else { + extractedIndexes = *Indexes; + } + + return TPortionDataAccessor(PortionInfo, std::move(extractedRecords), std::move(extractedIndexes), false); + } + + const std::vector& TestGetRecords() const { + AFL_VERIFY(Records); + return std::move(*Records); + } + std::vector ExtractRecords() { + AFL_VERIFY(Records); + return std::move(*Records); + } + std::vector ExtractIndexes() { + AFL_VERIFY(Indexes); + return std::move(*Indexes); + } + + TPortionDataAccessor SwitchPortionInfo(TPortionInfo&& newPortion) const { + return TPortionDataAccessor(std::make_shared(std::move(newPortion)), GetRecordsVerified(), GetIndexesVerified(), true); + } + + template + static void AggregateIndexChunksData( + const TAggregator& aggr, const std::vector& chunks, const std::set* columnIds, const bool validation) { + if (columnIds) { + auto itColumn = columnIds->begin(); + auto itRecord = chunks.begin(); + ui32 recordsInEntityCount = 0; + while (itRecord != chunks.end() && itColumn != columnIds->end()) { + if (itRecord->GetEntityId() < *itColumn) { + ++itRecord; + } else if (*itColumn < itRecord->GetEntityId()) { + AFL_VERIFY(!validation || recordsInEntityCount)("problem", "validation")("reason", "no_chunks_for_column")( + "column_id", *itColumn); + ++itColumn; + recordsInEntityCount = 0; + } else { + ++recordsInEntityCount; + aggr(*itRecord); + ++itRecord; + } + } + } else { + for (auto&& i : chunks) { + aggr(i); + } + } + } + + explicit TPortionDataAccessor(const TPortionInfo::TConstPtr& portionInfo, std::vector&& records, + std::vector&& indexes, const bool validate) + : PortionInfo(portionInfo) + , Records(std::move(records)) + , Indexes(std::move(indexes)) { + if (validate) { + FullValidation(); + } + } + + explicit TPortionDataAccessor(const TPortionInfo::TConstPtr& portionInfo, const std::vector& records, + const std::vector& indexes, const bool validate) + : PortionInfo(portionInfo) + , Records(records) + , Indexes(indexes) { + if (validate) { + FullValidation(); + } + } + + static TConclusion BuildFromProto( + const NKikimrColumnShardDataSharingProto::TPortionInfo& proto, const TIndexInfo& indexInfo, const IBlobGroupSelector& groupSelector); + + std::vector GetIndexInplaceDataVerified(const ui32 indexId) const { + if (!Indexes) { + return {}; + } + std::vector result; + for (auto&& i : *Indexes) { + if (i.GetEntityId() == indexId) { + result.emplace_back(i.GetBlobDataVerified()); + } + } + return result; + } + + std::set GetColumnIds() const { + std::set result; + for (auto&& i : GetRecordsVerified()) { + result.emplace(i.GetColumnId()); + } + return result; + } + + const TPortionInfo& GetPortionInfo() const { + return *PortionInfo; + } + + TPortionInfo& MutablePortionInfo() const { + return const_cast(*PortionInfo); + } + + std::shared_ptr MutablePortionInfoPtr() const { + return std::const_pointer_cast(PortionInfo); + } + + const TPortionInfo::TConstPtr& GetPortionInfoPtr() const { + return PortionInfo; + } + + void RemoveFromDatabase(IDbWrapper& db) const; + void SaveToDatabase(IDbWrapper& db, const ui32 firstPKColumnId, const bool saveOnlyMeta) const; + + NArrow::NSplitter::TSerializationStats GetSerializationStat(const ISnapshotSchema& schema) const { + NArrow::NSplitter::TSerializationStats result; + for (auto&& i : GetRecordsVerified()) { + if (schema.GetFieldByColumnIdOptional(i.ColumnId)) { + result.AddStat(i.GetSerializationStat(schema.GetFieldByColumnIdVerified(i.ColumnId)->name())); + } + } + return result; + } + + void SerializeToProto(NKikimrColumnShardDataSharingProto::TPortionInfo& proto) const; + + TConclusionStatus DeserializeFromProto(const NKikimrColumnShardDataSharingProto::TPortionInfo& proto); + + ui64 GetColumnRawBytes(const std::set& entityIds, const bool validation = true) const; + ui64 GetColumnBlobBytes(const std::set& entityIds, const bool validation = true) const; + ui64 GetIndexRawBytes(const std::set& entityIds, const bool validation = true) const; + ui64 GetIndexRawBytes(const bool validation = true) const; + + void FillBlobRangesByStorage(THashMap>& result, const TIndexInfo& indexInfo) const; + void FillBlobRangesByStorage(THashMap>& result, const TVersionedIndex& index) const; + void FillBlobIdsByStorage(THashMap>& result, const TIndexInfo& indexInfo) const; + void FillBlobIdsByStorage(THashMap>& result, const TVersionedIndex& index) const; + + THashMap>> RestoreEntityChunks( + NBlobOperations::NRead::TCompositeReadBlobs& blobs, const TIndexInfo& indexInfo) const; + + std::vector GetColumnChunksPointers(const ui32 columnId) const; + + THashMap DecodeBlobAddresses(NBlobOperations::NRead::TCompositeReadBlobs&& blobs, const TIndexInfo& indexInfo) const; + + THashMap> GetBlobIdsByStorage(const TIndexInfo& indexInfo) const { + THashMap> result; + FillBlobIdsByStorage(result, indexInfo); + return result; + } + + static TPortionDataAccessor BuildEmpty() { + return TPortionDataAccessor(); + } + + const TColumnRecord* GetRecordPointer(const TChunkAddress& address) const; + + bool HasEntityAddress(const TChunkAddress& address) const; + + bool HasIndexes(const std::set& ids) const { + auto idsCopy = ids; + for (auto&& i : GetIndexesVerified()) { + idsCopy.erase(i.GetIndexId()); + if (idsCopy.empty()) { + return true; + } + } + return false; + } + + TString DebugString() const; + + class TAssembleBlobInfo { + private: + YDB_READONLY_DEF(std::optional, ExpectedRowsCount); + ui32 DefaultRowsCount = 0; + std::shared_ptr DefaultValue; + TString Data; + const bool NeedCache = true; + + public: + ui32 GetExpectedRowsCountVerified() const { + AFL_VERIFY(ExpectedRowsCount); + return *ExpectedRowsCount; + } + + void SetExpectedRecordsCount(const ui32 expectedRowsCount) { + AFL_VERIFY(!ExpectedRowsCount); + ExpectedRowsCount = expectedRowsCount; + if (!Data) { + AFL_VERIFY(*ExpectedRowsCount == DefaultRowsCount); + } + } + + TAssembleBlobInfo(const ui32 rowsCount, const std::shared_ptr& defValue, const bool needCache = true) + : DefaultRowsCount(rowsCount) + , DefaultValue(defValue) + , NeedCache(needCache) { + AFL_VERIFY(DefaultRowsCount); + } + + TAssembleBlobInfo(const TString& data) + : Data(data) { + AFL_VERIFY(!!Data); + } + + ui32 GetDefaultRowsCount() const noexcept { + return DefaultRowsCount; + } + + const TString& GetData() const noexcept { + return Data; + } + + bool IsBlob() const { + return !DefaultRowsCount && !!Data; + } + + bool IsDefault() const { + return DefaultRowsCount && !Data; + } + + TConclusion> BuildRecordBatch(const TColumnLoader& loader) const; + NArrow::NAccessor::TDeserializeChunkedArray::TChunk BuildDeserializeChunk(const std::shared_ptr& loader) const; + }; + + class TPreparedColumn { + private: + std::shared_ptr Loader; + std::vector Blobs; + + public: + ui32 GetColumnId() const { + return Loader->GetColumnId(); + } + + const std::string& GetName() const { + return Loader->GetField()->name(); + } + + std::shared_ptr GetField() const { + return Loader->GetField(); + } + + TPreparedColumn(std::vector&& blobs, const std::shared_ptr& loader) + : Loader(loader) + , Blobs(std::move(blobs)) { + AFL_VERIFY(Loader); + } + + std::shared_ptr AssembleForSeqAccess() const; + TConclusion> AssembleAccessor() const; + }; + + class TPreparedBatchData { + private: + std::vector Columns; + size_t RowsCount = 0; + + public: + struct TAssembleOptions { + std::optional> IncludedColumnIds; + std::optional> ExcludedColumnIds; + std::map> ConstantColumnIds; + + bool IsConstantColumn(const ui32 columnId, std::shared_ptr& scalar) const { + if (ConstantColumnIds.empty()) { + return false; + } + auto it = ConstantColumnIds.find(columnId); + if (it == ConstantColumnIds.end()) { + return false; + } + scalar = it->second; + return true; + } + + bool IsAcceptedColumn(const ui32 columnId) const { + if (IncludedColumnIds && !IncludedColumnIds->contains(columnId)) { + return false; + } + if (ExcludedColumnIds && ExcludedColumnIds->contains(columnId)) { + return false; + } + return true; + } + }; + + std::shared_ptr GetFieldVerified(const ui32 columnId) const { + for (auto&& i : Columns) { + if (i.GetColumnId() == columnId) { + return i.GetField(); + } + } + AFL_VERIFY(false); + return nullptr; + } + + size_t GetColumnsCount() const { + return Columns.size(); + } + + size_t GetRowsCount() const { + return RowsCount; + } + + TPreparedBatchData(std::vector&& columns, const size_t rowsCount) + : Columns(std::move(columns)) + , RowsCount(rowsCount) { + } + + TConclusion> AssembleToGeneralContainer(const std::set& sequentialColumnIds) const; + }; + + class TColumnAssemblingInfo { + private: + std::vector BlobsInfo; + YDB_READONLY(ui32, ColumnId, 0); + const ui32 RecordsCount; + ui32 RecordsCountByChunks = 0; + const std::shared_ptr DataLoader; + const std::shared_ptr ResultLoader; + + public: + TColumnAssemblingInfo( + const ui32 recordsCount, const std::shared_ptr& dataLoader, const std::shared_ptr& resultLoader) + : ColumnId(resultLoader->GetColumnId()) + , RecordsCount(recordsCount) + , DataLoader(dataLoader) + , ResultLoader(resultLoader) { + AFL_VERIFY(ResultLoader); + if (DataLoader) { + AFL_VERIFY(ResultLoader->GetColumnId() == DataLoader->GetColumnId()); + AFL_VERIFY(DataLoader->GetField()->IsCompatibleWith(ResultLoader->GetField()))("data", DataLoader->GetField()->ToString())( + "result", ResultLoader->GetField()->ToString()); + } + } + + const std::shared_ptr& GetField() const { + return ResultLoader->GetField(); + } + + void AddBlobInfo(const ui32 expectedChunkIdx, const ui32 expectedRecordsCount, TAssembleBlobInfo&& info) { + AFL_VERIFY(expectedChunkIdx == BlobsInfo.size()); + info.SetExpectedRecordsCount(expectedRecordsCount); + RecordsCountByChunks += expectedRecordsCount; + BlobsInfo.emplace_back(std::move(info)); + } + + TPreparedColumn Compile() { + if (BlobsInfo.empty()) { + BlobsInfo.emplace_back( + TAssembleBlobInfo(RecordsCount, DataLoader ? DataLoader->GetDefaultValue() : ResultLoader->GetDefaultValue())); + return TPreparedColumn(std::move(BlobsInfo), ResultLoader); + } else { + AFL_VERIFY(RecordsCountByChunks == RecordsCount)("by_chunks", RecordsCountByChunks)("expected", RecordsCount); + AFL_VERIFY(DataLoader); + return TPreparedColumn(std::move(BlobsInfo), DataLoader); + } + } + }; + + TPreparedBatchData PrepareForAssemble(const ISnapshotSchema& dataSchema, const ISnapshotSchema& resultSchema, + THashMap& blobsData, const std::optional& defaultSnapshot = std::nullopt, + const bool restoreAbsent = true) const; + TPreparedBatchData PrepareForAssemble(const ISnapshotSchema& dataSchema, const ISnapshotSchema& resultSchema, + THashMap& blobsData, const std::optional& defaultSnapshot = std::nullopt, const bool restoreAbsent = true) const; + + class TPage { + private: + YDB_READONLY_DEF(std::vector, Records); + YDB_READONLY_DEF(std::vector, Indexes); + YDB_READONLY(ui32, RecordsCount, 0); + + public: + TPage(std::vector&& records, std::vector&& indexes, const ui32 recordsCount) + : Records(std::move(records)) + , Indexes(std::move(indexes)) + , RecordsCount(recordsCount) { + } + }; + + const std::vector& GetRecordsVerified() const { + AFL_VERIFY(Records); + return *Records; + } + + const std::vector& GetIndexesVerified() const { + AFL_VERIFY(Indexes); + return *Indexes; + } + + bool HasIndexes() const { + return !!Indexes; + } + + std::vector BuildPages() const; + ui64 GetMinMemoryForReadColumns(const std::optional>& columnIds) const; + + class TReadPage { + private: + YDB_READONLY(ui32, IndexStart, 0); + YDB_READONLY(ui32, RecordsCount, 0); + YDB_READONLY(ui64, MemoryUsage, 0); + + public: + TReadPage(const ui32 indexStart, const ui32 recordsCount, const ui64 memoryUsage) + : IndexStart(indexStart) + , RecordsCount(recordsCount) + , MemoryUsage(memoryUsage) { + AFL_VERIFY(RecordsCount); + } + }; + + std::vector BuildReadPages(const ui64 memoryLimit, const std::set& entityIds) const; +}; + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portions/meta.cpp b/ydb/core/tx/columnshard/engines/portions/meta.cpp index 9d7e374ec8f1..802648f03306 100644 --- a/ydb/core/tx/columnshard/engines/portions/meta.cpp +++ b/ydb/core/tx/columnshard/engines/portions/meta.cpp @@ -9,9 +9,16 @@ namespace NKikimr::NOlap { NKikimrTxColumnShard::TIndexPortionMeta TPortionMeta::SerializeToProto() const { + FullValidation(); NKikimrTxColumnShard::TIndexPortionMeta portionMeta; portionMeta.SetTierName(TierName); + portionMeta.SetCompactionLevel(CompactionLevel); portionMeta.SetDeletionsCount(DeletionsCount); + portionMeta.SetRecordsCount(RecordsCount); + portionMeta.SetColumnRawBytes(ColumnRawBytes); + portionMeta.SetColumnBlobBytes(ColumnBlobBytes); + portionMeta.SetIndexRawBytes(IndexRawBytes); + portionMeta.SetIndexBlobBytes(IndexBlobBytes); switch (Produced) { case TPortionMeta::EProduced::UNSPECIFIED: Y_ABORT_UNLESS(false); @@ -37,6 +44,9 @@ NKikimrTxColumnShard::TIndexPortionMeta TPortionMeta::SerializeToProto() const { RecordSnapshotMin.SerializeToProto(*portionMeta.MutableRecordSnapshotMin()); RecordSnapshotMax.SerializeToProto(*portionMeta.MutableRecordSnapshotMax()); + for (auto&& i : GetBlobIds()) { + *portionMeta.AddBlobIds() = i.GetLogoBlobId().AsBinaryString(); + } return portionMeta; } diff --git a/ydb/core/tx/columnshard/engines/portions/meta.h b/ydb/core/tx/columnshard/engines/portions/meta.h index ad57ef1325c3..f0d915e14c73 100644 --- a/ydb/core/tx/columnshard/engines/portions/meta.h +++ b/ydb/core/tx/columnshard/engines/portions/meta.h @@ -1,45 +1,119 @@ #pragma once +#include +#include #include #include #include -#include -#include + #include +#include + #include namespace NKikimr::NOlap { struct TIndexInfo; -struct TPortionMeta { +class TPortionMetaBase { +protected: + std::vector BlobIds; +public: + const std::vector& GetBlobIds() const { + return BlobIds; + } + + const TUnifiedBlobId& GetBlobId(const TBlobRangeLink16::TLinkId linkId) const { + AFL_VERIFY(linkId < GetBlobIds().size()); + return BlobIds[linkId]; + } + + ui32 GetBlobIdsCount() const { + return BlobIds.size(); + } + + void FullValidation() const { + for (auto&& i : BlobIds) { + AFL_VERIFY(i.BlobSize()); + } + AFL_VERIFY(BlobIds.size()); + } + + std::optional GetBlobIdxOptional(const TUnifiedBlobId& blobId) const { + AFL_VERIFY(blobId.IsValid()); + TBlobRangeLink16::TLinkId idx = 0; + for (auto&& i : BlobIds) { + if (i == blobId) { + return idx; + } + ++idx; + } + return std::nullopt; + } + + ui64 GetMetadataMemorySize() const { + return GetBlobIds().size() * sizeof(TUnifiedBlobId); + } + + TBlobRangeLink16::TLinkId GetBlobIdxVerified(const TUnifiedBlobId& blobId) const { + auto result = GetBlobIdxOptional(blobId); + AFL_VERIFY(result); + return *result; + } +}; + +class TPortionMeta: public TPortionMetaBase { private: - NArrow::TFirstLastSpecialKeys ReplaceKeyEdges; // first and last PK rows + using TBase = TPortionMetaBase; + NArrow::TFirstLastSpecialKeys ReplaceKeyEdges; // first and last PK rows YDB_READONLY_DEF(TString, TierName); YDB_READONLY(ui32, DeletionsCount, 0); + YDB_READONLY(ui32, CompactionLevel, 0); + YDB_READONLY(ui32, RecordsCount, 0); + YDB_READONLY(ui64, ColumnRawBytes, 0); + YDB_READONLY(ui32, ColumnBlobBytes, 0); + YDB_READONLY(ui32, IndexRawBytes, 0); + YDB_READONLY(ui32, IndexBlobBytes, 0); + friend class TPortionMetaConstructor; + friend class TPortionInfo; TPortionMeta(NArrow::TFirstLastSpecialKeys& pk, const TSnapshot& min, const TSnapshot& max) : ReplaceKeyEdges(pk) - , IndexKeyStart(pk.GetFirst()) - , IndexKeyEnd(pk.GetLast()) , RecordSnapshotMin(min) , RecordSnapshotMax(max) - { + , IndexKeyStart(pk.GetFirst()) + , IndexKeyEnd(pk.GetLast()) { AFL_VERIFY(IndexKeyStart <= IndexKeyEnd)("start", IndexKeyStart.DebugString())("end", IndexKeyEnd.DebugString()); } + TSnapshot RecordSnapshotMin; + TSnapshot RecordSnapshotMax; + + void FullValidation() const { + TBase::FullValidation(); + AFL_VERIFY(RecordsCount); + AFL_VERIFY(ColumnRawBytes); + AFL_VERIFY(ColumnBlobBytes); + } + public: + const NArrow::TFirstLastSpecialKeys& GetFirstLastPK() const { + return ReplaceKeyEdges; + } + + void ResetCompactionLevel(const ui32 level) { + CompactionLevel = level; + } + using EProduced = NPortion::EProduced; NArrow::TReplaceKey IndexKeyStart; NArrow::TReplaceKey IndexKeyEnd; - TSnapshot RecordSnapshotMin; - TSnapshot RecordSnapshotMax; EProduced Produced = EProduced::UNSPECIFIED; std::optional GetTierNameOptional() const; ui64 GetMetadataMemorySize() const { - return sizeof(TPortionMeta) + ReplaceKeyEdges.GetMemorySize(); + return sizeof(TPortionMeta) + ReplaceKeyEdges.GetMemorySize() + TBase::GetMetadataMemorySize(); } NKikimrTxColumnShard::TIndexPortionMeta SerializeToProto() const; @@ -55,12 +129,11 @@ class TPortionAddress { private: YDB_READONLY(ui64, PathId, 0); YDB_READONLY(ui64, PortionId, 0); + public: TPortionAddress(const ui64 pathId, const ui64 portionId) : PathId(pathId) - , PortionId(portionId) - { - + , PortionId(portionId) { } TString DebugString() const; @@ -74,12 +147,11 @@ class TPortionAddress { } }; -} // namespace NKikimr::NOlap +} // namespace NKikimr::NOlap -template<> +template <> struct THash { inline ui64 operator()(const NKikimr::NOlap::TPortionAddress& x) const noexcept { return CombineHashes(x.GetPortionId(), x.GetPathId()); } }; - diff --git a/ydb/core/tx/columnshard/engines/portions/portion_info.cpp b/ydb/core/tx/columnshard/engines/portions/portion_info.cpp index 6652bf9c4c5f..96d307390e83 100644 --- a/ydb/core/tx/columnshard/engines/portions/portion_info.cpp +++ b/ydb/core/tx/columnshard/engines/portions/portion_info.cpp @@ -1,272 +1,75 @@ +#include "column_record.h" +#include "constructor_portion.h" +#include "data_accessor.h" #include "portion_info.h" -#include "constructor.h" -#include + #include -#include #include #include -#include #include -#include -#include -#include -#include -#include - -#include namespace NKikimr::NOlap { -std::shared_ptr TPortionInfo::MaxValue(ui32 columnId) const { - std::shared_ptr result; - for (auto&& i : Records) { - if (i.ColumnId == columnId) { - if (!i.GetMeta().GetMax()) { - return nullptr; - } - if (!result || NArrow::ScalarCompare(result, i.GetMeta().GetMax()) < 0) { - result = i.GetMeta().GetMax(); - } - } - } - return result; -} - -ui64 TPortionInfo::GetColumnRawBytes(const std::set& entityIds, const bool validation) const { - ui64 sum = 0; - const auto aggr = [&](const TColumnRecord& r) { - sum += r.GetMeta().GetRawBytes(); - }; - AggregateIndexChunksData(aggr, Records, &entityIds, validation); - return sum; +ui64 TPortionInfo::GetColumnRawBytes() const { + return GetMeta().GetColumnRawBytes(); } -ui64 TPortionInfo::GetColumnBlobBytes(const std::set& entityIds, const bool validation) const { - ui64 sum = 0; - const auto aggr = [&](const TColumnRecord& r) { - sum += r.GetBlobRange().GetSize(); - }; - AggregateIndexChunksData(aggr, Records, &entityIds, validation); - return sum; -} - -ui64 TPortionInfo::GetColumnRawBytes(const bool validation) const { - ui64 sum = 0; - const auto aggr = [&](const TColumnRecord& r) { - sum += r.GetMeta().GetRawBytes(); - }; - AggregateIndexChunksData(aggr, Records, nullptr, validation); - return sum; -} - -ui64 TPortionInfo::GetColumnBlobBytes(const bool validation) const { - ui64 sum = 0; - const auto aggr = [&](const TColumnRecord& r) { - sum += r.GetBlobRange().GetSize(); - }; - AggregateIndexChunksData(aggr, Records, nullptr, validation); - return sum; -} - -ui64 TPortionInfo::GetIndexRawBytes(const std::set& entityIds, const bool validation) const { - ui64 sum = 0; - const auto aggr = [&](const TIndexChunk& r) { - sum += r.GetRawBytes(); - }; - AggregateIndexChunksData(aggr, Indexes, &entityIds, validation); - return sum; -} - -ui64 TPortionInfo::GetIndexRawBytes(const bool validation) const { - ui64 sum = 0; - const auto aggr = [&](const TIndexChunk& r) { - sum += r.GetRawBytes(); - }; - AggregateIndexChunksData(aggr, Indexes, nullptr, validation); - return sum; +ui64 TPortionInfo::GetColumnBlobBytes() const { + return GetMeta().GetColumnBlobBytes(); } TString TPortionInfo::DebugString(const bool withDetails) const { TStringBuilder sb; - sb << "(portion_id:" << Portion << ";" << - "path_id:" << PathId << ";records_count:" << NumRows() << ";" - "min_schema_snapshot:(" << MinSnapshotDeprecated.DebugString() << ");" - "schema_version:" << SchemaVersion.value_or(0) << ";"; + sb << "(portion_id:" << PortionId << ";" + << "path_id:" << PathId << ";records_count:" << GetRecordsCount() + << ";" + "min_schema_snapshot:(" + << MinSnapshotDeprecated.DebugString() + << ");" + "schema_version:" + << SchemaVersion.value_or(0) + << ";" + "level:" + << GetMeta().GetCompactionLevel() << ";"; if (withDetails) { - sb << - "records_snapshot_min:(" << RecordSnapshotMin().DebugString() << ");" << - "records_snapshot_max:(" << RecordSnapshotMax().DebugString() << ");" << - "from:" << IndexKeyStart().DebugString() << ";" << - "to:" << IndexKeyEnd().DebugString() << ";"; - } - sb << - "column_size:" << GetColumnBlobBytes() << ";" << - "index_size:" << GetIndexBlobBytes() << ";" << - "meta:(" << Meta.DebugString() << ");"; + sb << "records_snapshot_min:(" << RecordSnapshotMin().DebugString() << ");" + << "records_snapshot_max:(" << RecordSnapshotMax().DebugString() << ");" + << "from:" << IndexKeyStart().DebugString() << ";" + << "to:" << IndexKeyEnd().DebugString() << ";"; + } + sb << "column_size:" << GetColumnBlobBytes() << ";" + << "index_size:" << GetIndexBlobBytes() << ";" + << "meta:(" << Meta.DebugString() << ");"; if (RemoveSnapshot.Valid()) { sb << "remove_snapshot:(" << RemoveSnapshot.DebugString() << ");"; } - sb << "chunks:(" << Records.size() << ");"; - if (IS_TRACE_LOG_ENABLED(NKikimrServices::TX_COLUMNSHARD)) { - std::vector blobRanges; - for (auto&& i : Records) { - blobRanges.emplace_back(RestoreBlobRange(i.BlobRange)); - } - sb << "blobs:" << JoinSeq(",", blobRanges) << ";ranges_count:" << blobRanges.size() << ";"; - sb << "blob_ids:" << JoinSeq(",", BlobIds) << ";blobs_count:" << BlobIds.size() << ";"; - } return sb << ")"; } -std::vector TPortionInfo::GetColumnChunksPointers(const ui32 columnId) const { - std::vector result; - for (auto&& c : Records) { - if (c.ColumnId == columnId) { - Y_ABORT_UNLESS(c.Chunk == result.size()); - Y_ABORT_UNLESS(c.GetMeta().GetNumRows()); - result.emplace_back(&c); - } - } - return result; -} - -void TPortionInfo::RemoveFromDatabase(IDbWrapper& db) const { - db.ErasePortion(*this); - for (auto& record : Records) { - db.EraseColumn(*this, record); - } - for (auto& record : Indexes) { - db.EraseIndex(*this, record); - } -} - -void TPortionInfo::SaveToDatabase(IDbWrapper& db, const ui32 firstPKColumnId, const bool saveOnlyMeta) const { - FullValidation(); - db.WritePortion(*this); - if (!saveOnlyMeta) { - for (auto& record : Records) { - db.WriteColumn(*this, record, firstPKColumnId); - } - for (auto& record : Indexes) { - db.WriteIndex(*this, record); - } - } -} - -std::vector TPortionInfo::BuildPages() const { - std::vector pages; - struct TPart { - public: - const TColumnRecord* Record = nullptr; - const TIndexChunk* Index = nullptr; - const ui32 RecordsCount; - TPart(const TColumnRecord* record, const ui32 recordsCount) - : Record(record) - , RecordsCount(recordsCount) { - - } - TPart(const TIndexChunk* record, const ui32 recordsCount) - : Index(record) - , RecordsCount(recordsCount) { - - } - }; - std::map> entities; - std::map currentCursor; - ui32 currentSize = 0; - ui32 currentId = 0; - for (auto&& i : Records) { - if (currentId != i.GetColumnId()) { - currentSize = 0; - currentId = i.GetColumnId(); - } - currentSize += i.GetMeta().GetNumRows(); - ++currentCursor[currentSize]; - entities[i.GetColumnId()].emplace_back(&i, i.GetMeta().GetNumRows()); - } - for (auto&& i : Indexes) { - if (currentId != i.GetIndexId()) { - currentSize = 0; - currentId = i.GetIndexId(); - } - currentSize += i.GetRecordsCount(); - ++currentCursor[currentSize]; - entities[i.GetIndexId()].emplace_back(&i, i.GetRecordsCount()); - } - const ui32 entitiesCount = entities.size(); - ui32 predCount = 0; - for (auto&& i : currentCursor) { - if (i.second != entitiesCount) { - continue; - } - std::vector records; - std::vector indexes; - for (auto&& c : entities) { - ui32 readyCount = 0; - while (readyCount < i.first - predCount && c.second.size()) { - if (c.second.front().Record) { - records.emplace_back(c.second.front().Record); - } else { - AFL_VERIFY(c.second.front().Index); - indexes.emplace_back(c.second.front().Index); - } - readyCount += c.second.front().RecordsCount; - c.second.pop_front(); - } - AFL_VERIFY(readyCount == i.first - predCount)("ready", readyCount)("cursor", i.first)("pred_cursor", predCount); - } - pages.emplace_back(std::move(records), std::move(indexes), i.first - predCount); - predCount = i.first; - } - for (auto&& i : entities) { - AFL_VERIFY(i.second.empty()); - } - return pages; -} - ui64 TPortionInfo::GetMetadataMemorySize() const { - return sizeof(TPortionInfo) + Records.size() * (sizeof(TColumnRecord) + 8) + Indexes.size() * sizeof(TIndexChunk) + BlobIds.size() * sizeof(TUnifiedBlobId) - - sizeof(TPortionMeta) + Meta.GetMetadataMemorySize(); + return sizeof(TPortionInfo) - sizeof(TPortionMeta) + Meta.GetMetadataMemorySize(); } -ui64 TPortionInfo::GetTxVolume() const { - return 1024 + Records.size() * 256 + Indexes.size() * 256; +ui64 TPortionInfo::GetApproxChunksCount(const ui32 schemaColumnsCount) const { + return schemaColumnsCount * (GetRecordsCount() / 10000 + 1); } void TPortionInfo::SerializeToProto(NKikimrColumnShardDataSharingProto::TPortionInfo& proto) const { proto.SetPathId(PathId); - proto.SetPortionId(Portion); + proto.SetPortionId(PortionId); proto.SetSchemaVersion(GetSchemaVersionVerified()); *proto.MutableMinSnapshotDeprecated() = MinSnapshotDeprecated.SerializeToProto(); if (!RemoveSnapshot.IsZero()) { *proto.MutableRemoveSnapshot() = RemoveSnapshot.SerializeToProto(); } - for (auto&& i : BlobIds) { - *proto.AddBlobIds() = i.SerializeToProto(); - } *proto.MutableMeta() = Meta.SerializeToProto(); - - for (auto&& r : Records) { - *proto.AddRecords() = r.SerializeToProto(); - } - - for (auto&& r : Indexes) { - *proto.AddIndexes() = r.SerializeToProto(); - } } -TConclusionStatus TPortionInfo::DeserializeFromProto(const NKikimrColumnShardDataSharingProto::TPortionInfo& proto, const TIndexInfo& info) { +TConclusionStatus TPortionInfo::DeserializeFromProto(const NKikimrColumnShardDataSharingProto::TPortionInfo& proto) { PathId = proto.GetPathId(); - Portion = proto.GetPortionId(); + PortionId = proto.GetPortionId(); SchemaVersion = proto.GetSchemaVersion(); - for (auto&& i : proto.GetBlobIds()) { - auto blobId = TUnifiedBlobId::BuildFromProto(i); - if (!blobId) { - return blobId; - } - BlobIds.emplace_back(blobId.DetachResult()); - } { auto parse = MinSnapshotDeprecated.DeserializeFromProto(proto.GetMinSnapshotDeprecated()); if (!parse) { @@ -279,376 +82,38 @@ TConclusionStatus TPortionInfo::DeserializeFromProto(const NKikimrColumnShardDat return parse; } } - for (auto&& i : proto.GetRecords()) { - auto parse = TColumnRecord::BuildFromProto(i, info.GetColumnFeaturesVerified(i.GetColumnId())); - if (!parse) { - return parse; - } - Records.emplace_back(std::move(parse.DetachResult())); - } - for (auto&& i : proto.GetIndexes()) { - auto parse = TIndexChunk::BuildFromProto(i); - if (!parse) { - return parse; - } - Indexes.emplace_back(std::move(parse.DetachResult())); - } return TConclusionStatus::Success(); } -TConclusion TPortionInfo::BuildFromProto(const NKikimrColumnShardDataSharingProto::TPortionInfo& proto, const TIndexInfo& info) { - TPortionMetaConstructor constructor; - if (!constructor.LoadMetadata(proto.GetMeta(), info)) { - return TConclusionStatus::Fail("cannot parse meta"); - } - TPortionInfo result(constructor.Build()); - auto parse = result.DeserializeFromProto(proto, info); - if (!parse) { - return parse; - } - return result; -} - -THashMap TPortionInfo::DecodeBlobAddresses(NBlobOperations::NRead::TCompositeReadBlobs&& blobs, const TIndexInfo& indexInfo) const { - THashMap result; - for (auto&& i : blobs) { - for (auto&& b : i.second) { - bool found = false; - TString columnStorageId; - ui32 columnId = 0; - for (auto&& record : Records) { - if (RestoreBlobRange(record.GetBlobRange()) == b.first) { - if (columnId != record.GetColumnId()) { - columnStorageId = GetColumnStorageId(record.GetColumnId(), indexInfo); - } - if (columnStorageId != i.first) { - continue; - } - result.emplace(record.GetAddress(), std::move(b.second)); - found = true; - break; - } - } - if (found) { - continue; - } - for (auto&& record : Indexes) { - if (!record.HasBlobRange()) { - continue; - } - if (RestoreBlobRange(record.GetBlobRangeVerified()) == b.first) { - if (columnId != record.GetIndexId()) { - columnStorageId = indexInfo.GetIndexStorageId(record.GetIndexId()); - } - if (columnStorageId != i.first) { - continue; - } - result.emplace(record.GetAddress(), std::move(b.second)); - found = true; - break; - } - } - AFL_VERIFY(found)("blobs", blobs.DebugString())("records", DebugString(true))("problem", b.first); - } - } - return result; -} - const TString& TPortionInfo::GetColumnStorageId(const ui32 columnId, const TIndexInfo& indexInfo) const { + if (HasInsertWriteId()) { + return { NBlobOperations::TGlobal::DefaultStorageId }; + } return indexInfo.GetColumnStorageId(columnId, GetMeta().GetTierName()); } const TString& TPortionInfo::GetEntityStorageId(const ui32 columnId, const TIndexInfo& indexInfo) const { + if (HasInsertWriteId()) { + return { NBlobOperations::TGlobal::DefaultStorageId }; + } return indexInfo.GetEntityStorageId(columnId, GetMeta().GetTierName()); } +const TString& TPortionInfo::GetIndexStorageId(const ui32 indexId, const TIndexInfo& indexInfo) const { + if (HasInsertWriteId()) { + return { NBlobOperations::TGlobal::DefaultStorageId }; + } + return indexInfo.GetIndexStorageId(indexId); +} + ISnapshotSchema::TPtr TPortionInfo::GetSchema(const TVersionedIndex& index) const { AFL_VERIFY(SchemaVersion); if (SchemaVersion) { - auto schema = index.GetSchema(SchemaVersion.value()); + auto schema = index.GetSchemaVerified(SchemaVersion.value()); AFL_VERIFY(!!schema)("details", TStringBuilder() << "cannot find schema for version " << SchemaVersion.value()); return schema; } - return index.GetSchema(MinSnapshotDeprecated); -} - -void TPortionInfo::FillBlobRangesByStorage(THashMap>& result, const TIndexInfo& indexInfo) const { - for (auto&& i : Records) { - const TString& storageId = GetColumnStorageId(i.GetColumnId(), indexInfo); - AFL_VERIFY(result[storageId].emplace(RestoreBlobRange(i.GetBlobRange())).second)("blob_id", RestoreBlobRange(i.GetBlobRange()).ToString()); - } - for (auto&& i : Indexes) { - const TString& storageId = indexInfo.GetIndexStorageId(i.GetIndexId()); - if (auto bRange = i.GetBlobRangeOptional()) { - AFL_VERIFY(result[storageId].emplace(RestoreBlobRange(*bRange)).second)("blob_id", RestoreBlobRange(*bRange).ToString()); - } - } -} - -void TPortionInfo::FillBlobRangesByStorage(THashMap>& result, const TVersionedIndex& index) const { - auto schema = GetSchema(index); - return FillBlobRangesByStorage(result, schema->GetIndexInfo()); -} - -void TPortionInfo::FillBlobIdsByStorage(THashMap>& result, const TIndexInfo& indexInfo) const { - THashMap> local; - THashSet* currentHashLocal = nullptr; - THashSet* currentHashResult = nullptr; - std::optional lastEntityId; - TString lastStorageId; - ui32 lastBlobIdx = BlobIds.size(); - for (auto&& i : Records) { - if (!lastEntityId || *lastEntityId != i.GetEntityId()) { - const TString& storageId = GetColumnStorageId(i.GetEntityId(), indexInfo); - lastEntityId = i.GetEntityId(); - if (storageId != lastStorageId) { - currentHashResult = &result[storageId]; - currentHashLocal = &local[storageId]; - lastStorageId = storageId; - lastBlobIdx = BlobIds.size(); - } - } - if (lastBlobIdx != i.GetBlobRange().GetBlobIdxVerified() && currentHashLocal->emplace(i.GetBlobRange().GetBlobIdxVerified()).second) { - auto blobId = GetBlobId(i.GetBlobRange().GetBlobIdxVerified()); - AFL_VERIFY(currentHashResult); - AFL_VERIFY(currentHashResult->emplace(blobId).second)("blob_id", blobId.ToStringNew()); - lastBlobIdx = i.GetBlobRange().GetBlobIdxVerified(); - } - } - for (auto&& i : Indexes) { - if (!lastEntityId || *lastEntityId != i.GetEntityId()) { - const TString& storageId = indexInfo.GetIndexStorageId(i.GetEntityId()); - lastEntityId = i.GetEntityId(); - if (storageId != lastStorageId) { - currentHashResult = &result[storageId]; - currentHashLocal = &local[storageId]; - lastStorageId = storageId; - lastBlobIdx = BlobIds.size(); - } - } - if (auto bRange = i.GetBlobRangeOptional()) { - if (lastBlobIdx != bRange->GetBlobIdxVerified() && currentHashLocal->emplace(bRange->GetBlobIdxVerified()).second) { - auto blobId = GetBlobId(bRange->GetBlobIdxVerified()); - AFL_VERIFY(currentHashResult); - AFL_VERIFY(currentHashResult->emplace(blobId).second)("blob_id", blobId.ToStringNew()); - lastBlobIdx = bRange->GetBlobIdxVerified(); - } - } - } -} - -void TPortionInfo::FillBlobIdsByStorage(THashMap>& result, const TVersionedIndex& index) const { - auto schema = GetSchema(index); - return FillBlobIdsByStorage(result, schema->GetIndexInfo()); -} - -THashMap>> TPortionInfo::RestoreEntityChunks(NBlobOperations::NRead::TCompositeReadBlobs& blobs, const TIndexInfo& indexInfo) const { - THashMap>> result; - for (auto&& c : GetRecords()) { - const TString& storageId = GetColumnStorageId(c.GetColumnId(), indexInfo); - auto chunk = std::make_shared(blobs.Extract(storageId, RestoreBlobRange(c.GetBlobRange())), c, indexInfo.GetColumnFeaturesVerified(c.GetColumnId())); - chunk->SetChunkIdx(c.GetChunkIdx()); - AFL_VERIFY(result[storageId].emplace(c.GetAddress(), chunk).second); - } - for (auto&& c : GetIndexes()) { - const TString& storageId = indexInfo.GetIndexStorageId(c.GetIndexId()); - const TString blobData = [&]() -> TString { - if (auto bRange = c.GetBlobRangeOptional()) { - return blobs.Extract(storageId, RestoreBlobRange(*bRange)); - } else if (auto data = c.GetBlobDataOptional()) { - return *data; - } else { - AFL_VERIFY(false); - Y_UNREACHABLE(); - } - }(); - auto chunk = std::make_shared(c.GetAddress(), c.GetRecordsCount(), c.GetRawBytes(), blobData); - chunk->SetChunkIdx(c.GetChunkIdx()); - - AFL_VERIFY(result[storageId].emplace(c.GetAddress(), chunk).second); - } - return result; -} - -void TPortionInfo::ReorderChunks() { - { - auto pred = [](const TColumnRecord& l, const TColumnRecord& r) { - return l.GetAddress() < r.GetAddress(); - }; - std::sort(Records.begin(), Records.end(), pred); - std::optional chunk; - for (auto&& i : Records) { - if (!chunk) { - chunk = i.GetAddress(); - } else { - AFL_VERIFY(*chunk < i.GetAddress()); - chunk = i.GetAddress(); - } - AFL_VERIFY(chunk->GetEntityId()); - } - } - { - auto pred = [](const TIndexChunk& l, const TIndexChunk& r) { - return l.GetAddress() < r.GetAddress(); - }; - std::sort(Indexes.begin(), Indexes.end(), pred); - std::optional chunk; - for (auto&& i : Indexes) { - if (!chunk) { - chunk = i.GetAddress(); - } else { - AFL_VERIFY(*chunk < i.GetAddress()); - chunk = i.GetAddress(); - } - AFL_VERIFY(chunk->GetEntityId()); - } - } -} - -void TPortionInfo::FullValidation() const { - CheckChunksOrder(Records); - CheckChunksOrder(Indexes); - AFL_VERIFY(PathId); - AFL_VERIFY(Portion); - AFL_VERIFY(MinSnapshotDeprecated.Valid()); - std::set blobIdxs; - for (auto&& i : Records) { - blobIdxs.emplace(i.GetBlobRange().GetBlobIdxVerified()); - } - for (auto&& i : Indexes) { - if (auto bRange = i.GetBlobRangeOptional()) { - blobIdxs.emplace(bRange->GetBlobIdxVerified()); - } - } - if (BlobIds.size()) { - AFL_VERIFY(BlobIds.size() == blobIdxs.size()); - AFL_VERIFY(BlobIds.size() == *blobIdxs.rbegin() + 1); - } else { - AFL_VERIFY(blobIdxs.empty()); - } -} - -ui64 TPortionInfo::GetMinMemoryForReadColumns(const std::optional>& columnIds) const { - ui32 columnId = 0; - ui32 chunkIdx = 0; - - struct TDelta { - i64 BlobBytes = 0; - i64 RawBytes = 0; - void operator+=(const TDelta& add) { - BlobBytes += add.BlobBytes; - RawBytes += add.RawBytes; - } - }; - - std::map diffByPositions; - ui64 position = 0; - ui64 RawBytesCurrent = 0; - ui64 BlobBytesCurrent = 0; - std::optional recordsCount; - - const auto doFlushColumn = [&]() { - if (!recordsCount && position) { - recordsCount = position; - } else { - AFL_VERIFY(*recordsCount == position); - } - if (position) { - TDelta delta; - delta.RawBytes = -1 * RawBytesCurrent; - delta.BlobBytes = -1 * BlobBytesCurrent; - diffByPositions[position] += delta; - } - position = 0; - chunkIdx = 0; - RawBytesCurrent = 0; - BlobBytesCurrent = 0; - }; - - for (auto&& i : Records) { - if (columnIds && !columnIds->contains(i.GetColumnId())) { - continue; - } - if (columnId != i.GetColumnId()) { - if (columnId) { - doFlushColumn(); - } - AFL_VERIFY(i.GetColumnId() > columnId); - AFL_VERIFY(i.GetChunkIdx() == 0); - columnId = i.GetColumnId(); - } else { - AFL_VERIFY(i.GetChunkIdx() == chunkIdx + 1); - } - chunkIdx = i.GetChunkIdx(); - TDelta delta; - delta.RawBytes = -1 * RawBytesCurrent + i.GetMeta().GetRawBytes(); - delta.BlobBytes = -1 * BlobBytesCurrent + i.GetBlobRange().Size; - diffByPositions[position] += delta; - position += i.GetMeta().GetNumRows(); - RawBytesCurrent = i.GetMeta().GetRawBytes(); - BlobBytesCurrent = i.GetBlobRange().Size; - } - if (columnId) { - doFlushColumn(); - } - i64 maxRawBytes = 0; - TDelta current; - for (auto&& i : diffByPositions) { - current += i.second; - AFL_VERIFY(current.BlobBytes >= 0); - AFL_VERIFY(current.RawBytes >= 0); - if (maxRawBytes < current.RawBytes) { - maxRawBytes = current.RawBytes; - } - } - AFL_VERIFY(current.BlobBytes == 0)("real", current.BlobBytes); - AFL_VERIFY(current.RawBytes == 0)("real", current.RawBytes); - return maxRawBytes; -} - -namespace { -template -TPortionInfo::TPreparedBatchData PrepareForAssembleImpl(const TPortionInfo& portion, const ISnapshotSchema& dataSchema, const ISnapshotSchema& resultSchema, - THashMap& blobsData) { - std::vector columns; - columns.reserve(resultSchema.GetColumnIds().size()); - const ui32 rowsCount = portion.GetRecordsCount(); - for (auto&& i : resultSchema.GetColumnIds()) { - columns.emplace_back(rowsCount, dataSchema.GetColumnLoaderOptional(i), resultSchema.GetColumnLoaderVerified(i)); - } - { - int skipColumnId = -1; - TPortionInfo::TColumnAssemblingInfo* currentAssembler = nullptr; - for (auto& rec : portion.GetRecords()) { - if (skipColumnId == (int)rec.ColumnId) { - continue; - } - if (!currentAssembler || rec.ColumnId != currentAssembler->GetColumnId()) { - const i32 resultPos = resultSchema.GetFieldIndex(rec.ColumnId); - if (resultPos < 0) { - skipColumnId = rec.ColumnId; - continue; - } - AFL_VERIFY((ui32)resultPos < columns.size()); - currentAssembler = &columns[resultPos]; - } - auto it = blobsData.find(rec.GetAddress()); - AFL_VERIFY(it != blobsData.end())("size", blobsData.size())("address", rec.GetAddress().DebugString()); - currentAssembler->AddBlobInfo(rec.Chunk, rec.GetMeta().GetNumRows(), std::move(it->second)); - blobsData.erase(it); - } - } - - // Make chunked arrays for columns - std::vector preparedColumns; - preparedColumns.reserve(columns.size()); - for (auto& c : columns) { - preparedColumns.emplace_back(c.Compile()); - } - - return TPortionInfo::TPreparedBatchData(std::move(preparedColumns), rowsCount); -} - + return index.GetSchemaVerified(MinSnapshotDeprecated); } ISnapshotSchema::TPtr TPortionInfo::TSchemaCursor::GetSchema(const TPortionInfoConstructor& portion) { @@ -660,15 +125,6 @@ ISnapshotSchema::TPtr TPortionInfo::TSchemaCursor::GetSchema(const TPortionInfoC return CurrentSchema; } -TPortionInfo::TPreparedBatchData TPortionInfo::PrepareForAssemble( - const ISnapshotSchema& dataSchema, const ISnapshotSchema& resultSchema, THashMap& blobsData) const { - return PrepareForAssembleImpl(*this, dataSchema, resultSchema, blobsData); -} - -TPortionInfo::TPreparedBatchData TPortionInfo::PrepareForAssemble(const ISnapshotSchema& dataSchema, const ISnapshotSchema& resultSchema, THashMap& blobsData) const { - return PrepareForAssembleImpl(*this, dataSchema, resultSchema, blobsData); -} - bool TPortionInfo::NeedShardingFilter(const TGranuleShardingInfo& shardingInfo) const { if (ShardingVersion && shardingInfo.GetSnapshotVersion() <= *ShardingVersion) { return false; @@ -676,74 +132,19 @@ bool TPortionInfo::NeedShardingFilter(const TGranuleShardingInfo& shardingInfo) return true; } -std::shared_ptr TPortionInfo::TPreparedColumn::AssembleAccessor() const { - Y_ABORT_UNLESS(!Blobs.empty()); - - NArrow::NAccessor::TCompositeChunkedArray::TBuilder builder(GetField()->type()); - for (auto& blob : Blobs) { - auto chunkedArray = blob.BuildRecordBatch(*Loader); - builder.AddChunk(chunkedArray); - } - return builder.Finish(); -} - -std::shared_ptr TPortionInfo::TPreparedColumn::AssembleForSeqAccess() const { - Y_ABORT_UNLESS(!Blobs.empty()); - - std::vector chunks; - chunks.reserve(Blobs.size()); - ui64 recordsCount = 0; - for (auto& blob : Blobs) { - chunks.push_back(blob.BuildDeserializeChunk(Loader)); - if (!!blob.GetData()) { - recordsCount += blob.GetExpectedRowsCountVerified(); - } else { - recordsCount += blob.GetDefaultRowsCount(); - } - } - - return std::make_shared(recordsCount, Loader, std::move(chunks)); -} - -NArrow::NAccessor::TDeserializeChunkedArray::TChunk TPortionInfo::TAssembleBlobInfo::BuildDeserializeChunk( - const std::shared_ptr& loader) const { - if (DefaultRowsCount) { - Y_ABORT_UNLESS(!Data); - auto col = std::make_shared( - NArrow::TThreadSimpleArraysCache::Get(loader->GetField()->type(), DefaultValue, DefaultRowsCount)); - return NArrow::NAccessor::TDeserializeChunkedArray::TChunk(col); +NSplitter::TEntityGroups TPortionInfo::GetEntityGroupsByStorageId( + const TString& specialTier, const IStoragesManager& storages, const TIndexInfo& indexInfo) const { + if (HasInsertWriteId()) { + NSplitter::TEntityGroups groups(storages.GetDefaultOperator()->GetBlobSplitSettings(), IStoragesManager::DefaultStorageId); + return groups; } else { - AFL_VERIFY(ExpectedRowsCount); - return NArrow::NAccessor::TDeserializeChunkedArray::TChunk(*ExpectedRowsCount, Data); + return indexInfo.GetEntityGroupsByStorageId(specialTier, storages); } } -std::shared_ptr TPortionInfo::TAssembleBlobInfo::BuildRecordBatch(const TColumnLoader& loader) const { - if (DefaultRowsCount) { - Y_ABORT_UNLESS(!Data); - return std::make_shared( - NArrow::TThreadSimpleArraysCache::Get(loader.GetField()->type(), DefaultValue, DefaultRowsCount)); - } else { - AFL_VERIFY(ExpectedRowsCount); - return loader.ApplyVerified(Data, *ExpectedRowsCount); - } -} - -std::shared_ptr TPortionInfo::TPreparedBatchData::AssembleToGeneralContainer( - const std::set& sequentialColumnIds) const { - std::vector> columns; - std::vector> fields; - for (auto&& i : Columns) { - NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("column", i.GetField()->ToString())("id", i.GetColumnId()); - if (sequentialColumnIds.contains(i.GetColumnId())) { - columns.emplace_back(i.AssembleForSeqAccess()); - } else { - columns.emplace_back(i.AssembleAccessor()); - } - fields.emplace_back(i.GetField()); - } - - return std::make_shared(fields, std::move(columns)); +void TPortionInfo::SaveMetaToDatabase(IDbWrapper& db) const { + FullValidation(); + db.WritePortion(*this); } -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portions/portion_info.h b/ydb/core/tx/columnshard/engines/portions/portion_info.h index 6fa105745b05..48b12cd82b13 100644 --- a/ydb/core/tx/columnshard/engines/portions/portion_info.h +++ b/ydb/core/tx/columnshard/engines/portions/portion_info.h @@ -1,19 +1,17 @@ #pragma once #include "column_record.h" +#include "common.h" #include "index_chunk.h" #include "meta.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include -#include +#include +#include + +#include namespace NKikimrColumnShardDataSharingProto { class TPortionInfo; @@ -36,6 +34,7 @@ class TEntityChunk { YDB_READONLY(ui32, RecordsCount, 0); YDB_READONLY(ui64, RawBytes, 0); YDB_READONLY_DEF(TBlobRangeLink16, BlobRange); + public: const TChunkAddress& GetAddress() const { return Address; @@ -45,92 +44,120 @@ class TEntityChunk { : Address(address) , RecordsCount(recordsCount) , RawBytes(rawBytesSize) - , BlobRange(blobRange) - { - + , BlobRange(blobRange) { } }; class TPortionInfoConstructor; class TGranuleShardingInfo; +class TPortionDataAccessor; class TPortionInfo { public: + using TConstPtr = std::shared_ptr; + using TPtr = std::shared_ptr; using TRuntimeFeatures = ui8; - enum class ERuntimeFeature: TRuntimeFeatures { + enum class ERuntimeFeature : TRuntimeFeatures { Optimized = 1 /* "optimized" */ }; + private: + friend class TPortionDataAccessor; friend class TPortionInfoConstructor; - TPortionInfo(TPortionMeta&& meta) - : Meta(std::move(meta)) - { + TPortionInfo(const TPortionInfo&) = default; + TPortionInfo& operator=(const TPortionInfo&) = default; + + TPortionInfo(TPortionMeta&& meta) + : Meta(std::move(meta)) { + if (HasInsertWriteId()) { + AFL_VERIFY(!Meta.GetTierName()); + } } + std::optional CommitSnapshot; + std::optional InsertWriteId; + ui64 PathId = 0; - ui64 Portion = 0; // Id of independent (overlayed by PK) portion of data in pathId - TSnapshot MinSnapshotDeprecated = TSnapshot::Zero(); // {PlanStep, TxId} is min snapshot for {Granule, Portion} - TSnapshot RemoveSnapshot = TSnapshot::Zero(); // {XPlanStep, XTxId} is snapshot where the blob has been removed (i.e. compacted into another one) + ui64 PortionId = 0; // Id of independent (overlayed by PK) portion of data in pathId + TSnapshot MinSnapshotDeprecated = TSnapshot::Zero(); // {PlanStep, TxId} is min snapshot for {Granule, Portion} + TSnapshot RemoveSnapshot = TSnapshot::Zero(); std::optional SchemaVersion; std::optional ShardingVersion; TPortionMeta Meta; - YDB_READONLY_DEF(std::vector, Indexes); - YDB_READONLY(TRuntimeFeatures, RuntimeFeatures, 0); - std::vector BlobIds; - TConclusionStatus DeserializeFromProto(const NKikimrColumnShardDataSharingProto::TPortionInfo& proto, const TIndexInfo& info); - - template - static void CheckChunksOrder(const std::vector& chunks) { - ui32 entityId = 0; - ui32 chunkIdx = 0; - for (auto&& i : chunks) { - if (entityId != i.GetEntityId()) { - AFL_VERIFY(entityId < i.GetEntityId()); - AFL_VERIFY(i.GetChunkIdx() == 0); - entityId = i.GetEntityId(); - chunkIdx = 0; - } else { - AFL_VERIFY(i.GetChunkIdx() == chunkIdx + 1); - chunkIdx = i.GetChunkIdx(); - } - } - } + TRuntimeFeatures RuntimeFeatures = 0; - template - static void AggregateIndexChunksData(const TAggregator& aggr, const std::vector& chunks, const std::set* columnIds, const bool validation) { - if (columnIds) { - auto itColumn = columnIds->begin(); - auto itRecord = chunks.begin(); - ui32 recordsInEntityCount = 0; - while (itRecord != chunks.end() && itColumn != columnIds->end()) { - if (itRecord->GetEntityId() < *itColumn) { - ++itRecord; - } else if (*itColumn < itRecord->GetEntityId()) { - AFL_VERIFY(!validation || recordsInEntityCount)("problem", "validation")("reason", "no_chunks_for_column")("column_id", *itColumn); - ++itColumn; - recordsInEntityCount = 0; - } else { - ++recordsInEntityCount; - aggr(*itRecord); - ++itRecord; - } - } - } else { - for (auto&& i : chunks) { - aggr(i); - } - } + void FullValidation() const { + AFL_VERIFY(PathId); + AFL_VERIFY(PortionId); + AFL_VERIFY(MinSnapshotDeprecated.Valid()); + Meta.FullValidation(); } + + TConclusionStatus DeserializeFromProto(const NKikimrColumnShardDataSharingProto::TPortionInfo& proto); + public: - ui64 GetMinMemoryForReadColumns(const std::optional>& columnIds) const; + TPortionInfo(TPortionInfo&&) = default; + TPortionInfo& operator=(TPortionInfo&&) = default; + + ui32 PredictAccessorsMemory(const ISnapshotSchema::TPtr& schema) const { + return (GetRecordsCount() / 10000 + 1) * sizeof(TColumnRecord) * schema->GetColumnsCount() + schema->GetIndexesCount() * sizeof(TIndexChunk); + } + + ui32 PredictMetadataMemorySize(const ui32 columnsCount) const { + return (GetRecordsCount() / 10000 + 1) * sizeof(TColumnRecord) * columnsCount; + } + + void SaveMetaToDatabase(IDbWrapper& db) const; + + TPortionInfo MakeCopy() const { + return *this; + } + + const std::vector& GetBlobIds() const { + return Meta.GetBlobIds(); + } + + ui32 GetCompactionLevel() const { + return GetMeta().GetCompactionLevel(); + } bool NeedShardingFilter(const TGranuleShardingInfo& shardingInfo) const; + NSplitter::TEntityGroups GetEntityGroupsByStorageId( + const TString& specialTier, const IStoragesManager& storages, const TIndexInfo& indexInfo) const; + const std::optional& GetShardingVersionOptional() const { return ShardingVersion; } + bool HasCommitSnapshot() const { + return !!CommitSnapshot; + } + bool HasInsertWriteId() const { + return !!InsertWriteId; + } + const TSnapshot& GetCommitSnapshotVerified() const { + AFL_VERIFY(!!CommitSnapshot); + return *CommitSnapshot; + } + TInsertWriteId GetInsertWriteIdVerified() const { + AFL_VERIFY(InsertWriteId); + return *InsertWriteId; + } + const std::optional& GetCommitSnapshotOptional() const { + return CommitSnapshot; + } + const std::optional& GetInsertWriteIdOptional() const { + return InsertWriteId; + } + void SetCommitSnapshot(const TSnapshot& value) { + AFL_VERIFY(!!InsertWriteId); + AFL_VERIFY(!CommitSnapshot); + AFL_VERIFY(value.Valid()); + CommitSnapshot = value; + } + bool CrossSSWith(const TPortionInfo& p) const { return std::min(RecordSnapshotMax(), p.RecordSnapshotMax()) <= std::max(RecordSnapshotMin(), p.RecordSnapshotMin()); } @@ -148,16 +175,6 @@ class TPortionInfo { SetRemoveSnapshot(TSnapshot(planStep, txId)); } - std::vector GetIndexInplaceDataVerified(const ui32 indexId) const { - std::vector result; - for (auto&& i : Indexes) { - if (i.GetEntityId() == indexId) { - result.emplace_back(i.GetBlobDataVerified()); - } - } - return result; - } - void InitRuntimeFeature(const ERuntimeFeature feature, const bool activity) { if (activity) { AddRuntimeFeature(feature); @@ -174,6 +191,13 @@ class TPortionInfo { RuntimeFeatures &= (Max() - (TRuntimeFeatures)feature); } + TString GetTierNameDef(const TString& defaultTierName) const { + if (GetMeta().GetTierName()) { + return GetMeta().GetTierName(); + } + return defaultTierName; + } + bool HasRuntimeFeature(const ERuntimeFeature feature) const { if (feature == ERuntimeFeature::Optimized) { if ((RuntimeFeatures & (TRuntimeFeatures)feature)) { @@ -185,85 +209,35 @@ class TPortionInfo { return (RuntimeFeatures & (TRuntimeFeatures)feature); } - void FullValidation() const; - - bool HasIndexes(const std::set& ids) const { - auto idsCopy = ids; - for (auto&& i : Indexes) { - idsCopy.erase(i.GetIndexId()); - if (idsCopy.empty()) { - return true; - } - } - return false; - } - - void ReorderChunks(); - - THashMap>> RestoreEntityChunks(NBlobOperations::NRead::TCompositeReadBlobs& blobs, const TIndexInfo& indexInfo) const; - const TBlobRange RestoreBlobRange(const TBlobRangeLink16& linkRange) const { return linkRange.RestoreRange(GetBlobId(linkRange.GetBlobIdxVerified())); } const TUnifiedBlobId& GetBlobId(const TBlobRangeLink16::TLinkId linkId) const { - AFL_VERIFY(linkId < BlobIds.size()); - return BlobIds[linkId]; + return Meta.GetBlobId(linkId); } ui32 GetBlobIdsCount() const { - return BlobIds.size(); + return Meta.GetBlobIdsCount(); } - THashMap DecodeBlobAddresses(NBlobOperations::NRead::TCompositeReadBlobs&& blobs, const TIndexInfo& indexInfo) const; - const TString& GetColumnStorageId(const ui32 columnId, const TIndexInfo& indexInfo) const; + const TString& GetIndexStorageId(const ui32 columnId, const TIndexInfo& indexInfo) const; const TString& GetEntityStorageId(const ui32 entityId, const TIndexInfo& indexInfo) const; - ui64 GetTxVolume() const; // fake-correct method for determ volume on rewrite this portion in transaction progress - ui64 GetMetadataMemorySize() const; - - class TPage { - private: - YDB_READONLY_DEF(std::vector, Records); - YDB_READONLY_DEF(std::vector, Indexes); - YDB_READONLY(ui32, RecordsCount, 0); - public: - TPage(std::vector&& records, std::vector&& indexes, const ui32 recordsCount) - : Records(std::move(records)) - , Indexes(std::move(indexes)) - , RecordsCount(recordsCount) - { - - } - }; - - TString GetTierNameDef(const TString& defaultTierName) const { - if (GetMeta().GetTierName()) { - return GetMeta().GetTierName(); - } - return defaultTierName; + ui64 GetTxVolume() const { + return 1024; } - static TConclusion BuildFromProto(const NKikimrColumnShardDataSharingProto::TPortionInfo& proto, const TIndexInfo& info); - void SerializeToProto(NKikimrColumnShardDataSharingProto::TPortionInfo& proto) const; - - std::vector BuildPages() const; - - std::vector Records; + ui64 GetApproxChunksCount(const ui32 schemaColumnsCount) const; + ui64 GetMetadataMemorySize() const; - const std::vector& GetRecords() const { - return Records; - } + void SerializeToProto(NKikimrColumnShardDataSharingProto::TPortionInfo& proto) const; ui64 GetPathId() const { return PathId; } - void RemoveFromDatabase(IDbWrapper& db) const; - - void SaveToDatabase(IDbWrapper& db, const ui32 firstPKColumnId, const bool saveOnlyMeta) const; - bool OlderThen(const TPortionInfo& info) const { return RecordSnapshotMin() < info.RecordSnapshotMin(); } @@ -289,12 +263,12 @@ class TPortionInfo { } ui64 GetPortionId() const { - return Portion; + return PortionId; } NJson::TJsonValue SerializeToJsonVisual() const { NJson::TJsonValue result = NJson::JSON_MAP; - result.InsertValue("id", Portion); + result.InsertValue("id", PortionId); result.InsertValue("s_max", RecordSnapshotMax().GetPlanStep() / 1000); /* result.InsertValue("s_min", RecordSnapshotMin().GetPlanStep()); @@ -309,26 +283,6 @@ class TPortionInfo { static constexpr const ui32 BLOB_BYTES_LIMIT = 8 * 1024 * 1024; - std::vector GetColumnChunksPointers(const ui32 columnId) const; - - std::set GetColumnIds() const { - std::set result; - for (auto&& i : Records) { - result.emplace(i.GetColumnId()); - } - return result; - } - - NArrow::NSplitter::TSerializationStats GetSerializationStat(const ISnapshotSchema& schema) const { - NArrow::NSplitter::TSerializationStats result; - for (auto&& i : Records) { - if (schema.GetFieldByColumnIdOptional(i.ColumnId)) { - result.AddStat(i.GetSerializationStat(schema.GetFieldByColumnIdVerified(i.ColumnId)->name())); - } - } - return result; - } - const TPortionMeta& GetMeta() const { return Meta; } @@ -337,46 +291,10 @@ class TPortionInfo { return Meta; } - const TColumnRecord* GetRecordPointer(const TChunkAddress& address) const { - auto it = std::lower_bound(Records.begin(), Records.end(), address, [](const TColumnRecord& item, const TChunkAddress& address) { - return item.GetAddress() < address; - }); - if (it != Records.end() && it->GetAddress() == address) { - return &*it; - } - return nullptr; - } - - bool HasEntityAddress(const TChunkAddress& address) const { - { - auto it = std::lower_bound(Records.begin(), Records.end(), address, [](const TColumnRecord& item, const TChunkAddress& address) { - return item.GetAddress() < address; - }); - if (it != Records.end() && it->GetAddress() == address) { - return true; - } - } - { - auto it = std::lower_bound(Indexes.begin(), Indexes.end(), address, [](const TIndexChunk& item, const TChunkAddress& address) { - return item.GetAddress() < address; - }); - if (it != Indexes.end() && it->GetAddress() == address) { - return true; - } - } - return false; + bool ValidSnapshotInfo() const { + return MinSnapshotDeprecated.Valid() && PathId && PortionId; } - bool Empty() const { return Records.empty(); } - bool Produced() const { return Meta.GetProduced() != TPortionMeta::EProduced::UNSPECIFIED; } - bool Valid() const { return ValidSnapshotInfo() && !Empty() && Produced(); } - bool ValidSnapshotInfo() const { return MinSnapshotDeprecated.Valid() && PathId && Portion; } - bool IsInserted() const { return Meta.GetProduced() == TPortionMeta::EProduced::INSERTED; } - bool IsEvicted() const { return Meta.GetProduced() == TPortionMeta::EProduced::EVICTED; } - bool CanHaveDups() const { return !Produced(); /* || IsInserted(); */ } - bool CanIntersectOthers() const { return !Valid() || IsInserted() || IsEvicted(); } - size_t NumChunks() const { return Records.size(); } - TString DebugString(const bool withDetails = false) const; bool HasRemoveSnapshot() const { @@ -399,12 +317,8 @@ class TPortionInfo { return HasRemoveSnapshot(); } - ui64 GetPortion() const { - return Portion; - } - TPortionAddress GetAddress() const { - return TPortionAddress(PathId, Portion); + return TPortionAddress(PathId, PortionId); } void ResetShardingVersion() { @@ -415,11 +329,10 @@ class TPortionInfo { PathId = pathId; } - void SetPortion(const ui64 portion) { - Portion = portion; + void SetPortionId(const ui64 id) { + PortionId = id; } - const TSnapshot& GetMinSnapshotDeprecated() const { return MinSnapshotDeprecated; } @@ -446,19 +359,15 @@ class TPortionInfo { return SchemaVersion; } - bool IsVisible(const TSnapshot& snapshot) const { - if (Empty()) { - return false; - } - - const bool visible = (Meta.RecordSnapshotMin <= snapshot) && (!RemoveSnapshot.Valid() || snapshot < RemoveSnapshot); + bool IsVisible(const TSnapshot& snapshot, const bool checkCommitSnapshot = true) const { + const bool visible = (Meta.RecordSnapshotMin <= snapshot) && (!RemoveSnapshot.Valid() || snapshot < RemoveSnapshot) && + (!checkCommitSnapshot || !CommitSnapshot || *CommitSnapshot <= snapshot); - AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "IsVisible")("analyze_portion", DebugString())("visible", visible)("snapshot", snapshot.DebugString()); + AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "IsVisible")("analyze_portion", DebugString())("visible", visible)( + "snapshot", snapshot.DebugString()); return visible; } - std::shared_ptr MaxValue(ui32 columnId) const; - const NArrow::TReplaceKey& IndexKeyStart() const { return Meta.IndexKeyStart; } @@ -467,29 +376,41 @@ class TPortionInfo { return Meta.IndexKeyEnd; } - const TSnapshot& RecordSnapshotMin() const { - return Meta.RecordSnapshotMin; - } - - const TSnapshot& RecordSnapshotMax() const { - return Meta.RecordSnapshotMax; + const TSnapshot& RecordSnapshotMin(const std::optional& snapshotDefault = std::nullopt) const { + if (InsertWriteId) { + if (CommitSnapshot) { + return *CommitSnapshot; + } else { + AFL_VERIFY(snapshotDefault); + return *snapshotDefault; + } + } else { + return Meta.RecordSnapshotMin; + } } - - THashMap> GetBlobIdsByStorage(const TIndexInfo& indexInfo) const { - THashMap> result; - FillBlobIdsByStorage(result, indexInfo); - return result; + const TSnapshot& RecordSnapshotMax(const std::optional& snapshotDefault = std::nullopt) const { + if (InsertWriteId) { + if (CommitSnapshot) { + return *CommitSnapshot; + } else { + AFL_VERIFY(snapshotDefault); + return *snapshotDefault; + } + } else { + return Meta.RecordSnapshotMax; + } } class TSchemaCursor { const NOlap::TVersionedIndex& VersionedIndex; ISnapshotSchema::TPtr CurrentSchema; TSnapshot LastSnapshot = TSnapshot::Zero(); + public: TSchemaCursor(const NOlap::TVersionedIndex& versionedIndex) - : VersionedIndex(versionedIndex) - {} + : VersionedIndex(versionedIndex) { + } ISnapshotSchema::TPtr GetSchema(const TPortionInfoConstructor& portion); @@ -505,53 +426,20 @@ class TPortionInfo { ISnapshotSchema::TPtr GetSchema(const TVersionedIndex& index) const; - void FillBlobRangesByStorage(THashMap>& result, const TIndexInfo& indexInfo) const; - void FillBlobRangesByStorage(THashMap>& result, const TVersionedIndex& index) const; - - void FillBlobIdsByStorage(THashMap>& result, const TIndexInfo& indexInfo) const; - void FillBlobIdsByStorage(THashMap>& result, const TVersionedIndex& index) const; - ui32 GetRecordsCount() const { - ui32 result = 0; - std::optional columnIdFirst; - for (auto&& i : Records) { - if (!columnIdFirst || *columnIdFirst == i.ColumnId) { - result += i.GetMeta().GetNumRows(); - columnIdFirst = i.ColumnId; - } - } - return result; - } - - ui32 NumRows() const { - return GetRecordsCount(); + return GetMeta().GetRecordsCount(); } - ui32 NumRows(const ui32 columnId) const { - ui32 result = 0; - for (auto&& i : Records) { - if (columnId == i.ColumnId) { - result += i.GetMeta().GetNumRows(); - } - } - return result; - } - - ui64 GetIndexRawBytes(const std::set& columnIds, const bool validation = true) const; - ui64 GetIndexRawBytes(const bool validation = true) const; ui64 GetIndexBlobBytes() const noexcept { - ui64 sum = 0; - for (const auto& rec : Indexes) { - sum += rec.GetDataSize(); - } - return sum; + return GetMeta().GetIndexBlobBytes(); } - ui64 GetColumnRawBytes(const std::set& columnIds, const bool validation = true) const; - ui64 GetColumnRawBytes(const bool validation = true) const; + ui64 GetIndexRawBytes() const noexcept { + return GetMeta().GetIndexRawBytes(); + } - ui64 GetColumnBlobBytes(const std::set& columnIds, const bool validation = true) const; - ui64 GetColumnBlobBytes(const bool validation = true) const; + ui64 GetColumnRawBytes() const; + ui64 GetColumnBlobBytes() const; ui64 GetTotalBlobBytes() const noexcept { return GetIndexBlobBytes() + GetColumnBlobBytes(); @@ -560,193 +448,8 @@ class TPortionInfo { ui64 GetTotalRawBytes() const { return GetColumnRawBytes() + GetIndexRawBytes(); } -public: - class TAssembleBlobInfo { - private: - YDB_READONLY_DEF(std::optional, ExpectedRowsCount); - ui32 DefaultRowsCount = 0; - std::shared_ptr DefaultValue; - TString Data; - public: - ui32 GetExpectedRowsCountVerified() const { - AFL_VERIFY(ExpectedRowsCount); - return *ExpectedRowsCount; - } - - void SetExpectedRecordsCount(const ui32 expectedRowsCount) { - AFL_VERIFY(!ExpectedRowsCount); - ExpectedRowsCount = expectedRowsCount; - if (!Data) { - AFL_VERIFY(*ExpectedRowsCount == DefaultRowsCount); - } - } - - TAssembleBlobInfo(const ui32 rowsCount, const std::shared_ptr& defValue) - : DefaultRowsCount(rowsCount) - , DefaultValue(defValue) - { - AFL_VERIFY(DefaultRowsCount); - } - - TAssembleBlobInfo(const TString& data) - : Data(data) { - AFL_VERIFY(!!Data); - } - - ui32 GetDefaultRowsCount() const noexcept { - return DefaultRowsCount; - } - - const TString& GetData() const noexcept { - return Data; - } - - bool IsBlob() const { - return !DefaultRowsCount && !!Data; - } - - bool IsDefault() const { - return DefaultRowsCount && !Data; - } - - std::shared_ptr BuildRecordBatch(const TColumnLoader& loader) const; - NArrow::NAccessor::TDeserializeChunkedArray::TChunk BuildDeserializeChunk(const std::shared_ptr& loader) const; - }; - - class TPreparedColumn { - private: - std::shared_ptr Loader; - std::vector Blobs; - public: - ui32 GetColumnId() const { - return Loader->GetColumnId(); - } - - const std::string& GetName() const { - return Loader->GetField()->name(); - } - - std::shared_ptr GetField() const { - return Loader->GetField(); - } - - TPreparedColumn(std::vector&& blobs, const std::shared_ptr& loader) - : Loader(loader) - , Blobs(std::move(blobs)) { - AFL_VERIFY(Loader); - } - - std::shared_ptr AssembleForSeqAccess() const; - std::shared_ptr AssembleAccessor() const; - }; - - class TPreparedBatchData { - private: - std::vector Columns; - size_t RowsCount = 0; - public: - struct TAssembleOptions { - std::optional> IncludedColumnIds; - std::optional> ExcludedColumnIds; - std::map> ConstantColumnIds; - - bool IsConstantColumn(const ui32 columnId, std::shared_ptr& scalar) const { - if (ConstantColumnIds.empty()) { - return false; - } - auto it = ConstantColumnIds.find(columnId); - if (it == ConstantColumnIds.end()) { - return false; - } - scalar = it->second; - return true; - } - - bool IsAcceptedColumn(const ui32 columnId) const { - if (IncludedColumnIds && !IncludedColumnIds->contains(columnId)) { - return false; - } - if (ExcludedColumnIds && ExcludedColumnIds->contains(columnId)) { - return false; - } - return true; - } - }; - - std::shared_ptr GetFieldVerified(const ui32 columnId) const { - for (auto&& i : Columns) { - if (i.GetColumnId() == columnId) { - return i.GetField(); - } - } - AFL_VERIFY(false); - return nullptr; - } - - size_t GetColumnsCount() const { - return Columns.size(); - } - - size_t GetRowsCount() const { - return RowsCount; - } - - TPreparedBatchData(std::vector&& columns, const size_t rowsCount) - : Columns(std::move(columns)) - , RowsCount(rowsCount) { - } - - std::shared_ptr AssembleToGeneralContainer(const std::set& sequentialColumnIds) const; - }; - - class TColumnAssemblingInfo { - private: - std::vector BlobsInfo; - YDB_READONLY(ui32, ColumnId, 0); - const ui32 NumRows; - ui32 NumRowsByChunks = 0; - const std::shared_ptr DataLoader; - const std::shared_ptr ResultLoader; - public: - TColumnAssemblingInfo(const ui32 numRows, const std::shared_ptr& dataLoader, const std::shared_ptr& resultLoader) - : ColumnId(resultLoader->GetColumnId()) - , NumRows(numRows) - , DataLoader(dataLoader) - , ResultLoader(resultLoader) { - AFL_VERIFY(ResultLoader); - if (DataLoader) { - AFL_VERIFY(ResultLoader->GetColumnId() == DataLoader->GetColumnId()); - AFL_VERIFY(DataLoader->GetField()->IsCompatibleWith(ResultLoader->GetField()))("data", DataLoader->GetField()->ToString())("result", ResultLoader->GetField()->ToString()); - } - } - - const std::shared_ptr& GetField() const { - return ResultLoader->GetField(); - } - - void AddBlobInfo(const ui32 expectedChunkIdx, const ui32 expectedRecordsCount, TAssembleBlobInfo&& info) { - AFL_VERIFY(expectedChunkIdx == BlobsInfo.size()); - info.SetExpectedRecordsCount(expectedRecordsCount); - NumRowsByChunks += expectedRecordsCount; - BlobsInfo.emplace_back(std::move(info)); - } - - TPreparedColumn Compile() { - if (BlobsInfo.empty()) { - BlobsInfo.emplace_back(TAssembleBlobInfo(NumRows, DataLoader ? DataLoader->GetDefaultValue() : ResultLoader->GetDefaultValue())); - return TPreparedColumn(std::move(BlobsInfo), ResultLoader); - } else { - AFL_VERIFY(NumRowsByChunks == NumRows)("by_chunks", NumRowsByChunks)("expected", NumRows); - AFL_VERIFY(DataLoader); - return TPreparedColumn(std::move(BlobsInfo), DataLoader); - } - } - }; - - TPreparedBatchData PrepareForAssemble(const ISnapshotSchema& dataSchema, const ISnapshotSchema& resultSchema, THashMap& blobsData) const; - TPreparedBatchData PrepareForAssemble(const ISnapshotSchema& dataSchema, const ISnapshotSchema& resultSchema, THashMap& blobsData) const; - friend IOutputStream& operator << (IOutputStream& out, const TPortionInfo& info) { + friend IOutputStream& operator<<(IOutputStream& out, const TPortionInfo& info) { out << info.DebugString(); return out; } @@ -758,4 +461,4 @@ static_assert(std::is_nothrow_move_assignable::value); /// Ensure that TPortionInfo can be effectively constructed by moving the value. static_assert(std::is_nothrow_move_constructible::value); -} // namespace NKikimr::NOlap +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portions/read_with_blobs.cpp b/ydb/core/tx/columnshard/engines/portions/read_with_blobs.cpp index ae85ef59842c..6c0409216080 100644 --- a/ydb/core/tx/columnshard/engines/portions/read_with_blobs.cpp +++ b/ydb/core/tx/columnshard/engines/portions/read_with_blobs.cpp @@ -1,5 +1,7 @@ +#include "data_accessor.h" #include "read_with_blobs.h" #include "write_with_blobs.h" + #include #include #include @@ -9,23 +11,27 @@ namespace NKikimr::NOlap { void TReadPortionInfoWithBlobs::RestoreChunk(const std::shared_ptr& chunk) { auto address = chunk->GetChunkAddressVerified(); - AFL_VERIFY(GetPortionInfo().HasEntityAddress(address))("address", address.DebugString()); + AFL_VERIFY(PortionInfo.HasEntityAddress(address))("address", address.DebugString()); AFL_VERIFY(Chunks.emplace(address, chunk).second)("address", address.DebugString()); } -std::shared_ptr TReadPortionInfoWithBlobs::RestoreBatch( - const ISnapshotSchema& data, const ISnapshotSchema& resultSchema, const std::set& seqColumns) const { +TConclusion> TReadPortionInfoWithBlobs::RestoreBatch( + const ISnapshotSchema& data, const ISnapshotSchema& resultSchema, const std::set& seqColumns, const bool restoreAbsent) const { THashMap blobs; - for (auto&& i : PortionInfo.Records) { + NActors::TLogContextGuard gLogging = + NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("portion_id", PortionInfo.GetPortionInfo().GetPortionId()); + for (auto&& i : PortionInfo.GetRecordsVerified()) { blobs[i.GetAddress()] = GetBlobByAddressVerified(i.ColumnId, i.Chunk); Y_ABORT_UNLESS(blobs[i.GetAddress()].size() == i.BlobRange.Size); } - return PortionInfo.PrepareForAssemble(data, resultSchema, blobs).AssembleToGeneralContainer(seqColumns); + return PortionInfo.PrepareForAssemble(data, resultSchema, blobs, {}, restoreAbsent).AssembleToGeneralContainer(seqColumns); } -NKikimr::NOlap::TReadPortionInfoWithBlobs TReadPortionInfoWithBlobs::RestorePortion(const TPortionInfo& portion, NBlobOperations::NRead::TCompositeReadBlobs& blobs, const TIndexInfo& indexInfo) { +TReadPortionInfoWithBlobs TReadPortionInfoWithBlobs::RestorePortion( + const TPortionDataAccessor& portion, NBlobOperations::NRead::TCompositeReadBlobs& blobs, const TIndexInfo& indexInfo) { TReadPortionInfoWithBlobs result(portion); - THashMap>> records = result.PortionInfo.RestoreEntityChunks(blobs, indexInfo); + THashMap>> records = + result.PortionInfo.RestoreEntityChunks(blobs, indexInfo); for (auto&& [storageId, chunksByAddress] : records) { for (auto&& [_, chunk] : chunksByAddress) { result.RestoreChunk(chunk); @@ -34,11 +40,12 @@ NKikimr::NOlap::TReadPortionInfoWithBlobs TReadPortionInfoWithBlobs::RestorePort return result; } -std::vector TReadPortionInfoWithBlobs::RestorePortions(const std::vector& portions, NBlobOperations::NRead::TCompositeReadBlobs& blobs, +std::vector TReadPortionInfoWithBlobs::RestorePortions( + const std::vector& portions, NBlobOperations::NRead::TCompositeReadBlobs& blobs, const TVersionedIndex& tables) { std::vector result; for (auto&& i : portions) { - const auto schema = i.GetSchema(tables); + const auto schema = i.GetPortionInfo().GetSchema(tables); result.emplace_back(RestorePortion(i, blobs, schema->GetIndexInfo())); } return result; @@ -59,8 +66,9 @@ std::vector> TReadPortionInfoWithBlobs::GetEn return result; } -bool TReadPortionInfoWithBlobs::ExtractColumnChunks(const ui32 entityId, std::vector& records, std::vector>& chunks) { - records = GetPortionInfo().GetColumnChunksPointers(entityId); +bool TReadPortionInfoWithBlobs::ExtractColumnChunks( + const ui32 entityId, std::vector& records, std::vector>& chunks) { + records = PortionInfo.GetColumnChunksPointers(entityId); if (records.empty()) { return false; } @@ -79,13 +87,13 @@ bool TReadPortionInfoWithBlobs::ExtractColumnChunks(const ui32 entityId, std::ve } std::optional TReadPortionInfoWithBlobs::SyncPortion(TReadPortionInfoWithBlobs&& source, - const ISnapshotSchema::TPtr& from, const ISnapshotSchema::TPtr& to, const TString& targetTier, const std::shared_ptr& storages, - std::shared_ptr counters) { + const ISnapshotSchema::TPtr& from, const ISnapshotSchema::TPtr& to, const TString& targetTier, + const std::shared_ptr& storages, std::shared_ptr counters) { if (from->GetVersion() == to->GetVersion() && targetTier == source.GetPortionInfo().GetTierNameDef(IStoragesManager::DefaultStorageId)) { AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "we don't need sync portion"); return {}; } - NYDBTest::TControllers::GetColumnShardController()->OnPortionActualization(source.PortionInfo); + NYDBTest::TControllers::GetColumnShardController()->OnPortionActualization(source.PortionInfo.GetPortionInfo()); auto pages = source.PortionInfo.BuildPages(); std::vector pageSizes; for (auto&& p : pages) { @@ -108,18 +116,18 @@ std::optional TReadPortionInfoWithBlobs::SyncP } } - TPortionInfoConstructor constructor(source.PortionInfo, false, true); - constructor.SetMinSnapshotDeprecated(to->GetSnapshot()); - constructor.SetSchemaVersion(to->GetVersion()); - constructor.MutableMeta().ResetTierName(targetTier); + TPortionAccessorConstructor constructor = TPortionAccessorConstructor::BuildForRewriteBlobs(source.PortionInfo.GetPortionInfo()); + constructor.MutablePortionConstructor().SetMinSnapshotDeprecated(to->GetSnapshot()); + constructor.MutablePortionConstructor().SetSchemaVersion(to->GetVersion()); + constructor.MutablePortionConstructor().MutableMeta().ResetTierName(targetTier); TIndexInfo::TSecondaryData secondaryData; secondaryData.MutableExternalData() = entityChunksNew; for (auto&& i : to->GetIndexInfo().GetIndexes()) { - to->GetIndexInfo().AppendIndex(entityChunksNew, i.first, storages, secondaryData).Validate(); + to->GetIndexInfo().AppendIndex(entityChunksNew, i.first, storages, source.PortionInfo.GetPortionInfo().GetRecordsCount(), secondaryData).Validate(); } - const NSplitter::TEntityGroups groups = to->GetIndexInfo().GetEntityGroupsByStorageId(targetTier, *storages); + const NSplitter::TEntityGroups groups = source.PortionInfo.GetPortionInfo().GetEntityGroupsByStorageId(targetTier, *storages, to->GetIndexInfo()); auto schemaTo = std::make_shared(to, std::make_shared()); TGeneralSerializedSlice slice(secondaryData.GetExternalData(), schemaTo, counters); @@ -133,4 +141,4 @@ const TString& TReadPortionInfoWithBlobs::GetBlobByAddressVerified(const ui32 co return it->second->GetData(); } -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portions/read_with_blobs.h b/ydb/core/tx/columnshard/engines/portions/read_with_blobs.h index a9e24eb3c165..9d3136bf199b 100644 --- a/ydb/core/tx/columnshard/engines/portions/read_with_blobs.h +++ b/ydb/core/tx/columnshard/engines/portions/read_with_blobs.h @@ -20,25 +20,28 @@ class TReadPortionInfoWithBlobs: public TBasePortionInfoWithBlobs { YDB_READONLY_DEF(TBlobChunks, Chunks); void RestoreChunk(const std::shared_ptr& chunk); - TPortionInfo PortionInfo; + TPortionDataAccessor PortionInfo; - explicit TReadPortionInfoWithBlobs(TPortionInfo&& portionInfo) + explicit TReadPortionInfoWithBlobs(TPortionDataAccessor&& portionInfo) : PortionInfo(std::move(portionInfo)) { } - explicit TReadPortionInfoWithBlobs(const TPortionInfo& portionInfo) + explicit TReadPortionInfoWithBlobs(const TPortionDataAccessor& portionInfo) : PortionInfo(portionInfo) { } const TString& GetBlobByAddressVerified(const ui32 columnId, const ui32 chunkId) const; public: - static std::vector RestorePortions(const std::vector& portions, NBlobOperations::NRead::TCompositeReadBlobs& blobs, + static std::vector RestorePortions( + const std::vector& portions, NBlobOperations::NRead::TCompositeReadBlobs& blobs, const TVersionedIndex& tables); - static TReadPortionInfoWithBlobs RestorePortion(const TPortionInfo& portion, NBlobOperations::NRead::TCompositeReadBlobs& blobs, + static TReadPortionInfoWithBlobs RestorePortion( + const TPortionDataAccessor& portion, NBlobOperations::NRead::TCompositeReadBlobs& blobs, const TIndexInfo& indexInfo); - std::shared_ptr RestoreBatch(const ISnapshotSchema& data, const ISnapshotSchema& resultSchema, const std::set& seqColumns) const; + TConclusion> RestoreBatch( + const ISnapshotSchema& data, const ISnapshotSchema& resultSchema, const std::set& seqColumns, const bool restoreAbsent = true) const; static std::optional SyncPortion(TReadPortionInfoWithBlobs&& source, const ISnapshotSchema::TPtr& from, const ISnapshotSchema::TPtr& to, const TString& targetTier, const std::shared_ptr& storages, std::shared_ptr counters); @@ -52,11 +55,7 @@ class TReadPortionInfoWithBlobs: public TBasePortionInfoWithBlobs { } const TPortionInfo& GetPortionInfo() const { - return PortionInfo; - } - - TPortionInfo& GetPortionInfo() { - return PortionInfo; + return PortionInfo.GetPortionInfo(); } friend IOutputStream& operator << (IOutputStream& out, const TReadPortionInfoWithBlobs& info) { diff --git a/ydb/core/tx/columnshard/engines/portions/write_with_blobs.cpp b/ydb/core/tx/columnshard/engines/portions/write_with_blobs.cpp index 3f580531b749..0118b5ce60ba 100644 --- a/ydb/core/tx/columnshard/engines/portions/write_with_blobs.cpp +++ b/ydb/core/tx/columnshard/engines/portions/write_with_blobs.cpp @@ -17,7 +17,9 @@ void TWritePortionInfoWithBlobsConstructor::TBlobInfo::AddChunk(TWritePortionInf chunk->AddIntoPortionBeforeBlob(bRange, owner.GetPortionConstructor()); } -void TWritePortionInfoWithBlobsResult::TBlobInfo::RegisterBlobId(TWritePortionInfoWithBlobsResult& owner, const TUnifiedBlobId& blobId) const { +void TWritePortionInfoWithBlobsResult::TBlobInfo::RegisterBlobId(TWritePortionInfoWithBlobsResult& owner, const TUnifiedBlobId& blobId) { + AFL_VERIFY(!BlobId); + BlobId = blobId; const TBlobRangeLink16::TLinkId idx = owner.GetPortionConstructor().RegisterBlobId(blobId); for (auto&& i : Chunks) { owner.GetPortionConstructor().RegisterBlobIdx(i, idx); @@ -28,14 +30,15 @@ TWritePortionInfoWithBlobsConstructor TWritePortionInfoWithBlobsConstructor::Bui const THashMap>& inplaceChunks, const ui64 granule, const ui64 schemaVersion, const TSnapshot& snapshot, const std::shared_ptr& operators) { - TPortionInfoConstructor constructor(granule); - constructor.SetMinSnapshotDeprecated(snapshot); - constructor.SetSchemaVersion(schemaVersion); + TPortionAccessorConstructor constructor(granule); + constructor.MutablePortionConstructor().SetMinSnapshotDeprecated(snapshot); + constructor.MutablePortionConstructor().SetSchemaVersion(schemaVersion); return BuildByBlobs(std::move(chunks), inplaceChunks, std::move(constructor), operators); } -TWritePortionInfoWithBlobsConstructor TWritePortionInfoWithBlobsConstructor::BuildByBlobs( - std::vector&& chunks, const THashMap>& inplaceChunks, TPortionInfoConstructor&& constructor, const std::shared_ptr& operators) { +TWritePortionInfoWithBlobsConstructor TWritePortionInfoWithBlobsConstructor::BuildByBlobs(std::vector&& chunks, + const THashMap>& inplaceChunks, TPortionAccessorConstructor&& constructor, + const std::shared_ptr& operators) { TWritePortionInfoWithBlobsConstructor result(std::move(constructor)); for (auto&& blob : chunks) { auto storage = operators->GetOperatorVerified(blob.GetGroupName()); diff --git a/ydb/core/tx/columnshard/engines/portions/write_with_blobs.h b/ydb/core/tx/columnshard/engines/portions/write_with_blobs.h index dde424fd63b8..2bf02739e08a 100644 --- a/ydb/core/tx/columnshard/engines/portions/write_with_blobs.h +++ b/ydb/core/tx/columnshard/engines/portions/write_with_blobs.h @@ -1,12 +1,14 @@ #pragma once #include "base_with_blobs.h" -#include "constructor.h" +#include "constructor_accessor.h" +#include "data_accessor.h" -#include #include #include #include +#include + namespace NKikimr::NOlap { class TWritePortionInfoWithBlobsResult; @@ -22,17 +24,17 @@ class TWritePortionInfoWithBlobsConstructor: public TBasePortionInfoWithBlobs { std::vector> ChunksOrdered; bool Finished = false; void AddChunk(TWritePortionInfoWithBlobsConstructor& owner, const std::shared_ptr& chunk); + public: TBlobInfo(const std::shared_ptr& bOperator) - : Operator(bOperator) - { - + : Operator(bOperator) { } class TBuilder { private: TBlobInfo* OwnerBlob; TWritePortionInfoWithBlobsConstructor* OwnerPortion; + public: TBuilder(TBlobInfo& blob, TWritePortionInfoWithBlobsConstructor& portion) : OwnerBlob(&blob) @@ -68,12 +70,14 @@ class TWritePortionInfoWithBlobsConstructor: public TBasePortionInfoWithBlobs { return result; } }; + private: - std::optional PortionConstructor; + std::optional PortionConstructor; YDB_READONLY_DEF(std::vector, Blobs); - explicit TWritePortionInfoWithBlobsConstructor(TPortionInfoConstructor&& portionConstructor) + explicit TWritePortionInfoWithBlobsConstructor(TPortionAccessorConstructor&& portionConstructor) : PortionConstructor(std::move(portionConstructor)) { + AFL_VERIFY(!PortionConstructor->HaveBlobsData()); } TBlobInfo::TBuilder StartBlob(const std::shared_ptr& bOperator) { @@ -81,16 +85,17 @@ class TWritePortionInfoWithBlobsConstructor: public TBasePortionInfoWithBlobs { return TBlobInfo::TBuilder(Blobs.back(), *this); } friend class TWritePortionInfoWithBlobsResult; + public: std::vector> GetEntityChunks(const ui32 entityId) const; static TWritePortionInfoWithBlobsConstructor BuildByBlobs(std::vector&& chunks, - const THashMap>& inplaceChunks, - const ui64 granule, const ui64 schemaVersion, const TSnapshot& snapshot, const std::shared_ptr& operators); + const THashMap>& inplaceChunks, const ui64 granule, const ui64 schemaVersion, + const TSnapshot& snapshot, const std::shared_ptr& operators); static TWritePortionInfoWithBlobsConstructor BuildByBlobs(std::vector&& chunks, - const THashMap>& inplaceChunks, - TPortionInfoConstructor&& constructor, const std::shared_ptr& operators); + const THashMap>& inplaceChunks, TPortionAccessorConstructor&& constructor, + const std::shared_ptr& operators); std::vector& GetBlobs() { return Blobs; @@ -100,11 +105,10 @@ class TWritePortionInfoWithBlobsConstructor: public TBasePortionInfoWithBlobs { return TStringBuilder() << "blobs_count=" << Blobs.size() << ";"; } - TPortionInfoConstructor& GetPortionConstructor() { + TPortionAccessorConstructor& GetPortionConstructor() { AFL_VERIFY(!!PortionConstructor); return *PortionConstructor; } - }; class TWritePortionInfoWithBlobsResult { @@ -113,10 +117,16 @@ class TWritePortionInfoWithBlobsResult { private: using TBlobChunks = std::vector; YDB_READONLY_DEF(TBlobChunks, Chunks); + std::optional BlobId; const TString ResultBlob; YDB_READONLY_DEF(std::shared_ptr, Operator); public: + const TUnifiedBlobId& GetBlobIdVerified() const { + AFL_VERIFY(BlobId); + return *BlobId; + } + ui64 GetSize() const { return ResultBlob.size(); } @@ -124,22 +134,26 @@ class TWritePortionInfoWithBlobsResult { TBlobInfo(const TString& blobData, TBlobChunks&& chunks, const std::shared_ptr& stOperator) : Chunks(std::move(chunks)) , ResultBlob(blobData) - , Operator(stOperator) - { - + , Operator(stOperator) { } const TString& GetResultBlob() const { return ResultBlob; } - void RegisterBlobId(TWritePortionInfoWithBlobsResult& owner, const TUnifiedBlobId& blobId) const; + void RegisterBlobId(TWritePortionInfoWithBlobsResult& owner, const TUnifiedBlobId& blobId); }; + private: - std::optional PortionConstructor; - std::optional PortionResult; + std::optional PortionConstructor; + std::optional PortionResult; YDB_READONLY_DEF(std::vector, Blobs); + public: + std::vector& MutableBlobs() { + return Blobs; + } + TWritePortionInfoWithBlobsResult(TWritePortionInfoWithBlobsConstructor&& constructor) : PortionConstructor(std::move(constructor.PortionConstructor)) { for (auto&& i : constructor.Blobs) { @@ -160,17 +174,22 @@ class TWritePortionInfoWithBlobsResult { PortionConstructor.reset(); } - const TPortionInfo& GetPortionResult() const { + const TPortionDataAccessor& GetPortionResult() const { AFL_VERIFY(!PortionConstructor); AFL_VERIFY(!!PortionResult); return *PortionResult; } - TPortionInfoConstructor& GetPortionConstructor() { + TPortionAccessorConstructor& GetPortionConstructor() { AFL_VERIFY(!!PortionConstructor); AFL_VERIFY(!PortionResult); return *PortionConstructor; } + + std::shared_ptr DetachPortionConstructor() { + AFL_VERIFY(PortionConstructor); + return std::make_shared(std::move(*PortionConstructor)); + } }; -} // namespace NKikimr::NOlap +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portions/ya.make b/ydb/core/tx/columnshard/engines/portions/ya.make index ced1ad706c50..c586007489ad 100644 --- a/ydb/core/tx/columnshard/engines/portions/ya.make +++ b/ydb/core/tx/columnshard/engines/portions/ya.make @@ -6,11 +6,14 @@ SRCS( base_with_blobs.cpp read_with_blobs.cpp write_with_blobs.cpp - constructor.cpp + constructors.cpp + constructor_portion.cpp + constructor_accessor.cpp constructor_meta.cpp meta.cpp common.cpp index_chunk.cpp + data_accessor.cpp ) PEERDIR( diff --git a/ydb/core/tx/columnshard/engines/predicate/container.cpp b/ydb/core/tx/columnshard/engines/predicate/container.cpp index 8afcc1895e4d..b83e918b0348 100644 --- a/ydb/core/tx/columnshard/engines/predicate/container.cpp +++ b/ydb/core/tx/columnshard/engines/predicate/container.cpp @@ -1,5 +1,5 @@ #include "container.h" -#include +#include #include namespace NKikimr::NOlap { diff --git a/ydb/core/tx/columnshard/engines/predicate/container.h b/ydb/core/tx/columnshard/engines/predicate/container.h index 7d969cf9a759..926e89021cc3 100644 --- a/ydb/core/tx/columnshard/engines/predicate/container.h +++ b/ydb/core/tx/columnshard/engines/predicate/container.h @@ -49,6 +49,23 @@ class TPredicateContainer { } public: + bool IsSchemaEqualTo(const std::shared_ptr& schema) const { + if (!Object) { + return false; + } + return Object->IsEqualSchema(schema); + } + + bool IsEqualPointTo(const TPredicateContainer& item) const { + if (!Object != !item.Object) { + return false; + } + if (!Object) { + return IsForwardInterval() == item.IsForwardInterval(); + } + return Object->IsEqualTo(*item.Object); + } + NArrow::ECompareType GetCompareType() const { return CompareType; } diff --git a/ydb/core/tx/columnshard/engines/predicate/filter.h b/ydb/core/tx/columnshard/engines/predicate/filter.h index bbc70b5ff584..71255dcfbd7f 100644 --- a/ydb/core/tx/columnshard/engines/predicate/filter.h +++ b/ydb/core/tx/columnshard/engines/predicate/filter.h @@ -1,5 +1,9 @@ #pragma once #include "range.h" + +#include +#include + #include namespace NKikimr::NOlap { @@ -13,6 +17,18 @@ class TPKRangesFilter { public: TPKRangesFilter(const bool reverse); + std::optional GetFilteredCountLimit(const std::shared_ptr& pkSchema) { + ui32 result = 0; + for (auto&& i : SortedRanges) { + if (i.IsPointRange(pkSchema)) { + ++result; + } else { + return std::nullopt; + } + } + return result; + } + [[nodiscard]] TConclusionStatus Add( std::shared_ptr f, std::shared_ptr t, const std::shared_ptr& pkSchema); std::shared_ptr SerializeToRecordBatch(const std::shared_ptr& pkSchema) const; @@ -88,4 +104,158 @@ class TPKRangesFilter { } }; -} +class ICursorEntity { +private: + virtual ui64 DoGetEntityId() const = 0; + virtual ui64 DoGetEntityRecordsCount() const = 0; + +public: + ui64 GetEntityId() const { + return DoGetEntityId(); + } + ui64 GetEntityRecordsCount() const { + return DoGetEntityRecordsCount(); + } +}; + +class IScanCursor { +private: + virtual const std::shared_ptr& DoGetPKCursor() const = 0; + virtual bool DoCheckEntityIsBorder(const std::shared_ptr& entity, bool& usage) const = 0; + virtual bool DoCheckSourceIntervalUsage(const ui64 sourceId, const ui32 indexStart, const ui32 recordsCount) const = 0; + virtual TConclusionStatus DoDeserializeFromProto(const NKikimrKqp::TEvKqpScanCursor& proto) = 0; + virtual void DoSerializeToProto(NKikimrKqp::TEvKqpScanCursor& proto) const = 0; + +public: + virtual bool IsInitialized() const = 0; + + virtual ~IScanCursor() = default; + + const std::shared_ptr& GetPKCursor() const { + return DoGetPKCursor(); + } + + bool CheckSourceIntervalUsage(const ui64 sourceId, const ui32 indexStart, const ui32 recordsCount) const { + AFL_VERIFY(IsInitialized()); + return DoCheckSourceIntervalUsage(sourceId, indexStart, recordsCount); + } + + bool CheckEntityIsBorder(const std::shared_ptr& entity, bool& usage) const { + AFL_VERIFY(IsInitialized()); + return DoCheckEntityIsBorder(entity, usage); + } + + TConclusionStatus DeserializeFromProto(const NKikimrKqp::TEvKqpScanCursor& proto) { + return DoDeserializeFromProto(proto); + } + + NKikimrKqp::TEvKqpScanCursor SerializeToProto() const { + NKikimrKqp::TEvKqpScanCursor result; + DoSerializeToProto(result); + return result; + } +}; + +class TSimpleScanCursor: public IScanCursor { +private: + YDB_READONLY_DEF(std::shared_ptr, PrimaryKey); + YDB_READONLY(ui64, SourceId, 0); + YDB_READONLY(ui32, RecordIndex, 0); + + virtual void DoSerializeToProto(NKikimrKqp::TEvKqpScanCursor& proto) const override { + proto.MutableColumnShardSimple()->SetSourceId(SourceId); + proto.MutableColumnShardSimple()->SetStartRecordIndex(RecordIndex); + } + + virtual const std::shared_ptr& DoGetPKCursor() const override { + AFL_VERIFY(!!PrimaryKey); + return PrimaryKey; + } + + virtual bool IsInitialized() const override { + return !!SourceId; + } + + virtual bool DoCheckEntityIsBorder(const std::shared_ptr& entity, bool& usage) const override { + if (SourceId != entity->GetEntityId()) { + return false; + } + AFL_VERIFY(RecordIndex <= entity->GetEntityRecordsCount()); + usage = RecordIndex < entity->GetEntityRecordsCount(); + return true; + } + + virtual TConclusionStatus DoDeserializeFromProto(const NKikimrKqp::TEvKqpScanCursor& proto) override { + if (!proto.HasColumnShardSimple()) { + return TConclusionStatus::Success(); + } + if (!proto.GetColumnShardSimple().HasSourceId()) { + return TConclusionStatus::Fail("incorrect source id for cursor initialization"); + } + SourceId = proto.GetColumnShardSimple().GetSourceId(); + if (!proto.GetColumnShardSimple().HasStartRecordIndex()) { + return TConclusionStatus::Fail("incorrect record index for cursor initialization"); + } + RecordIndex = proto.GetColumnShardSimple().GetStartRecordIndex(); + return TConclusionStatus::Success(); + } + + virtual bool DoCheckSourceIntervalUsage(const ui64 sourceId, const ui32 indexStart, const ui32 recordsCount) const override { + AFL_VERIFY(sourceId == SourceId); + if (indexStart >= RecordIndex) { + return true; + } + AFL_VERIFY(indexStart + recordsCount <= RecordIndex); + return false; + } + +public: + TSimpleScanCursor() = default; + + TSimpleScanCursor(const std::shared_ptr& pk, const ui64 portionId, const ui32 recordIndex) + : PrimaryKey(pk) + , SourceId(portionId) + , RecordIndex(recordIndex) { + } +}; + +class TPlainScanCursor: public IScanCursor { +private: + YDB_READONLY_DEF(std::shared_ptr, PrimaryKey); + + virtual void DoSerializeToProto(NKikimrKqp::TEvKqpScanCursor& proto) const override { + *proto.MutableColumnShardPlain() = {}; + } + + virtual bool IsInitialized() const override { + return !!PrimaryKey; + } + + virtual const std::shared_ptr& DoGetPKCursor() const override { + AFL_VERIFY(!!PrimaryKey); + return PrimaryKey; + } + + virtual TConclusionStatus DoDeserializeFromProto(const NKikimrKqp::TEvKqpScanCursor& /*proto*/) override { + return TConclusionStatus::Success(); + } + + virtual bool DoCheckEntityIsBorder(const std::shared_ptr& /*entity*/, bool& usage) const override { + usage = true; + return true; + } + + virtual bool DoCheckSourceIntervalUsage(const ui64 /*sourceId*/, const ui32 /*indexStart*/, const ui32 /*recordsCount*/) const override { + return true; + } + +public: + TPlainScanCursor() = default; + + TPlainScanCursor(const std::shared_ptr& pk) + : PrimaryKey(pk) { + AFL_VERIFY(PrimaryKey); + } +}; + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/predicate/predicate.cpp b/ydb/core/tx/columnshard/engines/predicate/predicate.cpp index a6831ca2ad50..3959c9499c71 100644 --- a/ydb/core/tx/columnshard/engines/predicate/predicate.cpp +++ b/ydb/core/tx/columnshard/engines/predicate/predicate.cpp @@ -2,9 +2,10 @@ #include #include -#include #include +#include +#include namespace NKikimr::NOlap { @@ -137,6 +138,40 @@ std::shared_ptr TPredicate::CutNulls(const std::shared_ptr(fieldsNotNull), 1, colsNotNull); } +bool TPredicate::IsEqualSchema(const std::shared_ptr& schema) const { + AFL_VERIFY(Batch); + AFL_VERIFY(schema); + if (schema->num_fields() != Batch->schema()->num_fields()) { + return false; + } + for (i32 i = 0; i < schema->num_fields(); ++i) { + if (!schema->field(i)->Equals(Batch->schema()->field(i))) { + return false; + } + } + return true; +} + +bool TPredicate::IsEqualTo(const TPredicate& item) const { + AFL_VERIFY(Batch); + AFL_VERIFY(item.Batch); + AFL_VERIFY(Batch->num_rows() == 1); + AFL_VERIFY(item.Batch->num_rows() == 1); + if (Batch->schema()->num_fields() != item.Batch->schema()->num_fields()) { + return false; + } + for (i32 i = 0; i < Batch->schema()->num_fields(); ++i) { + if (!Batch->schema()->field(i)->Equals(item.Batch->schema()->field(i))) { + return false; + } + if (NArrow::ScalarCompare(NArrow::TStatusValidator::GetValid(Batch->column(i)->GetScalar(0)), + NArrow::TStatusValidator::GetValid(item.Batch->column(i)->GetScalar(0)))) { + return false; + } + } + return true; +} + IOutputStream& operator<<(IOutputStream& out, const TPredicate& pred) { out << NSsa::GetFunctionName(pred.Operation); diff --git a/ydb/core/tx/columnshard/engines/predicate/predicate.h b/ydb/core/tx/columnshard/engines/predicate/predicate.h index 8365971ea29e..8623c4d5108b 100644 --- a/ydb/core/tx/columnshard/engines/predicate/predicate.h +++ b/ydb/core/tx/columnshard/engines/predicate/predicate.h @@ -16,6 +16,8 @@ struct TPredicate { static std::shared_ptr CutNulls(const std::shared_ptr& batch); std::shared_ptr Batch; + bool IsEqualSchema(const std::shared_ptr& schema) const; + bool IsEqualTo(const TPredicate& item) const; NArrow::ECompareType GetCompareType() const { if (Operation == EOperation::GreaterEqual) { diff --git a/ydb/core/tx/columnshard/engines/predicate/range.h b/ydb/core/tx/columnshard/engines/predicate/range.h index 6f9f264b7d70..3c097345d88c 100644 --- a/ydb/core/tx/columnshard/engines/predicate/range.h +++ b/ydb/core/tx/columnshard/engines/predicate/range.h @@ -1,7 +1,8 @@ #pragma once #include "container.h" -#include -#include + +#include +#include namespace NKikimr::NOlap { @@ -15,11 +16,16 @@ class TPKRangeFilter { } public: - bool IsEmpty() const { return PredicateFrom.IsEmpty() && PredicateTo.IsEmpty(); } + bool IsPointRange(const std::shared_ptr& pkSchema) const { + return PredicateFrom.GetCompareType() == NArrow::ECompareType::GREATER_OR_EQUAL && + PredicateTo.GetCompareType() == NArrow::ECompareType::LESS_OR_EQUAL && PredicateFrom.IsEqualPointTo(PredicateTo) && + PredicateFrom.IsSchemaEqualTo(pkSchema); + } + const TPredicateContainer& GetPredicateFrom() const { return PredicateFrom; } @@ -48,4 +54,4 @@ class TPKRangeFilter { std::set GetColumnNames() const; }; -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/protos/portion_info.proto b/ydb/core/tx/columnshard/engines/protos/portion_info.proto index f7f38bb96ed7..0c589ce3eb41 100644 --- a/ydb/core/tx/columnshard/engines/protos/portion_info.proto +++ b/ydb/core/tx/columnshard/engines/protos/portion_info.proto @@ -1,4 +1,5 @@ import "ydb/library/formats/arrow/protos/ssa.proto"; +import "ydb/core/tx/columnshard/common/protos/blob_range.proto"; package NKikimrTxColumnShard; @@ -19,6 +20,13 @@ message TIndexPortionMeta { optional TSnapshot RecordSnapshotMin = 7; optional TSnapshot RecordSnapshotMax = 8; optional uint32 DeletionsCount = 10; + optional uint64 CompactionLevel = 11 [default = 0]; + optional uint32 RecordsCount = 12; + optional uint64 ColumnRawBytes = 13; + optional uint32 ColumnBlobBytes = 14; + optional uint32 IndexBlobBytes = 15; + optional uint64 IndexRawBytes = 16; + repeated string BlobIds = 17; } message TIndexColumnMeta { @@ -26,5 +34,16 @@ message TIndexColumnMeta { optional uint32 RawBytes = 2; optional NKikimrSSA.TProgram.TConstant MinValue = 3; optional NKikimrSSA.TProgram.TConstant MaxValue = 4; - optional TIndexPortionMeta PortionMeta = 5; // First PK column could contain portion info + optional TIndexPortionMeta PortionMeta = 5[deprecated = true]; // First PK column could contain portion info +} + +message TColumnChunkInfo { + optional uint32 SSColumnId = 1; + optional uint32 ChunkIdx = 2; + optional TIndexColumnMeta ChunkMetadata = 3; + optional NKikimrColumnShardProto.TBlobRangeLink16 BlobRangeLink = 4; +} + +message TIndexPortionAccessor { + repeated TColumnChunkInfo Chunks = 1; } diff --git a/ydb/core/tx/columnshard/engines/protos/ya.make b/ydb/core/tx/columnshard/engines/protos/ya.make index 5719eb76af10..c83be39aac46 100644 --- a/ydb/core/tx/columnshard/engines/protos/ya.make +++ b/ydb/core/tx/columnshard/engines/protos/ya.make @@ -6,6 +6,7 @@ SRCS( PEERDIR( ydb/library/formats/arrow/protos + ydb/core/tx/columnshard/common/protos ) diff --git a/ydb/core/tx/columnshard/engines/reader/abstract/constructor.cpp b/ydb/core/tx/columnshard/engines/reader/abstract/constructor.cpp index 96627da5f8fc..980f57097885 100644 --- a/ydb/core/tx/columnshard/engines/reader/abstract/constructor.cpp +++ b/ydb/core/tx/columnshard/engines/reader/abstract/constructor.cpp @@ -1,11 +1,13 @@ #include "constructor.h" + +#include #include #include namespace NKikimr::NOlap::NReader { -NKikimr::TConclusionStatus IScannerConstructor::ParseProgram(const TVersionedIndex* vIndex, - const NKikimrSchemeOp::EOlapProgramType programType, const TString& serializedProgram, TReadDescription& read, const IColumnResolver& columnResolver) const { +NKikimr::TConclusionStatus IScannerConstructor::ParseProgram(const TVersionedIndex* vIndex, const NKikimrSchemeOp::EOlapProgramType programType, + const TString& serializedProgram, TReadDescription& read, const IColumnResolver& columnResolver) const { AFL_VERIFY(!read.ColumnIds.size() || !read.ColumnNames.size()); std::vector names; std::set namesChecker; @@ -39,7 +41,7 @@ NKikimr::TConclusionStatus IScannerConstructor::ParseProgram(const TVersionedInd } //its possible dont use columns from filter where pk field compare with null and remove from PKFilter and program, but stay in kqp columns request if (vIndex) { - for (auto&& i : vIndex->GetSchema(read.GetSnapshot())->GetIndexInfo().GetReplaceKey()->field_names()) { + for (auto&& i : vIndex->GetSchemaVerified(read.GetSnapshot())->GetIndexInfo().GetReplaceKey()->field_names()) { const TString cId(i.data(), i.size()); namesChecker.erase(cId); programColumns.erase(cId); @@ -47,7 +49,8 @@ NKikimr::TConclusionStatus IScannerConstructor::ParseProgram(const TVersionedInd } const auto getDiffColumnsMessage = [&]() { - return TStringBuilder() << "ssa program has different columns with kqp request: kqp_columns=" << JoinSeq(",", namesChecker) << " vs program_columns=" << JoinSeq(",", programColumns); + return TStringBuilder() << "ssa program has different columns with kqp request: kqp_columns=" << JoinSeq(",", namesChecker) + << " vs program_columns=" << JoinSeq(",", programColumns); }; if (namesChecker.size() != programColumns.size()) { @@ -66,16 +69,31 @@ NKikimr::TConclusionStatus IScannerConstructor::ParseProgram(const TVersionedInd } } -NKikimr::TConclusion> IScannerConstructor::BuildReadMetadata(const NColumnShard::TColumnShard* self, const TReadDescription& read) const { +NKikimr::TConclusion> IScannerConstructor::BuildReadMetadata( + const NColumnShard::TColumnShard* self, const TReadDescription& read) const { TConclusion> result = DoBuildReadMetadata(self, read); if (result.IsFail()) { return result; } else if (!*result) { return result.DetachResult(); } else { - (*result)->Limit = ItemsLimit; + (*result)->SetRequestedLimit(ItemsLimit); + (*result)->SetScanIdentifier(read.GetScanIdentifier()); return result.DetachResult(); } } -} \ No newline at end of file +NKikimr::TConclusion> IScannerConstructor::BuildCursorFromProto( + const NKikimrKqp::TEvKqpScanCursor& proto) const { + auto result = DoBuildCursor(); + if (!result) { + return result; + } + auto status = result->DeserializeFromProto(proto); + if (status.IsFail()) { + return status; + } + return result; +} + +} // namespace NKikimr::NOlap::NReader diff --git a/ydb/core/tx/columnshard/engines/reader/abstract/constructor.h b/ydb/core/tx/columnshard/engines/reader/abstract/constructor.h index 1eb95f2b224f..21fbe1f0acea 100644 --- a/ydb/core/tx/columnshard/engines/reader/abstract/constructor.h +++ b/ydb/core/tx/columnshard/engines/reader/abstract/constructor.h @@ -8,6 +8,22 @@ namespace NKikimr::NOlap::NReader { +class TScannerConstructorContext { +private: + YDB_READONLY(TSnapshot, Snapshot, TSnapshot::Zero()); + YDB_READONLY(ui32, ItemsLimit, 0); + YDB_READONLY(bool, Reverse, false); + +public: + TScannerConstructorContext(const TSnapshot& snapshot, const ui32 itemsLimit, const bool reverse) + : Snapshot(snapshot) + , ItemsLimit(itemsLimit) + , Reverse(reverse) + { + + } +}; + class IScannerConstructor { protected: const TSnapshot Snapshot; @@ -17,17 +33,21 @@ class IScannerConstructor { const TString& serializedProgram, TReadDescription& read, const IColumnResolver& columnResolver) const; private: virtual TConclusion> DoBuildReadMetadata(const NColumnShard::TColumnShard* self, const TReadDescription& read) const = 0; + virtual std::shared_ptr DoBuildCursor() const = 0; + public: + using TFactory = NObjectFactory::TParametrizedObjectFactory; virtual ~IScannerConstructor() = default; - IScannerConstructor(const TSnapshot& snapshot, const ui64 itemsLimit, const bool reverse) - : Snapshot(snapshot) - , ItemsLimit(itemsLimit) - , IsReverse(reverse) + IScannerConstructor(const TScannerConstructorContext& context) + : Snapshot(context.GetSnapshot()) + , ItemsLimit(context.GetItemsLimit()) + , IsReverse(context.GetReverse()) { } + TConclusion> BuildCursorFromProto(const NKikimrKqp::TEvKqpScanCursor& proto) const; virtual TConclusionStatus ParseProgram(const TVersionedIndex* vIndex, const NKikimrTxDataShard::TEvKqpScan& proto, TReadDescription& read) const = 0; virtual std::vector GetPrimaryKeyScheme(const NColumnShard::TColumnShard* self) const = 0; TConclusion> BuildReadMetadata(const NColumnShard::TColumnShard* self, const TReadDescription& read) const; diff --git a/ydb/core/tx/columnshard/engines/reader/abstract/read_context.h b/ydb/core/tx/columnshard/engines/reader/abstract/read_context.h index 3b1d545094ac..f9cb5dac128a 100644 --- a/ydb/core/tx/columnshard/engines/reader/abstract/read_context.h +++ b/ydb/core/tx/columnshard/engines/reader/abstract/read_context.h @@ -4,13 +4,15 @@ #include #include #include -#include +#include #include #include namespace NKikimr::NOlap::NReader { +class TPartialReadResult; + class TComputeShardingPolicy { private: YDB_READONLY(ui32, ShardsCount, 0); @@ -42,6 +44,7 @@ class TComputeShardingPolicy { class TReadContext { private: YDB_READONLY_DEF(std::shared_ptr, StoragesManager); + YDB_READONLY_DEF(std::shared_ptr, DataAccessorsManager); const NColumnShard::TConcreteScanCounters Counters; TReadMetadataBase::TConstPtr ReadMetadata; NResourceBroker::NSubscribe::TTaskContext ResourcesTaskContext; @@ -50,6 +53,8 @@ class TReadContext { const TActorId ResourceSubscribeActorId; const TActorId ReadCoordinatorActorId; const TComputeShardingPolicy ComputeShardingPolicy; + std::shared_ptr AbortionFlag = std::make_shared(0); + std::shared_ptr ConstAbortionFlag = AbortionFlag; public: template @@ -59,6 +64,33 @@ class TReadContext { return result; } + const std::shared_ptr& GetScanCursor() const { + return ReadMetadata->GetScanCursor(); + } + + const std::shared_ptr& GetAbortionFlag() const { + return ConstAbortionFlag; + } + + void AbortWithError(const TString& errorMessage) { + if (AbortionFlag->Inc() == 1) { + NActors::TActivationContext::Send( + ScanActorId, std::make_unique(TConclusionStatus::Fail(errorMessage))); + } + } + + void Stop() { + AbortionFlag->Inc(); + } + + bool IsActive() const { + return AbortionFlag->Val() == 0; + } + + bool IsAborted() const { + return AbortionFlag->Val(); + } + bool IsReverse() const { return ReadMetadata->IsDescSorted(); } @@ -99,10 +131,13 @@ class TReadContext { return ResourcesTaskContext; } - TReadContext(const std::shared_ptr& storagesManager, const NColumnShard::TConcreteScanCounters& counters, - const TReadMetadataBase::TConstPtr& readMetadata, const TActorId& scanActorId, const TActorId& resourceSubscribeActorId, - const TActorId& readCoordinatorActorId, const TComputeShardingPolicy& computeShardingPolicy, const ui64 scanId) + TReadContext(const std::shared_ptr& storagesManager, + const std::shared_ptr& dataAccessorsManager, + const NColumnShard::TConcreteScanCounters& counters, const TReadMetadataBase::TConstPtr& readMetadata, const TActorId& scanActorId, + const TActorId& resourceSubscribeActorId, const TActorId& readCoordinatorActorId, const TComputeShardingPolicy& computeShardingPolicy, + const ui64 scanId) : StoragesManager(storagesManager) + , DataAccessorsManager(dataAccessorsManager) , Counters(counters) , ReadMetadata(readMetadata) , ResourcesTaskContext("CS::SCAN_READ", counters.ResourcesSubscriberCounters) @@ -110,7 +145,8 @@ class TReadContext { , ScanActorId(scanActorId) , ResourceSubscribeActorId(resourceSubscribeActorId) , ReadCoordinatorActorId(readCoordinatorActorId) - , ComputeShardingPolicy(computeShardingPolicy) { + , ComputeShardingPolicy(computeShardingPolicy) + { Y_ABORT_UNLESS(ReadMetadata); } }; diff --git a/ydb/core/tx/columnshard/engines/reader/abstract/read_metadata.cpp b/ydb/core/tx/columnshard/engines/reader/abstract/read_metadata.cpp index 88416a4d214f..803aa7030543 100644 --- a/ydb/core/tx/columnshard/engines/reader/abstract/read_metadata.cpp +++ b/ydb/core/tx/columnshard/engines/reader/abstract/read_metadata.cpp @@ -9,12 +9,12 @@ TDataStorageAccessor::TDataStorageAccessor(const std::unique_ptr& , Index(index) { } -std::shared_ptr TDataStorageAccessor::Select(const TReadDescription& readDescription) const { +std::shared_ptr TDataStorageAccessor::Select(const TReadDescription& readDescription, const bool withUncommitted) const { if (readDescription.ReadNothing) { return std::make_shared(); } AFL_VERIFY(readDescription.PKRangesFilter); - return Index->Select(readDescription.PathId, readDescription.GetSnapshot(), *readDescription.PKRangesFilter); + return Index->Select(readDescription.PathId, readDescription.GetSnapshot(), *readDescription.PKRangesFilter, withUncommitted); } ISnapshotSchema::TPtr TReadMetadataBase::GetLoadSchemaVerified(const TPortionInfo& portion) const { diff --git a/ydb/core/tx/columnshard/engines/reader/abstract/read_metadata.h b/ydb/core/tx/columnshard/engines/reader/abstract/read_metadata.h index d87fcf02868e..b5ac92866b60 100644 --- a/ydb/core/tx/columnshard/engines/reader/abstract/read_metadata.h +++ b/ydb/core/tx/columnshard/engines/reader/abstract/read_metadata.h @@ -24,13 +24,13 @@ class TDataStorageAccessor { public: TDataStorageAccessor(const std::unique_ptr& insertTable, const std::unique_ptr& index); - std::shared_ptr Select(const TReadDescription& readDescription) const; + std::shared_ptr Select(const TReadDescription& readDescription, const bool withUncommitted) const; std::vector GetCommitedBlobs(const TReadDescription& readDescription, const std::shared_ptr& pkSchema, const std::optional lockId, const TSnapshot& reqSnapshot) const; }; // Holds all metadata that is needed to perform read/scan -struct TReadMetadataBase { +class TReadMetadataBase { public: enum class ESorting { NONE = 0 /* "not_sorted" */, @@ -39,12 +39,16 @@ struct TReadMetadataBase { }; private: + YDB_ACCESSOR_DEF(TString, ScanIdentifier); + std::optional FilteredCountLimit; + std::optional RequestedLimit; const ESorting Sorting = ESorting::ASC; // Sorting inside returned batches std::shared_ptr PKRangesFilter; TProgramContainer Program; std::shared_ptr IndexVersionsPointer; TSnapshot RequestSnapshot; std::optional RequestShardingInfo; + std::shared_ptr ScanCursor; virtual void DoOnReadFinished(NColumnShard::TColumnShard& /*owner*/) const { } virtual void DoOnBeforeStartReading(NColumnShard::TColumnShard& /*owner*/) const { @@ -60,6 +64,23 @@ struct TReadMetadataBase { public: using TConstPtr = std::shared_ptr; + void SetRequestedLimit(const ui64 value) { + AFL_VERIFY(!RequestedLimit); + if (value == 0 || value >= Max()) { + return; + } + RequestedLimit = value; + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("requested_limit_detected", RequestedLimit); + } + + i64 GetLimitRobust() const { + return std::min(FilteredCountLimit.value_or(Max()), RequestedLimit.value_or(Max())); + } + + bool HasLimit() const { + return !!FilteredCountLimit || !!RequestedLimit; + } + void OnReplyConstruction(const ui64 tabletId, NKqp::NInternalImplementation::TEvScanData& scanData) const { DoOnReplyConstruction(tabletId, scanData); } @@ -68,6 +89,10 @@ struct TReadMetadataBase { return TxId; } + const std::shared_ptr& GetScanCursor() const { + return ScanCursor; + } + std::optional GetLockId() const { return LockId; } @@ -94,6 +119,14 @@ struct TReadMetadataBase { Y_ABORT_UNLESS(IsSorted() && value->IsReverse() == IsDescSorted()); Y_ABORT_UNLESS(!PKRangesFilter); PKRangesFilter = value; + if (ResultIndexSchema) { + FilteredCountLimit = PKRangesFilter->GetFilteredCountLimit(ResultIndexSchema->GetIndexInfo().GetReplaceKey()); + if (FilteredCountLimit) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("filter_limit_detected", FilteredCountLimit); + } else { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("filter_limit_not_detected", PKRangesFilter->DebugString()); + } + } } const TPKRangesFilter& GetPKRangesFilter() const { @@ -107,22 +140,20 @@ struct TReadMetadataBase { } ISnapshotSchema::TPtr GetResultSchema() const { + AFL_VERIFY(ResultIndexSchema); return ResultIndexSchema; } - bool HasGuaranteeExclusivePK() const { - return GetIndexInfo().GetExternalGuaranteeExclusivePK(); - } - ISnapshotSchema::TPtr GetLoadSchemaVerified(const TPortionInfo& porition) const; - const std::shared_ptr& GetBlobSchema(const ui64 version) const { - return GetIndexVersions().GetSchema(version)->GetIndexInfo().ArrowSchema(); + NArrow::TSchemaLiteView GetBlobSchema(const ui64 version) const { + return GetIndexVersions().GetSchemaVerified(version)->GetIndexInfo().ArrowSchema(); } const TIndexInfo& GetIndexInfo(const std::optional& version = {}) const { + AFL_VERIFY(ResultIndexSchema); if (version && version < RequestSnapshot) { - return GetIndexVersions().GetSchema(*version)->GetIndexInfo(); + return GetIndexVersions().GetSchemaVerified(*version)->GetIndexInfo(); } return ResultIndexSchema->GetIndexInfo(); } @@ -133,23 +164,24 @@ struct TReadMetadataBase { } TReadMetadataBase(const std::shared_ptr index, const ESorting sorting, const TProgramContainer& ssaProgram, - const std::shared_ptr& schema, const TSnapshot& requestSnapshot) + const std::shared_ptr& schema, const TSnapshot& requestSnapshot, const std::shared_ptr& scanCursor) : Sorting(sorting) , Program(ssaProgram) , IndexVersionsPointer(index) , RequestSnapshot(requestSnapshot) - , ResultIndexSchema(schema) { + , ScanCursor(scanCursor) + , ResultIndexSchema(schema) + { } virtual ~TReadMetadataBase() = default; - ui64 Limit = 0; - - virtual void Dump(IOutputStream& out) const { - out << " predicate{" << (PKRangesFilter ? PKRangesFilter->DebugString() : "no_initialized") << "}" + virtual TString DebugString() const { + return TStringBuilder() << " predicate{" << (PKRangesFilter ? PKRangesFilter->DebugString() : "no_initialized") << "}" << " " << Sorting << " sorted"; } std::set GetProcessingColumnIds() const { + AFL_VERIFY(ResultIndexSchema); std::set result; for (auto&& i : GetProgram().GetProcessingColumns()) { result.emplace(ResultIndexSchema->GetIndexInfo().GetColumnIdVerified(i)); @@ -169,12 +201,6 @@ struct TReadMetadataBase { virtual std::unique_ptr StartScan(const std::shared_ptr& readContext) const = 0; virtual std::vector GetKeyYqlSchema() const = 0; - // TODO: can this only be done for base class? - friend IOutputStream& operator<<(IOutputStream& out, const TReadMetadataBase& meta) { - meta.Dump(out); - return out; - } - const TProgramContainer& GetProgram() const { return Program; } @@ -184,6 +210,7 @@ struct TReadMetadataBase { } std::shared_ptr GetReplaceKey() const { + AFL_VERIFY(ResultIndexSchema); return ResultIndexSchema->GetIndexInfo().GetReplaceKey(); } diff --git a/ydb/core/tx/columnshard/engines/reader/actor/actor.cpp b/ydb/core/tx/columnshard/engines/reader/actor/actor.cpp index 907eee97ca25..cda6975f67f8 100644 --- a/ydb/core/tx/columnshard/engines/reader/actor/actor.cpp +++ b/ydb/core/tx/columnshard/engines/reader/actor/actor.cpp @@ -1,8 +1,10 @@ #include "actor.h" + +#include #include #include + #include -#include namespace NKikimr::NOlap::NReader { constexpr i64 DEFAULT_READ_AHEAD_BYTES = (i64)2 * 1024 * 1024 * 1024; @@ -14,6 +16,7 @@ class TInFlightGuard: NNonCopyable::TNonCopyable { private: static inline TAtomicCounter InFlightGlobal = 0; i64 InFlightGuarded = 0; + public: ~TInFlightGuard() { Return(InFlightGuarded); @@ -35,7 +38,7 @@ class TInFlightGuard: NNonCopyable::TNonCopyable { } }; -} +} // namespace void TColumnShardScan::PassAway() { Send(ResourceSubscribeActorId, new TEvents::TEvPoisonPill); @@ -43,11 +46,14 @@ void TColumnShardScan::PassAway() { IActor::PassAway(); } -TColumnShardScan::TColumnShardScan(const TActorId& columnShardActorId, const TActorId& scanComputeActorId, const std::shared_ptr& storagesManager, - const TComputeShardingPolicy& computeShardingPolicy, ui32 scanId, ui64 txId, ui32 scanGen, ui64 requestCookie, - ui64 tabletId, TDuration timeout, const TReadMetadataBase::TConstPtr& readMetadataRange, - NKikimrDataEvents::EDataFormat dataFormat, const NColumnShard::TScanCounters& scanCountersPool) +TColumnShardScan::TColumnShardScan(const TActorId& columnShardActorId, const TActorId& scanComputeActorId, + const std::shared_ptr& storagesManager, + const std::shared_ptr& dataAccessorsManager, + const TComputeShardingPolicy& computeShardingPolicy, ui32 scanId, ui64 txId, ui32 scanGen, ui64 requestCookie, ui64 tabletId, + TDuration timeout, const TReadMetadataBase::TConstPtr& readMetadataRange, NKikimrDataEvents::EDataFormat dataFormat, + const NColumnShard::TScanCounters& scanCountersPool) : StoragesManager(storagesManager) + , DataAccessorsManager(dataAccessorsManager) , ColumnShardActorId(columnShardActorId) , ScanComputeActorId(scanComputeActorId) , BlobCacheActorId(NBlobCache::MakeBlobCacheServiceId()) @@ -60,16 +66,15 @@ TColumnShardScan::TColumnShardScan(const TActorId& columnShardActorId, const TAc , ReadMetadataRange(readMetadataRange) , Timeout(timeout ? timeout + SCAN_HARD_TIMEOUT_GAP : SCAN_HARD_TIMEOUT) , ScanCountersPool(scanCountersPool) - , Stats(NTracing::TTraceClient::GetLocalClient("SHARD", ::ToString(TabletId)/*, "SCAN_TXID:" + ::ToString(TxId)*/)) + , Stats(NTracing::TTraceClient::GetLocalClient("SHARD", ::ToString(TabletId) /*, "SCAN_TXID:" + ::ToString(TxId)*/)) , ComputeShardingPolicy(computeShardingPolicy) { AFL_VERIFY(ReadMetadataRange); KeyYqlSchema = ReadMetadataRange->GetKeyYqlSchema(); } void TColumnShardScan::Bootstrap(const TActorContext& ctx) { - TLogContextGuard gLogging(NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD_SCAN) - ("SelfId", SelfId())("TabletId", TabletId)("ScanId", ScanId)("TxId", TxId)("ScanGen", ScanGen) - ); +// TLogContextGuard gLogging(NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD_SCAN) ("SelfId", SelfId())( +// "TabletId", TabletId)("ScanId", ScanId)("TxId", TxId)("ScanGen", ScanGen)); auto g = Stats->MakeGuard("bootstrap"); ScanActorId = ctx.SelfID; @@ -77,7 +82,7 @@ void TColumnShardScan::Bootstrap(const TActorContext& ctx) { ResourceSubscribeActorId = ctx.Register(new NResourceBroker::NSubscribe::TActor(TabletId, SelfId())); ReadCoordinatorActorId = ctx.Register(new NBlobOperations::NRead::TReadCoordinatorActor(TabletId, SelfId())); - std::shared_ptr context = std::make_shared(StoragesManager, ScanCountersPool, + std::shared_ptr context = std::make_shared(StoragesManager, DataAccessorsManager, ScanCountersPool, ReadMetadataRange, SelfId(), ResourceSubscribeActorId, ReadCoordinatorActorId, ComputeShardingPolicy, ScanId); ScanIterator = ReadMetadataRange->StartScan(context); auto startResult = ScanIterator->Start(); @@ -90,7 +95,8 @@ void TColumnShardScan::Bootstrap(const TActorContext& ctx) { ScheduleWakeup(GetDeadline()); // propagate self actor id // TODO: FlagSubscribeOnSession ? - Send(ScanComputeActorId, new NKqp::TEvKqpCompute::TEvScanInitActor(ScanId, ctx.SelfID, ScanGen, TabletId), IEventHandle::FlagTrackDelivery); + Send(ScanComputeActorId, new NKqp::TEvKqpCompute::TEvScanInitActor(ScanId, ctx.SelfID, ScanGen, TabletId), + IEventHandle::FlagTrackDelivery); Become(&TColumnShardScan::StateScan); ContinueProcessing(); @@ -98,7 +104,6 @@ void TColumnShardScan::Bootstrap(const TActorContext& ctx) { } void TColumnShardScan::HandleScan(NColumnShard::TEvPrivate::TEvTaskProcessedResult::TPtr& ev) { - --InFlightReads; auto g = Stats->MakeGuard("task_result"); auto result = ev->Get()->ExtractResult(); if (result.IsFail()) { @@ -136,6 +141,10 @@ void TColumnShardScan::HandleScan(NKqp::TEvKqpCompute::TEvScanDataAck::TPtr& ev) ContinueProcessing(); } +void TColumnShardScan::HandleScan(NActors::TEvents::TEvPoison::TPtr& /*ev*/) noexcept { + PassAway(); +} + void TColumnShardScan::HandleScan(NKqp::TEvKqp::TEvAbortExecution::TPtr& ev) noexcept { auto& msg = ev->Get()->Record; const TString reason = ev->Get()->GetIssues().ToOneLineString(); @@ -143,9 +152,8 @@ void TColumnShardScan::HandleScan(NKqp::TEvKqp::TEvAbortExecution::TPtr& ev) noe auto prio = msg.GetStatusCode() == NYql::NDqProto::StatusIds::SUCCESS ? NActors::NLog::PRI_DEBUG : NActors::NLog::PRI_WARN; LOG_LOG_S(*TlsActivationContext, prio, NKikimrServices::TX_COLUMNSHARD_SCAN, "Scan " << ScanActorId << " got AbortExecution" - << " txId: " << TxId << " scanId: " << ScanId << " gen: " << ScanGen << " tablet: " << TabletId - << " code: " << NYql::NDqProto::StatusIds_StatusCode_Name(msg.GetStatusCode()) - << " reason: " << reason); + << " txId: " << TxId << " scanId: " << ScanId << " gen: " << ScanGen << " tablet: " << TabletId + << " code: " << NYql::NDqProto::StatusIds_StatusCode_Name(msg.GetStatusCode()) << " reason: " << reason); AbortReason = std::move(reason); Finish(NColumnShard::TScanCounters::EStatusFinish::ExternalAbort); @@ -163,10 +171,8 @@ void TColumnShardScan::HandleScan(TEvents::TEvUndelivered::TPtr& ev) { } LOG_WARN_S(*TlsActivationContext, NKikimrServices::TX_COLUMNSHARD_SCAN, - "Scan " << ScanActorId << " undelivered event: " << eventType - << " txId: " << TxId << " scanId: " << ScanId << " gen: " << ScanGen << " tablet: " << TabletId - << " reason: " << ev->Get()->Reason - << " description: " << AbortReason); + "Scan " << ScanActorId << " undelivered event: " << eventType << " txId: " << TxId << " scanId: " << ScanId << " gen: " << ScanGen + << " tablet: " << TabletId << " reason: " << ev->Get()->Reason << " description: " << AbortReason); Finish(NColumnShard::TScanCounters::EStatusFinish::UndeliveredEvent); } @@ -174,7 +180,7 @@ void TColumnShardScan::HandleScan(TEvents::TEvUndelivered::TPtr& ev) { void TColumnShardScan::HandleScan(TEvents::TEvWakeup::TPtr& /*ev*/) { LOG_ERROR_S(*TlsActivationContext, NKikimrServices::TX_COLUMNSHARD_SCAN, "Scan " << ScanActorId << " guard execution timeout" - << " txId: " << TxId << " scanId: " << ScanId << " gen: " << ScanGen << " tablet: " << TabletId); + << " txId: " << TxId << " scanId: " << ScanId << " gen: " << ScanGen << " tablet: " << TabletId); if (TMonotonic::Now() >= GetDeadline()) { Finish(NColumnShard::TScanCounters::EStatusFinish::Deadline); @@ -235,28 +241,34 @@ bool TColumnShardScan::ProduceResults() noexcept { { MakeResult(0); if (shardedBatch.IsSharded()) { - AFL_INFO(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "compute_sharding_success")("count", shardedBatch.GetSplittedByShards().size())("info", ComputeShardingPolicy.DebugString()); + AFL_INFO(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "compute_sharding_success")( + "count", shardedBatch.GetSplittedByShards().size())("info", ComputeShardingPolicy.DebugString()); Result->SplittedBatches = shardedBatch.GetSplittedByShards(); } else { if (ComputeShardingPolicy.IsEnabled()) { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "compute_sharding_problems")("info", ComputeShardingPolicy.DebugString()); + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "compute_sharding_problems")( + "info", ComputeShardingPolicy.DebugString()); } } TMemoryProfileGuard mGuard("SCAN_PROFILE::RESULT::TO_KQP", IS_DEBUG_LOG_ENABLED(NKikimrServices::TX_COLUMNSHARD_SCAN_MEMORY)); Result->ArrowBatch = shardedBatch.GetRecordBatch(); Rows += batch->num_rows(); Bytes += NArrow::GetTableDataSize(Result->ArrowBatch); - - ACFL_DEBUG("stage", "data_format")("batch_size", NArrow::GetTableDataSize(Result->ArrowBatch))("num_rows", numRows)("batch_columns", JoinSeq(",", batch->schema()->field_names())); + + ACFL_DEBUG("stage", "data_format")("batch_size", NArrow::GetTableDataSize(Result->ArrowBatch))("num_rows", numRows)( + "batch_columns", JoinSeq(",", batch->schema()->field_names())); } if (CurrentLastReadKey) { - NArrow::NMerger::TSortableBatchPosition pNew(result.GetLastReadKey(), 0, result.GetLastReadKey()->schema()->field_names(), {}, ReadMetadataRange->IsDescSorted()); - NArrow::NMerger::TSortableBatchPosition pOld(CurrentLastReadKey, 0, CurrentLastReadKey->schema()->field_names(), {}, ReadMetadataRange->IsDescSorted()); - AFL_VERIFY(pOld < pNew)("old", pOld.DebugJson().GetStringRobust())("new", pNew.DebugJson().GetStringRobust()); + NArrow::NMerger::TSortableBatchPosition pNew(result.GetScanCursor()->GetPKCursor(), 0, + result.GetScanCursor()->GetPKCursor()->schema()->field_names(), {}, ReadMetadataRange->IsDescSorted()); + NArrow::NMerger::TSortableBatchPosition pOld(CurrentLastReadKey->GetPKCursor(), 0, + CurrentLastReadKey->GetPKCursor()->schema()->field_names(), {}, ReadMetadataRange->IsDescSorted()); + AFL_VERIFY(!(pNew < pOld))("old", pOld.DebugJson().GetStringRobust())("new", pNew.DebugJson().GetStringRobust()); } - CurrentLastReadKey = result.GetLastReadKey(); + CurrentLastReadKey = result.GetScanCursor(); - Result->LastKey = ConvertLastKey(result.GetLastReadKey()); + Result->LastKey = ConvertLastKey(result.GetScanCursor()->GetPKCursor()); + Result->LastCursorProto = result.GetScanCursor()->SerializeToProto(); SendResult(false, false); ScanIterator->OnSentDataFromInterval(result.GetNotFinishedIntervalIdx()); ACFL_DEBUG("stage", "finished")("iterator", ScanIterator->DebugString()); @@ -296,8 +308,9 @@ void TColumnShardScan::ContinueProcessing() { } } } - AFL_VERIFY(!ScanIterator || !ChunksLimiter.HasMore() || InFlightReads || ScanCountersPool.InWaiting())("scan_actor_id", ScanActorId)("tx_id", TxId)("scan_id", ScanId)("gen", ScanGen)("tablet", TabletId) - ("debug", ScanIterator->DebugString()); + AFL_VERIFY(!ScanIterator || !ChunksLimiter.HasMore() || ScanCountersPool.InWaiting())("scan_actor_id", ScanActorId)("tx_id", TxId)( + "scan_id", ScanId)("gen", ScanGen)("tablet", TabletId)("debug", + ScanIterator->DebugString())("counters", ScanCountersPool.DebugString()); } void TColumnShardScan::MakeResult(size_t reserveRows /*= 0*/) { @@ -358,20 +371,19 @@ bool TColumnShardScan::SendResult(bool pageFault, bool lastBatch) { PageFaults = 0; LOG_DEBUG_S(*TlsActivationContext, NKikimrServices::TX_COLUMNSHARD_SCAN, - "Scan " << ScanActorId << " send ScanData to " << ScanComputeActorId - << " txId: " << TxId << " scanId: " << ScanId << " gen: " << ScanGen << " tablet: " << TabletId - << " bytes: " << Bytes << " rows: " << Rows << " page faults: " << Result->PageFaults - << " finished: " << Result->Finished << " pageFault: " << Result->PageFault - << " arrow schema:\n" << (Result->ArrowBatch ? Result->ArrowBatch->schema()->ToString() : "")); + "Scan " << ScanActorId << " send ScanData to " << ScanComputeActorId << " txId: " << TxId << " scanId: " << ScanId << " gen: " << ScanGen + << " tablet: " << TabletId << " bytes: " << Bytes << " rows: " << Rows << " page faults: " << Result->PageFaults + << " finished: " << Result->Finished << " pageFault: " << Result->PageFault << " arrow schema:\n" + << (Result->ArrowBatch ? Result->ArrowBatch->schema()->ToString() : "")); Finished = Result->Finished; if (Finished) { - ALS_INFO(NKikimrServices::TX_COLUMNSHARD_SCAN) << - "Scanner finished " << ScanActorId << " and sent to " << ScanComputeActorId - << " packs: " << PacksSum << " txId: " << TxId << " scanId: " << ScanId << " gen: " << ScanGen << " tablet: " << TabletId - << " bytes: " << Bytes << "/" << BytesSum << " rows: " << Rows << "/" << RowsSum << " page faults: " << Result->PageFaults - << " finished: " << Result->Finished << " pageFault: " << Result->PageFault - << " stats:" << Stats->ToJson() << ";iterator:" << (ScanIterator ? ScanIterator->DebugString(false) : "NO"); + ALS_INFO(NKikimrServices::TX_COLUMNSHARD_SCAN) + << "Scanner finished " << ScanActorId << " and sent to " << ScanComputeActorId << " packs: " << PacksSum << " txId: " << TxId + << " scanId: " << ScanId << " gen: " << ScanGen << " tablet: " << TabletId << " bytes: " << Bytes << "/" << BytesSum + << " rows: " << Rows << "/" << RowsSum << " page faults: " << Result->PageFaults << " finished: " << Result->Finished + << " pageFault: " << Result->PageFault << " stats:" << Stats->ToJson() + << ";iterator:" << (ScanIterator ? ScanIterator->DebugString(false) : "NO"); Result->StatsOnFinished = std::make_shared(ScanIterator->GetStats()); } else { Y_ABORT_UNLESS(ChunksLimiter.Take(Bytes)); @@ -383,7 +395,7 @@ bool TColumnShardScan::SendResult(bool pageFault, bool lastBatch) { AckReceivedInstant.reset(); LastResultInstant = TMonotonic::Now(); - Send(ScanComputeActorId, Result.Release(), IEventHandle::FlagTrackDelivery); // TODO: FlagSubscribeOnSession ? + Send(ScanComputeActorId, Result.Release(), IEventHandle::FlagTrackDelivery); // TODO: FlagSubscribeOnSession ? ReportStats(); @@ -403,8 +415,7 @@ void TColumnShardScan::SendScanError(const TString& reason) { } void TColumnShardScan::Finish(const NColumnShard::TScanCounters::EStatusFinish status) { - LOG_DEBUG_S(*TlsActivationContext, NKikimrServices::TX_COLUMNSHARD_SCAN, - "Scan " << ScanActorId << " finished for tablet " << TabletId); + LOG_DEBUG_S(*TlsActivationContext, NKikimrServices::TX_COLUMNSHARD_SCAN, "Scan " << ScanActorId << " finished for tablet " << TabletId); Send(ColumnShardActorId, new NColumnShard::TEvPrivate::TEvReadFinished(RequestCookie, TxId)); AFL_VERIFY(StartInstant); @@ -432,4 +443,4 @@ TMonotonic TColumnShardScan::GetDeadline() const { } return *StartInstant + Timeout; } -} \ No newline at end of file +} // namespace NKikimr::NOlap::NReader diff --git a/ydb/core/tx/columnshard/engines/reader/actor/actor.h b/ydb/core/tx/columnshard/engines/reader/actor/actor.h index ebf199826421..db93e4cdd769 100644 --- a/ydb/core/tx/columnshard/engines/reader/actor/actor.h +++ b/ydb/core/tx/columnshard/engines/reader/actor/actor.h @@ -16,11 +16,14 @@ namespace NKikimr::NOlap::NReader { -class TColumnShardScan: public TActorBootstrapped, NArrow::IRowWriter { +class TColumnShardScan: public TActorBootstrapped, + NArrow::IRowWriter, + NColumnShard::TMonitoringObjectsCounter { private: TActorId ResourceSubscribeActorId; TActorId ReadCoordinatorActorId; const std::shared_ptr StoragesManager; + const std::shared_ptr DataAccessorsManager; std::optional StartInstant; public: @@ -32,9 +35,11 @@ class TColumnShardScan: public TActorBootstrapped, NArrow::IRo virtual void PassAway() override; TColumnShardScan(const TActorId& columnShardActorId, const TActorId& scanComputeActorId, - const std::shared_ptr& storagesManager, const TComputeShardingPolicy& computeShardingPolicy, ui32 scanId, ui64 txId, - ui32 scanGen, ui64 requestCookie, ui64 tabletId, TDuration timeout, const TReadMetadataBase::TConstPtr& readMetadataRange, - NKikimrDataEvents::EDataFormat dataFormat, const NColumnShard::TScanCounters& scanCountersPool); + const std::shared_ptr& storagesManager, + const std::shared_ptr& dataAccessorsManager, + const TComputeShardingPolicy& computeShardingPolicy, ui32 scanId, ui64 txId, ui32 scanGen, ui64 requestCookie, ui64 tabletId, + TDuration timeout, const TReadMetadataBase::TConstPtr& readMetadataRange, NKikimrDataEvents::EDataFormat dataFormat, + const NColumnShard::TScanCounters& scanCountersPool); void Bootstrap(const TActorContext& ctx); @@ -42,10 +47,11 @@ class TColumnShardScan: public TActorBootstrapped, NArrow::IRo STATEFN(StateScan) { auto g = Stats->MakeGuard("processing"); TLogContextGuard gLogging(NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD_SCAN) ("SelfId", SelfId())( - "TabletId", TabletId)("ScanId", ScanId)("TxId", TxId)("ScanGen", ScanGen)); + "TabletId", TabletId)("ScanId", ScanId)("TxId", TxId)("ScanGen", ScanGen)("task_identifier", ReadMetadataRange->GetScanIdentifier())); switch (ev->GetTypeRewrite()) { hFunc(NKqp::TEvKqpCompute::TEvScanDataAck, HandleScan); hFunc(NKqp::TEvKqp::TEvAbortExecution, HandleScan); + hFunc(NActors::TEvents::TEvPoison, HandleScan); hFunc(TEvents::TEvUndelivered, HandleScan); hFunc(TEvents::TEvWakeup, HandleScan); hFunc(NColumnShard::TEvPrivate::TEvTaskProcessedResult, HandleScan); @@ -64,6 +70,7 @@ class TColumnShardScan: public TActorBootstrapped, NArrow::IRo void ContinueProcessing(); void HandleScan(NKqp::TEvKqp::TEvAbortExecution::TPtr& ev) noexcept; + void HandleScan(NActors::TEvents::TEvPoison::TPtr& ev) noexcept; void HandleScan(TEvents::TEvUndelivered::TPtr& ev); @@ -133,8 +140,7 @@ class TColumnShardScan: public TActorBootstrapped, NArrow::IRo TChunksLimiter ChunksLimiter; THolder Result; - std::shared_ptr CurrentLastReadKey; - i64 InFlightReads = 0; + std::shared_ptr CurrentLastReadKey; bool Finished = false; std::optional LastResultInstant; diff --git a/ydb/core/tx/columnshard/engines/reader/common/description.h b/ydb/core/tx/columnshard/engines/reader/common/description.h index c180dcc8d067..b2d6bc72250f 100644 --- a/ydb/core/tx/columnshard/engines/reader/common/description.h +++ b/ydb/core/tx/columnshard/engines/reader/common/description.h @@ -11,6 +11,9 @@ struct TReadDescription { private: TSnapshot Snapshot; TProgramContainer Program; + std::shared_ptr ScanCursor; + YDB_ACCESSOR_DEF(TString, ScanIdentifier); + public: // Table ui64 TxId = 0; @@ -27,7 +30,17 @@ struct TReadDescription { // List of columns std::vector ColumnIds; std::vector ColumnNames; - + + const std::shared_ptr& GetScanCursor() const { + AFL_VERIFY(ScanCursor); + return ScanCursor; + } + + void SetScanCursor(const std::shared_ptr& cursor) { + AFL_VERIFY(!ScanCursor); + ScanCursor = cursor; + } + TReadDescription(const TSnapshot& snapshot, const bool isReverse) : Snapshot(snapshot) , PKRangesFilter(std::make_shared(isReverse)) { diff --git a/ydb/core/tx/columnshard/engines/reader/common/result.cpp b/ydb/core/tx/columnshard/engines/reader/common/result.cpp index e81e86bfc9d0..92f55f3dfc77 100644 --- a/ydb/core/tx/columnshard/engines/reader/common/result.cpp +++ b/ydb/core/tx/columnshard/engines/reader/common/result.cpp @@ -1,11 +1,14 @@ #include "result.h" +#include + namespace NKikimr::NOlap::NReader { class TCurrentBatch { private: std::vector> Results; ui64 RecordsCount = 0; + public: ui64 GetRecordsCount() const { return RecordsCount; @@ -49,4 +52,18 @@ std::vector> TPartialReadResult::SplitResult return result; } -} \ No newline at end of file +TPartialReadResult::TPartialReadResult(const std::vector>& resourceGuards, + const std::shared_ptr& gGuard, const NArrow::TShardedRecordBatch& batch, + const std::shared_ptr& scanCursor, const std::shared_ptr& context, + const std::optional notFinishedIntervalIdx) + : ResourceGuards(resourceGuards) + , GroupGuard(gGuard) + , ResultBatch(batch) + , ScanCursor(scanCursor) + , NotFinishedIntervalIdx(notFinishedIntervalIdx) + , Guard(TValidator::CheckNotNull(context)->GetCounters().GetResultsForReplyGuard()) { + Y_ABORT_UNLESS(ResultBatch.GetRecordsCount()); + Y_ABORT_UNLESS(ScanCursor); +} + +} // namespace NKikimr::NOlap::NReader diff --git a/ydb/core/tx/columnshard/engines/reader/common/result.h b/ydb/core/tx/columnshard/engines/reader/common/result.h index e3028b01b5ad..f4e7d7d4b1ee 100644 --- a/ydb/core/tx/columnshard/engines/reader/common/result.h +++ b/ydb/core/tx/columnshard/engines/reader/common/result.h @@ -7,19 +7,23 @@ #include #include + namespace NKikimr::NOlap::NReader { +class TReadContext; + // Represents a batch of rows produced by ASC or DESC scan with applied filters and partial aggregation class TPartialReadResult: public TNonCopyable { private: - YDB_READONLY_DEF(std::shared_ptr, ResourcesGuard); + YDB_READONLY_DEF(std::vector>, ResourceGuards); YDB_READONLY_DEF(std::shared_ptr, GroupGuard); NArrow::TShardedRecordBatch ResultBatch; // This 1-row batch contains the last key that was read while producing the ResultBatch. // NOTE: it might be different from the Key of last row in ResulBatch in case of filtering/aggregation/limit - std::shared_ptr LastReadKey; + std::shared_ptr ScanCursor; YDB_READONLY_DEF(std::optional, NotFinishedIntervalIdx); + const NColumnShard::TCounterGuard Guard; public: void Cut(const ui32 limit) { @@ -50,26 +54,18 @@ class TPartialReadResult: public TNonCopyable { return ResultBatch; } - const std::shared_ptr& GetLastReadKey() const { - return LastReadKey; + const std::shared_ptr& GetScanCursor() const { + return ScanCursor; } - explicit TPartialReadResult(std::shared_ptr&& resourcesGuard, - std::shared_ptr&& gGuard, const NArrow::TShardedRecordBatch& batch, - std::shared_ptr lastKey, const std::optional notFinishedIntervalIdx) - : ResourcesGuard(std::move(resourcesGuard)) - , GroupGuard(std::move(gGuard)) - , ResultBatch(batch) - , LastReadKey(lastKey) - , NotFinishedIntervalIdx(notFinishedIntervalIdx) { - Y_ABORT_UNLESS(ResultBatch.GetRecordsCount()); - Y_ABORT_UNLESS(LastReadKey); - Y_ABORT_UNLESS(LastReadKey->num_rows() == 1); - } + explicit TPartialReadResult(const std::vector>& resourceGuards, + const std::shared_ptr& gGuard, const NArrow::TShardedRecordBatch& batch, + const std::shared_ptr& scanCursor, const std::shared_ptr& context, + const std::optional notFinishedIntervalIdx); - explicit TPartialReadResult( - const NArrow::TShardedRecordBatch& batch, std::shared_ptr lastKey, const std::optional notFinishedIntervalIdx) - : TPartialReadResult(nullptr, nullptr, batch, lastKey, notFinishedIntervalIdx) { + explicit TPartialReadResult(const NArrow::TShardedRecordBatch& batch, const std::shared_ptr& scanCursor, + const std::shared_ptr& context, const std::optional notFinishedIntervalIdx) + : TPartialReadResult({}, nullptr, batch, scanCursor, context, notFinishedIntervalIdx) { } }; diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/constructor/read_metadata.cpp b/ydb/core/tx/columnshard/engines/reader/common_reader/constructor/read_metadata.cpp new file mode 100644 index 000000000000..56a14c9b23fe --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/constructor/read_metadata.cpp @@ -0,0 +1,121 @@ +#include "read_metadata.h" + +#include +#include +#include +#include + +namespace NKikimr::NOlap::NReader::NCommon { + +TConclusionStatus TReadMetadata::Init( + const NColumnShard::TColumnShard* owner, const TReadDescription& readDescription, const TDataStorageAccessor& dataAccessor) { + SetPKRangesFilter(readDescription.PKRangesFilter); + InitShardingInfo(readDescription.PathId); + TxId = readDescription.TxId; + LockId = readDescription.LockId; + if (LockId) { + owner->GetOperationsManager().RegisterLock(*LockId, owner->Generation()); + LockSharingInfo = owner->GetOperationsManager().GetLockVerified(*LockId).GetSharingInfo(); + } + + SelectInfo = dataAccessor.Select(readDescription, !!LockId); + if (LockId) { + for (auto&& i : SelectInfo->Portions) { + if (i->HasInsertWriteId() && !i->HasCommitSnapshot()) { + if (owner->HasLongTxWrites(i->GetInsertWriteIdVerified())) { + } else { + auto op = owner->GetOperationsManager().GetOperationByInsertWriteIdVerified(i->GetInsertWriteIdVerified()); + AddWriteIdToCheck(i->GetInsertWriteIdVerified(), op->GetLockId()); + } + } + } + } + + { + auto customConclusion = DoInitCustom(owner, readDescription, dataAccessor); + if (customConclusion.IsFail()) { + return customConclusion; + } + } + + StatsMode = readDescription.StatsMode; + return TConclusionStatus::Success(); +} + +std::set TReadMetadata::GetEarlyFilterColumnIds() const { + auto& indexInfo = ResultIndexSchema->GetIndexInfo(); + std::set result; + for (auto&& i : GetProgram().GetEarlyFilterColumns()) { + auto id = indexInfo.GetColumnIdOptional(i); + if (id) { + result.emplace(*id); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("early_filter_column", i); + } + } + return result; +} + +std::set TReadMetadata::GetPKColumnIds() const { + std::set result; + auto& indexInfo = ResultIndexSchema->GetIndexInfo(); + for (auto&& i : indexInfo.GetPrimaryKeyColumns()) { + Y_ABORT_UNLESS(result.emplace(indexInfo.GetColumnIdVerified(i.first)).second); + } + return result; +} + +NArrow::NMerger::TSortableBatchPosition TReadMetadata::BuildSortedPosition(const NArrow::TReplaceKey& key) const { + return NArrow::NMerger::TSortableBatchPosition(key.ToBatch(GetReplaceKey()), 0, GetReplaceKey()->field_names(), {}, IsDescSorted()); +} + +void TReadMetadata::DoOnReadFinished(NColumnShard::TColumnShard& owner) const { + if (!GetLockId()) { + return; + } + const ui64 lock = *GetLockId(); + if (GetBrokenWithCommitted()) { + owner.GetOperationsManager().GetLockVerified(lock).SetBroken(); + } else { + NOlap::NTxInteractions::TTxConflicts conflicts; + for (auto&& i : GetConflictableLockIds()) { + conflicts.Add(i, lock); + } + auto writer = std::make_shared(PathId, conflicts); + owner.GetOperationsManager().AddEventForLock(owner, lock, writer); + } +} + +void TReadMetadata::DoOnBeforeStartReading(NColumnShard::TColumnShard& owner) const { + if (!LockId) { + return; + } + auto evWriter = std::make_shared( + PathId, GetResultSchema()->GetIndexInfo().GetPrimaryKey(), GetPKRangesFilterPtr(), GetConflictableLockIds()); + owner.GetOperationsManager().AddEventForLock(owner, *LockId, evWriter); +} + +void TReadMetadata::DoOnReplyConstruction(const ui64 tabletId, NKqp::NInternalImplementation::TEvScanData& scanData) const { + if (LockSharingInfo) { + NKikimrDataEvents::TLock lockInfo; + lockInfo.SetLockId(LockSharingInfo->GetLockId()); + lockInfo.SetGeneration(LockSharingInfo->GetGeneration()); + lockInfo.SetDataShard(tabletId); + lockInfo.SetCounter(LockSharingInfo->GetCounter()); + lockInfo.SetPathId(PathId); + lockInfo.SetHasWrites(LockSharingInfo->HasWrites()); + if (LockSharingInfo->IsBroken()) { + scanData.LocksInfo.BrokenLocks.emplace_back(std::move(lockInfo)); + } else { + scanData.LocksInfo.Locks.emplace_back(std::move(lockInfo)); + } + } +} + +bool TReadMetadata::IsMyUncommitted(const TInsertWriteId writeId) const { + AFL_VERIFY(LockSharingInfo); + auto it = ConflictedWriteIds.find(writeId); + AFL_VERIFY(it != ConflictedWriteIds.end())("write_id", writeId)("write_ids_count", ConflictedWriteIds.size()); + return it->second.GetLockId() == LockSharingInfo->GetLockId(); +} + +} // namespace NKikimr::NOlap::NReader::NCommon diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/constructor/read_metadata.h b/ydb/core/tx/columnshard/engines/reader/common_reader/constructor/read_metadata.h new file mode 100644 index 000000000000..b7d87c2b3812 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/constructor/read_metadata.h @@ -0,0 +1,157 @@ +#pragma once +#include +#include +#include +#include + +#include + +namespace NKikimr::NColumnShard { +class TLockSharingInfo; +} + +namespace NKikimr::NOlap::NReader::NCommon { + +class TReadMetadata: public TReadMetadataBase { + using TBase = TReadMetadataBase; + +private: + const ui64 PathId; + std::shared_ptr BrokenWithCommitted = std::make_shared(); + std::shared_ptr LockSharingInfo; + + class TWriteIdInfo { + private: + const ui64 LockId; + std::shared_ptr Conflicts; + + public: + TWriteIdInfo(const ui64 lockId, const std::shared_ptr& counter) + : LockId(lockId) + , Conflicts(counter) { + } + + ui64 GetLockId() const { + return LockId; + } + + void MarkAsConflictable() const { + Conflicts->Inc(); + } + + bool IsConflictable() const { + return Conflicts->Val(); + } + }; + + THashMap> LockConflictCounters; + THashMap ConflictedWriteIds; + + virtual void DoOnReadFinished(NColumnShard::TColumnShard& owner) const override; + virtual void DoOnBeforeStartReading(NColumnShard::TColumnShard& owner) const override; + virtual void DoOnReplyConstruction(const ui64 tabletId, NKqp::NInternalImplementation::TEvScanData& scanData) const override; + + virtual TConclusionStatus DoInitCustom( + const NColumnShard::TColumnShard* owner, const TReadDescription& readDescription, const TDataStorageAccessor& dataAccessor) = 0; + +public: + using TConstPtr = std::shared_ptr; + + bool GetBrokenWithCommitted() const { + return BrokenWithCommitted->Val(); + } + THashSet GetConflictableLockIds() const { + THashSet result; + for (auto&& i : ConflictedWriteIds) { + if (i.second.IsConflictable()) { + result.emplace(i.second.GetLockId()); + } + } + return result; + } + + bool IsLockConflictable(const ui64 lockId) const { + auto it = LockConflictCounters.find(lockId); + AFL_VERIFY(it != LockConflictCounters.end()); + return it->second->Val(); + } + + bool IsWriteConflictable(const TInsertWriteId writeId) const { + auto it = ConflictedWriteIds.find(writeId); + AFL_VERIFY(it != ConflictedWriteIds.end()); + return it->second.IsConflictable(); + } + + void AddWriteIdToCheck(const TInsertWriteId writeId, const ui64 lockId) { + auto it = LockConflictCounters.find(lockId); + if (it == LockConflictCounters.end()) { + it = LockConflictCounters.emplace(lockId, std::make_shared()).first; + } + AFL_VERIFY(ConflictedWriteIds.emplace(writeId, TWriteIdInfo(lockId, it->second)).second); + } + + [[nodiscard]] bool IsMyUncommitted(const TInsertWriteId writeId) const; + + void SetConflictedWriteId(const TInsertWriteId writeId) const { + auto it = ConflictedWriteIds.find(writeId); + AFL_VERIFY(it != ConflictedWriteIds.end()); + it->second.MarkAsConflictable(); + } + + void SetBrokenWithCommitted() const { + BrokenWithCommitted->Inc(); + } + + NArrow::NMerger::TSortableBatchPosition BuildSortedPosition(const NArrow::TReplaceKey& key) const; + virtual std::shared_ptr BuildReader(const std::shared_ptr& context) const = 0; + + bool HasProcessingColumnIds() const { + return GetProgram().HasProcessingColumnIds(); + } + + ui64 GetPathId() const { + return PathId; + } + + std::shared_ptr SelectInfo; + NYql::NDqProto::EDqStatsMode StatsMode = NYql::NDqProto::EDqStatsMode::DQ_STATS_MODE_NONE; + std::shared_ptr ReadStats; + + TReadMetadata(const ui64 pathId, const std::shared_ptr info, const TSnapshot& snapshot, const ESorting sorting, + const TProgramContainer& ssaProgram, const std::shared_ptr& scanCursor) + : TBase(info, sorting, ssaProgram, info->GetSchemaVerified(snapshot), snapshot, scanCursor) + , PathId(pathId) + , ReadStats(std::make_shared()) { + } + + virtual std::vector GetKeyYqlSchema() const override { + return GetResultSchema()->GetIndexInfo().GetPrimaryKeyColumns(); + } + + TConclusionStatus Init( + const NColumnShard::TColumnShard* owner, const TReadDescription& readDescription, const TDataStorageAccessor& dataAccessor); + + std::set GetEarlyFilterColumnIds() const; + std::set GetPKColumnIds() const; + + virtual bool Empty() const = 0; + + size_t NumIndexedBlobs() const { + Y_ABORT_UNLESS(SelectInfo); + return SelectInfo->Stats().Blobs; + } + + virtual TString DebugString() const override { + TStringBuilder result; + + result << TBase::DebugString() << ";" << " index blobs: " << NumIndexedBlobs() << " committed blobs: " + << " at snapshot: " << GetRequestSnapshot().DebugString(); + + if (SelectInfo) { + result << ", " << SelectInfo->DebugString(); + } + return result; + } +}; + +} // namespace NKikimr::NOlap::NReader::NCommon diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/constructor/ya.make b/ydb/core/tx/columnshard/engines/reader/common_reader/constructor/ya.make new file mode 100644 index 000000000000..180dc0be1044 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/constructor/ya.make @@ -0,0 +1,12 @@ +LIBRARY() + +SRCS( + read_metadata.cpp +) + +PEERDIR( + ydb/core/tx/columnshard/engines/reader/abstract + ydb/core/kqp/compute_actor +) + +END() diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/columns_set.cpp b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/columns_set.cpp similarity index 98% rename from ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/columns_set.cpp rename to ydb/core/tx/columnshard/engines/reader/common_reader/iterator/columns_set.cpp index 24ef9a452e4c..0e3b8dee1b06 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/columns_set.cpp +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/columns_set.cpp @@ -2,7 +2,7 @@ #include #include -namespace NKikimr::NOlap::NReader::NPlain { +namespace NKikimr::NOlap::NReader::NCommon { TString TColumnsSet::DebugString() const { return TStringBuilder() << "(" diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/columns_set.h b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/columns_set.h similarity index 96% rename from ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/columns_set.h rename to ydb/core/tx/columnshard/engines/reader/common_reader/iterator/columns_set.h index 98e77f4971e9..45cbf7c2c951 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/columns_set.h +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/columns_set.h @@ -6,12 +6,19 @@ #include -namespace NKikimr::NOlap::NReader::NPlain { +namespace NKikimr::NOlap::NReader::NCommon { + +enum class EMemType { + Blob, + Raw, + RawSequential +}; enum class EStageFeaturesIndexes { - Filter = 0, - Fetching = 1, - Merge = 2 + Accessors = 0, + Filter = 1, + Fetching = 2, + Merge = 3 }; class TIndexesSet { @@ -204,4 +211,4 @@ class TColumnsSet: public TColumnsSetIds { TColumnsSet operator-(const TColumnsSet& external) const; }; -} // namespace NKikimr::NOlap::NReader::NPlain +} // namespace NKikimr::NOlap::NReader::NSimple diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/constructor.cpp b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/constructor.cpp new file mode 100644 index 000000000000..a67da1467e87 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/constructor.cpp @@ -0,0 +1,35 @@ +#include "constructor.h" + +#include +#include + +namespace NKikimr::NOlap::NReader::NCommon { + +void TBlobsFetcherTask::DoOnDataReady(const std::shared_ptr& /*resourcesGuard*/) { + Source->MutableStageData().AddBlobs(Source->DecodeBlobAddresses(ExtractBlobsData())); + AFL_VERIFY(Step.Next()); + auto task = std::make_shared(Source, std::move(Step), Context->GetCommonContext()->GetScanActorId()); + NConveyor::TScanServiceOperator::SendTaskToExecute(task); +} + +bool TBlobsFetcherTask::DoOnError(const TString& storageId, const TBlobRange& range, const IBlobsReadingAction::TErrorStatus& status) { + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD_SCAN)("error_on_blob_reading", range.ToString())( + "scan_actor_id", Context->GetCommonContext()->GetScanActorId())("status", status.GetErrorMessage())("status_code", status.GetStatus())( + "storage_id", storageId); + NActors::TActorContext::AsActorContext().Send( + Context->GetCommonContext()->GetScanActorId(), std::make_unique( + TConclusionStatus::Fail("cannot read blob range " + range.ToString()))); + return false; +} + +TBlobsFetcherTask::TBlobsFetcherTask(const std::vector>& readActions, + const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, + const std::shared_ptr& context, const TString& taskCustomer, const TString& externalTaskId) + : TBase(readActions, taskCustomer, externalTaskId) + , Source(sourcePtr) + , Step(step) + , Context(context) + , Guard(Context->GetCommonContext()->GetCounters().GetFetchBlobsGuard()) { +} + +} // namespace NKikimr::NOlap::NReader::NCommon diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/constructor.h b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/constructor.h similarity index 56% rename from ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/constructor.h rename to ydb/core/tx/columnshard/engines/reader/common_reader/iterator/constructor.h index 79e3e26c4e3c..f2f097d00f11 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/constructor.h +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/constructor.h @@ -1,12 +1,14 @@ #pragma once -#include -#include -#include -#include -#include +#include "fetching.h" #include "source.h" -namespace NKikimr::NOlap::NReader::NPlain { +#include +#include +#include +#include +#include + +namespace NKikimr::NOlap::NReader::NCommon { class TBlobsFetcherTask: public NBlobOperations::NRead::ITask, public NColumnShard::TMonitoringObjectsCounter { private: @@ -14,19 +16,22 @@ class TBlobsFetcherTask: public NBlobOperations::NRead::ITask, public NColumnSha const std::shared_ptr Source; TFetchingScriptCursor Step; const std::shared_ptr Context; + const NColumnShard::TCounterGuard Guard; virtual void DoOnDataReady(const std::shared_ptr& resourcesGuard) override; virtual bool DoOnError(const TString& storageId, const TBlobRange& range, const IBlobsReadingAction::TErrorStatus& status) override; -public: - TBlobsFetcherTask(const std::vector>& readActions, const std::shared_ptr& sourcePtr, - const TFetchingScriptCursor& step, const std::shared_ptr& context, const TString& taskCustomer, const TString& externalTaskId) - : TBase(readActions, taskCustomer, externalTaskId) - , Source(sourcePtr) - , Step(step) - , Context(context) - { +public: + template + TBlobsFetcherTask(const std::vector>& readActions, const std::shared_ptr& sourcePtr, + const TFetchingScriptCursor& step, const std::shared_ptr& context, const TString& taskCustomer, + const TString& externalTaskId) + : TBlobsFetcherTask(readActions, std::static_pointer_cast(sourcePtr), step, context, taskCustomer, externalTaskId) { } + + TBlobsFetcherTask(const std::vector>& readActions, + const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, + const std::shared_ptr& context, const TString& taskCustomer, const TString& externalTaskId); }; -} +} // namespace NKikimr::NOlap::NReader::NCommon diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/context.cpp b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/context.cpp new file mode 100644 index 000000000000..a33d9b2d5701 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/context.cpp @@ -0,0 +1,114 @@ +#include "context.h" + +#include +#include +#include +#include + +namespace NKikimr::NOlap::NReader::NCommon { + +TSpecialReadContext::TSpecialReadContext(const std::shared_ptr& commonContext) + : CommonContext(commonContext) { + ReadMetadata = CommonContext->GetReadMetadataPtrVerifiedAs(); + Y_ABORT_UNLESS(ReadMetadata->SelectInfo); + + double kffAccessors = 0.01; + double kffFilter = 0.45; + double kffFetching = 0.45; + double kffMerge = 0.10; + TString stagePrefix; + if (ReadMetadata->GetEarlyFilterColumnIds().size()) { + stagePrefix = "EF"; + kffFilter = 0.7; + kffFetching = 0.15; + kffMerge = 0.14; + kffAccessors = 0.01; + } else { + stagePrefix = "FO"; + kffFilter = 0.1; + kffFetching = 0.75; + kffMerge = 0.14; + kffAccessors = 0.01; + } + + std::vector> stages = { + NGroupedMemoryManager::TScanMemoryLimiterOperator::BuildStageFeatures( + stagePrefix + "::ACCESSORS", kffAccessors * TGlobalLimits::ScanMemoryLimit), + NGroupedMemoryManager::TScanMemoryLimiterOperator::BuildStageFeatures( + stagePrefix + "::FILTER", kffFilter * TGlobalLimits::ScanMemoryLimit), + NGroupedMemoryManager::TScanMemoryLimiterOperator::BuildStageFeatures( + stagePrefix + "::FETCHING", kffFetching * TGlobalLimits::ScanMemoryLimit), + NGroupedMemoryManager::TScanMemoryLimiterOperator::BuildStageFeatures(stagePrefix + "::MERGE", kffMerge * TGlobalLimits::ScanMemoryLimit) + }; + ProcessMemoryGuard = + NGroupedMemoryManager::TScanMemoryLimiterOperator::BuildProcessGuard(ReadMetadata->GetTxId(), stages); + ProcessScopeGuard = + NGroupedMemoryManager::TScanMemoryLimiterOperator::BuildScopeGuard(ReadMetadata->GetTxId(), GetCommonContext()->GetScanId()); + + auto readSchema = ReadMetadata->GetResultSchema(); + SpecColumns = std::make_shared(TIndexInfo::GetSnapshotColumnIdsSet(), readSchema); + IndexChecker = ReadMetadata->GetProgram().GetIndexChecker(); + { + auto predicateColumns = ReadMetadata->GetPKRangesFilter().GetColumnIds(ReadMetadata->GetIndexInfo()); + if (predicateColumns.size()) { + PredicateColumns = std::make_shared(predicateColumns, readSchema); + } else { + PredicateColumns = std::make_shared(); + } + } + { + std::set columnIds = { NPortion::TSpecialColumns::SPEC_COL_DELETE_FLAG_INDEX }; + DeletionColumns = std::make_shared(columnIds, ReadMetadata->GetResultSchema()); + } + + if (!!ReadMetadata->GetRequestShardingInfo()) { + auto shardingColumnIds = + ReadMetadata->GetIndexInfo().GetColumnIdsVerified(ReadMetadata->GetRequestShardingInfo()->GetShardingInfo()->GetColumnNames()); + ShardingColumns = std::make_shared(shardingColumnIds, ReadMetadata->GetResultSchema()); + } else { + ShardingColumns = std::make_shared(); + } + { + auto efColumns = ReadMetadata->GetEarlyFilterColumnIds(); + if (efColumns.size()) { + EFColumns = std::make_shared(efColumns, readSchema); + } else { + EFColumns = std::make_shared(); + } + } + if (ReadMetadata->HasProcessingColumnIds()) { + FFColumns = std::make_shared(ReadMetadata->GetProcessingColumnIds(), readSchema); + if (SpecColumns->Contains(*FFColumns) && !EFColumns->IsEmpty()) { + FFColumns = std::make_shared(*EFColumns + *SpecColumns); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("ff_modified", FFColumns->DebugString()); + } else { + AFL_VERIFY(!FFColumns->Contains(*SpecColumns))("info", FFColumns->DebugString()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("ff_first", FFColumns->DebugString()); + } + } else { + FFColumns = EFColumns; + } + if (FFColumns->IsEmpty()) { + ProgramInputColumns = SpecColumns; + } else { + ProgramInputColumns = FFColumns; + } + AllUsageColumns = std::make_shared(*FFColumns + *PredicateColumns); + + PKColumns = std::make_shared(ReadMetadata->GetPKColumnIds(), readSchema); + MergeColumns = std::make_shared(*PKColumns + *SpecColumns); + + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("columns_context_info", DebugString()); +} + +TString TSpecialReadContext::DebugString() const { + TStringBuilder sb; + sb << "ef=" << EFColumns->DebugString() << ";" + << "sharding=" << ShardingColumns->DebugString() << ";" + << "pk=" << PKColumns->DebugString() << ";" + << "ff=" << FFColumns->DebugString() << ";" + << "program_input=" << ProgramInputColumns->DebugString() << ";"; + return sb; +} + +} // namespace NKikimr::NOlap::NReader::NCommon diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/context.h b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/context.h new file mode 100644 index 000000000000..d0376d74d296 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/context.h @@ -0,0 +1,91 @@ +#pragma once +#include "columns_set.h" + +#include +#include +#include +#include + +namespace NKikimr::NOlap::NReader::NCommon { + +class TFetchingScript; +class IDataSource; + +class TSpecialReadContext { +private: + YDB_READONLY_DEF(std::shared_ptr, CommonContext); + YDB_READONLY_DEF(std::shared_ptr, ProcessMemoryGuard); + YDB_READONLY_DEF(std::shared_ptr, ProcessScopeGuard); + + YDB_READONLY_DEF(std::shared_ptr, SpecColumns); + YDB_READONLY_DEF(std::shared_ptr, MergeColumns); + YDB_READONLY_DEF(std::shared_ptr, ShardingColumns); + YDB_READONLY_DEF(std::shared_ptr, DeletionColumns); + YDB_READONLY_DEF(std::shared_ptr, EFColumns); + YDB_READONLY_DEF(std::shared_ptr, PredicateColumns); + YDB_READONLY_DEF(std::shared_ptr, PKColumns); + YDB_READONLY_DEF(std::shared_ptr, AllUsageColumns); + YDB_READONLY_DEF(std::shared_ptr, FFColumns); + YDB_READONLY_DEF(std::shared_ptr, ProgramInputColumns); + + YDB_READONLY_DEF(std::shared_ptr, MergeStageMemory); + YDB_READONLY_DEF(std::shared_ptr, FilterStageMemory); + YDB_READONLY_DEF(std::shared_ptr, FetchingStageMemory); + + TReadMetadata::TConstPtr ReadMetadata; + + virtual std::shared_ptr DoGetColumnsFetchingPlan(const std::shared_ptr& source) = 0; + +protected: + NIndexes::TIndexCheckerContainer IndexChecker; + std::shared_ptr EmptyColumns = std::make_shared(); + +public: + template + std::shared_ptr GetColumnsFetchingPlan(const std::shared_ptr& source) { + return GetColumnsFetchingPlan(std::static_pointer_cast(source)); + } + + std::shared_ptr GetColumnsFetchingPlan(const std::shared_ptr& source) { + return DoGetColumnsFetchingPlan(source); + } + + const TReadMetadata::TConstPtr& GetReadMetadata() const { + return ReadMetadata; + } + + template + std::shared_ptr GetReadMetadataVerifiedAs() const { + auto result = std::dynamic_pointer_cast(ReadMetadata); + AFL_VERIFY(!!result); + return result; + } + + ui64 GetProcessMemoryControlId() const { + AFL_VERIFY(ProcessMemoryGuard); + return ProcessMemoryGuard->GetProcessId(); + } + + bool IsActive() const { + return !CommonContext->IsAborted(); + } + + bool IsAborted() const { + return CommonContext->IsAborted(); + } + + void Abort() { + CommonContext->Stop(); + } + + virtual ~TSpecialReadContext() { + AFL_INFO(NKikimrServices::TX_COLUMNSHARD_SCAN)("fetching", DebugString()); + } + + TString DebugString() const; + virtual TString ProfileDebugString() const = 0; + + TSpecialReadContext(const std::shared_ptr& commonContext); +}; + +} // namespace NKikimr::NOlap::NReader::NCommon diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetch_steps.cpp b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetch_steps.cpp new file mode 100644 index 000000000000..0f70d6ef0476 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetch_steps.cpp @@ -0,0 +1,106 @@ +#include "fetch_steps.h" +#include "source.h" + +#include +#include +#include +#include + +#include + +namespace NKikimr::NOlap::NReader::NCommon { + +TConclusion TColumnBlobsFetchingStep::DoExecuteInplace( + const std::shared_ptr& source, const TFetchingScriptCursor& step) const { + return !source->StartFetchingColumns(source, step, Columns); +} + +ui64 TColumnBlobsFetchingStep::GetProcessingDataSize(const std::shared_ptr& source) const { + return source->GetColumnBlobBytes(Columns.GetColumnIds()); +} + +TConclusion TAssemblerStep::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { + source->AssembleColumns(Columns); + return true; +} + +ui64 TAssemblerStep::GetProcessingDataSize(const std::shared_ptr& source) const { + return source->GetColumnRawBytes(Columns->GetColumnIds()); +} + +TConclusion TOptionalAssemblerStep::DoExecuteInplace( + const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { + source->AssembleColumns(Columns, !source->IsSourceInMemory()); + return true; +} + +ui64 TOptionalAssemblerStep::GetProcessingDataSize(const std::shared_ptr& source) const { + return source->GetColumnsVolume(Columns->GetColumnIds(), EMemType::RawSequential); +} + +bool TAllocateMemoryStep::TFetchingStepAllocation::DoOnAllocated(std::shared_ptr&& guard, + const std::shared_ptr& /*allocation*/) { + auto data = Source.lock(); + if (!data || data->GetContext()->IsAborted()) { + guard->Release(); + return false; + } + if (StageIndex == EStageFeaturesIndexes::Accessors) { + data->MutableStageData().SetAccessorsGuard(std::move(guard)); + } else { + data->RegisterAllocationGuard(std::move(guard)); + } + Step.Next(); + auto task = std::make_shared(data, std::move(Step), data->GetContext()->GetCommonContext()->GetScanActorId()); + NConveyor::TScanServiceOperator::SendTaskToExecute(task); + return true; +} + +TAllocateMemoryStep::TFetchingStepAllocation::TFetchingStepAllocation( + const std::shared_ptr& source, const ui64 mem, const TFetchingScriptCursor& step, const EStageFeaturesIndexes stageIndex) + : TBase(mem) + , Source(source) + , Step(step) + , TasksGuard(source->GetContext()->GetCommonContext()->GetCounters().GetResourcesAllocationTasksGuard()) + , StageIndex(stageIndex) { +} + +void TAllocateMemoryStep::TFetchingStepAllocation::DoOnAllocationImpossible(const TString& errorMessage) { + auto sourcePtr = Source.lock(); + if (sourcePtr) { + sourcePtr->GetContext()->GetCommonContext()->AbortWithError( + "cannot allocate memory for step " + Step.GetName() + ": '" + errorMessage + "'"); + } +} + +TConclusion TAllocateMemoryStep::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const { + ui64 size = PredefinedSize.value_or(0); + for (auto&& i : Packs) { + ui32 sizeLocal = source->GetColumnsVolume(i.GetColumns().GetColumnIds(), i.GetMemType()); + if (source->GetStageData().GetUseFilter() && i.GetMemType() != EMemType::Blob && source->GetContext()->GetReadMetadata()->HasLimit()) { + const ui32 filtered = + source->GetStageData().GetFilteredCount(source->GetRecordsCount(), source->GetContext()->GetReadMetadata()->GetLimitRobust()); + if (filtered < source->GetRecordsCount()) { + sizeLocal = sizeLocal * 1.0 * filtered / source->GetRecordsCount(); + } + } + size += sizeLocal; + } + + auto allocation = std::make_shared(source, size, step, StageIndex); + NGroupedMemoryManager::TScanMemoryLimiterOperator::SendToAllocation(source->GetContext()->GetProcessMemoryControlId(), + source->GetContext()->GetCommonContext()->GetScanId(), source->GetMemoryGroupId(), { allocation }, (ui32)StageIndex); + return false; +} + +ui64 TAllocateMemoryStep::GetProcessingDataSize(const std::shared_ptr& /*source*/) const { + return 0; +} + +NKikimr::TConclusion TBuildStageResultStep::DoExecuteInplace( + const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { + source->BuildStageResult(source); + return true; +} + +} // namespace NKikimr::NOlap::NReader::NCommon diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetch_steps.h b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetch_steps.h new file mode 100644 index 000000000000..fa6f44309f18 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetch_steps.h @@ -0,0 +1,146 @@ +#pragma once +#include "fetching.h" + +#include + +namespace NKikimr::NOlap::NReader::NCommon { + +class TAllocateMemoryStep: public IFetchingStep { +private: + using TBase = IFetchingStep; + class TColumnsPack { + private: + YDB_READONLY_DEF(TColumnsSetIds, Columns); + YDB_READONLY(EMemType, MemType, EMemType::Blob); + + public: + TColumnsPack(const TColumnsSetIds& columns, const EMemType memType) + : Columns(columns) + , MemType(memType) { + } + }; + std::vector Packs; + THashMap> Control; + const EStageFeaturesIndexes StageIndex; + const std::optional PredefinedSize; + +protected: + class TFetchingStepAllocation: public NGroupedMemoryManager::IAllocation { + private: + using TBase = NGroupedMemoryManager::IAllocation; + std::weak_ptr Source; + TFetchingScriptCursor Step; + NColumnShard::TCounterGuard TasksGuard; + const EStageFeaturesIndexes StageIndex; + virtual bool DoOnAllocated(std::shared_ptr&& guard, + const std::shared_ptr& allocation) override; + virtual void DoOnAllocationImpossible(const TString& errorMessage) override; + + public: + TFetchingStepAllocation(const std::shared_ptr& source, const ui64 mem, const TFetchingScriptCursor& step, + const EStageFeaturesIndexes stageIndex); + }; + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + virtual ui64 GetProcessingDataSize(const std::shared_ptr& source) const override; + virtual TString DoDebugString() const override { + return TStringBuilder() << "stage=" << StageIndex << ";"; + } + +public: + void AddAllocation(const TColumnsSetIds& ids, const EMemType memType) { + if (!ids.GetColumnsCount()) { + return; + } + for (auto&& i : ids.GetColumnIds()) { + AFL_VERIFY(Control[i].emplace(memType).second); + } + Packs.emplace_back(ids, memType); + } + EStageFeaturesIndexes GetStage() const { + return StageIndex; + } + + TAllocateMemoryStep(const TColumnsSetIds& columns, const EMemType memType, const EStageFeaturesIndexes stageIndex) + : TBase("ALLOCATE_MEMORY::" + ::ToString(stageIndex)) + , StageIndex(stageIndex) { + AddAllocation(columns, memType); + } + + TAllocateMemoryStep(const ui64 memSize, const EStageFeaturesIndexes stageIndex) + : TBase("ALLOCATE_MEMORY::" + ::ToString(stageIndex)) + , StageIndex(stageIndex) + , PredefinedSize(memSize) { + } +}; + +class TAssemblerStep: public IFetchingStep { +private: + using TBase = IFetchingStep; + YDB_READONLY_DEF(std::shared_ptr, Columns); + virtual TString DoDebugString() const override { + return TStringBuilder() << "columns=" << Columns->DebugString() << ";"; + } + +public: + virtual ui64 GetProcessingDataSize(const std::shared_ptr& source) const override; + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + TAssemblerStep(const std::shared_ptr& columns, const TString& specName = Default()) + : TBase("ASSEMBLER" + (specName ? "::" + specName : "")) + , Columns(columns) { + AFL_VERIFY(Columns); + AFL_VERIFY(Columns->GetColumnsCount()); + } +}; + +class TBuildStageResultStep: public IFetchingStep { +private: + using TBase = IFetchingStep; + +public: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const override; + TBuildStageResultStep() + : TBase("BUILD_STAGE_RESULT") { + } +}; + +class TOptionalAssemblerStep: public IFetchingStep { +private: + using TBase = IFetchingStep; + YDB_READONLY_DEF(std::shared_ptr, Columns); + virtual TString DoDebugString() const override { + return TStringBuilder() << "columns=" << Columns->DebugString() << ";"; + } + +public: + virtual ui64 GetProcessingDataSize(const std::shared_ptr& source) const override; + + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + TOptionalAssemblerStep(const std::shared_ptr& columns, const TString& specName = Default()) + : TBase("OPTIONAL_ASSEMBLER" + (specName ? "::" + specName : "")) + , Columns(columns) { + AFL_VERIFY(Columns); + AFL_VERIFY(Columns->GetColumnsCount()); + } +}; + +class TColumnBlobsFetchingStep: public IFetchingStep { +private: + using TBase = IFetchingStep; + TColumnsSetIds Columns; + +protected: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + virtual TString DoDebugString() const override { + return TStringBuilder() << "columns=" << Columns.DebugString() << ";"; + } + +public: + virtual ui64 GetProcessingDataSize(const std::shared_ptr& source) const override; + TColumnBlobsFetchingStep(const TColumnsSetIds& columns) + : TBase("FETCHING_COLUMNS") + , Columns(columns) { + AFL_VERIFY(Columns.GetColumnsCount()); + } +}; + +} // namespace NKikimr::NOlap::NReader::NCommon diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetched_data.cpp b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetched_data.cpp new file mode 100644 index 000000000000..93c7f0afd2bd --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetched_data.cpp @@ -0,0 +1,21 @@ +#include "fetched_data.h" + +#include +#include +#include + +namespace NKikimr::NOlap::NReader::NCommon { + +void TFetchedData::SyncTableColumns(const std::vector>& fields, const ISnapshotSchema& schema) { + for (auto&& i : fields) { + if (Table->GetSchema()->GetFieldByName(i->name())) { + continue; + } + Table + ->AddField(i, std::make_shared(NArrow::TThreadSimpleArraysCache::Get( + i->type(), schema.GetExternalDefaultValueVerified(i->name()), Table->num_rows()))) + .Validate(); + } +} + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetched_data.h b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetched_data.h new file mode 100644 index 000000000000..421b612ec704 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetched_data.h @@ -0,0 +1,253 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace NKikimr::NOlap::NReader::NCommon { + +class TFetchedData { +private: + using TBlobs = THashMap; + YDB_ACCESSOR_DEF(TBlobs, Blobs); + YDB_READONLY_DEF(std::shared_ptr, Table); + YDB_READONLY_DEF(std::shared_ptr, Filter); + YDB_READONLY(bool, UseFilter, false); + + std::shared_ptr AccessorsGuard; + std::optional PortionAccessor; + bool DataAdded = false; + +public: + TString DebugString() const { + return TStringBuilder() << DataAdded; + } + + TFetchedData(const bool useFilter) + : UseFilter(useFilter) { + } + + void SetAccessorsGuard(std::shared_ptr&& guard) { + AFL_VERIFY(!AccessorsGuard); + AFL_VERIFY(!!guard); + AccessorsGuard = std::move(guard); + } + + void SetUseFilter(const bool value) { + if (UseFilter == value) { + return; + } + AFL_VERIFY(!DataAdded); + UseFilter = value; + } + + bool HasPortionAccessor() const { + return !!PortionAccessor; + } + + void SetPortionAccessor(TPortionDataAccessor&& accessor) { + AFL_VERIFY(!PortionAccessor); + PortionAccessor = std::move(accessor); + } + + const TPortionDataAccessor& GetPortionAccessor() const { + AFL_VERIFY(!!PortionAccessor); + return *PortionAccessor; + } + + ui32 GetFilteredCount(const ui32 recordsCount, const ui32 defLimit) const { + if (!Filter) { + return std::min(defLimit, recordsCount); + } + return Filter->GetFilteredCount().value_or(recordsCount); + } + + void SyncTableColumns(const std::vector>& fields, const ISnapshotSchema& schema); + + std::shared_ptr GetAppliedFilter() const { + return UseFilter ? Filter : nullptr; + } + + std::shared_ptr GetNotAppliedFilter() const { + return UseFilter ? nullptr : Filter; + } + + TString ExtractBlob(const TChunkAddress& address) { + auto it = Blobs.find(address); + AFL_VERIFY(it != Blobs.end()); + AFL_VERIFY(it->second.IsBlob()); + auto result = it->second.GetData(); + Blobs.erase(it); + return result; + } + + void AddBlobs(THashMap&& blobData) { + for (auto&& i : blobData) { + AFL_VERIFY(Blobs.emplace(i.first, std::move(i.second)).second); + } + } + + void AddDefaults(THashMap&& blobs) { + for (auto&& i : blobs) { + AFL_VERIFY(Blobs.emplace(i.first, std::move(i.second)).second); + } + } + + bool IsEmpty() const { + return (Filter && Filter->IsTotalDenyFilter()) || (Table && !Table->num_rows()); + } + + void Clear() { + Filter = std::make_shared(NArrow::TColumnFilter::BuildDenyFilter()); + Table = nullptr; + } + + void AddFilter(const std::shared_ptr& filter) { + DataAdded = true; + if (!filter) { + return; + } + return AddFilter(*filter); + } + + void CutFilter(const ui32 recordsCount, const ui32 limit, const bool reverse) { + auto filter = std::make_shared(NArrow::TColumnFilter::BuildAllowFilter()); + ui32 recordsCountImpl = Filter ? Filter->GetFilteredCount().value_or(recordsCount) : recordsCount; + if (recordsCountImpl < limit) { + return; + } + if (reverse) { + filter->Add(false, recordsCountImpl - limit); + filter->Add(true, limit); + } else { + filter->Add(true, limit); + filter->Add(false, recordsCountImpl - limit); + } + if (Filter) { + if (UseFilter) { + AddFilter(*filter); + } else { + AddFilter(Filter->CombineSequentialAnd(*filter)); + } + } else { + AddFilter(*filter); + } + } + + void AddFilter(const NArrow::TColumnFilter& filter) { + if (UseFilter && Table) { + AFL_VERIFY(filter.Apply(Table, + NArrow::TColumnFilter::TApplyContext().SetTrySlices(!HasAppData() || AppDataVerified().ColumnShardConfig.GetUseSlicesFilter()))); + } + if (!Filter) { + Filter = std::make_shared(filter); + } else if (UseFilter) { + *Filter = Filter->CombineSequentialAnd(filter); + } else { + *Filter = Filter->And(filter); + } + } + + void AddBatch(const std::shared_ptr& table) { + DataAdded = true; + AFL_VERIFY(table); + if (UseFilter) { + AddBatch(table->BuildTableVerified()); + } else { + if (!Table) { + Table = table; + } else { + auto mergeResult = Table->MergeColumnsStrictly(*table); + AFL_VERIFY(mergeResult.IsSuccess())("error", mergeResult.GetErrorMessage()); + } + } + } + + void AddBatch(const std::shared_ptr& table) { + DataAdded = true; + auto tableLocal = table; + if (Filter && UseFilter) { + AFL_VERIFY(Filter->Apply(tableLocal, + NArrow::TColumnFilter::TApplyContext().SetTrySlices(!HasAppData() || AppDataVerified().ColumnShardConfig.GetUseSlicesFilter()))); + } + if (!Table) { + Table = std::make_shared(tableLocal); + } else { + auto mergeResult = Table->MergeColumnsStrictly(NArrow::TGeneralContainer(tableLocal)); + AFL_VERIFY(mergeResult.IsSuccess())("error", mergeResult.GetErrorMessage()); + } + } +}; + +class TFetchedResult { +private: + YDB_READONLY_DEF(std::shared_ptr, Batch); + YDB_READONLY_DEF(std::shared_ptr, NotAppliedFilter); + std::optional> PagesToResult; + std::optional> ChunkToReply; + +public: + TFetchedResult(std::unique_ptr&& data) + : Batch(data->GetTable()) + , NotAppliedFilter(data->GetNotAppliedFilter()) { + } + + TPortionDataAccessor::TReadPage ExtractPageForResult() { + AFL_VERIFY(PagesToResult); + AFL_VERIFY(PagesToResult->size()); + auto result = PagesToResult->front(); + PagesToResult->pop_front(); + return result; + } + + const std::deque& GetPagesToResultVerified() const { + AFL_VERIFY(PagesToResult); + return *PagesToResult; + } + + void SetPages(std::vector&& pages) { + AFL_VERIFY(!PagesToResult); + PagesToResult = std::deque(pages.begin(), pages.end()); + } + + void SetResultChunk(std::shared_ptr&& table, const ui32 indexStart, const ui32 recordsCount) { + auto page = ExtractPageForResult(); + AFL_VERIFY(page.GetIndexStart() == indexStart)("real", page.GetIndexStart())("expected", indexStart); + AFL_VERIFY(page.GetRecordsCount() == recordsCount)("real", page.GetRecordsCount())("expected", recordsCount); + AFL_VERIFY(!ChunkToReply); + ChunkToReply = std::move(table); + } + + bool IsFinished() const { + return GetPagesToResultVerified().empty(); + } + + bool HasResultChunk() const { + return !!ChunkToReply; + } + + std::shared_ptr ExtractResultChunk() { + AFL_VERIFY(!!ChunkToReply); + auto result = std::move(*ChunkToReply); + ChunkToReply.reset(); + return result; + } + + bool IsEmpty() const { + return !Batch || Batch->num_rows() == 0 || (NotAppliedFilter && NotAppliedFilter->IsTotalDenyFilter()); + } +}; + +} // namespace NKikimr::NOlap::NReader::NCommon diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetching.cpp b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetching.cpp new file mode 100644 index 000000000000..1367599db410 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetching.cpp @@ -0,0 +1,166 @@ +#include "fetch_steps.h" +#include "fetching.h" +#include "source.h" + +#include +#include + +namespace NKikimr::NOlap::NReader::NCommon { + +bool TStepAction::DoApply(IDataReader& owner) const { + if (FinishedFlag) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "apply"); + Source->OnSourceFetchingFinishedSafe(owner, Source); + } + return true; +} + +TConclusionStatus TStepAction::DoExecuteImpl() { + if (Source->GetContext()->IsAborted()) { + return TConclusionStatus::Success(); + } + auto executeResult = Cursor.Execute(Source); + if (!executeResult) { + return executeResult; + } + if (*executeResult) { + FinishedFlag = true; + } + return TConclusionStatus::Success(); +} + +TStepAction::TStepAction(const std::shared_ptr& source, TFetchingScriptCursor&& cursor, const NActors::TActorId& ownerActorId) + : TBase(ownerActorId) + , Source(source) + , Cursor(std::move(cursor)) + , CountersGuard(Source->GetContext()->GetCommonContext()->GetCounters().GetAssembleTasksGuard()) { +} + +TConclusion TFetchingScriptCursor::Execute(const std::shared_ptr& source) { + AFL_VERIFY(source); + NMiniKQL::TThrowingBindTerminator bind; + Script->OnExecute(); + AFL_VERIFY(!Script->IsFinished(CurrentStepIdx)); + while (!Script->IsFinished(CurrentStepIdx)) { + if (source->HasStageData() && source->GetStageData().IsEmpty()) { + source->OnEmptyStageData(source); + break; + } + auto step = Script->GetStep(CurrentStepIdx); + TMemoryProfileGuard mGuard("SCAN_PROFILE::FETCHING::" + step->GetName() + "::" + Script->GetBranchName(), + IS_DEBUG_LOG_ENABLED(NKikimrServices::TX_COLUMNSHARD_SCAN_MEMORY)); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("scan_step", step->DebugString())("scan_step_idx", CurrentStepIdx); + AFL_VERIFY(!CurrentStartInstant); + CurrentStartInstant = TMonotonic::Now(); + AFL_VERIFY(!CurrentStartDataSize); + CurrentStartDataSize = step->GetProcessingDataSize(source); + const TConclusion resultStep = step->ExecuteInplace(source, *this); + if (!resultStep) { + return resultStep; + } + if (!*resultStep) { + return false; + } + FlushDuration(); + ++CurrentStepIdx; + } + return true; +} + +TString TFetchingScript::DebugString() const { + TStringBuilder sb; + TStringBuilder sbBranch; + for (auto&& i : Steps) { + if (i->GetSumDuration() > TDuration::MilliSeconds(10)) { + sbBranch << "{" << i->DebugString() << "};"; + } + } + if (!sbBranch) { + return ""; + } + sb << "{branch:" << BranchName << ";"; + if (FinishInstant && StartInstant) { + sb << "duration:" << *FinishInstant - *StartInstant << ";"; + } + + sb << "steps_10Ms:[" << sbBranch << "]}"; + return sb; +} + +TFetchingScript::TFetchingScript(const TSpecialReadContext& /*context*/) { +} + +void TFetchingScript::Allocation(const std::set& entityIds, const EStageFeaturesIndexes stage, const EMemType mType) { + if (Steps.size() == 0) { + AddStep(entityIds, mType, stage); + } else { + std::optional addIndex; + for (i32 i = Steps.size() - 1; i >= 0; --i) { + if (auto allocation = std::dynamic_pointer_cast(Steps[i])) { + if (allocation->GetStage() == stage) { + allocation->AddAllocation(entityIds, mType); + return; + } else { + addIndex = i + 1; + } + break; + } else if (std::dynamic_pointer_cast(Steps[i])) { + continue; + } else if (std::dynamic_pointer_cast(Steps[i])) { + continue; + } else { + addIndex = i + 1; + break; + } + } + AFL_VERIFY(addIndex); + InsertStep(*addIndex, entityIds, mType, stage); + } +} + +TString IFetchingStep::DebugString() const { + TStringBuilder sb; + sb << "name=" << Name << ";duration=" << SumDuration << ";" + << "size=" << 1e-9 * SumSize << ";details={" << DoDebugString() << "};"; + return sb; +} + +bool TColumnsAccumulator::AddFetchingStep(TFetchingScript& script, const TColumnsSetIds& columns, const EStageFeaturesIndexes stage) { + auto actualColumns = GetNotFetchedAlready(columns); + FetchingReadyColumns = FetchingReadyColumns + (TColumnsSetIds)columns; + if (!actualColumns.IsEmpty()) { + script.Allocation(columns.GetColumnIds(), stage, EMemType::Blob); + script.AddStep(std::make_shared(actualColumns)); + return true; + } + return false; +} + +bool TColumnsAccumulator::AddAssembleStep( + TFetchingScript& script, const TColumnsSetIds& columns, const TString& purposeId, const EStageFeaturesIndexes stage, const bool sequential) { + auto actualColumns = columns - AssemblerReadyColumns; + AssemblerReadyColumns = AssemblerReadyColumns + columns; + if (actualColumns.IsEmpty()) { + return false; + } + auto actualSet = std::make_shared(actualColumns.GetColumnIds(), FullSchema); + if (sequential) { + const auto notSequentialColumnIds = GuaranteeNotOptional->Intersect(*actualSet); + if (notSequentialColumnIds.size()) { + script.Allocation(notSequentialColumnIds, stage, EMemType::Raw); + std::shared_ptr cross = actualSet->BuildSamePtr(notSequentialColumnIds); + script.AddStep(cross, purposeId); + *actualSet = *actualSet - *cross; + } + if (!actualSet->IsEmpty()) { + script.Allocation(notSequentialColumnIds, stage, EMemType::RawSequential); + script.AddStep(actualSet, purposeId); + } + } else { + script.Allocation(actualColumns.GetColumnIds(), stage, EMemType::Raw); + script.AddStep(actualSet, purposeId); + } + return true; +} + +} // namespace NKikimr::NOlap::NReader::NCommon diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetching.h b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetching.h new file mode 100644 index 000000000000..34b60a608f21 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/fetching.h @@ -0,0 +1,254 @@ +#pragma once +#include "columns_set.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace NKikimr::NOlap::NReader::NCommon { + +class IDataSource; +class TSpecialReadContext; +class TFetchingScriptCursor; + +class TFetchingStepSignals: public NColumnShard::TCommonCountersOwner { +private: + using TBase = NColumnShard::TCommonCountersOwner; + NMonitoring::TDynamicCounters::TCounterPtr DurationCounter; + NMonitoring::TDynamicCounters::TCounterPtr BytesCounter; + +public: + TFetchingStepSignals(NColumnShard::TCommonCountersOwner&& owner) + : TBase(std::move(owner)) + , DurationCounter(TBase::GetDeriviative("Duration/Us")) + , BytesCounter(TBase::GetDeriviative("Bytes/Count")) { + } + + void AddDuration(const TDuration d) const { + DurationCounter->Add(d.MicroSeconds()); + } + + void AddBytes(const ui32 v) const { + BytesCounter->Add(v); + } +}; + +class TFetchingStepsSignalsCollection: public NColumnShard::TCommonCountersOwner { +private: + using TBase = NColumnShard::TCommonCountersOwner; + TMutex Mutex; + THashMap Collection; + TFetchingStepSignals GetSignalsImpl(const TString& name) { + TGuard g(Mutex); + auto it = Collection.find(name); + if (it == Collection.end()) { + it = Collection.emplace(name, TFetchingStepSignals(CreateSubGroup("step_name", name))).first; + } + return it->second; + } + +public: + TFetchingStepsSignalsCollection() + : TBase("ScanSteps") { + } + + static TFetchingStepSignals GetSignals(const TString& name) { + return Singleton()->GetSignalsImpl(name); + } +}; + +class IFetchingStep: public TNonCopyable { +private: + YDB_READONLY_DEF(TString, Name); + YDB_READONLY(TDuration, SumDuration, TDuration::Zero()); + YDB_READONLY(ui64, SumSize, 0); + TFetchingStepSignals Signals; + +protected: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const = 0; + virtual TString DoDebugString() const { + return ""; + } + +public: + void AddDuration(const TDuration d) { + SumDuration += d; + Signals.AddDuration(d); + } + void AddDataSize(const ui64 size) { + SumSize += size; + Signals.AddBytes(size); + } + + virtual ~IFetchingStep() = default; + + [[nodiscard]] TConclusion ExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const { + return DoExecuteInplace(source, step); + } + + virtual ui64 GetProcessingDataSize(const std::shared_ptr& /*source*/) const { + return 0; + } + + IFetchingStep(const TString& name) + : Name(name) + , Signals(TFetchingStepsSignalsCollection::GetSignals(name)) { + } + + TString DebugString() const; +}; + +class TFetchingScript { +private: + YDB_ACCESSOR(TString, BranchName, "UNDEFINED"); + std::vector> Steps; + std::optional StartInstant; + std::optional FinishInstant; + +public: + TFetchingScript(const TSpecialReadContext& context); + + void Allocation(const std::set& entityIds, const EStageFeaturesIndexes stage, const EMemType mType); + + void AddStepDataSize(const ui32 index, const ui64 size) { + GetStep(index)->AddDataSize(size); + } + + void AddStepDuration(const ui32 index, const TDuration d) { + FinishInstant = TMonotonic::Now(); + GetStep(index)->AddDuration(d); + } + + void OnExecute() { + if (!StartInstant) { + StartInstant = TMonotonic::Now(); + } + } + + TString DebugString() const; + + const std::shared_ptr& GetStep(const ui32 index) const { + AFL_VERIFY(index < Steps.size()); + return Steps[index]; + } + + template + std::shared_ptr AddStep(Args... args) { + auto result = std::make_shared(args...); + Steps.emplace_back(result); + return result; + } + + template + std::shared_ptr InsertStep(const ui32 index, Args... args) { + AFL_VERIFY(index <= Steps.size())("index", index)("size", Steps.size()); + auto result = std::make_shared(args...); + Steps.insert(Steps.begin() + index, result); + return result; + } + + void AddStep(const std::shared_ptr& step) { + AFL_VERIFY(step); + Steps.emplace_back(step); + } + + bool IsFinished(const ui32 currentStepIdx) const { + AFL_VERIFY(currentStepIdx <= Steps.size()); + return currentStepIdx == Steps.size(); + } + + ui32 Execute(const ui32 startStepIdx, const std::shared_ptr& source) const; +}; + +class TColumnsAccumulator { +private: + TColumnsSetIds FetchingReadyColumns; + TColumnsSetIds AssemblerReadyColumns; + ISnapshotSchema::TPtr FullSchema; + std::shared_ptr GuaranteeNotOptional; + +public: + TColumnsAccumulator(const std::shared_ptr& guaranteeNotOptional, const ISnapshotSchema::TPtr& fullSchema) + : FullSchema(fullSchema) + , GuaranteeNotOptional(guaranteeNotOptional) { + } + + TColumnsSetIds GetNotFetchedAlready(const TColumnsSetIds& columns) const { + return columns - FetchingReadyColumns; + } + + bool AddFetchingStep(TFetchingScript& script, const TColumnsSetIds& columns, const EStageFeaturesIndexes stage); + bool AddAssembleStep(TFetchingScript& script, const TColumnsSetIds& columns, const TString& purposeId, const EStageFeaturesIndexes stage, + const bool sequential); +}; + +class TFetchingScriptCursor { +private: + std::optional CurrentStartInstant; + std::optional CurrentStartDataSize; + ui32 CurrentStepIdx = 0; + std::shared_ptr Script; + void FlushDuration() { + AFL_VERIFY(CurrentStartInstant); + AFL_VERIFY(CurrentStartDataSize); + Script->AddStepDuration(CurrentStepIdx, TMonotonic::Now() - *CurrentStartInstant); + Script->AddStepDataSize(CurrentStepIdx, *CurrentStartDataSize); + CurrentStartInstant.reset(); + CurrentStartDataSize.reset(); + } + +public: + TFetchingScriptCursor(const std::shared_ptr& script, const ui32 index) + : CurrentStepIdx(index) + , Script(script) { + AFL_VERIFY(!Script->IsFinished(CurrentStepIdx)); + } + + const TString& GetName() const { + return Script->GetStep(CurrentStepIdx)->GetName(); + } + + TString DebugString() const { + return Script->GetStep(CurrentStepIdx)->DebugString(); + } + + bool Next() { + FlushDuration(); + return !Script->IsFinished(++CurrentStepIdx); + } + + TConclusion Execute(const std::shared_ptr& source); +}; + +class TStepAction: public IDataTasksProcessor::ITask { +private: + using TBase = IDataTasksProcessor::ITask; + std::shared_ptr Source; + TFetchingScriptCursor Cursor; + bool FinishedFlag = false; + const NColumnShard::TCounterGuard CountersGuard; + +protected: + virtual bool DoApply(IDataReader& owner) const override; + virtual TConclusionStatus DoExecuteImpl() override; + +public: + virtual TString GetTaskClassIdentifier() const override { + return "STEP_ACTION"; + } + + template + TStepAction(const std::shared_ptr& source, TFetchingScriptCursor&& cursor, const NActors::TActorId& ownerActorId) + : TStepAction(std::static_pointer_cast(source), std::move(cursor), ownerActorId) { + } + TStepAction(const std::shared_ptr& source, TFetchingScriptCursor&& cursor, const NActors::TActorId& ownerActorId); +}; + +} // namespace NKikimr::NOlap::NReader::NCommon diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/iterator.cpp b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/iterator.cpp new file mode 100644 index 000000000000..de8bf3830758 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/iterator.cpp @@ -0,0 +1,49 @@ +#include "iterator.h" + +#include + +namespace NKikimr::NOlap::NReader::NCommon { + +TColumnShardScanIterator::TColumnShardScanIterator(const std::shared_ptr& context, const TReadMetadata::TConstPtr& readMetadata) + : Context(context) + , ReadMetadata(readMetadata) + , ReadyResults(context->GetCounters()) { + IndexedData = readMetadata->BuildReader(Context); + Y_ABORT_UNLESS(Context->GetReadMetadata()->IsSorted()); +} + +TConclusion> TColumnShardScanIterator::GetBatch() { + FillReadyResults(); + return ReadyResults.pop_front(); +} + +void TColumnShardScanIterator::PrepareResults() { + FillReadyResults(); +} + +TConclusion TColumnShardScanIterator::ReadNextInterval() { + return IndexedData->ReadNextInterval(); +} + +void TColumnShardScanIterator::DoOnSentDataFromInterval(const ui32 intervalIdx) const { + return IndexedData->OnSentDataFromInterval(intervalIdx); +} + +TColumnShardScanIterator::~TColumnShardScanIterator() { + if (!IndexedData->IsFinished()) { + IndexedData->Abort("iterator destructor"); + } + ReadMetadata->ReadStats->PrintToLog(); +} + +void TColumnShardScanIterator::Apply(const std::shared_ptr& task) { + if (!IndexedData->IsFinished()) { + Y_ABORT_UNLESS(task->Apply(*IndexedData)); + } +} + +const TReadStats& TColumnShardScanIterator::GetStats() const { + return *ReadMetadata->ReadStats; +} + +} // namespace NKikimr::NOlap::NReader::NCommon diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/iterator.h b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/iterator.h new file mode 100644 index 000000000000..5de306cab085 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/iterator.h @@ -0,0 +1,105 @@ +#pragma once +#include +#include +#include + +namespace NKikimr::NOlap::NReader::NCommon { + +class TReadMetadata; + +class TReadyResults { +private: + const NColumnShard::TConcreteScanCounters Counters; + std::deque> Data; + i64 RecordsCount = 0; +public: + TString DebugString() const { + TStringBuilder sb; + sb + << "count:" << Data.size() << ";" + << "records_count:" << RecordsCount << ";" + ; + if (Data.size()) { + sb << "schema=" << Data.front()->GetResultBatch().schema()->ToString() << ";"; + } + return sb; + } + TReadyResults(const NColumnShard::TConcreteScanCounters& counters) + : Counters(counters) + { + + } + const std::shared_ptr& emplace_back(std::shared_ptr&& v) { + AFL_VERIFY(!!v); + RecordsCount += v->GetResultBatch().num_rows(); + Data.emplace_back(std::move(v)); + return Data.back(); + } + std::shared_ptr pop_front() { + if (Data.empty()) { + return {}; + } + auto result = std::move(Data.front()); + AFL_VERIFY(RecordsCount >= result->GetResultBatch().num_rows()); + RecordsCount -= result->GetResultBatch().num_rows(); + Data.pop_front(); + return result; + } + bool empty() const { + return Data.empty(); + } + size_t size() const { + return Data.size(); + } +}; + +class TColumnShardScanIterator: public TScanIteratorBase { +private: + virtual void DoOnSentDataFromInterval(const ui32 intervalIdx) const override; + +protected: + ui64 ItemsRead = 0; + const i64 MaxRowsInBatch = 5000; + std::shared_ptr Context; + std::shared_ptr ReadMetadata; + TReadyResults ReadyResults; + std::shared_ptr IndexedData; + +public: + TColumnShardScanIterator(const std::shared_ptr& context, const std::shared_ptr& readMetadata); + ~TColumnShardScanIterator(); + + virtual TConclusionStatus Start() override { + AFL_VERIFY(IndexedData); + return IndexedData->Start(); + } + + virtual std::optional GetAvailableResultsCount() const override { + return ReadyResults.size(); + } + + virtual const TReadStats& GetStats() const override; + + virtual TString DebugString(const bool verbose) const override { + return TStringBuilder() + << "ready_results:(" << ReadyResults.DebugString() << ");" + << "indexed_data:(" << IndexedData->DebugString(verbose) << ")" + ; + } + + virtual void Apply(const std::shared_ptr& task) override; + + bool Finished() const override { + return IndexedData->IsFinished() && ReadyResults.empty(); + } + + virtual TConclusion> GetBatch() override; + virtual void PrepareResults() override; + + virtual TConclusion ReadNextInterval() override; + +private: + virtual void FillReadyResults() = 0; +}; + +} diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/source.cpp b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/source.cpp new file mode 100644 index 000000000000..112b8a812d07 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/source.cpp @@ -0,0 +1,5 @@ +#include "source.h" + +namespace NKikimr::NOlap::NReader::NCommon { + +} // namespace NKikimr::NOlap::NReader::NCommon diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/source.h b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/source.h new file mode 100644 index 000000000000..473b1ecc5b51 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/source.h @@ -0,0 +1,208 @@ +#pragma once +#include "context.h" +#include "fetched_data.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace NKikimr::NOlap { +class IDataReader; +} + +namespace NKikimr::NOlap::NReader::NCommon { + +class TFetchingScriptCursor; + +class IDataSource: public ICursorEntity { +private: + YDB_READONLY(ui64, SourceId, 0); + YDB_READONLY(ui32, SourceIdx, 0); + YDB_READONLY(TSnapshot, RecordSnapshotMin, TSnapshot::Zero()); + YDB_READONLY(TSnapshot, RecordSnapshotMax, TSnapshot::Zero()); + YDB_READONLY_DEF(std::shared_ptr, Context); + YDB_READONLY(ui32, RecordsCount, 0); + YDB_READONLY_DEF(std::optional, ShardingVersionOptional); + YDB_READONLY(bool, HasDeletions, false); + std::optional MemoryGroupId; + + virtual bool DoAddTxConflict() = 0; + + virtual ui64 DoGetEntityId() const override { + return SourceId; + } + + virtual ui64 DoGetEntityRecordsCount() const override { + return RecordsCount; + } + + std::optional IsSourceInMemoryFlag; + TAtomic SourceFinishedSafeFlag = 0; + TAtomic StageResultBuiltFlag = 0; + virtual void DoOnSourceFetchingFinishedSafe(IDataReader& owner, const std::shared_ptr& sourcePtr) = 0; + virtual void DoBuildStageResult(const std::shared_ptr& sourcePtr) = 0; + virtual void DoOnEmptyStageData(const std::shared_ptr& sourcePtr) = 0; + + virtual bool DoStartFetchingColumns( + const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const TColumnsSetIds& columns) = 0; + virtual void DoAssembleColumns(const std::shared_ptr& columns, const bool sequential) = 0; + +protected: + std::vector> ResourceGuards; + std::unique_ptr StageData; + std::unique_ptr StageResult; + +public: + IDataSource(const ui64 sourceId, const ui32 sourceIdx, const std::shared_ptr& context, + const TSnapshot& recordSnapshotMin, const TSnapshot& recordSnapshotMax, const ui32 recordsCount, + const std::optional shardingVersion, const bool hasDeletions) + : SourceId(sourceId) + , SourceIdx(sourceIdx) + , RecordSnapshotMin(recordSnapshotMin) + , RecordSnapshotMax(recordSnapshotMax) + , Context(context) + , RecordsCount(recordsCount) + , ShardingVersionOptional(shardingVersion) + , HasDeletions(hasDeletions) { + } + + virtual ~IDataSource() = default; + + const std::vector>& GetResourceGuards() const { + return ResourceGuards; + } + + virtual THashMap DecodeBlobAddresses(NBlobOperations::NRead::TCompositeReadBlobs&& blobsOriginal) const = 0; + + bool IsSourceInMemory() const { + AFL_VERIFY(IsSourceInMemoryFlag); + return *IsSourceInMemoryFlag; + } + void SetSourceInMemory(const bool value) { + AFL_VERIFY(!IsSourceInMemoryFlag); + IsSourceInMemoryFlag = value; + if (!value) { + AFL_VERIFY(StageData); + StageData->SetUseFilter(value); + } + } + + void SetMemoryGroupId(const ui64 groupId) { + AFL_VERIFY(!MemoryGroupId); + MemoryGroupId = groupId; + } + + ui64 GetMemoryGroupId() const { + AFL_VERIFY(!!MemoryGroupId); + return *MemoryGroupId; + } + + virtual ui64 GetColumnsVolume(const std::set& columnIds, const EMemType type) const = 0; + + ui64 GetResourceGuardsMemory() const { + ui64 result = 0; + for (auto&& i : ResourceGuards) { + result += i->GetMemory(); + } + return result; + } + void RegisterAllocationGuard(const std::shared_ptr& guard) { + ResourceGuards.emplace_back(guard); + } + virtual ui64 GetColumnRawBytes(const std::set& columnIds) const = 0; + virtual ui64 GetColumnBlobBytes(const std::set& columnsIds) const = 0; + + void AssembleColumns(const std::shared_ptr& columns, const bool sequential = false) { + if (columns->IsEmpty()) { + return; + } + DoAssembleColumns(columns, sequential); + } + + bool StartFetchingColumns(const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const TColumnsSetIds& columns) { + return DoStartFetchingColumns(sourcePtr, step, columns); + } + + void OnSourceFetchingFinishedSafe(IDataReader& owner, const std::shared_ptr& sourcePtr) { + AFL_VERIFY(AtomicCas(&SourceFinishedSafeFlag, 1, 0)); + AFL_VERIFY(sourcePtr); + DoOnSourceFetchingFinishedSafe(owner, sourcePtr); + } + + void OnEmptyStageData(const std::shared_ptr& sourcePtr) { + AFL_VERIFY(AtomicCas(&StageResultBuiltFlag, 1, 0)); + AFL_VERIFY(sourcePtr); + AFL_VERIFY(!StageResult); + AFL_VERIFY(StageData); + DoOnEmptyStageData(sourcePtr); + AFL_VERIFY(StageResult); + AFL_VERIFY(!StageData); + } + + template + void BuildStageResult(const std::shared_ptr& sourcePtr) { + BuildStageResult(std::static_pointer_cast(sourcePtr)); + } + + void BuildStageResult(const std::shared_ptr& sourcePtr) { + TMemoryProfileGuard mpg("SCAN_PROFILE::STAGE_RESULT", IS_DEBUG_LOG_ENABLED(NKikimrServices::TX_COLUMNSHARD_SCAN_MEMORY)); + AFL_VERIFY(AtomicCas(&StageResultBuiltFlag, 1, 0)); + AFL_VERIFY(sourcePtr); + AFL_VERIFY(!StageResult); + AFL_VERIFY(StageData); + DoBuildStageResult(sourcePtr); + AFL_VERIFY(StageResult); + AFL_VERIFY(!StageData); + } + + bool AddTxConflict() { + if (!Context->GetCommonContext()->HasLock()) { + return false; + } + if (DoAddTxConflict()) { + StageData->Clear(); + return true; + } + return false; + } + + bool HasStageData() const { + return !!StageData; + } + + const TFetchedData& GetStageData() const { + AFL_VERIFY(StageData); + return *StageData; + } + + TFetchedData& MutableStageData() { + AFL_VERIFY(StageData); + return *StageData; + } + + bool HasStageResult() const { + return !!StageResult; + } + + const TFetchedResult& GetStageResult() const { + AFL_VERIFY(!!StageResult); + return *StageResult; + } + + TFetchedResult& MutableStageResult() { + AFL_VERIFY(!!StageResult); + return *StageResult; + } +}; + +} // namespace NKikimr::NOlap::NReader::NCommon diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/ya.make b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/ya.make new file mode 100644 index 000000000000..d0b8b414622e --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/iterator/ya.make @@ -0,0 +1,20 @@ +LIBRARY() + +SRCS( + columns_set.cpp + constructor.cpp + context.cpp + fetch_steps.cpp + fetched_data.cpp + fetching.cpp + iterator.cpp + source.cpp +) + +PEERDIR( + ydb/core/tx/columnshard/engines/scheme +) + +GENERATE_ENUM_SERIALIZATION(columns_set.h) + +END() diff --git a/ydb/core/tx/columnshard/engines/reader/common_reader/ya.make b/ydb/core/tx/columnshard/engines/reader/common_reader/ya.make new file mode 100644 index 000000000000..d974b7efac13 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/common_reader/ya.make @@ -0,0 +1,11 @@ +LIBRARY() + +SRCS( +) + +PEERDIR( + ydb/core/tx/columnshard/engines/reader/common_reader/iterator + ydb/core/tx/columnshard/engines/reader/common_reader/constructor +) + +END() diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/constructor.cpp b/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/constructor.cpp index ae28340c9932..e343b4674d8d 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/constructor.cpp +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/constructor.cpp @@ -3,13 +3,14 @@ #include "resolver.h" #include +#include namespace NKikimr::NOlap::NReader::NPlain { NKikimr::TConclusionStatus TIndexScannerConstructor::ParseProgram( const TVersionedIndex* vIndex, const NKikimrTxDataShard::TEvKqpScan& proto, TReadDescription& read) const { AFL_VERIFY(vIndex); - auto& indexInfo = vIndex->GetSchema(Snapshot)->GetIndexInfo(); + auto& indexInfo = vIndex->GetSchemaVerified(Snapshot)->GetIndexInfo(); TIndexColumnResolver columnResolver(indexInfo); return TBase::ParseProgram(vIndex, proto.GetOlapProgramType(), proto.GetOlapProgram(), read, columnResolver); } @@ -35,7 +36,7 @@ NKikimr::TConclusion> TIndexScannerConstructo TDataStorageAccessor dataAccessor(insertTable, index); AFL_VERIFY(read.PathId); auto readMetadata = std::make_shared(read.PathId, index->CopyVersionedIndexPtr(), read.GetSnapshot(), - IsReverse ? TReadMetadataBase::ESorting::DESC : TReadMetadataBase::ESorting::ASC, read.GetProgram()); + IsReverse ? TReadMetadataBase::ESorting::DESC : TReadMetadataBase::ESorting::ASC, read.GetProgram(), nullptr); auto initResult = readMetadata->Init(self, read, dataAccessor); if (!initResult) { @@ -44,4 +45,8 @@ NKikimr::TConclusion> TIndexScannerConstructo return static_pointer_cast(readMetadata); } +std::shared_ptr TIndexScannerConstructor::DoBuildCursor() const { + return std::make_shared(); +} + } // namespace NKikimr::NOlap::NReader::NPlain diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/constructor.h b/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/constructor.h index bb576fdbdc70..3a534cd0d936 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/constructor.h +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/constructor.h @@ -4,14 +4,23 @@ namespace NKikimr::NOlap::NReader::NPlain { class TIndexScannerConstructor: public IScannerConstructor { +public: + static TString GetClassNameStatic() { + return "PLAIN"; + } private: using TBase = IScannerConstructor; + static const inline TFactory::TRegistrator Registrator = + TFactory::TRegistrator(GetClassNameStatic()); + + virtual std::shared_ptr DoBuildCursor() const override; + protected: virtual TConclusion> DoBuildReadMetadata(const NColumnShard::TColumnShard* self, const TReadDescription& read) const override; public: - using TBase::TBase; virtual TConclusionStatus ParseProgram(const TVersionedIndex* vIndex, const NKikimrTxDataShard::TEvKqpScan& proto, TReadDescription& read) const override; virtual std::vector GetPrimaryKeyScheme(const NColumnShard::TColumnShard* self) const override; + using TBase::TBase; }; } \ No newline at end of file diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/read_metadata.cpp b/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/read_metadata.cpp index 5d93272c07b7..8ac0322909b8 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/read_metadata.cpp +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/read_metadata.cpp @@ -1,11 +1,8 @@ #include "read_metadata.h" -#include #include #include #include -#include -#include namespace NKikimr::NOlap::NReader::NPlain { @@ -13,22 +10,8 @@ std::unique_ptr TReadMetadata::StartScan(const std::shared_pt return std::make_unique(readContext, readContext->GetReadMetadataPtrVerifiedAs()); } -TConclusionStatus TReadMetadata::Init( +TConclusionStatus TReadMetadata::DoInitCustom( const NColumnShard::TColumnShard* owner, const TReadDescription& readDescription, const TDataStorageAccessor& dataAccessor) { - SetPKRangesFilter(readDescription.PKRangesFilter); - InitShardingInfo(readDescription.PathId); - TxId = readDescription.TxId; - LockId = readDescription.LockId; - if (LockId) { - owner->GetOperationsManager().RegisterLock(*LockId, owner->Generation()); - LockSharingInfo = owner->GetOperationsManager().GetLockVerified(*LockId).GetSharingInfo(); - } - - /// @note We could have column name changes between schema versions: - /// Add '1:foo', Drop '1:foo', Add '2:foo'. Drop should hide '1:foo' from reads. - /// It's expected that we have only one version on 'foo' in blob and could split them by schema {planStep:txId}. - /// So '1:foo' would be omitted in blob records for the column in new snapshots. And '2:foo' - in old ones. - /// It's not possible for blobs with several columns. There should be a special logic for them. CommittedBlobs = dataAccessor.GetCommitedBlobs(readDescription, ResultIndexSchema->GetIndexInfo().GetReplaceKey(), LockId, GetRequestSnapshot()); @@ -44,89 +27,11 @@ TConclusionStatus TReadMetadata::Init( } } - SelectInfo = dataAccessor.Select(readDescription); - StatsMode = readDescription.StatsMode; return TConclusionStatus::Success(); } -std::set TReadMetadata::GetEarlyFilterColumnIds() const { - auto& indexInfo = ResultIndexSchema->GetIndexInfo(); - std::set result; - for (auto&& i : GetProgram().GetEarlyFilterColumns()) { - auto id = indexInfo.GetColumnIdOptional(i); - if (id) { - result.emplace(*id); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("early_filter_column", i); - } - } - return result; -} - -std::set TReadMetadata::GetPKColumnIds() const { - std::set result; - auto& indexInfo = ResultIndexSchema->GetIndexInfo(); - for (auto&& i : indexInfo.GetPrimaryKeyColumns()) { - Y_ABORT_UNLESS(result.emplace(indexInfo.GetColumnIdVerified(i.first)).second); - } - return result; -} - std::shared_ptr TReadMetadata::BuildReader(const std::shared_ptr& context) const { return std::make_shared(context); } -NArrow::NMerger::TSortableBatchPosition TReadMetadata::BuildSortedPosition(const NArrow::TReplaceKey& key) const { - return NArrow::NMerger::TSortableBatchPosition(key.ToBatch(GetReplaceKey()), 0, GetReplaceKey()->field_names(), {}, IsDescSorted()); -} - -void TReadMetadata::DoOnReadFinished(NColumnShard::TColumnShard& owner) const { - if (!GetLockId()) { - return; - } - const ui64 lock = *GetLockId(); - if (GetBrokenWithCommitted()) { - owner.GetOperationsManager().GetLockVerified(lock).SetBroken(); - } else { - NOlap::NTxInteractions::TTxConflicts conflicts; - for (auto&& i : GetConflictableLockIds()) { - conflicts.Add(i, lock); - } - auto writer = std::make_shared(PathId, conflicts); - owner.GetOperationsManager().AddEventForLock(owner, lock, writer); - } -} - -void TReadMetadata::DoOnBeforeStartReading(NColumnShard::TColumnShard& owner) const { - if (!LockId) { - return; - } - auto evWriter = std::make_shared( - PathId, GetResultSchema()->GetIndexInfo().GetPrimaryKey(), GetPKRangesFilterPtr(), GetConflictableLockIds()); - owner.GetOperationsManager().AddEventForLock(owner, *LockId, evWriter); -} - -void TReadMetadata::DoOnReplyConstruction(const ui64 tabletId, NKqp::NInternalImplementation::TEvScanData& scanData) const { - if (LockSharingInfo) { - NKikimrDataEvents::TLock lockInfo; - lockInfo.SetLockId(LockSharingInfo->GetLockId()); - lockInfo.SetGeneration(LockSharingInfo->GetGeneration()); - lockInfo.SetDataShard(tabletId); - lockInfo.SetCounter(LockSharingInfo->GetCounter()); - lockInfo.SetPathId(PathId); - lockInfo.SetHasWrites(LockSharingInfo->HasWrites()); - if (LockSharingInfo->IsBroken()) { - scanData.LocksInfo.BrokenLocks.emplace_back(std::move(lockInfo)); - } else { - scanData.LocksInfo.Locks.emplace_back(std::move(lockInfo)); - } - } -} - -bool TReadMetadata::IsMyUncommitted(const TInsertWriteId writeId) const { - AFL_VERIFY(LockSharingInfo); - auto it = ConflictedWriteIds.find(writeId); - AFL_VERIFY(it != ConflictedWriteIds.end())("write_id", writeId)("write_ids_count", ConflictedWriteIds.size()); - return it->second.GetLockId() == LockSharingInfo->GetLockId(); -} - } // namespace NKikimr::NOlap::NReader::NPlain diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/read_metadata.h b/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/read_metadata.h index 5f5ad70db296..b0242d486aaa 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/read_metadata.h +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/read_metadata.h @@ -1,9 +1,5 @@ #pragma once -#include -#include -#include -#include -#include +#include namespace NKikimr::NColumnShard { class TLockSharingInfo; @@ -12,168 +8,28 @@ class TLockSharingInfo; namespace NKikimr::NOlap::NReader::NPlain { // Holds all metadata that is needed to perform read/scan -struct TReadMetadata : public TReadMetadataBase { - using TBase = TReadMetadataBase; - +class TReadMetadata: public NCommon::TReadMetadata { private: - const ui64 PathId; - std::shared_ptr BrokenWithCommitted = std::make_shared(); - std::shared_ptr LockSharingInfo; - - class TWriteIdInfo { - private: - const ui64 LockId; - std::shared_ptr Conflicts; - - public: - TWriteIdInfo(const ui64 lockId, const std::shared_ptr& counter) - : LockId(lockId) - , Conflicts(counter) { - } - - ui64 GetLockId() const { - return LockId; - } - - void MarkAsConflictable() const { - Conflicts->Inc(); - } - - bool IsConflictable() const { - return Conflicts->Val(); - } - }; - - THashMap> LockConflictCounters; - THashMap ConflictedWriteIds; - - virtual void DoOnReadFinished(NColumnShard::TColumnShard& owner) const override; - virtual void DoOnBeforeStartReading(NColumnShard::TColumnShard& owner) const override; - virtual void DoOnReplyConstruction(const ui64 tabletId, NKqp::NInternalImplementation::TEvScanData& scanData) const override; + using TBase = NCommon::TReadMetadata; + virtual TConclusionStatus DoInitCustom( + const NColumnShard::TColumnShard* owner, const TReadDescription& readDescription, const TDataStorageAccessor& dataAccessor) override; public: using TConstPtr = std::shared_ptr; + using TBase::TBase; - bool GetBrokenWithCommitted() const { - return BrokenWithCommitted->Val(); - } - THashSet GetConflictableLockIds() const { - THashSet result; - for (auto&& i : ConflictedWriteIds) { - if (i.second.IsConflictable()) { - result.emplace(i.second.GetLockId()); - } - } - return result; - } - - bool IsLockConflictable(const ui64 lockId) const { - auto it = LockConflictCounters.find(lockId); - AFL_VERIFY(it != LockConflictCounters.end()); - return it->second->Val(); - } - - bool IsWriteConflictable(const TInsertWriteId writeId) const { - auto it = ConflictedWriteIds.find(writeId); - AFL_VERIFY(it != ConflictedWriteIds.end()); - return it->second.IsConflictable(); - } - - void AddWriteIdToCheck(const TInsertWriteId writeId, const ui64 lockId) { - auto it = LockConflictCounters.find(lockId); - if (it == LockConflictCounters.end()) { - it = LockConflictCounters.emplace(lockId, std::make_shared()).first; - } - AFL_VERIFY(ConflictedWriteIds.emplace(writeId, TWriteIdInfo(lockId, it->second)).second); - } - - [[nodiscard]] bool IsMyUncommitted(const TInsertWriteId writeId) const; - - void SetConflictedWriteId(const TInsertWriteId writeId) const { - auto it = ConflictedWriteIds.find(writeId); - AFL_VERIFY(it != ConflictedWriteIds.end()); - it->second.MarkAsConflictable(); - } - - void SetBrokenWithCommitted() const { - BrokenWithCommitted->Inc(); - } - - NArrow::NMerger::TSortableBatchPosition BuildSortedPosition(const NArrow::TReplaceKey& key) const; - std::shared_ptr BuildReader(const std::shared_ptr& context) const; - - bool HasProcessingColumnIds() const { - return GetProgram().HasProcessingColumnIds(); - } - - ui64 GetPathId() const { - return PathId; - } - - std::shared_ptr SelectInfo; - NYql::NDqProto::EDqStatsMode StatsMode = NYql::NDqProto::EDqStatsMode::DQ_STATS_MODE_NONE; std::vector CommittedBlobs; - std::shared_ptr ReadStats; - - TReadMetadata(const ui64 pathId, const std::shared_ptr info, const TSnapshot& snapshot, const ESorting sorting, const TProgramContainer& ssaProgram) - : TBase(info, sorting, ssaProgram, info->GetSchema(snapshot), snapshot) - , PathId(pathId) - , ReadStats(std::make_shared()) - { - } - - virtual std::vector GetKeyYqlSchema() const override { - return GetResultSchema()->GetIndexInfo().GetPrimaryKeyColumns(); - } - - TConclusionStatus Init(const NColumnShard::TColumnShard* owner, const TReadDescription& readDescription, const TDataStorageAccessor& dataAccessor); - - std::vector GetColumnsOrder() const { - auto schema = GetResultSchema(); - std::vector result; - for (auto&& i : schema->GetSchema()->fields()) { - result.emplace_back(i->name()); - } - return result; - } - - std::set GetEarlyFilterColumnIds() const; - std::set GetPKColumnIds() const; - - bool Empty() const { + virtual bool Empty() const override { Y_ABORT_UNLESS(SelectInfo); - return SelectInfo->PortionsOrderedPK.empty() && CommittedBlobs.empty(); + return SelectInfo->Portions.empty() && CommittedBlobs.empty(); } - size_t NumIndexedChunks() const { - Y_ABORT_UNLESS(SelectInfo); - return SelectInfo->NumChunks(); - } + virtual std::shared_ptr BuildReader(const std::shared_ptr& context) const override; + virtual std::unique_ptr StartScan(const std::shared_ptr& readContext) const override; - size_t NumIndexedBlobs() const { - Y_ABORT_UNLESS(SelectInfo); - return SelectInfo->Stats().Blobs; - } - - std::unique_ptr StartScan(const std::shared_ptr& readContext) const override; - - void Dump(IOutputStream& out) const override { - out << " index chunks: " << NumIndexedChunks() - << " index blobs: " << NumIndexedBlobs() - << " committed blobs: " << CommittedBlobs.size() - // << " with program steps: " << (Program ? Program->Steps.size() : 0) - << " at snapshot: " << GetRequestSnapshot().DebugString(); - TBase::Dump(out); - if (SelectInfo) { - out << ", "; - SelectInfo->DebugStream(out); - } - } - - friend IOutputStream& operator << (IOutputStream& out, const TReadMetadata& meta) { - meta.Dump(out); - return out; + virtual TString DebugString() const override { + return TBase::DebugString() + ";committed=" + ::ToString(CommittedBlobs.size()); } }; -} +} // namespace NKikimr::NOlap::NReader::NPlain diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/ya.make b/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/ya.make index 1ab826414813..165408de6d67 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/ya.make +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/constructor/ya.make @@ -1,13 +1,14 @@ LIBRARY() SRCS( - constructor.cpp + GLOBAL constructor.cpp resolver.cpp read_metadata.cpp ) PEERDIR( ydb/core/tx/columnshard/engines/reader/abstract + ydb/core/tx/columnshard/engines/reader/common_reader/constructor ydb/core/kqp/compute_actor ) diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/constructor.cpp b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/constructor.cpp deleted file mode 100644 index 654315a1ab0b..000000000000 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/constructor.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "constructor.h" -#include -#include - -namespace NKikimr::NOlap::NReader::NPlain { - -void TBlobsFetcherTask::DoOnDataReady(const std::shared_ptr& /*resourcesGuard*/) { - Source->MutableStageData().AddBlobs(Source->DecodeBlobAddresses(ExtractBlobsData())); - AFL_VERIFY(Step.Next()); - auto task = std::make_shared(Source, std::move(Step), Context->GetCommonContext()->GetScanActorId()); - NConveyor::TScanServiceOperator::SendTaskToExecute(task); -} - -bool TBlobsFetcherTask::DoOnError(const TString& storageId, const TBlobRange& range, const IBlobsReadingAction::TErrorStatus& status) { - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD_SCAN)("error_on_blob_reading", range.ToString())("scan_actor_id", Context->GetCommonContext()->GetScanActorId()) - ("status", status.GetErrorMessage())("status_code", status.GetStatus())("storage_id", storageId); - NActors::TActorContext::AsActorContext().Send(Context->GetCommonContext()->GetScanActorId(), - std::make_unique(TConclusionStatus::Fail("cannot read blob range " + range.ToString()))); - return false; -} - -} diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/context.cpp b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/context.cpp index 0efd8bfbb9d2..bd7816f177f0 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/context.cpp +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/context.cpp @@ -1,37 +1,42 @@ #include "context.h" #include "source.h" +#include +#include #include namespace NKikimr::NOlap::NReader::NPlain { std::unique_ptr TSpecialReadContext::BuildMerger() const { - return std::make_unique( - ReadMetadata->GetReplaceKey(), ProgramInputColumns->GetSchema(), CommonContext->IsReverse(), IIndexInfo::GetSnapshotColumnNames()); + return std::make_unique(GetReadMetadata()->GetReplaceKey(), GetProgramInputColumns()->GetSchema(), + GetCommonContext()->IsReverse(), IIndexInfo::GetSnapshotColumnNames()); } ui64 TSpecialReadContext::GetMemoryForSources(const THashMap>& sources) { ui64 result = 0; - bool hasSequentialReadSources = false; for (auto&& i : sources) { - auto fetchingPlan = GetColumnsFetchingPlan(i.second); AFL_VERIFY(i.second->GetIntervalsCount()); const ui64 sourceMemory = std::max(1, i.second->GetResourceGuardsMemory() / i.second->GetIntervalsCount()); - if (!i.second->IsSourceInMemory()) { - hasSequentialReadSources = true; - } result += sourceMemory; } AFL_VERIFY(result); - if (hasSequentialReadSources) { - result += ReadSequentiallyBufferSize; - } + result += ReadSequentiallyBufferSize; return result; } -std::shared_ptr TSpecialReadContext::GetColumnsFetchingPlan(const std::shared_ptr& source) { - const bool needSnapshots = !source->GetExclusiveIntervalOnly() || ReadMetadata->GetRequestSnapshot() < source->GetRecordSnapshotMax() || - !source->IsSourceInMemory(); +std::shared_ptr TSpecialReadContext::DoGetColumnsFetchingPlan(const std::shared_ptr& sourceExt) { + auto source = std::static_pointer_cast(sourceExt); + if (source->NeedAccessorsFetching()) { + if (!AskAccumulatorsScript) { + AskAccumulatorsScript = std::make_shared(*this); + if (ui64 size = source->PredictAccessorsMemory()) { + AskAccumulatorsScript->AddStep(size, EStageFeaturesIndexes::Accessors); + } + AskAccumulatorsScript->AddStep(); + AskAccumulatorsScript->AddStep(*GetFFColumns()); + } + return AskAccumulatorsScript; + } const bool partialUsageByPK = [&]() { switch (source->GetUsageClass()) { case TPKRangeFilter::EUsageClass::PartialUsage: @@ -44,312 +49,182 @@ std::shared_ptr TSpecialReadContext::GetColumnsFetchingPlan(con }(); const bool useIndexes = (IndexChecker ? source->HasIndexes(IndexChecker->GetIndexIds()) : false); const bool isWholeExclusiveSource = source->GetExclusiveIntervalOnly() && source->IsSourceInMemory(); + const bool needSnapshots = GetReadMetadata()->GetRequestSnapshot() < source->GetRecordSnapshotMax() || !isWholeExclusiveSource; const bool hasDeletions = source->GetHasDeletions(); bool needShardingFilter = false; - if (!!ReadMetadata->GetRequestShardingInfo()) { + if (!!GetReadMetadata()->GetRequestShardingInfo()) { auto ver = source->GetShardingVersionOptional(); - if (!ver || *ver < ReadMetadata->GetRequestShardingInfo()->GetSnapshotVersion()) { + if (!ver || *ver < GetReadMetadata()->GetRequestShardingInfo()->GetSnapshotVersion()) { needShardingFilter = true; } } - auto result = CacheFetchingScripts[needSnapshots ? 1 : 0][isWholeExclusiveSource ? 1 : 0][partialUsageByPK ? 1 : 0][useIndexes ? 1 : 0] - [needShardingFilter ? 1 : 0][hasDeletions ? 1 : 0]; - if (!result) { - result = BuildColumnsFetchingPlan(needSnapshots, isWholeExclusiveSource, partialUsageByPK, useIndexes, needShardingFilter, hasDeletions); - CacheFetchingScripts[needSnapshots ? 1 : 0][isWholeExclusiveSource ? 1 : 0][partialUsageByPK ? 1 : 0][useIndexes ? 1 : 0] - [needShardingFilter ? 1 : 0][hasDeletions ? 1 : 0] = result; - } - AFL_VERIFY(result); - if (*result) { - return *result; - } else { - std::shared_ptr result = std::make_shared(*this); - result->SetBranchName("FAKE"); - result->AddStep(std::make_shared(source->GetRecordsCount())); - return result; - } -} - -class TColumnsAccumulator { -private: - TColumnsSetIds FetchingReadyColumns; - TColumnsSetIds AssemblerReadyColumns; - ISnapshotSchema::TPtr FullSchema; - std::shared_ptr GuaranteeNotOptional; - -public: - TColumnsAccumulator(const std::shared_ptr& guaranteeNotOptional, const ISnapshotSchema::TPtr& fullSchema) - : FullSchema(fullSchema) - , GuaranteeNotOptional(guaranteeNotOptional) { - } - - bool AddFetchingStep(TFetchingScript& script, const TColumnsSetIds& columns, const EStageFeaturesIndexes& stage) { - auto actualColumns = (TColumnsSetIds)columns - FetchingReadyColumns; - FetchingReadyColumns = FetchingReadyColumns + (TColumnsSetIds)columns; - if (!actualColumns.IsEmpty()) { - script.AddStep(std::make_shared(actualColumns, stage)); - script.AddStep(std::make_shared(actualColumns)); - return true; - } - return false; - } - bool AddAssembleStep(TFetchingScript& script, const TColumnsSetIds& columns, const TString& purposeId, const bool optional) { - auto actualColumns = (TColumnsSetIds)columns - AssemblerReadyColumns; - AssemblerReadyColumns = AssemblerReadyColumns + columns; - if (!actualColumns.IsEmpty()) { - auto actualSet = std::make_shared(actualColumns.GetColumnIds(), FullSchema); - if (optional) { - const auto notOptionalColumnIds = GuaranteeNotOptional->Intersect(*actualSet); - if (notOptionalColumnIds.size()) { - std::shared_ptr cross = actualSet->BuildSamePtr(notOptionalColumnIds); - script.AddStep(std::make_shared(cross, purposeId)); - *actualSet = *actualSet - *cross; - } - if (!actualSet->IsEmpty()) { - script.AddStep(std::make_shared(actualSet, purposeId)); - } - } else { - script.AddStep(std::make_shared(actualSet, purposeId)); + { + auto result = CacheFetchingScripts[needSnapshots ? 1 : 0][isWholeExclusiveSource ? 1 : 0][partialUsageByPK ? 1 : 0][useIndexes ? 1 : 0] + [needShardingFilter ? 1 : 0][hasDeletions ? 1 : 0]; + if (!result) { + TGuard wg(Mutex); + result = CacheFetchingScripts[needSnapshots ? 1 : 0][isWholeExclusiveSource ? 1 : 0][partialUsageByPK ? 1 : 0][useIndexes ? 1 : 0] + [needShardingFilter ? 1 : 0][hasDeletions ? 1 : 0]; + if (!result) { + result = BuildColumnsFetchingPlan( + needSnapshots, isWholeExclusiveSource, partialUsageByPK, useIndexes, needShardingFilter, hasDeletions); + CacheFetchingScripts[needSnapshots ? 1 : 0][isWholeExclusiveSource ? 1 : 0][partialUsageByPK ? 1 : 0][useIndexes ? 1 : 0] + [needShardingFilter ? 1 : 0][hasDeletions ? 1 : 0] = result; } - return true; } - return false; + AFL_VERIFY(result); + if (*result) { + return *result; + } else { + std::shared_ptr result = std::make_shared(*this); + result->SetBranchName("FAKE"); + result->AddStep(); + return result; + } } -}; +} std::shared_ptr TSpecialReadContext::BuildColumnsFetchingPlan(const bool needSnapshots, const bool exclusiveSource, const bool partialUsageByPredicateExt, const bool useIndexes, const bool needFilterSharding, const bool needFilterDeletion) const { std::shared_ptr result = std::make_shared(*this); - const bool partialUsageByPredicate = partialUsageByPredicateExt && PredicateColumns->GetColumnsCount(); + const bool partialUsageByPredicate = partialUsageByPredicateExt && GetPredicateColumns()->GetColumnsCount(); + + NCommon::TColumnsAccumulator acc(GetMergeColumns(), GetReadMetadata()->GetResultSchema()); if (!!IndexChecker && useIndexes && exclusiveSource) { result->AddStep(std::make_shared(std::make_shared(IndexChecker->GetIndexIds()))); result->AddStep(std::make_shared(IndexChecker)); } bool hasFilterSharding = false; - TColumnsAccumulator acc(MergeColumns, ReadMetadata->GetResultSchema()); - if (needFilterSharding && !ShardingColumns->IsEmpty()) { + if (needFilterSharding && !GetShardingColumns()->IsEmpty()) { hasFilterSharding = true; - TColumnsSetIds columnsFetch = *ShardingColumns; + TColumnsSetIds columnsFetch = *GetShardingColumns(); if (!exclusiveSource) { - columnsFetch = columnsFetch + *PKColumns + *SpecColumns; + columnsFetch = columnsFetch + *GetPKColumns() + *GetSpecColumns(); } acc.AddFetchingStep(*result, columnsFetch, EStageFeaturesIndexes::Filter); - acc.AddAssembleStep(*result, columnsFetch, "SPEC_SHARDING", false); + acc.AddAssembleStep(*result, columnsFetch, "SPEC_SHARDING", EStageFeaturesIndexes::Filter, false); result->AddStep(std::make_shared()); } - if (!EFColumns->GetColumnsCount() && !partialUsageByPredicate) { + if (!GetEFColumns()->GetColumnsCount() && !partialUsageByPredicate) { result->SetBranchName("simple"); - TColumnsSetIds columnsFetch = *FFColumns; + TColumnsSetIds columnsFetch = *GetFFColumns(); if (needFilterDeletion) { - columnsFetch = columnsFetch + *DeletionColumns; + columnsFetch = columnsFetch + *GetDeletionColumns(); } if (needSnapshots) { - columnsFetch = columnsFetch + *SpecColumns; + columnsFetch = columnsFetch + *GetSpecColumns(); } if (!exclusiveSource) { - columnsFetch = columnsFetch + *MergeColumns; + columnsFetch = columnsFetch + *GetMergeColumns(); } else { - if (columnsFetch.GetColumnsCount() == 1 && SpecColumns->Contains(columnsFetch) && !hasFilterSharding) { + if (columnsFetch.GetColumnsCount() == 1 && GetSpecColumns()->Contains(columnsFetch) && !hasFilterSharding) { return nullptr; } } if (columnsFetch.GetColumnsCount() || hasFilterSharding || needFilterDeletion) { acc.AddFetchingStep(*result, columnsFetch, EStageFeaturesIndexes::Fetching); if (needSnapshots) { - acc.AddAssembleStep(*result, *SpecColumns, "SPEC", false); - result->AddStep(std::make_shared()); + acc.AddAssembleStep(*result, *GetSpecColumns(), "SPEC", EStageFeaturesIndexes::Fetching, false); } if (!exclusiveSource) { - acc.AddAssembleStep(*result, *MergeColumns, "LAST_PK", false); + acc.AddAssembleStep(*result, *GetMergeColumns(), "LAST_PK", EStageFeaturesIndexes::Fetching, false); + } + if (needSnapshots) { + result->AddStep(std::make_shared()); } if (needFilterDeletion) { - acc.AddAssembleStep(*result, *DeletionColumns, "SPEC_DELETION", false); + acc.AddAssembleStep(*result, *GetDeletionColumns(), "SPEC_DELETION", EStageFeaturesIndexes::Fetching, false); result->AddStep(std::make_shared()); } - acc.AddAssembleStep(*result, columnsFetch, "LAST", true); + acc.AddAssembleStep(*result, columnsFetch, "LAST", EStageFeaturesIndexes::Fetching, !exclusiveSource); } else { return nullptr; } } else if (exclusiveSource) { result->SetBranchName("exclusive"); - TColumnsSet columnsFetch = *EFColumns; + TColumnsSet columnsFetch = *GetEFColumns(); if (needFilterDeletion) { - columnsFetch = columnsFetch + *DeletionColumns; + columnsFetch = columnsFetch + *GetDeletionColumns(); } - if (needSnapshots || FFColumns->Cross(*SpecColumns)) { - columnsFetch = columnsFetch + *SpecColumns; + if (needSnapshots || GetFFColumns()->Cross(*GetSpecColumns())) { + columnsFetch = columnsFetch + *GetSpecColumns(); } if (partialUsageByPredicate) { - columnsFetch = columnsFetch + *PredicateColumns; + columnsFetch = columnsFetch + *GetPredicateColumns(); } AFL_VERIFY(columnsFetch.GetColumnsCount()); acc.AddFetchingStep(*result, columnsFetch, EStageFeaturesIndexes::Filter); if (needFilterDeletion) { - acc.AddAssembleStep(*result, *DeletionColumns, "SPEC_DELETION", false); + acc.AddAssembleStep(*result, *GetDeletionColumns(), "SPEC_DELETION", EStageFeaturesIndexes::Filter, false); result->AddStep(std::make_shared()); } if (partialUsageByPredicate) { - acc.AddAssembleStep(*result, *PredicateColumns, "PREDICATE", false); + acc.AddAssembleStep(*result, *GetPredicateColumns(), "PREDICATE", EStageFeaturesIndexes::Filter, false); result->AddStep(std::make_shared()); } - if (needSnapshots || FFColumns->Cross(*SpecColumns)) { - acc.AddAssembleStep(*result, *SpecColumns, "SPEC", false); + if (needSnapshots || GetFFColumns()->Cross(*GetSpecColumns())) { + acc.AddAssembleStep(*result, *GetSpecColumns(), "SPEC", EStageFeaturesIndexes::Filter, false); result->AddStep(std::make_shared()); } - for (auto&& i : ReadMetadata->GetProgram().GetSteps()) { + for (auto&& i : GetReadMetadata()->GetProgram().GetSteps()) { if (i->GetFilterOriginalColumnIds().empty()) { break; } - TColumnsSet stepColumnIds(i->GetFilterOriginalColumnIds(), ReadMetadata->GetResultSchema()); - acc.AddAssembleStep(*result, stepColumnIds, "EF", true); + TColumnsSet stepColumnIds(i->GetFilterOriginalColumnIds(), GetReadMetadata()->GetResultSchema()); + acc.AddAssembleStep(*result, stepColumnIds, "EF", EStageFeaturesIndexes::Filter, false); result->AddStep(std::make_shared(i)); if (!i->IsFilterOnly()) { break; } } - if (GetReadMetadata()->Limit) { - result->AddStep(std::make_shared(GetReadMetadata()->Limit, GetReadMetadata()->IsDescSorted())); + if (GetReadMetadata()->HasLimit()) { + result->AddStep(std::make_shared(GetReadMetadata()->GetLimitRobust(), GetReadMetadata()->IsDescSorted())); } - acc.AddFetchingStep(*result, *FFColumns, EStageFeaturesIndexes::Fetching); - acc.AddAssembleStep(*result, *FFColumns, "LAST", true); + acc.AddFetchingStep(*result, *GetFFColumns(), EStageFeaturesIndexes::Fetching); + acc.AddAssembleStep(*result, *GetFFColumns(), "LAST", EStageFeaturesIndexes::Fetching, !exclusiveSource); } else { result->SetBranchName("merge"); - TColumnsSet columnsFetch = *MergeColumns + *EFColumns; + TColumnsSet columnsFetch = *GetMergeColumns() + *GetEFColumns(); if (needFilterDeletion) { - columnsFetch = columnsFetch + *DeletionColumns; + columnsFetch = columnsFetch + *GetDeletionColumns(); } AFL_VERIFY(columnsFetch.GetColumnsCount()); acc.AddFetchingStep(*result, columnsFetch, EStageFeaturesIndexes::Filter); - acc.AddAssembleStep(*result, *SpecColumns, "SPEC", false); - acc.AddAssembleStep(*result, *PKColumns, "PK", false); + acc.AddAssembleStep(*result, *GetSpecColumns(), "SPEC", EStageFeaturesIndexes::Filter, false); + acc.AddAssembleStep(*result, *GetPKColumns(), "PK", EStageFeaturesIndexes::Filter, false); if (needSnapshots) { result->AddStep(std::make_shared()); } if (needFilterDeletion) { - acc.AddAssembleStep(*result, *DeletionColumns, "SPEC_DELETION", false); + acc.AddAssembleStep(*result, *GetDeletionColumns(), "SPEC_DELETION", EStageFeaturesIndexes::Filter, false); result->AddStep(std::make_shared()); } if (partialUsageByPredicate) { result->AddStep(std::make_shared()); } - for (auto&& i : ReadMetadata->GetProgram().GetSteps()) { + for (auto&& i : GetReadMetadata()->GetProgram().GetSteps()) { if (i->GetFilterOriginalColumnIds().empty()) { break; } - TColumnsSet stepColumnIds(i->GetFilterOriginalColumnIds(), ReadMetadata->GetResultSchema()); - acc.AddAssembleStep(*result, stepColumnIds, "EF", true); + TColumnsSet stepColumnIds(i->GetFilterOriginalColumnIds(), GetReadMetadata()->GetResultSchema()); + acc.AddAssembleStep(*result, stepColumnIds, "EF", EStageFeaturesIndexes::Filter, false); result->AddStep(std::make_shared(i)); if (!i->IsFilterOnly()) { break; } } - acc.AddFetchingStep(*result, *FFColumns, EStageFeaturesIndexes::Fetching); - acc.AddAssembleStep(*result, *FFColumns, "LAST", true); + acc.AddFetchingStep(*result, *GetFFColumns(), EStageFeaturesIndexes::Fetching); + acc.AddAssembleStep(*result, *GetFFColumns(), "LAST", EStageFeaturesIndexes::Fetching, !exclusiveSource); } + result->AddStep(); return result; } TSpecialReadContext::TSpecialReadContext(const std::shared_ptr& commonContext) - : CommonContext(commonContext) { - - ReadMetadata = dynamic_pointer_cast(CommonContext->GetReadMetadata()); - Y_ABORT_UNLESS(ReadMetadata); - Y_ABORT_UNLESS(ReadMetadata->SelectInfo); - - double kffFilter = 0.45; - double kffFetching = 0.45; - double kffMerge = 0.10; - TString stagePrefix; - if (ReadMetadata->GetEarlyFilterColumnIds().size()) { - stagePrefix = "EF"; - kffFilter = 0.7; - kffFetching = 0.15; - kffMerge = 0.15; - } else { - stagePrefix = "FO"; - kffFilter = 0.1; - kffFetching = 0.75; - kffMerge = 0.15; - } - - std::vector> stages = { - NGroupedMemoryManager::TScanMemoryLimiterOperator::BuildStageFeatures( - stagePrefix + "::FILTER", kffFilter * TGlobalLimits::ScanMemoryLimit), - NGroupedMemoryManager::TScanMemoryLimiterOperator::BuildStageFeatures( - stagePrefix + "::FETCHING", kffFetching * TGlobalLimits::ScanMemoryLimit), - NGroupedMemoryManager::TScanMemoryLimiterOperator::BuildStageFeatures(stagePrefix + "::MERGE", kffMerge * TGlobalLimits::ScanMemoryLimit) - }; - ProcessMemoryGuard = - NGroupedMemoryManager::TScanMemoryLimiterOperator::BuildProcessGuard(CommonContext->GetReadMetadata()->GetTxId(), stages); - ProcessScopeGuard = - NGroupedMemoryManager::TScanMemoryLimiterOperator::BuildScopeGuard(CommonContext->GetReadMetadata()->GetTxId(), GetCommonContext()->GetScanId()); - - auto readSchema = ReadMetadata->GetResultSchema(); - SpecColumns = std::make_shared(TIndexInfo::GetSnapshotColumnIdsSet(), readSchema); - IndexChecker = ReadMetadata->GetProgram().GetIndexChecker(); - { - auto predicateColumns = ReadMetadata->GetPKRangesFilter().GetColumnIds(ReadMetadata->GetIndexInfo()); - if (predicateColumns.size()) { - PredicateColumns = std::make_shared(predicateColumns, readSchema); - } else { - PredicateColumns = std::make_shared(); - } - } - { - std::set columnIds = { NPortion::TSpecialColumns::SPEC_COL_DELETE_FLAG_INDEX }; - DeletionColumns = std::make_shared(columnIds, ReadMetadata->GetResultSchema()); - } - - if (!!ReadMetadata->GetRequestShardingInfo()) { - auto shardingColumnIds = - ReadMetadata->GetIndexInfo().GetColumnIdsVerified(ReadMetadata->GetRequestShardingInfo()->GetShardingInfo()->GetColumnNames()); - ShardingColumns = std::make_shared(shardingColumnIds, ReadMetadata->GetResultSchema()); - } else { - ShardingColumns = std::make_shared(); - } - { - auto efColumns = ReadMetadata->GetEarlyFilterColumnIds(); - if (efColumns.size()) { - EFColumns = std::make_shared(efColumns, readSchema); - } else { - EFColumns = std::make_shared(); - } - } - if (ReadMetadata->HasProcessingColumnIds()) { - FFColumns = std::make_shared(ReadMetadata->GetProcessingColumnIds(), readSchema); - if (SpecColumns->Contains(*FFColumns) && !EFColumns->IsEmpty()) { - FFColumns = std::make_shared(*EFColumns + *SpecColumns); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("ff_modified", FFColumns->DebugString()); - } else { - AFL_VERIFY(!FFColumns->Contains(*SpecColumns))("info", FFColumns->DebugString()); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("ff_first", FFColumns->DebugString()); - } - } else { - FFColumns = EFColumns; - } - if (FFColumns->IsEmpty()) { - ProgramInputColumns = SpecColumns; - } else { - ProgramInputColumns = FFColumns; - } - - PKColumns = std::make_shared(ReadMetadata->GetPKColumnIds(), readSchema); - MergeColumns = std::make_shared(*PKColumns + *SpecColumns); - - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("columns_context_info", DebugString()); -} - -TString TSpecialReadContext::DebugString() const { - TStringBuilder sb; - sb << "ef=" << EFColumns->DebugString() << ";" - << "sharding=" << ShardingColumns->DebugString() << ";" - << "pk=" << PKColumns->DebugString() << ";" - << "ff=" << FFColumns->DebugString() << ";" - << "program_input=" << ProgramInputColumns->DebugString() << ";"; - return sb; + : TBase(commonContext) { } TString TSpecialReadContext::ProfileDebugString() const { diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/context.h b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/context.h index 1ae41c039808..4b2f98497254 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/context.h +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/context.h @@ -1,84 +1,54 @@ #pragma once -#include "columns_set.h" #include "fetching.h" + +#include #include #include +#include +#include #include #include -#include namespace NKikimr::NOlap::NReader::NPlain { class IDataSource; +using TColumnsSet = NCommon::TColumnsSet; +using EStageFeaturesIndexes = NCommon::EStageFeaturesIndexes; +using TColumnsSetIds = NCommon::TColumnsSetIds; +using EMemType = NCommon::EMemType; +using TFetchingScript = NCommon::TFetchingScript; -class TSpecialReadContext { +class TSpecialReadContext: public NCommon::TSpecialReadContext { private: - YDB_READONLY_DEF(std::shared_ptr, CommonContext); - YDB_READONLY_DEF(std::shared_ptr, ProcessMemoryGuard); - YDB_READONLY_DEF(std::shared_ptr, ProcessScopeGuard); - - YDB_READONLY_DEF(std::shared_ptr, SpecColumns); - YDB_READONLY_DEF(std::shared_ptr, MergeColumns); - YDB_READONLY_DEF(std::shared_ptr, ShardingColumns); - YDB_READONLY_DEF(std::shared_ptr, DeletionColumns); - YDB_READONLY_DEF(std::shared_ptr, EFColumns); - YDB_READONLY_DEF(std::shared_ptr, PredicateColumns); - YDB_READONLY_DEF(std::shared_ptr, PKColumns); - YDB_READONLY_DEF(std::shared_ptr, FFColumns); - YDB_READONLY_DEF(std::shared_ptr, ProgramInputColumns); - + using TBase = NCommon::TSpecialReadContext; YDB_READONLY_DEF(std::shared_ptr, MergeStageMemory); YDB_READONLY_DEF(std::shared_ptr, FilterStageMemory); YDB_READONLY_DEF(std::shared_ptr, FetchingStageMemory); - TAtomic AbortFlag = 0; - NIndexes::TIndexCheckerContainer IndexChecker; - TReadMetadata::TConstPtr ReadMetadata; - std::shared_ptr EmptyColumns = std::make_shared(); - std::shared_ptr BuildColumnsFetchingPlan(const bool needSnapshotsFilter, const bool exclusiveSource, + std::shared_ptr BuildColumnsFetchingPlan(const bool needSnapshotsFilter, const bool exclusiveSource, const bool partialUsageByPredicate, const bool useIndexes, const bool needFilterSharding, const bool needFilterDeletion) const; + TMutex Mutex; std::array>, 2>, 2>, 2>, 2>, 2>, 2> CacheFetchingScripts; + std::shared_ptr AskAccumulatorsScript; + + virtual std::shared_ptr DoGetColumnsFetchingPlan(const std::shared_ptr& source) override; public: const ui64 ReduceMemoryIntervalLimit = NYDBTest::TControllers::GetColumnShardController()->GetReduceMemoryIntervalLimit(); const ui64 RejectMemoryIntervalLimit = NYDBTest::TControllers::GetColumnShardController()->GetRejectMemoryIntervalLimit(); const ui64 ReadSequentiallyBufferSize = TGlobalLimits::DefaultReadSequentiallyBufferSize; - ui64 GetProcessMemoryControlId() const { - AFL_VERIFY(ProcessMemoryGuard); - return ProcessMemoryGuard->GetProcessId(); - } ui64 GetMemoryForSources(const THashMap>& sources); ui64 GetRequestedMemoryBytes() const { return MergeStageMemory->GetFullMemory() + FilterStageMemory->GetFullMemory() + FetchingStageMemory->GetFullMemory(); } - const TReadMetadata::TConstPtr& GetReadMetadata() const { - return ReadMetadata; - } - - bool IsAborted() const { - return AtomicGet(AbortFlag); - } - - void Abort() { - AtomicSet(AbortFlag, 1); - } - - ~TSpecialReadContext() { - AFL_INFO(NKikimrServices::TX_COLUMNSHARD_SCAN)("profile", ProfileDebugString()); - AFL_INFO(NKikimrServices::TX_COLUMNSHARD_SCAN)("fetching", DebugString()); - } - std::unique_ptr BuildMerger() const; - TString DebugString() const; - TString ProfileDebugString() const; + virtual TString ProfileDebugString() const override; TSpecialReadContext(const std::shared_ptr& commonContext); - - std::shared_ptr GetColumnsFetchingPlan(const std::shared_ptr& source); }; -} +} // namespace NKikimr::NOlap::NReader::NPlain diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/fetched_data.cpp b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/fetched_data.cpp index bf38c466b75b..fa5100c1da27 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/fetched_data.cpp +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/fetched_data.cpp @@ -1,21 +1,5 @@ #include "fetched_data.h" -#include -#include -#include +namespace NKikimr::NOlap::NReader::NPlain { -namespace NKikimr::NOlap { - -void TFetchedData::SyncTableColumns(const std::vector>& fields, const ISnapshotSchema& schema) { - for (auto&& i : fields) { - if (Table->GetSchema()->GetFieldByName(i->name())) { - continue; - } - Table - ->AddField(i, std::make_shared(NArrow::TThreadSimpleArraysCache::Get( - i->type(), schema.GetExternalDefaultValueVerified(i->name()), Table->num_rows()))) - .Validate(); - } -} - -} // namespace NKikimr::NOlap +} // namespace NKikimr::NOlap::NReader::NPlain diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/fetched_data.h b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/fetched_data.h index b535c2bc4673..1f220943001f 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/fetched_data.h +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/fetched_data.h @@ -1,166 +1,22 @@ #pragma once -#include -#include -#include -#include -#include -#include +#include -#include -#include +namespace NKikimr::NOlap::NReader::NPlain { -#include -#include - -namespace NKikimr::NOlap { - -class TFetchedData { -protected: - using TBlobs = THashMap; - YDB_ACCESSOR_DEF(TBlobs, Blobs); - YDB_READONLY_DEF(std::shared_ptr, Table); - YDB_READONLY_DEF(std::shared_ptr, Filter); - YDB_READONLY(bool, UseFilter, false); +class TFetchedData: public NCommon::TFetchedData { +private: + using TBase = NCommon::TFetchedData; public: - TFetchedData(const bool useFilter) - : UseFilter(useFilter) { - } - - ui32 GetFilteredCount(const ui32 recordsCount, const ui32 defLimit) const { - if (!Filter) { - return std::min(defLimit, recordsCount); - } - return Filter->GetFilteredCount().value_or(recordsCount); - } - - void SyncTableColumns(const std::vector>& fields, const ISnapshotSchema& schema); - - std::shared_ptr GetAppliedFilter() const { - return UseFilter ? Filter : nullptr; - } - - std::shared_ptr GetNotAppliedFilter() const { - return UseFilter ? nullptr : Filter; - } - - TString ExtractBlob(const TChunkAddress& address) { - auto it = Blobs.find(address); - AFL_VERIFY(it != Blobs.end()); - AFL_VERIFY(it->second.IsBlob()); - auto result = it->second.GetData(); - Blobs.erase(it); - return result; - } - - void AddBlobs(THashMap&& blobData) { - for (auto&& i : blobData) { - AFL_VERIFY(Blobs.emplace(i.first, std::move(i.second)).second); - } - } - - void AddDefaults(THashMap&& blobs) { - for (auto&& i : blobs) { - AFL_VERIFY(Blobs.emplace(i.first, std::move(i.second)).second); - } - } - - bool IsEmpty() const { - return (Filter && Filter->IsTotalDenyFilter()) || (Table && !Table->num_rows()); - } - - void Clear() { - Filter = std::make_shared(NArrow::TColumnFilter::BuildDenyFilter()); - Table = nullptr; - } - - void AddFilter(const std::shared_ptr& filter) { - if (!filter) { - return; - } - return AddFilter(*filter); - } - - void CutFilter(const ui32 recordsCount, const ui32 limit, const bool reverse) { - auto filter = std::make_shared(NArrow::TColumnFilter::BuildAllowFilter()); - ui32 recordsCountImpl = Filter ? Filter->GetFilteredCount().value_or(recordsCount) : recordsCount; - if (recordsCountImpl < limit) { - return; - } - if (reverse) { - filter->Add(false, recordsCountImpl - limit); - filter->Add(true, limit); - } else { - filter->Add(true, limit); - filter->Add(false, recordsCountImpl - limit); - } - if (Filter) { - if (UseFilter) { - AddFilter(*filter); - } else { - AddFilter(Filter->CombineSequentialAnd(*filter)); - } - } else { - AddFilter(*filter); - } - - } - - void AddFilter(const NArrow::TColumnFilter& filter) { - if (UseFilter && Table) { - AFL_VERIFY(filter.Apply(Table)); - } - if (!Filter) { - Filter = std::make_shared(filter); - } else if (UseFilter) { - *Filter = Filter->CombineSequentialAnd(filter); - } else { - *Filter = Filter->And(filter); - } - } - - void AddBatch(const std::shared_ptr& table) { - AFL_VERIFY(table); - if (UseFilter) { - AddBatch(table->BuildTableVerified()); - } else { - if (!Table) { - Table = table; - } else { - auto mergeResult = Table->MergeColumnsStrictly(*table); - AFL_VERIFY(mergeResult.IsSuccess())("error", mergeResult.GetErrorMessage()); - } - } - } - - void AddBatch(const std::shared_ptr& table) { - auto tableLocal = table; - if (Filter && UseFilter) { - AFL_VERIFY(Filter->Apply(tableLocal)); - } - if (!Table) { - Table = std::make_shared(tableLocal); - } else { - auto mergeResult = Table->MergeColumnsStrictly(NArrow::TGeneralContainer(tableLocal)); - AFL_VERIFY(mergeResult.IsSuccess())("error", mergeResult.GetErrorMessage()); - } - } + using TBase::TBase; }; -class TFetchedResult { +class TFetchedResult: public NCommon::TFetchedResult { private: - YDB_READONLY_DEF(std::shared_ptr, Batch); - YDB_READONLY_DEF(std::shared_ptr, NotAppliedFilter); + using TBase = NCommon::TFetchedResult; public: - TFetchedResult(std::unique_ptr&& data) - : Batch(data->GetTable()) - , NotAppliedFilter(data->GetNotAppliedFilter()) { - } - - bool IsEmpty() const { - return !Batch || Batch->num_rows() == 0 || (NotAppliedFilter && NotAppliedFilter->IsTotalDenyFilter()); - } + using TBase::TBase; }; -} // namespace NKikimr::NOlap +} // namespace NKikimr::NOlap::NReader::NPlain diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/fetching.cpp b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/fetching.cpp index 2d0ec349aa6a..36b47d6ad5f1 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/fetching.cpp +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/fetching.cpp @@ -10,107 +10,22 @@ namespace NKikimr::NOlap::NReader::NPlain { -bool TStepAction::DoApply(IDataReader& /*owner*/) const { - if (FinishedFlag) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "apply"); - Source->SetIsReady(); - } - return true; -} - -TConclusionStatus TStepAction::DoExecuteImpl() { - if (Source->GetContext()->IsAborted()) { - return TConclusionStatus::Success(); - } - auto executeResult = Cursor.Execute(Source); - if (!executeResult) { - return executeResult; - } - if (*executeResult) { - Source->Finalize(); - FinishedFlag = true; - } - return TConclusionStatus::Success(); -} - -TConclusion TColumnBlobsFetchingStep::DoExecuteInplace( - const std::shared_ptr& source, const TFetchingScriptCursor& step) const { - return !source->StartFetchingColumns(source, step, Columns); -} - -ui64 TColumnBlobsFetchingStep::DoPredictRawBytes(const std::shared_ptr& source) const { - ui64 result = source->GetColumnRawBytes(Columns.GetColumnIds()); - if (source->GetContext()->GetReadMetadata()->Limit && source->GetExclusiveIntervalOnly()) { - result = std::max(result * 1.0 * source->GetContext()->GetReadMetadata()->Limit / source->GetRecordsCount(), - source->GetColumnBlobBytes(Columns.GetColumnIds())); - } - if (!result) { - return Columns.GetColumnIds().size() * source->GetRecordsCount() * - sizeof(ui32); // null for all records for all columns in future will be - } else { - return result; - } -} - -ui64 TColumnBlobsFetchingStep::GetProcessingDataSize(const std::shared_ptr& source) const { - return source->GetColumnBlobBytes(Columns.GetColumnIds()); -} - TConclusion TIndexBlobsFetchingStep::DoExecuteInplace( const std::shared_ptr& source, const TFetchingScriptCursor& step) const { return !source->StartFetchingIndexes(source, step, Indexes); } -ui64 TIndexBlobsFetchingStep::DoPredictRawBytes(const std::shared_ptr& source) const { - return source->GetIndexRawBytes(Indexes->GetIndexIdsSet()); -} - -TConclusion TAssemblerStep::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { - source->AssembleColumns(Columns); - return true; -} - -ui64 TAssemblerStep::GetProcessingDataSize(const std::shared_ptr& source) const { - return source->GetColumnRawBytes(Columns->GetColumnIds()); -} - -TConclusion TOptionalAssemblerStep::DoExecuteInplace( - const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { - source->AssembleColumns(Columns); - return true; -} - -bool TOptionalAssemblerStep::DoInitSourceSeqColumnIds(const std::shared_ptr& source) const { - for (auto&& i : Columns->GetColumnIds()) { - if (source->AddSequentialEntityIds(i)) { - return true; - } - } - return false; -} - -ui64 TOptionalAssemblerStep::GetProcessingDataSize(const std::shared_ptr& source) const { - return source->GetColumnRawBytes(Columns->GetColumnIds()); -} - TConclusion TFilterProgramStep::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { AFL_VERIFY(source); AFL_VERIFY(Step); - std::shared_ptr table; - if (source->IsSourceInMemory(Step->GetFilterOriginalColumnIds())) { - auto filter = Step->BuildFilter(source->GetStageData().GetTable()); - if (!filter.ok()) { - return TConclusionStatus::Fail(filter.status().message()); - } - source->MutableStageData().AddFilter(*filter); + auto filter = Step->BuildFilter(source->GetStageData().GetTable()); + if (!filter.ok()) { + return TConclusionStatus::Fail(filter.status().message()); } + source->MutableStageData().AddFilter(*filter); return true; } -ui64 TFilterProgramStep::DoPredictRawBytes(const std::shared_ptr& source) const { - return NArrow::TColumnFilter::GetPredictedMemorySize(source->GetRecordsCount()); -} - TConclusion TPredicateFilter::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { auto filter = source->GetContext()->GetReadMetadata()->GetPKRangesFilter().BuildFilter(source->GetStageData().GetTable()->BuildTableVerified()); @@ -155,122 +70,46 @@ TConclusion TShardingFilter::DoExecuteInplace(const std::shared_ptr TBuildFakeSpec::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { - std::vector> columns; - for (auto&& f : IIndexInfo::ArrowSchemaSnapshot()->fields()) { - columns.emplace_back(NArrow::TThreadSimpleArraysCache::GetConst(f->type(), NArrow::DefaultScalar(f->type()), Count)); - } - source->MutableStageData().AddBatch( - std::make_shared(arrow::RecordBatch::Make(TIndexInfo::ArrowSchemaSnapshot(), Count, columns))); - return true; -} - TConclusion TApplyIndexStep::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { source->ApplyIndex(IndexChecker); return true; } -TConclusion TFetchingScriptCursor::Execute(const std::shared_ptr& source) { - AFL_VERIFY(source); - NMiniKQL::TThrowingBindTerminator bind; - Script->OnExecute(); - AFL_VERIFY(!Script->IsFinished(CurrentStepIdx)); - while (!Script->IsFinished(CurrentStepIdx)) { - if (source->GetStageData().IsEmpty()) { - source->OnEmptyStageData(); - break; - } - auto step = Script->GetStep(CurrentStepIdx); - TMemoryProfileGuard mGuard("SCAN_PROFILE::FETCHING::" + step->GetName() + "::" + Script->GetBranchName(), - IS_DEBUG_LOG_ENABLED(NKikimrServices::TX_COLUMNSHARD_SCAN_MEMORY)); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("scan_step", step->DebugString())("scan_step_idx", CurrentStepIdx); - AFL_VERIFY(!CurrentStartInstant); - CurrentStartInstant = TMonotonic::Now(); - AFL_VERIFY(!CurrentStartDataSize); - CurrentStartDataSize = step->GetProcessingDataSize(source); - const TConclusion resultStep = step->ExecuteInplace(source, *this); - if (!resultStep) { - return resultStep; - } - if (!*resultStep) { - return false; - } - FlushDuration(); - ++CurrentStepIdx; - } - return true; -} - -bool TAllocateMemoryStep::TFetchingStepAllocation::DoOnAllocated(std::shared_ptr&& guard, - const std::shared_ptr& /*allocation*/) { - auto data = Source.lock(); - if (!data || data->GetContext()->IsAborted()) { - guard->Release(); - return false; - } - data->RegisterAllocationGuard(std::move(guard)); - Step.Next(); - auto task = std::make_shared(data, std::move(Step), data->GetContext()->GetCommonContext()->GetScanActorId()); - NConveyor::TScanServiceOperator::SendTaskToExecute(task); +NKikimr::TConclusion TFilterCutLimit::DoExecuteInplace( + const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { + source->MutableStageData().CutFilter(source->GetRecordsCount(), Limit, Reverse); return true; } -TAllocateMemoryStep::TFetchingStepAllocation::TFetchingStepAllocation( - const std::shared_ptr& source, const ui64 mem, const TFetchingScriptCursor& step) - : TBase(mem) - , Source(source) - , Step(step) - , TasksGuard(source->GetContext()->GetCommonContext()->GetCounters().GetResourcesAllocationTasksGuard()) { -} - -TConclusion TAllocateMemoryStep::DoExecuteInplace( +TConclusion TPortionAccessorFetchingStep::DoExecuteInplace( const std::shared_ptr& source, const TFetchingScriptCursor& step) const { - - auto allocation = std::make_shared(source, GetProcessingDataSize(source), step); - NGroupedMemoryManager::TScanMemoryLimiterOperator::SendToAllocation(source->GetContext()->GetProcessMemoryControlId(), - source->GetContext()->GetCommonContext()->GetScanId(), source->GetFirstIntervalId(), { allocation }, (ui32)StageIndex); - return false; + return !source->StartFetchingAccessor(source, step); } -ui64 TAllocateMemoryStep::GetProcessingDataSize(const std::shared_ptr& source) const { - ui64 size = source->GetColumnRawBytes(Columns.GetColumnIds()); - - if (source->GetStageData().GetUseFilter() && source->GetContext()->GetReadMetadata()->Limit) { - const ui32 filtered = source->GetStageData().GetFilteredCount(source->GetRecordsCount(), source->GetContext()->GetReadMetadata()->Limit); - if (filtered < source->GetRecordsCount()) { - size = std::max(size * 1.0 * filtered / source->GetRecordsCount(), source->GetColumnBlobBytes(Columns.GetColumnIds())); - } +TConclusion TDetectInMem::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { + if (Columns.GetColumnsCount()) { + source->SetSourceInMemory( + source->GetColumnRawBytes(Columns.GetColumnIds()) < NYDBTest::TControllers::GetColumnShardController()->GetMemoryLimitScanPortion()); + } else { + source->SetSourceInMemory(true); } - return size; + AFL_VERIFY(!source->NeedAccessorsFetching()); + auto plan = source->GetContext()->GetColumnsFetchingPlan(source); + source->InitFetchingPlan(plan); + TFetchingScriptCursor cursor(plan, 0); + auto task = std::make_shared(source, std::move(cursor), source->GetContext()->GetCommonContext()->GetScanActorId()); + NConveyor::TScanServiceOperator::SendTaskToExecute(task); + return false; } -TString TFetchingScript::DebugString() const { - TStringBuilder sb; - TStringBuilder sbBranch; - for (auto&& i : Steps) { - if (i->GetSumDuration() > TDuration::MilliSeconds(10)) { - sbBranch << "{" << i->DebugString() << "};"; - } - } - if (!sbBranch) { - return ""; - } - sb << "{branch:" << BranchName << ";limit:" << Limit << ";"; - if (FinishInstant && StartInstant) { - sb << "duration:" << *FinishInstant - *StartInstant << ";"; +TConclusion TBuildFakeSpec::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { + std::vector> columns; + for (auto&& f : IIndexInfo::ArrowSchemaSnapshot()->fields()) { + columns.emplace_back(NArrow::TThreadSimpleArraysCache::GetConst(f->type(), NArrow::DefaultScalar(f->type()), source->GetRecordsCount())); } - - sb << "steps_10Ms:[" << sbBranch << "]}"; - return sb; -} - -TFetchingScript::TFetchingScript(const TSpecialReadContext& context) - : Limit(context.GetReadMetadata()->Limit) { -} - -NKikimr::TConclusion TFilterCutLimit::DoExecuteInplace( - const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { - source->MutableStageData().CutFilter(source->GetRecordsCount(), Limit, Reverse); + source->MutableStageData().AddBatch(std::make_shared( + arrow::RecordBatch::Make(TIndexInfo::ArrowSchemaSnapshot(), source->GetRecordsCount(), columns))); + source->BuildStageResult(source); return true; } diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/fetching.h b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/fetching.h index 133aa4db3669..0762c4e5a5e0 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/fetching.h +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/fetching.h @@ -1,9 +1,9 @@ #pragma once -#include "columns_set.h" - #include #include #include +#include +#include #include #include #include @@ -11,196 +11,49 @@ #include namespace NKikimr::NOlap::NReader::NPlain { + +using TColumnsSet = NCommon::TColumnsSet; +using TIndexesSet = NCommon::TIndexesSet; +using EStageFeaturesIndexes = NCommon::EStageFeaturesIndexes; +using TColumnsSetIds = NCommon::TColumnsSetIds; +using EMemType = NCommon::EMemType; +using TFetchingScriptCursor = NCommon::TFetchingScriptCursor; +using TStepAction = NCommon::TStepAction; + class IDataSource; -class TFetchingScriptCursor; class TSpecialReadContext; -class IFetchingStep { +class IFetchingStep: public NCommon::IFetchingStep { private: - YDB_READONLY_DEF(TString, Name); - YDB_READONLY(TDuration, SumDuration, TDuration::Zero()); - YDB_READONLY(ui64, SumSize, 0); - -protected: + using TBase = NCommon::IFetchingStep; virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const = 0; - virtual TString DoDebugString() const { - return ""; - } - -public: - void AddDuration(const TDuration d) { - SumDuration += d; - } - void AddDataSize(const ui64 size) { - SumSize += size; - } - virtual ui64 DoPredictRawBytes(const std::shared_ptr& /*source*/) const { - return 0; - } - virtual bool DoInitSourceSeqColumnIds(const std::shared_ptr& /*source*/) const { - return false; - } - - virtual ~IFetchingStep() = default; - - [[nodiscard]] TConclusion ExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const { - return DoExecuteInplace(source, step); - } - virtual ui64 GetProcessingDataSize(const std::shared_ptr& /*source*/) const { return 0; } - IFetchingStep(const TString& name) - : Name(name) { - } - - TString DebugString() const { - TStringBuilder sb; - sb << "name=" << Name << ";duration=" << SumDuration << ";" - << "size=" << 1e-9 * SumSize << ";details={" << DoDebugString() << "};"; - return sb; - } -}; - -class TFetchingScript { -private: - YDB_ACCESSOR(TString, BranchName, "UNDEFINED"); - std::vector> Steps; - std::optional StartInstant; - std::optional FinishInstant; - const ui32 Limit; - -public: - TFetchingScript(const TSpecialReadContext& context); - - void AddStepDataSize(const ui32 index, const ui64 size) { - GetStep(index)->AddDataSize(size); - } - - void AddStepDuration(const ui32 index, const TDuration d) { - FinishInstant = TMonotonic::Now(); - GetStep(index)->AddDuration(d); - } - - void OnExecute() { - if (!StartInstant) { - StartInstant = TMonotonic::Now(); - } - } - - TString DebugString() const; - - const std::shared_ptr& GetStep(const ui32 index) const { - AFL_VERIFY(index < Steps.size()); - return Steps[index]; - } - - ui64 PredictRawBytes(const std::shared_ptr& source) const { - ui64 result = 0; - for (auto&& current : Steps) { - result += current->DoPredictRawBytes(source); - } - return result; - } - - void AddStep(const std::shared_ptr& step) { - AFL_VERIFY(step); - Steps.emplace_back(step); + virtual ui64 GetProcessingDataSize(const std::shared_ptr& source) const override final { + return GetProcessingDataSize(std::static_pointer_cast(source)); } - bool InitSourceSeqColumnIds(const std::shared_ptr& source) const { - for (auto it = Steps.rbegin(); it != Steps.rend(); ++it) { - if ((*it)->DoInitSourceSeqColumnIds(source)) { - return true; - } - } - return false; - } - - bool IsFinished(const ui32 currentStepIdx) const { - AFL_VERIFY(currentStepIdx <= Steps.size()); - return currentStepIdx == Steps.size(); - } - - ui32 Execute(const ui32 startStepIdx, const std::shared_ptr& source) const; -}; - -class TFetchingScriptCursor { -private: - std::optional CurrentStartInstant; - std::optional CurrentStartDataSize; - ui32 CurrentStepIdx = 0; - std::shared_ptr Script; - void FlushDuration() { - AFL_VERIFY(CurrentStartInstant); - AFL_VERIFY(CurrentStartDataSize); - Script->AddStepDuration(CurrentStepIdx, TMonotonic::Now() - *CurrentStartInstant); - Script->AddStepDataSize(CurrentStepIdx, *CurrentStartDataSize); - CurrentStartInstant.reset(); - CurrentStartDataSize.reset(); - } - -public: - TFetchingScriptCursor(const std::shared_ptr& script, const ui32 index) - : CurrentStepIdx(index) - , Script(script) { - } - - const TString& GetName() const { - return Script->GetStep(CurrentStepIdx)->GetName(); - } - - TString DebugString() const { - return Script->GetStep(CurrentStepIdx)->DebugString(); - } - - bool Next() { - FlushDuration(); - return !Script->IsFinished(++CurrentStepIdx); + virtual TConclusion DoExecuteInplace( + const std::shared_ptr& sourceExt, const TFetchingScriptCursor& step) const override final { + const auto source = std::static_pointer_cast(sourceExt); + return DoExecuteInplace(source, step); } - TConclusion Execute(const std::shared_ptr& source); -}; - -class TStepAction: public IDataTasksProcessor::ITask { -private: - using TBase = IDataTasksProcessor::ITask; - std::shared_ptr Source; - TFetchingScriptCursor Cursor; - bool FinishedFlag = false; - -protected: - virtual bool DoApply(IDataReader& owner) const override; - virtual TConclusionStatus DoExecuteImpl() override; - public: - virtual TString GetTaskClassIdentifier() const override { - return "STEP_ACTION"; - } - - TStepAction(const std::shared_ptr& source, TFetchingScriptCursor&& cursor, const NActors::TActorId& ownerActorId) - : TBase(ownerActorId) - , Source(source) - , Cursor(std::move(cursor)) { - } + using TBase::TBase; }; class TBuildFakeSpec: public IFetchingStep { private: using TBase = IFetchingStep; - const ui32 Count = 0; protected: virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; - virtual ui64 DoPredictRawBytes(const std::shared_ptr& /*source*/) const override { - return TIndexInfo::GetSpecialColumnsRecordSize() * Count; - } public: - TBuildFakeSpec(const ui32 count) - : TBase("FAKE_SPEC") - , Count(count) { - AFL_VERIFY(Count); + TBuildFakeSpec() + : TBase("FAKE_SPEC") { } }; @@ -219,62 +72,39 @@ class TApplyIndexStep: public IFetchingStep { } }; -class TAllocateMemoryStep: public IFetchingStep { +class TDetectInMemStep: public IFetchingStep { private: using TBase = IFetchingStep; - TColumnsSetIds Columns; - const EStageFeaturesIndexes StageIndex; + const TColumnsSetIds Columns; protected: - class TFetchingStepAllocation: public NGroupedMemoryManager::IAllocation { - private: - using TBase = NGroupedMemoryManager::IAllocation; - std::weak_ptr Source; - TFetchingScriptCursor Step; - NColumnShard::TCounterGuard TasksGuard; - virtual bool DoOnAllocated(std::shared_ptr&& guard, - const std::shared_ptr& allocation) override; - - public: - TFetchingStepAllocation(const std::shared_ptr& source, const ui64 mem, const TFetchingScriptCursor& step); - }; - virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; - virtual ui64 GetProcessingDataSize(const std::shared_ptr& source) const override; - virtual ui64 DoPredictRawBytes(const std::shared_ptr& /*source*/) const override { - return 0; - } virtual TString DoDebugString() const override { - return TStringBuilder() << "columns=" << Columns.DebugString() << ";stage=" << StageIndex << ";"; + return TStringBuilder() << "columns=" << Columns.DebugString() << ";"; } public: - TAllocateMemoryStep(const TColumnsSetIds& columns, const EStageFeaturesIndexes stageIndex) - : TBase("ALLOCATE_MEMORY::" + ::ToString(stageIndex)) - , Columns(columns) - , StageIndex(stageIndex) { + virtual ui64 GetProcessingDataSize(const std::shared_ptr& source) const override; + TDetectInMemStep(const TColumnsSetIds& columns) + : TBase("FETCHING_COLUMNS") + , Columns(columns) { AFL_VERIFY(Columns.GetColumnsCount()); } }; -class TColumnBlobsFetchingStep: public IFetchingStep { +class TPortionAccessorFetchingStep: public IFetchingStep { private: using TBase = IFetchingStep; - TColumnsSetIds Columns; protected: virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; - virtual ui64 DoPredictRawBytes(const std::shared_ptr& source) const override; virtual TString DoDebugString() const override { - return TStringBuilder() << "columns=" << Columns.DebugString() << ";"; + return TStringBuilder(); } public: - virtual ui64 GetProcessingDataSize(const std::shared_ptr& source) const override; - TColumnBlobsFetchingStep(const TColumnsSetIds& columns) - : TBase("FETCHING_COLUMNS") - , Columns(columns) { - AFL_VERIFY(Columns.GetColumnsCount()); + TPortionAccessorFetchingStep() + : TBase("FETCHING_ACCESSOR") { } }; @@ -285,7 +115,6 @@ class TIndexBlobsFetchingStep: public IFetchingStep { protected: virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; - virtual ui64 DoPredictRawBytes(const std::shared_ptr& source) const override; virtual TString DoDebugString() const override { return TStringBuilder() << "indexes=" << Indexes->DebugString() << ";"; } @@ -299,56 +128,11 @@ class TIndexBlobsFetchingStep: public IFetchingStep { } }; -class TAssemblerStep: public IFetchingStep { -private: - using TBase = IFetchingStep; - YDB_READONLY_DEF(std::shared_ptr, Columns); - virtual TString DoDebugString() const override { - return TStringBuilder() << "columns=" << Columns->DebugString() << ";"; - } - -public: - virtual ui64 GetProcessingDataSize(const std::shared_ptr& source) const override; - virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; - TAssemblerStep(const std::shared_ptr& columns, const TString& specName = Default()) - : TBase("ASSEMBLER" + (specName ? "::" + specName : "")) - , Columns(columns) { - AFL_VERIFY(Columns); - AFL_VERIFY(Columns->GetColumnsCount()); - } -}; - -class TOptionalAssemblerStep: public IFetchingStep { -private: - using TBase = IFetchingStep; - YDB_READONLY_DEF(std::shared_ptr, Columns); - virtual TString DoDebugString() const override { - return TStringBuilder() << "columns=" << Columns->DebugString() << ";"; - } - -protected: - virtual bool DoInitSourceSeqColumnIds(const std::shared_ptr& source) const override; - -public: - virtual ui64 GetProcessingDataSize(const std::shared_ptr& source) const override; - - virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; - TOptionalAssemblerStep(const std::shared_ptr& columns, const TString& specName = Default()) - : TBase("OPTIONAL_ASSEMBLER" + (specName ? "::" + specName : "")) - , Columns(columns) { - AFL_VERIFY(Columns); - AFL_VERIFY(Columns->GetColumnsCount()); - } -}; - class TFilterProgramStep: public IFetchingStep { private: using TBase = IFetchingStep; std::shared_ptr Step; -protected: - virtual ui64 DoPredictRawBytes(const std::shared_ptr& source) const override; - public: virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; TFilterProgramStep(const std::shared_ptr& step) @@ -363,18 +147,13 @@ class TFilterCutLimit: public IFetchingStep { const ui32 Limit; const bool Reverse; -protected: - virtual ui64 DoPredictRawBytes(const std::shared_ptr& /*source*/) const override { - return 0; - } - public: virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; TFilterCutLimit(const ui32 limit, const bool reverse) : TBase("LIMIT") , Limit(limit) - , Reverse(reverse) - { + , Reverse(reverse) { + AFL_VERIFY(Limit); } }; @@ -400,6 +179,19 @@ class TSnapshotFilter: public IFetchingStep { } }; +class TDetectInMem: public IFetchingStep { +private: + using TBase = IFetchingStep; + TColumnsSetIds Columns; + +public: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + TDetectInMem(const TColumnsSetIds& columns) + : TBase("DETECT_IN_MEM") + , Columns(columns) { + } +}; + class TDeletionFilter: public IFetchingStep { private: using TBase = IFetchingStep; diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/interval.cpp b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/interval.cpp index 9da043a366c1..7f44376f3ad9 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/interval.cpp +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/interval.cpp @@ -10,15 +10,12 @@ void TFetchingInterval::ConstructResult() { if (ready != WaitSourcesCount) { AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "skip_construct_result")("interval_idx", IntervalIdx)( "count", WaitSourcesCount)("ready", ready)("interval_id", GetIntervalId()); - return; - } else { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "start_construct_result")("interval_idx", IntervalIdx)( - "interval_id", GetIntervalId()); - } - if (AtomicCas(&SourcesFinalized, 1, 0)) { + } else if (AtomicCas(&SourcesFinalized, 1, 0)) { IntervalStateGuard.SetStatus(NColumnShard::TScanCounters::EIntervalStatus::WaitMergerStart); MergingContext->SetIntervalChunkMemory(Context->GetMemoryForSources(Sources)); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "start_construct_result")("interval_idx", IntervalIdx)( + "interval_id", GetIntervalId())("memory", MergingContext->GetIntervalChunkMemory())("count", WaitSourcesCount); auto task = std::make_shared(MergingContext, Context, std::move(Sources)); task->SetPriority(NConveyor::ITask::EPriority::High); @@ -81,6 +78,8 @@ void TFetchingInterval::OnPartSendingComplete() { } IntervalStateGuard.SetStatus(NColumnShard::TScanCounters::EIntervalStatus::WaitMergerContinue); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "continue_construct_result")("interval_idx", IntervalIdx)( + "interval_id", GetIntervalId())("memory", MergingContext->GetIntervalChunkMemory())("count", WaitSourcesCount); auto task = std::make_shared(MergingContext, Context, std::move(Merger)); task->SetPriority(NConveyor::ITask::EPriority::High); NGroupedMemoryManager::TScanMemoryLimiterOperator::SendToAllocation(Context->GetProcessMemoryControlId(), diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/iterator.cpp b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/iterator.cpp index f705deb4501c..931a7a85ffaf 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/iterator.cpp +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/iterator.cpp @@ -2,35 +2,9 @@ namespace NKikimr::NOlap::NReader::NPlain { -TColumnShardScanIterator::TColumnShardScanIterator(const std::shared_ptr& context, const TReadMetadata::TConstPtr& readMetadata) - : Context(context) - , ReadMetadata(readMetadata) - , ReadyResults(context->GetCounters()) -{ - IndexedData = readMetadata->BuildReader(Context); - Y_ABORT_UNLESS(Context->GetReadMetadata()->IsSorted()); -} - -TConclusion> TColumnShardScanIterator::GetBatch() { - FillReadyResults(); - return ReadyResults.pop_front(); -} - -void TColumnShardScanIterator::PrepareResults() { - FillReadyResults(); -} - -TConclusion TColumnShardScanIterator::ReadNextInterval() { - return IndexedData->ReadNextInterval(); -} - -void TColumnShardScanIterator::DoOnSentDataFromInterval(const ui32 intervalIdx) const { - return IndexedData->OnSentDataFromInterval(intervalIdx); -} - void TColumnShardScanIterator::FillReadyResults() { auto ready = IndexedData->ExtractReadyResults(MaxRowsInBatch); - i64 limitLeft = Context->GetReadMetadata()->Limit == 0 ? INT64_MAX : Context->GetReadMetadata()->Limit - ItemsRead; + i64 limitLeft = Context->GetReadMetadata()->GetLimitRobust() - ItemsRead; for (size_t i = 0; i < ready.size() && limitLeft; ++i) { auto& batch = ReadyResults.emplace_back(std::move(ready[i])); if (batch->GetResultBatch().num_rows() > limitLeft) { @@ -41,22 +15,10 @@ void TColumnShardScanIterator::FillReadyResults() { } if (limitLeft == 0) { - AFL_NOTICE(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "limit_reached_on_scan")("limit", Context->GetReadMetadata()->Limit)("ready", ItemsRead); + AFL_NOTICE(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "limit_reached_on_scan")( + "limit", Context->GetReadMetadata()->GetLimitRobust())("ready", ItemsRead); IndexedData->Abort("records count limit exhausted"); } } -TColumnShardScanIterator::~TColumnShardScanIterator() { - if (!IndexedData->IsFinished()) { - IndexedData->Abort("iterator destructor"); - } - ReadMetadata->ReadStats->PrintToLog(); -} - -void TColumnShardScanIterator::Apply(const std::shared_ptr& task) { - if (!IndexedData->IsFinished()) { - Y_ABORT_UNLESS(task->Apply(*IndexedData)); - } -} - -} +} // namespace NKikimr::NOlap::NReader::NPlain diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/iterator.h b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/iterator.h index 38b1fcc29882..eef490520499 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/iterator.h +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/iterator.h @@ -1,104 +1,15 @@ #pragma once -#include -#include -#include -#include +#include namespace NKikimr::NOlap::NReader::NPlain { -class TReadyResults { +class TColumnShardScanIterator: public NCommon::TColumnShardScanIterator { private: - const NColumnShard::TConcreteScanCounters Counters; - std::deque> Data; - i64 RecordsCount = 0; -public: - TString DebugString() const { - TStringBuilder sb; - sb - << "count:" << Data.size() << ";" - << "records_count:" << RecordsCount << ";" - ; - if (Data.size()) { - sb << "schema=" << Data.front()->GetResultBatch().schema()->ToString() << ";"; - } - return sb; - } - TReadyResults(const NColumnShard::TConcreteScanCounters& counters) - : Counters(counters) - { - - } - const std::shared_ptr& emplace_back(std::shared_ptr&& v) { - AFL_VERIFY(!!v); - RecordsCount += v->GetResultBatch().num_rows(); - Data.emplace_back(std::move(v)); - return Data.back(); - } - std::shared_ptr pop_front() { - if (Data.empty()) { - return {}; - } - auto result = std::move(Data.front()); - AFL_VERIFY(RecordsCount >= result->GetResultBatch().num_rows()); - RecordsCount -= result->GetResultBatch().num_rows(); - Data.pop_front(); - return result; - } - bool empty() const { - return Data.empty(); - } - size_t size() const { - return Data.size(); - } -}; - -class TColumnShardScanIterator: public TScanIteratorBase { -private: - std::shared_ptr Context; - const TReadMetadata::TConstPtr ReadMetadata; - TReadyResults ReadyResults; - std::shared_ptr IndexedData; - ui64 ItemsRead = 0; - const i64 MaxRowsInBatch = 5000; - virtual void DoOnSentDataFromInterval(const ui32 intervalIdx) const override; + using TBase = NCommon::TColumnShardScanIterator; + virtual void FillReadyResults() override; public: - TColumnShardScanIterator(const std::shared_ptr& context, const TReadMetadata::TConstPtr& readMetadata); - ~TColumnShardScanIterator(); - - virtual TConclusionStatus Start() override { - AFL_VERIFY(IndexedData); - return IndexedData->Start(); - } - - virtual std::optional GetAvailableResultsCount() const override { - return ReadyResults.size(); - } - - virtual const TReadStats& GetStats() const override { - return *ReadMetadata->ReadStats; - } - - virtual TString DebugString(const bool verbose) const override { - return TStringBuilder() - << "ready_results:(" << ReadyResults.DebugString() << ");" - << "indexed_data:(" << IndexedData->DebugString(verbose) << ")" - ; - } - - virtual void Apply(const std::shared_ptr& task) override; - - bool Finished() const override { - return IndexedData->IsFinished() && ReadyResults.empty(); - } - - virtual TConclusion> GetBatch() override; - virtual void PrepareResults() override; - - virtual TConclusion ReadNextInterval() override; - -private: - void FillReadyResults(); + using TBase::TBase; }; -} +} // namespace NKikimr::NOlap::NReader::NPlain diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/merge.cpp b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/merge.cpp index edea4214e298..241040efd333 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/merge.cpp +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/merge.cpp @@ -33,6 +33,8 @@ void TBaseMergeTask::PrepareResultBatch() { LastPK = nullptr; return; } + const ui64 dataSizeBefore = NArrow::GetTableDataSize(ResultBatch); + const ui64 memorySizeBefore = NArrow::GetTableMemorySize(ResultBatch); { ResultBatch = NArrow::TColumnOperator().VerifyIfAbsent().Extract(ResultBatch, Context->GetProgramInputColumns()->GetColumnNamesVector()); AFL_VERIFY((ui32)ResultBatch->num_columns() == Context->GetProgramInputColumns()->GetColumnNamesVector().size()); @@ -45,7 +47,10 @@ void TBaseMergeTask::PrepareResultBatch() { } else { ShardedBatch = NArrow::TShardedRecordBatch(ResultBatch); } - AllocationGuard->Update(NArrow::GetTableMemorySize(ResultBatch)); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "update_memory_merger")("before_data", dataSizeBefore)( + "before_memory", memorySizeBefore)("after_memory", NArrow::GetTableMemorySize(ResultBatch))( + "after_data", NArrow::GetTableDataSize(ResultBatch))("guard", AllocationGuard->GetMemory()); + // AllocationGuard->Update(NArrow::GetTableMemorySize(ResultBatch)); AFL_VERIFY(!!LastPK == !!ShardedBatch->GetRecordsCount())("lpk", !!LastPK)("sb", ShardedBatch->GetRecordsCount()); } else { AllocationGuard = nullptr; @@ -85,7 +90,7 @@ TConclusionStatus TStartMergeTask::DoExecuteImpl() { break; } } - if ((MergingContext->IsExclusiveInterval() || Context->GetCommonContext()->GetReadMetadata()->HasGuaranteeExclusivePK()) && + if ((MergingContext->IsExclusiveInterval()) && sourcesInMemory) { TMemoryProfileGuard mGuard("SCAN_PROFILE::MERGE::EXCLUSIVE", IS_DEBUG_LOG_ENABLED(NKikimrServices::TX_COLUMNSHARD_SCAN_MEMORY)); auto& container = Sources.begin()->second->GetStageResult().GetBatch(); diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/merge.h b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/merge.h index bbe2d11ccb3a..074a1c42f960 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/merge.h +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/merge.h @@ -77,6 +77,9 @@ class TBaseMergeTask: public IDataTasksProcessor::ITask, public NGroupedMemoryMa virtual bool DoApply(IDataReader& indexedDataRead) const override; virtual bool DoOnAllocated(std::shared_ptr&& guard, const std::shared_ptr& allocation) override; + virtual void DoOnAllocationImpossible(const TString& errorMessage) override { + Context->GetCommonContext()->AbortWithError("cannot allocate memory for merge task: '" + errorMessage + "'"); + } public: TBaseMergeTask(const std::shared_ptr& mergingContext, const std::shared_ptr& readContext) diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/plain_read_data.cpp b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/plain_read_data.cpp index 5780b3219180..328d9a38c40e 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/plain_read_data.cpp +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/plain_read_data.cpp @@ -1,15 +1,17 @@ #include "plain_read_data.h" +#include + namespace NKikimr::NOlap::NReader::NPlain { TPlainReadData::TPlainReadData(const std::shared_ptr& context) : TBase(context) - , SpecialReadContext(std::make_shared(context)) -{ + , SpecialReadContext(std::make_shared(context)) { ui32 sourceIdx = 0; std::deque> sources; - const auto& portions = GetReadMetadata()->SelectInfo->PortionsOrderedPK; - const auto& committed = GetReadMetadata()->CommittedBlobs; + const auto readMetadata = GetReadMetadataVerifiedAs(); + const auto& portions = GetReadMetadata()->SelectInfo->Portions; + const auto& committed = readMetadata->CommittedBlobs; ui64 compactedPortionsBytes = 0; ui64 insertedPortionsBytes = 0; ui64 committedPortionsBytes = 0; @@ -28,8 +30,7 @@ TPlainReadData::TPlainReadData(const std::shared_ptr& context) if (GetReadMetadata()->IsMyUncommitted(i.GetInsertWriteId())) { continue; } - if (GetReadMetadata()->GetPKRangesFilter().CheckPoint(i.GetFirst()) || - GetReadMetadata()->GetPKRangesFilter().CheckPoint(i.GetLast())) { + if (GetReadMetadata()->GetPKRangesFilter().CheckPoint(i.GetFirst()) || GetReadMetadata()->GetPKRangesFilter().CheckPoint(i.GetLast())) { GetReadMetadata()->SetConflictedWriteId(i.GetInsertWriteId()); } } @@ -49,28 +50,28 @@ TPlainReadData::TPlainReadData(const std::shared_ptr& context) Scanner = std::make_shared(std::move(sources), SpecialReadContext); auto& stats = GetReadMetadata()->ReadStats; - stats->IndexPortions = GetReadMetadata()->SelectInfo->PortionsOrderedPK.size(); + stats->IndexPortions = GetReadMetadata()->SelectInfo->Portions.size(); stats->IndexBatches = GetReadMetadata()->NumIndexedBlobs(); - stats->CommittedBatches = GetReadMetadata()->CommittedBlobs.size(); + stats->CommittedBatches = readMetadata->CommittedBlobs.size(); stats->SchemaColumns = (*SpecialReadContext->GetProgramInputColumns() - *SpecialReadContext->GetSpecColumns()).GetColumnsCount(); stats->CommittedPortionsBytes = committedPortionsBytes; stats->InsertedPortionsBytes = insertedPortionsBytes; stats->CompactedPortionsBytes = compactedPortionsBytes; - } std::vector> TPlainReadData::DoExtractReadyResults(const int64_t /*maxRowsInBatch*/) { auto result = std::move(PartialResults); PartialResults.clear(); -// auto result = TPartialReadResult::SplitResults(std::move(PartialResults), maxRowsInBatch); + // auto result = TPartialReadResult::SplitResults(std::move(PartialResults), maxRowsInBatch); ui32 count = 0; - for (auto&& r: result) { + for (auto&& r : result) { count += r->GetRecordsCount(); } AFL_VERIFY(count == ReadyResultsCount); ReadyResultsCount = 0; - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "DoExtractReadyResults")("result", result.size())("count", count)("finished", Scanner->IsFinished()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "DoExtractReadyResults")("result", result.size())("count", count)( + "finished", Scanner->IsFinished()); return result; } @@ -79,9 +80,9 @@ TConclusion TPlainReadData::DoReadNextInterval() { } void TPlainReadData::OnIntervalResult(const std::shared_ptr& result) { -// result->GetResourcesGuardOnly()->Update(result->GetMemorySize()); + // result->GetResourcesGuardOnly()->Update(result->GetMemorySize()); ReadyResultsCount += result->GetRecordsCount(); PartialResults.emplace_back(result); } -} +} // namespace NKikimr::NOlap::NReader::NPlain diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/plain_read_data.h b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/plain_read_data.h index 93d2a56bad14..960f49541bc6 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/plain_read_data.h +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/plain_read_data.h @@ -1,11 +1,11 @@ #pragma once -#include "columns_set.h" -#include "source.h" #include "scanner.h" +#include "source.h" #include #include #include +#include namespace NKikimr::NOlap::NReader::NPlain { @@ -16,6 +16,7 @@ class TPlainReadData: public IDataReader, TNonCopyable, NColumnShard::TMonitorin std::shared_ptr SpecialReadContext; std::vector> PartialResults; ui32 ReadyResultsCount = 0; + protected: virtual TConclusionStatus DoStart() override { return Scanner->Start(); @@ -42,12 +43,18 @@ class TPlainReadData: public IDataReader, TNonCopyable, NColumnShard::TMonitorin virtual bool DoIsFinished() const override { return (Scanner->IsFinished() && PartialResults.empty()); } + public: virtual void OnSentDataFromInterval(const ui32 intervalIdx) const override { Scanner->OnSentDataFromInterval(intervalIdx); } - const TReadMetadata::TConstPtr& GetReadMetadata() const { + template + std::shared_ptr GetReadMetadataVerifiedAs() const { + return SpecialReadContext->GetReadMetadataVerifiedAs(); + } + + const NCommon::TReadMetadata::TConstPtr& GetReadMetadata() const { return SpecialReadContext->GetReadMetadata(); } @@ -73,4 +80,4 @@ class TPlainReadData: public IDataReader, TNonCopyable, NColumnShard::TMonitorin } }; -} +} // namespace NKikimr::NOlap::NReader::NPlain diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/scanner.cpp b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/scanner.cpp index 87de386beda9..70e76a9c919c 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/scanner.cpp +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/scanner.cpp @@ -2,6 +2,7 @@ #include "scanner.h" #include +#include #include @@ -10,7 +11,7 @@ namespace NKikimr::NOlap::NReader::NPlain { void TScanHead::OnIntervalResult(std::shared_ptr&& allocationGuard, const std::optional& newBatch, const std::shared_ptr& lastPK, std::unique_ptr&& merger, const ui32 intervalIdx, TPlainReadData& reader) { - if (Context->GetReadMetadata()->Limit && (!newBatch || newBatch->GetRecordsCount() == 0) && InFlightLimit < MaxInFlight) { + if (Context->GetReadMetadata()->HasLimit() && (!newBatch || newBatch->GetRecordsCount() == 0) && InFlightLimit < MaxInFlight) { InFlightLimit = std::min(MaxInFlight, InFlightLimit * 4); } auto itInterval = FetchingIntervals.find(intervalIdx); @@ -27,7 +28,9 @@ void TScanHead::OnIntervalResult(std::shared_ptrsecond->GetGroupGuard(); } - AFL_VERIFY(ReadyIntervals.emplace(intervalIdx, std::make_shared(std::move(allocationGuard), std::move(gGuard), *newBatch, lastPK, callbackIdxSubscriver)).second); + std::vector> guards = { std::move(allocationGuard) }; + AFL_VERIFY(ReadyIntervals.emplace(intervalIdx, std::make_shared(guards, std::move(gGuard), *newBatch, + std::make_shared(lastPK), Context->GetCommonContext(), callbackIdxSubscriver)).second); } else { AFL_VERIFY(ReadyIntervals.emplace(intervalIdx, nullptr).second); } @@ -67,24 +70,24 @@ void TScanHead::OnIntervalResult(std::shared_ptrGetCommonContext()->GetReadMetadata()->HasGuaranteeExclusivePK(); TScanContext context; for (auto itPoint = BorderPoints.begin(); itPoint != BorderPoints.end(); ++itPoint) { auto& point = itPoint->second; context.OnStartPoint(point); if (context.GetIsSpecialPoint()) { - auto detectorResult = DetectSourcesFeatureInContextIntervalScan(context.GetCurrentSources(), guaranteeExclusivePK); for (auto&& i : context.GetCurrentSources()) { i.second->IncIntervalsCount(); } - if (!detectorResult) { - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "scanner_initializer_aborted")( - "reason", detectorResult.GetErrorMessage()); - Abort(); - return detectorResult; - } } + const bool isExclusive = context.GetCurrentSources().size() == 1; + for (auto&& i : context.GetCurrentSources()) { + i.second->SetExclusiveIntervalOnly((isExclusive && i.second->GetExclusiveIntervalOnly() && !context.GetIsSpecialPoint())); + } + for (auto&& i : point.GetFinishSources()) { + if (!i->NeedAccessorsFetching()) { + i->SetSourceInMemory(true); + } i->InitFetchingPlan(Context->GetColumnsFetchingPlan(i)); } context.OnFinishPoint(point); @@ -95,14 +98,6 @@ TConclusionStatus TScanHead::Start() { for (auto&& i : context.GetCurrentSources()) { i.second->IncIntervalsCount(); } - auto detectorResult = - DetectSourcesFeatureInContextIntervalScan(context.GetCurrentSources(), guaranteeExclusivePK || context.GetIsExclusiveInterval()); - if (!detectorResult) { - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "scanner_initializer_aborted")( - "reason", detectorResult.GetErrorMessage()); - Abort(); - return detectorResult; - } } } return TConclusionStatus::Success(); @@ -116,7 +111,7 @@ TScanHead::TScanHead(std::deque>&& sources, const s } } - if (Context->GetReadMetadata()->Limit) { + if (Context->GetReadMetadata()->HasLimit()) { InFlightLimit = 1; } else { InFlightLimit = MaxInFlight; @@ -129,121 +124,6 @@ TScanHead::TScanHead(std::deque>&& sources, const s } } -class TSourcesStorageForMemoryOptimization { -private: - class TSourceInfo { - private: - YDB_READONLY(ui64, Memory, 0); - YDB_READONLY_DEF(std::shared_ptr, Source); - YDB_READONLY_DEF(std::shared_ptr, FetchingInfo); - - public: - TSourceInfo(const std::shared_ptr& source, const std::shared_ptr& fetchingInfo) - : Source(source) - , FetchingInfo(fetchingInfo) { - Memory = FetchingInfo->PredictRawBytes(Source); - } - - NJson::TJsonValue DebugJson() const { - NJson::TJsonValue result = NJson::JSON_MAP; - result.InsertValue("source", Source->DebugJsonForMemory()); - result.InsertValue("memory", Memory); - // result.InsertValue("FetchingInfo", FetchingInfo->DebugJsonForMemory()); - return result; - } - - bool ReduceMemory() { - const bool result = FetchingInfo->InitSourceSeqColumnIds(Source); - if (result) { - Memory = FetchingInfo->PredictRawBytes(Source); - } - return result; - } - - bool operator<(const TSourceInfo& item) const { - return Memory < item.Memory; - } - - }; - - std::vector Sources; - YDB_READONLY(ui64, MemorySum, 0); - -public: - TString DebugString() const { - NJson::TJsonValue resultJson; - auto& memorySourcesArr = resultJson.InsertValue("sources_by_memory", NJson::JSON_ARRAY); - resultJson.InsertValue("sources_by_memory_count", Sources.size()); - for (auto&& it: Sources) { - auto& sourceMap = memorySourcesArr.AppendValue(NJson::JSON_MAP); - auto& sourcesArr = sourceMap.InsertValue("sources", NJson::JSON_ARRAY); - sourcesArr.AppendValue(it.DebugJson()); - } - return resultJson.GetStringRobust(); - } - - void AddSource(const std::shared_ptr& source, const std::shared_ptr& fetching) { - Sources.emplace_back(TSourceInfo(source, fetching)); - MemorySum += Sources.back().GetMemory(); - } - - bool Optimize(const ui64 memoryLimit) { - if (MemorySum <= memoryLimit) { - return true; - } - std::sort(Sources.begin(), Sources.end()); - while (true) { - std::vector nextSources; - while (memoryLimit < MemorySum && Sources.size()) { - const ui64 currentMemory = Sources.back().GetMemory(); - if (Sources.back().ReduceMemory()) { - AFL_VERIFY(currentMemory <= MemorySum); - MemorySum -= currentMemory; - MemorySum += Sources.back().GetMemory(); - nextSources.emplace_back(std::move(Sources.back())); - } - Sources.pop_back(); - } - if (nextSources.empty() || MemorySum <= memoryLimit) { - break; - } - std::sort(nextSources.begin(), nextSources.end()); - std::swap(nextSources, Sources); - } - return MemorySum <= memoryLimit; - } -}; - -TConclusionStatus TScanHead::DetectSourcesFeatureInContextIntervalScan( - const THashMap>& intervalSources, const bool isExclusiveInterval) const { - TSourcesStorageForMemoryOptimization optimizer; - for (auto&& i : intervalSources) { - if (!isExclusiveInterval) { - i.second->SetExclusiveIntervalOnly(false); - } - auto fetchingPlan = Context->GetColumnsFetchingPlan(i.second); - optimizer.AddSource(i.second, fetchingPlan); - } - const ui64 startMemory = optimizer.GetMemorySum(); - if (!optimizer.Optimize(Context->ReduceMemoryIntervalLimit) && Context->RejectMemoryIntervalLimit < optimizer.GetMemorySum()) { - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "next_internal_broken")("reason", "a lot of memory need")("start", startMemory)( - "reduce_limit", Context->ReduceMemoryIntervalLimit)("reject_limit", Context->RejectMemoryIntervalLimit)( - "need", optimizer.GetMemorySum())("path_id", Context->GetReadMetadata()->GetPathId())( - "details", IS_LOG_PRIORITY_ENABLED(NActors::NLog::PRI_DEBUG, NKikimrServices::TX_COLUMNSHARD_SCAN) ? optimizer.DebugString() - : "NEED_DEBUG_LEVEL"); - Context->GetCommonContext()->GetCounters().OnOptimizedIntervalMemoryFailed(optimizer.GetMemorySum()); - return TConclusionStatus::Fail("We need a lot of memory in time for interval scanner: " + ::ToString(optimizer.GetMemorySum()) + - " path_id: " + Context->GetReadMetadata()->GetPathId() + ". We need wait compaction processing. Sorry."); - } else if (optimizer.GetMemorySum() < startMemory) { - AFL_INFO(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "memory_reduce_active")("reason", "need reduce memory")("start", startMemory)( - "reduce_limit", Context->ReduceMemoryIntervalLimit)("reject_limit", Context->RejectMemoryIntervalLimit)( - "need", optimizer.GetMemorySum())("path_id", Context->GetReadMetadata()->GetPathId()); - Context->GetCommonContext()->GetCounters().OnOptimizedIntervalMemoryReduced(startMemory - optimizer.GetMemorySum()); - } - Context->GetCommonContext()->GetCounters().OnOptimizedIntervalMemoryRequired(optimizer.GetMemorySum()); - return TConclusionStatus::Success(); -} - TConclusion TScanHead::BuildNextInterval() { if (Context->IsAborted()) { return false; diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/source.cpp b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/source.cpp index 5e4d80fbfe43..5b181499d00f 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/source.cpp +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/source.cpp @@ -1,91 +1,109 @@ -#include "constructor.h" #include "fetched_data.h" #include "interval.h" #include "plain_read_data.h" #include "source.h" -#include #include #include +#include +#include #include #include #include +#include + namespace NKikimr::NOlap::NReader::NPlain { void IDataSource::InitFetchingPlan(const std::shared_ptr& fetching) { AFL_VERIFY(fetching); - AFL_VERIFY(!FetchingPlan); + // AFL_VERIFY(!FetchingPlan); FetchingPlan = fetching; } void IDataSource::RegisterInterval(TFetchingInterval& interval, const std::shared_ptr& sourcePtr) { AFL_VERIFY(FetchingPlan); - AFL_VERIFY(!Context->IsAborted()); + AFL_VERIFY(!GetContext()->IsAborted()); if (!IsReadyFlag) { AFL_VERIFY(Intervals.emplace(interval.GetIntervalIdx(), &interval).second); } if (AtomicCas(&SourceStartedFlag, 1, 0)) { - SetFirstIntervalId(interval.GetIntervalId()); + SetMemoryGroupId(interval.GetIntervalId()); AFL_VERIFY(FetchingPlan); - StageData = std::make_unique(GetExclusiveIntervalOnly() && IsSourceInMemory()); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("InitFetchingPlan", FetchingPlan->DebugString())("source_idx", SourceIdx); - NActors::TLogContextGuard logGuard(NActors::TLogContextBuilder::Build()("source", SourceIdx)("method", "InitFetchingPlan")); - if (Context->IsAborted()) { + StageData = std::make_unique(GetExclusiveIntervalOnly()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("InitFetchingPlan", FetchingPlan->DebugString())("source_idx", GetSourceIdx()); + NActors::TLogContextGuard logGuard(NActors::TLogContextBuilder::Build()("source", GetSourceIdx())("method", "InitFetchingPlan")); + if (GetContext()->IsAborted()) { AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "InitFetchingPlanAborted"); return; } TFetchingScriptCursor cursor(FetchingPlan, 0); - auto task = std::make_shared(sourcePtr, std::move(cursor), Context->GetCommonContext()->GetScanActorId()); + auto task = std::make_shared(sourcePtr, std::move(cursor), GetContext()->GetCommonContext()->GetScanActorId()); NConveyor::TScanServiceOperator::SendTaskToExecute(task); } } -void IDataSource::SetIsReady() { +void IDataSource::DoOnSourceFetchingFinishedSafe(IDataReader& /*owner*/, const std::shared_ptr& /*sourcePtr*/) { AFL_VERIFY(!IsReadyFlag); IsReadyFlag = true; for (auto&& i : Intervals) { - i.second->OnSourceFetchStageReady(SourceIdx); + i.second->OnSourceFetchStageReady(GetSourceIdx()); } - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "source_ready")("intervals_count", Intervals.size())("source_idx", SourceIdx); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "source_ready")("intervals_count", Intervals.size())("source_idx", GetSourceIdx()); Intervals.clear(); } +void IDataSource::DoOnEmptyStageData(const std::shared_ptr& sourcePtr) { + if (ResourceGuards.size()) { + if (ExclusiveIntervalOnly) { + ResourceGuards.back()->Update(0); + } else { + ResourceGuards.back()->Update(GetColumnRawBytes(GetContext()->GetPKColumns()->GetColumnIds())); + } + } + DoBuildStageResult(sourcePtr); +} + +void IDataSource::DoBuildStageResult(const std::shared_ptr& /*sourcePtr*/) { + TMemoryProfileGuard mpg("SCAN_PROFILE::STAGE_RESULT", IS_DEBUG_LOG_ENABLED(NKikimrServices::TX_COLUMNSHARD_SCAN_MEMORY)); + StageResult = std::make_unique(std::move(StageData)); + StageData.reset(); +} + void TPortionDataSource::NeedFetchColumns(const std::set& columnIds, TBlobsAction& blobsAction, - THashMap& defaultBlocks, const std::shared_ptr& filter) { + THashMap& defaultBlocks, const std::shared_ptr& filter) { const NArrow::TColumnFilter& cFilter = filter ? *filter : NArrow::TColumnFilter::BuildAllowFilter(); ui32 fetchedChunks = 0; ui32 nullChunks = 0; for (auto&& i : columnIds) { - auto columnChunks = Portion->GetColumnChunksPointers(i); + auto columnChunks = GetStageData().GetPortionAccessor().GetColumnChunksPointers(i); if (columnChunks.empty()) { continue; } - auto itFilter = cFilter.GetIterator(false, Portion->NumRows(i)); + auto itFilter = cFilter.GetIterator(false, Portion->GetRecordsCount()); bool itFinished = false; for (auto&& c : columnChunks) { AFL_VERIFY(!itFinished); - if (!itFilter.IsBatchForSkip(c->GetMeta().GetNumRows())) { - auto reading = - blobsAction.GetReading(Schema->GetIndexInfo().GetColumnStorageId(c->GetColumnId(), Portion->GetMeta().GetTierName())); + if (!itFilter.IsBatchForSkip(c->GetMeta().GetRecordsCount())) { + auto reading = blobsAction.GetReading(Portion->GetColumnStorageId(c->GetColumnId(), Schema->GetIndexInfo())); reading->SetIsBackgroundProcess(false); reading->AddRange(Portion->RestoreBlobRange(c->BlobRange)); ++fetchedChunks; } else { - defaultBlocks.emplace(c->GetAddress(), - TPortionInfo::TAssembleBlobInfo(c->GetMeta().GetNumRows(), Schema->GetExternalDefaultValueVerified(c->GetColumnId()))); + defaultBlocks.emplace(c->GetAddress(), TPortionDataAccessor::TAssembleBlobInfo(c->GetMeta().GetRecordsCount(), + Schema->GetExternalDefaultValueVerified(c->GetColumnId()))); ++nullChunks; } - itFinished = !itFilter.Next(c->GetMeta().GetNumRows()); + itFinished = !itFilter.Next(c->GetMeta().GetRecordsCount()); } - AFL_VERIFY(itFinished)("filter", itFilter.DebugString())("count", Portion->NumRows(i)); + AFL_VERIFY(itFinished)("filter", itFilter.DebugString())("count", Portion->GetRecordsCount()); } AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "chunks_stats")("fetch", fetchedChunks)("null", nullChunks)( "reading_actions", blobsAction.GetStorageIds())("columns", columnIds.size()); } bool TPortionDataSource::DoStartFetchingColumns( - const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const TColumnsSetIds& columns) { + const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const TColumnsSetIds& columns) { AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", step.GetName()); AFL_VERIFY(columns.GetColumnsCount()); AFL_VERIFY(!StageData->GetAppliedFilter() || !StageData->GetAppliedFilter()->IsTotalDenyFilter()); @@ -94,7 +112,7 @@ bool TPortionDataSource::DoStartFetchingColumns( TBlobsAction action(GetContext()->GetCommonContext()->GetStoragesManager(), NBlobOperations::EConsumer::SCAN); { - THashMap nullBlocks; + THashMap nullBlocks; NeedFetchColumns(columnIds, action, nullBlocks, StageData->GetAppliedFilter()); StageData->AddDefaults(std::move(nullBlocks)); } @@ -104,7 +122,8 @@ bool TPortionDataSource::DoStartFetchingColumns( return false; } - auto constructor = std::make_shared(readActions, sourcePtr, step, GetContext(), "CS::READ::" + step.GetName(), ""); + auto constructor = + std::make_shared(readActions, sourcePtr, step, GetContext(), "CS::READ::" + step.GetName(), ""); NActors::TActivationContext::AsActorContext().Register(new NOlap::NBlobOperations::NRead::TActor(constructor)); return true; } @@ -118,13 +137,13 @@ bool TPortionDataSource::DoStartFetchingIndexes( TBlobsAction action(GetContext()->GetCommonContext()->GetStoragesManager(), NBlobOperations::EConsumer::SCAN); { std::set indexIds; - for (auto&& i : Portion->GetIndexes()) { + for (auto&& i : GetStageData().GetPortionAccessor().GetIndexesVerified()) { if (!indexes->GetIndexIdsSet().contains(i.GetIndexId())) { continue; } indexIds.emplace(i.GetIndexId()); if (auto bRange = i.GetBlobRangeOptional()) { - auto readAction = action.GetReading(Schema->GetIndexInfo().GetIndexStorageId(i.GetIndexId())); + auto readAction = action.GetReading(Portion->GetIndexStorageId(i.GetIndexId(), Schema->GetIndexInfo())); readAction->SetIsBackgroundProcess(false); readAction->AddRange(Portion->RestoreBlobRange(*bRange)); } @@ -139,7 +158,8 @@ bool TPortionDataSource::DoStartFetchingIndexes( return false; } - auto constructor = std::make_shared(readingActions, sourcePtr, step, GetContext(), "CS::READ::" + step.GetName(), ""); + auto constructor = + std::make_shared(readingActions, sourcePtr, step, GetContext(), "CS::READ::" + step.GetName(), ""); NActors::TActivationContext::AsActorContext().Register(new NOlap::NBlobOperations::NRead::TActor(constructor)); return true; } @@ -151,7 +171,7 @@ void TPortionDataSource::DoApplyIndex(const NIndexes::TIndexCheckerContainer& in THashMap> indexBlobs; std::set indexIds = indexChecker->GetIndexIds(); // NActors::TLogContextGuard gLog = NActors::TLogContextBuilder::Build()("records_count", GetRecordsCount())("portion_id", Portion->GetAddress().DebugString()); - std::vector pages = Portion->BuildPages(); + std::vector pages = GetStageData().GetPortionAccessor().BuildPages(); NArrow::TColumnFilter constructor = NArrow::TColumnFilter::BuildAllowFilter(); for (auto&& p : pages) { for (auto&& i : p.GetIndexes()) { @@ -177,7 +197,7 @@ void TPortionDataSource::DoApplyIndex(const NIndexes::TIndexCheckerContainer& in constructor.Add(false, p.GetRecordsCount()); } } - AFL_VERIFY(constructor.Size() == Portion->GetRecordsCount()); + AFL_VERIFY(constructor.GetRecordsCountVerified() == Portion->GetRecordsCount()); if (constructor.IsTotalDenyFilter()) { StageData->AddFilter(NArrow::TColumnFilter::BuildDenyFilter()); } else if (constructor.IsTotalAllowFilter()) { @@ -187,14 +207,67 @@ void TPortionDataSource::DoApplyIndex(const NIndexes::TIndexCheckerContainer& in } } -void TPortionDataSource::DoAssembleColumns(const std::shared_ptr& columns) { +void TPortionDataSource::DoAssembleColumns(const std::shared_ptr& columns, const bool sequential) { auto blobSchema = GetContext()->GetReadMetadata()->GetLoadSchemaVerified(*Portion); - MutableStageData().AddBatch(Portion->PrepareForAssemble(*blobSchema, columns->GetFilteredSchemaVerified(), MutableStageData().MutableBlobs()) - .AssembleToGeneralContainer(SequentialEntityIds)); + + std::optional ss; + if (Portion->HasInsertWriteId()) { + if (Portion->HasCommitSnapshot()) { + ss = Portion->GetCommitSnapshotVerified(); + } else if (GetContext()->GetReadMetadata()->IsMyUncommitted(Portion->GetInsertWriteIdVerified())) { + ss = GetContext()->GetReadMetadata()->GetRequestSnapshot(); + } + } + + auto batch = GetStageData() + .GetPortionAccessor() + .PrepareForAssemble(*blobSchema, columns->GetFilteredSchemaVerified(), MutableStageData().MutableBlobs(), ss) + .AssembleToGeneralContainer(sequential ? columns->GetColumnIds() : std::set()) + .DetachResult(); + + MutableStageData().AddBatch(batch); +} + +namespace { +class TPortionAccessorFetchingSubscriber: public IDataAccessorRequestsSubscriber { +private: + TFetchingScriptCursor Step; + std::shared_ptr Source; + const NColumnShard::TCounterGuard Guard; + virtual const std::shared_ptr& DoGetAbortionFlag() const override { + return Source->GetContext()->GetCommonContext()->GetAbortionFlag(); + } + virtual void DoOnRequestsFinished(TDataAccessorsResult&& result) override { + AFL_VERIFY(!result.HasErrors()); + AFL_VERIFY(result.GetPortions().size() == 1)("count", result.GetPortions().size()); + Source->MutableStageData().SetPortionAccessor(std::move(result.ExtractPortionsVector().front())); + AFL_VERIFY(Step.Next()); + auto task = std::make_shared(Source, std::move(Step), Source->GetContext()->GetCommonContext()->GetScanActorId()); + NConveyor::TScanServiceOperator::SendTaskToExecute(task); + } + +public: + TPortionAccessorFetchingSubscriber(const TFetchingScriptCursor& step, const std::shared_ptr& source) + : Step(step) + , Source(source) + , Guard(Source->GetContext()->GetCommonContext()->GetCounters().GetFetcherAcessorsGuard()) { + } +}; +} // namespace + +bool TPortionDataSource::DoStartFetchingAccessor(const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step) { + AFL_VERIFY(!StageData->HasPortionAccessor()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", step.GetName())("fetching_info", step.DebugString()); + + std::shared_ptr request = std::make_shared("PLAIN::" + step.GetName()); + request->AddPortion(Portion); + request->RegisterSubscriber(std::make_shared(step, sourcePtr)); + GetContext()->GetCommonContext()->GetDataAccessorsManager()->AskData(request); + return true; } bool TCommittedDataSource::DoStartFetchingColumns( - const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const TColumnsSetIds& /*columns*/) { + const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const TColumnsSetIds& /*columns*/) { if (ReadStarted) { return false; } @@ -208,23 +281,28 @@ bool TCommittedDataSource::DoStartFetchingColumns( readAction->AddRange(CommittedBlob.GetBlobRange()); std::vector> actions = { readAction }; - auto constructor = std::make_shared(actions, sourcePtr, step, GetContext(), "CS::READ::" + step.GetName(), ""); + auto constructor = std::make_shared(actions, sourcePtr, step, GetContext(), "CS::READ::" + step.GetName(), ""); NActors::TActivationContext::AsActorContext().Register(new NOlap::NBlobOperations::NRead::TActor(constructor)); return true; } -void TCommittedDataSource::DoAssembleColumns(const std::shared_ptr& columns) { +void TCommittedDataSource::DoAssembleColumns(const std::shared_ptr& columns, const bool /*sequential*/) { TMemoryProfileGuard mGuard("SCAN_PROFILE::ASSEMBLER::COMMITTED", IS_DEBUG_LOG_ENABLED(NKikimrServices::TX_COLUMNSHARD_SCAN_MEMORY)); - const ISnapshotSchema::TPtr batchSchema = GetContext()->GetReadMetadata()->GetIndexVersions().GetSchemaVerified(GetCommitted().GetSchemaVersion()); + const ISnapshotSchema::TPtr batchSchema = + GetContext()->GetReadMetadata()->GetIndexVersions().GetSchemaVerified(GetCommitted().GetSchemaVersion()); const ISnapshotSchema::TPtr resultSchema = GetContext()->GetReadMetadata()->GetResultSchema(); if (!GetStageData().GetTable()) { AFL_VERIFY(GetStageData().GetBlobs().size() == 1); auto bData = MutableStageData().ExtractBlob(GetStageData().GetBlobs().begin()->first); auto schema = GetContext()->GetReadMetadata()->GetBlobSchema(CommittedBlob.GetSchemaVersion()); - auto rBatch = NArrow::DeserializeBatch(bData, std::make_shared(CommittedBlob.GetSchemaSubset().Apply(schema->fields()))); - AFL_VERIFY(rBatch)("schema", schema->ToString()); + auto rBatch = NArrow::DeserializeBatch( + bData, std::make_shared(CommittedBlob.GetSchemaSubset().Apply(schema.begin(), schema.end()))); + AFL_VERIFY(rBatch)("schema", schema.ToString()); auto batch = std::make_shared(rBatch); - batchSchema->AdaptBatchToSchema(*batch, resultSchema); + std::set columnIdsToDelete = batchSchema->GetColumnIdsToDelete(resultSchema); + if (!columnIdsToDelete.empty()) { + batch->DeleteFieldsByIndex(batchSchema->ConvertColumnIdsToIndexes(columnIdsToDelete)); + } TSnapshot ss = TSnapshot::Zero(); if (CommittedBlob.IsCommitted()) { ss = CommittedBlob.GetCommittedSnapshotVerified(); diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/source.h b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/source.h index 69b39059bff5..460cb8e85f62 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/source.h +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/source.h @@ -1,5 +1,4 @@ #pragma once -#include "columns_set.h" #include "context.h" #include "fetched_data.h" @@ -9,6 +8,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -26,97 +28,48 @@ class TPlainReadData; class IFetchTaskConstructor; class IFetchingStep; -class IDataSource { +class IDataSource: public NCommon::IDataSource { private: + using TBase = NCommon::IDataSource; YDB_ACCESSOR(bool, ExclusiveIntervalOnly, true); - YDB_READONLY(ui32, SourceIdx, 0); YDB_READONLY_DEF(NArrow::NMerger::TSortableBatchPosition, Start); YDB_READONLY_DEF(NArrow::NMerger::TSortableBatchPosition, Finish); NArrow::TReplaceKey StartReplaceKey; NArrow::TReplaceKey FinishReplaceKey; - YDB_READONLY_DEF(std::shared_ptr, Context); - YDB_READONLY(TSnapshot, RecordSnapshotMin, TSnapshot::Zero()); - YDB_READONLY(TSnapshot, RecordSnapshotMax, TSnapshot::Zero()); - YDB_READONLY(ui32, RecordsCount, 0); - YDB_READONLY_DEF(std::optional, ShardingVersionOptional); - YDB_READONLY(bool, HasDeletions, false); YDB_READONLY(ui32, IntervalsCount, 0); virtual NJson::TJsonValue DoDebugJson() const = 0; bool MergingStartedFlag = false; TAtomic SourceStartedFlag = 0; std::shared_ptr FetchingPlan; - std::vector> ResourceGuards; - std::optional FirstIntervalId; ui32 CurrentPlanStepIndex = 0; YDB_READONLY(TPKRangeFilter::EUsageClass, UsageClass, TPKRangeFilter::EUsageClass::PartialUsage); + virtual void DoOnSourceFetchingFinishedSafe(IDataReader& owner, const std::shared_ptr& /*sourcePtr*/) override; + virtual void DoBuildStageResult(const std::shared_ptr& sourcePtr) override; + virtual void DoOnEmptyStageData(const std::shared_ptr& sourcePtr) override; + protected: - bool IsSourceInMemoryFlag = true; THashMap Intervals; - std::unique_ptr StageData; - std::unique_ptr StageResult; - TAtomic FilterStageFlag = 0; bool IsReadyFlag = false; - virtual bool DoStartFetchingColumns( - const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const TColumnsSetIds& columns) = 0; virtual bool DoStartFetchingIndexes( const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const std::shared_ptr& indexes) = 0; - virtual void DoAssembleColumns(const std::shared_ptr& columns) = 0; virtual void DoAbort() = 0; virtual void DoApplyIndex(const NIndexes::TIndexCheckerContainer& indexMeta) = 0; - virtual bool DoAddSequentialEntityIds(const ui32 entityId) = 0; virtual NJson::TJsonValue DoDebugJsonForMemory() const { return NJson::JSON_MAP; } - virtual bool DoAddTxConflict() = 0; + virtual bool DoStartFetchingAccessor(const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step) = 0; public: - bool AddTxConflict() { - if (!Context->GetCommonContext()->HasLock()) { - return false; - } - if (DoAddTxConflict()) { - StageData->Clear(); - return true; - } - return false; - } - - ui64 GetResourceGuardsMemory() const { - ui64 result = 0; - for (auto&& i : ResourceGuards) { - result += i->GetMemory(); - } - return result; - } - - void RegisterAllocationGuard(const std::shared_ptr& guard) { - ResourceGuards.emplace_back(guard); - } - - bool IsSourceInMemory() const { - return IsSourceInMemoryFlag; - } - void SetFirstIntervalId(const ui64 value) { - AFL_VERIFY(!FirstIntervalId); - FirstIntervalId = value; - } - ui64 GetFirstIntervalId() const { - AFL_VERIFY(!!FirstIntervalId); - return *FirstIntervalId; - } - virtual bool IsSourceInMemory(const std::set& fieldIds) const = 0; - bool AddSequentialEntityIds(const ui32 entityId) { - if (DoAddSequentialEntityIds(entityId)) { - IsSourceInMemoryFlag = false; - return true; - } - return false; + virtual bool NeedAccessorsForRead() const = 0; + virtual bool NeedAccessorsFetching() const = 0; + virtual ui64 PredictAccessorsMemory() const = 0; + bool StartFetchingAccessor(const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step) { + return DoStartFetchingAccessor(sourcePtr, step); } - virtual THashMap DecodeBlobAddresses(NBlobOperations::NRead::TCompositeReadBlobs&& blobsOriginal) const = 0; virtual ui64 GetPathId() const = 0; virtual bool HasIndexes(const std::set& indexIds) const = 0; @@ -128,33 +81,10 @@ class IDataSource { return FinishReplaceKey; } - const TFetchedResult& GetStageResult() const { - AFL_VERIFY(!!StageResult); - return *StageResult; - } - - void SetIsReady(); - - void Finalize() { - TMemoryProfileGuard mpg("SCAN_PROFILE::STAGE_RESULT", IS_DEBUG_LOG_ENABLED(NKikimrServices::TX_COLUMNSHARD_SCAN_MEMORY)); - StageResult = std::make_unique(std::move(StageData)); - } - void ApplyIndex(const NIndexes::TIndexCheckerContainer& indexMeta) { return DoApplyIndex(indexMeta); } - void AssembleColumns(const std::shared_ptr& columns) { - if (columns->IsEmpty()) { - return; - } - DoAssembleColumns(columns); - } - - bool StartFetchingColumns(const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const TColumnsSetIds& columns) { - return DoStartFetchingColumns(sourcePtr, step, columns); - } - bool StartFetchingIndexes( const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const std::shared_ptr& indexes) { AFL_VERIFY(indexes); @@ -169,9 +99,7 @@ class IDataSource { ++IntervalsCount; } - virtual ui64 GetColumnRawBytes(const std::set& columnIds) const = 0; virtual ui64 GetIndexRawBytes(const std::set& indexIds) const = 0; - virtual ui64 GetColumnBlobBytes(const std::set& columnsIds) const = 0; bool IsMergingStarted() const { return MergingStartedFlag; @@ -190,13 +118,14 @@ class IDataSource { NJson::TJsonValue DebugJsonForMemory() const { NJson::TJsonValue result = NJson::JSON_MAP; result.InsertValue("details", DoDebugJsonForMemory()); - result.InsertValue("count", RecordsCount); + result.InsertValue("count", GetRecordsCount()); return result; } NJson::TJsonValue DebugJson() const { NJson::TJsonValue result = NJson::JSON_MAP; - result.InsertValue("source_idx", SourceIdx); + result.InsertValue("source_id", GetSourceId()); + result.InsertValue("source_idx", GetSourceIdx()); result.InsertValue("start", Start.DebugJson()); result.InsertValue("finish", Finish.DebugJson()); result.InsertValue("specific", DoDebugJson()); @@ -209,44 +138,17 @@ class IDataSource { return IsReadyFlag; } - void OnEmptyStageData() { - if (!ResourceGuards.size()) { - return; - } - if (ExclusiveIntervalOnly) { - ResourceGuards.back()->Update(0); - } else { - ResourceGuards.back()->Update(GetColumnRawBytes(Context->GetPKColumns()->GetColumnIds())); - } - } - - const TFetchedData& GetStageData() const { - AFL_VERIFY(StageData); - return *StageData; - } - - TFetchedData& MutableStageData() { - AFL_VERIFY(StageData); - return *StageData; - } - void RegisterInterval(TFetchingInterval& interval, const std::shared_ptr& sourcePtr); - IDataSource(const ui32 sourceIdx, const std::shared_ptr& context, const NArrow::TReplaceKey& start, + IDataSource(const ui64 sourceId, const ui32 sourceIdx, const std::shared_ptr& context, const NArrow::TReplaceKey& start, const NArrow::TReplaceKey& finish, const TSnapshot& recordSnapshotMin, const TSnapshot& recordSnapshotMax, const ui32 recordsCount, const std::optional shardingVersion, const bool hasDeletions) - : SourceIdx(sourceIdx) + : TBase(sourceId, sourceIdx, context, recordSnapshotMin, recordSnapshotMax, recordsCount, shardingVersion, hasDeletions) , Start(context->GetReadMetadata()->BuildSortedPosition(start)) , Finish(context->GetReadMetadata()->BuildSortedPosition(finish)) , StartReplaceKey(start) - , FinishReplaceKey(finish) - , Context(context) - , RecordSnapshotMin(recordSnapshotMin) - , RecordSnapshotMax(recordSnapshotMax) - , RecordsCount(recordsCount) - , ShardingVersionOptional(shardingVersion) - , HasDeletions(hasDeletions) { - UsageClass = Context->GetReadMetadata()->GetPKRangesFilter().IsPortionInPartialUsage(GetStartReplaceKey(), GetFinishReplaceKey()); + , FinishReplaceKey(finish) { + UsageClass = GetContext()->GetReadMetadata()->GetPKRangesFilter().IsPortionInPartialUsage(GetStartReplaceKey(), GetFinishReplaceKey()); AFL_VERIFY(UsageClass != TPKRangeFilter::EUsageClass::DontUsage); AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "portions_for_merge")("start", Start.DebugJson())("finish", Finish.DebugJson()); if (Start.IsReverseSort()) { @@ -263,126 +165,120 @@ class IDataSource { class TPortionDataSource: public IDataSource { private: using TBase = IDataSource; - std::set SequentialEntityIds; - std::shared_ptr Portion; + const TPortionInfo::TConstPtr Portion; std::shared_ptr Schema; - mutable THashMap FingerprintedData; void NeedFetchColumns(const std::set& columnIds, TBlobsAction& blobsAction, - THashMap& nullBlocks, const std::shared_ptr& filter); + THashMap& nullBlocks, const std::shared_ptr& filter); virtual void DoApplyIndex(const NIndexes::TIndexCheckerContainer& indexChecker) override; virtual bool DoStartFetchingColumns( - const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const TColumnsSetIds& columns) override; - virtual bool DoStartFetchingIndexes( - const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const std::shared_ptr& indexes) override; - virtual void DoAssembleColumns(const std::shared_ptr& columns) override; + const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const TColumnsSetIds& columns) override; + virtual bool DoStartFetchingIndexes(const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, + const std::shared_ptr& indexes) override; + virtual void DoAssembleColumns(const std::shared_ptr& columns, const bool sequential) override; virtual NJson::TJsonValue DoDebugJson() const override { NJson::TJsonValue result = NJson::JSON_MAP; result.InsertValue("type", "portion"); result.InsertValue("info", Portion->DebugString()); + result.InsertValue("commit", Portion->GetCommitSnapshotOptional().value_or(TSnapshot::Zero()).DebugString()); + result.InsertValue("insert", (ui64)Portion->GetInsertWriteIdOptional().value_or(TInsertWriteId(0))); return result; } virtual NJson::TJsonValue DoDebugJsonForMemory() const override { NJson::TJsonValue result = TBase::DoDebugJsonForMemory(); - auto columns = Portion->GetColumnIds(); - for (auto&& i : SequentialEntityIds) { - AFL_VERIFY(columns.erase(i)); - } - // result.InsertValue("sequential_columns", JoinSeq(",", SequentialEntityIds)); - if (SequentialEntityIds.size()) { - result.InsertValue("min_memory_seq", Portion->GetMinMemoryForReadColumns(SequentialEntityIds)); - result.InsertValue("min_memory_seq_blobs", Portion->GetColumnBlobBytes(SequentialEntityIds)); - result.InsertValue("in_mem", Portion->GetColumnRawBytes(columns, false)); + if (GetStageData().HasPortionAccessor()) { + auto columns = GetStageData().GetPortionAccessor().GetColumnIds(); + // result.InsertValue("sequential_columns", JoinSeq(",", SequentialEntityIds)); + result.InsertValue("in_mem", GetStageData().GetPortionAccessor().GetColumnRawBytes(columns, false)); + result.InsertValue("columns_in_mem", JoinSeq(",", columns)); } - result.InsertValue("columns_in_mem", JoinSeq(",", columns)); result.InsertValue("portion_id", Portion->GetPortionId()); result.InsertValue("raw", Portion->GetTotalRawBytes()); result.InsertValue("blob", Portion->GetTotalBlobBytes()); - result.InsertValue("read_memory", GetColumnRawBytes(Portion->GetColumnIds())); + result.InsertValue("read_memory", GetColumnRawBytes(GetStageData().GetPortionAccessor().GetColumnIds())); return result; } virtual void DoAbort() override; virtual ui64 GetPathId() const override { return Portion->GetPathId(); } - virtual bool DoAddSequentialEntityIds(const ui32 entityId) override { - FingerprintedData.clear(); - return SequentialEntityIds.emplace(entityId).second; - } + + virtual bool DoStartFetchingAccessor(const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step) override; public: + virtual ui64 PredictAccessorsMemory() const override { + return Portion->GetApproxChunksCount(GetContext()->GetCommonContext()->GetReadMetadata()->GetResultSchema()->GetColumnsCount()) * + sizeof(TColumnRecord); + } + + virtual bool NeedAccessorsForRead() const override { + return true; + } + + virtual bool NeedAccessorsFetching() const override { + return !StageData || !StageData->HasPortionAccessor(); + } + virtual bool DoAddTxConflict() override { - GetContext()->GetReadMetadata()->SetBrokenWithCommitted(); + if (Portion->HasCommitSnapshot() || !Portion->HasInsertWriteId()) { + GetContext()->GetReadMetadata()->SetBrokenWithCommitted(); + return true; + } else if (!GetContext()->GetReadMetadata()->IsMyUncommitted(Portion->GetInsertWriteIdVerified())) { + GetContext()->GetReadMetadata()->SetConflictedWriteId(Portion->GetInsertWriteIdVerified()); + return true; + } return false; } virtual bool HasIndexes(const std::set& indexIds) const override { - return Portion->HasIndexes(indexIds); + return Schema->GetIndexInfo().HasIndexes(indexIds); } virtual THashMap DecodeBlobAddresses(NBlobOperations::NRead::TCompositeReadBlobs&& blobsOriginal) const override { - return Portion->DecodeBlobAddresses(std::move(blobsOriginal), Schema->GetIndexInfo()); - } - - virtual bool IsSourceInMemory(const std::set& fieldIds) const override { - for (auto&& i : SequentialEntityIds) { - if (fieldIds.contains(i)) { - return false; - } + return GetStageData().GetPortionAccessor().DecodeBlobAddresses(std::move(blobsOriginal), Schema->GetIndexInfo()); + } + + virtual ui64 GetColumnsVolume(const std::set& columnIds, const EMemType type) const override { + AFL_VERIFY(columnIds.size()); + switch (type) { + case EMemType::Raw: + return GetStageData().GetPortionAccessor().GetColumnRawBytes(columnIds, false); + case EMemType::Blob: + return GetStageData().GetPortionAccessor().GetColumnBlobBytes(columnIds, false); + case EMemType::RawSequential: + return GetStageData().GetPortionAccessor().GetMinMemoryForReadColumns(columnIds); } - return true; } virtual ui64 GetColumnRawBytes(const std::set& columnsIds) const override { AFL_VERIFY(columnsIds.size()); - const ui64 fp = CombineHashes(*columnsIds.begin(), *columnsIds.rbegin()); - auto it = FingerprintedData.find(fp); - if (it != FingerprintedData.end()) { - return it->second; - } - ui64 result = 0; - if (SequentialEntityIds.size()) { - std::set selectedSeq; - std::set selectedInMem; - for (auto&& i : columnsIds) { - if (SequentialEntityIds.contains(i)) { - selectedSeq.emplace(i); - } else { - selectedInMem.emplace(i); - } - } - result = Portion->GetMinMemoryForReadColumns(selectedSeq) + Portion->GetColumnBlobBytes(selectedSeq) + - Portion->GetColumnRawBytes(selectedInMem, false); - } else { - result = Portion->GetColumnRawBytes(columnsIds, false); - } - FingerprintedData.emplace(fp, result); - return result; + return GetStageData().GetPortionAccessor().GetColumnRawBytes(columnsIds, false); } virtual ui64 GetColumnBlobBytes(const std::set& columnsIds) const override { - return Portion->GetColumnBlobBytes(columnsIds, false); + return GetStageData().GetPortionAccessor().GetColumnBlobBytes(columnsIds, false); } virtual ui64 GetIndexRawBytes(const std::set& indexIds) const override { - return Portion->GetIndexRawBytes(indexIds, false); + return GetStageData().GetPortionAccessor().GetIndexRawBytes(indexIds, false); } const TPortionInfo& GetPortionInfo() const { return *Portion; } - std::shared_ptr GetPortionInfoPtr() const { + const TPortionInfo::TConstPtr& GetPortionInfoPtr() const { return Portion; } TPortionDataSource(const ui32 sourceIdx, const std::shared_ptr& portion, const std::shared_ptr& context) - : TBase(sourceIdx, context, portion->IndexKeyStart(), portion->IndexKeyEnd(), portion->RecordSnapshotMin(), portion->RecordSnapshotMax(), - portion->GetRecordsCount(), portion->GetShardingVersionOptional(), portion->GetMeta().GetDeletionsCount()) + : TBase(portion->GetPortionId(), sourceIdx, context, portion->IndexKeyStart(), portion->IndexKeyEnd(), + portion->RecordSnapshotMin(TSnapshot::Zero()), portion->RecordSnapshotMax(TSnapshot::Zero()), portion->GetRecordsCount(), + portion->GetShardingVersionOptional(), portion->GetMeta().GetDeletionsCount()) , Portion(portion) - , Schema(GetContext()->GetReadMetadata()->GetLoadSchemaVerified(*Portion)) { + , Schema(GetContext()->GetReadMetadata()->GetLoadSchemaVerified(*portion)) { } }; @@ -396,7 +292,7 @@ class TCommittedDataSource: public IDataSource { } virtual bool DoStartFetchingColumns( - const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const TColumnsSetIds& columns) override; + const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const TColumnsSetIds& columns) override; virtual bool DoStartFetchingIndexes(const std::shared_ptr& /*sourcePtr*/, const TFetchingScriptCursor& /*step*/, const std::shared_ptr& /*indexes*/) override { return false; @@ -405,7 +301,7 @@ class TCommittedDataSource: public IDataSource { return; } - virtual void DoAssembleColumns(const std::shared_ptr& columns) override; + virtual void DoAssembleColumns(const std::shared_ptr& columns, const bool sequential) override; virtual NJson::TJsonValue DoDebugJson() const override { NJson::TJsonValue result = NJson::JSON_MAP; result.InsertValue("type", "commit"); @@ -415,9 +311,6 @@ class TCommittedDataSource: public IDataSource { virtual ui64 GetPathId() const override { return 0; } - virtual bool DoAddSequentialEntityIds(const ui32 /*entityId*/) override { - return false; - } virtual bool DoAddTxConflict() override { if (CommittedBlob.IsCommitted()) { @@ -431,6 +324,18 @@ class TCommittedDataSource: public IDataSource { } public: + virtual ui64 PredictAccessorsMemory() const override { + return 0; + } + + virtual bool NeedAccessorsForRead() const override { + return false; + } + + virtual bool NeedAccessorsFetching() const override { + return false; + } + virtual THashMap DecodeBlobAddresses(NBlobOperations::NRead::TCompositeReadBlobs&& blobsOriginal) const override { THashMap result; for (auto&& i : blobsOriginal) { @@ -441,10 +346,6 @@ class TCommittedDataSource: public IDataSource { return result; } - virtual bool IsSourceInMemory(const std::set& /*fieldIds*/) const override { - return true; - } - virtual bool HasIndexes(const std::set& /*indexIds*/) const override { return false; } @@ -457,6 +358,22 @@ class TCommittedDataSource: public IDataSource { return CommittedBlob.GetBlobRange().Size; } + virtual bool DoStartFetchingAccessor(const std::shared_ptr& /*sourcePtr*/, const TFetchingScriptCursor& /*step*/) override { + return false; + } + + virtual ui64 GetColumnsVolume(const std::set& columnIds, const EMemType type) const override { + AFL_VERIFY(columnIds.size()); + switch (type) { + case EMemType::Raw: + return GetColumnRawBytes(columnIds); + case EMemType::Blob: + return GetColumnBlobBytes(columnIds); + case EMemType::RawSequential: + return GetColumnRawBytes(columnIds); + } + } + virtual ui64 GetIndexRawBytes(const std::set& /*columnIds*/) const override { AFL_VERIFY(false); return 0; @@ -467,8 +384,9 @@ class TCommittedDataSource: public IDataSource { } TCommittedDataSource(const ui32 sourceIdx, const TCommittedBlob& committed, const std::shared_ptr& context) - : TBase(sourceIdx, context, committed.GetFirst(), committed.GetLast(), committed.GetCommittedSnapshotDef(TSnapshot::Zero()), - committed.GetCommittedSnapshotDef(TSnapshot::Zero()), committed.GetRecordsCount(), {}, committed.GetIsDelete()) + : TBase((ui64)committed.GetInsertWriteId(), sourceIdx, context, committed.GetFirst(), committed.GetLast(), + committed.GetCommittedSnapshotDef(TSnapshot::Zero()), committed.GetCommittedSnapshotDef(TSnapshot::Zero()), + committed.GetRecordsCount(), {}, committed.GetIsDelete()) , CommittedBlob(committed) { } }; diff --git a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/ya.make b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/ya.make index 93ba27575ade..d19dede6b2ba 100644 --- a/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/ya.make +++ b/ydb/core/tx/columnshard/engines/reader/plain_reader/iterator/ya.make @@ -2,13 +2,11 @@ LIBRARY() SRCS( scanner.cpp - constructor.cpp source.cpp interval.cpp fetched_data.cpp plain_read_data.cpp merge.cpp - columns_set.cpp context.cpp fetching.cpp iterator.cpp @@ -17,10 +15,9 @@ SRCS( PEERDIR( ydb/core/formats/arrow ydb/core/tx/columnshard/blobs_action + ydb/core/tx/columnshard/engines/reader/common_reader/iterator ydb/core/tx/conveyor/usage ydb/core/tx/limiter/grouped_memory/usage ) -GENERATE_ENUM_SERIALIZATION(columns_set.h) - END() diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/constructor.cpp b/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/constructor.cpp new file mode 100644 index 000000000000..4a3946192f13 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/constructor.cpp @@ -0,0 +1,47 @@ +#include "constructor.h" +#include "read_metadata.h" +#include "resolver.h" + +#include + +namespace NKikimr::NOlap::NReader::NSimple { + +NKikimr::TConclusionStatus TIndexScannerConstructor::ParseProgram( + const TVersionedIndex* vIndex, const NKikimrTxDataShard::TEvKqpScan& proto, TReadDescription& read) const { + AFL_VERIFY(vIndex); + auto& indexInfo = vIndex->GetSchemaVerified(Snapshot)->GetIndexInfo(); + TIndexColumnResolver columnResolver(indexInfo); + return TBase::ParseProgram(vIndex, proto.GetOlapProgramType(), proto.GetOlapProgram(), read, columnResolver); +} + +std::vector TIndexScannerConstructor::GetPrimaryKeyScheme(const NColumnShard::TColumnShard* self) const { + auto& indexInfo = self->TablesManager.GetIndexInfo(Snapshot); + return indexInfo.GetPrimaryKeyColumns(); +} + +NKikimr::TConclusion> TIndexScannerConstructor::DoBuildReadMetadata( + const NColumnShard::TColumnShard* self, const TReadDescription& read) const { + auto& insertTable = self->InsertTable; + auto& index = self->TablesManager.GetPrimaryIndex(); + if (!insertTable || !index) { + return std::shared_ptr(); + } + + if (read.GetSnapshot().GetPlanInstant() < self->GetMinReadSnapshot().GetPlanInstant()) { + return TConclusionStatus::Fail(TStringBuilder() << "Snapshot too old: " << read.GetSnapshot() << ". CS min read snapshot: " + << self->GetMinReadSnapshot() << ". now: " << TInstant::Now()); + } + + TDataStorageAccessor dataAccessor(insertTable, index); + AFL_VERIFY(read.PathId); + auto readMetadata = std::make_shared(read.PathId, index->CopyVersionedIndexPtr(), read.GetSnapshot(), + IsReverse ? TReadMetadataBase::ESorting::DESC : TReadMetadataBase::ESorting::ASC, read.GetProgram(), read.GetScanCursor()); + + auto initResult = readMetadata->Init(self, read, dataAccessor); + if (!initResult) { + return initResult; + } + return static_pointer_cast(readMetadata); +} + +} // namespace NKikimr::NOlap::NReader::NSimple diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/constructor.h b/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/constructor.h new file mode 100644 index 000000000000..76596f8dd94e --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/constructor.h @@ -0,0 +1,28 @@ +#pragma once +#include + +namespace NKikimr::NOlap::NReader::NSimple { + +class TIndexScannerConstructor: public IScannerConstructor { +public: + static TString GetClassNameStatic() { + return "SIMPLE"; + } + +private: + using TBase = IScannerConstructor; + static const inline TFactory::TRegistrator Registrator = + TFactory::TRegistrator(GetClassNameStatic()); + virtual std::shared_ptr DoBuildCursor() const override { + return std::make_shared(); + } + +protected: + virtual TConclusion> DoBuildReadMetadata(const NColumnShard::TColumnShard* self, const TReadDescription& read) const override; +public: + using TBase::TBase; + virtual TConclusionStatus ParseProgram(const TVersionedIndex* vIndex, const NKikimrTxDataShard::TEvKqpScan& proto, TReadDescription& read) const override; + virtual std::vector GetPrimaryKeyScheme(const NColumnShard::TColumnShard* self) const override; +}; + +} \ No newline at end of file diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/read_metadata.cpp b/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/read_metadata.cpp new file mode 100644 index 000000000000..31ad53590f62 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/read_metadata.cpp @@ -0,0 +1,26 @@ +#include "read_metadata.h" + +#include +#include +#include +#include + +namespace NKikimr::NOlap::NReader::NSimple { + +std::unique_ptr TReadMetadata::StartScan(const std::shared_ptr& readContext) const { + return std::make_unique(readContext, readContext->GetReadMetadataPtrVerifiedAs()); +} + +TConclusionStatus TReadMetadata::DoInitCustom( + const NColumnShard::TColumnShard* owner, const TReadDescription& readDescription, const TDataStorageAccessor& dataAccessor) { + Y_UNUSED(owner); + Y_UNUSED(readDescription); + Y_UNUSED(dataAccessor); + return TConclusionStatus::Success(); +} + +std::shared_ptr TReadMetadata::BuildReader(const std::shared_ptr& context) const { + return std::make_shared(context); +} + +} // namespace NKikimr::NOlap::NReader::NSimple diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/read_metadata.h b/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/read_metadata.h new file mode 100644 index 000000000000..be603922a060 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/read_metadata.h @@ -0,0 +1,25 @@ +#pragma once +#include + +namespace NKikimr::NOlap::NReader::NSimple { + +class TReadMetadata: public NCommon::TReadMetadata { + using TBase = NCommon::TReadMetadata; + +public: + using TConstPtr = std::shared_ptr; + using TBase::TBase; + + virtual bool Empty() const override { + Y_ABORT_UNLESS(SelectInfo); + return SelectInfo->Portions.empty(); + } + + virtual std::shared_ptr BuildReader(const std::shared_ptr& context) const override; + virtual TConclusionStatus DoInitCustom( + const NColumnShard::TColumnShard* owner, const TReadDescription& readDescription, const TDataStorageAccessor& dataAccessor) override; + + virtual std::unique_ptr StartScan(const std::shared_ptr& readContext) const override; +}; + +} // namespace NKikimr::NOlap::NReader::NSimple diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/resolver.cpp b/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/resolver.cpp new file mode 100644 index 000000000000..5f0452250202 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/resolver.cpp @@ -0,0 +1,5 @@ +#include "resolver.h" + +namespace NKikimr::NOlap::NReader::NSimple { + +} \ No newline at end of file diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/resolver.h b/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/resolver.h new file mode 100644 index 000000000000..6267658734e5 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/resolver.h @@ -0,0 +1,28 @@ +#pragma once +#include +#include + +namespace NKikimr::NOlap::NReader::NSimple { + +class TIndexColumnResolver: public IColumnResolver { + const NOlap::TIndexInfo& IndexInfo; + +public: + explicit TIndexColumnResolver(const NOlap::TIndexInfo& indexInfo) + : IndexInfo(indexInfo) { + } + + virtual std::optional GetColumnIdOptional(const TString& name) const override { + return IndexInfo.GetColumnIdOptional(name); + } + + TString GetColumnName(ui32 id, bool required) const override { + return IndexInfo.GetColumnName(id, required); + } + + NSsa::TColumnInfo GetDefaultColumn() const override { + return NSsa::TColumnInfo::Original((ui32)NOlap::TIndexInfo::ESpecialColumn::PLAN_STEP, NOlap::TIndexInfo::SPEC_COL_PLAN_STEP); + } +}; + +} \ No newline at end of file diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/ya.make b/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/ya.make new file mode 100644 index 000000000000..165408de6d67 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/constructor/ya.make @@ -0,0 +1,15 @@ +LIBRARY() + +SRCS( + GLOBAL constructor.cpp + resolver.cpp + read_metadata.cpp +) + +PEERDIR( + ydb/core/tx/columnshard/engines/reader/abstract + ydb/core/tx/columnshard/engines/reader/common_reader/constructor + ydb/core/kqp/compute_actor +) + +END() diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/context.cpp b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/context.cpp new file mode 100644 index 000000000000..a14f1c17c920 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/context.cpp @@ -0,0 +1,155 @@ +#include "context.h" +#include "source.h" + +#include +#include + +namespace NKikimr::NOlap::NReader::NSimple { + +std::shared_ptr TSpecialReadContext::DoGetColumnsFetchingPlan(const std::shared_ptr& sourceExt) { + const auto source = std::static_pointer_cast(sourceExt); + const bool needSnapshots = GetReadMetadata()->GetRequestSnapshot() < source->GetRecordSnapshotMax(); + if (!needSnapshots && GetFFColumns()->GetColumnIds().size() == 1 && + GetFFColumns()->GetColumnIds().contains(NOlap::NPortion::TSpecialColumns::SPEC_COL_PLAN_STEP_INDEX)) { + std::shared_ptr result = std::make_shared(*this); + source->SetSourceInMemory(true); + result->SetBranchName("FAKE"); + result->AddStep(); + result->AddStep(0, source->GetRecordsCount()); + return result; + } + if (!source->GetStageData().HasPortionAccessor()) { + if (!AskAccumulatorsScript) { + AskAccumulatorsScript = std::make_shared(*this); + AskAccumulatorsScript->AddStep( + source->PredictAccessorsSize(GetFFColumns()->GetColumnIds()), EStageFeaturesIndexes::Accessors); + AskAccumulatorsScript->AddStep(); + AskAccumulatorsScript->AddStep(*GetFFColumns()); + } + return AskAccumulatorsScript; + } + const bool partialUsageByPK = [&]() { + switch (source->GetUsageClass()) { + case TPKRangeFilter::EUsageClass::PartialUsage: + return true; + case TPKRangeFilter::EUsageClass::DontUsage: + return true; + case TPKRangeFilter::EUsageClass::FullUsage: + return false; + } + }(); + const bool useIndexes = (IndexChecker ? source->HasIndexes(IndexChecker->GetIndexIds()) : false); + const bool hasDeletions = source->GetHasDeletions(); + bool needShardingFilter = false; + if (!!GetReadMetadata()->GetRequestShardingInfo()) { + auto ver = source->GetShardingVersionOptional(); + if (!ver || *ver < GetReadMetadata()->GetRequestShardingInfo()->GetSnapshotVersion()) { + needShardingFilter = true; + } + } + { + auto result = CacheFetchingScripts[needSnapshots ? 1 : 0][partialUsageByPK ? 1 : 0][useIndexes ? 1 : 0][needShardingFilter ? 1 : 0] + [hasDeletions ? 1 : 0]; + if (!result) { + TGuard wg(Mutex); + result = CacheFetchingScripts[needSnapshots ? 1 : 0][partialUsageByPK ? 1 : 0][useIndexes ? 1 : 0][needShardingFilter ? 1 : 0] + [hasDeletions ? 1 : 0]; + if (!result) { + result = BuildColumnsFetchingPlan(needSnapshots, partialUsageByPK, useIndexes, needShardingFilter, hasDeletions); + CacheFetchingScripts[needSnapshots ? 1 : 0][partialUsageByPK ? 1 : 0][useIndexes ? 1 : 0][needShardingFilter ? 1 : 0] + [hasDeletions ? 1 : 0] = result; + } + } + AFL_VERIFY(result); + AFL_VERIFY(*result); + return *result; + } +} + +std::shared_ptr TSpecialReadContext::BuildColumnsFetchingPlan(const bool needSnapshots, const bool partialUsageByPredicateExt, + const bool useIndexes, const bool needFilterSharding, const bool needFilterDeletion) const { + std::shared_ptr result = std::make_shared(*this); + const bool partialUsageByPredicate = partialUsageByPredicateExt && GetPredicateColumns()->GetColumnsCount(); + + NCommon::TColumnsAccumulator acc(GetMergeColumns(), GetReadMetadata()->GetResultSchema()); + if (!!IndexChecker && useIndexes) { + result->AddStep(std::make_shared(std::make_shared(IndexChecker->GetIndexIds()))); + result->AddStep(std::make_shared(IndexChecker)); + } + if (needFilterSharding && !GetShardingColumns()->IsEmpty()) { + const TColumnsSetIds columnsFetch = *GetShardingColumns(); + acc.AddFetchingStep(*result, columnsFetch, EStageFeaturesIndexes::Filter); + acc.AddAssembleStep(*result, columnsFetch, "SPEC_SHARDING", EStageFeaturesIndexes::Filter, false); + result->AddStep(std::make_shared()); + } + { + result->SetBranchName("exclusive"); + TColumnsSet columnsFetch = *GetEFColumns(); + if (needFilterDeletion) { + columnsFetch = columnsFetch + *GetDeletionColumns(); + } + if (needSnapshots || GetFFColumns()->Cross(*GetSpecColumns())) { + columnsFetch = columnsFetch + *GetSpecColumns(); + } + if (partialUsageByPredicate) { + columnsFetch = columnsFetch + *GetPredicateColumns(); + } + + if (columnsFetch.GetColumnsCount()) { + acc.AddFetchingStep(*result, columnsFetch, EStageFeaturesIndexes::Filter); + } + + if (needFilterDeletion) { + acc.AddAssembleStep(*result, *GetDeletionColumns(), "SPEC_DELETION", EStageFeaturesIndexes::Filter, false); + result->AddStep(std::make_shared()); + } + if (partialUsageByPredicate) { + acc.AddAssembleStep(*result, *GetPredicateColumns(), "PREDICATE", EStageFeaturesIndexes::Filter, false); + result->AddStep(std::make_shared()); + } + if (needSnapshots || GetFFColumns()->Cross(*GetSpecColumns())) { + acc.AddAssembleStep(*result, *GetSpecColumns(), "SPEC", EStageFeaturesIndexes::Filter, false); + result->AddStep(std::make_shared()); + } + for (auto&& i : GetReadMetadata()->GetProgram().GetSteps()) { + if (i->GetFilterOriginalColumnIds().empty()) { + break; + } + TColumnsSet stepColumnIds(i->GetFilterOriginalColumnIds(), GetReadMetadata()->GetResultSchema()); + acc.AddAssembleStep(*result, stepColumnIds, "EF", EStageFeaturesIndexes::Filter, false); + result->AddStep(std::make_shared(i)); + if (!i->IsFilterOnly()) { + break; + } + } + if (GetReadMetadata()->HasLimit()) { + result->AddStep(std::make_shared(GetReadMetadata()->GetLimitRobust(), GetReadMetadata()->IsDescSorted())); + } + acc.AddFetchingStep(*result, *GetFFColumns(), EStageFeaturesIndexes::Fetching); + acc.AddAssembleStep(*result, *GetFFColumns(), "LAST", EStageFeaturesIndexes::Fetching, false); + } + result->AddStep(); + result->AddStep(); + return result; +} + +TSpecialReadContext::TSpecialReadContext(const std::shared_ptr& commonContext) + : TBase(commonContext) { +} + +TString TSpecialReadContext::ProfileDebugString() const { + TStringBuilder sb; + const auto GetBit = [](const ui32 val, const ui32 pos) -> ui32 { + return (val & (1 << pos)) ? 1 : 0; + }; + + for (ui32 i = 0; i < (1 << 5); ++i) { + auto script = CacheFetchingScripts[GetBit(i, 0)][GetBit(i, 1)][GetBit(i, 2)][GetBit(i, 3)][GetBit(i, 4)]; + if (script && *script) { + sb << (*script)->DebugString() << ";"; + } + } + return sb; +} + +} // namespace NKikimr::NOlap::NReader::NSimple diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/context.h b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/context.h new file mode 100644 index 000000000000..d1dd942a611c --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/context.h @@ -0,0 +1,38 @@ +#pragma once +#include "fetching.h" + +#include +#include +#include +#include +#include +#include + +namespace NKikimr::NOlap::NReader::NSimple { + +class IDataSource; +using TColumnsSet = NCommon::TColumnsSet; +using EStageFeaturesIndexes = NCommon::EStageFeaturesIndexes; +using TColumnsSetIds = NCommon::TColumnsSetIds; +using EMemType = NCommon::EMemType; +using TFetchingScript = NCommon::TFetchingScript; + +class TSpecialReadContext: public NCommon::TSpecialReadContext { +private: + using TBase = NCommon::TSpecialReadContext; + std::shared_ptr BuildColumnsFetchingPlan(const bool needSnapshots, const bool partialUsageByPredicateExt, + const bool useIndexes, const bool needFilterSharding, const bool needFilterDeletion) const; + TMutex Mutex; + std::array>, 2>, 2>, 2>, 2>, 2> + CacheFetchingScripts; + std::shared_ptr AskAccumulatorsScript; + + virtual std::shared_ptr DoGetColumnsFetchingPlan(const std::shared_ptr& source) override; + +public: + virtual TString ProfileDebugString() const override; + + TSpecialReadContext(const std::shared_ptr& commonContext); +}; + +} // namespace NKikimr::NOlap::NReader::NSimple diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/fetched_data.cpp b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/fetched_data.cpp new file mode 100644 index 000000000000..a8992503b43d --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/fetched_data.cpp @@ -0,0 +1,5 @@ +#include "fetched_data.h" + +namespace NKikimr::NOlap::NReader::NSimple { + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/fetched_data.h b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/fetched_data.h new file mode 100644 index 000000000000..ee72a7d4a0f1 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/fetched_data.h @@ -0,0 +1,22 @@ +#pragma once +#include + +namespace NKikimr::NOlap::NReader::NSimple { + +class TFetchedData: public NCommon::TFetchedData { +private: + using TBase = NCommon::TFetchedData; + +public: + using TBase::TBase; +}; + +class TFetchedResult: public NCommon::TFetchedResult { +private: + using TBase = NCommon::TFetchedResult; + +public: + using TBase::TBase; +}; + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/fetching.cpp b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/fetching.cpp new file mode 100644 index 000000000000..5f85aeed228e --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/fetching.cpp @@ -0,0 +1,209 @@ +#include "fetching.h" +#include "plain_read_data.h" +#include "source.h" + +#include +#include +#include + +#include + +#include + +namespace NKikimr::NOlap::NReader::NSimple { + +TConclusion TIndexBlobsFetchingStep::DoExecuteInplace( + const std::shared_ptr& source, const TFetchingScriptCursor& step) const { + return !source->StartFetchingIndexes(source, step, Indexes); +} + +TConclusion TFilterProgramStep::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { + AFL_VERIFY(source); + AFL_VERIFY(Step); + auto filter = Step->BuildFilter(source->GetStageData().GetTable()); + if (!filter.ok()) { + return TConclusionStatus::Fail(filter.status().message()); + } + source->MutableStageData().AddFilter(*filter); + return true; +} + +TConclusion TPredicateFilter::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { + auto filter = + source->GetContext()->GetReadMetadata()->GetPKRangesFilter().BuildFilter(source->GetStageData().GetTable()->BuildTableVerified()); + source->MutableStageData().AddFilter(filter); + return true; +} + +TConclusion TSnapshotFilter::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { + auto filter = MakeSnapshotFilter( + source->GetStageData().GetTable()->BuildTableVerified(), source->GetContext()->GetReadMetadata()->GetRequestSnapshot()); + if (filter.GetFilteredCount().value_or(source->GetRecordsCount()) != source->GetRecordsCount()) { + if (source->AddTxConflict()) { + return true; + } + } + source->MutableStageData().AddFilter(filter); + return true; +} + +TConclusion TDeletionFilter::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { + auto filterTable = source->GetStageData().GetTable()->BuildTableOptional(std::set({ TIndexInfo::SPEC_COL_DELETE_FLAG })); + if (!filterTable) { + return true; + } + AFL_VERIFY(filterTable->column(0)->type()->id() == arrow::boolean()->id()); + NArrow::TColumnFilter filter = NArrow::TColumnFilter::BuildAllowFilter(); + for (auto&& i : filterTable->column(0)->chunks()) { + auto filterFlags = static_pointer_cast(i); + for (ui32 i = 0; i < filterFlags->length(); ++i) { + filter.Add(!filterFlags->GetView(i)); + } + } + source->MutableStageData().AddFilter(filter); + return true; +} + +TConclusion TShardingFilter::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { + NYDBTest::TControllers::GetColumnShardController()->OnSelectShardingFilter(); + const auto& shardingInfo = source->GetContext()->GetReadMetadata()->GetRequestShardingInfo()->GetShardingInfo(); + auto filter = shardingInfo->GetFilter(source->GetStageData().GetTable()->BuildTableVerified()); + source->MutableStageData().AddFilter(filter); + return true; +} + +TConclusion TApplyIndexStep::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { + source->ApplyIndex(IndexChecker); + return true; +} + +NKikimr::TConclusion TFilterCutLimit::DoExecuteInplace( + const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { + source->MutableStageData().CutFilter(source->GetRecordsCount(), Limit, Reverse); + return true; +} + +TConclusion TPortionAccessorFetchingStep::DoExecuteInplace( + const std::shared_ptr& source, const TFetchingScriptCursor& step) const { + return !source->StartFetchingAccessor(source, step); +} + +TConclusion TDetectInMem::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { + if (Columns.GetColumnsCount()) { + source->SetSourceInMemory( + source->GetColumnRawBytes(Columns.GetColumnIds()) < NYDBTest::TControllers::GetColumnShardController()->GetMemoryLimitScanPortion()); + } else { + source->SetSourceInMemory(true); + } + AFL_VERIFY(source->GetStageData().HasPortionAccessor()); + auto plan = source->GetContext()->GetColumnsFetchingPlan(source); + source->InitFetchingPlan(plan); + TFetchingScriptCursor cursor(plan, 0); + auto task = std::make_shared(source, std::move(cursor), source->GetContext()->GetCommonContext()->GetScanActorId()); + NConveyor::TScanServiceOperator::SendTaskToExecute(task); + return false; +} + +namespace { +class TApplySourceResult: public IDataTasksProcessor::ITask { +private: + using TBase = IDataTasksProcessor::ITask; + YDB_READONLY_DEF(std::shared_ptr, Result); + YDB_READONLY_DEF(std::shared_ptr, Source); + YDB_READONLY(ui32, StartIndex, 0); + YDB_READONLY(ui32, OriginalRecordsCount, 0); + NColumnShard::TCounterGuard Guard; + TFetchingScriptCursor Step; + +public: + TString GetTaskClassIdentifier() const override { + return "TApplySourceResult"; + } + + TApplySourceResult(const std::shared_ptr& source, std::shared_ptr&& result, const ui32 startIndex, + const ui32 originalRecordsCount, const TFetchingScriptCursor& step) + : TBase(NActors::TActorId()) + , Result(result) + , Source(source) + , StartIndex(startIndex) + , OriginalRecordsCount(originalRecordsCount) + , Guard(source->GetContext()->GetCommonContext()->GetCounters().GetResultsForSourceGuard()) + , Step(step) { + } + + virtual TConclusionStatus DoExecuteImpl() override { + AFL_VERIFY(false)("event", "not applicable"); + return TConclusionStatus::Success(); + } + virtual bool DoApply(IDataReader& indexedDataRead) const override { + auto* plainReader = static_cast(&indexedDataRead); + auto resultCopy = Result; + Source->SetCursor(Step); + plainReader->MutableScanner().OnSourceReady(Source, std::move(resultCopy), StartIndex, OriginalRecordsCount, *plainReader); + return true; + } +}; + +} // namespace + +TConclusion TBuildResultStep::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const { + auto context = source->GetContext(); + NArrow::TGeneralContainer::TTableConstructionContext contextTableConstruct; + contextTableConstruct.SetColumnNames(context->GetProgramInputColumns()->GetColumnNamesVector()); + if (!source->IsSourceInMemory()) { + contextTableConstruct.SetStartIndex(StartIndex).SetRecordsCount(RecordsCount); + } else { + AFL_VERIFY(StartIndex == 0); + AFL_VERIFY(RecordsCount == source->GetRecordsCount())("records_count", RecordsCount)("source", source->GetRecordsCount()); + } + std::shared_ptr resultBatch; + if (!source->GetStageResult().IsEmpty()) { + resultBatch = source->GetStageResult().GetBatch()->BuildTableVerified(contextTableConstruct); + AFL_VERIFY((ui32)resultBatch->num_columns() == context->GetProgramInputColumns()->GetColumnNamesVector().size()); + if (auto filter = source->GetStageResult().GetNotAppliedFilter()) { + filter->Apply(resultBatch, NArrow::TColumnFilter::TApplyContext(StartIndex, RecordsCount).SetTrySlices(true)); + } + if (resultBatch && resultBatch->num_rows()) { + NArrow::TStatusValidator::Validate(context->GetReadMetadata()->GetProgram().ApplyProgram(resultBatch)); + } + } + NActors::TActivationContext::AsActorContext().Send(context->GetCommonContext()->GetScanActorId(), + new NColumnShard::TEvPrivate::TEvTaskProcessedResult( + std::make_shared(source, std::move(resultBatch), StartIndex, RecordsCount, step))); + return false; +} + +TConclusion TPrepareResultStep::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { + std::shared_ptr plan = std::make_shared(*source->GetContext()); + if (source->IsSourceInMemory()) { + AFL_VERIFY(source->GetStageResult().GetPagesToResultVerified().size() == 1); + } + for (auto&& i : source->GetStageResult().GetPagesToResultVerified()) { + if (source->GetIsStartedByCursor() && !source->GetContext()->GetCommonContext()->GetScanCursor()->CheckSourceIntervalUsage( + source->GetSourceId(), i.GetIndexStart(), i.GetRecordsCount())) { + continue; + } + plan->AddStep(i.GetIndexStart(), i.GetRecordsCount()); + } + AFL_VERIFY(!plan->IsFinished(0)); + source->InitFetchingPlan(plan); + + TFetchingScriptCursor cursor(plan, 0); + auto task = std::make_shared(source, std::move(cursor), source->GetContext()->GetCommonContext()->GetScanActorId()); + NConveyor::TScanServiceOperator::SendTaskToExecute(task); + return false; +} + +TConclusion TBuildFakeSpec::DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& /*step*/) const { + std::vector> columns; + for (auto&& f : IIndexInfo::ArrowSchemaSnapshot()->fields()) { + columns.emplace_back(NArrow::TThreadSimpleArraysCache::GetConst(f->type(), NArrow::DefaultScalar(f->type()), source->GetRecordsCount())); + } + source->MutableStageData().AddBatch(std::make_shared( + arrow::RecordBatch::Make(TIndexInfo::ArrowSchemaSnapshot(), source->GetRecordsCount(), columns))); + source->SetUsedRawBytes(0); + source->Finalize({}); + return true; +} + +} // namespace NKikimr::NOlap::NReader::NSimple diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/fetching.h b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/fetching.h new file mode 100644 index 000000000000..995597a168d6 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/fetching.h @@ -0,0 +1,282 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace NKikimr::NOlap::NReader::NSimple { + +class IDataSource; +using TColumnsSet = NCommon::TColumnsSet; +using TIndexesSet = NCommon::TIndexesSet; +using EStageFeaturesIndexes = NCommon::EStageFeaturesIndexes; +using TColumnsSetIds = NCommon::TColumnsSetIds; +using EMemType = NCommon::EMemType; +using TFetchingScriptCursor = NCommon::TFetchingScriptCursor; +using TStepAction = NCommon::TStepAction; + +class TSpecialReadContext; + +class IFetchingStep: public NCommon::IFetchingStep { +private: + using TBase = NCommon::IFetchingStep; + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const = 0; + + virtual ui64 GetProcessingDataSize(const std::shared_ptr& /*source*/) const { + return 0; + } + + virtual TConclusion DoExecuteInplace( + const std::shared_ptr& sourceExt, const TFetchingScriptCursor& step) const override final { + const auto source = std::static_pointer_cast(sourceExt); + return DoExecuteInplace(source, step); + } + + virtual ui64 GetProcessingDataSize(const std::shared_ptr& source) const override final { + return GetProcessingDataSize(std::static_pointer_cast(source)); + } + +public: + using TBase::TBase; + +}; + +class IDataSource; + +class TBuildFakeSpec: public IFetchingStep { +private: + using TBase = IFetchingStep; + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + +public: + TBuildFakeSpec() + : TBase("FAKE_SPEC") { + } +}; + +class TApplyIndexStep: public IFetchingStep { +private: + using TBase = IFetchingStep; + const NIndexes::TIndexCheckerContainer IndexChecker; + +protected: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + +public: + TApplyIndexStep(const NIndexes::TIndexCheckerContainer& indexChecker) + : TBase("APPLY_INDEX") + , IndexChecker(indexChecker) { + } +}; + +class TDetectInMemStep: public IFetchingStep { +private: + using TBase = IFetchingStep; + const TColumnsSetIds Columns; + +protected: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + virtual TString DoDebugString() const override { + return TStringBuilder() << "columns=" << Columns.DebugString() << ";"; + } + +public: + virtual ui64 GetProcessingDataSize(const std::shared_ptr& source) const override; + TDetectInMemStep(const TColumnsSetIds& columns) + : TBase("FETCHING_COLUMNS") + , Columns(columns) { + AFL_VERIFY(Columns.GetColumnsCount()); + } +}; + +class TPrepareResultStep: public IFetchingStep { +private: + using TBase = IFetchingStep; + +protected: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + virtual TString DoDebugString() const override { + return TStringBuilder(); + } + +public: + virtual ui64 GetProcessingDataSize(const std::shared_ptr& /*source*/) const override { + return 0; + } + TPrepareResultStep() + : TBase("PREPARE_RESULT") { + } +}; + +class TBuildResultStep: public IFetchingStep { +private: + using TBase = IFetchingStep; + const ui32 StartIndex; + const ui32 RecordsCount; + +protected: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + virtual TString DoDebugString() const override { + return TStringBuilder(); + } + +public: + virtual ui64 GetProcessingDataSize(const std::shared_ptr& /*source*/) const override { + return 0; + } + TBuildResultStep(const ui32 startIndex, const ui32 recordsCount) + : TBase("BUILD_RESULT") + , StartIndex(startIndex) + , RecordsCount(recordsCount) { + } +}; + +class TColumnBlobsFetchingStep: public IFetchingStep { +private: + using TBase = IFetchingStep; + TColumnsSetIds Columns; + +protected: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + virtual TString DoDebugString() const override { + return TStringBuilder() << "columns=" << Columns.DebugString() << ";"; + } + +public: + virtual ui64 GetProcessingDataSize(const std::shared_ptr& source) const override; + TColumnBlobsFetchingStep(const TColumnsSetIds& columns) + : TBase("FETCHING_COLUMNS") + , Columns(columns) { + AFL_VERIFY(Columns.GetColumnsCount()); + } +}; + +class TPortionAccessorFetchingStep: public IFetchingStep { +private: + using TBase = IFetchingStep; + +protected: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + virtual TString DoDebugString() const override { + return TStringBuilder(); + } + +public: + TPortionAccessorFetchingStep() + : TBase("FETCHING_ACCESSOR") { + } +}; + +class TIndexBlobsFetchingStep: public IFetchingStep { +private: + using TBase = IFetchingStep; + std::shared_ptr Indexes; + +protected: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + virtual TString DoDebugString() const override { + return TStringBuilder() << "indexes=" << Indexes->DebugString() << ";"; + } + +public: + TIndexBlobsFetchingStep(const std::shared_ptr& indexes) + : TBase("FETCHING_INDEXES") + , Indexes(indexes) { + AFL_VERIFY(Indexes); + AFL_VERIFY(Indexes->GetIndexesCount()); + } +}; + +class TFilterProgramStep: public IFetchingStep { +private: + using TBase = IFetchingStep; + std::shared_ptr Step; + +public: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + TFilterProgramStep(const std::shared_ptr& step) + : TBase("PROGRAM") + , Step(step) { + } +}; + +class TFilterCutLimit: public IFetchingStep { +private: + using TBase = IFetchingStep; + const ui32 Limit; + const bool Reverse; + +public: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + TFilterCutLimit(const ui32 limit, const bool reverse) + : TBase("LIMIT") + , Limit(limit) + , Reverse(reverse) { + AFL_VERIFY(Limit); + } +}; + +class TPredicateFilter: public IFetchingStep { +private: + using TBase = IFetchingStep; + +public: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + TPredicateFilter() + : TBase("PREDICATE") { + } +}; + +class TSnapshotFilter: public IFetchingStep { +private: + using TBase = IFetchingStep; + +public: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + TSnapshotFilter() + : TBase("SNAPSHOT") { + } +}; + +class TDetectInMem: public IFetchingStep { +private: + using TBase = IFetchingStep; + TColumnsSetIds Columns; + +public: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + TDetectInMem(const TColumnsSetIds& columns) + : TBase("DETECT_IN_MEM") + , Columns(columns) { + } +}; + +class TDeletionFilter: public IFetchingStep { +private: + using TBase = IFetchingStep; + +public: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + TDeletionFilter() + : TBase("DELETION") { + } +}; + +class TShardingFilter: public IFetchingStep { +private: + using TBase = IFetchingStep; + +public: + virtual TConclusion DoExecuteInplace(const std::shared_ptr& source, const TFetchingScriptCursor& step) const override; + TShardingFilter() + : TBase("SHARDING") { + } +}; + +} // namespace NKikimr::NOlap::NReader::NSimple diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/iterator.cpp b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/iterator.cpp new file mode 100644 index 000000000000..b7f5fc907d7d --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/iterator.cpp @@ -0,0 +1,17 @@ +#include "iterator.h" + +#include + +namespace NKikimr::NOlap::NReader::NSimple { + +void TColumnShardScanIterator::FillReadyResults() { + auto ready = IndexedData->ExtractReadyResults(MaxRowsInBatch); + const i64 limitLeft = Context->GetReadMetadata()->GetLimitRobust(); + for (size_t i = 0; i < ready.size(); ++i) { + auto& batch = ReadyResults.emplace_back(std::move(ready[i])); + AFL_VERIFY(batch->GetResultBatch().num_rows() <= limitLeft); + ItemsRead += batch->GetResultBatch().num_rows(); + } +} + +} // namespace NKikimr::NOlap::NReader::NSimple diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/iterator.h b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/iterator.h new file mode 100644 index 000000000000..5e92b150c4dc --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/iterator.h @@ -0,0 +1,15 @@ +#pragma once +#include + +namespace NKikimr::NOlap::NReader::NSimple { + +class TColumnShardScanIterator: public NCommon::TColumnShardScanIterator { +private: + using TBase = NCommon::TColumnShardScanIterator; + virtual void FillReadyResults() override; + +public: + using TBase::TBase; +}; + +} // namespace NKikimr::NOlap::NReader::NSimple diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/plain_read_data.cpp b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/plain_read_data.cpp new file mode 100644 index 000000000000..eb8c21e291b2 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/plain_read_data.cpp @@ -0,0 +1,62 @@ +#include "plain_read_data.h" + +#include + +namespace NKikimr::NOlap::NReader::NSimple { + +TPlainReadData::TPlainReadData(const std::shared_ptr& context) + : TBase(context) + , SpecialReadContext(std::make_shared(context)) { + ui32 sourceIdx = 0; + std::deque> sources; + const auto& portions = GetReadMetadata()->SelectInfo->Portions; + ui64 compactedPortionsBytes = 0; + ui64 insertedPortionsBytes = 0; + for (auto&& i : portions) { + if (i->GetMeta().GetProduced() == NPortion::EProduced::COMPACTED || i->GetMeta().GetProduced() == NPortion::EProduced::SPLIT_COMPACTED) { + compactedPortionsBytes += i->GetTotalBlobBytes(); + } else { + insertedPortionsBytes += i->GetTotalBlobBytes(); + } + + std::make_shared(sourceIdx++, i, SpecialReadContext); + sources.emplace_back(std::make_shared(sourceIdx++, i, SpecialReadContext)); + } + std::sort(sources.begin(), sources.end(), IDataSource::TCompareStartForScanSequence()); + Scanner = std::make_shared(std::move(sources), SpecialReadContext); + + auto& stats = GetReadMetadata()->ReadStats; + stats->IndexPortions = GetReadMetadata()->SelectInfo->Portions.size(); + stats->IndexBatches = GetReadMetadata()->NumIndexedBlobs(); + stats->SchemaColumns = (*SpecialReadContext->GetProgramInputColumns() - *SpecialReadContext->GetSpecColumns()).GetColumnsCount(); + stats->InsertedPortionsBytes = insertedPortionsBytes; + stats->CompactedPortionsBytes = compactedPortionsBytes; +} + +std::vector> TPlainReadData::DoExtractReadyResults(const int64_t /*maxRowsInBatch*/) { + auto result = std::move(PartialResults); + PartialResults.clear(); + // auto result = TPartialReadResult::SplitResults(std::move(PartialResults), maxRowsInBatch); + ui32 count = 0; + for (auto&& r : result) { + count += r->GetRecordsCount(); + } + AFL_VERIFY(count == ReadyResultsCount); + ReadyResultsCount = 0; + + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "DoExtractReadyResults")("result", result.size())("count", count)( + "finished", Scanner->IsFinished()); + return result; +} + +TConclusion TPlainReadData::DoReadNextInterval() { + return Scanner->BuildNextInterval(); +} + +void TPlainReadData::OnIntervalResult(const std::shared_ptr& result) { + // result->GetResourcesGuardOnly()->Update(result->GetMemorySize()); + ReadyResultsCount += result->GetRecordsCount(); + PartialResults.emplace_back(result); +} + +} // namespace NKikimr::NOlap::NReader::NSimple diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/plain_read_data.h b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/plain_read_data.h new file mode 100644 index 000000000000..adfe861d6319 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/plain_read_data.h @@ -0,0 +1,80 @@ +#pragma once +#include "scanner.h" +#include "source.h" + +#include +#include +#include +#include + +namespace NKikimr::NOlap::NReader::NSimple { + +class TPlainReadData: public IDataReader, TNonCopyable, NColumnShard::TMonitoringObjectsCounter { +private: + using TBase = IDataReader; + std::shared_ptr Scanner; + std::shared_ptr SpecialReadContext; + std::vector> PartialResults; + ui32 ReadyResultsCount = 0; + +protected: + virtual TConclusionStatus DoStart() override { + return Scanner->Start(); + } + + virtual TString DoDebugString(const bool verbose) const override { + TStringBuilder sb; + sb << SpecialReadContext->DebugString() << ";"; + if (verbose) { + sb << "intervals_schema=" << Scanner->DebugString(); + } + return sb; + } + + virtual std::vector> DoExtractReadyResults(const int64_t maxRowsInBatch) override; + virtual TConclusion DoReadNextInterval() override; + + virtual void DoAbort() override { + SpecialReadContext->Abort(); + Scanner->Abort(); + PartialResults.clear(); + Y_ABORT_UNLESS(IsFinished()); + } + virtual bool DoIsFinished() const override { + return (Scanner->IsFinished() && PartialResults.empty()); + } + +public: + const NCommon::TReadMetadata::TConstPtr& GetReadMetadata() const { + return SpecialReadContext->GetReadMetadata(); + } + + const std::shared_ptr& GetSpecialReadContext() const { + return SpecialReadContext; + } + + const TScanHead& GetScanner() const { + return *Scanner; + } + + TScanHead& MutableScanner() { + return *Scanner; + } + virtual void OnSentDataFromInterval(const ui32 sourceIdx) const override { + if (!SpecialReadContext->IsActive()) { + return; + } + Scanner->ContinueSource(sourceIdx); + } + + void OnIntervalResult(const std::shared_ptr& result); + + TPlainReadData(const std::shared_ptr& context); + ~TPlainReadData() { + if (SpecialReadContext->IsActive()) { + Abort("unexpected on destructor"); + } + } +}; + +} // namespace NKikimr::NOlap::NReader::NSimple diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/scanner.cpp b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/scanner.cpp new file mode 100644 index 000000000000..bc4e34df7f17 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/scanner.cpp @@ -0,0 +1,154 @@ +#include "plain_read_data.h" +#include "scanner.h" + +#include +#include + +#include + +namespace NKikimr::NOlap::NReader::NSimple { + +void TScanHead::OnSourceReady(const std::shared_ptr& source, std::shared_ptr&& tableExt, const ui32 startIndex, + const ui32 recordsCount, TPlainReadData& reader) { + + source->MutableResultRecordsCount() += tableExt ? tableExt->num_rows() : 0; + if (!tableExt || !tableExt->num_rows()) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("empty_source", source->DebugJson().GetStringRobust()); + } + Context->GetCommonContext()->GetCounters().OnSourceFinished( + source->GetRecordsCount(), source->GetUsedRawBytes(), tableExt ? tableExt->num_rows() : 0); + + if ((!tableExt || !tableExt->num_rows()) && Context->GetCommonContext()->GetReadMetadata()->HasLimit() && InFlightLimit < MaxInFlight) { + InFlightLimit = 2 * InFlightLimit; + } + source->MutableStageResult().SetResultChunk(std::move(tableExt), startIndex, recordsCount); + while (FetchingSources.size()) { + auto frontSource = FetchingSources.front(); + if (!frontSource->HasStageResult()) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "skip_no_result")("source_id", frontSource->GetSourceId())( + "source_idx", frontSource->GetSourceIdx()); + break; + } + if (!frontSource->GetStageResult().HasResultChunk()) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "skip_no_result_chunk")("source_id", frontSource->GetSourceId())( + "source_idx", frontSource->GetSourceIdx()); + break; + } + auto table = frontSource->MutableStageResult().ExtractResultChunk(); + const bool isFinished = frontSource->GetStageResult().IsFinished(); + std::optional sourceIdxToContinue; + if (!isFinished) { + sourceIdxToContinue = frontSource->GetSourceIdx(); + } + if (table && table->num_rows()) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "has_result")("source_id", frontSource->GetSourceId())( + "source_idx", frontSource->GetSourceIdx())("table", table->num_rows()); + auto cursor = + std::make_shared(frontSource->GetStartPKRecordBatch(), frontSource->GetSourceId(), startIndex + recordsCount); + reader.OnIntervalResult(std::make_shared(frontSource->GetResourceGuards(), frontSource->GetGroupGuard(), table, + cursor, Context->GetCommonContext(), sourceIdxToContinue)); + } else if (sourceIdxToContinue) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "continue_source")("source_id", frontSource->GetSourceId())( + "source_idx", frontSource->GetSourceIdx()); + ContinueSource(*sourceIdxToContinue); + break; + } + if (!isFinished) { + break; + } + AFL_VERIFY(FetchingSourcesByIdx.erase(frontSource->GetSourceIdx())); + FetchingSources.pop_front(); + frontSource->ClearResult(); + if (Context->GetCommonContext()->GetReadMetadata()->HasLimit() && FetchingSources.size() && frontSource->GetResultRecordsCount()) { + FinishedSources.emplace(frontSource); + while (FinishedSources.size() && (*FinishedSources.begin())->GetFinish() < FetchingSources.front()->GetStart()) { + auto fetchingSource = FetchingSources.front(); + auto finishedSource = *FinishedSources.begin(); + FetchedCount += finishedSource->GetResultRecordsCount(); + FinishedSources.erase(FinishedSources.begin()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "source_finished")("source_id", finishedSource->GetSourceId())( + "source_idx", finishedSource->GetSourceIdx())("limit", Context->GetCommonContext()->GetReadMetadata()->GetLimitRobust())( + "fetched", finishedSource->GetResultRecordsCount()); + if (FetchedCount > (ui64)Context->GetCommonContext()->GetReadMetadata()->GetLimitRobust()) { + AFL_NOTICE(NKikimrServices::TX_COLUMNSHARD)("event", "limit_exhausted")( + "limit", Context->GetCommonContext()->GetReadMetadata()->GetLimitRobust())("fetched", FetchedCount); + SortedSources.clear(); + } + } + } + } +} + +TConclusionStatus TScanHead::Start() { + for (auto&& i : SortedSources) { + i->InitFetchingPlan(Context->GetColumnsFetchingPlan(i)); + } + return TConclusionStatus::Success(); +} + +TScanHead::TScanHead(std::deque>&& sources, const std::shared_ptr& context) + : Context(context) { + if (HasAppData()) { + if (AppDataVerified().ColumnShardConfig.HasMaxInFlightIntervalsOnRequest()) { + MaxInFlight = AppDataVerified().ColumnShardConfig.GetMaxInFlightIntervalsOnRequest(); + } + } + if (Context->GetReadMetadata()->HasLimit()) { + InFlightLimit = 1; + } else { + InFlightLimit = MaxInFlight; + } + bool started = !context->GetCommonContext()->GetScanCursor()->IsInitialized(); + for (auto&& i : sources) { + if (!started) { + bool usage = false; + if (!context->GetCommonContext()->GetScanCursor()->CheckEntityIsBorder(i, usage)) { + continue; + } + started = true; + if (!usage) { + continue; + } + i->SetIsStartedByCursor(); + } + SortedSources.emplace_back(i); + } +} + +TConclusion TScanHead::BuildNextInterval() { + if (!Context->IsActive()) { + return false; + } + bool changed = false; + while (SortedSources.size() && FetchingSources.size() < InFlightLimit) { + SortedSources.front()->StartProcessing(SortedSources.front()); + FetchingSources.emplace_back(SortedSources.front()); + FetchingSourcesByIdx.emplace(SortedSources.front()->GetSourceIdx(), SortedSources.front()); + SortedSources.pop_front(); + changed = true; + } + return changed; +} + +const TReadContext& TScanHead::GetContext() const { + return *Context->GetCommonContext(); +} + +bool TScanHead::IsReverse() const { + return GetContext().GetReadMetadata()->IsDescSorted(); +} + +void TScanHead::Abort() { + AFL_VERIFY(!Context->IsActive()); + for (auto&& i : FetchingSources) { + i->Abort(); + } + for (auto&& i : SortedSources) { + i->Abort(); + } + FetchingSources.clear(); + SortedSources.clear(); + Y_ABORT_UNLESS(IsFinished()); +} + +} // namespace NKikimr::NOlap::NReader::NSimple diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/scanner.h b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/scanner.h new file mode 100644 index 000000000000..c60e2d436ee0 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/scanner.h @@ -0,0 +1,76 @@ +#pragma once +#include "source.h" +#include +#include +#include +#include + +namespace NKikimr::NOlap::NReader::NSimple { + +class TPlainReadData; + +class TDataSourceEndpoint { +private: + YDB_READONLY_DEF(std::vector>, StartSources); + YDB_READONLY_DEF(std::vector>, FinishSources); +public: + void AddStart(std::shared_ptr source) { + StartSources.emplace_back(source); + } + void AddFinish(std::shared_ptr source) { + FinishSources.emplace_back(source); + } +}; + +class TScanHead { +private: + std::shared_ptr Context; + THashMap> FetchingSourcesByIdx; + std::deque> SortedSources; + std::deque> FetchingSources; + std::set, IDataSource::TCompareFinishForScanSequence> FinishedSources; + ui64 FetchedCount = 0; + ui64 InFlightLimit = 1; + ui64 MaxInFlight = 256; +public: + + void ContinueSource(const ui32 sourceIdx) const { + auto it = FetchingSourcesByIdx.find(sourceIdx); + AFL_VERIFY(it != FetchingSourcesByIdx.end())("source_idx", sourceIdx)("count", FetchingSourcesByIdx.size()); + it->second->ContinueCursor(it->second); + } + + bool IsReverse() const; + void Abort(); + + bool IsFinished() const { + return FetchingSources.empty() && SortedSources.empty(); + } + + const TReadContext& GetContext() const; + + TString DebugString() const { + TStringBuilder sb; + sb << "S:"; + for (auto&& i : SortedSources) { + sb << i->GetSourceId() << ";"; + } + sb << "F:"; + for (auto&& i : FetchingSources) { + sb << i->GetSourceId() << ";"; + } + return sb; + } + + void OnSourceReady(const std::shared_ptr& source, std::shared_ptr&& table, const ui32 startIndex, + const ui32 recordsCount, TPlainReadData& reader); + + TConclusionStatus Start(); + + TScanHead(std::deque>&& sources, const std::shared_ptr& context); + + [[nodiscard]] TConclusion BuildNextInterval(); + +}; + +} diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/source.cpp b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/source.cpp new file mode 100644 index 000000000000..ea769af9b4ab --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/source.cpp @@ -0,0 +1,282 @@ +#include "fetched_data.h" +#include "plain_read_data.h" +#include "source.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace NKikimr::NOlap::NReader::NSimple { + +void IDataSource::InitFetchingPlan(const std::shared_ptr& fetching) { + AFL_VERIFY(fetching); + // AFL_VERIFY(!FetchingPlan); + FetchingPlan = fetching; +} + +void IDataSource::StartProcessing(const std::shared_ptr& sourcePtr) { + AFL_VERIFY(!ProcessingStarted); + AFL_VERIFY(FetchingPlan); + AFL_VERIFY(!GetContext()->IsAborted()); + ProcessingStarted = true; + SourceGroupGuard = NGroupedMemoryManager::TScanMemoryLimiterOperator::BuildGroupGuard( + GetContext()->GetProcessMemoryControlId(), GetContext()->GetCommonContext()->GetScanId()); + SetMemoryGroupId(SourceGroupGuard->GetGroupId()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("InitFetchingPlan", FetchingPlan->DebugString())("source_idx", GetSourceIdx()); + // NActors::TLogContextGuard logGuard(NActors::TLogContextBuilder::Build()("source", SourceIdx)("method", "InitFetchingPlan")); + TFetchingScriptCursor cursor(FetchingPlan, 0); + auto task = std::make_shared(sourcePtr, std::move(cursor), GetContext()->GetCommonContext()->GetScanActorId()); + NConveyor::TScanServiceOperator::SendTaskToExecute(task); +} + +void IDataSource::ContinueCursor(const std::shared_ptr& sourcePtr) { + AFL_VERIFY(!!ScriptCursor); + if (ScriptCursor->Next()) { + auto task = std::make_shared(sourcePtr, std::move(*ScriptCursor), GetContext()->GetCommonContext()->GetScanActorId()); + NConveyor::TScanServiceOperator::SendTaskToExecute(task); + ScriptCursor.reset(); + } +} + +void IDataSource::DoOnSourceFetchingFinishedSafe(IDataReader& owner, const std::shared_ptr& sourcePtr) { + auto* plainReader = static_cast(&owner); + plainReader->MutableScanner().OnSourceReady(std::static_pointer_cast(sourcePtr), nullptr, 0, GetRecordsCount(), *plainReader); +} + +void IDataSource::DoOnEmptyStageData(const std::shared_ptr& /*sourcePtr*/) { + ResourceGuards.clear(); + Finalize({}); +} + +void IDataSource::DoBuildStageResult(const std::shared_ptr& /*sourcePtr*/) { + Finalize(NYDBTest::TControllers::GetColumnShardController()->GetMemoryLimitScanPortion()); +} + +void IDataSource::Finalize(const std::optional memoryLimit) { + TMemoryProfileGuard mpg("SCAN_PROFILE::STAGE_RESULT", IS_DEBUG_LOG_ENABLED(NKikimrServices::TX_COLUMNSHARD_SCAN_MEMORY)); + if (memoryLimit) { + const auto accessor = StageData->GetPortionAccessor(); + StageResult = std::make_unique(std::move(StageData)); + StageResult->SetPages(accessor.BuildReadPages(*memoryLimit, GetContext()->GetProgramInputColumns()->GetColumnIds())); + } else { + StageResult = std::make_unique(std::move(StageData)); + StageResult->SetPages({ TPortionDataAccessor::TReadPage(0, GetRecordsCount(), 0) }); + } + StageData.reset(); +} + +void TPortionDataSource::NeedFetchColumns(const std::set& columnIds, TBlobsAction& blobsAction, + THashMap& defaultBlocks, const std::shared_ptr& filter) { + const NArrow::TColumnFilter& cFilter = filter ? *filter : NArrow::TColumnFilter::BuildAllowFilter(); + ui32 fetchedChunks = 0; + ui32 nullChunks = 0; + for (auto&& i : columnIds) { + auto columnChunks = GetStageData().GetPortionAccessor().GetColumnChunksPointers(i); + if (columnChunks.empty()) { + continue; + } + auto itFilter = cFilter.GetIterator(false, Portion->GetRecordsCount()); + bool itFinished = false; + for (auto&& c : columnChunks) { + AFL_VERIFY(!itFinished); + if (!itFilter.IsBatchForSkip(c->GetMeta().GetRecordsCount())) { + auto reading = blobsAction.GetReading(Portion->GetColumnStorageId(c->GetColumnId(), Schema->GetIndexInfo())); + reading->SetIsBackgroundProcess(false); + reading->AddRange(Portion->RestoreBlobRange(c->BlobRange)); + ++fetchedChunks; + } else { + defaultBlocks.emplace(c->GetAddress(), TPortionDataAccessor::TAssembleBlobInfo(c->GetMeta().GetRecordsCount(), + Schema->GetExternalDefaultValueVerified(c->GetColumnId()))); + ++nullChunks; + } + itFinished = !itFilter.Next(c->GetMeta().GetRecordsCount()); + } + AFL_VERIFY(itFinished)("filter", itFilter.DebugString())("count", Portion->GetRecordsCount()); + } + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "chunks_stats")("fetch", fetchedChunks)("null", nullChunks)( + "reading_actions", blobsAction.GetStorageIds())("columns", columnIds.size()); +} + +bool TPortionDataSource::DoStartFetchingColumns( + const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const TColumnsSetIds& columns) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", step.GetName()); + AFL_VERIFY(columns.GetColumnsCount()); + AFL_VERIFY(!StageData->GetAppliedFilter() || !StageData->GetAppliedFilter()->IsTotalDenyFilter()); + auto& columnIds = columns.GetColumnIds(); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", step.GetName())("fetching_info", step.DebugString()); + + TBlobsAction action(GetContext()->GetCommonContext()->GetStoragesManager(), NBlobOperations::EConsumer::SCAN); + { + THashMap nullBlocks; + NeedFetchColumns(columnIds, action, nullBlocks, StageData->GetAppliedFilter()); + StageData->AddDefaults(std::move(nullBlocks)); + } + + auto readActions = action.GetReadingActions(); + if (!readActions.size()) { + return false; + } + + auto constructor = + std::make_shared(readActions, sourcePtr, step, GetContext(), "CS::READ::" + step.GetName(), ""); + NActors::TActivationContext::AsActorContext().Register(new NOlap::NBlobOperations::NRead::TActor(constructor)); + return true; +} + +bool TPortionDataSource::DoStartFetchingIndexes( + const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const std::shared_ptr& indexes) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", step.GetName()); + AFL_VERIFY(indexes->GetIndexesCount()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", step.GetName())("fetching_info", step.DebugString()); + + TBlobsAction action(GetContext()->GetCommonContext()->GetStoragesManager(), NBlobOperations::EConsumer::SCAN); + { + std::set indexIds; + for (auto&& i : GetStageData().GetPortionAccessor().GetIndexesVerified()) { + if (!indexes->GetIndexIdsSet().contains(i.GetIndexId())) { + continue; + } + indexIds.emplace(i.GetIndexId()); + if (auto bRange = i.GetBlobRangeOptional()) { + auto readAction = action.GetReading(Portion->GetIndexStorageId(i.GetIndexId(), Schema->GetIndexInfo())); + readAction->SetIsBackgroundProcess(false); + readAction->AddRange(Portion->RestoreBlobRange(*bRange)); + } + } + if (indexes->GetIndexIdsSet().size() != indexIds.size()) { + return false; + } + } + auto readingActions = action.GetReadingActions(); + if (!readingActions.size()) { + NYDBTest::TControllers::GetColumnShardController()->OnIndexSelectProcessed({}); + return false; + } + + auto constructor = + std::make_shared(readingActions, sourcePtr, step, GetContext(), "CS::READ::" + step.GetName(), ""); + NActors::TActivationContext::AsActorContext().Register(new NOlap::NBlobOperations::NRead::TActor(constructor)); + return true; +} + +void TPortionDataSource::DoAbort() { +} + +void TPortionDataSource::DoApplyIndex(const NIndexes::TIndexCheckerContainer& indexChecker) { + THashMap> indexBlobs; + std::set indexIds = indexChecker->GetIndexIds(); + // NActors::TLogContextGuard gLog = NActors::TLogContextBuilder::Build()("records_count", GetRecordsCount())("portion_id", Portion->GetAddress().DebugString()); + std::vector pages = GetStageData().GetPortionAccessor().BuildPages(); + NArrow::TColumnFilter constructor = NArrow::TColumnFilter::BuildAllowFilter(); + for (auto&& p : pages) { + for (auto&& i : p.GetIndexes()) { + if (!indexIds.contains(i->GetIndexId())) { + continue; + } + if (i->HasBlobData()) { + indexBlobs[i->GetIndexId()].emplace_back(i->GetBlobDataVerified()); + } else { + indexBlobs[i->GetIndexId()].emplace_back(StageData->ExtractBlob(i->GetAddress())); + } + } + for (auto&& i : indexIds) { + if (!indexBlobs.contains(i)) { + return; + } + } + if (indexChecker->Check(indexBlobs)) { + NYDBTest::TControllers::GetColumnShardController()->OnIndexSelectProcessed(true); + constructor.Add(true, p.GetRecordsCount()); + } else { + NYDBTest::TControllers::GetColumnShardController()->OnIndexSelectProcessed(false); + constructor.Add(false, p.GetRecordsCount()); + } + } + AFL_VERIFY(constructor.GetRecordsCountVerified() == Portion->GetRecordsCount()); + if (constructor.IsTotalDenyFilter()) { + StageData->AddFilter(NArrow::TColumnFilter::BuildDenyFilter()); + } else if (constructor.IsTotalAllowFilter()) { + return; + } else { + StageData->AddFilter(constructor); + } +} + +void TPortionDataSource::DoAssembleColumns(const std::shared_ptr& columns, const bool sequential) { + auto blobSchema = GetContext()->GetReadMetadata()->GetLoadSchemaVerified(*Portion); + + std::optional ss; + if (Portion->HasInsertWriteId()) { + if (Portion->HasCommitSnapshot()) { + ss = Portion->GetCommitSnapshotVerified(); + } else if (GetContext()->GetReadMetadata()->IsMyUncommitted(Portion->GetInsertWriteIdVerified())) { + ss = GetContext()->GetReadMetadata()->GetRequestSnapshot(); + } + } + + auto batch = GetStageData() + .GetPortionAccessor() + .PrepareForAssemble(*blobSchema, columns->GetFilteredSchemaVerified(), MutableStageData().MutableBlobs(), ss) + .AssembleToGeneralContainer(sequential ? columns->GetColumnIds() : std::set()) + .DetachResult(); + + MutableStageData().AddBatch(batch); +} + +namespace { +class TPortionAccessorFetchingSubscriber: public IDataAccessorRequestsSubscriber { +private: + TFetchingScriptCursor Step; + std::shared_ptr Source; + const NColumnShard::TCounterGuard Guard; + virtual const std::shared_ptr& DoGetAbortionFlag() const override { + return Source->GetContext()->GetCommonContext()->GetAbortionFlag(); + } + + virtual void DoOnRequestsFinished(TDataAccessorsResult&& result) override { + AFL_VERIFY(!result.HasErrors()); + AFL_VERIFY(result.GetPortions().size() == 1)("count", result.GetPortions().size()); + Source->MutableStageData().SetPortionAccessor(std::move(result.ExtractPortionsVector().front())); + Source->InitUsedRawBytes(); + AFL_VERIFY(Step.Next()); + auto task = std::make_shared(Source, std::move(Step), Source->GetContext()->GetCommonContext()->GetScanActorId()); + NConveyor::TScanServiceOperator::SendTaskToExecute(task); + } +public: + TPortionAccessorFetchingSubscriber(const TFetchingScriptCursor& step, const std::shared_ptr& source) + : Step(step) + , Source(source) + , Guard(Source->GetContext()->GetCommonContext()->GetCounters().GetFetcherAcessorsGuard()) { + } +}; + +} // namespace + +bool TPortionDataSource::DoStartFetchingAccessor(const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step) { + AFL_VERIFY(!StageData->HasPortionAccessor()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", step.GetName())("fetching_info", step.DebugString()); + + std::shared_ptr request = std::make_shared("SIMPLE::" + step.GetName()); + request->AddPortion(Portion); + request->SetColumnIds(GetContext()->GetAllUsageColumns()->GetColumnIds()); + request->RegisterSubscriber(std::make_shared(step, sourcePtr)); + GetContext()->GetCommonContext()->GetDataAccessorsManager()->AskData(request); + return true; +} + +TPortionDataSource::TPortionDataSource( + const ui32 sourceIdx, const std::shared_ptr& portion, const std::shared_ptr& context) + : TBase(portion->GetPortionId(), sourceIdx, context, portion->IndexKeyStart(), portion->IndexKeyEnd(), + portion->RecordSnapshotMin(TSnapshot::Zero()), portion->RecordSnapshotMax(TSnapshot::Zero()), portion->GetRecordsCount(), + portion->GetShardingVersionOptional(), portion->GetMeta().GetDeletionsCount()) + , Portion(portion) + , Schema(GetContext()->GetReadMetadata()->GetLoadSchemaVerified(*portion)) { +} + +} // namespace NKikimr::NOlap::NReader::NSimple diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/source.h b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/source.h new file mode 100644 index 000000000000..1227b7ddfd0f --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/source.h @@ -0,0 +1,387 @@ +#pragma once +#include "context.h" +#include "fetched_data.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace NKikimr::NOlap { +class IDataReader; +} + +namespace NKikimr::NOlap::NReader::NSimple { + +class TFetchingInterval; +class TPlainReadData; +class IFetchTaskConstructor; +class IFetchingStep; +class TBuildFakeSpec; + +class TPortionPage { +private: + YDB_READONLY(ui32, StartIndex, 0); + YDB_READONLY(ui32, RecordsCount, 0); + YDB_READONLY(ui64, MemoryBytes, 0); + YDB_ACCESSOR_DEF(std::shared_ptr, Result); + +public: + TPortionPage(const ui32 startIndex, const ui32 recordsCount, const ui64 memoryBytes) + : StartIndex(startIndex) + , RecordsCount(recordsCount) + , MemoryBytes(memoryBytes) { + } +}; + +class TReplaceKeyAdapter { +private: + const bool Reverse = false; + const NArrow::TReplaceKey Value; + +public: + TReplaceKeyAdapter(const NArrow::TReplaceKey& rk, const bool reverse) + : Reverse(reverse) + , Value(rk) { + } + + std::partial_ordering Compare(const TReplaceKeyAdapter& item) const { + AFL_VERIFY(Reverse == item.Reverse); + const std::partial_ordering result = Value.CompareNotNull(item.Value); + if (result == std::partial_ordering::equivalent) { + return std::partial_ordering::equivalent; + } else if (result == std::partial_ordering::less) { + return Reverse ? std::partial_ordering::greater : std::partial_ordering::less; + } else if (result == std::partial_ordering::greater) { + return Reverse ? std::partial_ordering::less : std::partial_ordering::greater; + } else { + AFL_VERIFY(false); + return std::partial_ordering::less; + } + } + + bool operator<(const TReplaceKeyAdapter& item) const { + AFL_VERIFY(Reverse == item.Reverse); + const std::partial_ordering result = Value.CompareNotNull(item.Value); + if (result == std::partial_ordering::equivalent) { + return false; + } else if (result == std::partial_ordering::less) { + return !Reverse; + } else if (result == std::partial_ordering::greater) { + return Reverse; + } else { + AFL_VERIFY(false); + return false; + } + } + + TString DebugString() const { + return TStringBuilder() << "point:{" << Value.DebugString() << "};reverse:" << Reverse << ";"; + } +}; + +class IDataSource: public NCommon::IDataSource { +private: + using TBase = NCommon::IDataSource; + const TReplaceKeyAdapter Start; + const TReplaceKeyAdapter Finish; + virtual NJson::TJsonValue DoDebugJson() const = 0; + std::shared_ptr FetchingPlan; + YDB_READONLY(TPKRangeFilter::EUsageClass, UsageClass, TPKRangeFilter::EUsageClass::PartialUsage); + YDB_ACCESSOR(ui32, ResultRecordsCount, 0); + bool ProcessingStarted = false; + bool IsStartedByCursor = false; + friend class TBuildFakeSpec; + + std::optional ScriptCursor; + std::shared_ptr SourceGroupGuard; + + virtual void DoOnSourceFetchingFinishedSafe(IDataReader& owner, const std::shared_ptr& sourcePtr) override; + virtual void DoBuildStageResult(const std::shared_ptr& /*sourcePtr*/) override; + virtual void DoOnEmptyStageData(const std::shared_ptr& /*sourcePtr*/) override; + + void Finalize(const std::optional memoryLimit); + +protected: + std::optional UsedRawBytes; + + virtual bool DoStartFetchingIndexes( + const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const std::shared_ptr& indexes) = 0; + virtual void DoAbort() = 0; + virtual void DoApplyIndex(const NIndexes::TIndexCheckerContainer& indexMeta) = 0; + virtual NJson::TJsonValue DoDebugJsonForMemory() const { + return NJson::JSON_MAP; + } + virtual bool DoStartFetchingAccessor(const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step) = 0; + +public: + virtual void InitUsedRawBytes() = 0; + + ui64 GetUsedRawBytes() const { + AFL_VERIFY(UsedRawBytes); + return *UsedRawBytes; + } + + void SetUsedRawBytes(const ui64 value) { + AFL_VERIFY(!UsedRawBytes); + UsedRawBytes = value; + } + + const TReplaceKeyAdapter& GetStart() const { + return Start; + } + const TReplaceKeyAdapter GetFinish() const { + return Finish; + } + + bool GetIsStartedByCursor() const { + return IsStartedByCursor; + } + + const std::shared_ptr& GetGroupGuard() const { + AFL_VERIFY(SourceGroupGuard); + return SourceGroupGuard; + } + + ui64 GetMemoryGroupId() const { + AFL_VERIFY(SourceGroupGuard); + return SourceGroupGuard->GetGroupId(); + } + + virtual void ClearResult() { + StageData.reset(); + StageResult.reset(); + ResourceGuards.clear(); + SourceGroupGuard = nullptr; + } + + void SetIsStartedByCursor() { + IsStartedByCursor = true; + } + + void SetCursor(const TFetchingScriptCursor& scriptCursor) { + AFL_VERIFY(!ScriptCursor); + ScriptCursor = scriptCursor; + } + + void ContinueCursor(const std::shared_ptr& sourcePtr); + + class TCompareStartForScanSequence { + public: + bool operator()(const std::shared_ptr& l, const std::shared_ptr& r) const { + const std::partial_ordering compareResult = l->GetStart().Compare(r->GetStart()); + if (compareResult == std::partial_ordering::equivalent) { + return l->GetSourceId() < r->GetSourceId(); + } else { + return compareResult == std::partial_ordering::less; + } + }; + }; + + class TCompareFinishForScanSequence { + public: + bool operator()(const std::shared_ptr& l, const std::shared_ptr& r) const { + const std::partial_ordering compareResult = l->GetFinish().Compare(r->GetFinish()); + if (compareResult == std::partial_ordering::equivalent) { + return l->GetSourceId() < r->GetSourceId(); + } else { + return compareResult == std::partial_ordering::less; + } + }; + }; + + virtual std::shared_ptr GetStartPKRecordBatch() const = 0; + + void StartProcessing(const std::shared_ptr& sourcePtr); + virtual ui64 PredictAccessorsSize(const std::set& entityIds) const = 0; + + bool StartFetchingAccessor(const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step) { + return DoStartFetchingAccessor(sourcePtr, step); + } + + virtual ui64 GetPathId() const = 0; + virtual bool HasIndexes(const std::set& indexIds) const = 0; + + void ApplyIndex(const NIndexes::TIndexCheckerContainer& indexMeta) { + return DoApplyIndex(indexMeta); + } + + bool StartFetchingIndexes( + const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const std::shared_ptr& indexes) { + AFL_VERIFY(indexes); + return DoStartFetchingIndexes(sourcePtr, step, indexes); + } + void InitFetchingPlan(const std::shared_ptr& fetching); + + virtual ui64 GetIndexRawBytes(const std::set& indexIds) const = 0; + + void Abort() { + DoAbort(); + } + + NJson::TJsonValue DebugJsonForMemory() const { + NJson::TJsonValue result = NJson::JSON_MAP; + result.InsertValue("details", DoDebugJsonForMemory()); + result.InsertValue("count", GetRecordsCount()); + return result; + } + + NJson::TJsonValue DebugJson() const { + NJson::TJsonValue result = NJson::JSON_MAP; + result.InsertValue("source_id", GetSourceId()); + result.InsertValue("source_idx", GetSourceIdx()); + result.InsertValue("start", Start.DebugString()); + result.InsertValue("finish", Finish.DebugString()); + result.InsertValue("specific", DoDebugJson()); + return result; + } + + bool OnIntervalFinished(const ui32 intervalIdx); + + IDataSource(const ui64 sourceId, const ui32 sourceIdx, const std::shared_ptr& context, const NArrow::TReplaceKey& start, + const NArrow::TReplaceKey& finish, const TSnapshot& recordSnapshotMin, const TSnapshot& recordSnapshotMax, const ui32 recordsCount, + const std::optional shardingVersion, const bool hasDeletions) + : TBase(sourceId, sourceIdx, context, recordSnapshotMin, recordSnapshotMax, recordsCount, shardingVersion, hasDeletions) + , Start(context->GetReadMetadata()->IsDescSorted() ? finish : start, context->GetReadMetadata()->IsDescSorted()) + , Finish(context->GetReadMetadata()->IsDescSorted() ? start : finish, context->GetReadMetadata()->IsDescSorted()) { + StageData = std::make_unique(true); + UsageClass = GetContext()->GetReadMetadata()->GetPKRangesFilter().IsPortionInPartialUsage(start, finish); + AFL_VERIFY(UsageClass != TPKRangeFilter::EUsageClass::DontUsage); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "portions_for_merge")("start", Start.DebugString())( + "finish", Finish.DebugString()); + Y_ABORT_UNLESS(Start.Compare(Finish) != std::partial_ordering::greater); + } + + virtual ~IDataSource() = default; +}; + +class TPortionDataSource: public IDataSource { +private: + using TBase = IDataSource; + const TPortionInfo::TConstPtr Portion; + std::shared_ptr Schema; + + void NeedFetchColumns(const std::set& columnIds, TBlobsAction& blobsAction, + THashMap& nullBlocks, const std::shared_ptr& filter); + + virtual void InitUsedRawBytes() override { + AFL_VERIFY(!UsedRawBytes); + UsedRawBytes = StageData->GetPortionAccessor().GetColumnRawBytes(GetContext()->GetAllUsageColumns()->GetColumnIds(), false); + } + + virtual void DoApplyIndex(const NIndexes::TIndexCheckerContainer& indexChecker) override; + virtual bool DoStartFetchingColumns( + const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const TColumnsSetIds& columns) override; + virtual bool DoStartFetchingIndexes( + const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step, const std::shared_ptr& indexes) override; + virtual void DoAssembleColumns(const std::shared_ptr& columns, const bool sequential) override; + virtual NJson::TJsonValue DoDebugJson() const override { + NJson::TJsonValue result = NJson::JSON_MAP; + result.InsertValue("type", "portion"); + result.InsertValue("info", Portion->DebugString()); + result.InsertValue("commit", Portion->GetCommitSnapshotOptional().value_or(TSnapshot::Zero()).DebugString()); + result.InsertValue("insert", (ui64)Portion->GetInsertWriteIdOptional().value_or(TInsertWriteId(0))); + return result; + } + + virtual NJson::TJsonValue DoDebugJsonForMemory() const override { + NJson::TJsonValue result = TBase::DoDebugJsonForMemory(); + if (GetStageData().HasPortionAccessor()) { + auto columns = GetStageData().GetPortionAccessor().GetColumnIds(); + // result.InsertValue("sequential_columns", JoinSeq(",", SequentialEntityIds)); + result.InsertValue("in_mem", GetStageData().GetPortionAccessor().GetColumnRawBytes(columns, false)); + result.InsertValue("columns_in_mem", JoinSeq(",", columns)); + } + result.InsertValue("portion_id", Portion->GetPortionId()); + result.InsertValue("raw", Portion->GetTotalRawBytes()); + result.InsertValue("blob", Portion->GetTotalBlobBytes()); + result.InsertValue("read_memory", GetColumnRawBytes(GetStageData().GetPortionAccessor().GetColumnIds())); + return result; + } + virtual void DoAbort() override; + virtual ui64 GetPathId() const override { + return Portion->GetPathId(); + } + + virtual bool DoStartFetchingAccessor(const std::shared_ptr& sourcePtr, const TFetchingScriptCursor& step) override; + +public: + virtual ui64 PredictAccessorsSize(const std::set& entityIds) const override { + return Portion->GetApproxChunksCount(entityIds.size()) * sizeof(TColumnRecord); + } + + virtual std::shared_ptr GetStartPKRecordBatch() const override { + if (GetContext()->GetReadMetadata()->IsDescSorted()) { + AFL_VERIFY(Portion->GetMeta().GetFirstLastPK().GetBatch()->num_rows()); + return Portion->GetMeta().GetFirstLastPK().GetBatch()->Slice(Portion->GetMeta().GetFirstLastPK().GetBatch()->num_rows() - 1, 1); + } else { + return Portion->GetMeta().GetFirstLastPK().GetBatch()->Slice(0, 1); + } + } + + virtual bool DoAddTxConflict() override { + if (Portion->HasCommitSnapshot() || !Portion->HasInsertWriteId()) { + GetContext()->GetReadMetadata()->SetBrokenWithCommitted(); + return true; + } else if (!GetContext()->GetReadMetadata()->IsMyUncommitted(Portion->GetInsertWriteIdVerified())) { + GetContext()->GetReadMetadata()->SetConflictedWriteId(Portion->GetInsertWriteIdVerified()); + return true; + } + return false; + } + + virtual bool HasIndexes(const std::set& indexIds) const override { + return Schema->GetIndexInfo().HasIndexes(indexIds); + } + + virtual THashMap DecodeBlobAddresses(NBlobOperations::NRead::TCompositeReadBlobs&& blobsOriginal) const override { + return GetStageData().GetPortionAccessor().DecodeBlobAddresses(std::move(blobsOriginal), Schema->GetIndexInfo()); + } + + virtual ui64 GetColumnsVolume(const std::set& columnIds, const EMemType type) const override { + AFL_VERIFY(columnIds.size()); + switch (type) { + case EMemType::Raw: + return GetStageData().GetPortionAccessor().GetColumnRawBytes(columnIds, false); + case EMemType::Blob: + return GetStageData().GetPortionAccessor().GetColumnBlobBytes(columnIds, false); + case EMemType::RawSequential: + return GetStageData().GetPortionAccessor().GetMinMemoryForReadColumns(columnIds); + } + } + + virtual ui64 GetColumnRawBytes(const std::set& columnsIds) const override { + AFL_VERIFY(columnsIds.size()); + return GetStageData().GetPortionAccessor().GetColumnRawBytes(columnsIds, false); + } + + virtual ui64 GetColumnBlobBytes(const std::set& columnsIds) const override { + return GetStageData().GetPortionAccessor().GetColumnBlobBytes(columnsIds, false); + } + + virtual ui64 GetIndexRawBytes(const std::set& indexIds) const override { + return GetStageData().GetPortionAccessor().GetIndexRawBytes(indexIds, false); + } + + const TPortionInfo& GetPortionInfo() const { + return *Portion; + } + + const TPortionInfo::TConstPtr& GetPortionInfoPtr() const { + return Portion; + } + + TPortionDataSource(const ui32 sourceIdx, const std::shared_ptr& portion, const std::shared_ptr& context); +}; + +} // namespace NKikimr::NOlap::NReader::NSimple diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/ya.make b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/ya.make new file mode 100644 index 000000000000..45fef368d323 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/iterator/ya.make @@ -0,0 +1,21 @@ +LIBRARY() + +SRCS( + scanner.cpp + source.cpp + fetched_data.cpp + plain_read_data.cpp + context.cpp + fetching.cpp + iterator.cpp +) + +PEERDIR( + ydb/core/formats/arrow + ydb/core/tx/columnshard/blobs_action + ydb/core/tx/columnshard/engines/reader/common_reader/iterator + ydb/core/tx/conveyor/usage + ydb/core/tx/limiter/grouped_memory/usage +) + +END() diff --git a/ydb/core/tx/columnshard/engines/reader/simple_reader/ya.make b/ydb/core/tx/columnshard/engines/reader/simple_reader/ya.make new file mode 100644 index 000000000000..6926bde3581e --- /dev/null +++ b/ydb/core/tx/columnshard/engines/reader/simple_reader/ya.make @@ -0,0 +1,11 @@ +LIBRARY() + +SRCS( +) + +PEERDIR( + ydb/core/tx/columnshard/engines/reader/simple_reader/constructor + ydb/core/tx/columnshard/engines/reader/simple_reader/iterator +) + +END() diff --git a/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/filler.cpp b/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/filler.cpp index 2a23b12c3fae..cd65ff991335 100644 --- a/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/filler.cpp +++ b/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/filler.cpp @@ -23,7 +23,7 @@ NKikimr::TConclusionStatus TMetadataFromStore::DoFillMetadata(const NColumnShard auto pathInfos = logsIndex->GetTables(fromPathId, toPathId); for (auto&& pathInfo : pathInfos) { if (pathIds.emplace(pathInfo->GetPathId()).second) { - metadata->IndexGranules.emplace_back(BuildGranuleView(*pathInfo, metadata->IsDescSorted())); + metadata->IndexGranules.emplace_back(BuildGranuleView(*pathInfo, metadata->IsDescSorted(), metadata->GetRequestSnapshot())); } } } @@ -52,7 +52,7 @@ NKikimr::TConclusionStatus TMetadataFromTable::DoFillMetadata(const NColumnShard if (!pathInfo) { continue; } - metadata->IndexGranules.emplace_back(BuildGranuleView(*pathInfo, metadata->IsDescSorted())); + metadata->IndexGranules.emplace_back(BuildGranuleView(*pathInfo, metadata->IsDescSorted(), metadata->GetRequestSnapshot())); break; } } diff --git a/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/filler.h b/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/filler.h index 5b1199496819..8caecc01f5b7 100644 --- a/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/filler.h +++ b/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/filler.h @@ -9,8 +9,8 @@ class IMetadataFiller { private: virtual TConclusionStatus DoFillMetadata(const NColumnShard::TColumnShard* shard, const std::shared_ptr& metadata, const TReadDescription& read) const = 0; - virtual NAbstract::TGranuleMetaView DoBuildGranuleView(const TGranuleMeta& granule, const bool reverse) const { - return NAbstract::TGranuleMetaView(granule, reverse); + virtual NAbstract::TGranuleMetaView DoBuildGranuleView(const TGranuleMeta& granule, const bool reverse, const TSnapshot& reqSnapshot) const { + return NAbstract::TGranuleMetaView(granule, reverse, reqSnapshot); } public: virtual ~IMetadataFiller() = default; @@ -19,8 +19,8 @@ class IMetadataFiller { return DoFillMetadata(shard, metadata, read); } - NAbstract::TGranuleMetaView BuildGranuleView(const TGranuleMeta& granule, const bool reverse) const { - return DoBuildGranuleView(granule, reverse); + NAbstract::TGranuleMetaView BuildGranuleView(const TGranuleMeta& granule, const bool reverse, const TSnapshot& reqSnapshot) const { + return DoBuildGranuleView(granule, reverse, reqSnapshot); } }; diff --git a/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/granule_view.h b/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/granule_view.h index 356dfc446ed3..97144d686c96 100644 --- a/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/granule_view.h +++ b/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/granule_view.h @@ -1,5 +1,5 @@ #pragma once -#include +#include #include namespace NKikimr::NOlap::NReader::NSysView::NAbstract { @@ -11,10 +11,13 @@ class TGranuleMetaView { YDB_READONLY_DEF(TPortions, Portions); YDB_READONLY_DEF(std::vector, OptimizerTasks); public: - TGranuleMetaView(const TGranuleMeta& granule, const bool reverse) + TGranuleMetaView(const TGranuleMeta& granule, const bool reverse, const TSnapshot& reqSnapshot) : PathId(granule.GetPathId()) { for (auto&& i : granule.GetPortions()) { + if (i.second->IsRemovedFor(reqSnapshot)) { + continue; + } Portions.emplace_back(i.second); } diff --git a/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/iterator.cpp b/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/iterator.cpp index c47dd37eacb6..85f12b65ba78 100644 --- a/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/iterator.cpp +++ b/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/iterator.cpp @@ -1,5 +1,24 @@ #include "iterator.h" +#include namespace NKikimr::NOlap::NReader::NSysView::NAbstract { +TStatsIteratorBase::TStatsIteratorBase(const std::shared_ptr& context, const NTable::TScheme::TTableSchema& statsSchema) + : StatsSchema(statsSchema) + , Context(context) + , ReadMetadata(context->GetReadMetadataPtrVerifiedAs()) + , KeySchema(MakeArrowSchema(StatsSchema.Columns, StatsSchema.KeyColumns)) + , ResultSchema(MakeArrowSchema(StatsSchema.Columns, ReadMetadata->ResultColumnIds)) + , IndexGranules(ReadMetadata->IndexGranules) { + if (ResultSchema->num_fields() == 0) { + ResultSchema = KeySchema; + } + std::vector allColumnIds; + for (const auto& c : StatsSchema.Columns) { + allColumnIds.push_back(c.second.Id); + } + std::sort(allColumnIds.begin(), allColumnIds.end()); + DataSchema = MakeArrowSchema(StatsSchema.Columns, allColumnIds); } + +} // namespace NKikimr::NOlap::NReader::NSysView::NAbstract diff --git a/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/iterator.h b/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/iterator.h index 33be2ac027b1..32a3c5679ce3 100644 --- a/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/iterator.h +++ b/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/iterator.h @@ -14,13 +14,20 @@ class TStatsIteratorBase: public TScanIteratorBase { protected: virtual bool AppendStats(const std::vector>& builders, TGranuleMetaView& granule) const = 0; virtual ui32 PredictRecordsCount(const TGranuleMetaView& granule) const = 0; + std::shared_ptr Context; TReadStatsMetadata::TConstPtr ReadMetadata; const bool Reverse = false; std::shared_ptr KeySchema; std::shared_ptr ResultSchema; std::deque IndexGranules; + mutable THashMap FetchedAccessors; + public: + virtual bool IsReadyForBatch() const { + return true; + } + virtual TConclusionStatus Start() override { return TConclusionStatus::Success(); } @@ -31,8 +38,13 @@ class TStatsIteratorBase: public TScanIteratorBase { virtual TConclusion> GetBatch() override { while (!Finished()) { + if (!IsReadyForBatch()) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "batch_not_ready"); + return std::shared_ptr(); + } auto batchOpt = ExtractStatsBatch(); if (!batchOpt) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "no_batch_on_finished"); AFL_VERIFY(Finished()); return std::shared_ptr(); } @@ -50,13 +62,17 @@ class TStatsIteratorBase: public TScanIteratorBase { // Leave only requested columns auto resultBatch = NArrow::TColumnOperator().Adapt(originalBatch, ResultSchema).DetachResult(); - NArrow::TStatusValidator::Validate(ReadMetadata->GetProgram().ApplyProgram(resultBatch)); + auto applyConclusion = ReadMetadata->GetProgram().ApplyProgram(resultBatch); + if (!applyConclusion.ok()) { + return TConclusionStatus::Fail(applyConclusion.ToString()); + } if (resultBatch->num_rows() == 0) { continue; } auto table = NArrow::TStatusValidator::GetValid(arrow::Table::FromRecordBatches({resultBatch})); - return std::make_shared(table, lastKey, std::nullopt); + return std::make_shared(table, std::make_shared(lastKey), Context, std::nullopt); } + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "finished_iterator"); return std::shared_ptr(); } @@ -76,32 +92,13 @@ class TStatsIteratorBase: public TScanIteratorBase { AFL_VERIFY(*count == i->length()); } } - auto result = arrow::RecordBatch::Make(DataSchema, columns.front()->length(), columns); - if (result->num_rows()) { - return result; - } + return arrow::RecordBatch::Make(DataSchema, columns.front()->length(), columns); } return std::nullopt; } - TStatsIteratorBase(const NAbstract::TReadStatsMetadata::TConstPtr& readMetadata, const NTable::TScheme::TTableSchema& statsSchema) - : StatsSchema(statsSchema) - , ReadMetadata(readMetadata) - , KeySchema(MakeArrowSchema(StatsSchema.Columns, StatsSchema.KeyColumns)) - , ResultSchema(MakeArrowSchema(StatsSchema.Columns, ReadMetadata->ResultColumnIds)) - , IndexGranules(ReadMetadata->IndexGranules) - { - if (ResultSchema->num_fields() == 0) { - ResultSchema = KeySchema; - } - std::vector allColumnIds; - for (const auto& c : StatsSchema.Columns) { - allColumnIds.push_back(c.second.Id); - } - std::sort(allColumnIds.begin(), allColumnIds.end()); - DataSchema = MakeArrowSchema(StatsSchema.Columns, allColumnIds); - } + TStatsIteratorBase(const std::shared_ptr& context, const NTable::TScheme::TTableSchema& statsSchema); }; template @@ -140,8 +137,8 @@ class TStatsIterator : public TStatsIteratorBase { } }; - TStatsIterator(const NAbstract::TReadStatsMetadata::TConstPtr& readMetadata) - : TBase(readMetadata, StatsSchema) + TStatsIterator(const std::shared_ptr& context) + : TBase(context, StatsSchema) { } diff --git a/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/metadata.h b/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/metadata.h index c5068be3c82f..7a9ee6bd36b5 100644 --- a/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/metadata.h +++ b/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/metadata.h @@ -18,7 +18,7 @@ struct TReadStatsMetadata: public TReadMetadataBase { explicit TReadStatsMetadata(const std::shared_ptr& info, ui64 tabletId, const ESorting sorting, const TProgramContainer& ssaProgram, const std::shared_ptr& schema, const TSnapshot& requestSnapshot) - : TBase(info, sorting, ssaProgram, schema, requestSnapshot) + : TBase(info, sorting, ssaProgram, schema, requestSnapshot, nullptr) , TabletId(tabletId) { } }; diff --git a/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/policy.h b/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/policy.h index 72c528145579..dc277d498140 100644 --- a/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/policy.h +++ b/ydb/core/tx/columnshard/engines/reader/sys_view/abstract/policy.h @@ -10,7 +10,7 @@ namespace NKikimr::NOlap::NReader::NSysView::NAbstract { class ISysViewPolicy { private: - virtual std::unique_ptr DoCreateConstructor(const TSnapshot& snapshot, const ui64 itemsLimit, const bool reverse) const = 0; + virtual std::unique_ptr DoCreateConstructor(const TScannerConstructorContext& request) const = 0; virtual std::shared_ptr DoCreateMetadataFiller() const = 0; public: virtual ~ISysViewPolicy() = default; @@ -24,8 +24,8 @@ class ISysViewPolicy { AFL_VERIFY(!!result); return result; } - std::unique_ptr CreateConstructor(const TSnapshot& snapshot, const ui64 itemsLimit, const bool reverse) const { - auto result = DoCreateConstructor(snapshot, itemsLimit, reverse); + std::unique_ptr CreateConstructor(const TScannerConstructorContext& request) const { + auto result = DoCreateConstructor(request); AFL_VERIFY(!!result); return result; } diff --git a/ydb/core/tx/columnshard/engines/reader/sys_view/chunks/chunks.cpp b/ydb/core/tx/columnshard/engines/reader/sys_view/chunks/chunks.cpp index da3cc74f8e92..92fdf0689850 100644 --- a/ydb/core/tx/columnshard/engines/reader/sys_view/chunks/chunks.cpp +++ b/ydb/core/tx/columnshard/engines/reader/sys_view/chunks/chunks.cpp @@ -1,17 +1,20 @@ #include "chunks.h" + #include #include namespace NKikimr::NOlap::NReader::NSysView::NChunks { -void TStatsIterator::AppendStats(const std::vector>& builders, const TPortionInfo& portion) const { +void TStatsIterator::AppendStats( + const std::vector>& builders, const TPortionDataAccessor& portionPtr) const { + const TPortionInfo& portion = portionPtr.GetPortionInfo(); auto portionSchema = ReadMetadata->GetLoadSchemaVerified(portion); auto it = PortionType.find(portion.GetMeta().Produced); if (it == PortionType.end()) { it = PortionType.emplace(portion.GetMeta().Produced, ::ToString(portion.GetMeta().Produced)).first; } const arrow::util::string_view prodView = it->second.GetView(); - const bool activity = !portion.IsRemovedFor(ReadMetadata->GetRequestSnapshot()); + const bool activity = !portion.HasRemoveSnapshot(); static const TString ConstantEntityIsColumn = "COL"; static const arrow::util::string_view ConstantEntityIsColumnView = arrow::util::string_view(ConstantEntityIsColumn.data(), ConstantEntityIsColumn.size()); @@ -21,7 +24,7 @@ void TStatsIterator::AppendStats(const std::vector records; - for (auto&& r : portion.Records) { + for (auto&& r : portionPtr.GetRecordsVerified()) { records.emplace_back(&r); } if (Reverse) { @@ -35,7 +38,7 @@ void TStatsIterator::AppendStats(const std::vector(*builders[0], portion.GetPathId()); NArrow::Append(*builders[1], prodView); NArrow::Append(*builders[2], ReadMetadata->TabletId); - NArrow::Append(*builders[3], r->GetMeta().GetNumRows()); + NArrow::Append(*builders[3], r->GetMeta().GetRecordsCount()); NArrow::Append(*builders[4], r->GetMeta().GetRawBytes()); NArrow::Append(*builders[5], portion.GetPortionId()); NArrow::Append(*builders[6], r->GetChunkIdx()); @@ -51,8 +54,9 @@ void TStatsIterator::AppendStats(const std::vectorGetColumnId()); if (it == entityStorages.end()) { - it = entityStorages.emplace(r->GetColumnId(), - portionSchema->GetIndexInfo().GetEntityStorageId(r->GetColumnId(), portion.GetMeta().GetTierName())).first; + it = + entityStorages.emplace(r->GetColumnId(), portion.GetEntityStorageId(r->GetColumnId(), portionSchema->GetIndexInfo())) + .first; } lastTierName = it->second.GetView(); } @@ -63,8 +67,10 @@ void TStatsIterator::AppendStats(const std::vectorGetBlobRange().GetBlobIdxVerified()); if (itBlobIdString == blobsIds.end()) { - itBlobIdString = blobsIds.emplace( - r->GetBlobRange().GetBlobIdxVerified(), portion.GetBlobId(r->GetBlobRange().GetBlobIdxVerified()).ToStringLegacy()).first; + itBlobIdString = blobsIds + .emplace(r->GetBlobRange().GetBlobIdxVerified(), + portion.GetBlobId(r->GetBlobRange().GetBlobIdxVerified()).ToStringLegacy()) + .first; } NArrow::Append( *builders[9], arrow::util::string_view(itBlobIdString->second.data(), itBlobIdString->second.size())); @@ -79,7 +85,7 @@ void TStatsIterator::AppendStats(const std::vector indexes; - for (auto&& r : portion.GetIndexes()) { + for (auto&& r : portionPtr.GetIndexesVerified()) { indexes.emplace_back(&r); } if (Reverse) { @@ -106,7 +112,7 @@ void TStatsIterator::AppendStats(const std::vector(*builders[11], bData->size()); } NArrow::Append(*builders[12], activity); - const auto tierName = portionSchema->GetIndexInfo().GetEntityStorageId(r->GetIndexId(), portion.GetMeta().GetTierName()); + const auto tierName = portion.GetEntityStorageId(r->GetIndexId(), portionSchema->GetIndexInfo()); std::string strTierName(tierName.data(), tierName.size()); NArrow::Append(*builders[13], strTierName); NArrow::Append(*builders[14], ConstantEntityIsIndexView); @@ -115,25 +121,32 @@ void TStatsIterator::AppendStats(const std::vector TReadStatsMetadata::StartScan(const std::shared_ptr& readContext) const { - return std::make_unique(readContext->GetReadMetadataPtrVerifiedAs()); + return std::make_unique(readContext); } std::vector> TReadStatsMetadata::GetKeyYqlSchema() const { return GetColumns(TStatsIterator::StatsSchema, TStatsIterator::StatsSchema.KeyColumns); } -std::shared_ptr TConstructor::BuildMetadata(const NColumnShard::TColumnShard* self, const TReadDescription& read) const { +std::shared_ptr TConstructor::BuildMetadata( + const NColumnShard::TColumnShard* self, const TReadDescription& read) const { auto* index = self->GetIndexOptional(); return std::make_shared(index ? index->CopyVersionedIndexPtr() : nullptr, self->TabletID(), - IsReverse ? TReadMetadataBase::ESorting::DESC : TReadMetadataBase::ESorting::ASC, - read.GetProgram(), index ? index->GetVersionedIndex().GetLastSchema() : nullptr, read.GetSnapshot()); + IsReverse ? TReadMetadataBase::ESorting::DESC : TReadMetadataBase::ESorting::ASC, read.GetProgram(), + index ? index->GetVersionedIndex().GetLastSchema() : nullptr, read.GetSnapshot()); } bool TStatsIterator::AppendStats(const std::vector>& builders, NAbstract::TGranuleMetaView& granule) const { ui64 recordsCount = 0; - while (auto portion = granule.PopFrontPortion()) { - recordsCount += portion->GetRecords().size() + portion->GetIndexes().size(); - AppendStats(builders, *portion); + while (granule.GetPortions().size()) { + auto it = FetchedAccessors.find(granule.GetPortions().front()->GetPortionId()); + if (it == FetchedAccessors.end()) { + break; + } + recordsCount += it->second.GetRecordsVerified().size() + it->second.GetIndexesVerified().size(); + AppendStats(builders, it->second); + granule.PopFrontPortion(); + FetchedAccessors.erase(it); if (recordsCount > 10000) { break; } @@ -144,12 +157,71 @@ bool TStatsIterator::AppendStats(const std::vectorGetRecords().size() + portion->GetIndexes().size(); + auto it = FetchedAccessors.find(portion->GetPortionId()); + if (it == FetchedAccessors.end()) { + break; + } + recordsCount += it->second.GetRecordsVerified().size() + it->second.GetIndexesVerified().size(); if (recordsCount > 10000) { break; } } + AFL_VERIFY(recordsCount || granule.GetPortions().empty()); return recordsCount; } +TConclusionStatus TStatsIterator::Start() { + ProcessGuard = NGroupedMemoryManager::TScanMemoryLimiterOperator::BuildProcessGuard(ReadMetadata->GetTxId(), {}); + ScopeGuard = NGroupedMemoryManager::TScanMemoryLimiterOperator::BuildScopeGuard(ReadMetadata->GetTxId(), 1); + const ui32 columnsCount = ReadMetadata->GetKeyYqlSchema().size(); + for (auto&& i : IndexGranules) { + GroupGuards.emplace_back(NGroupedMemoryManager::TScanMemoryLimiterOperator::BuildGroupGuard(ReadMetadata->GetTxId(), 1)); + for (auto&& p : i.GetPortions()) { + std::shared_ptr request = std::make_shared("SYS_VIEW::CHUNKS"); + request->AddPortion(p); + auto allocation = std::make_shared(request, p->PredictMetadataMemorySize(columnsCount), Context); + request->RegisterSubscriber(allocation); + + NGroupedMemoryManager::TScanMemoryLimiterOperator::SendToAllocation( + ProcessGuard->GetProcessId(), ScopeGuard->GetScopeId(), GroupGuards.back()->GetGroupId(), { allocation }, std::nullopt); + } + } + return TConclusionStatus::Success(); } + +bool TStatsIterator::IsReadyForBatch() const { + if (!IndexGranules.size()) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "batch_ready_check")("result", false)("reason", "no_granules"); + return false; + } + if (!IndexGranules.front().GetPortions().size()) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "batch_ready_check")("result", true)("reason", "no_granule_portions"); + return true; + } + if (FetchedAccessors.contains(IndexGranules.front().GetPortions().front()->GetPortionId())) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "batch_ready_check")("result", true)("reason", "portion_fetched"); + return true; + } + return false; +} + +TStatsIterator::TFetchingAccessorAllocation::TFetchingAccessorAllocation( + const std::shared_ptr& request, const ui64 mem, const std::shared_ptr& context) + : TBase(mem) + , AccessorsManager(context->GetDataAccessorsManager()) + , Request(request) + , WaitingCountersGuard(context->GetCounters().GetFetcherAcessorsGuard()) + , OwnerId(context->GetScanActorId()) + , Context(context) { +} + +void TStatsIterator::TFetchingAccessorAllocation::DoOnAllocationImpossible(const TString& errorMessage) { + Request = nullptr; + Context->AbortWithError("cannot allocate memory for take accessors info: " + errorMessage); +} + +const std::shared_ptr& TStatsIterator::TFetchingAccessorAllocation::DoGetAbortionFlag() const { + return Context->GetAbortionFlag(); +} + +} // namespace NKikimr::NOlap::NReader::NSysView::NChunks diff --git a/ydb/core/tx/columnshard/engines/reader/sys_view/chunks/chunks.h b/ydb/core/tx/columnshard/engines/reader/sys_view/chunks/chunks.h index 6fb758f46911..c09a4f6d448b 100644 --- a/ydb/core/tx/columnshard/engines/reader/sys_view/chunks/chunks.h +++ b/ydb/core/tx/columnshard/engines/reader/sys_view/chunks/chunks.h @@ -1,28 +1,33 @@ #pragma once +#include #include #include #include -#include +#include namespace NKikimr::NOlap::NReader::NSysView::NChunks { class TConstructor: public TStatScannerConstructor { private: using TBase = TStatScannerConstructor; + protected: - virtual std::shared_ptr BuildMetadata(const NColumnShard::TColumnShard* self, const TReadDescription& read) const override; + virtual std::shared_ptr BuildMetadata( + const NColumnShard::TColumnShard* self, const TReadDescription& read) const override; + public: using TBase::TBase; }; -class TReadStatsMetadata: public NAbstract::TReadStatsMetadata, std::enable_shared_from_this { +class TReadStatsMetadata: public NAbstract::TReadStatsMetadata { private: using TBase = NAbstract::TReadStatsMetadata; using TSysViewSchema = NKikimr::NSysView::Schema::PrimaryIndexStats; + public: using TBase::TBase; - virtual std::unique_ptr StartScan(const std::shared_ptr& /*readContext*/) const override; + virtual std::unique_ptr StartScan(const std::shared_ptr& readContext) const override; virtual std::vector> GetKeyYqlSchema() const override; }; @@ -53,39 +58,122 @@ class TStatsIterator: public NAbstract::TStatsIterator ColumnNamesById; mutable THashMap PortionType; mutable THashMap> EntityStorageNames; + std::shared_ptr ProcessGuard; + std::shared_ptr ScopeGuard; + std::vector> GroupGuards; using TBase = NAbstract::TStatsIterator; - virtual bool AppendStats(const std::vector>& builders, NAbstract::TGranuleMetaView& granule) const override; + + virtual bool IsReadyForBatch() const override; + virtual bool AppendStats( + const std::vector>& builders, NAbstract::TGranuleMetaView& granule) const override; virtual ui32 PredictRecordsCount(const NAbstract::TGranuleMetaView& granule) const override; - void AppendStats(const std::vector>& builders, const TPortionInfo& portion) const; + void AppendStats(const std::vector>& builders, const TPortionDataAccessor& portion) const; + + class TApplyResult: public IDataTasksProcessor::ITask { + private: + using TBase = IDataTasksProcessor::ITask; + YDB_READONLY_DEF(std::vector, Accessors); + NColumnShard::TCounterGuard WaitingCountersGuard; + public: + TString GetTaskClassIdentifier() const override { + return "TApplyResult"; + } + + TApplyResult(const std::vector& accessors, NColumnShard::TCounterGuard&& waitingCountersGuard) + : TBase(NActors::TActorId()) + , Accessors(accessors) + , WaitingCountersGuard(std::move(waitingCountersGuard)) + { + } + + virtual TConclusionStatus DoExecuteImpl() override { + AFL_VERIFY(false)("event", "not applicable"); + return TConclusionStatus::Success(); + } + virtual bool DoApply(IDataReader& /*indexedDataRead*/) const override { + AFL_VERIFY(false); + return false; + } + }; + + class TFetchingAccessorAllocation: public NGroupedMemoryManager::IAllocation, public IDataAccessorRequestsSubscriber { + private: + using TBase = NGroupedMemoryManager::IAllocation; + std::shared_ptr Guard; + std::shared_ptr AccessorsManager; + std::shared_ptr Request; + NColumnShard::TCounterGuard WaitingCountersGuard; + const NActors::TActorId OwnerId; + const std::shared_ptr Context; + + virtual const std::shared_ptr& DoGetAbortionFlag() const override; + virtual bool DoOnAllocated(std::shared_ptr&& guard, + const std::shared_ptr& /*selfPtr*/) override { + Guard = std::move(guard); + AccessorsManager->AskData(std::move(Request)); + return true; + } + virtual void DoOnAllocationImpossible(const TString& errorMessage) override; + + virtual void DoOnRequestsFinished(TDataAccessorsResult&& result) override { + if (result.HasErrors()) { + NActors::TActivationContext::AsActorContext().Send( + OwnerId, new NColumnShard::TEvPrivate::TEvTaskProcessedResult(TConclusionStatus::Fail("cannot fetch accessors"))); + } else { + AFL_VERIFY(result.GetPortions().size() == 1)("count", result.GetPortions().size()); + NActors::TActivationContext::AsActorContext().Send( + OwnerId, new NColumnShard::TEvPrivate::TEvTaskProcessedResult( + std::make_shared(result.ExtractPortionsVector(), std::move(WaitingCountersGuard)))); + } + } + + public: + TFetchingAccessorAllocation(const std::shared_ptr& request, const ui64 mem, const std::shared_ptr& context); + }; + + virtual void Apply(const std::shared_ptr& task) override { + if (IndexGranules.empty()) { + return; + } + auto result = std::dynamic_pointer_cast(task); + AFL_VERIFY(result); + AFL_VERIFY(result->GetAccessors().size() == 1); + FetchedAccessors.emplace(result->GetAccessors().front().GetPortionInfo().GetPortionId(), result->GetAccessors().front()); + } + + virtual TConclusionStatus Start() override; + public: using TBase::TBase; }; class TStoreSysViewPolicy: public NAbstract::ISysViewPolicy { protected: - virtual std::unique_ptr DoCreateConstructor(const TSnapshot& snapshot, const ui64 itemsLimit, const bool reverse) const override { - return std::make_unique(snapshot, itemsLimit, reverse); + virtual std::unique_ptr DoCreateConstructor(const TScannerConstructorContext& request) const override { + return std::make_unique(request); } virtual std::shared_ptr DoCreateMetadataFiller() const override { return std::make_shared(); } -public: - static const inline TFactory::TRegistrator Registrator = TFactory::TRegistrator(TString(::NKikimr::NSysView::StorePrimaryIndexStatsName)); +public: + static const inline TFactory::TRegistrator Registrator = + TFactory::TRegistrator(TString(::NKikimr::NSysView::StorePrimaryIndexStatsName)); }; class TTableSysViewPolicy: public NAbstract::ISysViewPolicy { protected: - virtual std::unique_ptr DoCreateConstructor(const TSnapshot& snapshot, const ui64 itemsLimit, const bool reverse) const override { - return std::make_unique(snapshot, itemsLimit, reverse); + virtual std::unique_ptr DoCreateConstructor(const TScannerConstructorContext& request) const override { + return std::make_unique(request); } virtual std::shared_ptr DoCreateMetadataFiller() const override { return std::make_shared(); } -public: - static const inline TFactory::TRegistrator Registrator = TFactory::TRegistrator(TString(::NKikimr::NSysView::TablePrimaryIndexStatsName)); +public: + static const inline TFactory::TRegistrator Registrator = + TFactory::TRegistrator(TString(::NKikimr::NSysView::TablePrimaryIndexStatsName)); }; -} +} // namespace NKikimr::NOlap::NReader::NSysView::NChunks diff --git a/ydb/core/tx/columnshard/engines/reader/sys_view/constructor/constructor.h b/ydb/core/tx/columnshard/engines/reader/sys_view/constructor/constructor.h index 02b3220a9565..64ef291fc81a 100644 --- a/ydb/core/tx/columnshard/engines/reader/sys_view/constructor/constructor.h +++ b/ydb/core/tx/columnshard/engines/reader/sys_view/constructor/constructor.h @@ -13,6 +13,10 @@ class TStatScannerConstructor: public IScannerConstructor { private: using TBase = IScannerConstructor; + virtual std::shared_ptr DoBuildCursor() const override { + return nullptr; + } + virtual std::shared_ptr BuildMetadata(const NColumnShard::TColumnShard* self, const TReadDescription& read) const = 0; virtual TConclusion> DoBuildReadMetadata(const NColumnShard::TColumnShard* self, const TReadDescription& read) const override { diff --git a/ydb/core/tx/columnshard/engines/reader/sys_view/granules/granules.cpp b/ydb/core/tx/columnshard/engines/reader/sys_view/granules/granules.cpp index 4ab8e7ad2bbc..f6b1424927b4 100644 --- a/ydb/core/tx/columnshard/engines/reader/sys_view/granules/granules.cpp +++ b/ydb/core/tx/columnshard/engines/reader/sys_view/granules/granules.cpp @@ -16,7 +16,7 @@ bool TStatsIterator::AppendStats(const std::vector TReadStatsMetadata::StartScan(const std::shared_ptr& readContext) const { - return std::make_unique(readContext->GetReadMetadataPtrVerifiedAs()); + return std::make_unique(readContext); } std::vector> TReadStatsMetadata::GetKeyYqlSchema() const { diff --git a/ydb/core/tx/columnshard/engines/reader/sys_view/granules/granules.h b/ydb/core/tx/columnshard/engines/reader/sys_view/granules/granules.h index 8effbf9b6618..ec0ed83714bd 100644 --- a/ydb/core/tx/columnshard/engines/reader/sys_view/granules/granules.h +++ b/ydb/core/tx/columnshard/engines/reader/sys_view/granules/granules.h @@ -41,8 +41,8 @@ class TStatsIterator : public NAbstract::TStatsIterator DoCreateConstructor(const TSnapshot& snapshot, const ui64 itemsLimit, const bool reverse) const override { - return std::make_unique(snapshot, itemsLimit, reverse); + virtual std::unique_ptr DoCreateConstructor(const TScannerConstructorContext& request) const override { + return std::make_unique(request); } virtual std::shared_ptr DoCreateMetadataFiller() const override { return std::make_shared(); @@ -54,8 +54,8 @@ class TStoreSysViewPolicy: public NAbstract::ISysViewPolicy { class TTableSysViewPolicy: public NAbstract::ISysViewPolicy { protected: - virtual std::unique_ptr DoCreateConstructor(const TSnapshot& snapshot, const ui64 itemsLimit, const bool reverse) const override { - return std::make_unique(snapshot, itemsLimit, reverse); + virtual std::unique_ptr DoCreateConstructor(const TScannerConstructorContext& request) const override { + return std::make_unique(request); } virtual std::shared_ptr DoCreateMetadataFiller() const override { return std::make_shared(); diff --git a/ydb/core/tx/columnshard/engines/reader/sys_view/optimizer/optimizer.cpp b/ydb/core/tx/columnshard/engines/reader/sys_view/optimizer/optimizer.cpp index dc88fd26abde..bbeb039f7910 100644 --- a/ydb/core/tx/columnshard/engines/reader/sys_view/optimizer/optimizer.cpp +++ b/ydb/core/tx/columnshard/engines/reader/sys_view/optimizer/optimizer.cpp @@ -23,7 +23,7 @@ bool TStatsIterator::AppendStats(const std::vector TReadStatsMetadata::StartScan(const std::shared_ptr& readContext) const { - return std::make_unique(readContext->GetReadMetadataPtrVerifiedAs()); + return std::make_unique(readContext); } std::vector> TReadStatsMetadata::GetKeyYqlSchema() const { diff --git a/ydb/core/tx/columnshard/engines/reader/sys_view/optimizer/optimizer.h b/ydb/core/tx/columnshard/engines/reader/sys_view/optimizer/optimizer.h index c442c46242cb..d31545fe619c 100644 --- a/ydb/core/tx/columnshard/engines/reader/sys_view/optimizer/optimizer.h +++ b/ydb/core/tx/columnshard/engines/reader/sys_view/optimizer/optimizer.h @@ -41,8 +41,8 @@ class TStatsIterator : public NAbstract::TStatsIterator DoCreateConstructor(const TSnapshot& snapshot, const ui64 itemsLimit, const bool reverse) const override { - return std::make_unique(snapshot, itemsLimit, reverse); + virtual std::unique_ptr DoCreateConstructor(const TScannerConstructorContext& request) const override { + return std::make_unique(request); } virtual std::shared_ptr DoCreateMetadataFiller() const override { return std::make_shared(); @@ -76,8 +77,8 @@ class TStoreSysViewPolicy: public NAbstract::ISysViewPolicy { class TTableSysViewPolicy: public NAbstract::ISysViewPolicy { protected: - virtual std::unique_ptr DoCreateConstructor(const TSnapshot& snapshot, const ui64 itemsLimit, const bool reverse) const override { - return std::make_unique(snapshot, itemsLimit, reverse); + virtual std::unique_ptr DoCreateConstructor(const TScannerConstructorContext& request) const override { + return std::make_unique(request); } virtual std::shared_ptr DoCreateMetadataFiller() const override { return std::make_shared(); diff --git a/ydb/core/tx/columnshard/engines/reader/sys_view/portions/portions.cpp b/ydb/core/tx/columnshard/engines/reader/sys_view/portions/portions.cpp index 83b2306a1bff..a1c4f5a50376 100644 --- a/ydb/core/tx/columnshard/engines/reader/sys_view/portions/portions.cpp +++ b/ydb/core/tx/columnshard/engines/reader/sys_view/portions/portions.cpp @@ -10,29 +10,30 @@ void TStatsIterator::AppendStats(const std::vector(*builders[1], prod); NArrow::Append(*builders[2], ReadMetadata->TabletId); - NArrow::Append(*builders[3], portion.NumRows()); + NArrow::Append(*builders[3], portion.GetRecordsCount()); NArrow::Append(*builders[4], portion.GetColumnRawBytes()); NArrow::Append(*builders[5], portion.GetIndexRawBytes()); NArrow::Append(*builders[6], portion.GetColumnBlobBytes()); NArrow::Append(*builders[7], portion.GetIndexBlobBytes()); NArrow::Append(*builders[8], portion.GetPortionId()); - NArrow::Append(*builders[9], !portion.IsRemovedFor(ReadMetadata->GetRequestSnapshot())); + NArrow::Append(*builders[9], !portion.HasRemoveSnapshot()); auto tierName = portion.GetTierNameDef(NBlobOperations::TGlobal::DefaultStorageId); NArrow::Append(*builders[10], arrow::util::string_view(tierName.data(), tierName.size())); - NJson::TJsonValue statReport = NJson::JSON_ARRAY; - for (auto&& i : portion.GetIndexes()) { - if (!i.HasBlobData()) { - continue; - } - auto schema = portion.GetSchema(ReadMetadata->GetIndexVersions()); - auto indexMeta = schema->GetIndexInfo().GetIndexVerified(i.GetEntityId()); - statReport.AppendValue(indexMeta->SerializeDataToJson(i, schema->GetIndexInfo())); - } - auto statInfo = statReport.GetStringRobust(); + const TString statInfo = Default(); NArrow::Append(*builders[11], arrow::util::string_view(statInfo.data(), statInfo.size())); NArrow::Append(*builders[12], portion.HasRuntimeFeature(TPortionInfo::ERuntimeFeature::Optimized)); + NArrow::Append(*builders[13], portion.GetMeta().GetCompactionLevel()); + { + NJson::TJsonValue details = NJson::JSON_MAP; + details.InsertValue("snapshot_min", portion.RecordSnapshotMin().SerializeToJson()); + details.InsertValue("snapshot_max", portion.RecordSnapshotMax().SerializeToJson()); + details.InsertValue("primary_key_min", portion.IndexKeyStart().DebugString()); + details.InsertValue("primary_key_max", portion.IndexKeyEnd().DebugString()); + const auto detailsInfo = details.GetStringRobust(); + NArrow::Append(*builders[14], arrow::util::string_view(detailsInfo.data(), detailsInfo.size())); + } } ui32 TStatsIterator::PredictRecordsCount(const NAbstract::TGranuleMetaView& granule) const { @@ -52,7 +53,7 @@ bool TStatsIterator::AppendStats(const std::vector TReadStatsMetadata::StartScan(const std::shared_ptr& readContext) const { - return std::make_unique(readContext->GetReadMetadataPtrVerifiedAs()); + return std::make_unique(readContext); } std::vector> TReadStatsMetadata::GetKeyYqlSchema() const { diff --git a/ydb/core/tx/columnshard/engines/reader/sys_view/portions/portions.h b/ydb/core/tx/columnshard/engines/reader/sys_view/portions/portions.h index 82cf42beff06..9f5fd67fb8c9 100644 --- a/ydb/core/tx/columnshard/engines/reader/sys_view/portions/portions.h +++ b/ydb/core/tx/columnshard/engines/reader/sys_view/portions/portions.h @@ -38,8 +38,8 @@ class TStatsIterator : public NAbstract::TStatsIterator DoCreateConstructor(const TSnapshot& snapshot, const ui64 itemsLimit, const bool reverse) const override { - return std::make_unique(snapshot, itemsLimit, reverse); + virtual std::unique_ptr DoCreateConstructor(const TScannerConstructorContext& request) const override { + return std::make_unique(request); } virtual std::shared_ptr DoCreateMetadataFiller() const override { return std::make_shared(); @@ -51,8 +51,8 @@ class TStoreSysViewPolicy: public NAbstract::ISysViewPolicy { class TTableSysViewPolicy: public NAbstract::ISysViewPolicy { protected: - virtual std::unique_ptr DoCreateConstructor(const TSnapshot& snapshot, const ui64 itemsLimit, const bool reverse) const override { - return std::make_unique(snapshot, itemsLimit, reverse); + virtual std::unique_ptr DoCreateConstructor(const TScannerConstructorContext& request) const override { + return std::make_unique(request); } virtual std::shared_ptr DoCreateMetadataFiller() const override { return std::make_shared(); diff --git a/ydb/core/tx/columnshard/engines/reader/transaction/tx_internal_scan.cpp b/ydb/core/tx/columnshard/engines/reader/transaction/tx_internal_scan.cpp index 55d28a5a61f4..0cbb573a4057 100644 --- a/ydb/core/tx/columnshard/engines/reader/transaction/tx_internal_scan.cpp +++ b/ydb/core/tx/columnshard/engines/reader/transaction/tx_internal_scan.cpp @@ -36,15 +36,16 @@ void TTxInternalScan::Complete(const TActorContext& ctx) { auto scanComputeActor = InternalScanEvent->Sender; const TSnapshot snapshot = request.ReadToSnapshot.value_or(NOlap::TSnapshot(Self->LastPlannedStep, Self->LastPlannedTxId)); const NActors::TLogContextGuard gLogging = - NActors::TLogContextBuilder::Build()("tablet", Self->TabletID())("snapshot", snapshot.DebugString()); + NActors::TLogContextBuilder::Build()("tablet", Self->TabletID())("snapshot", snapshot.DebugString())("task_id", request.TaskIdentifier); TReadMetadataPtr readMetadataRange; + TScannerConstructorContext context(snapshot, 0, request.GetReverse()); { TReadDescription read(snapshot, request.GetReverse()); + read.SetScanIdentifier(request.TaskIdentifier); read.PathId = request.GetPathId(); read.LockId = LockId; read.ReadNothing = !Self->TablesManager.HasTable(read.PathId); - std::unique_ptr scannerConstructor( - new NPlain::TIndexScannerConstructor(snapshot, request.GetItemsLimit(), request.GetReverse())); + std::unique_ptr scannerConstructor(new NPlain::TIndexScannerConstructor(context)); read.ColumnIds = request.GetColumnIds(); read.ColumnNames = request.GetColumnNames(); if (request.RangesFilter) { @@ -67,10 +68,9 @@ void TTxInternalScan::Complete(const TActorContext& ctx) { readMetadataRange = TValidator::CheckNotNull(newRange.DetachResult()); } } - TStringBuilder detailedInfo; - if (IS_LOG_PRIORITY_ENABLED(NActors::NLog::PRI_TRACE, NKikimrServices::TX_COLUMNSHARD)) { - detailedInfo << " read metadata: (" << *readMetadataRange << ")"; + if (IS_LOG_PRIORITY_ENABLED(NActors::NLog::PRI_TRACE, NKikimrServices::TX_COLUMNSHARD_SCAN)) { + detailedInfo << " read metadata: (" << readMetadataRange->DebugString() << ")"; } const TVersionedIndex* index = nullptr; @@ -80,12 +80,13 @@ void TTxInternalScan::Complete(const TActorContext& ctx) { readMetadataRange->OnBeforeStartReading(*Self); const ui64 requestCookie = Self->InFlightReadsTracker.AddInFlightRequest(readMetadataRange, index); - auto scanActor = ctx.Register(new TColumnShardScan(Self->SelfId(), scanComputeActor, Self->GetStoragesManager(), TComputeShardingPolicy(), - ScanId, LockId.value_or(0), ScanGen, requestCookie, Self->TabletID(), TDuration::Max(), readMetadataRange, - NKikimrDataEvents::FORMAT_ARROW, - Self->Counters.GetScanCounters())); + auto scanActorId = ctx.Register(new TColumnShardScan(Self->SelfId(), scanComputeActor, Self->GetStoragesManager(), + Self->DataAccessorsManager.GetObjectPtrVerified(), + TComputeShardingPolicy(), ScanId, LockId.value_or(0), ScanGen, requestCookie, Self->TabletID(), TDuration::Max(), readMetadataRange, + NKikimrDataEvents::FORMAT_ARROW, Self->Counters.GetScanCounters())); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "TTxInternalScan started")("actor_id", scanActor)("trace_detailed", detailedInfo); + Self->InFlightReadsTracker.AddScanActorId(requestCookie, scanActorId); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "TTxInternalScan started")("actor_id", scanActorId)("trace_detailed", detailedInfo); } } // namespace NKikimr::NOlap::NReader diff --git a/ydb/core/tx/columnshard/engines/reader/transaction/tx_scan.cpp b/ydb/core/tx/columnshard/engines/reader/transaction/tx_scan.cpp index 74f09deb0197..503b3e00222e 100644 --- a/ydb/core/tx/columnshard/engines/reader/transaction/tx_scan.cpp +++ b/ydb/core/tx/columnshard/engines/reader/transaction/tx_scan.cpp @@ -38,6 +38,7 @@ void TTxScan::Complete(const TActorContext& ctx) { if (snapshot.IsZero()) { snapshot = Self->GetLastTxSnapshot(); } + TScannerConstructorContext context(snapshot, request.HasItemsLimit() ? request.GetItemsLimit() : 0, request.GetReverse()); const auto scanId = request.GetScanId(); const ui64 txId = request.GetTxId(); const ui32 scanGen = request.GetGeneration(); @@ -62,17 +63,45 @@ void TTxScan::Complete(const TActorContext& ctx) { read.PathId = request.GetLocalPathId(); read.ReadNothing = !Self->TablesManager.HasTable(read.PathId); read.TableName = table; - bool isIndex = false; + + const TString defaultReader = + [&]() { + const TString defGlobal = + AppDataVerified().ColumnShardConfig.GetReaderClassName() ? AppDataVerified().ColumnShardConfig.GetReaderClassName() : "PLAIN"; + if (Self->HasIndex()) { + return Self->GetIndexAs() + .GetVersionedIndex() + .GetLastSchema() + ->GetIndexInfo() + .GetScanReaderPolicyName() + .value_or(defGlobal); + } else { + return defGlobal; + } + }(); std::unique_ptr scannerConstructor = [&]() { - const ui64 itemsLimit = request.HasItemsLimit() ? request.GetItemsLimit() : 0; auto sysViewPolicy = NSysView::NAbstract::ISysViewPolicy::BuildByPath(read.TableName); - isIndex = !sysViewPolicy; if (!sysViewPolicy) { - return std::unique_ptr(new NPlain::TIndexScannerConstructor(snapshot, itemsLimit, request.GetReverse())); + auto constructor = NReader::IScannerConstructor::TFactory::MakeHolder( + request.GetCSScanPolicy() ? request.GetCSScanPolicy() : defaultReader, context); + if (!constructor) { + return std::unique_ptr(); + } + return std::unique_ptr(constructor.Release()); } else { - return sysViewPolicy->CreateConstructor(snapshot, itemsLimit, request.GetReverse()); + return sysViewPolicy->CreateConstructor(context); } }(); + if (!scannerConstructor) { + return SendError("cannot build scanner", AppDataVerified().ColumnShardConfig.GetReaderClassName(), ctx); + } + { + auto cursorConclusion = scannerConstructor->BuildCursorFromProto(request.GetScanCursor()); + if (cursorConclusion.IsFail()) { + return SendError("cannot build scanner cursor", cursorConclusion.GetErrorMessage(), ctx); + } + read.SetScanCursor(cursorConclusion.DetachResult()); + } read.ColumnIds.assign(request.GetColumnTags().begin(), request.GetColumnTags().end()); read.StatsMode = request.GetStatsMode(); @@ -110,7 +139,7 @@ void TTxScan::Complete(const TActorContext& ctx) { TStringBuilder detailedInfo; if (IS_LOG_PRIORITY_ENABLED(NActors::NLog::PRI_TRACE, NKikimrServices::TX_COLUMNSHARD)) { - detailedInfo << " read metadata: (" << *readMetadataRange << ")" + detailedInfo << " read metadata: (" << readMetadataRange->DebugString() << ")" << " req: " << request; } @@ -125,10 +154,12 @@ void TTxScan::Complete(const TActorContext& ctx) { TComputeShardingPolicy shardingPolicy; AFL_VERIFY(shardingPolicy.DeserializeFromProto(request.GetComputeShardingPolicy())); - auto scanActor = ctx.Register(new TColumnShardScan(Self->SelfId(), scanComputeActor, Self->GetStoragesManager(), shardingPolicy, scanId, + auto scanActorId = ctx.Register(new TColumnShardScan(Self->SelfId(), scanComputeActor, Self->GetStoragesManager(), + Self->DataAccessorsManager.GetObjectPtrVerified(), shardingPolicy, scanId, txId, scanGen, requestCookie, Self->TabletID(), timeout, readMetadataRange, dataFormat, Self->Counters.GetScanCounters())); + Self->InFlightReadsTracker.AddScanActorId(requestCookie, scanActorId); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "TTxScan started")("actor_id", scanActor)("trace_detailed", detailedInfo); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "TTxScan started")("actor_id", scanActorId)("trace_detailed", detailedInfo); } } // namespace NKikimr::NOlap::NReader diff --git a/ydb/core/tx/columnshard/engines/reader/ya.make b/ydb/core/tx/columnshard/engines/reader/ya.make index c1a5dbd87327..c74241203529 100644 --- a/ydb/core/tx/columnshard/engines/reader/ya.make +++ b/ydb/core/tx/columnshard/engines/reader/ya.make @@ -12,6 +12,7 @@ PEERDIR( ydb/core/tx/columnshard/resources ydb/core/tx/program ydb/core/tx/columnshard/engines/reader/plain_reader + ydb/core/tx/columnshard/engines/reader/simple_reader ydb/core/tx/columnshard/engines/reader/sys_view ydb/core/tx/columnshard/engines/reader/abstract ydb/core/tx/columnshard/engines/reader/common diff --git a/ydb/core/tx/columnshard/engines/scheme/abstract/column_ids.cpp b/ydb/core/tx/columnshard/engines/scheme/abstract/column_ids.cpp new file mode 100644 index 000000000000..053f717c0f96 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/scheme/abstract/column_ids.cpp @@ -0,0 +1,3 @@ +#include "column_ids.h" + +namespace NKikimr::NOlap {} diff --git a/ydb/core/tx/columnshard/engines/scheme/abstract/column_ids.h b/ydb/core/tx/columnshard/engines/scheme/abstract/column_ids.h new file mode 100644 index 000000000000..0e38152dbe38 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/scheme/abstract/column_ids.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +#include +#include + +#include + +namespace NKikimr::NOlap { + +class TColumnIdsView: private TNonCopyable { +private: + std::span ColumnIds; + + class TIterator: public NArrow::NUtil::TRandomAccessIteratorClone::iterator, TIterator> { + using TBase = NArrow::NUtil::TRandomAccessIteratorClone::iterator, TIterator>; + + public: + using TBase::TRandomAccessIteratorClone; + }; + +public: + template + TColumnIdsView(const It begin, const It end) + : ColumnIds(begin, end) { + } + + TIterator begin() const { + return ColumnIds.begin(); + } + + TIterator end() const { + return ColumnIds.end(); + } + + ui32 operator[](size_t idx) const { + AFL_VERIFY(idx < ColumnIds.size())("idx", idx)("size", ColumnIds.size()); + return ColumnIds[idx]; + } + + ui64 size() const { + return ColumnIds.size(); + } +}; + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/scheme/abstract/index_info.cpp b/ydb/core/tx/columnshard/engines/scheme/abstract/index_info.cpp index c1c31cb5487f..643840074487 100644 --- a/ydb/core/tx/columnshard/engines/scheme/abstract/index_info.cpp +++ b/ydb/core/tx/columnshard/engines/scheme/abstract/index_info.cpp @@ -8,8 +8,8 @@ namespace NKikimr::NOlap { -std::shared_ptr IIndexInfo::GetColumnLoaderVerified(const ui32 columnId) const { - auto result = GetColumnLoaderOptional(columnId); +const std::shared_ptr& IIndexInfo::GetColumnLoaderVerified(const ui32 columnId) const { + const auto& result = GetColumnLoaderOptional(columnId); AFL_VERIFY(result); return result; } @@ -24,9 +24,9 @@ void IIndexInfo::AddDeleteFlagsColumn(NArrow::TGeneralContainer& batch, const bo void IIndexInfo::AddSnapshotColumns(NArrow::TGeneralContainer& batch, const TSnapshot& snapshot, const ui64 insertWriteId) { const i64 numRows = batch.num_rows(); - batch.AddField(arrow::field(SPEC_COL_PLAN_STEP, arrow::uint64()), NArrow::MakeUI64Array(snapshot.GetPlanStep(), numRows)).Validate(); - batch.AddField(arrow::field(SPEC_COL_TX_ID, arrow::uint64()), NArrow::MakeUI64Array(snapshot.GetTxId(), numRows)).Validate(); - batch.AddField(arrow::field(SPEC_COL_WRITE_ID, arrow::uint64()), NArrow::MakeUI64Array(insertWriteId, numRows)).Validate(); + batch.AddField(PlanStepField, NArrow::MakeUI64Array(snapshot.GetPlanStep(), numRows)).Validate(); + batch.AddField(TxIdField, NArrow::MakeUI64Array(snapshot.GetTxId(), numRows)).Validate(); + batch.AddField(WriteIdField, NArrow::MakeUI64Array(insertWriteId, numRows)).Validate(); } void IIndexInfo::NormalizeDeletionColumn(NArrow::TGeneralContainer& batch) { diff --git a/ydb/core/tx/columnshard/engines/scheme/abstract/index_info.h b/ydb/core/tx/columnshard/engines/scheme/abstract/index_info.h index 19fbe2267a7e..7102cbaefc50 100644 --- a/ydb/core/tx/columnshard/engines/scheme/abstract/index_info.h +++ b/ydb/core/tx/columnshard/engines/scheme/abstract/index_info.h @@ -31,6 +31,11 @@ class IIndexInfo { static constexpr const char* SPEC_COL_TX_ID = NOlap::NPortion::TSpecialColumns::SPEC_COL_TX_ID; static constexpr const char* SPEC_COL_WRITE_ID = NOlap::NPortion::TSpecialColumns::SPEC_COL_WRITE_ID; static constexpr const char* SPEC_COL_DELETE_FLAG = NOlap::NPortion::TSpecialColumns::SPEC_COL_DELETE_FLAG; + static constexpr ui32 SpecialColumnsCount = 4; + + static const inline std::shared_ptr PlanStepField = arrow::field(SPEC_COL_PLAN_STEP, arrow::uint64()); + static const inline std::shared_ptr TxIdField = arrow::field(SPEC_COL_TX_ID, arrow::uint64()); + static const inline std::shared_ptr WriteIdField = arrow::field(SPEC_COL_WRITE_ID, arrow::uint64()); static const char* GetDeleteFlagColumnName() { return SPEC_COL_DELETE_FLAG; @@ -115,11 +120,9 @@ class IIndexInfo { return result; } - [[nodiscard]] static std::vector AddSpecialFieldIds(const std::vector& baseColumnIds) { - std::vector result = baseColumnIds; + static void AddSpecialFieldIds(std::vector& baseColumnIds) { const auto& cIds = GetSystemColumnIds(); - result.insert(result.end(), cIds.begin(), cIds.end()); - return result; + baseColumnIds.insert(baseColumnIds.end(), cIds.begin(), cIds.end()); } [[nodiscard]] static std::set AddSpecialFieldIds(const std::set& baseColumnIds) { @@ -143,8 +146,8 @@ class IIndexInfo { static std::shared_ptr GetColumnFieldOptional(const ui32 columnId); static std::shared_ptr GetColumnFieldVerified(const ui32 columnId); - virtual std::shared_ptr GetColumnLoaderOptional(const ui32 columnId) const = 0; - std::shared_ptr GetColumnLoaderVerified(const ui32 columnId) const; + virtual const std::shared_ptr& GetColumnLoaderOptional(const ui32 columnId) const = 0; + const std::shared_ptr& GetColumnLoaderVerified(const ui32 columnId) const; static void NormalizeDeletionColumn(NArrow::TGeneralContainer& batch); diff --git a/ydb/core/tx/columnshard/engines/scheme/abstract/schema_version.cpp b/ydb/core/tx/columnshard/engines/scheme/abstract/schema_version.cpp new file mode 100644 index 000000000000..8f630cd397d1 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/scheme/abstract/schema_version.cpp @@ -0,0 +1,3 @@ +#include "schema_version.h" + +namespace NKikimr::NOlap {} diff --git a/ydb/core/tx/columnshard/engines/scheme/abstract/schema_version.h b/ydb/core/tx/columnshard/engines/scheme/abstract/schema_version.h new file mode 100644 index 000000000000..2930b6d8d102 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/scheme/abstract/schema_version.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include + +namespace NKikimr::NOlap { + +class TSchemaVersionId { +private: + YDB_READONLY_DEF(ui64, PresetId); + YDB_READONLY_DEF(ui64, Version); + +public: + bool operator==(const TSchemaVersionId& other) const { + return std::tie(PresetId, Version) == std::tie(other.PresetId, other.Version); + } + + TSchemaVersionId(const ui64 presetId, const ui64 version) + : PresetId(presetId) + , Version(version) { + } +}; + +} + +template <> +struct THash { + inline size_t operator()(const NKikimr::NOlap::TSchemaVersionId& key) const { + return CombineHashes(key.GetPresetId(), key.GetVersion()); + } +}; diff --git a/ydb/core/tx/columnshard/engines/scheme/abstract/ya.make b/ydb/core/tx/columnshard/engines/scheme/abstract/ya.make index 79b12f94389e..bf3aac5302b7 100644 --- a/ydb/core/tx/columnshard/engines/scheme/abstract/ya.make +++ b/ydb/core/tx/columnshard/engines/scheme/abstract/ya.make @@ -2,6 +2,8 @@ LIBRARY() SRCS( index_info.cpp + column_ids.cpp + schema_version.cpp ) PEERDIR( diff --git a/ydb/core/tx/columnshard/engines/scheme/column/info.cpp b/ydb/core/tx/columnshard/engines/scheme/column/info.cpp index f139bc63bcd5..2f125700931a 100644 --- a/ydb/core/tx/columnshard/engines/scheme/column/info.cpp +++ b/ydb/core/tx/columnshard/engines/scheme/column/info.cpp @@ -64,21 +64,22 @@ std::vector> TSimpleColumnInf AFL_VERIFY(Loader); const auto checkNeedActualize = [&]() { if (!Serializer.IsEqualTo(sourceColumnFeatures.Serializer)) { - AFL_NOTICE(NKikimrServices::TX_COLUMNSHARD)("event", "actualization")("reason", "serializer") + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_ACTUALIZATION)("event", "actualization")("reason", "serializer") ("from", sourceColumnFeatures.Serializer.SerializeToProto().DebugString()) ("to", Serializer.SerializeToProto().DebugString()); return true; } if (!Loader->IsEqualTo(*sourceColumnFeatures.Loader)) { - AFL_NOTICE(NKikimrServices::TX_COLUMNSHARD)("event", "actualization")("reason", "loader"); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_ACTUALIZATION)("event", "actualization")("reason", "loader"); return true; } if (!!DictionaryEncoding != !!sourceColumnFeatures.DictionaryEncoding) { - AFL_NOTICE(NKikimrServices::TX_COLUMNSHARD)("event", "actualization")("reason", "dictionary")("from", !!sourceColumnFeatures.DictionaryEncoding)("to", !!DictionaryEncoding); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_ACTUALIZATION)("event", "actualization")("reason", "dictionary")( + "from", !!sourceColumnFeatures.DictionaryEncoding)("to", !!DictionaryEncoding); return true; } if (!!DictionaryEncoding && !DictionaryEncoding->IsEqualTo(*sourceColumnFeatures.DictionaryEncoding)) { - AFL_NOTICE(NKikimrServices::TX_COLUMNSHARD)("event", "actualization")("reason", "dictionary_encoding") + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_ACTUALIZATION)("event", "actualization")("reason", "dictionary_encoding") ("from", sourceColumnFeatures.DictionaryEncoding->SerializeToProto().DebugString()) ("to", DictionaryEncoding->SerializeToProto().DebugString()) ; @@ -91,7 +92,13 @@ std::vector> TSimpleColumnInf } std::vector> result; for (auto&& s : source) { - auto data = sourceColumnFeatures.Loader->ApplyRawVerified(s->GetData()); + std::shared_ptr data; + if (!DataAccessorConstructor.IsEqualTo(sourceColumnFeatures.DataAccessorConstructor)) { + auto chunkedArray = sourceColumnFeatures.Loader->ApplyVerified(s->GetData(), s->GetRecordsCountVerified()); + data = DataAccessorConstructor.Construct(chunkedArray, Loader->BuildAccessorContext(s->GetRecordsCountVerified())); + } else { + data = sourceColumnFeatures.Loader->ApplyRawVerified(s->GetData()); + } result.emplace_back(s->CopyWithAnotherBlob(GetColumnSaver().Apply(data), *this)); } return result; diff --git a/ydb/core/tx/columnshard/engines/scheme/common/cache.cpp b/ydb/core/tx/columnshard/engines/scheme/common/cache.cpp new file mode 100644 index 000000000000..9be4dd958459 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/scheme/common/cache.cpp @@ -0,0 +1,3 @@ +#include "cache.h" + +namespace NKikimr::NOlap {} diff --git a/ydb/core/tx/columnshard/engines/scheme/common/cache.h b/ydb/core/tx/columnshard/engines/scheme/common/cache.h new file mode 100644 index 000000000000..8ccdfdc03445 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/scheme/common/cache.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +#include + +namespace NKikimr::NOlap { + +template +class TObjectCache : std::enable_shared_from_this> { +private: + THashMap> Objects; + mutable TMutex Mutex; + +public: + class TEntryGuard { + private: + TKey Key; + std::shared_ptr Object; + std::weak_ptr Cache; + + public: + TEntryGuard(TKey key, const std::shared_ptr object, TObjectCache* cache) + : Key(key) + , Object(object) + , Cache(cache->weak_from_this()) { + } + + const TObject* operator->() const { + return Object.get(); + } + const TObject& operator*() const { + return *Object; + } + + ~TEntryGuard() { + Object.reset(); + if (auto cache = Cache.lock()) { + cache->TryFree(Key); + } + } + }; + +public: + TEntryGuard Upsert(TKey key, TObject&& object) { + TGuard lock(Mutex); + auto* findSchema = Objects.FindPtr(key); + std::shared_ptr cachedObject; + if (findSchema) { + cachedObject = findSchema->lock(); + } + if (!cachedObject) { + cachedObject = std::make_shared(std::move(object)); + Objects[key] = cachedObject; + } + return TEntryGuard(std::move(key), cachedObject, this); + } + + void TryFree(const TKey& key) { + TGuard lock(Mutex); + auto findObject = Objects.FindPtr(key); + if (findObject) { + if (findObject->expired()) { + Objects.erase(key); + } + } + } +}; + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/scheme/common/ya.make b/ydb/core/tx/columnshard/engines/scheme/common/ya.make new file mode 100644 index 000000000000..6d84704af41d --- /dev/null +++ b/ydb/core/tx/columnshard/engines/scheme/common/ya.make @@ -0,0 +1,13 @@ +LIBRARY() + +SRCS( + cache.cpp +) + +PEERDIR( + ydb/library/actors/core +) + +YQL_LAST_ABI_VERSION() + +END() diff --git a/ydb/core/tx/columnshard/engines/scheme/defaults/common/ya.make b/ydb/core/tx/columnshard/engines/scheme/defaults/common/ya.make index a34b917e4df3..216ae4e37917 100644 --- a/ydb/core/tx/columnshard/engines/scheme/defaults/common/ya.make +++ b/ydb/core/tx/columnshard/engines/scheme/defaults/common/ya.make @@ -9,6 +9,7 @@ PEERDIR( contrib/libs/apache/arrow ydb/library/conclusion ydb/core/scheme_types + ydb/library/actors/core ) END() diff --git a/ydb/core/tx/columnshard/engines/scheme/index_info.cpp b/ydb/core/tx/columnshard/engines/scheme/index_info.cpp index 0569724bb7ba..62abfcf9be9a 100644 --- a/ydb/core/tx/columnshard/engines/scheme/index_info.cpp +++ b/ydb/core/tx/columnshard/engines/scheme/index_info.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -11,12 +10,9 @@ #include #include -namespace NKikimr::NOlap { +#include -TIndexInfo::TIndexInfo(const TString& name) - : Name(name) { - CompactionPlannerConstructor = NStorageOptimizer::IOptimizerPlannerConstructor::BuildDefault(); -} +namespace NKikimr::NOlap { bool TIndexInfo::CheckCompatible(const TIndexInfo& other) const { if (!other.GetPrimaryKey()->Equals(PrimaryKey)) { @@ -32,25 +28,23 @@ ui32 TIndexInfo::GetColumnIdVerified(const std::string& name) const { } std::optional TIndexInfo::GetColumnIdOptional(const std::string& name) const { - const auto pred = [](const TNameInfo& item, const std::string& value) { - return item.GetName() < value; - }; - auto it = std::lower_bound(ColumnNames.begin(), ColumnNames.end(), name, pred); - if (it != ColumnNames.end() && it->GetName() == name) { - return it->GetColumnId(); + auto idx = GetColumnIndexOptional(name); + if (!idx) { + return std::nullopt; } - return IIndexInfo::GetColumnIdOptional(name); + AFL_VERIFY(*idx < SchemaColumnIdsWithSpecials.size()); + return SchemaColumnIdsWithSpecials[*idx]; } std::optional TIndexInfo::GetColumnIndexOptional(const std::string& name) const { - const auto pred = [](const TNameInfo& item, const std::string& value) { - return item.GetName() < value; - }; - auto it = std::lower_bound(ColumnNames.begin(), ColumnNames.end(), name, pred); - if (it != ColumnNames.end() && it->GetName() == name) { - return it->GetColumnIdx(); + auto it = std::lower_bound(ColumnIdxSortedByName.begin(), ColumnIdxSortedByName.end(), name, [this](const ui32 idx, const std::string name) { + AFL_VERIFY(idx < ColumnFeatures.size()); + return ColumnFeatures[idx]->GetColumnName() < name; + }); + if (it != ColumnIdxSortedByName.end() && SchemaWithSpecials->GetFieldByIndexVerified(*it)->name() == name) { + return *it; } - return IIndexInfo::GetColumnIndexOptional(name, ColumnNames.size()); + return std::nullopt; } TString TIndexInfo::GetColumnName(const ui32 id, bool required) const { @@ -63,11 +57,12 @@ TString TIndexInfo::GetColumnName(const ui32 id, bool required) const { } } -const std::vector& TIndexInfo::GetColumnIds(const bool withSpecial) const { +TColumnIdsView TIndexInfo::GetColumnIds(const bool withSpecial) const { if (withSpecial) { - return SchemaColumnIdsWithSpecials; + return {SchemaColumnIdsWithSpecials.begin(), SchemaColumnIdsWithSpecials.end()}; } else { - return SchemaColumnIds; + AFL_VERIFY(SpecialColumnsCount < SchemaColumnIdsWithSpecials.size()); + return {SchemaColumnIdsWithSpecials.begin(), SchemaColumnIdsWithSpecials.end() - SpecialColumnsCount}; } } @@ -80,7 +75,8 @@ std::vector TIndexInfo::GetColumnNames(const std::vector& ids) co return out; } -std::vector TIndexInfo::GetColumnSTLNames(const std::vector& ids) const { +std::vector TIndexInfo::GetColumnSTLNames(const bool withSpecial) const { + const TColumnIdsView ids = GetColumnIds(withSpecial); std::vector out; out.reserve(ids.size()); for (ui32 id : ids) { @@ -89,9 +85,9 @@ std::vector TIndexInfo::GetColumnSTLNames(const std::vector& return out; } -const std::shared_ptr& TIndexInfo::ArrowSchema() const { - AFL_VERIFY(Schema); - return Schema; +NArrow::TSchemaLiteView TIndexInfo::ArrowSchema() const { + const auto& schema = ArrowSchemaWithSpecials(); + return std::span>(schema->fields().begin(), schema->fields().end() - SpecialColumnsCount); } const std::shared_ptr& TIndexInfo::ArrowSchemaWithSpecials() const { @@ -125,15 +121,9 @@ void TIndexInfo::SetAllKeys(const std::shared_ptr& operators, PKColumns.emplace_back(TNameTypeInfo(it->second.Name, it->second.PType)); } - for (const auto& [colId, column] : columns) { - if (NArrow::IsPrimitiveYqlType(column.PType)) { - MinMaxIdxColumnsIds.insert(colId); - } - } - MinMaxIdxColumnsIds.insert(GetPKFirstColumnId()); - if (!Schema) { - AFL_VERIFY(!SchemaWithSpecials); + if (!SchemaWithSpecials) { InitializeCaches(operators, columns, nullptr); + Precalculate(); } } @@ -141,10 +131,10 @@ TColumnSaver TIndexInfo::GetColumnSaver(const ui32 columnId) const { return GetColumnFeaturesVerified(columnId).GetColumnSaver(); } -std::shared_ptr TIndexInfo::GetColumnLoaderOptional(const ui32 columnId) const { +const std::shared_ptr& TIndexInfo::GetColumnLoaderOptional(const ui32 columnId) const { const auto& cFeatures = GetColumnFeaturesOptional(columnId); if (!cFeatures) { - return nullptr; + return Default>(); } else { return cFeatures->GetLoader(); } @@ -187,35 +177,64 @@ std::shared_ptr TIndexInfo::GetColumnSchema(const ui32 columnId) return GetColumnsSchema({ columnId }); } -bool TIndexInfo::DeserializeFromProto(const NKikimrSchemeOp::TColumnTableSchema& schema, const std::shared_ptr& operators, - const std::shared_ptr& cache) { - if (schema.GetEngine() != NKikimrSchemeOp::COLUMN_ENGINE_REPLACING_TIMESERIES) { - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("event", "cannot_parse_index_info")("reason", "incorrect_engine_in_schema"); +void TIndexInfo::DeserializeOptionsFromProto(const NKikimrSchemeOp::TColumnTableSchemeOptions& optionsProto) { + TMemoryProfileGuard g("TIndexInfo::DeserializeFromProto::Options"); + SchemeNeedActualization = optionsProto.GetSchemeNeedActualization(); + if (optionsProto.HasScanReaderPolicyName()) { + ScanReaderPolicyName = optionsProto.GetScanReaderPolicyName(); + } + if (optionsProto.HasCompactionPlannerConstructor()) { + auto container = + NStorageOptimizer::TOptimizerPlannerConstructorContainer::BuildFromProto(optionsProto.GetCompactionPlannerConstructor()); + CompactionPlannerConstructor = container.DetachResult().GetObjectPtrVerified(); + } else { + CompactionPlannerConstructor = NStorageOptimizer::IOptimizerPlannerConstructor::BuildDefault(); + } + if (optionsProto.HasMetadataManagerConstructor()) { + auto container = + NDataAccessorControl::TMetadataManagerConstructorContainer::BuildFromProto(optionsProto.GetMetadataManagerConstructor()); + MetadataManagerConstructor = container.DetachResult().GetObjectPtrVerified(); + } else { + MetadataManagerConstructor = NDataAccessorControl::IManagerConstructor::BuildDefault(); + } +} + +bool TIndexInfo::DeserializeDefaultCompressionFromProto(const NKikimrSchemeOp::TCompressionOptions& compressionProto) { + TMemoryProfileGuard g("TIndexInfo::DeserializeFromProto::Serializer"); + NArrow::NSerialization::TSerializerContainer container; + if (!container.DeserializeFromProto(compressionProto)) { + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("event", "cannot_parse_index_info")("reason", "cannot_parse_default_serializer"); return false; } - AFL_VERIFY(cache); + DefaultSerializer = container; + return true; +} - { - TMemoryProfileGuard g("TIndexInfo::DeserializeFromProto::Optimizer"); - SchemeNeedActualization = schema.GetOptions().GetSchemeNeedActualization(); - ExternalGuaranteeExclusivePK = schema.GetOptions().GetExternalGuaranteeExclusivePK(); - if (schema.GetOptions().HasCompactionPlannerConstructor()) { - auto container = - NStorageOptimizer::TOptimizerPlannerConstructorContainer::BuildFromProto(schema.GetOptions().GetCompactionPlannerConstructor()); - CompactionPlannerConstructor = container.DetachResult().GetObjectPtrVerified(); - } else { - AFL_VERIFY(!!CompactionPlannerConstructor); +TConclusion> TIndexInfo::CreateColumnFeatures(const NTable::TColumn& col, + const NKikimrSchemeOp::TOlapColumnDescription& colProto, const std::shared_ptr& operators, + const std::shared_ptr& cache) const { + const TString fingerprint = cache ? ("C:" + colProto.SerializeAsString()) : Default(); + const auto createPred = [&]() -> TConclusion> { + auto f = BuildDefaultColumnFeatures(col, operators); + auto parsed = f->DeserializeFromProto(colProto, operators); + if (parsed.IsFail()) { + return parsed; } - } + return f; + }; + return cache->GetOrCreateColumnFeatures(fingerprint, createPred); +} + +bool TIndexInfo::DeserializeFromProto(const NKikimrSchemeOp::TColumnTableSchema& schema, const std::shared_ptr& operators, + const std::shared_ptr& cache) { + AFL_VERIFY(cache); + + DeserializeOptionsFromProto(schema.GetOptions()); if (schema.HasDefaultCompression()) { - TMemoryProfileGuard g("TIndexInfo::DeserializeFromProto::Serializer"); - NArrow::NSerialization::TSerializerContainer container; - if (!container.DeserializeFromProto(schema.GetDefaultCompression())) { - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("event", "cannot_parse_index_info")("reason", "cannot_parse_default_serializer"); + if (!DeserializeDefaultCompressionFromProto(schema.GetDefaultCompression())) { return false; } - DefaultSerializer = container; } { TMemoryProfileGuard g("TIndexInfo::DeserializeFromProto::Indexes"); @@ -226,49 +245,40 @@ bool TIndexInfo::DeserializeFromProto(const NKikimrSchemeOp::TColumnTableSchema& } } THashMap columns; + AFL_VERIFY(PKColumnIds.empty()); { TMemoryProfileGuard g("TIndexInfo::DeserializeFromProto::Columns"); + THashMap columnIds; for (const auto& col : schema.GetColumns()) { - const ui32 id = col.GetId(); - const TString& name = cache->GetStringCache(col.GetName()); - const bool notNull = col.HasNotNull() ? col.GetNotNull() : false; - auto typeInfoMod = NScheme::TypeInfoModFromProtoColumnType(col.GetTypeId(), col.HasTypeInfo() ? &col.GetTypeInfo() : nullptr); - columns[id] = NTable::TColumn(name, id, typeInfoMod.TypeInfo, cache->GetStringCache(typeInfoMod.TypeMod), notNull); + auto tableCol = BuildColumnFromProto(col, cache); + auto id = tableCol.Id; + AFL_VERIFY(columnIds.emplace(tableCol.Name, id).second); + AFL_VERIFY(columns.emplace(id, std::move(tableCol)).second); + } + for (const auto& keyName : schema.GetKeyColumnNames()) { + const ui32* findColumnId = columnIds.FindPtr(keyName); + AFL_VERIFY(findColumnId); + auto it = columns.find(*findColumnId); + AFL_VERIFY(it != columns.end()); + it->second.KeyOrder = PKColumnIds.size(); + PKColumnIds.push_back(*findColumnId); } - ColumnNames = TNameInfo::BuildColumnNames(columns); - } - PKColumnIds.clear(); - for (const auto& keyName : schema.GetKeyColumnNames()) { - const ui32 columnId = GetColumnIdVerified(keyName); - auto it = columns.find(columnId); - AFL_VERIFY(it != columns.end()); - it->second.KeyOrder = PKColumnIds.size(); - PKColumnIds.push_back(columnId); } InitializeCaches(operators, columns, cache, false); SetAllKeys(operators, columns); { TMemoryProfileGuard g("TIndexInfo::DeserializeFromProto::Columns::Features"); for (const auto& col : schema.GetColumns()) { - THashMap> it; - const TString fingerprint = cache ? ("C:" + col.SerializeAsString()) : Default(); - const auto createPred = [&]() -> TConclusion> { - auto f = BuildDefaultColumnFeatures(col.GetId(), columns, operators); - auto parsed = f->DeserializeFromProto(col, operators); - if (parsed.IsFail()) { - return parsed; - } - return f; - }; - auto fConclusion = cache->GetOrCreateColumnFeatures(fingerprint, createPred); + auto it = columns.find(col.GetId()); + AFL_VERIFY(it != columns.end()); + auto fConclusion = CreateColumnFeatures(it->second, col, operators, cache); if (fConclusion.IsFail()) { - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("event", "cannot_parse_column_feature")("reason", fConclusion.GetErrorMessage()); + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("event", "cannot_build_column_feature")("reason", fConclusion.GetErrorMessage()); return false; } ColumnFeatures.emplace_back(fConclusion.DetachResult()); } for (auto&& cId : GetSystemColumnIds()) { - THashMap> it; const TString fingerprint = "SC:" + ::ToString(cId); const auto createPred = [&]() -> TConclusion> { return BuildDefaultColumnFeatures(cId, {}, operators); @@ -283,6 +293,8 @@ bool TIndexInfo::DeserializeFromProto(const NKikimrSchemeOp::TColumnTableSchema& } Version = schema.GetVersion(); + Precalculate(); + Validate(); return true; } @@ -299,37 +311,29 @@ std::vector GetColumns(const NTable::TScheme::TTableSchema& table std::optional TIndexInfo::BuildFromProto(const NKikimrSchemeOp::TColumnTableSchema& schema, const std::shared_ptr& operators, const std::shared_ptr& cache) { - TIndexInfo result(""); + TIndexInfo result; if (!result.DeserializeFromProto(schema, operators, cache)) { return std::nullopt; } return result; } -std::vector> MakeArrowFields(const NTable::TScheme::TTableSchema::TColumns& columns, const std::vector& ids, - const std::shared_ptr& cache) { +std::optional TIndexInfo::BuildFromProto(const NKikimrSchemeOp::TColumnTableSchemaDiff& diff, const TIndexInfo& prevSchema, + const std::shared_ptr& operators, const std::shared_ptr& cache) { + TSchemaDiffView diffView; + diffView.DeserializeFromProto(diff).Validate(); + return TIndexInfo(prevSchema, diffView, operators, cache); +} + +std::vector> TIndexInfo::MakeArrowFields( + const NTable::TScheme::TTableSchema::TColumns& columns, const std::vector& ids, const std::shared_ptr& cache) { std::vector> fields; for (const ui32 id : ids) { AFL_VERIFY(!TIndexInfo::IsSpecialColumn(id)); auto it = columns.find(id); AFL_VERIFY(it != columns.end()); - - const auto& column = it->second; - std::string colName(column.Name.data(), column.Name.size()); - auto arrowType = NArrow::GetArrowType(column.PType); - AFL_VERIFY(arrowType.ok()); - auto f = std::make_shared(colName, arrowType.ValueUnsafe(), !column.NotNull); - if (cache) { - auto fFound = cache->GetField(f->ToString(true)); - if (!fFound) { - cache->RegisterField(f->ToString(true), f); - fields.emplace_back(f); - } else { - fields.emplace_back(fFound); - } - } else { - fields.emplace_back(f); - } + auto f = TIndexInfo::BuildArrowField(it->second, cache); + fields.emplace_back(f); } return fields; @@ -337,30 +341,30 @@ std::vector> MakeArrowFields(const NTable::TScheme std::shared_ptr MakeArrowSchema( const NTable::TScheme::TTableSchema::TColumns& columns, const std::vector& ids, const std::shared_ptr& cache) { - return std::make_shared(MakeArrowFields(columns, ids, cache)); + return std::make_shared(TIndexInfo::MakeArrowFields(columns, ids, cache)); } -void TIndexInfo::InitializeCaches(const std::shared_ptr& operators, const THashMap& columns, const std::shared_ptr& cache, - const bool withColumnFeatures) { +void TIndexInfo::InitializeCaches(const std::shared_ptr& operators, const THashMap& columns, + const std::shared_ptr& cache, const bool withColumnFeatures) { { TMemoryProfileGuard g("TIndexInfo::DeserializeFromProto::InitializeCaches::Schema"); - AFL_VERIFY(!Schema); - SchemaColumnIds.reserve(columns.size()); + AFL_VERIFY(!SchemaWithSpecials); + SchemaColumnIdsWithSpecials.reserve(columns.size()); for (const auto& [id, _] : columns) { - SchemaColumnIds.push_back(id); + SchemaColumnIdsWithSpecials.push_back(id); } - std::sort(SchemaColumnIds.begin(), SchemaColumnIds.end()); - auto originalFields = MakeArrowFields(columns, SchemaColumnIds, cache); - Schema = std::make_shared(originalFields); + std::sort(SchemaColumnIdsWithSpecials.begin(), SchemaColumnIdsWithSpecials.end()); + auto originalFields = TIndexInfo::MakeArrowFields(columns, SchemaColumnIdsWithSpecials, cache); IIndexInfo::AddSpecialFields(originalFields); SchemaWithSpecials = std::make_shared(originalFields); } { TMemoryProfileGuard g("TIndexInfo::DeserializeFromProto::InitializeCaches::SchemaFields"); - SchemaColumnIdsWithSpecials = IIndexInfo::AddSpecialFieldIds(SchemaColumnIds); + IIndexInfo::AddSpecialFieldIds(SchemaColumnIdsWithSpecials); } if (withColumnFeatures) { + AFL_VERIFY(ColumnFeatures.empty()); { TMemoryProfileGuard g("TIndexInfo::DeserializeFromProto::InitializeCaches::Columns"); for (auto&& c : columns) { @@ -393,7 +397,7 @@ NSplitter::TEntityGroups TIndexInfo::GetEntityGroupsByStorageId(const TString& s return groups; } -std::shared_ptr TIndexInfo::GetCompactionPlannerConstructor() const { +const std::shared_ptr& TIndexInfo::GetCompactionPlannerConstructor() const { AFL_VERIFY(!!CompactionPlannerConstructor); return CompactionPlannerConstructor; } @@ -408,14 +412,15 @@ std::shared_ptr TIndexInfo::GetColumnExternalDefaultValueVerified } NKikimr::TConclusionStatus TIndexInfo::AppendIndex(const THashMap>>& originalData, - const ui32 indexId, const std::shared_ptr& operators, TSecondaryData& result) const { + const ui32 indexId, const std::shared_ptr& operators, const ui32 recordsCount, TSecondaryData& result) const { auto it = Indexes.find(indexId); AFL_VERIFY(it != Indexes.end()); auto& index = it->second; - std::shared_ptr chunk = index->BuildIndex(originalData, *this); + std::shared_ptr chunk = index->BuildIndex(originalData, recordsCount, *this); auto opStorage = operators->GetOperatorVerified(index->GetStorageId()); if ((i64)chunk->GetPackedSize() > opStorage->GetBlobSplitSettings().GetMaxBlobSize()) { - return TConclusionStatus::Fail("blob size for secondary data (" + ::ToString(indexId) + ") bigger than limit (" + + return TConclusionStatus::Fail("blob size for secondary data (" + ::ToString(indexId) + ":" + ::ToString(chunk->GetPackedSize()) + ":" + + ::ToString(recordsCount) + ") bigger than limit (" + ::ToString(opStorage->GetBlobSplitSettings().GetMaxBlobSize()) + ")"); } if (index->GetStorageId() == IStoragesManager::LocalMetadataStorageId) { @@ -453,13 +458,21 @@ std::shared_ptr TIndexInfo::GetIndexMetaC } std::vector TIndexInfo::GetEntityIds() const { - auto result = GetColumnIds(true); + const TColumnIdsView columnIds = GetColumnIds(true); + std::vector result(columnIds.begin(), columnIds.end()); for (auto&& i : Indexes) { result.emplace_back(i.first); } return result; } +std::shared_ptr TIndexInfo::BuildDefaultColumnFeatures( + const NTable::TColumn& column, const std::shared_ptr& operators) const { + AFL_VERIFY(!IsSpecialColumn(column.Id)); + return std::make_shared(column.Id, GetColumnFieldVerified(column.Id), DefaultSerializer, operators->GetDefaultOperator(), + NArrow::IsPrimitiveYqlType(column.PType), column.Id == GetPKFirstColumnId(), false, nullptr, column.GetCorrectKeyOrder()); +} + std::shared_ptr TIndexInfo::BuildDefaultColumnFeatures( const ui32 columnId, const THashMap& columns, const std::shared_ptr& operators) const { if (IsSpecialColumn(columnId)) { @@ -478,4 +491,134 @@ std::shared_ptr TIndexInfo::GetColumnExternalDefaultValueByIndexV return ColumnFeatures[colIndex]->GetDefaultValue().GetValue(); } +TIndexInfo::TIndexInfo(const TIndexInfo& original, const TSchemaDiffView& diff, const std::shared_ptr& operators, + const std::shared_ptr& cache) { + { + std::vector> fields; + const auto addFromOriginal = [&](const ui32 index) { + AFL_VERIFY(index < original.SchemaColumnIdsWithSpecials.size()); + const ui32 originalColId = original.SchemaColumnIdsWithSpecials[index]; + SchemaColumnIdsWithSpecials.emplace_back(originalColId); + if (!IIndexInfo::IsSpecialColumn(originalColId)) { + AFL_VERIFY(index < original.SchemaColumnIdsWithSpecials.size() - SpecialColumnsCount); + fields.emplace_back(original.SchemaWithSpecials->field(index)); + } + }; + + const auto addFromDiff = [&](const NKikimrSchemeOp::TOlapColumnDescription& col, const std::optional /*originalIndex*/) { + const ui32 colId = col.GetId(); + AFL_VERIFY(!IIndexInfo::IsSpecialColumn(colId)); + SchemaColumnIdsWithSpecials.emplace_back(colId); + auto tableCol = BuildColumnFromProto(col, cache); + fields.emplace_back(BuildArrowField(tableCol, cache)); + }; + diff.ApplyForColumns(original.SchemaColumnIdsWithSpecials, addFromOriginal, addFromDiff); + IIndexInfo::AddSpecialFields(fields); + SchemaWithSpecials = std::make_shared(fields); + PKColumnIds = original.PKColumnIds; + PKColumns = original.PKColumns; + } + { + const auto addFromOriginal = [&](const ui32 index) { + ColumnFeatures.emplace_back(original.ColumnFeatures[index]); + }; + + const auto addFromDiff = [&](const NKikimrSchemeOp::TOlapColumnDescription& col, const std::optional originalIndex) { + auto tableCol = BuildColumnFromProto(col, cache); + if (originalIndex && original.ColumnFeatures[*originalIndex]->GetPKColumnIndex()) { + tableCol.KeyOrder = *original.ColumnFeatures[*originalIndex]->GetPKColumnIndex(); + } + ColumnFeatures.emplace_back(CreateColumnFeatures(tableCol, col, operators, cache).DetachResult()); + }; + diff.ApplyForColumns(original.SchemaColumnIdsWithSpecials, addFromOriginal, addFromDiff); + } + { + TMemoryProfileGuard g("TIndexInfo::ApplyDiff::Indexes"); + Indexes = original.Indexes; + for (auto&& i : diff.GetModifiedIndexes()) { + if (!i.second) { + AFL_VERIFY(Indexes.erase(i.first)); + } else { + auto it = Indexes.find(i.first); + NIndexes::TIndexMetaContainer meta; + AFL_VERIFY(meta.DeserializeFromProto(*i.second)); + if (it != Indexes.end()) { + it->second = std::move(meta); + } else { + Indexes.emplace(i.first, std::move(meta)); + } + } + } + } + + DeserializeOptionsFromProto(diff.GetSchemaOptions()); + Version = diff.GetVersion(); + PrimaryKey = original.PrimaryKey; + if (diff.GetCompressionOptions()) { + DeserializeDefaultCompressionFromProto(*diff.GetCompressionOptions()); + } + Precalculate(); + Validate(); +} + +void TIndexInfo::Precalculate() { + BuildColumnIndexByName(); + UsedStorageIds = std::make_shared>(); + for (auto&& i : ColumnFeatures) { + UsedStorageIds->emplace(i->GetOperator()->GetStorageId()); + } +} + +void TIndexInfo::BuildColumnIndexByName() { + const ui32 columnCount = SchemaColumnIdsWithSpecials.size(); + std::erase_if(ColumnIdxSortedByName, [columnCount](const ui32 idx) { + return idx >= columnCount; + }); + ColumnIdxSortedByName.reserve(columnCount); + for (ui32 i = 0; i < columnCount; ++i) { + ColumnIdxSortedByName.push_back(i); + } + + std::sort(ColumnIdxSortedByName.begin(), ColumnIdxSortedByName.end(), [this](const ui32 lhs, const ui32 rhs) { + return CompareColumnIdxByName(lhs, rhs); + }); +} + +void TIndexInfo::Validate() const { + AFL_VERIFY(!!UsedStorageIds); + AFL_VERIFY(ColumnFeatures.size() == SchemaColumnIdsWithSpecials.size()); + AFL_VERIFY(ColumnFeatures.size() == (ui32)SchemaWithSpecials->num_fields()); + { + ui32 idx = 0; + for (auto&& i : SchemaColumnIdsWithSpecials) { + AFL_VERIFY(i == ColumnFeatures[idx]->GetColumnId()); + AFL_VERIFY(SchemaWithSpecials->field(idx)->name() == ColumnFeatures[idx]->GetColumnName()); + ++idx; + } + } + AFL_VERIFY(std::is_sorted(SchemaColumnIdsWithSpecials.begin(), SchemaColumnIdsWithSpecials.end())); + + AFL_VERIFY(ColumnFeatures.size() == ColumnIdxSortedByName.size()); + AFL_VERIFY(std::is_sorted(ColumnIdxSortedByName.begin(), ColumnIdxSortedByName.end(), [this](const ui32 lhs, const ui32 rhs) { + return CompareColumnIdxByName(lhs, rhs); + })); + + { + ui32 pkIdx = 0; + for (auto&& i : PKColumnIds) { + const ui32 idx = GetColumnIndexVerified(i); + AFL_VERIFY(ColumnFeatures[idx]->GetPKColumnIndex()); + AFL_VERIFY(*ColumnFeatures[idx]->GetPKColumnIndex() == pkIdx); + ++pkIdx; + } + } +} + +TIndexInfo TIndexInfo::BuildDefault() { + TIndexInfo result; + result.CompactionPlannerConstructor = NStorageOptimizer::IOptimizerPlannerConstructor::BuildDefault(); + result.MetadataManagerConstructor = NDataAccessorControl::IManagerConstructor::BuildDefault(); + return result; +} + } // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/scheme/index_info.h b/ydb/core/tx/columnshard/engines/scheme/index_info.h index ea7cd7784d82..eaf4f63eb2ea 100644 --- a/ydb/core/tx/columnshard/engines/scheme/index_info.h +++ b/ydb/core/tx/columnshard/engines/scheme/index_info.h @@ -1,6 +1,8 @@ #pragma once #include "column_features.h" +#include "objects_cache.h" +#include "schema_diff.h" #include "tier_info.h" #include "abstract/index_info.h" @@ -8,12 +10,15 @@ #include #include -#include #include #include #include #include #include +#include +#include + +#include #include @@ -24,7 +29,7 @@ class Schema; } // namespace arrow namespace NKikimr::NOlap { - +class TPortionInfo; namespace NIndexes::NMax { class TIndexMeta; } @@ -41,135 +46,122 @@ class TSnapshotColumnInfo; class ISnapshotSchema; using TNameTypeInfo = std::pair; -class TSchemaObjectsCache { -private: - THashMap> Fields; - THashMap> ColumnFeatures; - THashSet StringsCache; - mutable ui64 AcceptionFieldsCount = 0; - mutable ui64 AcceptionFeaturesCount = 0; - -public: - const TString& GetStringCache(const TString& original) { - auto it = StringsCache.find(original); - if (it == StringsCache.end()) { - it = StringsCache.emplace(original).first; - } - return *it; - } - - void RegisterField(const TString& fingerprint, const std::shared_ptr& f) { - AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "register_field")("fp", fingerprint)("f", f->ToString()); - AFL_VERIFY(Fields.emplace(fingerprint, f).second); - } - void RegisterColumnFeatures(const TString& fingerprint, const std::shared_ptr& f) { - AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "register_column_features")("fp", fingerprint)("info", f->DebugString()); - AFL_VERIFY(ColumnFeatures.emplace(fingerprint, f).second); - } - std::shared_ptr GetField(const TString& fingerprint) const { - auto it = Fields.find(fingerprint); - if (it == Fields.end()) { - AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "get_field_miss")("fp", fingerprint)("count", Fields.size())( - "acc", AcceptionFieldsCount); - return nullptr; - } - if (++AcceptionFieldsCount % 1000 == 0) { - AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "get_field_accept")("fp", fingerprint)("count", Fields.size())( - "acc", AcceptionFieldsCount); - } - return it->second; - } - template - TConclusion> GetOrCreateColumnFeatures(const TString& fingerprint, const TConstructor& constructor) { - auto it = ColumnFeatures.find(fingerprint); - if (it == ColumnFeatures.end()) { - AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "get_column_features_miss")("fp", UrlEscapeRet(fingerprint))( - "count", ColumnFeatures.size())("acc", AcceptionFeaturesCount); - TConclusion> resultConclusion = constructor(); - if (resultConclusion.IsFail()) { - return resultConclusion; - } - it = ColumnFeatures.emplace(fingerprint, resultConclusion.DetachResult()).first; - AFL_VERIFY(it->second); - } else { - if (++AcceptionFeaturesCount % 1000 == 0) { - AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "get_column_features_accept")("fp", UrlEscapeRet(fingerprint))( - "count", ColumnFeatures.size())("acc", AcceptionFeaturesCount); - } - } - return it->second; - } -}; - /// Column engine index description in terms of tablet's local table. /// We have to use YDB types for keys here. struct TIndexInfo: public IIndexInfo { private: using TColumns = THashMap; + friend class TPortionInfo; + friend class TPortionDataAccessor; - class TNameInfo { - private: - YDB_READONLY_DEF(TString, Name); - YDB_READONLY(ui32, ColumnId, 0); - YDB_READONLY(ui32, ColumnIdx, 0); - public: - TNameInfo(const TString& name, const ui32 columnId, const ui32 columnIdx) - : Name(name) - , ColumnId(columnId) - , ColumnIdx(columnIdx) - { - - } - - static std::vector BuildColumnNames(const TColumns& columns) { - std::vector result; - for (auto&& i : columns) { - result.emplace_back(TNameInfo(i.second.Name, i.first, 0)); - } - { - const auto pred = [](const TNameInfo& l, const TNameInfo& r) { - return l.ColumnId < r.ColumnId; - }; - std::sort(result.begin(), result.end(), pred); - } - ui32 idx = 0; - for (auto&& i : result) { - i.ColumnIdx = idx++; - } - { - const auto pred = [](const TNameInfo& l, const TNameInfo& r) { - return l.Name < r.Name; - }; - std::sort(result.begin(), result.end(), pred); - } - return result; - } - }; - - std::vector ColumnNames; + std::vector ColumnIdxSortedByName; std::vector PKColumnIds; std::vector PKColumns; std::vector> ColumnFeatures; THashMap Indexes; - TIndexInfo(const TString& name); + std::shared_ptr> UsedStorageIds; + bool SchemeNeedActualization = false; std::shared_ptr CompactionPlannerConstructor; - bool ExternalGuaranteeExclusivePK = false; + std::shared_ptr MetadataManagerConstructor; + std::optional ScanReaderPolicyName; + + ui64 Version = 0; + std::vector SchemaColumnIdsWithSpecials; + std::shared_ptr SchemaWithSpecials; + std::shared_ptr PrimaryKey; + NArrow::NSerialization::TSerializerContainer DefaultSerializer = NArrow::NSerialization::TSerializerContainer::GetDefaultSerializer(); + + TIndexInfo() = default; + + static std::shared_ptr BuildArrowField(const NTable::TColumn& column, const std::shared_ptr& cache) { + auto arrowType = NArrow::GetArrowType(column.PType); + AFL_VERIFY(arrowType.ok()); + auto f = std::make_shared(column.Name, arrowType.ValueUnsafe(), !column.NotNull); + if (cache) { + return cache->GetOrInsertField(f); + } else { + return f; + } + } + + static NTable::TColumn BuildColumnFromProto( + const NKikimrSchemeOp::TOlapColumnDescription& col, const std::shared_ptr& cache) { + const ui32 id = col.GetId(); + const TString& name = cache->GetStringCache(col.GetName()); + const bool notNull = col.HasNotNull() ? col.GetNotNull() : false; + auto typeInfoMod = NScheme::TypeInfoModFromProtoColumnType(col.GetTypeId(), col.HasTypeInfo() ? &col.GetTypeInfo() : nullptr); + return NTable::TColumn(name, id, typeInfoMod.TypeInfo, cache->GetStringCache(typeInfoMod.TypeMod), notNull); + } + + TIndexInfo(const TIndexInfo& original, const TSchemaDiffView& diff, const std::shared_ptr& operators, + const std::shared_ptr& cache); + + void DeserializeOptionsFromProto(const NKikimrSchemeOp::TColumnTableSchemeOptions& optionsProto); + bool DeserializeDefaultCompressionFromProto(const NKikimrSchemeOp::TCompressionOptions& compressionProto); + TConclusion> CreateColumnFeatures(const NTable::TColumn& col, + const NKikimrSchemeOp::TOlapColumnDescription& colProto, const std::shared_ptr& operators, + const std::shared_ptr& cache) const; + + void Validate() const; + void Precalculate(); + void BuildColumnIndexByName(); + bool DeserializeFromProto(const NKikimrSchemeOp::TColumnTableSchema& schema, const std::shared_ptr& operators, const std::shared_ptr& cache); void InitializeCaches(const std::shared_ptr& operators, const THashMap& columns, const std::shared_ptr& cache, const bool withColumnFeatures = true); std::shared_ptr BuildDefaultColumnFeatures( const ui32 columnId, const THashMap& columns, const std::shared_ptr& operators) const; + std::shared_ptr BuildDefaultColumnFeatures( + const NTable::TColumn& column, const std::shared_ptr& operators) const; + + const TString& GetIndexStorageId(const ui32 indexId) const { + auto it = Indexes.find(indexId); + AFL_VERIFY(it != Indexes.end()); + return it->second->GetStorageId(); + } + + const TString& GetColumnStorageId(const ui32 columnId, const TString& specialTier) const { + if (specialTier && specialTier != IStoragesManager::DefaultStorageId) { + return specialTier; + } else { + return GetColumnFeaturesVerified(columnId).GetOperator()->GetStorageId(); + } + } + + const TString& GetEntityStorageId(const ui32 entityId, const TString& specialTier) const { + auto it = Indexes.find(entityId); + if (it != Indexes.end()) { + return it->second->GetStorageId(); + } + return GetColumnStorageId(entityId, specialTier); + } + + void SetAllKeys(const std::shared_ptr& operators, const THashMap& columns); + + bool CompareColumnIdxByName(const ui32 lhs, const ui32 rhs) const { + AFL_VERIFY(lhs < ColumnFeatures.size()); + AFL_VERIFY(rhs < ColumnFeatures.size()); + return ColumnFeatures[lhs]->GetColumnName() < ColumnFeatures[rhs]->GetColumnName(); + } public: + NSplitter::TEntityGroups GetEntityGroupsByStorageId(const TString& specialTier, const IStoragesManager& storages) const; std::optional GetPKColumnIndexByIndexVerified(const ui32 columnIndex) const { AFL_VERIFY(columnIndex < ColumnFeatures.size()); return ColumnFeatures[columnIndex]->GetPKColumnIndex(); } - std::shared_ptr GetCompactionPlannerConstructor() const; + static std::vector> MakeArrowFields( + const NTable::TScheme::TTableSchema::TColumns& columns, const std::vector& ids, const std::shared_ptr& cache); + + const std::shared_ptr& GetCompactionPlannerConstructor() const; + const std::shared_ptr& GetMetadataManagerConstructor() const { + AFL_VERIFY(MetadataManagerConstructor); + return MetadataManagerConstructor; + } bool IsNullableVerifiedByIndex(const ui32 colIndex) const { AFL_VERIFY(colIndex < ColumnFeatures.size()); return ColumnFeatures[colIndex]->GetIsNullable(); @@ -183,9 +175,8 @@ struct TIndexInfo: public IIndexInfo { std::shared_ptr GetColumnExternalDefaultValueVerified(const ui32 colId) const; std::shared_ptr GetColumnExternalDefaultValueByIndexVerified(const ui32 colIndex) const; - - bool GetExternalGuaranteeExclusivePK() const { - return ExternalGuaranteeExclusivePK; + const std::optional& GetScanReaderPolicyName() const { + return ScanReaderPolicyName; } const TColumnFeatures& GetColumnFeaturesVerified(const ui32 columnId) const { @@ -200,55 +191,26 @@ struct TIndexInfo: public IIndexInfo { } } - NSplitter::TEntityGroups GetEntityGroupsByStorageId(const TString& specialTier, const IStoragesManager& storages) const; - bool GetSchemeNeedActualization() const { return SchemeNeedActualization; } std::set GetUsedStorageIds(const TString& portionTierName) const { - std::set result; if (portionTierName && portionTierName != IStoragesManager::DefaultStorageId) { - result.emplace(portionTierName); + return { portionTierName }; } else { - for (auto&& i : ColumnFeatures) { - result.emplace(i->GetOperator()->GetStorageId()); - } + return *UsedStorageIds; } - return result; } const THashMap& GetIndexes() const { return Indexes; } - const TString& GetIndexStorageId(const ui32 indexId) const { - auto it = Indexes.find(indexId); - AFL_VERIFY(it != Indexes.end()); - return it->second->GetStorageId(); - } - - const TString& GetColumnStorageId(const ui32 columnId, const TString& specialTier) const { - if (specialTier && specialTier != IStoragesManager::DefaultStorageId) { - return specialTier; - } else { - return GetColumnFeaturesVerified(columnId).GetOperator()->GetStorageId(); - } - } - - const TString& GetEntityStorageId(const ui32 entityId, const TString& specialTier) const { - auto it = Indexes.find(entityId); - if (it != Indexes.end()) { - return it->second->GetStorageId(); - } - return GetColumnStorageId(entityId, specialTier); - } - TString DebugString() const { TStringBuilder sb; sb << "(" << "version=" << Version << ";" - << "name=" << Name << ";" << ")"; for (auto&& i : ColumnFeatures) { sb << i->GetColumnName() << ":" << i->DebugString() << ";"; @@ -256,23 +218,13 @@ struct TIndexInfo: public IIndexInfo { return sb; } - void SetAllKeys(const std::shared_ptr& operators, const THashMap& columns); - -public: - static TIndexInfo BuildDefault() { - TIndexInfo result("dummy"); - return result; - } + static TIndexInfo BuildDefault(); - static TIndexInfo BuildDefault( - const std::shared_ptr& operators, const TColumns& columns, const std::vector& pkNames) { + static TIndexInfo BuildDefault(const std::shared_ptr& operators, const TColumns& columns, const std::vector& pkIds) { TIndexInfo result = BuildDefault(); - result.ColumnNames = TNameInfo::BuildColumnNames(columns); - for (auto&& i : pkNames) { - const ui32 columnId = result.GetColumnIdVerified(i); - result.PKColumnIds.emplace_back(columnId); - } + result.PKColumnIds = pkIds; result.SetAllKeys(operators, columns); + result.Validate(); return result; } @@ -283,6 +235,8 @@ struct TIndexInfo: public IIndexInfo { static std::optional BuildFromProto(const NKikimrSchemeOp::TColumnTableSchema& schema, const std::shared_ptr& operators, const std::shared_ptr& cache); + static std::optional BuildFromProto(const NKikimrSchemeOp::TColumnTableSchemaDiff& schema, const TIndexInfo& prevSchema, + const std::shared_ptr& operators, const std::shared_ptr& cache); bool HasColumnId(const ui32 columnId) const { return !!GetColumnIndexOptional(columnId); @@ -296,6 +250,15 @@ struct TIndexInfo: public IIndexInfo { return Indexes.contains(indexId); } + bool HasIndexes(const std::set& indexIds) const { + for (auto&& i : indexIds) { + if (!Indexes.contains(i)) { + return false; + } + } + return true; + } + std::optional GetColumnIndexOptional(const ui32 id) const; ui32 GetColumnIndexVerified(const ui32 id) const { auto result = GetColumnIndexOptional(id); @@ -307,7 +270,7 @@ struct TIndexInfo: public IIndexInfo { std::shared_ptr GetColumnSchema(const ui32 columnId) const; std::shared_ptr GetColumnsSchema(const std::set& columnIds) const; TColumnSaver GetColumnSaver(const ui32 columnId) const; - virtual std::shared_ptr GetColumnLoaderOptional(const ui32 columnId) const override; + virtual const std::shared_ptr& GetColumnLoaderOptional(const ui32 columnId) const override; std::optional GetColumnNameOptional(const ui32 columnId) const { auto f = GetColumnFieldOptional(columnId); if (!f) { @@ -350,11 +313,11 @@ struct TIndexInfo: public IIndexInfo { }; [[nodiscard]] TConclusion AppendIndexes(const THashMap>>& primaryData, - const std::shared_ptr& operators) const { + const std::shared_ptr& operators, const ui32 recordsCount) const { TSecondaryData result; result.MutableExternalData() = primaryData; for (auto&& i : Indexes) { - auto conclusion = AppendIndex(primaryData, i.first, operators, result); + auto conclusion = AppendIndex(primaryData, i.first, operators, recordsCount, result); if (conclusion.IsFail()) { return conclusion; } @@ -366,7 +329,7 @@ struct TIndexInfo: public IIndexInfo { std::shared_ptr GetIndexMetaCountMinSketch(const std::set& columnIds) const; [[nodiscard]] TConclusionStatus AppendIndex(const THashMap>>& originalData, - const ui32 indexId, const std::shared_ptr& operators, TSecondaryData& result) const; + const ui32 indexId, const std::shared_ptr& operators, const ui32 recordsCount, TSecondaryData& result) const; /// Returns an id of the column located by name. The name should exists in the schema. ui32 GetColumnIdVerified(const std::string& name) const; @@ -385,8 +348,8 @@ struct TIndexInfo: public IIndexInfo { /// Returns names of columns defined by the specific ids. std::vector GetColumnNames(const std::vector& ids) const; - std::vector GetColumnSTLNames(const std::vector& ids) const; - const std::vector& GetColumnIds(const bool withSpecial = true) const; + std::vector GetColumnSTLNames(const bool withSpecial = true) const; + TColumnIdsView GetColumnIds(const bool withSpecial = true) const; ui32 GetColumnIdByIndexVerified(const ui32 index) const { AFL_VERIFY(index < SchemaColumnIdsWithSpecials.size()); return SchemaColumnIdsWithSpecials[index]; @@ -408,23 +371,18 @@ struct TIndexInfo: public IIndexInfo { return PKColumnIds[0]; } - const std::shared_ptr& GetReplaceKey() const { return PrimaryKey; } - const std::shared_ptr& GetPrimaryKey() const { return PrimaryKey; } - - void CheckTtlColumn(const TString& ttlColumn) const { - Y_ABORT_UNLESS(!ttlColumn.empty()); - Y_ABORT_UNLESS(MinMaxIdxColumnsIds.contains(GetColumnIdVerified(ttlColumn))); + const std::shared_ptr& GetReplaceKey() const { + return PrimaryKey; + } + const std::shared_ptr& GetPrimaryKey() const { + return PrimaryKey; } std::vector GetColumnIds(const std::vector& columnNames) const; - const std::shared_ptr& ArrowSchema() const; + NArrow::TSchemaLiteView ArrowSchema() const; const std::shared_ptr& ArrowSchemaWithSpecials() const; - const THashSet& GetMinMaxIdxColumns() const { - return MinMaxIdxColumnsIds; - } - bool AllowTtlOverColumn(const TString& name) const; /// Returns whether the sorting keys defined. @@ -443,23 +401,10 @@ struct TIndexInfo: public IIndexInfo { NArrow::NSerialization::TSerializerContainer GetDefaultSerializer() const { return DefaultSerializer; } - -private: - ui64 Version = 0; - TString Name; - std::vector SchemaColumnIds; - std::vector SchemaColumnIdsWithSpecials; - std::shared_ptr SchemaWithSpecials; - std::shared_ptr Schema; - std::shared_ptr PrimaryKey; - THashSet MinMaxIdxColumnsIds; - NArrow::NSerialization::TSerializerContainer DefaultSerializer = NArrow::NSerialization::TSerializerContainer::GetDefaultSerializer(); }; std::shared_ptr MakeArrowSchema(const NTable::TScheme::TTableSchema::TColumns& columns, const std::vector& ids, const std::shared_ptr& cache = nullptr); -std::vector> MakeArrowFields(const NTable::TScheme::TTableSchema::TColumns& columns, const std::vector& ids, - const std::shared_ptr& cache = nullptr); /// Extracts columns with the specific ids from the schema. std::vector GetColumns(const NTable::TScheme::TTableSchema& tableSchema, const std::vector& ids); diff --git a/ydb/core/tx/columnshard/engines/scheme/indexes/abstract/meta.h b/ydb/core/tx/columnshard/engines/scheme/indexes/abstract/meta.h index d5185cbca236..55c25ace4869 100644 --- a/ydb/core/tx/columnshard/engines/scheme/indexes/abstract/meta.h +++ b/ydb/core/tx/columnshard/engines/scheme/indexes/abstract/meta.h @@ -31,7 +31,8 @@ class IIndexMeta { YDB_READONLY(ui32, IndexId, 0); YDB_READONLY(TString, StorageId, IStoragesManager::DefaultStorageId); protected: - virtual std::shared_ptr DoBuildIndex(const THashMap>>& data, const TIndexInfo& indexInfo) const = 0; + virtual std::shared_ptr DoBuildIndex(const THashMap>>& data, + const ui32 recordsCount, const TIndexInfo& indexInfo) const = 0; virtual void DoFillIndexCheckers(const std::shared_ptr& info, const NSchemeShard::TOlapSchema& schema) const = 0; virtual bool DoDeserializeFromProto(const NKikimrSchemeOp::TOlapIndexDescription& proto) = 0; virtual void DoSerializeToProto(NKikimrSchemeOp::TOlapIndexDescription& proto) const = 0; @@ -67,8 +68,8 @@ class IIndexMeta { virtual ~IIndexMeta() = default; - std::shared_ptr BuildIndex(const THashMap>>& data, const TIndexInfo& indexInfo) const { - return DoBuildIndex(data, indexInfo); + std::shared_ptr BuildIndex(const THashMap>>& data, const ui32 recordsCount, const TIndexInfo& indexInfo) const { + return DoBuildIndex(data, recordsCount, indexInfo); } void FillIndexCheckers(const std::shared_ptr& info, const NSchemeShard::TOlapSchema& schema) const { diff --git a/ydb/core/tx/columnshard/engines/scheme/indexes/abstract/program.cpp b/ydb/core/tx/columnshard/engines/scheme/indexes/abstract/program.cpp index d6a3e9b800e5..aa2b40c2a658 100644 --- a/ydb/core/tx/columnshard/engines/scheme/indexes/abstract/program.cpp +++ b/ydb/core/tx/columnshard/engines/scheme/indexes/abstract/program.cpp @@ -235,8 +235,10 @@ class TOriginalColumn: public IRequestNode { class TPackAnd: public IRequestNode { private: using TBase = IRequestNode; - THashMap> Conditions; + THashMap> Equals; + THashMap Likes; bool IsEmptyFlag = false; + protected: virtual bool DoCollapse() override { return false; @@ -247,10 +249,19 @@ class TPackAnd: public IRequestNode { if (IsEmptyFlag) { result.InsertValue("empty", true); } - auto& arrJson = result.InsertValue("conditions", NJson::JSON_ARRAY); - for (auto&& i : Conditions) { - auto& jsonCondition = arrJson.AppendValue(NJson::JSON_MAP); - jsonCondition.InsertValue(i.first, i.second->ToString()); + { + auto& arrJson = result.InsertValue("equals", NJson::JSON_ARRAY); + for (auto&& i : Equals) { + auto& jsonCondition = arrJson.AppendValue(NJson::JSON_MAP); + jsonCondition.InsertValue(i.first, i.second->ToString()); + } + } + { + auto& arrJson = result.InsertValue("likes", NJson::JSON_ARRAY); + for (auto&& i : Likes) { + auto& jsonCondition = arrJson.AppendValue(NJson::JSON_MAP); + jsonCondition.InsertValue(i.first, i.second.ToString()); + } } return result; } @@ -259,32 +270,53 @@ class TPackAnd: public IRequestNode { } public: TPackAnd(const TPackAnd&) = default; + TPackAnd(const TString& cName, const std::shared_ptr& value) : TBase(GetNextId("PackAnd")) { - AddCondition(cName, value); + AddEqual(cName, value); + } + + TPackAnd(const TString& cName, const TLikePart& part) + : TBase(GetNextId("PackAnd")) { + AddLike(cName, TLikeDescription(part)); } const THashMap>& GetEquals() const { - return Conditions; + return Equals; + } + + const THashMap& GetLikes() const { + return Likes; } bool IsEmpty() const { return IsEmptyFlag; } - void AddCondition(const TString& cName, const std::shared_ptr& value) { + void AddEqual(const TString& cName, const std::shared_ptr& value) { AFL_VERIFY(value); - auto it = Conditions.find(cName); - if (it == Conditions.end()) { - Conditions.emplace(cName, value); + auto it = Equals.find(cName); + if (it == Equals.end()) { + Equals.emplace(cName, value); } else if (it->second->Equals(*value)) { return; } else { IsEmptyFlag = true; } } + void AddLike(const TString& cName, const TLikeDescription& value) { + auto it = Likes.find(cName); + if (it == Likes.end()) { + Likes.emplace(cName, value); + } else { + it->second.Merge(value); + } + } void Merge(const TPackAnd& add) { - for (auto&& i : add.Conditions) { - AddCondition(i.first, i.second); + for (auto&& i : add.Equals) { + AddEqual(i.first, i.second); + } + for (auto&& i : add.Likes) { + AddLike(i.first, i.second); } } }; @@ -313,6 +345,26 @@ class TOperationNode: public IRequestNode { Parent->Exchange(GetNodeName(), std::make_shared(Children[0]->As()->GetColumnName(), Children[1]->As()->GetConstant())); return true; } + const bool isLike = (Operation == NYql::TKernelRequestBuilder::EBinaryOp::StringContains || + Operation == NYql::TKernelRequestBuilder::EBinaryOp::StartsWith || + Operation == NYql::TKernelRequestBuilder::EBinaryOp::EndsWith); + if (isLike && Children.size() == 2 && Children[1]->Is() && Children[0]->Is()) { + auto scalar = Children[1]->As()->GetConstant(); + AFL_VERIFY(scalar->type->id() == arrow::binary()->id()); + auto scalarString = static_pointer_cast(scalar); + std::optional op; + if (Operation == NYql::TKernelRequestBuilder::EBinaryOp::StringContains) { + op = TLikePart::EOperation::Contains; + } else if (Operation == NYql::TKernelRequestBuilder::EBinaryOp::EndsWith) { + op = TLikePart::EOperation::EndsWith; + } else if (Operation == NYql::TKernelRequestBuilder::EBinaryOp::StartsWith) { + op = TLikePart::EOperation::StartsWith; + } + AFL_VERIFY(op); + TLikePart likePart(*op, TString((const char*)scalarString->value->data(), scalarString->value->size())); + Parent->Exchange(GetNodeName(), std::make_shared(Children[0]->As()->GetColumnName(), likePart)); + return true; + } if (Operation == NYql::TKernelRequestBuilder::EBinaryOp::And) { if (Parent->Is() && Parent->As()->Operation == NYql::TKernelRequestBuilder::EBinaryOp::And) { Parent->Attach(Children); @@ -407,7 +459,7 @@ class TNormalForm { if (arg.IsGenerated()) { auto it = Nodes.find(arg.GetColumnName()); if (it == Nodes.end()) { - AFL_CRIT(NKikimrServices::TX_COLUMNSHARD)("event", "program_arg_is_missing")("program", program.DebugString()); + AFL_CRIT(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "program_arg_is_missing")("program", program.DebugString()); return false; } argNodes.emplace_back(it->second); @@ -442,8 +494,13 @@ class TNormalForm { }; std::shared_ptr TDataForIndexesCheckers::Build(const TProgramContainer& program) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("program", program.DebugString()); - auto fStep = program.GetSteps().front(); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("program", program.DebugString()); + auto& steps = program.GetStepsVerified(); + if (!steps.size()) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_SCAN)("event", "no_steps_in_program"); + return nullptr; + } + auto fStep = steps.front(); TNormalForm nForm; for (auto&& s : fStep->GetAssignes()) { if (!nForm.Add(s, program)) { @@ -454,9 +511,10 @@ std::shared_ptr TDataForIndexesCheckers::Build(const TP if (!rootNode) { return nullptr; } + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("original_program", rootNode->SerializeToJson()); while (rootNode->Collapse()) { } - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("collapsed_program", rootNode->SerializeToJson()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("collapsed_program", rootNode->SerializeToJson()); if (rootNode->GetChildren().size() != 1) { return nullptr; } @@ -465,14 +523,14 @@ std::shared_ptr TDataForIndexesCheckers::Build(const TP if (orNode->GetOperation() == NYql::TKernelRequestBuilder::EBinaryOp::Or) { for (auto&& i : orNode->GetChildren()) { if (auto* andPackNode = i->As()) { - result->AddBranch(andPackNode->GetEquals()); + result->AddBranch(andPackNode->GetEquals(), andPackNode->GetLikes()); } else if (auto* operationNode = i->As()) { if (operationNode->GetOperation() == NYql::TKernelRequestBuilder::EBinaryOp::And) { TPackAnd* pack = operationNode->FindFirst(); if (!pack) { return nullptr; } - result->AddBranch(pack->GetEquals()); + result->AddBranch(pack->GetEquals(), pack->GetLikes()); } } else { return nullptr; @@ -480,7 +538,7 @@ std::shared_ptr TDataForIndexesCheckers::Build(const TP } } } else if (auto* andPackNode = rootNode->GetChildren().front()->As()) { - result->AddBranch(andPackNode->GetEquals()); + result->AddBranch(andPackNode->GetEquals(), andPackNode->GetLikes()); } else { return nullptr; } diff --git a/ydb/core/tx/columnshard/engines/scheme/indexes/abstract/program.h b/ydb/core/tx/columnshard/engines/scheme/indexes/abstract/program.h index 898c4210b035..eb2d6efca9ac 100644 --- a/ydb/core/tx/columnshard/engines/scheme/indexes/abstract/program.h +++ b/ydb/core/tx/columnshard/engines/scheme/indexes/abstract/program.h @@ -3,30 +3,109 @@ namespace NKikimr::NOlap::NIndexes::NRequest { +class TLikePart { +public: + enum class EOperation { + StartsWith, + EndsWith, + Contains + }; + +private: + YDB_READONLY(EOperation, Operation, EOperation::Contains); + YDB_READONLY_DEF(TString, Value); + +public: + TLikePart(const EOperation op, const TString& value) + : Operation(op) + , Value(value) { + } + + static TLikePart MakeStart(const TString& value) { + return TLikePart(EOperation::StartsWith, value); + } + static TLikePart MakeEnd(const TString& value) { + return TLikePart(EOperation::EndsWith, value); + } + static TLikePart MakeContains(const TString& value) { + return TLikePart(EOperation::Contains, value); + } + + TString ToString() const { + if (Operation == EOperation::StartsWith) { + return '%' + Value; + } + if (Operation == EOperation::EndsWith) { + return Value + '%'; + } + if (Operation == EOperation::Contains) { + return Value; + } + AFL_VERIFY(false); + return ""; + } +}; + +class TLikeDescription { +private: + THashMap LikeSequences; + +public: + TLikeDescription(const TLikePart& likePart) { + LikeSequences.emplace(likePart.ToString(), likePart); + } + + const THashMap& GetLikeSequences() const { + return LikeSequences; + } + + void Merge(const TLikeDescription& d) { + for (auto&& i : d.LikeSequences) { + LikeSequences.emplace(i.first, i.second); + } + } + + TString ToString() const { + TStringBuilder sb; + sb << "["; + for (auto&& i : LikeSequences) { + sb << i.first << ","; + } + sb << "];"; + return sb; + } +}; + class TBranchCoverage { private: THashMap> Equals; + THashMap Likes; YDB_ACCESSOR_DEF(std::vector>, Indexes); + public: - TBranchCoverage(const THashMap>& equals) + TBranchCoverage(const THashMap>& equals, const THashMap& likes) : Equals(equals) - { - + , Likes(likes) { } const THashMap>& GetEquals() const { return Equals; } + const THashMap& GetLikes() const { + return Likes; + } + std::shared_ptr GetAndChecker() const; }; class TDataForIndexesCheckers { private: YDB_READONLY_DEF(std::vector>, Branches); + public: - void AddBranch(const THashMap>& equalsData) { - Branches.emplace_back(std::make_shared(equalsData)); + void AddBranch(const THashMap>& equalsData, const THashMap& likesData) { + Branches.emplace_back(std::make_shared(equalsData, likesData)); } static std::shared_ptr Build(const TProgramContainer& program); @@ -34,4 +113,4 @@ class TDataForIndexesCheckers { TIndexCheckerContainer GetCoverChecker() const; }; -} // namespace NKikimr::NOlap::NIndexes::NRequest \ No newline at end of file +} // namespace NKikimr::NOlap::NIndexes::NRequest diff --git a/ydb/core/tx/columnshard/engines/scheme/objects_cache.cpp b/ydb/core/tx/columnshard/engines/scheme/objects_cache.cpp new file mode 100644 index 000000000000..8b8d2f44b022 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/scheme/objects_cache.cpp @@ -0,0 +1,12 @@ +#include "objects_cache.h" + +#include + +namespace NKikimr::NOlap { + +TSchemaObjectsCache::TSchemasCache::TEntryGuard TSchemaObjectsCache::UpsertIndexInfo(const ui64 presetId, TIndexInfo&& indexInfo) { + const TSchemaVersionId versionId(presetId, indexInfo.GetVersion()); + return SchemasByVersion.Upsert(versionId, std::move(indexInfo)); +} + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/scheme/objects_cache.h b/ydb/core/tx/columnshard/engines/scheme/objects_cache.h new file mode 100644 index 000000000000..cf7dd7793eb6 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/scheme/objects_cache.h @@ -0,0 +1,103 @@ +#pragma once +#include "column_features.h" + +#include +#include + +#include +#include +#include + +namespace NKikimr::NOlap { + +class TSchemaObjectsCache { +private: + THashMap> Fields; + mutable ui64 AcceptionFieldsCount = 0; + mutable TMutex FieldsMutex; + + THashMap> ColumnFeatures; + mutable ui64 AcceptionFeaturesCount = 0; + mutable TMutex FeaturesMutex; + + using TSchemasCache = TObjectCache; + TSchemasCache SchemasByVersion; + + THashSet StringsCache; + mutable TMutex StringsMutex; + +public: + const TString& GetStringCache(const TString& original) { + TGuard lock(StringsMutex); + auto it = StringsCache.find(original); + if (it == StringsCache.end()) { + it = StringsCache.emplace(original).first; + } + return *it; + } + + std::shared_ptr GetOrInsertField(const std::shared_ptr& f) { + TGuard lock(FieldsMutex); + const TString fingerprint = f->ToString(true); + auto it = Fields.find(fingerprint); + if (it == Fields.end()) { + AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "get_field_miss")("fp", fingerprint)("count", Fields.size())( + "acc", AcceptionFieldsCount); + it = Fields.emplace(fingerprint, f).first; + } + if (++AcceptionFieldsCount % 1000 == 0) { + AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "get_field_accept")("fp", fingerprint)("count", Fields.size())( + "acc", AcceptionFieldsCount); + } + return it->second; + } + + template + TConclusion> GetOrCreateColumnFeatures(const TString& fingerprint, const TConstructor& constructor) { + TGuard lock(FeaturesMutex); + auto it = ColumnFeatures.find(fingerprint); + if (it == ColumnFeatures.end()) { + AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "get_column_features_miss")("fp", UrlEscapeRet(fingerprint))( + "count", ColumnFeatures.size())("acc", AcceptionFeaturesCount); + TConclusion> resultConclusion = constructor(); + if (resultConclusion.IsFail()) { + return resultConclusion; + } + it = ColumnFeatures.emplace(fingerprint, resultConclusion.DetachResult()).first; + AFL_VERIFY(it->second); + } else { + if (++AcceptionFeaturesCount % 1000 == 0) { + AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "get_column_features_accept")("fp", UrlEscapeRet(fingerprint))( + "count", ColumnFeatures.size())("acc", AcceptionFeaturesCount); + } + } + return it->second; + } + + TSchemasCache::TEntryGuard UpsertIndexInfo(const ui64 presetId, TIndexInfo&& indexInfo); +}; + +class TSchemaCachesManager { +private: + THashMap> CacheByTableOwner; + TMutex Mutex; + + std::shared_ptr GetCacheImpl(const ui64 ownerPathId) { + if (!ownerPathId) { + return std::make_shared(); + } + TGuard lock(Mutex); + auto findCache = CacheByTableOwner.FindPtr(ownerPathId); + if (findCache) { + return *findCache; + } + return CacheByTableOwner.emplace(ownerPathId, std::make_shared()).first->second; + } + +public: + static std::shared_ptr GetCache(const ui64 ownerPathId) { + return Singleton()->GetCacheImpl(ownerPathId); + } +}; + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/scheme/schema_diff.cpp b/ydb/core/tx/columnshard/engines/scheme/schema_diff.cpp new file mode 100644 index 000000000000..43d2e48e257a --- /dev/null +++ b/ydb/core/tx/columnshard/engines/scheme/schema_diff.cpp @@ -0,0 +1,116 @@ +#include "schema_diff.h" +#include + +namespace NKikimr::NOlap { +NKikimrSchemeOp::TColumnTableSchemaDiff TSchemaDiffView::MakeSchemasDiff( + const NKikimrSchemeOp::TColumnTableSchema& current, const NKikimrSchemeOp::TColumnTableSchema& next) { + NKikimrSchemeOp::TColumnTableSchemaDiff result; + result.SetVersion(next.GetVersion()); + *result.MutableDefaultCompression() = next.GetDefaultCompression(); + *result.MutableOptions() = next.GetOptions(); + + { + THashMap nextIds; + for (auto&& i : next.GetColumns()) { + AFL_VERIFY(nextIds.emplace(i.GetId(), i).second); + } + THashSet currentIds; + for (auto&& i : current.GetColumns()) { + auto it = nextIds.find(i.GetId()); + if (it == nextIds.end()) { + result.AddDropColumns(i.GetId()); + } else if (it->second.SerializeAsString() != i.SerializeAsString()) { + *result.AddUpsertColumns() = it->second; + } + currentIds.emplace(i.GetId()); + } + for (auto&& i : next.GetColumns()) { + if (currentIds.contains(i.GetId())) { + continue; + } + *result.AddUpsertColumns() = i; + } + } + { + THashMap nextIds; + for (auto&& i : next.GetIndexes()) { + AFL_VERIFY(nextIds.emplace(i.GetId(), i).second); + } + THashSet currentIds; + for (auto&& i : current.GetIndexes()) { + auto it = nextIds.find(i.GetId()); + if (it == nextIds.end()) { + result.AddDropIndexes(i.GetId()); + } else if (it->second.SerializeAsString() != i.SerializeAsString()) { + *result.AddUpsertIndexes() = it->second; + } + currentIds.emplace(i.GetId()); + } + for (auto&& i : next.GetIndexes()) { + if (currentIds.contains(i.GetId())) { + continue; + } + *result.AddUpsertIndexes() = i; + } + } + return result; +} + +TConclusionStatus TSchemaDiffView::DeserializeFromProto(const NKikimrSchemeOp::TColumnTableSchemaDiff& proto) { + SchemaOptions = &proto.GetOptions(); + Version = proto.GetVersion(); + if (proto.HasDefaultCompression()) { + CompressionOptions = &proto.GetDefaultCompression(); + } + for (auto&& i : proto.GetUpsertColumns()) { + AFL_VERIFY(ModifiedColumns.emplace(i.GetId(), &i).second); + } + for (auto&& i : proto.GetDropColumns()) { + AFL_VERIFY(ModifiedColumns.emplace(i, nullptr).second); + } + for (auto&& i : proto.GetUpsertIndexes()) { + AFL_VERIFY(ModifiedIndexes.emplace(i.GetId(), &i).second); + } + for (auto&& i : proto.GetDropIndexes()) { + AFL_VERIFY(ModifiedIndexes.emplace(i, nullptr).second); + } + return TConclusionStatus::Success(); +} + +ui64 TSchemaDiffView::GetVersion() const { + AFL_VERIFY(Version); + return Version; +} + +void TSchemaDiffView::ApplyForColumns(const std::vector& originalColumnIds, + const std::function& addFromOriginal, + const std::function originalIndex)>& addFromDiff) const { + auto it = ModifiedColumns.begin(); + ui32 i = 0; + while (i < originalColumnIds.size() || it != ModifiedColumns.end()) { + AFL_VERIFY(i != originalColumnIds.size()); + const ui32 originalColId = originalColumnIds[i]; + + if (it == ModifiedColumns.end() || originalColId < it->first) { + addFromOriginal(i); + ++i; + } else if (it->first == originalColId) { + if (it->second) { + addFromDiff(*it->second, i); + } + ++it; + ++i; + } else if (it->first < originalColId) { + AFL_VERIFY(it->second); + addFromDiff(*it->second, std::nullopt); + ++it; + } + } +} + +const NKikimrSchemeOp::TColumnTableSchemeOptions& TSchemaDiffView::GetSchemaOptions() const { + AFL_VERIFY(SchemaOptions); + return *SchemaOptions; +} + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/scheme/schema_diff.h b/ydb/core/tx/columnshard/engines/scheme/schema_diff.h new file mode 100644 index 000000000000..048a8ab915b0 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/scheme/schema_diff.h @@ -0,0 +1,41 @@ +#pragma once +#include +#include + +namespace NKikimr::NOlap { + +class TSchemaDiffView { +private: + ui64 Version = 0; + const NKikimrSchemeOp::TColumnTableSchemeOptions* SchemaOptions = nullptr; + const NKikimrSchemeOp::TCompressionOptions* CompressionOptions = nullptr; + std::map ModifiedColumns; + std::map ModifiedIndexes; + +public: + TSchemaDiffView() = default; + + void ApplyForColumns(const std::vector& originalColumnIds, + const std::function& addFromOriginal, + const std::function originalIndex)>& addFromDiff) const; + + static NKikimrSchemeOp::TColumnTableSchemaDiff MakeSchemasDiff( + const NKikimrSchemeOp::TColumnTableSchema& current, const NKikimrSchemeOp::TColumnTableSchema& next); + + const NKikimrSchemeOp::TColumnTableSchemeOptions& GetSchemaOptions() const; + const NKikimrSchemeOp::TCompressionOptions* GetCompressionOptions() const { + return CompressionOptions; + } + const std::map& GetModifiedColumns() const { + return ModifiedColumns; + } + const std::map& GetModifiedIndexes() const { + return ModifiedIndexes; + } + + ui64 GetVersion() const;; + + TConclusionStatus DeserializeFromProto(const NKikimrSchemeOp::TColumnTableSchemaDiff& proto); +}; + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/portion_info.cpp b/ydb/core/tx/columnshard/engines/scheme/schema_version.cpp similarity index 51% rename from ydb/core/tx/columnshard/engines/portion_info.cpp rename to ydb/core/tx/columnshard/engines/scheme/schema_version.cpp index 9b11963e99be..051a743efc13 100644 --- a/ydb/core/tx/columnshard/engines/portion_info.cpp +++ b/ydb/core/tx/columnshard/engines/scheme/schema_version.cpp @@ -1,5 +1,4 @@ -#include "portion_info.h" +#include "schema_version.h" namespace NKikimr::NOlap { - } diff --git a/ydb/core/tx/columnshard/engines/scheme/schema_version.h b/ydb/core/tx/columnshard/engines/scheme/schema_version.h new file mode 100644 index 000000000000..e52645a26ba0 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/scheme/schema_version.h @@ -0,0 +1,46 @@ +#pragma once +#include +#include + +#include + +namespace NKikimr::NOlap { +class TSchemaPresetVersionInfo { +private: + NKikimrTxColumnShard::TSchemaPresetVersionInfo Proto; + +public: + TSchemaPresetVersionInfo(const NKikimrTxColumnShard::TSchemaPresetVersionInfo& proto) + : Proto(proto) { + } + + const NKikimrTxColumnShard::TSchemaPresetVersionInfo& GetProto() const { + return Proto; + } + + auto operator<=>(const TSchemaPresetVersionInfo& rhs) const { + return std::tuple(Proto.GetId(), Proto.GetSinceStep(), Proto.GetSinceTxId()) <=> std::tuple(rhs.Proto.GetId(), rhs.Proto.GetSinceStep(), rhs.Proto.GetSinceTxId()); + } + + void SaveToLocalDb(NIceDb::TNiceDb& db) { + using namespace NKikimr::NColumnShard; + db.Table().Key(Proto.GetId(), Proto.GetSinceStep(), Proto.GetSinceTxId()).Update(Proto.SerializeAsString()); + } + + TSnapshot GetSnapshot() const { + return TSnapshot(Proto.GetSinceStep(), Proto.GetSinceTxId()); + } + + NOlap::IColumnEngine::TSchemaInitializationData GetSchema() const { + return NOlap::IColumnEngine::TSchemaInitializationData(Proto); + } + + ui64 ColumnsSize() const { + if (Proto.HasSchema()) { + return Proto.GetSchema().ColumnsSize(); + } + AFL_VERIFY(Proto.HasDiff()); + return Proto.GetDiff().UpsertColumnsSize() + Proto.GetDiff().UpsertIndexesSize(); + } +}; +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/scheme/tiering/tier_info.cpp b/ydb/core/tx/columnshard/engines/scheme/tiering/tier_info.cpp index 659062338bb0..a36e02595d38 100644 --- a/ydb/core/tx/columnshard/engines/scheme/tiering/tier_info.cpp +++ b/ydb/core/tx/columnshard/engines/scheme/tiering/tier_info.cpp @@ -19,12 +19,15 @@ std::optional TTierInfo::ScalarToInstant(const std::shared_ptr& max, const TInstant now) const { +TTiering::TTieringContext TTiering::GetTierToMove(const std::shared_ptr& max, const TInstant now, const bool skipEviction) const { AFL_VERIFY(OrderedTiers.size()); std::optional nextTierName; std::optional nextTierDuration; for (auto& tierRef : GetOrderedTiers()) { auto& tierInfo = tierRef.Get(); + if (skipEviction && tierInfo.GetName() != NTiering::NCommon::DeleteTierName) { + continue; + } auto mpiOpt = tierInfo.ScalarToInstant(max); Y_ABORT_UNLESS(mpiOpt); const TInstant maxTieringPortionInstant = *mpiOpt; diff --git a/ydb/core/tx/columnshard/engines/scheme/tiering/tier_info.h b/ydb/core/tx/columnshard/engines/scheme/tiering/tier_info.h index 8d290a8adcf2..adc32dc954f2 100644 --- a/ydb/core/tx/columnshard/engines/scheme/tiering/tier_info.h +++ b/ydb/core/tx/columnshard/engines/scheme/tiering/tier_info.h @@ -1,13 +1,16 @@ #pragma once #include "common.h" +#include #include -#include #include #include + +#include + #include -#include #include +#include namespace NKikimr::NOlap { @@ -28,8 +31,7 @@ class TTierInfo { : Name(tierName) , EvictColumnName(column) , EvictDuration(evictDuration) - , TtlUnitsInSecond(unitsInSecond) - { + , TtlUnitsInSecond(unitsInSecond) { Y_ABORT_UNLESS(!!Name); Y_ABORT_UNLESS(!!EvictColumnName); } @@ -53,6 +55,21 @@ class TTierInfo { return std::make_shared(NTiering::NCommon::DeleteTierName, evictDuration, ttlColumn, unitsInSecond); } + static ui32 GetUnitsInSecond(const NKikimrSchemeOp::TTTLSettings::EUnit timeUnit) { + switch (timeUnit) { + case NKikimrSchemeOp::TTTLSettings::UNIT_SECONDS: + return 1; + case NKikimrSchemeOp::TTTLSettings::UNIT_MILLISECONDS: + return 1000; + case NKikimrSchemeOp::TTTLSettings::UNIT_MICROSECONDS: + return 1000 * 1000; + case NKikimrSchemeOp::TTTLSettings::UNIT_NANOSECONDS: + return 1000 * 1000 * 1000; + case NKikimrSchemeOp::TTTLSettings::UNIT_AUTO: + return 0; + } + } + TString GetDebugString() const { TStringBuilder sb; sb << "name=" << Name << ";duration=" << EvictDuration << ";column=" << EvictColumnName << ";serializer="; @@ -106,6 +123,7 @@ class TTierRef { }; class TTiering { + using TProto = NKikimrSchemeOp::TColumnDataLifeCycle::TTtl; using TTiersMap = THashMap>; TTiersMap TierByName; TSet OrderedTiers; @@ -149,7 +167,7 @@ class TTiering { } }; - TTieringContext GetTierToMove(const std::shared_ptr& max, const TInstant now) const; + TTieringContext GetTierToMove(const std::shared_ptr& max, const TInstant now, const bool skipEviction) const; const TTiersMap& GetTierByName() const { return TierByName; @@ -199,6 +217,46 @@ class TTiering { return {}; } + TConclusionStatus DeserializeFromProto(const TProto& serialized) { + if (serialized.HasExpireAfterBytes()) { + return TConclusionStatus::Fail("TTL by size is not supported."); + } + if (!serialized.HasColumnName()) { + return TConclusionStatus::Fail("Missing column name in TTL settings"); + } + + const TString ttlColumnName = serialized.GetColumnName(); + const ui32 unitsInSecond = TTierInfo::GetUnitsInSecond(serialized.GetColumnUnit()); + + if (!serialized.TiersSize()) { + // legacy schema + if (!Add(TTierInfo::MakeTtl(TDuration::Seconds(serialized.GetExpireAfterSeconds()), ttlColumnName, unitsInSecond))) { + return TConclusionStatus::Fail("Invalid ttl settings"); + } + } + for (const auto& tier : serialized.GetTiers()) { + if (!tier.HasApplyAfterSeconds()) { + return TConclusionStatus::Fail("Missing eviction delay in tier description"); + } + std::shared_ptr tierInfo; + switch (tier.GetActionCase()) { + case NKikimrSchemeOp::TTTLSettings_TTier::kDelete: + tierInfo = TTierInfo::MakeTtl(TDuration::Seconds(tier.GetApplyAfterSeconds()), ttlColumnName, unitsInSecond); + break; + case NKikimrSchemeOp::TTTLSettings_TTier::kEvictToExternalStorage: + tierInfo = std::make_shared(CanonizePath(tier.GetEvictToExternalStorage().GetStorage()), + TDuration::Seconds(tier.GetApplyAfterSeconds()), ttlColumnName, unitsInSecond); + break; + case NKikimrSchemeOp::TTTLSettings_TTier::ACTION_NOT_SET: + return TConclusionStatus::Fail("No action in tier"); + } + if (!Add(tierInfo)) { + return TConclusionStatus::Fail("Invalid tier settings"); + } + } + return TConclusionStatus::Success(); + } + const TString& GetEvictColumnName() const { AFL_VERIFY(TTLColumnName); return *TTLColumnName; @@ -211,6 +269,26 @@ class TTiering { } return sb; } + + THashSet GetUsedTiers() const { + THashSet tiers; + for (const auto& [name, info] : TierByName) { + if (name != NTiering::NCommon::DeleteTierName) { + tiers.emplace(name); + } + } + return tiers; + } + + static THashSet GetUsedTiers(const TProto& ttlSettings) { + THashSet usedTiers; + for (const auto& tier : ttlSettings.GetTiers()) { + if (tier.HasEvictToExternalStorage()) { + usedTiers.emplace(CanonizePath(tier.GetEvictToExternalStorage().GetStorage())); + } + } + return usedTiers; + } }; } diff --git a/ydb/core/tx/columnshard/engines/scheme/versions/abstract_scheme.cpp b/ydb/core/tx/columnshard/engines/scheme/versions/abstract_scheme.cpp index b302a847e458..3a833a465ac8 100644 --- a/ydb/core/tx/columnshard/engines/scheme/versions/abstract_scheme.cpp +++ b/ydb/core/tx/columnshard/engines/scheme/versions/abstract_scheme.cpp @@ -1,8 +1,16 @@ #include "abstract_scheme.h" -#include +#include #include +#include +#include +#include +#include +#include +#include + #include + #include namespace NKikimr::NOlap { @@ -24,7 +32,6 @@ std::set ISnapshotSchema::GetPkColumnsIds() const { result.emplace(GetColumnId(field->name())); } return result; - } TConclusion> ISnapshotSchema::NormalizeBatch( @@ -54,7 +61,7 @@ TConclusion> ISnapshotSchema::Normali } if (restoreColumnIds.contains(columnId)) { AFL_VERIFY(!!GetExternalDefaultValueVerified(columnId) || GetIndexInfo().IsNullableVerified(columnId))("column_name", - GetIndexInfo().GetColumnName(columnId, false))("id", columnId); + GetIndexInfo().GetColumnName(columnId, false))("id", columnId); result->AddField(resultField, GetColumnLoaderVerified(columnId)->BuildDefaultAccessor(batch->num_rows())).Validate(); } } @@ -77,7 +84,7 @@ TConclusion> ISnapshotSchema::PrepareForModi NArrow::TStatusValidator::Validate(incomingBatch->ValidateFull()); #endif - const std::shared_ptr dstSchema = GetIndexInfo().ArrowSchema(); + NArrow::TSchemaLiteView dstSchema = GetIndexInfo().ArrowSchema(); std::vector> pkColumns; pkColumns.resize(GetIndexInfo().GetReplaceKey()->num_fields()); ui32 pkColumnsCount = 0; @@ -96,7 +103,7 @@ TConclusion> ISnapshotSchema::PrepareForModi return TConclusionStatus::Success(); } if (pkFieldIdx) { - return TConclusionStatus::Fail("null data for pk column is impossible for '" + dstSchema->field(targetIdx)->name() + "'"); + return TConclusionStatus::Fail("null data for pk column is impossible for '" + dstSchema.field(targetIdx)->name() + "'"); } switch (mType) { case NEvWrite::EModificationType::Replace: @@ -108,8 +115,7 @@ TConclusion> ISnapshotSchema::PrepareForModi if (GetIndexInfo().GetColumnExternalDefaultValueByIndexVerified(targetIdx)) { return TConclusionStatus::Success(); } else { - return TConclusionStatus::Fail( - "empty field for non-default column: '" + dstSchema->field(targetIdx)->name() + "'"); + return TConclusionStatus::Fail("empty field for non-default column: '" + dstSchema.field(targetIdx)->name() + "'"); } } case NEvWrite::EModificationType::Delete: @@ -120,8 +126,8 @@ TConclusion> ISnapshotSchema::PrepareForModi const auto nameResolver = [&](const std::string& fieldName) -> i32 { return GetIndexInfo().GetColumnIndexOptional(fieldName).value_or(-1); }; - auto batchConclusion = - NArrow::TColumnOperator().SkipIfAbsent().ErrorOnDifferentFieldTypes().AdaptIncomingToDestinationExt(incomingBatch, dstSchema, pred, nameResolver); + auto batchConclusion = NArrow::TColumnOperator().SkipIfAbsent().ErrorOnDifferentFieldTypes().AdaptIncomingToDestinationExt( + incomingBatch, dstSchema, pred, nameResolver); if (batchConclusion.IsFail()) { return batchConclusion; } @@ -133,20 +139,27 @@ TConclusion> ISnapshotSchema::PrepareForModi return batch; } -void ISnapshotSchema::AdaptBatchToSchema(NArrow::TGeneralContainer& batch, const ISnapshotSchema::TPtr& targetSchema) const { - if (targetSchema->GetVersion() != GetVersion()) { - std::vector columnIdxToDelete; - for (size_t columnIdx = 0; columnIdx < batch.GetSchema()->GetFields().size(); ++columnIdx) { - const std::optional targetColumnId = targetSchema->GetColumnIdOptional(batch.GetSchema()->field(columnIdx)->name()); - const ui32 batchColumnId = GetColumnIdVerified(GetFieldByIndex(columnIdx)->name()); - if (!targetColumnId || *targetColumnId != batchColumnId) { - columnIdxToDelete.emplace_back(columnIdx); - } - } - if (!columnIdxToDelete.empty()) { - batch.DeleteFieldsByIndex(columnIdxToDelete); +std::set ISnapshotSchema::GetColumnIdsToDelete(const ISnapshotSchema::TPtr& targetSchema) const { + if (targetSchema->GetVersion() == GetVersion()) { + return {}; + } + std::set columnIdxsToDelete; + for (const auto& columnIdx : GetColumnIds()) { + const std::optional targetColumnId = targetSchema->GetColumnIdOptional(GetFieldByColumnIdOptional(columnIdx)->name()); + if (!targetColumnId || *targetColumnId != columnIdx) { + columnIdxsToDelete.emplace(columnIdx); } } + return columnIdxsToDelete; +} + +std::vector ISnapshotSchema::ConvertColumnIdsToIndexes(const std::set& idxs) const { + std::vector columnIndexes; + for (const auto& id : idxs) { + AFL_VERIFY(HasColumnId(id)); + columnIndexes.emplace_back(GetFieldIndex(id)); + } + return columnIndexes; } ui32 ISnapshotSchema::GetColumnId(const std::string& columnName) const { @@ -188,7 +201,7 @@ std::vector ISnapshotSchema::GetPKColumnNames() const { std::vector> ISnapshotSchema::GetAbsentFields(const std::shared_ptr& existsSchema) const { std::vector> result; - for (auto&& f : GetIndexInfo().ArrowSchema()->fields()) { + for (auto&& f : GetIndexInfo().ArrowSchema()) { if (!existsSchema->GetFieldByName(f->name())) { result.emplace_back(f); } @@ -208,9 +221,9 @@ TConclusionStatus ISnapshotSchema::CheckColumnsDefault(const std::vector> ISnapshotSchema::BuildDefaultBatch( - const std::vector>& fields, const ui32 rowsCount, const bool force) const { + const NArrow::TSchemaLiteView& schema, const ui32 rowsCount, const bool force) const { std::vector> columns; - for (auto&& i : fields) { + for (auto&& i : schema) { const ui32 columnId = GetColumnIdVerified(i->name()); auto defaultValue = GetExternalDefaultValueVerified(columnId); if (!defaultValue && !GetIndexInfo().IsNullableVerified(columnId)) { @@ -222,7 +235,7 @@ TConclusion> ISnapshotSchema::BuildDefaultBa } columns.emplace_back(NArrow::TThreadSimpleArraysCache::Get(i->type(), defaultValue, rowsCount)); } - return arrow::RecordBatch::Make(std::make_shared(fields), rowsCount, columns); + return arrow::RecordBatch::Make(std::make_shared(arrow::FieldVector(schema.begin(), schema.end())), rowsCount, columns); } std::shared_ptr ISnapshotSchema::GetExternalDefaultValueVerified(const std::string& columnName) const { @@ -270,4 +283,66 @@ std::set ISnapshotSchema::GetColumnsWithDifferentDefaults( return result; } +TConclusion ISnapshotSchema::PrepareForWrite(const ISnapshotSchema::TPtr& selfPtr, const ui64 pathId, + const std::shared_ptr& incomingBatch, const NEvWrite::EModificationType mType, + const std::shared_ptr& storagesManager, const std::shared_ptr& splitterCounters) const { + AFL_VERIFY(incomingBatch->num_rows()); + auto itIncoming = incomingBatch->schema()->fields().begin(); + auto itIncomingEnd = incomingBatch->schema()->fields().end(); + auto itIndex = GetIndexInfo().ArrowSchema().begin(); + auto itIndexEnd = GetIndexInfo().ArrowSchema().end(); + THashMap>> chunks; + + std::shared_ptr schemaDetails( + new TDefaultSchemaDetails(selfPtr, std::make_shared())); + + while (itIncoming != itIncomingEnd && itIndex != itIndexEnd) { + if ((*itIncoming)->name() == (*itIndex)->name()) { + const ui32 incomingIndex = itIncoming - incomingBatch->schema()->fields().begin(); + const ui32 columnIndex = itIndex - GetIndexInfo().ArrowSchema().begin(); + const ui32 columnId = GetIndexInfo().GetColumnIdByIndexVerified(columnIndex); + auto loader = GetIndexInfo().GetColumnLoaderVerified(columnId); + auto saver = GetIndexInfo().GetColumnSaver(columnId); + saver.AddSerializerWithBorder(100, NArrow::NSerialization::TNativeSerializer::GetUncompressed()); + saver.AddSerializerWithBorder(100000000, NArrow::NSerialization::TNativeSerializer::GetFast()); + const auto& columnFeatures = GetIndexInfo().GetColumnFeaturesVerified(columnId); + auto accessor = std::make_shared(incomingBatch->column(incomingIndex)); + std::shared_ptr rbToWrite = + loader->GetAccessorConstructor()->Construct(accessor, loader->BuildAccessorContext(accessor->GetRecordsCount())); + std::shared_ptr arrToWrite = + loader->GetAccessorConstructor()->Construct(rbToWrite, loader->BuildAccessorContext(accessor->GetRecordsCount())).DetachResult(); + + std::vector> columnChunks = { std::make_shared( + saver.Apply(rbToWrite), arrToWrite, TChunkAddress(columnId, 0), columnFeatures) }; + AFL_VERIFY(chunks.emplace(columnId, std::move(columnChunks)).second); + ++itIncoming; + ++itIndex; + } else { + ++itIndex; + } + } + AFL_VERIFY(itIncoming == itIncomingEnd); + + TGeneralSerializedSlice slice(chunks, schemaDetails, splitterCounters); + std::vector blobs; + if (!slice.GroupBlobs(blobs, NSplitter::TEntityGroups(NYDBTest::TControllers::GetColumnShardController()->GetBlobSplitSettings(), + NBlobOperations::TGlobal::DefaultStorageId))) { + return TConclusionStatus::Fail("cannot split data for appropriate blobs size"); + } + auto constructor = + TWritePortionInfoWithBlobsConstructor::BuildByBlobs(std::move(blobs), {}, pathId, GetVersion(), GetSnapshot(), storagesManager); + + NArrow::TFirstLastSpecialKeys primaryKeys(slice.GetFirstLastPKBatch(GetIndexInfo().GetReplaceKey())); + const ui32 deletionsCount = (mType == NEvWrite::EModificationType::Delete) ? incomingBatch->num_rows() : 0; + constructor.GetPortionConstructor().MutablePortionConstructor().AddMetadata(*this, deletionsCount, primaryKeys, std::nullopt); + constructor.GetPortionConstructor().MutablePortionConstructor().MutableMeta().SetTierName(IStoragesManager::DefaultStorageId); + constructor.GetPortionConstructor().MutablePortionConstructor().MutableMeta().SetCompactionLevel(0); + constructor.GetPortionConstructor().MutablePortionConstructor().MutableMeta().UpdateRecordsMeta(NPortion::EProduced::INSERTED); + return TWritePortionInfoWithBlobsResult(std::move(constructor)); } + +ui32 ISnapshotSchema::GetIndexesCount() const { + return GetIndexInfo().GetIndexes().size(); +} + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/scheme/versions/abstract_scheme.h b/ydb/core/tx/columnshard/engines/scheme/versions/abstract_scheme.h index 962989d75fb2..825f3f7e543b 100644 --- a/ydb/core/tx/columnshard/engines/scheme/versions/abstract_scheme.h +++ b/ydb/core/tx/columnshard/engines/scheme/versions/abstract_scheme.h @@ -1,11 +1,12 @@ #pragma once #include - -#include #include -#include - +#include +#include #include +#include +#include +#include #include @@ -13,21 +14,23 @@ namespace NKikimr::NOlap { struct TIndexInfo; class TSaverContext; +class TWritePortionInfoWithBlobsResult; class ISnapshotSchema { protected: virtual TString DoDebugString() const = 0; + public: using TPtr = std::shared_ptr; - virtual ~ISnapshotSchema() {} + virtual ~ISnapshotSchema() = default; virtual std::shared_ptr GetColumnLoaderOptional(const ui32 columnId) const = 0; std::shared_ptr GetColumnLoaderVerified(const ui32 columnId) const; std::shared_ptr GetColumnLoaderOptional(const std::string& columnName) const; std::shared_ptr GetColumnLoaderVerified(const std::string& columnName) const; bool IsSpecialColumnId(const ui32 columnId) const; - virtual const std::vector& GetColumnIds() const = 0; + virtual TColumnIdsView GetColumnIds() const = 0; virtual NArrow::NAccessor::TColumnSaver GetColumnSaver(const ui32 columnId) const = 0; NArrow::NAccessor::TColumnSaver GetColumnSaver(const TString& columnName) const { @@ -43,7 +46,7 @@ class ISnapshotSchema { std::shared_ptr GetExternalDefaultValueVerified(const ui32 columnId) const; TConclusion> BuildDefaultBatch( - const std::vector>& fields, const ui32 rowsCount, const bool force) const; + const NArrow::TSchemaLiteView& schema, const ui32 rowsCount, const bool force) const; TConclusionStatus CheckColumnsDefault(const std::vector>& fields) const; std::vector GetPKColumnNames() const; @@ -68,16 +71,23 @@ class ISnapshotSchema { virtual const TSnapshot& GetSnapshot() const = 0; virtual ui64 GetVersion() const = 0; virtual ui32 GetColumnsCount() const = 0; + ui32 GetIndexesCount() const; std::set GetPkColumnsIds() const; - static std::set GetColumnsWithDifferentDefaults(const THashMap& schemas, const ISnapshotSchema::TPtr& targetSchema); + static std::set GetColumnsWithDifferentDefaults( + const THashMap& schemas, const ISnapshotSchema::TPtr& targetSchema); - [[nodiscard]] TConclusion> NormalizeBatch( - const ISnapshotSchema& dataSchema, const std::shared_ptr& batch, const std::set& restoreColumnIds) const; + [[nodiscard]] TConclusion> NormalizeBatch(const ISnapshotSchema& dataSchema, + const std::shared_ptr& batch, const std::set& restoreColumnIds) const; [[nodiscard]] TConclusion> PrepareForModification( const std::shared_ptr& incomingBatch, const NEvWrite::EModificationType mType) const; + [[nodiscard]] TConclusion PrepareForWrite(const ISnapshotSchema::TPtr& selfPtr, const ui64 pathId, + const std::shared_ptr& incomingBatch, const NEvWrite::EModificationType mType, + const std::shared_ptr& storagesManager, const std::shared_ptr& splitterCounters) const; void AdaptBatchToSchema(NArrow::TGeneralContainer& batch, const ISnapshotSchema::TPtr& targetSchema) const; + std::set GetColumnIdsToDelete(const ISnapshotSchema::TPtr& targetSchema) const; + std::vector ConvertColumnIdsToIndexes(const std::set& idxs) const; }; -} // namespace NKikimr::NOlap +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/scheme/versions/filtered_scheme.h b/ydb/core/tx/columnshard/engines/scheme/versions/filtered_scheme.h index 8fc82ee6a304..417fcaa0775f 100644 --- a/ydb/core/tx/columnshard/engines/scheme/versions/filtered_scheme.h +++ b/ydb/core/tx/columnshard/engines/scheme/versions/filtered_scheme.h @@ -2,7 +2,7 @@ #include "abstract_scheme.h" -#include +#include namespace NKikimr::NOlap { @@ -18,8 +18,8 @@ class TFilteredSnapshotSchema: public ISnapshotSchema { TFilteredSnapshotSchema(const ISnapshotSchema::TPtr& originalSnapshot, const std::vector& columnIds); TFilteredSnapshotSchema(const ISnapshotSchema::TPtr& originalSnapshot, const std::set& columnIds); - virtual const std::vector& GetColumnIds() const override { - return ColumnIds; + virtual TColumnIdsView GetColumnIds() const override { + return {ColumnIds.begin(), ColumnIds.end()}; } TColumnSaver GetColumnSaver(const ui32 columnId) const override; std::shared_ptr GetColumnLoaderOptional(const ui32 columnId) const override; diff --git a/ydb/core/tx/columnshard/engines/scheme/versions/snapshot_scheme.cpp b/ydb/core/tx/columnshard/engines/scheme/versions/snapshot_scheme.cpp index 05277b7b8967..451145bee0f4 100644 --- a/ydb/core/tx/columnshard/engines/scheme/versions/snapshot_scheme.cpp +++ b/ydb/core/tx/columnshard/engines/scheme/versions/snapshot_scheme.cpp @@ -1,32 +1,32 @@ #include "snapshot_scheme.h" +#include namespace NKikimr::NOlap { -TSnapshotSchema::TSnapshotSchema(TIndexInfo&& indexInfo, const TSnapshot& snapshot) +TSnapshotSchema::TSnapshotSchema(TObjectCache::TEntryGuard&& indexInfo, const TSnapshot& snapshot) : IndexInfo(std::move(indexInfo)) - , Schema(IndexInfo.ArrowSchemaWithSpecials()) - , Snapshot(snapshot) -{ + , Schema(IndexInfo->ArrowSchemaWithSpecials()) + , Snapshot(snapshot) { } TColumnSaver TSnapshotSchema::GetColumnSaver(const ui32 columnId) const { - return IndexInfo.GetColumnSaver(columnId); + return IndexInfo->GetColumnSaver(columnId); } std::shared_ptr TSnapshotSchema::GetColumnLoaderOptional(const ui32 columnId) const { - return IndexInfo.GetColumnLoaderOptional(columnId); + return IndexInfo->GetColumnLoaderOptional(columnId); } std::optional TSnapshotSchema::GetColumnIdOptional(const std::string& columnName) const { - return IndexInfo.GetColumnIdOptional(columnName); + return IndexInfo->GetColumnIdOptional(columnName); } ui32 TSnapshotSchema::GetColumnIdVerified(const std::string& columnName) const { - return IndexInfo.GetColumnIdVerified(columnName); + return IndexInfo->GetColumnIdVerified(columnName); } int TSnapshotSchema::GetFieldIndex(const ui32 columnId) const { - return IndexInfo.GetColumnIndexOptional(columnId).value_or(-1); + return IndexInfo->GetColumnIndexOptional(columnId).value_or(-1); } const std::shared_ptr& TSnapshotSchema::GetSchema() const { @@ -34,7 +34,7 @@ const std::shared_ptr& TSnapshotSchema::GetSchema() const { } const TIndexInfo& TSnapshotSchema::GetIndexInfo() const { - return IndexInfo; + return *IndexInfo; } const TSnapshot& TSnapshotSchema::GetSnapshot() const { @@ -46,7 +46,7 @@ ui32 TSnapshotSchema::GetColumnsCount() const { } ui64 TSnapshotSchema::GetVersion() const { - return IndexInfo.GetVersion(); + return IndexInfo->GetVersion(); } } diff --git a/ydb/core/tx/columnshard/engines/scheme/versions/snapshot_scheme.h b/ydb/core/tx/columnshard/engines/scheme/versions/snapshot_scheme.h index 5fa3c4ef7551..0cf6aa147d66 100644 --- a/ydb/core/tx/columnshard/engines/scheme/versions/snapshot_scheme.h +++ b/ydb/core/tx/columnshard/engines/scheme/versions/snapshot_scheme.h @@ -2,13 +2,15 @@ #include "abstract_scheme.h" -#include +#include + +#include namespace NKikimr::NOlap { class TSnapshotSchema: public ISnapshotSchema { private: - TIndexInfo IndexInfo; + TObjectCache::TEntryGuard IndexInfo; std::shared_ptr Schema; TSnapshot Snapshot; protected: @@ -16,15 +18,15 @@ class TSnapshotSchema: public ISnapshotSchema { return TStringBuilder() << "(" "schema=" << Schema->ToString() << ";" << "snapshot=" << Snapshot.DebugString() << ";" << - "index_info=" << IndexInfo.DebugString() << ";" << + "index_info=" << IndexInfo->DebugString() << ";" << ")" ; } public: - TSnapshotSchema(TIndexInfo&& indexInfo, const TSnapshot& snapshot); + TSnapshotSchema(TObjectCache::TEntryGuard&& indexInfo, const TSnapshot& snapshot); - virtual const std::vector& GetColumnIds() const override { - return IndexInfo.GetColumnIds(); + virtual TColumnIdsView GetColumnIds() const override { + return IndexInfo->GetColumnIds(); } TColumnSaver GetColumnSaver(const ui32 columnId) const override; diff --git a/ydb/core/tx/columnshard/engines/scheme/versions/versioned_index.cpp b/ydb/core/tx/columnshard/engines/scheme/versions/versioned_index.cpp index d9a858f349c3..1642449df51f 100644 --- a/ydb/core/tx/columnshard/engines/scheme/versions/versioned_index.cpp +++ b/ydb/core/tx/columnshard/engines/scheme/versions/versioned_index.cpp @@ -6,15 +6,15 @@ namespace NKikimr::NOlap { -const TIndexInfo* TVersionedIndex::AddIndex(const TSnapshot& snapshot, TIndexInfo&& indexInfo) { +const TIndexInfo* TVersionedIndex::AddIndex(const TSnapshot& snapshot, TObjectCache::TEntryGuard&& indexInfo) { if (Snapshots.empty()) { - PrimaryKey = indexInfo.GetPrimaryKey(); + PrimaryKey = indexInfo->GetPrimaryKey(); } else { - Y_ABORT_UNLESS(PrimaryKey->Equals(indexInfo.GetPrimaryKey())); + Y_ABORT_UNLESS(PrimaryKey->Equals(indexInfo->GetPrimaryKey())); } - const bool needActualization = indexInfo.GetSchemeNeedActualization(); - auto newVersion = indexInfo.GetVersion(); + const bool needActualization = indexInfo->GetSchemeNeedActualization(); + auto newVersion = indexInfo->GetVersion(); auto itVersion = SnapshotByVersion.emplace(newVersion, std::make_shared(std::move(indexInfo), snapshot)); if (!itVersion.second) { AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("message", "Skip registered version")("version", LastSchemaVersion); diff --git a/ydb/core/tx/columnshard/engines/scheme/versions/versioned_index.h b/ydb/core/tx/columnshard/engines/scheme/versions/versioned_index.h index fe554a790d8f..81a57cd65eab 100644 --- a/ydb/core/tx/columnshard/engines/scheme/versions/versioned_index.h +++ b/ydb/core/tx/columnshard/engines/scheme/versions/versioned_index.h @@ -1,5 +1,8 @@ #pragma once #include "abstract_scheme.h" + +#include +#include #include namespace NKikimr::NOlap { @@ -33,6 +36,11 @@ class TVersionedIndex { ISnapshotSchema::TPtr SchemeForActualization; public: + bool IsEqualTo(const TVersionedIndex& vIndex) { + return LastSchemaVersion == vIndex.LastSchemaVersion && SnapshotByVersion.size() == vIndex.SnapshotByVersion.size() && + ShardingInfo.size() == vIndex.ShardingInfo.size() && SchemeVersionForActualization == vIndex.SchemeVersionForActualization; + } + ISnapshotSchema::TPtr GetLastCriticalSchema() const { return SchemeForActualization; } @@ -73,7 +81,7 @@ class TVersionedIndex { return sb; } - ISnapshotSchema::TPtr GetSchema(const ui64 version) const { + ISnapshotSchema::TPtr GetSchemaOptional(const ui64 version) const { auto it = SnapshotByVersion.find(version); return it == SnapshotByVersion.end() ? nullptr : it->second; } @@ -84,17 +92,27 @@ class TVersionedIndex { return it->second; } - ISnapshotSchema::TPtr GetSchema(const TSnapshot& version) const { + ISnapshotSchema::TPtr GetSchemaVerified(const TSnapshot& version) const { for (auto it = Snapshots.rbegin(); it != Snapshots.rend(); ++it) { if (it->first <= version) { return it->second; } } Y_ABORT_UNLESS(!Snapshots.empty()); -// Y_ABORT_UNLESS(version.IsZero()); return Snapshots.begin()->second; } + ISnapshotSchema::TPtr GetLastSchemaBeforeOrEqualSnapshotOptional(const ui64 version) const { + if (SnapshotByVersion.empty()) { + return nullptr; + } + auto upperBound = SnapshotByVersion.upper_bound(version); + if (upperBound == SnapshotByVersion.begin()) { + return nullptr; + } + return std::prev(upperBound)->second; + } + ISnapshotSchema::TPtr GetLastSchema() const { Y_ABORT_UNLESS(!Snapshots.empty()); return Snapshots.rbegin()->second; @@ -108,7 +126,7 @@ class TVersionedIndex { return PrimaryKey; } - const TIndexInfo* AddIndex(const TSnapshot& snapshot, TIndexInfo&& indexInfo); + const TIndexInfo* AddIndex(const TSnapshot& snapshot, TObjectCache::TEntryGuard&& indexInfo); bool LoadShardingInfo(IDbWrapper& db); }; diff --git a/ydb/core/tx/columnshard/engines/scheme/versions/ya.make b/ydb/core/tx/columnshard/engines/scheme/versions/ya.make index 63dc44a74899..5b9cc7eff7c5 100644 --- a/ydb/core/tx/columnshard/engines/scheme/versions/ya.make +++ b/ydb/core/tx/columnshard/engines/scheme/versions/ya.make @@ -9,6 +9,7 @@ SRCS( PEERDIR( ydb/core/tx/columnshard/engines/scheme/abstract + ydb/core/tx/columnshard/engines/scheme/common ) END() diff --git a/ydb/core/tx/columnshard/engines/scheme/ya.make b/ydb/core/tx/columnshard/engines/scheme/ya.make index 744458ff4dcb..295da3556bb9 100644 --- a/ydb/core/tx/columnshard/engines/scheme/ya.make +++ b/ydb/core/tx/columnshard/engines/scheme/ya.make @@ -7,6 +7,9 @@ SRCS( index_info.cpp tier_info.cpp column_features.cpp + schema_diff.cpp + objects_cache.cpp + schema_version.cpp ) PEERDIR( @@ -19,6 +22,7 @@ PEERDIR( ydb/core/tx/columnshard/engines/scheme/versions ydb/core/tx/columnshard/engines/scheme/tiering ydb/core/tx/columnshard/engines/scheme/column + ydb/core/tx/columnshard/engines/scheme/common ydb/core/tx/columnshard/engines/scheme/defaults ydb/core/formats/arrow/accessor ydb/core/tx/columnshard/blobs_action/abstract diff --git a/ydb/core/tx/columnshard/engines/storage/actualizer/abstract/abstract.h b/ydb/core/tx/columnshard/engines/storage/actualizer/abstract/abstract.h index 56db4cf2fa4f..b0ef05dfd5c8 100644 --- a/ydb/core/tx/columnshard/engines/storage/actualizer/abstract/abstract.h +++ b/ydb/core/tx/columnshard/engines/storage/actualizer/abstract/abstract.h @@ -1,6 +1,7 @@ #pragma once #include "context.h" +#include #include namespace NKikimr::NOlap::NActualizer { @@ -9,12 +10,16 @@ class IActualizer { protected: virtual void DoAddPortion(const TPortionInfo& info, const TAddExternalContext& context) = 0; virtual void DoRemovePortion(const ui64 portionId) = 0; - virtual void DoExtractTasks(TTieringProcessContext& tasksContext, const TExternalTasksContext& externalContext, TInternalTasksContext& internalContext) = 0; + virtual void DoExtractTasks( + TTieringProcessContext& tasksContext, const TExternalTasksContext& externalContext, TInternalTasksContext& internalContext) = 0; + public: virtual ~IActualizer() = default; - void ExtractTasks(TTieringProcessContext& tasksContext, const TExternalTasksContext& externalContext, TInternalTasksContext& internalContext) { + void ExtractTasks( + TTieringProcessContext& tasksContext, const TExternalTasksContext& externalContext, TInternalTasksContext& internalContext) { return DoExtractTasks(tasksContext, externalContext, internalContext); } + void AddPortion(const std::shared_ptr& info, const TAddExternalContext& context) { AFL_VERIFY(info); if (info->HasRemoveSnapshot()) { @@ -27,4 +32,4 @@ class IActualizer { } }; -} \ No newline at end of file +} // namespace NKikimr::NOlap::NActualizer diff --git a/ydb/core/tx/columnshard/engines/storage/actualizer/abstract/context.h b/ydb/core/tx/columnshard/engines/storage/actualizer/abstract/context.h index 3e50ee118d43..e49ebaed0d25 100644 --- a/ydb/core/tx/columnshard/engines/storage/actualizer/abstract/context.h +++ b/ydb/core/tx/columnshard/engines/storage/actualizer/abstract/context.h @@ -13,6 +13,34 @@ namespace NKikimr::NOlap::NActualizer { class TTieringProcessContext; +class TActualizationContext { +private: + YDB_READONLY_DEF(TInstant, Now); + +public: + TActualizationContext(const TInstant now) + : Now(now) { + } +}; + +class TActualizationBuildingContext { +private: + YDB_READONLY_DEF(TInstant, Now); + const THashMap>& Portions; + +public: + TActualizationBuildingContext(const TInstant now, const THashMap>& portions) + : Now(now) + , Portions(portions) { + } + + const std::shared_ptr& GetPortionVerified(const ui64 portionId) const { + auto it = Portions.find(portionId); + AFL_VERIFY(it != Portions.end()); + return it->second; + } +}; + class TAddExternalContext { private: YDB_READONLY_DEF(TInstant, Now); diff --git a/ydb/core/tx/columnshard/engines/storage/actualizer/counters/counters.h b/ydb/core/tx/columnshard/engines/storage/actualizer/counters/counters.h index 7bf8aa895f69..e803df700f7b 100644 --- a/ydb/core/tx/columnshard/engines/storage/actualizer/counters/counters.h +++ b/ydb/core/tx/columnshard/engines/storage/actualizer/counters/counters.h @@ -34,13 +34,13 @@ class TPortionCategoryCounters { } void AddPortion(const std::shared_ptr& p) { - RecordsCount->Add(p->NumRows()); + RecordsCount->Add(p->GetRecordsCount()); Count->Add(1); Bytes->Add(p->GetTotalBlobBytes()); } void RemovePortion(const std::shared_ptr& p) { - RecordsCount->Remove(p->NumRows()); + RecordsCount->Remove(p->GetRecordsCount()); Count->Remove(1); Bytes->Remove(p->GetTotalBlobBytes()); } diff --git a/ydb/core/tx/columnshard/engines/storage/actualizer/index/index.cpp b/ydb/core/tx/columnshard/engines/storage/actualizer/index/index.cpp index 91805b0ef283..47a1e4510d81 100644 --- a/ydb/core/tx/columnshard/engines/storage/actualizer/index/index.cpp +++ b/ydb/core/tx/columnshard/engines/storage/actualizer/index/index.cpp @@ -36,19 +36,28 @@ void TGranuleActualizationIndex::RefreshScheme(const TAddExternalContext& contex NYDBTest::TControllers::GetColumnShardController()->OnActualizationRefreshScheme(); } -TGranuleActualizationIndex::TGranuleActualizationIndex(const ui64 pathId, const TVersionedIndex& versionedIndex) +TGranuleActualizationIndex::TGranuleActualizationIndex(const ui64 pathId, const TVersionedIndex& versionedIndex, const std::shared_ptr& storagesManager) : PathId(pathId) , VersionedIndex(versionedIndex) + , StoragesManager(storagesManager) { Y_UNUSED(PathId); } void TGranuleActualizationIndex::Start() { AFL_VERIFY(Actualizers.empty()); - TieringActualizer = std::make_shared(PathId, VersionedIndex); + TieringActualizer = std::make_shared(PathId, VersionedIndex, StoragesManager); SchemeActualizer = std::make_shared(PathId, VersionedIndex); Actualizers.emplace_back(TieringActualizer); Actualizers.emplace_back(SchemeActualizer); } +std::vector TGranuleActualizationIndex::CollectMetadataRequests( + const THashMap& portions) { + if (!TieringActualizer) { + return {}; + } + return TieringActualizer->BuildMetadataRequests(PathId, portions, TieringActualizer); +} + } diff --git a/ydb/core/tx/columnshard/engines/storage/actualizer/index/index.h b/ydb/core/tx/columnshard/engines/storage/actualizer/index/index.h index a67fac3a5cdb..df3c0768d223 100644 --- a/ydb/core/tx/columnshard/engines/storage/actualizer/index/index.h +++ b/ydb/core/tx/columnshard/engines/storage/actualizer/index/index.h @@ -1,11 +1,12 @@ #pragma once +#include #include #include namespace NKikimr::NOlap { class TVersionedIndex; class TTiering; -} +} // namespace NKikimr::NOlap namespace NKikimr::NOlap::NActualizer { class TTieringActualizer; @@ -21,9 +22,17 @@ class TGranuleActualizationIndex { const ui64 PathId; const TVersionedIndex& VersionedIndex; + std::shared_ptr StoragesManager; + public: + std::vector CollectMetadataRequests(const THashMap& portions); + + bool IsStarted() const { + return Actualizers.size(); + } + void Start(); - TGranuleActualizationIndex(const ui64 pathId, const TVersionedIndex& versionedIndex); + TGranuleActualizationIndex(const ui64 pathId, const TVersionedIndex& versionedIndex, const std::shared_ptr& storagesManager); void ExtractActualizationTasks(TTieringProcessContext& tasksContext, const NActualizer::TExternalTasksContext& externalContext) const; @@ -34,4 +43,4 @@ class TGranuleActualizationIndex { void RemovePortion(const std::shared_ptr& portion); }; -} \ No newline at end of file +} // namespace NKikimr::NOlap::NActualizer diff --git a/ydb/core/tx/columnshard/engines/storage/actualizer/scheme/counters.h b/ydb/core/tx/columnshard/engines/storage/actualizer/scheme/counters.h index 95aa18603f46..834447f62226 100644 --- a/ydb/core/tx/columnshard/engines/storage/actualizer/scheme/counters.h +++ b/ydb/core/tx/columnshard/engines/storage/actualizer/scheme/counters.h @@ -13,10 +13,31 @@ class TSchemeGlobalCounters: public NColumnShard::TCommonCountersOwner { std::shared_ptr QueueSizeInternalWrite; std::shared_ptr QueueSizeExternalWrite; + + NMonitoring::TDynamicCounters::TCounterPtr Extracts; + NMonitoring::TDynamicCounters::TCounterPtr SkipNotOptimized; + NMonitoring::TDynamicCounters::TCounterPtr SkipNotReadyWrite; + NMonitoring::TDynamicCounters::TCounterPtr SkipPortionNotActualizable; + NMonitoring::TDynamicCounters::TCounterPtr EmptyTargetSchema; + NMonitoring::TDynamicCounters::TCounterPtr RefreshEmpty; + NMonitoring::TDynamicCounters::TCounterPtr SkipPortionToRemove; + NMonitoring::TDynamicCounters::TCounterPtr RefreshValue; + NMonitoring::TDynamicCounters::TCounterPtr AddPortion; + NMonitoring::TDynamicCounters::TCounterPtr RemovePortion; + public: TSchemeGlobalCounters() : TBase("SchemeActualizer") - { + , Extracts(TBase::GetDeriviative("Extracts/Count")) + , SkipNotOptimized(TBase::GetDeriviative("SkipNotOptimized/Count")) + , SkipNotReadyWrite(TBase::GetDeriviative("SkipNotReadyWrite/Count")) + , SkipPortionNotActualizable(TBase::GetDeriviative("SkipPortionNotActualizable/Count")) + , EmptyTargetSchema(TBase::GetDeriviative("EmptyTargetSchema/Count")) + , RefreshEmpty(TBase::GetDeriviative("RefreshEmpty/Count")) + , SkipPortionToRemove(TBase::GetDeriviative("SkipPortionToRemove/Count")) + , RefreshValue(TBase::GetDeriviative("RefreshValue/Count")) + , AddPortion(TBase::GetDeriviative("AddPortion/Count")) + , RemovePortion(TBase::GetDeriviative("RemovePortion/Count")) { QueueSizeExternalWrite = TBase::GetValueAutoAggregations("Granule/Scheme/Actualization/QueueSize/ExternalWrite"); QueueSizeInternalWrite = TBase::GetValueAutoAggregations("Granule/Scheme/Actualization/QueueSize/InternalWrite"); } @@ -28,7 +49,36 @@ class TSchemeGlobalCounters: public NColumnShard::TCommonCountersOwner { static std::shared_ptr BuildQueueSizeInternalWrite() { return Singleton()->QueueSizeInternalWrite->GetClient(); } - + static void OnAddPortion() { + Singleton()->AddPortion->Add(1); + } + static void OnRemovePortion() { + Singleton()->RemovePortion->Add(1); + } + static void OnSkipPortionNotActualizable() { + Singleton()->SkipPortionNotActualizable->Add(1); + } + static void OnEmptyTargetSchema() { + Singleton()->EmptyTargetSchema->Add(1); + } + static void OnRefreshEmpty() { + Singleton()->RefreshEmpty->Add(1); + } + static void OnSkipPortionToRemove() { + Singleton()->SkipPortionToRemove->Add(1); + } + static void OnRefreshValue() { + Singleton()->RefreshValue->Add(1); + } + static void OnExtract() { + Singleton()->Extracts->Add(1); + } + static void OnSkipNotOptimized() { + Singleton()->SkipNotOptimized->Add(1); + } + static void OnSkipNotReadyWrite() { + Singleton()->SkipNotReadyWrite->Add(1); + } }; class TSchemeCounters { diff --git a/ydb/core/tx/columnshard/engines/storage/actualizer/scheme/scheme.cpp b/ydb/core/tx/columnshard/engines/storage/actualizer/scheme/scheme.cpp index b2def23842d4..d8e7044101c8 100644 --- a/ydb/core/tx/columnshard/engines/storage/actualizer/scheme/scheme.cpp +++ b/ydb/core/tx/columnshard/engines/storage/actualizer/scheme/scheme.cpp @@ -23,6 +23,7 @@ std::optionalAddPortionForActualizer(1); AFL_VERIFY(PortionsToActualizeScheme[actualizationInfo->GetAddress()].emplace(info.GetPortionId()).second); AFL_VERIFY(PortionsInfo.emplace(info.GetPortionId(), actualizationInfo->ExtractFindId()).second); @@ -44,6 +47,7 @@ void TSchemeActualizer::DoAddPortion(const TPortionInfo& info, const TAddExterna void TSchemeActualizer::DoRemovePortion(const ui64 portionId) { auto it = PortionsInfo.find(portionId); if (it == PortionsInfo.end()) { + TSchemeGlobalCounters::OnSkipPortionToRemove(); return; } auto itAddress = PortionsToActualizeScheme.find(it->second.GetRWAddress()); @@ -53,19 +57,25 @@ void TSchemeActualizer::DoRemovePortion(const ui64 portionId) { if (itAddress->second.empty()) { PortionsToActualizeScheme.erase(itAddress); } + TSchemeGlobalCounters::OnRemovePortion(); PortionsInfo.erase(it); } void TSchemeActualizer::DoExtractTasks(TTieringProcessContext& tasksContext, const TExternalTasksContext& externalContext, TInternalTasksContext& /*internalContext*/) { THashSet portionsToRemove; + TSchemeGlobalCounters::OnExtract(); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_ACTUALIZATION)("rw_count", PortionsToActualizeScheme.size()); for (auto&& [address, portions] : PortionsToActualizeScheme) { if (!tasksContext.IsRWAddressAvailable(address)) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_ACTUALIZATION)("event", "skip_not_ready_for_write"); + TSchemeGlobalCounters::OnSkipNotReadyWrite(); continue; } for (auto&& portionId : portions) { auto portion = externalContext.GetPortionVerified(portionId); if (!address.WriteIs(NBlobOperations::TGlobal::DefaultStorageId) && !address.WriteIs(NTiering::NCommon::DeleteTierName)) { if (!portion->HasRuntimeFeature(TPortionInfo::ERuntimeFeature::Optimized)) { + TSchemeGlobalCounters::OnSkipNotOptimized(); continue; } } @@ -75,7 +85,8 @@ void TSchemeActualizer::DoExtractTasks(TTieringProcessContext& tasksContext, con TPortionEvictionFeatures features(portionScheme, info->GetTargetScheme(), portion->GetTierNameDef(IStoragesManager::DefaultStorageId)); features.SetTargetTierName(portion->GetTierNameDef(IStoragesManager::DefaultStorageId)); - if (!tasksContext.AddPortion(*portion, std::move(features), {})) { + if (!tasksContext.AddPortion(portion, std::move(features), {})) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_ACTUALIZATION)("event", "cannot_add_portion")("context", tasksContext.DebugString()); break; } else { portionsToRemove.emplace(portion->GetPortionId()); @@ -97,14 +108,16 @@ void TSchemeActualizer::DoExtractTasks(TTieringProcessContext& tasksContext, con } Counters.QueueSizeInternalWrite->SetValue(waitQueueInternal); Counters.QueueSizeExternalWrite->SetValue(waitQueueExternal); - + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_ACTUALIZATION)("internal_queue", waitQueueInternal)("external_queue", waitQueueExternal); } void TSchemeActualizer::Refresh(const TAddExternalContext& externalContext) { TargetSchema = VersionedIndex.GetLastCriticalSchema(); if (!TargetSchema) { + TSchemeGlobalCounters::OnRefreshEmpty(); AFL_VERIFY(PortionsInfo.empty()); } else { + TSchemeGlobalCounters::OnRefreshValue(); NYDBTest::TControllers::GetColumnShardController()->AddPortionForActualizer(-1 * PortionsInfo.size()); PortionsInfo.clear(); PortionsToActualizeScheme.clear(); @@ -114,4 +127,10 @@ void TSchemeActualizer::Refresh(const TAddExternalContext& externalContext) { } } +TSchemeActualizer::TSchemeActualizer(const ui64 pathId, const TVersionedIndex& versionedIndex) + : PathId(pathId) + , VersionedIndex(versionedIndex) { + Y_UNUSED(PathId); +} + } diff --git a/ydb/core/tx/columnshard/engines/storage/actualizer/scheme/scheme.h b/ydb/core/tx/columnshard/engines/storage/actualizer/scheme/scheme.h index f67335d1f553..ef6f2cd05895 100644 --- a/ydb/core/tx/columnshard/engines/storage/actualizer/scheme/scheme.h +++ b/ydb/core/tx/columnshard/engines/storage/actualizer/scheme/scheme.h @@ -63,11 +63,7 @@ class TSchemeActualizer: public IActualizer { public: void Refresh(const TAddExternalContext& externalContext); - TSchemeActualizer(const ui64 pathId, const TVersionedIndex& versionedIndex) - : PathId(pathId) - , VersionedIndex(versionedIndex) { - Y_UNUSED(PathId); - } + TSchemeActualizer(const ui64 pathId, const TVersionedIndex& versionedIndex); }; } \ No newline at end of file diff --git a/ydb/core/tx/columnshard/engines/storage/actualizer/tiering/tiering.cpp b/ydb/core/tx/columnshard/engines/storage/actualizer/tiering/tiering.cpp index f664eb6afb6a..25bb2f711131 100644 --- a/ydb/core/tx/columnshard/engines/storage/actualizer/tiering/tiering.cpp +++ b/ydb/core/tx/columnshard/engines/storage/actualizer/tiering/tiering.cpp @@ -1,15 +1,18 @@ #include "tiering.h" -#include + +#include +#include +#include +#include #include +#include #include -#include -#include -#include #include namespace NKikimr::NOlap::NActualizer { -std::shared_ptr TTieringActualizer::GetTargetSchema(const std::shared_ptr& portionSchema) const { +std::shared_ptr TTieringActualizer::GetTargetSchema( + const std::shared_ptr& portionSchema) const { if (!TargetCriticalSchema) { return portionSchema; } @@ -19,27 +22,29 @@ std::shared_ptr TTieringActualizer::GetTargetSc return portionSchema; } -std::optional TTieringActualizer::BuildActualizationInfo(const TPortionInfo& portion, const TInstant now) const { +std::optional TTieringActualizer::BuildActualizationInfo( + const TPortionInfo& portion, const TInstant now) const { std::shared_ptr portionSchema = portion.GetSchema(VersionedIndex); std::shared_ptr targetSchema = GetTargetSchema(portionSchema); const TString& currentTierName = portion.GetTierNameDef(IStoragesManager::DefaultStorageId); if (Tiering) { AFL_VERIFY(TieringColumnId); - auto indexMeta = portionSchema->GetIndexInfo().GetIndexMetaMax(*TieringColumnId); std::shared_ptr max; - if (!indexMeta) { - max = portion.MaxValue(*TieringColumnId); - if (!max) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "scalar_less_not_max"); + { + auto it = MaxByPortionId.find(portion.GetPortionId()); + if (it == MaxByPortionId.end()) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "data not ready"); + return {}; + } else if (!it->second) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "no data for ttl usage (need to create index or use first pk column)"); return {}; + } else { + max = it->second; } - } else { - NYDBTest::TControllers::GetColumnShardController()->OnStatisticsUsage(NIndexes::TIndexMetaContainer(indexMeta)); - const std::vector data = portion.GetIndexInplaceDataVerified(indexMeta->GetIndexId()); - max = indexMeta->GetMaxScalarVerified(data, portionSchema->GetIndexInfo().GetColumnFieldVerified(*TieringColumnId)->type()); } - auto tieringInfo = Tiering->GetTierToMove(max, now); + const bool skipEviction = !NYDBTest::TControllers::GetColumnShardController()->CheckPortionForEvict(portion); + auto tieringInfo = Tiering->GetTierToMove(max, now, skipEviction); AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("tiering_info", tieringInfo.DebugString()); std::optional d; std::set storagesWrite; @@ -52,6 +57,13 @@ std::optional TTieringActualizer::Bu targetTierName = tieringInfo.GetNextTierNameVerified(); } if (d) { + if (targetTierName != NTiering::NCommon::DeleteTierName) { + if (const auto op = StoragesManager->GetOperatorOptional(targetTierName); !op || !op->IsReady()) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "skip_eviction")("reason", "storage_not_ready")("tier", targetTierName)( + "portion", portion.GetPortionId()); + return std::nullopt; + } + } // if (currentTierName == "deploy_logs_s3" && targetTierName == IStoragesManager::DefaultStorageId) { // AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("tiering_info", tieringInfo.DebugString())("max", max->ToString())("now", now.ToString())("d", *d)("tiering", Tiering->GetDebugString())("pathId", PathId); // AFL_VERIFY(false)("tiering_info", tieringInfo.DebugString())("max", max->ToString())("now", now.ToString())("d", *d)("tiering", Tiering->GetDebugString())("pathId", PathId); @@ -73,25 +85,67 @@ std::optional TTieringActualizer::Bu return {}; } +void TTieringActualizer::AddPortionImpl(const TPortionInfo& portion, const TInstant now) { + auto info = BuildActualizationInfo(portion, now); + if (!info) { + return; + } + AFL_VERIFY(PortionIdByWaitDuration[info->GetAddress()].AddPortion(*info, portion.GetPortionId(), now)); + auto address = info->GetAddress(); + TFindActualizationInfo findId(std::move(address), info->GetWaitInstant(now)); + AFL_VERIFY(PortionsInfo.emplace(portion.GetPortionId(), std::move(findId)).second); +} + void TTieringActualizer::DoAddPortion(const TPortionInfo& portion, const TAddExternalContext& addContext) { + AFL_VERIFY(PathId == portion.GetPathId()); if (!addContext.GetPortionExclusiveGuarantee()) { if (PortionsInfo.contains(portion.GetPortionId())) { return; } } else { - AFL_VERIFY(!PortionsInfo.contains(portion.GetPortionId())); + AFL_VERIFY(!PortionsInfo.contains(portion.GetPortionId()))("id", portion.GetPortionId())("path_id", portion.GetPathId()); + AFL_VERIFY(!NewPortionIds.contains(portion.GetPortionId()))("id", portion.GetPortionId())("path_id", portion.GetPathId()); } - auto info = BuildActualizationInfo(portion, addContext.GetNow()); - if (!info) { + if (!Tiering || MaxByPortionId.contains(portion.GetPortionId())) { + AddPortionImpl(portion, addContext.GetNow()); + } else { + auto schema = portion.GetSchema(VersionedIndex); + if (*TValidator::CheckNotNull(TieringColumnId) == schema->GetIndexInfo().GetPKColumnIds().front()) { + NYDBTest::TControllers::GetColumnShardController()->OnMaxValueUsage(); + auto max = NArrow::TStatusValidator::GetValid(portion.GetMeta().GetFirstLastPK().GetFirst().Column(0).GetScalar(0)); + AFL_VERIFY(MaxByPortionId.emplace(portion.GetPortionId(), max).second); + AddPortionImpl(portion, addContext.GetNow()); + } else { + NewPortionIds.emplace(portion.GetPortionId()); + } + } +} + +void TTieringActualizer::ActualizePortionInfo(const TPortionDataAccessor& accessor, const TActualizationContext& context) { + if (!NewPortionIds.erase(accessor.GetPortionInfo().GetPortionId())) { return; } - AFL_VERIFY(PortionIdByWaitDuration[info->GetAddress()].AddPortion(*info, portion.GetPortionId(), addContext.GetNow())); - auto address = info->GetAddress(); - TFindActualizationInfo findId(std::move(address), info->GetWaitInstant(addContext.GetNow())); - AFL_VERIFY(PortionsInfo.emplace(portion.GetPortionId(), std::move(findId)).second); + if (NewPortionIds.empty()) { + NYDBTest::TControllers::GetColumnShardController()->OnTieringMetadataActualized(); + } + auto& portion = accessor.GetPortionInfo(); + if (Tiering) { + std::shared_ptr portionSchema = portion.GetSchema(VersionedIndex); + std::shared_ptr max; + AFL_VERIFY(*TieringColumnId != portionSchema->GetIndexInfo().GetPKColumnIds().front()); + if (auto indexMeta = portionSchema->GetIndexInfo().GetIndexMetaMax(*TieringColumnId)) { + NYDBTest::TControllers::GetColumnShardController()->OnStatisticsUsage(NIndexes::TIndexMetaContainer(indexMeta)); + const std::vector data = accessor.GetIndexInplaceDataVerified(indexMeta->GetIndexId()); + max = indexMeta->GetMaxScalarVerified(data, portionSchema->GetIndexInfo().GetColumnFieldVerified(*TieringColumnId)->type()); + } + AFL_VERIFY(MaxByPortionId.emplace(portion.GetPortionId(), max).second); + } + AddPortionImpl(portion, context.GetNow()); } void TTieringActualizer::DoRemovePortion(const ui64 portionId) { + MaxByPortionId.erase(portionId); + NewPortionIds.erase(portionId); auto it = PortionsInfo.find(portionId); if (it == PortionsInfo.end()) { return; @@ -104,7 +158,8 @@ void TTieringActualizer::DoRemovePortion(const ui64 portionId) { PortionsInfo.erase(it); } -void TTieringActualizer::DoExtractTasks(TTieringProcessContext& tasksContext, const TExternalTasksContext& externalContext, TInternalTasksContext& /*internalContext*/) { +void TTieringActualizer::DoExtractTasks( + TTieringProcessContext& tasksContext, const TExternalTasksContext& externalContext, TInternalTasksContext& /*internalContext*/) { THashSet portionIds; for (auto&& [address, addressPortions] : PortionIdByWaitDuration) { if (addressPortions.GetPortions().size() && tasksContext.GetActualInstant() < addressPortions.GetPortions().begin()->first) { @@ -121,20 +176,15 @@ void TTieringActualizer::DoExtractTasks(TTieringProcessContext& tasksContext, co } bool limitEnriched = false; for (auto&& p : portions) { - auto portion = externalContext.GetPortionVerified(p); - if (!address.WriteIs(NBlobOperations::TGlobal::DefaultStorageId) && !address.WriteIs(NTiering::NCommon::DeleteTierName)) { - if (!portion->HasRuntimeFeature(TPortionInfo::ERuntimeFeature::Optimized)) { - Counters.SkipEvictionForCompaction->Add(1); - continue; - } - } + const auto& portion = externalContext.GetPortionVerified(p); auto info = BuildActualizationInfo(*portion, tasksContext.GetActualInstant()); AFL_VERIFY(info); auto portionScheme = portion->GetSchema(VersionedIndex); - TPortionEvictionFeatures features(portionScheme, info->GetTargetScheme(), portion->GetTierNameDef(IStoragesManager::DefaultStorageId)); + TPortionEvictionFeatures features( + portionScheme, info->GetTargetScheme(), portion->GetTierNameDef(IStoragesManager::DefaultStorageId)); features.SetTargetTierName(info->GetTargetTierName()); - if (!tasksContext.AddPortion(*portion, std::move(features), info->GetLateness())) { + if (!tasksContext.AddPortion(portion, std::move(features), info->GetLateness())) { limitEnriched = true; break; } else { @@ -168,23 +218,78 @@ void TTieringActualizer::DoExtractTasks(TTieringProcessContext& tasksContext, co for (auto&& i : portionIds) { RemovePortion(i); } - } void TTieringActualizer::Refresh(const std::optional& info, const TAddExternalContext& externalContext) { Tiering = info; + std::optional newTieringColumnId; if (Tiering) { - TieringColumnId = VersionedIndex.GetLastSchema()->GetColumnId(Tiering->GetEvictColumnName()); - } else { - TieringColumnId = {}; + newTieringColumnId = VersionedIndex.GetLastSchema()->GetColumnId(Tiering->GetEvictColumnName()); } TargetCriticalSchema = VersionedIndex.GetLastCriticalSchema(); PortionsInfo.clear(); + NewPortionIds.clear(); PortionIdByWaitDuration.clear(); + if (newTieringColumnId != TieringColumnId) { + MaxByPortionId.clear(); + } + TieringColumnId = newTieringColumnId; for (auto&& i : externalContext.GetPortions()) { AddPortion(i.second, externalContext); } } +namespace { +class TActualizationReply: public IMetadataAccessorResultProcessor { +private: + std::weak_ptr TieringActualizer; + virtual void DoApplyResult(NResourceBroker::NSubscribe::TResourceContainer&& result, TColumnEngineForLogs& /*engine*/) override { + auto locked = TieringActualizer.lock(); + if (!locked) { + return; + } + TActualizationContext context(HasAppData() ? AppDataVerified().TimeProvider->Now() : TInstant::Now()); + for (auto&& [_, portion] : result.GetValue().GetPortions()) { + locked->ActualizePortionInfo(portion, context); + } + } + +public: + TActualizationReply(const std::shared_ptr& tieringActualizer) + : TieringActualizer(tieringActualizer) { + AFL_VERIFY(tieringActualizer); + } +}; + +} // namespace + +std::vector TTieringActualizer::BuildMetadataRequests( + const ui64 /*pathId*/, const THashMap& portions, const std::shared_ptr& index) { + if (NewPortionIds.empty()) { + NYDBTest::TControllers::GetColumnShardController()->OnTieringMetadataActualized(); + return {}; + } + + const ui64 batchMemorySoftLimit = NYDBTest::TControllers::GetColumnShardController()->GetMetadataRequestSoftMemoryLimit(); + std::vector requests; + std::shared_ptr currentRequest; + for (auto&& i : NewPortionIds) { + if (!currentRequest) { + currentRequest = std::make_shared("TIERING_ACTUALIZER"); + } + auto it = portions.find(i); + AFL_VERIFY(it != portions.end()); + currentRequest->AddPortion(it->second); + if (currentRequest->PredictAccessorsMemory(it->second->GetSchema(VersionedIndex)) >= batchMemorySoftLimit) { + requests.emplace_back(currentRequest, std::make_shared(index)); + currentRequest.reset(); + } + } + if (currentRequest) { + requests.emplace_back(std::move(currentRequest), std::make_shared(index)); + } + return requests; } + +} // namespace NKikimr::NOlap::NActualizer diff --git a/ydb/core/tx/columnshard/engines/storage/actualizer/tiering/tiering.h b/ydb/core/tx/columnshard/engines/storage/actualizer/tiering/tiering.h index fd982e5dcf24..83b4cd719330 100644 --- a/ydb/core/tx/columnshard/engines/storage/actualizer/tiering/tiering.h +++ b/ydb/core/tx/columnshard/engines/storage/actualizer/tiering/tiering.h @@ -1,12 +1,15 @@ #pragma once #include "counters.h" + +#include +#include #include #include -#include -#include +#include namespace NKikimr::NOlap { class TTiering; +class TCSMetadataRequest; } namespace NKikimr::NOlap::NActualizer { @@ -115,26 +118,36 @@ class TTieringActualizer: public IActualizer { std::shared_ptr TargetCriticalSchema; const ui64 PathId; const TVersionedIndex& VersionedIndex; + const std::shared_ptr& StoragesManager; THashMap PortionIdByWaitDuration; THashMap PortionsInfo; + THashSet NewPortionIds; + THashMap> MaxByPortionId; std::shared_ptr GetTargetSchema(const std::shared_ptr& portionSchema) const; std::optional BuildActualizationInfo(const TPortionInfo& portion, const TInstant now) const; + void AddPortionImpl(const TPortionInfo& portion, const TInstant now); + virtual void DoAddPortion(const TPortionInfo& portion, const TAddExternalContext& addContext) override; virtual void DoRemovePortion(const ui64 portionId) override; virtual void DoExtractTasks(TTieringProcessContext& tasksContext, const TExternalTasksContext& externalContext, TInternalTasksContext& internalContext) override; - public: + void ActualizePortionInfo(const TPortionDataAccessor& accessor, const TActualizationContext& context); + std::vector BuildMetadataRequests( + const ui64 pathId, const THashMap& portions, const std::shared_ptr& index); + void Refresh(const std::optional& info, const TAddExternalContext& externalContext); - TTieringActualizer(const ui64 pathId, const TVersionedIndex& versionedIndex) + TTieringActualizer(const ui64 pathId, const TVersionedIndex& versionedIndex, const std::shared_ptr& storagesManager) : PathId(pathId) , VersionedIndex(versionedIndex) + , StoragesManager(storagesManager) { Y_UNUSED(PathId); + AFL_VERIFY(StoragesManager); } }; diff --git a/ydb/core/tx/columnshard/engines/storage/chunks/column.h b/ydb/core/tx/columnshard/engines/storage/chunks/column.h index 9de818c49fb6..7d010d3c4158 100644 --- a/ydb/core/tx/columnshard/engines/storage/chunks/column.h +++ b/ydb/core/tx/columnshard/engines/storage/chunks/column.h @@ -20,7 +20,7 @@ class TChunkPreparation: public IPortionColumnChunk { return Data; } virtual ui32 DoGetRecordsCountImpl() const override { - return Record.GetMeta().GetNumRows(); + return Record.GetMeta().GetRecordsCount(); } virtual ui64 DoGetRawBytesImpl() const override { return Record.GetMeta().GetRawBytes(); @@ -59,11 +59,13 @@ class TChunkPreparation: public IPortionColumnChunk { TChunkPreparation(const TString& data, const std::shared_ptr& column, const TChunkAddress& address, const TSimpleColumnInfo& columnInfo) : TBase(address.GetColumnId()) , Data(data) - , Record(address, column, columnInfo) + , Record(address, column) , ColumnInfo(columnInfo) { Y_ABORT_UNLESS(column->GetRecordsCount()); - First = column->GetScalar(0); - Last = column->GetScalar(column->GetRecordsCount() - 1); + if (ColumnInfo.GetPKColumnIndex()) { + First = column->GetScalar(0); + Last = column->GetScalar(column->GetRecordsCount() - 1); + } Record.BlobRange.Size = data.size(); } }; diff --git a/ydb/core/tx/columnshard/engines/storage/chunks/data.cpp b/ydb/core/tx/columnshard/engines/storage/chunks/data.cpp index 007dff83e914..a95732b1c2af 100644 --- a/ydb/core/tx/columnshard/engines/storage/chunks/data.cpp +++ b/ydb/core/tx/columnshard/engines/storage/chunks/data.cpp @@ -1,10 +1,10 @@ #include "data.h" #include -#include +#include namespace NKikimr::NOlap::NChunks { -void TPortionIndexChunk::DoAddIntoPortionBeforeBlob(const TBlobRangeLink16& bRange, TPortionInfoConstructor& portionInfo) const { +void TPortionIndexChunk::DoAddIntoPortionBeforeBlob(const TBlobRangeLink16& bRange, TPortionAccessorConstructor& portionInfo) const { AFL_VERIFY(!bRange.IsValid()); portionInfo.AddIndex(TIndexChunk(GetEntityId(), GetChunkIdxVerified(), RecordsCount, RawBytes, bRange)); } @@ -14,7 +14,7 @@ std::shared_ptr TPortionIndexChunk::DoCopyWithAnotherBlob( return std::make_shared(GetChunkAddressVerified(), RecordsCount, RawBytes, std::move(data)); } -void TPortionIndexChunk::DoAddInplaceIntoPortion(TPortionInfoConstructor& portionInfo) const { +void TPortionIndexChunk::DoAddInplaceIntoPortion(TPortionAccessorConstructor& portionInfo) const { portionInfo.AddIndex(TIndexChunk(GetEntityId(), GetChunkIdxVerified(), RecordsCount, RawBytes, GetData())); } diff --git a/ydb/core/tx/columnshard/engines/storage/chunks/data.h b/ydb/core/tx/columnshard/engines/storage/chunks/data.h index e3f22ae2ed9d..4a4510a14e41 100644 --- a/ydb/core/tx/columnshard/engines/storage/chunks/data.h +++ b/ydb/core/tx/columnshard/engines/storage/chunks/data.h @@ -35,9 +35,9 @@ class TPortionIndexChunk: public IPortionDataChunk { virtual std::shared_ptr DoGetLastScalar() const override { return nullptr; } - virtual void DoAddIntoPortionBeforeBlob(const TBlobRangeLink16& bRange, TPortionInfoConstructor& portionInfo) const override; + virtual void DoAddIntoPortionBeforeBlob(const TBlobRangeLink16& bRange, TPortionAccessorConstructor& portionInfo) const override; virtual std::shared_ptr DoCopyWithAnotherBlob(TString&& data, const TSimpleColumnInfo& /*columnInfo*/) const override; - virtual void DoAddInplaceIntoPortion(TPortionInfoConstructor& portionInfo) const override; + virtual void DoAddInplaceIntoPortion(TPortionAccessorConstructor& portionInfo) const override; public: TPortionIndexChunk(const TChunkAddress& address, const ui32 recordsCount, const ui64 rawBytes, const TString& data) diff --git a/ydb/core/tx/columnshard/engines/storage/granule.cpp b/ydb/core/tx/columnshard/engines/storage/granule.cpp deleted file mode 100644 index c8c704bfa40d..000000000000 --- a/ydb/core/tx/columnshard/engines/storage/granule.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "granule.h" diff --git a/ydb/core/tx/columnshard/engines/storage/granule.h b/ydb/core/tx/columnshard/engines/storage/granule.h deleted file mode 100644 index 49587a09545e..000000000000 --- a/ydb/core/tx/columnshard/engines/storage/granule.h +++ /dev/null @@ -1,2 +0,0 @@ -#pragma once -#include "granule/granule.h" diff --git a/ydb/core/tx/columnshard/engines/storage/granule/granule.cpp b/ydb/core/tx/columnshard/engines/storage/granule/granule.cpp index ebb2b9acde63..7dbb009ccf7c 100644 --- a/ydb/core/tx/columnshard/engines/storage/granule/granule.cpp +++ b/ydb/core/tx/columnshard/engines/storage/granule/granule.cpp @@ -1,32 +1,33 @@ #include "granule.h" +#include "stages.h" #include "storage.h" -#include -#include -#include #include +#include +#include #include +#include +#include + +#include namespace NKikimr::NOlap { -void TGranuleMeta::UpsertPortion(const TPortionInfo& info) { - AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "upsert_portion")("portion", info.DebugString())("path_id", GetPathId()); - auto it = Portions.find(info.GetPortion()); - AFL_VERIFY(info.GetPathId() == GetPathId())("event", "incompatible_granule")("portion", info.DebugString())("path_id", GetPathId()); +void TGranuleMeta::AppendPortion(const TPortionDataAccessor& info, const bool addAsAccessor) { + AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "upsert_portion")("portion", info.GetPortionInfo().DebugString())( + "path_id", GetPathId()); + auto it = Portions.find(info.GetPortionInfo().GetPortionId()); + AFL_VERIFY(info.GetPortionInfo().GetPathId() == GetPathId())("event", "incompatible_granule")( + "portion", info.GetPortionInfo().DebugString())("path_id", GetPathId()); - AFL_VERIFY(info.Valid())("event", "invalid_portion")("portion", info.DebugString()); - AFL_VERIFY(info.ValidSnapshotInfo())("event", "incorrect_portion_snapshots")("portion", info.DebugString()); - for (auto& record : info.Records) { - AFL_VERIFY(record.Valid())("event", "incorrect_record")("record", record.DebugString())("portion", info.DebugString()); - } + AFL_VERIFY(info.GetPortionInfo().ValidSnapshotInfo())("event", "incorrect_portion_snapshots")( + "portion", info.GetPortionInfo().DebugString()); - if (it == Portions.end()) { - OnBeforeChangePortion(nullptr); - auto portionNew = std::make_shared(info); - it = Portions.emplace(portionNew->GetPortion(), portionNew).first; - } else { - OnBeforeChangePortion(it->second); - it->second = std::make_shared(info); + AFL_VERIFY(it == Portions.end()); + OnBeforeChangePortion(nullptr); + it = Portions.emplace(info.GetPortionInfo().GetPortionId(), info.MutablePortionInfoPtr()).first; + if (addAsAccessor) { + DataAccessorsManager->AddPortion(info); } OnAfterChangePortion(it->second, nullptr); } @@ -39,13 +40,15 @@ bool TGranuleMeta::ErasePortion(const ui64 portion) { } else { AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "portion_erased")("portion_info", it->second->DebugString())("pathId", PathId); } + DataAccessorsManager->RemovePortion(it->second); OnBeforeChangePortion(it->second); Portions.erase(it); OnAfterChangePortion(nullptr, nullptr); return true; } -void TGranuleMeta::OnAfterChangePortion(const std::shared_ptr portionAfter, NStorageOptimizer::IOptimizerPlanner::TModificationGuard* modificationGuard) { +void TGranuleMeta::OnAfterChangePortion(const std::shared_ptr portionAfter, + NStorageOptimizer::IOptimizerPlanner::TModificationGuard* modificationGuard, const bool onLoad) { if (portionAfter) { PortionInfoGuard.OnNewPortion(portionAfter); if (!portionAfter->HasRemoveSnapshot()) { @@ -56,7 +59,9 @@ void TGranuleMeta::OnAfterChangePortion(const std::shared_ptr port OptimizerPlanner->StartModificationGuard().AddPortion(portionAfter); } NActualizer::TAddExternalContext context(HasAppData() ? AppDataVerified().TimeProvider->Now() : TInstant::Now(), Portions); - ActualizationIndex->AddPortion(portionAfter, context); + if (!onLoad) { + ActualizationIndex->AddPortion(portionAfter, context); + } } Stats->OnAddPortion(*portionAfter); } @@ -91,22 +96,18 @@ void TGranuleMeta::OnBeforeChangePortion(const std::shared_ptr por void TGranuleMeta::OnCompactionFinished() { AllowInsertionFlag = false; - Y_ABORT_UNLESS(Activity.erase(EActivity::GeneralCompaction)); AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "OnCompactionFinished")("info", DebugString()); Stats->UpdateGranuleInfo(*this); } void TGranuleMeta::OnCompactionFailed(const TString& reason) { AllowInsertionFlag = false; - Y_ABORT_UNLESS(Activity.erase(EActivity::GeneralCompaction)); AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "OnCompactionFailed")("reason", reason)("info", DebugString()); Stats->UpdateGranuleInfo(*this); } void TGranuleMeta::OnCompactionStarted() { AllowInsertionFlag = false; - Y_ABORT_UNLESS(Activity.empty()); - Activity.emplace(EActivity::GeneralCompaction); } void TGranuleMeta::RebuildAdditiveMetrics() const { @@ -130,29 +131,38 @@ const NKikimr::NOlap::TGranuleAdditiveSummary& TGranuleMeta::GetAdditiveSummary( return *AdditiveSummaryCache; } -TGranuleMeta::TGranuleMeta(const ui64 pathId, const TGranulesStorage& owner, const NColumnShard::TGranuleDataCounters& counters, const TVersionedIndex& versionedIndex) +TGranuleMeta::TGranuleMeta( + const ui64 pathId, const TGranulesStorage& owner, const NColumnShard::TGranuleDataCounters& counters, const TVersionedIndex& versionedIndex) : PathId(pathId) + , DataAccessorsManager(owner.GetDataAccessorsManager()) , Counters(counters) , PortionInfoGuard(owner.GetCounters().BuildPortionBlobsGuard()) , Stats(owner.GetStats()) , StoragesManager(owner.GetStoragesManager()) , PortionsIndex(*this, Counters.GetPortionsIndexCounters()) { - NStorageOptimizer::IOptimizerPlannerConstructor::TBuildContext context(PathId, owner.GetStoragesManager(), versionedIndex.GetLastSchema()->GetIndexInfo().GetPrimaryKey()); + NStorageOptimizer::IOptimizerPlannerConstructor::TBuildContext context( + PathId, owner.GetStoragesManager(), versionedIndex.GetLastSchema()->GetIndexInfo().GetPrimaryKey()); OptimizerPlanner = versionedIndex.GetLastSchema()->GetIndexInfo().GetCompactionPlannerConstructor()->BuildPlanner(context).DetachResult(); + NDataAccessorControl::TManagerConstructionContext mmContext(DataAccessorsManager->GetTabletActorId(), false); + ResetAccessorsManager(versionedIndex.GetLastSchema()->GetIndexInfo().GetMetadataManagerConstructor(), mmContext); AFL_VERIFY(!!OptimizerPlanner); - ActualizationIndex = std::make_shared(PathId, versionedIndex); - + ActualizationIndex = std::make_unique(PathId, versionedIndex, StoragesManager); } -std::shared_ptr TGranuleMeta::UpsertPortionOnLoad(TPortionInfo&& portion) { - auto portionId = portion.GetPortionId(); - auto emplaceInfo = Portions.emplace(portionId, std::make_shared(std::move(portion))); - AFL_VERIFY(emplaceInfo.second); - return emplaceInfo.first->second; +void TGranuleMeta::UpsertPortionOnLoad(const std::shared_ptr& portion) { + if (portion->HasInsertWriteId() && !portion->HasCommitSnapshot()) { + const TInsertWriteId insertWriteId = portion->GetInsertWriteIdVerified(); + AFL_VERIFY(InsertedPortions.emplace(insertWriteId, portion).second); + AFL_VERIFY(!Portions.contains(portion->GetPortionId())); + } else { + auto portionId = portion->GetPortionId(); + AFL_VERIFY(Portions.emplace(portionId, portion).second); + } } void TGranuleMeta::BuildActualizationTasks(NActualizer::TTieringProcessContext& context, const TDuration actualizationLag) const { if (context.GetActualInstant() < NextActualizations) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "skip_actualization")("waiting", NextActualizations - context.GetActualInstant()); return; } NActualizer::TExternalTasksContext extTasks(Portions); @@ -160,7 +170,14 @@ void TGranuleMeta::BuildActualizationTasks(NActualizer::TTieringProcessContext& NextActualizations = context.GetActualInstant() + actualizationLag; } -void TGranuleMeta::ResetOptimizer(const std::shared_ptr& constructor, std::shared_ptr& storages, const std::shared_ptr& pkSchema) { +void TGranuleMeta::ResetAccessorsManager(const std::shared_ptr& constructor, + const NDataAccessorControl::TManagerConstructionContext& context) { + MetadataMemoryManager = constructor->Build(context).DetachResult(); + DataAccessorsManager->RegisterController(MetadataMemoryManager->BuildCollector(PathId), context.IsUpdate()); +} + +void TGranuleMeta::ResetOptimizer(const std::shared_ptr& constructor, + std::shared_ptr& storages, const std::shared_ptr& pkSchema) { if (constructor->ApplyToCurrentObject(OptimizerPlanner)) { return; } @@ -176,5 +193,127 @@ void TGranuleMeta::ResetOptimizer(const std::shared_ptrModifyPortions(portions, {}); } +/* + +void TGranuleMeta::ResetMetadataManager(const std::shared_ptr& constructor, + std::shared_ptr& storages, const std::shared_ptr& pkSchema) { + if (constructor->ApplyToCurrentObject(MetadataMemoryManager)) { + return; + } + NStorageOptimizer::IManagerConstructor::TBuildContext context(PathId, storages, pkSchema); + MetadataMemoryManager = constructor->Build(context).DetachResult(); + AFL_VERIFY(!!OptimizerPlanner); + THashMap> portions; + for (auto&& i : Portions) { + if (i.second->HasRemoveSnapshot()) { + continue; + } + portions.emplace(i.first, i.second); + } + OptimizerPlanner->ModifyPortions(portions, {}); +} +*/ + +std::shared_ptr TGranuleMeta::BuildLoader( + const std::shared_ptr& dsGroupSelector, const TVersionedIndex& vIndex) { + auto portionsLoader = std::make_shared("portions", &vIndex, this, dsGroupSelector); + auto metadataLoader = MetadataMemoryManager->BuildLoader(vIndex, this, dsGroupSelector); + auto commonFinish = std::make_shared("granule_finished_common", this); + + auto result = std::make_shared("granule"); + result->AddChildren(portionsLoader); + if (metadataLoader) { + result->AddChildren(metadataLoader); + } + result->AddChildren(commonFinish); + return result; +} + +bool TGranuleMeta::TestingLoad(IDbWrapper& db, const TVersionedIndex& versionedIndex) { + TInGranuleConstructors constructors; + { + if (!db.LoadPortions(PathId, [&](TPortionInfoConstructor&& portion, const NKikimrTxColumnShard::TIndexPortionMeta& metaProto) { + const TIndexInfo& indexInfo = portion.GetSchema(versionedIndex)->GetIndexInfo(); + AFL_VERIFY(portion.MutableMeta().LoadMetadata(metaProto, indexInfo, db.GetDsGroupSelectorVerified())); + AFL_VERIFY(constructors.AddConstructorVerified(std::move(portion))); + })) { + return false; + } + } + + { + TPortionInfo::TSchemaCursor schema(versionedIndex); + if (!db.LoadColumns(PathId, [&](TColumnChunkLoadContextV2&& loadContext) { + auto* constructor = constructors.GetConstructorVerified(loadContext.GetPortionId()); + for (auto&& i : loadContext.BuildRecordsV1()) { + constructor->LoadRecord(std::move(i)); + } + })) { + return false; + } + } + + { + if (!db.LoadIndexes(PathId, [&](const ui64 /*pathId*/, const ui64 portionId, TIndexChunkLoadContext&& loadContext) { + auto* constructor = constructors.GetConstructorVerified(portionId); + constructor->LoadIndex(std::move(loadContext)); + })) { + return false; + }; + } + for (auto&& [portionId, constructor] : constructors) { + auto accessor = constructor.Build(false); + DataAccessorsManager->AddPortion(accessor); + UpsertPortionOnLoad(accessor.MutablePortionInfoPtr()); + } + return true; +} + +void TGranuleMeta::InsertPortionOnComplete(const TPortionDataAccessor& portion, IColumnEngine& /*engine*/) { + AFL_VERIFY(InsertedPortions.emplace(portion.GetPortionInfo().GetInsertWriteIdVerified(), portion.MutablePortionInfoPtr()).second); + AFL_VERIFY(InsertedAccessors.emplace(portion.GetPortionInfo().GetInsertWriteIdVerified(), portion).second); + DataAccessorsManager->AddPortion(portion); +} + +void TGranuleMeta::InsertPortionOnExecute(NTabletFlatExecutor::TTransactionContext& txc, const TPortionDataAccessor& portion) const { + AFL_VERIFY(!InsertedPortions.contains(portion.GetPortionInfo().GetInsertWriteIdVerified())); + TDbWrapper wrapper(txc.DB, nullptr); + portion.SaveToDatabase(wrapper, 0, false); +} + +void TGranuleMeta::CommitPortionOnExecute( + NTabletFlatExecutor::TTransactionContext& txc, const TInsertWriteId insertWriteId, const TSnapshot& snapshot) const { + auto it = InsertedPortions.find(insertWriteId); + AFL_VERIFY(it != InsertedPortions.end()); + it->second->SetCommitSnapshot(snapshot); + TDbWrapper wrapper(txc.DB, nullptr); + it->second->SaveMetaToDatabase(wrapper); +} + +void TGranuleMeta::CommitPortionOnComplete(const TInsertWriteId insertWriteId, IColumnEngine& engine) { + auto it = InsertedPortions.find(insertWriteId); + AFL_VERIFY(it != InsertedPortions.end()); + InsertedPortions.erase(it); + { + auto it = InsertedAccessors.find(insertWriteId); + if (it != InsertedAccessors.end()) { + (static_cast(&engine))->AppendPortion(it->second, false); + InsertedAccessors.erase(it); + } + } +} + +void TGranuleMeta::CommitImmediateOnExecute( + NTabletFlatExecutor::TTransactionContext& txc, const TSnapshot& snapshot, const TPortionDataAccessor& portion) const { + AFL_VERIFY(!InsertedPortions.contains(portion.GetPortionInfo().GetInsertWriteIdVerified())); + portion.MutablePortionInfo().SetCommitSnapshot(snapshot); + TDbWrapper wrapper(txc.DB, nullptr); + portion.SaveToDatabase(wrapper, 0, false); +} + +void TGranuleMeta::CommitImmediateOnComplete(const std::shared_ptr /*portion*/, IColumnEngine& /*engine*/) { + AFL_VERIFY(false); + // (static_cast(engine)).AppendPortion(portion); +} -} // namespace NKikimr::NOlap +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/storage/granule/granule.h b/ydb/core/tx/columnshard/engines/storage/granule/granule.h index d79ef50e1883..e5ead2a7ab47 100644 --- a/ydb/core/tx/columnshard/engines/storage/granule/granule.h +++ b/ydb/core/tx/columnshard/engines/storage/granule/granule.h @@ -1,48 +1,39 @@ #pragma once #include "portions_index.h" -#include -#include - +#include +#include #include +#include +#include #include #include +#include +#include #include -#include -#include - namespace NKikimr::NOlap { +namespace NLoading { +class TPortionsLoadContext; +} + class TGranulesStorage; class TGranulesStat; class TColumnChunkLoadContext; +class TVersionedIndex; class TDataClassSummary: public NColumnShard::TBaseGranuleDataClassSummary { private: friend class TGranuleMeta; - THashMap ColumnStats; public: - const THashMap& GetColumnStats() const { - return ColumnStats; - } - void AddPortion(const TPortionInfo& info) { ColumnPortionsSize += info.GetColumnBlobBytes(); TotalPortionsSize += info.GetTotalBlobBytes(); MetadataMemoryPortionsSize += info.GetMetadataMemorySize(); - RecordsCount += info.NumRows(); + RecordsCount += info.GetRecordsCount(); ++PortionsCount; - - for (auto&& c : info.Records) { - auto it = ColumnStats.find(c.ColumnId); - if (it == ColumnStats.end()) { - it = ColumnStats.emplace(c.ColumnId, c.GetSerializationStat()).first; - } else { - it->second.AddStat(c.GetSerializationStat()); - } - } } void RemovePortion(const TPortionInfo& info) { @@ -52,19 +43,10 @@ class TDataClassSummary: public NColumnShard::TBaseGranuleDataClassSummary { Y_ABORT_UNLESS(ColumnPortionsSize >= 0); TotalPortionsSize -= info.GetTotalBlobBytes(); Y_ABORT_UNLESS(TotalPortionsSize >= 0); - RecordsCount -= info.NumRows(); + RecordsCount -= info.GetRecordsCount(); Y_ABORT_UNLESS(RecordsCount >= 0); --PortionsCount; Y_ABORT_UNLESS(PortionsCount >= 0); - - for (auto&& c : info.Records) { - auto it = ColumnStats.find(c.ColumnId); - if (it == ColumnStats.end()) { - it = ColumnStats.emplace(c.ColumnId, c.GetSerializationStat()).first; - } else { - it->second.RemoveStat(c.GetSerializationStat()); - } - } } }; @@ -73,6 +55,7 @@ class TGranuleAdditiveSummary { TDataClassSummary Inserted; TDataClassSummary Compacted; friend class TGranuleMeta; + public: const TDataClassSummary& GetInserted() const { return Inserted; @@ -94,12 +77,11 @@ class TGranuleAdditiveSummary { private: const NColumnShard::TGranuleDataCounters& Counters; TGranuleAdditiveSummary& Owner; + public: TEditGuard(const NColumnShard::TGranuleDataCounters& counters, TGranuleAdditiveSummary& owner) : Counters(counters) - , Owner(owner) - { - + , Owner(owner) { } ~TEditGuard() { @@ -107,14 +89,14 @@ class TGranuleAdditiveSummary { } void AddPortion(const TPortionInfo& info) { - if (info.IsInserted()) { + if (info.GetMeta().GetProduced() == NPortion::EProduced::INSERTED) { Owner.Inserted.AddPortion(info); } else { Owner.Compacted.AddPortion(info); } } void RemovePortion(const TPortionInfo& info) { - if (info.IsInserted()) { + if (info.GetMeta().GetProduced() == NPortion::EProduced::INSERTED) { Owner.Inserted.RemovePortion(info); } else { Owner.Compacted.RemovePortion(info); @@ -132,47 +114,134 @@ class TGranuleAdditiveSummary { }; class TGranuleMeta: TNonCopyable { -public: - enum class EActivity { - GeneralCompaction - }; - private: TMonotonic ModificationLastTime = TMonotonic::Now(); THashMap> Portions; + THashMap> InsertedPortions; + THashMap InsertedAccessors; mutable std::optional AdditiveSummaryCache; void RebuildHardMetrics() const; void RebuildAdditiveMetrics() const; - std::set Activity; mutable bool AllowInsertionFlag = false; const ui64 PathId; + std::shared_ptr DataAccessorsManager; const NColumnShard::TGranuleDataCounters Counters; NColumnShard::TEngineLogsCounters::TPortionsInfoGuard PortionInfoGuard; std::shared_ptr Stats; std::shared_ptr StoragesManager; std::shared_ptr OptimizerPlanner; - std::shared_ptr ActualizationIndex; + std::shared_ptr MetadataMemoryManager; + std::unique_ptr ActualizationIndex; mutable TInstant NextActualizations = TInstant::Zero(); NGranule::NPortionsIndex::TPortionsIndex PortionsIndex; void OnBeforeChangePortion(const std::shared_ptr portionBefore); - void OnAfterChangePortion(const std::shared_ptr portionAfter, NStorageOptimizer::IOptimizerPlanner::TModificationGuard* modificationGuard); + void OnAfterChangePortion( + const std::shared_ptr portionAfter, NStorageOptimizer::IOptimizerPlanner::TModificationGuard* modificationGuard, const bool onLoad = false); void OnAdditiveSummaryChange() const; YDB_READONLY(TMonotonic, LastCompactionInstant, TMonotonic::Zero()); + + TConclusion> GetInnerPortion(const TPortionInfo::TConstPtr& portion) const { + if (!portion) { + return TConclusionStatus::Fail("empty input portion pointer"); + } + auto it = Portions.find(portion->GetPortionId()); + if (it == Portions.end()) { + return TConclusionStatus::Fail("portion id is incorrect: " + ::ToString(portion->GetPortionId())); + } + if (portion->GetPathId() != GetPathId()) { + return TConclusionStatus::Fail( + "portion path_id is incorrect: " + ::ToString(portion->GetPathId()) + " != " + ::ToString(GetPathId())); + } + return it->second; + } + bool DataAccessorConstructed = false; + public: + std::vector CollectMetadataRequests() { + return ActualizationIndex->CollectMetadataRequests(Portions); + } + + const NStorageOptimizer::IOptimizerPlanner& GetOptimizerPlanner() const { + return *OptimizerPlanner; + } + + std::shared_ptr BuildLoader(const std::shared_ptr& dsGroupSelector, const TVersionedIndex& vIndex); + bool TestingLoad(IDbWrapper& db, const TVersionedIndex& versionedIndex); + const std::shared_ptr& GetDataAccessorsManager() const { + return DataAccessorsManager; + } + + std::unique_ptr BuildDataAccessor() { + AFL_VERIFY(!DataAccessorConstructed); + DataAccessorConstructed = true; + return MetadataMemoryManager->BuildCollector(PathId); + } + void RefreshTiering(const std::optional& tiering) { NActualizer::TAddExternalContext context(HasAppData() ? AppDataVerified().TimeProvider->Now() : TInstant::Now(), Portions); ActualizationIndex->RefreshTiering(tiering, context); } + template + void ModifyPortionOnExecute( + IDbWrapper& wrapper, const TPortionDataAccessor& portion, const TModifier& modifier, const ui32 firstPKColumnId) const { + const auto innerPortion = GetInnerPortion(portion.GetPortionInfoPtr()).DetachResult(); + AFL_VERIFY((ui64)innerPortion.get() == (ui64)&portion.GetPortionInfo()); + auto copy = innerPortion->MakeCopy(); + modifier(copy); + if (!HasAppData() || AppDataVerified().ColumnShardConfig.GetColumnChunksV0Usage()) { + auto accessorCopy = portion.SwitchPortionInfo(std::move(copy)); + accessorCopy.SaveToDatabase(wrapper, firstPKColumnId, false); + } else { + copy.SaveMetaToDatabase(wrapper); + } + } + + template + void ModifyPortionOnComplete(const TPortionInfo::TConstPtr& portion, const TModifier& modifier) { + const auto innerPortion = GetInnerPortion(portion).DetachResult(); + AFL_VERIFY((ui64)innerPortion.get() == (ui64)portion.get()); + OnBeforeChangePortion(innerPortion); + modifier(innerPortion); + OnAfterChangePortion(innerPortion, nullptr); + } + + void InsertPortionOnExecute(NTabletFlatExecutor::TTransactionContext& txc, const TPortionDataAccessor& portion) const; + void InsertPortionOnComplete(const TPortionDataAccessor& portion, IColumnEngine& engine); + + void CommitPortionOnExecute( + NTabletFlatExecutor::TTransactionContext& txc, const TInsertWriteId insertWriteId, const TSnapshot& snapshot) const; + void CommitPortionOnComplete(const TInsertWriteId insertWriteId, IColumnEngine& engine); + + void AbortPortionOnExecute(NTabletFlatExecutor::TTransactionContext& txc, const TInsertWriteId insertWriteId, const TSnapshot ssRemove) const { + auto it = InsertedPortions.find(insertWriteId); + AFL_VERIFY(it != InsertedPortions.end()); + it->second->SetCommitSnapshot(ssRemove); + it->second->SetRemoveSnapshot(ssRemove); + TDbWrapper wrapper(txc.DB, nullptr); + it->second->SaveMetaToDatabase(wrapper); + } + + void AbortPortionOnComplete(const TInsertWriteId insertWriteId, IColumnEngine& engine) { + CommitPortionOnComplete(insertWriteId, engine); + } + + void CommitImmediateOnExecute( + NTabletFlatExecutor::TTransactionContext& txc, const TSnapshot& snapshot, const TPortionDataAccessor& portion) const; + void CommitImmediateOnComplete(const std::shared_ptr portion, IColumnEngine& engine); + std::vector GetOptimizerTasksDescription() const { return OptimizerPlanner->GetTasksDescription(); } - void ResetOptimizer(const std::shared_ptr& constructor, std::shared_ptr& storages, const std::shared_ptr& pkSchema); + void ResetOptimizer(const std::shared_ptr& constructor, + std::shared_ptr& storages, const std::shared_ptr& pkSchema); + void ResetAccessorsManager(const std::shared_ptr& constructor, + const NDataAccessorControl::TManagerConstructionContext& context); void RefreshScheme() { NActualizer::TAddExternalContext context(HasAppData() ? AppDataVerified().TimeProvider->Now() : TInstant::Now(), Portions); @@ -207,7 +276,8 @@ class TGranuleMeta: TNonCopyable { void BuildActualizationTasks(NActualizer::TTieringProcessContext& context, const TDuration actualizationLag) const; - std::shared_ptr GetOptimizationTask(std::shared_ptr self, const std::shared_ptr& locksManager) const { + std::shared_ptr GetOptimizationTask( + std::shared_ptr self, const std::shared_ptr& locksManager) const { return OptimizerPlanner->GetOptimizationTask(self, locksManager); } @@ -228,19 +298,20 @@ class TGranuleMeta: TNonCopyable { void OnAfterPortionsLoad() { auto g = OptimizerPlanner->StartModificationGuard(); for (auto&& i : Portions) { - OnAfterChangePortion(i.second, &g); + OnAfterChangePortion(i.second, &g, true); } - } + if (MetadataMemoryManager->NeedPrefetch() && Portions.size()) { + auto request = std::make_shared("PREFETCH_GRANULE::" + ::ToString(PathId)); + for (auto&& p : Portions) { + request->AddPortion(p.second); + } + request->RegisterSubscriber(std::make_shared()); - std::shared_ptr BuildSerializationStats(ISnapshotSchema::TPtr schema) const { - auto result = std::make_shared(); - for (auto&& i : GetAdditiveSummary().GetCompacted().GetColumnStats()) { - auto field = schema->GetFieldByColumnIdVerified(i.first); - NArrow::NSplitter::TColumnSerializationStat columnInfo(i.first, field->name()); - columnInfo.Merge(i.second); - result->AddStat(columnInfo); + DataAccessorsManager->AskData(request); + } + if (ActualizationIndex->IsStarted()) { + RefreshScheme(); } - return result; } const TGranuleAdditiveSummary& GetAdditiveSummary() const; @@ -249,10 +320,6 @@ class TGranuleMeta: TNonCopyable { return OptimizerPlanner->GetUsefulMetric(); } - bool IsLockedOptimizer(const std::shared_ptr& dataLocksManager) const { - return OptimizerPlanner->IsLocked(dataLocksManager); - } - void ActualizeOptimizer(const TInstant currentInstant, const TDuration recalcLag) const { if (OptimizerPlanner->GetActualizationInstant() + recalcLag < currentInstant) { OptimizerPlanner->Actualize(currentInstant); @@ -260,7 +327,7 @@ class TGranuleMeta: TNonCopyable { } bool IsErasable() const { - return Activity.empty() && Portions.empty(); + return Portions.empty(); } void OnCompactionStarted(); @@ -268,23 +335,26 @@ class TGranuleMeta: TNonCopyable { void OnCompactionFailed(const TString& reason); void OnCompactionFinished(); - void UpsertPortion(const TPortionInfo& info); + void AppendPortion(const TPortionDataAccessor& info, const bool addAsAccessor = true); TString DebugString() const { return TStringBuilder() << "(granule:" << GetPathId() << ";" - << "path_id:" << GetPathId() << ";" - << "size:" << GetAdditiveSummary().GetGranuleSize() << ";" - << "portions_count:" << Portions.size() << ";" - << ")" - ; + << "path_id:" << GetPathId() << ";" + << "size:" << GetAdditiveSummary().GetGranuleSize() << ";" + << "portions_count:" << Portions.size() << ";" + << ")"; } - std::shared_ptr UpsertPortionOnLoad(TPortionInfo&& portion); + void UpsertPortionOnLoad(const std::shared_ptr& portion); const THashMap>& GetPortions() const { return Portions; } + const THashMap>& GetInsertedPortions() const { + return InsertedPortions; + } + std::vector> GetPortionsVector() const { std::vector> result; for (auto&& i : Portions) { @@ -313,9 +383,12 @@ class TGranuleMeta: TNonCopyable { bool ErasePortion(const ui64 portion); - explicit TGranuleMeta(const ui64 pathId, const TGranulesStorage& owner, const NColumnShard::TGranuleDataCounters& counters, const TVersionedIndex& versionedIndex); + explicit TGranuleMeta(const ui64 pathId, const TGranulesStorage& owner, const NColumnShard::TGranuleDataCounters& counters, + const TVersionedIndex& versionedIndex); - bool Empty() const noexcept { return Portions.empty(); } + bool Empty() const noexcept { + return Portions.empty(); + } }; -} // namespace NKikimr::NOlap +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/storage/granule/portions_index.cpp b/ydb/core/tx/columnshard/engines/storage/granule/portions_index.cpp index e56487e5f8ef..a8b0c2eb59cb 100644 --- a/ydb/core/tx/columnshard/engines/storage/granule/portions_index.cpp +++ b/ydb/core/tx/columnshard/engines/storage/granule/portions_index.cpp @@ -3,114 +3,26 @@ namespace NKikimr::NOlap::NGranule::NPortionsIndex { -TPortionsIndex::TPortionIntervals TPortionsIndex::GetIntervalFeatures(const TPortionInfo& inputPortion, const THashSet& skipPortions) const { - auto itFrom = Points.find(inputPortion.IndexKeyStart()); - AFL_VERIFY(itFrom != Points.end()); - auto itTo = Points.find(inputPortion.IndexKeyEnd()); - AFL_VERIFY(itTo != Points.end()); - TPortionIntervals portionExcludeIntervals; - while (true) { - std::optional nextKey; - for (auto&& [p, _] : itFrom->second.GetPortionIds()) { - if (skipPortions.contains(p)) { - continue; - } - const auto& portionCross = Owner.GetPortionVerified(p); - if (!portionCross.CrossSSWith(inputPortion)) { - continue; - } - if (!nextKey || *nextKey < portionCross.IndexKeyEnd()) { - nextKey = portionCross.IndexKeyEnd(); - } +bool TPortionsIndex::HasOlderIntervals(const TPortionInfo& inputPortion, const THashSet& skipPortions) const { + for (auto&& [_, p] : Portions) { + if (p->GetPortionId() == inputPortion.GetPortionId()) { + continue; } - if (nextKey) { - nextKey = std::min(inputPortion.IndexKeyEnd(), *nextKey); - portionExcludeIntervals.Add(itFrom->first, *nextKey); - auto itFromNext = Points.find(*nextKey); - AFL_VERIFY(itFromNext != Points.end()); - if (itFromNext == itTo) { - break; - } - if (itFromNext == itFrom) { - ++itFrom; - } else { - itFrom = itFromNext; - } - AFL_VERIFY(itFrom != Points.end()); - } else { - if (itFrom == itTo) { - break; - } - ++itFrom; - AFL_VERIFY(itFrom != Points.end()); + if (inputPortion.IndexKeyEnd() < p->IndexKeyStart()) { + continue; } - - } - return portionExcludeIntervals; -} - -void TPortionsIndex::RemovePortion(const std::shared_ptr& p) { - auto itFrom = Points.find(p->IndexKeyStart()); - AFL_VERIFY(itFrom != Points.end()); - auto itTo = Points.find(p->IndexKeyEnd()); - AFL_VERIFY(itTo != Points.end()); - { - const TPortionInfoStat stat(p); - auto it = itFrom; - while (true) { - RemoveFromMemoryUsageControl(it->second.GetIntervalStats()); - it->second.RemoveContained(stat); - RawMemoryUsage.Add(it->second.GetIntervalStats().GetMinRawBytes()); - BlobMemoryUsage.Add(it->second.GetIntervalStats().GetBlobBytes()); - if (it == itTo) { - break; - } - AFL_VERIFY(++it != Points.end()); - } - } - if (itFrom != itTo) { - itFrom->second.RemoveStart(p); - if (itFrom->second.IsEmpty()) { - RemoveFromMemoryUsageControl(itFrom->second.GetIntervalStats()); - Points.erase(itFrom); + if (p->IndexKeyEnd() < inputPortion.IndexKeyStart()) { + continue; } - itTo->second.RemoveFinish(p); - if (itTo->second.IsEmpty()) { - RemoveFromMemoryUsageControl(itTo->second.GetIntervalStats()); - Points.erase(itTo); + if (skipPortions.contains(p->GetPortionId())) { + continue; } - } else { - itTo->second.RemoveStart(p); - itTo->second.RemoveFinish(p); - if (itTo->second.IsEmpty()) { - RemoveFromMemoryUsageControl(itTo->second.GetIntervalStats()); - Points.erase(itTo); - } - } - RawMemoryUsage.FlushCounters(); - BlobMemoryUsage.FlushCounters(); -} - -void TPortionsIndex::AddPortion(const std::shared_ptr& p) { - auto itFrom = InsertPoint(p->IndexKeyStart()); - itFrom->second.AddStart(p); - auto itTo = InsertPoint(p->IndexKeyEnd()); - itTo->second.AddFinish(p); - - auto it = itFrom; - const TPortionInfoStat stat(p); - while (true) { - RemoveFromMemoryUsageControl(it->second.GetIntervalStats()); - it->second.AddContained(stat); - RawMemoryUsage.Add(it->second.GetIntervalStats().GetMinRawBytes()); - BlobMemoryUsage.Add(it->second.GetIntervalStats().GetBlobBytes()); - if (it == itTo) { - break; + if (inputPortion.RecordSnapshotMax() < p->RecordSnapshotMin()) { + continue; } - AFL_VERIFY(++it != Points.end()); + return true; } - RawMemoryUsage.FlushCounters(); - BlobMemoryUsage.FlushCounters(); + return false; } } \ No newline at end of file diff --git a/ydb/core/tx/columnshard/engines/storage/granule/portions_index.h b/ydb/core/tx/columnshard/engines/storage/granule/portions_index.h index 981943dc4dab..d93b544c7aa1 100644 --- a/ydb/core/tx/columnshard/engines/storage/granule/portions_index.h +++ b/ydb/core/tx/columnshard/engines/storage/granule/portions_index.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include namespace NKikimr::NOlap { class TGranuleMeta; @@ -8,218 +9,29 @@ class TGranuleMeta; namespace NKikimr::NOlap::NGranule::NPortionsIndex { -class TPortionInfoStat { -private: - std::shared_ptr PortionInfo; - YDB_READONLY(ui64, MinRawBytes, 0); - YDB_READONLY(ui64, BlobBytes, 0); - -public: - TPortionInfoStat(const std::shared_ptr& portionInfo) - : PortionInfo(portionInfo) - , MinRawBytes(PortionInfo->GetMinMemoryForReadColumns({})) - , BlobBytes(PortionInfo->GetTotalBlobBytes()) - { - - } - - const TPortionInfo& GetPortionInfoVerified() const { - AFL_VERIFY(PortionInfo); - return *PortionInfo; - } -}; - -class TIntervalInfoStat { -private: - YDB_READONLY(ui64, MinRawBytes, 0); - YDB_READONLY(ui64, BlobBytes, 0); - -public: - void Add(const TPortionInfoStat& source) { - MinRawBytes += source.GetMinRawBytes(); - BlobBytes += source.GetBlobBytes(); - } - - void Sub(const TPortionInfoStat& source) { - AFL_VERIFY(MinRawBytes >= source.GetMinRawBytes()); - MinRawBytes -= source.GetMinRawBytes(); - AFL_VERIFY(BlobBytes >= source.GetBlobBytes()); - BlobBytes -= source.GetBlobBytes(); - AFL_VERIFY(!!BlobBytes == !!MinRawBytes); - } - - bool operator!() const { - return !BlobBytes && !MinRawBytes; - } -}; - -class TPortionsPKPoint { -private: - THashMap> Start; - THashMap> Finish; - THashMap PortionIds; - YDB_READONLY_DEF(TIntervalInfoStat, IntervalStats); - -public: - const THashMap>& GetStart() const { - return Start; - } - - void ProvidePortions(const TPortionsPKPoint& source) { - IntervalStats = TIntervalInfoStat(); - for (auto&& [i, stat] : source.PortionIds) { - if (source.Finish.contains(i)) { - continue; - } - AddContained(stat); - } - } - - const THashMap& GetPortionIds() const { - return PortionIds; - } - - bool IsEmpty() const { - return Start.empty() && Finish.empty(); - } - - void AddContained(const TPortionInfoStat& stat) { - if (!stat.GetPortionInfoVerified().HasRemoveSnapshot()) { - IntervalStats.Add(stat); - } - AFL_VERIFY(PortionIds.emplace(stat.GetPortionInfoVerified().GetPortionId(), stat).second); - } - - void RemoveContained(const TPortionInfoStat& stat) { - if (!stat.GetPortionInfoVerified().HasRemoveSnapshot()) { - IntervalStats.Sub(stat); - } - AFL_VERIFY(PortionIds.erase(stat.GetPortionInfoVerified().GetPortionId())); - AFL_VERIFY(PortionIds.size() || !IntervalStats); - } - - void RemoveStart(const std::shared_ptr& p) { - auto it = Start.find(p->GetPortionId()); - AFL_VERIFY(it != Start.end()); - Start.erase(it); - } - void RemoveFinish(const std::shared_ptr& p) { - auto it = Finish.find(p->GetPortionId()); - AFL_VERIFY(it != Finish.end()); - Finish.erase(it); - } - - void AddStart(const std::shared_ptr& p) { - AFL_VERIFY(Start.emplace(p->GetPortionId(), p).second); - } - void AddFinish(const std::shared_ptr& p) { - AFL_VERIFY(Finish.emplace(p->GetPortionId(), p).second); - } -}; - -class TIntervalMemoryMonitoring { -private: - std::map CountMemoryUsages; - const NColumnShard::TIntervalMemoryCounters& Counters; - -public: - void Add(const ui64 mem) { - ++CountMemoryUsages[mem]; - } - - void Remove(const ui64 mem) { - auto it = CountMemoryUsages.find(mem); - AFL_VERIFY(it != CountMemoryUsages.end())("mem", mem); - if (!--it->second) { - CountMemoryUsages.erase(it); - } - } - - TIntervalMemoryMonitoring(const NColumnShard::TIntervalMemoryCounters& counters) - : Counters(counters) - { - - } - - ui64 GetMax() const { - if (CountMemoryUsages.size()) { - return CountMemoryUsages.rbegin()->first; - } else { - return 0; - } - } - - void FlushCounters() const { - Counters.MinReadBytes->SetValue(GetMax()); - } -}; - class TPortionsIndex { private: - std::map Points; - TIntervalMemoryMonitoring RawMemoryUsage; - TIntervalMemoryMonitoring BlobMemoryUsage; + THashMap> Portions; const TGranuleMeta& Owner; - std::map::iterator InsertPoint(const NArrow::TReplaceKey& key) { - auto it = Points.find(key); - if (it == Points.end()) { - it = Points.emplace(key, TPortionsPKPoint()).first; - if (it != Points.begin()) { - auto itPred = it; - --itPred; - it->second.ProvidePortions(itPred->second); - } - RawMemoryUsage.Add(it->second.GetIntervalStats().GetMinRawBytes()); - BlobMemoryUsage.Add(it->second.GetIntervalStats().GetBlobBytes()); - } - return it; - } - - void RemoveFromMemoryUsageControl(const TIntervalInfoStat& stat) { - RawMemoryUsage.Remove(stat.GetMinRawBytes()); - BlobMemoryUsage.Remove(stat.GetBlobBytes()); - } - public: TPortionsIndex(const TGranuleMeta& owner, const NColumnShard::TPortionsIndexCounters& counters) - : RawMemoryUsage(counters.RawBytes) - , BlobMemoryUsage(counters.BlobBytes) - , Owner(owner) + : Owner(owner) { - - } - - ui64 GetMinRawMemoryRead() const { - return RawMemoryUsage.GetMax(); + Y_UNUSED(Owner); + Y_UNUSED(counters); } - ui64 GetMinBlobMemoryRead() const { - return BlobMemoryUsage.GetMax(); + void AddPortion(const std::shared_ptr& p) { + AFL_VERIFY(p); + AFL_VERIFY(Portions.emplace(p->GetPortionId(), p).second); } - - const std::map& GetPoints() const { - return Points; + void RemovePortion(const std::shared_ptr& p) { + AFL_VERIFY(p); + AFL_VERIFY(Portions.erase(p->GetPortionId())); } - void AddPortion(const std::shared_ptr& p); - - void RemovePortion(const std::shared_ptr& p); - - class TPortionIntervals { - private: - YDB_READONLY_DEF(std::vector, ExcludedIntervals); - public: - void Add(const NArrow::TReplaceKey& from, const NArrow::TReplaceKey& to) { - if (ExcludedIntervals.empty() || ExcludedIntervals.back().GetFinish() != from) { - ExcludedIntervals.emplace_back(NArrow::TReplaceKeyInterval(from, to)); - } else { - ExcludedIntervals.back().SetFinish(to); - } - } - }; - - TPortionIntervals GetIntervalFeatures(const TPortionInfo& inputPortion, const THashSet& skipPortions) const; + bool HasOlderIntervals(const TPortionInfo& inputPortion, const THashSet& skipPortions) const; }; diff --git a/ydb/core/tx/columnshard/engines/storage/granule/stages.cpp b/ydb/core/tx/columnshard/engines/storage/granule/stages.cpp new file mode 100644 index 000000000000..84c7ffd6d0c7 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/granule/stages.cpp @@ -0,0 +1,76 @@ +#include "granule.h" +#include "stages.h" + +#include +#include + +namespace NKikimr::NOlap::NLoading { + +bool TGranuleOnlyPortionsReader::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + TDbWrapper db(txc.DB, &*DsGroupSelector); + std::vector portions; + if (!db.LoadPortions(Self->GetPathId(), [&](TPortionInfoConstructor&& portion, const NKikimrTxColumnShard::TIndexPortionMeta& metaProto) { + const TIndexInfo& indexInfo = portion.GetSchema(*VersionedIndex)->GetIndexInfo(); + AFL_VERIFY(portion.MutableMeta().LoadMetadata(metaProto, indexInfo, *DsGroupSelector)); + portions.emplace_back(portion.Build()); + })) { + return false; + } + for (auto&& i : portions) { + Self->UpsertPortionOnLoad(i); + } + return true; +} + +bool TGranuleOnlyPortionsReader::DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + NIceDb::TNiceDb db(txc.DB); + return db.Table().Prefix(Self->GetPathId()).Select().IsReady(); +} + +bool TGranuleColumnsReader::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + TDbWrapper db(txc.DB, &*DsGroupSelector); + TPortionInfo::TSchemaCursor schema(*VersionedIndex); + Context->ClearRecords(); + return db.LoadColumns(Self->GetPathId(), [&](TColumnChunkLoadContextV2&& loadContext) { + Context->Add(std::move(loadContext)); + }); +} + +bool TGranuleColumnsReader::DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + NIceDb::TNiceDb db(txc.DB); + return db.Table().Prefix(Self->GetPathId()).Select().IsReady(); +} + +bool TGranuleIndexesReader::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + TDbWrapper db(txc.DB, &*DsGroupSelector); + Context->ClearIndexes(); + return db.LoadIndexes(Self->GetPathId(), [&](const ui64 /*pathId*/, const ui64 /*portionId*/, TIndexChunkLoadContext&& loadContext) { + Context->Add(std::move(loadContext)); + }); +} + +bool TGranuleIndexesReader::DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + NIceDb::TNiceDb db(txc.DB); + return db.Table().Prefix(Self->GetPathId()).Select().IsReady(); +} + +bool TGranuleFinishAccessorsLoading::DoExecute(NTabletFlatExecutor::TTransactionContext& /*txc*/, const TActorContext& /*ctx*/) { + THashMap constructors = Context->ExtractConstructors(); + AFL_VERIFY(Self->GetPortions().size() == constructors.size()); + for (auto&& i : Self->GetPortions()) { + auto it = constructors.find(i.first); + AFL_VERIFY(it != constructors.end()); + auto accessor = TPortionAccessorConstructor::BuildForLoading(i.second, std::move(it->second.MutableRecords()), std::move(it->second.MutableIndexes())); + Self->GetDataAccessorsManager()->AddPortion(accessor); + } + return true; +} + +bool TGranuleFinishCommonLoading::DoExecute(NTabletFlatExecutor::TTransactionContext& /*txc*/, const TActorContext& /*ctx*/) { + AFL_VERIFY(!Started); + Started = true; + Self->OnAfterPortionsLoad(); + return true; +} + +} // namespace NKikimr::NOlap::NLoading diff --git a/ydb/core/tx/columnshard/engines/storage/granule/stages.h b/ydb/core/tx/columnshard/engines/storage/granule/stages.h new file mode 100644 index 000000000000..2dcac36dc4b0 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/granule/stages.h @@ -0,0 +1,169 @@ +#pragma once +#include +#include +#include + +namespace NKikimr::NOlap { +class TGranuleMeta; +} + +namespace NKikimr::NOlap::NLoading { + +class TPortionDataAccessors { +private: + YDB_ACCESSOR_DEF(std::vector, Records); + std::vector Indexes; + +public: + std::vector& MutableIndexes() { + return Indexes; + } + + TPortionDataAccessors() = default; +}; + +class TPortionsLoadContext { +private: + THashMap Constructors; + TPortionDataAccessors& MutableConstructor(const ui64 portionId) { + auto it = Constructors.find(portionId); + if (it == Constructors.end()) { + it = Constructors.emplace(portionId, TPortionDataAccessors()).first; + } + return it->second; + } + +public: + void ClearRecords() { + for (auto&& i : Constructors) { + i.second.MutableRecords().clear(); + } + } + + void ClearIndexes() { + for (auto&& i : Constructors) { + i.second.MutableIndexes().clear(); + } + } + + THashMap&& ExtractConstructors() { + return std::move(Constructors); + } + + void Add(TIndexChunkLoadContext&& chunk) { + auto& constructor = MutableConstructor(chunk.GetPortionId()); + constructor.MutableIndexes().emplace_back(std::move(chunk)); + } + void Add(TColumnChunkLoadContextV1&& chunk) { + auto& constructor = MutableConstructor(chunk.GetPortionId()); + constructor.MutableRecords().emplace_back(std::move(chunk)); + } + void Add(TColumnChunkLoadContextV2&& chunk) { + for (auto&& i : chunk.BuildRecordsV1()) { + Add(std::move(i)); + } + } +}; + +class TGranuleOnlyPortionsReader: public ITxReader { +private: + using TBase = ITxReader; + + const std::shared_ptr DsGroupSelector; + TGranuleMeta* Self = nullptr; + const TVersionedIndex* VersionedIndex; + + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& ctx) override; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& ctx) override; + +public: + TGranuleOnlyPortionsReader(const TString& name, const TVersionedIndex* versionedIndex, TGranuleMeta* self, + const std::shared_ptr& dsGroupSelector) + : TBase(name) + , DsGroupSelector(dsGroupSelector) + , Self(self) + , VersionedIndex(versionedIndex) { + AFL_VERIFY(!!DsGroupSelector); + AFL_VERIFY(VersionedIndex); + AFL_VERIFY(Self); + } +}; + +class TGranuleFinishCommonLoading: public ITxReader { +private: + using TBase = ITxReader; + TGranuleMeta* Self = nullptr; + bool Started = false; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& /*txc*/, const TActorContext& /*ctx*/) override { + return true; + } + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& /*txc*/, const TActorContext& /*ctx*/) override; + +public: + TGranuleFinishCommonLoading(const TString& name, TGranuleMeta* self) + : TBase(name) + , Self(self) { + AFL_VERIFY(Self); + } +}; + +class IGranuleTxReader: public ITxReader { +private: + using TBase = ITxReader; + +protected: + const std::shared_ptr DsGroupSelector; + TGranuleMeta* Self = nullptr; + const TVersionedIndex* VersionedIndex; + const std::shared_ptr Context; + +public: + IGranuleTxReader(const TString& name, const TVersionedIndex* versionedIndex, TGranuleMeta* self, + const std::shared_ptr& dsGroupSelector, const std::shared_ptr& context) + : TBase(name) + , DsGroupSelector(dsGroupSelector) + , Self(self) + , VersionedIndex(versionedIndex) + , Context(context) { + AFL_VERIFY(!!DsGroupSelector); + AFL_VERIFY(VersionedIndex); + AFL_VERIFY(Self); + AFL_VERIFY(Context); + } +}; + +class TGranuleColumnsReader: public IGranuleTxReader { +private: + using TBase = IGranuleTxReader; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + +public: + using TBase::TBase; +}; + +class TGranuleIndexesReader: public IGranuleTxReader { +private: + using TBase = IGranuleTxReader; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + +public: + using TBase::TBase; +}; + +class TGranuleFinishAccessorsLoading: public IGranuleTxReader { +private: + using TBase = IGranuleTxReader; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& /*txc*/, const TActorContext& /*ctx*/) override { + return true; + } + +public: + using TBase::TBase; +}; + +} // namespace NKikimr::NOlap::NLoading diff --git a/ydb/core/tx/columnshard/engines/storage/granule/storage.cpp b/ydb/core/tx/columnshard/engines/storage/granule/storage.cpp index b017464eefeb..53499a8bcf1d 100644 --- a/ydb/core/tx/columnshard/engines/storage/granule/storage.cpp +++ b/ydb/core/tx/columnshard/engines/storage/granule/storage.cpp @@ -1,51 +1,91 @@ #include "storage.h" + #include namespace NKikimr::NOlap { -std::shared_ptr TGranulesStorage::GetGranuleForCompaction(const std::shared_ptr& dataLocksManager) const { +namespace { +class TGranuleOrdered { +private: + NStorageOptimizer::TOptimizationPriority Priority; + YDB_READONLY_DEF(std::shared_ptr, Granule); + +public: + const NStorageOptimizer::TOptimizationPriority& GetPriority() const { + return Priority; + } + + TGranuleOrdered(const NStorageOptimizer::TOptimizationPriority& priority, const std::shared_ptr& meta) + : Priority(priority) + , Granule(meta) { + } + + bool operator<(const TGranuleOrdered& item) const { + return Priority < item.Priority; + } +}; +} // namespace + +std::optional TGranulesStorage::GetCompactionPriority( + const std::shared_ptr& dataLocksManager, const std::set& pathIds, const std::optional waitingPriority, + std::shared_ptr* granuleResult) const { const TInstant now = HasAppData() ? AppDataVerified().TimeProvider->Now() : TInstant::Now(); - std::map> granulesSorted; - ui32 countChecker = 0; + std::vector granulesSorted; std::optional priorityChecker; + std::shared_ptr maxPriorityGranule; const TDuration actualizationLag = NYDBTest::TControllers::GetColumnShardController()->GetCompactionActualizationLag(); - for (auto&& i : Tables) { - NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("path_id", i.first); - i.second->ActualizeOptimizer(now, actualizationLag); - auto gPriority = i.second->GetCompactionPriority(); - if (gPriority.IsZero() || (priorityChecker && gPriority < *priorityChecker)) { - continue; + const auto actor = [&](const ui64 /*pathId*/, const std::shared_ptr& granule) { + // NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("path_id", i.first); + if (pathIds.empty()) { + granule->ActualizeOptimizer(now, actualizationLag); } - granulesSorted.emplace(gPriority, i.second); - if (++countChecker % 100 == 0) { - for (auto&& it = granulesSorted.rbegin(); it != granulesSorted.rend(); ++it) { - if (!it->second->IsLockedOptimizer(dataLocksManager)) { - priorityChecker = it->first; - break; - } - } + auto gPriority = granule->GetCompactionPriority(); + if (gPriority.IsZero() || (waitingPriority && gPriority.GetGeneralPriority() < *waitingPriority)) { + return; } - } - if (granulesSorted.empty()) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "no_granules"); - return nullptr; - } - for (auto&& it = granulesSorted.rbegin(); it != granulesSorted.rend(); ++it) { - if (priorityChecker && it->first < *priorityChecker) { - continue; + granulesSorted.emplace_back(gPriority, granule); + }; + if (pathIds.size()) { + for (auto&& pathId : pathIds) { + auto it = Tables.find(pathId); + AFL_VERIFY(it != Tables.end()); + actor(it->first, it->second); } - NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("path_id", it->second->GetPathId()); - if (it->second->IsLockedOptimizer(dataLocksManager)) { - Counters.OnGranuleOptimizerLocked(); - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "skip_optimizer_throught_lock")("priority", it->first.DebugString()); - } else { - AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("event", "granule_compaction_weight")("priority", it->first.DebugString()); - return it->second; + } else { + for (auto&& i : Tables) { + actor(i.first, i.second); } } + std::sort(granulesSorted.begin(), granulesSorted.end()); + while (granulesSorted.size()) { + auto lockName = dataLocksManager->IsLocked(*granulesSorted.back().GetGranule(), NDataLocks::ELockCategory::Compaction); + if (!lockName) { + priorityChecker = granulesSorted.back().GetPriority(); + maxPriorityGranule = granulesSorted.back().GetGranule(); + break; + } + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "granule_locked")("path_id", granulesSorted.back().GetGranule()->GetPathId())( + "lock_id", *lockName); + granulesSorted.pop_back(); + } + if (granuleResult) { + *granuleResult = maxPriorityGranule; + } + return priorityChecker; +} - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "all_significant_granules_locked")("count", granulesSorted.size()); - return nullptr; +std::shared_ptr TGranulesStorage::GetGranuleForCompaction(const std::shared_ptr& dataLocksManager) const { + std::shared_ptr granuleMaxPriority; + std::optional priorityChecker = + GetCompactionPriority(dataLocksManager, {}, std::nullopt, &granuleMaxPriority); + if (!granuleMaxPriority) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "no_granules"); + return nullptr; + } + NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("path_id", granuleMaxPriority->GetPathId()); + AFL_VERIFY(!dataLocksManager->IsLocked(*granuleMaxPriority, NDataLocks::ELockCategory::Compaction)); + AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("event", "granule_compaction_weight")("priority", priorityChecker->DebugString()); + return granuleMaxPriority; } -} // namespace NKikimr::NOlap +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/storage/granule/storage.h b/ydb/core/tx/columnshard/engines/storage/granule/storage.h index b925e42fd7fa..5bcf76b3fac6 100644 --- a/ydb/core/tx/columnshard/engines/storage/granule/storage.h +++ b/ydb/core/tx/columnshard/engines/storage/granule/storage.h @@ -1,8 +1,11 @@ #pragma once #include "granule.h" -#include + #include #include +#include +#include +#include namespace NKikimr::NOlap { @@ -31,9 +34,7 @@ class TGranulesStat { public: TGranulesStat(const NColumnShard::TEngineLogsCounters& counters) - : Counters(counters) - { - + : Counters(counters) { } const NColumnShard::TEngineLogsCounters& GetCounters() const { @@ -43,6 +44,7 @@ class TGranulesStat { class TModificationGuard: TNonCopyable { private: TGranulesStat& Owner; + public: TModificationGuard(TGranulesStat& storage) : Owner(storage) { @@ -89,29 +91,54 @@ class TGranulesStat { const i64 value = SumMetadataMemoryPortionsSize.Add(portion.GetMetadataMemorySize()); Counters.OnIndexMetadataUsageBytes(value); } - }; class TGranulesStorage { private: const NColumnShard::TEngineLogsCounters Counters; + const std::shared_ptr DataAccessorsManager; std::shared_ptr StoragesManager; - THashMap> Tables; // pathId into Granule that equal to Table + THashMap> Tables; // pathId into Granule that equal to Table std::shared_ptr Stats; + public: - TGranulesStorage(const NColumnShard::TEngineLogsCounters counters, const std::shared_ptr& storagesManager) + const std::shared_ptr& GetDataAccessorsManager() const { + return DataAccessorsManager; + } + + std::vector CollectMetadataRequests() { + std::vector result; + for (auto&& i : Tables) { + auto r = i.second->CollectMetadataRequests(); + if (!r.size()) { + continue; + } + result.insert(result.end(), r.begin(), r.end()); + } + return result; + } + + TGranulesStorage(const NColumnShard::TEngineLogsCounters counters, + const std::shared_ptr& dataAccessorsManager, + const std::shared_ptr& storagesManager) : Counters(counters) + , DataAccessorsManager(dataAccessorsManager) , StoragesManager(storagesManager) - , Stats(std::make_shared(Counters)) - { + , Stats(std::make_shared(Counters)) { + AFL_VERIFY(DataAccessorsManager); + AFL_VERIFY(StoragesManager); + } + void FetchDataAccessors(const std::shared_ptr& request) const { + DataAccessorsManager->AskData(request); } const std::shared_ptr& GetStats() const { return Stats; } - std::shared_ptr RegisterTable(const ui64 pathId, const NColumnShard::TGranuleDataCounters& counters, const TVersionedIndex& versionedIndex) { + std::shared_ptr RegisterTable( + const ui64 pathId, const NColumnShard::TGranuleDataCounters& counters, const TVersionedIndex& versionedIndex) { auto infoEmplace = Tables.emplace(pathId, std::make_shared(pathId, *this, counters, versionedIndex)); AFL_VERIFY(infoEmplace.second); return infoEmplace.first->second; @@ -125,6 +152,7 @@ class TGranulesStorage { if (!it->second->IsErasable()) { return false; } + DataAccessorsManager->UnregisterController(pathId); Tables.erase(it); return true; } @@ -171,6 +199,12 @@ class TGranulesStorage { return it->second; } + std::shared_ptr GetGranuleVerified(const ui64 pathId) const { + auto it = Tables.find(pathId); + AFL_VERIFY(it != Tables.end()); + return it->second; + } + const std::shared_ptr& GetStoragesManager() const { return StoragesManager; } @@ -180,7 +214,9 @@ class TGranulesStorage { } std::shared_ptr GetGranuleForCompaction(const std::shared_ptr& locksManager) const; - + std::optional GetCompactionPriority(const std::shared_ptr& locksManager, + const std::set& pathIds = Default>(), const std::optional waitingPriority = std::nullopt, + std::shared_ptr* granuleResult = nullptr) const; }; -} // namespace NKikimr::NOlap +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/engines/storage/granule/ya.make b/ydb/core/tx/columnshard/engines/storage/granule/ya.make index 3326bfb5c64f..533afbec74c0 100644 --- a/ydb/core/tx/columnshard/engines/storage/granule/ya.make +++ b/ydb/core/tx/columnshard/engines/storage/granule/ya.make @@ -4,6 +4,7 @@ SRCS( granule.cpp storage.cpp portions_index.cpp + stages.cpp ) PEERDIR( diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/bloom/checker.h b/ydb/core/tx/columnshard/engines/storage/indexes/bloom/checker.h index 740af9f1720d..8192a2fb8cf9 100644 --- a/ydb/core/tx/columnshard/engines/storage/indexes/bloom/checker.h +++ b/ydb/core/tx/columnshard/engines/storage/indexes/bloom/checker.h @@ -1,22 +1,92 @@ #pragma once #include + +#include + namespace NKikimr::NOlap::NIndexes { class TFixStringBitsStorage { private: YDB_READONLY_DEF(TString, Data); + template + class TSizeDetector {}; + + template <> + class TSizeDetector> { + public: + static ui32 GetSize(const std::vector& v) { + return v.size(); + } + }; + + template <> + class TSizeDetector { + public: + static ui32 GetSize(const TDynBitMap& v) { + return v.Size(); + } + }; + public: TFixStringBitsStorage(const TString& data) - : Data(data) - {} + : Data(data) { + } + + static ui32 GrowBitsCountToByte(const ui32 bitsCount) { + const ui32 bytesCount = bitsCount / 8; + return (bytesCount + ((bitsCount % 8) ? 1 : 0)) * 8; + } + + TString DebugString() const { + TStringBuilder sb; + ui32 count1 = 0; + ui32 count0 = 0; + for (ui32 i = 0; i < GetSizeBits(); ++i) { + if (Get(i)) { +// sb << 1 << " "; + ++count1; + } else { +// sb << 0 << " "; + ++count0; + } +// if (i % 20 == 0) { +// sb << i << " "; +// } + } + sb << GetSizeBits() << "=" << count0 << "[0]+" << count1 << "[1]"; + return sb; + } + + template + TFixStringBitsStorage(const TBitsVector& bitsVector) + : TFixStringBitsStorage(TSizeDetector::GetSize(bitsVector)) { + ui32 byteIdx = 0; + ui8 byteCurrent = 0; + ui8 shiftCurrent = 1; + for (ui32 i = 0; i < TSizeDetector::GetSize(bitsVector); ++i) { + if (i && i % 8 == 0) { + Data[byteIdx] = (char)byteCurrent; + byteCurrent = 0; + shiftCurrent = 1; + ++byteIdx; + } + if (bitsVector[i]) { + byteCurrent += shiftCurrent; + } + shiftCurrent = (shiftCurrent << 1); + } + if (byteCurrent) { + Data[byteIdx] = (char)byteCurrent; + } + } ui32 GetSizeBits() const { return Data.size() * 8; } TFixStringBitsStorage(const ui32 sizeBits) - : Data(sizeBits / 8 + ((sizeBits % 8) ? 1 : 0), '\0') { + : Data(GrowBitsCountToByte(sizeBits) / 8, '\0') { } void Set(const bool val, const ui32 idx) { @@ -43,26 +113,27 @@ class TBloomFilterChecker: public TSimpleIndexChecker { static TString GetClassNameStatic() { return "BLOOM_FILTER"; } + private: using TBase = TSimpleIndexChecker; std::set HashValues; static inline auto Registrator = TFactory::TRegistrator(GetClassNameStatic()); + protected: virtual bool DoDeserializeFromProtoImpl(const NKikimrSSA::TProgram::TOlapIndexChecker& proto) override; virtual void DoSerializeToProtoImpl(NKikimrSSA::TProgram::TOlapIndexChecker& proto) const override; virtual bool DoCheckImpl(const std::vector& blobs) const override; + public: TBloomFilterChecker() = default; TBloomFilterChecker(const ui32 indexId, std::set&& hashes) : TBase(indexId) - , HashValues(std::move(hashes)) - { - + , HashValues(std::move(hashes)) { } virtual TString GetClassName() const override { return GetClassNameStatic(); } }; -} // namespace NKikimr::NOlap::NIndexes \ No newline at end of file +} // namespace NKikimr::NOlap::NIndexes diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/bloom/meta.cpp b/ydb/core/tx/columnshard/engines/storage/indexes/bloom/meta.cpp index a2d84cb10f6d..09b09e21dcf6 100644 --- a/ydb/core/tx/columnshard/engines/storage/indexes/bloom/meta.cpp +++ b/ydb/core/tx/columnshard/engines/storage/indexes/bloom/meta.cpp @@ -10,26 +10,21 @@ namespace NKikimr::NOlap::NIndexes { -TString TBloomIndexMeta::DoBuildIndexImpl(TChunkedBatchReader& reader) const { - std::set hashes; - { - NArrow::NHash::NXX64::TStreamStringHashCalcer hashCalcer(0); +TString TBloomIndexMeta::DoBuildIndexImpl(TChunkedBatchReader& reader, const ui32 recordsCount) const { + const ui32 bitsCount = TFixStringBitsStorage::GrowBitsCountToByte(HashesCount * recordsCount / std::log(2)); + std::vector filterBits(bitsCount, false); + for (ui32 i = 0; i < HashesCount; ++i) { + NArrow::NHash::NXX64::TStreamStringHashCalcer_H3 hashCalcer(i); for (reader.Start(); reader.IsCorrect(); reader.ReadNext()) { hashCalcer.Start(); for (auto&& i : reader) { NArrow::NHash::TXX64::AppendField(i.GetCurrentChunk(), i.GetCurrentRecordIndex(), hashCalcer); } - hashes.emplace(hashCalcer.Finish()); + filterBits[hashCalcer.Finish() % bitsCount] = true; } } - const ui32 bitsCount = HashesCount * hashes.size() / std::log(2); - TFixStringBitsStorage bits(bitsCount); - const auto pred = [&bits](const ui64 hash) { - bits.Set(true, hash % bits.GetSizeBits()); - }; - BuildHashesSet(hashes, pred); - return bits.GetData(); + return TFixStringBitsStorage(filterBits).GetData(); } void TBloomIndexMeta::DoFillIndexCheckers(const std::shared_ptr& info, const NSchemeShard::TOlapSchema& schema) const { @@ -51,16 +46,13 @@ void TBloomIndexMeta::DoFillIndexCheckers(const std::shared_ptr hashes; - const auto pred = [&hashes](const ui64 hash) { - hashes.emplace(hash); - }; - NArrow::NHash::NXX64::TStreamStringHashCalcer calcer(0); for (ui32 i = 0; i < HashesCount; ++i) { + NArrow::NHash::NXX64::TStreamStringHashCalcer_H3 calcer(i); calcer.Start(); for (auto&& i : foundColumns) { NArrow::NHash::TXX64::AppendField(i.second, calcer); } - BuildHashesSet(calcer.Finish(), pred); + hashes.emplace(calcer.Finish()); } branch->MutableIndexes().emplace_back(std::make_shared(GetIndexId(), std::move(hashes))); } diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/bloom/meta.h b/ydb/core/tx/columnshard/engines/storage/indexes/bloom/meta.h index ac07cd4793de..cfd9bc85cf20 100644 --- a/ydb/core/tx/columnshard/engines/storage/indexes/bloom/meta.h +++ b/ydb/core/tx/columnshard/engines/storage/indexes/bloom/meta.h @@ -52,7 +52,7 @@ class TBloomIndexMeta: public TIndexByColumns { } virtual void DoFillIndexCheckers(const std::shared_ptr& info, const NSchemeShard::TOlapSchema& schema) const override; - virtual TString DoBuildIndexImpl(TChunkedBatchReader& reader) const override; + virtual TString DoBuildIndexImpl(TChunkedBatchReader& reader, const ui32 recordsCount) const override; virtual bool DoDeserializeFromProto(const NKikimrSchemeOp::TOlapIndexDescription& proto) override { AFL_VERIFY(TBase::DoDeserializeFromProto(proto)); diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/checker.cpp b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/checker.cpp new file mode 100644 index 000000000000..9eed96423754 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/checker.cpp @@ -0,0 +1,54 @@ +#include "checker.h" + +#include +#include + +#include + +#include +#include + +namespace NKikimr::NOlap::NIndexes::NBloomNGramm { + +void TFilterChecker::DoSerializeToProtoImpl(NKikimrSSA::TProgram::TOlapIndexChecker& proto) const { + for (auto&& i : HashValues) { + proto.MutableBloomNGrammFilter()->AddHashValues(i); + } +} + +bool TFilterChecker::DoCheckImpl(const std::vector& blobs) const { + AFL_VERIFY(blobs.size() == 1); + for (auto&& blob : blobs) { + TFixStringBitsStorage bits(blob); + bool found = true; + TStringBuilder sb; + for (auto&& i : HashValues) { + sb << i % bits.GetSizeBits() << ","; + if (!bits.Get(i % bits.GetSizeBits())) { + found = false; + break; + } + } + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_SCAN)("size", bits.GetSizeBits())("found", found)("hashes", sb)("details", bits.DebugString()); + if (found) { + // AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("size", bArray.length())("data", bArray.ToString())("index_id", GetIndexId()); + return true; + } + } + return false; +} + +bool TFilterChecker::DoDeserializeFromProtoImpl(const NKikimrSSA::TProgram::TOlapIndexChecker& proto) { + if (!proto.HasBloomNGrammFilter()) { + return false; + } + for (auto&& i : proto.GetBloomNGrammFilter().GetHashValues()) { + HashValues.emplace(i); + } + if (HashValues.empty()) { + return false; + } + return true; +} + +} // namespace NKikimr::NOlap::NIndexes::NBloomNGramm diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/checker.h b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/checker.h new file mode 100644 index 000000000000..37a4f3c31637 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/checker.h @@ -0,0 +1,33 @@ +#pragma once +#include +namespace NKikimr::NOlap::NIndexes::NBloomNGramm { + +class TFilterChecker: public TSimpleIndexChecker { +public: + static TString GetClassNameStatic() { + return "BLOOM_NGRAMM_FILTER"; + } + +private: + using TBase = TSimpleIndexChecker; + std::set HashValues; + static inline auto Registrator = TFactory::TRegistrator(GetClassNameStatic()); + +protected: + virtual bool DoDeserializeFromProtoImpl(const NKikimrSSA::TProgram::TOlapIndexChecker& proto) override; + virtual void DoSerializeToProtoImpl(NKikimrSSA::TProgram::TOlapIndexChecker& proto) const override; + + virtual bool DoCheckImpl(const std::vector& blobs) const override; + +public: + TFilterChecker() = default; + TFilterChecker(const ui32 indexId, std::set&& hashes) + : TBase(indexId) + , HashValues(std::move(hashes)) { + } + virtual TString GetClassName() const override { + return GetClassNameStatic(); + } +}; + +} // namespace NKikimr::NOlap::NIndexes::NBloomNGramm diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/const.cpp b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/const.cpp new file mode 100644 index 000000000000..7f5af16eab37 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/const.cpp @@ -0,0 +1,23 @@ +#include "const.h" + +#include + +namespace NKikimr::NOlap::NIndexes::NBloomNGramm { + +TString TConstants::GetRecordsCountIntervalString() { + return TStringBuilder() << "[" << MinRecordsCount << ", " << MaxRecordsCount << "]"; +} + +TString TConstants::GetHashesCountIntervalString() { + return TStringBuilder() << "[" << MinHashesCount << ", " << MaxHashesCount << "]"; +} + +TString TConstants::GetFilterSizeBytesIntervalString() { + return TStringBuilder() << "[" << MinFilterSizeBytes << ", " << MaxFilterSizeBytes << "]"; +} + +TString TConstants::GetNGrammSizeIntervalString() { + return TStringBuilder() << "[" << MinNGrammSize << ", " << MaxNGrammSize << "]"; +} + +} // namespace NKikimr::NOlap::NIndexes::NBloomNGramm diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/const.h b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/const.h new file mode 100644 index 000000000000..2718cc0a37b2 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/const.h @@ -0,0 +1,38 @@ +#pragma once +#include +namespace NKikimr::NOlap::NIndexes::NBloomNGramm { + +class TConstants { +public: + static constexpr ui32 MinNGrammSize = 3; + static constexpr ui32 MaxNGrammSize = 8; + static constexpr ui32 MinHashesCount = 1; + static constexpr ui32 MaxHashesCount = 8; + static constexpr ui32 MinFilterSizeBytes = 128; + static constexpr ui32 MaxFilterSizeBytes = 1 << 20; + static constexpr ui32 MinRecordsCount = 128; + static constexpr ui32 MaxRecordsCount = 1000000; + + static bool CheckRecordsCount(const ui32 value) { + return MinRecordsCount <= value && value <= MaxRecordsCount; + } + + static bool CheckNGrammSize(const ui32 value) { + return MinNGrammSize <= value && value <= MaxNGrammSize; + } + + static bool CheckHashesCount(const ui32 value) { + return MinHashesCount <= value && value <= MaxHashesCount; + } + + static bool CheckFilterSizeBytes(const ui32 value) { + return MinFilterSizeBytes <= value && value <= MaxFilterSizeBytes; + } + + static TString GetHashesCountIntervalString(); + static TString GetFilterSizeBytesIntervalString(); + static TString GetNGrammSizeIntervalString(); + static TString GetRecordsCountIntervalString(); +}; + +} // namespace NKikimr::NOlap::NIndexes::NBloomNGramm diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/constructor.cpp b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/constructor.cpp new file mode 100644 index 000000000000..8929d9fd4c53 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/constructor.cpp @@ -0,0 +1,107 @@ +#include "const.h" +#include "constructor.h" +#include "meta.h" + +#include + +namespace NKikimr::NOlap::NIndexes::NBloomNGramm { + +std::shared_ptr TIndexConstructor::DoCreateIndexMeta( + const ui32 indexId, const TString& indexName, const NSchemeShard::TOlapSchema& currentSchema, NSchemeShard::IErrorCollector& errors) const { + auto* columnInfo = currentSchema.GetColumns().GetByName(ColumnName); + if (!columnInfo) { + errors.AddError("no column with name " + ColumnName); + return nullptr; + } + const ui32 columnId = columnInfo->GetId(); + return std::make_shared(indexId, indexName, GetStorageId().value_or(NBlobOperations::TGlobal::DefaultStorageId), columnId, + HashesCount, FilterSizeBytes, NGrammSize, RecordsCount); +} + +TConclusionStatus TIndexConstructor::DoDeserializeFromJson(const NJson::TJsonValue& jsonInfo) { + if (!jsonInfo.Has("column_name")) { + return TConclusionStatus::Fail("column_name have to be in bloom ngramm filter features"); + } + if (!jsonInfo["column_name"].GetString(&ColumnName)) { + return TConclusionStatus::Fail("column_name have to be string in bloom ngramm filter features"); + } + if (!ColumnName) { + return TConclusionStatus::Fail("empty column_name in bloom ngramm filter features"); + } + + if (!jsonInfo["records_count"].IsUInteger()) { + return TConclusionStatus::Fail("records_count have to be in bloom filter features as uint field"); + } + RecordsCount = jsonInfo["records_count"].GetUInteger(); + if (!TConstants::CheckRecordsCount(RecordsCount)) { + return TConclusionStatus::Fail("records_count have to be in bloom ngramm filter in interval " + TConstants::GetRecordsCountIntervalString()); + } + + if (!jsonInfo["ngramm_size"].IsUInteger()) { + return TConclusionStatus::Fail("ngramm_size have to be in bloom filter features as uint field"); + } + NGrammSize = jsonInfo["ngramm_size"].GetUInteger(); + if (!TConstants::CheckNGrammSize(NGrammSize)) { + return TConclusionStatus::Fail("ngramm_size have to be in bloom ngramm filter in interval " + TConstants::GetNGrammSizeIntervalString()); + } + + if (!jsonInfo["filter_size_bytes"].IsUInteger()) { + return TConclusionStatus::Fail("filter_size_bytes have to be in bloom filter features as uint field"); + } + FilterSizeBytes = jsonInfo["filter_size_bytes"].GetUInteger(); + if (!TConstants::CheckFilterSizeBytes(FilterSizeBytes)) { + return TConclusionStatus::Fail( + "filter_size_bytes have to be in bloom ngramm filter in interval " + TConstants::GetFilterSizeBytesIntervalString()); + } + + if (!jsonInfo["hashes_count"].IsUInteger()) { + return TConclusionStatus::Fail("hashes_count have to be in bloom filter features as uint field"); + } + HashesCount = jsonInfo["hashes_count"].GetUInteger(); + if (!TConstants::CheckHashesCount(HashesCount)) { + return TConclusionStatus::Fail( + "hashes_count have to be in bloom ngramm filter in interval " + TConstants::GetHashesCountIntervalString()); + } + return TConclusionStatus::Success(); +} + +NKikimr::TConclusionStatus TIndexConstructor::DoDeserializeFromProto(const NKikimrSchemeOp::TOlapIndexRequested& proto) { + if (!proto.HasBloomNGrammFilter()) { + const TString errorMessage = "not found BloomNGrammFilter section in proto: \"" + proto.DebugString() + "\""; + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("problem", errorMessage); + return TConclusionStatus::Fail(errorMessage); + } + auto& bFilter = proto.GetBloomNGrammFilter(); + RecordsCount = bFilter.GetRecordsCount(); + if (!TConstants::CheckRecordsCount(RecordsCount)) { + return TConclusionStatus::Fail("RecordsCount have to be in " + TConstants::GetRecordsCountIntervalString()); + } + NGrammSize = bFilter.GetNGrammSize(); + if (!TConstants::CheckNGrammSize(NGrammSize)) { + return TConclusionStatus::Fail("NGrammSize have to be in " + TConstants::GetNGrammSizeIntervalString()); + } + FilterSizeBytes = bFilter.GetFilterSizeBytes(); + if (!TConstants::CheckFilterSizeBytes(FilterSizeBytes)) { + return TConclusionStatus::Fail("FilterSizeBytes have to be in " + TConstants::GetFilterSizeBytesIntervalString()); + } + HashesCount = bFilter.GetHashesCount(); + if (!TConstants::CheckHashesCount(HashesCount)) { + return TConclusionStatus::Fail("HashesCount size have to be in " + TConstants::GetHashesCountIntervalString()); + } + ColumnName = bFilter.GetColumnName(); + if (!ColumnName) { + return TConclusionStatus::Fail("empty column name"); + } + return TConclusionStatus::Success(); +} + +void TIndexConstructor::DoSerializeToProto(NKikimrSchemeOp::TOlapIndexRequested& proto) const { + auto* filterProto = proto.MutableBloomNGrammFilter(); + filterProto->SetColumnName(ColumnName); + filterProto->SetRecordsCount(RecordsCount); + filterProto->SetNGrammSize(NGrammSize); + filterProto->SetFilterSizeBytes(FilterSizeBytes); + filterProto->SetHashesCount(HashesCount); +} + +} // namespace NKikimr::NOlap::NIndexes::NBloomNGramm diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/constructor.h b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/constructor.h new file mode 100644 index 000000000000..209b1a5de8f0 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/constructor.h @@ -0,0 +1,36 @@ +#pragma once +#include +namespace NKikimr::NOlap::NIndexes::NBloomNGramm { + +class TIndexConstructor: public IIndexMetaConstructor { +public: + static TString GetClassNameStatic() { + return "BLOOM_NGRAMM_FILTER"; + } + +private: + TString ColumnName; + ui32 NGrammSize = 3; + ui32 FilterSizeBytes = 512; + ui32 HashesCount = 2; + ui32 RecordsCount = 10000; + static inline auto Registrator = TFactory::TRegistrator(GetClassNameStatic()); + +protected: + virtual std::shared_ptr DoCreateIndexMeta(const ui32 indexId, const TString& indexName, + const NSchemeShard::TOlapSchema& currentSchema, NSchemeShard::IErrorCollector& errors) const override; + + virtual TConclusionStatus DoDeserializeFromJson(const NJson::TJsonValue& jsonInfo) override; + + virtual TConclusionStatus DoDeserializeFromProto(const NKikimrSchemeOp::TOlapIndexRequested& proto) override; + virtual void DoSerializeToProto(NKikimrSchemeOp::TOlapIndexRequested& proto) const override; + +public: + TIndexConstructor() = default; + + virtual TString GetClassName() const override { + return GetClassNameStatic(); + } +}; + +} // namespace NKikimr::NOlap::NIndexes::NBloomNGramm diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/meta.cpp b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/meta.cpp new file mode 100644 index 000000000000..9f22ea0934d6 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/meta.cpp @@ -0,0 +1,281 @@ +#include "checker.h" +#include "const.h" +#include "meta.h" + +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace NKikimr::NOlap::NIndexes::NBloomNGramm { + +class TNGrammBuilder { +private: + const ui32 HashesCount; + + template + class THashesBuilder { + public: + static ui64 Build(const ui8* data, const ui64 h) { + return THashesBuilder::Build(data + 1, (h ^ uint64_t(*data)) * 16777619); + } + }; + + template <> + class THashesBuilder<0> { + public: + static ui64 Build(const ui8* /*data*/, const ui64 hash) { + return hash; + } + }; + + template + class THashesCountSelector { + static constexpr ui64 HashStart = (ui64)HashIdx * (ui64)2166136261; + public: + template + static void BuildHashes(const ui8* data, TActor& actor) { + actor(THashesBuilder::Build(data, HashStart)); + THashesCountSelector::BuildHashes(data, actor); + } + }; + + template + class THashesCountSelector<0, CharsCount> { + public: + template + static void BuildHashes(const ui8* /*data*/, TActor& /*actor*/) { + } + }; + + template + class THashesSelector { + private: + template + static void BuildHashesImpl( + const ui8* data, const ui32 dataSize, const std::optional op, TActor& actor) { + TBuffer fakeString; + if (!op || op == NRequest::TLikePart::EOperation::StartsWith) { + for (ui32 c = 1; c <= CharsCount; ++c) { + fakeString.Clear(); + fakeString.Fill('\0', CharsCount - c); + fakeString.Append((const char*)data, std::min((ui32)c, dataSize)); + if (fakeString.size() < CharsCount) { + fakeString.Fill('\0', CharsCount - fakeString.size()); + } + THashesCountSelector::BuildHashes((const ui8*)fakeString.data(), actor); + } + } + ui32 c = 0; + for (; c + CharsCount <= dataSize; ++c) { + THashesCountSelector::BuildHashes(data + c, actor); + } + if (!op || op == NRequest::TLikePart::EOperation::EndsWith) { + for (; c < dataSize; ++c) { + fakeString.Clear(); + fakeString.Append((const char*)data + c, dataSize - c); + fakeString.Fill('\0', CharsCount - fakeString.size()); + THashesCountSelector::BuildHashes((const ui8*)fakeString.data(), actor); + } + } + } + + public: + template + static void BuildHashes(const ui8* data, const ui32 dataSize, const ui32 hashesCount, const ui32 nGrammSize, + const std::optional op, TActor& actor) { + if (HashesCount == hashesCount && CharsCount == nGrammSize) { + BuildHashesImpl(data, dataSize, op, actor); + } else if (HashesCount > hashesCount && CharsCount > nGrammSize) { + THashesSelector::BuildHashes(data, dataSize, hashesCount, nGrammSize, op, actor); + } else if (HashesCount > hashesCount) { + THashesSelector::BuildHashes(data, dataSize, hashesCount, nGrammSize, op, actor); + } else if (CharsCount > nGrammSize) { + THashesSelector::BuildHashes(data, dataSize, hashesCount, nGrammSize, op, actor); + } else { + AFL_VERIFY(false); + } + } + }; + + template + class THashesSelector<0, CharsCount> { + public: + template + static void BuildHashes(const ui8* /*data*/, const ui32 /*dataSize*/, const ui32 /*hashesCount*/, const ui32 /*nGrammSize*/, + const std::optional /*op*/, TActor& /*actor*/) { + AFL_VERIFY(false); + } + }; + + template + class THashesSelector { + public: + template + static void BuildHashes(const ui8* /*data*/, const ui32 /*dataSize*/, const ui32 /*hashesCount*/, const ui32 /*nGrammSize*/, + const std::optional /*op*/, TActor& /*actor*/) { + AFL_VERIFY(false); + } + }; + + template <> + class THashesSelector<0, 0> { + public: + template + static void BuildHashes(const ui8* /*data*/, const ui32 /*dataSize*/, const ui32 /*hashesCount*/, const ui32 /*nGrammSize*/, + const std::optional /*op*/, TActor& /*actor*/) { + AFL_VERIFY(false); + } + }; + + template + void BuildNGramms( + const char* data, const ui32 dataSize, const std::optional op, const ui32 nGrammSize, TAction& pred) { + THashesSelector::BuildHashes( + (const ui8*)data, dataSize, HashesCount, nGrammSize, op, pred); + } + +public: + TNGrammBuilder(const ui32 hashesCount) + : HashesCount(hashesCount) { + } + + template + void FillNGrammHashes(const ui32 nGrammSize, const std::shared_ptr& array, TFiller& fillData) { + AFL_VERIFY(array->type_id() == arrow::utf8()->id())("id", array->type()->ToString()); + NArrow::SwitchType(array->type_id(), [&](const auto& type) { + using TWrap = std::decay_t; + using T = typename TWrap::T; + using TArray = typename arrow::TypeTraits::ArrayType; + auto& typedArray = static_cast(*array); + + for (ui32 row = 0; row < array->length(); ++row) { + if (array->IsNull(row)) { + continue; + } + if constexpr (arrow::has_string_view()) { + auto value = typedArray.GetView(row); + BuildNGramms(value.data(), value.size(), {}, nGrammSize, fillData); + } else { + AFL_VERIFY(false); + } + } + return true; + }); + } + + template + void FillNGrammHashes(const ui32 nGrammSize, const NRequest::TLikePart::EOperation op, const TString& userReq, TFiller& fillData) { + BuildNGramms(userReq.data(), userReq.size(), op, nGrammSize, fillData); + } +}; + +class TVectorInserter { +private: + TDynBitMap& Values; + const ui32 Size; + +public: + TVectorInserter(TDynBitMap& values) + : Values(values) + , Size(values.Size()) { + AFL_VERIFY(values.Size()); + } + + void operator()(const ui64 hash) { + Values.Set(hash % Size); + } +}; + +class TVectorInserterPower2 { +private: + TDynBitMap& Values; + const ui32 SizeMask; + +public: + TVectorInserterPower2(TDynBitMap& values) + : Values(values) + , SizeMask(values.Size() - 1) { + AFL_VERIFY(values.Size()); + } + + void operator()(const ui64 hash) { + Values.Set(hash & SizeMask); + } +}; + +TString TIndexMeta::DoBuildIndexImpl(TChunkedBatchReader& reader, const ui32 recordsCount) const { + AFL_VERIFY(reader.GetColumnsCount() == 1)("count", reader.GetColumnsCount()); + TNGrammBuilder builder(HashesCount); + + TDynBitMap bitMap; + ui32 size = FilterSizeBytes * 8; + if ((size & (size - 1)) == 0) { + ui32 recordsCountBase = RecordsCount; + while (recordsCountBase < recordsCount && size * 2 <= TConstants::MaxFilterSizeBytes) { + size <<= 1; + recordsCountBase *= 2; + } + } else { + size *= ((recordsCount <= RecordsCount) ? 1.0 : (1.0 * recordsCount / RecordsCount)); + } + bitMap.Reserve(size * 8); + + const auto doFillFilter = [&](auto& inserter) { + for (reader.Start(); reader.IsCorrect();) { + builder.FillNGrammHashes(NGrammSize, reader.begin()->GetCurrentChunk(), inserter); + reader.ReadNext(reader.begin()->GetCurrentChunk()->length()); + } + }; + + if ((size & (size - 1)) == 0) { + TVectorInserterPower2 inserter(bitMap); + doFillFilter(inserter); + } else { + TVectorInserter inserter(bitMap); + doFillFilter(inserter); + } + return TFixStringBitsStorage(bitMap).GetData(); +} + +void TIndexMeta::DoFillIndexCheckers( + const std::shared_ptr& info, const NSchemeShard::TOlapSchema& schema) const { + for (auto&& branch : info->GetBranches()) { + std::map foundColumns; + for (auto&& cId : ColumnIds) { + auto c = schema.GetColumns().GetById(cId); + if (!c) { + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("error", "incorrect index column")("id", cId); + return; + } + auto it = branch->GetLikes().find(c->GetName()); + if (it == branch->GetLikes().end()) { + break; + } + foundColumns.emplace(cId, it->second); + } + if (foundColumns.size() != ColumnIds.size()) { + continue; + } + + std::set hashes; + const auto predSet = [&](const ui64 hashSecondary) { + hashes.emplace(hashSecondary); + }; + TNGrammBuilder builder(HashesCount); + for (auto&& c : foundColumns) { + for (auto&& ls : c.second.GetLikeSequences()) { + builder.FillNGrammHashes(NGrammSize, ls.second.GetOperation(), ls.second.GetValue(), predSet); + } + } + branch->MutableIndexes().emplace_back(std::make_shared(GetIndexId(), std::move(hashes))); + } +} + +} // namespace NKikimr::NOlap::NIndexes::NBloomNGramm diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/meta.h b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/meta.h new file mode 100644 index 000000000000..1e9135e8c9d7 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/meta.h @@ -0,0 +1,108 @@ +#pragma once +#include +namespace NKikimr::NOlap::NIndexes::NBloomNGramm { + +class TIndexMeta: public TIndexByColumns { +public: + static TString GetClassNameStatic() { + return "BLOOM_NGRAMM_FILTER"; + } +private: + using TBase = TIndexByColumns; + std::shared_ptr ResultSchema; + ui32 NGrammSize = 3; + ui32 FilterSizeBytes = 512; + ui32 RecordsCount = 10000; + ui32 HashesCount = 2; + static inline auto Registrator = TFactory::TRegistrator(GetClassNameStatic()); + void Initialize() { + AFL_VERIFY(!ResultSchema); + std::vector> fields = {std::make_shared("", arrow::boolean())}; + ResultSchema = std::make_shared(fields); + AFL_VERIFY(TConstants::CheckHashesCount(HashesCount)); + AFL_VERIFY(TConstants::CheckFilterSizeBytes(FilterSizeBytes)); + AFL_VERIFY(TConstants::CheckNGrammSize(NGrammSize)); + AFL_VERIFY(TConstants::CheckRecordsCount(RecordsCount)); + } + +protected: + virtual TConclusionStatus DoCheckModificationCompatibility(const IIndexMeta& newMeta) const override { + const auto* bMeta = dynamic_cast(&newMeta); + if (!bMeta) { + return TConclusionStatus::Fail( + "cannot read meta as appropriate class: " + GetClassName() + ". Meta said that class name is " + newMeta.GetClassName()); + } + if (HashesCount != bMeta->HashesCount) { + return TConclusionStatus::Fail("cannot modify hashes count"); + } + if (NGrammSize != bMeta->NGrammSize) { + return TConclusionStatus::Fail("cannot modify ngramm size"); + } + return TBase::CheckSameColumnsForModification(newMeta); + } + virtual void DoFillIndexCheckers(const std::shared_ptr& info, const NSchemeShard::TOlapSchema& schema) const override; + + virtual TString DoBuildIndexImpl(TChunkedBatchReader& reader, const ui32 recordsCount) const override; + + virtual bool DoDeserializeFromProto(const NKikimrSchemeOp::TOlapIndexDescription& proto) override { + AFL_VERIFY(TBase::DoDeserializeFromProto(proto)); + AFL_VERIFY(proto.HasBloomNGrammFilter()); + auto& bFilter = proto.GetBloomNGrammFilter(); + if (bFilter.HasRecordsCount()) { + RecordsCount = bFilter.GetRecordsCount(); + if (!TConstants::CheckRecordsCount(RecordsCount)) { + return false; + } + } + HashesCount = bFilter.GetHashesCount(); + if (!TConstants::CheckHashesCount(HashesCount)) { + return false; + } + NGrammSize = bFilter.GetNGrammSize(); + if (!TConstants::CheckNGrammSize(NGrammSize)) { + return false; + } + FilterSizeBytes = bFilter.GetFilterSizeBytes(); + if (!TConstants::CheckFilterSizeBytes(FilterSizeBytes)) { + return false; + } + if (!bFilter.HasColumnId() || !bFilter.GetColumnId()) { + return false; + } + ColumnIds.emplace(bFilter.GetColumnId()); + Initialize(); + return true; + } + virtual void DoSerializeToProto(NKikimrSchemeOp::TOlapIndexDescription& proto) const override { + auto* filterProto = proto.MutableBloomNGrammFilter(); + AFL_VERIFY(TConstants::CheckNGrammSize(NGrammSize)); + AFL_VERIFY(TConstants::CheckFilterSizeBytes(FilterSizeBytes)); + AFL_VERIFY(TConstants::CheckHashesCount(HashesCount)); + AFL_VERIFY(TConstants::CheckRecordsCount(RecordsCount)); + AFL_VERIFY(ColumnIds.size() == 1); + filterProto->SetRecordsCount(RecordsCount); + filterProto->SetNGrammSize(NGrammSize); + filterProto->SetFilterSizeBytes(FilterSizeBytes); + filterProto->SetHashesCount(HashesCount); + filterProto->SetColumnId(*ColumnIds.begin()); + } + +public: + TIndexMeta() = default; + TIndexMeta(const ui32 indexId, const TString& indexName, const TString& storageId, const ui32 columnId, const ui32 hashesCount, + const ui32 filterSizeBytes, const ui32 nGrammSize, const ui32 recordsCount) + : TBase(indexId, indexName, { columnId }, storageId) + , NGrammSize(nGrammSize) + , FilterSizeBytes(filterSizeBytes) + , RecordsCount(recordsCount) + , HashesCount(hashesCount) + { + Initialize(); + } + + virtual TString GetClassName() const override { + return GetClassNameStatic(); + } +}; + +} // namespace NKikimr::NOlap::NIndexes diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/ya.make b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/ya.make new file mode 100644 index 000000000000..ef15149eb3ba --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm/ya.make @@ -0,0 +1,16 @@ +LIBRARY() + +SRCS( + GLOBAL constructor.cpp + GLOBAL meta.cpp + GLOBAL checker.cpp + const.cpp +) + +PEERDIR( + ydb/core/protos + ydb/core/formats/arrow + ydb/core/tx/columnshard/engines/storage/indexes/portions +) + +END() diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/count_min_sketch/meta.cpp b/ydb/core/tx/columnshard/engines/storage/indexes/count_min_sketch/meta.cpp index 80d154a751be..12166d3add1b 100644 --- a/ydb/core/tx/columnshard/engines/storage/indexes/count_min_sketch/meta.cpp +++ b/ydb/core/tx/columnshard/engines/storage/indexes/count_min_sketch/meta.cpp @@ -11,7 +11,7 @@ namespace NKikimr::NOlap::NIndexes::NCountMinSketch { -TString TIndexMeta::DoBuildIndexImpl(TChunkedBatchReader& reader) const { +TString TIndexMeta::DoBuildIndexImpl(TChunkedBatchReader& reader, const ui32 /*recordsCount*/) const { auto sketch = std::unique_ptr(TCountMinSketch::Create()); for (auto& colReader : reader) { diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/count_min_sketch/meta.h b/ydb/core/tx/columnshard/engines/storage/indexes/count_min_sketch/meta.h index 2c23af1fefdb..cb7abe56c614 100644 --- a/ydb/core/tx/columnshard/engines/storage/indexes/count_min_sketch/meta.h +++ b/ydb/core/tx/columnshard/engines/storage/indexes/count_min_sketch/meta.h @@ -25,7 +25,7 @@ class TIndexMeta: public TIndexByColumns { virtual void DoFillIndexCheckers(const std::shared_ptr& info, const NSchemeShard::TOlapSchema& schema) const override; - virtual TString DoBuildIndexImpl(TChunkedBatchReader& reader) const override; + virtual TString DoBuildIndexImpl(TChunkedBatchReader& reader, const ui32 recordsCount) const override; virtual bool DoDeserializeFromProto(const NKikimrSchemeOp::TOlapIndexDescription& proto) override { AFL_VERIFY(TBase::DoDeserializeFromProto(proto)); diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/max/meta.cpp b/ydb/core/tx/columnshard/engines/storage/indexes/max/meta.cpp index b672f278e017..20cd31857c7a 100644 --- a/ydb/core/tx/columnshard/engines/storage/indexes/max/meta.cpp +++ b/ydb/core/tx/columnshard/engines/storage/indexes/max/meta.cpp @@ -3,14 +3,13 @@ #include #include #include -#include #include #include namespace NKikimr::NOlap::NIndexes::NMax { -TString TIndexMeta::DoBuildIndexImpl(TChunkedBatchReader& reader) const { +TString TIndexMeta::DoBuildIndexImpl(TChunkedBatchReader& reader, const ui32 /*recordsCount*/) const { std::shared_ptr result; AFL_VERIFY(reader.GetColumnsCount() == 1)("count", reader.GetColumnsCount()); { diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/max/meta.h b/ydb/core/tx/columnshard/engines/storage/indexes/max/meta.h index ef58ede92956..4c2705bc672c 100644 --- a/ydb/core/tx/columnshard/engines/storage/indexes/max/meta.h +++ b/ydb/core/tx/columnshard/engines/storage/indexes/max/meta.h @@ -1,5 +1,6 @@ #pragma once #include + namespace NKikimr::NOlap::NIndexes::NMax { class TIndexMeta: public TIndexByColumns { @@ -18,7 +19,7 @@ class TIndexMeta: public TIndexByColumns { virtual void DoFillIndexCheckers( const std::shared_ptr& info, const NSchemeShard::TOlapSchema& schema) const override; - virtual TString DoBuildIndexImpl(TChunkedBatchReader& reader) const override; + virtual TString DoBuildIndexImpl(TChunkedBatchReader& reader, const ui32 recordsCount) const override; virtual bool DoDeserializeFromProto(const NKikimrSchemeOp::TOlapIndexDescription& proto) override { AFL_VERIFY(TBase::DoDeserializeFromProto(proto)); diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/portions/meta.cpp b/ydb/core/tx/columnshard/engines/storage/indexes/portions/meta.cpp index 3f8634cac619..00c024063584 100644 --- a/ydb/core/tx/columnshard/engines/storage/indexes/portions/meta.cpp +++ b/ydb/core/tx/columnshard/engines/storage/indexes/portions/meta.cpp @@ -1,13 +1,12 @@ #include "meta.h" #include -#include #include #include namespace NKikimr::NOlap::NIndexes { std::shared_ptr TIndexByColumns::DoBuildIndex( - const THashMap>>& data, const TIndexInfo& indexInfo) const { + const THashMap>>& data, const ui32 recordsCount, const TIndexInfo& indexInfo) const { AFL_VERIFY(Serializer); AFL_VERIFY(data.size()); std::vector columnReaders; @@ -16,12 +15,8 @@ std::shared_ptr TIndexByColumns::DoBuildIndex AFL_VERIFY(it != data.end()); columnReaders.emplace_back(it->second, indexInfo.GetColumnLoaderVerified(i)); } - ui32 recordsCount = 0; - for (auto&& i : data.begin()->second) { - recordsCount += i->GetRecordsCountVerified(); - } TChunkedBatchReader reader(std::move(columnReaders)); - const TString indexData = DoBuildIndexImpl(reader); + const TString indexData = DoBuildIndexImpl(reader, recordsCount); return std::make_shared(TChunkAddress(GetIndexId(), 0), recordsCount, indexData.size(), indexData); } diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/portions/meta.h b/ydb/core/tx/columnshard/engines/storage/indexes/portions/meta.h index 5356d5c4302d..f8e601c80fca 100644 --- a/ydb/core/tx/columnshard/engines/storage/indexes/portions/meta.h +++ b/ydb/core/tx/columnshard/engines/storage/indexes/portions/meta.h @@ -13,9 +13,10 @@ class TIndexByColumns: public IIndexMeta { protected: std::set ColumnIds; - virtual TString DoBuildIndexImpl(TChunkedBatchReader& reader) const = 0; + virtual TString DoBuildIndexImpl(TChunkedBatchReader& reader, const ui32 recordsCount) const = 0; - virtual std::shared_ptr DoBuildIndex(const THashMap>>& data, const TIndexInfo& indexInfo) const override final; + virtual std::shared_ptr DoBuildIndex(const THashMap>>& data, + const ui32 recordsCount, const TIndexInfo& indexInfo) const override final; virtual bool DoDeserializeFromProto(const NKikimrSchemeOp::TOlapIndexDescription& proto) override; TConclusionStatus CheckSameColumnsForModification(const IIndexMeta& newMeta) const; diff --git a/ydb/core/tx/columnshard/engines/storage/indexes/ya.make b/ydb/core/tx/columnshard/engines/storage/indexes/ya.make index 0459c906d836..b12360d2627d 100644 --- a/ydb/core/tx/columnshard/engines/storage/indexes/ya.make +++ b/ydb/core/tx/columnshard/engines/storage/indexes/ya.make @@ -3,6 +3,7 @@ LIBRARY() PEERDIR( ydb/core/tx/columnshard/engines/storage/indexes/portions ydb/core/tx/columnshard/engines/storage/indexes/bloom + ydb/core/tx/columnshard/engines/storage/indexes/bloom_ngramm ydb/core/tx/columnshard/engines/storage/indexes/max ydb/core/tx/columnshard/engines/storage/indexes/count_min_sketch ) diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/abstract/optimizer.h b/ydb/core/tx/columnshard/engines/storage/optimizer/abstract/optimizer.h index 4bd196e552d0..25339cb18171 100644 --- a/ydb/core/tx/columnshard/engines/storage/optimizer/abstract/optimizer.h +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/abstract/optimizer.h @@ -5,9 +5,8 @@ #include #include -#include - #include +#include namespace NKikimr::NOlap { class TColumnEngineChanges; @@ -17,7 +16,7 @@ class TPortionInfo; namespace NDataLocks { class TManager; } -} +} // namespace NKikimr::NOlap namespace NKikimr::NOlap::NStorageOptimizer { @@ -28,10 +27,13 @@ class TOptimizationPriority { TOptimizationPriority(const i64 level, const i64 levelWeight) : Level(level) , InternalLevelWeight(levelWeight) { - } public: + ui64 GetGeneralPriority() const { + return ((ui64)Level << 56) + InternalLevelWeight; + } + bool operator<(const TOptimizationPriority& item) const { return std::tie(Level, InternalLevelWeight) < std::tie(item.Level, item.InternalLevelWeight); } @@ -55,7 +57,6 @@ class TOptimizationPriority { static TOptimizationPriority Zero() { return TOptimizationPriority(0, 0); } - }; class TTaskDescription { @@ -66,11 +67,10 @@ class TTaskDescription { YDB_ACCESSOR_DEF(TString, Details); YDB_ACCESSOR_DEF(ui64, WeightCategory); YDB_ACCESSOR_DEF(i64, Weight); + public: TTaskDescription(const ui64 taskId) - : TaskId(taskId) - { - + : TaskId(taskId) { } bool operator<(const TTaskDescription& item) const { @@ -82,9 +82,12 @@ class IOptimizerPlanner { private: const ui64 PathId; YDB_READONLY(TInstant, ActualizationInstant, TInstant::Zero()); + protected: - virtual void DoModifyPortions(const THashMap>& add, const THashMap>& remove) = 0; - virtual std::shared_ptr DoGetOptimizationTask(std::shared_ptr granule, const std::shared_ptr& dataLocksManager) const = 0; + virtual void DoModifyPortions(const THashMap>& add, + const THashMap>& remove) = 0; + virtual std::shared_ptr DoGetOptimizationTask( + std::shared_ptr granule, const std::shared_ptr& dataLocksManager) const = 0; virtual TOptimizationPriority DoGetUsefulMetric() const = 0; virtual void DoActualize(const TInstant currentInstant) = 0; virtual TString DoDebugString() const { @@ -95,12 +98,17 @@ class IOptimizerPlanner { } virtual bool DoIsLocked(const std::shared_ptr& dataLocksManager) const = 0; virtual std::vector DoGetTasksDescription() const = 0; + virtual TConclusionStatus DoCheckWriteData() const { + return TConclusionStatus::Success(); + } public: IOptimizerPlanner(const ui64 pathId) - : PathId(pathId) - { + : PathId(pathId) { + } + TConclusionStatus CheckWriteData() const { + return DoCheckWriteData(); } std::vector GetTasksDescription() const { @@ -112,13 +120,13 @@ class IOptimizerPlanner { IOptimizerPlanner& Owner; THashMap> AddPortions; THashMap> RemovePortions; + public: TModificationGuard& AddPortion(const std::shared_ptr& portion); TModificationGuard& RemovePortion(const std::shared_ptr& portion); TModificationGuard(IOptimizerPlanner& owner) - : Owner(owner) - { + : Owner(owner) { } ~TModificationGuard() { Owner.ModifyPortions(AddPortions, RemovePortions); @@ -135,20 +143,19 @@ class IOptimizerPlanner { } virtual NArrow::NMerger::TIntervalPositions GetBucketPositions() const = 0; - bool IsLocked(const std::shared_ptr& dataLocksManager) const { - return DoIsLocked(dataLocksManager); - } NJson::TJsonValue SerializeToJsonVisual() const { return DoSerializeToJsonVisual(); } - void ModifyPortions(const THashMap>& add, const THashMap>& remove) { + void ModifyPortions(const THashMap>& add, + const THashMap>& remove) { NActors::TLogContextGuard g(NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("path_id", PathId)); DoModifyPortions(add, remove); } - std::shared_ptr GetOptimizationTask(std::shared_ptr granule, const std::shared_ptr& dataLocksManager) const; + std::shared_ptr GetOptimizationTask( + std::shared_ptr granule, const std::shared_ptr& dataLocksManager) const; TOptimizationPriority GetUsefulMetric() const { return DoGetUsefulMetric(); } @@ -165,17 +172,18 @@ class IOptimizerPlannerConstructor { YDB_READONLY(ui64, PathId, 0); YDB_READONLY_DEF(std::shared_ptr, Storages); YDB_READONLY_DEF(std::shared_ptr, PKSchema); + public: TBuildContext(const ui64 pathId, const std::shared_ptr& storages, const std::shared_ptr& pkSchema) : PathId(pathId) , Storages(storages) , PKSchema(pkSchema) { - } }; using TFactory = NObjectFactory::TObjectFactory; using TProto = NKikimrSchemeOp::TCompactionPlannerConstructorContainer; + private: virtual TConclusion> DoBuildPlanner(const TBuildContext& context) const = 0; virtual void DoSerializeToProto(TProto& proto) const = 0; @@ -185,7 +193,6 @@ class IOptimizerPlannerConstructor { virtual bool DoApplyToCurrentObject(IOptimizerPlanner& current) const = 0; public: - static std::shared_ptr BuildDefault() { auto result = TFactory::MakeHolder("l-buckets"); AFL_VERIFY(!!result); @@ -225,12 +232,12 @@ class IOptimizerPlannerConstructor { bool DeserializeFromProto(const TProto& proto) { return DoDeserializeFromProto(proto); } - }; class TOptimizerPlannerConstructorContainer: public NBackgroundTasks::TInterfaceProtoContainer { private: using TBase = NBackgroundTasks::TInterfaceProtoContainer; + public: using TBase::TBase; @@ -241,7 +248,6 @@ class TOptimizerPlannerConstructorContainer: public NBackgroundTasks::TInterface } return result; } - }; -} // namespace NKikimr::NOlap +} // namespace NKikimr::NOlap::NStorageOptimizer diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lbuckets/planner/counters.cpp b/ydb/core/tx/columnshard/engines/storage/optimizer/lbuckets/planner/counters.cpp index 5d2501faad92..250c5b659ade 100644 --- a/ydb/core/tx/columnshard/engines/storage/optimizer/lbuckets/planner/counters.cpp +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lbuckets/planner/counters.cpp @@ -1,18 +1,5 @@ #include "counters.h" -#include namespace NKikimr::NOlap::NStorageOptimizer::NLBuckets { -void TPortionCategoryCounters::AddPortion(const std::shared_ptr& p) { - RecordsCount->Add(p->NumRows()); - Count->Add(1); - Bytes->Add(p->GetTotalBlobBytes()); -} - -void TPortionCategoryCounters::RemovePortion(const std::shared_ptr& p) { - RecordsCount->Remove(p->NumRows()); - Count->Remove(1); - Bytes->Remove(p->GetTotalBlobBytes()); -} - } diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lbuckets/planner/counters.h b/ydb/core/tx/columnshard/engines/storage/optimizer/lbuckets/planner/counters.h index 64297bf21f79..e643a35cfa7c 100644 --- a/ydb/core/tx/columnshard/engines/storage/optimizer/lbuckets/planner/counters.h +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lbuckets/planner/counters.h @@ -1,5 +1,6 @@ #pragma once #include +#include namespace NKikimr::NOlap { class TPortionInfo; @@ -7,39 +8,8 @@ class TPortionInfo; namespace NKikimr::NOlap::NStorageOptimizer::NLBuckets { -class TPortionCategoryCounterAgents: public NColumnShard::TCommonCountersOwner { -private: - using TBase = NColumnShard::TCommonCountersOwner; -public: - const std::shared_ptr RecordsCount; - const std::shared_ptr Count; - const std::shared_ptr Bytes; - TPortionCategoryCounterAgents(NColumnShard::TCommonCountersOwner& base, const TString& categoryName) - : TBase(base, "category", categoryName) - , RecordsCount(TBase::GetValueAutoAggregations("ByGranule/Portions/RecordsCount")) - , Count(TBase::GetValueAutoAggregations("ByGranule/Portions/Count")) - , Bytes(TBase::GetValueAutoAggregations("ByGranule/Portions/Bytes")) - { - } -}; - -class TPortionCategoryCounters { -private: - std::shared_ptr RecordsCount; - std::shared_ptr Count; - std::shared_ptr Bytes; -public: - TPortionCategoryCounters(TPortionCategoryCounterAgents& agents) - { - RecordsCount = agents.RecordsCount->GetClient(); - Count = agents.Count->GetClient(); - Bytes = agents.Bytes->GetClient(); - } - - void AddPortion(const std::shared_ptr& p); - - void RemovePortion(const std::shared_ptr& p); -}; +using TPortionCategoryCounterAgents = NColumnShard::TPortionCategoryCounterAgents; +using TPortionCategoryCounters = NColumnShard::TPortionCategoryCounters; class TGlobalCounters: public NColumnShard::TCommonCountersOwner { private: @@ -54,6 +24,7 @@ class TGlobalCounters: public NColumnShard::TCommonCountersOwner { std::shared_ptr OldestCriticalActuality; std::shared_ptr MergeCoefficient; + public: NMonitoring::TDynamicCounters::TCounterPtr FinalBucketTaskCounter; NMonitoring::TDynamicCounters::TCounterPtr MiddleBucketTaskCounter; @@ -65,8 +36,7 @@ class TGlobalCounters: public NColumnShard::TCommonCountersOwner { NMonitoring::TDynamicCounters::TCounterPtr OptimizersCount; TGlobalCounters() - : TBase("BucketsStorageOptimizer") - { + : TBase("BucketsStorageOptimizer") { PortionsForMerge = std::make_shared(*this, "for_merge"); PortionsAlone = std::make_shared(*this, "alone"); SmallPortions = std::make_shared(*this, "small"); @@ -119,12 +89,12 @@ class TGlobalCounters: public NColumnShard::TCommonCountersOwner { static std::shared_ptr BuildFuturePortionsAggregation() { return std::make_shared(*Singleton()->FuturePortions); } - }; class TCounters { private: std::shared_ptr OldestCriticalActuality; + public: const std::shared_ptr PortionsForMerge; const std::shared_ptr PortionsAlone; @@ -140,11 +110,13 @@ class TCounters { if (isFinalBucket) { Singleton()->FinalBucketTaskCounter->Add(1); Singleton()->HistogramFinalBucketTask->Collect((Now() - youngestSnapshot).MilliSeconds() * 0.001, 1); - Singleton()->HistogramFinalBucketTaskSnapshotsDiff->Collect((youngestSnapshot - oldestSnapshot).MilliSeconds() * 0.001, 1); + Singleton()->HistogramFinalBucketTaskSnapshotsDiff->Collect( + (youngestSnapshot - oldestSnapshot).MilliSeconds() * 0.001, 1); } else { Singleton()->MiddleBucketTaskCounter->Add(1); Singleton()->HistogramMiddleBucketTask->Collect((Now() - youngestSnapshot).MilliSeconds() * 0.001, 1); - Singleton()->HistogramMiddleBucketTaskSnapshotsDiff->Collect((youngestSnapshot - oldestSnapshot).MilliSeconds() * 0.001, 1); + Singleton()->HistogramMiddleBucketTaskSnapshotsDiff->Collect( + (youngestSnapshot - oldestSnapshot).MilliSeconds() * 0.001, 1); } } @@ -157,15 +129,13 @@ class TCounters { , MergeCoefficient(TGlobalCounters::BuildMergeCoefficientAggregation()) , HistogramDiffSnapshots(TGlobalCounters::BuildHistogramDiffSnapshots()) , BucketsForMerge(Singleton()->BucketsForMerge->GetClient()) - , OptimizersCount(std::make_shared(Singleton()->OptimizersCount)) - { + , OptimizersCount(std::make_shared(Singleton()->OptimizersCount)) { OldestCriticalActuality = TGlobalCounters::BuildOldestCriticalActualityAggregation(); } void OnMinProblemSnapshot(const TDuration d) { OldestCriticalActuality->SetValue(d.MilliSeconds(), TInstant::Now() + TDuration::Seconds(10)); } - }; -} +} // namespace NKikimr::NOlap::NStorageOptimizer::NLBuckets diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lbuckets/planner/optimizer.h b/ydb/core/tx/columnshard/engines/storage/optimizer/lbuckets/planner/optimizer.h index d686fc719112..4dee5f628128 100644 --- a/ydb/core/tx/columnshard/engines/storage/optimizer/lbuckets/planner/optimizer.h +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lbuckets/planner/optimizer.h @@ -43,12 +43,12 @@ class TSimplePortionsGroupInfo { void AddPortion(const std::shared_ptr& p) { Bytes += p->GetTotalBlobBytes(); Count += 1; - RecordsCount += p->NumRows(); + RecordsCount += p->GetRecordsCount(); } void RemovePortion(const std::shared_ptr& p) { Bytes -= p->GetTotalBlobBytes(); Count -= 1; - RecordsCount -= p->NumRows(); + RecordsCount -= p->GetRecordsCount(); AFL_VERIFY(Bytes >= 0); AFL_VERIFY(Count >= 0); AFL_VERIFY(RecordsCount >= 0); @@ -215,20 +215,20 @@ class TPortionsPool { bool IsLocked(const std::shared_ptr& dataLocksManager) const { for (auto&& f : Futures) { for (auto&& p : f.second) { - if (auto lockInfo = dataLocksManager->IsLocked(*p.second)) { + if (auto lockInfo = dataLocksManager->IsLocked(*p.second, NDataLocks::ELockCategory::Compaction)) { AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "optimization_locked")("reason", *lockInfo); return true; } } } for (auto&& i : PreActuals) { - if (auto lockInfo = dataLocksManager->IsLocked(*i.second)) { + if (auto lockInfo = dataLocksManager->IsLocked(*i.second, NDataLocks::ELockCategory::Compaction)) { AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "optimization_locked")("reason", *lockInfo); return true; } } for (auto&& i : Actuals) { - if (auto lockInfo = dataLocksManager->IsLocked(*i.second)) { + if (auto lockInfo = dataLocksManager->IsLocked(*i.second, NDataLocks::ELockCategory::Compaction)) { AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "optimization_locked")("reason", *lockInfo); return true; } @@ -383,20 +383,20 @@ class TPortionsPool { return Actuals; } - std::vector> GetOptimizerTaskPortions(const ui64 sizeLimit, std::optional& separatePoint) const { - std::vector> sorted; + std::vector GetOptimizerTaskPortions(const ui64 sizeLimit, std::optional& separatePoint) const { + std::vector sorted; for (auto&& i : Actuals) { sorted.emplace_back(i.second); } for (auto&& i : PreActuals) { sorted.emplace_back(i.second); } - const auto pred = [](const std::shared_ptr& l, const std::shared_ptr& r) { + const auto pred = [](const TPortionInfo::TConstPtr& l, const TPortionInfo::TConstPtr& r) { return l->IndexKeyStart() < r->IndexKeyStart(); }; std::sort(sorted.begin(), sorted.end(), pred); - std::vector> result; + std::vector result; std::shared_ptr predictor = NCompaction::TGeneralCompactColumnEngineChanges::BuildMemoryPredictor(); ui64 txSizeLimit = 0; for (auto&& i : sorted) { @@ -405,7 +405,7 @@ class TPortionsPool { break; } txSizeLimit += i->GetTxVolume(); - if (predictor->AddPortion(*i) > sizeLimit && result.size() > 1) { + if (predictor->AddPortion(i) > sizeLimit && result.size() > 1) { break; } } @@ -746,7 +746,7 @@ class TPortionsBucket: public TMoveOnly { bool IsLocked(const std::shared_ptr& dataLocksManager) const { if (MainPortion) { - if (auto lockInfo = dataLocksManager->IsLocked(*MainPortion)) { + if (auto lockInfo = dataLocksManager->IsLocked(*MainPortion, NDataLocks::ELockCategory::Compaction)) { AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "optimization_locked")("reason", *lockInfo); return true; } @@ -852,7 +852,7 @@ class TPortionsBucket: public TMoveOnly { std::optional stopPoint; std::optional stopInstant; const ui64 memLimit = HasAppData() ? AppDataVerified().ColumnShardConfig.GetCompactionMemoryLimit() : 512 * 1024 * 1024; - std::vector> portions = Others.GetOptimizerTaskPortions(memLimit, stopPoint); + std::vector portions = Others.GetOptimizerTaskPortions(memLimit, stopPoint); bool forceMergeForTests = false; if (nextBorder) { if (MainPortion) { @@ -885,7 +885,7 @@ class TPortionsBucket: public TMoveOnly { ui64 size = 0; for (auto&& i : portions) { size += i->GetTotalBlobBytes(); - if (locksManager->IsLocked(*i)) { + if (locksManager->IsLocked(*i, NDataLocks::ELockCategory::Compaction)) { AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("info", Others.DebugString())("event", "skip_optimization")("reason", "busy"); return nullptr; } @@ -939,13 +939,14 @@ class TPortionsBucket: public TMoveOnly { dest.MoveNextBorderTo(*this); } - void Actualize(const TInstant currentInstant) { + [[nodiscard]] bool Actualize(const TInstant currentInstant) { if (currentInstant < NextActualizeInstant) { - return; + return false; } auto gChartsThis = StartModificationGuard(); NextActualizeInstant = Others.Actualize(currentInstant); RebuildOptimizedFeature(currentInstant); + return true; } void SplitOthersWith(TPortionsBucket& dest) { @@ -962,11 +963,18 @@ class TPortionsBucket: public TMoveOnly { class TPortionBuckets { private: + + struct TReverseComparator { + bool operator()(const i64 l, const i64 r) const { + return r < l; + } + }; + const std::shared_ptr PrimaryKeysSchema; const std::shared_ptr StoragesManager; std::shared_ptr LeftBucket; std::map> Buckets; - std::map> BucketsByWeight; + std::map, TReverseComparator> BucketsByWeight; std::shared_ptr Counters; std::vector> GetAffectedBuckets(const NArrow::TReplaceKey& fromInclude, const NArrow::TReplaceKey& toInclude) { std::vector> result; @@ -984,7 +992,11 @@ class TPortionBuckets { } void RemoveBucketFromRating(const std::shared_ptr& bucket) { - auto it = BucketsByWeight.find(bucket->GetLastWeight()); + return RemoveBucketFromRating(bucket, bucket->GetLastWeight()); + } + + void RemoveBucketFromRating(const std::shared_ptr& bucket, const i64 rating) { + auto it = BucketsByWeight.find(rating); AFL_VERIFY(it != BucketsByWeight.end()); AFL_VERIFY(it->second.erase(bucket.get())); if (it->second.empty()) { @@ -1068,10 +1080,8 @@ class TPortionBuckets { if (BucketsByWeight.empty()) { return false; } - if (BucketsByWeight.rbegin()->second.empty()) { - return false; - } - const TPortionsBucket* bucketForOptimization = *BucketsByWeight.rbegin()->second.begin(); + AFL_VERIFY(BucketsByWeight.begin()->second.size()); + const TPortionsBucket* bucketForOptimization = *BucketsByWeight.begin()->second.begin(); return bucketForOptimization->IsLocked(dataLocksManager); } @@ -1087,18 +1097,20 @@ class TPortionBuckets { void Actualize(const TInstant currentInstant) { RemoveBucketFromRating(LeftBucket); - LeftBucket->Actualize(currentInstant); + Y_UNUSED(LeftBucket->Actualize(currentInstant)); AddBucketToRating(LeftBucket); for (auto&& i : Buckets) { - RemoveBucketFromRating(i.second); - i.second->Actualize(currentInstant); - AddBucketToRating(i.second); + const i64 rating = i.second->GetLastWeight(); + if (i.second->Actualize(currentInstant)) { + RemoveBucketFromRating(i.second, rating); + AddBucketToRating(i.second); + } } } i64 GetWeight() const { AFL_VERIFY(BucketsByWeight.size()); - return BucketsByWeight.rbegin()->first; + return BucketsByWeight.begin()->first; } void RemovePortion(const std::shared_ptr& portion) { @@ -1112,11 +1124,11 @@ class TPortionBuckets { std::shared_ptr BuildOptimizationTask(std::shared_ptr granule, const std::shared_ptr& locksManager) const { AFL_VERIFY(BucketsByWeight.size()); - if (!BucketsByWeight.rbegin()->first) { + if (!BucketsByWeight.begin()->first) { return nullptr; } - AFL_VERIFY(BucketsByWeight.rbegin()->second.size()); - const TPortionsBucket* bucketForOptimization = *BucketsByWeight.rbegin()->second.begin(); + AFL_VERIFY(BucketsByWeight.begin()->second.size()); + const TPortionsBucket* bucketForOptimization = *BucketsByWeight.begin()->second.begin(); if (bucketForOptimization == LeftBucket.get()) { if (Buckets.size()) { return bucketForOptimization->BuildOptimizationTask(granule, locksManager, &Buckets.begin()->first, PrimaryKeysSchema, StoragesManager); @@ -1185,10 +1197,6 @@ class TPortionBuckets { AFL_VERIFY(i.second->GetStartPos()); result.AddPosition(*i.second->GetStartPos(), false); } - if (Buckets.size() && Buckets.rbegin()->second->GetPortion()->GetRecordsCount() > 1) { - NArrow::NMerger::TSortableBatchPosition pos(Buckets.rbegin()->second->GetPortion()->IndexKeyEnd().ToBatch(PrimaryKeysSchema), 0, PrimaryKeysSchema->field_names(), {}, false); - result.AddPosition(std::move(pos), false); - } return result; } }; @@ -1208,7 +1216,7 @@ class TOptimizerPlanner: public IOptimizerPlanner { return Buckets.IsLocked(dataLocksManager); } - virtual void DoModifyPortions(const THashMap>& add, const THashMap>& remove) override { + virtual void DoModifyPortions(const THashMap& add, const THashMap& remove) override { const TInstant now = TInstant::Now(); for (auto&& [_, i] : remove) { if (i->GetMeta().GetTierName() != IStoragesManager::DefaultStorageId && i->GetMeta().GetTierName() != "") { diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/constructor/constructor.cpp b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/constructor/constructor.cpp new file mode 100644 index 000000000000..bb81a47a5307 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/constructor/constructor.cpp @@ -0,0 +1,80 @@ +#include "constructor.h" +#include + +namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets { + +NKikimr::TConclusion> TOptimizerPlannerConstructor::DoBuildPlanner(const TBuildContext& context) const { + return std::make_shared(context.GetPathId(), context.GetStorages(), context.GetPKSchema(), Levels); +} + +bool TOptimizerPlannerConstructor::DoApplyToCurrentObject(IOptimizerPlanner& current) const { + auto* itemClass = dynamic_cast(¤t); + if (!itemClass) { + return false; + } + return true; +} + +bool TOptimizerPlannerConstructor::DoIsEqualTo(const IOptimizerPlannerConstructor& item) const { + const auto* itemClass = dynamic_cast(&item); + AFL_VERIFY(itemClass); + if (Levels.size() != itemClass->Levels.size()) { + return false; + } + for (ui32 i = 0; i < Levels.size(); ++i) { + if (!Levels[i]->IsEqualTo(*itemClass->Levels[i].GetObjectPtrVerified())) { + return false; + } + } + return true; +} + +void TOptimizerPlannerConstructor::DoSerializeToProto(TProto& proto) const { + *proto.MutableLCBuckets() = NKikimrSchemeOp::TCompactionPlannerConstructorContainer::TLCOptimizer(); + for (auto&& i : Levels) { + *proto.MutableLCBuckets()->AddLevels() = i.SerializeToProto(); + } +} + +bool TOptimizerPlannerConstructor::DoDeserializeFromProto(const TProto& proto) { + if (!proto.HasLCBuckets()) { + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("error", "cannot parse lc-buckets optimizer from proto")("proto", proto.DebugString()); + return false; + } + for (auto&& i : proto.GetLCBuckets().GetLevels()) { + TLevelConstructorContainer lContainer; + if (!lContainer.DeserializeFromProto(i)) { + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("error", "cannot parse lc-bucket level")("proto", i.DebugString()); + return false; + } + Levels.emplace_back(std::move(lContainer)); + } + return true; +} + +NKikimr::TConclusionStatus TOptimizerPlannerConstructor::DoDeserializeFromJson(const NJson::TJsonValue& jsonInfo) { + if (!jsonInfo.Has("levels")) { + return TConclusionStatus::Fail("no levels description"); + } + if (!jsonInfo["levels"].IsArray()) { + return TConclusionStatus::Fail("levels have to been array in json description"); + } + auto& arr = jsonInfo["levels"].GetArray(); + if (!arr.size()) { + return TConclusionStatus::Fail("no objects in json array 'levels'"); + } + for (auto&& i : arr) { + const auto className = i["class_name"].GetStringRobust(); + auto level = ILevelConstructor::TFactory::MakeHolder(className); + if (!level) { + return TConclusionStatus::Fail("incorrect level class_name: " + className); + } + if (!level->DeserializeFromJson(i)) { + return TConclusionStatus::Fail("cannot parse level: " + i.GetStringRobust()); + } + Levels.emplace_back(TLevelConstructorContainer(std::shared_ptr(level.Release()))); + } + return TConclusionStatus::Success(); +} + +} // namespace NKikimr::NOlap::NStorageOptimizer::NLBuckets diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/constructor/constructor.h b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/constructor/constructor.h new file mode 100644 index 000000000000..78a73993c57f --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/constructor/constructor.h @@ -0,0 +1,83 @@ +#pragma once +#include +#include +#include + +namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets { + +class ILevelConstructor { +private: + virtual std::shared_ptr DoBuildLevel( + const std::shared_ptr& nextLevel, const ui32 indexLevel, const TLevelCounters& counters) const = 0; + virtual TConclusionStatus DoDeserializeFromJson(const NJson::TJsonValue& json) = 0; + virtual bool DoDeserializeFromProto(const NKikimrSchemeOp::TCompactionLevelConstructorContainer& proto) = 0; + virtual void DoSerializeToProto(NKikimrSchemeOp::TCompactionLevelConstructorContainer& proto) const = 0; + virtual bool IsEqualToSameClass(const ILevelConstructor& item) const = 0; + +public: + using TFactory = NObjectFactory::TObjectFactory; + using TProto = NKikimrSchemeOp::TCompactionLevelConstructorContainer; + + virtual ~ILevelConstructor() = default; + + bool IsEqualTo(const ILevelConstructor& item) const { + if (GetClassName() != item.GetClassName()) { + return false; + } + return IsEqualToSameClass(item); + } + + std::shared_ptr BuildLevel( + const std::shared_ptr& nextLevel, const ui32 indexLevel, const TLevelCounters& counters) const { + return DoBuildLevel(nextLevel, indexLevel, counters); + } + + TConclusionStatus DeserializeFromJson(const NJson::TJsonValue& json) { + return DoDeserializeFromJson(json); + } + + bool DeserializeFromProto(const TProto& proto) { + return DoDeserializeFromProto(proto); + } + void SerializeToProto(NKikimrSchemeOp::TCompactionLevelConstructorContainer& proto) const { + return DoSerializeToProto(proto); + } + virtual TString GetClassName() const = 0; +}; + +class TLevelConstructorContainer: public NBackgroundTasks::TInterfaceProtoContainer { +private: + using TBase = NBackgroundTasks::TInterfaceProtoContainer; + +public: + using TBase::TBase; +}; + +class TOptimizerPlannerConstructor: public IOptimizerPlannerConstructor { +public: + static TString GetClassNameStatic() { + return "lc-buckets"; + } + +private: + std::vector Levels; + + static inline const TFactory::TRegistrator Registrator = + TFactory::TRegistrator(GetClassNameStatic()); + + virtual void DoSerializeToProto(TProto& proto) const override; + + virtual bool DoDeserializeFromProto(const TProto& proto) override; + virtual TConclusionStatus DoDeserializeFromJson(const NJson::TJsonValue& jsonInfo) override; + virtual bool DoApplyToCurrentObject(IOptimizerPlanner& current) const override; + + virtual TConclusion> DoBuildPlanner(const TBuildContext& context) const override; + virtual bool DoIsEqualTo(const IOptimizerPlannerConstructor& item) const override; + +public: + virtual TString GetClassName() const override { + return GetClassNameStatic(); + } +}; + +} // namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/constructor/ya.make b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/constructor/ya.make new file mode 100644 index 000000000000..86918b521992 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/constructor/ya.make @@ -0,0 +1,15 @@ +LIBRARY() + +SRCS( + GLOBAL constructor.cpp + GLOBAL zero_level.cpp +) + +PEERDIR( + contrib/libs/apache/arrow + ydb/core/protos + ydb/core/formats/arrow + ydb/core/tx/columnshard/engines/changes/abstract +) + +END() diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/constructor/zero_level.cpp b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/constructor/zero_level.cpp new file mode 100644 index 000000000000..7b5d8599ec1c --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/constructor/zero_level.cpp @@ -0,0 +1,60 @@ +#include "zero_level.h" + +#include + +namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets { + +TConclusionStatus TZeroLevelConstructor::DoDeserializeFromJson(const NJson::TJsonValue& json) { + if (!json.IsMap()) { + return TConclusionStatus::Fail("incorrect level description"); + } + if (json.Has("portions_live_duration")) { + const auto& jsonValue = json["portions_live_duration"]; + if (!jsonValue.IsString()) { + return TConclusionStatus::Fail("incorrect portions_live_duration value (have to be similar as 10s, 20m, 30d, etc)"); + } + TDuration d; + if (!TDuration::TryParse(jsonValue.GetString(), d)) { + return TConclusionStatus::Fail("cannot parse portions_live_duration value " + jsonValue.GetString()); + } + PortionsLiveDuration = d; + } + if (json.Has("expected_blobs_size")) { + const auto& jsonValue = json["expected_blobs_size"]; + if (!jsonValue.IsUInteger()) { + return TConclusionStatus::Fail("incorrect expected_blobs_size value (have to be unsigned int)"); + } + ExpectedBlobsSize = jsonValue.GetUInteger(); + } + return TConclusionStatus::Success(); +} + +bool TZeroLevelConstructor::DoDeserializeFromProto(const NKikimrSchemeOp::TCompactionLevelConstructorContainer& proto) { + if (!proto.HasZeroLevel()) { + return true; + } + if (proto.GetZeroLevel().HasPortionsLiveDurationSeconds()) { + PortionsLiveDuration = TDuration::Seconds(proto.GetZeroLevel().GetPortionsLiveDurationSeconds()); + } + if (proto.GetZeroLevel().HasExpectedBlobsSize()) { + ExpectedBlobsSize = proto.GetZeroLevel().GetExpectedBlobsSize(); + } + return true; +} + +void TZeroLevelConstructor::DoSerializeToProto(NKikimrSchemeOp::TCompactionLevelConstructorContainer& proto) const { + if (PortionsLiveDuration) { + proto.MutableZeroLevel()->SetPortionsLiveDurationSeconds(PortionsLiveDuration->Seconds()); + } + if (ExpectedBlobsSize) { + proto.MutableZeroLevel()->SetExpectedBlobsSize(*ExpectedBlobsSize); + } +} + +std::shared_ptr TZeroLevelConstructor::DoBuildLevel( + const std::shared_ptr& nextLevel, const ui32 indexLevel, const TLevelCounters& counters) const { + return std::make_shared( + indexLevel, nextLevel, counters, PortionsLiveDuration.value_or(TDuration::Max()), ExpectedBlobsSize.value_or((ui64)1 << 20)); +} + +} // namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/constructor/zero_level.h b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/constructor/zero_level.h new file mode 100644 index 000000000000..b80dc88d2a62 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/constructor/zero_level.h @@ -0,0 +1,34 @@ +#pragma once +#include "constructor.h" + +namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets { + +class TZeroLevelConstructor: public ILevelConstructor { +public: + static TString GetClassNameStatic() { + return "Zero"; + } + +private: + std::optional PortionsLiveDuration; + std::optional ExpectedBlobsSize; + + virtual std::shared_ptr DoBuildLevel( + const std::shared_ptr& nextLevel, const ui32 indexLevel, const TLevelCounters& counters) const override; + virtual TConclusionStatus DoDeserializeFromJson(const NJson::TJsonValue& json) override; + virtual bool DoDeserializeFromProto(const NKikimrSchemeOp::TCompactionLevelConstructorContainer& proto) override; + virtual void DoSerializeToProto(NKikimrSchemeOp::TCompactionLevelConstructorContainer& proto) const override; + virtual bool IsEqualToSameClass(const ILevelConstructor& item) const override { + const auto& itemCast = dynamic_cast(item); + return PortionsLiveDuration == itemCast.PortionsLiveDuration && ExpectedBlobsSize == itemCast.ExpectedBlobsSize; + } + + static const inline TFactory::TRegistrator Registrator = TFactory::TRegistrator(GetClassNameStatic()); + +public: + virtual TString GetClassName() const override { + return GetClassNameStatic(); + } +}; + +} // namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/abstract.cpp b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/abstract.cpp new file mode 100644 index 000000000000..b4256237b47d --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/abstract.cpp @@ -0,0 +1,57 @@ +#include "abstract.h" + +namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets { + +NArrow::NMerger::TIntervalPositions TCompactionTaskData::GetCheckPositions( + const std::shared_ptr& pkSchema, const bool withMoved) { + NArrow::NMerger::TIntervalPositions result; + for (auto&& i : GetFinishPoints(withMoved)) { + result.AddPosition(NArrow::NMerger::TSortableBatchPosition(i.ToBatch(pkSchema), 0, pkSchema->field_names(), {}, false), false); + } + return result; +} + +std::vector TCompactionTaskData::GetFinishPoints(const bool withMoved) { + std::vector points; + if (MemoryUsage > ((ui64)1 << 30)) { + for (auto&& i : Portions) { + if (!CurrentLevelPortionIds.contains(i->GetPortionId())) { + points.emplace_back(i->IndexKeyStart()); + } + } + std::sort(points.begin(), points.end()); + return points; + } + + THashSet middlePortions; + for (auto&& i : Chains) { + for (auto&& p : i.GetPortions()) { + middlePortions.emplace(p->GetPortionId()); + } + } + THashSet endPortions; + for (auto&& i : Chains) { + if (!i.GetNotIncludedNextPortion()) { + continue; + } + if (middlePortions.contains(i.GetNotIncludedNextPortion()->GetPortionId())) { + continue; + } + if (!endPortions.emplace(i.GetNotIncludedNextPortion()->GetPortionId()).second) { + continue; + } + points.emplace_back(i.GetNotIncludedNextPortion()->IndexKeyStart()); + } + if (withMoved) { + for (auto&& i : GetMovePortions()) { + points.emplace_back(i->IndexKeyStart()); + } + } + if (StopSeparation) { + points.emplace_back(*StopSeparation); + } + std::sort(points.begin(), points.end()); + return points; +} + +} diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/abstract.h b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/abstract.h new file mode 100644 index 000000000000..6eac27d4dc13 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/abstract.h @@ -0,0 +1,384 @@ +#pragma once +#include "counters.h" + +#include +#include + +#include + +namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets { + +class TOrderedPortion { +private: + TPortionInfo::TConstPtr Portion; + NArrow::TReplaceKey Start; + ui64 PortionId; + NArrow::NMerger::TSortableBatchPosition StartPosition; + +public: + const TPortionInfo::TConstPtr& GetPortion() const { + AFL_VERIFY(Portion); + return Portion; + } + + const NArrow::TReplaceKey& GetStart() const { + return Start; + } + + const NArrow::NMerger::TSortableBatchPosition& GetStartPosition() const { + AFL_VERIFY(Portion); + return StartPosition; + } + + TOrderedPortion(const TPortionInfo::TConstPtr& portion) + : Portion(portion) + , Start(portion->IndexKeyStart()) + , PortionId(portion->GetPortionId()) + , StartPosition(Portion->GetMeta().GetFirstLastPK().GetBatch(), 0, false) { + } + + TOrderedPortion(const TPortionInfo::TPtr& portion) + : Portion(portion) + , Start(portion->IndexKeyStart()) + , PortionId(portion->GetPortionId()) + , StartPosition(Portion->GetMeta().GetFirstLastPK().GetBatch(), 0, false) { + } + + TOrderedPortion(const NArrow::TReplaceKey& start) + : Start(start) + , PortionId(Max()) { + } + + bool operator<(const TOrderedPortion& item) const { + auto cmp = Start.CompareNotNull(item.Start); + if (cmp == std::partial_ordering::equivalent) { + return PortionId < item.PortionId; + } else { + return cmp == std::partial_ordering::less; + } + } +}; + +class TChainAddress { +private: + YDB_READONLY(ui64, FromPortionId, 0); + YDB_READONLY(ui64, ToPortionId, 0); + bool LastIsSeparator = false; + +public: + TChainAddress(const ui64 from, const ui64 to, const bool lastIsSeparator) + : FromPortionId(from) + , ToPortionId(to) + , LastIsSeparator(lastIsSeparator) { + } + + bool operator<(const TChainAddress& item) const { + return std::tie(FromPortionId, ToPortionId, LastIsSeparator) < std::tie(item.FromPortionId, item.ToPortionId, item.LastIsSeparator); + } + + TString DebugString() const { + return TStringBuilder() << FromPortionId << "-" << ToPortionId << ":" << LastIsSeparator; + } +}; + +class TPortionsChain { +private: + std::vector Portions; + + TPortionInfo::TConstPtr NotIncludedNextPortion; + +public: + const std::vector& GetPortions() const { + return Portions; + } + + const TPortionInfo::TConstPtr& GetNotIncludedNextPortion() const { + return NotIncludedNextPortion; + } + + TChainAddress GetAddress() const { + if (Portions.size()) { + return TChainAddress(Portions.front()->GetPortionId(), + NotIncludedNextPortion ? NotIncludedNextPortion->GetPortionId() : Portions.back()->GetPortionId(), !!NotIncludedNextPortion); + } else { + AFL_VERIFY(NotIncludedNextPortion); + return TChainAddress(NotIncludedNextPortion->GetPortionId(), NotIncludedNextPortion->GetPortionId(), true); + } + } + + TPortionsChain(const std::vector& portions, const TPortionInfo::TConstPtr& notIncludedNextPortion) + : Portions(portions) + , NotIncludedNextPortion(notIncludedNextPortion) { + AFL_VERIFY(Portions.size() || !!NotIncludedNextPortion); + } +}; + +class TCompactionTaskData { +private: + YDB_ACCESSOR_DEF(std::vector, Portions); + const ui64 TargetCompactionLevel = 0; + std::shared_ptr Predictor = + NCompaction::TGeneralCompactColumnEngineChanges::BuildMemoryPredictor(); + ui64 MemoryUsage = 0; + THashSet UsedPortionIds; + THashSet RepackPortionIds; + + TSimplePortionsGroupInfo CurrentLevelPortionsInfo; + TSimplePortionsGroupInfo TargetLevelPortionsInfo; + + std::set NextLevelChainIds; + THashSet NextLevelPortionIds; + THashSet CurrentLevelPortionIds; + std::vector Chains; + std::optional StopSeparation; + +public: + ui64 GetTargetCompactionLevel() const { + if (MemoryUsage > ((ui64)1 << 30)) { + AFL_VERIFY(TargetCompactionLevel); + return TargetCompactionLevel - 1; + } else { + return TargetCompactionLevel; + } + } + + void SetStopSeparation(const NArrow::TReplaceKey& point) { + AFL_VERIFY(!StopSeparation); + StopSeparation = point; + } + + std::vector GetRepackPortions(const ui32 /*levelIdx*/) const { + std::vector result; + if (MemoryUsage > ((ui64)1 << 30)) { + auto predictor = NCompaction::TGeneralCompactColumnEngineChanges::BuildMemoryPredictor(); + for (auto&& i : Portions) { + if (CurrentLevelPortionIds.contains(i->GetPortionId())) { + if (predictor->AddPortion(i) < MemoryUsage || result.size() < 2) { + result.emplace_back(i); + } else { + break; + } + } + } + return result; + } else { + return Portions; + } + auto moveIds = GetMovePortionIds(); + for (auto&& i : Portions) { + if (!moveIds.contains(i->GetPortionId())) { + result.emplace_back(i); + } + } + return result; + } + + std::vector GetMovePortions() const { + if (MemoryUsage > ((ui64)1 << 30)) { + return {}; + } + auto moveIds = GetMovePortionIds(); + std::vector result; + for (auto&& i : Portions) { + if (moveIds.contains(i->GetPortionId())) { + result.emplace_back(i); + } + } + return result; + } + + ui64 GetRepackPortionsVolume() const { + return TargetLevelPortionsInfo.GetRawBytes(); + } + + THashSet GetMovePortionIds() const { + auto movePortionIds = CurrentLevelPortionIds; + for (auto&& i : RepackPortionIds) { + movePortionIds.erase(i); + } + return movePortionIds; + } + + TString DebugString() const { + TStringBuilder sb; + sb << "target_level_chains:["; + for (auto&& i : NextLevelChainIds) { + sb << i.DebugString() << ","; + } + sb << "];target_level_portions:[" << JoinSeq(",", NextLevelPortionIds) << "];current_level_portions_info:{" + << CurrentLevelPortionsInfo.DebugString() << "};target_level_portions_info:{" << TargetLevelPortionsInfo.DebugString() << "};"; + sb << "move_portion_ids:[" << JoinSeq(",", GetMovePortionIds()) << "]"; + return sb; + } + + TCompactionTaskData() = default; + + const THashSet& GetPortionIds() const { + return UsedPortionIds; + } + + bool Contains(const ui64 portionId) const { + return UsedPortionIds.contains(portionId); + } + + bool IsEmpty() const { + return !Portions.size(); + } + + NArrow::NMerger::TIntervalPositions GetCheckPositions(const std::shared_ptr& pkSchema, const bool withMoved); + std::vector GetFinishPoints(const bool withMoved); + + void AddCurrentLevelPortion(const TPortionInfo::TConstPtr& portion, std::optional&& chain, const bool repackMoved) { + AFL_VERIFY(UsedPortionIds.emplace(portion->GetPortionId()).second); + AFL_VERIFY(CurrentLevelPortionIds.emplace(portion->GetPortionId()).second); + Portions.emplace_back(portion); + CurrentLevelPortionsInfo.AddPortion(portion); + if (repackMoved || (chain && chain->GetPortions().size())) { + MemoryUsage = Predictor->AddPortion(portion); + } + + if (chain) { + if (chain->GetPortions().size()) { + RepackPortionIds.emplace(portion->GetPortionId()); + } + if (NextLevelChainIds.emplace(chain->GetAddress()).second) { + Chains.emplace_back(std::move(*chain)); + for (auto&& i : Chains.back().GetPortions()) { + if (!UsedPortionIds.emplace(i->GetPortionId()).second) { + AFL_VERIFY(NextLevelPortionIds.contains(i->GetPortionId())); + continue; + } + TargetLevelPortionsInfo.AddPortion(i); + Portions.emplace_back(i); + MemoryUsage = Predictor->AddPortion(i); + AFL_VERIFY(NextLevelPortionIds.emplace(i->GetPortionId()).second); + } + } + } + } + + bool CanTakeMore() const { + if (Portions.size() <= 1) { + return true; + } + return MemoryUsage < (((ui64)512) << 20) && CurrentLevelPortionsInfo.GetCount() + TargetLevelPortionsInfo.GetCount() < 1000 + && Portions.size() < 10000; + } + + TCompactionTaskData(const ui64 targetCompactionLevel) + : TargetCompactionLevel(targetCompactionLevel) { + } +}; + +class IPortionsLevel { +private: + virtual void DoModifyPortions(const std::vector& add, const std::vector& remove) = 0; + virtual ui64 DoGetWeight() const = 0; + virtual NArrow::NMerger::TIntervalPositions DoGetBucketPositions(const std::shared_ptr& pkSchema) const = 0; + virtual TCompactionTaskData DoGetOptimizationTask() const = 0; + virtual std::optional DoGetAffectedPortions(const NArrow::TReplaceKey& from, const NArrow::TReplaceKey& to) const = 0; + virtual ui64 DoGetAffectedPortionBytes(const NArrow::TReplaceKey& from, const NArrow::TReplaceKey& to) const = 0; + + virtual NJson::TJsonValue DoSerializeToJson() const { + return NJson::JSON_MAP; + } + + virtual TString DoDebugString() const { + return ""; + } + + YDB_READONLY(ui64, LevelId, 0); + +protected: + std::shared_ptr NextLevel; + TSimplePortionsGroupInfo PortionsInfo; + mutable std::optional PredOptimization = TInstant::Now(); + +public: + bool HasData() const { + return PortionsInfo.GetCount(); + } + + virtual std::optional GetPackKff() const { + if (PortionsInfo.GetRawBytes()) { + return 1.0 * PortionsInfo.GetBlobBytes() / PortionsInfo.GetRawBytes(); + } else if (!NextLevel) { + return std::nullopt; + } else { + return NextLevel->GetPackKff(); + } + } + + const TSimplePortionsGroupInfo& GetPortionsInfo() const { + return PortionsInfo; + } + + const std::shared_ptr& GetNextLevel() const { + return NextLevel; + } + + virtual ~IPortionsLevel() = default; + IPortionsLevel(const ui64 levelId, const std::shared_ptr& nextLevel) + : LevelId(levelId) + , NextLevel(nextLevel) { + } + + bool CanTakePortion(const TPortionInfo::TConstPtr& portion) const { + auto chain = GetAffectedPortions(portion->IndexKeyStart(), portion->IndexKeyEnd()); + if (chain && chain->GetPortions().size()) { + return false; + } + return true; + } + + virtual bool IsLocked(const std::shared_ptr& locksManager) const = 0; + + virtual TTaskDescription GetTaskDescription() const { + TTaskDescription result(0); + result.SetWeight(GetWeight()); + result.SetDetails(SerializeToJson().GetStringRobust()); + return result; + } + + NJson::TJsonValue SerializeToJson() const { + NJson::TJsonValue result = NJson::JSON_MAP; + result.InsertValue("level", LevelId); + result.InsertValue("weight", GetWeight()); + result.InsertValue("portions", PortionsInfo.SerializeToJson()); + result.InsertValue("details", DoSerializeToJson()); + return result; + } + + TString DebugString() const { + return DoDebugString(); + } + + std::optional GetAffectedPortions(const NArrow::TReplaceKey& from, const NArrow::TReplaceKey& to) const { + return DoGetAffectedPortions(from, to); + } + + ui64 GetAffectedPortionBytes(const NArrow::TReplaceKey& from, const NArrow::TReplaceKey& to) const { + return DoGetAffectedPortionBytes(from, to); + } + + void ModifyPortions(const std::vector& add, const std::vector& remove) { + return DoModifyPortions(add, remove); + } + + ui64 GetWeight() const { + return DoGetWeight(); + } + + NArrow::NMerger::TIntervalPositions GetBucketPositions(const std::shared_ptr& pkSchema) const { + return DoGetBucketPositions(pkSchema); + } + + TCompactionTaskData GetOptimizationTask() const { + AFL_VERIFY(NextLevel); + TCompactionTaskData result = DoGetOptimizationTask(); + AFL_VERIFY(!result.IsEmpty()); + return result; + } +}; + +} // namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/accumulation_level.cpp b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/accumulation_level.cpp new file mode 100644 index 000000000000..fcd7fcbb9bb2 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/accumulation_level.cpp @@ -0,0 +1,5 @@ +#include "accumulation_level.h" + +namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets { + +} diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/accumulation_level.h b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/accumulation_level.h new file mode 100644 index 000000000000..9d70fc76a649 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/accumulation_level.h @@ -0,0 +1,111 @@ +#pragma once +#include "abstract.h" +#include "counters.h" + +namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets { + +class TAccumulationLevelPortions: public IPortionsLevel { +private: + using TBase = IPortionsLevel; + const TLevelCounters LevelCounters; + + std::set Portions; + + virtual std::optional DoGetAffectedPortions( + const NArrow::TReplaceKey& /*from*/, const NArrow::TReplaceKey& /*to*/) const override { + return std::nullopt; + } + + virtual ui64 DoGetAffectedPortionBytes(const NArrow::TReplaceKey& /*from*/, const NArrow::TReplaceKey& /*to*/) const override { + return 0; + } + + virtual ui64 DoGetWeight() const override { + if (PortionsInfo.GetCount() <= 1) { + return 0; + } + + THashSet portionIds; + ui64 affectedRawBytes = 0; + auto chain = + NextLevel->GetAffectedPortions(Portions.begin()->GetPortion()->IndexKeyStart(), Portions.rbegin()->GetPortion()->IndexKeyEnd()); + if (chain) { + auto it = Portions.begin(); + auto itNext = chain->GetPortions().begin(); + while (it != Portions.end() && itNext != chain->GetPortions().end()) { + const auto& nextLevelPortion = *itNext; + if (nextLevelPortion->IndexKeyEnd() < it->GetPortion()->IndexKeyStart()) { + ++itNext; + } else if (it->GetPortion()->IndexKeyEnd() < nextLevelPortion->IndexKeyStart()) { + ++it; + } else { + if (portionIds.emplace(nextLevelPortion->GetPortionId()).second) { + affectedRawBytes += nextLevelPortion->GetTotalRawBytes(); + } + ++itNext; + } + } + } + const ui64 mb = ((affectedRawBytes + PortionsInfo.GetRawBytes()) >> 20) + 1; + return 1000.0 * PortionsInfo.GetCount() * PortionsInfo.GetCount() / mb; + } + +public: + TAccumulationLevelPortions(const ui64 levelId, const std::shared_ptr& nextLevel, const TLevelCounters& levelCounters) + : TBase(levelId, nextLevel) + , LevelCounters(levelCounters) { + } + + virtual bool IsLocked(const std::shared_ptr& locksManager) const override { + for (auto&& i : Portions) { + if (locksManager->IsLocked(*i.GetPortion(), NDataLocks::ELockCategory::Compaction)) { + return true; + } + } + return false; + } + + virtual void DoModifyPortions(const std::vector& add, const std::vector& remove) override { + for (auto&& i : remove) { + auto it = Portions.find(i); + AFL_VERIFY(it != Portions.end()); + AFL_VERIFY(it->GetPortion()->GetPortionId() == i->GetPortionId()); + PortionsInfo.RemovePortion(i); + Portions.erase(it); + LevelCounters.Portions->RemovePortion(i); + } + for (auto&& i : add) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "add_accum")("portion_id", i->GetPortionId())( + "blob_size", i->GetTotalBlobBytes()); + AFL_VERIFY(Portions.emplace(i).second); + PortionsInfo.AddPortion(i); + LevelCounters.Portions->AddPortion(i); + } + } + + virtual TCompactionTaskData DoGetOptimizationTask() const override { + AFL_VERIFY(Portions.size()); + std::shared_ptr targetLevel = GetNextLevel(); + AFL_VERIFY(targetLevel); + TCompactionTaskData result(targetLevel->GetLevelId()); + { + for (auto&& i : Portions) { + result.AddCurrentLevelPortion( + i.GetPortion(), targetLevel->GetAffectedPortions(i.GetPortion()->IndexKeyStart(), i.GetPortion()->IndexKeyEnd()), true); + if (!result.CanTakeMore()) { + result.SetStopSeparation(i.GetPortion()->IndexKeyStart()); + break; + } + } + } + return result; + } + + virtual NArrow::NMerger::TIntervalPositions DoGetBucketPositions(const std::shared_ptr& /*pkSchema*/) const override { + AFL_VERIFY(false); + NArrow::NMerger::TIntervalPositions result; + return result; + } +}; + +} // namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/common_level.cpp b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/common_level.cpp new file mode 100644 index 000000000000..e05da7148a3d --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/common_level.cpp @@ -0,0 +1,77 @@ +#include "common_level.h" + +namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets { + +void TLevelPortions::DoModifyPortions(const std::vector& add, const std::vector& remove) { + for (auto&& i : remove) { + auto it = Portions.find(i); + AFL_VERIFY(it != Portions.end()); + AFL_VERIFY(it->GetPortion()->GetPortionId() == i->GetPortionId()); + PortionsInfo.RemovePortion(i); + Portions.erase(it); + LevelCounters.Portions->RemovePortion(i); + } + TStringBuilder sb; + for (auto&& i : add) { + sb << i->GetPortionId() << ","; + auto info = Portions.emplace(i); + i->AddRuntimeFeature(TPortionInfo::ERuntimeFeature::Optimized); + AFL_VERIFY(info.second); + PortionsInfo.AddPortion(i); + if (StrictOneLayer) { + { + auto it = info.first; + ++it; + if (it != Portions.end()) { + AFL_VERIFY(i->IndexKeyEnd() < it->GetStart())("start", i->IndexKeyStart().DebugString())("end", i->IndexKeyEnd().DebugString())( + "next", it->GetStart().DebugString())("next1", it->GetStart().DebugString())( + "next2", it->GetPortion()->IndexKeyEnd().DebugString())("level_id", GetLevelId())( + "portion_id_new", i->GetPortionId())("portion_id_old", it->GetPortion()->GetPortionId())( + "portion_old", it->GetPortion()->DebugString())("add", sb); + } + } + { + auto it = info.first; + if (it != Portions.begin()) { + --it; + AFL_VERIFY(it->GetPortion()->IndexKeyEnd() < i->IndexKeyStart()) + ("start", i->IndexKeyStart().DebugString())("finish", i->IndexKeyEnd().DebugString())("pred_start", + it->GetPortion()->IndexKeyStart().DebugString())("pred_finish", it->GetPortion()->IndexKeyEnd().DebugString())("level_id", GetLevelId())( + "portion_id_new", i->GetPortionId())("portion_id_old", it->GetPortion()->GetPortionId())("add", sb); + } + } + } + LevelCounters.Portions->AddPortion(i); + } +} + +TCompactionTaskData TLevelPortions::DoGetOptimizationTask() const { + AFL_VERIFY(GetNextLevel()); + ui64 compactedData = 0; + TCompactionTaskData result(GetNextLevel()->GetLevelId()); + auto itFwd = Portions.begin(); + AFL_VERIFY(itFwd != Portions.end()); + auto itBkwd = itFwd; + if (itFwd != Portions.begin()) { + --itBkwd; + } + while (GetLevelBlobBytesLimit() * 0.5 + compactedData < (ui64)PortionsInfo.GetBlobBytes() && + (itBkwd != Portions.begin() || itFwd != Portions.end()) && result.CanTakeMore()) { + if (itFwd != Portions.end() && + (itBkwd == Portions.begin() || itBkwd->GetPortion()->GetTotalBlobBytes() <= itFwd->GetPortion()->GetTotalBlobBytes())) { + auto portion = itFwd->GetPortion(); + compactedData += portion->GetTotalBlobBytes(); + result.AddCurrentLevelPortion(portion, GetNextLevel()->GetAffectedPortions(portion->IndexKeyStart(), portion->IndexKeyEnd()), false); + ++itFwd; + } else if (itBkwd != Portions.begin() && + (itFwd == Portions.end() || itFwd->GetPortion()->GetTotalBlobBytes() < itBkwd->GetPortion()->GetTotalBlobBytes())) { + auto portion = itBkwd->GetPortion(); + compactedData += portion->GetTotalBlobBytes(); + result.AddCurrentLevelPortion(portion, GetNextLevel()->GetAffectedPortions(portion->IndexKeyStart(), portion->IndexKeyEnd()), false); + --itBkwd; + } + } + return result; +} + +} diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/common_level.h b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/common_level.h new file mode 100644 index 000000000000..4ab0942fb3c1 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/common_level.h @@ -0,0 +1,130 @@ +#pragma once +#include "abstract.h" +#include "counters.h" + +namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets { + +class TLevelPortions: public IPortionsLevel { +private: + using TBase = IPortionsLevel; + + std::set Portions; + const TLevelCounters LevelCounters; + const double BytesLimitFraction = 1; + const ui64 ExpectedPortionSize = (1 << 20); + const bool StrictOneLayer = true; + std::shared_ptr SummaryPortionsInfo; + + ui64 GetLevelBlobBytesLimit() const { + const ui32 discrete = SummaryPortionsInfo->GetBlobBytes() / (150 << 20); + return (discrete + 1) * (150 << 20) * BytesLimitFraction; + } + + virtual NJson::TJsonValue DoSerializeToJson() const override { + NJson::TJsonValue result = NJson::JSON_MAP; + result.InsertValue("expected_portion_size", ExpectedPortionSize); + result.InsertValue("bytes_limit", GetLevelBlobBytesLimit()); + result.InsertValue("total_bytes", SummaryPortionsInfo->GetBlobBytes()); + result.InsertValue("fraction", BytesLimitFraction); + return result; + } + + virtual std::optional DoGetAffectedPortions(const NArrow::TReplaceKey& from, const NArrow::TReplaceKey& to) const override { + if (Portions.empty()) { + return std::nullopt; + } + std::vector result; + auto itFrom = Portions.upper_bound(from); + auto itTo = Portions.upper_bound(to); + if (itFrom != Portions.begin()) { + auto it = itFrom; + --it; + if (from <= it->GetPortion()->IndexKeyEnd()) { + result.insert(result.begin(), it->GetPortion()); + } + } + for (auto it = itFrom; it != itTo; ++it) { + result.emplace_back(it->GetPortion()); + } + if (itTo != Portions.end()) { + return TPortionsChain(std::move(result), itTo->GetPortion()); + } else if (result.size()) { + return TPortionsChain(std::move(result), nullptr); + } else { + return std::nullopt; + } + } + + virtual ui64 DoGetWeight() const override { + if (!GetNextLevel()) { + return 0; + } + if ((ui64)PortionsInfo.GetBlobBytes() > GetLevelBlobBytesLimit() && PortionsInfo.GetCount() > 2 && + (ui64)PortionsInfo.GetBlobBytes() > ExpectedPortionSize * 2) { + return ((ui64)GetLevelId() << 48) + PortionsInfo.GetBlobBytes() - GetLevelBlobBytesLimit(); + } else { + return 0; + } + } + +public: + TLevelPortions(const ui64 levelId, const double bytesLimitFraction, const ui64 expectedPortionSize, + const std::shared_ptr& nextLevel, const std::shared_ptr& summaryPortionsInfo, + const TLevelCounters& levelCounters, const bool strictOneLayer = true) + : TBase(levelId, nextLevel) + , LevelCounters(levelCounters) + , BytesLimitFraction(bytesLimitFraction) + , ExpectedPortionSize(expectedPortionSize) + , StrictOneLayer(strictOneLayer) + , SummaryPortionsInfo(summaryPortionsInfo) + { + } + + ui64 GetExpectedPortionSize() const { + return ExpectedPortionSize; + } + + virtual bool IsLocked(const std::shared_ptr& locksManager) const override { + for (auto&& i : Portions) { + if (locksManager->IsLocked(*i.GetPortion(), NDataLocks::ELockCategory::Compaction)) { + return true; + } + } + return false; + } + + virtual ui64 DoGetAffectedPortionBytes(const NArrow::TReplaceKey& from, const NArrow::TReplaceKey& to) const override { + if (Portions.empty()) { + return 0; + } + ui64 result = 0; + auto itFrom = Portions.upper_bound(from); + auto itTo = Portions.upper_bound(to); + if (itFrom != Portions.begin()) { + auto it = itFrom; + --it; + if (from <= it->GetPortion()->IndexKeyEnd()) { + result += it->GetPortion()->GetTotalRawBytes(); + } + } + for (auto it = itFrom; it != itTo; ++it) { + result += it->GetPortion()->GetTotalRawBytes(); + } + return result; + } + + virtual void DoModifyPortions(const std::vector& add, const std::vector& remove) override; + + virtual TCompactionTaskData DoGetOptimizationTask() const override; + + virtual NArrow::NMerger::TIntervalPositions DoGetBucketPositions(const std::shared_ptr& pkSchema) const override { + NArrow::NMerger::TIntervalPositions result; + const auto& sortingColumns = pkSchema->field_names(); + for (auto&& i : Portions) { + result.AddPosition(i.GetStartPosition(), false); + } + return result; + } +}; + +} // namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/counters.cpp b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/counters.cpp new file mode 100644 index 000000000000..a429ee576fdd --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/counters.cpp @@ -0,0 +1,5 @@ +#include "counters.h" + +namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets { + +} diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/counters.h b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/counters.h new file mode 100644 index 000000000000..95d4924b3ece --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/counters.h @@ -0,0 +1,78 @@ +#pragma once +#include +#include + +#include + +namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets { + +using TPortionCategoryCounterAgents = NColumnShard::TPortionCategoryCounterAgents; +using TPortionCategoryCounters = NColumnShard::TPortionCategoryCounters; + +class TLevelAgents { +private: + const ui32 LevelId; + +public: + const std::shared_ptr Portions; + + TLevelAgents(const ui32 levelId, NColumnShard::TCommonCountersOwner& baseOwner) + : LevelId(levelId) + , Portions(std::make_shared(baseOwner, "level=" + ::ToString(LevelId))) { + } +}; + +class TGlobalCounters: public NColumnShard::TCommonCountersOwner { +private: + using TBase = NColumnShard::TCommonCountersOwner; + std::vector> Levels; + +public: + TGlobalCounters() + : TBase("LeveledCompactionOptimizer") { + for (ui32 i = 0; i <= 10; ++i) { + Levels.emplace_back(std::make_shared(i, *this)); + } + } + + static std::shared_ptr BuildPortionsCounter(const ui32 levelId) { + AFL_VERIFY(levelId < Singleton()->Levels.size()); + return std::make_shared(*Singleton()->Levels[levelId]->Portions); + } +}; + +class TLevelCounters { +public: + const std::shared_ptr Portions; + TLevelCounters(const ui32 levelId) + : Portions(TGlobalCounters::BuildPortionsCounter(levelId)) { + } +}; + +class TCounters { +public: + std::vector Levels; + + TCounters() { + for (ui32 i = 0; i < 10; ++i) { + Levels.emplace_back(i); + } + } + + const TLevelCounters& GetLevelCounters(const ui32 levelIdx) const { + AFL_VERIFY(levelIdx < Levels.size())("idx", levelIdx)("count", Levels.size()); + return Levels[levelIdx]; + } + + void AddPortion(const ui32 levelId, const std::shared_ptr& portion) { + AFL_VERIFY(levelId < Levels.size()); + Levels[levelId].Portions->AddPortion(portion); + } + + void RemovePortion(const ui32 levelId, const std::shared_ptr& portion) { + AFL_VERIFY(levelId < Levels.size()); + Levels[levelId].Portions->RemovePortion(portion); + } +}; + +} // namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/optimizer.cpp b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/optimizer.cpp new file mode 100644 index 000000000000..c229de85a682 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/optimizer.cpp @@ -0,0 +1,72 @@ +#include "accumulation_level.h" +#include "common_level.h" +#include "optimizer.h" +#include "zero_level.h" + +#include + +#include + +namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets { + +TOptimizerPlanner::TOptimizerPlanner(const ui64 pathId, const std::shared_ptr& storagesManager, + const std::shared_ptr& primaryKeysSchema, const std::vector& levelConstructors) + : TBase(pathId) + , Counters(std::make_shared()) + , StoragesManager(storagesManager) + , PrimaryKeysSchema(primaryKeysSchema) { + std::shared_ptr nextLevel; + /* + const ui64 maxPortionBlobBytes = (ui64)1 << 20; + Levels.emplace_back( + std::make_shared(2, 0.9, maxPortionBlobBytes, nullptr, PortionsInfo, Counters->GetLevelCounters(2))); +*/ + if (levelConstructors.size()) { + std::shared_ptr nextLevel; + ui32 idx = levelConstructors.size(); + for (auto it = levelConstructors.rbegin(); it != levelConstructors.rend(); ++it) { + --idx; + Levels.emplace_back((*it)->BuildLevel(nextLevel, idx, Counters->GetLevelCounters(idx))); + nextLevel = Levels.back(); + } + } else { + Levels.emplace_back(std::make_shared(2, nullptr, Counters->GetLevelCounters(2), TDuration::Max(), 1 << 20)); + Levels.emplace_back(std::make_shared(1, Levels.back(), Counters->GetLevelCounters(1), TDuration::Max(), 1 << 20)); + Levels.emplace_back( + std::make_shared(0, Levels.back(), Counters->GetLevelCounters(0), TDuration::Seconds(180), 1 << 20)); + } + std::reverse(Levels.begin(), Levels.end()); + RefreshWeights(); +} + +std::shared_ptr TOptimizerPlanner::DoGetOptimizationTask( + std::shared_ptr granule, const std::shared_ptr& locksManager) const { + AFL_VERIFY(LevelsByWeight.size()); + auto level = LevelsByWeight.begin()->second; + auto data = level->GetOptimizationTask(); + TSaverContext saverContext(StoragesManager); + std::shared_ptr result; + // if (level->GetLevelId() == 0) { + result = + std::make_shared(granule, data.GetRepackPortions(level->GetLevelId()), saverContext); + // } else { + // result = std::make_shared( + // granule, data.GetRepackPortions(level->GetLevelId()), saverContext); + // result->AddMovePortions(data.GetMovePortions()); + // } + result->SetTargetCompactionLevel(data.GetTargetCompactionLevel()); + auto levelPortions = std::dynamic_pointer_cast(Levels[data.GetTargetCompactionLevel()]); + if (levelPortions) { + result->SetPortionExpectedSize(levelPortions->GetExpectedPortionSize()); + } + auto positions = data.GetCheckPositions(PrimaryKeysSchema, level->GetLevelId() > 1); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("task_id", result->GetTaskIdentifier())("positions", positions.DebugString())( + "level", level->GetLevelId())("target", data.GetTargetCompactionLevel())("data", data.DebugString()); + result->SetCheckPoints(std::move(positions)); + for (auto&& i : result->GetSwitchedPortions()) { + AFL_VERIFY(!locksManager->IsLocked(i, NDataLocks::ELockCategory::Compaction)); + } + return result; +} + +} // namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/optimizer.h b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/optimizer.h new file mode 100644 index 000000000000..0f576672243e --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/optimizer.h @@ -0,0 +1,153 @@ +#pragma once +#include "abstract.h" +#include "counters.h" + +namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets { + +class TLevelConstructorContainer; + +class TOptimizerPlanner: public IOptimizerPlanner { +private: + using TBase = IOptimizerPlanner; + std::shared_ptr Counters; + std::shared_ptr PortionsInfo = std::make_shared(); + TInstant LastActualization = TInstant::Now(); + + std::vector> Levels; + class TReverseSorting { + public: + bool operator()(const ui64 l, const ui64 r) const { + return r < l; + } + }; + std::map, TReverseSorting> LevelsByWeight; + const std::shared_ptr StoragesManager; + const std::shared_ptr PrimaryKeysSchema; + virtual std::vector DoGetTasksDescription() const override { + std::vector result; + for (auto&& i : Levels) { + result.emplace_back(i->GetTaskDescription()); + } + return result; + } + + void RefreshWeights() { + LevelsByWeight.clear(); + for (ui32 i = 0; i < Levels.size(); ++i) { + LevelsByWeight.emplace(Levels[i]->GetWeight(), Levels[i]); + } + } + +protected: + virtual bool DoIsLocked(const std::shared_ptr& dataLocksManager) const override { + for (auto&& i : Levels) { + if (i->IsLocked(dataLocksManager)) { + return true; + } + } + return false; + } + + virtual void DoModifyPortions( + const THashMap& add, const THashMap& remove) override { + std::vector> removePortionsByLevel; + removePortionsByLevel.resize(Levels.size()); + for (auto&& [_, i] : remove) { + if (i->GetMeta().GetTierName() != IStoragesManager::DefaultStorageId && i->GetMeta().GetTierName() != "") { + continue; + } + PortionsInfo->RemovePortion(i); + AFL_VERIFY(i->GetCompactionLevel() < Levels.size()); + removePortionsByLevel[i->GetCompactionLevel()].emplace_back(i); + } + for (ui32 i = 0; i < Levels.size(); ++i) { + Levels[i]->ModifyPortions({}, removePortionsByLevel[i]); + } + for (auto&& [_, i] : add) { + if (i->GetMeta().GetTierName() != IStoragesManager::DefaultStorageId && i->GetMeta().GetTierName() != "") { + continue; + } + PortionsInfo->AddPortion(i); + if (i->GetCompactionLevel() && (i->GetCompactionLevel() >= Levels.size() || !Levels[i->GetCompactionLevel()]->CanTakePortion(i))) { + i->MutableMeta().ResetCompactionLevel(0); + } + AFL_VERIFY(i->GetCompactionLevel() < Levels.size()); + if (i->GetMeta().GetCompactionLevel()) { + Levels[i->GetMeta().GetCompactionLevel()]->ModifyPortions({ i }, {}); + } + } + + for (auto&& [_, i] : add) { + if (i->GetMeta().GetTierName() != IStoragesManager::DefaultStorageId && i->GetMeta().GetTierName() != "") { + continue; + } + AFL_VERIFY(i->GetCompactionLevel() < Levels.size()); + if (i->GetCompactionLevel()) { + continue; + } + if (i->GetTotalBlobBytes() > 512 * 1024 && i->GetMeta().GetProduced() != NPortion::EProduced::INSERTED) { + for (i32 levelIdx = Levels.size() - 1; levelIdx >= 0; --levelIdx) { + if (Levels[levelIdx]->CanTakePortion(i)) { + Levels[levelIdx]->ModifyPortions({i}, {}); + i->MutableMeta().ResetCompactionLevel(levelIdx); + break; + } + } + } else { + Levels[0]->ModifyPortions({ i }, {}); + } + } + RefreshWeights(); + } + virtual std::shared_ptr DoGetOptimizationTask( + std::shared_ptr granule, const std::shared_ptr& locksManager) const override; + + virtual void DoActualize(const TInstant currentInstant) override { + if (currentInstant - LastActualization > TDuration::Seconds(180)) { + LastActualization = currentInstant; + } else { + return; + } + RefreshWeights(); + } + + virtual TOptimizationPriority DoGetUsefulMetric() const override { + AFL_VERIFY(LevelsByWeight.size()); + const ui64 levelPriority = LevelsByWeight.begin()->first; + if (levelPriority) { + return TOptimizationPriority::Critical(levelPriority); + } else { + return TOptimizationPriority::Zero(); + } + } + + virtual TString DoDebugString() const override { + TStringBuilder sb; + sb << "["; + for (auto&& i : Levels) { + sb << "{" << i->GetLevelId() << ":" << i->DebugString() << "},"; + } + sb << "]"; + return sb; + } + + virtual NJson::TJsonValue DoSerializeToJsonVisual() const override { + NJson::TJsonValue arr = NJson::JSON_MAP; + NJson::TJsonValue& arrLevels = arr.InsertValue("levels", NJson::JSON_ARRAY); + for (auto&& i : Levels) { + arrLevels.AppendValue(i->SerializeToJson()); + } + return arr; + } + +public: + virtual NArrow::NMerger::TIntervalPositions GetBucketPositions() const override { + NArrow::NMerger::TIntervalPositions result = Levels.back()->GetBucketPositions(PrimaryKeysSchema); + return result; + } + + TOptimizerPlanner(const ui64 pathId, const std::shared_ptr& storagesManager, + const std::shared_ptr& primaryKeysSchema, const std::vector& levelConstructors); +}; + +} // namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/ya.make b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/ya.make new file mode 100644 index 000000000000..7eba1467e8b1 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/ya.make @@ -0,0 +1,18 @@ +LIBRARY() + +SRCS( + abstract.cpp + zero_level.cpp + common_level.cpp + GLOBAL optimizer.cpp + counters.cpp +) + +PEERDIR( + contrib/libs/apache/arrow + ydb/core/protos + ydb/core/formats/arrow + ydb/core/tx/columnshard/engines/changes/abstract +) + +END() diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/zero_level.cpp b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/zero_level.cpp new file mode 100644 index 000000000000..18d09fe007aa --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/zero_level.cpp @@ -0,0 +1,64 @@ +#include "zero_level.h" + +namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets { + +TCompactionTaskData TZeroLevelPortions::DoGetOptimizationTask() const { + AFL_VERIFY(Portions.size()); + TCompactionTaskData result(NextLevel->GetLevelId()); + for (auto&& i : Portions) { + result.AddCurrentLevelPortion( + i.GetPortion(), NextLevel->GetAffectedPortions(i.GetPortion()->IndexKeyStart(), i.GetPortion()->IndexKeyEnd()), true); + if (!result.CanTakeMore()) { +// result.SetStopSeparation(i.GetPortion()->IndexKeyStart()); + break; + } + } + if (result.CanTakeMore()) { + PredOptimization = TInstant::Now(); + } else { + PredOptimization = std::nullopt; + } + return result; +} + +ui64 TZeroLevelPortions::DoGetWeight() const { + if (!NextLevel || Portions.size() < 10) { + return 0; + } + if (PredOptimization && TInstant::Now() - *PredOptimization < DurationToDrop) { + if (PortionsInfo.PredictPackedBlobBytes(GetPackKff()) < ExpectedBlobsSize) { + return 0; + } + } + + const ui64 affectedRawBytes = + NextLevel->GetAffectedPortionBytes(Portions.begin()->GetPortion()->IndexKeyStart(), Portions.rbegin()->GetPortion()->IndexKeyEnd()); + /* + THashSet portionIds; + auto chain = + targetLevel->GetAffectedPortions(Portions.begin()->GetPortion()->IndexKeyStart(), Portions.rbegin()->GetPortion()->IndexKeyEnd()); + ui64 affectedRawBytes = 0; + if (chain) { + auto it = Portions.begin(); + auto itNext = chain->GetPortions().begin(); + while (it != Portions.end() && itNext != chain->GetPortions().end()) { + const auto& nextLevelPortion = *itNext; + if (nextLevelPortion->IndexKeyEnd() < it->GetPortion()->IndexKeyStart()) { + ++itNext; + } else if (it->GetPortion()->IndexKeyEnd() < nextLevelPortion->IndexKeyStart()) { + ++it; + } else { + if (portionIds.emplace(nextLevelPortion->GetPortionId()).second) { + affectedRawBytes += nextLevelPortion->GetTotalRawBytes(); + } + ++itNext; + } + } + } +*/ + + const ui64 mb = (affectedRawBytes + PortionsInfo.GetRawBytes()) / 1000000 + 1; + return 1000.0 * PortionsInfo.GetCount() * PortionsInfo.GetCount() / mb; +} + +} // namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/zero_level.h b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/zero_level.h new file mode 100644 index 000000000000..cd7385501e3e --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner/zero_level.h @@ -0,0 +1,104 @@ +#pragma once +#include "abstract.h" +#include "counters.h" + +namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets { + +class TZeroLevelPortions: public IPortionsLevel { +private: + using TBase = IPortionsLevel; + const TLevelCounters LevelCounters; + const TDuration DurationToDrop; + const ui64 ExpectedBlobsSize; + class TOrderedPortion { + private: + YDB_READONLY_DEF(TPortionInfo::TConstPtr, Portion); + + public: + TOrderedPortion(const TPortionInfo::TConstPtr& portion) + : Portion(portion) { + } + + TOrderedPortion(const TPortionInfo::TPtr& portion) + : Portion(portion) { + } + + bool operator==(const TOrderedPortion& item) const { + return item.Portion->GetPathId() == Portion->GetPathId() && item.Portion->GetPortionId() == Portion->GetPortionId(); + } + + bool operator<(const TOrderedPortion& item) const { + auto cmp = Portion->IndexKeyStart().CompareNotNull(item.Portion->IndexKeyStart()); + if (cmp == std::partial_ordering::equivalent) { + return Portion->GetPortionId() < item.Portion->GetPortionId(); + } else { + return cmp == std::partial_ordering::less; + } + } + }; + std::set Portions; + + virtual NArrow::NMerger::TIntervalPositions DoGetBucketPositions(const std::shared_ptr& /*pkSchema*/) const override { + return NArrow::NMerger::TIntervalPositions(); + } + + virtual std::optional DoGetAffectedPortions( + const NArrow::TReplaceKey& /*from*/, const NArrow::TReplaceKey& /*to*/) const override { + return std::nullopt; + } + + virtual ui64 DoGetAffectedPortionBytes(const NArrow::TReplaceKey& /*from*/, const NArrow::TReplaceKey& /*to*/) const override { + return 0; + } + + virtual void DoModifyPortions(const std::vector& add, const std::vector& remove) override { + const bool constructionFlag = Portions.empty(); + if (constructionFlag) { + std::vector ordered; + ordered.reserve(add.size()); + for (auto&& i : add) { + ordered.emplace_back(i); + } + std::sort(ordered.begin(), ordered.end()); + AFL_VERIFY(std::unique(ordered.begin(), ordered.end()) == ordered.end()); + Portions = std::set(ordered.begin(), ordered.end()); + } + for (auto&& i : add) { + if (!constructionFlag) { + AFL_VERIFY(Portions.emplace(i).second); + } + PortionsInfo.AddPortion(i); + LevelCounters.Portions->AddPortion(i); + i->InitRuntimeFeature(TPortionInfo::ERuntimeFeature::Optimized, !NextLevel); + } + for (auto&& i : remove) { + AFL_VERIFY(Portions.erase(i)); + LevelCounters.Portions->RemovePortion(i); + PortionsInfo.RemovePortion(i); + } + } + + virtual bool IsLocked(const std::shared_ptr& locksManager) const override { + for (auto&& i : Portions) { + if (locksManager->IsLocked(*i.GetPortion(), NDataLocks::ELockCategory::Compaction)) { + return true; + } + } + return false; + } + + virtual ui64 DoGetWeight() const override; + + virtual TCompactionTaskData DoGetOptimizationTask() const override; + +public: + TZeroLevelPortions(const ui32 levelIdx, const std::shared_ptr& nextLevel, const TLevelCounters& levelCounters, const TDuration durationToDrop, const ui64 expectedBlobsSize) + : TBase(levelIdx, nextLevel) + , LevelCounters(levelCounters) + , DurationToDrop(durationToDrop) + , ExpectedBlobsSize(expectedBlobsSize) + { + } +}; + +} // namespace NKikimr::NOlap::NStorageOptimizer::NLCBuckets diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/ya.make b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/ya.make new file mode 100644 index 000000000000..8847ed7e2c81 --- /dev/null +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/ya.make @@ -0,0 +1,11 @@ +LIBRARY() + +SRCS( +) + +PEERDIR( + ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/planner + ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets/constructor +) + +END() diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/common/optimizer.h b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/common/optimizer.h index 553bd195ec39..f9a3c61cf244 100644 --- a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/common/optimizer.h +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/common/optimizer.h @@ -31,12 +31,12 @@ class TSimplePortionsGroupInfo { void AddPortion(const std::shared_ptr& p) { Bytes += p->GetTotalBlobBytes(); Count += 1; - RecordsCount += p->NumRows(); + RecordsCount += p->GetRecordsCount(); } void RemovePortion(const std::shared_ptr& p) { Bytes -= p->GetTotalBlobBytes(); Count -= 1; - RecordsCount -= p->NumRows(); + RecordsCount -= p->GetRecordsCount(); AFL_VERIFY(Bytes >= 0); AFL_VERIFY(Count >= 0); AFL_VERIFY(RecordsCount >= 0); diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/counters/counters.h b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/counters/counters.h index f7020d3de83a..0f04067f8ef4 100644 --- a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/counters/counters.h +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/counters/counters.h @@ -35,13 +35,13 @@ class TPortionCategoryCounters { } void AddPortion(const std::shared_ptr& p) { - RecordsCount->Add(p->NumRows()); + RecordsCount->Add(p->GetRecordsCount()); Count->Add(1); Bytes->Add(p->GetTotalBlobBytes()); } void RemovePortion(const std::shared_ptr& p) { - RecordsCount->Remove(p->NumRows()); + RecordsCount->Remove(p->GetRecordsCount()); Count->Remove(1); Bytes->Remove(p->GetTotalBlobBytes()); } diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/index/bucket.cpp b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/index/bucket.cpp index ec344a674fd7..943e785cd961 100644 --- a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/index/bucket.cpp +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/index/bucket.cpp @@ -1,29 +1,33 @@ #include "bucket.h" -#include + +#include #include #include -#include +#include namespace NKikimr::NOlap::NStorageOptimizer::NSBuckets { void TPortionsBucket::RebuildOptimizedFeature(const TInstant currentInstant) const { for (auto&& [_, p] : Portions) { - p.MutablePortionInfo().InitRuntimeFeature(TPortionInfo::ERuntimeFeature::Optimized, Portions.size() == 1 && currentInstant > p->RecordSnapshotMax().GetPlanInstant() + - NYDBTest::TControllers::GetColumnShardController()->GetLagForCompactionBeforeTierings() - ); + p.MutablePortionInfo().InitRuntimeFeature(TPortionInfo::ERuntimeFeature::Optimized, + Portions.size() == 1 && + currentInstant > p->RecordSnapshotMax().GetPlanInstant() + + NYDBTest::TControllers::GetColumnShardController()->GetLagForCompactionBeforeTierings()); } } -std::shared_ptr TPortionsBucket::BuildOptimizationTask(std::shared_ptr granule, - const std::shared_ptr& locksManager, const std::shared_ptr& primaryKeysSchema, const std::shared_ptr& storagesManager) const { +std::shared_ptr TPortionsBucket::BuildOptimizationTask(std::shared_ptr granule, + const std::shared_ptr& locksManager, const std::shared_ptr& primaryKeysSchema, + const std::shared_ptr& storagesManager) const { auto context = Logic->BuildTask(TInstant::Now(), GetMemLimit(), *this); AFL_VERIFY(context.GetPortions().size() > 1)("size", context.GetPortions().size()); ui64 size = 0; for (auto&& i : context.GetPortions()) { size += i->GetTotalBlobBytes(); - AFL_VERIFY(!locksManager->IsLocked(*i)); + AFL_VERIFY(!locksManager->IsLocked(*i, NDataLocks::ELockCategory::Compaction)); } - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("size", size)("next", Finish.DebugString())("count", context.GetPortions().size())("event", "start_optimization"); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("size", size)("next", Finish.DebugString())("count", context.GetPortions().size())( + "event", "start_optimization"); TSaverContext saverContext(storagesManager); auto result = std::make_shared(granule, context.GetPortions(), saverContext); for (auto&& i : context.GetSplitRightOpenIntervalPoints()) { @@ -35,7 +39,7 @@ std::shared_ptr TPortionsBucket::BuildOpti bool TPortionsBucket::IsLocked(const std::shared_ptr& dataLocksManager) const { for (auto&& i : Portions) { - if (dataLocksManager->IsLocked(*i.second.GetPortionInfo())) { + if (dataLocksManager->IsLocked(*i.second.GetPortionInfo(), NDataLocks::ELockCategory::Compaction)) { return true; } } @@ -46,4 +50,4 @@ ui64 TPortionsBucket::GetMemLimit() const { return HasAppData() ? AppDataVerified().ColumnShardConfig.GetCompactionMemoryLimit() : 512 * 1024 * 1024; } -} +} // namespace NKikimr::NOlap::NStorageOptimizer::NSBuckets diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/index/index.h b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/index/index.h index 56bddb8547fb..ed75da95f46c 100644 --- a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/index/index.h +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/index/index.h @@ -35,7 +35,13 @@ class TPortionBuckets { const std::shared_ptr StoragesManager; std::map> Buckets; - std::map> BucketsByWeight; + struct TReverseComparator { + bool operator()(const i64 l, const i64 r) const { + return r < l; + } + }; + + std::map, TReverseComparator> BucketsByWeight; THashSet RatedBuckets; TInstant CurrentWeightInstant = TInstant::Now(); @@ -123,8 +129,8 @@ class TPortionBuckets { bool IsLocked(const std::shared_ptr& dataLocksManager) const { AFL_VERIFY(BucketsByWeight.size()); - AFL_VERIFY(BucketsByWeight.rbegin()->second.size()); - const auto bucket = BucketsByWeight.rbegin()->second.begin()->GetBucketVerified(); + AFL_VERIFY(BucketsByWeight.begin()->second.size()); + const auto bucket = BucketsByWeight.begin()->second.begin()->GetBucketVerified(); return bucket->IsLocked(dataLocksManager); } @@ -154,7 +160,7 @@ class TPortionBuckets { i64 GetWeight() const { AFL_VERIFY(BucketsByWeight.size()); - return BucketsByWeight.rbegin()->first; + return BucketsByWeight.begin()->first; } void RemovePortion(const std::shared_ptr& portion) { @@ -211,9 +217,9 @@ class TPortionBuckets { std::shared_ptr BuildOptimizationTask(std::shared_ptr granule, const std::shared_ptr& locksManager) const { AFL_VERIFY(BucketsByWeight.size()); - AFL_VERIFY(BucketsByWeight.rbegin()->first); - AFL_VERIFY(BucketsByWeight.rbegin()->second.size()); - const std::shared_ptr bucketForOptimization = BucketsByWeight.rbegin()->second.begin()->GetBucketVerified(); + AFL_VERIFY(BucketsByWeight.begin()->first); + AFL_VERIFY(BucketsByWeight.begin()->second.size()); + const std::shared_ptr bucketForOptimization = BucketsByWeight.begin()->second.begin()->GetBucketVerified(); auto it = Buckets.find(bucketForOptimization->GetStart()); AFL_VERIFY(it != Buckets.end()); ++it; diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/abstract/logic.h b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/abstract/logic.h index b2d169db8698..3b17aa26d452 100644 --- a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/abstract/logic.h +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/abstract/logic.h @@ -21,10 +21,10 @@ class TCalcWeightResult { class TCompactionTaskResult { private: - YDB_READONLY_DEF(std::vector>, Portions); + YDB_READONLY_DEF(std::vector, Portions); YDB_READONLY_DEF(std::vector, SplitRightOpenIntervalPoints); // [-inf, p1), [p1, p2), ... public: - TCompactionTaskResult(std::vector>&& portions, std::vector&& points) + TCompactionTaskResult(std::vector&& portions, std::vector&& points) : Portions(std::move(portions)) , SplitRightOpenIntervalPoints(std::move(points)) { diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/one_head/logic.cpp b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/one_head/logic.cpp index c71fd2dbbb09..110a5a3c89e7 100644 --- a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/one_head/logic.cpp +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/one_head/logic.cpp @@ -4,14 +4,14 @@ namespace NKikimr::NOlap::NStorageOptimizer::NSBuckets { -std::vector> TOneHeadLogic::GetPortionsForMerge(const TInstant now, const ui64 memLimit, const TBucketInfo& bucket, std::vector* stopPoints, TInstant* stopInstant) const { - std::vector> result; +std::vector TOneHeadLogic::GetPortionsForMerge(const TInstant now, const ui64 memLimit, const TBucketInfo& bucket, std::vector* stopPoints, TInstant* stopInstant) const { + std::vector result; std::vector splitKeys; ui64 memUsage = 0; ui64 txSizeLimit = 0; std::shared_ptr predictor = NCompaction::TGeneralCompactColumnEngineChanges::BuildMemoryPredictor(); { - THashMap> currentCompactedPortions; + THashMap currentCompactedPortions; bool compactedFinished = false; bool finished = false; for (auto&& [pk, portions] : bucket.GetPKPortions()) { @@ -40,7 +40,7 @@ std::vector> TOneHeadLogic::GetPor compactedFinished = currentCompactedPortions.empty(); } else { result.emplace_back(p.GetPortionInfo()); - memUsage = predictor->AddPortion(*p.GetPortionInfo()); + memUsage = predictor->AddPortion(p.GetPortionInfo()); txSizeLimit += p->GetTxVolume(); } } diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/one_head/logic.h b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/one_head/logic.h index 32c955831d3e..0de2fac5d518 100644 --- a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/one_head/logic.h +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/one_head/logic.h @@ -7,7 +7,7 @@ class TOneHeadLogic: public IOptimizationLogic { private: const TDuration FreshnessCheckDuration = TDuration::Seconds(300); - std::vector> GetPortionsForMerge(const TInstant now, const ui64 memLimit, const TBucketInfo& bucket, + std::vector GetPortionsForMerge(const TInstant now, const ui64 memLimit, const TBucketInfo& bucket, std::vector* stopPoints, TInstant* stopInstant) const; virtual TCalcWeightResult DoCalcWeight(const TInstant now, const TBucketInfo& bucket) const override { @@ -27,7 +27,7 @@ class TOneHeadLogic: public IOptimizationLogic { virtual TCompactionTaskResult DoBuildTask(const TInstant now, const ui64 memLimit, const TBucketInfo& bucket) const override { std::vector stopPoints; - std::vector> portions = GetPortionsForMerge(now, memLimit, bucket, &stopPoints, nullptr); + std::vector portions = GetPortionsForMerge(now, memLimit, bucket, &stopPoints, nullptr); return TCompactionTaskResult(std::move(portions), std::move(stopPoints)); } public: diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/slices/logic.cpp b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/slices/logic.cpp index 28d2914ed392..8dacb1eca867 100644 --- a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/slices/logic.cpp +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/slices/logic.cpp @@ -1,24 +1,26 @@ #include "logic.h" -#include + #include +#include namespace NKikimr::NOlap::NStorageOptimizer::NSBuckets { static const ui64 compactedDetector = 512 * 1024; -std::vector> TTimeSliceLogic::GetPortionsForMerge(const TInstant /*now*/, const ui64 memLimit, - const TBucketInfo& bucket) const { - std::vector> result; +std::vector TTimeSliceLogic::GetPortionsForMerge( + const TInstant /*now*/, const ui64 memLimit, const TBucketInfo& bucket) const { + std::vector result; { ui64 memUsage = 0; ui64 txSizeLimit = 0; - std::shared_ptr predictor = NCompaction::TGeneralCompactColumnEngineChanges::BuildMemoryPredictor(); + std::shared_ptr predictor = + NCompaction::TGeneralCompactColumnEngineChanges::BuildMemoryPredictor(); for (auto&& [maxInstant, portions] : bucket.GetSnapshotPortions()) { for (auto&& [_, p] : portions) { if (p.GetTotalBlobBytes() > compactedDetector) { continue; } - memUsage = predictor->AddPortion(*p.GetPortionInfo()); + memUsage = predictor->AddPortion(p.GetPortionInfo()); txSizeLimit += p->GetTxVolume(); result.emplace_back(p.GetPortionInfo()); } @@ -34,9 +36,10 @@ std::vector> TTimeSliceLogic::GetP return result; } -NKikimr::NOlap::NStorageOptimizer::NSBuckets::TCompactionTaskResult TTimeSliceLogic::DoBuildTask(const TInstant now, const ui64 memLimit, const TBucketInfo& bucket) const { +NKikimr::NOlap::NStorageOptimizer::NSBuckets::TCompactionTaskResult TTimeSliceLogic::DoBuildTask( + const TInstant now, const ui64 memLimit, const TBucketInfo& bucket) const { std::vector stopPoints; - std::vector> portions = GetPortionsForMerge(now, memLimit, bucket); + std::vector portions = GetPortionsForMerge(now, memLimit, bucket); std::vector splitKeys; { @@ -65,7 +68,8 @@ NKikimr::NOlap::NStorageOptimizer::NSBuckets::TCompactionTaskResult TTimeSliceLo return TCompactionTaskResult(std::move(portions), std::move(splitKeys)); } -NKikimr::NOlap::NStorageOptimizer::NSBuckets::TCalcWeightResult TTimeSliceLogic::DoCalcWeight(const TInstant /*now*/, const TBucketInfo& bucket) const { +NKikimr::NOlap::NStorageOptimizer::NSBuckets::TCalcWeightResult TTimeSliceLogic::DoCalcWeight( + const TInstant /*now*/, const TBucketInfo& bucket) const { ui64 size = 0; ui64 count = 0; for (auto&& [maxInstant, portions] : bucket.GetSnapshotPortions()) { @@ -89,4 +93,4 @@ NKikimr::NOlap::NStorageOptimizer::NSBuckets::TCalcWeightResult TTimeSliceLogic: } } -} +} // namespace NKikimr::NOlap::NStorageOptimizer::NSBuckets diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/slices/logic.h b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/slices/logic.h index 370cb9119de4..2d65adf0a1af 100644 --- a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/slices/logic.h +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/logic/slices/logic.h @@ -7,7 +7,7 @@ class TTimeSliceLogic: public IOptimizationLogic { private: TDuration FreshnessCheckDuration = TDuration::Seconds(300); - std::vector> GetPortionsForMerge(const TInstant now, const ui64 memLimit, const TBucketInfo& bucket) const; + std::vector GetPortionsForMerge(const TInstant now, const ui64 memLimit, const TBucketInfo& bucket) const; virtual TCalcWeightResult DoCalcWeight(const TInstant now, const TBucketInfo& bucket) const override; diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/optimizer/optimizer.h b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/optimizer/optimizer.h index 7d756f09deff..7d3dfdddfaba 100644 --- a/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/optimizer/optimizer.h +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets/optimizer/optimizer.h @@ -22,7 +22,7 @@ class TOptimizerPlanner: public IOptimizerPlanner { return Buckets.IsLocked(dataLocksManager); } - virtual void DoModifyPortions(const THashMap>& add, const THashMap>& remove) override { + virtual void DoModifyPortions(const THashMap& add, const THashMap& remove) override { for (auto&& [_, i] : remove) { if (i->GetMeta().GetTierName() != IStoragesManager::DefaultStorageId && i->GetMeta().GetTierName() != "") { continue; @@ -42,7 +42,8 @@ class TOptimizerPlanner: public IOptimizerPlanner { Buckets.AddPortion(i); } } - virtual std::shared_ptr DoGetOptimizationTask(std::shared_ptr granule, const std::shared_ptr& locksManager) const override { + virtual std::shared_ptr DoGetOptimizationTask( + std::shared_ptr granule, const std::shared_ptr& locksManager) const override { return Buckets.BuildOptimizationTask(granule, locksManager); } virtual void DoActualize(const TInstant currentInstant) override { @@ -72,7 +73,8 @@ class TOptimizerPlanner: public IOptimizerPlanner { Buckets.ResetLogic(logic); } - TOptimizerPlanner(const ui64 pathId, const std::shared_ptr& storagesManager, const std::shared_ptr& primaryKeysSchema, const std::shared_ptr& logic); + TOptimizerPlanner(const ui64 pathId, const std::shared_ptr& storagesManager, + const std::shared_ptr& primaryKeysSchema, const std::shared_ptr& logic); }; } // namespace NKikimr::NOlap::NStorageOptimizer::NSBuckets diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/ut/ut_optimizer.cpp b/ydb/core/tx/columnshard/engines/storage/optimizer/ut/ut_optimizer.cpp index 420a9e5901e9..a329234bf938 100644 --- a/ydb/core/tx/columnshard/engines/storage/optimizer/ut/ut_optimizer.cpp +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/ut/ut_optimizer.cpp @@ -83,8 +83,8 @@ Y_UNIT_TEST_SUITE(StorageOptimizer) { auto task = dynamic_pointer_cast(planner.GetOptimizationTask(limits, nullptr)); Y_ABORT_UNLESS(task); Y_ABORT_UNLESS(task->SwitchedPortions.size() == 2); - Y_ABORT_UNLESS(task->SwitchedPortions[0].GetPortion() == 1); - Y_ABORT_UNLESS(task->SwitchedPortions[1].GetPortion() == 2); + Y_ABORT_UNLESS(task->SwitchedPortions[0].GetPortionId() == 1); + Y_ABORT_UNLESS(task->SwitchedPortions[1].GetPortionId() == 2); } }; diff --git a/ydb/core/tx/columnshard/engines/storage/optimizer/ya.make b/ydb/core/tx/columnshard/engines/storage/optimizer/ya.make index e5b1c1905986..c7739f8c8782 100644 --- a/ydb/core/tx/columnshard/engines/storage/optimizer/ya.make +++ b/ydb/core/tx/columnshard/engines/storage/optimizer/ya.make @@ -3,6 +3,7 @@ LIBRARY() PEERDIR( ydb/core/tx/columnshard/engines/storage/optimizer/abstract ydb/core/tx/columnshard/engines/storage/optimizer/lbuckets + ydb/core/tx/columnshard/engines/storage/optimizer/lcbuckets ydb/core/tx/columnshard/engines/storage/optimizer/sbuckets ) diff --git a/ydb/core/tx/columnshard/engines/storage/storage.cpp b/ydb/core/tx/columnshard/engines/storage/storage.cpp deleted file mode 100644 index 85252e68069f..000000000000 --- a/ydb/core/tx/columnshard/engines/storage/storage.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "storage.h" diff --git a/ydb/core/tx/columnshard/engines/storage/storage.h b/ydb/core/tx/columnshard/engines/storage/storage.h deleted file mode 100644 index 7e9fe7bb73a8..000000000000 --- a/ydb/core/tx/columnshard/engines/storage/storage.h +++ /dev/null @@ -1,2 +0,0 @@ -#pragma once -#include "granule/storage.h" diff --git a/ydb/core/tx/columnshard/engines/tier_info.cpp b/ydb/core/tx/columnshard/engines/tier_info.cpp deleted file mode 100644 index 8114b5f11af6..000000000000 --- a/ydb/core/tx/columnshard/engines/tier_info.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "tier_info.h" - -namespace NKikimr::NOlap { - -} diff --git a/ydb/core/tx/columnshard/engines/tier_info.h b/ydb/core/tx/columnshard/engines/tier_info.h deleted file mode 100644 index beaf9cefc262..000000000000 --- a/ydb/core/tx/columnshard/engines/tier_info.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -#include "scheme/tier_info.h" diff --git a/ydb/core/tx/columnshard/engines/ut/ut_insert_table.cpp b/ydb/core/tx/columnshard/engines/ut/ut_insert_table.cpp index d840a5a64f37..718e416edf29 100644 --- a/ydb/core/tx/columnshard/engines/ut/ut_insert_table.cpp +++ b/ydb/core/tx/columnshard/engines/ut/ut_insert_table.cpp @@ -16,6 +16,13 @@ namespace { class TTestInsertTableDB : public IDbWrapper { public: + virtual const IBlobGroupSelector* GetDsGroupSelector() const override { + return &Default(); + } + + virtual void WriteColumns(const NOlap::TPortionInfo& /*portion*/, const NKikimrTxColumnShard::TIndexPortionAccessor& /*proto*/) override { + + } void Insert(const TInsertedData&) override { } void Commit(const TCommittedData&) override { @@ -42,7 +49,8 @@ class TTestInsertTableDB : public IDbWrapper { } virtual void ErasePortion(const NOlap::TPortionInfo& /*portion*/) override { } - virtual bool LoadPortions(const std::function& /*callback*/) override { + virtual bool LoadPortions(const std::optional /*reqPathId*/, + const std::function& /*callback*/) override { return true; } @@ -50,7 +58,7 @@ class TTestInsertTableDB : public IDbWrapper { } void EraseColumn(const TPortionInfo&, const TColumnRecord&) override { } - bool LoadColumns(const std::function&) override { + bool LoadColumns(const std::optional /*reqPathId*/, const std::function&) override { return true; } @@ -58,7 +66,8 @@ class TTestInsertTableDB : public IDbWrapper { } virtual void EraseIndex(const TPortionInfo& /*portion*/, const TIndexChunk& /*row*/) override { } - virtual bool LoadIndexes(const std::function& /*callback*/) override { + virtual bool LoadIndexes(const std::optional /*reqPathId*/, + const std::function& /*callback*/) override { return true; } @@ -84,6 +93,7 @@ Y_UNIT_TEST_SUITE(TColumnEngineTestInsertTable) { // insert, not commited auto userData1 = std::make_shared(tableId, TBlobRange(blobId1), TLocalHelper::GetMetaProto(), indexSnapshot, std::nullopt); + insertTable.RegisterPathInfo(tableId); bool ok = insertTable.Insert(dbTable, TInsertedData(writeId, userData1)); UNIT_ASSERT(ok); diff --git a/ydb/core/tx/columnshard/engines/ut/ut_logs_engine.cpp b/ydb/core/tx/columnshard/engines/ut/ut_logs_engine.cpp index de303d972750..85975a5dc822 100644 --- a/ydb/core/tx/columnshard/engines/ut/ut_logs_engine.cpp +++ b/ydb/core/tx/columnshard/engines/ut/ut_logs_engine.cpp @@ -33,12 +33,26 @@ namespace { std::shared_ptr EmptyDataLocksManager = std::make_shared(); -class TTestDbWrapper : public IDbWrapper { +class TTestDbWrapper: public IDbWrapper { private: - std::map> LoadContexts; + std::map LoadContexts; + public: + virtual void WriteColumns(const NOlap::TPortionInfo& portion, const NKikimrTxColumnShard::TIndexPortionAccessor& proto) override { + auto it = LoadContexts.find(portion.GetAddress()); + if (it == LoadContexts.end()) { + LoadContexts.emplace(portion.GetAddress(), TColumnChunkLoadContextV2(portion.GetPathId(), portion.GetPortionId(), proto)); + } else { + it->second = TColumnChunkLoadContextV2(portion.GetPathId(), portion.GetPortionId(), proto); + } + } + + virtual const IBlobGroupSelector* GetDsGroupSelector() const override { + return &Default(); + } + struct TIndex { - THashMap> Columns; // pathId -> portions + THashMap> Columns; // pathId -> portions THashMap Counters; }; @@ -71,8 +85,7 @@ class TTestDbWrapper : public IDbWrapper { Aborted.erase(data.GetInsertWriteId()); } - bool Load(TInsertTableAccessor& accessor, - const TInstant&) override { + bool Load(TInsertTableAccessor& accessor, const TInstant&) override { for (auto&& i : Inserted) { accessor.AddInserted(std::move(i.second), true); } @@ -80,7 +93,7 @@ class TTestDbWrapper : public IDbWrapper { accessor.AddAborted(std::move(i.second), true); } for (auto&& i : Committed) { - for (auto&& c: i.second) { + for (auto&& c : i.second) { auto copy = c; accessor.AddCommitted(std::move(copy), true); } @@ -88,13 +101,24 @@ class TTestDbWrapper : public IDbWrapper { return true; } - virtual void WritePortion(const NOlap::TPortionInfo& /*portion*/) override { - + virtual void WritePortion(const NOlap::TPortionInfo& portion) override { + auto it = Portions.find(portion.GetPortionId()); + if (it == Portions.end()) { + Portions.emplace(portion.GetPortionId(), portion.MakeCopy()); + } else { + it->second = portion.MakeCopy(); + } } - virtual void ErasePortion(const NOlap::TPortionInfo& /*portion*/) override { - + virtual void ErasePortion(const NOlap::TPortionInfo& portion) override { + AFL_VERIFY(Portions.erase(portion.GetPortionId())); } - virtual bool LoadPortions(const std::function& /*callback*/) override { + virtual bool LoadPortions(const std::optional pathId, + const std::function& callback) override { + for (auto&& i : Portions) { + if (!pathId || *pathId == i.second.GetPathId()) { + callback(NOlap::TPortionInfoConstructor(i.second, false, false), i.second.GetMeta().SerializeToProto()); + } + } return true; } @@ -105,28 +129,24 @@ class TTestDbWrapper : public IDbWrapper { } auto& data = Indices[0].Columns[portion.GetPathId()]; - NOlap::TColumnChunkLoadContext loadContext(row.GetAddress(), portion.RestoreBlobRange(row.BlobRange), rowProto); - auto itInsertInfo = LoadContexts[portion.GetAddress()].emplace(row.GetAddress(), loadContext); - if (!itInsertInfo.second) { - itInsertInfo.first->second = loadContext; - } - auto it = data.find(portion.GetPortion()); + auto it = data.find(portion.GetPortionId()); if (it == data.end()) { - it = data.emplace(portion.GetPortion(), TPortionInfoConstructor(portion, false, true)).first; + it = data.emplace(portion.GetPortionId(), TPortionInfoConstructor(portion, true, true)).first; } else { - Y_ABORT_UNLESS(portion.GetPathId() == it->second.GetPathId() && portion.GetPortion() == it->second.GetPortionIdVerified()); + Y_ABORT_UNLESS(portion.GetPathId() == it->second.MutablePortionConstructor().GetPathId() && + portion.GetPortionId() == it->second.MutablePortionConstructor().GetPortionIdVerified()); } - it->second.SetMinSnapshotDeprecated(portion.GetMinSnapshotDeprecated()); + it->second.MutablePortionConstructor().SetMinSnapshotDeprecated(portion.GetMinSnapshotDeprecated()); if (portion.HasRemoveSnapshot()) { - if (!it->second.HasRemoveSnapshot()) { - it->second.SetRemoveSnapshot(portion.GetRemoveSnapshotVerified()); + if (!it->second.MutablePortionConstructor().HasRemoveSnapshot()) { + it->second.MutablePortionConstructor().SetRemoveSnapshot(portion.GetRemoveSnapshotVerified()); } } else { - AFL_VERIFY(!it->second.HasRemoveSnapshot()); + AFL_VERIFY(!it->second.MutablePortionConstructor().HasRemoveSnapshot()); } bool replaced = false; - for (auto& rec : it->second.MutableRecords()) { + for (auto& rec : it->second.TestMutableRecords()) { if (rec.IsEqualTest(row)) { rec = row; replaced = true; @@ -134,13 +154,13 @@ class TTestDbWrapper : public IDbWrapper { } } if (!replaced) { - it->second.MutableRecords().emplace_back(row); + it->second.TestMutableRecords().emplace_back(row); } } void EraseColumn(const TPortionInfo& portion, const TColumnRecord& row) override { auto& data = Indices[0].Columns[portion.GetPathId()]; - auto it = data.find(portion.GetPortion()); + auto it = data.find(portion.GetPortionId()); Y_ABORT_UNLESS(it != data.end()); auto& portionLocal = it->second; @@ -150,30 +170,35 @@ class TTestDbWrapper : public IDbWrapper { filtered.push_back(rec); } } - portionLocal.MutableRecords().swap(filtered); + portionLocal.TestMutableRecords().swap(filtered); } - bool LoadColumns(const std::function& callback) override { + bool LoadColumns(const std::optional reqPathId, const std::function& callback) override { auto& columns = Indices[0].Columns; for (auto& [pathId, portions] : columns) { + if (pathId && *reqPathId != pathId) { + continue; + } for (auto& [portionId, portionLocal] : portions) { - auto copy = portionLocal; - copy.MutableRecords().clear(); - for (const auto& rec : portionLocal.GetRecords()) { - auto itContextLoader = LoadContexts[copy.GetAddress()].find(rec.GetAddress()); - Y_ABORT_UNLESS(itContextLoader != LoadContexts[copy.GetAddress()].end()); - auto address = copy.GetAddress(); - callback(std::move(copy), itContextLoader->second); - LoadContexts[address].erase(itContextLoader); - } + auto copy = portionLocal.MakeCopy(); + copy.TestMutableRecords().clear(); + auto it = LoadContexts.find(portionLocal.GetPortionConstructor().GetAddress()); + AFL_VERIFY(it != LoadContexts.end()); + callback(std::move(it->second)); + LoadContexts.erase(it); } } return true; } - virtual void WriteIndex(const TPortionInfo& /*portion*/, const TIndexChunk& /*row*/) override {} - virtual void EraseIndex(const TPortionInfo& /*portion*/, const TIndexChunk& /*row*/) override {} - virtual bool LoadIndexes(const std::function& /*callback*/) override { return true; } + virtual void WriteIndex(const TPortionInfo& /*portion*/, const TIndexChunk& /*row*/) override { + } + virtual void EraseIndex(const TPortionInfo& /*portion*/, const TIndexChunk& /*row*/) override { + } + virtual bool LoadIndexes(const std::optional /*reqPathId*/, + const std::function& /*callback*/) override { + return true; + } void WriteCounter(ui32 counterId, ui64 value) override { auto& counters = Indices[0].Counters; @@ -192,25 +217,22 @@ class TTestDbWrapper : public IDbWrapper { THashMap Inserted; THashMap> Committed; THashMap Aborted; + THashMap Portions; THashMap Indices; }; static const std::vector testColumns = { // PK - NArrow::NTest::TTestColumn("timestamp", TTypeInfo(NTypeIds::Timestamp) ), - NArrow::NTest::TTestColumn("resource_type", TTypeInfo(NTypeIds::Utf8) ), - NArrow::NTest::TTestColumn("resource_id", TTypeInfo(NTypeIds::Utf8) ), - NArrow::NTest::TTestColumn("uid", TTypeInfo(NTypeIds::Utf8) ), + NArrow::NTest::TTestColumn("timestamp", TTypeInfo(NTypeIds::Timestamp)), + NArrow::NTest::TTestColumn("resource_type", TTypeInfo(NTypeIds::Utf8)), NArrow::NTest::TTestColumn("resource_id", TTypeInfo(NTypeIds::Utf8)), + NArrow::NTest::TTestColumn("uid", TTypeInfo(NTypeIds::Utf8)), // - NArrow::NTest::TTestColumn("message", TTypeInfo(NTypeIds::Utf8) ) + NArrow::NTest::TTestColumn("message", TTypeInfo(NTypeIds::Utf8)) }; -static const std::vector testKey = { - NArrow::NTest::TTestColumn("timestamp", TTypeInfo(NTypeIds::Timestamp) ), - NArrow::NTest::TTestColumn("resource_type", TTypeInfo(NTypeIds::Utf8) ), - NArrow::NTest::TTestColumn("resource_id", TTypeInfo(NTypeIds::Utf8) ), - NArrow::NTest::TTestColumn("uid", TTypeInfo(NTypeIds::Utf8) ) -}; +static const std::vector testKey = { NArrow::NTest::TTestColumn("timestamp", TTypeInfo(NTypeIds::Timestamp)), + NArrow::NTest::TTestColumn("resource_type", TTypeInfo(NTypeIds::Utf8)), NArrow::NTest::TTestColumn("resource_id", TTypeInfo(NTypeIds::Utf8)), + NArrow::NTest::TTestColumn("uid", TTypeInfo(NTypeIds::Utf8)) }; template class TBuilder { @@ -227,8 +249,7 @@ class TBuilder { }; TBuilder() - : Schema(NArrow::MakeArrowSchema(testColumns)) - { + : Schema(NArrow::MakeArrowSchema(testColumns)) { auto status = arrow::RecordBatchBuilder::Make(Schema, arrow::default_memory_pool(), &BatchBuilder); Y_ABORT_UNLESS(status.ok()); } @@ -269,7 +290,7 @@ TString MakeTestBlob(i64 start = 0, i64 end = 100, ui32 step = 1) { for (i64 ts = start; ts < end; ts += step) { TString str = ToString(ts); TString sortedStr = Sprintf("%05ld", (long)ts); - builder.AddRow({ts, sortedStr, str, str, str}); + builder.AddRow({ ts, sortedStr, str, str, str }); } auto batch = builder.Finish(); return NArrow::SerializeBatchNoCompression(batch); @@ -278,13 +299,13 @@ TString MakeTestBlob(i64 start = 0, i64 end = 100, ui32 step = 1) { void AddIdsToBlobs(std::vector& portions, NBlobOperations::NRead::TCompositeReadBlobs& blobs, ui32& step) { for (auto& portion : portions) { THashMap blobsData; - for (auto& b : portion.GetBlobs()) { + for (auto& b : portion.MutableBlobs()) { const auto blobId = MakeUnifiedBlobId(++step, b.GetSize()); b.RegisterBlobId(portion, blobId); blobsData.emplace(blobId, b.GetResultBlob()); } for (auto&& rec : portion.GetPortionConstructor().GetRecords()) { - auto range = portion.GetPortionConstructor().RestoreBlobRange(rec.BlobRange); + auto range = portion.GetPortionConstructor().RestoreBlobRangeSlow(rec.BlobRange, rec.GetAddress()); auto it = blobsData.find(range.BlobId); AFL_VERIFY(it != blobsData.end()); const TString& data = it->second; @@ -308,20 +329,20 @@ bool Insert(TColumnEngineForLogs& engine, TTestDbWrapper& db, TSnapshot snap, st NOlap::TConstructionContext context(engine.GetVersionedIndex(), NColumnShard::TIndexationCounters("Indexation"), snap); Y_ABORT_UNLESS(changes->ConstructBlobs(context).Ok()); - UNIT_ASSERT_VALUES_EQUAL(changes->AppendedPortions.size(), 1); + UNIT_ASSERT_VALUES_EQUAL(changes->GetAppendedPortions().size(), 1); ui32 blobsCount = 0; - for (auto&& i : changes->AppendedPortions) { + for (auto&& i : changes->GetAppendedPortions()) { blobsCount += i.GetBlobs().size(); } - UNIT_ASSERT_VALUES_EQUAL(blobsCount, 1); // add 2 columns: planStep, txId + AFL_VERIFY(blobsCount == 5 || blobsCount == 1)("count", blobsCount); - AddIdsToBlobs(changes->AppendedPortions, blobs, step); + AddIdsToBlobs(changes->MutableAppendedPortions(), blobs, step); const bool result = engine.ApplyChangesOnTxCreate(changes, snap) && engine.ApplyChangesOnExecute(db, changes, snap); - NOlap::TWriteIndexContext contextExecute(nullptr, db, engine); + NOlap::TWriteIndexContext contextExecute(nullptr, db, engine, snap); changes->WriteIndexOnExecute(nullptr, contextExecute); - NOlap::TWriteIndexCompleteContext contextComplete(NActors::TActivationContext::AsActorContext(), 0, 0, TDuration::Zero(), engine); + NOlap::TWriteIndexCompleteContext contextComplete(NActors::TActivationContext::AsActorContext(), 0, 0, TDuration::Zero(), engine, snap); changes->WriteIndexOnComplete(nullptr, contextComplete); changes->AbortEmergency("testing"); return result; @@ -333,30 +354,61 @@ struct TExpected { ui32 NewGranules; }; +class TTestCompactionAccessorsSubscriber: public NOlap::IDataAccessorRequestsSubscriber { +private: + std::shared_ptr Changes; + const std::shared_ptr VersionedIndex; + virtual const std::shared_ptr& DoGetAbortionFlag() const override { + return Default>(); + } + + virtual void DoOnRequestsFinished(TDataAccessorsResult&& result) override { + const TDataAccessorsInitializationContext context(VersionedIndex); + Changes->SetFetchedDataAccessors(std::move(result), context); + } + +public: + TTestCompactionAccessorsSubscriber( + const std::shared_ptr& changes, const std::shared_ptr& versionedIndex) + : Changes(changes) + , VersionedIndex(versionedIndex) { + } +}; + bool Compact(TColumnEngineForLogs& engine, TTestDbWrapper& db, TSnapshot snap, NBlobOperations::NRead::TCompositeReadBlobs&& blobs, ui32& step, - const TExpected& /*expected*/, THashMap* blobsPool = nullptr) { - std::shared_ptr changes = dynamic_pointer_cast(engine.StartCompaction(EmptyDataLocksManager)); + const TExpected& /*expected*/, THashMap* blobsPool = nullptr) { + std::shared_ptr changes = + dynamic_pointer_cast(engine.StartCompaction(EmptyDataLocksManager)); UNIT_ASSERT(changes); // UNIT_ASSERT_VALUES_EQUAL(changes->SwitchedPortions.size(), expected.SrcPortions); - changes->Blobs = std::move(blobs); changes->StartEmergency(); + { + auto request = changes->ExtractDataAccessorsRequest(); + request->RegisterSubscriber( + std::make_shared(changes, std::make_shared(engine.GetVersionedIndex()))); + engine.FetchDataAccessors(changes->ExtractDataAccessorsRequest()); + } + changes->Blobs = std::move(blobs); NOlap::TConstructionContext context(engine.GetVersionedIndex(), NColumnShard::TIndexationCounters("Compaction"), NOlap::TSnapshot(step, 1)); Y_ABORT_UNLESS(changes->ConstructBlobs(context).Ok()); // UNIT_ASSERT_VALUES_EQUAL(changes->AppendedPortions.size(), expected.NewPortions); - AddIdsToBlobs(changes->AppendedPortions, changes->Blobs, step); + AddIdsToBlobs(changes->MutableAppendedPortions(), changes->Blobs, step); // UNIT_ASSERT_VALUES_EQUAL(changes->GetTmpGranuleIds().size(), expected.NewGranules); const bool result = engine.ApplyChangesOnTxCreate(changes, snap) && engine.ApplyChangesOnExecute(db, changes, snap); - NOlap::TWriteIndexContext contextExecute(nullptr, db, engine); + NOlap::TWriteIndexContext contextExecute(nullptr, db, engine, snap); changes->WriteIndexOnExecute(nullptr, contextExecute); - NOlap::TWriteIndexCompleteContext contextComplete(NActors::TActivationContext::AsActorContext(), 0, 0, TDuration::Zero(), engine); + NOlap::TWriteIndexCompleteContext contextComplete(NActors::TActivationContext::AsActorContext(), 0, 0, TDuration::Zero(), engine, snap); changes->WriteIndexOnComplete(nullptr, contextComplete); if (blobsPool) { - for (auto&& i : changes->AppendedPortions) { - for (auto&& r : i.GetPortionResult().GetRecords()) { - Y_ABORT_UNLESS(blobsPool->emplace(i.GetPortionResult().RestoreBlobRange(r.BlobRange), i.GetBlobByRangeVerified(r.ColumnId, r.Chunk)).second); + for (auto&& i : changes->GetAppendedPortions()) { + for (auto&& r : i.GetPortionResult().TestGetRecords()) { + Y_ABORT_UNLESS(blobsPool + ->emplace(i.GetPortionResult().GetPortionInfo().RestoreBlobRange(r.BlobRange), + i.GetBlobByRangeVerified(r.ColumnId, r.Chunk)) + .second); } } } @@ -371,32 +423,66 @@ bool Cleanup(TColumnEngineForLogs& engine, TTestDbWrapper& db, TSnapshot snap, u if (!expectedToDrop && !changes) { return true; } - UNIT_ASSERT_VALUES_EQUAL(changes->PortionsToDrop.size(), expectedToDrop); - + UNIT_ASSERT_VALUES_EQUAL(changes->GetPortionsToDrop().size(), expectedToDrop); changes->StartEmergency(); const bool result = engine.ApplyChangesOnTxCreate(changes, snap) && engine.ApplyChangesOnExecute(db, changes, snap); - NOlap::TWriteIndexContext contextExecute(nullptr, db, engine); + NOlap::TWriteIndexContext contextExecute(nullptr, db, engine, snap); changes->WriteIndexOnExecute(nullptr, contextExecute); - NOlap::TWriteIndexCompleteContext contextComplete(NActors::TActivationContext::AsActorContext(), 0, 0, TDuration::Zero(), engine); + NOlap::TWriteIndexCompleteContext contextComplete(NActors::TActivationContext::AsActorContext(), 0, 0, TDuration::Zero(), engine, snap); changes->WriteIndexOnComplete(nullptr, contextComplete); changes->AbortEmergency("testing"); return result; } -bool Ttl(TColumnEngineForLogs& engine, TTestDbWrapper& db, - const THashMap& pathEviction, ui32 expectedToDrop) { +namespace { +class TTestMetadataAccessorsSubscriber: public NOlap::IDataAccessorRequestsSubscriber { +private: + std::shared_ptr Processor; + TColumnEngineForLogs& Engine; + + virtual const std::shared_ptr& DoGetAbortionFlag() const override { + return Default>(); + } + virtual void DoOnRequestsFinished(TDataAccessorsResult&& result) override { + Processor->ApplyResult( + NOlap::NResourceBroker::NSubscribe::TResourceContainer::BuildForTest(std::move(result)), Engine); + } + +public: + TTestMetadataAccessorsSubscriber(const std::shared_ptr& processor, TColumnEngineForLogs& engine) + : Processor(processor) + , Engine(engine) { + } +}; + +} + +bool Ttl(TColumnEngineForLogs& engine, TTestDbWrapper& db, const THashMap& pathEviction, ui32 expectedToDrop) { + engine.StartActualization(pathEviction); + std::vector requests = engine.CollectMetadataRequests(); + for (auto&& i : requests) { + i.GetRequest()->RegisterSubscriber(std::make_shared(i.GetProcessor(), engine)); + engine.FetchDataAccessors(i.GetRequest()); + } + std::vector> vChanges = engine.StartTtl(pathEviction, EmptyDataLocksManager, 512 * 1024 * 1024); AFL_VERIFY(vChanges.size() == 1)("count", vChanges.size()); auto changes = vChanges.front(); UNIT_ASSERT_VALUES_EQUAL(changes->GetPortionsToRemove().size(), expectedToDrop); - changes->StartEmergency(); + { + auto request = changes->ExtractDataAccessorsRequest(); + request->RegisterSubscriber( + std::make_shared(changes, std::make_shared(engine.GetVersionedIndex()))); + engine.FetchDataAccessors(changes->ExtractDataAccessorsRequest()); + } const bool result = engine.ApplyChangesOnTxCreate(changes, TSnapshot(1, 1)) && engine.ApplyChangesOnExecute(db, changes, TSnapshot(1, 1)); - NOlap::TWriteIndexContext contextExecute(nullptr, db, engine); + NOlap::TWriteIndexContext contextExecute(nullptr, db, engine, TSnapshot(1, 1)); changes->WriteIndexOnExecute(nullptr, contextExecute); - NOlap::TWriteIndexCompleteContext contextComplete(NActors::TActivationContext::AsActorContext(), 0, 0, TDuration::Zero(), engine); + NOlap::TWriteIndexCompleteContext contextComplete( + NActors::TActivationContext::AsActorContext(), 0, 0, TDuration::Zero(), engine, TSnapshot(1, 1)); changes->WriteIndexOnComplete(nullptr, contextComplete); changes->AbortEmergency("testing"); return result; @@ -418,7 +504,7 @@ std::shared_ptr MakeStrPredicate(const std::string& key, NArrow::EOp return std::make_shared(op, arrow::RecordBatch::Make(std::make_shared(std::move(fields)), 1, { *res })); } -} // namespace +} // namespace std::shared_ptr InitializeStorageManager() { return NKikimr::NOlap::TTestStoragesManager::GetInstance(); @@ -427,13 +513,12 @@ std::shared_ptr InitializeStorageManager() { std::shared_ptr CommonStoragesManager = InitializeStorageManager(); Y_UNIT_TEST_SUITE(TColumnEngineTestLogs) { - void WriteLoadRead(const std::vector& ydbSchema, - const std::vector& key) { + void WriteLoadRead(const std::vector& ydbSchema, const std::vector& key) { TTestBasicRuntime runtime; TTestDbWrapper db; TIndexInfo tableInfo = NColumnShard::BuildTableInfo(ydbSchema, key); - std::vector paths = {1, 2}; + std::vector paths = { 1, 2 }; TString testBlob = MakeTestBlob(); @@ -444,16 +529,17 @@ Y_UNIT_TEST_SUITE(TColumnEngineTestLogs) { // PlanStep, TxId, PathId, DedupId, BlobId, Data, [Metadata] // load TSnapshot indexSnapshot(1, 1); - TColumnEngineForLogs engine(0, CommonStoragesManager, indexSnapshot, TIndexInfo(tableInfo)); + TColumnEngineForLogs engine( + 0, std::make_shared(), NDataAccessorControl::TLocalManager::BuildForTests(), CommonStoragesManager, indexSnapshot, 0, TIndexInfo(tableInfo)); for (auto&& i : paths) { engine.RegisterTable(i); } - engine.Load(db); + engine.TestingLoad(db); - std::vector dataToIndex = { - TCommittedData(TUserData::Build(paths[0], blobRanges[0], TLocalHelper::GetMetaProto(), 0, {}), TSnapshot(1, 2), 0, (TInsertWriteId)2), - TCommittedData(TUserData::Build(paths[0], blobRanges[1], TLocalHelper::GetMetaProto(), 0, {}), TSnapshot(2, 1), 0, (TInsertWriteId)1) - }; + std::vector dataToIndex = { TCommittedData( + TUserData::Build(paths[0], blobRanges[0], TLocalHelper::GetMetaProto(), 0, {}), TSnapshot(1, 2), 0, (TInsertWriteId)2), + TCommittedData( + TUserData::Build(paths[0], blobRanges[1], TLocalHelper::GetMetaProto(), 0, {}), TSnapshot(2, 1), 0, (TInsertWriteId)1) }; // write @@ -478,33 +564,32 @@ Y_UNIT_TEST_SUITE(TColumnEngineTestLogs) { columnIds.insert(indexInfo.GetColumnIdVerified(c.GetName())); } - { // select from snap before insert + { // select from snap before insert ui64 planStep = 1; ui64 txId = 0; - auto selectInfo = engine.Select(paths[0], TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false)); - UNIT_ASSERT_VALUES_EQUAL(selectInfo->PortionsOrderedPK.size(), 0); + auto selectInfo = engine.Select(paths[0], TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false), false); + UNIT_ASSERT_VALUES_EQUAL(selectInfo->Portions.size(), 0); } - { // select from snap between insert (greater txId) + { // select from snap between insert (greater txId) ui64 planStep = 1; ui64 txId = 2; - auto selectInfo = engine.Select(paths[0], TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false)); - UNIT_ASSERT_VALUES_EQUAL(selectInfo->PortionsOrderedPK.size(), 0); + auto selectInfo = engine.Select(paths[0], TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false), false); + UNIT_ASSERT_VALUES_EQUAL(selectInfo->Portions.size(), 0); } - { // select from snap after insert (greater planStep) + { // select from snap after insert (greater planStep) ui64 planStep = 2; ui64 txId = 1; - auto selectInfo = engine.Select(paths[0], TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false)); - UNIT_ASSERT_VALUES_EQUAL(selectInfo->PortionsOrderedPK.size(), 1); - UNIT_ASSERT_VALUES_EQUAL(selectInfo->PortionsOrderedPK[0]->NumChunks(), columnIds.size() + TIndexInfo::GetSnapshotColumnIdsSet().size() - 1); + auto selectInfo = engine.Select(paths[0], TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false), false); + UNIT_ASSERT_VALUES_EQUAL(selectInfo->Portions.size(), 1); } - { // select another pathId + { // select another pathId ui64 planStep = 2; ui64 txId = 1; - auto selectInfo = engine.Select(paths[1], TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false)); - UNIT_ASSERT_VALUES_EQUAL(selectInfo->PortionsOrderedPK.size(), 0); + auto selectInfo = engine.Select(paths[1], TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false), false); + UNIT_ASSERT_VALUES_EQUAL(selectInfo->Portions.size(), 0); } } @@ -513,18 +598,14 @@ Y_UNIT_TEST_SUITE(TColumnEngineTestLogs) { } Y_UNIT_TEST(IndexWriteLoadReadStrPK) { - std::vector key = { - NArrow::NTest::TTestColumn("resource_type", TTypeInfo(NTypeIds::Utf8) ), - NArrow::NTest::TTestColumn("resource_id", TTypeInfo(NTypeIds::Utf8) ), - NArrow::NTest::TTestColumn("uid", TTypeInfo(NTypeIds::Utf8) ), - NArrow::NTest::TTestColumn("timestamp", TTypeInfo(NTypeIds::Timestamp) ) - }; + std::vector key = { NArrow::NTest::TTestColumn("resource_type", TTypeInfo(NTypeIds::Utf8)), + NArrow::NTest::TTestColumn("resource_id", TTypeInfo(NTypeIds::Utf8)), NArrow::NTest::TTestColumn("uid", TTypeInfo(NTypeIds::Utf8)), + NArrow::NTest::TTestColumn("timestamp", TTypeInfo(NTypeIds::Timestamp)) }; WriteLoadRead(testColumns, key); } - void ReadWithPredicates(const std::vector& ydbSchema, - const std::vector& key) { + void ReadWithPredicates(const std::vector& ydbSchema, const std::vector& key) { TTestBasicRuntime runtime; TTestDbWrapper db; TIndexInfo tableInfo = NColumnShard::BuildTableInfo(ydbSchema, key); @@ -533,9 +614,10 @@ Y_UNIT_TEST_SUITE(TColumnEngineTestLogs) { ui32 step = 1000; TSnapshot indexSnapshot(1, 1); - TColumnEngineForLogs engine(0, CommonStoragesManager, indexSnapshot, TIndexInfo(tableInfo)); + TColumnEngineForLogs engine( + 0, std::make_shared(), NDataAccessorControl::TLocalManager::BuildForTests(), CommonStoragesManager, indexSnapshot, 0, TIndexInfo(tableInfo)); engine.RegisterTable(pathId); - engine.Load(db); + engine.TestingLoad(db); // insert ui64 planStep = 1; @@ -562,8 +644,8 @@ Y_UNIT_TEST_SUITE(TColumnEngineTestLogs) { // compact planStep = 2; -// bool ok = Compact(engine, db, TSnapshot(planStep, 1), std::move(blobs), step, {20, 4, 4}); -// UNIT_ASSERT(ok); + // bool ok = Compact(engine, db, TSnapshot(planStep, 1), std::move(blobs), step, {20, 4, 4}); + // UNIT_ASSERT(ok); // read @@ -574,10 +656,10 @@ Y_UNIT_TEST_SUITE(TColumnEngineTestLogs) { const TIndexInfo& indexInfo = engine.GetVersionedIndex().GetLastSchema()->GetIndexInfo(); THashSet oneColumnId = { indexInfo.GetColumnIdVerified(key[0].GetName()) }; - { // full scan + { // full scan ui64 txId = 1; - auto selectInfo = engine.Select(pathId, TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false)); - UNIT_ASSERT_VALUES_EQUAL(selectInfo->PortionsOrderedPK.size(), 20); + auto selectInfo = engine.Select(pathId, TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false), false); + UNIT_ASSERT_VALUES_EQUAL(selectInfo->Portions.size(), 20); } // predicates @@ -590,20 +672,20 @@ Y_UNIT_TEST_SUITE(TColumnEngineTestLogs) { } NOlap::TPKRangesFilter pkFilter(false); Y_ABORT_UNLESS(pkFilter.Add(gt10k, nullptr, indexInfo.GetReplaceKey())); - auto selectInfo = engine.Select(pathId, TSnapshot(planStep, txId), pkFilter); - UNIT_ASSERT_VALUES_EQUAL(selectInfo->PortionsOrderedPK.size(), 10); + auto selectInfo = engine.Select(pathId, TSnapshot(planStep, txId), pkFilter, false); + UNIT_ASSERT_VALUES_EQUAL(selectInfo->Portions.size(), 10); } { ui64 txId = 1; - std::shared_ptr lt10k = MakePredicate(8999, NArrow::EOperation::Less); // TODO: better border checks + std::shared_ptr lt10k = MakePredicate(8999, NArrow::EOperation::Less); // TODO: better border checks if (key[0].GetType() == TTypeInfo(NTypeIds::Utf8)) { lt10k = MakeStrPredicate("08999", NArrow::EOperation::Less); } NOlap::TPKRangesFilter pkFilter(false); Y_ABORT_UNLESS(pkFilter.Add(nullptr, lt10k, indexInfo.GetReplaceKey())); - auto selectInfo = engine.Select(pathId, TSnapshot(planStep, txId), pkFilter); - UNIT_ASSERT_VALUES_EQUAL(selectInfo->PortionsOrderedPK.size(), 9); + auto selectInfo = engine.Select(pathId, TSnapshot(planStep, txId), pkFilter, false); + UNIT_ASSERT_VALUES_EQUAL(selectInfo->Portions.size(), 9); } } @@ -612,12 +694,9 @@ Y_UNIT_TEST_SUITE(TColumnEngineTestLogs) { } Y_UNIT_TEST(IndexReadWithPredicatesStrPK) { - std::vector key = { - NArrow::NTest::TTestColumn("resource_type", TTypeInfo(NTypeIds::Utf8) ), - NArrow::NTest::TTestColumn("resource_id", TTypeInfo(NTypeIds::Utf8) ), - NArrow::NTest::TTestColumn("uid", TTypeInfo(NTypeIds::Utf8) ), - NArrow::NTest::TTestColumn("timestamp", TTypeInfo(NTypeIds::Timestamp) ) - }; + std::vector key = { NArrow::NTest::TTestColumn("resource_type", TTypeInfo(NTypeIds::Utf8)), + NArrow::NTest::TTestColumn("resource_id", TTypeInfo(NTypeIds::Utf8)), NArrow::NTest::TTestColumn("uid", TTypeInfo(NTypeIds::Utf8)), + NArrow::NTest::TTestColumn("timestamp", TTypeInfo(NTypeIds::Timestamp)) }; ReadWithPredicates(testColumns, key); } @@ -626,7 +705,8 @@ Y_UNIT_TEST_SUITE(TColumnEngineTestLogs) { TTestBasicRuntime runtime; TTestDbWrapper db; auto csDefaultControllerGuard = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); - TIndexInfo tableInfo = NColumnShard::BuildTableInfo(testColumns, testKey);; + TIndexInfo tableInfo = NColumnShard::BuildTableInfo(testColumns, testKey); + ; ui64 pathId = 1; ui32 step = 1000; @@ -635,9 +715,10 @@ Y_UNIT_TEST_SUITE(TColumnEngineTestLogs) { ui64 planStep = 1; TSnapshot indexSnapshot(1, 1); - TColumnEngineForLogs engine(0, CommonStoragesManager, indexSnapshot, TIndexInfo(tableInfo)); + TColumnEngineForLogs engine( + 0, std::make_shared(), NDataAccessorControl::TLocalManager::BuildForTests(), CommonStoragesManager, indexSnapshot, 0, TIndexInfo(tableInfo)); engine.RegisterTable(pathId); - engine.Load(db); + engine.TestingLoad(db); ui64 numRows = 1000; ui64 rowPos = 0; @@ -659,16 +740,17 @@ Y_UNIT_TEST_SUITE(TColumnEngineTestLogs) { UNIT_ASSERT(ok); } - { // check it's overloaded after reload - TColumnEngineForLogs tmpEngine(0, CommonStoragesManager, TSnapshot::Zero(), TIndexInfo(tableInfo)); + { // check it's overloaded after reload + TColumnEngineForLogs tmpEngine( + 0, std::make_shared(), NDataAccessorControl::TLocalManager::BuildForTests(), CommonStoragesManager, TSnapshot::Zero(), 0, TIndexInfo(tableInfo)); tmpEngine.RegisterTable(pathId); - tmpEngine.Load(db); + tmpEngine.TestingLoad(db); } // compact planStep = 2; - bool ok = Compact(engine, db, TSnapshot(planStep, 1), std::move(blobsAll), step, {23, 5, 5}); + bool ok = Compact(engine, db, TSnapshot(planStep, 1), std::move(blobsAll), step, { 23, 5, 5 }); UNIT_ASSERT(ok); // success write after compaction @@ -690,10 +772,11 @@ Y_UNIT_TEST_SUITE(TColumnEngineTestLogs) { UNIT_ASSERT(ok); } - { // check it's not overloaded after reload - TColumnEngineForLogs tmpEngine(0, CommonStoragesManager, TSnapshot::Zero(), TIndexInfo(tableInfo)); + { // check it's not overloaded after reload + TColumnEngineForLogs tmpEngine( + 0, std::make_shared(), NDataAccessorControl::TLocalManager::BuildForTests(), CommonStoragesManager, TSnapshot::Zero(), 0, TIndexInfo(tableInfo)); tmpEngine.RegisterTable(pathId); - tmpEngine.Load(db); + tmpEngine.TestingLoad(db); } } @@ -711,16 +794,17 @@ Y_UNIT_TEST_SUITE(TColumnEngineTestLogs) { ui64 planStep = 1; TSnapshot indexSnapshot(1, 1); { - TColumnEngineForLogs engine(0, CommonStoragesManager, indexSnapshot, TIndexInfo(tableInfo)); + TColumnEngineForLogs engine( + 0, std::make_shared(), NDataAccessorControl::TLocalManager::BuildForTests(), CommonStoragesManager, indexSnapshot, 0, TIndexInfo(tableInfo)); engine.RegisterTable(pathId); - engine.Load(db); + engine.TestingLoad(db); const ui64 numRows = 1000; const ui64 txCount = 20; const ui64 tsIncrement = 1; const auto blobTsRange = numRows * tsIncrement; - const auto gap = TDuration::Hours(1); //much longer than blobTsRange*txCount - auto blobStartTs = (TInstant::Now() - gap).MicroSeconds(); + const auto gap = TDuration::Hours(1); //much longer than blobTsRange*txCount + auto blobStartTs = (TInstant::Now() - gap).MicroSeconds(); for (ui64 txId = 1; txId <= txCount; ++txId) { TString testBlob = MakeTestBlob(blobStartTs, blobStartTs + blobTsRange, tsIncrement); auto blobRange = MakeBlobRange(++step, testBlob.size()); @@ -737,9 +821,9 @@ Y_UNIT_TEST_SUITE(TColumnEngineTestLogs) { bool ok = Insert(engine, db, ss, std::move(dataToIndex), blobs, step); UNIT_ASSERT(ok); blobStartTs += blobTsRange; - if (txId == txCount / 2) { - //Make a gap. - //NB After this gap, some rows may be in the future at the point of setting TTL + if (txId == txCount / 2) { + //Make a gap. + //NB After this gap, some rows may be in the future at the point of setting TTL blobStartTs += gap.MicroSeconds(); } } @@ -750,25 +834,25 @@ Y_UNIT_TEST_SUITE(TColumnEngineTestLogs) { // bool ok = Compact(engine, db, TSnapshot(planStep, 1), std::move(blobs), step, {20, 4, 4}); // UNIT_ASSERT(ok); - // read + // read planStep = 3; const TIndexInfo& indexInfo = engine.GetVersionedIndex().GetLastSchema()->GetIndexInfo(); - THashSet oneColumnId = {indexInfo.GetColumnIdVerified(testColumns[0].GetName())}; + THashSet oneColumnId = { indexInfo.GetColumnIdVerified(testColumns[0].GetName()) }; - { // full scan + { // full scan ui64 txId = 1; - auto selectInfo = engine.Select(pathId, TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false)); - UNIT_ASSERT_VALUES_EQUAL(selectInfo->PortionsOrderedPK.size(), 20); + auto selectInfo = engine.Select(pathId, TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false), false); + UNIT_ASSERT_VALUES_EQUAL(selectInfo->Portions.size(), 20); } // Cleanup Cleanup(engine, db, TSnapshot(planStep, 1), 0); - { // full scan + { // full scan ui64 txId = 1; - auto selectInfo = engine.Select(pathId, TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false)); - UNIT_ASSERT_VALUES_EQUAL(selectInfo->PortionsOrderedPK.size(), 20); + auto selectInfo = engine.Select(pathId, TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false), false); + UNIT_ASSERT_VALUES_EQUAL(selectInfo->Portions.size(), 20); } // TTL @@ -777,32 +861,33 @@ Y_UNIT_TEST_SUITE(TColumnEngineTestLogs) { NOlap::TTiering tiering; AFL_VERIFY(tiering.Add(NOlap::TTierInfo::MakeTtl(gap, "timestamp"))); pathTtls.emplace(pathId, std::move(tiering)); - Ttl(engine, db, pathTtls, txCount / 2 ); + Ttl(engine, db, pathTtls, txCount / 2); // read + load + read - { // full scan + { // full scan ui64 txId = 1; - auto selectInfo = engine.Select(pathId, TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false)); - UNIT_ASSERT_VALUES_EQUAL(selectInfo->PortionsOrderedPK.size(), 10); + auto selectInfo = engine.Select(pathId, TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false), false); + UNIT_ASSERT_VALUES_EQUAL(selectInfo->Portions.size(), 10); } } { // load - TColumnEngineForLogs engine(0, CommonStoragesManager, indexSnapshot, TIndexInfo(tableInfo)); + TColumnEngineForLogs engine( + 0, std::make_shared(), NDataAccessorControl::TLocalManager::BuildForTests(), CommonStoragesManager, indexSnapshot, 0, TIndexInfo(tableInfo)); engine.RegisterTable(pathId); - engine.Load(db); + engine.TestingLoad(db); const TIndexInfo& indexInfo = engine.GetVersionedIndex().GetLastSchema()->GetIndexInfo(); THashSet oneColumnId = { indexInfo.GetColumnIdVerified(testColumns[0].GetName()) }; - { // full scan + { // full scan ui64 txId = 1; - auto selectInfo = engine.Select(pathId, TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false)); - UNIT_ASSERT_VALUES_EQUAL(selectInfo->PortionsOrderedPK.size(), 10); + auto selectInfo = engine.Select(pathId, TSnapshot(planStep, txId), NOlap::TPKRangesFilter(false), false); + UNIT_ASSERT_VALUES_EQUAL(selectInfo->Portions.size(), 10); } } } } -} +} // namespace NKikimr diff --git a/ydb/core/tx/columnshard/engines/ut/ut_program.cpp b/ydb/core/tx/columnshard/engines/ut/ut_program.cpp index f957cfea5592..1a08ea68dc44 100644 --- a/ydb/core/tx/columnshard/engines/ut/ut_program.cpp +++ b/ydb/core/tx/columnshard/engines/ut/ut_program.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/ydb/core/tx/columnshard/engines/writer/compacted_blob_constructor.cpp b/ydb/core/tx/columnshard/engines/writer/compacted_blob_constructor.cpp index 9836a72a60cc..be69c73aff28 100644 --- a/ydb/core/tx/columnshard/engines/writer/compacted_blob_constructor.cpp +++ b/ydb/core/tx/columnshard/engines/writer/compacted_blob_constructor.cpp @@ -20,7 +20,7 @@ TCompactedWriteController::TCompactedWriteController(const TActorId& dstActor, T auto* pInfo = changes.GetWritePortionInfo(i); Y_ABORT_UNLESS(pInfo); TWritePortionInfoWithBlobsResult& portionWithBlobs = *pInfo; - for (auto&& b : portionWithBlobs.GetBlobs()) { + for (auto&& b : portionWithBlobs.MutableBlobs()) { auto& task = AddWriteTask(TBlobWriteInfo::BuildWriteTask(b.GetResultBlob(), changes.MutableBlobsAction().GetWriting(b.GetOperator()->GetStorageId()))); b.RegisterBlobId(portionWithBlobs, task.GetBlobId()); WriteVolume += b.GetSize(); diff --git a/ydb/core/tx/columnshard/engines/writer/indexed_blob_constructor.h b/ydb/core/tx/columnshard/engines/writer/indexed_blob_constructor.h index cca506a29dbe..4867ac18b7ad 100644 --- a/ydb/core/tx/columnshard/engines/writer/indexed_blob_constructor.h +++ b/ydb/core/tx/columnshard/engines/writer/indexed_blob_constructor.h @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include namespace NKikimr::NColumnShard { @@ -67,7 +67,8 @@ class TWritingBlob { if (BlobSize + batch.GetSplittedBlobs().GetSize() < 8 * 1024 * 1024) { Ranges.emplace_back(&batch); BlobSize += batch.GetSplittedBlobs().GetSize(); - batch.SetRange(TBlobRange(TUnifiedBlobId(0, 0, 0, 0, 0, 0, BlobSize), BlobData.size(), batch.GetSplittedBlobs().GetSize())); + batch.SetRange(TBlobRange( + TUnifiedBlobId(0, 0, 0, 0, 0, 0, BlobSize), BlobSize - batch.GetSplittedBlobs().GetSize(), batch.GetSplittedBlobs().GetSize())); BlobData.emplace_back(batch.GetSplittedBlobs().GetData()); return true; } else { @@ -96,7 +97,7 @@ class TWriteAggregation { YDB_READONLY(ui64, Size, 0); YDB_READONLY(ui64, Rows, 0); YDB_ACCESSOR_DEF(std::vector, SplittedBlobs); - YDB_READONLY_DEF(TVector, InsertWriteIds); + YDB_READONLY_DEF(std::vector, InsertWriteIds); YDB_READONLY_DEF(std::shared_ptr, BlobsAction); YDB_READONLY_DEF(NArrow::TSchemaSubset, SchemaSubset); std::shared_ptr RecordBatch; diff --git a/ydb/core/tx/columnshard/engines/ya.make b/ydb/core/tx/columnshard/engines/ya.make index d49a325a7832..2715fdd21631 100644 --- a/ydb/core/tx/columnshard/engines/ya.make +++ b/ydb/core/tx/columnshard/engines/ya.make @@ -7,12 +7,8 @@ LIBRARY() SRCS( column_engine_logs.cpp column_engine.cpp - column_features.cpp db_wrapper.cpp - index_info.cpp filter.cpp - portion_info.cpp - tier_info.cpp defs.cpp ) @@ -33,6 +29,7 @@ PEERDIR( ydb/core/tx/columnshard/engines/changes ydb/core/tx/columnshard/engines/portions ydb/core/tx/columnshard/engines/protos + ydb/core/tx/columnshard/engines/loading ydb/core/tx/program ydb/core/tx/columnshard/common diff --git a/ydb/core/tx/columnshard/export/actor/export_actor.h b/ydb/core/tx/columnshard/export/actor/export_actor.h index ce4c4d517ecc..9089277daf27 100644 --- a/ydb/core/tx/columnshard/export/actor/export_actor.h +++ b/ydb/core/tx/columnshard/export/actor/export_actor.h @@ -82,6 +82,7 @@ class TActor: public NBackground::TSessionActor { virtual void OnBootstrap(const TActorContext& /*ctx*/) override { auto evStart = ExportSession->GetTask().GetSelector()->BuildRequestInitiator(ExportSession->GetCursor()); evStart->Record.SetGeneration((ui64)TabletId); + evStart->Record.SetCSScanPolicy("PLAIN"); Send(TabletActorId, evStart.release()); Become(&TActor::StateFunc); } diff --git a/ydb/core/tx/columnshard/hooks/abstract/abstract.cpp b/ydb/core/tx/columnshard/hooks/abstract/abstract.cpp index b275e17f2fdb..28d07734adbb 100644 --- a/ydb/core/tx/columnshard/hooks/abstract/abstract.cpp +++ b/ydb/core/tx/columnshard/hooks/abstract/abstract.cpp @@ -23,4 +23,9 @@ ui64 ICSController::GetGuaranteeIndexationStartBytesLimit() const { const ui64 defaultValue = NColumnShard::TSettings::GuaranteeIndexationStartBytesLimit; return DoGetGuaranteeIndexationStartBytesLimit(defaultValue); } + +bool ICSController::CheckPortionForEvict(const NOlap::TPortionInfo& portion) const { + return portion.HasRuntimeFeature(NOlap::TPortionInfo::ERuntimeFeature::Optimized) && !portion.HasInsertWriteId(); +} + } diff --git a/ydb/core/tx/columnshard/hooks/abstract/abstract.h b/ydb/core/tx/columnshard/hooks/abstract/abstract.h index c104590235d3..7e3d7d11b289 100644 --- a/ydb/core/tx/columnshard/hooks/abstract/abstract.h +++ b/ydb/core/tx/columnshard/hooks/abstract/abstract.h @@ -1,10 +1,11 @@ #pragma once #include +#include #include #include -#include -#include +#include +#include #include #include @@ -24,6 +25,7 @@ namespace NKikimr::NOlap { class TColumnEngineChanges; class IBlobsGCAction; class TPortionInfo; +class TDataAccessorsResult; namespace NIndexes { class TIndexMetaContainer; } @@ -61,6 +63,9 @@ class ICSController { }; protected: + virtual std::optional DoGetStalenessLivetimePing() const { + return {}; + } virtual void DoOnTabletInitCompleted(const ::NKikimr::NColumnShard::TColumnShard& /*shard*/) { return; } @@ -85,7 +90,10 @@ class ICSController { virtual void DoOnDataSharingStarted(const ui64 /*tabletId*/, const TString& /*sessionId*/) { } - virtual TDuration DoGetPingCheckPeriod(const TDuration defaultValue) const { + virtual TDuration DoGetUsedSnapshotLivetime(const TDuration defaultValue) const { + return defaultValue; + } + virtual ui64 DoGetLimitForPortionsMetadataAsk(const ui64 defaultValue) const { return defaultValue; } virtual TDuration DoGetOverridenGCPeriod(const TDuration defaultValue) const { @@ -103,13 +111,16 @@ class ICSController { virtual ui64 DoGetRejectMemoryIntervalLimit(const ui64 defaultValue) const { return defaultValue; } + virtual ui64 DoGetMetadataRequestSoftMemoryLimit(const ui64 defaultValue) const { + return defaultValue; + } virtual ui64 DoGetReadSequentiallyBufferSize(const ui64 defaultValue) const { return defaultValue; } virtual ui64 DoGetSmallPortionSizeDetector(const ui64 defaultValue) const { return defaultValue; } - virtual TDuration DoGetReadTimeoutClean(const TDuration defaultValue) const { + virtual TDuration DoGetMaxReadStaleness(const TDuration defaultValue) const { return defaultValue; } virtual TDuration DoGetGuaranteeIndexationInterval(const TDuration defaultValue) const { @@ -130,6 +141,12 @@ class ICSController { virtual TDuration DoGetLagForCompactionBeforeTierings(const TDuration defaultValue) const { return defaultValue; } + virtual ui64 DoGetMemoryLimitScanPortion(const ui64 defaultValue) const { + return defaultValue; + } + virtual const NOlap::NSplitter::TSplitSettings& DoGetBlobSplitSettings(const NOlap::NSplitter::TSplitSettings& defaultValue) const { + return defaultValue; + } private: inline static const NKikimrConfig::TColumnShardConfig DefaultConfig = {}; @@ -142,13 +159,31 @@ class ICSController { } public: + const NOlap::NSplitter::TSplitSettings& GetBlobSplitSettings( + const NOlap::NSplitter::TSplitSettings& defaultValue = Default()) { + return DoGetBlobSplitSettings(defaultValue); + } + virtual void OnRequestTracingChanges( const std::set& /*snapshotsToSave*/, const std::set& /*snapshotsToRemove*/) { } - TDuration GetPingCheckPeriod() const { - const TDuration defaultValue = 0.6 * GetReadTimeoutClean(); - return DoGetPingCheckPeriod(defaultValue); + virtual NKikimrProto::EReplyStatus OverrideBlobPutResultOnWrite(const NKikimrProto::EReplyStatus originalStatus) const { + return originalStatus; + } + + ui64 GetMemoryLimitScanPortion() const { + return DoGetMemoryLimitScanPortion(GetConfig().GetMemoryLimitScanPortion()); + } + virtual bool CheckPortionForEvict(const NOlap::TPortionInfo& portion) const; + + TDuration GetStalenessLivetimePing(const TDuration defValue) const { + const auto val = DoGetStalenessLivetimePing(); + if (!val || defValue < *val) { + return defValue; + } else { + return *val; + } } virtual bool IsBackgroundEnabled(const EBackground /*id*/) const { @@ -166,6 +201,11 @@ class ICSController { virtual void OnSelectShardingFilter() { } + ui64 GetLimitForPortionsMetadataAsk() const { + const ui64 defaultValue = GetConfig().GetLimitForPortionsMetadataAsk(); + return DoGetLimitForPortionsMetadataAsk(defaultValue); + } + TDuration GetCompactionActualizationLag() const { const TDuration defaultValue = TDuration::MilliSeconds(GetConfig().GetCompactionActualizationLagMs()); return DoGetCompactionActualizationLag(defaultValue); @@ -189,6 +229,10 @@ class ICSController { const ui64 defaultValue = NOlap::TGlobalLimits::DefaultRejectMemoryIntervalLimit; return DoGetRejectMemoryIntervalLimit(defaultValue); } + ui64 GetMetadataRequestSoftMemoryLimit() const { + const ui64 defaultValue = 100 * (1 << 20); + return DoGetMetadataRequestSoftMemoryLimit(defaultValue); + } virtual bool NeedForceCompactionBacketsConstruction() const { return false; } @@ -215,6 +259,8 @@ class ICSController { } virtual void OnPortionActualization(const NOlap::TPortionInfo& /*info*/) { } + virtual void OnTieringMetadataActualized() { + } virtual void OnMaxValueUsage() { } @@ -249,9 +295,16 @@ class ICSController { } virtual void OnIndexSelectProcessed(const std::optional /*result*/) { } - TDuration GetReadTimeoutClean() const { + TDuration GetMaxReadStaleness() const { const TDuration defaultValue = TDuration::MilliSeconds(GetConfig().GetMaxReadStaleness_ms()); - return DoGetReadTimeoutClean(defaultValue); + return DoGetMaxReadStaleness(defaultValue); + } + TDuration GetMaxReadStalenessInMem() const { + return 0.9 * GetMaxReadStaleness(); + } + TDuration GetUsedSnapshotLivetime() const { + const TDuration defaultValue = 0.6 * GetMaxReadStaleness(); + return DoGetUsedSnapshotLivetime(defaultValue); } virtual EOptimizerCompactionWeightControl GetCompactionControl() const { return EOptimizerCompactionWeightControl::Force; @@ -272,10 +325,8 @@ class ICSController { return nullptr; } - virtual NMetadata::NFetcher::ISnapshot::TPtr GetFallbackTiersSnapshot() const { - static std::shared_ptr result = - std::make_shared(TInstant::Now()); - return result; + virtual THashMap GetOverrideTierConfigs() const { + return {}; } virtual void OnSwitchToWork(const ui64 tabletId) { diff --git a/ydb/core/tx/columnshard/hooks/abstract/ya.make b/ydb/core/tx/columnshard/hooks/abstract/ya.make index 1fc805cb1b47..6837b6bd29ea 100644 --- a/ydb/core/tx/columnshard/hooks/abstract/ya.make +++ b/ydb/core/tx/columnshard/hooks/abstract/ya.make @@ -5,7 +5,10 @@ SRCS( ) PEERDIR( - ydb/core/tx/tiering + ydb/core/tx/tiering/tier + ydb/core/tx/columnshard/blobs_action/protos + ydb/core/tx/columnshard/data_sharing/protos + ydb/library/yql/core/expr_nodes ) END() diff --git a/ydb/core/tx/columnshard/hooks/testing/controller.cpp b/ydb/core/tx/columnshard/hooks/testing/controller.cpp index 9cf3a7e7e9b5..e048c11614ed 100644 --- a/ydb/core/tx/columnshard/hooks/testing/controller.cpp +++ b/ydb/core/tx/columnshard/hooks/testing/controller.cpp @@ -1,11 +1,14 @@ #include "controller.h" -#include + #include -#include +#include #include #include #include +#include #include +#include + #include namespace NKikimr::NYDBTest::NColumnShard { @@ -22,16 +25,18 @@ void TController::DoOnAfterGCAction(const ::NKikimr::NColumnShard::TColumnShard& } } -void TController::CheckInvariants(const ::NKikimr::NColumnShard::TColumnShard& shard, TCheckContext& context) const { +void TController::CheckInvariants(const ::NKikimr::NColumnShard::TColumnShard& shard, TCheckContext& /*context*/) const { if (!shard.HasIndex()) { return; } + /* const auto& index = shard.GetIndexAs(); std::vector> granules = index.GetTables({}, {}); THashMap> ids; for (auto&& i : granules) { + auto accessor = i->GetDataAccessorPtrVerifiedAs(); for (auto&& p : i->GetPortions()) { - p.second->FillBlobIdsByStorage(ids, index.GetVersionedIndex()); + accessor->BuildAccessor(p.second).FillBlobIdsByStorage(ids, index.GetVersionedIndex()); } } for (auto&& i : ids) { @@ -60,6 +65,7 @@ void TController::CheckInvariants(const ::NKikimr::NColumnShard::TColumnShard& s } } context.AddCategories(shard.TabletID(), std::move(shardBlobsCategories)); + */ } TController::TCheckContext TController::CheckInvariants() const { @@ -118,7 +124,8 @@ bool TController::IsTrivialLinks() const { return true; } -::NKikimr::NColumnShard::TBlobPutResult::TPtr TController::OverrideBlobPutResultOnCompaction(const ::NKikimr::NColumnShard::TBlobPutResult::TPtr original, const NOlap::TWriteActionsCollection& actions) const { +::NKikimr::NColumnShard::TBlobPutResult::TPtr TController::OverrideBlobPutResultOnCompaction( + const ::NKikimr::NColumnShard::TBlobPutResult::TPtr original, const NOlap::TWriteActionsCollection& actions) const { if (IndexWriteControllerEnabled) { return original; } @@ -138,4 +145,4 @@ ::NKikimr::NColumnShard::TBlobPutResult::TPtr TController::OverrideBlobPutResult return result; } -} +} // namespace NKikimr::NYDBTest::NColumnShard diff --git a/ydb/core/tx/columnshard/hooks/testing/controller.h b/ydb/core/tx/columnshard/hooks/testing/controller.h index a8e259877fd0..356eb913527a 100644 --- a/ydb/core/tx/columnshard/hooks/testing/controller.h +++ b/ydb/core/tx/columnshard/hooks/testing/controller.h @@ -12,7 +12,8 @@ namespace NKikimr::NYDBTest::NColumnShard { class TController: public TReadOnlyController { private: using TBase = TReadOnlyController; - YDB_ACCESSOR_DEF(std::optional, OverrideRequestsTracePingCheckPeriod); + YDB_ACCESSOR_DEF(std::optional, OverrideUsedSnapshotLivetime); + YDB_ACCESSOR_DEF(std::optional, OverrideStalenessLivetimePing); YDB_ACCESSOR_DEF(std::optional, OverrideLagForCompactionBeforeTierings); YDB_ACCESSOR(std::optional, OverrideGuaranteeIndexationInterval, TDuration::Zero()); YDB_ACCESSOR(std::optional, OverridePeriodicWakeupActivationPeriod, std::nullopt); @@ -21,7 +22,13 @@ class TController: public TReadOnlyController { YDB_ACCESSOR(std::optional, OverrideOptimizerFreshnessCheckDuration, TDuration::Zero()); YDB_ACCESSOR_DEF(std::optional, OverrideCompactionActualizationLag); YDB_ACCESSOR_DEF(std::optional, OverrideTasksActualizationLag); - YDB_ACCESSOR_DEF(std::optional, OverrideReadTimeoutClean); + YDB_ACCESSOR_DEF(std::optional, OverrideMaxReadStaleness); + YDB_ACCESSOR(std::optional, OverrideMemoryLimitForPortionReading, 100); + YDB_ACCESSOR(std::optional, OverrideLimitForPortionsMetadataAsk, 1); + YDB_ACCESSOR(std::optional, OverrideBlobSplitSettings, NOlap::NSplitter::TSplitSettings::BuildForTests()); + + YDB_ACCESSOR_DEF(std::optional, OverrideBlobPutResultOnWriteValue); + EOptimizerCompactionWeightControl CompactionControl = EOptimizerCompactionWeightControl::Force; YDB_ACCESSOR(std::optional, OverrideReduceMemoryIntervalLimit, 1024); @@ -129,15 +136,35 @@ class TController: public TReadOnlyController { THashSet SharingIds; protected: - virtual ::NKikimr::NColumnShard::TBlobPutResult::TPtr OverrideBlobPutResultOnCompaction(const ::NKikimr::NColumnShard::TBlobPutResult::TPtr original, const NOlap::TWriteActionsCollection& actions) const override; + virtual const NOlap::NSplitter::TSplitSettings& DoGetBlobSplitSettings(const NOlap::NSplitter::TSplitSettings& defaultValue) const override { + if (OverrideBlobSplitSettings) { + return *OverrideBlobSplitSettings; + } else { + return defaultValue; + } + } + virtual ::NKikimr::NColumnShard::TBlobPutResult::TPtr OverrideBlobPutResultOnCompaction( + const ::NKikimr::NColumnShard::TBlobPutResult::TPtr original, const NOlap::TWriteActionsCollection& actions) const override; + + virtual ui64 DoGetLimitForPortionsMetadataAsk(const ui64 defaultValue) const override { + return OverrideLimitForPortionsMetadataAsk.value_or(defaultValue); + } + + + virtual ui64 DoGetMemoryLimitScanPortion(const ui64 defaultValue) const override { + return OverrideMemoryLimitForPortionReading.value_or(defaultValue); + } + virtual TDuration DoGetLagForCompactionBeforeTierings(const TDuration def) const override { return OverrideLagForCompactionBeforeTierings.value_or(def); } - virtual TDuration DoGetPingCheckPeriod(const TDuration def) const override { - return OverrideRequestsTracePingCheckPeriod.value_or(def); + virtual TDuration DoGetUsedSnapshotLivetime(const TDuration def) const override { + return OverrideUsedSnapshotLivetime.value_or(def); + } + virtual std::optional DoGetStalenessLivetimePing() const override { + return OverrideStalenessLivetimePing; } - virtual TDuration DoGetCompactionActualizationLag(const TDuration def) const override { return OverrideCompactionActualizationLag.value_or(def); } @@ -172,8 +199,8 @@ class TController: public TReadOnlyController { virtual TDuration DoGetOptimizerFreshnessCheckDuration(const TDuration defaultValue) const override { return OverrideOptimizerFreshnessCheckDuration.value_or(defaultValue); } - virtual TDuration DoGetReadTimeoutClean(const TDuration def) const override { - return OverrideReadTimeoutClean.value_or(def); + virtual TDuration DoGetMaxReadStaleness(const TDuration def) const override { + return OverrideMaxReadStaleness.value_or(def); } virtual ui64 DoGetReduceMemoryIntervalLimit(const ui64 def) const override { return OverrideReduceMemoryIntervalLimit.value_or(def); @@ -181,6 +208,10 @@ class TController: public TReadOnlyController { virtual ui64 DoGetRejectMemoryIntervalLimit(const ui64 def) const override { return OverrideRejectMemoryIntervalLimit.value_or(def); } + virtual ui64 DoGetMetadataRequestSoftMemoryLimit(const ui64 def) const override { + Y_UNUSED(def); + return 0; + } virtual EOptimizerCompactionWeightControl GetCompactionControl() const override { return CompactionControl; } @@ -196,6 +227,10 @@ class TController: public TReadOnlyController { } public: + virtual NKikimrProto::EReplyStatus OverrideBlobPutResultOnWrite(const NKikimrProto::EReplyStatus originalStatus) const override { + return OverrideBlobPutResultOnWriteValue.value_or(originalStatus); + } + const TAtomicCounter& GetIndexWriteControllerBrokeCount() const { return IndexWriteControllerBrokeCount; } diff --git a/ydb/core/tx/columnshard/inflight_request_tracker.cpp b/ydb/core/tx/columnshard/inflight_request_tracker.cpp index 6b7830b26cb0..da287c2f6b99 100644 --- a/ydb/core/tx/columnshard/inflight_request_tracker.cpp +++ b/ydb/core/tx/columnshard/inflight_request_tracker.cpp @@ -13,15 +13,14 @@ NOlap::NReader::TReadMetadataBase::TConstPtr TInFlightReadsTracker::ExtractInFli ui64 cookie, const NOlap::TVersionedIndex* /*index*/, const TInstant now) { auto it = RequestsMeta.find(cookie); AFL_VERIFY(it != RequestsMeta.end())("cookie", cookie); + AFL_VERIFY(ActorIds.erase(cookie)); const NOlap::NReader::TReadMetadataBase::TConstPtr readMetaBase = it->second; { { auto it = SnapshotsLive.find(readMetaBase->GetRequestSnapshot()); AFL_VERIFY(it != SnapshotsLive.end()); - if (it->second.DelRequest(cookie, now)) { - SnapshotsLive.erase(it); - } + Y_UNUSED(it->second.DelRequest(cookie, now)); } if (NOlap::NReader::NPlain::TReadMetadata::TConstPtr readMeta = @@ -93,27 +92,29 @@ class TTransactionSavePersistentSnapshots: public NOlap::NDataSharing::TExtended } // namespace std::unique_ptr TInFlightReadsTracker::Ping( - TColumnShard* self, const TDuration critDuration, const TInstant now) { + TColumnShard* self, const TDuration stalenessInMem, const TDuration usedSnapshotLivetime, const TInstant now) { std::set snapshotsToSave; - std::set snapshotsToFree; + std::set snapshotsToFreeInDB; + std::set snapshotsToFreeInMem; for (auto&& i : SnapshotsLive) { - if (i.second.Ping(critDuration, now)) { + if (i.second.IsExpired(usedSnapshotLivetime, now)) { if (i.second.GetIsLock()) { - Counters->OnSnapshotLocked(); - snapshotsToSave.emplace(i.first); - } else { Counters->OnSnapshotUnlocked(); - snapshotsToFree.emplace(i.first); + snapshotsToFreeInDB.emplace(i.first); } + snapshotsToFreeInMem.emplace(i.first); + } else if (i.second.CheckToLock(stalenessInMem, usedSnapshotLivetime, now)) { + Counters->OnSnapshotLocked(); + snapshotsToSave.emplace(i.first); } } - for (auto&& i : snapshotsToFree) { + for (auto&& i : snapshotsToFreeInMem) { SnapshotsLive.erase(i); } Counters->OnSnapshotsInfo(SnapshotsLive.size(), GetSnapshotToClean()); - if (snapshotsToFree.size() || snapshotsToSave.size()) { - NYDBTest::TControllers::GetColumnShardController()->OnRequestTracingChanges(snapshotsToSave, snapshotsToFree); - return std::make_unique(self, std::move(snapshotsToSave), std::move(snapshotsToFree)); + if (snapshotsToFreeInDB.size() || snapshotsToSave.size()) { + NYDBTest::TControllers::GetColumnShardController()->OnRequestTracingChanges(snapshotsToSave, snapshotsToFreeInMem); + return std::make_unique(self, std::move(snapshotsToSave), std::move(snapshotsToFreeInDB)); } else { return nullptr; } diff --git a/ydb/core/tx/columnshard/inflight_request_tracker.h b/ydb/core/tx/columnshard/inflight_request_tracker.h index 0aeec5acddbe..ec49366847bc 100644 --- a/ydb/core/tx/columnshard/inflight_request_tracker.h +++ b/ydb/core/tx/columnshard/inflight_request_tracker.h @@ -17,7 +17,6 @@ using NOlap::IBlobInUseTracker; class TSnapshotLiveInfo { private: const NOlap::TSnapshot Snapshot; - std::optional LastPingInstant; std::optional LastRequestFinishedInstant; THashSet Requests; YDB_READONLY(bool, IsLock, false); @@ -48,22 +47,32 @@ class TSnapshotLiveInfo { static TSnapshotLiveInfo BuildFromDatabase(const NOlap::TSnapshot& reqSnapshot) { TSnapshotLiveInfo result(reqSnapshot); - result.LastPingInstant = TInstant::Now(); - result.LastRequestFinishedInstant = result.LastPingInstant; + result.LastRequestFinishedInstant = TInstant::Now(); result.IsLock = true; return result; } - bool Ping(const TDuration critDuration, const TInstant now) { - LastPingInstant = now; - if (Requests.empty()) { - AFL_VERIFY(LastRequestFinishedInstant); - if (critDuration < *LastPingInstant - *LastRequestFinishedInstant && IsLock) { - IsLock = false; + bool IsExpired(const TDuration critDuration, const TInstant now) const { + if (Requests.size()) { + return false; + } + AFL_VERIFY(LastRequestFinishedInstant); + return critDuration < now - *LastRequestFinishedInstant; + } + + bool CheckToLock(const TDuration snapshotLivetime, const TDuration usedSnapshotGuaranteeLivetime, const TInstant now) { + if (IsLock) { + return false; + } + + if (Requests.size()) { + if (now + usedSnapshotGuaranteeLivetime > Snapshot.GetPlanInstant() + snapshotLivetime) { + IsLock = true; return true; } } else { - if (critDuration < *LastPingInstant - Snapshot.GetPlanInstant() && !IsLock) { + AFL_VERIFY(LastRequestFinishedInstant); + if (*LastRequestFinishedInstant + usedSnapshotGuaranteeLivetime > Snapshot.GetPlanInstant() + snapshotLivetime) { IsLock = true; return true; } @@ -76,6 +85,12 @@ class TInFlightReadsTracker { private: std::map SnapshotsLive; std::shared_ptr Counters; + THashMap ActorIds; + + std::shared_ptr StoragesManager; + ui64 NextCookie = 1; + THashMap RequestsMeta; + NOlap::TSelectInfo::TStats SelectStatsDelta; public: std::optional GetSnapshotToClean() const { @@ -88,11 +103,15 @@ class TInFlightReadsTracker { bool LoadFromDatabase(NTable::TDatabase& db); - [[nodiscard]] std::unique_ptr Ping(TColumnShard* self, const TDuration critDuration, const TInstant now); + [[nodiscard]] std::unique_ptr Ping( + TColumnShard* self, const TDuration stalenessInMem, const TDuration usedSnapshotLivetime, const TInstant now); // Returns a unique cookie associated with this request [[nodiscard]] ui64 AddInFlightRequest( NOlap::NReader::TReadMetadataBase::TConstPtr readMeta, const NOlap::TVersionedIndex* index); + void AddScanActorId(const ui64 cookie, const NActors::TActorId& actorId) { + AFL_VERIFY(ActorIds.emplace(cookie, actorId).second); + } [[nodiscard]] NOlap::NReader::TReadMetadataBase::TConstPtr ExtractInFlightRequest(ui64 cookie, const NOlap::TVersionedIndex* index, const TInstant now); @@ -102,6 +121,12 @@ class TInFlightReadsTracker { return delta; } + void Stop(TColumnShard* /*self*/) { + for (auto&& i : ActorIds) { + NActors::TActivationContext::Send(i.second, std::make_unique()); + } + } + TInFlightReadsTracker(const std::shared_ptr& storagesManager, const std::shared_ptr& counters) : Counters(counters) , StoragesManager(storagesManager) { @@ -110,12 +135,6 @@ class TInFlightReadsTracker { private: void AddToInFlightRequest( const ui64 cookie, NOlap::NReader::TReadMetadataBase::TConstPtr readMetaBase, const NOlap::TVersionedIndex* index); - -private: - std::shared_ptr StoragesManager; - ui64 NextCookie = 1; - THashMap RequestsMeta; - NOlap::TSelectInfo::TStats SelectStatsDelta; }; } // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/columnshard/loading/stages.cpp b/ydb/core/tx/columnshard/loading/stages.cpp new file mode 100644 index 000000000000..a0db85a87aaf --- /dev/null +++ b/ydb/core/tx/columnshard/loading/stages.cpp @@ -0,0 +1,231 @@ +#include "stages.h" + +#include +#include +#include +#include + +namespace NKikimr::NColumnShard::NLoading { + +bool TInsertTableInitializer::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + NIceDb::TNiceDb db(txc.DB); + TBlobGroupSelector dsGroupSelector(Self->Info()); + NOlap::TDbWrapper dbTable(txc.DB, &dsGroupSelector); + auto localInsertTable = std::make_unique(); + for (auto&& i : Self->TablesManager.GetTables()) { + localInsertTable->RegisterPathInfo(i.first); + } + if (!localInsertTable->Load(db, dbTable, TAppData::TimeProvider->Now())) { + ACFL_ERROR("step", "TInsertTable::Load_Fails"); + return false; + } + Self->InsertTable.swap(localInsertTable); + return true; +} + +bool TInsertTableInitializer::DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + NIceDb::TNiceDb db(txc.DB); + return Schema::Precharge(db, txc.DB.GetScheme()); +} + +bool TTxControllerInitializer::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + auto localTxController = std::make_unique(*Self); + if (!localTxController->Load(txc)) { + return false; + } + Self->ProgressTxController.swap(localTxController); + return true; +} + +bool TTxControllerInitializer::DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + NIceDb::TNiceDb db(txc.DB); + return Schema::Precharge(db, txc.DB.GetScheme()); +} + +bool TOperationsManagerInitializer::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + auto localOperationsManager = std::make_unique(); + if (!localOperationsManager->Load(txc)) { + return false; + } + Self->OperationsManager.swap(localOperationsManager); + return true; +} + +bool TOperationsManagerInitializer::DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + NIceDb::TNiceDb db(txc.DB); + return (int)Schema::Precharge(db, txc.DB.GetScheme()) & + (int)Schema::Precharge(db, txc.DB.GetScheme()); +} + +bool TStoragesManagerInitializer::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + AFL_VERIFY(Self->StoragesManager); + return Self->StoragesManager->LoadIdempotency(txc.DB); +} + +bool TStoragesManagerInitializer::DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + NIceDb::TNiceDb db(txc.DB); + return (int)Schema::Precharge(db, txc.DB.GetScheme()) & + (int)Schema::Precharge(db, txc.DB.GetScheme()) & + (int)Schema::Precharge(db, txc.DB.GetScheme()) & + (int)Schema::Precharge(db, txc.DB.GetScheme()) & + (int)Schema::Precharge(db, txc.DB.GetScheme()); +} + +bool TLongTxInitializer::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + NIceDb::TNiceDb db(txc.DB); + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return false; + } + + while (!rowset.EndOfSet()) { + const TInsertWriteId writeId = (TInsertWriteId)rowset.GetValue(); + const ui32 writePartId = rowset.GetValue(); + NKikimrLongTxService::TLongTxId proto; + Y_ABORT_UNLESS(proto.ParseFromString(rowset.GetValue())); + const auto longTxId = NLongTxService::TLongTxId::FromProto(proto); + + std::optional granuleShardingVersion; + if (rowset.HaveValue() && + rowset.GetValue()) { + granuleShardingVersion = rowset.GetValue(); + } + + Self->LoadLongTxWrite(writeId, writePartId, longTxId, granuleShardingVersion); + + if (!rowset.Next()) { + return false; + } + } + return true; +} + +bool TLongTxInitializer::DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + NIceDb::TNiceDb db(txc.DB); + return Schema::Precharge(db, txc.DB.GetScheme()); +} + +bool TDBLocksInitializer::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + if (txc.DB.GetScheme().GetTableInfo(Schema::Locks::TableId)) { + TColumnShardLocksDb locksDb(*Self, txc); + if (!Self->SysLocks.Load(locksDb)) { + return false; + } + } + return true; +} + +bool TDBLocksInitializer::DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + NIceDb::TNiceDb db(txc.DB); + return Schema::Precharge(db, txc.DB.GetScheme()); +} + +bool TBackgroundSessionsInitializer::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + return Self->BackgroundSessionsManager->LoadIdempotency(txc); +} + +bool TSharingSessionsInitializer::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + auto local = std::make_shared(); + if (!local->Load(txc.DB, Self->TablesManager.GetPrimaryIndexAsOptional())) { + return false; + } + Self->SharingSessionsManager = local; + return true; +} + +bool TInFlightReadsInitializer::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + TInFlightReadsTracker local(Self->StoragesManager, Self->Counters.GetRequestsTracingCounters()); + if (!local.LoadFromDatabase(txc.DB)) { + return false; + } + Self->InFlightReadsTracker = std::move(local); + return true; +} + +bool TSpecialValuesInitializer::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + NIceDb::TNiceDb db(txc.DB); + if (!Schema::GetSpecialValueOpt(db, Schema::EValueIds::CurrentSchemeShardId, Self->CurrentSchemeShardId)) { + return false; + } + if (!Schema::GetSpecialValueOpt(db, Schema::EValueIds::LastSchemaSeqNoGeneration, Self->LastSchemaSeqNo.Generation)) { + return false; + } + if (!Schema::GetSpecialValueOpt(db, Schema::EValueIds::LastSchemaSeqNoRound, Self->LastSchemaSeqNo.Round)) { + return false; + } + if (!Schema::GetSpecialProtoValue(db, Schema::EValueIds::ProcessingParams, Self->ProcessingParams)) { + return false; + } + if (!Schema::GetSpecialValueOpt(db, Schema::EValueIds::LastPlannedStep, Self->LastPlannedStep)) { + return false; + } + if (!Schema::GetSpecialValueOpt(db, Schema::EValueIds::LastPlannedTxId, Self->LastPlannedTxId)) { + return false; + } + if (!Schema::GetSpecialValueOpt(db, Schema::EValueIds::LastExportNumber, Self->LastExportNo)) { + return false; + } + if (!Schema::GetSpecialValueOpt(db, Schema::EValueIds::OwnerPathId, Self->OwnerPathId)) { + return false; + } + if (!Schema::GetSpecialValueOpt(db, Schema::EValueIds::OwnerPath, Self->OwnerPath)) { + return false; + } + + { + ui64 lastCompletedStep = 0; + ui64 lastCompletedTx = 0; + if (!Schema::GetSpecialValueOpt(db, Schema::EValueIds::LastCompletedStep, lastCompletedStep)) { + return false; + } + if (!Schema::GetSpecialValueOpt(db, Schema::EValueIds::LastCompletedTxId, lastCompletedTx)) { + return false; + } + Self->LastCompletedTx = NOlap::TSnapshot(lastCompletedStep, lastCompletedTx); + } + + return true; +} + +bool TSpecialValuesInitializer::DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + NIceDb::TNiceDb db(txc.DB); + return Schema::Precharge(db, txc.DB.GetScheme()); +} + +bool TTablesManagerInitializer::DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + NIceDb::TNiceDb db(txc.DB); + TTablesManager tablesManagerLocal(Self->StoragesManager, Self->DataAccessorsManager.GetObjectPtrVerified(), + NOlap::TSchemaCachesManager::GetCache(Self->OwnerPathId), Self->TabletID()); + { + TMemoryProfileGuard g("TTxInit/TTablesManager"); + if (!tablesManagerLocal.InitFromDB(db)) { + return false; + } + } + Self->Counters.GetTabletCounters()->SetCounter(COUNTER_TABLES, tablesManagerLocal.GetTables().size()); + Self->Counters.GetTabletCounters()->SetCounter(COUNTER_TABLE_PRESETS, tablesManagerLocal.GetSchemaPresets().size()); + Self->Counters.GetTabletCounters()->SetCounter(COUNTER_TABLE_TTLS, tablesManagerLocal.GetTtl().size()); + + Self->TablesManager = std::move(tablesManagerLocal); + return true; +} + +std::shared_ptr TTablesManagerInitializer::BuildNextReaderAfterLoad() { + if (Self->TablesManager.HasPrimaryIndex()) { + return Self->TablesManager.MutablePrimaryIndex().BuildLoader(std::make_shared(Self->Info())); + } else { + return nullptr; + } +} + +bool TTablesManagerInitializer::DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) { + NIceDb::TNiceDb db(txc.DB); + return (int)Schema::Precharge(db, txc.DB.GetScheme()) & + (int)Schema::Precharge(db, txc.DB.GetScheme()) & + (int)Schema::Precharge(db, txc.DB.GetScheme()) & + (int)Schema::Precharge(db, txc.DB.GetScheme()) & + (int)Schema::Precharge(db, txc.DB.GetScheme()) & + (int)Schema::Precharge(db, txc.DB.GetScheme()); +} + +} // namespace NKikimr::NColumnShard::NLoading diff --git a/ydb/core/tx/columnshard/loading/stages.h b/ydb/core/tx/columnshard/loading/stages.h new file mode 100644 index 000000000000..7fced6b3eba4 --- /dev/null +++ b/ydb/core/tx/columnshard/loading/stages.h @@ -0,0 +1,141 @@ +#pragma once +#include + +namespace NKikimr::NColumnShard { +class TColumnShard; + +} + +namespace NKikimr::NColumnShard::NLoading { + +class ITxShardInitReader: public ITxReader { +private: + using TBase = ITxReader; + +protected: + TColumnShard* Self = nullptr; + +public: + ITxShardInitReader(const TString& name, TColumnShard* shard) + : TBase(name) + , Self(shard) { + } +}; + +class TInsertTableInitializer: public ITxShardInitReader { +private: + using TBase = ITxShardInitReader; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + +public: + using TBase::TBase; +}; + +class TTxControllerInitializer: public ITxShardInitReader { +private: + using TBase = ITxShardInitReader; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + +public: + using TBase::TBase; +}; + +class TOperationsManagerInitializer: public ITxShardInitReader { +private: + using TBase = ITxShardInitReader; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + +public: + using TBase::TBase; +}; + +class TStoragesManagerInitializer: public ITxShardInitReader { +private: + using TBase = ITxShardInitReader; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + +public: + using TBase::TBase; +}; +class TLongTxInitializer: public ITxShardInitReader { +private: + using TBase = ITxShardInitReader; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + +public: + using TBase::TBase; +}; + +class TDBLocksInitializer: public ITxShardInitReader { +private: + using TBase = ITxShardInitReader; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + +public: + using TBase::TBase; +}; + +class TBackgroundSessionsInitializer: public ITxShardInitReader { +private: + using TBase = ITxShardInitReader; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& /*txc*/, const TActorContext& /*ctx*/) override { + return true; + } + +public: + using TBase::TBase; +}; + +class TSharingSessionsInitializer: public ITxShardInitReader { +private: + using TBase = ITxShardInitReader; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& /*txc*/, const TActorContext& /*ctx*/) override { + return true; + } + +public: + using TBase::TBase; +}; + +class TInFlightReadsInitializer: public ITxShardInitReader { +private: + using TBase = ITxShardInitReader; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& /*txc*/, const TActorContext& /*ctx*/) override { + return true; + } + +public: + using TBase::TBase; +}; + +class TSpecialValuesInitializer: public ITxShardInitReader { +private: + using TBase = ITxShardInitReader; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + +public: + using TBase::TBase; +}; + +class TTablesManagerInitializer: public ITxShardInitReader { +private: + using TBase = ITxShardInitReader; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + virtual std::shared_ptr BuildNextReaderAfterLoad() override; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& /*ctx*/) override; + +public: + using TBase::TBase; +}; + +} // namespace NKikimr::NColumnShard::NLoading diff --git a/ydb/core/tx/columnshard/loading/ya.make b/ydb/core/tx/columnshard/loading/ya.make new file mode 100644 index 000000000000..f6aa2b9e641e --- /dev/null +++ b/ydb/core/tx/columnshard/loading/ya.make @@ -0,0 +1,14 @@ +LIBRARY() + +SRCS( + stages.cpp +) + +PEERDIR( + ydb/core/tx/columnshard/tx_reader + ydb/services/metadata/abstract + ydb/core/tx/columnshard/blobs_action/events + ydb/core/tx/columnshard/data_sharing +) + +END() diff --git a/ydb/core/tx/columnshard/normalizer/abstract/abstract.cpp b/ydb/core/tx/columnshard/normalizer/abstract/abstract.cpp index 75005013fbed..7ca284d9d8d5 100644 --- a/ydb/core/tx/columnshard/normalizer/abstract/abstract.cpp +++ b/ydb/core/tx/columnshard/normalizer/abstract/abstract.cpp @@ -8,6 +8,7 @@ namespace NKikimr::NOlap { TNormalizationController::INormalizerComponent::TPtr TNormalizationController::RegisterNormalizer(INormalizerComponent::TPtr normalizer) { AFL_VERIFY(normalizer); + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "normalizer_register")("description", normalizer->DebugString()); Counters.emplace_back(normalizer->GetClassName()); Normalizers.emplace_back(normalizer); return normalizer; @@ -30,12 +31,19 @@ bool TNormalizationController::TNormalizationController::IsNormalizationFinished bool TNormalizationController::SwitchNormalizer() { if (IsNormalizationFinished()) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "normalization_finished"); return false; } - Y_ABORT_UNLESS(!GetNormalizer()->HasActiveTasks()); + AFL_VERIFY(!GetNormalizer()->HasActiveTasks()); GetCounters().OnNormalizerFinish(); Normalizers.pop_front(); Counters.pop_front(); + if (Normalizers.size()) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "normalizer_switched")("description", Normalizers.front()->DebugString())( + "id", Normalizers.front()->GetEnumSequentialId()); + } else { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "normalization_finished"); + } return !IsNormalizationFinished(); } @@ -51,7 +59,10 @@ void TNormalizationController::OnNormalizerFinished(NIceDb::TNiceDb& db) const { if (auto seqId = GetNormalizer()->GetSequentialId()) { NColumnShard::Schema::SaveSpecialValue(db, NColumnShard::Schema::EValueIds::LastNormalizerSequentialId, *seqId); } - NColumnShard::Schema::FinishNormalizer(db, GetNormalizer()->GetClassName(), GetNormalizer()->GetUniqueDescription(), GetNormalizer()->GetUniqueId()); + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "normalizer_finished")("description", GetNormalizer()->DebugString())( + "id", GetNormalizer()->GetSequentialId()); + NColumnShard::Schema::FinishNormalizer( + db, GetNormalizer()->GetClassName(), GetNormalizer()->GetUniqueDescription(), GetNormalizer()->GetUniqueId()); } void TNormalizationController::InitNormalizers(const TInitContext& ctx) { @@ -63,8 +74,10 @@ void TNormalizationController::InitNormalizers(const TInitContext& ctx) { if (FinishedNormalizers.contains(TNormalizerFullId(i.GetClassName(), i.GetDescription()))) { AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("warning", "repair already processed")("description", i.GetDescription()); } else { - auto normalizer = RegisterNormalizer(std::shared_ptr(INormalizerComponent::TFactory::Construct(i.GetClassName(), ctx))); - normalizer->SetIsRepair(true).SetUniqueDescription(i.GetDescription()); + auto component = INormalizerComponent::TFactory::MakeHolder(i.GetClassName(), ctx); + AFL_VERIFY(component)("class_name", i.GetClassName()); + auto normalizer = RegisterNormalizer(std::shared_ptr(component.Release())); + normalizer->SetIsRepair(true).SetIsDryRun(i.GetDryRun()).SetUniqueDescription(i.GetDescription()); } } } @@ -74,6 +87,7 @@ void TNormalizationController::InitNormalizers(const TInitContext& ctx) { LastSavedNormalizerId = {}; } + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "normalization_start")("last_saved_id", LastSavedNormalizerId); auto normalizers = GetEnumAllValues(); auto lastRegisteredNormalizer = ENormalizerSequentialId::Granules; for (auto nType : normalizers) { @@ -83,6 +97,9 @@ void TNormalizationController::InitNormalizers(const TInitContext& ctx) { if (nType == ENormalizerSequentialId::MAX) { continue; } + if (::ToString(nType).StartsWith("Deprecated")) { + continue; + } auto normalizer = RegisterNormalizer(std::shared_ptr(INormalizerComponent::TFactory::Construct(::ToString(nType), ctx))); AFL_VERIFY(normalizer->GetEnumSequentialIdVerified() == nType); AFL_VERIFY(lastRegisteredNormalizer <= nType)("current", ToString(nType))("last", ToString(lastRegisteredNormalizer)); @@ -134,7 +151,7 @@ bool TNormalizationController::InitControllerState(NIceDb::TNiceDb& db) { } NKikimr::TConclusion> TNormalizationController::INormalizerComponent::Init(const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) { - AFL_NOTICE(NKikimrServices::TX_COLUMNSHARD)("event", "normalization_init")("last", controller.GetLastSavedNormalizerId()) + AFL_NOTICE(NKikimrServices::TX_COLUMNSHARD)("event", "normalizer_init")("last", controller.GetLastSavedNormalizerId()) ("seq_id", GetSequentialId())("type", GetEnumSequentialId()); auto result = DoInit(controller, txc); if (!result.IsSuccess()) { diff --git a/ydb/core/tx/columnshard/normalizer/abstract/abstract.h b/ydb/core/tx/columnshard/normalizer/abstract/abstract.h index e75099ecd9ba..636177db8f74 100644 --- a/ydb/core/tx/columnshard/normalizer/abstract/abstract.h +++ b/ydb/core/tx/columnshard/normalizer/abstract/abstract.h @@ -1,15 +1,16 @@ #pragma once #include -#include - #include #include + +#include #include + #include namespace NKikimr::NIceDb { - class TNiceDb; +class TNiceDb; } namespace NKikimr::NOlap { @@ -21,6 +22,7 @@ class TNormalizerCounters: public NColumnShard::TCommonCountersOwner { NMonitoring::TDynamicCounters::TCounterPtr StartedCount; NMonitoring::TDynamicCounters::TCounterPtr FinishedCount; NMonitoring::TDynamicCounters::TCounterPtr FailedCount; + public: TNormalizerCounters(const TString& normalizerName) : TBase("Normalizer") { @@ -49,16 +51,23 @@ class TNormalizerCounters: public NColumnShard::TCommonCountersOwner { } }; -enum class ENormalizerSequentialId: ui32 { +enum class ENormalizerSequentialId : ui32 { Granules = 1, Chunks, - PortionsCleaner, + DeprecatedPortionsCleaner, TablesCleaner, - PortionsMetadata, + DeprecatedPortionsMetadata, CleanGranuleId, - EmptyPortionsCleaner, + DeprecatedEmptyPortionsCleaner, CleanInsertionDedup, GCCountersNormalizer, + RestorePortionFromChunks, + SyncPortionFromChunks, + DeprecatedRestoreV1Chunks, + SyncMinSnapshotFromChunks, + DeprecatedRestoreV1Chunks_V1, + RestoreV1Chunks_V2, + RestoreV2Chunks, MAX }; @@ -67,19 +76,20 @@ class TNormalizationContext { YDB_ACCESSOR_DEF(TActorId, ResourceSubscribeActor); YDB_ACCESSOR_DEF(TActorId, ShardActor); std::shared_ptr ResourcesGuard; + public: void SetResourcesGuard(std::shared_ptr rg) { ResourcesGuard = rg; } }; - class TNormalizationController; class INormalizerTask { public: using TPtr = std::shared_ptr; - virtual ~INormalizerTask() {} + virtual ~INormalizerTask() { + } virtual void Start(const TNormalizationController& controller, const TNormalizationContext& nCtx) = 0; }; @@ -87,7 +97,8 @@ class INormalizerTask { class INormalizerChanges { public: using TPtr = std::shared_ptr; - virtual ~INormalizerChanges() {} + virtual ~INormalizerChanges() { + } virtual bool ApplyOnExecute(NTabletFlatExecutor::TTransactionContext& txc, const TNormalizationController& normalizationContext) const = 0; virtual void ApplyOnComplete(const TNormalizationController& normalizationContext) const { @@ -95,10 +106,14 @@ class INormalizerChanges { } virtual ui64 GetSize() const = 0; + virtual TString DebugString() const { + return TStringBuilder() << "size=" << GetSize(); + } }; class TTrivialNormalizerTask: public INormalizerTask { INormalizerChanges::TPtr Changes; + public: TTrivialNormalizerTask(const INormalizerChanges::TPtr& changes) : Changes(changes) { @@ -111,10 +126,24 @@ class TTrivialNormalizerTask: public INormalizerTask { class TNormalizationController { public: class TInitContext { + private: TIntrusiveConstPtr StorageInfo; + const ui64 TabletId; + const NActors::TActorId TabletActorId; + public: - TInitContext(TTabletStorageInfo* info) - : StorageInfo(info) { + TInitContext(TTabletStorageInfo* info, const ui64 tabletId, const NActors::TActorId& actorId) + : StorageInfo(info) + , TabletId(tabletId) + , TabletActorId(actorId) { + } + + ui64 GetTabletId() const { + return TabletId; + } + + const NActors::TActorId& GetTabletActorId() const { + return TabletActorId; } TIntrusiveConstPtr GetStorageInfo() const { @@ -126,6 +155,7 @@ class TNormalizationController { private: YDB_READONLY_DEF(TString, ClassName); YDB_READONLY_DEF(TString, Description); + public: bool operator<(const TNormalizerFullId& item) const { if (ClassName == item.ClassName) { @@ -136,29 +166,36 @@ class TNormalizationController { TNormalizerFullId(const TString& className, const TString& description) : ClassName(className) - , Description(description) - { - + , Description(description) { } }; class INormalizerComponent { private: YDB_ACCESSOR(bool, IsRepair, false); + YDB_ACCESSOR(bool, IsDryRun, false); YDB_ACCESSOR_DEF(TString, UniqueDescription); YDB_ACCESSOR(TString, UniqueId, TGUID::CreateTimebased().AsUuidString()); - + virtual TString DoDebugString() const { return ""; } virtual std::optional DoGetEnumSequentialId() const = 0; + protected: + const ui64 TabletId; + const NActors::TActorId TabletActorId; + public: using TPtr = std::shared_ptr; using TFactory = NObjectFactory::TParametrizedObjectFactory; - virtual ~INormalizerComponent() {} + virtual ~INormalizerComponent() = default; + INormalizerComponent(const TInitContext& context) + : TabletId(context.GetTabletId()) + , TabletActorId(context.GetTabletActorId()) { + } TNormalizerFullId GetNormalizerFullId() const { return TNormalizerFullId(GetClassName(), UniqueDescription); @@ -168,13 +205,10 @@ class TNormalizationController { return AtomicGet(ActiveTasksCount) > 0; } - void OnResultReady() { - AFL_VERIFY(ActiveTasksCount > 0); - AtomicDecrement(ActiveTasksCount); - } - - i64 GetActiveTasksCount() const { - return AtomicGet(ActiveTasksCount); + [[nodiscard]] ui64 DecActiveCounters() { + const i64 result = AtomicDecrement(ActiveTasksCount); + AFL_VERIFY(result >= 0); + return result; } std::optional GetEnumSequentialId() const { @@ -218,10 +252,12 @@ class TNormalizationController { } } - TConclusion> Init(const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc); + TConclusion> Init( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc); private: - virtual TConclusion> DoInit(const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) = 0; + virtual TConclusion> DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) = 0; TAtomic ActiveTasksCount = 0; }; @@ -236,11 +272,13 @@ class TNormalizationController { std::set FinishedNormalizers; std::map StartedNormalizers; YDB_READONLY_DEF(std::optional, LastSavedNormalizerId); + private: INormalizerComponent::TPtr RegisterNormalizer(INormalizerComponent::TPtr normalizer); public: - TNormalizationController(std::shared_ptr storagesManager, const std::shared_ptr& counters) + TNormalizationController(std::shared_ptr storagesManager, + const std::shared_ptr& counters) : StoragesManager(storagesManager) , TaskSubscription("CS::NORMALIZER", counters) { } @@ -261,7 +299,7 @@ class TNormalizationController { TString DebugString() const { return TStringBuilder() << "normalizers_count=" << Normalizers.size() - << ";current_normalizer=" << (Normalizers.size() ? Normalizers.front()->DebugString() : "NO_DATA"); + << ";current_normalizer=" << (Normalizers.size() ? Normalizers.front()->DebugString() : "NO_DATA"); } const INormalizerComponent::TPtr& GetNormalizer() const; @@ -269,4 +307,4 @@ class TNormalizationController { bool SwitchNormalizer(); const TNormalizerCounters& GetCounters() const; }; -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/normalizer/granule/clean_granule.h b/ydb/core/tx/columnshard/normalizer/granule/clean_granule.h index 7cf6dd032057..cfb1402d8233 100644 --- a/ydb/core/tx/columnshard/normalizer/granule/clean_granule.h +++ b/ydb/core/tx/columnshard/normalizer/granule/clean_granule.h @@ -1,21 +1,25 @@ #pragma once -#include #include - +#include namespace NKikimr::NOlap { class TCleanGranuleIdNormalizer: public TNormalizationController::INormalizerComponent { +private: + using TBase = TNormalizationController::INormalizerComponent; + public: static TString GetClassNameStatic() { return ::ToString(ENormalizerSequentialId::CleanGranuleId); } + private: class TNormalizerResult; static inline INormalizerComponent::TFactory::TRegistrator Registrator = INormalizerComponent::TFactory::TRegistrator(GetClassNameStatic()); + public: virtual std::optional DoGetEnumSequentialId() const override { return ENormalizerSequentialId::CleanGranuleId; @@ -25,10 +29,12 @@ class TCleanGranuleIdNormalizer: public TNormalizationController::INormalizerCom return GetClassNameStatic(); } - TCleanGranuleIdNormalizer(const TNormalizationController::TInitContext&) { + TCleanGranuleIdNormalizer(const TNormalizationController::TInitContext& context) + : TBase(context) { } - virtual TConclusion> DoInit(const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; + virtual TConclusion> DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; }; -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/normalizer/granule/normalizer.h b/ydb/core/tx/columnshard/normalizer/granule/normalizer.h index a8d09c80a781..333dd3f0d57a 100644 --- a/ydb/core/tx/columnshard/normalizer/granule/normalizer.h +++ b/ydb/core/tx/columnshard/normalizer/granule/normalizer.h @@ -1,23 +1,28 @@ #pragma once -#include #include - +#include namespace NKikimr::NOlap { class TGranulesNormalizer: public TNormalizationController::INormalizerComponent { +private: + using TBase = TNormalizationController::INormalizerComponent; + public: static TString GetClassNameStatic() { return ::ToString(ENormalizerSequentialId::Granules); } + private: class TNormalizerResult; - static const inline INormalizerComponent::TFactory::TRegistrator Registrator = INormalizerComponent::TFactory::TRegistrator( - GetClassNameStatic()); + static const inline INormalizerComponent::TFactory::TRegistrator Registrator = + INormalizerComponent::TFactory::TRegistrator(GetClassNameStatic()); + public: - TGranulesNormalizer(const TNormalizationController::TInitContext&) { + TGranulesNormalizer(const TNormalizationController::TInitContext& context) + : TBase(context) { } virtual std::optional DoGetEnumSequentialId() const override { @@ -28,7 +33,8 @@ class TGranulesNormalizer: public TNormalizationController::INormalizerComponent return GetClassNameStatic(); } - virtual TConclusion> DoInit(const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; + virtual TConclusion> DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; }; -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/normalizer/insert_table/broken_dedup.cpp b/ydb/core/tx/columnshard/normalizer/insert_table/broken_dedup.cpp index 5a0934261879..b1e1ed58d043 100644 --- a/ydb/core/tx/columnshard/normalizer/insert_table/broken_dedup.cpp +++ b/ydb/core/tx/columnshard/normalizer/insert_table/broken_dedup.cpp @@ -93,7 +93,6 @@ TConclusion> TInsertionsDedupNormalizer::DoIn if (constructor.GetRecType() == NColumnShard::Schema::EInsertTableIds::Committed) { AFL_VERIFY(constructor.GetPlanStep()); } else { - AFL_VERIFY(!constructor.GetPlanStep()); if (constructor.GetRecType() == NColumnShard::Schema::EInsertTableIds::Aborted) { insertions[constructor.GetInsertWriteId()].SetAborted(constructor); } else if (constructor.GetRecType() == NColumnShard::Schema::EInsertTableIds::Inserted) { diff --git a/ydb/core/tx/columnshard/normalizer/insert_table/broken_dedup.h b/ydb/core/tx/columnshard/normalizer/insert_table/broken_dedup.h index c9a935e24371..232f0ee9a562 100644 --- a/ydb/core/tx/columnshard/normalizer/insert_table/broken_dedup.h +++ b/ydb/core/tx/columnshard/normalizer/insert_table/broken_dedup.h @@ -1,16 +1,19 @@ #pragma once -#include #include - +#include namespace NKikimr::NOlap::NInsertionDedup { class TInsertionsDedupNormalizer: public TNormalizationController::INormalizerComponent { +private: + using TBase = TNormalizationController::INormalizerComponent; + public: static TString GetClassNameStatic() { return "CleanInsertionDedup"; } + private: class TNormalizerResult; @@ -18,7 +21,8 @@ class TInsertionsDedupNormalizer: public TNormalizationController::INormalizerCo INormalizerComponent::TFactory::TRegistrator(GetClassNameStatic()); public: - TInsertionsDedupNormalizer(const TNormalizationController::TInitContext&) { + TInsertionsDedupNormalizer(const TNormalizationController::TInitContext& context) + : TBase(context) { } virtual std::optional DoGetEnumSequentialId() const override { @@ -29,7 +33,8 @@ class TInsertionsDedupNormalizer: public TNormalizationController::INormalizerCo return GetClassNameStatic(); } - virtual TConclusion> DoInit(const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; + virtual TConclusion> DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; }; -} +} // namespace NKikimr::NOlap::NInsertionDedup diff --git a/ydb/core/tx/columnshard/normalizer/portion/broken_blobs.cpp b/ydb/core/tx/columnshard/normalizer/portion/broken_blobs.cpp index 238638d9596a..86cea4553988 100644 --- a/ydb/core/tx/columnshard/normalizer/portion/broken_blobs.cpp +++ b/ydb/core/tx/columnshard/normalizer/portion/broken_blobs.cpp @@ -1,47 +1,42 @@ #include "broken_blobs.h" -#include -#include +#include #include +#include +#include +#include #include -#include - +#include namespace NKikimr::NOlap::NNormalizer::NBrokenBlobs { -class TNormalizerResult : public INormalizerChanges { - THashMap> BrokenPortions; +class TNormalizerResult: public INormalizerChanges { + THashMap BrokenPortions; std::shared_ptr> Schemas; + public: - TNormalizerResult(THashMap>&& portions, const std::shared_ptr>& schemas) + TNormalizerResult(THashMap&& portions, const std::shared_ptr>& schemas) : BrokenPortions(std::move(portions)) - , Schemas(schemas) - {} + , Schemas(schemas) { + } bool ApplyOnExecute(NTabletFlatExecutor::TTransactionContext& txc, const TNormalizationController& normController) const override { NOlap::TBlobManagerDb blobManagerDb(txc.DB); - + TDbWrapper db(txc.DB, nullptr); for (auto&& [_, portionInfo] : BrokenPortions) { - auto schema = Schemas->FindPtr(portionInfo->GetPortionId()); - AFL_VERIFY(!!schema)("portion_id", portionInfo->GetPortionId()); - AFL_CRIT(NKikimrServices::TX_COLUMNSHARD)("event", "portion_removed_as_broken")("portion_id", portionInfo->GetAddress().DebugString()); - portionInfo->SetRemoveSnapshot(TSnapshot(1, 1)); - portionInfo->SaveToDatabase(db, (*schema)->GetIndexInfo().GetPKFirstColumnId(), false); + auto schema = Schemas->FindPtr(portionInfo.GetPortionInfo().GetPortionId()); + AFL_VERIFY(!!schema)("portion_id", portionInfo.GetPortionInfo().GetPortionId()); + AFL_CRIT(NKikimrServices::TX_COLUMNSHARD)("event", "portion_removed_as_broken")( + "portion_id", portionInfo.GetPortionInfo().GetAddress().DebugString()); + auto copy = portionInfo.GetPortionInfo().MakeCopy(); + copy.SetRemoveSnapshot(TSnapshot(1, 1)); + copy.SaveMetaToDatabase(db); } if (BrokenPortions.size()) { - TStringBuilder sb; - ui64 recordsCount = 0; - sb << "path_ids:["; - for (auto&& [_, p] : BrokenPortions) { - sb << p->GetPathId() << ","; - recordsCount += p->GetRecordsCount(); - } - sb << "];"; - sb << "records_count:" << recordsCount; NIceDb::TNiceDb db(txc.DB); - normController.AddNormalizerEvent(db, "REMOVE_PORTIONS", sb); + normController.AddNormalizerEvent(db, "REMOVE_PORTIONS", DebugString()); } return true; } @@ -52,6 +47,19 @@ class TNormalizerResult : public INormalizerChanges { ui64 GetSize() const override { return BrokenPortions.size(); } + + TString DebugString() const override { + TStringBuilder sb; + ui64 recordsCount = 0; + sb << "path_ids=["; + for (auto&& [_, p] : BrokenPortions) { + sb << p.GetPortionInfo().GetPathId() << ","; + recordsCount += p.GetPortionInfo().GetRecordsCount(); + } + sb << "]"; + sb << ";records_count=" << recordsCount; + return sb; + } }; class TReadTask: public NOlap::NBlobOperations::NRead::ITask { @@ -59,34 +67,58 @@ class TReadTask: public NOlap::NBlobOperations::NRead::ITask { using TBase = NOlap::NBlobOperations::NRead::ITask; TNormalizationContext NormContext; std::shared_ptr> Schemas; - THashMap>> PortionsByBlobId; - THashMap> BrokenPortions; + THashMap> PortionsByBlobId; + THashMap BrokenPortions; + public: TReadTask(const TNormalizationContext& nCtx, const std::vector>& actions, - std::shared_ptr> schemas, THashMap < TString, THashMap>>&& portionsByBlobId) + std::shared_ptr> schemas, + THashMap>&& portionsByBlobId) : TBase(actions, "CS::NORMALIZER") , NormContext(nCtx) , Schemas(std::move(schemas)) - , PortionsByBlobId(portionsByBlobId) - { + , PortionsByBlobId(portionsByBlobId) { } protected: virtual void DoOnDataReady(const std::shared_ptr& /*resourcesGuard*/) override { + NBlobOperations::NRead::TCompositeReadBlobs blobs = ExtractBlobsData(); + + THashSet readyPortions; + for (auto&& i : BrokenPortions) { + readyPortions.emplace(i.first); + } + NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("event", "broken_data_found"); + for (auto&& i : PortionsByBlobId) { + for (auto&& [_, p] : i.second) { + if (readyPortions.emplace(p.GetPortionInfo().GetPortionId()).second) { + auto it = Schemas->find(p.GetPortionInfo().GetPortionId()); + AFL_VERIFY(it != Schemas->end()); + auto restored = TReadPortionInfoWithBlobs::RestorePortion(p, blobs, it->second->GetIndexInfo()); + auto restoredBatch = restored.RestoreBatch(*it->second, *it->second, {}); + if (restoredBatch.IsFail()) { + AFL_CRIT(NKikimrServices::TX_COLUMNSHARD)("portion", p.DebugString())("fail", restoredBatch.GetErrorMessage()); + BrokenPortions.emplace(p.GetPortionInfo().GetPortionId(), p); + } + } + } + } + auto changes = std::make_shared(std::move(BrokenPortions), Schemas); - TActorContext::AsActorContext().Send(NormContext.GetShardActor(), std::make_unique(changes)); + TActorContext::AsActorContext().Send( + NormContext.GetShardActor(), std::make_unique(changes)); } virtual bool DoOnError(const TString& storageId, const TBlobRange& range, const IBlobsReadingAction::TErrorStatus& status) override { - NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("blob_id", range.GetBlobId().ToStringNew()) - ("error", status.GetErrorMessage())("status", status.GetStatus())("event", "broken_blob_found")("storage_id", storageId); + NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("blob_id", range.GetBlobId().ToStringNew())( + "error", status.GetErrorMessage())("status", status.GetStatus())("event", "broken_blob_found")("storage_id", storageId); AFL_VERIFY(status.GetStatus() == NKikimrProto::EReplyStatus::NODATA)("status", status.GetStatus()); auto itStorage = PortionsByBlobId.find(storageId); AFL_VERIFY(itStorage != PortionsByBlobId.end()); auto it = itStorage->second.find(range.GetBlobId()); AFL_VERIFY(it != itStorage->second.end()); - AFL_CRIT(NKikimrServices::TX_COLUMNSHARD)("portion", it->second->GetAddress().DebugString()); - BrokenPortions.emplace(it->second->GetPortionId(), it->second); + AFL_CRIT(NKikimrServices::TX_COLUMNSHARD)("portion", it->second.GetPortionInfo().GetAddress().DebugString()); + BrokenPortions.emplace(it->second.GetPortionInfo().GetPortionId(), it->second); return true; } @@ -95,16 +127,18 @@ class TReadTask: public NOlap::NBlobOperations::NRead::ITask { }; class TBrokenBlobsTask: public INormalizerTask { - THashMap> Blobs; - THashMap>> PortionsByBlobId; + THashMap> Blobs; + THashMap> PortionsByBlobId; const std::shared_ptr> Schemas; + public: - TBrokenBlobsTask(THashMap>&& blobs, THashMap>>&& portionsByBlobId, + TBrokenBlobsTask(THashMap>&& blobs, + THashMap>&& portionsByBlobId, const std::shared_ptr>& schemas) : Blobs(std::move(blobs)) , PortionsByBlobId(portionsByBlobId) - , Schemas(schemas) - {} + , Schemas(schemas) { + } void Start(const TNormalizationController& controller, const TNormalizationContext& nCtx) override { ui64 memSize = 0; @@ -113,52 +147,49 @@ class TBrokenBlobsTask: public INormalizerTask { auto op = controller.GetStoragesManager()->GetOperatorVerified(storageId); actions.emplace_back(op->StartReadingAction(NBlobOperations::EConsumer::NORMALIZER)); for (auto&& b : data) { - memSize += b.BlobSize(); - actions.back()->AddRange(TBlobRange::FromBlobId(b)); + memSize += b.GetBlobSize(); + actions.back()->AddRange(b); } } NOlap::NResourceBroker::NSubscribe::ITask::StartResourceSubscription( nCtx.GetResourceSubscribeActor(), std::make_shared( - std::make_shared(nCtx, actions, Schemas, std::move(PortionsByBlobId)), 0, memSize, "CS::NORMALIZER", controller.GetTaskSubscription())); + std::make_shared(nCtx, actions, Schemas, std::move(PortionsByBlobId)), 0, memSize, + "CS::NORMALIZER", controller.GetTaskSubscription())); } }; - -bool TNormalizer::CheckPortion(const NColumnShard::TTablesManager& /*tablesManager*/, const TPortionInfo& /*portionInfo*/) const { +bool TNormalizer::CheckPortion(const NColumnShard::TTablesManager& /*tablesManager*/, const TPortionDataAccessor& /*portionInfo*/) const { return false; } -INormalizerTask::TPtr TNormalizer::BuildTask(std::vector>&& portions, std::shared_ptr> schemas) const { - THashMap> blobIds; - THashMap>> portionByBlobId; +INormalizerTask::TPtr TNormalizer::BuildTask( + std::vector&& portions, std::shared_ptr> schemas) const { + THashMap> blobIds; + THashMap> portionByBlobId; for (auto&& portion : portions) { - auto schemaPtr = schemas->FindPtr(portion->GetPortionId()); - THashMap> blobsByStorage; - portion->FillBlobIdsByStorage(blobsByStorage, schemaPtr->get()->GetIndexInfo()); + auto schemaPtr = schemas->FindPtr(portion.GetPortionInfo().GetPortionId()); + THashMap> blobsByStorage; + portion.FillBlobRangesByStorage(blobsByStorage, schemaPtr->get()->GetIndexInfo()); if (blobsByStorage.size() > 1 || !blobsByStorage.contains(NBlobOperations::TGlobal::DefaultStorageId)) { continue; } - for (auto&& i: blobsByStorage) { + for (auto&& i : blobsByStorage) { + AFL_VERIFY(i.first == NBlobOperations::TGlobal::DefaultStorageId)("details", "Invalid storage for normalizer")( + "storage_id", i.first); for (auto&& b : i.second) { - AFL_VERIFY(portionByBlobId[i.first].emplace(b, portion).second); + portionByBlobId[i.first].emplace(b.BlobId, portion); + AFL_VERIFY(blobIds[i.first].emplace(b).second); } } } - for (auto&& [storageId, blobs] : portionByBlobId) { - AFL_VERIFY(storageId == NBlobOperations::TGlobal::DefaultStorageId)("details", "Invalid storage for normalizer")("storage_id", storageId); - for (auto&& [blobId, _] : blobs) { - AFL_VERIFY(blobIds[storageId].emplace(blobId).second); - } - } if (blobIds.empty()) { return nullptr; } return std::make_shared(std::move(blobIds), std::move(portionByBlobId), schemas); } - TConclusion TNormalizer::DoInitImpl(const TNormalizationController&, NTabletFlatExecutor::TTransactionContext&) { +TConclusion TNormalizer::DoInitImpl(const TNormalizationController&, NTabletFlatExecutor::TTransactionContext&) { return true; } - -} +} // namespace NKikimr::NOlap::NNormalizer::NBrokenBlobs diff --git a/ydb/core/tx/columnshard/normalizer/portion/broken_blobs.h b/ydb/core/tx/columnshard/normalizer/portion/broken_blobs.h index 704e54d44333..249de9213e13 100644 --- a/ydb/core/tx/columnshard/normalizer/portion/broken_blobs.h +++ b/ydb/core/tx/columnshard/normalizer/portion/broken_blobs.h @@ -1,17 +1,17 @@ #pragma once #include "normalizer.h" -#include -#include +#include +#include namespace NKikimr::NColumnShard { - class TTablesManager; +class TTablesManager; } namespace NKikimr::NOlap::NNormalizer::NBrokenBlobs { -class TNormalizer : public TPortionsNormalizerBase { +class TNormalizer: public TPortionsNormalizerBase { public: static TString GetClassNameStatic() { return "BROKEN_BLOBS"; @@ -19,8 +19,8 @@ class TNormalizer : public TPortionsNormalizerBase { private: static inline TFactory::TRegistrator Registrator = TFactory::TRegistrator(GetClassNameStatic()); -public: +public: class TNormalizerResult; class TTask; @@ -34,17 +34,18 @@ class TNormalizer : public TPortionsNormalizerBase { } TNormalizer(const TNormalizationController::TInitContext& info) - : TPortionsNormalizerBase(info) - {} + : TPortionsNormalizerBase(info) { + } virtual std::set GetColumnsFilter(const ISnapshotSchema::TPtr& /*schema*/) const override { return {}; } - virtual INormalizerTask::TPtr BuildTask(std::vector>&& portions, std::shared_ptr> schemas) const override; + virtual INormalizerTask::TPtr BuildTask( + std::vector&& portions, std::shared_ptr> schemas) const override; virtual TConclusion DoInitImpl(const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; - virtual bool CheckPortion(const NColumnShard::TTablesManager& tablesManager, const TPortionInfo& portionInfo) const override; + virtual bool CheckPortion(const NColumnShard::TTablesManager& tablesManager, const TPortionDataAccessor& portionInfo) const override; }; -} +} // namespace NKikimr::NOlap::NNormalizer::NBrokenBlobs diff --git a/ydb/core/tx/columnshard/normalizer/portion/chunks.cpp b/ydb/core/tx/columnshard/normalizer/portion/chunks.cpp index f42f38061e45..e96a18d5e871 100644 --- a/ydb/core/tx/columnshard/normalizer/portion/chunks.cpp +++ b/ydb/core/tx/columnshard/normalizer/portion/chunks.cpp @@ -1,16 +1,18 @@ #include "chunks.h" #include "normalizer.h" +#include +#include +#include #include #include -#include - namespace NKikimr::NOlap { class TChunksNormalizer::TNormalizerResult: public INormalizerChanges { std::vector Chunks; std::shared_ptr> Schemas; + public: TNormalizerResult(std::vector&& chunks) : Chunks(std::move(chunks)) { @@ -21,17 +23,15 @@ class TChunksNormalizer::TNormalizerResult: public INormalizerChanges { NIceDb::TNiceDb db(txc.DB); for (auto&& chunkInfo : Chunks) { - NKikimrTxColumnShard::TIndexColumnMeta metaProto = chunkInfo.GetMetaProto(); - metaProto.SetNumRows(chunkInfo.GetUpdate().GetNumRows()); + metaProto.SetNumRows(chunkInfo.GetUpdate().GetRecordsCount()); metaProto.SetRawBytes(chunkInfo.GetUpdate().GetRawBytes()); const auto& key = chunkInfo.GetKey(); - db.Table().Key(key.GetIndex(), key.GetGranule(), key.GetColumnIdx(), - key.GetPlanStep(), key.GetTxId(), key.GetPortion(), key.GetChunk()).Update( - NIceDb::TUpdate(metaProto.SerializeAsString()) - ); + db.Table() + .Key(key.GetIndex(), key.GetGranule(), key.GetColumnIdx(), key.GetPlanStep(), key.GetTxId(), key.GetPortion(), key.GetChunk()) + .Update(NIceDb::TUpdate(metaProto.SerializeAsString())); } return true; } @@ -44,10 +44,12 @@ class TChunksNormalizer::TNormalizerResult: public INormalizerChanges { class TRowsAndBytesChangesTask: public NConveyor::ITask { public: using TDataContainer = std::vector; + private: NBlobOperations::NRead::TCompositeReadBlobs Blobs; std::vector Chunks; TNormalizationContext NormContext; + protected: virtual TConclusionStatus DoExecute(const std::shared_ptr& /*taskPtr*/) override { for (auto&& chunkInfo : Chunks) { @@ -58,26 +60,28 @@ class TRowsAndBytesChangesTask: public NConveyor::ITask { auto columnLoader = chunkInfo.GetLoader(); Y_ABORT_UNLESS(!!columnLoader); - TPortionInfo::TAssembleBlobInfo assembleBlob(blobData); + TPortionDataAccessor::TAssembleBlobInfo assembleBlob(blobData); assembleBlob.SetExpectedRecordsCount(chunkInfo.GetRecordsCount()); - auto batch = assembleBlob.BuildRecordBatch(*columnLoader); + auto batch = assembleBlob.BuildRecordBatch(*columnLoader).DetachResult(); Y_ABORT_UNLESS(!!batch); - chunkInfo.MutableUpdate().SetNumRows(batch->GetRecordsCount()); + chunkInfo.MutableUpdate().SetRecordsCount(batch->GetRecordsCount()); chunkInfo.MutableUpdate().SetRawBytes(batch->GetRawSizeVerified()); } auto changes = std::make_shared(std::move(Chunks)); - TActorContext::AsActorContext().Send(NormContext.GetShardActor(), std::make_unique(changes)); + TActorContext::AsActorContext().Send( + NormContext.GetShardActor(), std::make_unique(changes)); return TConclusionStatus::Success(); } public: - TRowsAndBytesChangesTask(NBlobOperations::NRead::TCompositeReadBlobs&& blobs, const TNormalizationContext& nCtx, std::vector&& chunks, std::shared_ptr>) + TRowsAndBytesChangesTask(NBlobOperations::NRead::TCompositeReadBlobs&& blobs, const TNormalizationContext& nCtx, + std::vector&& chunks, std::shared_ptr>) : Blobs(std::move(blobs)) , Chunks(std::move(chunks)) - , NormContext(nCtx) - {} + , NormContext(nCtx) { + } virtual TString GetTaskClassIdentifier() const override { const static TString name = "TRowsAndBytesChangesTask"; @@ -94,10 +98,11 @@ class TRowsAndBytesChangesTask: public NConveyor::ITask { }; void TChunksNormalizer::TChunkInfo::InitSchema(const NColumnShard::TTablesManager& tm) { - Schema = tm.GetPrimaryIndexSafe().GetVersionedIndex().GetSchema(NOlap::TSnapshot(Key.GetPlanStep(), Key.GetTxId())); + Schema = tm.GetPrimaryIndexSafe().GetVersionedIndex().GetSchemaVerified(NOlap::TSnapshot(Key.GetPlanStep(), Key.GetTxId())); } -TConclusion> TChunksNormalizer::DoInit(const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) { +TConclusion> TChunksNormalizer::DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) { using namespace NColumnShard; NIceDb::TNiceDb db(txc.DB); @@ -135,7 +140,8 @@ TConclusion> TChunksNormalizer::DoInit(const return tasks; } - TTablesManager tablesManager(controller.GetStoragesManager(), 0); + TTablesManager tablesManager(controller.GetStoragesManager(), std::make_shared(nullptr), + std::make_shared(), 0); if (!tablesManager.InitFromDB(db)) { ACFL_TRACE("normalizer", "TChunksNormalizer")("error", "can't initialize tables manager"); return TConclusionStatus::Fail("Can't load index"); @@ -160,4 +166,4 @@ TConclusion> TChunksNormalizer::DoInit(const return tasks; } -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/normalizer/portion/chunks.h b/ydb/core/tx/columnshard/normalizer/portion/chunks.h index c8a09669c7b8..5c20b9b0b21f 100644 --- a/ydb/core/tx/columnshard/normalizer/portion/chunks.h +++ b/ydb/core/tx/columnshard/normalizer/portion/chunks.h @@ -1,114 +1,119 @@ #pragma once -#include #include - #include - +#include namespace NKikimr::NColumnShard { - class TTablesManager; +class TTablesManager; } namespace NKikimr::NOlap { - class TChunksNormalizer : public TNormalizationController::INormalizerComponent { +class TChunksNormalizer: public TNormalizationController::INormalizerComponent { +private: + using TBase = TNormalizationController::INormalizerComponent; + +public: + static TString GetClassNameStatic() { + return ::ToString(ENormalizerSequentialId::Chunks); + } + + virtual std::optional DoGetEnumSequentialId() const override { + return ENormalizerSequentialId::Chunks; + } + + virtual TString GetClassName() const override { + return GetClassNameStatic(); + } + + class TNormalizerResult; + + class TKey { + YDB_READONLY(ui64, Index, 0); + YDB_READONLY(ui64, Granule, 0); + YDB_READONLY(ui64, ColumnIdx, 0); + YDB_READONLY(ui64, PlanStep, 0); + YDB_READONLY(ui64, TxId, 0); + YDB_READONLY(ui64, Portion, 0); + YDB_READONLY(ui64, Chunk, 0); + + public: + template + void Load(TRowset& rowset) { + using namespace NColumnShard; + Index = rowset.template GetValue(); + Granule = rowset.template GetValue(); + ColumnIdx = rowset.template GetValue(); + PlanStep = rowset.template GetValue(); + TxId = rowset.template GetValue(); + Portion = rowset.template GetValue(); + Chunk = rowset.template GetValue(); + } + + bool operator<(const TKey& other) const { + return std::make_tuple(Portion, Chunk, ColumnIdx) < std::make_tuple(other.Portion, other.Chunk, other.ColumnIdx); + } + }; + + class TUpdate { + YDB_ACCESSOR(ui64, RecordsCount, 0); + YDB_ACCESSOR(ui64, RawBytes, 0); + }; + + class TChunkInfo { + YDB_READONLY_DEF(TKey, Key); + TColumnChunkLoadContext CLContext; + ISnapshotSchema::TPtr Schema; + + YDB_ACCESSOR_DEF(TUpdate, Update); + public: + template + TChunkInfo(TKey&& key, const TSource& rowset, const IBlobGroupSelector* dsGroupSelector) + : Key(std::move(key)) + , CLContext(rowset, dsGroupSelector) { + } - static TString GetClassNameStatic() { - return ::ToString(ENormalizerSequentialId::Chunks); + ui32 GetRecordsCount() const { + return CLContext.GetMetaProto().GetNumRows(); } - virtual std::optional DoGetEnumSequentialId() const override { - return ENormalizerSequentialId::Chunks; + const TBlobRange& GetBlobRange() const { + return CLContext.GetBlobRange(); } - virtual TString GetClassName() const override { - return GetClassNameStatic(); + const NKikimrTxColumnShard::TIndexColumnMeta& GetMetaProto() const { + return CLContext.GetMetaProto(); } - class TNormalizerResult; - - class TKey { - YDB_READONLY(ui64, Index, 0); - YDB_READONLY(ui64, Granule, 0); - YDB_READONLY(ui64, ColumnIdx, 0); - YDB_READONLY(ui64, PlanStep, 0); - YDB_READONLY(ui64, TxId, 0); - YDB_READONLY(ui64, Portion, 0); - YDB_READONLY(ui64, Chunk, 0); - - public: - template - void Load(TRowset& rowset) { - using namespace NColumnShard; - Index = rowset.template GetValue(); - Granule = rowset.template GetValue(); - ColumnIdx = rowset.template GetValue(); - PlanStep = rowset.template GetValue(); - TxId = rowset.template GetValue(); - Portion = rowset.template GetValue(); - Chunk = rowset.template GetValue(); - } - - bool operator<(const TKey& other) const { - return std::make_tuple(Portion, Chunk, ColumnIdx) < std::make_tuple(other.Portion, other.Chunk, other.ColumnIdx); - } - }; - - class TUpdate { - YDB_ACCESSOR(ui64, NumRows, 0); - YDB_ACCESSOR(ui64, RawBytes, 0); - }; - - class TChunkInfo { - YDB_READONLY_DEF(TKey, Key); - TColumnChunkLoadContext CLContext; - ISnapshotSchema::TPtr Schema; - - YDB_ACCESSOR_DEF(TUpdate, Update); - public: - template - TChunkInfo(TKey&& key, const TSource& rowset, const IBlobGroupSelector* dsGroupSelector) - : Key(std::move(key)) - , CLContext(rowset, dsGroupSelector) - {} - - ui32 GetRecordsCount() const { - return CLContext.GetMetaProto().GetNumRows(); - } - - const TBlobRange& GetBlobRange() const { - return CLContext.GetBlobRange(); - } - - const NKikimrTxColumnShard::TIndexColumnMeta& GetMetaProto() const { - return CLContext.GetMetaProto(); - } - - bool NormalizationRequired() const { - return !CLContext.GetMetaProto().HasNumRows() || !CLContext.GetMetaProto().HasRawBytes(); - } - - std::shared_ptr GetLoader() const { - return Schema->GetColumnLoaderVerified(Key.GetColumnIdx()); - } - void InitSchema(const NColumnShard::TTablesManager& tm); - - bool operator<(const TChunkInfo& other) const { - return Key < other.Key; - } - }; - - static inline INormalizerComponent::TFactory::TRegistrator Registrator = INormalizerComponent::TFactory::TRegistrator(GetClassNameStatic()); - public: - TChunksNormalizer(const TNormalizationController::TInitContext& info) - : DsGroupSelector(info.GetStorageInfo()) - {} + bool NormalizationRequired() const { + return !CLContext.GetMetaProto().HasNumRows() || !CLContext.GetMetaProto().HasRawBytes(); + } - virtual TConclusion> DoInit(const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; + std::shared_ptr GetLoader() const { + return Schema->GetColumnLoaderVerified(Key.GetColumnIdx()); + } + void InitSchema(const NColumnShard::TTablesManager& tm); - private: - NColumnShard::TBlobGroupSelector DsGroupSelector; + bool operator<(const TChunkInfo& other) const { + return Key < other.Key; + } }; -} + + static inline INormalizerComponent::TFactory::TRegistrator Registrator = + INormalizerComponent::TFactory::TRegistrator(GetClassNameStatic()); + +public: + TChunksNormalizer(const TNormalizationController::TInitContext& info) + : TBase(info) + , DsGroupSelector(info.GetStorageInfo()) { + } + + virtual TConclusion> DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; + +private: + NColumnShard::TBlobGroupSelector DsGroupSelector; +}; +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/normalizer/portion/chunks_actualization.cpp b/ydb/core/tx/columnshard/normalizer/portion/chunks_actualization.cpp new file mode 100644 index 000000000000..b66b199a797f --- /dev/null +++ b/ydb/core/tx/columnshard/normalizer/portion/chunks_actualization.cpp @@ -0,0 +1,188 @@ +#include "chunks_actualization.h" +#include "normalizer.h" + +#include +#include +#include +#include + +namespace NKikimr::NOlap::NSyncChunksWithPortions1 { + +class TPatchItem { +private: + TPortionLoadContext PortionInfo; + YDB_READONLY_DEF(std::vector, Records); + YDB_READONLY_DEF(std::vector, Indexes); + +public: + const TPortionLoadContext& GetPortionInfo() const { + return PortionInfo; + } + + TPatchItem(TPortionLoadContext&& portion, std::vector&& records, std::vector&& indexes) + : PortionInfo(std::move(portion)) + , Records(std::move(records)) + , Indexes(std::move(indexes)) + { + + } +}; + +class TChanges: public INormalizerChanges { +private: + std::vector Patches; + +public: + TChanges(std::vector&& patches) + : Patches(std::move(patches)) { + } + virtual bool ApplyOnExecute(NTabletFlatExecutor::TTransactionContext& txc, const TNormalizationController&) const override { + using namespace NColumnShard; + NIceDb::TNiceDb db(txc.DB); + for (auto&& i : Patches) { + auto meta = i.GetPortionInfo().GetMetaProto(); + ui32 recordsCount = 0; + ui64 columnRawBytes = 0; + ui64 indexRawBytes = 0; + ui32 columnBlobBytes = 0; + ui32 indexBlobBytes = 0; + + for (auto&& c : i.GetRecords()) { + columnRawBytes += c.GetMetaProto().GetRawBytes(); + columnBlobBytes += c.GetBlobRange().GetSize(); + if (i.GetRecords().front().GetAddress().GetColumnId() == c.GetAddress().GetColumnId()) { + recordsCount += c.GetMetaProto().GetNumRows(); + } + } + for (auto&& c : i.GetIndexes()) { + columnRawBytes += c.GetRawBytes(); + columnBlobBytes += c.GetDataSize(); + } + meta.SetRecordsCount(recordsCount); + meta.SetColumnRawBytes(columnRawBytes); + meta.SetColumnBlobBytes(columnBlobBytes); + meta.SetIndexRawBytes(indexRawBytes); + meta.SetIndexBlobBytes(indexBlobBytes); + + db.Table() + .Key(i.GetPortionInfo().GetPathId(), i.GetPortionInfo().GetPortionId()) + .Update(NIceDb::TUpdate(meta.SerializeAsString())); + } + + return true; + } + + virtual ui64 GetSize() const override { + return Patches.size(); + } + +}; + +TConclusion> TNormalizer::DoInit( + const TNormalizationController& /*controller*/, NTabletFlatExecutor::TTransactionContext& txc) { + using namespace NColumnShard; + NIceDb::TNiceDb db(txc.DB); + + if (!AppDataVerified().ColumnShardConfig.GetColumnChunksV0Usage()) { + return std::vector(); + } + + bool ready = true; + ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); + ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); + ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); + if (!ready) { + return TConclusionStatus::Fail("Not ready"); + } + + THashMap dbPortions; + THashMap> recordsByPortion; + THashMap> indexesByPortion; + + { + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return TConclusionStatus::Fail("Not ready"); + } + + while (!rowset.EndOfSet()) { + TPortionLoadContext portion(rowset); + AFL_VERIFY(dbPortions.emplace(portion.GetPortionId(), portion).second); + + if (!rowset.Next()) { + return TConclusionStatus::Fail("Not ready"); + } + } + } + + { + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return TConclusionStatus::Fail("Not ready"); + } + + while (!rowset.EndOfSet()) { + TColumnChunkLoadContext chunk(rowset, &DsGroupSelector); + const ui64 portionId = chunk.GetPortionId(); + AFL_VERIFY(dbPortions.contains(portionId)); + recordsByPortion[portionId].emplace_back(std::move(chunk)); + + if (!rowset.Next()) { + return TConclusionStatus::Fail("Not ready"); + } + } + } + + { + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return TConclusionStatus::Fail("Not ready"); + } + + while (!rowset.EndOfSet()) { + TIndexChunkLoadContext chunk(rowset, &DsGroupSelector); + const ui64 portionId = chunk.GetPortionId(); + AFL_VERIFY(dbPortions.contains(portionId)); + indexesByPortion[portionId].emplace_back(std::move(chunk)); + + if (!rowset.Next()) { + return TConclusionStatus::Fail("Not ready"); + } + } + } + AFL_VERIFY(dbPortions.size() == recordsByPortion.size())("portions", dbPortions.size())("records", recordsByPortion.size()); + + for (auto&& i : indexesByPortion) { + AFL_VERIFY(dbPortions.contains(i.first)); + } + + std::vector tasks; + if (dbPortions.empty()) { + return tasks; + } + + std::vector package; + + for (auto&& [_, portion] : dbPortions) { + if (portion.GetMetaProto().GetRecordsCount()) { + continue; + } + auto itRecords = recordsByPortion.find(portion.GetPortionId()); + AFL_VERIFY(itRecords != recordsByPortion.end()); + auto itIndexes = indexesByPortion.find(portion.GetPortionId()); + auto indexes = (itIndexes == indexesByPortion.end()) ? Default>() : itIndexes->second; + package.emplace_back(std::move(portion), std::move(itRecords->second), std::move(indexes)); + if (package.size() == 100) { + std::vector local; + local.swap(package); + tasks.emplace_back(std::make_shared(std::make_shared(std::move(local)))); + } + } + + if (package.size() > 0) { + tasks.emplace_back(std::make_shared(std::make_shared(std::move(package)))); + } + return tasks; +} + +} // namespace NKikimr::NOlap::NChunksActualization diff --git a/ydb/core/tx/columnshard/normalizer/portion/chunks_actualization.h b/ydb/core/tx/columnshard/normalizer/portion/chunks_actualization.h new file mode 100644 index 000000000000..8daa81015836 --- /dev/null +++ b/ydb/core/tx/columnshard/normalizer/portion/chunks_actualization.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +namespace NKikimr::NColumnShard { +class TTablesManager; +} + +namespace NKikimr::NOlap::NSyncChunksWithPortions1 { + +class TNormalizer: public TNormalizationController::INormalizerComponent { +private: + using TBase = TNormalizationController::INormalizerComponent; + +public: + static TString GetClassNameStatic() { + return ::ToString(ENormalizerSequentialId::SyncPortionFromChunks); + } + + virtual std::optional DoGetEnumSequentialId() const override { + return ENormalizerSequentialId::SyncPortionFromChunks; + } + + virtual TString GetClassName() const override { + return GetClassNameStatic(); + } + + class TNormalizerResult; + + static inline INormalizerComponent::TFactory::TRegistrator Registrator = + INormalizerComponent::TFactory::TRegistrator(GetClassNameStatic()); + +public: + TNormalizer(const TNormalizationController::TInitContext& info) + : TBase(info) + , DsGroupSelector(info.GetStorageInfo()) { + } + + virtual TConclusion> DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; + +private: + NColumnShard::TBlobGroupSelector DsGroupSelector; +}; +} // namespace NKikimr::NOlap::NSyncChunksWithPortions1 diff --git a/ydb/core/tx/columnshard/normalizer/portion/clean.cpp b/ydb/core/tx/columnshard/normalizer/portion/clean.cpp index d1e00669f8b3..6384c32e34f2 100644 --- a/ydb/core/tx/columnshard/normalizer/portion/clean.cpp +++ b/ydb/core/tx/columnshard/normalizer/portion/clean.cpp @@ -1,23 +1,22 @@ #include "clean.h" -#include -#include +#include #include +#include +#include #include -#include - - namespace NKikimr::NOlap { -class TBlobsRemovingResult : public INormalizerChanges { +class TBlobsRemovingResult: public INormalizerChanges { std::shared_ptr RemovingAction; - std::vector> Portions; + std::vector Portions; + public: - TBlobsRemovingResult(std::shared_ptr removingAction, std::vector>&& portions) + TBlobsRemovingResult(std::shared_ptr removingAction, std::vector&& portions) : RemovingAction(removingAction) - , Portions(std::move(portions)) - {} + , Portions(std::move(portions)) { + } bool ApplyOnExecute(NTabletFlatExecutor::TTransactionContext& txc, const TNormalizationController& /* normController */) const override { NOlap::TBlobManagerDb blobManagerDb(txc.DB); @@ -25,8 +24,9 @@ class TBlobsRemovingResult : public INormalizerChanges { TDbWrapper db(txc.DB, nullptr); for (auto&& portion : Portions) { - AFL_CRIT(NKikimrServices::TX_COLUMNSHARD)("message", "remove lost portion")("path_id", portion->GetPathId())("portion_id", portion->GetPortionId()); - portion->RemoveFromDatabase(db); + AFL_CRIT(NKikimrServices::TX_COLUMNSHARD)("message", "remove lost portion")("path_id", portion.GetPortionInfo().GetPathId())( + "portion_id", portion.GetPortionInfo().GetPortionId()); + portion.RemoveFromDatabase(db); } return true; } @@ -40,36 +40,40 @@ class TBlobsRemovingResult : public INormalizerChanges { } }; -class TBlobsRemovingTask : public INormalizerTask { +class TBlobsRemovingTask: public INormalizerTask { std::vector Blobs; - std::vector> Portions; + std::vector Portions; + public: - TBlobsRemovingTask(std::vector&& blobs, std::vector>&& portions) + TBlobsRemovingTask(std::vector&& blobs, std::vector&& portions) : Blobs(std::move(blobs)) - , Portions(std::move(portions)) - {} + , Portions(std::move(portions)) { + } void Start(const TNormalizationController& controller, const TNormalizationContext& nCtx) override { controller.GetCounters().CountObjects(Blobs.size()); - auto removeAction = controller.GetStoragesManager()->GetDefaultOperator()->StartDeclareRemovingAction(NBlobOperations::EConsumer::NORMALIZER); + auto removeAction = + controller.GetStoragesManager()->GetDefaultOperator()->StartDeclareRemovingAction(NBlobOperations::EConsumer::NORMALIZER); for (auto&& blobId : Blobs) { removeAction->DeclareSelfRemove(blobId); } - TActorContext::AsActorContext().Send(nCtx.GetShardActor(), std::make_unique(std::make_shared(removeAction, std::move(Portions)))); + TActorContext::AsActorContext().Send( + nCtx.GetShardActor(), std::make_unique( + std::make_shared(removeAction, std::move(Portions)))); } }; - -bool TCleanPortionsNormalizer::CheckPortion(const NColumnShard::TTablesManager& tablesManager, const TPortionInfo& portionInfo) const { - return tablesManager.HasTable(portionInfo.GetAddress().GetPathId(), true); +bool TCleanPortionsNormalizer::CheckPortion(const NColumnShard::TTablesManager& tablesManager, const TPortionDataAccessor& portionInfo) const { + return tablesManager.HasTable(portionInfo.GetPortionInfo().GetAddress().GetPathId(), true); } -INormalizerTask::TPtr TCleanPortionsNormalizer::BuildTask(std::vector>&& portions, std::shared_ptr> schemas) const { +INormalizerTask::TPtr TCleanPortionsNormalizer::BuildTask( + std::vector&& portions, std::shared_ptr> schemas) const { std::vector blobIds; THashMap> blobsByStorage; for (auto&& portion : portions) { - auto schemaPtr = schemas->FindPtr(portion->GetPortionId()); - portion->FillBlobIdsByStorage(blobsByStorage, schemaPtr->get()->GetIndexInfo()); + auto schemaPtr = schemas->FindPtr(portion.GetPortionInfo().GetPortionId()); + portion.FillBlobIdsByStorage(blobsByStorage, schemaPtr->get()->GetIndexInfo()); } for (auto&& [storageId, blobs] : blobsByStorage) { if (storageId == NBlobOperations::TGlobal::DefaultStorageId) { @@ -84,9 +88,8 @@ INormalizerTask::TPtr TCleanPortionsNormalizer::BuildTask(std::vector(std::move(blobIds), std::move(portions)); } - TConclusion TCleanPortionsNormalizer::DoInitImpl(const TNormalizationController&, NTabletFlatExecutor::TTransactionContext&) { +TConclusion TCleanPortionsNormalizer::DoInitImpl(const TNormalizationController&, NTabletFlatExecutor::TTransactionContext&) { return true; } - -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/normalizer/portion/clean.h b/ydb/core/tx/columnshard/normalizer/portion/clean.h index 9902ae92ab05..6b01d65b3770 100644 --- a/ydb/core/tx/columnshard/normalizer/portion/clean.h +++ b/ydb/core/tx/columnshard/normalizer/portion/clean.h @@ -1,31 +1,33 @@ #pragma once #include "normalizer.h" -#include -#include +#include +#include namespace NKikimr::NColumnShard { - class TTablesManager; +class TTablesManager; } namespace NKikimr::NOlap { -class TCleanPortionsNormalizer : public TPortionsNormalizerBase { +class TCleanPortionsNormalizer: public TPortionsNormalizerBase { public: static TString GetClassNameStatic() { - return ::ToString(ENormalizerSequentialId::PortionsCleaner); + return "PortionsCleaner"; } private: - static inline TFactory::TRegistrator Registrator = TFactory::TRegistrator(GetClassNameStatic()); + static inline TFactory::TRegistrator Registrator = + TFactory::TRegistrator(GetClassNameStatic()); + public: class TNormalizerResult; class TTask; public: virtual std::optional DoGetEnumSequentialId() const override { - return ENormalizerSequentialId::PortionsCleaner; + return std::nullopt; } virtual TString GetClassName() const override { @@ -33,17 +35,18 @@ class TCleanPortionsNormalizer : public TPortionsNormalizerBase { } TCleanPortionsNormalizer(const TNormalizationController::TInitContext& info) - : TPortionsNormalizerBase(info) - {} + : TPortionsNormalizerBase(info) { + } virtual std::set GetColumnsFilter(const ISnapshotSchema::TPtr& /*schema*/) const override { return {}; } - virtual INormalizerTask::TPtr BuildTask(std::vector>&& portions, std::shared_ptr> schemas) const override; + virtual INormalizerTask::TPtr BuildTask( + std::vector&& portions, std::shared_ptr> schemas) const override; virtual TConclusion DoInitImpl(const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; - virtual bool CheckPortion(const NColumnShard::TTablesManager& tablesManager, const TPortionInfo& portionInfo) const override; + virtual bool CheckPortion(const NColumnShard::TTablesManager& tablesManager, const TPortionDataAccessor& portionInfo) const override; }; -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/normalizer/portion/clean_empty.cpp b/ydb/core/tx/columnshard/normalizer/portion/clean_empty.cpp index 56a258e0be26..ec0be99fd8dc 100644 --- a/ydb/core/tx/columnshard/normalizer/portion/clean_empty.cpp +++ b/ydb/core/tx/columnshard/normalizer/portion/clean_empty.cpp @@ -1,120 +1,330 @@ #include "clean_empty.h" + +#include #include +namespace NKikimr::NOlap::NSyncChunksWithPortions { + +class IDBModifier { +public: + virtual void Apply(NIceDb::TNiceDb& db) = 0; + virtual ~IDBModifier() = default; +}; + +class TRemoveV0: public IDBModifier { +private: + const TPortionAddress PortionAddress; + std::vector Chunks; + virtual void Apply(NIceDb::TNiceDb& db) override { + for (auto&& i : Chunks) { + AFL_CRIT(NKikimrServices::TX_COLUMNSHARD)("event", "remove_portion_v0")("path_id", PortionAddress.GetPathId())( + "portion_id", PortionAddress.GetPortionId())("chunk", i.GetAddress().DebugString()); + db.Table() + .Key(0, 0, i.GetAddress().GetColumnId(), i.GetMinSnapshotDeprecated().GetPlanStep(), + i.GetMinSnapshotDeprecated().GetTxId(), PortionAddress.GetPortionId(), i.GetAddress().GetChunkIdx()) + .Delete(); + } + } + +public: + TRemoveV0(const TPortionAddress& portionAddress, const std::vector& chunks) + : PortionAddress(portionAddress) + , Chunks(chunks) { + } +}; + +class TRemoveV1: public IDBModifier { +private: + const TPortionAddress PortionAddress; + std::vector Chunks; + virtual void Apply(NIceDb::TNiceDb& db) override { + for (auto&& i : Chunks) { + AFL_CRIT(NKikimrServices::TX_COLUMNSHARD)("event", "remove_portion_v1")("path_id", PortionAddress.GetPathId())( + "portion_id", PortionAddress.GetPortionId())("chunk", i.DebugString()); + db.Table() + .Key(PortionAddress.GetPathId(), PortionAddress.GetPortionId(), i.GetColumnId(), i.GetChunkIdx()) + .Delete(); + } + } -namespace NKikimr::NOlap { +public: + TRemoveV1(const TPortionAddress& portionAddress, const std::vector& chunks) + : PortionAddress(portionAddress) + , Chunks(chunks) { + } +}; + +class TRemoveV2: public IDBModifier { +private: + const TPortionAddress PortionAddress; + std::vector Chunks; + virtual void Apply(NIceDb::TNiceDb& db) override { + AFL_CRIT(NKikimrServices::TX_COLUMNSHARD)("event", "remove_portion_v2")("path_id", PortionAddress.GetPathId())( + "portion_id", PortionAddress.GetPortionId()); + db.Table().Key(PortionAddress.GetPathId(), PortionAddress.GetPortionId()).Delete(); + } + +public: + TRemoveV2(const TPortionAddress& portionAddress) + : PortionAddress(portionAddress) { + } +}; + +class TRemovePortion: public IDBModifier { +private: + const TPortionAddress PortionAddress; + std::vector Chunks; + virtual void Apply(NIceDb::TNiceDb& db) override { + AFL_CRIT(NKikimrServices::TX_COLUMNSHARD)("event", "remove_portion")("path_id", PortionAddress.GetPathId())( + "portion_id", PortionAddress.GetPortionId()); + db.Table().Key(PortionAddress.GetPathId(), PortionAddress.GetPortionId()).Delete(); + } + +public: + TRemovePortion(const TPortionAddress& portionAddress) + : PortionAddress(portionAddress) { + } +}; namespace { -std::optional> GetColumnPortionAddresses(NTabletFlatExecutor::TTransactionContext& txc) { +bool GetColumnPortionAddresses(NTabletFlatExecutor::TTransactionContext& txc, std::map>& resultV0, + std::map>& resultV1, std::map>& resultV2, + std::map>& portions, NColumnShard::TBlobGroupSelector& dsGroupSelector) { using namespace NColumnShard; NIceDb::TNiceDb db(txc.DB); + if (!Schema::Precharge(db, txc.DB.GetScheme())) { + return false; + } + if (!Schema::Precharge(db, txc.DB.GetScheme())) { + return false; + } + if (!Schema::Precharge(db, txc.DB.GetScheme())) { + return false; + } if (!Schema::Precharge(db, txc.DB.GetScheme())) { - return std::nullopt; + return false; } - THashSet usedPortions; - auto rowset = db.Table().Select< - Schema::IndexColumns::PathId, - Schema::IndexColumns::Portion - >(); - if (!rowset.IsReady()) { - return std::nullopt; + + { + std::map> usedPortions; + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return false; + } + while (!rowset.EndOfSet()) { + TColumnChunkLoadContext chunk(rowset, &dsGroupSelector); + usedPortions[chunk.GetPortionAddress()].emplace_back(chunk); + if (!rowset.Next()) { + return false; + } + } + std::map> tasks; + for (auto&& i : usedPortions) { + tasks.emplace(i.first, std::make_shared(i.first, i.second)); + } + std::swap(resultV0, tasks); } - while (!rowset.EndOfSet()) { - usedPortions.emplace( - rowset.GetValue(), - rowset.GetValue() - ); - if (!rowset.Next()) { - return std::nullopt; + { + std::map> usedPortions; + auto rowset = db.Table() + .Select(); + if (!rowset.IsReady()) { + return false; + } + while (!rowset.EndOfSet()) { + TPortionAddress address(rowset.GetValue(), rowset.GetValue()); + TChunkAddress cAddress(rowset.GetValue(), rowset.GetValue()); + usedPortions[address].emplace_back(cAddress); + if (!rowset.Next()) { + return false; + } } + std::map> tasks; + for (auto&& i : usedPortions) { + tasks.emplace(i.first, std::make_shared(i.first, i.second)); + } + std::swap(resultV1, tasks); } - return usedPortions; + { + std::map> usedPortions; + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return false; + } + while (!rowset.EndOfSet()) { + TPortionAddress portionAddress( + rowset.GetValue(), rowset.GetValue()); + usedPortions.emplace(portionAddress, std::make_shared(portionAddress)); + if (!rowset.Next()) { + return false; + } + } + std::swap(resultV2, usedPortions); + } + { + std::map> usedPortions; + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return false; + } + while (!rowset.EndOfSet()) { + TPortionAddress portionAddress( + rowset.GetValue(), rowset.GetValue()); + usedPortions.emplace(portionAddress, std::make_shared(portionAddress)); + if (!rowset.Next()) { + return false; + } + } + std::swap(portions, usedPortions); + } + return true; } using TBatch = std::vector; -std::optional> GetPortionsToDelete(NTabletFlatExecutor::TTransactionContext& txc) { +class TIterator { +private: + using TIteratorImpl = std::map>::const_iterator; + TIteratorImpl Current; + TIteratorImpl End; + +public: + TIterator(std::map>& source) + : Current(source.begin()) + , End(source.end()) { + } + bool Next() { + AFL_VERIFY(IsValid()); + ++Current; + return Current != End; + } + + bool IsValid() const { + return Current != End; + } + + TPortionAddress GetPortionAddress() const { + AFL_VERIFY(IsValid()); + return Current->first; + } + + std::shared_ptr GetModification() const { + AFL_VERIFY(IsValid()); + return Current->second; + } + + bool operator<(const TIterator& item) const { + AFL_VERIFY(IsValid()); + AFL_VERIFY(item.IsValid()); + return Current->first < item.Current->first; + } +}; + +std::optional>>> GetPortionsToDelete( + NTabletFlatExecutor::TTransactionContext& txc, NColumnShard::TBlobGroupSelector& dsGroupSelector) { using namespace NColumnShard; - const auto usedPortions = GetColumnPortionAddresses(txc); - if (!usedPortions) { + std::map> v0Portions; + std::map> v1Portions; + std::map> v2Portions; + std::map> portions; + if (!GetColumnPortionAddresses(txc, v0Portions, v1Portions, v2Portions, portions, dsGroupSelector)) { return std::nullopt; } - const size_t MaxBatchSize = 10000; - NIceDb::TNiceDb db(txc.DB); - if (!Schema::Precharge(db, txc.DB.GetScheme())) { - return std::nullopt; + std::vector pack; + std::map> iteration; + const bool v0Usage = AppDataVerified().ColumnShardConfig.GetColumnChunksV0Usage(); + const bool v1Usage = AppDataVerified().ColumnShardConfig.GetColumnChunksV1Usage(); + ui32 SourcesCount = 2; + if (v0Usage) { + ++SourcesCount; + if (v0Portions.size()) { + iteration[v0Portions.begin()->first].emplace_back(v0Portions); + } } - auto rowset = db.Table().Select< - Schema::IndexPortions::PathId, - Schema::IndexPortions::PortionId - >(); - if (!rowset.IsReady()) { - return std::nullopt; + if (v1Usage) { + ++SourcesCount; + if (v1Portions.size()) { + iteration[v1Portions.begin()->first].emplace_back(v1Portions); + } } - std::vector result; - TBatch portionsToDelete; - while (!rowset.EndOfSet()) { - TPortionAddress addr( - rowset.GetValue(), - rowset.GetValue() - ); - if (!usedPortions->contains(addr)) { - ACFL_WARN("normalizer", "TCleanEmptyPortionsNormalizer")("message", TStringBuilder() << addr.DebugString() << " marked for deletion"); - portionsToDelete.emplace_back(std::move(addr)); - if (portionsToDelete.size() == MaxBatchSize) { - result.emplace_back(std::move(portionsToDelete)); - portionsToDelete = TBatch{}; - } + { + if (v2Portions.size()) { + iteration[v2Portions.begin()->first].emplace_back(v2Portions); } - if (!rowset.Next()) { - return std::nullopt; + } + { + if (portions.size()) { + iteration[portions.begin()->first].emplace_back(portions); + } + } + std::vector>> result; + std::vector> modificationsPack; + ui32 countPortionsForRemove = 0; + while (iteration.size()) { + auto v = iteration.begin()->second; + const bool isCorrect = (v.size() == SourcesCount); + iteration.erase(iteration.begin()); + for (auto&& i : v) { + if (!isCorrect) { + modificationsPack.emplace_back(i.GetModification()); + if (modificationsPack.size() == 100) { + result.emplace_back(std::vector>()); + countPortionsForRemove += modificationsPack.size(); + std::swap(result.back(), modificationsPack); + } + } + if (i.Next()) { + iteration[i.GetPortionAddress()].emplace_back(i); + } } } - if (!portionsToDelete.empty()) { - result.emplace_back(std::move(portionsToDelete)); + if (modificationsPack.size()) { + countPortionsForRemove += modificationsPack.size(); + result.emplace_back(std::move(modificationsPack)); } + AFL_CRIT(NKikimrServices::TX_COLUMNSHARD)("tasks_for_remove", countPortionsForRemove); return result; } -class TChanges : public INormalizerChanges { +class TChanges: public INormalizerChanges { public: - TChanges(TBatch&& addresses) - : Addresses(addresses) - {} + TChanges(std::vector>&& modifications) + : Modifications(std::move(modifications)) { + } bool ApplyOnExecute(NTabletFlatExecutor::TTransactionContext& txc, const TNormalizationController&) const override { using namespace NColumnShard; NIceDb::TNiceDb db(txc.DB); - for(const auto& a: Addresses) { - db.Table().Key( - a.GetPathId(), - a.GetPortionId() - ).Delete(); + for (const auto& m : Modifications) { + m->Apply(db); } ACFL_WARN("normalizer", "TCleanEmptyPortionsNormalizer")("message", TStringBuilder() << GetSize() << " portions deleted"); return true; } ui64 GetSize() const override { - return Addresses.size(); + return Modifications.size(); } + private: - const TBatch Addresses; + const std::vector> Modifications; }; -} //namespace +} //namespace -TConclusion> TCleanEmptyPortionsNormalizer::DoInit(const TNormalizationController&, NTabletFlatExecutor::TTransactionContext& txc) { +TConclusion> TCleanEmptyPortionsNormalizer::DoInit( + const TNormalizationController&, NTabletFlatExecutor::TTransactionContext& txc) { using namespace NColumnShard; - auto batchesToDelete = GetPortionsToDelete(txc); + auto batchesToDelete = GetPortionsToDelete(txc, DsGroupSelector); if (!batchesToDelete) { - return TConclusionStatus::Fail("Not ready"); + return TConclusionStatus::Fail("Not ready"); } - + std::vector result; - for (auto&& b: *batchesToDelete) { + for (auto&& b : *batchesToDelete) { result.emplace_back(std::make_shared(std::make_shared(std::move(b)))); } return result; } -} //namespace NKikimr::NOlap +} // namespace NKikimr::NOlap::NSyncChunksWithPortions diff --git a/ydb/core/tx/columnshard/normalizer/portion/clean_empty.h b/ydb/core/tx/columnshard/normalizer/portion/clean_empty.h index 920b3d8c0f56..9b4104365d9a 100644 --- a/ydb/core/tx/columnshard/normalizer/portion/clean_empty.h +++ b/ydb/core/tx/columnshard/normalizer/portion/clean_empty.h @@ -2,20 +2,26 @@ #include -namespace NKikimr::NOlap { +namespace NKikimr::NOlap::NSyncChunksWithPortions { class TCleanEmptyPortionsNormalizer : public TNormalizationController::INormalizerComponent { - +private: + using TBase = TNormalizationController::INormalizerComponent; static TString ClassName() { - return ToString(ENormalizerSequentialId::EmptyPortionsCleaner); + return "EmptyPortionsCleaner"; } static inline auto Registrator = INormalizerComponent::TFactory::TRegistrator(ClassName()); + + NColumnShard::TBlobGroupSelector DsGroupSelector; + public: - TCleanEmptyPortionsNormalizer(const TNormalizationController::TInitContext&) - {} + TCleanEmptyPortionsNormalizer(const TNormalizationController::TInitContext& info) + : TBase(info) + , DsGroupSelector(info.GetStorageInfo()) { + } std::optional DoGetEnumSequentialId() const override { - return ENormalizerSequentialId::EmptyPortionsCleaner; + return std::nullopt; } TString GetClassName() const override { diff --git a/ydb/core/tx/columnshard/normalizer/portion/leaked_blobs.cpp b/ydb/core/tx/columnshard/normalizer/portion/leaked_blobs.cpp new file mode 100644 index 000000000000..8c25bd455bd3 --- /dev/null +++ b/ydb/core/tx/columnshard/normalizer/portion/leaked_blobs.cpp @@ -0,0 +1,292 @@ +#include "leaked_blobs.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace NKikimr::NOlap { + +class TLeakedBlobsNormalizerChanges: public INormalizerChanges { +private: + THashSet Leaks; + const ui64 TabletId; + NColumnShard::TBlobGroupSelector DsGroupSelector; + ui64 LeakeadBlobsSize; + +public: + TLeakedBlobsNormalizerChanges(THashSet&& leaks, const ui64 tabletId, NColumnShard::TBlobGroupSelector dsGroupSelector) + : Leaks(std::move(leaks)) + , TabletId(tabletId) + , DsGroupSelector(dsGroupSelector) { + LeakeadBlobsSize = 0; + for (const auto& blob : Leaks) { + LeakeadBlobsSize += blob.BlobSize(); + } + } + + bool ApplyOnExecute(NTabletFlatExecutor::TTransactionContext& txc, const TNormalizationController& /*normController*/) const override { + NIceDb::TNiceDb db(txc.DB); + for (auto&& i : Leaks) { + TUnifiedBlobId blobId(DsGroupSelector.GetGroup(i), i); + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("normalizer", "leaked_blobs")("blob_id", blobId.ToStringLegacy()); + db.Table().Key(blobId.ToStringLegacy(), TabletId).Update(); + } + AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("normalizer", "leaked_blobs")("removed_blobs", Leaks.size()); + + return true; + } + + void ApplyOnComplete(const TNormalizationController& /* normController */) const override { + } + + ui64 GetSize() const override { + return Leaks.size(); + } + + TString DebugString() const override { + TStringBuilder sb; + sb << "tablet=" << TabletId; + sb << ";leaked_blob_count=" << Leaks.size(); + sb << ";leaked_blobs_size=" << LeakeadBlobsSize; + auto blobSampleEnd = Leaks.begin(); + for (ui64 i = 0; i < 10 && blobSampleEnd != Leaks.end(); ++i, ++blobSampleEnd) { + } + sb << ";leaked_blobs=[" << JoinStrings(Leaks.begin(), blobSampleEnd, ",") << "]"; + return sb; + } +}; + +class TRemoveLeakedBlobsActor: public TActorBootstrapped { +private: + TVector Channels; + THashSet CSBlobIds; + THashSet BSBlobIds; + TActorId CSActorId; + ui64 CSTabletId; + i32 WaitingCount = 0; + THashSet WaitingRequests; + NColumnShard::TBlobGroupSelector DsGroupSelector; + + void CheckFinish() { + if (WaitingCount) { + return; + } + AFL_VERIFY(CSBlobIds.size() <= BSBlobIds.size())("cs", CSBlobIds.size())("bs", BSBlobIds.size())( + "error", "have to use broken blobs repair"); + for (auto&& i : CSBlobIds) { + AFL_VERIFY(BSBlobIds.erase(i))("error", "have to use broken blobs repair")("blob_id", i); + } + TActorContext::AsActorContext().Send( + CSActorId, std::make_unique( + std::make_shared(std::move(BSBlobIds), CSTabletId, DsGroupSelector))); + PassAway(); + } + +public: + TRemoveLeakedBlobsActor(TVector&& channels, THashSet&& csBlobIDs, TActorId csActorId, ui64 csTabletId, + const NColumnShard::TBlobGroupSelector& dsGroupSelector) + : Channels(std::move(channels)) + , CSBlobIds(std::move(csBlobIDs)) + , CSActorId(csActorId) + , CSTabletId(csTabletId) + , DsGroupSelector(dsGroupSelector) { + } + + void Bootstrap(const TActorContext& ctx) { + Y_UNUSED(ctx); + WaitingCount = 0; + + for (auto it = Channels.begin(); it != Channels.end(); ++it) { + if (it->Channel < 2) { + continue; + } + for (auto&& i : it->History) { + TLogoBlobID from(CSTabletId, 0, 0, it->Channel, 0, 0); + TLogoBlobID to(CSTabletId, Max(), Max(), it->Channel, TLogoBlobID::MaxBlobSize, TLogoBlobID::MaxCookie); + auto request = MakeHolder(CSTabletId, from, to, false, TInstant::Max(), true); + SendToBSProxy(SelfId(), i.GroupID, request.Release(), ++WaitingCount); + WaitingRequests.emplace(WaitingCount); + } + } + CheckFinish(); + + Become(&TThis::StateWait); + } + + void Handle(TEvBlobStorage::TEvRangeResult::TPtr& ev, const TActorContext& /*ctx*/) { + TEvBlobStorage::TEvRangeResult* msg = ev->Get(); + AFL_VERIFY(msg->Status == NKikimrProto::OK)("status", msg->Status)("error", msg->ErrorReason); + AFL_VERIFY(--WaitingCount >= 0); + AFL_VERIFY(WaitingRequests.erase(ev->Cookie)); + for (auto& resp : msg->Responses) { + AFL_VERIFY(!resp.Buffer); + BSBlobIds.emplace(resp.Id); + } + CheckFinish(); + } + + STFUNC(StateWait) { + switch (ev->GetTypeRewrite()) { + HFunc(TEvBlobStorage::TEvRangeResult, Handle); + default: + AFL_VERIFY(false); + } + } +}; + +TLeakedBlobsNormalizer::TLeakedBlobsNormalizer(const TNormalizationController::TInitContext& info) + : TBase(info) + , Channels(info.GetStorageInfo()->Channels) + , DsGroupSelector(info.GetStorageInfo()) { +} + +class TRemoveLeakedBlobsTask: public INormalizerTask { + TVector Channels; + THashSet CSBlobIDs; + ui64 TabletId; + TActorId ActorId; + NColumnShard::TBlobGroupSelector DsGroupSelector; + +public: + TRemoveLeakedBlobsTask(TVector&& channels, THashSet&& csBlobIDs, ui64 tabletId, TActorId actorId, + const NColumnShard::TBlobGroupSelector& dsGroupSelector) + : Channels(std::move(channels)) + , CSBlobIDs(std::move(csBlobIDs)) + , TabletId(tabletId) + , ActorId(actorId) + , DsGroupSelector(dsGroupSelector) { + } + void Start(const TNormalizationController& /*controller*/, const TNormalizationContext& /*nCtx*/) override { + NActors::TActivationContext::Register( + new TRemoveLeakedBlobsActor(std::move(Channels), std::move(CSBlobIDs), ActorId, TabletId, DsGroupSelector)); + } +}; + +TConclusion> TLeakedBlobsNormalizer::DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) { + using namespace NColumnShard; + AFL_VERIFY(AppDataVerified().FeatureFlags.GetEnableWritePortionsOnInsert()); + NIceDb::TNiceDb db(txc.DB); + const bool ready = (int)Schema::Precharge(db, txc.DB.GetScheme()) & + (int)Schema::Precharge(db, txc.DB.GetScheme()) & + (int)Schema::Precharge(db, txc.DB.GetScheme()) & + (int)Schema::Precharge(db, txc.DB.GetScheme()); + if (!ready) { + return TConclusionStatus::Fail("Not ready"); + } + + NColumnShard::TTablesManager tablesManager(controller.GetStoragesManager(), std::make_shared(nullptr), + std::make_shared(), TabletId); + + if (!tablesManager.InitFromDB(db)) { + ACFL_TRACE("normalizer", "TPortionsNormalizer")("error", "can't initialize tables manager"); + return TConclusionStatus::Fail("Can't load index"); + } + + if (!tablesManager.HasPrimaryIndex()) { + return std::vector{}; + } + + THashSet csBlobIDs; + auto conclusion = LoadPortionBlobIds(tablesManager, db, csBlobIDs); + if (conclusion.IsFail()) { + return conclusion; + } + + return std::vector{ std::make_shared( + std::move(Channels), std::move(csBlobIDs), TabletId, TabletActorId, DsGroupSelector) }; +} + +TConclusionStatus TLeakedBlobsNormalizer::LoadPortionBlobIds( + const NColumnShard::TTablesManager& tablesManager, NIceDb::TNiceDb& db, THashSet& result) { + TDbWrapper wrapper(db.GetDatabase(), nullptr); + if (Portions.empty()) { + THashMap portionsLocal; + if (!wrapper.LoadPortions({}, [&](TPortionInfoConstructor&& portion, const NKikimrTxColumnShard::TIndexPortionMeta& metaProto) { + const TIndexInfo& indexInfo = + portion.GetSchema(tablesManager.GetPrimaryIndexAsVerified().GetVersionedIndex())->GetIndexInfo(); + AFL_VERIFY(portion.MutableMeta().LoadMetadata(metaProto, indexInfo, DsGroupSelector)); + const ui64 portionId = portion.GetPortionIdVerified(); + AFL_VERIFY(portionsLocal.emplace(portionId, std::move(portion)).second); + })) { + return TConclusionStatus::Fail("repeated read db"); + } + Portions = std::move(portionsLocal); + } + if (Records.empty()) { + THashMap> recordsLocal; + if (!wrapper.LoadColumns(std::nullopt, [&](TColumnChunkLoadContextV2&& chunk) { + const ui64 portionId = chunk.GetPortionId(); + for (auto&& i : chunk.BuildRecordsV1()) { + recordsLocal[portionId].emplace_back(std::move(i)); + } + })) { + return TConclusionStatus::Fail("repeated read db"); + } + Records = std::move(recordsLocal); + } + if (Indexes.empty()) { + THashMap> indexesLocal; + if (!wrapper.LoadIndexes(std::nullopt, [&](const ui64 /*pathId*/, const ui64 /*portionId*/, TIndexChunkLoadContext&& indexChunk) { + const ui64 portionId = indexChunk.GetPortionId(); + indexesLocal[portionId].emplace_back(std::move(indexChunk)); + })) { + return TConclusionStatus::Fail("repeated read db"); + } + Indexes = std::move(indexesLocal); + } + if (BlobsToDelete.empty()) { + THashSet blobsToDelete; + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return TConclusionStatus::Fail("Not ready: BlobsToDeleteWT"); + } + while (!rowset.EndOfSet()) { + const TString& blobIdStr = rowset.GetValue(); + TString error; + TUnifiedBlobId blobId = TUnifiedBlobId::ParseFromString(blobIdStr, &DsGroupSelector, error); + AFL_VERIFY(blobId.IsValid())("event", "cannot_parse_blob")("error", error)("original_string", blobIdStr); + blobsToDelete.emplace(blobId); + if (!rowset.Next()) { + return TConclusionStatus::Fail("Local table is not loaded: BlobsToDeleteWT"); + } + } + BlobsToDelete = std::move(blobsToDelete); + } + AFL_VERIFY(Portions.size() == Records.size())("portions", Portions.size())("records", Records.size()); + THashSet resultLocal; + for (auto&& i : Portions) { + auto itRecords = Records.find(i.first); + AFL_VERIFY(itRecords != Records.end()); + auto itIndexes = Indexes.find(i.first); + std::vector indexes; + if (itIndexes != Indexes.end()) { + indexes = std::move(itIndexes->second); + } + TPortionDataAccessor accessor = + TPortionAccessorConstructor::BuildForLoading(i.second.Build(), std::move(itRecords->second), std::move(indexes)); + THashMap> blobIdsByStorage; + accessor.FillBlobIdsByStorage(blobIdsByStorage, tablesManager.GetPrimaryIndexAsVerified().GetVersionedIndex()); + auto it = blobIdsByStorage.find(NBlobOperations::TGlobal::DefaultStorageId); + if (it == blobIdsByStorage.end()) { + continue; + } + for (auto&& c : it->second) { + resultLocal.emplace(c.GetLogoBlobId()); + } + for (const auto& c : BlobsToDelete) { + resultLocal.emplace(c.GetLogoBlobId()); + } + } + std::swap(resultLocal, result); + return TConclusionStatus::Success(); +} + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/normalizer/portion/leaked_blobs.h b/ydb/core/tx/columnshard/normalizer/portion/leaked_blobs.h new file mode 100644 index 000000000000..1a4617e2cd67 --- /dev/null +++ b/ydb/core/tx/columnshard/normalizer/portion/leaked_blobs.h @@ -0,0 +1,50 @@ +#pragma once + +#include "normalizer.h" + +#include +#include + +namespace NKikimr::NOlap { +class TLeakedBlobsNormalizer: public TNormalizationController::INormalizerComponent { +private: + using TBase = TNormalizationController::INormalizerComponent; +public: + static TString GetClassNameStatic() { + return "LeakedBlobsNormalizer"; + } + +private: + static inline TFactory::TRegistrator Registrator = + TFactory::TRegistrator(GetClassNameStatic()); + +public: + class TNormalizerResult; + class TTask; + +public: + virtual std::optional DoGetEnumSequentialId() const override { + return std::nullopt; + } + + virtual TString GetClassName() const override { + return GetClassNameStatic(); + } + + TLeakedBlobsNormalizer(const TNormalizationController::TInitContext& info); + + TConclusionStatus LoadPortionBlobIds(const NColumnShard::TTablesManager& tablesManager, NIceDb::TNiceDb& db, THashSet& result); + + virtual TConclusion> DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; + +private: + TVector Channels; + TActorId TRemoveLeakedBlobsActorId; + NColumnShard::TBlobGroupSelector DsGroupSelector; + THashMap Portions; + THashMap> Records; + THashMap> Indexes; + THashSet BlobsToDelete; +}; +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/normalizer/portion/normalizer.cpp b/ydb/core/tx/columnshard/normalizer/portion/normalizer.cpp index 63cea8b19952..f1ce6882b727 100644 --- a/ydb/core/tx/columnshard/normalizer/portion/normalizer.cpp +++ b/ydb/core/tx/columnshard/normalizer/portion/normalizer.cpp @@ -1,12 +1,15 @@ #include "normalizer.h" -#include -#include #include +#include +#include +#include +#include namespace NKikimr::NOlap { -TConclusion> TPortionsNormalizerBase::DoInit(const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) { +TConclusion> TPortionsNormalizerBase::DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) { auto initRes = DoInitImpl(controller, txc); if (initRes.IsFail()) { @@ -22,7 +25,8 @@ TConclusion> TPortionsNormalizerBase::DoInit( return TConclusionStatus::Fail("Not ready"); } - NColumnShard::TTablesManager tablesManager(controller.GetStoragesManager(), 0); + NColumnShard::TTablesManager tablesManager(controller.GetStoragesManager(), std::make_shared(nullptr), + std::make_shared(), 0); if (!tablesManager.InitFromDB(db)) { ACFL_TRACE("normalizer", "TPortionsNormalizer")("error", "can't initialize tables manager"); return TConclusionStatus::Fail("Can't load index"); @@ -33,8 +37,14 @@ TConclusion> TPortionsNormalizerBase::DoInit( return tasks; } - THashMap portions; + THashMap portions; auto schemas = std::make_shared>(); + { + auto conclusion = InitPortions(tablesManager, db, portions); + if (conclusion.IsFail()) { + return conclusion; + } + } { auto conclusion = InitColumns(tablesManager, db, portions); if (conclusion.IsFail()) { @@ -49,22 +59,21 @@ TConclusion> TPortionsNormalizerBase::DoInit( } TPortionInfo::TSchemaCursor schema(tablesManager.GetPrimaryIndexSafe().GetVersionedIndex()); for (auto&& [_, p] : portions) { - (*schemas)[p.GetPortionIdVerified()] = schema.GetSchema(p); + (*schemas)[p.GetPortionConstructor().GetPortionIdVerified()] = schema.GetSchema(p.GetPortionConstructor()); } - std::vector> package; - package.reserve(100); + std::vector package; ui64 brokenPortioncCount = 0; for (auto&& portionConstructor : portions) { - auto portionInfo = std::make_shared(portionConstructor.second.Build(false)); - if (CheckPortion(tablesManager, *portionInfo)) { + auto portionInfo = portionConstructor.second.Build(false); + if (CheckPortion(tablesManager, portionInfo)) { continue; } ++brokenPortioncCount; package.emplace_back(portionInfo); if (package.size() == 1000) { - std::vector> local; + std::vector local; local.swap(package); auto task = BuildTask(std::move(local), schemas); if (!!task) { @@ -83,43 +92,45 @@ TConclusion> TPortionsNormalizerBase::DoInit( return tasks; } +TConclusionStatus TPortionsNormalizerBase::InitPortions( + const NColumnShard::TTablesManager& tablesManager, NIceDb::TNiceDb& db, THashMap& constructors) { + TDbWrapper wrapper(db.GetDatabase(), nullptr); + if (!wrapper.LoadPortions({}, [&](TPortionInfoConstructor&& portion, const NKikimrTxColumnShard::TIndexPortionMeta& metaProto) { + const TIndexInfo& indexInfo = + portion.GetSchema(tablesManager.GetPrimaryIndexAsVerified().GetVersionedIndex())->GetIndexInfo(); + AFL_VERIFY(portion.MutableMeta().LoadMetadata(metaProto, indexInfo, DsGroupSelector)); + const ui64 portionId = portion.GetPortionIdVerified(); + AFL_VERIFY(constructors.emplace(portionId, TPortionAccessorConstructor(std::move(portion))).second); + })) { + return TConclusionStatus::Fail("repeated read db"); + } + return TConclusionStatus::Success(); +} + TConclusionStatus TPortionsNormalizerBase::InitColumns( - const NColumnShard::TTablesManager& tablesManager, NIceDb::TNiceDb& db, THashMap& portions) { + const NColumnShard::TTablesManager& tablesManager, NIceDb::TNiceDb& db, THashMap& portions) { using namespace NColumnShard; auto columnsFilter = GetColumnsFilter(tablesManager.GetPrimaryIndexSafe().GetVersionedIndex().GetLastSchema()); - auto rowset = db.Table().Select(); + auto rowset = db.Table().Select(); if (!rowset.IsReady()) { return TConclusionStatus::Fail("Not ready"); } TPortionInfo::TSchemaCursor schema(tablesManager.GetPrimaryIndexSafe().GetVersionedIndex()); - auto initPortion = [&](TPortionInfoConstructor&& portion, const TColumnChunkLoadContext& loadContext) { - auto currentSchema = schema.GetSchema(portion); - portion.SetSchemaVersion(currentSchema->GetVersion()); - + auto initPortion = [&](TColumnChunkLoadContextV1&& loadContext) { if (!columnsFilter.empty() && !columnsFilter.contains(loadContext.GetAddress().GetColumnId())) { return; } - auto it = portions.find(portion.GetPortionIdVerified()); - if (it == portions.end()) { - const ui64 portionId = portion.GetPortionIdVerified(); - it = portions.emplace(portionId, std::move(portion)).first; - } else { - it->second.Merge(std::move(portion)); - } - it->second.LoadRecord(currentSchema->GetIndexInfo(), loadContext); + auto it = portions.find(loadContext.GetPortionId()); + AFL_VERIFY(it != portions.end()); + it->second.LoadRecord(std::move(loadContext)); }; while (!rowset.EndOfSet()) { - TPortionInfoConstructor portion(rowset.GetValue(), rowset.GetValue()); - Y_ABORT_UNLESS(rowset.GetValue() == 0); - - portion.SetMinSnapshotDeprecated( - NOlap::TSnapshot(rowset.GetValue(), rowset.GetValue())); - portion.SetRemoveSnapshot(rowset.GetValue(), rowset.GetValue()); - - NOlap::TColumnChunkLoadContext chunkLoadContext(rowset, &DsGroupSelector); - initPortion(std::move(portion), chunkLoadContext); + NOlap::TColumnChunkLoadContextV2 chunkLoadContext(rowset); + for (auto&& i : chunkLoadContext.BuildRecordsV1()) { + initPortion(std::move(i)); + } if (!rowset.Next()) { return TConclusionStatus::Fail("Not ready"); @@ -128,7 +139,7 @@ TConclusionStatus TPortionsNormalizerBase::InitColumns( return TConclusionStatus::Success(); } -TConclusionStatus TPortionsNormalizerBase::InitIndexes(NIceDb::TNiceDb& db, THashMap& portions) { +TConclusionStatus TPortionsNormalizerBase::InitIndexes(NIceDb::TNiceDb& db, THashMap& portions) { using IndexIndexes = NColumnShard::Schema::IndexIndexes; auto rowset = db.Table().Select(); if (!rowset.IsReady()) { @@ -140,7 +151,7 @@ TConclusionStatus TPortionsNormalizerBase::InitIndexes(NIceDb::TNiceDb& db, THas auto it = portions.find(rowset.GetValue()); AFL_VERIFY(it != portions.end()); - it->second.LoadIndex(chunkLoadContext); + it->second.LoadIndex(std::move(chunkLoadContext)); if (!rowset.Next()) { return TConclusionStatus::Fail("Not ready"); @@ -149,4 +160,4 @@ TConclusionStatus TPortionsNormalizerBase::InitIndexes(NIceDb::TNiceDb& db, THas return TConclusionStatus::Success(); } -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/normalizer/portion/normalizer.h b/ydb/core/tx/columnshard/normalizer/portion/normalizer.h index 8c23395eba0b..edfdad23ff4c 100644 --- a/ydb/core/tx/columnshard/normalizer/portion/normalizer.h +++ b/ydb/core/tx/columnshard/normalizer/portion/normalizer.h @@ -1,19 +1,16 @@ #pragma once -#include -#include #include - -#include -#include #include #include - #include - +#include +#include +#include +#include namespace NKikimr::NColumnShard { - class TTablesManager; +class TTablesManager; } namespace NKikimr::NOlap { @@ -26,18 +23,19 @@ class TReadPortionsTask: public NOlap::NBlobOperations::NRead::ITask { TNormalizationContext NormContext; public: - TReadPortionsTask(const TNormalizationContext& nCtx, const std::vector>& actions, typename TConveyorTask::TDataContainer&& data, std::shared_ptr> schemas) + TReadPortionsTask(const TNormalizationContext& nCtx, const std::vector>& actions, + typename TConveyorTask::TDataContainer&& data, std::shared_ptr> schemas) : TBase(actions, "CS::NORMALIZER") , Data(std::move(data)) , Schemas(std::move(schemas)) - , NormContext(nCtx) - { + , NormContext(nCtx) { } protected: virtual void DoOnDataReady(const std::shared_ptr& resourcesGuard) override { NormContext.SetResourcesGuard(resourcesGuard); - std::shared_ptr task = std::make_shared(std::move(ExtractBlobsData()), NormContext, std::move(Data), Schemas); + std::shared_ptr task = + std::make_shared(std::move(ExtractBlobsData()), NormContext, std::move(Data), Schemas); NConveyor::TCompServiceOperator::SendTaskToExecute(task); } @@ -51,18 +49,20 @@ class TReadPortionsTask: public NOlap::NBlobOperations::NRead::ITask { }; template -class TPortionsNormalizerTask : public INormalizerTask { +class TPortionsNormalizerTask: public INormalizerTask { typename TConveyorTask::TDataContainer Package; std::shared_ptr> Schemas; + public: TPortionsNormalizerTask(typename TConveyorTask::TDataContainer&& package) - : Package(std::move(package)) - {} + : Package(std::move(package)) { + } - TPortionsNormalizerTask(typename TConveyorTask::TDataContainer&& package, const std::shared_ptr> schemas) + TPortionsNormalizerTask( + typename TConveyorTask::TDataContainer&& package, const std::shared_ptr> schemas) : Package(std::move(package)) - , Schemas(schemas) - {} + , Schemas(schemas) { + } void Start(const TNormalizationController& controller, const TNormalizationContext& nCtx) override { controller.GetCounters().CountObjects(Package.size()); @@ -72,31 +72,38 @@ class TPortionsNormalizerTask : public INormalizerTask { TConveyorTask::FillBlobRanges(readingAction, data); memSize += TConveyorTask::GetMemSize(data); } - std::vector> actions = {readingAction}; + std::vector> actions = { readingAction }; NOlap::NResourceBroker::NSubscribe::ITask::StartResourceSubscription( - nCtx.GetResourceSubscribeActor(),std::make_shared( - std::make_shared>(nCtx, actions, std::move(Package), Schemas), 1, memSize, "CS::NORMALIZER", controller.GetTaskSubscription())); + nCtx.GetResourceSubscribeActor(), std::make_shared( + std::make_shared>(nCtx, actions, std::move(Package), Schemas), + 1, memSize, "CS::NORMALIZER", controller.GetTaskSubscription())); } }; -class TPortionsNormalizerBase : public TNormalizationController::INormalizerComponent { +class TPortionsNormalizerBase: public TNormalizationController::INormalizerComponent { +private: + using TBase = TNormalizationController::INormalizerComponent; public: TPortionsNormalizerBase(const TNormalizationController::TInitContext& info) - : DsGroupSelector(info.GetStorageInfo()) - {} + : TBase(info) + , DsGroupSelector(info.GetStorageInfo()) { + } + TConclusionStatus InitPortions( + const NColumnShard::TTablesManager& tablesManager, NIceDb::TNiceDb& db, THashMap& portions); TConclusionStatus InitColumns( - const NColumnShard::TTablesManager& tablesManager, NIceDb::TNiceDb& db, THashMap& portions); - TConclusionStatus InitIndexes(NIceDb::TNiceDb& db, THashMap& portions); + const NColumnShard::TTablesManager& tablesManager, NIceDb::TNiceDb& db, THashMap& portions); + TConclusionStatus InitIndexes(NIceDb::TNiceDb& db, THashMap& portions); virtual TConclusion> DoInit( const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override final; protected: - virtual INormalizerTask::TPtr BuildTask(std::vector>&& portions, std::shared_ptr> schemas) const = 0; - virtual TConclusion DoInitImpl(const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) = 0; + virtual INormalizerTask::TPtr BuildTask( + std::vector&& portions, std::shared_ptr> schemas) const = 0; + virtual TConclusion DoInitImpl(const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) = 0; - virtual bool CheckPortion(const NColumnShard::TTablesManager& tablesManager, const TPortionInfo& /*portionInfo*/) const = 0; + virtual bool CheckPortion(const NColumnShard::TTablesManager& tablesManager, const TPortionDataAccessor& portionInfo) const = 0; virtual std::set GetColumnsFilter(const ISnapshotSchema::TPtr& schema) const { return schema->GetPkColumnsIds(); @@ -106,4 +113,4 @@ class TPortionsNormalizerBase : public TNormalizationController::INormalizerComp NColumnShard::TBlobGroupSelector DsGroupSelector; }; -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/normalizer/portion/portion.cpp b/ydb/core/tx/columnshard/normalizer/portion/portion.cpp index 739715f44125..9dee3f2e1a60 100644 --- a/ydb/core/tx/columnshard/normalizer/portion/portion.cpp +++ b/ydb/core/tx/columnshard/normalizer/portion/portion.cpp @@ -1,31 +1,30 @@ #include "portion.h" -#include -#include -#include - #include - +#include +#include +#include namespace NKikimr::NOlap { -class TPortionsNormalizer::TNormalizerResult : public INormalizerChanges { - std::vector> Portions; +class TPortionsNormalizer::TNormalizerResult: public INormalizerChanges { + std::vector Portions; std::shared_ptr> Schemas; + public: - TNormalizerResult(std::vector>&& portions, std::shared_ptr> schemas) + TNormalizerResult(std::vector&& portions, std::shared_ptr> schemas) : Portions(std::move(portions)) - , Schemas(schemas) - {} + , Schemas(schemas) { + } bool ApplyOnExecute(NTabletFlatExecutor::TTransactionContext& txc, const TNormalizationController& /* normController */) const override { using namespace NColumnShard; TDbWrapper db(txc.DB, nullptr); for (auto&& portionInfo : Portions) { - auto schema = Schemas->FindPtr(portionInfo->GetPortionId()); - AFL_VERIFY(!!schema)("portion_id", portionInfo->GetPortionId()); - portionInfo->SaveToDatabase(db, (*schema)->GetIndexInfo().GetPKFirstColumnId(), true); + auto schema = Schemas->FindPtr(portionInfo.GetPortionInfo().GetPortionId()); + AFL_VERIFY(!!schema)("portion_id", portionInfo.GetPortionInfo().GetPortionId()); + portionInfo.SaveToDatabase(db, (*schema)->GetIndexInfo().GetPKFirstColumnId(), true); } return true; } @@ -35,15 +34,16 @@ class TPortionsNormalizer::TNormalizerResult : public INormalizerChanges { } }; -bool TPortionsNormalizer::CheckPortion(const NColumnShard::TTablesManager&, const TPortionInfo& portionInfo) const { - return KnownPortions.contains(portionInfo.GetAddress()); +bool TPortionsNormalizer::CheckPortion(const NColumnShard::TTablesManager&, const TPortionDataAccessor& portionInfo) const { + return KnownPortions.contains(portionInfo.GetPortionInfo().GetAddress()); } -INormalizerTask::TPtr TPortionsNormalizer::BuildTask(std::vector>&& portions, std::shared_ptr> schemas) const { +INormalizerTask::TPtr TPortionsNormalizer::BuildTask( + std::vector&& portions, std::shared_ptr> schemas) const { return std::make_shared(std::make_shared(std::move(portions), schemas)); } - TConclusion TPortionsNormalizer::DoInitImpl(const TNormalizationController&, NTabletFlatExecutor::TTransactionContext& txc) { +TConclusion TPortionsNormalizer::DoInitImpl(const TNormalizationController&, NTabletFlatExecutor::TTransactionContext& txc) { using namespace NColumnShard; NIceDb::TNiceDb db(txc.DB); @@ -71,5 +71,4 @@ INormalizerTask::TPtr TPortionsNormalizer::BuildTask(std::vector DoGetEnumSequentialId() const override { - return ENormalizerSequentialId::PortionsMetadata; + return std::nullopt; } virtual TString GetClassName() const override { @@ -37,10 +37,10 @@ class TPortionsNormalizer : public TPortionsNormalizerBase { : TPortionsNormalizerBase(info) {} - virtual INormalizerTask::TPtr BuildTask(std::vector>&& portions, std::shared_ptr> schemas) const override; + virtual INormalizerTask::TPtr BuildTask(std::vector&& portions, std::shared_ptr> schemas) const override; virtual TConclusion DoInitImpl(const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; - virtual bool CheckPortion(const NColumnShard::TTablesManager& tablesManager, const TPortionInfo& portionInfo) const override; + virtual bool CheckPortion(const NColumnShard::TTablesManager& tablesManager, const TPortionDataAccessor& portionInfo) const override; private: THashSet KnownPortions; diff --git a/ydb/core/tx/columnshard/normalizer/portion/restore_portion_from_chunks.cpp b/ydb/core/tx/columnshard/normalizer/portion/restore_portion_from_chunks.cpp new file mode 100644 index 000000000000..b6681250975c --- /dev/null +++ b/ydb/core/tx/columnshard/normalizer/portion/restore_portion_from_chunks.cpp @@ -0,0 +1,148 @@ +#include "normalizer.h" +#include "restore_portion_from_chunks.h" + +#include +#include +#include +#include + +namespace NKikimr::NOlap::NRestorePortionsFromChunks { + +class TPatchItem { +private: + YDB_READONLY(ui32, SchemaVersion, 0); + TColumnChunkLoadContext ChunkInfo; + +public: + const TColumnChunkLoadContext& GetChunkInfo() const { + return ChunkInfo; + } + + TPatchItem(const ui32 schemaVersion, TColumnChunkLoadContext&& chunkInfo) + : SchemaVersion(schemaVersion) + , ChunkInfo(std::move(chunkInfo)) { + } +}; + +class TChanges: public INormalizerChanges { +private: + std::vector Patches; + +public: + TChanges(std::vector&& patches) + : Patches(std::move(patches)) { + } + virtual bool ApplyOnExecute(NTabletFlatExecutor::TTransactionContext& txc, const TNormalizationController&) const override { + using namespace NColumnShard; + NIceDb::TNiceDb db(txc.DB); + for (auto&& i : Patches) { + AFL_VERIFY(i.GetChunkInfo().GetMetaProto().HasPortionMeta()); + auto metaProtoString = i.GetChunkInfo().GetMetaProto().GetPortionMeta().SerializeAsString(); + using IndexPortions = NColumnShard::Schema::IndexPortions; + const auto removeSnapshot = i.GetChunkInfo().GetRemoveSnapshot(); + const auto minSnapshotDeprecated = i.GetChunkInfo().GetMinSnapshotDeprecated(); + db.Table() + .Key(i.GetChunkInfo().GetPathId(), i.GetChunkInfo().GetPortionId()) + .Update(NIceDb::TUpdate(i.GetSchemaVersion()), NIceDb::TUpdate(0), + NIceDb::TUpdate(0), NIceDb::TUpdate(0), + NIceDb::TUpdate(0), NIceDb::TUpdate(removeSnapshot.GetPlanStep()), + NIceDb::TUpdate(removeSnapshot.GetTxId()), + NIceDb::TUpdate(minSnapshotDeprecated.GetPlanStep()), + NIceDb::TUpdate(minSnapshotDeprecated.GetTxId()), + NIceDb::TUpdate(metaProtoString)); + } + + return true; + } + + virtual ui64 GetSize() const override { + return Patches.size(); + } +}; + +TConclusion> TNormalizer::DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) { + using namespace NColumnShard; + NIceDb::TNiceDb db(txc.DB); + + bool ready = true; + ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); + ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); + if (!ready) { + return TConclusionStatus::Fail("Not ready"); + } + + TTablesManager tablesManager(controller.GetStoragesManager(), std::make_shared(nullptr), + std::make_shared(), 0); + if (!tablesManager.InitFromDB(db)) { + ACFL_TRACE("normalizer", "TChunksNormalizer")("error", "can't initialize tables manager"); + return TConclusionStatus::Fail("Can't load index"); + } + + THashMap dbPortions; + + { + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return TConclusionStatus::Fail("Not ready"); + } + + while (!rowset.EndOfSet()) { + TPortionLoadContext portion(rowset); + AFL_VERIFY(dbPortions.emplace(portion.GetPortionId(), portion).second); + + if (!rowset.Next()) { + return TConclusionStatus::Fail("Not ready"); + } + } + } + + THashMap portionsToWrite; + { + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return TConclusionStatus::Fail("Not ready"); + } + + THashSet portionsToRestore; + while (!rowset.EndOfSet()) { + TColumnChunkLoadContext chunk(rowset, &DsGroupSelector); + if (!dbPortions.contains(chunk.GetPortionId())) { + portionsToRestore.emplace(chunk.GetPortionId()); + if (chunk.GetMetaProto().HasPortionMeta()) { + AFL_VERIFY(portionsToWrite.emplace(chunk.GetPortionId(), chunk).second); + } + } + + if (!rowset.Next()) { + return TConclusionStatus::Fail("Not ready"); + } + } + AFL_VERIFY(portionsToRestore.size() == portionsToWrite.size()); + } + + std::vector tasks; + if (portionsToWrite.empty()) { + return tasks; + } + + std::vector package; + + for (auto&& [_, chunkWithPortionData] : portionsToWrite) { + package.emplace_back( + tablesManager.GetPrimaryIndexSafe().GetVersionedIndex().GetSchemaVerified(chunkWithPortionData.GetMinSnapshotDeprecated())->GetVersion(), + std::move(chunkWithPortionData)); + if (package.size() == 100) { + std::vector local; + local.swap(package); + tasks.emplace_back(std::make_shared(std::make_shared(std::move(local)))); + } + } + + if (package.size() > 0) { + tasks.emplace_back(std::make_shared(std::make_shared(std::move(package)))); + } + return tasks; +} + +} // namespace NKikimr::NOlap::NRestorePortionsFromChunks diff --git a/ydb/core/tx/columnshard/normalizer/portion/restore_portion_from_chunks.h b/ydb/core/tx/columnshard/normalizer/portion/restore_portion_from_chunks.h new file mode 100644 index 000000000000..110b0e35a7c4 --- /dev/null +++ b/ydb/core/tx/columnshard/normalizer/portion/restore_portion_from_chunks.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +namespace NKikimr::NColumnShard { +class TTablesManager; +} + +namespace NKikimr::NOlap::NRestorePortionsFromChunks { + +class TNormalizer: public TNormalizationController::INormalizerComponent { +private: + using TBase = TNormalizationController::INormalizerComponent; + +public: + static TString GetClassNameStatic() { + return ::ToString(ENormalizerSequentialId::RestorePortionFromChunks); + } + + virtual std::optional DoGetEnumSequentialId() const override { + return ENormalizerSequentialId::RestorePortionFromChunks; + } + + virtual TString GetClassName() const override { + return GetClassNameStatic(); + } + + class TNormalizerResult; + + static inline INormalizerComponent::TFactory::TRegistrator Registrator = + INormalizerComponent::TFactory::TRegistrator(GetClassNameStatic()); + +public: + TNormalizer(const TNormalizationController::TInitContext& info) + : TBase(info) + , DsGroupSelector(info.GetStorageInfo()) { + } + + virtual TConclusion> DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; + +private: + NColumnShard::TBlobGroupSelector DsGroupSelector; +}; +} // namespace NKikimr::NOlap::NRestorePortionsFromChunks diff --git a/ydb/core/tx/columnshard/normalizer/portion/restore_v1_chunks.cpp b/ydb/core/tx/columnshard/normalizer/portion/restore_v1_chunks.cpp new file mode 100644 index 000000000000..b7f33ea7f7e4 --- /dev/null +++ b/ydb/core/tx/columnshard/normalizer/portion/restore_v1_chunks.cpp @@ -0,0 +1,254 @@ +#include "normalizer.h" +#include "restore_v1_chunks.h" + +#include +#include +#include +#include + +namespace NKikimr::NOlap::NRestoreV1Chunks { + +class TPatchItemAddV1 { +private: + TPortionLoadContext PortionInfo; + std::map ChunksInfo; + THashMap IndexByBlob; + std::vector BlobIds; + +public: + const ui32& GetIndexByBlob(const TUnifiedBlobId& blobId) const { + auto it = IndexByBlob.find(blobId); + AFL_VERIFY(it != IndexByBlob.end()); + return it->second; + } + const std::vector& GetBlobIds() const { + return BlobIds; + } + + const TPortionLoadContext& GetPortionInfo() const { + return PortionInfo; + } + + const std::map& GetChunksInfo() const { + return ChunksInfo; + } + + TPatchItemAddV1(const TPortionLoadContext& portionInfo, std::map&& chunksInfo) + : PortionInfo(portionInfo) + , ChunksInfo(std::move(chunksInfo)) { + for (auto&& i : ChunksInfo) { + auto it = IndexByBlob.find(i.second.GetBlobRange().GetBlobId()); + if (it == IndexByBlob.end()) { + IndexByBlob.emplace(i.second.GetBlobRange().GetBlobId(), IndexByBlob.size()); + BlobIds.emplace_back(i.second.GetBlobRange().GetBlobId()); + } + } + } +}; + +class TChangesAddV1: public INormalizerChanges { +private: + std::vector Patches; + +public: + TChangesAddV1(std::vector&& patches) + : Patches(std::move(patches)) { + } + virtual bool ApplyOnExecute(NTabletFlatExecutor::TTransactionContext& txc, const TNormalizationController&) const override { + using namespace NColumnShard; + NIceDb::TNiceDb db(txc.DB); + using IndexPortions = NColumnShard::Schema::IndexPortions; + using IndexColumnsV1 = NColumnShard::Schema::IndexColumnsV1; + for (auto&& i : Patches) { + auto metaProto = i.GetPortionInfo().GetMetaProto(); + metaProto.ClearBlobIds(); + AFL_VERIFY(!metaProto.GetBlobIds().size()); + for (auto&& b : i.GetBlobIds()) { + *metaProto.AddBlobIds() = b.GetLogoBlobId().AsBinaryString(); + } + ui32 idx = 0; + for (auto&& b : metaProto.GetBlobIds()) { + auto logo = TLogoBlobID::FromBinary(b); + AFL_VERIFY(i.GetBlobIds()[idx++].GetLogoBlobId() == logo); + } + db.Table() + .Key(i.GetPortionInfo().GetPathId(), i.GetPortionInfo().GetPortionId()) + .Update(NIceDb::TUpdate(metaProto.SerializeAsString())); + for (auto&& [_, c] : i.GetChunksInfo()) { + db.Table() + .Key(c.GetPathId(), c.GetPortionId(), c.GetAddress().GetColumnId(), c.GetAddress().GetChunkIdx()) + .Update(NIceDb::TUpdate(c.GetMetaProto().SerializeAsString()), + NIceDb::TUpdate(i.GetIndexByBlob(c.GetBlobRange().GetBlobId())), + NIceDb::TUpdate(c.GetBlobRange().GetOffset()), + NIceDb::TUpdate(c.GetBlobRange().GetSize())); + } + } + + return true; + } + + virtual ui64 GetSize() const override { + return Patches.size(); + } +}; + +class TPatchItemRemoveV1 { +private: + TColumnChunkLoadContextV1 ChunkInfo; + +public: + const TColumnChunkLoadContextV1& GetChunkInfo() const { + return ChunkInfo; + } + + TPatchItemRemoveV1(const TColumnChunkLoadContextV1& chunkInfo) + : ChunkInfo(chunkInfo) { + } +}; + +class TChangesRemoveV1: public INormalizerChanges { +private: + std::vector Patches; + +public: + TChangesRemoveV1(std::vector&& patches) + : Patches(std::move(patches)) { + } + virtual bool ApplyOnExecute(NTabletFlatExecutor::TTransactionContext& txc, const TNormalizationController&) const override { + using namespace NColumnShard; + NIceDb::TNiceDb db(txc.DB); + using IndexColumnsV1 = NColumnShard::Schema::IndexColumnsV1; + for (auto&& i : Patches) { + db.Table() + .Key(i.GetChunkInfo().GetPathId(), i.GetChunkInfo().GetPortionId(), i.GetChunkInfo().GetAddress().GetEntityId(), + i.GetChunkInfo().GetAddress().GetChunkIdx()) + .Delete(); + } + + return true; + } + + virtual ui64 GetSize() const override { + return Patches.size(); + } +}; + +TConclusion> TNormalizer::DoInit( + const TNormalizationController& /*controller*/, NTabletFlatExecutor::TTransactionContext& txc) { + using namespace NColumnShard; + NIceDb::TNiceDb db(txc.DB); + + bool ready = true; + ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); + ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); + ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); + if (!ready) { + return TConclusionStatus::Fail("Not ready"); + } + THashMap portions0; + THashSet existPortions0; + THashMap> columns0; + THashMap columns1Remove; + + { + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return TConclusionStatus::Fail("Not ready"); + } + + while (!rowset.EndOfSet()) { + TPortionLoadContext portion(rowset); + existPortions0.emplace(portion.GetPortionId()); + if (!portion.GetMetaProto().BlobIdsSize()) { + AFL_VERIFY(portions0.emplace(portion.GetPortionId(), portion).second); + } + + if (!rowset.Next()) { + return TConclusionStatus::Fail("Not ready"); + } + } + } + + { + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return TConclusionStatus::Fail("Not ready"); + } + + while (!rowset.EndOfSet()) { + TColumnChunkLoadContext chunk(rowset, &DsGroupSelector); + if (portions0.contains(chunk.GetPortionId())) { + AFL_VERIFY(columns0[chunk.GetPortionId()].emplace(chunk.GetFullChunkAddress(), chunk).second); + } + + if (!rowset.Next()) { + return TConclusionStatus::Fail("Not ready"); + } + } + } + + { + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return TConclusionStatus::Fail("Not ready"); + } + + while (!rowset.EndOfSet()) { + TColumnChunkLoadContextV1 chunk(rowset); + //AFL_VERIFY(!portions0.contains(chunk.GetPortionId())); + if (!existPortions0.contains(chunk.GetPortionId())) { + AFL_VERIFY(columns1Remove.emplace(chunk.GetFullChunkAddress(), chunk).second); + } + + if (!rowset.Next()) { + return TConclusionStatus::Fail("Not ready"); + } + } + } + + std::vector tasks; + if (columns1Remove.empty() && portions0.empty()) { + return tasks; + } + + AFL_VERIFY(AppDataVerified().ColumnShardConfig.GetColumnChunksV0Usage()); + AFL_VERIFY(AppDataVerified().ColumnShardConfig.GetColumnChunksV1Usage()); + { + std::vector package; + for (auto&& [portionId, chunkInfo] : columns1Remove) { + package.emplace_back(chunkInfo); + if (package.size() == 100) { + std::vector local; + local.swap(package); + tasks.emplace_back(std::make_shared(std::make_shared(std::move(local)))); + } + } + + if (package.size() > 0) { + tasks.emplace_back(std::make_shared(std::make_shared(std::move(package)))); + } + } + + { + std::vector package; + for (auto&& [portionId, portionInfo] : portions0) { + auto it = columns0.find(portionId); + AFL_VERIFY(it != columns0.end()); + package.emplace_back(portionInfo, std::move(it->second)); + columns0.erase(it); + if (package.size() == 100) { + std::vector local; + local.swap(package); + tasks.emplace_back(std::make_shared(std::make_shared(std::move(local)))); + } + } + + if (package.size() > 0) { + tasks.emplace_back(std::make_shared(std::make_shared(std::move(package)))); + } + } + + return tasks; +} + +} // namespace NKikimr::NOlap::NRestoreV1Chunks diff --git a/ydb/core/tx/columnshard/normalizer/portion/restore_v1_chunks.h b/ydb/core/tx/columnshard/normalizer/portion/restore_v1_chunks.h new file mode 100644 index 000000000000..eecfa5602200 --- /dev/null +++ b/ydb/core/tx/columnshard/normalizer/portion/restore_v1_chunks.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +namespace NKikimr::NColumnShard { +class TTablesManager; +} + +namespace NKikimr::NOlap::NRestoreV1Chunks { + +class TNormalizer: public TNormalizationController::INormalizerComponent { +private: + using TBase = TNormalizationController::INormalizerComponent; + +public: + static TString GetClassNameStatic() { + return ::ToString(ENormalizerSequentialId::RestoreV1Chunks_V2); + } + + virtual std::optional DoGetEnumSequentialId() const override { + return ENormalizerSequentialId::RestoreV1Chunks_V2; + } + + virtual TString GetClassName() const override { + return GetClassNameStatic(); + } + + class TNormalizerResult; + + static inline INormalizerComponent::TFactory::TRegistrator Registrator = + INormalizerComponent::TFactory::TRegistrator(GetClassNameStatic()); + +public: + TNormalizer(const TNormalizationController::TInitContext& info) + : TBase(info) + , DsGroupSelector(info.GetStorageInfo()) { + } + + virtual TConclusion> DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; + +private: + NColumnShard::TBlobGroupSelector DsGroupSelector; +}; +} // namespace NKikimr::NOlap::NRestoreV1Chunks diff --git a/ydb/core/tx/columnshard/normalizer/portion/restore_v2_chunks.cpp b/ydb/core/tx/columnshard/normalizer/portion/restore_v2_chunks.cpp new file mode 100644 index 000000000000..434dcceefba0 --- /dev/null +++ b/ydb/core/tx/columnshard/normalizer/portion/restore_v2_chunks.cpp @@ -0,0 +1,194 @@ +#include "normalizer.h" +#include "restore_v2_chunks.h" + +#include +#include +#include +#include + +namespace NKikimr::NOlap::NRestoreV2Chunks { + +class TV2BuildTask { +private: + TPortionAddress PortionAddress; + std::vector Chunks; + +public: + const TPortionAddress& GetPortionAddress() const { + return PortionAddress; + } + + ui64 GetPathId() const { + return PortionAddress.GetPathId(); + } + + ui64 GetPortionId() const { + return PortionAddress.GetPortionId(); + } + + void AddChunk(const TColumnChunkLoadContextV1& chunk) { + Chunks.emplace_back(chunk); + } + + NKikimrTxColumnShard::TIndexPortionAccessor BuildProto() const { + const auto pred = [](const TColumnChunkLoadContextV1& l, const TColumnChunkLoadContextV1& r) { + return l.GetAddress() < r.GetAddress(); + }; + auto chunks = Chunks; + std::sort(chunks.begin(), chunks.end(), pred); + NKikimrTxColumnShard::TIndexPortionAccessor result; + for (auto&& c : chunks) { + *result.AddChunks() = c.SerializeToDBProto(); + } + return result; + } + + TV2BuildTask(const TPortionAddress& address) + : PortionAddress(address) { + } +}; + +class TChangesAddV2: public INormalizerChanges { +private: + std::vector Patches; + +public: + TChangesAddV2(std::vector&& patches) + : Patches(std::move(patches)) { + } + virtual bool ApplyOnExecute(NTabletFlatExecutor::TTransactionContext& txc, const TNormalizationController&) const override { + using namespace NColumnShard; + NIceDb::TNiceDb db(txc.DB); + using IndexColumnsV2 = NColumnShard::Schema::IndexColumnsV2; + for (auto&& i : Patches) { + auto metaProto = i.BuildProto(); + db.Table() + .Key(i.GetPathId(), i.GetPortionId()) + .Update(NIceDb::TUpdate(metaProto.SerializeAsString())); + } + + return true; + } + + virtual ui64 GetSize() const override { + return Patches.size(); + } +}; + +class TPatchItemRemoveV1 { +private: + TColumnChunkLoadContextV1 ChunkInfo; + +public: + const TColumnChunkLoadContextV1& GetChunkInfo() const { + return ChunkInfo; + } + + TPatchItemRemoveV1(const TColumnChunkLoadContextV1& chunkInfo) + : ChunkInfo(chunkInfo) { + } +}; + +class TChangesRemoveV1: public INormalizerChanges { +private: + std::vector Patches; + +public: + TChangesRemoveV1(std::vector&& patches) + : Patches(std::move(patches)) { + } + virtual bool ApplyOnExecute(NTabletFlatExecutor::TTransactionContext& txc, const TNormalizationController&) const override { + using namespace NColumnShard; + NIceDb::TNiceDb db(txc.DB); + using IndexColumnsV1 = NColumnShard::Schema::IndexColumnsV1; + for (auto&& i : Patches) { + db.Table() + .Key(i.GetChunkInfo().GetPathId(), i.GetChunkInfo().GetPortionId(), i.GetChunkInfo().GetAddress().GetEntityId(), + i.GetChunkInfo().GetAddress().GetChunkIdx()) + .Delete(); + } + + return true; + } + + virtual ui64 GetSize() const override { + return Patches.size(); + } +}; + +TConclusion> TNormalizer::DoInit( + const TNormalizationController& /*controller*/, NTabletFlatExecutor::TTransactionContext& txc) { + using namespace NColumnShard; + NIceDb::TNiceDb db(txc.DB); + + bool ready = true; + ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); + ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); + if (!ready) { + return TConclusionStatus::Fail("Not ready"); + } + THashSet readyPortions; + THashMap buildPortions; + { + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return TConclusionStatus::Fail("Not ready"); + } + + while (!rowset.EndOfSet()) { + AFL_VERIFY(readyPortions.emplace(TPortionAddress(rowset.template GetValue(), + rowset.template GetValue())).second); + if (!rowset.Next()) { + return TConclusionStatus::Fail("Not ready"); + } + } + } + + { + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return TConclusionStatus::Fail("Not ready"); + } + + while (!rowset.EndOfSet()) { + TColumnChunkLoadContextV1 chunk(rowset); + if (!readyPortions.contains(chunk.GetPortionAddress())) { + auto it = buildPortions.find(chunk.GetPortionAddress()); + if (it == buildPortions.end()) { + it = buildPortions.emplace(chunk.GetPortionAddress(), TV2BuildTask(chunk.GetPortionAddress())).first; + } + it->second.AddChunk(chunk); + } + + if (!rowset.Next()) { + return TConclusionStatus::Fail("Not ready"); + } + } + } + + std::vector tasks; + if (buildPortions.empty()) { + return tasks; + } + AFL_VERIFY(AppDataVerified().ColumnShardConfig.GetColumnChunksV1Usage()); + + { + std::vector package; + for (auto&& [portionAddress, portionInfos] : buildPortions) { + package.emplace_back(std::move(portionInfos)); + if (package.size() == 100) { + std::vector local; + local.swap(package); + tasks.emplace_back(std::make_shared(std::make_shared(std::move(local)))); + } + } + + if (package.size() > 0) { + tasks.emplace_back(std::make_shared(std::make_shared(std::move(package)))); + } + } + + return tasks; +} + +} // namespace NKikimr::NOlap::NRestoreV1Chunks diff --git a/ydb/core/tx/columnshard/normalizer/portion/restore_v2_chunks.h b/ydb/core/tx/columnshard/normalizer/portion/restore_v2_chunks.h new file mode 100644 index 000000000000..c872da0af365 --- /dev/null +++ b/ydb/core/tx/columnshard/normalizer/portion/restore_v2_chunks.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +namespace NKikimr::NColumnShard { +class TTablesManager; +} + +namespace NKikimr::NOlap::NRestoreV2Chunks { + +class TNormalizer: public TNormalizationController::INormalizerComponent { +private: + using TBase = TNormalizationController::INormalizerComponent; + +public: + static TString GetClassNameStatic() { + return ::ToString(ENormalizerSequentialId::RestoreV2Chunks); + } + + virtual std::optional DoGetEnumSequentialId() const override { + return ENormalizerSequentialId::RestoreV2Chunks; + } + + virtual TString GetClassName() const override { + return GetClassNameStatic(); + } + + class TNormalizerResult; + + static inline INormalizerComponent::TFactory::TRegistrator Registrator = + INormalizerComponent::TFactory::TRegistrator(GetClassNameStatic()); + +public: + TNormalizer(const TNormalizationController::TInitContext& info) + : TBase(info) + , DsGroupSelector(info.GetStorageInfo()) { + } + + virtual TConclusion> DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; + +private: + NColumnShard::TBlobGroupSelector DsGroupSelector; +}; +} // namespace NKikimr::NOlap::NRestoreV2Chunks diff --git a/ydb/core/tx/columnshard/normalizer/portion/snapshot_from_chunks.cpp b/ydb/core/tx/columnshard/normalizer/portion/snapshot_from_chunks.cpp new file mode 100644 index 000000000000..7b5a9a21b0bf --- /dev/null +++ b/ydb/core/tx/columnshard/normalizer/portion/snapshot_from_chunks.cpp @@ -0,0 +1,137 @@ +#include "snapshot_from_chunks.h" +#include "normalizer.h" + +#include +#include +#include +#include + +namespace NKikimr::NOlap::NSyncMinSnapshotFromChunks { + +class TPatchItem { +private: + TPortionLoadContext PortionInfo; + YDB_READONLY(NOlap::TSnapshot, Snapshot, NOlap::TSnapshot::Zero()); + +public: + const TPortionLoadContext& GetPortionInfo() const { + return PortionInfo; + } + + TPatchItem(TPortionLoadContext&& portion, const NOlap::TSnapshot& snapshot) + : PortionInfo(std::move(portion)) + , Snapshot(snapshot) { + } +}; + +class TChanges: public INormalizerChanges { +private: + std::vector Patches; + +public: + TChanges(std::vector&& patches) + : Patches(std::move(patches)) { + } + virtual bool ApplyOnExecute(NTabletFlatExecutor::TTransactionContext& txc, const TNormalizationController&) const override { + using namespace NColumnShard; + NIceDb::TNiceDb db(txc.DB); + for (auto&& i : Patches) { + db.Table() + .Key(i.GetPortionInfo().GetPathId(), i.GetPortionInfo().GetPortionId()) + .Update(NIceDb::TUpdate(i.GetSnapshot().GetPlanStep()), + NIceDb::TUpdate(i.GetSnapshot().GetTxId()) + ); + } + + return true; + } + + virtual ui64 GetSize() const override { + return Patches.size(); + } + +}; + +TConclusion> TNormalizer::DoInit( + const TNormalizationController& /*controller*/, NTabletFlatExecutor::TTransactionContext& txc) { + using namespace NColumnShard; + NIceDb::TNiceDb db(txc.DB); + + bool ready = true; + ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); + ready = ready & Schema::Precharge(db, txc.DB.GetScheme()); + if (!ready) { + return TConclusionStatus::Fail("Not ready"); + } + + THashMap dbPortions; + THashMap initSnapshot; + + { + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return TConclusionStatus::Fail("Not ready"); + } + + while (!rowset.EndOfSet()) { + TPortionLoadContext portion(rowset); + if (!portion.GetDeprecatedMinSnapshot()) { + AFL_VERIFY(dbPortions.emplace(portion.GetPortionId(), portion).second); + } + + if (!rowset.Next()) { + return TConclusionStatus::Fail("Not ready"); + } + } + } + + { + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return TConclusionStatus::Fail("Not ready"); + } + + while (!rowset.EndOfSet()) { + TColumnChunkLoadContext chunk(rowset, &DsGroupSelector); + const ui64 portionId = chunk.GetPortionId(); + if (dbPortions.contains(portionId)) { + auto it = initSnapshot.find(portionId); + if (it == initSnapshot.end()) { + initSnapshot.emplace(portionId, chunk.GetMinSnapshotDeprecated()); + } else { + AFL_VERIFY(it->second == chunk.GetMinSnapshotDeprecated()); + } + } + + if (!rowset.Next()) { + return TConclusionStatus::Fail("Not ready"); + } + } + } + AFL_VERIFY(dbPortions.size() == initSnapshot.size())("portions", dbPortions.size())("records", initSnapshot.size()); + + std::vector tasks; + if (dbPortions.empty()) { + return tasks; + } + + std::vector package; + + for (auto&& [portionId, portion] : dbPortions) { + auto it = initSnapshot.find(portionId); + AFL_VERIFY(it != initSnapshot.end()); + package.emplace_back(std::move(portion), it->second); + if (package.size() == 100) { + std::vector local; + local.swap(package); + tasks.emplace_back(std::make_shared(std::make_shared(std::move(local)))); + } + } + + if (package.size() > 0) { + tasks.emplace_back(std::make_shared(std::make_shared(std::move(package)))); + } + return tasks; +} + +} // namespace NKikimr::NOlap::NChunksActualization diff --git a/ydb/core/tx/columnshard/normalizer/portion/snapshot_from_chunks.h b/ydb/core/tx/columnshard/normalizer/portion/snapshot_from_chunks.h new file mode 100644 index 000000000000..5272bd1dadc8 --- /dev/null +++ b/ydb/core/tx/columnshard/normalizer/portion/snapshot_from_chunks.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +namespace NKikimr::NColumnShard { +class TTablesManager; +} + +namespace NKikimr::NOlap::NSyncMinSnapshotFromChunks { + +class TNormalizer: public TNormalizationController::INormalizerComponent { +private: + using TBase = TNormalizationController::INormalizerComponent; + +public: + static TString GetClassNameStatic() { + return ::ToString(ENormalizerSequentialId::SyncMinSnapshotFromChunks); + } + + virtual std::optional DoGetEnumSequentialId() const override { + return ENormalizerSequentialId::SyncMinSnapshotFromChunks; + } + + virtual TString GetClassName() const override { + return GetClassNameStatic(); + } + + class TNormalizerResult; + + static inline INormalizerComponent::TFactory::TRegistrator Registrator = + INormalizerComponent::TFactory::TRegistrator(GetClassNameStatic()); + +public: + TNormalizer(const TNormalizationController::TInitContext& info) + : TBase(info) + , DsGroupSelector(info.GetStorageInfo()) { + } + + virtual TConclusion> DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; + +private: + NColumnShard::TBlobGroupSelector DsGroupSelector; +}; +} // namespace NKikimr::NOlap::NSyncMinSnapshotFromChunks diff --git a/ydb/core/tx/columnshard/normalizer/portion/special_cleaner.h b/ydb/core/tx/columnshard/normalizer/portion/special_cleaner.h index bac01c3f3d27..4af64a29fccc 100644 --- a/ydb/core/tx/columnshard/normalizer/portion/special_cleaner.h +++ b/ydb/core/tx/columnshard/normalizer/portion/special_cleaner.h @@ -6,6 +6,9 @@ namespace NKikimr::NOlap::NNormalizer::NSpecialColumns { class TDeleteTrashImpl: public TNormalizationController::INormalizerComponent { +private: + using TBase = TNormalizationController::INormalizerComponent; + public: struct TKey { ui32 Index; @@ -20,13 +23,13 @@ class TDeleteTrashImpl: public TNormalizationController::INormalizerComponent { using TKeyBatch = std::vector; private: - std::optional> KeysToDelete(NTabletFlatExecutor::TTransactionContext& txc, const size_t maxBatchSize); virtual std::set GetColumnIdsToDelete() const = 0; public: - TDeleteTrashImpl(const TNormalizationController::TInitContext&) { + TDeleteTrashImpl(const TNormalizationController::TInitContext& context) + : TBase(context) { } virtual TConclusion> DoInit( @@ -36,6 +39,7 @@ class TDeleteTrashImpl: public TNormalizationController::INormalizerComponent { class TRemoveDeleteFlag: public TDeleteTrashImpl { private: using TBase = TDeleteTrashImpl; + public: static TString GetClassNameStatic() { return "RemoveDeleteFlag"; @@ -86,9 +90,8 @@ class TRemoveWriteId: public TDeleteTrashImpl { public: TRemoveWriteId(const TNormalizationController::TInitContext& context) - : TBase(context) - { + : TBase(context) { } }; -} // namespace NKikimr::NOlap +} // namespace NKikimr::NOlap::NNormalizer::NSpecialColumns diff --git a/ydb/core/tx/columnshard/normalizer/portion/ya.make b/ydb/core/tx/columnshard/normalizer/portion/ya.make index e53cf5497b8c..e7eaf752badd 100644 --- a/ydb/core/tx/columnshard/normalizer/portion/ya.make +++ b/ydb/core/tx/columnshard/normalizer/portion/ya.make @@ -8,6 +8,12 @@ SRCS( GLOBAL clean_empty.cpp GLOBAL broken_blobs.cpp GLOBAL special_cleaner.cpp + GLOBAL chunks_actualization.cpp + GLOBAL restore_portion_from_chunks.cpp + GLOBAL restore_v1_chunks.cpp + GLOBAL restore_v2_chunks.cpp + GLOBAL snapshot_from_chunks.cpp + GLOBAL leaked_blobs.cpp ) PEERDIR( diff --git a/ydb/core/tx/columnshard/normalizer/schema_version/version.cpp b/ydb/core/tx/columnshard/normalizer/schema_version/version.cpp new file mode 100644 index 000000000000..0632e9d395f1 --- /dev/null +++ b/ydb/core/tx/columnshard/normalizer/schema_version/version.cpp @@ -0,0 +1,226 @@ +#include "version.h" + +namespace NKikimr::NOlap { + +class TSchemaVersionNormalizer::TNormalizerResult: public INormalizerChanges { +private: + class TKey { + private: + std::optional Version; + + public: + ui64 Step; + ui64 TxId; + ui32 Id; + + public: + TKey() = default; + + ui64 GetVersion() const { + AFL_VERIFY(Version); + return *Version; + } + + TKey(ui32 id, ui64 step, ui64 txId, const std::optional version) + : Version(version) + , Step(step) + , TxId(txId) + , Id(id) { + } + + bool operator<(const TKey& item) const { + if (Id == item.Id) { + const bool result = std::tie(Step, TxId) < std::tie(item.Step, item.TxId); + if (Version && item.Version) { + const bool resultVersions = Version < item.Version; + AFL_VERIFY(result == resultVersions); + } + return result; + } else { + return Id < item.Id; + } + } + + bool operator==(const TKey& item) const { + return std::tie(Id, Step, TxId, Version) == std::tie(item.Id, item.Step, item.TxId, item.Version); + } + }; + + class TTableKey { + public: + ui64 PathId; + ui64 Step; + ui64 TxId; + + public: + TTableKey(ui64 pathId, ui64 step, ui64 txId) + : PathId(pathId) + , Step(step) + , TxId(txId) { + } + }; + + std::vector VersionsToRemove; + std::vector TableVersionsToRemove; + +public: + TNormalizerResult(std::vector&& versions, std::vector&& tableVersions) + : VersionsToRemove(versions) + , TableVersionsToRemove(tableVersions) { + } + + bool ApplyOnExecute(NTabletFlatExecutor::TTransactionContext& txc, const TNormalizationController& /* normController */) const override { + using namespace NColumnShard; + NIceDb::TNiceDb db(txc.DB); + for (auto& key : VersionsToRemove) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "Removing schema version in TSchemaVersionNormalizer")("version", key.GetVersion()); + db.Table().Key(key.Id, key.Step, key.TxId).Delete(); + } + for (auto& key : TableVersionsToRemove) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "Removing table version in TSchemaVersionNormalizer")("pathId", key.PathId)( + "plan_step", key.Step)("tx_id", key.TxId); + db.Table().Key(key.PathId, key.Step, key.TxId).Delete(); + } + return true; + } + + ui64 GetSize() const override { + return VersionsToRemove.size(); + } + + static std::optional> Init(NTabletFlatExecutor::TTransactionContext& txc) { + using namespace NColumnShard; + THashSet usedSchemaVersions; + NIceDb::TNiceDb db(txc.DB); + { + auto rowset = db.Table().Select(); + if (rowset.IsReady()) { + while (!rowset.EndOfSet()) { + usedSchemaVersions.insert(rowset.GetValue()); + if (!rowset.Next()) { + return std::nullopt; + } + } + } else { + return std::nullopt; + } + } + { + auto rowset = db.Table().Select(); + if (rowset.IsReady()) { + while (!rowset.EndOfSet()) { + if (rowset.HaveValue()) { + usedSchemaVersions.insert(rowset.GetValue()); + if (!rowset.Next()) { + return std::nullopt; + } + } + } + } else { + return std::nullopt; + } + } + + std::map schemaIdUsability; + std::vector unusedTableSchemaIds; + std::vector changes; + + { + THashMap maxByPresetId; + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return std::nullopt; + } + while (!rowset.EndOfSet()) { + const ui32 id = rowset.GetValue(); + NKikimrTxColumnShard::TSchemaPresetVersionInfo info; + Y_ABORT_UNLESS(info.ParseFromString(rowset.GetValue())); + AFL_VERIFY(info.HasSchema()); + ui64 version = info.GetSchema().GetVersion(); + TKey presetVersionKey(id, rowset.GetValue(), + rowset.GetValue(), version); + auto it = maxByPresetId.find(id); + if (it == maxByPresetId.end()) { + it = maxByPresetId.emplace(id, presetVersionKey).first; + } else if (it->second < presetVersionKey) { + it->second = presetVersionKey; + } + AFL_VERIFY(schemaIdUsability.emplace(presetVersionKey, usedSchemaVersions.contains(version)).second); + + if (!rowset.Next()) { + return std::nullopt; + } + } + for (auto&& i : maxByPresetId) { + auto it = schemaIdUsability.find(i.second); + AFL_VERIFY(it != schemaIdUsability.end()); + AFL_VERIFY(it->first == i.second); + it->second = true; + } + { + auto rowset = db.Table().Select(); + if (!rowset.IsReady()) { + return std::nullopt; + } + + while (!rowset.EndOfSet()) { + const ui64 pathId = rowset.GetValue(); + + NKikimrTxColumnShard::TTableVersionInfo versionInfo; + Y_ABORT_UNLESS(versionInfo.ParseFromString(rowset.GetValue())); + auto it = schemaIdUsability.find(TKey(versionInfo.GetSchemaPresetId(), + rowset.GetValue(), rowset.GetValue(), {})); + AFL_VERIFY(it != schemaIdUsability.end()); + if (!it->second) { + unusedTableSchemaIds.emplace_back(pathId, rowset.GetValue(), + rowset.GetValue()); + } + + if (!rowset.Next()) { + return std::nullopt; + } + } + } + + std::vector tableVersionToRemove; + std::vector presetVersionsToRemove; + auto addNormalizationTask = [&](const ui32 limit) { + if (presetVersionsToRemove.size() + tableVersionToRemove.size() > limit) { + changes.emplace_back( + std::make_shared(std::move(presetVersionsToRemove), std::move(tableVersionToRemove))); + presetVersionsToRemove = std::vector(); + tableVersionToRemove = std::vector(); + } + }; + for (const auto& id : schemaIdUsability) { + if (!id.second) { + presetVersionsToRemove.push_back(id.first); + addNormalizationTask(10000); + } + } + + for (const auto& id : unusedTableSchemaIds) { + tableVersionToRemove.push_back(id); + addNormalizationTask(10000); + } + + addNormalizationTask(0); + return changes; + } + } +}; + +TConclusion> TSchemaVersionNormalizer::DoInit( + const TNormalizationController&, NTabletFlatExecutor::TTransactionContext& txc) { + auto changes = TNormalizerResult::Init(txc); + if (!changes) { + return TConclusionStatus::Fail("Not ready"); + } + std::vector tasks; + for (auto&& c : *changes) { + tasks.emplace_back(std::make_shared(c)); + } + return tasks; +} + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/normalizer/schema_version/version.h b/ydb/core/tx/columnshard/normalizer/schema_version/version.h new file mode 100644 index 000000000000..4d526734fa81 --- /dev/null +++ b/ydb/core/tx/columnshard/normalizer/schema_version/version.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +namespace NKikimr::NColumnShard { +class TTablesManager; +} + +namespace NKikimr::NOlap { + +class TSchemaVersionNormalizer: public TNormalizationController::INormalizerComponent { +private: + using TBase = TNormalizationController::INormalizerComponent; + +public: + static TString GetClassNameStatic() { + return "SchemaVersionCleaner"; + } + +private: + static inline TFactory::TRegistrator Registrator = + TFactory::TRegistrator(GetClassNameStatic()); + +public: + class TNormalizerResult; + class TTask; + +public: + virtual std::optional DoGetEnumSequentialId() const override { + return std::nullopt; + } + + virtual TString GetClassName() const override { + return GetClassNameStatic(); + } + + TSchemaVersionNormalizer(const TNormalizationController::TInitContext& context) + : TBase(context) { + } + + virtual TConclusion> DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; +}; + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/normalizer/schema_version/ya.make b/ydb/core/tx/columnshard/normalizer/schema_version/ya.make new file mode 100644 index 000000000000..d0412bcdd927 --- /dev/null +++ b/ydb/core/tx/columnshard/normalizer/schema_version/ya.make @@ -0,0 +1,11 @@ +LIBRARY() + +SRCS( + GLOBAL version.cpp +) + +PEERDIR( + ydb/core/tx/columnshard/normalizer/abstract +) + +END() diff --git a/ydb/core/tx/columnshard/normalizer/tables/normalizer.h b/ydb/core/tx/columnshard/normalizer/tables/normalizer.h index cfab565e5a67..21826e7605c0 100644 --- a/ydb/core/tx/columnshard/normalizer/tables/normalizer.h +++ b/ydb/core/tx/columnshard/normalizer/tables/normalizer.h @@ -1,21 +1,24 @@ #pragma once -#include #include - +#include namespace NKikimr::NOlap { class TRemovedTablesNormalizer: public TNormalizationController::INormalizerComponent { +private: + using TBase = TNormalizationController::INormalizerComponent; + public: static TString GetClassNameStatic() { return ::ToString(ENormalizerSequentialId::TablesCleaner); } private: - static inline INormalizerComponent::TFactory::TRegistrator Registrator = INormalizerComponent::TFactory::TRegistrator( - GetClassNameStatic()); + static inline INormalizerComponent::TFactory::TRegistrator Registrator = + INormalizerComponent::TFactory::TRegistrator(GetClassNameStatic()); class TNormalizerResult; + public: virtual std::optional DoGetEnumSequentialId() const override { return ENormalizerSequentialId::TablesCleaner; @@ -25,10 +28,12 @@ class TRemovedTablesNormalizer: public TNormalizationController::INormalizerComp return GetClassNameStatic(); } - TRemovedTablesNormalizer(const TNormalizationController::TInitContext&) - {} + TRemovedTablesNormalizer(const TNormalizationController::TInitContext& context) + : TBase(context) { + } - virtual TConclusion> DoInit(const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; + virtual TConclusion> DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; }; -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/normalizer/tablet/broken_txs.h b/ydb/core/tx/columnshard/normalizer/tablet/broken_txs.h index 1ff68530bf35..45befd504b61 100644 --- a/ydb/core/tx/columnshard/normalizer/tablet/broken_txs.h +++ b/ydb/core/tx/columnshard/normalizer/tablet/broken_txs.h @@ -1,24 +1,28 @@ #pragma once -#include #include - +#include namespace NKikimr::NOlap { class TBrokenTxsNormalizer: public TNormalizationController::INormalizerComponent { +private: + using TBase = TNormalizationController::INormalizerComponent; + public: static TString GetClassNameStatic() { return "BrokenTxsNormalizer"; } + private: class TNormalizerResult; - static const inline INormalizerComponent::TFactory::TRegistrator Registrator = + static const inline INormalizerComponent::TFactory::TRegistrator Registrator = INormalizerComponent::TFactory::TRegistrator(GetClassNameStatic()); public: - TBrokenTxsNormalizer(const TNormalizationController::TInitContext&) { + TBrokenTxsNormalizer(const TNormalizationController::TInitContext& context) + : TBase(context) { } virtual std::optional DoGetEnumSequentialId() const override { @@ -29,7 +33,8 @@ class TBrokenTxsNormalizer: public TNormalizationController::INormalizerComponen return GetClassNameStatic(); } - virtual TConclusion> DoInit(const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; + virtual TConclusion> DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; }; -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/normalizer/tablet/gc_counters.h b/ydb/core/tx/columnshard/normalizer/tablet/gc_counters.h index 8787da559489..c48aee45d796 100644 --- a/ydb/core/tx/columnshard/normalizer/tablet/gc_counters.h +++ b/ydb/core/tx/columnshard/normalizer/tablet/gc_counters.h @@ -1,23 +1,28 @@ #pragma once -#include #include - +#include namespace NKikimr::NOlap { class TGCCountersNormalizer: public TNormalizationController::INormalizerComponent { +private: + using TBase = TNormalizationController::INormalizerComponent; + public: static TString GetClassNameStatic() { return "GCCountersNormalizer"; } + private: class TNormalizerResult; - static const inline INormalizerComponent::TFactory::TRegistrator Registrator = + static const inline INormalizerComponent::TFactory::TRegistrator Registrator = INormalizerComponent::TFactory::TRegistrator(GetClassNameStatic()); + public: - TGCCountersNormalizer(const TNormalizationController::TInitContext&) { + TGCCountersNormalizer(const TNormalizationController::TInitContext& context) + : TBase(context) { } virtual std::optional DoGetEnumSequentialId() const override { @@ -28,7 +33,8 @@ class TGCCountersNormalizer: public TNormalizationController::INormalizerCompone return GetClassNameStatic(); } - virtual TConclusion> DoInit(const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; + virtual TConclusion> DoInit( + const TNormalizationController& controller, NTabletFlatExecutor::TTransactionContext& txc) override; }; -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/normalizer/ya.make b/ydb/core/tx/columnshard/normalizer/ya.make index ced78fd812af..f48c3308ac38 100644 --- a/ydb/core/tx/columnshard/normalizer/ya.make +++ b/ydb/core/tx/columnshard/normalizer/ya.make @@ -7,6 +7,7 @@ PEERDIR( ydb/core/tx/columnshard/normalizer/tables ydb/core/tx/columnshard/normalizer/portion ydb/core/tx/columnshard/normalizer/insert_table + ydb/core/tx/columnshard/normalizer/schema_version ) END() diff --git a/ydb/core/tx/columnshard/operations/batch_builder/builder.cpp b/ydb/core/tx/columnshard/operations/batch_builder/builder.cpp index 39aa61a9a008..0a3c6134a1c9 100644 --- a/ydb/core/tx/columnshard/operations/batch_builder/builder.cpp +++ b/ydb/core/tx/columnshard/operations/batch_builder/builder.cpp @@ -16,65 +16,74 @@ void TBuildBatchesTask::ReplyError(const TString& message, const NColumnShard::T TWritingBuffer buffer(writeDataPtr->GetBlobsAction(), { std::make_shared(*writeDataPtr) }); auto result = NColumnShard::TEvPrivate::TEvWriteBlobsResult::Error(NKikimrProto::EReplyStatus::CORRUPTED, std::move(buffer), message, errorClass); - TActorContext::AsActorContext().Send(ParentActorId, result.release()); + TActorContext::AsActorContext().Send(Context.GetTabletActorId(), result.release()); } TConclusionStatus TBuildBatchesTask::DoExecute(const std::shared_ptr& /*taskPtr*/) { + const NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("scope", "TBuildBatchesTask::DoExecute"); + if (!Context.IsActive()) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "abort_external"); + ReplyError("writing aborted", NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass::Internal); + return TConclusionStatus::Fail("writing aborted"); + } TConclusion> batchConclusion = WriteData.GetData()->ExtractBatch(); if (batchConclusion.IsFail()) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "abort_on_extract")("reason", batchConclusion.GetErrorMessage()); ReplyError( "cannot extract incoming batch: " + batchConclusion.GetErrorMessage(), NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass::Internal); return TConclusionStatus::Fail("cannot extract incoming batch: " + batchConclusion.GetErrorMessage()); } - WritingCounters->OnIncomingData(NArrow::GetBatchDataSize(*batchConclusion)); + Context.GetWritingCounters()->OnIncomingData(NArrow::GetBatchDataSize(*batchConclusion)); auto preparedConclusion = - ActualSchema->PrepareForModification(batchConclusion.DetachResult(), WriteData.GetWriteMeta().GetModificationType()); + Context.GetActualSchema()->PrepareForModification(batchConclusion.DetachResult(), WriteData.GetWriteMeta().GetModificationType()); if (preparedConclusion.IsFail()) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "abort_on_prepare")("reason", preparedConclusion.GetErrorMessage()); ReplyError("cannot prepare incoming batch: " + preparedConclusion.GetErrorMessage(), - NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass::Internal); + NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass::Request); return TConclusionStatus::Fail("cannot prepare incoming batch: " + preparedConclusion.GetErrorMessage()); } auto batch = preparedConclusion.DetachResult(); std::shared_ptr merger; switch (WriteData.GetWriteMeta().GetModificationType()) { case NEvWrite::EModificationType::Upsert: { - const std::vector> defaultFields = ActualSchema->GetAbsentFields(batch->schema()); + const std::vector> defaultFields = Context.GetActualSchema()->GetAbsentFields(batch->schema()); if (defaultFields.empty()) { std::shared_ptr task = - std::make_shared(TabletId, ParentActorId, BufferActorId, std::move(WriteData), batch, ActualSchema); + std::make_shared(BufferActorId, std::move(WriteData), batch, Context); NConveyor::TInsertServiceOperator::AsyncTaskToExecute(task); return TConclusionStatus::Success(); } else { - auto insertionConclusion = ActualSchema->CheckColumnsDefault(defaultFields); - auto conclusion = ActualSchema->BuildDefaultBatch(ActualSchema->GetIndexInfo().ArrowSchema()->fields(), 1, true); + auto insertionConclusion = Context.GetActualSchema()->CheckColumnsDefault(defaultFields); + auto conclusion = + Context.GetActualSchema()->BuildDefaultBatch(Context.GetActualSchema()->GetIndexInfo().ArrowSchema(), 1, true); AFL_VERIFY(!conclusion.IsFail())("error", conclusion.GetErrorMessage()); auto batchDefault = conclusion.DetachResult(); NArrow::NMerger::TSortableBatchPosition pos( batchDefault, 0, batchDefault->schema()->field_names(), batchDefault->schema()->field_names(), false); merger = std::make_shared( - batch, ActualSchema, insertionConclusion.IsSuccess() ? "" : insertionConclusion.GetErrorMessage(), pos); + batch, Context.GetActualSchema(), insertionConclusion.IsSuccess() ? "" : insertionConclusion.GetErrorMessage(), pos); break; } } case NEvWrite::EModificationType::Insert: { - merger = std::make_shared(batch, ActualSchema); + merger = std::make_shared(batch, Context.GetActualSchema()); break; } case NEvWrite::EModificationType::Update: { - merger = std::make_shared(batch, ActualSchema, ""); + merger = std::make_shared(batch, Context.GetActualSchema(), ""); break; } case NEvWrite::EModificationType::Replace: case NEvWrite::EModificationType::Delete: { std::shared_ptr task = - std::make_shared(TabletId, ParentActorId, BufferActorId, std::move(WriteData), batch, ActualSchema); + std::make_shared(BufferActorId, std::move(WriteData), batch, Context); NConveyor::TInsertServiceOperator::AsyncTaskToExecute(task); return TConclusionStatus::Success(); } } - std::shared_ptr task = std::make_shared( - TabletId, ParentActorId, BufferActorId, std::move(WriteData), merger, ActualSchema, ActualSnapshot, batch); + std::shared_ptr task = + std::make_shared(BufferActorId, std::move(WriteData), merger, ActualSnapshot, batch, Context); NActors::TActivationContext::AsActorContext().Register(new NDataReader::TActor(task)); return TConclusionStatus::Success(); diff --git a/ydb/core/tx/columnshard/operations/batch_builder/builder.h b/ydb/core/tx/columnshard/operations/batch_builder/builder.h index 654dd4ba8035..c6684a712a86 100644 --- a/ydb/core/tx/columnshard/operations/batch_builder/builder.h +++ b/ydb/core/tx/columnshard/operations/batch_builder/builder.h @@ -3,20 +3,18 @@ #include #include #include +#include #include #include namespace NKikimr::NOlap { -class TBuildBatchesTask: public NConveyor::ITask { +class TBuildBatchesTask: public NConveyor::ITask, public NColumnShard::TMonitoringObjectsCounter { private: NEvWrite::TWriteData WriteData; - const ui64 TabletId; - const NActors::TActorId ParentActorId; const NActors::TActorId BufferActorId; - const std::shared_ptr ActualSchema; const TSnapshot ActualSnapshot; - const std::shared_ptr WritingCounters; + const TWritingContext Context; void ReplyError(const TString& message, const NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass errorClass); protected: @@ -27,17 +25,12 @@ class TBuildBatchesTask: public NConveyor::ITask { return "Write::ConstructBatches"; } - TBuildBatchesTask(const ui64 tabletId, const NActors::TActorId parentActorId, const NActors::TActorId bufferActorId, - NEvWrite::TWriteData&& writeData, const std::shared_ptr& actualSchema, const TSnapshot& actualSnapshot, - const std::shared_ptr& writingCounters) + TBuildBatchesTask( + const NActors::TActorId bufferActorId, NEvWrite::TWriteData&& writeData, const TWritingContext& context) : WriteData(std::move(writeData)) - , TabletId(tabletId) - , ParentActorId(parentActorId) , BufferActorId(bufferActorId) - , ActualSchema(actualSchema) - , ActualSnapshot(actualSnapshot) - , WritingCounters(writingCounters) - { + , ActualSnapshot(context.GetApplyToSnapshot()) + , Context(context) { } }; } // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/operations/batch_builder/merger.cpp b/ydb/core/tx/columnshard/operations/batch_builder/merger.cpp index 823f6ac1cf3d..ceb87eb027e5 100644 --- a/ydb/core/tx/columnshard/operations/batch_builder/merger.cpp +++ b/ydb/core/tx/columnshard/operations/batch_builder/merger.cpp @@ -18,7 +18,7 @@ NKikimr::TConclusionStatus IMerger::Finish() { NKikimr::TConclusionStatus IMerger::AddExistsDataOrdered(const std::shared_ptr& data) { AFL_VERIFY(data); NArrow::NMerger::TRWSortableBatchPosition existsPosition(data, 0, Schema->GetPKColumnNames(), - Schema->GetIndexInfo().GetColumnSTLNames(Schema->GetIndexInfo().GetColumnIds(false)), false); + Schema->GetIndexInfo().GetColumnSTLNames(false), false); bool exsistFinished = !existsPosition.InitPosition(0); while (!IncomingFinished && !exsistFinished) { auto cmpResult = IncomingPosition.Compare(existsPosition); @@ -47,27 +47,26 @@ NKikimr::TConclusionStatus TUpdateMerger::OnEqualKeys(const NArrow::NMerger::TSo auto rGuard = Builder.StartRecord(); AFL_VERIFY(Schema->GetIndexInfo().GetColumnIds(false).size() == exists.GetData().GetColumns().size()) ("index", Schema->GetIndexInfo().GetColumnIds(false).size())("exists", exists.GetData().GetColumns().size()); - for (i32 columnIdx = 0; columnIdx < Schema->GetIndexInfo().ArrowSchema()->num_fields(); ++columnIdx) { - const std::optional& incomingColumnIdx = IncomingColumnRemap[columnIdx]; - if (incomingColumnIdx && HasIncomingDataFlags[*incomingColumnIdx]->GetView(incoming.GetPosition())) { - const ui32 idxChunk = incoming.GetData().GetPositionInChunk(*incomingColumnIdx, incoming.GetPosition()); - rGuard.Add(*incoming.GetData().GetPositionAddress(*incomingColumnIdx).GetArray(), idxChunk); - } else { - const ui32 idxChunk = exists.GetData().GetPositionInChunk(columnIdx, exists.GetPosition()); - rGuard.Add(*exists.GetData().GetPositionAddress(columnIdx).GetArray(), idxChunk); + for (i32 columnIdx = 0; columnIdx < Schema->GetIndexInfo().ArrowSchema().num_fields(); ++columnIdx) { + const std::optional& incomingColumnIdx = IncomingColumnRemap[columnIdx]; + if (incomingColumnIdx && HasIncomingDataFlags[*incomingColumnIdx]->GetView(incoming.GetPosition())) { + const ui32 idxChunk = incoming.GetData().GetPositionInChunk(*incomingColumnIdx, incoming.GetPosition()); + rGuard.Add(*incoming.GetData().GetPositionAddress(*incomingColumnIdx).GetArray(), idxChunk); + } else { + const ui32 idxChunk = exists.GetData().GetPositionInChunk(columnIdx, exists.GetPosition()); + rGuard.Add(*exists.GetData().GetPositionAddress(columnIdx).GetArray(), idxChunk); + } } - } return TConclusionStatus::Success(); } TUpdateMerger::TUpdateMerger(const std::shared_ptr& incoming, const std::shared_ptr& actualSchema, const TString& insertDenyReason, const std::optional& defaultExists /*= {}*/) : TBase(incoming, actualSchema) - , Builder(actualSchema->GetIndexInfo().ArrowSchema()->fields()) + , Builder({ actualSchema->GetIndexInfo().ArrowSchema().begin(), actualSchema->GetIndexInfo().ArrowSchema().end() }) , DefaultExists(defaultExists) - , InsertDenyReason(insertDenyReason) -{ - for (auto&& f : actualSchema->GetIndexInfo().ArrowSchema()->fields()) { + , InsertDenyReason(insertDenyReason) { + for (auto&& f : actualSchema->GetIndexInfo().ArrowSchema()) { auto fIdx = IncomingData->schema()->GetFieldIndex(f->name()); if (fIdx == -1) { IncomingColumnRemap.emplace_back(); @@ -86,5 +85,4 @@ TUpdateMerger::TUpdateMerger(const std::shared_ptr& incoming } } } - } diff --git a/ydb/core/tx/columnshard/operations/batch_builder/restore.cpp b/ydb/core/tx/columnshard/operations/batch_builder/restore.cpp index e13c7fc74eaf..04bd6621960e 100644 --- a/ydb/core/tx/columnshard/operations/batch_builder/restore.cpp +++ b/ydb/core/tx/columnshard/operations/batch_builder/restore.cpp @@ -1,34 +1,38 @@ #include "restore.h" -#include + #include +#include #include namespace NKikimr::NOlap { -std::unique_ptr TModificationRestoreTask::DoBuildRequestInitiator() const { +std::unique_ptr TModificationRestoreTask::DoBuildRequestInitiator() const { auto request = std::make_unique(LocalPathId, WriteData.GetWriteMeta().GetLockIdOptional()); + request->TaskIdentifier = GetTaskId(); request->ReadToSnapshot = Snapshot; - auto pkData = NArrow::TColumnOperator().VerifyIfAbsent().Extract(IncomingData, ActualSchema->GetPKColumnNames()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_RESTORE)("event", "restore_start")("count", IncomingData ? IncomingData->num_rows() : 0)( + "task_id", WriteData.GetWriteMeta().GetId()); + auto pkData = NArrow::TColumnOperator().VerifyIfAbsent().Extract(IncomingData, Context.GetActualSchema()->GetPKColumnNames()); request->RangesFilter = TPKRangesFilter::BuildFromRecordBatchLines(pkData, false); - for (auto&& i : ActualSchema->GetIndexInfo().GetColumnIds(false)) { - request->AddColumn(i, ActualSchema->GetIndexInfo().GetColumnName(i)); + for (auto&& i : Context.GetActualSchema()->GetIndexInfo().GetColumnIds(false)) { + request->AddColumn(i, Context.GetActualSchema()->GetIndexInfo().GetColumnName(i)); } return request; } -NKikimr::TConclusionStatus TModificationRestoreTask::DoOnDataChunk(const std::shared_ptr& data) { +TConclusionStatus TModificationRestoreTask::DoOnDataChunk(const std::shared_ptr& data) { auto result = Merger->AddExistsDataOrdered(data); if (result.IsFail()) { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "merge_data_problems") - ("write_id", WriteData.GetWriteMeta().GetWriteId())("tablet_id", TabletId)("message", result.GetErrorMessage()); + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_RESTORE)("event", "merge_data_problems")("write_id", WriteData.GetWriteMeta().GetWriteId())( + "tablet_id", GetTabletId())("message", result.GetErrorMessage()); SendErrorMessage(result.GetErrorMessage(), NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass::Request); } return result; } void TModificationRestoreTask::DoOnError(const TString& errorMessage) { - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("event", "restore_data_problems")("write_id", WriteData.GetWriteMeta().GetWriteId())( - "tablet_id", TabletId)("message", errorMessage); + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD_RESTORE)("event", "restore_data_problems")("write_id", WriteData.GetWriteMeta().GetWriteId())( + "tablet_id", GetTabletId())("message", errorMessage); SendErrorMessage(errorMessage, NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass::Internal); } @@ -36,36 +40,50 @@ NKikimr::TConclusionStatus TModificationRestoreTask::DoOnFinished() { { auto result = Merger->Finish(); if (result.IsFail()) { + OnError("cannot finish merger: " + result.GetErrorMessage()); return result; } } auto batchResult = Merger->BuildResultBatch(); - std::shared_ptr task = std::make_shared( - TabletId, ParentActorId, BufferActorId, std::move(WriteData), batchResult, ActualSchema); + std::shared_ptr task = + std::make_shared(BufferActorId, std::move(WriteData), batchResult, Context); NConveyor::TInsertServiceOperator::AsyncTaskToExecute(task); return TConclusionStatus::Success(); } -TModificationRestoreTask::TModificationRestoreTask(const ui64 tabletId, const NActors::TActorId parentActorId, const NActors::TActorId bufferActorId, NEvWrite::TWriteData&& writeData, const std::shared_ptr& merger, const std::shared_ptr& actualSchema, const TSnapshot actualSnapshot, const std::shared_ptr& incomingData) - : TBase(tabletId, parentActorId) +TModificationRestoreTask::TModificationRestoreTask(const NActors::TActorId bufferActorId, NEvWrite::TWriteData&& writeData, + const std::shared_ptr& merger, const TSnapshot actualSnapshot, const std::shared_ptr& incomingData, + const TWritingContext& context) + : TBase(context.GetTabletId(), context.GetTabletActorId(), writeData.GetWriteMeta().GetId() + "::" + ::ToString(writeData.GetWriteMeta().GetWriteId())) , WriteData(std::move(writeData)) - , TabletId(tabletId) - , ParentActorId(parentActorId) , BufferActorId(bufferActorId) , Merger(merger) - , ActualSchema(actualSchema) , LocalPathId(WriteData.GetWriteMeta().GetTableId()) , Snapshot(actualSnapshot) - , IncomingData(incomingData) { - + , IncomingData(incomingData) + , Context(context) { } -void TModificationRestoreTask::SendErrorMessage(const TString& errorMessage, const NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass errorClass) { +void TModificationRestoreTask::SendErrorMessage( + const TString& errorMessage, const NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass errorClass) { auto writeDataPtr = std::make_shared(std::move(WriteData)); TWritingBuffer buffer(writeDataPtr->GetBlobsAction(), { std::make_shared(*writeDataPtr) }); - auto evResult = NColumnShard::TEvPrivate::TEvWriteBlobsResult::Error(NKikimrProto::EReplyStatus::CORRUPTED, std::move(buffer), errorMessage, errorClass); - TActorContext::AsActorContext().Send(ParentActorId, evResult.release()); + auto evResult = + NColumnShard::TEvPrivate::TEvWriteBlobsResult::Error(NKikimrProto::EReplyStatus::CORRUPTED, std::move(buffer), errorMessage, errorClass); + TActorContext::AsActorContext().Send(Context.GetTabletActorId(), evResult.release()); } +TDuration TModificationRestoreTask::GetTimeout() const { + static const TDuration criticalTimeoutDuration = TDuration::Seconds(1200); + if (!HasAppData()) { + return criticalTimeoutDuration; + } + if (!AppDataVerified().ColumnShardConfig.HasRestoreDataOnWriteTimeoutSeconds() || + !AppDataVerified().ColumnShardConfig.GetRestoreDataOnWriteTimeoutSeconds()) { + return criticalTimeoutDuration; + } + return TDuration::Seconds(AppDataVerified().ColumnShardConfig.GetRestoreDataOnWriteTimeoutSeconds()); } + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/operations/batch_builder/restore.h b/ydb/core/tx/columnshard/operations/batch_builder/restore.h index b69a856a8a58..246c6e9e8a75 100644 --- a/ydb/core/tx/columnshard/operations/batch_builder/restore.h +++ b/ydb/core/tx/columnshard/operations/batch_builder/restore.h @@ -4,21 +4,20 @@ #include #include #include +#include namespace NKikimr::NOlap { -class TModificationRestoreTask: public NDataReader::IRestoreTask { +class TModificationRestoreTask: public NDataReader::IRestoreTask, public NColumnShard::TMonitoringObjectsCounter { private: using TBase = NDataReader::IRestoreTask; NEvWrite::TWriteData WriteData; - const ui64 TabletId; - const NActors::TActorId ParentActorId; const NActors::TActorId BufferActorId; std::shared_ptr Merger; - const std::shared_ptr ActualSchema; const ui64 LocalPathId; const TSnapshot Snapshot; std::shared_ptr IncomingData; + const TWritingContext Context; virtual std::unique_ptr DoBuildRequestInitiator() const override; virtual TConclusionStatus DoOnDataChunk(const std::shared_ptr& data) override; @@ -27,9 +26,14 @@ class TModificationRestoreTask: public NDataReader::IRestoreTask { void SendErrorMessage(const TString& errorMessage, const NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass errorClass); public: - TModificationRestoreTask(const ui64 tabletId, const NActors::TActorId parentActorId, const NActors::TActorId bufferActorId, - NEvWrite::TWriteData&& writeData, const std::shared_ptr& merger, const std::shared_ptr& actualSchema, - const TSnapshot actualSnapshot, const std::shared_ptr& incomingData); + virtual bool IsActive() const override { + return Context.IsActive(); + } + + virtual TDuration GetTimeout() const override; + + TModificationRestoreTask(const NActors::TActorId bufferActorId, NEvWrite::TWriteData&& writeData, const std::shared_ptr& merger, + const TSnapshot actualSnapshot, const std::shared_ptr& incomingData, const TWritingContext& context); }; } // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/operations/common/context.cpp b/ydb/core/tx/columnshard/operations/common/context.cpp new file mode 100644 index 000000000000..76af2c9fa917 --- /dev/null +++ b/ydb/core/tx/columnshard/operations/common/context.cpp @@ -0,0 +1,5 @@ +#include "context.h" + +namespace NKikimr::NOlap { + +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/operations/common/context.h b/ydb/core/tx/columnshard/operations/common/context.h new file mode 100644 index 000000000000..c511f9983b5c --- /dev/null +++ b/ydb/core/tx/columnshard/operations/common/context.h @@ -0,0 +1,40 @@ +#pragma once +#include +#include +#include +#include + +namespace NKikimr::NOlap { + +class TWritingContext { +private: + YDB_READONLY(ui64, TabletId, 0); + YDB_READONLY(NActors::TActorId, TabletActorId, NActors::TActorId()); + YDB_READONLY_DEF(std::shared_ptr, ActualSchema); + YDB_READONLY_DEF(std::shared_ptr, StoragesManager); + YDB_READONLY_DEF(std::shared_ptr, SplitterCounters); + YDB_READONLY_DEF(std::shared_ptr, WritingCounters); + YDB_READONLY(TSnapshot, ApplyToSnapshot, TSnapshot::Zero()); + const std::shared_ptr ActivityChecker; + +public: + bool IsActive() const { + return ActivityChecker->Val(); + } + + TWritingContext(const ui64 tabletId, const NActors::TActorId& tabletActorId, const std::shared_ptr& actualSchema, + const std::shared_ptr& operators, const std::shared_ptr& splitterCounters, + const std::shared_ptr& writingCounters, const TSnapshot& applyToSnapshot, + const std::shared_ptr& activityChecker) + : TabletId(tabletId) + , TabletActorId(tabletActorId) + , ActualSchema(actualSchema) + , StoragesManager(operators) + , SplitterCounters(splitterCounters) + , WritingCounters(writingCounters) + , ApplyToSnapshot(applyToSnapshot) + , ActivityChecker(activityChecker) { + AFL_VERIFY(ActivityChecker); + } +}; +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/operations/common/ya.make b/ydb/core/tx/columnshard/operations/common/ya.make new file mode 100644 index 000000000000..8416d5dce223 --- /dev/null +++ b/ydb/core/tx/columnshard/operations/common/ya.make @@ -0,0 +1,16 @@ +LIBRARY() + +SRCS( + context.cpp +) + +PEERDIR( + ydb/core/tx/conveyor/usage + ydb/core/tx/data_events + ydb/core/formats/arrow + ydb/core/tx/columnshard/engines/scheme/versions + ydb/core/tx/columnshard/engines/scheme + ydb/core/tx/columnshard/engines/writer +) + +END() diff --git a/ydb/core/tx/columnshard/operations/events.cpp b/ydb/core/tx/columnshard/operations/events.cpp new file mode 100644 index 000000000000..734d03157d69 --- /dev/null +++ b/ydb/core/tx/columnshard/operations/events.cpp @@ -0,0 +1,20 @@ +#include "events.h" + +#include +#include + +namespace NKikimr::NColumnShard { + +void TInsertedPortion::Finalize(TColumnShard* shard, NTabletFlatExecutor::TTransactionContext& txc) { + AFL_VERIFY(PortionInfoConstructor); + auto* lastPortionId = shard->MutableIndexAs().GetLastPortionPointer(); + PortionInfoConstructor->MutablePortionConstructor().SetPortionId(++*lastPortionId); + NOlap::TDbWrapper wrapper(txc.DB, nullptr); + wrapper.WriteCounter(NOlap::TColumnEngineForLogs::LAST_PORTION, *lastPortionId); + PortionInfo = PortionInfoConstructor->Build(true); + PortionInfoConstructor = nullptr; +} + +} // namespace NKikimr::NColumnShard + +namespace NKikimr::NColumnShard::NPrivateEvents::NWrite {} diff --git a/ydb/core/tx/columnshard/operations/events.h b/ydb/core/tx/columnshard/operations/events.h new file mode 100644 index 000000000000..b2d5bd9b8e93 --- /dev/null +++ b/ydb/core/tx/columnshard/operations/events.h @@ -0,0 +1,111 @@ +#pragma once +#include +#include +#include +#include + +namespace NKikimr::NColumnShard { + +class TInsertedPortion { +private: + YDB_READONLY_DEF(std::shared_ptr, PortionInfoConstructor); + std::optional PortionInfo; + YDB_READONLY_DEF(std::shared_ptr, PKBatch); + +public: + const NOlap::TPortionDataAccessor& GetPortionInfo() const { + AFL_VERIFY(PortionInfo); + return *PortionInfo; + } + TInsertedPortion(NOlap::TWritePortionInfoWithBlobsResult&& portion, const std::shared_ptr& pkBatch) + : PortionInfoConstructor(portion.DetachPortionConstructor()) + , PKBatch(pkBatch) { + AFL_VERIFY(PKBatch); + } + + void Finalize(TColumnShard* shard, NTabletFlatExecutor::TTransactionContext& txc); +}; + +class TInsertedPortions { +private: + NEvWrite::TWriteMeta WriteMeta; + YDB_ACCESSOR_DEF(std::vector, Portions); + YDB_READONLY(ui64, DataSize, 0); + YDB_READONLY_DEF(std::vector, InsertWriteIds); + +public: + ui64 GetRecordsCount() const { + ui64 result = 0; + for (auto&& i : Portions) { + result += i.GetPKBatch()->num_rows(); + } + return result; + } + + const NEvWrite::TWriteMeta& GetWriteMeta() const { + return WriteMeta; + } + + void AddInsertWriteId(const NOlap::TInsertWriteId id) { + InsertWriteIds.emplace_back(id); + } + + void Finalize(TColumnShard* shard, NTabletFlatExecutor::TTransactionContext& txc); + + TInsertedPortions(const NEvWrite::TWriteMeta& writeMeta, std::vector&& portions, const ui64 dataSize) + : WriteMeta(writeMeta) + , Portions(std::move(portions)) + , DataSize(dataSize) { + AFL_VERIFY(!WriteMeta.HasLongTxId()); + for (auto&& i : Portions) { + AFL_VERIFY(i.GetPKBatch()); + } + } +}; + +class TNoDataWrite { +private: + NEvWrite::TWriteMeta WriteMeta; + YDB_READONLY(ui64, DataSize, 0); + +public: + const NEvWrite::TWriteMeta& GetWriteMeta() const { + return WriteMeta; + } + + TNoDataWrite(const NEvWrite::TWriteMeta& writeMeta, const ui64 dataSize) + : WriteMeta(writeMeta) + , DataSize(dataSize) { + AFL_VERIFY(!WriteMeta.HasLongTxId()); + } +}; + +} // namespace NKikimr::NColumnShard + +namespace NKikimr::NColumnShard::NPrivateEvents::NWrite { + +class TEvWritePortionResult: public TEventLocal { +private: + YDB_READONLY_DEF(NKikimrProto::EReplyStatus, WriteStatus); + YDB_READONLY_DEF(std::shared_ptr, WriteAction); + std::vector InsertedPacks; + std::vector NoData; + +public: + std::vector&& DetachInsertedPacks() { + return std::move(InsertedPacks); + } + std::vector&& DetachNoDataWrites() { + return std::move(NoData); + } + + TEvWritePortionResult(const NKikimrProto::EReplyStatus writeStatus, const std::shared_ptr& writeAction, + std::vector&& portions, std::vector&& noData) + : WriteStatus(writeStatus) + , WriteAction(writeAction) + , InsertedPacks(portions) + , NoData(noData) { + } +}; + +} // namespace NKikimr::NColumnShard::NPrivateEvents::NWrite diff --git a/ydb/core/tx/columnshard/operations/manager.cpp b/ydb/core/tx/columnshard/operations/manager.cpp index 1527ec5d028d..6e4d1783745e 100644 --- a/ydb/core/tx/columnshard/operations/manager.cpp +++ b/ydb/core/tx/columnshard/operations/manager.cpp @@ -28,8 +28,8 @@ bool TOperationsManager::Load(NTabletFlatExecutor::TTransactionContext& txc) { NKikimrTxColumnShard::TInternalOperationData metaProto; Y_ABORT_UNLESS(metaProto.ParseFromString(metadata)); - auto operation = std::make_shared( - writeId, lockId, cookie, status, TInstant::Seconds(createdAtSec), granuleShardingVersionId, NEvWrite::EModificationType::Upsert); + auto operation = std::make_shared(0, writeId, lockId, cookie, status, TInstant::Seconds(createdAtSec), + granuleShardingVersionId, NEvWrite::EModificationType::Upsert, false); operation->FromProto(metaProto); LinkInsertWriteIdToOperationWriteId(operation->GetInsertWriteIds(), operation->GetWriteId()); AFL_VERIFY(operation->GetStatus() != EOperationStatus::Draft); @@ -201,11 +201,11 @@ void TOperationsManager::LinkTransactionOnExecute(const ui64 lockId, const ui64 void TOperationsManager::LinkTransactionOnComplete(const ui64 /*lockId*/, const ui64 /*txId*/) { } -TWriteOperation::TPtr TOperationsManager::RegisterOperation( - const ui64 lockId, const ui64 cookie, const std::optional granuleShardingVersionId, const NEvWrite::EModificationType mType) { +TWriteOperation::TPtr TOperationsManager::RegisterOperation(const ui64 pathId, const ui64 lockId, const ui64 cookie, + const std::optional granuleShardingVersionId, const NEvWrite::EModificationType mType, const bool portionsWriting) { auto writeId = BuildNextOperationWriteId(); - auto operation = std::make_shared( - writeId, lockId, cookie, EOperationStatus::Draft, AppData()->TimeProvider->Now(), granuleShardingVersionId, mType); + auto operation = std::make_shared(pathId, writeId, lockId, cookie, EOperationStatus::Draft, AppData()->TimeProvider->Now(), + granuleShardingVersionId, mType, portionsWriting); Y_ABORT_UNLESS(Operations.emplace(operation->GetWriteId(), operation).second); GetLockVerified(operation->GetLockId()).MutableWriteOperations().emplace_back(operation); GetLockVerified(operation->GetLockId()).AddWrite(); @@ -255,9 +255,6 @@ TConclusion TOperationsManager::GetBehaviour(const NEvents: return EOperationBehaviour::NoTxWrite; } - if (evWrite.Record.HasTxId() && evWrite.Record.GetTxMode() == NKikimrDataEvents::TEvWrite::MODE_PREPARE) { - return EOperationBehaviour::InTxWrite; - } AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("proto", evWrite.Record.DebugString())("event", "undefined behaviour"); return TConclusionStatus::Fail("undefined request for detect tx type"); } diff --git a/ydb/core/tx/columnshard/operations/manager.h b/ydb/core/tx/columnshard/operations/manager.h index 9e2651e24da0..453a0cff7feb 100644 --- a/ydb/core/tx/columnshard/operations/manager.h +++ b/ydb/core/tx/columnshard/operations/manager.h @@ -126,6 +126,12 @@ class TOperationsManager { public: + void StopWriting() { + for (auto&& i : Operations) { + i.second->StopWriting(); + } + } + TWriteOperation::TPtr GetOperationByInsertWriteIdVerified(const TInsertWriteId insertWriteId) const { auto it = InsertWriteIdToOpWriteId.find(insertWriteId); AFL_VERIFY(it != InsertWriteIdToOpWriteId.end()); @@ -182,8 +188,8 @@ class TOperationsManager { return *result; } - TWriteOperation::TPtr RegisterOperation( - const ui64 lockId, const ui64 cookie, const std::optional granuleShardingVersionId, const NEvWrite::EModificationType mType); + TWriteOperation::TPtr RegisterOperation(const ui64 pathId, const ui64 lockId, const ui64 cookie, const std::optional granuleShardingVersionId, + const NEvWrite::EModificationType mType, const bool portionsWriting); bool RegisterLock(const ui64 lockId, const ui64 generationId) { if (LockFeatures.contains(lockId)) { return false; @@ -208,6 +214,10 @@ class TOperationsManager { } } + bool HasReadLocks(const ui64 pathId) const { + return InteractionsContext.HasReadIntervals(pathId); + } + TOperationsManager(); private: diff --git a/ydb/core/tx/columnshard/operations/slice_builder/builder.cpp b/ydb/core/tx/columnshard/operations/slice_builder/builder.cpp index d79eb2708cec..5a0ae23efce6 100644 --- a/ydb/core/tx/columnshard/operations/slice_builder/builder.cpp +++ b/ydb/core/tx/columnshard/operations/slice_builder/builder.cpp @@ -1,27 +1,33 @@ #include "builder.h" + +#include #include -#include -#include -#include #include +#include +#include +#include +#include namespace NKikimr::NOlap { -std::optional> TBuildSlicesTask::BuildSlices() { +std::optional> TBuildSlicesTask::BuildSlices() { if (!OriginalBatch->num_rows()) { return std::vector(); } - NArrow::TBatchSplitttingContext context(NColumnShard::TLimits::GetMaxBlobSize()); + const auto splitSettings = NYDBTest::TControllers::GetColumnShardController()->GetBlobSplitSettings(); + NArrow::TBatchSplitttingContext context(splitSettings.GetMaxBlobSize()); context.SetFieldsForSpecialKeys(WriteData.GetPrimaryKeySchema()); auto splitResult = NArrow::SplitByBlobSize(OriginalBatch, context); if (splitResult.IsFail()) { - AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("event", TStringBuilder() << "cannot split batch in according to limits: " + splitResult.GetErrorMessage()); + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_WRITE)( + "event", TStringBuilder() << "cannot split batch in according to limits: " + splitResult.GetErrorMessage()); return {}; } auto result = splitResult.DetachResult(); if (result.size() > 1) { for (auto&& i : result) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "strange_blobs_splitting")("blob", i.DebugString())("original_size", WriteData.GetSize()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "strange_blobs_splitting")("blob", i.DebugString())( + "original_size", WriteData.GetSize()); } } return result; @@ -30,61 +36,168 @@ std::optional> TBuildSlicesTask:: void TBuildSlicesTask::ReplyError(const TString& message, const NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass errorClass) { auto writeDataPtr = std::make_shared(std::move(WriteData)); TWritingBuffer buffer(writeDataPtr->GetBlobsAction(), { std::make_shared(*writeDataPtr) }); - auto result = NColumnShard::TEvPrivate::TEvWriteBlobsResult::Error( - NKikimrProto::EReplyStatus::CORRUPTED, std::move(buffer), message, errorClass); - TActorContext::AsActorContext().Send(ParentActorId, result.release()); + auto result = + NColumnShard::TEvPrivate::TEvWriteBlobsResult::Error(NKikimrProto::EReplyStatus::CORRUPTED, std::move(buffer), message, errorClass); + TActorContext::AsActorContext().Send(Context.GetTabletActorId(), result.release()); } +class TPortionWriteController: public NColumnShard::IWriteController, + public NColumnShard::TMonitoringObjectsCounter { +public: + class TInsertPortion { + private: + TWritePortionInfoWithBlobsResult Portion; + std::shared_ptr PKBatch; + + public: + TWritePortionInfoWithBlobsResult& MutablePortion() { + return Portion; + } + const TWritePortionInfoWithBlobsResult& GetPortion() const { + return Portion; + } + TWritePortionInfoWithBlobsResult&& ExtractPortion() { + return std::move(Portion); + } + const std::shared_ptr& GetPKBatch() const { + return PKBatch; + } + TInsertPortion(TWritePortionInfoWithBlobsResult&& portion, const std::shared_ptr pkBatch) + : Portion(std::move(portion)) + , PKBatch(pkBatch) { + AFL_VERIFY(PKBatch); + } + }; + +private: + const std::shared_ptr Action; + std::vector Portions; + NEvWrite::TWriteMeta WriteMeta; + TActorId DstActor; + const ui64 DataSize; + void DoOnReadyResult(const NActors::TActorContext& ctx, const NColumnShard::TBlobPutResult::TPtr& putResult) override { + std::vector portions; + std::vector noDataWrites; + for (auto&& i : Portions) { + portions.emplace_back(i.ExtractPortion(), i.GetPKBatch()); + } + NColumnShard::TInsertedPortions pack(std::move(WriteMeta), std::move(portions), DataSize); + std::vector packs = { pack }; + auto result = std::make_unique( + putResult->GetPutStatus(), Action, std::move(packs), std::move(noDataWrites)); + ctx.Send(DstActor, result.release()); + } + virtual void DoOnStartSending() override { + } + +public: + TPortionWriteController(const TActorId& dstActor, const std::shared_ptr& action, const NEvWrite::TWriteMeta& writeMeta, + std::vector&& portions, const ui64 dataSize) + : Action(action) + , Portions(std::move(portions)) + , WriteMeta(writeMeta) + , DstActor(dstActor) + , DataSize(dataSize) + { + for (auto&& p : Portions) { + for (auto&& b : p.MutablePortion().MutableBlobs()) { + auto& task = AddWriteTask(TBlobWriteInfo::BuildWriteTask(b.GetResultBlob(), action)); + b.RegisterBlobId(p.MutablePortion(), task.GetBlobId()); + } + } + } +}; + TConclusionStatus TBuildSlicesTask::DoExecute(const std::shared_ptr& /*taskPtr*/) { - NActors::TLogContextGuard g(NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("tablet_id", TabletId)("parent_id", ParentActorId)); + const NActors::TLogContextGuard g = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD_WRITE)("tablet_id", TabletId)("parent_id", + Context.GetTabletActorId())("write_id", WriteData.GetWriteMeta().GetWriteId())("table_id", WriteData.GetWriteMeta().GetTableId()); + if (!Context.IsActive()) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "abort_execution"); + ReplyError("execution aborted", NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass::Internal); + return TConclusionStatus::Fail("execution aborted"); + } if (!OriginalBatch) { - AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("event", "ev_write_bad_data")("write_id", WriteData.GetWriteMeta().GetWriteId())("table_id", WriteData.GetWriteMeta().GetTableId()); + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "ev_write_bad_data"); ReplyError("no data in batch", NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass::Internal); return TConclusionStatus::Fail("no data in batch"); } - const auto& indexSchema = ActualSchema->GetIndexInfo().ArrowSchema(); - auto subsetConclusion = NArrow::TColumnOperator().IgnoreOnDifferentFieldTypes().BuildSequentialSubset(OriginalBatch, indexSchema); - if (subsetConclusion.IsFail()) { - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("event", "unadaptable schemas")("index", indexSchema->ToString())( - "problem", subsetConclusion.GetErrorMessage()); - ReplyError( - "unadaptable schema: " + subsetConclusion.GetErrorMessage(), - NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass::Internal); - return TConclusionStatus::Fail("cannot reorder schema: " + subsetConclusion.GetErrorMessage()); - } - NArrow::TSchemaSubset subset = subsetConclusion.DetachResult(); + if (WriteData.GetWritePortions()) { + if (OriginalBatch->num_rows() == 0) { + std::vector portions; + std::vector noDataWrites = { NColumnShard::TNoDataWrite(WriteData.GetWriteMeta(), WriteData.GetSize()) }; + auto result = std::make_unique( + NKikimrProto::EReplyStatus::OK, nullptr, std::move(portions), std::move(noDataWrites)); + NActors::TActivationContext::AsActorContext().Send(Context.GetTabletActorId(), result.release()); + } else { + auto batches = NArrow::NMerger::TRWSortableBatchPosition::SplitByBordersInIntervalPositions(OriginalBatch, + Context.GetActualSchema()->GetIndexInfo().GetPrimaryKey()->field_names(), WriteData.GetData()->GetSeparationPoints()); + std::vector portions; + for (auto&& batch : batches) { + if (!batch) { + continue; + } + auto portionConclusion = + Context.GetActualSchema()->PrepareForWrite(Context.GetActualSchema(), WriteData.GetWriteMeta().GetTableId(), batch, + WriteData.GetWriteMeta().GetModificationType(), Context.GetStoragesManager(), Context.GetSplitterCounters()); + if (portionConclusion.IsFail()) { + ReplyError(portionConclusion.GetErrorMessage(), NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass::Request); + return portionConclusion; + } + std::shared_ptr pkBatch = + NArrow::TColumnOperator().Extract(batch, Context.GetActualSchema()->GetIndexInfo().GetPrimaryKey()->fields()); + portions.emplace_back(portionConclusion.DetachResult(), pkBatch); + } + auto writeController = std::make_shared( + Context.GetTabletActorId(), WriteData.GetBlobsAction(), WriteData.GetWriteMeta(), std::move(portions), WriteData.GetSize()); + if (WriteData.GetBlobsAction()->NeedDraftTransaction()) { + TActorContext::AsActorContext().Send( + Context.GetTabletActorId(), std::make_unique(writeController)); + } else { + TActorContext::AsActorContext().Register(NColumnShard::CreateWriteActor(TabletId, writeController, TInstant::Max())); + } + } + } else { + const auto& indexSchema = Context.GetActualSchema()->GetIndexInfo().ArrowSchema(); + auto subsetConclusion = NArrow::TColumnOperator().IgnoreOnDifferentFieldTypes().BuildSequentialSubset(OriginalBatch, indexSchema); + if (subsetConclusion.IsFail()) { + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "unadaptable schemas")("index", indexSchema.ToString())( + "problem", subsetConclusion.GetErrorMessage()); + ReplyError("unadaptable schema: " + subsetConclusion.GetErrorMessage(), + NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass::Internal); + return TConclusionStatus::Fail("cannot reorder schema: " + subsetConclusion.GetErrorMessage()); + } + NArrow::TSchemaSubset subset = subsetConclusion.DetachResult(); - if (OriginalBatch->num_columns() != indexSchema->num_fields()) { - AFL_VERIFY(OriginalBatch->num_columns() < indexSchema->num_fields())("original", OriginalBatch->num_columns())( - "index", indexSchema->num_fields()); - if (HasAppData() && !AppDataVerified().FeatureFlags.GetEnableOptionalColumnsInColumnShard() && - WriteData.GetWriteMeta().GetModificationType() != NEvWrite::EModificationType::Delete) { - subset = NArrow::TSchemaSubset::AllFieldsAccepted(); - const std::vector& columnIdsVector = ActualSchema->GetIndexInfo().GetColumnIds(false); - const std::set columnIdsSet(columnIdsVector.begin(), columnIdsVector.end()); - auto normalized = - ActualSchema->NormalizeBatch(*ActualSchema, std::make_shared(OriginalBatch), columnIdsSet).DetachResult(); - OriginalBatch = NArrow::ToBatch(normalized->BuildTableVerified(), true); + if (OriginalBatch->num_columns() != indexSchema.num_fields()) { + AFL_VERIFY(OriginalBatch->num_columns() < indexSchema.num_fields())("original", OriginalBatch->num_columns())( + "index", indexSchema.num_fields()); + if (HasAppData() && !AppDataVerified().FeatureFlags.GetEnableOptionalColumnsInColumnShard() && + WriteData.GetWriteMeta().GetModificationType() != NEvWrite::EModificationType::Delete) { + subset = NArrow::TSchemaSubset::AllFieldsAccepted(); + const auto columnIdsVector = Context.GetActualSchema()->GetIndexInfo().GetColumnIds(false); + const std::set columnIdsSet(columnIdsVector.begin(), columnIdsVector.end()); + auto normalized = + Context.GetActualSchema() + ->NormalizeBatch(*Context.GetActualSchema(), std::make_shared(OriginalBatch), columnIdsSet) + .DetachResult(); + OriginalBatch = NArrow::ToBatch(normalized->BuildTableVerified(), true); + } } - } - WriteData.MutableWriteMeta().SetWriteMiddle2StartInstant(TMonotonic::Now()); - auto batches = BuildSlices(); - WriteData.MutableWriteMeta().SetWriteMiddle3StartInstant(TMonotonic::Now()); - if (batches) { - auto writeDataPtr = std::make_shared(std::move(WriteData)); - writeDataPtr->SetSchemaSubset(std::move(subset)); - std::shared_ptr pkBatch; - if (!writeDataPtr->GetWriteMeta().HasLongTxId()) { - pkBatch = NArrow::TColumnOperator().Extract(OriginalBatch, ActualSchema->GetIndexInfo().GetPrimaryKey()->fields()); + WriteData.MutableWriteMeta().SetWriteMiddle2StartInstant(TMonotonic::Now()); + auto batches = BuildSlices(); + WriteData.MutableWriteMeta().SetWriteMiddle3StartInstant(TMonotonic::Now()); + if (batches) { + auto writeDataPtr = std::make_shared(std::move(WriteData)); + writeDataPtr->SetSchemaSubset(std::move(subset)); + std::shared_ptr pkBatch = + NArrow::TColumnOperator().Extract(OriginalBatch, Context.GetActualSchema()->GetIndexInfo().GetPrimaryKey()->fields()); + auto result = std::make_unique(writeDataPtr, std::move(*batches), pkBatch); + TActorContext::AsActorContext().Send(BufferActorId, result.release()); + } else { + ReplyError("Cannot slice input to batches", NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass::Internal); + return TConclusionStatus::Fail("Cannot slice input to batches"); } - auto result = std::make_unique(writeDataPtr, std::move(*batches), pkBatch); - TActorContext::AsActorContext().Send(BufferActorId, result.release()); - } else { - ReplyError("Cannot slice input to batches", NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass::Internal); - return TConclusionStatus::Fail("Cannot slice input to batches"); } - return TConclusionStatus::Success(); } - -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/operations/slice_builder/builder.h b/ydb/core/tx/columnshard/operations/slice_builder/builder.h index a22b0c7d6ca7..105a7a8f4626 100644 --- a/ydb/core/tx/columnshard/operations/slice_builder/builder.h +++ b/ydb/core/tx/columnshard/operations/slice_builder/builder.h @@ -2,20 +2,20 @@ #include #include #include +#include #include #include namespace NKikimr::NOlap { -class TBuildSlicesTask: public NConveyor::ITask { +class TBuildSlicesTask: public NConveyor::ITask, public NColumnShard::TMonitoringObjectsCounter { private: NEvWrite::TWriteData WriteData; const ui64 TabletId; - const NActors::TActorId ParentActorId; const NActors::TActorId BufferActorId; std::shared_ptr OriginalBatch; std::optional> BuildSlices(); - const std::shared_ptr ActualSchema; + const TWritingContext Context; void ReplyError(const TString& message, const NColumnShard::TEvPrivate::TEvWriteBlobsResult::EErrorClass errorClass); protected: @@ -26,14 +26,13 @@ class TBuildSlicesTask: public NConveyor::ITask { return "Write::ConstructBlobs::Slices"; } - TBuildSlicesTask(const ui64 tabletId, const NActors::TActorId parentActorId, const NActors::TActorId bufferActorId, - NEvWrite::TWriteData&& writeData, const std::shared_ptr& batch, const std::shared_ptr& actualSchema) + TBuildSlicesTask(const NActors::TActorId bufferActorId, NEvWrite::TWriteData&& writeData, const std::shared_ptr& batch, + const TWritingContext& context) : WriteData(std::move(writeData)) - , TabletId(tabletId) - , ParentActorId(parentActorId) + , TabletId(WriteData.GetWriteMeta().GetTableId()) , BufferActorId(bufferActorId) , OriginalBatch(batch) - , ActualSchema(actualSchema) { + , Context(context) { } }; } // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/operations/write.cpp b/ydb/core/tx/columnshard/operations/write.cpp index b69c25b8de8a..ef288c11390c 100644 --- a/ydb/core/tx/columnshard/operations/write.cpp +++ b/ydb/core/tx/columnshard/operations/write.cpp @@ -7,60 +7,75 @@ #include #include #include +#include #include #include namespace NKikimr::NColumnShard { -TWriteOperation::TWriteOperation(const TOperationWriteId writeId, const ui64 lockId, const ui64 cookie, const EOperationStatus& status, - const TInstant createdAt, const std::optional granuleShardingVersionId, const NEvWrite::EModificationType mType) - : Status(status) +TWriteOperation::TWriteOperation(const ui64 pathId, const TOperationWriteId writeId, const ui64 lockId, const ui64 cookie, + const EOperationStatus& status, const TInstant createdAt, const std::optional granuleShardingVersionId, + const NEvWrite::EModificationType mType, const bool writePortions) + : PathId(pathId) + , Status(status) , CreatedAt(createdAt) , WriteId(writeId) , LockId(lockId) , Cookie(cookie) , GranuleShardingVersionId(granuleShardingVersionId) , ModificationType(mType) -{ + , WritePortions(writePortions) { } -void TWriteOperation::Start(TColumnShard& owner, const ui64 tableId, const NEvWrite::IDataContainer::TPtr& data, const NActors::TActorId& source, - const std::shared_ptr& schema, const TActorContext& ctx, const NOlap::TSnapshot& applyToSnapshot) { +void TWriteOperation::Start( + TColumnShard& owner, const NEvWrite::IDataContainer::TPtr& data, const NActors::TActorId& source, const NOlap::TWritingContext& context) { Y_ABORT_UNLESS(Status == EOperationStatus::Draft); - NEvWrite::TWriteMeta writeMeta((ui64)WriteId, tableId, source, GranuleShardingVersionId); + NEvWrite::TWriteMeta writeMeta((ui64)WriteId, GetPathId(), source, GranuleShardingVersionId, GetIdentifier()); writeMeta.SetLockId(LockId); writeMeta.SetModificationType(ModificationType); + NEvWrite::TWriteData writeData(writeMeta, data, owner.TablesManager.GetPrimaryIndex()->GetReplaceKey(), + owner.StoragesManager->GetInsertOperator()->StartWritingAction(NOlap::NBlobOperations::EConsumer::WRITING_OPERATOR), WritePortions); std::shared_ptr task = - std::make_shared(owner.TabletID(), ctx.SelfID, owner.BufferizationWriteActorId, - NEvWrite::TWriteData(writeMeta, data, owner.TablesManager.GetPrimaryIndex()->GetReplaceKey(), - owner.StoragesManager->GetInsertOperator()->StartWritingAction(NOlap::NBlobOperations::EConsumer::WRITING_OPERATOR)), - schema, applyToSnapshot, owner.Counters.GetCSCounters().WritingCounters); - NConveyor::TCompServiceOperator::SendTaskToExecute(task); + std::make_shared(owner.BufferizationWriteActorId, std::move(writeData), context); + NConveyor::TInsertServiceOperator::AsyncTaskToExecute(task); Status = EOperationStatus::Started; } void TWriteOperation::CommitOnExecute( TColumnShard& owner, NTabletFlatExecutor::TTransactionContext& txc, const NOlap::TSnapshot& snapshot) const { - Y_ABORT_UNLESS(Status == EOperationStatus::Prepared); + Y_ABORT_UNLESS(Status == EOperationStatus::Prepared || InsertWriteIds.empty()); TBlobGroupSelector dsGroupSelector(owner.Info()); NOlap::TDbWrapper dbTable(txc.DB, &dsGroupSelector); - for (auto gWriteId : InsertWriteIds) { + if (!WritePortions) { + THashSet insertWriteIds(InsertWriteIds.begin(), InsertWriteIds.end()); auto pathExists = [&](ui64 pathId) { return owner.TablesManager.HasTable(pathId); }; - - const auto counters = owner.InsertTable->Commit(dbTable, snapshot.GetPlanStep(), snapshot.GetTxId(), { gWriteId }, pathExists); - owner.Counters.GetTabletCounters()->OnWriteCommitted(counters); + if (insertWriteIds.size()) { + const auto counters = owner.InsertTable->Commit(dbTable, snapshot.GetPlanStep(), snapshot.GetTxId(), insertWriteIds, pathExists); + owner.Counters.GetTabletCounters()->OnWriteCommitted(counters); + } + } else { + for (auto&& i : InsertWriteIds) { + owner.MutableIndexAs().MutableGranuleVerified(PathId).CommitPortionOnExecute(txc, i, snapshot); + } } } void TWriteOperation::CommitOnComplete(TColumnShard& owner, const NOlap::TSnapshot& /*snapshot*/) const { - Y_ABORT_UNLESS(Status == EOperationStatus::Prepared); - owner.UpdateInsertTableCounters(); + Y_ABORT_UNLESS(Status == EOperationStatus::Prepared || InsertWriteIds.empty()); + if (!WritePortions) { + owner.UpdateInsertTableCounters(); + } else { + for (auto&& i : InsertWriteIds) { + owner.MutableIndexAs().MutableGranuleVerified(PathId).CommitPortionOnComplete( + i, owner.MutableIndexAs()); + } + } } void TWriteOperation::OnWriteFinish( @@ -91,12 +106,17 @@ void TWriteOperation::ToProto(NKikimrTxColumnShard::TInternalOperationData& prot proto.AddInternalWriteIds((ui64)writeId); } proto.SetModificationType((ui32)ModificationType); + proto.SetWritePortions(WritePortions); + proto.SetPathId(PathId); } void TWriteOperation::FromProto(const NKikimrTxColumnShard::TInternalOperationData& proto) { for (auto&& writeId : proto.GetInternalWriteIds()) { InsertWriteIds.push_back(TInsertWriteId(writeId)); } + WritePortions = proto.GetWritePortions(); + PathId = proto.GetPathId(); + AFL_VERIFY(!WritePortions || PathId); if (proto.HasModificationType()) { ModificationType = (NEvWrite::EModificationType)proto.GetModificationType(); } else { @@ -105,18 +125,30 @@ void TWriteOperation::FromProto(const NKikimrTxColumnShard::TInternalOperationDa } void TWriteOperation::AbortOnExecute(TColumnShard& owner, NTabletFlatExecutor::TTransactionContext& txc) const { - Y_ABORT_UNLESS(Status == EOperationStatus::Prepared); - + Y_ABORT_UNLESS(Status != EOperationStatus::Draft); + StopWriting(); TBlobGroupSelector dsGroupSelector(owner.Info()); NOlap::TDbWrapper dbTable(txc.DB, &dsGroupSelector); - THashSet writeIds; - writeIds.insert(InsertWriteIds.begin(), InsertWriteIds.end()); - owner.InsertTable->Abort(dbTable, writeIds); + if (!WritePortions) { + THashSet writeIds; + writeIds.insert(InsertWriteIds.begin(), InsertWriteIds.end()); + owner.InsertTable->Abort(dbTable, writeIds); + } else { + for (auto&& i : InsertWriteIds) { + owner.MutableIndexAs().MutableGranuleVerified(PathId).AbortPortionOnExecute(txc, i, owner.GetCurrentSnapshotForInternalModification()); + } + } } -void TWriteOperation::AbortOnComplete(TColumnShard& /*owner*/) const { - Y_ABORT_UNLESS(Status == EOperationStatus::Prepared); +void TWriteOperation::AbortOnComplete(TColumnShard& owner) const { + Y_ABORT_UNLESS(Status != EOperationStatus::Draft); + if (WritePortions) { + for (auto&& i : InsertWriteIds) { + owner.MutableIndexAs().MutableGranuleVerified(PathId).AbortPortionOnComplete( + i, owner.MutableIndexAs()); + } + } } } // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/columnshard/operations/write.h b/ydb/core/tx/columnshard/operations/write.h index 5251402347c0..416dc7d3ec2e 100644 --- a/ydb/core/tx/columnshard/operations/write.h +++ b/ydb/core/tx/columnshard/operations/write.h @@ -1,8 +1,11 @@ #pragma once +#include "common/context.h" + #include #include #include +#include #include #include #include @@ -37,14 +40,16 @@ enum class EOperationStatus : ui32 { enum class EOperationBehaviour : ui32 { Undefined = 1, - InTxWrite = 2, WriteWithLock = 3, CommitWriteLock = 4, AbortWriteLock = 5, NoTxWrite = 6 }; -class TWriteOperation { +class TWriteOperation: public TMonitoringObjectsCounter { +private: + YDB_READONLY(TString, Identifier, TGUID::CreateTimebased().AsGuidString()); + YDB_READONLY(ui64, PathId, 0); YDB_READONLY(EOperationStatus, Status, EOperationStatus::Draft); YDB_READONLY_DEF(TInstant, CreatedAt); YDB_READONLY_DEF(TOperationWriteId, WriteId); @@ -54,21 +59,33 @@ class TWriteOperation { YDB_ACCESSOR(EOperationBehaviour, Behaviour, EOperationBehaviour::Undefined); YDB_READONLY_DEF(std::optional, GranuleShardingVersionId); YDB_READONLY(NEvWrite::EModificationType, ModificationType, NEvWrite::EModificationType::Upsert); + bool WritePortions = false; + const std::shared_ptr Activity = std::make_shared(1); public: using TPtr = std::shared_ptr; - TWriteOperation(const TOperationWriteId writeId, const ui64 lockId, const ui64 cookie, const EOperationStatus& status, const TInstant createdAt, - const std::optional granuleShardingVersionId, const NEvWrite::EModificationType mType); + void StopWriting() const { + *Activity = 0; + } + + TWriteOperation(const ui64 pathId, const TOperationWriteId writeId, const ui64 lockId, const ui64 cookie, const EOperationStatus& status, + const TInstant createdAt, const std::optional granuleShardingVersionId, const NEvWrite::EModificationType mType, + const bool writePortions); - void Start(TColumnShard& owner, const ui64 tableId, const NEvWrite::IDataContainer::TPtr& data, const NActors::TActorId& source, - const std::shared_ptr& schema, const TActorContext& ctx, const NOlap::TSnapshot& applyToSnapshot); - void OnWriteFinish(NTabletFlatExecutor::TTransactionContext& txc, const std::vector& insertWriteIds, const bool ephemeralFlag); + void Start( + TColumnShard& owner, const NEvWrite::IDataContainer::TPtr& data, const NActors::TActorId& source, const NOlap::TWritingContext& context); + void OnWriteFinish( + NTabletFlatExecutor::TTransactionContext& txc, const std::vector& insertWriteIds, const bool ephemeralFlag); void CommitOnExecute(TColumnShard& owner, NTabletFlatExecutor::TTransactionContext& txc, const NOlap::TSnapshot& snapshot) const; void CommitOnComplete(TColumnShard& owner, const NOlap::TSnapshot& snapshot) const; void AbortOnExecute(TColumnShard& owner, NTabletFlatExecutor::TTransactionContext& txc) const; void AbortOnComplete(TColumnShard& owner) const; + std::shared_ptr GetActivityChecker() const { + return Activity; + } + void Out(IOutputStream& out) const { out << "write_id=" << (ui64)WriteId << ";lock_id=" << LockId; } diff --git a/ydb/core/tx/columnshard/operations/write_data.cpp b/ydb/core/tx/columnshard/operations/write_data.cpp index 0f7440aaf5e4..452c539c3682 100644 --- a/ydb/core/tx/columnshard/operations/write_data.cpp +++ b/ydb/core/tx/columnshard/operations/write_data.cpp @@ -1,20 +1,20 @@ #include "write_data.h" +#include #include - namespace NKikimr::NColumnShard { bool TArrowData::Parse(const NKikimrDataEvents::TEvWrite_TOperation& proto, const NEvWrite::IPayloadReader& payload) { - if(proto.GetPayloadFormat() != NKikimrDataEvents::FORMAT_ARROW) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "invalid_payload_format")("payload_format", (ui64)proto.GetPayloadFormat()); + if (proto.GetPayloadFormat() != NKikimrDataEvents::FORMAT_ARROW) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "invalid_payload_format")("payload_format", (ui64)proto.GetPayloadFormat()); return false; } IncomingData = payload.GetDataFromPayload(proto.GetPayloadIndex()); if (proto.HasType()) { auto type = TEnumOperator::DeserializeFromProto(proto.GetType()); if (!type) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "invalid_modification_type")("proto", proto.DebugString()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "invalid_modification_type")("proto", proto.DebugString()); return false; } ModificationType = *type; @@ -48,8 +48,9 @@ TConclusion> TArrowData::ExtractBatch() { } else { result = NArrow::DeserializeBatch(IncomingData, std::make_shared(BatchSchema->GetSchema()->fields())); } - - IncomingData = ""; + + TString emptyString; + std::swap(IncomingData, emptyString); return result; } @@ -65,16 +66,25 @@ bool TProtoArrowData::ParseFromProto(const NKikimrTxColumnShard::TEvWrite& proto if (proto.HasMeta()) { const auto& incomingDataScheme = proto.GetMeta().GetSchema(); if (incomingDataScheme.empty() || proto.GetMeta().GetFormat() != NKikimrTxColumnShard::FORMAT_ARROW) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "invalid_data_format"); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "invalid_data_format"); return false; } ArrowSchema = NArrow::DeserializeSchema(incomingDataScheme); if (!ArrowSchema) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "cannot_deserialize_data"); return false; } } OriginalDataSize = IncomingData.size(); - return !IncomingData.empty() && IncomingData.size() <= NColumnShard::TLimits::GetMaxBlobSize(); + if (IncomingData.empty()) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "empty_data"); + return false; + } + if (NColumnShard::TLimits::GetMaxBlobSize() < IncomingData.size() && !AppDataVerified().FeatureFlags.GetEnableWritePortionsOnInsert()) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "too_big_blob"); + return false; + } + return true; } TConclusion> TProtoArrowData::ExtractBatch() { @@ -88,4 +98,4 @@ ui64 TProtoArrowData::GetSchemaVersion() const { return IndexSchema->GetVersion(); } -} +} // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/columnshard/operations/ya.make b/ydb/core/tx/columnshard/operations/ya.make index c0bd3f234b78..84dbd2f5b671 100644 --- a/ydb/core/tx/columnshard/operations/ya.make +++ b/ydb/core/tx/columnshard/operations/ya.make @@ -4,6 +4,7 @@ SRCS( write.cpp write_data.cpp manager.cpp + events.cpp ) PEERDIR( @@ -15,6 +16,7 @@ PEERDIR( ydb/core/tx/columnshard/transactions/locks ydb/core/tx/columnshard/operations/batch_builder ydb/core/tx/columnshard/operations/slice_builder + ydb/core/tx/columnshard/operations/common ) END() diff --git a/ydb/core/tx/columnshard/resource_subscriber/container.cpp b/ydb/core/tx/columnshard/resource_subscriber/container.cpp new file mode 100644 index 000000000000..93b6f4f2d1c4 --- /dev/null +++ b/ydb/core/tx/columnshard/resource_subscriber/container.cpp @@ -0,0 +1,3 @@ +#include "container.h" + +namespace NKikimr::NOlap::NResourceBroker::NSubscribe {} diff --git a/ydb/core/tx/columnshard/resource_subscriber/container.h b/ydb/core/tx/columnshard/resource_subscriber/container.h new file mode 100644 index 000000000000..d52a882ab400 --- /dev/null +++ b/ydb/core/tx/columnshard/resource_subscriber/container.h @@ -0,0 +1,61 @@ +#pragma once + +#include "task.h" + +namespace NKikimr::NOlap::NResourceBroker::NSubscribe { + +template +class TResourceContainer: private TMoveOnly { +private: + std::optional Value; + std::shared_ptr ResourcesGuard; + + TResourceContainer(T&& value) + : Value(std::move(value)) { + } + +public: + const T& GetValue() const { + AFL_VERIFY(Value); + return *Value; + } + + T ExtractValue() { + AFL_VERIFY(Value); + T value = std::move(*Value); + Value.reset(); + return value; + } + + std::shared_ptr ExtractResourcesGuard() { + AFL_VERIFY(ResourcesGuard); + return std::move(ResourcesGuard); + } + + TResourceContainer(TResourceContainer&& other) + : Value(std::move(other.Value)) + , ResourcesGuard(std::move(other.ResourcesGuard)) { + } + TResourceContainer& operator=(TResourceContainer&& other) { + std::swap(Value, other.Value); + std::swap(ResourcesGuard, other.ResourcesGuard); + return *this; + } + + TResourceContainer(T&& value, std::shared_ptr&& guard) + : Value(std::move(value)) + , ResourcesGuard(std::move(guard)) { + AFL_VERIFY(ResourcesGuard); + } + + ~TResourceContainer() { + if (!Value) { + AFL_VERIFY(!ResourcesGuard); + } + } + + static TResourceContainer BuildForTest(T&& value) { + return TResourceContainer(std::move(value)); + } +}; +} // namespace NKikimr::NOlap::NResourceBroker::NSubscribe diff --git a/ydb/core/tx/columnshard/resource_subscriber/ya.make b/ydb/core/tx/columnshard/resource_subscriber/ya.make index ca14869e77f3..01be1eb5bc57 100644 --- a/ydb/core/tx/columnshard/resource_subscriber/ya.make +++ b/ydb/core/tx/columnshard/resource_subscriber/ya.make @@ -5,6 +5,7 @@ SRCS( counters.cpp task.cpp events.cpp + container.cpp ) PEERDIR( diff --git a/ydb/core/tx/columnshard/splitter/abstract/chunk_meta.cpp b/ydb/core/tx/columnshard/splitter/abstract/chunk_meta.cpp index 1e66bfb46e07..b0f368afb1aa 100644 --- a/ydb/core/tx/columnshard/splitter/abstract/chunk_meta.cpp +++ b/ydb/core/tx/columnshard/splitter/abstract/chunk_meta.cpp @@ -5,20 +5,11 @@ namespace NKikimr::NOlap { TSimpleChunkMeta::TSimpleChunkMeta( - const std::shared_ptr& column, const bool needMax, const bool isSortedColumn) { + const std::shared_ptr& column) { Y_ABORT_UNLESS(column); Y_ABORT_UNLESS(column->GetRecordsCount()); - NumRows = column->GetRecordsCount(); + RecordsCount = column->GetRecordsCount(); RawBytes = column->GetRawSizeVerified(); - - if (needMax) { - if (!isSortedColumn) { - Max = column->GetMaxScalar(); - } else { - Max = column->GetScalar(column->GetRecordsCount() - 1); - } -// AFL_VERIFY(Max); - } } } diff --git a/ydb/core/tx/columnshard/splitter/abstract/chunk_meta.h b/ydb/core/tx/columnshard/splitter/abstract/chunk_meta.h index 526a2a037967..6b9964a5d91e 100644 --- a/ydb/core/tx/columnshard/splitter/abstract/chunk_meta.h +++ b/ydb/core/tx/columnshard/splitter/abstract/chunk_meta.h @@ -14,33 +14,22 @@ namespace NKikimr::NOlap { class TSimpleChunkMeta { protected: - std::shared_ptr Max; - ui32 NumRows = 0; + ui32 RecordsCount = 0; ui32 RawBytes = 0; TSimpleChunkMeta() = default; public: - TSimpleChunkMeta(const std::shared_ptr& column, const bool needMinMax, const bool isSortedColumn); + TSimpleChunkMeta(const std::shared_ptr& column); ui64 GetMetadataSize() const { - return sizeof(ui32) + sizeof(ui32) + 8 * 3 * 2; + return sizeof(ui32) + sizeof(ui32); } - std::shared_ptr GetMax() const { - return Max; - } - ui32 GetNumRows() const { - return NumRows; - } ui32 GetRecordsCount() const { - return NumRows; + return RecordsCount; } ui32 GetRawBytes() const { return RawBytes; } - bool HasMax() const noexcept { - return Max.get(); - } - }; } diff --git a/ydb/core/tx/columnshard/splitter/abstract/chunks.h b/ydb/core/tx/columnshard/splitter/abstract/chunks.h index d0300915f098..e3747bc80b4d 100644 --- a/ydb/core/tx/columnshard/splitter/abstract/chunks.h +++ b/ydb/core/tx/columnshard/splitter/abstract/chunks.h @@ -13,7 +13,7 @@ class TSplitterCounters; namespace NKikimr::NOlap { class TPortionInfo; -class TPortionInfoConstructor; +class TPortionAccessorConstructor; class TSimpleColumnInfo; class IPortionDataChunk { @@ -34,8 +34,8 @@ class IPortionDataChunk { virtual std::shared_ptr DoGetFirstScalar() const = 0; virtual std::shared_ptr DoGetLastScalar() const = 0; - virtual void DoAddIntoPortionBeforeBlob(const TBlobRangeLink16& bRange, TPortionInfoConstructor& portionInfo) const = 0; - virtual void DoAddInplaceIntoPortion(TPortionInfoConstructor& /*portionInfo*/) const { + virtual void DoAddIntoPortionBeforeBlob(const TBlobRangeLink16& bRange, TPortionAccessorConstructor& portionInfo) const = 0; + virtual void DoAddInplaceIntoPortion(TPortionAccessorConstructor& /*portionInfo*/) const { AFL_VERIFY(false)("problem", "implemented only in index chunks"); } virtual std::shared_ptr DoCopyWithAnotherBlob(TString&& /*data*/, const TSimpleColumnInfo& /*columnInfo*/) const { @@ -126,12 +126,12 @@ class IPortionDataChunk { } } - void AddIntoPortionBeforeBlob(const TBlobRangeLink16& bRange, TPortionInfoConstructor& portionInfo) const { + void AddIntoPortionBeforeBlob(const TBlobRangeLink16& bRange, TPortionAccessorConstructor& portionInfo) const { AFL_VERIFY(!bRange.IsValid()); return DoAddIntoPortionBeforeBlob(bRange, portionInfo); } - void AddInplaceIntoPortion(TPortionInfoConstructor& portionInfo) const { + void AddInplaceIntoPortion(TPortionAccessorConstructor& portionInfo) const { return DoAddInplaceIntoPortion(portionInfo); } }; diff --git a/ydb/core/tx/columnshard/splitter/batch_slice.cpp b/ydb/core/tx/columnshard/splitter/batch_slice.cpp index 7f6cc05c1e7b..1e33b4bf9777 100644 --- a/ydb/core/tx/columnshard/splitter/batch_slice.cpp +++ b/ydb/core/tx/columnshard/splitter/batch_slice.cpp @@ -99,6 +99,8 @@ bool TGeneralSerializedSlice::GroupBlobsImpl(const NSplitter::TGroupFeatures& fe chunksInProgress.PopFront(i); hasNoSplitChanges = true; } else { + // in this case chunksInProgress[i] size >= Max - Min for case nextPartSize >= features.GetSplitSettings().GetMaxBlobSize() + // in this case chunksInProgress[i] size >= Max - 2 * Min for case nextOtherSize < features.GetSplitSettings().GetMinBlobSize() Y_ABORT_UNLESS((i64)chunksInProgress[i]->GetPackedSize() > features.GetSplitSettings().GetMinBlobSize() - partSize); Y_ABORT_UNLESS(otherSize - (features.GetSplitSettings().GetMinBlobSize() - partSize) >= features.GetSplitSettings().GetMinBlobSize()); diff --git a/ydb/core/tx/columnshard/splitter/batch_slice.h b/ydb/core/tx/columnshard/splitter/batch_slice.h index f1b019544d8c..9dc256df8280 100644 --- a/ydb/core/tx/columnshard/splitter/batch_slice.h +++ b/ydb/core/tx/columnshard/splitter/batch_slice.h @@ -33,12 +33,6 @@ class TDefaultSchemaDetails: public NArrow::NSplitter::ISchemaDetailInfo { virtual std::shared_ptr GetField(const ui32 columnId) const override { return Schema->GetFieldByColumnIdOptional(columnId); } - virtual bool NeedMinMaxForColumn(const ui32 columnId) const override { - return Schema->GetIndexInfo().GetMinMaxIdxColumns().contains(columnId); - } - virtual bool IsSortedColumn(const ui32 columnId) const override { - return Schema->GetIndexInfo().IsSortedColumn(columnId); - } virtual std::optional GetColumnSerializationStats(const ui32 columnId) const override { auto stats = Stats->GetColumnInfo(columnId); diff --git a/ydb/core/tx/columnshard/splitter/chunks.cpp b/ydb/core/tx/columnshard/splitter/chunks.cpp index 8ebbd736c12c..3b71e5f2a70e 100644 --- a/ydb/core/tx/columnshard/splitter/chunks.cpp +++ b/ydb/core/tx/columnshard/splitter/chunks.cpp @@ -1,11 +1,14 @@ #include "chunks.h" + #include +#include +#include #include -#include namespace NKikimr::NOlap { -std::vector> IPortionColumnChunk::DoInternalSplit(const TColumnSaver& saver, const std::shared_ptr& counters, const std::vector& splitSizes) const { +std::vector> IPortionColumnChunk::DoInternalSplit( + const TColumnSaver& saver, const std::shared_ptr& counters, const std::vector& splitSizes) const { ui64 sumSize = 0; for (auto&& i : splitSizes) { sumSize += i; @@ -25,10 +28,10 @@ std::vector> IPortionColumnChunk::DoInternalS return result; } -void IPortionColumnChunk::DoAddIntoPortionBeforeBlob(const TBlobRangeLink16& bRange, TPortionInfoConstructor& portionInfo) const { +void IPortionColumnChunk::DoAddIntoPortionBeforeBlob(const TBlobRangeLink16& bRange, TPortionAccessorConstructor& portionInfo) const { AFL_VERIFY(!bRange.IsValid()); TColumnRecord rec(GetChunkAddressVerified(), bRange, BuildSimpleChunkMeta()); portionInfo.AppendOneChunkColumn(std::move(rec)); } -} +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/splitter/chunks.h b/ydb/core/tx/columnshard/splitter/chunks.h index 021ea7e47a4f..315fc18604e8 100644 --- a/ydb/core/tx/columnshard/splitter/chunks.h +++ b/ydb/core/tx/columnshard/splitter/chunks.h @@ -25,7 +25,7 @@ class IPortionColumnChunk : public IPortionDataChunk { return DoGetRecordsCountImpl(); } - virtual void DoAddIntoPortionBeforeBlob(const TBlobRangeLink16& bRange, TPortionInfoConstructor& portionInfo) const override; + virtual void DoAddIntoPortionBeforeBlob(const TBlobRangeLink16& bRange, TPortionAccessorConstructor& portionInfo) const override; virtual std::vector> DoInternalSplitImpl(const TColumnSaver& saver, const std::shared_ptr& counters, const std::vector& splitSizes) const = 0; @@ -129,8 +129,7 @@ class TChunkedBatchReader { bool IsCorrectFlag = true; public: TChunkedBatchReader(const std::vector& columnReaders) - : Columns(columnReaders) - { + : Columns(columnReaders) { AFL_VERIFY(Columns.size()); for (auto&& i : Columns) { AFL_VERIFY(i.IsCorrect()); @@ -148,6 +147,16 @@ class TChunkedBatchReader { } } + bool ReadNext(const ui32 count) { + for (ui32 i = 0; i < count; ++i) { + if (!ReadNext()) { + AFL_VERIFY(i + 1 == count); + return false; + } + } + return true; + } + bool ReadNext() { std::optional result; for (auto&& i : Columns) { diff --git a/ydb/core/tx/columnshard/splitter/settings.h b/ydb/core/tx/columnshard/splitter/settings.h index d370a5206047..208227cdb628 100644 --- a/ydb/core/tx/columnshard/splitter/settings.h +++ b/ydb/core/tx/columnshard/splitter/settings.h @@ -14,8 +14,10 @@ namespace NKikimr::NOlap::NSplitter { class TSplitSettings { private: +// DefaultMaxBlobSize - 2 * DefaultMinBlobSize have to been enought to "guarantee" records count > 1 through blobs splitting static const inline i64 DefaultMaxBlobSize = 8 * 1024 * 1024; - static const inline i64 DefaultMinBlobSize = 4 * 1024 * 1024; + static const inline i64 DefaultMinBlobSize = 3 * 1024 * 1024; + static const inline i64 DefaultMinRecordsCount = 10000; static const inline i64 DefaultMaxPortionSize = 6 * DefaultMaxBlobSize; YDB_ACCESSOR(i64, MaxBlobSize, DefaultMaxBlobSize); @@ -24,6 +26,10 @@ class TSplitSettings { YDB_ACCESSOR(i64, MaxPortionSize, DefaultMaxPortionSize); public: + static TSplitSettings BuildForTests(const double scaleKff = 1) { + return TSplitSettings().SetMaxBlobSize(1024 * 10 * scaleKff).SetMinBlobSize(256 * 10 * scaleKff); + } + ui64 GetExpectedRecordsCountOnPage() const { return 1.5 * MinRecordsCount; } diff --git a/ydb/core/tx/columnshard/splitter/ut/ut_splitter.cpp b/ydb/core/tx/columnshard/splitter/ut/ut_splitter.cpp index 7ca04ee36933..fc3d44c286e8 100644 --- a/ydb/core/tx/columnshard/splitter/ut/ut_splitter.cpp +++ b/ydb/core/tx/columnshard/splitter/ut/ut_splitter.cpp @@ -30,13 +30,6 @@ Y_UNIT_TEST_SUITE(Splitter) { } public: - virtual bool NeedMinMaxForColumn(const ui32 /*columnId*/) const override { - return true; - } - virtual bool IsSortedColumn(const ui32 /*columnId*/) const override { - return false; - } - virtual std::optional GetColumnSerializationStats( const ui32 /*columnId*/) const override { return {}; diff --git a/ydb/core/tx/columnshard/splitter/ut/ya.make b/ydb/core/tx/columnshard/splitter/ut/ya.make index c7a6a0be4c0c..edd65010f571 100644 --- a/ydb/core/tx/columnshard/splitter/ut/ya.make +++ b/ydb/core/tx/columnshard/splitter/ut/ya.make @@ -20,6 +20,7 @@ PEERDIR( ydb/core/tx/columnshard/engines/storage/chunks ydb/core/tx/columnshard/engines/storage/indexes/max ydb/core/tx/columnshard/engines/storage/indexes/count_min_sketch + ydb/core/tx/columnshard/data_accessor ydb/core/tx ydb/core/mind ydb/library/yql/minikql/comp_nodes/llvm14 diff --git a/ydb/core/tx/columnshard/tables_manager.cpp b/ydb/core/tx/columnshard/tables_manager.cpp index a138e26d74b7..cd6eda199c41 100644 --- a/ydb/core/tx/columnshard/tables_manager.cpp +++ b/ydb/core/tx/columnshard/tables_manager.cpp @@ -1,6 +1,6 @@ +#include "columnshard_schema.h" #include "tables_manager.h" -#include "columnshard_schema.h" #include "engines/column_engine_logs.h" #include "transactions/transactions/tx_add_sharding_info.h" @@ -44,65 +44,71 @@ bool TTablesManager::FillMonitoringReport(NTabletFlatExecutor::TTransactionConte } bool TTablesManager::InitFromDB(NIceDb::TNiceDb& db) { - using TTableVersionsInfo = TVersionedSchema; - - THashMap schemaPresets; - THashMap tableVersions; { + TLoadTimeSignals::TLoadTimer timer = LoadTimeCounters->TableLoadTimeCounters.StartGuard(); TMemoryProfileGuard g("TTablesManager/InitFromDB::Tables"); auto rowset = db.Table().Select(); if (!rowset.IsReady()) { + timer.AddLoadingFail(); return false; } while (!rowset.EndOfSet()) { TTableInfo table; if (!table.InitFromDB(rowset)) { + timer.AddLoadingFail(); return false; } if (table.IsDropped()) { - PathsToDrop.insert(table.GetPathId()); + AFL_VERIFY(PathsToDrop[table.GetDropVersionVerified()].emplace(table.GetPathId()).second); } - AFL_VERIFY(tableVersions.emplace(table.GetPathId(), TTableVersionsInfo()).second); AFL_VERIFY(Tables.emplace(table.GetPathId(), std::move(table)).second); if (!rowset.Next()) { + timer.AddLoadingFail(); return false; } } } - bool isFakePresetOnly = true; + std::optional preset; { + TLoadTimeSignals::TLoadTimer timer = LoadTimeCounters->SchemaPresetLoadTimeCounters.StartGuard(); TMemoryProfileGuard g("TTablesManager/InitFromDB::SchemaPresets"); auto rowset = db.Table().Select(); if (!rowset.IsReady()) { + timer.AddLoadingFail(); return false; } - while (!rowset.EndOfSet()) { - TSchemaPreset preset; - preset.InitFromDB(rowset); + if (!rowset.EndOfSet()) { + preset = TSchemaPreset(); + preset->InitFromDB(rowset); - if (preset.IsStandaloneTable()) { - Y_VERIFY_S(!preset.GetName(), "Preset name: " + preset.GetName()); + if (preset->IsStandaloneTable()) { + Y_VERIFY_S(!preset->GetName(), "Preset name: " + preset->GetName()); + AFL_VERIFY(!preset->Id); } else { - Y_VERIFY_S(preset.GetName() == "default", "Preset name: " + preset.GetName()); - isFakePresetOnly = false; + Y_VERIFY_S(preset->GetName() == "default", "Preset name: " + preset->GetName()); + AFL_VERIFY(preset->Id); } - AFL_VERIFY(schemaPresets.emplace(preset.GetId(), preset).second); - AFL_VERIFY(SchemaPresetsIds.emplace(preset.GetId()).second); + AFL_VERIFY(SchemaPresetsIds.emplace(preset->GetId()).second); if (!rowset.Next()) { + timer.AddLoadingFail(); return false; } } + + AFL_VERIFY(rowset.EndOfSet())("reson", "multiple_presets_not_supported"); } { + TLoadTimeSignals::TLoadTimer timer = LoadTimeCounters->TableVersionsLoadTimeCounters.StartGuard(); TMemoryProfileGuard g("TTablesManager/InitFromDB::Versions"); auto rowset = db.Table().Select(); if (!rowset.IsReady()) { + timer.AddLoadingFail(); return false; } @@ -111,106 +117,107 @@ bool TTablesManager::InitFromDB(NIceDb::TNiceDb& db) { const ui64 pathId = rowset.GetValue(); Y_ABORT_UNLESS(Tables.contains(pathId)); NOlap::TSnapshot version( - rowset.GetValue(), - rowset.GetValue()); + rowset.GetValue(), rowset.GetValue()); auto& table = Tables[pathId]; - auto& versionsInfo = tableVersions[pathId]; NKikimrTxColumnShard::TTableVersionInfo versionInfo; Y_ABORT_UNLESS(versionInfo.ParseFromString(rowset.GetValue())); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "load_table_version")("path_id", pathId)("snapshot", version)("version", versionInfo.HasSchema() ? versionInfo.GetSchema().GetVersion() : -1); - Y_ABORT_UNLESS(schemaPresets.contains(versionInfo.GetSchemaPresetId())); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "load_table_version")("path_id", pathId)("snapshot", version); + AFL_VERIFY(preset); + AFL_VERIFY(preset->Id == versionInfo.GetSchemaPresetId())("preset", preset->Id)("table", versionInfo.GetSchemaPresetId()); if (!table.IsDropped()) { auto& ttlSettings = versionInfo.GetTtlSettings(); - if (ttlSettings.HasEnabled()) { - auto vIt = lastVersion.find(pathId); - if (vIt == lastVersion.end() || vIt->second < version) { - TTtl::TDescription description(ttlSettings.GetEnabled()); - Ttl.SetPathTtl(pathId, std::move(description)); - lastVersion.emplace(pathId, version); + auto vIt = lastVersion.find(pathId); + if (vIt == lastVersion.end()) { + vIt = lastVersion.emplace(pathId, version).first; + } + if (vIt->second <= version) { + if (ttlSettings.HasEnabled()) { + NOlap::TTiering deserializedTtl; + AFL_VERIFY(deserializedTtl.DeserializeFromProto(ttlSettings.GetEnabled()).IsSuccess()); + Ttl[pathId] = std::move(deserializedTtl); + } else { + Ttl.erase(pathId); } + vIt->second = version; } } table.AddVersion(version); - versionsInfo.AddVersion(version, versionInfo); if (!rowset.Next()) { + timer.AddLoadingFail(); return false; } } } { + TLoadTimeSignals::TLoadTimer timer = LoadTimeCounters->SchemaPresetVersionsLoadTimeCounters.StartGuard(); TMemoryProfileGuard g("TTablesManager/InitFromDB::PresetVersions"); + auto rowset = db.Table().Select(); if (!rowset.IsReady()) { + timer.AddLoadingFail(); return false; } while (!rowset.EndOfSet()) { const ui32 id = rowset.GetValue(); - Y_ABORT_UNLESS(schemaPresets.contains(id)); - auto& preset = schemaPresets[id]; + AFL_VERIFY(preset); + AFL_VERIFY(preset->Id == id)("preset", preset->Id)("schema", id); NOlap::TSnapshot version( - rowset.GetValue(), - rowset.GetValue()); + rowset.GetValue(), rowset.GetValue()); TSchemaPreset::TSchemaPresetVersionInfo info; Y_ABORT_UNLESS(info.ParseFromString(rowset.GetValue())); - AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("event", "load_preset")("preset_id", id)("snapshot", version)("version", info.HasSchema() ? info.GetSchema().GetVersion() : -1); - preset.AddVersion(version, info); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "load_preset")("preset_id", id)("snapshot", version)( + "version", info.HasSchema() ? info.GetSchema().GetVersion() : -1); + + AFL_VERIFY(info.HasSchema()); + AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("event", "index_schema")("preset_id", id)("snapshot", version)( + "version", info.GetSchema().GetVersion()); + NOlap::IColumnEngine::TSchemaInitializationData schemaInitializationData(info); + if (!PrimaryIndex) { + PrimaryIndex = std::make_unique( + TabletId, SchemaObjectsCache, DataAccessorsManager, StoragesManager, version, preset->Id, schemaInitializationData); + } else if (PrimaryIndex->GetVersionedIndex().IsEmpty() || + info.GetSchema().GetVersion() > PrimaryIndex->GetVersionedIndex().GetLastSchema()->GetVersion()) { + PrimaryIndex->RegisterSchemaVersion(version, preset->Id, schemaInitializationData); + } else { + PrimaryIndex->RegisterOldSchemaVersion(version, preset->Id, schemaInitializationData); + } + if (!rowset.Next()) { + timer.AddLoadingFail(); return false; } } } TMemoryProfileGuard g("TTablesManager/InitFromDB::Other"); - for (const auto& [id, preset] : schemaPresets) { - if (isFakePresetOnly) { - Y_ABORT_UNLESS(id == 0); - } else { - Y_ABORT_UNLESS(id > 0); - } - for (const auto& [version, schemaInfo] : preset.GetVersionsById()) { - if (schemaInfo.HasSchema()) { - AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("event", "index_schema")("preset_id", id)("snapshot", version)("version", schemaInfo.GetSchema().GetVersion()); - if (!PrimaryIndex) { - PrimaryIndex = std::make_unique(TabletId, StoragesManager, preset.GetMinVersionForId(schemaInfo.GetSchema().GetVersion()), schemaInfo.GetSchema()); - } else { - PrimaryIndex->RegisterSchemaVersion(preset.GetMinVersionForId(schemaInfo.GetSchema().GetVersion()), schemaInfo.GetSchema()); - } - } - } - } for (auto&& i : Tables) { PrimaryIndex->RegisterTable(i.first); } return true; } -bool TTablesManager::LoadIndex(NOlap::TDbWrapper& idxDB) { - if (PrimaryIndex) { - if (!PrimaryIndex->Load(idxDB)) { - return false; - } - } - return true; -} - -bool TTablesManager::HasTable(const ui64 pathId, bool withDeleted) const { +bool TTablesManager::HasTable(const ui64 pathId, const bool withDeleted, const std::optional minReadSnapshot) const { auto it = Tables.find(pathId); if (it == Tables.end()) { return false; } - if (it->second.IsDropped()) { + if (it->second.IsDropped(minReadSnapshot)) { return withDeleted; } return true; } -bool TTablesManager::IsReadyForWrite(const ui64 pathId) const { - return HasPrimaryIndex() && HasTable(pathId); +bool TTablesManager::IsReadyForStartWrite(const ui64 pathId, const bool withDeleted) const { + return HasPrimaryIndex() && HasTable(pathId, withDeleted); +} + +bool TTablesManager::IsReadyForFinishWrite(const ui64 pathId, const NOlap::TSnapshot& minReadSnapshot) const { + return HasPrimaryIndex() && HasTable(pathId, false, minReadSnapshot); } bool TTablesManager::HasPreset(const ui32 presetId) const { @@ -223,10 +230,7 @@ const TTableInfo& TTablesManager::GetTable(const ui64 pathId) const { } ui64 TTablesManager::GetMemoryUsage() const { - ui64 memory = - Tables.size() * sizeof(TTableInfo) + - PathsToDrop.size() * sizeof(ui64) + - Ttl.PathsCount() * sizeof(TTtl::TDescription); + ui64 memory = Tables.size() * sizeof(TTableInfo) + PathsToDrop.size() * sizeof(ui64) + Ttl.size() * sizeof(NOlap::TTiering); if (PrimaryIndex) { memory += PrimaryIndex->MemoryUsage(); } @@ -237,8 +241,8 @@ void TTablesManager::DropTable(const ui64 pathId, const NOlap::TSnapshot& versio AFL_VERIFY(Tables.contains(pathId)); auto& table = Tables[pathId]; table.SetDropVersion(version); - PathsToDrop.insert(pathId); - Ttl.DropPathTtl(pathId); + AFL_VERIFY(PathsToDrop[version].emplace(pathId).second); + Ttl.erase(pathId); Schema::SaveTableDropVersion(db, pathId, version.GetPlanStep(), version.GetTxId()); } @@ -252,7 +256,7 @@ void TTablesManager::RegisterTable(TTableInfo&& table, NIceDb::TNiceDb& db) { Y_ABORT_UNLESS(!HasTable(table.GetPathId())); Y_ABORT_UNLESS(table.IsEmpty()); - Schema::SaveTableInfo(db, table.GetPathId(), table.GetTieringUsage()); + Schema::SaveTableInfo(db, table.GetPathId()); const ui64 pathId = table.GetPathId(); AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("method", "RegisterTable")("path_id", pathId); AFL_VERIFY(Tables.emplace(pathId, std::move(table)).second)("path_id", pathId)("size", Tables.size()); @@ -270,7 +274,8 @@ bool TTablesManager::RegisterSchemaPreset(const TSchemaPreset& schemaPreset, NIc return true; } -void TTablesManager::AddSchemaVersion(const ui32 presetId, const NOlap::TSnapshot& version, const NKikimrSchemeOp::TColumnTableSchema& schema, NIceDb::TNiceDb& db, std::shared_ptr& manager) { +void TTablesManager::AddSchemaVersion( + const ui32 presetId, const NOlap::TSnapshot& version, const NKikimrSchemeOp::TColumnTableSchema& schema, NIceDb::TNiceDb& db) { Y_ABORT_UNLESS(SchemaPresetsIds.contains(presetId)); TSchemaPreset::TSchemaPresetVersionInfo versionInfo; @@ -279,76 +284,98 @@ void TTablesManager::AddSchemaVersion(const ui32 presetId, const NOlap::TSnapsho versionInfo.SetSinceTxId(version.GetTxId()); *versionInfo.MutableSchema() = schema; + auto it = ActualSchemaForPreset.find(presetId); + if (it == ActualSchemaForPreset.end()) { + ActualSchemaForPreset.emplace(presetId, schema); + } else { + *versionInfo.MutableDiff() = NOlap::TSchemaDiffView::MakeSchemasDiff(it->second, schema); + it->second = schema; + } + Schema::SaveSchemaPresetVersionInfo(db, presetId, version, versionInfo); - if (versionInfo.HasSchema()) { - if (!PrimaryIndex) { - PrimaryIndex = std::make_unique(TabletId, StoragesManager, version, schema); - for (auto&& i : Tables) { - PrimaryIndex->RegisterTable(i.first); - } - if (manager->IsReady()) { - PrimaryIndex->OnTieringModified(manager, Ttl, {}); - } - } else { - PrimaryIndex->RegisterSchemaVersion(version, schema); - } - for (auto& columnName : Ttl.TtlColumns()) { - PrimaryIndex->GetVersionedIndex().GetLastSchema()->GetIndexInfo().CheckTtlColumn(columnName); + if (!PrimaryIndex) { + PrimaryIndex = std::make_unique(TabletId, SchemaObjectsCache, DataAccessorsManager, StoragesManager, + version, presetId, NOlap::IColumnEngine::TSchemaInitializationData(versionInfo)); + for (auto&& i : Tables) { + PrimaryIndex->RegisterTable(i.first); } + PrimaryIndex->OnTieringModified(Ttl); + } else { + PrimaryIndex->RegisterSchemaVersion(version, presetId, NOlap::IColumnEngine::TSchemaInitializationData(versionInfo)); } } -std::unique_ptr TTablesManager::CreateAddShardingInfoTx(TColumnShard& owner, const ui64 pathId, const ui64 versionId, const NSharding::TGranuleShardingLogicContainer& tabletShardingLogic) const { +std::unique_ptr TTablesManager::CreateAddShardingInfoTx( + TColumnShard& owner, const ui64 pathId, const ui64 versionId, const NSharding::TGranuleShardingLogicContainer& tabletShardingLogic) const { return std::make_unique(owner, tabletShardingLogic, pathId, versionId); } -void TTablesManager::AddTableVersion(const ui64 pathId, const NOlap::TSnapshot& version, const NKikimrTxColumnShard::TTableVersionInfo& versionInfo, NIceDb::TNiceDb& db, std::shared_ptr& manager) { +void TTablesManager::AddTableVersion(const ui64 pathId, const NOlap::TSnapshot& version, + const NKikimrTxColumnShard::TTableVersionInfo& versionInfo, const std::optional& schema, NIceDb::TNiceDb& db) { auto it = Tables.find(pathId); AFL_VERIFY(it != Tables.end()); auto& table = it->second; + bool isTtlModified = false; + if (versionInfo.HasTtlSettings()) { + isTtlModified = true; + const auto& ttlSettings = versionInfo.GetTtlSettings(); + if (ttlSettings.HasEnabled()) { + NOlap::TTiering deserializedTtl; + AFL_VERIFY(deserializedTtl.DeserializeFromProto(ttlSettings.GetEnabled()).IsSuccess()); + Ttl[pathId] = std::move(deserializedTtl); + } else { + Ttl.erase(pathId); + } + } + if (versionInfo.HasSchemaPresetId()) { + AFL_VERIFY(!schema); Y_ABORT_UNLESS(SchemaPresetsIds.contains(versionInfo.GetSchemaPresetId())); - } else if (versionInfo.HasSchema()) { + } else if (schema) { TSchemaPreset fakePreset; if (SchemaPresetsIds.empty()) { - TSchemaPreset fakePreset; Y_ABORT_UNLESS(RegisterSchemaPreset(fakePreset, db)); - AddSchemaVersion(fakePreset.GetId(), version, versionInfo.GetSchema(), db, manager); } else { Y_ABORT_UNLESS(SchemaPresetsIds.contains(fakePreset.GetId())); - AddSchemaVersion(fakePreset.GetId(), version, versionInfo.GetSchema(), db, manager); } + AddSchemaVersion(fakePreset.GetId(), version, *schema, db); } - if (versionInfo.HasTtlSettings()) { - const auto& ttlSettings = versionInfo.GetTtlSettings(); - if (ttlSettings.HasEnabled()) { - Ttl.SetPathTtl(pathId, TTtl::TDescription(ttlSettings.GetEnabled())); - } else { - Ttl.DropPathTtl(pathId); - } - if (PrimaryIndex && manager->IsReady()) { - PrimaryIndex->OnTieringModified(manager, Ttl, pathId); + if (isTtlModified) { + if (PrimaryIndex) { + if (auto findTtl = Ttl.FindPtr(pathId)) { + PrimaryIndex->OnTieringModified(*findTtl, pathId); + } else { + PrimaryIndex->OnTieringModified({}, pathId); + } } } Schema::SaveTableVersionInfo(db, pathId, version, versionInfo); table.AddVersion(version); } -TTablesManager::TTablesManager(const std::shared_ptr& storagesManager, const ui64 tabletId) +TTablesManager::TTablesManager(const std::shared_ptr& storagesManager, + const std::shared_ptr& dataAccessorsManager, + const std::shared_ptr& schemaCache, const ui64 tabletId) : StoragesManager(storagesManager) + , DataAccessorsManager(dataAccessorsManager) + , LoadTimeCounters(std::make_unique()) + , SchemaObjectsCache(schemaCache) , TabletId(tabletId) { + AFL_VERIFY(SchemaObjectsCache); } bool TTablesManager::TryFinalizeDropPathOnExecute(NTable::TDatabase& dbTable, const ui64 pathId) const { - auto itDrop = PathsToDrop.find(pathId); + const auto& itTable = Tables.find(pathId); + AFL_VERIFY(itTable != Tables.end())("problem", "No schema for path")("path_id", pathId); + auto itDrop = PathsToDrop.find(itTable->second.GetDropVersionVerified()); AFL_VERIFY(itDrop != PathsToDrop.end()); + AFL_VERIFY(itDrop->second.contains(pathId)); + AFL_VERIFY(!GetPrimaryIndexSafe().HasDataInPathId(pathId)); NIceDb::TNiceDb db(dbTable); NColumnShard::Schema::EraseTableInfo(db, pathId); - const auto& itTable = Tables.find(pathId); - AFL_VERIFY(itTable != Tables.end())("problem", "No schema for path")("path_id", pathId); for (auto&& tableVersion : itTable->second.GetVersions()) { NColumnShard::Schema::EraseTableVersionInfo(db, pathId, tableVersion); } @@ -356,13 +383,18 @@ bool TTablesManager::TryFinalizeDropPathOnExecute(NTable::TDatabase& dbTable, co } bool TTablesManager::TryFinalizeDropPathOnComplete(const ui64 pathId) { - auto itDrop = PathsToDrop.find(pathId); - AFL_VERIFY(itDrop != PathsToDrop.end()); - AFL_VERIFY(!GetPrimaryIndexSafe().HasDataInPathId(pathId)); - AFL_VERIFY(MutablePrimaryIndex().ErasePathId(pathId)); - PathsToDrop.erase(itDrop); const auto& itTable = Tables.find(pathId); AFL_VERIFY(itTable != Tables.end())("problem", "No schema for path")("path_id", pathId); + { + auto itDrop = PathsToDrop.find(itTable->second.GetDropVersionVerified()); + AFL_VERIFY(itDrop != PathsToDrop.end()); + AFL_VERIFY(itDrop->second.erase(pathId)); + if (itDrop->second.empty()) { + PathsToDrop.erase(itDrop); + } + } + AFL_VERIFY(!GetPrimaryIndexSafe().HasDataInPathId(pathId)); + AFL_VERIFY(MutablePrimaryIndex().ErasePathId(pathId)); Tables.erase(itTable); AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("method", "TryFinalizeDropPathOnComplete")("path_id", pathId)("size", Tables.size()); return true; diff --git a/ydb/core/tx/columnshard/tables_manager.h b/ydb/core/tx/columnshard/tables_manager.h index 1481882e41f8..f3fc89b2582f 100644 --- a/ydb/core/tx/columnshard/tables_manager.h +++ b/ydb/core/tx/columnshard/tables_manager.h @@ -1,24 +1,27 @@ #pragma once -#include "blobs_action/abstract/storages_manager.h" #include "columnshard_schema.h" -#include "columnshard_ttl.h" + +#include "blobs_action/abstract/storages_manager.h" +#include "data_accessor/manager.h" #include "engines/column_engine.h" -#include #include -#include #include +#include +#include +#include namespace NKikimr::NColumnShard { -template +template class TVersionedSchema { private: TMap Versions; TMap VersionsById; TMap MinVersionById; + public: bool IsEmpty() const { return VersionsById.empty(); @@ -28,6 +31,10 @@ class TVersionedSchema { return VersionsById; } + TMap& MutableVersionsById() { + return VersionsById; + } + NOlap::TSnapshot GetMinVersionForId(const ui64 sVersion) const { auto it = MinVersionById.find(sVersion); Y_ABORT_UNLESS(it != MinVersionById.end()); @@ -42,19 +49,21 @@ class TVersionedSchema { VersionsById.emplace(ssVersion, versionInfo); Y_ABORT_UNLESS(Versions.emplace(snapshot, ssVersion).second); - if (MinVersionById.contains(ssVersion)) { - MinVersionById.emplace(ssVersion, std::min(snapshot, MinVersionById.at(ssVersion))); - } else { + auto it = MinVersionById.find(ssVersion); + if (it == MinVersionById.end()) { MinVersionById.emplace(ssVersion, snapshot); + } else { + it->second = std::min(snapshot, it->second); } } }; -class TSchemaPreset : public TVersionedSchema { +class TSchemaPreset: public TVersionedSchema { public: using TSchemaPresetVersionInfo = NKikimrTxColumnShard::TSchemaPresetVersionInfo; ui32 Id = 0; TString Name; + public: bool IsStandaloneTable() const { return Id == 0; @@ -84,20 +93,10 @@ class TSchemaPreset : public TVersionedSchema DropVersion; YDB_READONLY_DEF(TSet, Versions); public: - const TString& GetTieringUsage() const { - return TieringUsage; - } - - TTableInfo& SetTieringUsage(const TString& data) { - TieringUsage = data; - return *this; - } - bool IsEmpty() const { return Versions.empty(); } @@ -106,7 +105,13 @@ class TTableInfo { return PathId; } + const NOlap::TSnapshot& GetDropVersionVerified() const { + AFL_VERIFY(DropVersion); + return *DropVersion; + } + void SetDropVersion(const NOlap::TSnapshot& version) { + AFL_VERIFY(!DropVersion)("exists", DropVersion->DebugString())("version", version.DebugString()); DropVersion = version; } @@ -114,22 +119,28 @@ class TTableInfo { Versions.insert(snapshot); } - bool IsDropped() const { - return DropVersion.has_value(); + bool IsDropped(const std::optional& minReadSnapshot = std::nullopt) const { + if (!DropVersion) { + return false; + } + if (!minReadSnapshot) { + return true; + } + return *DropVersion < *minReadSnapshot; } TTableInfo() = default; TTableInfo(const ui64 pathId) - : PathId(pathId) - {} + : PathId(pathId) { + } template bool InitFromDB(const TRow& rowset) { PathId = rowset.template GetValue(); - TieringUsage = rowset.template GetValue(); if (rowset.template HaveValue() && rowset.template HaveValue()) { - DropVersion.emplace(rowset.template GetValue(), rowset.template GetValue()); + DropVersion.emplace( + rowset.template GetValue(), rowset.template GetValue()); } return true; } @@ -139,31 +150,47 @@ class TTablesManager { private: THashMap Tables; THashSet SchemaPresetsIds; - THashSet PathsToDrop; - TTtl Ttl; + THashMap ActualSchemaForPreset; + std::map> PathsToDrop; + THashMap Ttl; std::unique_ptr PrimaryIndex; std::shared_ptr StoragesManager; + std::shared_ptr DataAccessorsManager; + std::unique_ptr LoadTimeCounters; + std::shared_ptr SchemaObjectsCache; ui64 TabletId = 0; + public: - TTablesManager(const std::shared_ptr& storagesManager, const ui64 tabletId); + friend class TTxInit; + + TTablesManager(const std::shared_ptr& storagesManager, + const std::shared_ptr& dataAccessorsManager, + const std::shared_ptr& schemaCache, const ui64 tabletId); + + const std::unique_ptr& GetLoadTimeCounters() const { + return LoadTimeCounters; + } bool TryFinalizeDropPathOnExecute(NTable::TDatabase& dbTable, const ui64 pathId) const; bool TryFinalizeDropPathOnComplete(const ui64 pathId); - const TTtl& GetTtl() const { + const THashMap& GetTtl() const { return Ttl; } - bool AddTtls(THashMap& eviction) { - return Ttl.AddTtls(eviction); - } - - const THashSet& GetPathsToDrop() const { + const std::map>& GetPathsToDrop() const { return PathsToDrop; } - THashSet& MutablePathsToDrop() { - return PathsToDrop; + THashSet GetPathsToDrop(const NOlap::TSnapshot& minReadSnapshot) const { + THashSet result; + for (auto&& i : PathsToDrop) { + if (minReadSnapshot < i.first) { + break; + } + result.insert(i.second.begin(), i.second.end()); + } + return result; } const THashMap& GetTables() const { @@ -185,7 +212,7 @@ class TTablesManager { const NOlap::TIndexInfo& GetIndexInfo(const NOlap::TSnapshot& version) const { Y_ABORT_UNLESS(!!PrimaryIndex); - return PrimaryIndex->GetVersionedIndex().GetSchema(version)->GetIndexInfo(); + return PrimaryIndex->GetVersionedIndex().GetSchemaVerified(version)->GetIndexInfo(); } const std::unique_ptr& GetPrimaryIndex() const { @@ -224,13 +251,13 @@ class TTablesManager { } bool InitFromDB(NIceDb::TNiceDb& db); - bool LoadIndex(NOlap::TDbWrapper& db); const TTableInfo& GetTable(const ui64 pathId) const; ui64 GetMemoryUsage() const; - bool HasTable(const ui64 pathId, bool withDeleted = false) const; - bool IsReadyForWrite(const ui64 pathId) const; + bool HasTable(const ui64 pathId, const bool withDeleted = false, const std::optional minReadSnapshot = std::nullopt) const; + bool IsReadyForStartWrite(const ui64 pathId, const bool withDeleted) const; + bool IsReadyForFinishWrite(const ui64 pathId, const NOlap::TSnapshot& minReadSnapshot) const; bool HasPreset(const ui32 presetId) const; void DropTable(const ui64 pathId, const NOlap::TSnapshot& version, NIceDb::TNiceDb& db); @@ -239,11 +266,14 @@ class TTablesManager { void RegisterTable(TTableInfo&& table, NIceDb::TNiceDb& db); bool RegisterSchemaPreset(const TSchemaPreset& schemaPreset, NIceDb::TNiceDb& db); - void AddSchemaVersion(const ui32 presetId, const NOlap::TSnapshot& version, const NKikimrSchemeOp::TColumnTableSchema& schema, NIceDb::TNiceDb& db, std::shared_ptr& manager); - void AddTableVersion(const ui64 pathId, const NOlap::TSnapshot& version, const NKikimrTxColumnShard::TTableVersionInfo& versionInfo, NIceDb::TNiceDb& db, std::shared_ptr& manager); + void AddSchemaVersion( + const ui32 presetId, const NOlap::TSnapshot& version, const NKikimrSchemeOp::TColumnTableSchema& schema, NIceDb::TNiceDb& db); + void AddTableVersion(const ui64 pathId, const NOlap::TSnapshot& version, const NKikimrTxColumnShard::TTableVersionInfo& versionInfo, + const std::optional& schema, NIceDb::TNiceDb& db); bool FillMonitoringReport(NTabletFlatExecutor::TTransactionContext& txc, NJson::TJsonValue& json); - [[nodiscard]] std::unique_ptr CreateAddShardingInfoTx(TColumnShard& owner, const ui64 pathId, const ui64 versionId, const NSharding::TGranuleShardingLogicContainer& tabletShardingLogic) const; + [[nodiscard]] std::unique_ptr CreateAddShardingInfoTx(TColumnShard& owner, const ui64 pathId, + const ui64 versionId, const NSharding::TGranuleShardingLogicContainer& tabletShardingLogic) const; }; -} +} // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/columnshard/test_helper/columnshard_ut_common.cpp b/ydb/core/tx/columnshard/test_helper/columnshard_ut_common.cpp index 31de6ffef8a5..e10877719934 100644 --- a/ydb/core/tx/columnshard/test_helper/columnshard_ut_common.cpp +++ b/ydb/core/tx/columnshard/test_helper/columnshard_ut_common.cpp @@ -1,15 +1,18 @@ #include "columnshard_ut_common.h" #include "shard_reader.h" -#include -#include -#include - #include #include +#include #include -#include +#include +#include +#include +#include +#include +#include #include + #include namespace NKikimr::NTxUT { @@ -45,8 +48,8 @@ void TTester::Setup(TTestActorRuntime& runtime) { runtime.UpdateCurrentTime(TInstant::Now()); } -void ProvideTieringSnapshot(TTestBasicRuntime& runtime, const TActorId& sender, NMetadata::NFetcher::ISnapshot::TPtr snapshot) { - auto event = std::make_unique(snapshot); +void RefreshTiering(TTestBasicRuntime& runtime, const TActorId& sender) { + auto event = std::make_unique(); ForwardToTablet(runtime, TTestTxConfig::TxTablet0, sender, event.release()); } @@ -63,7 +66,7 @@ bool ProposeSchemaTx(TTestBasicRuntime& runtime, TActorId& sender, const TString return (res.GetStatus() == NKikimrTxColumnShard::PREPARED); } -void PlanSchemaTx(TTestBasicRuntime& runtime, TActorId& sender, NOlap::TSnapshot snap) { +void PlanSchemaTx(TTestBasicRuntime& runtime, const TActorId& sender, NOlap::TSnapshot snap) { auto plan = std::make_unique(snap.GetPlanStep(), 0, TTestTxConfig::TxTablet0); auto tx = plan->Record.AddTransactions(); tx->SetTxId(snap.GetTxId()); @@ -78,7 +81,7 @@ void PlanSchemaTx(TTestBasicRuntime& runtime, TActorId& sender, NOlap::TSnapshot UNIT_ASSERT_EQUAL(res.GetStatus(), NKikimrTxColumnShard::SUCCESS); } -void PlanWriteTx(TTestBasicRuntime& runtime, TActorId& sender, NOlap::TSnapshot snap, bool waitResult) { +void PlanWriteTx(TTestBasicRuntime& runtime, const TActorId& sender, NOlap::TSnapshot snap, bool waitResult) { auto plan = std::make_unique(snap.GetPlanStep(), 0, TTestTxConfig::TxTablet0); auto tx = plan->Record.AddTransactions(); tx->SetTxId(snap.GetTxId()); @@ -96,26 +99,29 @@ void PlanWriteTx(TTestBasicRuntime& runtime, TActorId& sender, NOlap::TSnapshot ui32 WaitWriteResult(TTestBasicRuntime& runtime, ui64 shardId, std::vector* writeIds) { TAutoPtr handle; - auto event = runtime.GrabEdgeEvent(handle); + auto event = runtime.GrabEdgeEvent(handle); UNIT_ASSERT(event); - auto& resWrite = Proto(event); + auto& resWrite = event->Record; UNIT_ASSERT_EQUAL(resWrite.GetOrigin(), shardId); - UNIT_ASSERT_EQUAL(resWrite.GetTxInitiator(), 0); - if (writeIds && resWrite.GetStatus() == NKikimrTxColumnShard::EResultStatus::SUCCESS) { - writeIds->push_back(resWrite.GetWriteId()); + if (writeIds && resWrite.GetStatus() == NKikimrDataEvents::TEvWriteResult::STATUS_PREPARED) { + writeIds->push_back(resWrite.GetTxId()); } return resWrite.GetStatus(); } -bool WriteDataImpl(TTestBasicRuntime& runtime, TActorId& sender, const ui64 shardId, const ui64 tableId, - const NLongTxService::TLongTxId& longTxId, const ui64 writeId, - const TString& data, const std::shared_ptr& schema, std::vector* writeIds, const NEvWrite::EModificationType mType) { +bool WriteDataImpl(TTestBasicRuntime& runtime, TActorId& sender, const ui64 shardId, const ui64 tableId, const ui64 writeId, + const TString& data, const std::shared_ptr& schema, std::vector* writeIds, const NEvWrite::EModificationType mType, const ui64 lockId) { const TString dedupId = ToString(writeId); - auto write = std::make_unique(sender, longTxId, tableId, dedupId, data, writeId, mType); - Y_ABORT_UNLESS(schema); - write->SetArrowSchema(NArrow::SerializeSchema(*schema)); + auto write = std::make_unique(writeId, NKikimrDataEvents::TEvWrite::MODE_IMMEDIATE); + write->SetLockId(lockId, 1); + auto& operation = write->AddOperation(TEnumOperator::SerializeToWriteProto(mType), TTableId(0, tableId, 1), {}, + 0, NKikimrDataEvents::FORMAT_ARROW); + *operation.MutablePayloadSchema() = NArrow::SerializeSchema(*schema); + NEvWrite::TPayloadWriter writer(*write); + auto dataCopy = data; + writer.AddDataToPayload(std::move(dataCopy)); ForwardToTablet(runtime, shardId, sender, write.release()); if (writeIds) { @@ -125,25 +131,21 @@ bool WriteDataImpl(TTestBasicRuntime& runtime, TActorId& sender, const ui64 shar } bool WriteData(TTestBasicRuntime& runtime, TActorId& sender, const ui64 shardId, const ui64 writeId, const ui64 tableId, const TString& data, - const std::vector& ydbSchema, std::vector* writeIds, const NEvWrite::EModificationType mType) { - NLongTxService::TLongTxId longTxId; - UNIT_ASSERT(longTxId.ParseString("ydb://long-tx/01ezvvxjdk2hd4vdgjs68knvp8?node_id=1")); - return WriteDataImpl( - runtime, sender, shardId, tableId, longTxId, writeId, data, NArrow::MakeArrowSchema(ydbSchema), writeIds, mType); + const std::vector& ydbSchema, std::vector* writeIds, const NEvWrite::EModificationType mType, + const ui64 lockId) { + return WriteDataImpl(runtime, sender, shardId, tableId, writeId, data, NArrow::MakeArrowSchema(ydbSchema), writeIds, mType, lockId); } bool WriteData(TTestBasicRuntime& runtime, TActorId& sender, const ui64 writeId, const ui64 tableId, const TString& data, const std::vector& ydbSchema, bool waitResult, std::vector* writeIds, - const NEvWrite::EModificationType mType) { - NLongTxService::TLongTxId longTxId; - UNIT_ASSERT(longTxId.ParseString("ydb://long-tx/01ezvvxjdk2hd4vdgjs68knvp8?node_id=1")); + const NEvWrite::EModificationType mType, const ui64 lockId) { if (writeIds) { - return WriteDataImpl(runtime, sender, TTestTxConfig::TxTablet0, tableId, longTxId, writeId, data, - NArrow::MakeArrowSchema(ydbSchema), writeIds, mType); + return WriteDataImpl( + runtime, sender, TTestTxConfig::TxTablet0, tableId, writeId, data, NArrow::MakeArrowSchema(ydbSchema), writeIds, mType, lockId); } std::vector ids; - return WriteDataImpl(runtime, sender, TTestTxConfig::TxTablet0, tableId, longTxId, writeId, data, - NArrow::MakeArrowSchema(ydbSchema), waitResult ? &ids : nullptr, mType); + return WriteDataImpl(runtime, sender, TTestTxConfig::TxTablet0, tableId, writeId, data, NArrow::MakeArrowSchema(ydbSchema), + waitResult ? &ids : nullptr, mType, lockId); } std::optional WriteData(TTestBasicRuntime& runtime, TActorId& sender, const NLongTxService::TLongTxId& longTxId, @@ -205,31 +207,32 @@ void ScanIndexStats(TTestBasicRuntime& runtime, TActorId& sender, const std::vec ForwardToTablet(runtime, TTestTxConfig::TxTablet0, sender, scan.release()); } -void ProposeCommit(TTestBasicRuntime& runtime, TActorId& sender, ui64 shardId, ui64 txId, const std::vector& writeIds) { - NKikimrTxColumnShard::ETransactionKind txKind = NKikimrTxColumnShard::ETransactionKind::TX_KIND_COMMIT; - TString txBody = TTestSchema::CommitTxBody(0, writeIds); +void ProposeCommit(TTestBasicRuntime& runtime, TActorId& sender, ui64 shardId, ui64 txId, const std::vector& writeIds, const ui64 lockId) { + Y_UNUSED(writeIds); + auto write = std::make_unique(txId, NKikimrDataEvents::TEvWrite::MODE_PREPARE); + auto* lock = write->Record.MutableLocks()->AddLocks(); + lock->SetLockId(lockId); + write->Record.MutableLocks()->SetOp(NKikimrDataEvents::TKqpLocks::Commit); - ForwardToTablet(runtime, shardId, sender, - new TEvColumnShard::TEvProposeTransaction(txKind, sender, txId, txBody)); + ForwardToTablet(runtime, shardId, sender, write.release()); TAutoPtr handle; - auto event = runtime.GrabEdgeEvent(handle); + auto event = runtime.GrabEdgeEvent(handle); UNIT_ASSERT(event); - auto& res = Proto(event); - UNIT_ASSERT_EQUAL(res.GetTxKind(), txKind); - UNIT_ASSERT_EQUAL(res.GetTxId(), txId); - UNIT_ASSERT_EQUAL(res.GetStatus(), NKikimrTxColumnShard::EResultStatus::PREPARED); + auto& res = event->Record; + AFL_VERIFY(res.GetTxId() == txId)("tx_id", txId)("res", res.GetTxId()); + UNIT_ASSERT_EQUAL(res.GetStatus(), NKikimrDataEvents::TEvWriteResult::STATUS_PREPARED); } -void ProposeCommit(TTestBasicRuntime& runtime, TActorId& sender, ui64 txId, const std::vector& writeIds) { - ProposeCommit(runtime, sender, TTestTxConfig::TxTablet0, txId, writeIds); +void ProposeCommit(TTestBasicRuntime& runtime, TActorId& sender, ui64 txId, const std::vector& writeIds, const ui64 lockId) { + ProposeCommit(runtime, sender, TTestTxConfig::TxTablet0, txId, writeIds, lockId); } void PlanCommit(TTestBasicRuntime& runtime, TActorId& sender, ui64 planStep, const TSet& txIds) { PlanCommit(runtime, sender, TTestTxConfig::TxTablet0, planStep, txIds); } -void Wakeup(TTestBasicRuntime& runtime, TActorId& sender, const ui64 shardId) { +void Wakeup(TTestBasicRuntime& runtime, const TActorId& sender, const ui64 shardId) { auto wakeup = std::make_unique(true); ForwardToTablet(runtime, shardId, sender, wakeup.release()); } @@ -246,12 +249,12 @@ void PlanCommit(TTestBasicRuntime& runtime, TActorId& sender, ui64 shardId, ui64 TAutoPtr handle; for (ui32 i = 0; i < txIds.size(); ++i) { - auto event = runtime.GrabEdgeEvent(handle); + auto event = runtime.GrabEdgeEvent(handle); UNIT_ASSERT(event); - auto& res = Proto(event); + auto& res = event->Record; UNIT_ASSERT(txIds.contains(res.GetTxId())); - UNIT_ASSERT_EQUAL(res.GetStatus(), NKikimrTxColumnShard::EResultStatus::SUCCESS); + UNIT_ASSERT_EQUAL(res.GetStatus(), NKikimrDataEvents::TEvWriteResult::STATUS_COMPLETED); } Wakeup(runtime, sender, shardId); } @@ -369,44 +372,33 @@ TSerializedTableRange MakeTestRange(std::pair range, bool inclusiveF TConstArrayRef(cellsTo), inclusiveTo); } -NMetadata::NFetcher::ISnapshot::TPtr TTestSchema::BuildSnapshot(const TTableSpecials& specials) { - std::unique_ptr cs(new NColumnShard::NTiers::TConfigsSnapshot(Now())); +THashMap TTestSchema::BuildSnapshot(const TTableSpecials& specials) { if (specials.Tiers.empty()) { - return cs; + return {}; } - NColumnShard::NTiers::TTieringRule tRule; - tRule.SetTieringRuleId("Tiering1"); + THashMap tiers; for (auto&& tier : specials.Tiers) { - if (!tRule.GetDefaultColumn()) { - tRule.SetDefaultColumn(tier.TtlColumn); - } - UNIT_ASSERT(tRule.GetDefaultColumn() == tier.TtlColumn); { - NKikimrSchemeOp::TStorageTierConfig cProto; - cProto.SetName(tier.Name); - *cProto.MutableObjectStorage() = tier.S3; + NKikimrSchemeOp::TCompressionOptions compressionProto; if (tier.Codec) { - cProto.MutableCompression()->SetCodec(tier.GetCodecId()); + compressionProto.SetCodec(tier.GetCodecId()); } if (tier.CompressionLevel) { - cProto.MutableCompression()->SetLevel(*tier.CompressionLevel); + compressionProto.SetLevel(*tier.CompressionLevel); } - NColumnShard::NTiers::TTierConfig tConfig(tier.Name, cProto); - cs->MutableTierConfigs().emplace(tConfig.GetTierName(), tConfig); + NColumnShard::NTiers::TTierConfig tConfig(tier.S3, compressionProto); + tiers.emplace(tier.Name, tConfig); } - tRule.AddInterval(tier.Name, TDuration::Seconds((*tier.EvictAfter).Seconds())); } - cs->MutableTableTierings().emplace(tRule.GetTieringRuleId(), tRule); - return cs; + return tiers; } void TTestSchema::InitSchema(const std::vector& columns, const std::vector& pk, const TTableSpecials& specials, NKikimrSchemeOp::TColumnTableSchema* schema) { - schema->SetEngine(NKikimrSchemeOp::COLUMN_ENGINE_REPLACING_TIMESERIES); for (ui32 i = 0; i < columns.size(); ++i) { *schema->MutableColumns()->Add() = columns[i].CreateColumn(i + 1); - if (!specials.NeedTestStatistics()) { + if (!specials.NeedTestStatistics(pk)) { continue; } if (NOlap::NIndexes::NMax::TIndexMeta::IsAvailableType(columns[i].GetType())) { @@ -435,19 +427,26 @@ namespace NKikimr::NColumnShard { NOlap::TIndexInfo BuildTableInfo(const std::vector& ydbSchema, const std::vector& key) { THashMap columns; + THashMap columnIdByName; for (ui32 i = 0; i < ydbSchema.size(); ++i) { ui32 id = i + 1; auto& name = ydbSchema[i].GetName(); auto& type = ydbSchema[i].GetType(); columns[id] = NTable::TColumn(name, id, type, ""); + AFL_VERIFY(columnIdByName.emplace(name, id).second); } - std::vector pkNames; + std::vector pkIds; + ui32 idx = 0; for (const auto& c : key) { - pkNames.push_back(c.GetName()); + auto it = columnIdByName.FindPtr(c.GetName()); + AFL_VERIFY(it); + AFL_VERIFY(*it < columns.size()); + columns[*it].KeyOrder = idx++; + pkIds.push_back(*it); } - return NOlap::TIndexInfo::BuildDefault(NOlap::TTestStoragesManager::GetInstance(), columns, pkNames); + return NOlap::TIndexInfo::BuildDefault(NOlap::TTestStoragesManager::GetInstance(), columns, pkIds); } void SetupSchema(TTestBasicRuntime& runtime, TActorId& sender, const TString& txBody, const NOlap::TSnapshot& snapshot, bool succeed) { diff --git a/ydb/core/tx/columnshard/test_helper/columnshard_ut_common.h b/ydb/core/tx/columnshard/test_helper/columnshard_ut_common.h index 7594be5da952..611fc1e2a28d 100644 --- a/ydb/core/tx/columnshard/test_helper/columnshard_ut_common.h +++ b/ydb/core/tx/columnshard/test_helper/columnshard_ut_common.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -107,7 +108,7 @@ struct TTestSchema { s3Config.SetProxyPort(8080); s3Config.SetProxyScheme(NKikimrSchemeOp::TS3Settings::HTTP); #else - s3Config.SetEndpoint("fake"); + s3Config.SetEndpoint("fake.fake"); s3Config.SetSecretKey("fakeSecret"); #endif s3Config.SetRequestTimeoutMs(10000); @@ -118,20 +119,14 @@ struct TTestSchema { }; struct TTableSpecials : public TStorageTier { - private: - bool NeedTestStatisticsFlag = true; public: std::vector Tiers; bool WaitEmptyAfter = false; TTableSpecials() noexcept = default; - bool NeedTestStatistics() const { - return NeedTestStatisticsFlag; - } - - void SetNeedTestStatistics(const bool value) { - NeedTestStatisticsFlag = value; + bool NeedTestStatistics(const std::vector& pk) const { + return GetTtlColumn() != pk.front().GetName(); } bool HasTiers() const { @@ -161,6 +156,13 @@ struct TTestSchema { result << ";TTL=" << TStorageTier::DebugString(); return result; } + + TString GetTtlColumn() const { + for (const auto& tier : Tiers) { + UNIT_ASSERT_VALUES_EQUAL(tier.TtlColumn, TtlColumn); + } + return TtlColumn; + } }; using TTestColumn = NArrow::NTest::TTestColumn; static auto YdbSchema(const TTestColumn& firstKeyItem = TTestColumn("timestamp", TTypeInfo(NTypeIds::Timestamp))) { @@ -246,22 +248,25 @@ struct TTestSchema { const TTableSpecials& specials, NKikimrSchemeOp::TColumnTableSchema* schema); - static void InitTtl(const TTableSpecials& specials, NKikimrSchemeOp::TColumnDataLifeCycle::TTtl* ttl) { - Y_ABORT_UNLESS(specials.HasTtl()); - Y_ABORT_UNLESS(!specials.TtlColumn.empty()); - ttl->SetColumnName(specials.TtlColumn); - ttl->SetExpireAfterSeconds((*specials.EvictAfter).Seconds()); - } - static bool InitTiersAndTtl(const TTableSpecials& specials, NKikimrSchemeOp::TColumnDataLifeCycle* ttlSettings) { ttlSettings->SetVersion(1); - if (specials.HasTiers()) { - ttlSettings->SetUseTiering("Tiering1"); + if (!specials.HasTiers() && !specials.HasTtl()) { + return false; + } + ttlSettings->MutableEnabled()->SetColumnName(specials.TtlColumn); + for (const auto& tier : specials.Tiers) { + UNIT_ASSERT(tier.EvictAfter); + UNIT_ASSERT_EQUAL(specials.TtlColumn, tier.TtlColumn); + auto* tierSettings = ttlSettings->MutableEnabled()->AddTiers(); + tierSettings->MutableEvictToExternalStorage()->SetStorage(tier.Name); + tierSettings->SetApplyAfterSeconds(tier.EvictAfter->Seconds()); } if (specials.HasTtl()) { - InitTtl(specials, ttlSettings->MutableEnabled()); + auto* tier = ttlSettings->MutableEnabled()->AddTiers(); + tier->MutableDelete(); + tier->SetApplyAfterSeconds((*specials.EvictAfter).Seconds()); } - return specials.HasTiers() || specials.HasTtl(); + return true; } static TString CreateTableTxBody(ui64 pathId, const std::vector& columns, @@ -355,7 +360,7 @@ struct TTestSchema { return out; } - static NMetadata::NFetcher::ISnapshot::TPtr BuildSnapshot(const TTableSpecials& specials); + static THashMap BuildSnapshot(const TTableSpecials& specials); static TString CommitTxBody(ui64, const std::vector& writeIds) { NKikimrTxColumnShard::TCommitTxBody proto; @@ -400,19 +405,20 @@ struct TTestSchema { } }; +void RefreshTiering(TTestBasicRuntime& runtime, const TActorId& sender); + bool ProposeSchemaTx(TTestBasicRuntime& runtime, TActorId& sender, const TString& txBody, NOlap::TSnapshot snap); -void ProvideTieringSnapshot(TTestBasicRuntime& runtime, const TActorId& sender, NMetadata::NFetcher::ISnapshot::TPtr snapshot); -void PlanSchemaTx(TTestBasicRuntime& runtime, TActorId& sender, NOlap::TSnapshot snap); +void PlanSchemaTx(TTestBasicRuntime& runtime, const TActorId& sender, NOlap::TSnapshot snap); -void PlanWriteTx(TTestBasicRuntime& runtime, TActorId& sender, NOlap::TSnapshot snap, bool waitResult = true); +void PlanWriteTx(TTestBasicRuntime& runtime, const TActorId& sender, NOlap::TSnapshot snap, bool waitResult = true); bool WriteData(TTestBasicRuntime& runtime, TActorId& sender, const ui64 shardId, const ui64 writeId, const ui64 tableId, const TString& data, const std::vector& ydbSchema, std::vector* writeIds, - const NEvWrite::EModificationType mType = NEvWrite::EModificationType::Upsert); + const NEvWrite::EModificationType mType = NEvWrite::EModificationType::Upsert, const ui64 lockId = 1); bool WriteData(TTestBasicRuntime& runtime, TActorId& sender, const ui64 writeId, const ui64 tableId, const TString& data, const std::vector& ydbSchema, bool waitResult = true, std::vector* writeIds = nullptr, - const NEvWrite::EModificationType mType = NEvWrite::EModificationType::Upsert); + const NEvWrite::EModificationType mType = NEvWrite::EModificationType::Upsert, const ui64 lockId = 1); std::optional WriteData(TTestBasicRuntime& runtime, TActorId& sender, const NLongTxService::TLongTxId& longTxId, ui64 tableId, const ui64 writePartId, const TString& data, @@ -423,8 +429,8 @@ ui32 WaitWriteResult(TTestBasicRuntime& runtime, ui64 shardId, std::vector void ScanIndexStats(TTestBasicRuntime& runtime, TActorId& sender, const std::vector& pathIds, NOlap::TSnapshot snap, ui64 scanId = 0); -void ProposeCommit(TTestBasicRuntime& runtime, TActorId& sender, ui64 shardId, ui64 txId, const std::vector& writeIds); -void ProposeCommit(TTestBasicRuntime& runtime, TActorId& sender, ui64 txId, const std::vector& writeIds); +void ProposeCommit(TTestBasicRuntime& runtime, TActorId& sender, ui64 shardId, ui64 txId, const std::vector& writeIds, const ui64 lockId = 1); +void ProposeCommit(TTestBasicRuntime& runtime, TActorId& sender, ui64 txId, const std::vector& writeIds, const ui64 lockId = 1); void PlanCommit(TTestBasicRuntime& runtime, TActorId& sender, ui64 shardId, ui64 planStep, const TSet& txIds); void PlanCommit(TTestBasicRuntime& runtime, TActorId& sender, ui64 planStep, const TSet& txIds); @@ -435,7 +441,7 @@ inline void PlanCommit(TTestBasicRuntime& runtime, TActorId& sender, ui64 planSt PlanCommit(runtime, sender, planStep, ids); } -void Wakeup(TTestBasicRuntime& runtime, TActorId& sender, const ui64 shardId); +void Wakeup(TTestBasicRuntime& runtime, const TActorId& sender, const ui64 shardId); struct TTestBlobOptions { THashSet NullColumns; diff --git a/ydb/core/tx/columnshard/test_helper/controllers.cpp b/ydb/core/tx/columnshard/test_helper/controllers.cpp index 997a700d901b..a9f1a877a13b 100644 --- a/ydb/core/tx/columnshard/test_helper/controllers.cpp +++ b/ydb/core/tx/columnshard/test_helper/controllers.cpp @@ -1,7 +1,8 @@ #include "columnshard_ut_common.h" #include "controllers.h" -#include + #include +#include namespace NKikimr::NOlap { @@ -10,13 +11,13 @@ void TWaitCompactionController::OnTieringModified(const std::shared_ptr tiers) { + OverrideTiers = std::move(tiers); ui32 startCount = TiersModificationsCount; - NTxUT::ProvideTieringSnapshot(runtime, tabletActorId, snapshot); + NTxUT::RefreshTiering(runtime, tabletActorId); while (TiersModificationsCount == startCount) { runtime.SimulateSleep(TDuration::Seconds(1)); } } - -} \ No newline at end of file +} // namespace NKikimr::NOlap diff --git a/ydb/core/tx/columnshard/test_helper/controllers.h b/ydb/core/tx/columnshard/test_helper/controllers.h index 68cd6a1dc4ed..281058322ac7 100644 --- a/ydb/core/tx/columnshard/test_helper/controllers.h +++ b/ydb/core/tx/columnshard/test_helper/controllers.h @@ -1,6 +1,7 @@ #pragma once -#include #include +#include +#include namespace NKikimr::NOlap { @@ -8,11 +9,14 @@ class TWaitCompactionController: public NYDBTest::NColumnShard::TController { private: using TBase = NKikimr::NYDBTest::ICSController; TAtomicCounter ExportsFinishedCount = 0; - NMetadata::NFetcher::ISnapshot::TPtr CurrentConfig; + THashMap OverrideTiers; ui32 TiersModificationsCount = 0; + YDB_READONLY(TAtomicCounter, TieringMetadataActualizationCount, 0); YDB_READONLY(TAtomicCounter, StatisticsUsageCount, 0); YDB_READONLY(TAtomicCounter, MaxValueUsageCount, 0); YDB_ACCESSOR_DEF(std::optional, SmallSizeDetector); + YDB_ACCESSOR(bool, SkipSpecialCheckForEvict, false); + protected: virtual void OnTieringModified(const std::shared_ptr& /*tiers*/) override; virtual void OnExportFinished() override { @@ -34,6 +38,15 @@ class TWaitCompactionController: public NYDBTest::NColumnShard::TController { return TDuration::Zero(); } public: + virtual bool CheckPortionForEvict(const TPortionInfo& portion) const override { + if (SkipSpecialCheckForEvict) { + return true; + } else { + return TBase::CheckPortionForEvict(portion); + } + } + + TWaitCompactionController() { SetOverridePeriodicWakeupActivationPeriod(TDuration::Seconds(1)); } @@ -42,20 +55,20 @@ class TWaitCompactionController: public NYDBTest::NColumnShard::TController { return ExportsFinishedCount.Val(); } + virtual void OnTieringMetadataActualized() override { + TieringMetadataActualizationCount.Inc(); + } virtual void OnStatisticsUsage(const NKikimr::NOlap::NIndexes::TIndexMetaContainer& /*statOperator*/) override { StatisticsUsageCount.Inc(); } virtual void OnMaxValueUsage() override { MaxValueUsageCount.Inc(); } - void SetTiersSnapshot(TTestBasicRuntime& runtime, const TActorId& tabletActorId, const NMetadata::NFetcher::ISnapshot::TPtr& snapshot); + void OverrideTierConfigs( + TTestBasicRuntime& runtime, const TActorId& tabletActorId, THashMap tiers); - virtual NMetadata::NFetcher::ISnapshot::TPtr GetFallbackTiersSnapshot() const override { - if (CurrentConfig) { - return CurrentConfig; - } else { - return TBase::GetFallbackTiersSnapshot(); - } + THashMap GetOverrideTierConfigs() const override { + return OverrideTiers; } }; diff --git a/ydb/core/tx/columnshard/test_helper/shard_reader.cpp b/ydb/core/tx/columnshard/test_helper/shard_reader.cpp new file mode 100644 index 000000000000..6b3ce1a5a1b4 --- /dev/null +++ b/ydb/core/tx/columnshard/test_helper/shard_reader.cpp @@ -0,0 +1,101 @@ +#include "shard_reader.h" + +namespace NKikimr::NTxUT { + +std::unique_ptr TShardReader::BuildStartEvent() const { + auto ev = std::make_unique(); + ev->Record.SetLocalPathId(PathId); + ev->Record.MutableSnapshot()->SetStep(Snapshot.GetPlanStep()); + ev->Record.MutableSnapshot()->SetTxId(Snapshot.GetTxId()); + + ev->Record.SetStatsMode(NYql::NDqProto::DQ_STATS_MODE_FULL); + ev->Record.SetTxId(Snapshot.GetTxId()); + + ev->Record.SetReverse(Reverse); + ev->Record.SetItemsLimit(Limit); + + ev->Record.SetDataFormat(NKikimrDataEvents::FORMAT_ARROW); + + auto protoRanges = ev->Record.MutableRanges(); + protoRanges->Reserve(Ranges.size()); + for (auto& range : Ranges) { + auto newRange = protoRanges->Add(); + range.Serialize(*newRange); + } + + if (ProgramProto) { + NKikimrSSA::TOlapProgram olapProgram; + { + TString programBytes; + TStringOutput stream(programBytes); + ProgramProto->SerializeToArcadiaStream(&stream); + olapProgram.SetProgram(programBytes); + } + { + TString programBytes; + TStringOutput stream(programBytes); + olapProgram.SerializeToArcadiaStream(&stream); + ev->Record.SetOlapProgram(programBytes); + } + ev->Record.SetOlapProgramType(NKikimrSchemeOp::EOlapProgramType::OLAP_PROGRAM_SSA_PROGRAM_WITH_PARAMETERS); + } else if (SerializedProgram) { + ev->Record.SetOlapProgram(*SerializedProgram); + ev->Record.SetOlapProgramType(NKikimrSchemeOp::EOlapProgramType::OLAP_PROGRAM_SSA_PROGRAM_WITH_PARAMETERS); + } + + return ev; +} + +NKikimr::NTxUT::TShardReader& TShardReader::SetReplyColumns(const std::vector& replyColumns) { + AFL_VERIFY(!SerializedProgram); + if (!ProgramProto) { + ProgramProto = NKikimrSSA::TProgram(); + } + for (auto&& command : *ProgramProto->MutableCommand()) { + if (command.HasProjection()) { + NKikimrSSA::TProgram::TProjection proj; + for (auto&& i : replyColumns) { + proj.AddColumns()->SetName(i); + } + *command.MutableProjection() = proj; + return *this; + } + } + { + auto* command = ProgramProto->AddCommand(); + NKikimrSSA::TProgram::TProjection proj; + for (auto&& i : replyColumns) { + proj.AddColumns()->SetName(i); + } + *command->MutableProjection() = proj; + } + return *this; +} + +NKikimr::NTxUT::TShardReader& TShardReader::SetReplyColumnIds(const std::vector& replyColumnIds) { + AFL_VERIFY(!SerializedProgram); + if (!ProgramProto) { + ProgramProto = NKikimrSSA::TProgram(); + } + for (auto&& command : *ProgramProto->MutableCommand()) { + if (command.HasProjection()) { + NKikimrSSA::TProgram::TProjection proj; + for (auto&& i : replyColumnIds) { + proj.AddColumns()->SetId(i); + } + *command.MutableProjection() = proj; + return *this; + } + } + { + auto* command = ProgramProto->AddCommand(); + NKikimrSSA::TProgram::TProjection proj; + for (auto&& i : replyColumnIds) { + proj.AddColumns()->SetId(i); + } + *command->MutableProjection() = proj; + } + return *this; +} + +} diff --git a/ydb/core/tx/columnshard/test_helper/shard_reader.h b/ydb/core/tx/columnshard/test_helper/shard_reader.h index 2beaa5a782d9..eb9f041062a6 100644 --- a/ydb/core/tx/columnshard/test_helper/shard_reader.h +++ b/ydb/core/tx/columnshard/test_helper/shard_reader.h @@ -28,53 +28,7 @@ class TShardReader { std::vector ReplyColumns; std::vector Ranges; - std::unique_ptr BuildStartEvent() const { - auto ev = std::make_unique(); - ev->Record.SetLocalPathId(PathId); - ev->Record.MutableSnapshot()->SetStep(Snapshot.GetPlanStep()); - ev->Record.MutableSnapshot()->SetTxId(Snapshot.GetTxId()); - - ev->Record.SetStatsMode(NYql::NDqProto::DQ_STATS_MODE_FULL); - ev->Record.SetTxId(Snapshot.GetTxId()); - - ev->Record.SetReverse(Reverse); - ev->Record.SetItemsLimit(Limit); - - ev->Record.SetDataFormat(NKikimrDataEvents::FORMAT_ARROW); - - auto protoRanges = ev->Record.MutableRanges(); - protoRanges->Reserve(Ranges.size()); - for (auto& range : Ranges) { - auto newRange = protoRanges->Add(); - range.Serialize(*newRange); - } - - if (ProgramProto) { - NKikimrSSA::TOlapProgram olapProgram; - { - TString programBytes; - TStringOutput stream(programBytes); - ProgramProto->SerializeToArcadiaStream(&stream); - olapProgram.SetProgram(programBytes); - } - { - TString programBytes; - TStringOutput stream(programBytes); - olapProgram.SerializeToArcadiaStream(&stream); - ev->Record.SetOlapProgram(programBytes); - } - ev->Record.SetOlapProgramType( - NKikimrSchemeOp::EOlapProgramType::OLAP_PROGRAM_SSA_PROGRAM_WITH_PARAMETERS - ); - } else if (SerializedProgram) { - ev->Record.SetOlapProgram(*SerializedProgram); - ev->Record.SetOlapProgramType( - NKikimrSchemeOp::EOlapProgramType::OLAP_PROGRAM_SSA_PROGRAM_WITH_PARAMETERS - ); - } - - return ev; - } + std::unique_ptr BuildStartEvent() const; std::vector> ResultBatches; YDB_READONLY(ui32, IterationsCount, 0); @@ -100,57 +54,9 @@ class TShardReader { return r ? r->num_rows() : 0; } - TShardReader& SetReplyColumns(const std::vector& replyColumns) { - AFL_VERIFY(!SerializedProgram); - if (!ProgramProto) { - ProgramProto = NKikimrSSA::TProgram(); - } - for (auto&& command : *ProgramProto->MutableCommand()) { - if (command.HasProjection()) { - NKikimrSSA::TProgram::TProjection proj; - for (auto&& i : replyColumns) { - proj.AddColumns()->SetName(i); - } - *command.MutableProjection() = proj; - return *this; - } - } - { - auto* command = ProgramProto->AddCommand(); - NKikimrSSA::TProgram::TProjection proj; - for (auto&& i : replyColumns) { - proj.AddColumns()->SetName(i); - } - *command->MutableProjection() = proj; - } - return *this; - } + TShardReader& SetReplyColumns(const std::vector& replyColumns); - TShardReader& SetReplyColumnIds(const std::vector& replyColumnIds) { - AFL_VERIFY(!SerializedProgram); - if (!ProgramProto) { - ProgramProto = NKikimrSSA::TProgram(); - } - for (auto&& command : *ProgramProto->MutableCommand()) { - if (command.HasProjection()) { - NKikimrSSA::TProgram::TProjection proj; - for (auto&& i : replyColumnIds) { - proj.AddColumns()->SetId(i); - } - *command.MutableProjection() = proj; - return *this; - } - } - { - auto* command = ProgramProto->AddCommand(); - NKikimrSSA::TProgram::TProjection proj; - for (auto&& i : replyColumnIds) { - proj.AddColumns()->SetId(i); - } - *command->MutableProjection() = proj; - } - return *this; - } + TShardReader& SetReplyColumnIds(const std::vector& replyColumnIds); TShardReader& SetProgram(const NKikimrSSA::TProgram& p) { AFL_VERIFY(!ProgramProto); diff --git a/ydb/core/tx/columnshard/test_helper/shard_writer.cpp b/ydb/core/tx/columnshard/test_helper/shard_writer.cpp new file mode 100644 index 000000000000..92e262d2f776 --- /dev/null +++ b/ydb/core/tx/columnshard/test_helper/shard_writer.cpp @@ -0,0 +1,63 @@ +#include "shard_writer.h" + +#include +#include +#include +#include +#include + +namespace NKikimr::NTxUT { + +NKikimrDataEvents::TEvWriteResult::EStatus TShardWriter::StartCommit(const ui64 txId) { + auto evCommit = std::make_unique(txId, NKikimrDataEvents::TEvWrite::MODE_IMMEDIATE); + evCommit->Record.MutableLocks()->SetOp(NKikimrDataEvents::TKqpLocks::Commit); + auto* lock = evCommit->Record.MutableLocks()->AddLocks(); + lock->SetLockId(LockId); + ForwardToTablet(Runtime, TTestTxConfig::TxTablet0, Sender, evCommit.release()); + + TAutoPtr handle; + auto event = Runtime.GrabEdgeEvent(handle); + AFL_VERIFY(event); + + return event->Record.GetStatus(); +} + +NKikimrDataEvents::TEvWriteResult::EStatus TShardWriter::Abort(const ui64 txId) { + auto evCommit = std::make_unique(txId, NKikimrDataEvents::TEvWrite::MODE_IMMEDIATE); + evCommit->Record.MutableLocks()->SetOp(NKikimrDataEvents::TKqpLocks::Rollback); + auto* lock = evCommit->Record.MutableLocks()->AddLocks(); + lock->SetLockId(LockId); + ForwardToTablet(Runtime, TTestTxConfig::TxTablet0, Sender, evCommit.release()); + + TAutoPtr handle; + auto event = Runtime.GrabEdgeEvent(handle); + AFL_VERIFY(event); + + return event->Record.GetStatus(); +} + +NKikimrDataEvents::TEvWriteResult::EStatus TShardWriter::Write( + const std::shared_ptr& batch, const std::vector& columnIds, const ui64 txId) { + TString blobData = NArrow::SerializeBatchNoCompression(batch); +// AFL_VERIFY(blobData.size() < NColumnShard::TLimits::GetMaxBlobSize()); + + auto evWrite = std::make_unique(NKikimrDataEvents::TEvWrite::MODE_IMMEDIATE); + evWrite->SetTxId(txId); + evWrite->SetLockId(LockId, LockNodeId); + const ui64 payloadIndex = NEvWrite::TPayloadWriter(*evWrite).AddDataToPayload(std::move(blobData)); + evWrite->AddOperation(NKikimrDataEvents::TEvWrite::TOperation::OPERATION_REPLACE, { OwnerId, PathId, SchemaVersion }, columnIds, + payloadIndex, NKikimrDataEvents::FORMAT_ARROW); + + ForwardToTablet(Runtime, TabletId, Sender, evWrite.release()); + + TAutoPtr handle; + auto event = Runtime.GrabEdgeEvent(handle); + AFL_VERIFY(event); + + AFL_VERIFY(event->Record.GetOrigin() == TabletId); + AFL_VERIFY(event->Record.GetTxId() == LockId); + + return event->Record.GetStatus(); +} + +} // namespace NKikimr::NTxUT diff --git a/ydb/core/tx/columnshard/test_helper/shard_writer.h b/ydb/core/tx/columnshard/test_helper/shard_writer.h new file mode 100644 index 000000000000..b43e9749a69b --- /dev/null +++ b/ydb/core/tx/columnshard/test_helper/shard_writer.h @@ -0,0 +1,43 @@ +#pragma once +#include +#include + +#include + +#include + +namespace NKikimr::NTxUT { + +class TShardWriter { +private: + TTestBasicRuntime& Runtime; + const ui64 TabletId; + const ui64 PathId; + const ui64 LockId; + YDB_ACCESSOR(ui64, SchemaVersion, 1); + YDB_ACCESSOR(ui64, OwnerId, 0); + YDB_ACCESSOR(ui64, LockNodeId, 1); + const TActorId Sender; + +public: + TShardWriter(TTestBasicRuntime& runtime, const ui64 tabletId, const ui64 pathId, const ui64 lockId) + : Runtime(runtime) + , TabletId(tabletId) + , PathId(pathId) + , LockId(lockId) + , Sender(Runtime.AllocateEdgeActor()) + { + } + + const TActorId& GetSender() const { + return Sender; + } + + [[nodiscard]] NKikimrDataEvents::TEvWriteResult::EStatus StartCommit(const ui64 txId); + [[nodiscard]] NKikimrDataEvents::TEvWriteResult::EStatus Abort(const ui64 txId); + + [[nodiscard]] NKikimrDataEvents::TEvWriteResult::EStatus Write( + const std::shared_ptr& batch, const std::vector& columnIds, const ui64 txId); +}; + +} // namespace NKikimr::NTxUT diff --git a/ydb/core/tx/columnshard/test_helper/ya.make b/ydb/core/tx/columnshard/test_helper/ya.make index cab4937293dd..d4b96709720b 100644 --- a/ydb/core/tx/columnshard/test_helper/ya.make +++ b/ydb/core/tx/columnshard/test_helper/ya.make @@ -14,6 +14,8 @@ SRCS( helper.cpp controllers.cpp columnshard_ut_common.cpp + shard_reader.cpp + shard_writer.cpp ) IF (OS_WINDOWS) diff --git a/ydb/core/tx/columnshard/transactions/locks/interaction.h b/ydb/core/tx/columnshard/transactions/locks/interaction.h index abd9ef92f6d5..4cd0c9185ef6 100644 --- a/ydb/core/tx/columnshard/transactions/locks/interaction.h +++ b/ydb/core/tx/columnshard/transactions/locks/interaction.h @@ -350,7 +350,7 @@ class TReadIntervals { AFL_VERIFY(writtenPrimaryKeys); auto it = IntervalsInfo.begin(); THashSet affectedTxIds; - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("batch", writtenPrimaryKeys->ToString())("info", DebugJson().GetStringRobust()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("batch", writtenPrimaryKeys->ToString())("info", DebugJson().GetStringRobust()); for (ui32 i = 0; i < writtenPrimaryKeys->num_rows();) { if (it == IntervalsInfo.end()) { return affectedTxIds; @@ -400,6 +400,10 @@ class TInteractionsContext { THashMap ReadIntervalsByPathId; public: + bool HasReadIntervals(const ui64 pathId) const { + return ReadIntervalsByPathId.contains(pathId); + } + NJson::TJsonValue DebugJson() const { NJson::TJsonValue result = NJson::JSON_MAP; for (auto&& i : ReadIntervalsByPathId) { diff --git a/ydb/core/tx/columnshard/transactions/operators/ev_write/primary.h b/ydb/core/tx/columnshard/transactions/operators/ev_write/primary.h index 40a2a6586ab4..3bb8dffa156f 100644 --- a/ydb/core/tx/columnshard/transactions/operators/ev_write/primary.h +++ b/ydb/core/tx/columnshard/transactions/operators/ev_write/primary.h @@ -23,7 +23,6 @@ class TEvWriteCommitPrimaryTransactionOperator: public TEvWriteCommitSyncTransac std::set WaitShardsBrokenFlags; std::set WaitShardsResultAck; std::optional TxBroken; - mutable TAtomicCounter ControlCounter = 0; virtual NKikimrTxColumnShard::TCommitWriteTxBody SerializeToProto() const override { NKikimrTxColumnShard::TCommitWriteTxBody result; @@ -48,7 +47,7 @@ class TEvWriteCommitPrimaryTransactionOperator: public TEvWriteCommitSyncTransac virtual bool DoParseImpl(TColumnShard& /*owner*/, const NKikimrTxColumnShard::TCommitWriteTxBody& commitTxBody) override { if (!commitTxBody.HasPrimaryTabletData()) { - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("event", "cannot read proto")("proto", commitTxBody.DebugString()); + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD_TX)("event", "cannot read proto")("proto", commitTxBody.DebugString()); return false; } auto& protoData = commitTxBody.GetPrimaryTabletData(); @@ -84,28 +83,32 @@ class TEvWriteCommitPrimaryTransactionOperator: public TEvWriteCommitSyncTransac const ui64 TxId; const ui64 TabletId; const bool BrokenFlag; + bool SendAckFlag = false; virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const NActors::TActorContext& /*ctx*/) override { - auto op = Self->GetProgressTxController().GetTxOperatorVerifiedAs(TxId); - auto copy = *op; - if (copy.WaitShardsBrokenFlags.erase(TabletId)) { - copy.TxBroken = copy.TxBroken.value_or(false) || BrokenFlag; - Self->GetProgressTxController().WriteTxOperatorInfo(txc, TxId, copy.SerializeToProto().SerializeAsString()); + auto op = Self->GetProgressTxController().GetTxOperatorVerifiedAs(TxId, true); + if (!op) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_TX)("event", "repeated shard broken_flag info")("shard_id", TabletId)("reason", "absent operation"); + } else if (!op->WaitShardsBrokenFlags.erase(TabletId)) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_TX)("event", "repeated shard broken_flag info")("shard_id", TabletId); } else { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "repeated shard broken_flag info")("shard_id", TabletId); + op->TxBroken = op->TxBroken.value_or(false) || BrokenFlag; + Self->GetProgressTxController().WriteTxOperatorInfo(txc, TxId, op->SerializeToProto().SerializeAsString()); + SendAckFlag = true; } return true; } virtual void DoComplete(const NActors::TActorContext& /*ctx*/) override { - auto op = Self->GetProgressTxController().GetTxOperatorVerifiedAs(TxId); - if (op->WaitShardsBrokenFlags.erase(TabletId)) { - op->TxBroken = op->TxBroken.value_or(false) || BrokenFlag; - op->SendBrokenFlagAck(*Self, TabletId); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "remove_tablet_id")("wait", JoinSeq(",", op->WaitShardsBrokenFlags))( + auto op = Self->GetProgressTxController().GetTxOperatorVerifiedAs(TxId, true); + if (!op) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_TX)("event", "repeated shard broken_flag info")("shard_id", TabletId)("reason", "absent operator"); + } else if (!SendAckFlag) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_TX)("event", "repeated shard broken_flag info")("shard_id", TabletId); + } else { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "remove_tablet_id")("wait", JoinSeq(",", op->WaitShardsBrokenFlags))( "receive", TabletId); + op->SendBrokenFlagAck(*Self, TabletId); op->InitializeRequests(*Self); - } else { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "repeated shard broken_flag info")("shard_id", TabletId); } } @@ -130,22 +133,22 @@ class TEvWriteCommitPrimaryTransactionOperator: public TEvWriteCommitSyncTransac const ui64 TabletId; virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const NActors::TActorContext& /*ctx*/) override { - auto op = Self->GetProgressTxController().GetTxOperatorVerifiedAs(TxId); - auto copy = *op; - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "ack_tablet")("wait", JoinSeq(",", op->WaitShardsResultAck))("receive", TabletId); - AFL_VERIFY(copy.WaitShardsResultAck.erase(TabletId)); - Self->GetProgressTxController().WriteTxOperatorInfo(txc, TxId, copy.SerializeToProto().SerializeAsString()); + auto op = Self->GetProgressTxController().GetTxOperatorVerifiedAs(TxId, true); + if (!op) { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "ack_tablet_duplication")("receive", TabletId)( + "reason", "operation absent"); + } else if (!op->WaitShardsResultAck.erase(TabletId)) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_TX)("event", "ack_tablet_duplication")("wait", JoinSeq(",", op->WaitShardsResultAck))( + "receive", TabletId); + } else { + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "ack_tablet")("wait", JoinSeq(",", op->WaitShardsResultAck))( + "receive", TabletId); + Self->GetProgressTxController().WriteTxOperatorInfo(txc, TxId, op->SerializeToProto().SerializeAsString()); + op->CheckFinished(*Self); + } return true; } virtual void DoComplete(const NActors::TActorContext& /*ctx*/) override { - auto op = Self->GetProgressTxController().GetTxOperatorVerifiedAs(TxId); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "ack_tablet")("wait", JoinSeq(",", op->WaitShardsResultAck))( - "receive", TabletId); - if (!op->WaitShardsResultAck.erase(TabletId)) { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "ack_tablet_duplication")("wait", JoinSeq(",", op->WaitShardsResultAck))( - "receive", TabletId); - } - op->CheckFinished(*Self); } public: @@ -174,7 +177,7 @@ class TEvWriteCommitPrimaryTransactionOperator: public TEvWriteCommitSyncTransac void CheckFinished(TColumnShard& owner) { if (WaitShardsResultAck.empty()) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "finished"); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "finished"); owner.EnqueueProgressTx(NActors::TActivationContext::AsActorContext(), GetTxId()); } } @@ -248,7 +251,7 @@ class TEvWriteCommitPrimaryTransactionOperator: public TEvWriteCommitSyncTransac if (op->WaitShardsBrokenFlags.empty()) { AFL_VERIFY(op->WaitShardsResultAck.erase(Self->TabletID())); } - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "remove_tablet_id")("wait", JoinSeq(",", op->WaitShardsBrokenFlags))( + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "remove_tablet_id")("wait", JoinSeq(",", op->WaitShardsBrokenFlags))( "receive", Self->TabletID()); op->CheckFinished(*Self); } @@ -265,13 +268,11 @@ class TEvWriteCommitPrimaryTransactionOperator: public TEvWriteCommitSyncTransac InitializeRequests(owner); } + virtual bool DoIsInProgress() const override { + return WaitShardsResultAck.size(); + } virtual std::unique_ptr DoBuildTxPrepareForProgress(TColumnShard* owner) const override { - if (WaitShardsResultAck.empty()) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "skip_prepare_for_progress")("lock_id", LockId); - return nullptr; - } - AFL_VERIFY(ControlCounter.Inc() <= 1); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "prepare_for_progress_started")("lock_id", LockId); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "prepare_for_progress_started")("lock_id", LockId); return std::make_unique(owner, GetTxId()); } diff --git a/ydb/core/tx/columnshard/transactions/operators/ev_write/secondary.h b/ydb/core/tx/columnshard/transactions/operators/ev_write/secondary.h index ae249b07995f..0d5fc6e84648 100644 --- a/ydb/core/tx/columnshard/transactions/operators/ev_write/secondary.h +++ b/ydb/core/tx/columnshard/transactions/operators/ev_write/secondary.h @@ -20,7 +20,6 @@ class TEvWriteCommitSecondaryTransactionOperator: public TEvWriteCommitSyncTrans bool NeedReceiveBroken = false; bool ReceiveAck = false; bool SelfBroken = false; - mutable TAtomicCounter ControlCounter = 0; std::optional TxBroken; virtual NKikimrTxColumnShard::TCommitWriteTxBody SerializeToProto() const override { @@ -38,7 +37,7 @@ class TEvWriteCommitSecondaryTransactionOperator: public TEvWriteCommitSyncTrans virtual bool DoParseImpl(TColumnShard& /*owner*/, const NKikimrTxColumnShard::TCommitWriteTxBody& commitTxBody) override { if (!commitTxBody.HasSecondaryTabletData()) { - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("event", "cannot read proto")("proto", commitTxBody.DebugString()); + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD_TX)("event", "cannot read proto")("proto", commitTxBody.DebugString()); return false; } auto& protoData = commitTxBody.GetSecondaryTabletData(); @@ -63,20 +62,27 @@ class TEvWriteCommitSecondaryTransactionOperator: public TEvWriteCommitSyncTrans private: using TBase = NOlap::NDataSharing::TExtendedTransactionBase; const ui64 TxId; - - virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const NActors::TActorContext& /*ctx*/) override { - auto op = Self->GetProgressTxController().GetTxOperatorVerifiedAs(TxId); - auto copy = *op; - copy.ReceiveAck = true; - auto proto = copy.SerializeToProto(); - Self->GetProgressTxController().WriteTxOperatorInfo(txc, TxId, proto.SerializeAsString()); + bool NeedContinueFlag = false; + + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const NActors::TActorContext& ctx) override { + Y_UNUSED(ctx); + auto op = Self->GetProgressTxController().GetTxOperatorVerifiedAs(TxId, true); + if (!op) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "duplication_tablet_ack_flag")("txId", TxId); + } else { + op->ReceiveAck = true; + if (!op->NeedReceiveBroken) { + op->TxBroken = false; + } + Self->GetProgressTxController().WriteTxOperatorInfo(txc, TxId, op->SerializeToProto().SerializeAsString()); + if (!op->NeedReceiveBroken) { + NeedContinueFlag = true; + } + } return true; } virtual void DoComplete(const NActors::TActorContext& ctx) override { - auto op = Self->GetProgressTxController().GetTxOperatorVerifiedAs(TxId); - op->ReceiveAck = true; - if (!op->NeedReceiveBroken) { - op->TxBroken = false; + if (NeedContinueFlag) { Self->EnqueueProgressTx(ctx, TxId); } } @@ -100,25 +106,31 @@ class TEvWriteCommitSecondaryTransactionOperator: public TEvWriteCommitSyncTrans const ui64 TxId; const bool BrokenFlag; - virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const NActors::TActorContext& /*ctx*/) override { - auto op = Self->GetProgressTxController().GetTxOperatorVerifiedAs(TxId); - auto copy = *op; - copy.TxBroken = BrokenFlag; - auto proto = copy.SerializeToProto(); - Self->GetProgressTxController().WriteTxOperatorInfo(txc, TxId, proto.SerializeAsString()); - if (BrokenFlag) { - Self->GetProgressTxController().ExecuteOnCancel(TxId, txc); + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const NActors::TActorContext& ctx) override { + Y_UNUSED(ctx); + auto op = Self->GetProgressTxController().GetTxOperatorVerifiedAs(TxId, true); + if (!op) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "duplication_tablet_broken_flag")("txId", TxId); + } else { + op->TxBroken = BrokenFlag; + Self->GetProgressTxController().WriteTxOperatorInfo(txc, TxId, op->SerializeToProto().SerializeAsString()); + if (BrokenFlag) { + Self->GetProgressTxController().ExecuteOnCancel(TxId, txc); + } } return true; } virtual void DoComplete(const NActors::TActorContext& ctx) override { - auto op = Self->GetProgressTxController().GetTxOperatorVerifiedAs(TxId); - op->TxBroken = BrokenFlag; - op->SendBrokenFlagAck(*Self); - if (BrokenFlag) { - Self->GetProgressTxController().CompleteOnCancel(TxId, ctx); + auto op = Self->GetProgressTxController().GetTxOperatorVerifiedAs(TxId, true); + if (!op) { + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_WRITE)("event", "duplication_tablet_broken_flag")("txId", TxId); + } else { + op->SendBrokenFlagAck(*Self); + if (BrokenFlag) { + Self->GetProgressTxController().CompleteOnCancel(TxId, ctx); + } + Self->EnqueueProgressTx(ctx, TxId); } - Self->EnqueueProgressTx(ctx, TxId); } public: @@ -187,13 +199,11 @@ class TEvWriteCommitSecondaryTransactionOperator: public TEvWriteCommitSyncTrans } }; + virtual bool DoIsInProgress() const override { + return !TxBroken && (NeedReceiveBroken || !ReceiveAck); + } virtual std::unique_ptr DoBuildTxPrepareForProgress(TColumnShard* owner) const override { - if (TxBroken || (!NeedReceiveBroken && ReceiveAck)) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "skip_prepare_for_progress")("lock_id", LockId); - return nullptr; - } - AFL_VERIFY(ControlCounter.Inc() <= 1); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "prepare_for_progress_started")("lock_id", LockId); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "prepare_for_progress_started")("lock_id", LockId); return std::make_unique(owner, GetTxId()); } diff --git a/ydb/core/tx/columnshard/transactions/operators/schema.cpp b/ydb/core/tx/columnshard/transactions/operators/schema.cpp index d4019542bf1e..74a755db20c1 100644 --- a/ydb/core/tx/columnshard/transactions/operators/schema.cpp +++ b/ydb/core/tx/columnshard/transactions/operators/schema.cpp @@ -106,10 +106,6 @@ NKikimr::TConclusionStatus TSchemaTransactionOperator::ValidateTableSchema(const NTypeIds::Utf8, NTypeIds::Decimal }; - if (!schema.HasEngine() || - schema.GetEngine() != NKikimrSchemeOp::EColumnTableEngine::COLUMN_ENGINE_REPLACING_TIMESERIES) { - return TConclusionStatus::Fail("Invalid scheme engine: " + (schema.HasEngine() ? NKikimrSchemeOp::EColumnTableEngine_Name(schema.GetEngine()) : TString("No"))); - } if (!schema.KeyColumnNamesSize()) { return TConclusionStatus::Fail("There is no key columns"); diff --git a/ydb/core/tx/columnshard/transactions/operators/sharing.cpp b/ydb/core/tx/columnshard/transactions/operators/sharing.cpp index ec90f07c16eb..666ee719cb71 100644 --- a/ydb/core/tx/columnshard/transactions/operators/sharing.cpp +++ b/ydb/core/tx/columnshard/transactions/operators/sharing.cpp @@ -52,11 +52,24 @@ void TSharingTransactionOperator::DoStartProposeOnComplete(TColumnShard& /*owner } bool TSharingTransactionOperator::ProgressOnExecute( - TColumnShard& /*owner*/, const NOlap::TSnapshot& /*version*/, NTabletFlatExecutor::TTransactionContext& /*txc*/) { + TColumnShard& owner, const NOlap::TSnapshot& /*version*/, NTabletFlatExecutor::TTransactionContext& txc) { + if (!SharingTask) { + return true; + } + if (!TxFinish) { + TxFinish = SharingTask->AckInitiatorFinished(&owner, SharingTask).DetachResult(); + } + TxFinish->Execute(txc, NActors::TActivationContext::AsActorContext()); + return true; } bool TSharingTransactionOperator::ProgressOnComplete(TColumnShard& owner, const TActorContext& ctx) { + if (!SharingTask) { + return true; + } + AFL_VERIFY(!!TxFinish); + TxFinish->Complete(ctx); for (TActorId subscriber : NotifySubscribers) { auto event = MakeHolder(owner.TabletID(), GetTxId()); ctx.Send(subscriber, event.Release(), 0, 0); diff --git a/ydb/core/tx/columnshard/transactions/operators/sharing.h b/ydb/core/tx/columnshard/transactions/operators/sharing.h index 13c7df7cad0e..c5c961d98ba2 100644 --- a/ydb/core/tx/columnshard/transactions/operators/sharing.h +++ b/ydb/core/tx/columnshard/transactions/operators/sharing.h @@ -17,6 +17,7 @@ class TSharingTransactionOperator: public IProposeTxOperator, public TMonitoring mutable std::unique_ptr TxPropose; mutable std::unique_ptr TxConfirm; mutable std::unique_ptr TxAbort; + mutable std::unique_ptr TxFinish; static inline auto Registrator = TFactory::TRegistrator(NKikimrTxColumnShard::TX_KIND_SHARING); THashSet NotifySubscribers; virtual TTxController::TProposeResult DoStartProposeOnExecute(TColumnShard& owner, NTabletFlatExecutor::TTransactionContext& txc) override; diff --git a/ydb/core/tx/columnshard/transactions/tx_controller.cpp b/ydb/core/tx/columnshard/transactions/tx_controller.cpp index afb1e8a33d50..a32ebbf0250b 100644 --- a/ydb/core/tx/columnshard/transactions/tx_controller.cpp +++ b/ydb/core/tx/columnshard/transactions/tx_controller.cpp @@ -86,7 +86,7 @@ bool TTxController::Load(NTabletFlatExecutor::TTransactionContext& txc) { return false; } } - AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("override", countOverrideDeadline)("no_dl", countNoDeadline)("dl", countWithDeadline)( + AFL_INFO(NKikimrServices::TX_COLUMNSHARD_TX)("override", countOverrideDeadline)("no_dl", countNoDeadline)("dl", countWithDeadline)( "operators", Operators.size())("plan", PlanQueue.size())("dl_queue", DeadlineQueue.size()); return true; } @@ -277,10 +277,10 @@ TDuration TTxController::GetTxCompleteLag(ui64 timecastStep) const { TTxController::EPlanResult TTxController::PlanTx(const ui64 planStep, const ui64 txId, NTabletFlatExecutor::TTransactionContext& txc) { auto it = Operators.find(txId); if (it == Operators.end()) { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("event", "skip_plan_tx")("tx_id", txId); + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_TX)("event", "skip_plan_tx")("tx_id", txId); return EPlanResult::Skipped; } else { - AFL_TRACE(NKikimrServices::TX_COLUMNSHARD)("event", "plan_tx")("tx_id", txId)("plan_step", it->second->MutableTxInfo().PlanStep); + AFL_TRACE(NKikimrServices::TX_COLUMNSHARD_TX)("event", "plan_tx")("tx_id", txId)("plan_step", it->second->MutableTxInfo().PlanStep); } auto& txInfo = it->second->MutableTxInfo(); if (txInfo.PlanStep == 0) { @@ -308,12 +308,12 @@ std::shared_ptr TTxController::StartPropose const TTxController::TTxInfo& txInfo, const TString& txBody, NTabletFlatExecutor::TTransactionContext& txc) { NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("method", "TTxController::StartProposeOnExecute")("tx_info", txInfo.DebugString()); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "start"); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "start"); std::shared_ptr txOperator( TTxController::ITransactionOperator::TFactory::Construct(txInfo.TxKind, txInfo)); AFL_VERIFY(!!txOperator); if (!txOperator->Parse(Owner, txBody)) { - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("error", "cannot parse txOperator"); + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD_TX)("error", "cannot parse txOperator"); return txOperator; } Counters.OnStartProposeOnExecute(txOperator->GetOpType()); @@ -321,13 +321,13 @@ std::shared_ptr TTxController::StartPropose auto txInfoPtr = GetTxInfo(txInfo.TxId); if (!!txInfoPtr) { if (!txOperator->CheckAllowUpdate(*txInfoPtr)) { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("error", "incorrect duplication")("actual_tx", txInfoPtr->DebugString()); + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_TX)("error", "incorrect duplication")("actual_tx", txInfoPtr->DebugString()); TTxController::TProposeResult proposeResult(NKikimrTxColumnShard::EResultStatus::ERROR, TStringBuilder() << "Another commit TxId# " << txInfo.TxId << " has already been proposed"); txOperator->SetProposeStartInfo(proposeResult); return txOperator; } else { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("error", "update duplication data")("deprecated_tx", txInfoPtr->DebugString()); + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_TX)("error", "update duplication data")("deprecated_tx", txInfoPtr->DebugString()); return UpdateTxSourceInfo(txOperator->GetTxInfo(), txc); } } else { @@ -337,9 +337,9 @@ std::shared_ptr TTxController::StartPropose } else { RegisterTx(txOperator, txBody, txc); } - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "registered"); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "registered"); } else { - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("error", "problem on start")( + AFL_ERROR(NKikimrServices::TX_COLUMNSHARD_TX)("error", "problem on start")( "message", txOperator->GetProposeStartInfoVerified().GetStatusMessage()); } return txOperator; @@ -349,7 +349,7 @@ std::shared_ptr TTxController::StartPropose void TTxController::StartProposeOnComplete(ITransactionOperator& txOperator, const TActorContext& ctx) { NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("method", "TTxController::StartProposeOnComplete")("tx_id", txOperator.GetTxId()); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "start"); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "start"); txOperator.StartProposeOnComplete(Owner, ctx); Counters.OnStartProposeOnComplete(txOperator.GetOpType()); } @@ -357,18 +357,18 @@ void TTxController::StartProposeOnComplete(ITransactionOperator& txOperator, con void TTxController::FinishProposeOnExecute(const ui64 txId, NTabletFlatExecutor::TTransactionContext& txc) { NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("method", "TTxController::FinishProposeOnExecute")("tx_id", txId); if (auto txOperator = GetTxOperatorOptional(txId)) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "start"); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "start"); txOperator->FinishProposeOnExecute(Owner, txc); Counters.OnFinishProposeOnExecute(txOperator->GetOpType()); } else { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("error", "cannot found txOperator in propose transaction base")("tx_id", txId); + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_TX)("error", "cannot found txOperator in propose transaction base")("tx_id", txId); } } void TTxController::FinishProposeOnComplete(ITransactionOperator& txOperator, const TActorContext& ctx) { NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("method", "TTxController::FinishProposeOnComplete")("tx_id", txOperator.GetTxId()); - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("event", "start")("tx_info", txOperator.GetTxInfo().DebugString()); + AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD_TX)("event", "start")("tx_info", txOperator.GetTxInfo().DebugString()); TTxController::TProposeResult proposeResult = txOperator.GetProposeStartInfoVerified(); AFL_VERIFY(!txOperator.IsFail()); txOperator.FinishProposeOnComplete(Owner, ctx); @@ -379,7 +379,7 @@ void TTxController::FinishProposeOnComplete(ITransactionOperator& txOperator, co void TTxController::FinishProposeOnComplete(const ui64 txId, const TActorContext& ctx) { auto txOperator = GetTxOperatorOptional(txId); if (!txOperator) { - AFL_WARN(NKikimrServices::TX_COLUMNSHARD)("error", "cannot found txOperator in propose transaction finish")("tx_id", txId); + AFL_WARN(NKikimrServices::TX_COLUMNSHARD_TX)("error", "cannot found txOperator in propose transaction finish")("tx_id", txId); return; } return FinishProposeOnComplete(*txOperator, ctx); diff --git a/ydb/core/tx/columnshard/transactions/tx_controller.h b/ydb/core/tx/columnshard/transactions/tx_controller.h index e48f10d3796d..e4b92144ca26 100644 --- a/ydb/core/tx/columnshard/transactions/tx_controller.h +++ b/ydb/core/tx/columnshard/transactions/tx_controller.h @@ -198,6 +198,8 @@ class TTxController { std::optional Status = EStatus::Created; private: + mutable TAtomicCounter PreparationsStarted = 0; + friend class TTxController; virtual bool DoParse(TColumnShard& owner, const TString& data) = 0; virtual TTxController::TProposeResult DoStartProposeOnExecute(TColumnShard& owner, NTabletFlatExecutor::TTransactionContext& txc) = 0; @@ -215,6 +217,9 @@ class TTxController { return false; } + virtual bool DoIsInProgress() const { + return false; + } virtual std::unique_ptr DoBuildTxPrepareForProgress(TColumnShard* /*owner*/) const { return nullptr; } @@ -240,6 +245,10 @@ class TTxController { using TFactory = NObjectFactory::TParametrizedObjectFactory; using OpType = TString; + bool IsInProgress() const { + return DoIsInProgress(); + } + bool PingTimeout(TColumnShard& owner, const TMonotonic now) { return DoPingTimeout(owner, now); } @@ -257,6 +266,13 @@ class TTxController { } std::unique_ptr BuildTxPrepareForProgress(TColumnShard* owner) const { + if (!IsInProgress()) { + return nullptr; + } + if (PreparationsStarted.Val()) { + return nullptr; + } + PreparationsStarted.Inc(); return DoBuildTxPrepareForProgress(owner); } @@ -418,8 +434,11 @@ class TTxController { return TValidator::CheckNotNull(GetTxOperatorOptional(txId)); } template - std::shared_ptr GetTxOperatorVerifiedAs(const ui64 txId) const { + std::shared_ptr GetTxOperatorVerifiedAs(const ui64 txId, const bool optionalExists = false) const { auto result = GetTxOperatorOptional(txId); + if (optionalExists && !result) { + return nullptr; + } AFL_VERIFY(result); auto resultClass = dynamic_pointer_cast(result); AFL_VERIFY(resultClass); diff --git a/ydb/core/tx/columnshard/tx_reader/abstract.cpp b/ydb/core/tx/columnshard/tx_reader/abstract.cpp new file mode 100644 index 000000000000..bbda1c5cee65 --- /dev/null +++ b/ydb/core/tx/columnshard/tx_reader/abstract.cpp @@ -0,0 +1,38 @@ +#include "abstract.h" + +namespace NKikimr { + +bool ITxReader::Execute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& ctx) { + if (IsReady) { + if (!NextReaderAfterLoad) { + return true; + } else { + return NextReaderAfterLoad->Execute(txc, ctx); + } + } + IsStarted = true; + { + TMemoryProfileGuard g("ITxReader/" + StageName + "/Precharge"); + NColumnShard::TLoadTimeSignals::TLoadTimer timer = PrechargeCounters.StartGuard(); + NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("load_stage_name", "PRECHARGE:" + StageName); + if (!DoPrecharge(txc, ctx)) { + timer.AddLoadingFail(); + return false; + } + } + + { + TMemoryProfileGuard g("ITxReader/" + StageName + "/Read"); + NColumnShard::TLoadTimeSignals::TLoadTimer timer = ReaderCounters.StartGuard(); + NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("load_stage_name", "EXECUTE:" + StageName); + if (!DoExecute(txc, ctx)) { + timer.AddLoadingFail(); + return false; + } + } + IsReady = true; + NextReaderAfterLoad = BuildNextReaderAfterLoad(); + return NextReaderAfterLoad ? NextReaderAfterLoad->Execute(txc, ctx) : true; +} + +} \ No newline at end of file diff --git a/ydb/core/tx/columnshard/tx_reader/abstract.h b/ydb/core/tx/columnshard/tx_reader/abstract.h new file mode 100644 index 000000000000..109e6cfb7227 --- /dev/null +++ b/ydb/core/tx/columnshard/tx_reader/abstract.h @@ -0,0 +1,44 @@ +#pragma once +#include +#include + +#include + +namespace NKikimr { + +class ITxReader { +private: + YDB_READONLY_DEF(TString, StageName); + bool IsReady = false; + bool IsStarted = false; + std::shared_ptr NextReaderAfterLoad; + NColumnShard::TLoadTimeSignals PrechargeCounters; + NColumnShard::TLoadTimeSignals ReaderCounters; + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& ctx) = 0; + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& ctx) = 0; + + virtual std::shared_ptr BuildNextReaderAfterLoad() { + return nullptr; + } + +public: + virtual ~ITxReader() = default; + void AddNamePrefix(const TString& prefix) { + StageName = prefix + StageName; + } + + ITxReader(const TString& stageName) + : StageName(stageName) + , PrechargeCounters(NColumnShard::TLoadTimeSignals::TSignalsRegistry::GetSignal("PRECHARGE:" + stageName)) + , ReaderCounters(NColumnShard::TLoadTimeSignals::TSignalsRegistry::GetSignal("EXECUTE:" + stageName)) { + AFL_VERIFY(StageName); + } + + bool GetIsStarted() const { + return IsStarted; + } + + bool Execute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& ctx); +}; + +} // namespace NKikimr diff --git a/ydb/core/tx/columnshard/tx_reader/composite.cpp b/ydb/core/tx/columnshard/tx_reader/composite.cpp new file mode 100644 index 000000000000..ae25a605db5d --- /dev/null +++ b/ydb/core/tx/columnshard/tx_reader/composite.cpp @@ -0,0 +1,5 @@ +#include "composite.h" + +namespace NKikimr { + +} \ No newline at end of file diff --git a/ydb/core/tx/columnshard/tx_reader/composite.h b/ydb/core/tx/columnshard/tx_reader/composite.h new file mode 100644 index 000000000000..849e9c5c1c1a --- /dev/null +++ b/ydb/core/tx/columnshard/tx_reader/composite.h @@ -0,0 +1,34 @@ +#pragma once +#include "abstract.h" + +namespace NKikimr { + +class TTxCompositeReader: public ITxReader { +private: + using TBase = ITxReader; + std::vector> Children; + + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& /*txc*/, const TActorContext& /*ctx*/) override { + return true; + } + + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& ctx) override { + for (auto&& i : Children) { + if (!i->Execute(txc, ctx)) { + return false; + } + } + return true; + } + +public: + using TBase::TBase; + + void AddChildren(const std::shared_ptr& reader) { + AFL_VERIFY(!GetIsStarted()); + reader->AddNamePrefix(GetStageName() + "/"); + Children.emplace_back(reader); + } +}; + +} // namespace NKikimr diff --git a/ydb/core/tx/columnshard/tx_reader/lambda.cpp b/ydb/core/tx/columnshard/tx_reader/lambda.cpp new file mode 100644 index 000000000000..50222b22d030 --- /dev/null +++ b/ydb/core/tx/columnshard/tx_reader/lambda.cpp @@ -0,0 +1,5 @@ +#include "lambda.h" + +namespace NKikimr { + +} \ No newline at end of file diff --git a/ydb/core/tx/columnshard/tx_reader/lambda.h b/ydb/core/tx/columnshard/tx_reader/lambda.h new file mode 100644 index 000000000000..a6e81bd50802 --- /dev/null +++ b/ydb/core/tx/columnshard/tx_reader/lambda.h @@ -0,0 +1,33 @@ +#pragma once +#include "abstract.h" + +namespace NKikimr { + +class TTxLambdaReader: public ITxReader { +private: + using TBase = ITxReader; + +public: + using TFunction = std::function; + +private: + TFunction PrechargeFunction; + TFunction ExecuteFunction; + + virtual bool DoPrecharge(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& ctx) override { + return PrechargeFunction(txc, ctx); + } + + virtual bool DoExecute(NTabletFlatExecutor::TTransactionContext& txc, const TActorContext& ctx) override { + return ExecuteFunction(txc, ctx); + } + +public: + TTxLambdaReader(const TString& name, const TFunction& precharge, const TFunction& execute) + : TBase(name) + , PrechargeFunction(precharge) + , ExecuteFunction(execute) { + } +}; + +} // namespace NKikimr diff --git a/ydb/core/tx/columnshard/tx_reader/ya.make b/ydb/core/tx/columnshard/tx_reader/ya.make new file mode 100644 index 000000000000..8e9d0533295a --- /dev/null +++ b/ydb/core/tx/columnshard/tx_reader/ya.make @@ -0,0 +1,14 @@ +LIBRARY() + +SRCS( + abstract.cpp + composite.cpp + lambda.cpp +) + +PEERDIR( + ydb/core/tablet_flat + ydb/core/tx/columnshard/counters +) + +END() diff --git a/ydb/core/tx/columnshard/ut_rw/ut_backup.cpp b/ydb/core/tx/columnshard/ut_rw/ut_backup.cpp index 6463e4a0a266..8ca1b5a87245 100644 --- a/ydb/core/tx/columnshard/ut_rw/ut_backup.cpp +++ b/ydb/core/tx/columnshard/ut_rw/ut_backup.cpp @@ -82,7 +82,6 @@ Y_UNIT_TEST_SUITE(Backup) { PlanCommit(runtime, sender, ++planStep, txId); } - const ui32 start = csControllerGuard->GetInsertStartedCounter().Val(); TestWaitCondition(runtime, "insert compacted", [&]() { ++writeId; @@ -90,7 +89,7 @@ Y_UNIT_TEST_SUITE(Backup) { WriteData(runtime, sender, writeId, tableId, MakeTestBlob({writeId * 100, (writeId + 1) * 100}, schema), schema, true, &writeIds); ProposeCommit(runtime, sender, ++txId, writeIds); PlanCommit(runtime, sender, ++planStep, txId); - return csControllerGuard->GetInsertStartedCounter().Val() > start + 1; + return true; }, TDuration::Seconds(1000)); NKikimrTxColumnShard::TBackupTxBody txBody; @@ -99,7 +98,7 @@ Y_UNIT_TEST_SUITE(Backup) { txBody.MutableBackupTask()->SetTableId(tableId); txBody.MutableBackupTask()->SetSnapshotStep(backupSnapshot.GetPlanStep()); txBody.MutableBackupTask()->SetSnapshotTxId(backupSnapshot.GetTxId()); - txBody.MutableBackupTask()->MutableS3Settings()->SetEndpoint("fake"); + txBody.MutableBackupTask()->MutableS3Settings()->SetEndpoint("fake.fake"); txBody.MutableBackupTask()->MutableS3Settings()->SetSecretKey("fakeSecret"); AFL_VERIFY(csControllerGuard->GetFinishedExportsCount() == 0); UNIT_ASSERT(ProposeTx(runtime, sender, NKikimrTxColumnShard::TX_KIND_BACKUP, txBody.SerializeAsString(), ++txId)); diff --git a/ydb/core/tx/columnshard/ut_rw/ut_columnshard_read_write.cpp b/ydb/core/tx/columnshard/ut_rw/ut_columnshard_read_write.cpp index ad5ec1f688fd..bafd74ff8fe2 100644 --- a/ydb/core/tx/columnshard/ut_rw/ut_columnshard_read_write.cpp +++ b/ydb/core/tx/columnshard/ut_rw/ut_columnshard_read_write.cpp @@ -1,25 +1,28 @@ -#include -#include #include -#include -#include -#include -#include +#include #include -#include -#include #include -#include +#include +#include +#include #include #include -#include +#include +#include #include #include +#include + #include -#include #include #include +#include +#include + +#include +#include #include +#include namespace NKikimr { @@ -27,8 +30,7 @@ using namespace NColumnShard; using namespace Tests; using namespace NTxUT; -namespace -{ +namespace { namespace NTypeIds = NScheme::NTypeIds; using TTypeId = NScheme::TTypeId; @@ -37,8 +39,8 @@ using TTypeInfo = NScheme::TTypeInfo; using TDefaultTestsController = NKikimr::NYDBTest::NColumnShard::TController; template -bool DataHas(const std::vector>& batches, std::pair range, - bool requireUniq = false, const std::string& columnName = "timestamp") { +bool DataHas(const std::vector>& batches, std::pair range, bool requireUniq = false, + const std::string& columnName = "timestamp", const bool inverseCheck = false) { static constexpr const bool isStrKey = std::is_same_v; THashMap keys; @@ -79,24 +81,24 @@ bool DataHas(const std::vector>& batches, st } } + bool problems = false; for (auto& [key, count] : keys) { - if (!count) { + if (!count && !inverseCheck) { Cerr << "No key: " << key << "\n"; - return false; + problems = true; } if (requireUniq && count > 1) { Cerr << "Not unique key: " << key << " (count: " << count << ")\n"; - return false; + problems = true; } } - return true; + return !problems; } template -bool DataHas(const std::vector& blobs, const TString& srtSchema, std::pair range, - bool requireUniq = false, const std::string& columnName = "timestamp") { - +bool DataHas(const std::vector& blobs, const TString& srtSchema, std::pair range, bool requireUniq = false, + const std::string& columnName = "timestamp") { auto schema = NArrow::DeserializeSchema(srtSchema); std::vector> batches; for (auto& blob : blobs) { @@ -106,6 +108,11 @@ bool DataHas(const std::vector& blobs, const TString& srtSchema, std::p return DataHas(batches, range, requireUniq, columnName); } +bool DataNotHas(const std::vector>& batches, std::pair range, bool requireUniq = false, + const std::string& columnName = "timestamp") { + return DataHas(batches, range, requireUniq, columnName, true); +} + template bool DataHasOnly(const std::vector>& batches, std::pair range) { static constexpr const bool isStrKey = std::is_same_v; @@ -350,7 +357,7 @@ void TestWrite(const TestTableDescription& table) { const auto& ydbSchema = table.Schema; - bool ok = WriteData(runtime, sender, writeId++, tableId, MakeTestBlob({0, 100}, ydbSchema), ydbSchema); + bool ok = WriteData(runtime, sender, writeId++, tableId, MakeTestBlob({ 0, 100 }, ydbSchema), ydbSchema); UNIT_ASSERT(ok); auto schema = ydbSchema; @@ -364,13 +371,13 @@ void TestWrite(const TestTableDescription& table) { TTestBlobOptions optsNulls; optsNulls.NullColumns.emplace("timestamp"); - ok = WriteData(runtime, sender, writeId++, tableId, MakeTestBlob({0, 100}, ydbSchema, optsNulls), ydbSchema); + ok = WriteData(runtime, sender, writeId++, tableId, MakeTestBlob({ 0, 100 }, ydbSchema, optsNulls), ydbSchema); UNIT_ASSERT(!ok); // missing columns schema = NArrow::NTest::TTestColumn::CropSchema(schema, 4); - ok = WriteData(runtime, sender, writeId++, tableId, MakeTestBlob({0, 100}, schema), schema); + ok = WriteData(runtime, sender, writeId++, tableId, MakeTestBlob({ 0, 100 }, schema), schema); UNIT_ASSERT(ok); // wrong first key column type (with supported layout: Int64 vs Timestamp) @@ -378,8 +385,7 @@ void TestWrite(const TestTableDescription& table) { schema = ydbSchema; schema[0].SetType(TTypeInfo(NTypeIds::Int64)); - ok = WriteData(runtime, sender, writeId++, tableId, MakeTestBlob({0, 100}, schema), - schema); + ok = WriteData(runtime, sender, writeId++, tableId, MakeTestBlob({ 0, 100 }, schema), schema); UNIT_ASSERT(!ok); // wrong type (no additional schema - fails in case of wrong layout) @@ -387,7 +393,7 @@ void TestWrite(const TestTableDescription& table) { for (size_t i = 0; i < ydbSchema.size(); ++i) { schema = ydbSchema; schema[i].SetType(TTypeInfo(NTypeIds::Int8)); - ok = WriteData(runtime, sender, writeId++, tableId, MakeTestBlob({0, 100}, schema), schema); + ok = WriteData(runtime, sender, writeId++, tableId, MakeTestBlob({ 0, 100 }, schema), schema); UNIT_ASSERT(!ok); } @@ -396,14 +402,14 @@ void TestWrite(const TestTableDescription& table) { for (size_t i = 0; i < ydbSchema.size(); ++i) { schema = ydbSchema; schema[i].SetType(TTypeInfo(NTypeIds::Int64)); - ok = WriteData(runtime, sender, writeId++, tableId, MakeTestBlob({0, 100}, schema), schema); + ok = WriteData(runtime, sender, writeId++, tableId, MakeTestBlob({ 0, 100 }, schema), schema); UNIT_ASSERT(ok == (ydbSchema[i].GetType() == TTypeInfo(NTypeIds::Int64))); } schema = ydbSchema; schema[1].SetType(TTypeInfo(NTypeIds::Utf8)); schema[5].SetType(TTypeInfo(NTypeIds::Int32)); - ok = WriteData(runtime, sender, writeId++, tableId, MakeTestBlob({0, 100}, schema), schema); + ok = WriteData(runtime, sender, writeId++, tableId, MakeTestBlob({ 0, 100 }, schema), schema); UNIT_ASSERT(!ok); // reordered columns @@ -415,16 +421,16 @@ void TestWrite(const TestTableDescription& table) { schema.push_back(NArrow::NTest::TTestColumn(name, typeInfo)); } - ok = WriteData(runtime, sender, writeId++, tableId, MakeTestBlob({0, 100}, schema), schema); + ok = WriteData(runtime, sender, writeId++, tableId, MakeTestBlob({ 0, 100 }, schema), schema); UNIT_ASSERT(ok); // too much data - TString bigData = MakeTestBlob({0, 150 * 1000}, ydbSchema); + TString bigData = MakeTestBlob({ 0, 150 * 1000 }, ydbSchema); UNIT_ASSERT(bigData.size() > NColumnShard::TLimits::GetMaxBlobSize()); UNIT_ASSERT(bigData.size() < NColumnShard::TLimits::GetMaxBlobSize() + 2 * 1024 * 1024); ok = WriteData(runtime, sender, writeId++, tableId, bigData, ydbSchema); - UNIT_ASSERT(!ok); + UNIT_ASSERT(ok); } void TestWriteOverload(const TestTableDescription& table) { @@ -445,7 +451,7 @@ void TestWriteOverload(const TestTableDescription& table) { SetupSchema(runtime, sender, tableId, table); - TString testBlob = MakeTestBlob({0, 100 * 1000}, table.Schema); + TString testBlob = MakeTestBlob({ 0, 100 * 1000 }, table.Schema); UNIT_ASSERT(testBlob.size() > NOlap::TCompactionLimits::MAX_BLOB_SIZE / 2); UNIT_ASSERT(testBlob.size() < NOlap::TCompactionLimits::MAX_BLOB_SIZE); @@ -482,14 +488,14 @@ void TestWriteOverload(const TestTableDescription& table) { UNIT_ASSERT(WriteData(runtime, sender, ++writeId, tableId, testBlob, table.Schema, false)); } - UNIT_ASSERT_VALUES_EQUAL(WaitWriteResult(runtime, TTestTxConfig::TxTablet0), (ui32)NKikimrTxColumnShard::EResultStatus::OVERLOADED); + UNIT_ASSERT_VALUES_EQUAL(WaitWriteResult(runtime, TTestTxConfig::TxTablet0), (ui32)NKikimrDataEvents::TEvWriteResult::STATUS_OVERLOADED); while (capturedWrites.size()) { resendOneCaptured(); - UNIT_ASSERT_VALUES_EQUAL(WaitWriteResult(runtime, TTestTxConfig::TxTablet0), (ui32)NKikimrTxColumnShard::EResultStatus::SUCCESS); + UNIT_ASSERT_VALUES_EQUAL(WaitWriteResult(runtime, TTestTxConfig::TxTablet0), (ui32)NKikimrDataEvents::TEvWriteResult::STATUS_COMPLETED); } - UNIT_ASSERT(WriteData(runtime, sender, ++writeId, tableId, testBlob, table.Schema)); // OK after overload + UNIT_ASSERT(WriteData(runtime, sender, ++writeId, tableId, testBlob, table.Schema)); // OK after overload } // TODO: Improve test. It does not catch KIKIMR-14890 @@ -514,7 +520,7 @@ void TestWriteReadDup(const TestTableDescription& table = {}) { SetupSchema(runtime, sender, tableId); constexpr ui32 numRows = 10; - std::pair portion = {10, 10 + numRows}; + std::pair portion = { 10, 10 + numRows }; auto testData = MakeTestBlob(portion, ydbSchema); TAutoPtr handle; @@ -524,8 +530,9 @@ void TestWriteReadDup(const TestTableDescription& table = {}) { TSet txIds; for (ui32 i = 0; i <= 5; ++i) { std::vector writeIds; - UNIT_ASSERT(WriteData(runtime, sender, ++writeId, tableId, testData, ydbSchema, true, &writeIds)); - ProposeCommit(runtime, sender, ++txId, writeIds); + ++txId; + UNIT_ASSERT(WriteData(runtime, sender, ++writeId, tableId, testData, ydbSchema, true, &writeIds, NEvWrite::EModificationType::Upsert, txId)); + ProposeCommit(runtime, sender, txId, writeIds, txId); txIds.insert(txId); } PlanCommit(runtime, sender, planStep, txIds); @@ -533,82 +540,20 @@ void TestWriteReadDup(const TestTableDescription& table = {}) { // read if (planStep != initPlanStep) { TShardReader reader(runtime, TTestTxConfig::TxTablet0, tableId, NOlap::TSnapshot(planStep - 1, Max())); - reader.SetReplyColumns({"timestamp"}); + reader.SetReplyColumns({ "timestamp" }); auto rb = reader.ReadAll(); UNIT_ASSERT(reader.IsCorrectlyFinished()); UNIT_ASSERT(CheckOrdered(rb)); - UNIT_ASSERT(DataHas({rb}, portion, true)); - } - } -} - -void TestWriteReadLongTxDup() { - TTestBasicRuntime runtime; - TTester::Setup(runtime); - auto csDefaultControllerGuard = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); - - TActorId sender = runtime.AllocateEdgeActor(); - CreateTestBootstrapper(runtime, CreateTestTabletInfo(TTestTxConfig::TxTablet0, TTabletTypes::ColumnShard), &CreateColumnShard); - - TDispatchOptions options; - options.FinalEvents.push_back(TDispatchOptions::TFinalEventCondition(TEvTablet::EvBoot)); - runtime.DispatchEvents(options); - - // - - ui64 tableId = 1; - auto ydbSchema = TTestSchema::YdbSchema(); - SetupSchema(runtime, sender, tableId); - - constexpr ui32 numRows = 10; - std::pair portion = {10, 10 + numRows}; - - NLongTxService::TLongTxId longTxId; - UNIT_ASSERT(longTxId.ParseString("ydb://long-tx/01ezvvxjdk2hd4vdgjs68knvp8?node_id=1")); - - ui64 txId = 0; - ui64 planStep = 100; - std::optional writeId; - - // Only the first blob with dedup pair {longTx, dedupId} should be inserted - // Others should return OK (write retries emulation) - for (ui32 i = 0; i < 4; ++i) { - auto data = MakeTestBlob({portion.first + i, portion.second + i}, ydbSchema); - UNIT_ASSERT(data.size() < NColumnShard::TLimits::MIN_BYTES_TO_INSERT); - - auto writeIdOpt = WriteData(runtime, sender, longTxId, tableId, 1, data, ydbSchema); - UNIT_ASSERT(writeIdOpt); - if (!i) { - writeId = *writeIdOpt; + UNIT_ASSERT(DataHas({ rb }, portion, true)); } - UNIT_ASSERT_EQUAL(*writeIdOpt, *writeId); - } - - ProposeCommit(runtime, sender, ++txId, {*writeId}); - TSet txIds = {txId}; - PlanCommit(runtime, sender, planStep, txIds); - - // read - TAutoPtr handle; - { - TShardReader reader(runtime, TTestTxConfig::TxTablet0, tableId, NOlap::TSnapshot(planStep, txId)); - reader.SetReplyColumns(TTestSchema::ExtractNames(ydbSchema)); - auto rb = reader.ReadAll(); - UNIT_ASSERT(reader.IsCorrectlyFinished()); - UNIT_ASSERT(rb); - UNIT_ASSERT(rb->num_rows()); - Y_UNUSED(NArrow::TColumnOperator().VerifyIfAbsent().Extract(rb, TTestSchema::ExtractNames(ydbSchema))); - UNIT_ASSERT((ui32)rb->num_columns() == TTestSchema::ExtractNames(ydbSchema).size()); - UNIT_ASSERT(CheckOrdered(rb)); - UNIT_ASSERT(DataHas({rb}, portion, true)); - UNIT_ASSERT(DataHasOnly({rb}, portion)); } } void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString codec = "") { auto csControllerGuard = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); csControllerGuard->DisableBackground(NKikimr::NYDBTest::ICSController::EBackground::Compaction); - csControllerGuard->SetOverrideReadTimeoutClean(TDuration::Max()); + csControllerGuard->SetOverrideMaxReadStaleness(TDuration::Max()); + csControllerGuard->SetOverrideBlobSplitSettings(NOlap::NSplitter::TSplitSettings()); TTestBasicRuntime runtime; TTester::Setup(runtime); @@ -622,8 +567,8 @@ void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString options.FinalEvents.push_back(TDispatchOptions::TFinalEventCondition(TEvTablet::EvBoot)); runtime.DispatchEvents(options); - auto write = [&](TTestBasicRuntime& runtime, TActorId& sender, ui64 writeId, ui64 tableId, - const TString& data, const std::vector& ydbSchema, std::vector& intWriteIds) { + auto write = [&](TTestBasicRuntime& runtime, TActorId& sender, ui64 writeId, ui64 tableId, const TString& data, + const std::vector& ydbSchema, std::vector& intWriteIds) { bool ok = WriteData(runtime, sender, writeId, tableId, data, ydbSchema, true, &intWriteIds); if (reboots) { RebootTablet(runtime, TTestTxConfig::TxTablet0, sender); @@ -631,8 +576,7 @@ void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString return ok; }; - auto proposeCommit = [&](TTestBasicRuntime& runtime, TActorId& sender, ui64 txId, - const std::vector& writeIds) { + auto proposeCommit = [&](TTestBasicRuntime& runtime, TActorId& sender, ui64 txId, const std::vector& writeIds) { ProposeCommit(runtime, sender, txId, writeIds); if (reboots) { RebootTablet(runtime, TTestTxConfig::TxTablet0, sender); @@ -660,12 +604,8 @@ void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString // -----xx.. // xx---- // -xxxxx - std::vector> portion = { - {200, 300}, - {250, 250 + 80 * 1000}, // committed -> index - {0, 100}, - {50, 300} - }; + std::vector> portion = { { 200, 300 }, { 250, 250 + 80 * 1000 }, // committed -> index + { 0, 100 }, { 50, 300 } }; // write 1: ins:1, cmt:0, idx:0 @@ -678,7 +618,7 @@ void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString NActors::TLogContextGuard guard = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("TEST_STEP", 1); TShardReader reader(runtime, TTestTxConfig::TxTablet0, tableId, NOlap::TSnapshot(0, 1)); - reader.SetReplyColumns({"resource_type"}); + reader.SetReplyColumns({ "resource_type" }); auto rb = reader.ReadAll(); UNIT_ASSERT(reader.IsCorrectlyFinished()); UNIT_ASSERT_EQUAL(rb, nullptr); @@ -695,7 +635,7 @@ void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString NActors::TLogContextGuard guard = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("TEST_STEP", 2); TShardReader reader(runtime, TTestTxConfig::TxTablet0, tableId, NOlap::TSnapshot(0, 1)); - reader.SetReplyColumns({"resource_type"}); + reader.SetReplyColumns({ "resource_type" }); auto rb = reader.ReadAll(); UNIT_ASSERT(reader.IsCorrectlyFinished()); UNIT_ASSERT_EQUAL(rb, nullptr); @@ -713,14 +653,14 @@ void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString UNIT_ASSERT(rb->num_rows()); UNIT_ASSERT(reader.IsCorrectlyFinished()); UNIT_ASSERT(CheckOrdered(rb)); - UNIT_ASSERT(DataHas({rb}, portion[0])); + UNIT_ASSERT(DataHas({ rb }, portion[0])); } // read 4 (column by id) { NActors::TLogContextGuard guard = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("TEST_STEP", 4); TShardReader reader(runtime, TTestTxConfig::TxTablet0, tableId, NOlap::TSnapshot(planStep, txId)); - reader.SetReplyColumnIds({1}); + reader.SetReplyColumnIds({ 1 }); auto rb = reader.ReadAll(); UNIT_ASSERT(rb); Y_UNUSED(NArrow::TColumnOperator().VerifyIfAbsent().Extract(rb, std::vector({ "timestamp" }))); @@ -728,14 +668,14 @@ void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString UNIT_ASSERT(rb->num_rows()); UNIT_ASSERT(reader.IsCorrectlyFinished()); UNIT_ASSERT(CheckOrdered(rb)); - UNIT_ASSERT(DataHas({rb}, portion[0])); + UNIT_ASSERT(DataHas({ rb }, portion[0])); } // read 5 (2 columns by name) { NActors::TLogContextGuard guard = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("TEST_STEP", 5); TShardReader reader(runtime, TTestTxConfig::TxTablet0, tableId, NOlap::TSnapshot(planStep, txId)); - reader.SetReplyColumns({"timestamp", "message"}); + reader.SetReplyColumns({ "timestamp", "message" }); auto rb = reader.ReadAll(); UNIT_ASSERT(rb); Y_UNUSED(NArrow::TColumnOperator().VerifyIfAbsent().Extract(rb, std::vector({ "timestamp", "message" }))); @@ -743,7 +683,7 @@ void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString UNIT_ASSERT(rb->num_rows()); UNIT_ASSERT(reader.IsCorrectlyFinished()); UNIT_ASSERT(CheckOrdered(rb)); - UNIT_ASSERT(DataHas({rb}, portion[0])); + UNIT_ASSERT(DataHas({ rb }, portion[0])); } // write 2 (big portion of data): ins:1, cmt:1, idx:0 @@ -773,7 +713,7 @@ void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString { NActors::TLogContextGuard guard = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("TEST_STEP", 6); TShardReader reader(runtime, TTestTxConfig::TxTablet0, tableId, NOlap::TSnapshot(0, 1)); - reader.SetReplyColumns({"timestamp", "message"}); + reader.SetReplyColumns({ "timestamp", "message" }); auto rb = reader.ReadAll(); UNIT_ASSERT(!rb); UNIT_ASSERT(reader.IsCorrectlyFinished()); @@ -791,9 +731,9 @@ void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString UNIT_ASSERT((ui32)rb->num_columns() == TTestSchema::ExtractNames(ydbSchema).size()); UNIT_ASSERT(rb->num_rows()); UNIT_ASSERT(CheckOrdered(rb)); - UNIT_ASSERT(DataHas({rb}, portion[0])); - UNIT_ASSERT(!DataHas({rb}, portion[1])); - UNIT_ASSERT(!DataHas({rb}, portion[2])); + UNIT_ASSERT(DataHas({ rb }, portion[0])); + UNIT_ASSERT(DataNotHas({ rb }, portion[1])); + UNIT_ASSERT(DataNotHas({ rb }, portion[2])); } // read 8, planstep 22 (full index) @@ -808,9 +748,9 @@ void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString UNIT_ASSERT((ui32)rb->num_columns() == TTestSchema::ExtractNames(ydbSchema).size()); UNIT_ASSERT(rb->num_rows()); UNIT_ASSERT(CheckOrdered(rb)); - UNIT_ASSERT(DataHas({rb}, portion[0])); - UNIT_ASSERT(DataHas({rb}, portion[1])); - UNIT_ASSERT(!DataHas({rb}, portion[2])); + UNIT_ASSERT(DataHas({ rb }, portion[0])); + UNIT_ASSERT(DataHas({ rb }, portion[1])); + UNIT_ASSERT(DataNotHas({ rb }, portion[2])); } // commit 3: ins:0, cmt:1, idx:1 @@ -838,10 +778,10 @@ void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString UNIT_ASSERT((ui32)rb->num_columns() == TTestSchema::ExtractNames(ydbSchema).size()); UNIT_ASSERT(rb->num_rows()); UNIT_ASSERT(CheckOrdered(rb)); - UNIT_ASSERT(DataHas({rb}, portion[0])); - UNIT_ASSERT(DataHas({rb}, portion[1])); - UNIT_ASSERT(DataHas({rb}, portion[2])); - UNIT_ASSERT(!DataHas({rb}, portion[3])); + UNIT_ASSERT(DataHas({ rb }, portion[0])); + UNIT_ASSERT(DataHas({ rb }, portion[1])); + UNIT_ASSERT(DataHas({ rb }, portion[2])); + UNIT_ASSERT(DataNotHas({ rb }, portion[3])); } // commit 4: ins:0, cmt:2, idx:1 (with duplicates in PK) @@ -863,11 +803,11 @@ void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString UNIT_ASSERT((ui32)rb->num_columns() == TTestSchema::ExtractNames(ydbSchema).size()); UNIT_ASSERT(rb->num_rows()); UNIT_ASSERT(CheckOrdered(rb)); - UNIT_ASSERT(DataHas({rb}, portion[0])); - UNIT_ASSERT(DataHas({rb}, portion[1])); - UNIT_ASSERT(DataHas({rb}, portion[2])); - UNIT_ASSERT(DataHas({rb}, portion[3])); - UNIT_ASSERT(DataHas({rb}, {0, 500}, true)); + UNIT_ASSERT(DataHas({ rb }, portion[0])); + UNIT_ASSERT(DataHas({ rb }, portion[1])); + UNIT_ASSERT(DataHas({ rb }, portion[2])); + UNIT_ASSERT(DataHas({ rb }, portion[3])); + UNIT_ASSERT(DataHas({ rb }, { 0, 500 }, false)); const ui64 compactedBytes = reader.GetReadStat("compacted_bytes"); const ui64 insertedBytes = reader.GetReadStat("inserted_bytes"); @@ -896,13 +836,12 @@ void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString } } - // read 11 (range predicate: closed interval) { NActors::TLogContextGuard guard = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("TEST_STEP", 11); TShardReader reader(runtime, TTestTxConfig::TxTablet0, tableId, NOlap::TSnapshot(24, txId)); reader.SetReplyColumns(TTestSchema::ExtractNames(ydbSchema)); - reader.AddRange(MakeTestRange({10, 42}, true, true, testYdbPk)); + reader.AddRange(MakeTestRange({ 10, 42 }, true, true, testYdbPk)); auto rb = reader.ReadAll(); UNIT_ASSERT(rb); UNIT_ASSERT(reader.IsCorrectlyFinished()); @@ -910,8 +849,8 @@ void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString UNIT_ASSERT((ui32)rb->num_columns() == TTestSchema::ExtractNames(ydbSchema).size()); UNIT_ASSERT(rb->num_rows()); UNIT_ASSERT(CheckOrdered(rb)); - UNIT_ASSERT(DataHas({rb}, {10, 42 + 1})); - UNIT_ASSERT(DataHasOnly({rb}, {10, 42 + 1})); + UNIT_ASSERT(DataHas({ rb }, { 10, 42 + 1 })); + UNIT_ASSERT(DataHasOnly({ rb }, { 10, 42 + 1 })); } // read 12 (range predicate: open interval) @@ -919,7 +858,7 @@ void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString NActors::TLogContextGuard guard = NActors::TLogContextBuilder::Build(NKikimrServices::TX_COLUMNSHARD)("TEST_STEP", 11); TShardReader reader(runtime, TTestTxConfig::TxTablet0, tableId, NOlap::TSnapshot(24, txId)); reader.SetReplyColumns(TTestSchema::ExtractNames(ydbSchema)); - reader.AddRange(MakeTestRange({10, 42}, false, false, testYdbPk)); + reader.AddRange(MakeTestRange({ 10, 42 }, false, false, testYdbPk)); auto rb = reader.ReadAll(); UNIT_ASSERT(rb); UNIT_ASSERT(reader.IsCorrectlyFinished()); @@ -927,8 +866,8 @@ void TestWriteRead(bool reboots, const TestTableDescription& table = {}, TString UNIT_ASSERT((ui32)rb->num_columns() == TTestSchema::ExtractNames(ydbSchema).size()); UNIT_ASSERT(rb->num_rows()); UNIT_ASSERT(CheckOrdered(rb)); - UNIT_ASSERT(DataHas({rb}, {10 + 1, 41 + 1})); - UNIT_ASSERT(DataHasOnly({rb}, {10 + 1, 41 + 1})); + UNIT_ASSERT(DataHas({ rb }, { 10 + 1, 41 + 1 })); + UNIT_ASSERT(DataHasOnly({ rb }, { 10 + 1, 41 + 1 })); } } @@ -936,6 +875,7 @@ void TestCompactionInGranuleImpl(bool reboots, const TestTableDescription& table TTestBasicRuntime runtime; TTester::Setup(runtime); auto csDefaultControllerGuard = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); + runtime.SetLogPriority(NKikimrServices::TX_COLUMNSHARD_SCAN, NActors::NLog::PRI_DEBUG); TActorId sender = runtime.AllocateEdgeActor(); CreateTestBootstrapper(runtime, CreateTestTabletInfo(TTestTxConfig::TxTablet0, TTabletTypes::ColumnShard), &CreateColumnShard); @@ -944,8 +884,8 @@ void TestCompactionInGranuleImpl(bool reboots, const TestTableDescription& table options.FinalEvents.push_back(TDispatchOptions::TFinalEventCondition(TEvTablet::EvBoot)); runtime.DispatchEvents(options); - auto write = [&](TTestBasicRuntime& runtime, TActorId& sender, ui64 writeId, ui64 tableId, - const TString& data, const std::vector& ydbSchema, std::vector& writeIds) { + auto write = [&](TTestBasicRuntime& runtime, TActorId& sender, ui64 writeId, ui64 tableId, const TString& data, + const std::vector& ydbSchema, std::vector& writeIds) { bool ok = WriteData(runtime, sender, writeId, tableId, data, ydbSchema, true, &writeIds); if (reboots) { RebootTablet(runtime, TTestTxConfig::TxTablet0, sender); @@ -953,8 +893,7 @@ void TestCompactionInGranuleImpl(bool reboots, const TestTableDescription& table return ok; }; - auto proposeCommit = [&](TTestBasicRuntime& runtime, TActorId& sender, ui64 txId, - const std::vector& writeIds) { + auto proposeCommit = [&](TTestBasicRuntime& runtime, TActorId& sender, ui64 txId, const std::vector& writeIds) { ProposeCommit(runtime, sender, txId, writeIds); if (reboots) { RebootTablet(runtime, TTestTxConfig::TxTablet0, sender); @@ -983,14 +922,14 @@ void TestCompactionInGranuleImpl(bool reboots, const TestTableDescription& table // Write same keys: merge on compaction static const ui32 triggerPortionSize = 75 * 1000; - std::pair triggerPortion = {0, triggerPortionSize}; + std::pair triggerPortion = { 0, triggerPortionSize }; TString triggerData = MakeTestBlob(triggerPortion, ydbSchema); UNIT_ASSERT(triggerData.size() > NColumnShard::TLimits::MIN_BYTES_TO_INSERT); UNIT_ASSERT(triggerData.size() < NColumnShard::TLimits::GetMaxBlobSize()); static const ui32 portionSize = 1; - ui32 numWrites = NColumnShard::TLimits::MIN_SMALL_BLOBS_TO_INSERT; // trigger InsertTable -> Index + ui32 numWrites = NColumnShard::TLimits::MIN_SMALL_BLOBS_TO_INSERT; // trigger InsertTable -> Index // inserts triggered by count ui32 pos = triggerPortionSize; @@ -998,7 +937,7 @@ void TestCompactionInGranuleImpl(bool reboots, const TestTableDescription& table std::vector ids; ids.reserve(numWrites); for (ui32 w = 0; w < numWrites; ++w, ++writeId, pos += portionSize) { - std::pair portion = {pos, pos + portionSize}; + std::pair portion = { pos, pos + portionSize }; TString data = MakeTestBlob(portion, ydbSchema); UNIT_ASSERT(WriteData(runtime, sender, writeId, tableId, data, ydbSchema, true, &ids)); @@ -1011,7 +950,7 @@ void TestCompactionInGranuleImpl(bool reboots, const TestTableDescription& table proposeCommit(runtime, sender, txId, ids); planCommit(runtime, sender, planStep, txId); } - std::pair smallWrites = {triggerPortionSize, pos}; + std::pair smallWrites = { triggerPortionSize, pos }; // inserts triggered by size NOlap::TCompactionLimits engineLimits; @@ -1031,17 +970,17 @@ void TestCompactionInGranuleImpl(bool reboots, const TestTableDescription& table for (ui32 i = 0; i < 2; ++i) { TShardReader reader(runtime, TTestTxConfig::TxTablet0, tableId, NOlap::TSnapshot(planStep, txId)); - reader.SetReplyColumns({"timestamp", "message"}); + reader.SetReplyColumns({ "timestamp", "message" }); auto rb = reader.ReadAll(); UNIT_ASSERT(rb); UNIT_ASSERT(reader.IsCorrectlyFinished()); if (ydbPk[0].GetType() == TTypeInfo(NTypeIds::String) || ydbPk[0].GetType() == TTypeInfo(NTypeIds::Utf8)) { - UNIT_ASSERT(DataHas({rb}, triggerPortion, true)); - UNIT_ASSERT(DataHas({rb}, smallWrites, true)); + UNIT_ASSERT(DataHas({ rb }, triggerPortion, true)); + UNIT_ASSERT(DataHas({ rb }, smallWrites, true)); } else { - UNIT_ASSERT(DataHas({rb}, triggerPortion, true)); - UNIT_ASSERT(DataHas({rb}, smallWrites, true)); + UNIT_ASSERT(DataHas({ rb }, triggerPortion, true)); + UNIT_ASSERT(DataHas({ rb }, smallWrites, true)); } RebootTablet(runtime, TTestTxConfig::TxTablet0, sender); } @@ -1054,7 +993,7 @@ using TAggAssignment = NKikimrSSA::TProgram::TAggregateAssignment; static NKikimrSSA::TProgram MakeSelect(TAssignment::EFunction compareId = TAssignment::FUNC_CMP_EQUAL) { NKikimrSSA::TProgram ssa; - std::vector columnIds = {1, 9, 5}; + std::vector columnIds = { 1, 9, 5 }; ui32 tmpColumnId = 100; auto* line1 = ssa.AddCommand(); @@ -1078,7 +1017,7 @@ static NKikimrSSA::TProgram MakeSelect(TAssignment::EFunction compareId = TAssig static NKikimrSSA::TProgram MakeSelectLike(TAssignment::EFunction likeId, const TString& pattern) { NKikimrSSA::TProgram ssa; - std::vector columnIds = {6}; // message + std::vector columnIds = { 6 }; // message auto* line1 = ssa.AddCommand(); auto* l1_assign = line1->MutableAssign(); @@ -1102,9 +1041,7 @@ static NKikimrSSA::TProgram MakeSelectLike(TAssignment::EFunction likeId, const } // SELECT min(x), max(x), some(x), count(x) FROM t [GROUP BY key[0], key[1], ...] -NKikimrSSA::TProgram MakeSelectAggregates(ui32 columnId, const std::vector& keys = {}, - bool addProjection = true) -{ +NKikimrSSA::TProgram MakeSelectAggregates(ui32 columnId, const std::vector& keys = {}, bool addProjection = true) { NKikimrSSA::TProgram ssa; auto* line1 = ssa.AddCommand(); @@ -1150,10 +1087,8 @@ NKikimrSSA::TProgram MakeSelectAggregates(ui32 columnId, const std::vector } // SELECT min(x), max(x), some(x), count(x) FROM t WHERE y = 1 [GROUP BY key[0], key[1], ...] -NKikimrSSA::TProgram MakeSelectAggregatesWithFilter(ui32 columnId, ui32 filterColumnId, - const std::vector& keys = {}, - bool addProjection = true) -{ +NKikimrSSA::TProgram MakeSelectAggregatesWithFilter( + ui32 columnId, ui32 filterColumnId, const std::vector& keys = {}, bool addProjection = true) { NKikimrSSA::TProgram ssa; auto* line1 = ssa.AddCommand(); @@ -1218,8 +1153,7 @@ NKikimrSSA::TProgram MakeSelectAggregatesWithFilter(ui32 columnId, ui32 filterCo return ssa; } -void TestReadWithProgram(const TestTableDescription& table = {}) -{ +void TestReadWithProgram(const TestTableDescription& table = {}) { TTestBasicRuntime runtime; TTester::Setup(runtime); auto csDefaultControllerGuard = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); @@ -1238,9 +1172,9 @@ void TestReadWithProgram(const TestTableDescription& table = {}) SetupSchema(runtime, sender, tableId, table); - { // write some data + { // write some data std::vector writeIds; - bool ok = WriteData(runtime, sender, writeId, tableId, MakeTestBlob({0, 100}, table.Schema), table.Schema, true, &writeIds); + bool ok = WriteData(runtime, sender, writeId, tableId, MakeTestBlob({ 0, 100 }, table.Schema), table.Schema, true, &writeIds); UNIT_ASSERT(ok); ProposeCommit(runtime, sender, txId, writeIds); @@ -1290,7 +1224,7 @@ void TestReadWithProgram(const TestTableDescription& table = {}) UNIT_ASSERT(rb->num_rows()); Y_UNUSED(NArrow::TColumnOperator().VerifyIfAbsent().Extract(rb, std::vector({ "level", "timestamp" }))); UNIT_ASSERT(rb->num_columns() == 2); - UNIT_ASSERT(DataHas({rb}, {0, 100}, true)); + UNIT_ASSERT(DataHas({ rb }, { 0, 100 }, true)); break; case 2: UNIT_ASSERT(!rb || !rb->num_rows()); @@ -1309,8 +1243,7 @@ void TestReadWithProgramLike(const TestTableDescription& table = {}) { auto csDefaultControllerGuard = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); TActorId sender = runtime.AllocateEdgeActor(); - CreateTestBootstrapper(runtime, - CreateTestTabletInfo(TTestTxConfig::TxTablet0, TTabletTypes::ColumnShard), &CreateColumnShard); + CreateTestBootstrapper(runtime, CreateTestTabletInfo(TTestTxConfig::TxTablet0, TTabletTypes::ColumnShard), &CreateColumnShard); TDispatchOptions options; options.FinalEvents.push_back(TDispatchOptions::TFinalEventCondition(TEvTablet::EvBoot)); @@ -1323,9 +1256,9 @@ void TestReadWithProgramLike(const TestTableDescription& table = {}) { SetupSchema(runtime, sender, tableId, table); - { // write some data + { // write some data std::vector writeIds; - bool ok = WriteData(runtime, sender, writeId, tableId, MakeTestBlob({0, 100}, table.Schema), table.Schema, true, &writeIds); + bool ok = WriteData(runtime, sender, writeId, tableId, MakeTestBlob({ 0, 100 }, table.Schema), table.Schema, true, &writeIds); UNIT_ASSERT(ok); ProposeCommit(runtime, sender, txId, writeIds); @@ -1333,14 +1266,10 @@ void TestReadWithProgramLike(const TestTableDescription& table = {}) { } TString pattern = "1"; - std::vector ssas = { - MakeSelectLike(TAssignment::FUNC_STR_MATCH, pattern), - MakeSelectLike(TAssignment::FUNC_STR_MATCH_IGNORE_CASE, pattern), - MakeSelectLike(TAssignment::FUNC_STR_STARTS_WITH, pattern), - MakeSelectLike(TAssignment::FUNC_STR_STARTS_WITH_IGNORE_CASE, pattern), - MakeSelectLike(TAssignment::FUNC_STR_ENDS_WITH, pattern), - MakeSelectLike(TAssignment::FUNC_STR_ENDS_WITH_IGNORE_CASE, pattern) - }; + std::vector ssas = { MakeSelectLike(TAssignment::FUNC_STR_MATCH, pattern), + MakeSelectLike(TAssignment::FUNC_STR_MATCH_IGNORE_CASE, pattern), MakeSelectLike(TAssignment::FUNC_STR_STARTS_WITH, pattern), + MakeSelectLike(TAssignment::FUNC_STR_STARTS_WITH_IGNORE_CASE, pattern), MakeSelectLike(TAssignment::FUNC_STR_ENDS_WITH, pattern), + MakeSelectLike(TAssignment::FUNC_STR_ENDS_WITH_IGNORE_CASE, pattern) }; ui32 i = 0; for (auto& ssa : ssas) { @@ -1355,15 +1284,15 @@ void TestReadWithProgramLike(const TestTableDescription& table = {}) { switch (i) { case 0: case 1: - UNIT_ASSERT(CheckColumns(rb, {"message"}, 19)); + UNIT_ASSERT(CheckColumns(rb, { "message" }, 19)); break; case 2: case 3: - UNIT_ASSERT(CheckColumns(rb, {"message"}, 11)); + UNIT_ASSERT(CheckColumns(rb, { "message" }, 11)); break; case 4: case 5: - UNIT_ASSERT(CheckColumns(rb, {"message"}, 10)); + UNIT_ASSERT(CheckColumns(rb, { "message" }, 10)); break; default: break; @@ -1391,9 +1320,9 @@ void TestSomePrograms(const TestTableDescription& table) { SetupSchema(runtime, sender, tableId, table); - { // write some data + { // write some data std::vector writeIds; - bool ok = WriteData(runtime, sender, writeId, tableId, MakeTestBlob({0, 100}, table.Schema), table.Schema, true, &writeIds); + bool ok = WriteData(runtime, sender, writeId, tableId, MakeTestBlob({ 0, 100 }, table.Schema), table.Schema, true, &writeIds); UNIT_ASSERT(ok); ProposeCommit(runtime, sender, txId, writeIds); @@ -1426,15 +1355,14 @@ void TestSomePrograms(const TestTableDescription& table) { struct TReadAggregateResult { ui32 NumRows = 1; - std::vector MinValues = {0}; - std::vector MaxValues = {99}; - std::vector Counts = {100}; + std::vector MinValues = { 0 }; + std::vector MaxValues = { 99 }; + std::vector Counts = { 100 }; }; -void TestReadAggregate(const std::vector& ydbSchema, const TString& testDataBlob, - bool addProjection, const std::vector& aggKeys = {}, - const TReadAggregateResult& expectedResult = {}, - const TReadAggregateResult& expectedFiltered = {1, {1}, {1}, {1}}) { +void TestReadAggregate(const std::vector& ydbSchema, const TString& testDataBlob, bool addProjection, + const std::vector& aggKeys = {}, const TReadAggregateResult& expectedResult = {}, + const TReadAggregateResult& expectedFiltered = { 1, { 1 }, { 1 }, { 1 } }) { TTestBasicRuntime runtime; TTester::Setup(runtime); auto csDefaultControllerGuard = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); @@ -1452,10 +1380,10 @@ void TestReadAggregate(const std::vector& ydbSchema, ui64 txId = 100; auto pk = NArrow::NTest::TTestColumn::CropSchema(ydbSchema, 4); - TestTableDescription table{.Schema = ydbSchema, .Pk = pk}; + TestTableDescription table{ .Schema = ydbSchema, .Pk = pk }; SetupSchema(runtime, sender, tableId, table); - { // write some data + { // write some data std::vector writeIds; bool ok = WriteData(runtime, sender, writeId, tableId, testDataBlob, table.Schema, true, &writeIds); UNIT_ASSERT(ok); @@ -1469,11 +1397,9 @@ void TestReadAggregate(const std::vector& ydbSchema, std::vector programs; THashSet isFiltered; THashSet checkResult; - THashSet intTypes = { - NTypeIds::Int8, NTypeIds::Int16, NTypeIds::Int32, NTypeIds::Int64, - NTypeIds::Uint8, NTypeIds::Uint16, NTypeIds::Uint32, NTypeIds::Uint64, - NTypeIds::Timestamp, NTypeIds::Date32, NTypeIds::Datetime64, NTypeIds::Timestamp64, NTypeIds::Interval64 - }; + THashSet intTypes = { NTypeIds::Int8, NTypeIds::Int16, NTypeIds::Int32, NTypeIds::Int64, NTypeIds::Uint8, NTypeIds::Uint16, + NTypeIds::Uint32, NTypeIds::Uint64, NTypeIds::Timestamp, NTypeIds::Date32, NTypeIds::Datetime64, NTypeIds::Timestamp64, + NTypeIds::Interval64 }; THashSet strTypes = { NTypeIds::Utf8, NTypeIds::String //NTypeIds::Yson, NTypeIds::Json, NTypeIds::JsonDocument @@ -1481,8 +1407,7 @@ void TestReadAggregate(const std::vector& ydbSchema, ui32 prog = 0; for (ui32 i = 0; i < ydbSchema.size(); ++i, ++prog) { - if (intTypes.contains(ydbSchema[i].GetType().GetTypeId()) || - strTypes.contains(ydbSchema[i].GetType().GetTypeId())) { + if (intTypes.contains(ydbSchema[i].GetType().GetTypeId()) || strTypes.contains(ydbSchema[i].GetType().GetTypeId())) { checkResult.insert(prog); } @@ -1498,8 +1423,7 @@ void TestReadAggregate(const std::vector& ydbSchema, for (ui32 i = 0; i < ydbSchema.size(); ++i, ++prog) { isFiltered.insert(prog); - if (intTypes.contains(ydbSchema[i].GetType().GetTypeId()) || - strTypes.contains(ydbSchema[i].GetType().GetTypeId())) { + if (intTypes.contains(ydbSchema[i].GetType().GetTypeId()) || strTypes.contains(ydbSchema[i].GetType().GetTypeId())) { checkResult.insert(prog); } @@ -1513,8 +1437,8 @@ void TestReadAggregate(const std::vector& ydbSchema, UNIT_ASSERT(program.SerializeToString(&programs.back())); } - std::vector namedColumns = {"res_min", "res_max", "res_some", "res_count"}; - std::vector unnamedColumns = {"100", "101", "102", "103"}; + std::vector namedColumns = { "res_min", "res_max", "res_some", "res_count" }; + std::vector unnamedColumns = { "100", "101", "102", "103" }; if (!addProjection) { for (auto& key : aggKeys) { namedColumns.push_back(ydbSchema[key].GetName()); @@ -1534,7 +1458,7 @@ void TestReadAggregate(const std::vector& ydbSchema, if (checkResult.contains(prog)) { if (isFiltered.contains(prog)) { UNIT_ASSERT(CheckColumns(batch, namedColumns, expectedFiltered.NumRows)); - if (aggKeys.empty()) { // TODO: ORDER BY for compare + if (aggKeys.empty()) { // TODO: ORDER BY for compare UNIT_ASSERT(CheckIntValues(batch->GetColumnByName("res_min"), expectedFiltered.MinValues)); UNIT_ASSERT(CheckIntValues(batch->GetColumnByName("res_max"), expectedFiltered.MaxValues)); UNIT_ASSERT(CheckIntValues(batch->GetColumnByName("res_some"), expectedFiltered.MinValues)); @@ -1542,7 +1466,7 @@ void TestReadAggregate(const std::vector& ydbSchema, UNIT_ASSERT(CheckIntValues(batch->GetColumnByName("res_count"), expectedFiltered.Counts)); } else { UNIT_ASSERT(CheckColumns(batch, unnamedColumns, expectedResult.NumRows)); - if (aggKeys.empty()) { // TODO: ORDER BY for compare + if (aggKeys.empty()) { // TODO: ORDER BY for compare UNIT_ASSERT(CheckIntValues(batch->GetColumnByName("100"), expectedResult.MinValues)); UNIT_ASSERT(CheckIntValues(batch->GetColumnByName("101"), expectedResult.MaxValues)); UNIT_ASSERT(CheckIntValues(batch->GetColumnByName("102"), expectedResult.MinValues)); @@ -1555,10 +1479,9 @@ void TestReadAggregate(const std::vector& ydbSchema, } } -} +} // namespace Y_UNIT_TEST_SUITE(EvWrite) { - Y_UNIT_TEST(WriteInTransaction) { using namespace NArrow; @@ -1566,48 +1489,37 @@ Y_UNIT_TEST_SUITE(EvWrite) { TTester::Setup(runtime); auto csDefaultControllerGuard = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); - const ui64 ownerId = 0; const ui64 tableId = 1; - const ui64 schemaVersion = 1; - const std::vector schema = { - NArrow::NTest::TTestColumn("key", TTypeInfo(NTypeIds::Uint64)), - NArrow::NTest::TTestColumn("field", TTypeInfo(NTypeIds::Utf8)) - }; - const std::vector columnsIds = {1, 2}; + const std::vector schema = { NArrow::NTest::TTestColumn("key", TTypeInfo(NTypeIds::Uint64)), + NArrow::NTest::TTestColumn("field", TTypeInfo(NTypeIds::Utf8)) }; + const std::vector columnsIds = { 1, 2 }; PrepareTablet(runtime, tableId, schema); const ui64 txId = 111; - NConstruction::IArrayBuilder::TPtr keyColumn = std::make_shared>>("key"); + NConstruction::IArrayBuilder::TPtr keyColumn = + std::make_shared>>("key"); NConstruction::IArrayBuilder::TPtr column = std::make_shared>( "field", NConstruction::TStringPoolFiller(8, 100)); auto batch = NConstruction::TRecordBatchConstructor({ keyColumn, column }).BuildBatch(2048); - TString blobData = NArrow::SerializeBatchNoCompression(batch); - UNIT_ASSERT(blobData.size() < TLimits::GetMaxBlobSize()); - - auto evWrite = std::make_unique(NKikimrDataEvents::TEvWrite::MODE_PREPARE); - evWrite->SetTxId(txId); - ui64 payloadIndex = NEvWrite::TPayloadWriter(*evWrite).AddDataToPayload(std::move(blobData)); - evWrite->AddOperation(NKikimrDataEvents::TEvWrite::TOperation::OPERATION_REPLACE, {ownerId, tableId, schemaVersion}, columnsIds, payloadIndex, NKikimrDataEvents::FORMAT_ARROW); - TActorId sender = runtime.AllocateEdgeActor(); - ForwardToTablet(runtime, TTestTxConfig::TxTablet0, sender, evWrite.release()); + NTxUT::TShardWriter writer(runtime, TTestTxConfig::TxTablet0, tableId, 222); + AFL_VERIFY(writer.Write(batch, {1, 2}, txId) == NKikimrDataEvents::TEvWriteResult::STATUS_COMPLETED); + AFL_VERIFY(writer.StartCommit(txId) == NKikimrDataEvents::TEvWriteResult::STATUS_PREPARED); { - TAutoPtr handle; - auto event = runtime.GrabEdgeEvent(handle); - UNIT_ASSERT(event); - UNIT_ASSERT_VALUES_EQUAL(event->Record.GetOrigin(), TTestTxConfig::TxTablet0); - UNIT_ASSERT_VALUES_EQUAL(event->Record.GetTxId(), txId); - UNIT_ASSERT_VALUES_EQUAL((ui64)event->Record.GetStatus(), (ui64)NKikimrDataEvents::TEvWriteResult::STATUS_PREPARED); + NTxUT::TShardWriter writer(runtime, TTestTxConfig::TxTablet0, tableId, 444); + AFL_VERIFY(writer.StartCommit(444) == NKikimrDataEvents::TEvWriteResult::STATUS_BAD_REQUEST); + } + { auto readResult = ReadAllAsBatch(runtime, tableId, NOlap::TSnapshot(10, txId), schema); UNIT_ASSERT_VALUES_EQUAL(readResult->num_rows(), 0); - PlanWriteTx(runtime, sender, NOlap::TSnapshot(11, txId)); + PlanWriteTx(runtime, writer.GetSender(), NOlap::TSnapshot(11, txId)); } - auto readResult = ReadAllAsBatch(runtime, tableId, NOlap::TSnapshot(11, txId), schema); + auto readResult = ReadAllAsBatch(runtime, tableId, NOlap::TSnapshot::MaxForPlanStep(11), schema); UNIT_ASSERT_VALUES_EQUAL(readResult->num_rows(), 2048); } @@ -1618,46 +1530,26 @@ Y_UNIT_TEST_SUITE(EvWrite) { TTester::Setup(runtime); auto csDefaultControllerGuard = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); - - const ui64 ownerId = 0; const ui64 tableId = 1; - const ui64 schemaVersion = 1; - const std::vector schema = { - NArrow::NTest::TTestColumn("key", TTypeInfo(NTypeIds::Uint64)), - NArrow::NTest::TTestColumn("field", TTypeInfo(NTypeIds::Utf8)) - }; - const std::vector columnsIds = {1, 2}; + const std::vector schema = { NArrow::NTest::TTestColumn("key", TTypeInfo(NTypeIds::Uint64)), + NArrow::NTest::TTestColumn("field", TTypeInfo(NTypeIds::Utf8)) }; + const std::vector columnsIds = { 1, 2 }; PrepareTablet(runtime, tableId, schema); const ui64 txId = 111; - NConstruction::IArrayBuilder::TPtr keyColumn = std::make_shared>>("key"); + NConstruction::IArrayBuilder::TPtr keyColumn = + std::make_shared>>("key"); NConstruction::IArrayBuilder::TPtr column = std::make_shared>( "field", NConstruction::TStringPoolFiller(8, 100)); auto batch = NConstruction::TRecordBatchConstructor({ keyColumn, column }).BuildBatch(2048); - TString blobData = NArrow::SerializeBatchNoCompression(batch); - UNIT_ASSERT(blobData.size() < TLimits::GetMaxBlobSize()); + NTxUT::TShardWriter writer(runtime, TTestTxConfig::TxTablet0, tableId, 222); + AFL_VERIFY(writer.Write(batch, {1, 2}, txId) == NKikimrDataEvents::TEvWriteResult::STATUS_COMPLETED); + AFL_VERIFY(writer.Abort(txId) == NKikimrDataEvents::TEvWriteResult::STATUS_COMPLETED); - auto evWrite = std::make_unique(NKikimrDataEvents::TEvWrite::MODE_PREPARE); - evWrite->SetTxId(txId); - ui64 payloadIndex = NEvWrite::TPayloadWriter(*evWrite).AddDataToPayload(std::move(blobData)); - evWrite->AddOperation(NKikimrDataEvents::TEvWrite::TOperation::OPERATION_REPLACE, {ownerId, tableId, schemaVersion}, columnsIds, payloadIndex, NKikimrDataEvents::FORMAT_ARROW); + PlanWriteTx(runtime, writer.GetSender(), NOlap::TSnapshot(10, txId + 1), false); - TActorId sender = runtime.AllocateEdgeActor(); - ForwardToTablet(runtime, TTestTxConfig::TxTablet0, sender, evWrite.release()); - - ui64 outdatedStep = 11; - { - TAutoPtr handle; - auto event = runtime.GrabEdgeEvent(handle); - UNIT_ASSERT(event); - UNIT_ASSERT_VALUES_EQUAL((ui64)event->Record.GetStatus(), (ui64)NKikimrDataEvents::TEvWriteResult::STATUS_PREPARED); - - outdatedStep = event->Record.GetMaxStep() + 1; - PlanWriteTx(runtime, sender, NOlap::TSnapshot(outdatedStep, txId + 1), false); - } - - auto readResult = ReadAllAsBatch(runtime, tableId, NOlap::TSnapshot(outdatedStep, txId), schema); + auto readResult = ReadAllAsBatch(runtime, tableId, NOlap::TSnapshot::MaxForPlanStep(10), schema); UNIT_ASSERT_VALUES_EQUAL(readResult->num_rows(), 0); } @@ -1668,19 +1560,14 @@ Y_UNIT_TEST_SUITE(EvWrite) { TTester::Setup(runtime); auto csDefaultControllerGuard = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); - - const ui64 ownerId = 0; const ui64 tableId = 1; - const ui64 schemaVersion = 1; - const std::vector schema = { - NArrow::NTest::TTestColumn("key", TTypeInfo(NTypeIds::Uint64)), - NArrow::NTest::TTestColumn("field", TTypeInfo(NTypeIds::Utf8)) - }; - const std::vector columnsIds = {1, 2}; + const std::vector schema = { NArrow::NTest::TTestColumn("key", TTypeInfo(NTypeIds::Uint64)), + NArrow::NTest::TTestColumn("field", TTypeInfo(NTypeIds::Utf8)) }; PrepareTablet(runtime, tableId, schema); const ui64 txId = 111; - NConstruction::IArrayBuilder::TPtr keyColumn = std::make_shared>>("key"); + NConstruction::IArrayBuilder::TPtr keyColumn = + std::make_shared>>("key"); NConstruction::IArrayBuilder::TPtr column = std::make_shared>( "field", NConstruction::TStringPoolFiller(8, TLimits::GetMaxBlobSize() / 1024)); @@ -1688,23 +1575,13 @@ Y_UNIT_TEST_SUITE(EvWrite) { TString blobData = NArrow::SerializeBatchNoCompression(batch); UNIT_ASSERT(blobData.size() > TLimits::GetMaxBlobSize()); - auto evWrite = std::make_unique(NKikimrDataEvents::TEvWrite::MODE_PREPARE); - evWrite->SetTxId(txId); - ui64 payloadIndex = NEvWrite::TPayloadWriter(*evWrite).AddDataToPayload(std::move(blobData)); - evWrite->AddOperation(NKikimrDataEvents::TEvWrite::TOperation::OPERATION_REPLACE, {ownerId, tableId, schemaVersion}, columnsIds, payloadIndex, NKikimrDataEvents::FORMAT_ARROW); + NTxUT::TShardWriter writer(runtime, TTestTxConfig::TxTablet0, tableId, 222); + AFL_VERIFY(writer.Write(batch, {1, 2}, txId) == NKikimrDataEvents::TEvWriteResult::STATUS_COMPLETED); + AFL_VERIFY(writer.StartCommit(txId) == NKikimrDataEvents::TEvWriteResult::STATUS_PREPARED); - TActorId sender = runtime.AllocateEdgeActor(); - ForwardToTablet(runtime, TTestTxConfig::TxTablet0, sender, evWrite.release()); - - { - TAutoPtr handle; - auto event = runtime.GrabEdgeEvent(handle); - UNIT_ASSERT(event); - UNIT_ASSERT_VALUES_EQUAL((ui64)event->Record.GetStatus(), (ui64)NKikimrDataEvents::TEvWriteResult::STATUS_PREPARED); - PlanWriteTx(runtime, sender, NOlap::TSnapshot(11, txId)); - } + PlanWriteTx(runtime, writer.GetSender(), NOlap::TSnapshot(11, txId)); - auto readResult = ReadAllAsBatch(runtime, tableId, NOlap::TSnapshot(11, txId), schema); + auto readResult = ReadAllAsBatch(runtime, tableId, NOlap::TSnapshot::MaxForPlanStep(11), schema); UNIT_ASSERT_VALUES_EQUAL(readResult->num_rows(), 2048); } @@ -1715,93 +1592,44 @@ Y_UNIT_TEST_SUITE(EvWrite) { TTester::Setup(runtime); auto csDefaultControllerGuard = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); - - const ui64 ownerId = 0; const ui64 tableId = 1; - const ui64 schemaVersion = 1; - const std::vector schema = { - NArrow::NTest::TTestColumn("key", TTypeInfo(NTypeIds::Uint64) ), - NArrow::NTest::TTestColumn("field", TTypeInfo(NTypeIds::Utf8) ) - }; - const std::vector columnsIds = {1, 2}; + const std::vector schema = { NArrow::NTest::TTestColumn("key", TTypeInfo(NTypeIds::Uint64)), + NArrow::NTest::TTestColumn("field", TTypeInfo(NTypeIds::Utf8)) }; + const std::vector columnIds = { 1, 2 }; PrepareTablet(runtime, tableId, schema); const ui64 txId = 111; - const ui64 lockId = 110; + NTxUT::TShardWriter writer(runtime, TTestTxConfig::TxTablet0, tableId, 222); { - NConstruction::IArrayBuilder::TPtr keyColumn = std::make_shared>>("key"); - NConstruction::IArrayBuilder::TPtr column = std::make_shared>("field", NConstruction::TStringPoolFiller(8, 100)); + NConstruction::IArrayBuilder::TPtr keyColumn = + std::make_shared>>("key"); + NConstruction::IArrayBuilder::TPtr column = + std::make_shared>( + "field", NConstruction::TStringPoolFiller(8, 100)); auto batch = NConstruction::TRecordBatchConstructor({ keyColumn, column }).BuildBatch(2048); - TString blobData = NArrow::SerializeBatchNoCompression(batch); - UNIT_ASSERT(blobData.size() < TLimits::GetMaxBlobSize()); - auto evWrite = std::make_unique(NKikimrDataEvents::TEvWrite::MODE_IMMEDIATE); - evWrite->SetLockId(lockId, 1); - - ui64 payloadIndex = NEvWrite::TPayloadWriter(*evWrite).AddDataToPayload(std::move(blobData)); - evWrite->AddOperation(NKikimrDataEvents::TEvWrite::TOperation::OPERATION_REPLACE, {ownerId, tableId, schemaVersion}, columnsIds, payloadIndex, NKikimrDataEvents::FORMAT_ARROW); - - TActorId sender = runtime.AllocateEdgeActor(); - ForwardToTablet(runtime, TTestTxConfig::TxTablet0, sender, evWrite.release()); - + AFL_VERIFY(writer.Write(batch, columnIds, txId) == NKikimrDataEvents::TEvWriteResult::STATUS_COMPLETED); { - TAutoPtr handle; - auto event = runtime.GrabEdgeEvent(handle); - UNIT_ASSERT(event); - UNIT_ASSERT_VALUES_EQUAL(event->Record.GetOrigin(), TTestTxConfig::TxTablet0); - UNIT_ASSERT_VALUES_EQUAL(event->Record.GetTxId(), lockId); - UNIT_ASSERT_VALUES_EQUAL((ui64)event->Record.GetStatus(), (ui64)NKikimrDataEvents::TEvWriteResult::STATUS_COMPLETED); - - auto readResult = ReadAllAsBatch(runtime, tableId, NOlap::TSnapshot(10, lockId), schema); + auto readResult = ReadAllAsBatch(runtime, tableId, NOlap::TSnapshot(10, txId), schema); UNIT_ASSERT_VALUES_EQUAL(readResult->num_rows(), 0); } } { - NConstruction::IArrayBuilder::TPtr keyColumn = std::make_shared>>("key", 2049); - NConstruction::IArrayBuilder::TPtr column = std::make_shared>("field", NConstruction::TStringPoolFiller(8, 100)); + NConstruction::IArrayBuilder::TPtr keyColumn = + std::make_shared>>("key", 2049); + NConstruction::IArrayBuilder::TPtr column = + std::make_shared>( + "field", NConstruction::TStringPoolFiller(8, 100)); auto batch = NConstruction::TRecordBatchConstructor({ keyColumn, column }).BuildBatch(2048); - TString blobData = NArrow::SerializeBatchNoCompression(batch); - UNIT_ASSERT(blobData.size() < TLimits::GetMaxBlobSize()); - auto evWrite = std::make_unique(NKikimrDataEvents::TEvWrite::MODE_IMMEDIATE); - evWrite->SetLockId(lockId, 1); - - ui64 payloadIndex = NEvWrite::TPayloadWriter(*evWrite).AddDataToPayload(std::move(blobData)); - evWrite->AddOperation(NKikimrDataEvents::TEvWrite::TOperation::OPERATION_REPLACE, {ownerId, tableId, schemaVersion}, columnsIds, payloadIndex, NKikimrDataEvents::FORMAT_ARROW); - - TActorId sender = runtime.AllocateEdgeActor(); - ForwardToTablet(runtime, TTestTxConfig::TxTablet0, sender, evWrite.release()); + AFL_VERIFY(writer.Write(batch, columnIds, txId) == NKikimrDataEvents::TEvWriteResult::STATUS_COMPLETED); { - TAutoPtr handle; - auto event = runtime.GrabEdgeEvent(handle); - UNIT_ASSERT(event); - UNIT_ASSERT_VALUES_EQUAL(event->Record.GetOrigin(), TTestTxConfig::TxTablet0); - UNIT_ASSERT_VALUES_EQUAL(event->Record.GetTxId(), lockId); - UNIT_ASSERT_VALUES_EQUAL((ui64)event->Record.GetStatus(), (ui64)NKikimrDataEvents::TEvWriteResult::STATUS_COMPLETED); - auto readResult = ReadAllAsBatch(runtime, tableId, NOlap::TSnapshot(10, txId), schema); UNIT_ASSERT_VALUES_EQUAL(readResult->num_rows(), 0); } } { - auto evWrite = std::make_unique(NKikimrDataEvents::TEvWrite::MODE_PREPARE); - evWrite->SetTxId(txId); - evWrite->Record.MutableLocks()->SetOp(NKikimrDataEvents::TKqpLocks::Commit); - auto* lock = evWrite->Record.MutableLocks()->AddLocks(); - lock->SetLockId(lockId); - - TActorId sender = runtime.AllocateEdgeActor(); - ForwardToTablet(runtime, TTestTxConfig::TxTablet0, sender, evWrite.release()); - - { - TAutoPtr handle; - auto event = runtime.GrabEdgeEvent(handle); - UNIT_ASSERT(event); - UNIT_ASSERT_VALUES_EQUAL(event->Record.GetOrigin(), TTestTxConfig::TxTablet0); - UNIT_ASSERT_VALUES_EQUAL((ui64)event->Record.GetStatus(), (ui64)NKikimrDataEvents::TEvWriteResult::STATUS_PREPARED); - UNIT_ASSERT_VALUES_EQUAL(event->Record.GetTxId(), txId); - } - - PlanWriteTx(runtime, sender, NOlap::TSnapshot(11, txId)); + AFL_VERIFY(writer.StartCommit(txId) == NKikimrDataEvents::TEvWriteResult::STATUS_PREPARED); + PlanWriteTx(runtime, writer.GetSender(), NOlap::TSnapshot(11, txId)); } auto readResult = ReadAllAsBatch(runtime, tableId, NOlap::TSnapshot(11, txId), schema); @@ -1847,7 +1675,6 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { Y_UNIT_TEST(WriteReadDuplicate) { TestWriteReadDup(); - TestWriteReadLongTxDup(); } Y_UNIT_TEST(WriteReadModifications) { @@ -1881,7 +1708,8 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { { TSet txIds; std::vector writeIds; - UNIT_ASSERT(WriteData(runtime, sender, ++writeId, tableId, testData, ydbSchema, true, &writeIds, NEvWrite::EModificationType::Update)); + UNIT_ASSERT( + WriteData(runtime, sender, ++writeId, tableId, testData, ydbSchema, true, &writeIds, NEvWrite::EModificationType::Update)); ProposeCommit(runtime, sender, ++txId, writeIds); txIds.insert(txId); PlanCommit(runtime, sender, planStep, txIds); @@ -1896,7 +1724,8 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { { TSet txIds; std::vector writeIds; - UNIT_ASSERT(WriteData(runtime, sender, ++writeId, tableId, testData, ydbSchema, true, &writeIds, NEvWrite::EModificationType::Insert)); + UNIT_ASSERT( + WriteData(runtime, sender, ++writeId, tableId, testData, ydbSchema, true, &writeIds, NEvWrite::EModificationType::Insert)); ProposeCommit(runtime, sender, ++txId, writeIds); txIds.insert(txId); PlanCommit(runtime, sender, planStep, txIds); @@ -1912,7 +1741,8 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { { TSet txIds; std::vector writeIds; - UNIT_ASSERT(WriteData(runtime, sender, ++writeId, tableId, testData, ydbSchema, true, &writeIds, NEvWrite::EModificationType::Upsert)); + UNIT_ASSERT( + WriteData(runtime, sender, ++writeId, tableId, testData, ydbSchema, true, &writeIds, NEvWrite::EModificationType::Upsert)); ProposeCommit(runtime, sender, ++txId, writeIds); txIds.insert(txId); PlanCommit(runtime, sender, planStep, txIds); @@ -1928,7 +1758,8 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { { TSet txIds; std::vector writeIds; - UNIT_ASSERT(WriteData(runtime, sender, ++writeId, tableId, testData, ydbSchema, true, &writeIds, NEvWrite::EModificationType::Update)); + UNIT_ASSERT( + WriteData(runtime, sender, ++writeId, tableId, testData, ydbSchema, true, &writeIds, NEvWrite::EModificationType::Update)); ProposeCommit(runtime, sender, ++txId, writeIds); txIds.insert(txId); PlanCommit(runtime, sender, planStep, txIds); @@ -1944,12 +1775,14 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { { TSet txIds; std::vector writeIds; - UNIT_ASSERT(!WriteData(runtime, sender, ++writeId, tableId, testData, ydbSchema, true, &writeIds, NEvWrite::EModificationType::Insert)); + UNIT_ASSERT( + !WriteData(runtime, sender, ++writeId, tableId, testData, ydbSchema, true, &writeIds, NEvWrite::EModificationType::Insert)); } { TSet txIds; std::vector writeIds; - UNIT_ASSERT(WriteData(runtime, sender, ++writeId, tableId, testData, ydbSchema, true, &writeIds, NEvWrite::EModificationType::Delete)); + UNIT_ASSERT( + WriteData(runtime, sender, ++writeId, tableId, testData, ydbSchema, true, &writeIds, NEvWrite::EModificationType::Delete)); ProposeCommit(runtime, sender, ++txId, writeIds); txIds.insert(txId); PlanCommit(runtime, sender, planStep, txIds); @@ -2011,7 +1844,7 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { schema[0].SetType(TTypeInfo(typeId)); pk[0].SetType(TTypeInfo(typeId)); - TestTableDescription table{.Schema = schema, .Pk = pk}; + TestTableDescription table{ .Schema = schema, .Pk = pk }; TestCompactionInGranuleImpl(reboot, table); } @@ -2047,7 +1880,6 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { TestCompactionInGranule(false, NTypeIds::Datetime); } - Y_UNIT_TEST(CompactionInGranule_PKString_Reboot) { TestCompactionInGranule(true, NTypeIds::String); } @@ -2090,16 +1922,10 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { Y_UNIT_TEST(ReadSomePrograms) { TestTableDescription table; - table.Schema = { - NArrow::NTest::TTestColumn("timestamp", TTypeInfo(NTypeIds::Timestamp) ), - NArrow::NTest::TTestColumn("resource_id", TTypeInfo(NTypeIds::Utf8) ), - NArrow::NTest::TTestColumn("uid", TTypeInfo(NTypeIds::Utf8) ), - NArrow::NTest::TTestColumn("level", TTypeInfo(NTypeIds::Int32) ), - NArrow::NTest::TTestColumn("message", TTypeInfo(NTypeIds::Utf8) ) - }; - table.Pk = { - NArrow::NTest::TTestColumn("timestamp", TTypeInfo(NTypeIds::Timestamp) ) - }; + table.Schema = { NArrow::NTest::TTestColumn("timestamp", TTypeInfo(NTypeIds::Timestamp)), + NArrow::NTest::TTestColumn("resource_id", TTypeInfo(NTypeIds::Utf8)), NArrow::NTest::TTestColumn("uid", TTypeInfo(NTypeIds::Utf8)), + NArrow::NTest::TTestColumn("level", TTypeInfo(NTypeIds::Int32)), NArrow::NTest::TTestColumn("message", TTypeInfo(NTypeIds::Utf8)) }; + table.Pk = { NArrow::NTest::TTestColumn("timestamp", TTypeInfo(NTypeIds::Timestamp)) }; TestSomePrograms(table); } @@ -2122,14 +1948,12 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { counts.push_back(1); } - THashSet sameValTypes = { - NTypeIds::Yson, NTypeIds::Json, NTypeIds::JsonDocument - }; + THashSet sameValTypes = { NTypeIds::Yson, NTypeIds::Json, NTypeIds::JsonDocument }; // TODO: query needs normalization to compare with expected TReadAggregateResult resDefault = { 100, {}, {}, counts }; - TReadAggregateResult resFiltered = { 1, {}, {}, {1} }; - TReadAggregateResult resGrouped = { 1, {}, {}, {100} }; + TReadAggregateResult resFiltered = { 1, {}, {}, { 1 } }; + TReadAggregateResult resGrouped = { 1, {}, {}, { 100 } }; for (ui32 key = 0; key < schema.size(); ++key) { Cerr << "-- group by key: " << key << "\n"; @@ -2143,8 +1967,7 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { } for (ui32 key = 0; key < schema.size() - 1; ++key) { Cerr << "-- group by key: " << key << ", " << key + 1 << "\n"; - if (sameValTypes.contains(schema[key].GetType().GetTypeId()) && - sameValTypes.contains(schema[key + 1].GetType().GetTypeId())) { + if (sameValTypes.contains(schema[key].GetType().GetTypeId()) && sameValTypes.contains(schema[key + 1].GetType().GetTypeId())) { TestReadAggregate(schema, testBlob, (key % 2), { key, key + 1 }, resGrouped, resFiltered); } else { TestReadAggregate(schema, testBlob, (key % 2), { key, key + 1 }, resDefault, resFiltered); @@ -2152,8 +1975,7 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { } for (ui32 key = 0; key < schema.size() - 2; ++key) { Cerr << "-- group by key: " << key << ", " << key + 1 << ", " << key + 2 << "\n"; - if (sameValTypes.contains(schema[key].GetType().GetTypeId()) && - sameValTypes.contains(schema[key + 1].GetType().GetTypeId()) && + if (sameValTypes.contains(schema[key].GetType().GetTypeId()) && sameValTypes.contains(schema[key + 1].GetType().GetTypeId()) && sameValTypes.contains(schema[key + 1].GetType().GetTypeId())) { TestReadAggregate(schema, testBlob, (key % 2), { key, key + 1, key + 2 }, resGrouped, resFiltered); } else { @@ -2170,12 +1992,13 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { const std::vector YdbPk; public: - TTabletReadPredicateTest(TTestBasicRuntime& runtime, const ui64 planStep, const ui64 txId, const std::vector& ydbPk) + TTabletReadPredicateTest( + TTestBasicRuntime& runtime, const ui64 planStep, const ui64 txId, const std::vector& ydbPk) : Runtime(runtime) , PlanStep(planStep) , TxId(txId) - , YdbPk(ydbPk) - {} + , YdbPk(ydbPk) { + } class TBorder { private: @@ -2185,14 +2008,15 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { public: TBorder(const std::vector& values, const bool include = false) : Border(values) - , Include(include) - {} + , Include(include) { + } - bool GetInclude() const noexcept { return Include; } + bool GetInclude() const noexcept { + return Include; + } - std::vector GetCellVec(const std::vector& pk, - std::vector& mem, bool trailingNulls = false) const - { + std::vector GetCellVec( + const std::vector& pk, std::vector& mem, bool trailingNulls = false) const { UNIT_ASSERT(Border.size() <= pk.size()); std::vector cells; size_t i = 0; @@ -2213,16 +2037,25 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { TTestCaseOptions() = default; - TTestCaseOptions& SetFrom(const TBorder& border) { From = border; return *this; } - TTestCaseOptions& SetTo(const TBorder& border) { To = border; return *this; } - TTestCaseOptions& SetExpectedCount(ui32 count) { ExpectedCount = count; return *this; } + TTestCaseOptions& SetFrom(const TBorder& border) { + From = border; + return *this; + } + TTestCaseOptions& SetTo(const TBorder& border) { + To = border; + return *this; + } + TTestCaseOptions& SetExpectedCount(ui32 count) { + ExpectedCount = count; + return *this; + } TSerializedTableRange MakeRange(const std::vector& pk) const { std::vector mem; auto cellsFrom = From ? From->GetCellVec(pk, mem, false) : std::vector(); auto cellsTo = To ? To->GetCellVec(pk, mem) : std::vector(); return TSerializedTableRange(TConstArrayRef(cellsFrom), (From ? From->GetInclude() : false), - TConstArrayRef(cellsTo), (To ? To->GetInclude(): false)); + TConstArrayRef(cellsTo), (To ? To->GetInclude() : false)); } }; @@ -2233,17 +2066,17 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { void Execute() { const ui64 tableId = 1; - std::set useFields = {"timestamp", "message"}; - { // read with predicate (FROM) + std::set useFields = { "timestamp", "message" }; + { // read with predicate (FROM) TShardReader reader(Owner.Runtime, TTestTxConfig::TxTablet0, tableId, NOlap::TSnapshot(Owner.PlanStep, Owner.TxId)); - reader.SetReplyColumns({"timestamp", "message"}); + reader.SetReplyColumns({ "timestamp", "message" }); reader.AddRange(MakeRange(Owner.YdbPk)); auto rb = reader.ReadAll(); UNIT_ASSERT(reader.IsCorrectlyFinished()); if (ExpectedCount) { if (*ExpectedCount) { UNIT_ASSERT(CheckOrdered(rb)); - UNIT_ASSERT(CheckColumns(rb, {"timestamp", "message"}, ExpectedCount)); + UNIT_ASSERT(CheckColumns(rb, { "timestamp", "message" }, ExpectedCount)); } else { UNIT_ASSERT(!rb || !rb->num_rows()); } @@ -2255,8 +2088,7 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { TTestCase(TTabletReadPredicateTest& owner, const TString& testCaseName, const TTestCaseOptions& opts = {}) : TTestCaseOptions(opts) , Owner(owner) - , TestCaseName(testCaseName) - { + , TestCaseName(testCaseName) { Cerr << "TEST CASE " << TestCaseName << " START..." << Endl; } @@ -2279,7 +2111,7 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { void TestCompactionSplitGranuleImpl(const TestTableDescription& table, const TTestBlobOptions& testBlobOptions = {}) { TTestBasicRuntime runtime; TTester::Setup(runtime); - runtime.SetLogPriority(NKikimrServices::TX_COLUMNSHARD_SCAN, NActors::NLog::PRI_NOTICE); + runtime.SetLogPriority(NKikimrServices::TX_COLUMNSHARD_SCAN, NActors::NLog::PRI_DEBUG); auto csDefaultControllerGuard = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); csDefaultControllerGuard->SetSmallSizeDetector(1LLU << 20); @@ -2331,18 +2163,17 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { for (ui32 i = 0; i < 2; ++i) { { TShardReader reader(runtime, TTestTxConfig::TxTablet0, tableId, NOlap::TSnapshot(planStep, txId)); - reader.SetReplyColumns({"timestamp", "message"}); + reader.SetReplyColumns({ "timestamp", "message" }); auto rb = reader.ReadAll(); UNIT_ASSERT(reader.IsCorrectlyFinished()); UNIT_ASSERT(CheckOrdered(rb)); if (testBlobOptions.SameValueColumns.contains("timestamp")) { UNIT_ASSERT(!testBlobOptions.SameValueColumns.contains("message")); - UNIT_ASSERT(DataHas({rb}, {0, fullNumRows}, true, "message")); + UNIT_ASSERT(DataHas({ rb }, { 0, fullNumRows }, true, "message")); } else { - UNIT_ASSERT(isStrPk0 - ? DataHas({rb}, {0, fullNumRows}, true, "timestamp") - : DataHas({rb}, {0, fullNumRows}, true, "timestamp")); + UNIT_ASSERT(isStrPk0 ? DataHas({ rb }, { 0, fullNumRows }, true, "timestamp") + : DataHas({ rb }, { 0, fullNumRows }, true, "timestamp")); } } std::vector val0 = { 0 }; @@ -2351,9 +2182,9 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { std::vector val9999 = { 99999 }; std::vector val1M = { 1000000000 }; std::vector val1M_1 = { 1000000001 }; - std::vector valNumRows = {fullNumRows}; - std::vector valNumRows_1 = {fullNumRows - 1 }; - std::vector valNumRows_2 = {fullNumRows - 2 }; + std::vector valNumRows = { fullNumRows }; + std::vector valNumRows_1 = { fullNumRows - 1 }; + std::vector valNumRows_2 = { fullNumRows - 2 }; { UNIT_ASSERT(table.Pk.size() >= 2); @@ -2365,7 +2196,7 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { val9999 = { sameValue, 99999 }; val1M = { sameValue, 1000000000 }; val1M_1 = { sameValue, 1000000001 }; - valNumRows = { sameValue, fullNumRows}; + valNumRows = { sameValue, fullNumRows }; valNumRows_1 = { sameValue, fullNumRows - 1 }; valNumRows_2 = { sameValue, fullNumRows - 2 }; } @@ -2382,8 +2213,8 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { testAgent.Test("[0:1)").SetFrom(TBorder(val0, true)).SetTo(TBorder(val1, false)).SetExpectedCount(1); testAgent.Test("(0:1)").SetFrom(TBorder(val0, false)).SetTo(TBorder(val1, false)).SetExpectedCount(0); testAgent.Test("outscope1").SetFrom(TBorder(val1M, true)).SetTo(TBorder(val1M_1, true)).SetExpectedCount(0); -// VERIFIED AS INCORRECT INTERVAL (its good) -// testAgent.Test("[0-0)").SetFrom(TTabletReadPredicateTest::TBorder(0, true)).SetTo(TBorder(0, false)).SetExpectedCount(0); + // VERIFIED AS INCORRECT INTERVAL (its good) + // testAgent.Test("[0-0)").SetFrom(TTabletReadPredicateTest::TBorder(0, true)).SetTo(TBorder(0, false)).SetExpectedCount(0); if (isStrPk0) { testAgent.Test("(99990:").SetFrom(TBorder(val9990, false)).SetExpectedCount(109); @@ -2401,8 +2232,8 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { } const TInstant start = TInstant::Now(); bool success = false; - while (!success && TInstant::Now() - start < TDuration::Seconds(30)) { // Get index stats - ScanIndexStats(runtime, sender, {tableId, 42}, NOlap::TSnapshot(planStep, txId), 0); + while (!success && TInstant::Now() - start < TDuration::Seconds(30)) { // Get index stats + ScanIndexStats(runtime, sender, { tableId, 42 }, NOlap::TSnapshot(planStep, txId), 0); auto scanInited = runtime.GrabEdgeEvent(handle); auto& msg = scanInited->Record; auto scanActorId = ActorIdFromProto(msg.GetScanActorId()); @@ -2440,11 +2271,12 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { if (!activity) { continue; } - Cerr << "[" << __LINE__ << "] " << activity << " " << table.Pk[0].GetType().GetTypeId() << " " - << pathId << " " << kindStr << " " << numRows << " " << numBytes << " " << numRawBytes << "\n"; + Cerr << "[" << __LINE__ << "] " << activity << " " << table.Pk[0].GetType().GetTypeId() << " " << pathId << " " << kindStr + << " " << numRows << " " << numBytes << " " << numRawBytes << "\n"; if (pathId == tableId) { - if (kindStr == ::ToString(NOlap::NPortion::EProduced::COMPACTED) || kindStr == ::ToString(NOlap::NPortion::EProduced::SPLIT_COMPACTED) || numBytes > (4LLU << 20)) { + if (kindStr == ::ToString(NOlap::NPortion::EProduced::COMPACTED) || + kindStr == ::ToString(NOlap::NPortion::EProduced::SPLIT_COMPACTED) || numBytes > (4LLU << 20)) { sumCompactedBytes += numBytes; sumCompactedRows += numRows; //UNIT_ASSERT(numRawBytes > numBytes); @@ -2461,11 +2293,11 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { } } Cerr << "compacted=" << sumCompactedRows << ";inserted=" << sumInsertedRows << ";expected=" << fullNumRows << ";" << Endl; - if (!sumInsertedRows && sumCompactedRows == fullNumRows) { + if (sumCompactedRows && sumInsertedRows + sumCompactedRows == fullNumRows) { success = true; RebootTablet(runtime, TTestTxConfig::TxTablet0, sender); UNIT_ASSERT(sumCompactedRows < sumCompactedBytes); - UNIT_ASSERT(sumInsertedBytes == 0); + UNIT_ASSERT(sumInsertedRows <= sumInsertedBytes); } else { Wakeup(runtime, sender, TTestTxConfig::TxTablet0); } @@ -2483,7 +2315,7 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { pk[0].SetType(TTypeInfo(typeId)); schema[1].SetType(TTypeInfo(typeId)); pk[1].SetType(TTypeInfo(typeId)); - TestTableDescription table{.Schema = schema, .Pk = pk}; + TestTableDescription table{ .Schema = schema, .Pk = pk }; TestCompactionSplitGranuleImpl(table, opts); } @@ -2542,7 +2374,7 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { // Write some test data to advance the time { - std::pair triggerPortion = {1, 1000}; + std::pair triggerPortion = { 1, 1000 }; TString triggerData = MakeTestBlob(triggerPortion, ydbSchema); std::vector writeIds; @@ -2581,11 +2413,10 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { // Try to read snapshot that is too old { TShardReader reader(runtime, TTestTxConfig::TxTablet0, tableId, NOlap::TSnapshot(planStep - staleness.MilliSeconds(), Max())); - reader.SetReplyColumns({"timestamp", "message"}); + reader.SetReplyColumns({ "timestamp", "message" }); reader.ReadAll(); UNIT_ASSERT(reader.IsError()); } - } void TestCompactionGC() { @@ -2593,6 +2424,7 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { auto csDefaultControllerGuard = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); csDefaultControllerGuard->DisableBackground(NKikimr::NYDBTest::ICSController::EBackground::Indexation); csDefaultControllerGuard->SetOverridePeriodicWakeupActivationPeriod(TDuration::Seconds(1)); + csDefaultControllerGuard->SetOverrideBlobSplitSettings(NOlap::NSplitter::TSplitSettings()); TTester::Setup(runtime); runtime.SetLogPriority(NKikimrServices::BLOB_CACHE, NActors::NLog::PRI_INFO); @@ -2632,10 +2464,10 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { // Cerr << "EvWriteIndex" << Endl << *msg->IndexChanges << Endl; if (auto append = dynamic_pointer_cast(msg->IndexChanges)) { - Y_ABORT_UNLESS(append->AppendedPortions.size()); + Y_ABORT_UNLESS(append->GetAppendedPortions().size()); TStringBuilder sb; sb << "Added portions:"; - for (const auto& portion : append->AppendedPortions) { + for (const auto& portion : append->GetAppendedPortions()) { Y_UNUSED(portion); ++addedPortions; sb << " " << addedPortions; @@ -2644,36 +2476,36 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { Cerr << sb; } if (auto compact = dynamic_pointer_cast(msg->IndexChanges)) { - Y_ABORT_UNLESS(compact->SwitchedPortions.size()); + Y_ABORT_UNLESS(compact->GetSwitchedPortions().size()); ++compactionsHappened; TStringBuilder sb; sb << "Compaction old portions:"; - ui64 srcPathId{0}; - for (const auto& portionInfo : compact->SwitchedPortions) { - const ui64 pathId = portionInfo.GetPathId(); + ui64 srcPathId{ 0 }; + for (const auto& portionInfo : compact->GetSwitchedPortions()) { + const ui64 pathId = portionInfo->GetPathId(); UNIT_ASSERT(!srcPathId || srcPathId == pathId); srcPathId = pathId; - oldPortions.insert(portionInfo.GetPortion()); - sb << portionInfo.GetPortion() << ","; + oldPortions.insert(portionInfo->GetPortionId()); + sb << portionInfo->GetPortionId() << ","; } sb << Endl; Cerr << sb; } if (auto cleanup = dynamic_pointer_cast(msg->IndexChanges)) { - Y_ABORT_UNLESS(cleanup->PortionsToDrop.size()); + Y_ABORT_UNLESS(cleanup->GetPortionsToDrop().size()); ++cleanupsHappened; TStringBuilder sb; sb << "Cleanup old portions:"; - for (const auto& portion : cleanup->PortionsToDrop) { - sb << " " << portion.GetPortion(); - deletedPortions.insert(portion.GetPortion()); + for (const auto& portion : cleanup->GetPortionsToDrop()) { + sb << " " << portion->GetPortionId(); + deletedPortions.insert(portion->GetPortionId()); } sb << Endl; Cerr << sb; } } else if (auto* msg = TryGetPrivateEvent(ev)) { { - const std::vector prefixes = {"Delay Delete Blob "}; + const std::vector prefixes = { "Delay Delete Blob " }; for (TString prefix : prefixes) { size_t pos = msg->Line.find(prefix); if (pos != TString::npos) { @@ -2716,7 +2548,7 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { // Write different keys: grow on compaction static const ui32 triggerPortionSize = 75 * 1000; - std::pair triggerPortion = {0, triggerPortionSize}; + std::pair triggerPortion = { 0, triggerPortionSize }; TString triggerData = MakeTestBlob(triggerPortion, ydbSchema); UNIT_ASSERT(triggerData.size() > NColumnShard::TLimits::MIN_BYTES_TO_INSERT); UNIT_ASSERT(triggerData.size() < NColumnShard::TLimits::GetMaxBlobSize()); @@ -2736,7 +2568,7 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { // Do a small write that is not indexed so that we will get a committed blob in read request { - TString smallData = MakeTestBlob({0, 2}, ydbSchema); + TString smallData = MakeTestBlob({ 0, 2 }, ydbSchema); UNIT_ASSERT(smallData.size() < 100 * 1024); std::vector writeIds; UNIT_ASSERT(WriteData(runtime, sender, writeId, tableId, smallData, ydbSchema, true, &writeIds)); @@ -2751,7 +2583,7 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { --planStep; --txId; Cerr << compactionsHappened << Endl; -// UNIT_ASSERT_GE(compactionsHappened, 3); // we catch it three times per action + // UNIT_ASSERT_GE(compactionsHappened, 3); // we catch it three times per action ui64 previousCompactionsHappened = compactionsHappened; ui64 previousCleanupsHappened = cleanupsHappened; @@ -2760,7 +2592,7 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { // This request is expected to read at least 1 committed blob and several index portions // These committed blob and portions must not be deleted by the BlobManager until the read request finishes TShardReader reader(runtime, TTestTxConfig::TxTablet0, tableId, NOlap::TSnapshot(planStep - 1, Max())); - reader.SetReplyColumns({"timestamp", "message"}); + reader.SetReplyColumns({ "timestamp", "message" }); auto rb = reader.ReadAll(); UNIT_ASSERT(reader.IsCorrectlyFinished()); UNIT_ASSERT(CheckOrdered(rb)); @@ -2824,7 +2656,8 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { PlanCommit(runtime, sender, planStep, txId); } UNIT_ASSERT_EQUAL(cleanupsHappened, 0); - csDefaultControllerGuard->SetOverrideRequestsTracePingCheckPeriod(TDuration::Zero()); + csDefaultControllerGuard->SetOverrideStalenessLivetimePing(TDuration::Zero()); + csDefaultControllerGuard->SetOverrideUsedSnapshotLivetime(TDuration::Zero()); { auto read = std::make_unique(); ForwardToTablet(runtime, TTestTxConfig::TxTablet0, sender, read.release()); @@ -2868,4 +2701,4 @@ Y_UNIT_TEST_SUITE(TColumnShardTestReadWrite) { } } -} +} // namespace NKikimr diff --git a/ydb/core/tx/columnshard/ut_rw/ut_normalizer.cpp b/ydb/core/tx/columnshard/ut_rw/ut_normalizer.cpp index 734047952707..d76d79b3bcf6 100644 --- a/ydb/core/tx/columnshard/ut_rw/ut_normalizer.cpp +++ b/ydb/core/tx/columnshard/ut_rw/ut_normalizer.cpp @@ -1,16 +1,13 @@ #include -#include - #include #include -#include - #include +#include +#include -#include #include #include - +#include namespace NKikimr { @@ -34,23 +31,29 @@ struct TPortionRecord { ui32 Size = 0; }; - class TNormalizerChecker { public: - virtual ~TNormalizerChecker() {} + virtual ~TNormalizerChecker() { + } + + virtual void CorrectConfigurationOnStart(NKikimrConfig::TColumnShardConfig& /*columnShardConfig*/) const { + } + + virtual void CorrectFeatureFlagsOnStart(TFeatureFlags& /*featuresFlags*/) const { + } virtual ui64 RecordsCountAfterReboot(const ui64 initialRecodsCount) const { return initialRecodsCount; } }; -class TPathIdCleaner : public NYDBTest::ILocalDBModifier { +class TColumnChunksCleaner: public NYDBTest::ILocalDBModifier { public: virtual void Apply(NTabletFlatExecutor::TTransactionContext& txc) const override { using namespace NColumnShard; NIceDb::TNiceDb db(txc.DB); - THashMap portion2Key; + std::vector portion2Key; std::optional pathId; { auto rowset = db.Table().Select(); @@ -75,7 +78,7 @@ class TPathIdCleaner : public NYDBTest::ILocalDBModifier { pathId = rowset.GetValue(); - portion2Key[key.Portion] = key; + portion2Key.emplace_back(std::move(key)); UNIT_ASSERT(rowset.Next()); } @@ -83,81 +86,43 @@ class TPathIdCleaner : public NYDBTest::ILocalDBModifier { UNIT_ASSERT(pathId.has_value()); - for (auto&& [ portionId, key ] : portion2Key) { - db.Table().Key(key.Index, key.Granule, key.ColumnIdx, - key.PlanStep, key.TxId, key.Portion, key.Chunk).Delete(); - - db.Table().Key(key.Index, 1, key.ColumnIdx, - key.PlanStep, key.TxId, key.Portion, key.Chunk).Update( - NIceDb::TUpdate(key.XPlanStep), - NIceDb::TUpdate(key.XTxId), - NIceDb::TUpdate(key.Blob), - NIceDb::TUpdate(key.Metadata), - NIceDb::TUpdate(key.Offset), - NIceDb::TUpdate(key.Size), - - NIceDb::TNull() - ); - } + for (auto&& key : portion2Key) { + NKikimrTxColumnShard::TIndexColumnMeta metaProto; + UNIT_ASSERT(metaProto.ParseFromArray(key.Metadata.data(), key.Metadata.size())); + metaProto.ClearNumRows(); + metaProto.ClearRawBytes(); - db.Table().Key(0, *pathId, "1").Update( - NIceDb::TUpdate(1), - NIceDb::TUpdate(1), - NIceDb::TUpdate(1), - NIceDb::TUpdate("") - ); + db.Table() + .Key(key.Index, key.Granule, key.ColumnIdx, key.PlanStep, key.TxId, key.Portion, key.Chunk) + .Update(NIceDb::TUpdate(metaProto.SerializeAsString())); + } } }; -class TColumnChunksCleaner : public NYDBTest::ILocalDBModifier { +class TSchemaVersionsCleaner : public NYDBTest::ILocalDBModifier { public: virtual void Apply(NTabletFlatExecutor::TTransactionContext& txc) const override { using namespace NColumnShard; NIceDb::TNiceDb db(txc.DB); - - std::vector portion2Key; - std::optional pathId; + // Add invalid widow schema, if SchemaVersionCleaner will not erase it, then test will fail { - auto rowset = db.Table().Select(); - UNIT_ASSERT(rowset.IsReady()); - - while (!rowset.EndOfSet()) { - TPortionRecord key; - key.Index = rowset.GetValue(); - key.Granule = rowset.GetValue(); - key.ColumnIdx = rowset.GetValue(); - key.PlanStep = rowset.GetValue(); - key.TxId = rowset.GetValue(); - key.Portion = rowset.GetValue(); - key.Chunk = rowset.GetValue(); - - key.XPlanStep = rowset.GetValue(); - key.XTxId = rowset.GetValue(); - key.Blob = rowset.GetValue(); - key.Metadata = rowset.GetValue(); - key.Offset = rowset.GetValue(); - key.Size = rowset.GetValue(); - - pathId = rowset.GetValue(); - - portion2Key.emplace_back(std::move(key)); - - UNIT_ASSERT(rowset.Next()); - } + NKikimrTxColumnShard::TSchemaPresetVersionInfo info; + info.SetId(1); + info.SetSinceStep(5); + info.SetSinceTxId(1); + info.MutableSchema()->SetVersion(0); + db.Table().Key(1, 5, 1).Update( + NIceDb::TUpdate(info.SerializeAsString())); } - UNIT_ASSERT(pathId.has_value()); - - for (auto&& key: portion2Key) { - NKikimrTxColumnShard::TIndexColumnMeta metaProto; - UNIT_ASSERT(metaProto.ParseFromArray(key.Metadata.data(), key.Metadata.size())); - metaProto.ClearNumRows(); - metaProto.ClearRawBytes(); - - db.Table().Key(key.Index, key.Granule, key.ColumnIdx, - key.PlanStep, key.TxId, key.Portion, key.Chunk).Update( - NIceDb::TUpdate(metaProto.SerializeAsString()) - ); + { + // Add invalid widow table version, if SchemaVersionCleaner will not erase it, then test will fail + NKikimrTxColumnShard::TTableVersionInfo versionInfo; + versionInfo.SetSchemaPresetId(1); + versionInfo.SetSinceStep(5); + versionInfo.SetSinceTxId(1); + db.Table().Key(1, 5, 1).Update( + NIceDb::TUpdate(versionInfo.SerializeAsString())); } } }; @@ -174,34 +139,51 @@ class TPortionsCleaner : public NYDBTest::ILocalDBModifier { UNIT_ASSERT(rowset.IsReady()); while (!rowset.EndOfSet()) { - NOlap::TPortionAddress addr(rowset.GetValue(), rowset.GetValue()); + NOlap::TPortionAddress addr( + rowset.GetValue(), rowset.GetValue()); portions.emplace_back(addr); UNIT_ASSERT(rowset.Next()); } } - for (auto&& key: portions) { + for (auto&& key : portions) { db.Table().Key(key.GetPathId(), key.GetPortionId()).Delete(); } } }; - -class TEmptyPortionsCleaner : public NYDBTest::ILocalDBModifier { +class TEmptyPortionsCleaner: public NYDBTest::ILocalDBModifier { public: virtual void Apply(NTabletFlatExecutor::TTransactionContext& txc) const override { using namespace NColumnShard; NIceDb::TNiceDb db(txc.DB); for (size_t pathId = 100; pathId != 299; ++pathId) { - for (size_t portionId = 1000; portionId != 1199; ++portionId) { - db.Table().Key(pathId, portionId).Update(); + for (size_t portionId = pathId * 100000 + 1000; portionId != pathId * 100000 + 1199; ++portionId) { + NKikimrTxColumnShard::TIndexPortionMeta metaProto; + metaProto.SetDeletionsCount(0); + metaProto.SetIsInserted(true); + + const auto schema = std::make_shared( + arrow::FieldVector({ std::make_shared("key1", arrow::uint64()), std::make_shared("key2", arrow::uint64()) })); + auto batch = NArrow::MakeEmptyBatch(schema, 1); + NArrow::TFirstLastSpecialKeys keys(batch); + metaProto.SetPrimaryKeyBorders(keys.SerializePayloadToString()); + metaProto.MutableRecordSnapshotMin()->SetPlanStep(0); + metaProto.MutableRecordSnapshotMin()->SetTxId(0); + metaProto.MutableRecordSnapshotMax()->SetPlanStep(0); + metaProto.MutableRecordSnapshotMax()->SetTxId(0); + db.Table() + .Key(pathId, portionId) + .Update(NIceDb::TUpdate(1), + NIceDb::TUpdate(metaProto.SerializeAsString()), + NIceDb::TUpdate(10), + NIceDb::TUpdate(10)); } } } }; - -class TTablesCleaner : public NYDBTest::ILocalDBModifier { +class TTablesCleaner: public NYDBTest::ILocalDBModifier { public: virtual void Apply(NTabletFlatExecutor::TTransactionContext& txc) const override { using namespace NColumnShard; @@ -219,7 +201,7 @@ class TTablesCleaner : public NYDBTest::ILocalDBModifier { } } - for (auto&& key: tables) { + for (auto&& key : tables) { db.Table().Key(key).Delete(); } @@ -244,10 +226,9 @@ class TTablesCleaner : public NYDBTest::ILocalDBModifier { } } - for (auto&& key: versions) { + for (auto&& key : versions) { db.Table().Key(key.PathId, key.Step, key.TxId).Delete(); } - } }; @@ -255,6 +236,7 @@ template class TPrepareLocalDBController: public NKikimr::NYDBTest::NColumnShard::TController { private: using TBase = NKikimr::NYDBTest::ICSController; + public: NYDBTest::ILocalDBModifier::TPtr BuildLocalBaseModifier() const override { return std::make_shared(); @@ -262,7 +244,6 @@ class TPrepareLocalDBController: public NKikimr::NYDBTest::NColumnShard::TContro }; Y_UNIT_TEST_SUITE(Normalizers) { - template void TestNormalizerImpl(const TNormalizerChecker& checker = TNormalizerChecker()) { using namespace NArrow; @@ -271,52 +252,34 @@ Y_UNIT_TEST_SUITE(Normalizers) { TTestBasicRuntime runtime; TTester::Setup(runtime); - const ui64 ownerId = 0; + checker.CorrectConfigurationOnStart(runtime.GetAppData().ColumnShardConfig); + checker.CorrectFeatureFlagsOnStart(runtime.GetAppData().FeatureFlags); + const ui64 tableId = 1; - const ui64 schemaVersion = 1; - const std::vector schema = { - NArrow::NTest::TTestColumn("key1", TTypeInfo(NTypeIds::Uint64)), - NArrow::NTest::TTestColumn("key2", TTypeInfo(NTypeIds::Uint64)), - NArrow::NTest::TTestColumn("field", TTypeInfo(NTypeIds::Utf8) ) - }; - const std::vector columnsIds = { 1, 2, 3}; + const std::vector schema = { NArrow::NTest::TTestColumn("key1", TTypeInfo(NTypeIds::Uint64)), + NArrow::NTest::TTestColumn("key2", TTypeInfo(NTypeIds::Uint64)), NArrow::NTest::TTestColumn("field", TTypeInfo(NTypeIds::Utf8)) }; + const std::vector columnsIds = { 1, 2, 3 }; PrepareTablet(runtime, tableId, schema, 2); const ui64 txId = 111; - NConstruction::IArrayBuilder::TPtr key1Column = std::make_shared>>("key1"); - NConstruction::IArrayBuilder::TPtr key2Column = std::make_shared>>("key2"); + NConstruction::IArrayBuilder::TPtr key1Column = + std::make_shared>>("key1"); + NConstruction::IArrayBuilder::TPtr key2Column = + std::make_shared>>("key2"); NConstruction::IArrayBuilder::TPtr column = std::make_shared>( "field", NConstruction::TStringPoolFiller(8, 100)); auto batch = NConstruction::TRecordBatchConstructor({ key1Column, key2Column, column }).BuildBatch(20048); - TString blobData = NArrow::SerializeBatchNoCompression(batch); - - auto evWrite = std::make_unique(NKikimrDataEvents::TEvWrite::MODE_PREPARE); - evWrite->SetTxId(txId); - ui64 payloadIndex = NEvWrite::TPayloadWriter(*evWrite).AddDataToPayload(std::move(blobData)); - evWrite->AddOperation(NKikimrDataEvents::TEvWrite::TOperation::OPERATION_REPLACE, {ownerId, tableId, schemaVersion}, columnsIds, payloadIndex, NKikimrDataEvents::FORMAT_ARROW); - - TActorId sender = runtime.AllocateEdgeActor(); - ForwardToTablet(runtime, TTestTxConfig::TxTablet0, sender, evWrite.release()); - { - TAutoPtr handle; - auto event = runtime.GrabEdgeEvent(handle); - UNIT_ASSERT(event); - UNIT_ASSERT_VALUES_EQUAL((ui64)event->Record.GetStatus(), (ui64)NKikimrDataEvents::TEvWriteResult::STATUS_PREPARED); - - PlanWriteTx(runtime, sender, NOlap::TSnapshot(11, txId)); - } + NTxUT::TShardWriter writer(runtime, TTestTxConfig::TxTablet0, tableId, 222); + AFL_VERIFY(writer.Write(batch, {1, 2, 3}, txId) == NKikimrDataEvents::TEvWriteResult::STATUS_COMPLETED); + AFL_VERIFY(writer.StartCommit(txId) == NKikimrDataEvents::TEvWriteResult::STATUS_PREPARED); + PlanWriteTx(runtime, writer.GetSender(), NOlap::TSnapshot(11, txId)); { auto readResult = ReadAllAsBatch(runtime, tableId, NOlap::TSnapshot(11, txId), schema); UNIT_ASSERT_VALUES_EQUAL(readResult->num_rows(), 20048); - while (!csControllerGuard->GetInsertFinishedCounter().Val()) { - Cerr << csControllerGuard->GetInsertStartedCounter().Val() << Endl; - Wakeup(runtime, sender, TTestTxConfig::TxTablet0); - runtime.SimulateSleep(TDuration::Seconds(1)); - } } - RebootTablet(runtime, TTestTxConfig::TxTablet0, sender); + RebootTablet(runtime, TTestTxConfig::TxTablet0, writer.GetSender()); { auto readResult = ReadAllAsBatch(runtime, tableId, NOlap::TSnapshot(11, txId), schema); @@ -324,32 +287,78 @@ Y_UNIT_TEST_SUITE(Normalizers) { } } - Y_UNIT_TEST(PathIdNormalizer) { - TestNormalizerImpl(); - } - Y_UNIT_TEST(ColumnChunkNormalizer) { TestNormalizerImpl(); } Y_UNIT_TEST(PortionsNormalizer) { - TestNormalizerImpl(); + class TLocalNormalizerChecker: public TNormalizerChecker { + public: + virtual ui64 RecordsCountAfterReboot(const ui64 /*initialRecodsCount*/) const override { + return 0; + } + virtual void CorrectFeatureFlagsOnStart(TFeatureFlags & featuresFlags) const override{ + featuresFlags.SetEnableWritePortionsOnInsert(true); + } + virtual void CorrectConfigurationOnStart(NKikimrConfig::TColumnShardConfig& columnShardConfig) const override { + { + auto* repair = columnShardConfig.MutableRepairs()->Add(); + repair->SetClassName("EmptyPortionsCleaner"); + repair->SetDescription("Removing unsync portions"); + } + { + auto* repair = columnShardConfig.MutableRepairs()->Add(); + repair->SetClassName("LeakedBlobsNormalizer"); + repair->SetDescription("Removing leaked blobs"); + } + } + }; + TestNormalizerImpl(TLocalNormalizerChecker()); + } + + Y_UNIT_TEST(SchemaVersionsNormalizer) { + class TLocalNormalizerChecker: public TNormalizerChecker { + public: + virtual void CorrectFeatureFlagsOnStart(TFeatureFlags& featuresFlags) const override { + featuresFlags.SetEnableWritePortionsOnInsert(true); + } + virtual void CorrectConfigurationOnStart(NKikimrConfig::TColumnShardConfig& columnShardConfig) const override { + auto* repair = columnShardConfig.MutableRepairs()->Add(); + repair->SetClassName("SchemaVersionCleaner"); + repair->SetDescription("Removing unused schema versions"); + } + }; + TestNormalizerImpl(TLocalNormalizerChecker()); } Y_UNIT_TEST(CleanEmptyPortionsNormalizer) { - TestNormalizerImpl(); + class TLocalNormalizerChecker: public TNormalizerChecker { + public: + virtual void CorrectConfigurationOnStart(NKikimrConfig::TColumnShardConfig& columnShardConfig) const override { + auto* repair = columnShardConfig.MutableRepairs()->Add(); + repair->SetClassName("EmptyPortionsCleaner"); + repair->SetDescription("Removing unsync portions"); + } + }; + TestNormalizerImpl(TLocalNormalizerChecker()); } + Y_UNIT_TEST(EmptyTablesNormalizer) { - class TLocalNormalizerChecker : public TNormalizerChecker { + class TLocalNormalizerChecker: public TNormalizerChecker { public: ui64 RecordsCountAfterReboot(const ui64) const override { return 0; } + virtual void CorrectConfigurationOnStart(NKikimrConfig::TColumnShardConfig& columnShardConfig) const override { + auto* repair = columnShardConfig.MutableRepairs()->Add(); + repair->SetClassName("PortionsCleaner"); + repair->SetDescription("Removing dirty portions withno tables"); + } }; TLocalNormalizerChecker checker; TestNormalizerImpl(checker); } } -} // namespace NKikimr +} // namespace NKikimr diff --git a/ydb/core/tx/columnshard/ut_schema/ut_columnshard_schema.cpp b/ydb/core/tx/columnshard/ut_schema/ut_columnshard_schema.cpp index deb7be3d89e9..205286404ee6 100644 --- a/ydb/core/tx/columnshard/ut_schema/ut_columnshard_schema.cpp +++ b/ydb/core/tx/columnshard/ut_schema/ut_columnshard_schema.cpp @@ -100,6 +100,22 @@ bool TriggerTTL(TTestBasicRuntime& runtime, TActorId& sender, NOlap::TSnapshot s return (res.GetStatus() == NKikimrTxColumnShard::SUCCESS); } +bool TriggerMetadata( + TTestBasicRuntime& runtime, TActorId& sender, NYDBTest::TControllers::TGuard& controller) { + auto isDone = [initialCounter = controller->GetTieringMetadataActualizationCount().Val(), &controller]() { + return controller->GetTieringMetadataActualizationCount().Val() != initialCounter; + }; + + auto event = std::make_unique(); + ForwardToTablet(runtime, TTestTxConfig::TxTablet0, sender, event.release()); + + const TInstant deadline = TInstant::Now() + TDuration::Seconds(5); + while (!isDone() && TInstant::Now() < deadline) { + runtime.SimulateSleep(TDuration::Seconds(1)); + } + return isDone(); +} + bool CheckSame(const std::shared_ptr& batch, const ui32 expectedSize, const std::string& columnName, i64 seconds) { UNIT_ASSERT(batch); @@ -175,7 +191,7 @@ void TestTtl(bool reboots, bool internal, TTestSchema::TTableSpecials spec = {}, auto csControllerGuard = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); csControllerGuard->DisableBackground(NKikimr::NYDBTest::ICSController::EBackground::Compaction); csControllerGuard->SetOverrideTasksActualizationLag(TDuration::Zero()); - std::vector ts = {1600000000, 1620000000}; + std::vector ts = { 1600000000, 1620000000 }; ui32 ttlIncSeconds = 1; for (auto& c : ydbSchema) { @@ -223,7 +239,7 @@ void TestTtl(bool reboots, bool internal, TTestSchema::TTableSpecials spec = {}, TTestSchema::CreateInitShardTxBody(tableId, ydbSchema, testYdbPk, spec, "/Root/olapStore"), NOlap::TSnapshot(++planStep, ++txId)); if (spec.HasTiers()) { - csControllerGuard->SetTiersSnapshot(runtime, sender, TTestSchema::BuildSnapshot(spec)); + csControllerGuard->OverrideTierConfigs(runtime, sender, TTestSchema::BuildSnapshot(spec)); } // @@ -245,7 +261,7 @@ void TestTtl(bool reboots, bool internal, TTestSchema::TTableSpecials spec = {}, if (internal) { TriggerTTL(runtime, sender, NOlap::TSnapshot(++planStep, ++txId), {}, 0, spec.TtlColumn); } else { - TriggerTTL(runtime, sender, NOlap::TSnapshot(++planStep, ++txId), {tableId}, ts[0] + ttlIncSeconds, spec.TtlColumn); + TriggerTTL(runtime, sender, NOlap::TSnapshot(++planStep, ++txId), { tableId }, ts[0] + ttlIncSeconds, spec.TtlColumn); } while (csControllerGuard->GetTTLFinishedCounter().Val() != csControllerGuard->GetTTLStartedCounter().Val()) { runtime.SimulateSleep(TDuration::Seconds(1)); // wait all finished before (ttl especially) @@ -277,7 +293,7 @@ void TestTtl(bool reboots, bool internal, TTestSchema::TTableSpecials spec = {}, TTestSchema::AlterTableTxBody(tableId, 2, spec), NOlap::TSnapshot(++planStep, ++txId)); if (spec.HasTiers()) { - csControllerGuard->SetTiersSnapshot(runtime, sender, TTestSchema::BuildSnapshot(spec)); + csControllerGuard->OverrideTierConfigs(runtime, sender, TTestSchema::BuildSnapshot(spec)); } if (internal) { @@ -304,7 +320,7 @@ void TestTtl(bool reboots, bool internal, TTestSchema::TTableSpecials spec = {}, NOlap::TSnapshot(++planStep, ++txId)); UNIT_ASSERT(ok); if (spec.HasTiers()) { - csControllerGuard->SetTiersSnapshot(runtime, sender, TTestSchema::BuildSnapshot(TTestSchema::TTableSpecials())); + csControllerGuard->OverrideTierConfigs(runtime, sender, TTestSchema::BuildSnapshot(TTestSchema::TTableSpecials())); } PlanSchemaTx(runtime, sender, NOlap::TSnapshot(planStep, txId)); @@ -332,7 +348,7 @@ void TestTtl(bool reboots, bool internal, TTestSchema::TTableSpecials spec = {}, UNIT_ASSERT(CheckSame(rb, PORTION_ROWS, spec.TtlColumn, ts[0])); } - if (spec.NeedTestStatistics()) { + if (spec.NeedTestStatistics(testYdbPk)) { AFL_VERIFY(csControllerGuard->GetStatisticsUsageCount().Val()); AFL_VERIFY(!csControllerGuard->GetMaxValueUsageCount().Val()); } else { @@ -567,7 +583,7 @@ std::vector> TestTiers(bool reboots, const std::vectorSetTiersSnapshot(runtime, sender, TTestSchema::BuildSnapshot(specs[0])); + csControllerGuard->OverrideTierConfigs(runtime, sender, TTestSchema::BuildSnapshot(specs[0])); } for (auto& data : blobs) { @@ -601,14 +617,14 @@ std::vector> TestTiers(bool reboots, const std::vector OK, misconfig after export => ERROR if (i > 1) { expectedReadResult = EExpectedResult::ERROR; } originalEndpoint = spec.S3.GetEndpoint(); - spec.S3.SetEndpoint("fake"); + spec.S3.SetEndpoint("fake.fake"); tIdxCorrect = tIdx++; } break; @@ -620,8 +636,9 @@ std::vector> TestTiers(bool reboots, const std::vectorSetTiersSnapshot(runtime, sender, TTestSchema::BuildSnapshot(specs[i])); + csControllerGuard->OverrideTierConfigs(runtime, sender, TTestSchema::BuildSnapshot(specs[i])); } + UNIT_ASSERT(TriggerMetadata(runtime, sender, csControllerGuard)); if (eventLoss) { if (*eventLoss == i) { @@ -643,7 +660,6 @@ std::vector> TestTiers(bool reboots, const std::vectorInitializeScanner(); reader->Ack(); } - // Eviction TriggerTTL(runtime, sender, NOlap::TSnapshot(++planStep, ++txId), {}, 0, specs[i].TtlColumn); @@ -663,7 +679,7 @@ std::vector> TestTiers(bool reboots, const std::vectorSetTiersSnapshot(runtime, sender, TTestSchema::BuildSnapshot(specs[i])); + csControllerGuard->OverrideTierConfigs(runtime, sender, TTestSchema::BuildSnapshot(specs[i])); } @@ -699,13 +715,13 @@ std::vector> TestTiers(bool reboots, const std::vectorGetStatisticsUsageCount().Val()); - AFL_VERIFY(!csControllerGuard->GetMaxValueUsageCount().Val()); - } else { - AFL_VERIFY(!csControllerGuard->GetStatisticsUsageCount().Val()); - AFL_VERIFY(csControllerGuard->GetMaxValueUsageCount().Val()); - } +// if (specs[0].TtlColumn != testYdbPk.front().GetName()) { +// AFL_VERIFY(csControllerGuard->GetStatisticsUsageCount().Val()); +// AFL_VERIFY(!csControllerGuard->GetMaxValueUsageCount().Val()); +// } else { +// AFL_VERIFY(!csControllerGuard->GetStatisticsUsageCount().Val()); +// AFL_VERIFY(csControllerGuard->GetMaxValueUsageCount().Val()); +// } return specRowsBytes; } @@ -853,11 +869,10 @@ std::vector> TestOneTierExport(const TTestSchema::TTableSp return rowsBytes; } -void TestTwoHotTiers(bool reboot, bool changeTtl, const bool statisticsUsage, const EInitialEviction initial = EInitialEviction::None, +void TestTwoHotTiers(bool reboot, bool changeTtl, const EInitialEviction initial = EInitialEviction::None, bool revCompaction = false) { TTestSchema::TTableSpecials spec; spec.SetTtlColumn("timestamp"); - spec.SetNeedTestStatistics(statisticsUsage); spec.Tiers.emplace_back(TTestSchema::TStorageTier("tier0").SetTtlColumn("timestamp")); spec.Tiers.emplace_back(TTestSchema::TStorageTier("tier1").SetTtlColumn("timestamp")); spec.Tiers[(revCompaction ? 0 : 1)].SetCodec("zstd"); @@ -890,13 +905,12 @@ void TestTwoHotTiers(bool reboot, bool changeTtl, const bool statisticsUsage, co } } -void TestHotAndColdTiers(bool reboot, const EInitialEviction initial, const bool statisticsUsage) { +void TestHotAndColdTiers(bool reboot, const EInitialEviction initial) { TTestSchema::TTableSpecials spec; spec.SetTtlColumn("timestamp"); spec.Tiers.emplace_back(TTestSchema::TStorageTier("tier0").SetTtlColumn("timestamp")); spec.Tiers.emplace_back(TTestSchema::TStorageTier("tier1").SetTtlColumn("timestamp")); spec.Tiers.back().S3 = TTestSchema::TStorageTier::FakeS3(); - spec.SetNeedTestStatistics(statisticsUsage); TestTiersAndTtl(spec, reboot, initial); } @@ -1021,6 +1035,7 @@ void TestDropWriteRace() { ui64 tableId = 1; ui64 planStep = 1000000000; // greater then delays ui64 txId = 100; + ui32 writeId = 0; NLongTxService::TLongTxId longTxId; UNIT_ASSERT(longTxId.ParseString("ydb://long-tx/01ezvvxjdk2hd4vdgjs68knvp8?node_id=1")); @@ -1031,9 +1046,9 @@ void TestDropWriteRace() { UNIT_ASSERT(data.size() < NColumnShard::TLimits::MIN_BYTES_TO_INSERT); // Write into InsertTable - auto writeIdOpt = WriteData(runtime, sender, longTxId, tableId, 1, data, testYdbSchema); - UNIT_ASSERT(writeIdOpt); - ProposeCommit(runtime, sender, ++txId, {*writeIdOpt}); + ++txId; + AFL_VERIFY(WriteData(runtime, sender, ++writeId, tableId, data, testYdbSchema)); + ProposeCommit(runtime, sender, txId, { writeId }); auto commitTxId = txId; // Drop table @@ -1049,7 +1064,7 @@ void TestDropWriteRace() { void TestCompaction(std::optional numWrites = {}) { TTestBasicRuntime runtime; TTester::Setup(runtime); - auto csDefaultControllerGuard = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); + auto csControllerGuard = NKikimr::NYDBTest::TControllers::RegisterCSControllerGuard(); TActorId sender = runtime.AllocateEdgeActor(); CreateTestBootstrapper(runtime, @@ -1085,7 +1100,7 @@ void TestCompaction(std::optional numWrites = {}) { SetupSchema(runtime, sender, TTestSchema::AlterTableTxBody(tableId, 1, spec), NOlap::TSnapshot(++planStep, ++txId)); - ProvideTieringSnapshot(runtime, sender, TTestSchema::BuildSnapshot(spec)); + csControllerGuard->OverrideTierConfigs(runtime, sender, TTestSchema::BuildSnapshot(spec)); // Writes @@ -1285,91 +1300,91 @@ Y_UNIT_TEST_SUITE(TColumnShardTestSchema) { // TODO: EnableOneTierAfterTtl, EnableTtlAfterOneTier Y_UNIT_TEST(HotTiers) { - TestTwoHotTiers(false, false, false); + TestTwoHotTiers(false, false); } Y_UNIT_TEST(RebootHotTiers) { - TestTwoHotTiers(true, false, false); + TestTwoHotTiers(true, false); } Y_UNIT_TEST(HotTiersWithStat) { - TestTwoHotTiers(false, false, true); + TestTwoHotTiers(false, false); } Y_UNIT_TEST(RebootHotTiersWithStat) { - TestTwoHotTiers(true, false, true); + TestTwoHotTiers(true, false); } Y_UNIT_TEST(HotTiersRevCompression) { - TestTwoHotTiers(false, false, false, EInitialEviction::None, true); + TestTwoHotTiers(false, false, EInitialEviction::None); } Y_UNIT_TEST(RebootHotTiersRevCompression) { - TestTwoHotTiers(true, false, false, EInitialEviction::None, true); + TestTwoHotTiers(true, false, EInitialEviction::None); } Y_UNIT_TEST(HotTiersTtl) { NColumnShard::gAllowLogBatchingDefaultValue = false; - TestTwoHotTiers(false, true, false); + TestTwoHotTiers(false, true); } Y_UNIT_TEST(RebootHotTiersTtl) { NColumnShard::gAllowLogBatchingDefaultValue = false; - TestTwoHotTiers(true, true, false); + TestTwoHotTiers(true, true); } Y_UNIT_TEST(HotTiersTtlWithStat) { NColumnShard::gAllowLogBatchingDefaultValue = false; - TestTwoHotTiers(false, true, true); + TestTwoHotTiers(false, true); } Y_UNIT_TEST(RebootHotTiersTtlWithStat) { NColumnShard::gAllowLogBatchingDefaultValue = false; - TestTwoHotTiers(true, true, true); + TestTwoHotTiers(true, true); } Y_UNIT_TEST(HotTiersAfterTtl) { - TestTwoHotTiers(false, false, false, EInitialEviction::Ttl); + TestTwoHotTiers(false, false, EInitialEviction::Ttl); } Y_UNIT_TEST(RebootHotTiersAfterTtl) { - TestTwoHotTiers(true, false, false, EInitialEviction::Ttl); + TestTwoHotTiers(true, false, EInitialEviction::Ttl); } // TODO: EnableTtlAfterHotTiers Y_UNIT_TEST(ColdTiers) { - TestHotAndColdTiers(false, EInitialEviction::Tiering, false); + TestHotAndColdTiers(false, EInitialEviction::Tiering); } Y_UNIT_TEST(RebootColdTiers) { //NColumnShard::gAllowLogBatchingDefaultValue = false; - TestHotAndColdTiers(true, EInitialEviction::Tiering, false); + TestHotAndColdTiers(true, EInitialEviction::Tiering); } Y_UNIT_TEST(ColdTiersWithStat) { - TestHotAndColdTiers(false, EInitialEviction::Tiering, true); + TestHotAndColdTiers(false, EInitialEviction::Tiering); } Y_UNIT_TEST(RebootColdTiersWithStat) { //NColumnShard::gAllowLogBatchingDefaultValue = false; - TestHotAndColdTiers(true, EInitialEviction::Tiering, true); + TestHotAndColdTiers(true, EInitialEviction::Tiering); } Y_UNIT_TEST(EnableColdTiersAfterNoEviction) { - TestHotAndColdTiers(false, EInitialEviction::None, false); + TestHotAndColdTiers(false, EInitialEviction::None); } Y_UNIT_TEST(RebootEnableColdTiersAfterNoEviction) { - TestHotAndColdTiers(true, EInitialEviction::None, false); + TestHotAndColdTiers(true, EInitialEviction::None); } Y_UNIT_TEST(EnableColdTiersAfterTtl) { - TestHotAndColdTiers(false, EInitialEviction::Ttl, false); + TestHotAndColdTiers(false, EInitialEviction::Ttl); } Y_UNIT_TEST(RebootEnableColdTiersAfterTtl) { - TestHotAndColdTiers(true, EInitialEviction::Ttl, false); + TestHotAndColdTiers(true, EInitialEviction::Ttl); } Y_UNIT_TEST(OneColdTier) { diff --git a/ydb/core/tx/columnshard/write_actor.cpp b/ydb/core/tx/columnshard/write_actor.cpp index 25cbb99b5915..7478eb1120df 100644 --- a/ydb/core/tx/columnshard/write_actor.cpp +++ b/ydb/core/tx/columnshard/write_actor.cpp @@ -34,6 +34,8 @@ class TWriteActor: public TActorBootstrapped, public TMonitoringObj YellowStopChannels.insert(msg->Id.Channel()); } + status = NYDBTest::TControllers::GetColumnShardController()->OverrideBlobPutResultOnWrite(status); + if (status != NKikimrProto::OK) { ACFL_ERROR("event", "TEvPutResult")("blob_id", msg->Id.ToString())("status", status)("error", msg->ErrorReason); WriteController->Abort("cannot write blob " + msg->Id.ToString() + ", status: " + ::ToString(status) + ". reason: " + msg->ErrorReason); diff --git a/ydb/core/tx/columnshard/ya.make b/ydb/core/tx/columnshard/ya.make index f1f4df107ffe..055037e5d508 100644 --- a/ydb/core/tx/columnshard/ya.make +++ b/ydb/core/tx/columnshard/ya.make @@ -55,11 +55,16 @@ PEERDIR( ydb/core/tx/columnshard/data_sharing ydb/core/tx/columnshard/subscriber ydb/core/tx/columnshard/export + ydb/core/tx/columnshard/tx_reader + ydb/core/tx/columnshard/loading + ydb/core/tx/columnshard/data_accessor ydb/core/tx/columnshard/resource_subscriber ydb/core/tx/columnshard/normalizer ydb/core/tx/columnshard/blobs_action/storages_manager + ydb/core/tx/columnshard/data_accessor/in_mem ydb/core/tx/tiering ydb/core/tx/conveyor/usage + ydb/core/tx/priorities/service ydb/core/tx/tracing ydb/core/tx/long_tx_service/public ydb/core/util diff --git a/ydb/core/tx/conveyor/service/service.cpp b/ydb/core/tx/conveyor/service/service.cpp index 68900c3e25ec..c34acba81c9b 100644 --- a/ydb/core/tx/conveyor/service/service.cpp +++ b/ydb/core/tx/conveyor/service/service.cpp @@ -13,7 +13,7 @@ TDistributor::TDistributor(const TConfig& config, const TString& conveyorName, T void TDistributor::Bootstrap() { const ui32 workersCount = Config.GetWorkersCountForConveyor(NKqp::TStagePredictor::GetUsableThreads()); - AFL_NOTICE(NKikimrServices::TX_CONVEYOR)("name", ConveyorName)("action", "conveyor_registered")("config", Config.DebugString()); + AFL_NOTICE(NKikimrServices::TX_CONVEYOR)("name", ConveyorName)("action", "conveyor_registered")("config", Config.DebugString())("actor_id", SelfId()); for (ui32 i = 0; i < workersCount; ++i) { const double usage = Config.GetWorkerCPUUsage(i); Workers.emplace_back(Register(new TWorker(ConveyorName, usage, SelfId()))); @@ -46,10 +46,10 @@ void TDistributor::HandleMain(TEvInternal::TEvTaskProcessedResult::TPtr& ev) { } void TDistributor::HandleMain(TEvExecution::TEvNewTask::TPtr& ev) { - AFL_DEBUG(NKikimrServices::TX_CONVEYOR)("action", "add_task")("sender", ev->Sender); Counters.IncomingRate->Inc(); const TString taskClass = ev->Get()->GetTask()->GetTaskClassIdentifier(); + AFL_DEBUG(NKikimrServices::TX_CONVEYOR)("action", "add_task")("sender", ev->Sender)("task", taskClass); auto itSignal = Signals.find(taskClass); if (itSignal == Signals.end()) { itSignal = Signals.emplace(taskClass, std::make_shared("Conveyor/" + ConveyorName, taskClass)).first; diff --git a/ydb/core/tx/conveyor/service/service.h b/ydb/core/tx/conveyor/service/service.h index f13629f967ee..0fb25be6464a 100644 --- a/ydb/core/tx/conveyor/service/service.h +++ b/ydb/core/tx/conveyor/service/service.h @@ -89,8 +89,8 @@ class TDistributor: public TActorBootstrapped { public: STATEFN(StateMain) { - NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("name", ConveyorName) - ("workers", Workers.size())("waiting", Waiting.size())("actor_id", SelfId()); +// NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("name", ConveyorName) +// ("workers", Workers.size())("waiting", Waiting.size())("actor_id", SelfId()); switch (ev->GetTypeRewrite()) { hFunc(TEvExecution::TEvNewTask, HandleMain); hFunc(TEvInternal::TEvTaskProcessedResult, HandleMain); diff --git a/ydb/core/tx/conveyor/usage/service.h b/ydb/core/tx/conveyor/usage/service.h index 6ba3c3320fde..22928ae3e6f3 100644 --- a/ydb/core/tx/conveyor/usage/service.h +++ b/ydb/core/tx/conveyor/usage/service.h @@ -41,9 +41,9 @@ class TServiceOperatorImpl { context.Register(new TAsyncTaskExecutor(task)); } static bool SendTaskToExecute(const std::shared_ptr& task) { - auto& context = NActors::TActorContext::AsActorContext(); - const NActors::TActorId& selfId = context.SelfID; - if (TSelf::IsEnabled()) { + if (TSelf::IsEnabled() && NActors::TlsActivationContext) { + auto& context = NActors::TActorContext::AsActorContext(); + const NActors::TActorId& selfId = context.SelfID; context.Send(MakeServiceId(selfId.NodeId()), new NConveyor::TEvExecution::TEvNewTask(task)); return true; } else { diff --git a/ydb/core/tx/data_events/columnshard_splitter.cpp b/ydb/core/tx/data_events/columnshard_splitter.cpp index 19a787167270..14a9a8eba5aa 100644 --- a/ydb/core/tx/data_events/columnshard_splitter.cpp +++ b/ydb/core/tx/data_events/columnshard_splitter.cpp @@ -1,8 +1,12 @@ #include "columnshard_splitter.h" +#include +#include + namespace NKikimr::NEvWrite { -NKikimr::NEvWrite::IShardsSplitter::TYdbConclusionStatus TColumnShardShardsSplitter::DoSplitData(const NSchemeCache::TSchemeCacheNavigate::TEntry& schemeEntry, const IEvWriteDataAccessor& data) { +NKikimr::NEvWrite::IShardsSplitter::TYdbConclusionStatus TColumnShardShardsSplitter::DoSplitData( + const NSchemeCache::TSchemeCacheNavigate::TEntry& schemeEntry, const IEvWriteDataAccessor& data) { if (schemeEntry.Kind != NSchemeCache::TSchemeCacheNavigate::KindColumnTable) { return TYdbConclusionStatus::Fail(Ydb::StatusIds::SCHEME_ERROR, "The specified path is not an column table"); } @@ -25,9 +29,6 @@ NKikimr::NEvWrite::IShardsSplitter::TYdbConclusionStatus TColumnShardShardsSplit if (sharding.ColumnShardsSize() == 0) { return TYdbConclusionStatus::Fail(Ydb::StatusIds::SCHEME_ERROR, "No shards to write to"); } - if (!scheme.HasEngine() || scheme.GetEngine() == NKikimrSchemeOp::COLUMN_ENGINE_NONE) { - return TYdbConclusionStatus::Fail(Ydb::StatusIds::SCHEME_ERROR, "Wrong column table configuration"); - } std::shared_ptr batch; if (data.HasDeserializedBatch()) { @@ -36,7 +37,8 @@ NKikimr::NEvWrite::IShardsSplitter::TYdbConclusionStatus TColumnShardShardsSplit std::shared_ptr arrowScheme = ExtractArrowSchema(scheme); batch = NArrow::DeserializeBatch(data.GetSerializedData(), arrowScheme); if (!batch) { - return TYdbConclusionStatus::Fail(Ydb::StatusIds::SCHEME_ERROR, TString("cannot deserialize batch with schema ") + arrowScheme->ToString()); + return TYdbConclusionStatus::Fail( + Ydb::StatusIds::SCHEME_ERROR, TString("cannot deserialize batch with schema ") + arrowScheme->ToString()); } auto res = batch->ValidateFull(); @@ -55,12 +57,13 @@ NKikimr::NEvWrite::IShardsSplitter::TYdbConclusionStatus TColumnShardShardsSplit return SplitImpl(batch, shardingConclusion.DetachResult()); } -NKikimr::NEvWrite::IShardsSplitter::TYdbConclusionStatus TColumnShardShardsSplitter::SplitImpl(const std::shared_ptr& batch, - const std::shared_ptr& sharding) -{ +NKikimr::NEvWrite::IShardsSplitter::TYdbConclusionStatus TColumnShardShardsSplitter::SplitImpl( + const std::shared_ptr& batch, const std::shared_ptr& sharding) { Y_ABORT_UNLESS(batch); - auto split = sharding->SplitByShards(batch, NColumnShard::TLimits::GetMaxBlobSize() * 0.875); + auto split = sharding->SplitByShards(batch, AppDataVerified().FeatureFlags.GetEnableWritePortionsOnInsert() + ? NOlap::NSplitter::TSplitSettings().GetExpectedPortionSize() + : NColumnShard::TLimits::GetMaxBlobSize() * 0.875); if (split.IsFail()) { return TYdbConclusionStatus::Fail(Ydb::StatusIds::SCHEME_ERROR, split.GetErrorMessage()); } @@ -69,7 +72,8 @@ NKikimr::NEvWrite::IShardsSplitter::TYdbConclusionStatus TColumnShardShardsSplit const TString schemaString = NArrow::SerializeSchema(*batch->schema()); for (auto&& [shardId, chunks] : split.GetResult()) { for (auto&& c : chunks) { - result.AddShardInfo(shardId, std::make_shared(schemaString, c.GetData(), c.GetRowsCount(), sharding->GetShardInfoVerified(shardId).GetShardingVersion())); + result.AddShardInfo(shardId, std::make_shared(schemaString, c.GetData(), c.GetRowsCount(), + sharding->GetShardInfoVerified(shardId).GetShardingVersion())); } } @@ -88,4 +92,4 @@ std::shared_ptr TColumnShardShardsSplitter::ExtractArrowSchema(co return NArrow::TStatusValidator::GetValid(NArrow::MakeArrowSchema(columns)); } -} +} // namespace NKikimr::NEvWrite diff --git a/ydb/core/tx/data_events/common/error_codes.cpp b/ydb/core/tx/data_events/common/error_codes.cpp new file mode 100644 index 000000000000..f7cc9bae1e1f --- /dev/null +++ b/ydb/core/tx/data_events/common/error_codes.cpp @@ -0,0 +1,32 @@ +#include "error_codes.h" + +namespace NKikimr::NEvWrite::NErrorCodes { + +TConclusion TOperator::GetStatusInfo( + const NKikimrDataEvents::TEvWriteResult::EStatus value) { + switch (value) { + case NKikimrDataEvents::TEvWriteResult::STATUS_UNSPECIFIED: + case NKikimrDataEvents::TEvWriteResult::STATUS_PREPARED: + case NKikimrDataEvents::TEvWriteResult::STATUS_COMPLETED: + return TConclusionStatus::Fail("Incorrect status for interpretation to YdbStatus"); + case NKikimrDataEvents::TEvWriteResult::STATUS_ABORTED: + return TYdbStatusInfo(Ydb::StatusIds::ABORTED, NYql::TIssuesIds::KIKIMR_OPERATION_ABORTED, "Request aborted"); + case NKikimrDataEvents::TEvWriteResult::STATUS_DISK_SPACE_EXHAUSTED: + return TYdbStatusInfo(Ydb::StatusIds::INTERNAL_ERROR, NYql::TIssuesIds::KIKIMR_DISK_SPACE_EXHAUSTED, "Disk space exhausted"); + case NKikimrDataEvents::TEvWriteResult::STATUS_INTERNAL_ERROR: + return TYdbStatusInfo(Ydb::StatusIds::INTERNAL_ERROR, NYql::TIssuesIds::KIKIMR_INTERNAL_ERROR, "Request aborted"); + case NKikimrDataEvents::TEvWriteResult::STATUS_OVERLOADED: + return TYdbStatusInfo(Ydb::StatusIds::OVERLOADED, NYql::TIssuesIds::KIKIMR_OVERLOADED, "System overloaded"); + case NKikimrDataEvents::TEvWriteResult::STATUS_CANCELLED: + return TYdbStatusInfo(Ydb::StatusIds::CANCELLED, NYql::TIssuesIds::KIKIMR_OPERATION_CANCELLED, "Request cancelled"); + case NKikimrDataEvents::TEvWriteResult::STATUS_BAD_REQUEST: + return TYdbStatusInfo(Ydb::StatusIds::BAD_REQUEST, NYql::TIssuesIds::KIKIMR_BAD_REQUEST, "Incorrect request"); + case NKikimrDataEvents::TEvWriteResult::STATUS_SCHEME_CHANGED: + return TYdbStatusInfo(Ydb::StatusIds::SCHEME_ERROR, NYql::TIssuesIds::KIKIMR_SCHEMA_CHANGED, "Schema changed"); + case NKikimrDataEvents::TEvWriteResult::STATUS_LOCKS_BROKEN: { + return TYdbStatusInfo(Ydb::StatusIds::ABORTED, NYql::TIssuesIds::KIKIMR_LOCKS_INVALIDATED, "Transaction locks invalidated."); + } + } +} + +} \ No newline at end of file diff --git a/ydb/core/tx/data_events/common/error_codes.h b/ydb/core/tx/data_events/common/error_codes.h new file mode 100644 index 000000000000..a859b8bfdcb8 --- /dev/null +++ b/ydb/core/tx/data_events/common/error_codes.h @@ -0,0 +1,31 @@ +#pragma once +#include +#include + +#include +#include +#include +#include + +namespace NKikimr::NEvWrite::NErrorCodes { + +class TOperator { +public: + class TYdbStatusInfo { + private: + YDB_READONLY(Ydb::StatusIds::StatusCode, YdbStatusCode, Ydb::StatusIds::STATUS_CODE_UNSPECIFIED); + YDB_READONLY(NYql::TIssuesIds::EIssueCode, IssueCode, NYql::TIssuesIds::UNEXPECTED); + YDB_READONLY_DEF(TString, IssueGeneralText); + + public: + TYdbStatusInfo(const Ydb::StatusIds::StatusCode code, const NYql::TIssuesIds::EIssueCode issueCode, const TString& issueMessage) + : YdbStatusCode(code) + , IssueCode(issueCode) + , IssueGeneralText(issueMessage) { + } + }; + + static TConclusion GetStatusInfo(const NKikimrDataEvents::TEvWriteResult::EStatus value); +}; + +} // namespace NKikimr::NEvWrite::NErrorCodes diff --git a/ydb/core/tx/data_events/common/modification_type.h b/ydb/core/tx/data_events/common/modification_type.h index cf9f8d90e24f..f93eeda183e3 100644 --- a/ydb/core/tx/data_events/common/modification_type.h +++ b/ydb/core/tx/data_events/common/modification_type.h @@ -49,18 +49,18 @@ class TEnumOperator { } } - static TProto SerializeToProto(const NEvWrite::EModificationType value) { + static NKikimrDataEvents::TEvWrite::TOperation::EOperationType SerializeToWriteProto(const NEvWrite::EModificationType value) { switch (value) { case NEvWrite::EModificationType::Upsert: - return NKikimrTxColumnShard::TEvWrite::OPERATION_UPSERT; + return NKikimrDataEvents::TEvWrite::TOperation::OPERATION_UPSERT; case NEvWrite::EModificationType::Insert: - return NKikimrTxColumnShard::TEvWrite::OPERATION_INSERT; + return NKikimrDataEvents::TEvWrite::TOperation::OPERATION_INSERT; case NEvWrite::EModificationType::Delete: - return NKikimrTxColumnShard::TEvWrite::OPERATION_DELETE; + return NKikimrDataEvents::TEvWrite::TOperation::OPERATION_DELETE; case NEvWrite::EModificationType::Replace: - return NKikimrTxColumnShard::TEvWrite::OPERATION_REPLACE; + return NKikimrDataEvents::TEvWrite::TOperation::OPERATION_REPLACE; case NEvWrite::EModificationType::Update: - return NKikimrTxColumnShard::TEvWrite::OPERATION_UPDATE; + return NKikimrDataEvents::TEvWrite::TOperation::OPERATION_UPDATE; } } @@ -81,6 +81,21 @@ class TEnumOperator { } } + static TProto SerializeToProto(const NEvWrite::EModificationType value) { + switch (value) { + case NEvWrite::EModificationType::Upsert: + return NKikimrTxColumnShard::TEvWrite::OPERATION_UPSERT; + case NEvWrite::EModificationType::Insert: + return NKikimrTxColumnShard::TEvWrite::OPERATION_INSERT; + case NEvWrite::EModificationType::Delete: + return NKikimrTxColumnShard::TEvWrite::OPERATION_DELETE; + case NEvWrite::EModificationType::Replace: + return NKikimrTxColumnShard::TEvWrite::OPERATION_REPLACE; + case NEvWrite::EModificationType::Update: + return NKikimrTxColumnShard::TEvWrite::OPERATION_UPDATE; + } + } + static NEvWrite::EModificationType DeserializeFromProto(const NKikimrTxColumnShard::TEvWrite::EModificationType value) { switch (value) { case NKikimrTxColumnShard::TEvWrite::OPERATION_UPSERT: diff --git a/ydb/core/tx/data_events/common/ya.make b/ydb/core/tx/data_events/common/ya.make index acbb8ebd252f..33e4947a1a3f 100644 --- a/ydb/core/tx/data_events/common/ya.make +++ b/ydb/core/tx/data_events/common/ya.make @@ -2,10 +2,14 @@ LIBRARY() PEERDIR( ydb/core/protos + ydb/library/conclusion + ydb/library/yql/core/issue/protos + ydb/public/api/protos ) SRCS( modification_type.cpp + error_codes.cpp ) END() diff --git a/ydb/core/tx/data_events/events.h b/ydb/core/tx/data_events/events.h index bd4f06284e9d..87279ec784f0 100644 --- a/ydb/core/tx/data_events/events.h +++ b/ydb/core/tx/data_events/events.h @@ -62,7 +62,8 @@ struct TDataEvents { return *this; } - void AddOperation(NKikimrDataEvents::TEvWrite_TOperation::EOperationType operationType, const TTableId& tableId, const std::vector& columnIds, + NKikimrDataEvents::TEvWrite::TOperation& AddOperation(NKikimrDataEvents::TEvWrite_TOperation::EOperationType operationType, + const TTableId& tableId, const std::vector& columnIds, ui64 payloadIndex, NKikimrDataEvents::EDataFormat payloadFormat) { Y_ABORT_UNLESS(operationType != NKikimrDataEvents::TEvWrite::TOperation::OPERATION_UNSPECIFIED); Y_ABORT_UNLESS(payloadFormat != NKikimrDataEvents::FORMAT_UNSPECIFIED); @@ -75,6 +76,7 @@ struct TDataEvents { operation->MutableTableId()->SetTableId(tableId.PathId.LocalPathId); operation->MutableTableId()->SetSchemaVersion(tableId.SchemaVersion); operation->MutableColumnIds()->Assign(columnIds.begin(), columnIds.end()); + return *operation; } ui64 GetTxId() const { @@ -94,7 +96,7 @@ struct TDataEvents { static std::unique_ptr BuildError(const ui64 origin, const ui64 txId, const NKikimrDataEvents::TEvWriteResult::EStatus& status, const TString& errorMsg) { auto result = std::make_unique(); - ACFL_ERROR("event", "ev_write_error")("status", NKikimrDataEvents::TEvWriteResult::EStatus_Name(status))("details", errorMsg)("tx_id", txId); + ACFL_WARN("event", "ev_write_error")("status", NKikimrDataEvents::TEvWriteResult::EStatus_Name(status))("details", errorMsg)("tx_id", txId); result->Record.SetOrigin(origin); result->Record.SetTxId(txId); result->Record.SetStatus(status); diff --git a/ydb/core/tx/data_events/shard_writer.cpp b/ydb/core/tx/data_events/shard_writer.cpp index e09a1bbc657a..d4d38101e6be 100644 --- a/ydb/core/tx/data_events/shard_writer.cpp +++ b/ydb/core/tx/data_events/shard_writer.cpp @@ -1,4 +1,5 @@ #include "shard_writer.h" +#include "common/error_codes.h" #include #include @@ -41,7 +42,9 @@ namespace NKikimr::NEvWrite { } TShardWriter::TShardWriter(const ui64 shardId, const ui64 tableId, const ui64 schemaVersion, const TString& dedupId, const IShardInfo::TPtr& data, - const NWilson::TProfileSpan& parentSpan, TWritersController::TPtr externalController, const ui32 writePartIdx, const EModificationType mType, const bool immediateWrite) + const NWilson::TProfileSpan& parentSpan, TWritersController::TPtr externalController, const ui32 writePartIdx, const EModificationType mType, const bool immediateWrite, + const std::optional timeout + ) : ShardId(shardId) , WritePartIdx(writePartIdx) , TableId(tableId) @@ -53,6 +56,7 @@ namespace NKikimr::NEvWrite { , ActorSpan(parentSpan.BuildChildrenSpan("ShardWriter")) , ModificationType(mType) , ImmediateWrite(immediateWrite) + , Timeout(timeout) { } @@ -70,6 +74,9 @@ namespace NKikimr::NEvWrite { void TShardWriter::Bootstrap() { SendWriteRequest(); + if (Timeout) { + Schedule(*Timeout, new TEvents::TEvWakeup(1)); + } Become(&TShardWriter::StateMain); } @@ -86,8 +93,9 @@ namespace NKikimr::NEvWrite { auto gPassAway = PassAwayGuard(); if (ydbStatus != NKikimrDataEvents::TEvWriteResult::STATUS_COMPLETED) { - ExternalController->OnFail(Ydb::StatusIds::GENERIC_ERROR, - TStringBuilder() << "Cannot write data into shard " << ShardId << " in longTx " << + auto statusInfo = NEvWrite::NErrorCodes::TOperator::GetStatusInfo(ydbStatus).DetachResult(); + ExternalController->OnFail(statusInfo.GetYdbStatusCode(), + TStringBuilder() << "Cannot write data into shard(" << statusInfo.GetIssueGeneralText() << ") " << ShardId << " in longTx " << ExternalController->GetLongTxId().ToString()); return; } @@ -136,8 +144,17 @@ namespace NKikimr::NEvWrite { } } - void TShardWriter::HandleTimeout(const TActorContext& /*ctx*/) { - RetryWriteRequest(false); + void TShardWriter::Handle(NActors::TEvents::TEvWakeup::TPtr& ev) { + if (ev->Get()->Tag) { + auto gPassAway = PassAwayGuard(); + ExternalController->OnFail(Ydb::StatusIds::TIMEOUT, TStringBuilder() + << "Cannot write data (TIMEOUT) into shard " << ShardId << " in longTx " + << ExternalController->GetLongTxId().ToString()); + ExternalController->GetCounters()->OnGlobalTimeout(); + } else { + ExternalController->GetCounters()->OnRetryTimeout(); + RetryWriteRequest(false); + } } bool TShardWriter::RetryWriteRequest(const bool delayed) { @@ -145,7 +162,7 @@ namespace NKikimr::NEvWrite { return false; } if (delayed) { - Schedule(OverloadTimeout(), new TEvents::TEvWakeup()); + Schedule(OverloadTimeout(), new TEvents::TEvWakeup(0)); } else { ++NumRetries; SendWriteRequest(); diff --git a/ydb/core/tx/data_events/shard_writer.h b/ydb/core/tx/data_events/shard_writer.h index 323bffa5056f..b3c53d3d36d7 100644 --- a/ydb/core/tx/data_events/shard_writer.h +++ b/ydb/core/tx/data_events/shard_writer.h @@ -1,16 +1,17 @@ #pragma once -#include "common/modification_type.h" #include "events.h" #include "shards_splitter.h" -#include +#include "common/modification_type.h" + #include +#include #include + +#include #include #include -#include - namespace NKikimr::NEvWrite { @@ -19,6 +20,7 @@ class TWriteIdForShard { YDB_READONLY(ui64, ShardId, 0); YDB_READONLY(ui64, WriteId, 0); YDB_READONLY(ui64, WritePartId, 0); + public: TWriteIdForShard() = default; TWriteIdForShard(const ui64 shardId, const ui64 writeId, const ui32 writePartId) @@ -37,9 +39,13 @@ class TCSUploadCounters: public NColumnShard::TCommonCountersOwner { NMonitoring::THistogramPtr FailedFullReplyDuration; NMonitoring::THistogramPtr BytesDistribution; NMonitoring::THistogramPtr RowsDistribution; + NMonitoring::THistogramPtr ShardsCountDistribution; NMonitoring::TDynamicCounters::TCounterPtr RowsCount; NMonitoring::TDynamicCounters::TCounterPtr BytesCount; NMonitoring::TDynamicCounters::TCounterPtr FailsCount; + NMonitoring::TDynamicCounters::TCounterPtr GlobalTimeoutCount; + NMonitoring::TDynamicCounters::TCounterPtr RetryTimeoutCount; + public: TCSUploadCounters() : TBase("CSUpload") @@ -49,10 +55,21 @@ class TCSUploadCounters: public NColumnShard::TCommonCountersOwner { , FailedFullReplyDuration(TBase::GetHistogram("Replies/Failed/Full/DurationMs", NMonitoring::ExponentialHistogram(15, 2, 10))) , BytesDistribution(TBase::GetHistogram("Requests/Bytes", NMonitoring::ExponentialHistogram(15, 2, 1024))) , RowsDistribution(TBase::GetHistogram("Requests/Rows", NMonitoring::ExponentialHistogram(15, 2, 16))) + , ShardsCountDistribution(TBase::GetHistogram("Requests/ShardSplits", NMonitoring::LinearHistogram(50, 1, 1))) , RowsCount(TBase::GetDeriviative("Rows")) , BytesCount(TBase::GetDeriviative("Bytes")) - , FailsCount(TBase::GetDeriviative("Fails")) { + , FailsCount(TBase::GetDeriviative("Fails")) + , GlobalTimeoutCount(TBase::GetDeriviative("GlobalTimeouts")) + , RetryTimeoutCount(TBase::GetDeriviative("RetryTimeouts")) + { + } + void OnGlobalTimeout() const { + GlobalTimeoutCount->Inc(); + } + + void OnRetryTimeout() const { + RetryTimeoutCount->Inc(); } void OnRequest(const ui64 rows, const ui64 bytes) const { @@ -66,6 +83,10 @@ class TCSUploadCounters: public NColumnShard::TCommonCountersOwner { FailsCount->Add(1); } + void OnSplitByShards(const ui64 shardsCount) const { + ShardsCountDistribution->Collect(shardsCount); + } + void OnCSReply(const TDuration d) const { CSReplyDuration->Collect(d.MilliSeconds()); } @@ -110,6 +131,7 @@ class TWritersController { LongTxActorId.Send(NLongTxService::MakeLongTxServiceID(LongTxActorId.NodeId()), req.Release()); } } + public: using TPtr = std::shared_ptr; @@ -131,10 +153,10 @@ class TWritersController { , Issues(issues) { } }; - }; - TWritersController(const ui32 writesCount, const NActors::TActorIdentity& longTxActorId, const NLongTxService::TLongTxId& longTxId, const bool immediateWrite); + TWritersController(const ui32 writesCount, const NActors::TActorIdentity& longTxActorId, const NLongTxService::TLongTxId& longTxId, + const bool immediateWrite); void OnSuccess(const ui64 shardId, const ui64 writeId, const ui32 writePartId); void OnFail(const Ydb::StatusIds::StatusCode code, const TString& message); }; @@ -158,40 +180,43 @@ class TShardWriter: public NActors::TActorBootstrapped { NWilson::TProfileSpan ActorSpan; EModificationType ModificationType; const bool ImmediateWrite = false; + const std::optional Timeout; void SendWriteRequest(); static TDuration OverloadTimeout() { return TDuration::MilliSeconds(OverloadedDelayMs); } void SendToTablet(THolder event) { - Send(LeaderPipeCache, new TEvPipeCache::TEvForward(event.Release(), ShardId, true), - IEventHandle::FlagTrackDelivery, 0, ActorSpan.GetTraceId()); + Send(LeaderPipeCache, new TEvPipeCache::TEvForward(event.Release(), ShardId, true), IEventHandle::FlagTrackDelivery, 0, + ActorSpan.GetTraceId()); } virtual void PassAway() override { Send(LeaderPipeCache, new TEvPipeCache::TEvUnlink(0)); TBase::PassAway(); } + public: TShardWriter(const ui64 shardId, const ui64 tableId, const ui64 schemaVersion, const TString& dedupId, const IShardInfo::TPtr& data, const NWilson::TProfileSpan& parentSpan, TWritersController::TPtr externalController, const ui32 writePartIdx, - const EModificationType mType, const bool immediateWrite); + const EModificationType mType, const bool immediateWrite, const std::optional timeout = std::nullopt); STFUNC(StateMain) { switch (ev->GetTypeRewrite()) { hFunc(TEvColumnShard::TEvWriteResult, Handle); hFunc(TEvPipeCache::TEvDeliveryProblem, Handle); hFunc(NEvents::TDataEvents::TEvWriteResult, Handle); - CFunc(TEvents::TSystem::Wakeup, HandleTimeout); + hFunc(NActors::TEvents::TEvWakeup, Handle); } } void Bootstrap(); + void Handle(NActors::TEvents::TEvWakeup::TPtr& ev); void Handle(TEvColumnShard::TEvWriteResult::TPtr& ev); void Handle(TEvPipeCache::TEvDeliveryProblem::TPtr& ev); void Handle(NEvents::TDataEvents::TEvWriteResult::TPtr& ev); - void HandleTimeout(const TActorContext& ctx); + private: bool RetryWriteRequest(const bool delayed = true); }; -} +} // namespace NKikimr::NEvWrite diff --git a/ydb/core/tx/data_events/write_data.cpp b/ydb/core/tx/data_events/write_data.cpp index 390667624dda..05237dbb0701 100644 --- a/ydb/core/tx/data_events/write_data.cpp +++ b/ydb/core/tx/data_events/write_data.cpp @@ -1,20 +1,22 @@ #include "write_data.h" + #include #include -#include +#include namespace NKikimr::NEvWrite { -TWriteData::TWriteData(const TWriteMeta& writeMeta, IDataContainer::TPtr data, const std::shared_ptr& primaryKeySchema, const std::shared_ptr& blobsAction) +TWriteData::TWriteData(const TWriteMeta& writeMeta, IDataContainer::TPtr data, const std::shared_ptr& primaryKeySchema, + const std::shared_ptr& blobsAction, const bool writePortions) : WriteMeta(writeMeta) , Data(data) , PrimaryKeySchema(primaryKeySchema) , BlobsAction(blobsAction) -{ + , WritePortions(writePortions) { Y_ABORT_UNLESS(Data); Y_ABORT_UNLESS(PrimaryKeySchema); Y_ABORT_UNLESS(BlobsAction); } -} +} // namespace NKikimr::NEvWrite diff --git a/ydb/core/tx/data_events/write_data.h b/ydb/core/tx/data_events/write_data.h index 0acbec1bcf98..f629a68d4937 100644 --- a/ydb/core/tx/data_events/write_data.h +++ b/ydb/core/tx/data_events/write_data.h @@ -1,13 +1,15 @@ #pragma once #include "common/modification_type.h" -#include #include -#include -#include +#include +#include +#include #include #include +#include + #include namespace NKikimr::NOlap { @@ -17,9 +19,13 @@ class IBlobsWritingAction; namespace NKikimr::NEvWrite { class IDataContainer { +private: + YDB_ACCESSOR_DEF(NArrow::NMerger::TIntervalPositions, SeparationPoints); + public: using TPtr = std::shared_ptr; - virtual ~IDataContainer() {} + virtual ~IDataContainer() { + } virtual TConclusion> ExtractBatch() = 0; virtual ui64 GetSchemaVersion() const = 0; virtual ui64 GetSize() const = 0; @@ -31,13 +37,13 @@ class TWriteMeta { YDB_READONLY(ui64, TableId, 0); YDB_ACCESSOR_DEF(NActors::TActorId, Source); YDB_ACCESSOR_DEF(std::optional, GranuleShardingVersion); + YDB_READONLY(TString, Id, TGUID::CreateTimebased().AsUuidString()); // Long Tx logic YDB_OPT(NLongTxService::TLongTxId, LongTxId); YDB_ACCESSOR(ui64, WritePartId, 0); YDB_ACCESSOR_DEF(TString, DedupId); - YDB_READONLY(TString, Id, TGUID::CreateTimebased().AsUuidString()); YDB_ACCESSOR(EModificationType, ModificationType, EModificationType::Replace); YDB_READONLY(TMonotonic, WriteStartInstant, TMonotonic::Now()); YDB_ACCESSOR(TMonotonic, WriteMiddle1StartInstant, TMonotonic::Now()); @@ -47,6 +53,7 @@ class TWriteMeta { YDB_ACCESSOR(TMonotonic, WriteMiddle5StartInstant, TMonotonic::Now()); YDB_ACCESSOR(TMonotonic, WriteMiddle6StartInstant, TMonotonic::Now()); std::optional LockId; + public: void SetLockId(const ui64 lockId) { LockId = lockId; @@ -73,12 +80,14 @@ class TWriteMeta { } } - TWriteMeta(const ui64 writeId, const ui64 tableId, const NActors::TActorId& source, const std::optional granuleShardingVersion) + TWriteMeta(const ui64 writeId, const ui64 tableId, const NActors::TActorId& source, const std::optional granuleShardingVersion, + const TString& writingIdentifier) : WriteId(writeId) , TableId(tableId) , Source(source) , GranuleShardingVersion(granuleShardingVersion) - {} + , Id(writingIdentifier) { + } }; class TWriteData { @@ -88,8 +97,11 @@ class TWriteData { YDB_READONLY_DEF(std::shared_ptr, PrimaryKeySchema); YDB_READONLY_DEF(std::shared_ptr, BlobsAction); YDB_ACCESSOR_DEF(std::optional, SchemaSubset); + YDB_READONLY(bool, WritePortions, false); + public: - TWriteData(const TWriteMeta& writeMeta, IDataContainer::TPtr data, const std::shared_ptr& primaryKeySchema, const std::shared_ptr& blobsAction); + TWriteData(const TWriteMeta& writeMeta, IDataContainer::TPtr data, const std::shared_ptr& primaryKeySchema, + const std::shared_ptr& blobsAction, const bool writePortions); const NArrow::TSchemaSubset& GetSchemaSubsetVerified() const { AFL_VERIFY(SchemaSubset); @@ -109,4 +121,4 @@ class TWriteData { } }; -} +} // namespace NKikimr::NEvWrite diff --git a/ydb/core/tx/limiter/grouped_memory/service/allocation.cpp b/ydb/core/tx/limiter/grouped_memory/service/allocation.cpp index 2d04be2c9cef..1dfe27953eb8 100644 --- a/ydb/core/tx/limiter/grouped_memory/service/allocation.cpp +++ b/ydb/core/tx/limiter/grouped_memory/service/allocation.cpp @@ -16,11 +16,11 @@ TAllocationInfo::TAllocationInfo(const ui64 processId, const ui64 scopeId, const AFL_VERIFY(Allocation); AFL_INFO(NKikimrServices::GROUPED_MEMORY_LIMITER)("event", "add")("id", Allocation->GetIdentifier())("stage", Stage->GetName()); AllocatedVolume = Allocation->GetMemory(); - Stage->Add(AllocatedVolume, Allocation->IsAllocated()); if (allocation->IsAllocated()) { AFL_INFO(NKikimrServices::GROUPED_MEMORY_LIMITER)("event", "allocated_on_add")("allocation_id", Identifier)("stage", Stage->GetName()); Allocation = nullptr; } + Stage->Add(AllocatedVolume, GetAllocationStatus() == EAllocationStatus::Allocated); } } // namespace NKikimr::NOlap::NGroupedMemoryManager diff --git a/ydb/core/tx/limiter/grouped_memory/service/allocation.h b/ydb/core/tx/limiter/grouped_memory/service/allocation.h index dcbf2971367c..14b7316ac760 100644 --- a/ydb/core/tx/limiter/grouped_memory/service/allocation.h +++ b/ydb/core/tx/limiter/grouped_memory/service/allocation.h @@ -48,12 +48,17 @@ class TAllocationInfo: public NColumnShard::TMonitoringObjectsCounterGetName()); AFL_VERIFY(Allocation)("status", GetAllocationStatus())("volume", AllocatedVolume)("id", Identifier)("stage", Stage->GetName())( "allocation_internal_group_id", AllocationInternalGroupId); + auto allocationResult = Stage->Allocate(AllocatedVolume); + if (allocationResult.IsFail()) { + AllocationFailed = true; + Allocation->OnAllocationImpossible(allocationResult.GetErrorMessage()); + Allocation = nullptr; + return false; + } const bool result = Allocation->OnAllocated( std::make_shared(ProcessId, ScopeId, Allocation->GetIdentifier(), ownerId, Allocation->GetMemory()), Allocation); - if (result) { - Stage->Allocate(AllocatedVolume); - } else { - Stage->Free(AllocatedVolume, false); + if (!result) { + Stage->Free(AllocatedVolume, true); AllocationFailed = true; } Allocation = nullptr; diff --git a/ydb/core/tx/limiter/grouped_memory/service/counters.h b/ydb/core/tx/limiter/grouped_memory/service/counters.h index 3c96b3b8b9a4..1d55b7b17f4a 100644 --- a/ydb/core/tx/limiter/grouped_memory/service/counters.h +++ b/ydb/core/tx/limiter/grouped_memory/service/counters.h @@ -10,6 +10,7 @@ class TStageCounters: public NColumnShard::TCommonCountersOwner { NMonitoring::TDynamicCounters::TCounterPtr AllocatedChunks; NMonitoring::TDynamicCounters::TCounterPtr WaitingBytes; NMonitoring::TDynamicCounters::TCounterPtr WaitingChunks; + NMonitoring::TDynamicCounters::TCounterPtr AllocationFailCount; public: TStageCounters(const TCommonCountersOwner& owner, const TString& name) @@ -17,7 +18,12 @@ class TStageCounters: public NColumnShard::TCommonCountersOwner { , AllocatedBytes(TBase::GetValue("Allocated/Bytes")) , AllocatedChunks(TBase::GetValue("Allocated/Count")) , WaitingBytes(TBase::GetValue("Waiting/Bytes")) - , WaitingChunks(TBase::GetValue("Waiting/Count")) { + , WaitingChunks(TBase::GetValue("Waiting/Count")) + , AllocationFailCount(TBase::GetValue("AllocationFails/Count")) { + } + + void OnCannotAllocate() { + AllocationFailCount->Add(1); } void Add(const ui64 volume, const bool allocated) { diff --git a/ydb/core/tx/limiter/grouped_memory/usage/abstract.h b/ydb/core/tx/limiter/grouped_memory/usage/abstract.h index d92120f46fb6..df314b8a9475 100644 --- a/ydb/core/tx/limiter/grouped_memory/usage/abstract.h +++ b/ydb/core/tx/limiter/grouped_memory/usage/abstract.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace NKikimr::NOlap::NGroupedMemoryManager { @@ -83,7 +84,7 @@ class TPositiveControlInteger { Value += value; } void Sub(const ui64 value) { - AFL_VERIFY(value <= Value); + AFL_VERIFY(value <= Value)("base", Value)("delta", value); Value -= value; } ui64 Val() const { @@ -95,6 +96,7 @@ class TStageFeatures { private: YDB_READONLY_DEF(TString, Name); YDB_READONLY(ui64, Limit, 0); + YDB_READONLY(ui64, HardLimit, 0); YDB_ACCESSOR_DEF(TPositiveControlInteger, Usage); YDB_ACCESSOR_DEF(TPositiveControlInteger, Waiting); std::shared_ptr Owner; @@ -114,27 +116,41 @@ class TStageFeatures { return Usage.Val() + Waiting.Val(); } - TStageFeatures( - const TString& name, const ui64 limit, const std::shared_ptr& owner, const std::shared_ptr& counters) + TStageFeatures(const TString& name, const ui64 limit, const ui64 hardLimit, const std::shared_ptr& owner, + const std::shared_ptr& counters) : Name(name) , Limit(limit) + , HardLimit(hardLimit) , Owner(owner) , Counters(counters) { } - void Allocate(const ui64 volume) { + [[nodiscard]] TConclusionStatus Allocate(const ui64 volume) { Waiting.Sub(volume); + if (HardLimit < Usage.Val() + volume) { + Counters->OnCannotAllocate(); + AFL_DEBUG(NKikimrServices::GROUPED_MEMORY_LIMITER)("name", Name)("event", "cannot_allocate")("limit", HardLimit)( + "usage", Usage.Val())( + "delta", volume); + return TConclusionStatus::Fail(TStringBuilder() << Name << "::(limit:" << HardLimit << ";val:" << Usage.Val() << ";delta=" << volume << ");"); + } Usage.Add(volume); + AFL_DEBUG(NKikimrServices::GROUPED_MEMORY_LIMITER)("name", Name)("event", "allocate")("usage", Usage.Val())("delta", volume); if (Counters) { Counters->Add(volume, true); Counters->Sub(volume, false); } if (Owner) { - Owner->Allocate(volume); + const auto ownerResult = Owner->Allocate(volume); + if (ownerResult.IsFail()) { + Free(volume, true, false); + return ownerResult; + } } + return TConclusionStatus::Success(); } - void Free(const ui64 volume, const bool allocated) { + void Free(const ui64 volume, const bool allocated, const bool withOwner = true) { if (Counters) { Counters->Sub(volume, allocated); } @@ -143,8 +159,9 @@ class TStageFeatures { } else { Waiting.Sub(volume); } + AFL_DEBUG(NKikimrServices::GROUPED_MEMORY_LIMITER)("name", Name)("event", "free")("usage", Usage.Val())("delta", volume); - if (Owner) { + if (withOwner && Owner) { Owner->Free(volume, allocated); } } @@ -154,6 +171,8 @@ class TStageFeatures { Counters->Sub(from, allocated); Counters->Add(to, allocated); } + AFL_DEBUG(NKikimrServices::GROUPED_MEMORY_LIMITER)("name", Name)("event", "update")("usage", Usage.Val())("waiting", Waiting.Val())( + "allocated", allocated)("from", from)("to", to); if (allocated) { Usage.Sub(from); Usage.Add(to); @@ -199,6 +218,7 @@ class IAllocation { YDB_READONLY(ui64, Identifier, Counter.Inc()); YDB_READONLY(ui64, Memory, 0); bool Allocated = false; + virtual void DoOnAllocationImpossible(const TString& errorMessage) = 0; virtual bool DoOnAllocated( std::shared_ptr&& guard, const std::shared_ptr& allocation) = 0; @@ -216,6 +236,10 @@ class IAllocation { return Allocated; } + void OnAllocationImpossible(const TString& errorMessage) { + DoOnAllocationImpossible(errorMessage); + } + [[nodiscard]] bool OnAllocated( std::shared_ptr&& guard, const std::shared_ptr& allocation); }; diff --git a/ydb/core/tx/limiter/grouped_memory/usage/config.cpp b/ydb/core/tx/limiter/grouped_memory/usage/config.cpp index 17fe55975744..ee01e1ef3f66 100644 --- a/ydb/core/tx/limiter/grouped_memory/usage/config.cpp +++ b/ydb/core/tx/limiter/grouped_memory/usage/config.cpp @@ -7,13 +7,16 @@ bool TConfig::DeserializeFromProto(const NKikimrConfig::TGroupedMemoryLimiterCon if (config.HasMemoryLimit()) { MemoryLimit = config.GetMemoryLimit(); } + if (config.HasHardMemoryLimit()) { + HardMemoryLimit = config.GetHardMemoryLimit(); + } Enabled = config.GetEnabled(); return true; } TString TConfig::DebugString() const { TStringBuilder sb; - sb << "MemoryLimit=" << MemoryLimit << ";Enabled=" << Enabled << ";"; + sb << "MemoryLimit=" << MemoryLimit << ";HardMemoryLimit=" << HardMemoryLimit << ";Enabled=" << Enabled << ";"; return sb; } diff --git a/ydb/core/tx/limiter/grouped_memory/usage/config.h b/ydb/core/tx/limiter/grouped_memory/usage/config.h index 91a9b5bc7afe..c3a69680a199 100644 --- a/ydb/core/tx/limiter/grouped_memory/usage/config.h +++ b/ydb/core/tx/limiter/grouped_memory/usage/config.h @@ -8,6 +8,7 @@ class TConfig { private: YDB_READONLY(bool, Enabled, true); YDB_READONLY(ui64, MemoryLimit, ui64(3) << 30); + YDB_READONLY(ui64, HardMemoryLimit, ui64(10) << 30); public: diff --git a/ydb/core/tx/limiter/grouped_memory/usage/service.h b/ydb/core/tx/limiter/grouped_memory/usage/service.h index 8192743218b1..b662494d7b0b 100644 --- a/ydb/core/tx/limiter/grouped_memory/usage/service.h +++ b/ydb/core/tx/limiter/grouped_memory/usage/service.h @@ -15,13 +15,14 @@ class TServiceOperatorImpl { private: TConfig ServiceConfig = TConfig::BuildDisabledConfig(); std::shared_ptr Counters; - std::shared_ptr DefaultStageFeatures = std::make_shared("DEFAULT", ((ui64)3) << 30, nullptr, nullptr); + std::shared_ptr DefaultStageFeatures = + std::make_shared("DEFAULT", ((ui64)3) << 30, ((ui64)10) << 30, nullptr, nullptr); using TSelf = TServiceOperatorImpl; static void Register(const TConfig& serviceConfig, TIntrusivePtr<::NMonitoring::TDynamicCounters> counters) { Singleton()->Counters = std::make_shared(counters, TMemoryLimiterPolicy::Name); Singleton()->ServiceConfig = serviceConfig; - Singleton()->DefaultStageFeatures = std::make_shared( - "GLOBAL", serviceConfig.GetMemoryLimit(), nullptr, Singleton()->Counters->BuildStageCounters("general")); + Singleton()->DefaultStageFeatures = std::make_shared("GLOBAL", serviceConfig.GetMemoryLimit(), + serviceConfig.GetHardMemoryLimit(), nullptr, Singleton()->Counters->BuildStageCounters("general")); } static const TString& GetMemoryLimiterName() { Y_ABORT_UNLESS(TMemoryLimiterPolicy::Name.size() == 4); @@ -35,7 +36,7 @@ class TServiceOperatorImpl { } else { AFL_VERIFY(Singleton()->DefaultStageFeatures); return std::make_shared( - name, limit, Singleton()->DefaultStageFeatures, Singleton()->Counters->BuildStageCounters(name)); + name, limit, Max(), Singleton()->DefaultStageFeatures, Singleton()->Counters->BuildStageCounters(name)); } } diff --git a/ydb/core/tx/priorities/service/counters.cpp b/ydb/core/tx/priorities/service/counters.cpp new file mode 100644 index 000000000000..f0448e9f02f3 --- /dev/null +++ b/ydb/core/tx/priorities/service/counters.cpp @@ -0,0 +1,19 @@ +#include "counters.h" + +namespace NKikimr::NPrioritiesQueue { + + TCounters::TCounters(const TString& queueName, TIntrusivePtr<::NMonitoring::TDynamicCounters> baseSignals) + : TBase("Priorities/" + queueName, baseSignals) + , UsedCount(TBase::GetValue("UsedCount")) + , Ask(TBase::GetDeriviative("Ask")) + , AskMax(TBase::GetDeriviative("AskMax")) + , Free(TBase::GetDeriviative("Free")) + , FreeNoClient(TBase::GetDeriviative("FreeNoClient")) + , Register(TBase::GetDeriviative("Register")) + , Unregister(TBase::GetDeriviative("Unregister")) + , QueueSize(TBase::GetValue("QueueSize")) + , Clients(TBase::GetDeriviative("Clients")) + , Limit(TBase::GetValue("Limit")) { +} + +} diff --git a/ydb/core/tx/priorities/service/counters.h b/ydb/core/tx/priorities/service/counters.h new file mode 100644 index 000000000000..7eb2202d9556 --- /dev/null +++ b/ydb/core/tx/priorities/service/counters.h @@ -0,0 +1,27 @@ +#pragma once +#include + +#include + +namespace NKikimr::NPrioritiesQueue { + +class TCounters: public NColumnShard::TCommonCountersOwner { +private: + using TBase = NColumnShard::TCommonCountersOwner; + +public: + const ::NMonitoring::TDynamicCounters::TCounterPtr UsedCount; + const ::NMonitoring::TDynamicCounters::TCounterPtr Ask; + const ::NMonitoring::TDynamicCounters::TCounterPtr AskMax; + const ::NMonitoring::TDynamicCounters::TCounterPtr Free; + const ::NMonitoring::TDynamicCounters::TCounterPtr FreeNoClient; + const ::NMonitoring::TDynamicCounters::TCounterPtr Register; + const ::NMonitoring::TDynamicCounters::TCounterPtr Unregister; + const ::NMonitoring::TDynamicCounters::TCounterPtr QueueSize; + const ::NMonitoring::TDynamicCounters::TCounterPtr Clients; + const ::NMonitoring::TDynamicCounters::TCounterPtr Limit; + + TCounters(const TString& queueName, TIntrusivePtr<::NMonitoring::TDynamicCounters> baseSignals); +}; + +} // namespace NKikimr::NPrioritiesQueue diff --git a/ydb/core/tx/priorities/service/manager.cpp b/ydb/core/tx/priorities/service/manager.cpp new file mode 100644 index 000000000000..219c39de1df6 --- /dev/null +++ b/ydb/core/tx/priorities/service/manager.cpp @@ -0,0 +1,103 @@ +#include "manager.h" + +#include +#include + +#include + +namespace NKikimr::NPrioritiesQueue { + +void TManager::AllocateNext() { + while (WaitingQueue.size() && UsedCount + WaitingQueue.begin()->second.GetSize() <= Config.GetLimit()) { + auto& waitRequest = WaitingQueue.begin()->second; + auto it = Clients.find(waitRequest.GetClientId()); + AFL_VERIFY(it != Clients.end()); + UsedCount += waitRequest.GetSize(); + it->second.MutableCount() += waitRequest.GetSize(); + it->second.SetLastPriority(std::nullopt); + waitRequest.GetRequest()->OnAllocated(std::make_shared(ServiceActorId, waitRequest.GetClientId(), waitRequest.GetSize())); + WaitingQueue.erase(WaitingQueue.begin()); + } + Counters->QueueSize->Set(WaitingQueue.size()); + Counters->UsedCount->Set(UsedCount); +} + +void TManager::RemoveFromQueue(const TClientStatus& client) { + if (!client.GetLastPriority()) { + return; + } + AFL_VERIFY(WaitingQueue.erase(*client.GetLastPriority())); + Counters->QueueSize->Set(WaitingQueue.size()); +} + +void TManager::Free(const ui64 clientId, const ui32 count) { + auto it = Clients.find(clientId); + if (it == Clients.end()) { + Counters->FreeNoClient->Inc(); + return; + } + Counters->Free->Inc(); + AFL_VERIFY(it->second.GetCount() <= UsedCount); + AFL_VERIFY(count <= it->second.GetCount()); + it->second.MutableCount() -= count; + UsedCount -= count; + AllocateNext(); +} + +TManager::TClientStatus& TManager::GetClientVerified(const ui64 clientId) { + auto it = Clients.find(clientId); + AFL_VERIFY(it != Clients.end()); + return it->second; +} + +void TManager::Ask(const ui64 clientId, const ui32 count, const std::shared_ptr& request, const ui64 extPriority) { + AFL_VERIFY(request); + Counters->Ask->Inc(); + AskImpl(GetClientVerified(clientId), extPriority, TAskRequest(clientId, request, count)); +} + +void TManager::AskMax(const ui64 clientId, const ui32 count, const std::shared_ptr& request, const ui64 extPriority) { + AFL_VERIFY(request); + Counters->AskMax->Inc(); + auto& client = GetClientVerified(clientId); + if (client.GetLastPriority() && extPriority < client.GetLastPriority()->GetExternalPriority()) { + return; + } + AskImpl(client, extPriority, TAskRequest(clientId, request, count)); +} + +void TManager::AskImpl(TClientStatus& client, const ui64 extPriority, TAskRequest&& request) { + RemoveFromQueue(client); + AFL_VERIFY(request.GetSize() <= Config.GetLimit())("requested", request.GetSize())("limit", Config.GetLimit()); + TPriority priority(extPriority); + client.SetLastPriority(priority); + AFL_VERIFY(WaitingQueue.emplace(priority, std::move(request)).second); + AllocateNext(); +} + +void TManager::RegisterClient(const ui64 clientId) { + Counters->Register->Inc(); + AFL_VERIFY(Clients.emplace(clientId, clientId).second); + Counters->Clients->Set(Clients.size()); +} + +void TManager::UnregisterClient(const ui64 clientId) { + Counters->Unregister->Inc(); + auto it = Clients.find(clientId); + AFL_VERIFY(it != Clients.end()); + AFL_VERIFY(it->second.GetCount() <= UsedCount); + UsedCount -= it->second.GetCount(); + RemoveFromQueue(it->second); + Clients.erase(it); + AllocateNext(); + Counters->Clients->Set(Clients.size()); +} + +TManager::TManager(const std::shared_ptr& counters, const TConfig& config, const NActors::TActorId& serviceActorId) + : Counters(counters) + , Config(config) + , ServiceActorId(serviceActorId) { + AFL_VERIFY(Counters); +} + +} // namespace NKikimr::NPrioritiesQueue diff --git a/ydb/core/tx/priorities/service/manager.h b/ydb/core/tx/priorities/service/manager.h new file mode 100644 index 000000000000..6c37c61c2a8e --- /dev/null +++ b/ydb/core/tx/priorities/service/manager.h @@ -0,0 +1,91 @@ +#pragma once +#include "counters.h" + +#include +#include + +#include + +namespace NKikimr::NPrioritiesQueue { + +class TManager { +private: + std::shared_ptr Counters; + const TConfig Config; + const NActors::TActorId ServiceActorId; + + class TPriority { + private: + ui64 ExternalPriority; + static inline TAtomicCounter Counter = 0; + ui64 Sequence = Counter.Inc(); + + public: + TPriority(const ui64 priority) + : ExternalPriority(priority) { + } + + ui64 GetExternalPriority() const { + return ExternalPriority; + } + + bool operator<(const TPriority& item) const { + if (item.ExternalPriority < ExternalPriority) { + return true; + } else if (ExternalPriority < item.ExternalPriority) { + return false; + } else { + return item.Sequence < Sequence; + } + } + }; + + class TClientStatus: TNonCopyable { + private: + YDB_READONLY(ui64, ClientId, 0); + YDB_ACCESSOR(ui32, Count, 0); + YDB_ACCESSOR_DEF(std::optional, LastPriority); + + public: + TClientStatus(const ui64 clientId) + : ClientId(clientId) { + } + }; + + THashMap Clients; + + class TAskRequest { + private: + YDB_READONLY(ui64, ClientId, 0); + YDB_READONLY_DEF(std::shared_ptr, Request); + YDB_READONLY(ui32, Size, 0); + + public: + TAskRequest(const ui64 clientId, const std::shared_ptr& request, const ui32 size) + : ClientId(clientId) + , Request(request) + , Size(size) { + } + }; + + ui32 UsedCount = 0; + std::map WaitingQueue; + + void AllocateNext(); + + void RemoveFromQueue(const TClientStatus& client); + void AskImpl(TClientStatus& client, const ui64 extPriority, TAskRequest&& request); + TClientStatus& GetClientVerified(const ui64 clientId); + +public: + TManager(const std::shared_ptr& counters, const TConfig& config, const NActors::TActorId& serviceActorId); + + void Ask(const ui64 client, const ui32 count, const std::shared_ptr& request, const ui64 extPriority); + void AskMax(const ui64 client, const ui32 count, const std::shared_ptr& request, const ui64 extPriority); + void Free(const ui64 client, const ui32 count); + + void RegisterClient(const ui64 clientId); + void UnregisterClient(const ui64 clientId); +}; + +} // namespace NKikimr::NPrioritiesQueue diff --git a/ydb/core/tx/priorities/service/service.cpp b/ydb/core/tx/priorities/service/service.cpp new file mode 100644 index 000000000000..be41623e7f8c --- /dev/null +++ b/ydb/core/tx/priorities/service/service.cpp @@ -0,0 +1,19 @@ +#include "service.h" +#include +#include + +namespace NKikimr::NPrioritiesQueue { + +void TDistributor::Bootstrap() { + Counters->Limit->Set(Config.GetLimit()); + Manager = std::make_unique(Counters, Config, SelfId()); + Become(&TDistributor::StateMain); +} + +TDistributor::TDistributor(const TConfig& config, const TString& queueName, TIntrusivePtr<::NMonitoring::TDynamicCounters> baseSignals) + : Counters(std::make_shared(queueName, baseSignals)) + , QueueName(queueName) + , Config(config) { +} + +} diff --git a/ydb/core/tx/priorities/service/service.h b/ydb/core/tx/priorities/service/service.h new file mode 100644 index 000000000000..663f3519d971 --- /dev/null +++ b/ydb/core/tx/priorities/service/service.h @@ -0,0 +1,59 @@ +#pragma once +#include "counters.h" +#include "manager.h" + +#include + +#include +#include + +namespace NKikimr::NPrioritiesQueue { + +class TDistributor: public TActorBootstrapped { +private: + std::shared_ptr Counters; + const TString QueueName = "common"; + const TConfig Config; + std::unique_ptr Manager; + + void Handle(TEvExecution::TEvRegisterClient::TPtr& ev) { + Manager->RegisterClient(ev->Get()->GetClientId()); + } + + void Handle(TEvExecution::TEvUnregisterClient::TPtr& ev) { + Manager->UnregisterClient(ev->Get()->GetClientId()); + } + + void Handle(TEvExecution::TEvAsk::TPtr& ev) { + Manager->Ask(ev->Get()->GetClientId(), ev->Get()->GetCount(), ev->Get()->GetRequest(), ev->Get()->GetPriority()); + } + + void Handle(TEvExecution::TEvAskMax::TPtr& ev) { + Manager->AskMax(ev->Get()->GetClientId(), ev->Get()->GetCount(), ev->Get()->GetRequest(), ev->Get()->GetPriority()); + } + + void Handle(TEvExecution::TEvFree::TPtr& ev) { + Manager->Free(ev->Get()->GetClientId(), ev->Get()->GetCount()); + } + +public: + STATEFN(StateMain) { + NActors::TLogContextGuard lGuard = NActors::TLogContextBuilder::Build()("name", QueueName)("actor_id", SelfId()); + switch (ev->GetTypeRewrite()) { + hFunc(TEvExecution::TEvRegisterClient, Handle); + hFunc(TEvExecution::TEvUnregisterClient, Handle); + hFunc(TEvExecution::TEvAsk, Handle); + hFunc(TEvExecution::TEvAskMax, Handle); + hFunc(TEvExecution::TEvFree, Handle); + default: + AFL_ERROR(NKikimrServices::TX_PRIORITIES_QUEUE)("problem", "unexpected event for task executor")("ev_type", ev->GetTypeName()); + break; + } + } + + TDistributor(const TConfig& config, const TString& queueName, TIntrusivePtr<::NMonitoring::TDynamicCounters> baseSignals); + + void Bootstrap(); +}; + +} // namespace NKikimr::NPrioritiesQueue diff --git a/ydb/core/tx/priorities/service/ya.make b/ydb/core/tx/priorities/service/ya.make new file mode 100644 index 000000000000..172fd7e35eda --- /dev/null +++ b/ydb/core/tx/priorities/service/ya.make @@ -0,0 +1,14 @@ +LIBRARY() + +SRCS( + service.cpp + manager.cpp + counters.cpp +) + +PEERDIR( + ydb/core/tx/priorities/usage + ydb/core/protos +) + +END() diff --git a/ydb/core/tx/priorities/usage/abstract.cpp b/ydb/core/tx/priorities/usage/abstract.cpp new file mode 100644 index 000000000000..f62d66331ebd --- /dev/null +++ b/ydb/core/tx/priorities/usage/abstract.cpp @@ -0,0 +1,24 @@ +#include "abstract.h" +#include "events.h" + +#include +#include + +namespace NKikimr::NPrioritiesQueue { + +TAllocationGuard::~TAllocationGuard() { + if (!Released) { + Release(); + } +} + +void TAllocationGuard::Release() { + AFL_VERIFY(!Released); + if (TlsActivationContext) { + auto& context = NActors::TActorContext::AsActorContext(); + context.Send(ServiceActorId, new TEvExecution::TEvFree(ClientId, Count)); + } + Released = true; +} + +} // namespace NKikimr::NPrioritiesQueue diff --git a/ydb/core/tx/priorities/usage/abstract.h b/ydb/core/tx/priorities/usage/abstract.h new file mode 100644 index 000000000000..8d5a46e3884d --- /dev/null +++ b/ydb/core/tx/priorities/usage/abstract.h @@ -0,0 +1,37 @@ +#pragma once +#include + +namespace NKikimr::NPrioritiesQueue { + +class TAllocationGuard { +private: + const NActors::TActorId ServiceActorId; + const ui64 ClientId; + const ui32 Count; + bool Released = false; + +public: + TAllocationGuard(const NActors::TActorId& serviceActorId, const ui64 clientId, const ui32 count) + : ServiceActorId(serviceActorId) + , ClientId(clientId) + , Count(count) { + } + + ~TAllocationGuard(); + + void Release(); +}; + +class IRequest { +protected: + virtual void DoOnAllocated(const std::shared_ptr& guard) = 0; + +public: + virtual ~IRequest() = default; + + void OnAllocated(const std::shared_ptr& guard) { + return DoOnAllocated(guard); + } +}; + +} // namespace NKikimr::NPrioritiesQueue diff --git a/ydb/core/tx/priorities/usage/config.cpp b/ydb/core/tx/priorities/usage/config.cpp new file mode 100644 index 000000000000..6b2647ac2269 --- /dev/null +++ b/ydb/core/tx/priorities/usage/config.cpp @@ -0,0 +1,25 @@ +#include "config.h" +#include + +namespace NKikimr::NPrioritiesQueue { + +bool TConfig::DeserializeFromProto(const NKikimrConfig::TPrioritiesQueueConfig& config) { + if (!config.HasEnabled()) { + EnabledFlag = true; + } else { + EnabledFlag = config.GetEnabled(); + } + if (config.HasLimit()) { + Limit = config.GetLimit(); + } + return true; +} + +TString TConfig::DebugString() const { + TStringBuilder sb; + sb << "Limit=" << Limit << ";"; + sb << "Enabled=" << EnabledFlag << ";"; + return sb; +} + +} diff --git a/ydb/core/tx/priorities/usage/config.h b/ydb/core/tx/priorities/usage/config.h new file mode 100644 index 000000000000..b423ec08a24f --- /dev/null +++ b/ydb/core/tx/priorities/usage/config.h @@ -0,0 +1,16 @@ +#pragma once +#include +#include + +namespace NKikimr::NPrioritiesQueue { + +class TConfig { +private: + YDB_READONLY(ui32, Limit, 32); + YDB_READONLY_FLAG(Enabled, true); +public: + bool DeserializeFromProto(const NKikimrConfig::TPrioritiesQueueConfig& config); + TString DebugString() const; +}; + +} diff --git a/ydb/core/tx/priorities/usage/events.cpp b/ydb/core/tx/priorities/usage/events.cpp new file mode 100644 index 000000000000..f96de64e62f6 --- /dev/null +++ b/ydb/core/tx/priorities/usage/events.cpp @@ -0,0 +1,25 @@ +#include "events.h" + +#include + +namespace NKikimr::NPrioritiesQueue { + +TEvExecution::TEvAsk::TEvAsk(const ui64 clientId, const ui32 count, const std::shared_ptr& request, const ui64 priority) + : ClientId(clientId) + , Count(count) + , Request(request) + , Priority(priority) { + AFL_VERIFY(Request); + AFL_VERIFY(Count); +} + +TEvExecution::TEvAskMax::TEvAskMax(const ui64 clientId, const ui32 count, const std::shared_ptr& request, const ui64 priority) + : ClientId(clientId) + , Count(count) + , Request(request) + , Priority(priority) { + AFL_VERIFY(Request); + AFL_VERIFY(Count); +} + +} // namespace NKikimr::NPrioritiesQueue diff --git a/ydb/core/tx/priorities/usage/events.h b/ydb/core/tx/priorities/usage/events.h new file mode 100644 index 000000000000..47f3c4f973af --- /dev/null +++ b/ydb/core/tx/priorities/usage/events.h @@ -0,0 +1,93 @@ +#pragma once +#include "abstract.h" + +#include + +#include +#include +#include +#include + +namespace NKikimr::NPrioritiesQueue { + +struct TEvExecution { + enum EEv { + EvRegisterClient = EventSpaceBegin(TKikimrEvents::ES_PRIORITY_QUEUE), + EvUnregisterClient, + EvAsk, + EvAskMax, + EvFree, + EvAllocated, + EvEnd + }; + + static_assert(EvEnd < EventSpaceEnd(TKikimrEvents::ES_PRIORITY_QUEUE), "expected EvEnd < EventSpaceEnd"); + + class TEvRegisterClient: public NActors::TEventLocal { + private: + YDB_READONLY(ui64, ClientId, 0); + + public: + TEvRegisterClient(const ui64 clientId) + : ClientId(clientId) { + } + }; + + class TEvUnregisterClient: public NActors::TEventLocal { + private: + YDB_READONLY(ui64, ClientId, 0); + + public: + TEvUnregisterClient(const ui64 clientId) + : ClientId(clientId) { + } + }; + + class TEvAsk: public NActors::TEventLocal { + private: + YDB_READONLY(ui64, ClientId, 0); + YDB_READONLY(ui32, Count, 0); + YDB_READONLY_DEF(std::shared_ptr, Request); + YDB_READONLY(ui64, Priority, 0); + + public: + TEvAsk(const ui64 clientId, const ui32 count, const std::shared_ptr& request, const ui64 priority); + }; + + class TEvAskMax: public NActors::TEventLocal { + private: + YDB_READONLY(ui64, ClientId, 0); + YDB_READONLY(ui32, Count, 0); + YDB_READONLY_DEF(std::shared_ptr, Request); + YDB_READONLY(ui64, Priority, 0); + + public: + TEvAskMax(const ui64 clientId, const ui32 count, const std::shared_ptr& request, const ui64 priority); + }; + + class TEvFree: public NActors::TEventLocal { + private: + YDB_READONLY(ui64, ClientId, 0); + YDB_READONLY(ui32, Count, 0); + + public: + TEvFree(const ui64 clientId, const ui32 count) + : ClientId(clientId) + , Count(count) { + } + }; + + class TEvAllocated: public NActors::TEventLocal { + private: + YDB_READONLY(ui64, RequestId, 0); + YDB_READONLY_DEF(std::shared_ptr, Guard); + + public: + TEvAllocated(const ui32 requestId, const std::shared_ptr& guard) + : RequestId(requestId) + , Guard(guard) { + } + }; +}; + +} // namespace NKikimr::NPrioritiesQueue diff --git a/ydb/core/tx/priorities/usage/service.cpp b/ydb/core/tx/priorities/usage/service.cpp new file mode 100644 index 000000000000..9345e792cb0f --- /dev/null +++ b/ydb/core/tx/priorities/usage/service.cpp @@ -0,0 +1,5 @@ +#include "service.h" + +namespace NKikimr::NPrioritiesQueue { + +} diff --git a/ydb/core/tx/priorities/usage/service.h b/ydb/core/tx/priorities/usage/service.h new file mode 100644 index 000000000000..79149e918eba --- /dev/null +++ b/ydb/core/tx/priorities/usage/service.h @@ -0,0 +1,77 @@ +#pragma once +#include "config.h" +#include +#include +#include +#include + +namespace NKikimr::NPrioritiesQueue { + +template +class TServiceOperatorImpl { +private: + using TSelf = TServiceOperatorImpl; + std::atomic IsEnabledFlag = false; + static void Register(const TConfig& serviceConfig) { + Singleton()->IsEnabledFlag = serviceConfig.IsEnabled(); + } + static const TString& GetQueueName() { + Y_ABORT_UNLESS(TQueuePolicy::Name.size() == 4); + return TQueuePolicy::Name; + } +public: + [[nodiscard]] static ui64 RegisterClient() { + static TAtomicCounter Counter = 0; + const ui64 id = Counter.Inc(); + if (TSelf::IsEnabled()) { + auto& context = NActors::TActorContext::AsActorContext(); + context.Send(MakeServiceId(), new TEvExecution::TEvRegisterClient(id)); + } + return id; + } + static void UnregisterClient(const ui64 clientId) { + auto& context = NActors::TActorContext::AsActorContext(); + if (TSelf::IsEnabled()) { + context.Send(MakeServiceId(), new TEvExecution::TEvUnregisterClient(clientId)); + } + } + static void Ask(const ui64 clientId, const ui64 priority, const std::shared_ptr& request, const ui32 count = 1) { + AFL_VERIFY(request); + if (TSelf::IsEnabled()) { + NActors::TActorContext::AsActorContext().Send(MakeServiceId(), new TEvExecution::TEvAsk(clientId, count, request, priority)); + } else { + request->OnAllocated(std::make_shared(NActors::TActorId(), clientId, count)); + } + } + static void AskMax(const ui64 clientId, const ui64 priority, const std::shared_ptr& request, const ui32 count = 1) { + AFL_VERIFY(request); + if (TSelf::IsEnabled()) { + NActors::TActorContext::AsActorContext().Send(MakeServiceId(), new TEvExecution::TEvAskMax(clientId, count, request, priority)); + } else { + request->OnAllocated(std::make_shared(NActors::TActorId(), clientId, count)); + } + } + static bool IsEnabled() { + return Singleton()->IsEnabledFlag; + } + static NActors::TActorId MakeServiceId() { + return NActors::TActorId(NActors::TActorContext::AsActorContext().SelfID.NodeId(), "SrvcPrqe" + GetQueueName()); + } + static NActors::TActorId MakeServiceId(const ui64 nodeId) { + return NActors::TActorId(nodeId, "SrvcPrqe" + GetQueueName()); + } + static NActors::IActor* CreateService(const TConfig& config, TIntrusivePtr<::NMonitoring::TDynamicCounters> queueSignals) { + Register(config); + return new TDistributor(config, GetQueueName(), queueSignals); + } + +}; + +class TCompConveyorPolicy { +public: + static const inline TString Name = "Comp"; +}; + +using TCompServiceOperator = TServiceOperatorImpl; + +} diff --git a/ydb/core/tx/priorities/usage/ya.make b/ydb/core/tx/priorities/usage/ya.make new file mode 100644 index 000000000000..ba7690de7307 --- /dev/null +++ b/ydb/core/tx/priorities/usage/ya.make @@ -0,0 +1,15 @@ +LIBRARY() + +SRCS( + abstract.cpp + events.cpp + config.cpp + service.cpp +) + +PEERDIR( + ydb/library/actors/core + ydb/core/protos +) + +END() diff --git a/ydb/core/tx/program/program.h b/ydb/core/tx/program/program.h index 3ab18eccc9d1..41cd2a06db1b 100644 --- a/ydb/core/tx/program/program.h +++ b/ydb/core/tx/program/program.h @@ -85,6 +85,11 @@ class TProgramContainer { } } + const std::vector>& GetStepsVerified() const { + AFL_VERIFY(!!Program); + return Program->Steps; + } + template inline arrow::Status ApplyProgram(std::shared_ptr& batch) const { if (Program) { diff --git a/ydb/core/tx/replication/controller/secret_resolver.cpp b/ydb/core/tx/replication/controller/secret_resolver.cpp index cbc289ec9fb4..03c2d603772b 100644 --- a/ydb/core/tx/replication/controller/secret_resolver.cpp +++ b/ydb/core/tx/replication/controller/secret_resolver.cpp @@ -47,12 +47,12 @@ class TSecretResolver: public TActorBootstrapped { void Handle(NMetadata::NProvider::TEvRefreshSubscriberData::TPtr& ev) { const auto* snapshot = ev->Get()->GetSnapshotAs(); - TString secretValue; - if (!snapshot->GetSecretValue(NMetadata::NSecret::TSecretIdOrValue::BuildAsId(SecretId), secretValue)) { - return Reply(false, TStringBuilder() << "Secret '" << SecretName << "' not found"); + auto secretValue = snapshot->GetSecretValue(NMetadata::NSecret::TSecretIdOrValue::BuildAsId(SecretId)); + if (secretValue.IsFail()) { + return Reply(false, secretValue.GetErrorMessage()); } - Reply(secretValue); + Reply(secretValue.DetachResult()); } template diff --git a/ydb/core/tx/schemeshard/common/validation.cpp b/ydb/core/tx/schemeshard/common/validation.cpp index c05a3c6902db..11216965c91c 100644 --- a/ydb/core/tx/schemeshard/common/validation.cpp +++ b/ydb/core/tx/schemeshard/common/validation.cpp @@ -1,5 +1,12 @@ #include "validation.h" +#include +#include + +extern "C" { +#include +} + namespace NKikimr::NSchemeShard::NValidation { bool TTTLValidator::ValidateUnit(const NScheme::TTypeId columnType, NKikimrSchemeOp::TTTLSettings::EUnit unit, TString& errStr) { @@ -30,4 +37,34 @@ bool TTTLValidator::ValidateUnit(const NScheme::TTypeId columnType, NKikimrSchem return true; } -} \ No newline at end of file +bool TTTLValidator::ValidateTiers(const google::protobuf::RepeatedPtrField& tiers, TString& errStr) { + for (i64 i = 0; i < tiers.size(); ++i) { + const auto& tier = tiers[i]; + if (!tier.HasApplyAfterSeconds()) { + errStr = TStringBuilder() << "Tier " << i << ": missing ApplyAfterSeconds"; + return false; + } + if (i != 0 && tier.GetApplyAfterSeconds() <= tiers[i - 1].GetApplyAfterSeconds()) { + errStr = TStringBuilder() << "Tiers in the sequence must have increasing ApplyAfterSeconds: " + << tiers[i - 1].GetApplyAfterSeconds() << " (tier " << i - 1 + << ") >= " << tier.GetApplyAfterSeconds() << " (tier " << i << ")"; + return false; + } + switch (tier.GetActionCase()) { + case NKikimrSchemeOp::TTTLSettings_TTier::kDelete: + if (i + 1 != tiers.size()) { + errStr = TStringBuilder() << "Tier " << i << ": only the last tier in TTL settings can have Delete action"; + return false; + } + break; + case NKikimrSchemeOp::TTTLSettings_TTier::kEvictToExternalStorage: + break; + case NKikimrSchemeOp::TTTLSettings_TTier::ACTION_NOT_SET: + errStr = TStringBuilder() << "Tier " << i << ": missing Action"; + return false; + } + } + return true; +} + +} diff --git a/ydb/core/tx/schemeshard/common/validation.h b/ydb/core/tx/schemeshard/common/validation.h index 12d1e801e2ad..5e27e07e1343 100644 --- a/ydb/core/tx/schemeshard/common/validation.h +++ b/ydb/core/tx/schemeshard/common/validation.h @@ -8,6 +8,17 @@ namespace NKikimr::NSchemeShard::NValidation { class TTTLValidator { public: +// <<<<<<< HEAD +// static bool ValidateUnit(const NScheme::TTypeId columnType, NKikimrSchemeOp::TTTLSettings::EUnit unit, TString& errStr); +// static bool ValidateTiers(const NKikimrSchemeOp::TTTLSettings::TEnabled ttlSettings, TString& errStr); +// ======= +// static bool ValidateUnit(const NScheme::TTypeInfo columnType, NKikimrSchemeOp::TTTLSettings::EUnit unit, TString& errStr); +// static bool ValidateTiers(const google::protobuf::RepeatedPtrField& tiers, TString& errStr); + +// private: +// >>>>>>> b2da93a482 (configure tiering on CS via ttl (#12095)) static bool ValidateUnit(const NScheme::TTypeId columnType, NKikimrSchemeOp::TTTLSettings::EUnit unit, TString& errStr); + static bool ValidateTiers(const google::protobuf::RepeatedPtrField& tiers, TString& errStr); + }; -} \ No newline at end of file +} diff --git a/ydb/core/tx/schemeshard/olap/column_families/schema.cpp b/ydb/core/tx/schemeshard/olap/column_families/schema.cpp new file mode 100644 index 000000000000..7667e984d532 --- /dev/null +++ b/ydb/core/tx/schemeshard/olap/column_families/schema.cpp @@ -0,0 +1,152 @@ +#include "schema.h" + +#include + +namespace NKikimr::NSchemeShard { + +void TOlapColumnFamily::Serialize(NKikimrSchemeOp::TFamilyDescription& columnFamily) const { + columnFamily.SetId(Id); + TBase::Serialize(columnFamily); +} + +void TOlapColumnFamily::ParseFromLocalDB(const NKikimrSchemeOp::TFamilyDescription& columnFamily) { + Id = columnFamily.GetId(); + TBase::ParseFromLocalDB(columnFamily); +} + +const TOlapColumnFamily* TOlapColumnFamiliesDescription::GetById(const ui32 id) const noexcept { + auto it = ColumnFamilies.find(id); + if (it == ColumnFamilies.end()) { + return nullptr; + } + return &it->second; +} + +const TOlapColumnFamily* TOlapColumnFamiliesDescription::GetByIdVerified(const ui32 id) const noexcept { + return TValidator::CheckNotNull(GetById(id)); +} + +const TOlapColumnFamily* TOlapColumnFamiliesDescription::GetByName(const TString& name) const noexcept { + auto it = ColumnFamiliesByName.find(name); + if (it.IsEnd()) { + return nullptr; + } + return GetByIdVerified(it->second); +} + +bool TOlapColumnFamiliesDescription::ApplyUpdate( + const TOlapColumnFamiliesUpdate& schemaUpdate, IErrorCollector& errors, ui32& NextColumnFamilyId) { + for (auto&& family : schemaUpdate.GetAddColumnFamilies()) { + auto familyName = family.GetName(); + if (ColumnFamiliesByName.contains(familyName)) { + errors.AddError(NKikimrScheme::StatusAlreadyExists, TStringBuilder() << "column family '" << familyName << "' already exists"); + return false; + } + ui32 index = 0; + if (familyName != "default") { + index = NextColumnFamilyId++; + } + TOlapColumnFamily columFamilyAdd(family, index); + Y_ABORT_UNLESS(ColumnFamilies.emplace(columFamilyAdd.GetId(), columFamilyAdd).second); + Y_ABORT_UNLESS(ColumnFamiliesByName.emplace(columFamilyAdd.GetName(), columFamilyAdd.GetId()).second); + } + + for (auto&& family : schemaUpdate.GetAlterColumnFamily()) { + auto familyName = family.GetName(); + auto it = ColumnFamiliesByName.find(familyName); + if (it.IsEnd()) { + errors.AddError( + NKikimrScheme::StatusSchemeError, TStringBuilder() << "column family '" << familyName << "' not exists for altering"); + return false; + } else { + auto itColumnFamily = ColumnFamilies.find(it->second); + Y_ABORT_UNLESS(itColumnFamily != ColumnFamilies.end()); + Y_ABORT_UNLESS(AlterColumnFamiliesId.insert(it->second).second); + TOlapColumnFamily& alterColumnFamily = itColumnFamily->second; + if (!alterColumnFamily.ApplyDiff(family, errors)) { + return false; + } + } + } + return true; +} + +bool TOlapColumnFamiliesDescription::Parse(const NKikimrSchemeOp::TColumnTableSchema& tableSchema) { + for (const auto& family : tableSchema.GetColumnFamilies()) { + TOlapColumnFamily columFamily; + columFamily.ParseFromLocalDB(family); + Y_ABORT_UNLESS(ColumnFamilies.emplace(columFamily.GetId(), columFamily).second); + Y_ABORT_UNLESS(ColumnFamiliesByName.emplace(columFamily.GetName(), columFamily.GetId()).second); + } + return true; +} + +void TOlapColumnFamiliesDescription::Serialize(NKikimrSchemeOp::TColumnTableSchema& tableSchema) const { + for (const auto& [_, family] : ColumnFamilies) { + family.Serialize(*tableSchema.AddColumnFamilies()); + } +} + +bool TOlapColumnFamiliesDescription::Validate(const NKikimrSchemeOp::TColumnTableSchema& opSchema, IErrorCollector& errors) const { + ui32 lastColumnFamilyId = 0; + THashSet usedColumnFamilies; + for (const auto& familyProto : opSchema.GetColumnFamilies()) { + if (familyProto.GetName().empty()) { + errors.AddError("column family can't have an empty name"); + return false; + } + + const TString& columnFamilyName = familyProto.GetName(); + auto* family = GetByName(columnFamilyName); + if (!family) { + errors.AddError("column family '" + columnFamilyName + "' does not match schema preset"); + return false; + } + + if (familyProto.HasId() && familyProto.GetId() != family->GetId()) { + errors.AddError("column family '" + columnFamilyName + "' has id " + familyProto.GetId() + " that does not match schema preset"); + return false; + } + + if (!usedColumnFamilies.insert(family->GetId()).second) { + errors.AddError("column family '" + columnFamilyName + "' is specified multiple times"); + return false; + } + + if (familyProto.GetId() < lastColumnFamilyId) { + errors.AddError("column family order does not match schema preset"); + return false; + } + lastColumnFamilyId = familyProto.GetId(); + + if (!familyProto.HasColumnCodec()) { + errors.AddError("missing column codec for column family '" + columnFamilyName + "'"); + return false; + } + + auto serializerProto = ConvertFamilyDescriptionToProtoSerializer(familyProto); + if (serializerProto.IsFail()) { + errors.AddError(serializerProto.GetErrorMessage()); + return false; + } + NArrow::NSerialization::TSerializerContainer serializer; + if (!serializer.DeserializeFromProto(serializerProto.GetResult())) { + errors.AddError(TStringBuilder() << "can't deserialize column family `" << columnFamilyName << "` from proto "); + return false; + } + if (!family->GetSerializerContainer().IsEqualTo(serializer)) { + errors.AddError(TStringBuilder() << "compression from column family '" << columnFamilyName << "` is not matching schema preset"); + return false; + } + } + + for (const auto& [_, family] : ColumnFamilies) { + if (!usedColumnFamilies.contains(family.GetId())) { + errors.AddError("specified schema is missing some schema preset column families"); + return false; + } + } + + return true; +} +} diff --git a/ydb/core/tx/schemeshard/olap/column_families/schema.h b/ydb/core/tx/schemeshard/olap/column_families/schema.h new file mode 100644 index 000000000000..046321f8a2e9 --- /dev/null +++ b/ydb/core/tx/schemeshard/olap/column_families/schema.h @@ -0,0 +1,44 @@ +#pragma once +#include "update.h" + +namespace NKikimr::NSchemeShard { + +class TOlapColumnFamily: public TOlapColumnFamlilyAdd { +private: + using TBase = TOlapColumnFamlilyAdd; + YDB_READONLY(ui32, Id, Max()); + +public: + TOlapColumnFamily() = default; + TOlapColumnFamily(const TOlapColumnFamlilyAdd& base, ui32 Id) + : TBase(base) + , Id(Id) + { + } + + void Serialize(NKikimrSchemeOp::TFamilyDescription& columnFamily) const; + void ParseFromLocalDB(const NKikimrSchemeOp::TFamilyDescription& columnFamily); +}; + +class TOlapColumnFamiliesDescription { +public: +private: + using TOlapColumnFamilies = TMap; + using TOlapColumnFamiliesByName = THashMap; + + YDB_READONLY_DEF(TOlapColumnFamilies, ColumnFamilies); + YDB_READONLY_DEF(TOlapColumnFamiliesByName, ColumnFamiliesByName); + YDB_READONLY_DEF(THashSet, AlterColumnFamiliesId); + +public: + const TOlapColumnFamily* GetById(const ui32 id) const noexcept; + const TOlapColumnFamily* GetByIdVerified(const ui32 id) const noexcept; + const TOlapColumnFamily* GetByName(const TString& name) const noexcept; + + bool ApplyUpdate(const TOlapColumnFamiliesUpdate& schemaUpdate, IErrorCollector& errors, ui32& NextColumnFamilyId); + + bool Parse(const NKikimrSchemeOp::TColumnTableSchema& tableSchema); + void Serialize(NKikimrSchemeOp::TColumnTableSchema& tableSchema) const; + bool Validate(const NKikimrSchemeOp::TColumnTableSchema& opSchema, IErrorCollector& errors) const; +}; +} diff --git a/ydb/core/tx/schemeshard/olap/column_families/update.cpp b/ydb/core/tx/schemeshard/olap/column_families/update.cpp new file mode 100644 index 000000000000..9460f8f1be0d --- /dev/null +++ b/ydb/core/tx/schemeshard/olap/column_families/update.cpp @@ -0,0 +1,196 @@ +#include "update.h" + +#include +#include +#include + +namespace NKikimr::NSchemeShard { + +NKikimr::TConclusion ConvertFamilyDescriptionToProtoSerializer( + const NKikimrSchemeOp::TFamilyDescription& familyDescription) { + NKikimrSchemeOp::TOlapColumn::TSerializer result; + if (!familyDescription.HasColumnCodec()) { + return NKikimr::TConclusionStatus::Fail(TStringBuilder() + << "family `" << familyDescription.GetName() + << "`: can't convert TFamilyDescription to Serializer: field `ColumnCodec` is empty"); + } + auto codec = NArrow::CompressionFromProto(familyDescription.GetColumnCodec()); + if (!codec.has_value()) { + return NKikimr::TConclusionStatus::Fail(TStringBuilder() << "family `" << familyDescription.GetName() << "`: unknown codec"); + } + if (familyDescription.HasColumnCodecLevel() && !NArrow::SupportsCompressionLevel(codec.value())) { + return NKikimr::TConclusionStatus::Fail(TStringBuilder() << "family `" << familyDescription.GetName() << "`: codec `" + << NArrow::CompressionToString(familyDescription.GetColumnCodec()) + << "` is not support compression level"); + } + if (familyDescription.HasColumnCodecLevel()) { + int level = familyDescription.GetColumnCodecLevel(); + int minLevel = NArrow::MinimumCompressionLevel(codec.value()).value(); + int maxLevel = NArrow::MaximumCompressionLevel(codec.value()).value(); + if (level < minLevel || level > maxLevel) { + return NKikimr::TConclusionStatus::Fail(TStringBuilder() + << "family `" << familyDescription.GetName() << "`: incorrect level for codec `" + << NArrow::CompressionToString(familyDescription.GetColumnCodec()) << "`. expected: [" + << minLevel << ":" << maxLevel << "]"); + } + } + + result.SetClassName("ARROW_SERIALIZER"); + auto arrowCompression = result.MutableArrowCompression(); + arrowCompression->SetCodec(familyDescription.GetColumnCodec()); + if (familyDescription.HasColumnCodecLevel()) { + arrowCompression->SetLevel(familyDescription.GetColumnCodecLevel()); + } + return result; +} + +NKikimr::TConclusion ConvertSerializerContainerToFamilyDescription( + const NArrow::NSerialization::TSerializerContainer& serializer) { + NKikimrSchemeOp::TFamilyDescription result; + if (serializer->GetClassName().empty()) { + return NKikimr::TConclusionStatus::Fail("convert TSerializerContainer to TFamilyDescription: field `ClassName` is empty"); + } + if (serializer.GetClassName() == NArrow::NSerialization::TNativeSerializer::GetClassNameStatic()) { + std::shared_ptr nativeSerializer = + serializer.GetObjectPtrVerifiedAs(); + result.SetColumnCodec(NKikimr::NArrow::CompressionToProto(nativeSerializer->GetCodecType())); + auto level = nativeSerializer->GetCodecLevel(); + if (level.has_value()) { + result.SetColumnCodecLevel(level.value()); + } + } else { + return NKikimr::TConclusionStatus::Fail("convert TSerializerContainer to TFamilyDescription: Unknown value in field `ClassName`"); + } + return result; +} + +bool TOlapColumnFamlilyDiff::ParseFromRequest(const NKikimrSchemeOp::TFamilyDescription& diffColumnFamily, IErrorCollector& errors) { + if (!diffColumnFamily.HasName()) { + errors.AddError("column family: empty field name"); + return false; + } + + Name = diffColumnFamily.GetName(); + if (diffColumnFamily.HasColumnCodec()) { + Codec = diffColumnFamily.GetColumnCodec(); + } + if (diffColumnFamily.HasColumnCodecLevel()) { + CodecLevel = diffColumnFamily.GetColumnCodecLevel(); + } + return true; +} + +bool TOlapColumnFamlilyAdd::ParseFromRequest(const NKikimrSchemeOp::TFamilyDescription& columnFamily, IErrorCollector& errors) { + if (!columnFamily.HasName()) { + errors.AddError("column family: empty field Name"); + return false; + } + + Name = columnFamily.GetName(); + auto serializer = ConvertFamilyDescriptionToProtoSerializer(columnFamily); + if (serializer.IsFail()) { + errors.AddError(serializer.GetErrorMessage()); + return false; + } + auto resultBuild = NArrow::NSerialization::TSerializerContainer::BuildFromProto(serializer.GetResult()); + if (resultBuild.IsFail()) { + errors.AddError(resultBuild.GetErrorMessage()); + return false; + } + SerializerContainer = resultBuild.GetResult(); + return true; +} + +void TOlapColumnFamlilyAdd::ParseFromLocalDB(const NKikimrSchemeOp::TFamilyDescription& columnFamily) { + Name = columnFamily.GetName(); + auto serializer = ConvertFamilyDescriptionToProtoSerializer(columnFamily); + Y_VERIFY_S(serializer.IsSuccess(), serializer.GetErrorMessage()); + Y_VERIFY(SerializerContainer.DeserializeFromProto(serializer.GetResult())); +} + +void TOlapColumnFamlilyAdd::Serialize(NKikimrSchemeOp::TFamilyDescription& columnFamily) const { + auto result = ConvertSerializerContainerToFamilyDescription(SerializerContainer); + Y_VERIFY_S(result.IsSuccess(), result.GetErrorMessage()); + columnFamily.SetName(Name); + columnFamily.SetColumnCodec(result->GetColumnCodec()); + if (result->HasColumnCodecLevel()) { + columnFamily.SetColumnCodecLevel(result->GetColumnCodecLevel()); + } +} + +bool TOlapColumnFamlilyAdd::ApplyDiff(const TOlapColumnFamlilyDiff& diffColumnFamily, IErrorCollector& errors) { + Y_ABORT_UNLESS(GetName() == diffColumnFamily.GetName()); + auto newColumnFamily = ConvertSerializerContainerToFamilyDescription(SerializerContainer); + if (newColumnFamily.IsFail()) { + errors.AddError(newColumnFamily.GetErrorMessage()); + return false; + } + newColumnFamily->SetName(GetName()); + auto codec = diffColumnFamily.GetCodec(); + if (codec.has_value()) { + newColumnFamily->SetColumnCodec(codec.value()); + newColumnFamily->ClearColumnCodecLevel(); + } + auto codecLevel = diffColumnFamily.GetCodecLevel(); + if (codecLevel.has_value()) { + newColumnFamily->SetColumnCodecLevel(codecLevel.value()); + } + auto serializer = ConvertFamilyDescriptionToProtoSerializer(newColumnFamily.GetResult()); + if (serializer.IsFail()) { + errors.AddError(serializer.GetErrorMessage()); + return false; + } + auto resultBuild = NArrow::NSerialization::TSerializerContainer::BuildFromProto(serializer.GetResult()); + if (resultBuild.IsFail()) { + errors.AddError(resultBuild.GetErrorMessage()); + return false; + } + SerializerContainer = resultBuild.GetResult(); + return true; +} + +bool TOlapColumnFamiliesUpdate::Parse(const NKikimrSchemeOp::TColumnTableSchema& tableSchema, IErrorCollector& errors) { + TSet familyNames; + for (auto&& family : tableSchema.GetColumnFamilies()) { + auto familyName = family.GetName(); + if (!familyNames.emplace(familyName).second) { + errors.AddError(NKikimrScheme::StatusSchemeError, TStringBuilder() << "duplicate column family '" << familyName << "'"); + return false; + } + TOlapColumnFamlilyAdd columnFamily; + if (!columnFamily.ParseFromRequest(family, errors)) { + return false; + } + familyNames.insert(familyName); + AddColumnFamilies.emplace_back(columnFamily); + } + + return true; +} + +bool TOlapColumnFamiliesUpdate::Parse(const NKikimrSchemeOp::TAlterColumnTableSchema& alterRequest, IErrorCollector& errors) { + TSet addColumnFamilies; + for (auto&& family : alterRequest.GetAddColumnFamily()) { + auto familyName = family.GetName(); + if (!addColumnFamilies.emplace(familyName).second) { + errors.AddError(NKikimrScheme::StatusSchemeError, TStringBuilder() << "duplicate column family '" << familyName << "'"); + return false; + } + TOlapColumnFamlilyAdd columnFamily({}); + if (!columnFamily.ParseFromRequest(family, errors)) { + return false; + } + addColumnFamilies.insert(familyName); + AddColumnFamilies.emplace_back(columnFamily); + } + + for (auto&& family : alterRequest.GetAlterColumnFamily()) { + TOlapColumnFamlilyDiff columnFamily({}); + if (!columnFamily.ParseFromRequest(family, errors)) { + return false; + } + AlterColumnFamily.emplace_back(columnFamily); + } + return true; +} +} diff --git a/ydb/core/tx/schemeshard/olap/column_families/update.h b/ydb/core/tx/schemeshard/olap/column_families/update.h new file mode 100644 index 000000000000..63c0e53af16d --- /dev/null +++ b/ydb/core/tx/schemeshard/olap/column_families/update.h @@ -0,0 +1,45 @@ +#pragma once +#include +#include +#include + +#include + +namespace NKikimr::NSchemeShard { + +[[nodiscard]] NKikimr::TConclusion ConvertFamilyDescriptionToProtoSerializer( + const NKikimrSchemeOp::TFamilyDescription& familyDescription); + +class TOlapColumnFamlilyDiff { +private: + YDB_ACCESSOR_DEF(TString, Name); + YDB_ACCESSOR_DEF(std::optional, Codec); + YDB_ACCESSOR_DEF(std::optional, CodecLevel); + +public: + bool ParseFromRequest(const NKikimrSchemeOp::TFamilyDescription& diffColumnFamily, IErrorCollector& errors); +}; + +class TOlapColumnFamlilyAdd { +private: + YDB_READONLY_DEF(TString, Name); + YDB_READONLY_DEF(NArrow::NSerialization::TSerializerContainer, SerializerContainer); + +public: + bool ParseFromRequest(const NKikimrSchemeOp::TFamilyDescription& columnFamily, IErrorCollector& errors); + void ParseFromLocalDB(const NKikimrSchemeOp::TFamilyDescription& columnFamily); + void Serialize(NKikimrSchemeOp::TFamilyDescription& columnSchema) const; + bool ApplyDiff(const TOlapColumnFamlilyDiff& diffColumn, IErrorCollector& errors); +}; + +class TOlapColumnFamiliesUpdate { +private: + YDB_READONLY_DEF(TVector, AddColumnFamilies); + YDB_READONLY_DEF(TVector, AlterColumnFamily); + +public: + bool Parse(const NKikimrSchemeOp::TColumnTableSchema& tableSchema, IErrorCollector& errors); + bool Parse(const NKikimrSchemeOp::TAlterColumnTableSchema& alterRequest, IErrorCollector& errors); +}; + +} diff --git a/ydb/core/tx/schemeshard/olap/column_families/ya.make b/ydb/core/tx/schemeshard/olap/column_families/ya.make new file mode 100644 index 000000000000..75f48026bdfc --- /dev/null +++ b/ydb/core/tx/schemeshard/olap/column_families/ya.make @@ -0,0 +1,17 @@ +LIBRARY() + +SRCS( + update.cpp + schema.cpp +) + +PEERDIR( + ydb/core/protos + ydb/core/formats/arrow/dictionary + ydb/core/formats/arrow/serializer + ydb/core/tx/schemeshard/olap/common +) + +YQL_LAST_ABI_VERSION() + +END() diff --git a/ydb/core/tx/schemeshard/olap/columns/schema.cpp b/ydb/core/tx/schemeshard/olap/columns/schema.cpp index 959ae5de4264..1f03bc8860ea 100644 --- a/ydb/core/tx/schemeshard/olap/columns/schema.cpp +++ b/ydb/core/tx/schemeshard/olap/columns/schema.cpp @@ -15,7 +15,8 @@ void TOlapColumnSchema::ParseFromLocalDB(const NKikimrSchemeOp::TOlapColumnDescr Id = columnSchema.GetId(); } -bool TOlapColumnsDescription::ApplyUpdate(const TOlapColumnsUpdate& schemaUpdate, IErrorCollector& errors, ui32& nextEntityId) { +bool TOlapColumnsDescription::ApplyUpdate( + const TOlapColumnsUpdate& schemaUpdate, const TOlapColumnFamiliesDescription& columnFamilies, IErrorCollector& errors, ui32& nextEntityId) { if (Columns.empty() && schemaUpdate.GetAddColumns().empty()) { errors.AddError(NKikimrScheme::StatusSchemeError, "No add columns specified"); return false; @@ -38,10 +39,27 @@ bool TOlapColumnsDescription::ApplyUpdate(const TOlapColumnsUpdate& schemaUpdate return false; } } - TOlapColumnSchema newColumn(column, nextEntityId++); + std::optional columnFamilyId; + if (column.GetColumnFamilyName().has_value()) { + TString familyName = column.GetColumnFamilyName().value(); + const TOlapColumnFamily* columnFamily = columnFamilies.GetByName(familyName); + + if (!columnFamily) { + errors.AddError(NKikimrScheme::StatusSchemeError, TStringBuilder() + << "Cannot set column family `" << familyName << "` for column `" + << column.GetName() << "`. Family not found"); + return false; + } + columnFamilyId = columnFamily->GetId(); + } + TOlapColumnSchema newColumn(column, nextEntityId++, columnFamilyId); if (newColumn.GetKeyOrder()) { Y_ABORT_UNLESS(orderedKeyColumnIds.emplace(*newColumn.GetKeyOrder(), newColumn.GetId()).second); } + if (!newColumn.GetSerializer().has_value() && !columnFamilies.GetColumnFamilies().empty() && + !newColumn.ApplySerializerFromColumnFamily(columnFamilies, errors)) { + return false; + } Y_ABORT_UNLESS(ColumnsByName.emplace(newColumn.GetName(), newColumn.GetId()).second); Y_ABORT_UNLESS(Columns.emplace(newColumn.GetId(), std::move(newColumn)).second); @@ -56,7 +74,7 @@ bool TOlapColumnsDescription::ApplyUpdate(const TOlapColumnsUpdate& schemaUpdate auto itColumn = Columns.find(it->second); Y_ABORT_UNLESS(itColumn != Columns.end()); TOlapColumnSchema& newColumn = itColumn->second; - if (!newColumn.ApplyDiff(columnDiff, errors)) { + if (!newColumn.ApplyDiff(columnDiff, columnFamilies, errors)) { return false; } } @@ -89,6 +107,21 @@ bool TOlapColumnsDescription::ApplyUpdate(const TOlapColumnsUpdate& schemaUpdate Columns.erase(columnInfo->GetId()); } + auto alterColumnFamiliesId = columnFamilies.GetAlterColumnFamiliesId(); + if (!alterColumnFamiliesId.empty()) { + for (auto& [_, column] : Columns) { + if (!column.GetColumnFamilyId().has_value()) { + errors.AddError(NKikimrScheme::StatusSchemeError, + TStringBuilder() << "Cannot alter family for column `" << column.GetName() << "`. Column family is not set"); + return false; + } + ui32 id = column.GetColumnFamilyId().value(); + if (alterColumnFamiliesId.contains(id)) { + column.SetSerializer(columnFamilies.GetByIdVerified(id)->GetSerializerContainer()); + } + } + } + return true; } @@ -153,6 +186,12 @@ bool TOlapColumnsDescription::Validate(const NKikimrSchemeOp::TColumnTableSchema return false; } + if (colProto.HasColumnFamilyId() && colProto.GetColumnFamilyId() != col->GetColumnFamilyId()) { + errors.AddError(TStringBuilder() << "Column '" << colName << "' has column family id " << colProto.GetColumnFamilyId() + << " that does not match schema preset"); + return false; + } + if (!usedColumns.insert(col->GetId()).second) { errors.AddError("Column '" + colName + "' is specified multiple times"); return false; diff --git a/ydb/core/tx/schemeshard/olap/columns/schema.h b/ydb/core/tx/schemeshard/olap/columns/schema.h index 0abec2776918..c8604d55e997 100644 --- a/ydb/core/tx/schemeshard/olap/columns/schema.h +++ b/ydb/core/tx/schemeshard/olap/columns/schema.h @@ -3,16 +3,18 @@ namespace NKikimr::NSchemeShard { -class TOlapColumnSchema: public TOlapColumnAdd { +class TOlapColumnSchema: public TOlapColumnBase { private: - using TBase = TOlapColumnAdd; + using TBase = TOlapColumnBase; YDB_READONLY(ui32, Id, Max()); public: - TOlapColumnSchema(const TOlapColumnAdd& base, const ui32 id) + TOlapColumnSchema(const TOlapColumnBase& base, const ui32 id, const std::optional columnFamilyId = {}) : TBase(base) - , Id(id) { - + , Id(id) + { + ColumnFamilyId = columnFamilyId; } + TOlapColumnSchema(const std::optional& keyOrder) : TBase(keyOrder) { @@ -51,7 +53,8 @@ class TOlapColumnsDescription { const TOlapColumnSchema* GetByIdVerified(const ui32 id) const noexcept; - bool ApplyUpdate(const TOlapColumnsUpdate& schemaUpdate, IErrorCollector& errors, ui32& nextEntityId); + bool ApplyUpdate(const TOlapColumnsUpdate& schemaUpdate, const TOlapColumnFamiliesDescription& columnFamilies, IErrorCollector& errors, + ui32& nextEntityId); void Parse(const NKikimrSchemeOp::TColumnTableSchema& tableSchema); void Serialize(NKikimrSchemeOp::TColumnTableSchema& tableSchema) const; diff --git a/ydb/core/tx/schemeshard/olap/columns/update.cpp b/ydb/core/tx/schemeshard/olap/columns/update.cpp index c66da237c712..628859956289 100644 --- a/ydb/core/tx/schemeshard/olap/columns/update.cpp +++ b/ydb/core/tx/schemeshard/olap/columns/update.cpp @@ -11,43 +11,82 @@ extern "C" { namespace NKikimr::NSchemeShard { - bool TOlapColumnAdd::ParseFromRequest(const NKikimrSchemeOp::TOlapColumnDescription& columnSchema, IErrorCollector& errors) { - if (!columnSchema.GetName()) { - errors.AddError("Columns cannot have an empty name"); - return false; - } - Name = columnSchema.GetName(); - NotNullFlag = columnSchema.GetNotNull(); - TypeName = columnSchema.GetType(); +bool TOlapColumnDiff::ParseFromRequest(const NKikimrSchemeOp::TOlapColumnDiff& columnSchema, IErrorCollector& errors) { + Name = columnSchema.GetName(); + if (!!columnSchema.GetStorageId()) { StorageId = columnSchema.GetStorageId(); - if (columnSchema.HasSerializer()) { - NArrow::NSerialization::TSerializerContainer serializer; - if (!serializer.DeserializeFromProto(columnSchema.GetSerializer())) { - errors.AddError("Cannot parse serializer info"); - return false; - } - Serializer = serializer; - } - if (columnSchema.HasDictionaryEncoding()) { - auto settings = NArrow::NDictionary::TEncodingSettings::BuildFromProto(columnSchema.GetDictionaryEncoding()); - if (!settings) { - errors.AddError("Cannot parse dictionary compression info: " + settings.GetErrorMessage()); - return false; - } - DictionaryEncoding = *settings; + } + if (!Name) { + errors.AddError("empty field name"); + return false; + } + if (columnSchema.HasDefaultValue()) { + DefaultValue = columnSchema.GetDefaultValue(); + } + if (columnSchema.HasDataAccessorConstructor()) { + if (!AccessorConstructor.DeserializeFromProto(columnSchema.GetDataAccessorConstructor())) { + errors.AddError("cannot parse accessor constructor from proto"); + return false; } + } - if (columnSchema.HasTypeId()) { - errors.AddError(TStringBuilder() << "Cannot set TypeId for column '" << Name << ", use Type"); + if (columnSchema.HasColumnFamilyName()) { + ColumnFamilyName = columnSchema.GetColumnFamilyName(); + } + if (columnSchema.HasSerializer()) { + if (!Serializer.DeserializeFromProto(columnSchema.GetSerializer())) { + errors.AddError("cannot parse serializer diff from proto"); return false; } + } + if (!DictionaryEncoding.DeserializeFromProto(columnSchema.GetDictionaryEncoding())) { + errors.AddError("cannot parse dictionary encoding diff from proto"); + return false; + } + return true; +} - if (!columnSchema.HasType()) { - errors.AddError(TStringBuilder() << "Missing Type for column '" << Name); +bool TOlapColumnBase::ParseFromRequest(const NKikimrSchemeOp::TOlapColumnDescription& columnSchema, IErrorCollector& errors) { + if (!columnSchema.GetName()) { + errors.AddError("Columns cannot have an empty name"); + return false; + } + Name = columnSchema.GetName(); + NotNullFlag = columnSchema.GetNotNull(); + TypeName = columnSchema.GetType(); + StorageId = columnSchema.GetStorageId(); + if (columnSchema.HasColumnFamilyId()) { + ColumnFamilyId = columnSchema.GetColumnFamilyId(); + } + if (columnSchema.HasSerializer()) { + NArrow::NSerialization::TSerializerContainer serializer; + if (!serializer.DeserializeFromProto(columnSchema.GetSerializer())) { + errors.AddError("Cannot parse serializer info"); return false; } + Serializer = serializer; + } + if (columnSchema.HasDictionaryEncoding()) { + auto settings = NArrow::NDictionary::TEncodingSettings::BuildFromProto(columnSchema.GetDictionaryEncoding()); + if (!settings) { + errors.AddError("Cannot parse dictionary compression info: " + settings.GetErrorMessage()); + return false; + } + DictionaryEncoding = *settings; + } - if (const auto& typeName = NMiniKQL::AdaptLegacyYqlType(TypeName); typeName.StartsWith("pg")) { + if (columnSchema.HasTypeId()) { + errors.AddError(TStringBuilder() << "Cannot set TypeId for column '" << Name << ", use Type"); + return false; + } + + if (!columnSchema.HasType()) { + errors.AddError(TStringBuilder() << "Missing Type for column '" << Name); + return false; + } + + + if (const auto& typeName = NMiniKQL::AdaptLegacyYqlType(TypeName); typeName.StartsWith("pg")) { const auto typeDesc = NPg::TypeDescFromPgTypeName(typeName); if (!(typeDesc && TOlapColumnAdd::IsAllowedPgType(NPg::PgTypeIdFromTypeDesc(typeDesc)))) { errors.AddError(TStringBuilder() << "Type '" << typeName << "' specified for column '" << Name << "' is not supported"); @@ -70,183 +109,255 @@ namespace NKikimr::NSchemeShard { errors.AddError(TStringBuilder() << "Type '" << typeName << "' specified for column '" << Name << "' is not supported"); return false; } - } - auto arrowTypeResult = NArrow::GetArrowType(Type); - const auto arrowTypeStatus = arrowTypeResult.status(); - if (!arrowTypeStatus.ok()) { - errors.AddError(TStringBuilder() << "Column '" << Name << "': " << arrowTypeStatus.ToString()); + } + + // TString errStr; + // Y_ABORT_UNLESS(AppData()->TypeRegistry); + // if (!GetTypeInfo(AppData()->TypeRegistry->GetType(TypeName), columnSchema.GetTypeInfo(), TypeName, Name, Type, errStr)) { + // errors.AddError(errStr); + // return false; + // } + + // if (Type.GetTypeId() == NScheme::NTypeIds::Pg) { + // if (!IsAllowedPgType(NPg::PgTypeIdFromTypeDesc(Type.GetTypeDesc()))) { + // errors.AddError(TStringBuilder() << "Type '" << TypeName << "' specified for column '" << Name << "' is not supported"); + // return false; + // } + // } else { + // if (!IsAllowedType(Type.GetTypeId())) { + // errors.AddError(TStringBuilder() << "Type '" << TypeName << "' specified for column '" << Name << "' is not supported"); + // return false; + // } + // } + + auto arrowTypeResult = NArrow::GetArrowType(Type); + const auto arrowTypeStatus = arrowTypeResult.status(); + if (!arrowTypeStatus.ok()) { + errors.AddError(TStringBuilder() << "Column '" << Name << "': " << arrowTypeStatus.ToString()); + return false; + } + if (columnSchema.HasDefaultValue()) { + auto conclusion = DefaultValue.DeserializeFromProto(columnSchema.GetDefaultValue()); + if (conclusion.IsFail()) { + errors.AddError(conclusion.GetErrorMessage()); return false; } - if (columnSchema.HasDefaultValue()) { - auto conclusion = DefaultValue.DeserializeFromProto(columnSchema.GetDefaultValue()); - if (conclusion.IsFail()) { - errors.AddError(conclusion.GetErrorMessage()); - return false; - } - if (!DefaultValue.IsCompatibleType(*arrowTypeResult)) { - errors.AddError("incompatible types for default write: def" + DefaultValue.DebugString() + ", col:" + (*arrowTypeResult)->ToString()); - return false; - } + if (!DefaultValue.IsCompatibleType(*arrowTypeResult)) { + errors.AddError( + "incompatible types for default write: def" + DefaultValue.DebugString() + ", col:" + (*arrowTypeResult)->ToString()); + return false; } - return true; } + return true; +} - void TOlapColumnAdd::ParseFromLocalDB(const NKikimrSchemeOp::TOlapColumnDescription& columnSchema) { - Name = columnSchema.GetName(); - TypeName = columnSchema.GetType(); - StorageId = columnSchema.GetStorageId(); +void TOlapColumnBase::ParseFromLocalDB(const NKikimrSchemeOp::TOlapColumnDescription& columnSchema) { + Name = columnSchema.GetName(); + TypeName = columnSchema.GetType(); + StorageId = columnSchema.GetStorageId(); - if (columnSchema.HasTypeInfo()) { - Type = NScheme::TypeInfoModFromProtoColumnType( - columnSchema.GetTypeId(), &columnSchema.GetTypeInfo()) - .TypeInfo; - } else { - Type = NScheme::TypeInfoModFromProtoColumnType( - columnSchema.GetTypeId(), nullptr) - .TypeInfo; - } - auto arrowType = NArrow::TStatusValidator::GetValid(NArrow::GetArrowType(Type)); - if (columnSchema.HasDefaultValue()) { - DefaultValue.DeserializeFromProto(columnSchema.GetDefaultValue()).Validate(); - AFL_VERIFY(DefaultValue.IsCompatibleType(arrowType)); - } - if (columnSchema.HasSerializer()) { - NArrow::NSerialization::TSerializerContainer serializer; - AFL_VERIFY(serializer.DeserializeFromProto(columnSchema.GetSerializer())); - Serializer = serializer; - } else if (columnSchema.HasCompression()) { - NArrow::NSerialization::TSerializerContainer serializer; - serializer.DeserializeFromProto(columnSchema.GetCompression()).Validate(); - Serializer = serializer; - } - if (columnSchema.HasDataAccessorConstructor()) { - NArrow::NAccessor::TConstructorContainer container; - AFL_VERIFY(container.DeserializeFromProto(columnSchema.GetDataAccessorConstructor())); - AccessorConstructor = container; - } - if (columnSchema.HasDictionaryEncoding()) { - auto settings = NArrow::NDictionary::TEncodingSettings::BuildFromProto(columnSchema.GetDictionaryEncoding()); - Y_ABORT_UNLESS(settings.IsSuccess()); - DictionaryEncoding = *settings; - } - if (columnSchema.HasNotNull()) { - NotNullFlag = columnSchema.GetNotNull(); - } else { - NotNullFlag = false; - } + if (columnSchema.HasTypeInfo()) { + Type = NScheme::TypeInfoModFromProtoColumnType(columnSchema.GetTypeId(), &columnSchema.GetTypeInfo()).TypeInfo; + } else { + Type = NScheme::TypeInfoModFromProtoColumnType(columnSchema.GetTypeId(), nullptr).TypeInfo; + } + auto arrowType = NArrow::TStatusValidator::GetValid(NArrow::GetArrowType(Type)); + if (columnSchema.HasDefaultValue()) { + DefaultValue.DeserializeFromProto(columnSchema.GetDefaultValue()).Validate(); + AFL_VERIFY(DefaultValue.IsCompatibleType(arrowType)); + } + if (columnSchema.HasColumnFamilyId()) { + ColumnFamilyId = columnSchema.GetColumnFamilyId(); + } + if (columnSchema.HasSerializer()) { + NArrow::NSerialization::TSerializerContainer serializer; + AFL_VERIFY(serializer.DeserializeFromProto(columnSchema.GetSerializer())); + Serializer = serializer; + } else if (columnSchema.HasCompression()) { + NArrow::NSerialization::TSerializerContainer serializer; + serializer.DeserializeFromProto(columnSchema.GetCompression()).Validate(); + Serializer = serializer; } + if (columnSchema.HasDataAccessorConstructor()) { + NArrow::NAccessor::TConstructorContainer container; + AFL_VERIFY(container.DeserializeFromProto(columnSchema.GetDataAccessorConstructor())); + AccessorConstructor = container; + } + if (columnSchema.HasDictionaryEncoding()) { + auto settings = NArrow::NDictionary::TEncodingSettings::BuildFromProto(columnSchema.GetDictionaryEncoding()); + Y_ABORT_UNLESS(settings.IsSuccess()); + DictionaryEncoding = *settings; + } + if (columnSchema.HasNotNull()) { + NotNullFlag = columnSchema.GetNotNull(); + } else { + NotNullFlag = false; + } +} - void TOlapColumnAdd::Serialize(NKikimrSchemeOp::TOlapColumnDescription& columnSchema) const { - columnSchema.SetName(Name); - columnSchema.SetType(TypeName); - columnSchema.SetNotNull(NotNullFlag); - columnSchema.SetStorageId(StorageId); - *columnSchema.MutableDefaultValue() = DefaultValue.SerializeToProto(); - if (Serializer) { - Serializer->SerializeToProto(*columnSchema.MutableSerializer()); - } - if (AccessorConstructor) { - *columnSchema.MutableDataAccessorConstructor() = AccessorConstructor.SerializeToProto(); - } - if (DictionaryEncoding) { - *columnSchema.MutableDictionaryEncoding() = DictionaryEncoding->SerializeToProto(); - } +void TOlapColumnBase::Serialize(NKikimrSchemeOp::TOlapColumnDescription& columnSchema) const { + columnSchema.SetName(Name); + columnSchema.SetType(TypeName); + columnSchema.SetNotNull(NotNullFlag); + columnSchema.SetStorageId(StorageId); + *columnSchema.MutableDefaultValue() = DefaultValue.SerializeToProto(); + if (ColumnFamilyId.has_value()) { + columnSchema.SetColumnFamilyId(ColumnFamilyId.value()); + } + if (Serializer) { + Serializer->SerializeToProto(*columnSchema.MutableSerializer()); + } + if (AccessorConstructor) { + *columnSchema.MutableDataAccessorConstructor() = AccessorConstructor.SerializeToProto(); + } + if (DictionaryEncoding) { + *columnSchema.MutableDictionaryEncoding() = DictionaryEncoding->SerializeToProto(); + } - auto columnType = NScheme::ProtoColumnTypeFromTypeInfoMod(Type, ""); - columnSchema.SetTypeId(columnType.TypeId); - if (columnType.TypeInfo) { - *columnSchema.MutableTypeInfo() = *columnType.TypeInfo; - } + auto columnType = NScheme::ProtoColumnTypeFromTypeInfoMod(Type, ""); + columnSchema.SetTypeId(columnType.TypeId); + if (columnType.TypeInfo) { + *columnSchema.MutableTypeInfo() = *columnType.TypeInfo; } +} - bool TOlapColumnAdd::ApplyDiff(const TOlapColumnDiff& diffColumn, IErrorCollector& errors) { - Y_ABORT_UNLESS(GetName() == diffColumn.GetName()); - if (diffColumn.GetDefaultValue()) { - auto conclusion = DefaultValue.ParseFromString(*diffColumn.GetDefaultValue(), Type); - if (conclusion.IsFail()) { - errors.AddError(conclusion.GetErrorMessage()); - return false; - } - } - if (!!diffColumn.GetAccessorConstructor()) { - auto conclusion = diffColumn.GetAccessorConstructor()->BuildConstructor(); - if (conclusion.IsFail()) { - errors.AddError(conclusion.GetErrorMessage()); - return false; - } - AccessorConstructor = conclusion.DetachResult(); +bool TOlapColumnBase::ApplySerializerFromColumnFamily(const TOlapColumnFamiliesDescription& columnFamilies, IErrorCollector& errors) { + if (GetColumnFamilyId().has_value()) { + SetSerializer(columnFamilies.GetByIdVerified(GetColumnFamilyId().value())->GetSerializerContainer()); + } else { + TString familyName = "default"; + const TOlapColumnFamily* columnFamily = columnFamilies.GetByName(familyName); + + if (!columnFamily) { + errors.AddError(NKikimrScheme::StatusSchemeError, + TStringBuilder() << "Cannot set column family `" << familyName << "` for column `" << GetName() << "`. Family not found"); + return false; } - if (diffColumn.GetStorageId()) { - StorageId = *diffColumn.GetStorageId(); + + ColumnFamilyId = columnFamily->GetId(); + SetSerializer(columnFamilies.GetByIdVerified(columnFamily->GetId())->GetSerializerContainer()); + } + return true; +} + +bool TOlapColumnBase::ApplyDiff( + const TOlapColumnDiff& diffColumn, const TOlapColumnFamiliesDescription& columnFamilies, IErrorCollector& errors) { + Y_ABORT_UNLESS(GetName() == diffColumn.GetName()); + if (diffColumn.GetDefaultValue()) { + auto conclusion = DefaultValue.ParseFromString(*diffColumn.GetDefaultValue(), Type); + if (conclusion.IsFail()) { + errors.AddError(conclusion.GetErrorMessage()); + return false; } - if (diffColumn.GetSerializer()) { - Serializer = diffColumn.GetSerializer(); + } + if (!!diffColumn.GetAccessorConstructor()) { + auto conclusion = diffColumn.GetAccessorConstructor()->BuildConstructor(); + if (conclusion.IsFail()) { + errors.AddError(conclusion.GetErrorMessage()); + return false; } - { - auto result = diffColumn.GetDictionaryEncoding().Apply(DictionaryEncoding); - if (!result) { - errors.AddError("Cannot merge dictionary encoding info: " + result.GetErrorMessage()); - return false; - } + AccessorConstructor = conclusion.DetachResult(); + } + if (diffColumn.GetStorageId()) { + StorageId = *diffColumn.GetStorageId(); + } + if (diffColumn.GetColumnFamilyName().has_value()) { + TString columnFamilyName = diffColumn.GetColumnFamilyName().value(); + const TOlapColumnFamily* columnFamily = columnFamilies.GetByName(columnFamilyName); + if (!columnFamily) { + errors.AddError(NKikimrScheme::StatusSchemeError, TStringBuilder() << "Cannot alter column family `" << columnFamilyName + << "` for column `" << GetName() << "`. Family not found"); + return false; } - return true; + ColumnFamilyId = columnFamily->GetId(); } - bool TOlapColumnAdd::IsAllowedType(ui32 typeId) { - if (!NScheme::NTypeIds::IsYqlType(typeId)) { + if (diffColumn.GetSerializer()) { + Serializer = diffColumn.GetSerializer(); + } else { + if (!columnFamilies.GetColumnFamilies().empty() && !ApplySerializerFromColumnFamily(columnFamilies, errors)) { return false; } - - switch (typeId) { - case NYql::NProto::Bool: - case NYql::NProto::Interval: - case NYql::NProto::DyNumber: - return false; - default: - break; + } + { + auto result = diffColumn.GetDictionaryEncoding().Apply(DictionaryEncoding); + if (!result) { + errors.AddError("Cannot merge dictionary encoding info: " + result.GetErrorMessage()); + return false; } - return true; } + return true; +} - bool TOlapColumnAdd::IsAllowedPgType(ui32 pgTypeId) { - switch (pgTypeId) { - case INT2OID: - case INT4OID: - case INT8OID: - case FLOAT4OID: - case FLOAT8OID: - return true; - default: - break; - } +bool TOlapColumnBase::IsAllowedType(ui32 typeId) { + if (!NScheme::NTypeIds::IsYqlType(typeId)) { return false; } - bool TOlapColumnAdd::IsAllowedPkType(ui32 typeId) { - switch (typeId) { - case NYql::NProto::Int8: - case NYql::NProto::Uint8: // Byte - case NYql::NProto::Int16: - case NYql::NProto::Uint16: - case NYql::NProto::Int32: - case NYql::NProto::Uint32: - case NYql::NProto::Int64: - case NYql::NProto::Uint64: - case NYql::NProto::String: - case NYql::NProto::Utf8: - case NYql::NProto::Date: - case NYql::NProto::Datetime: - case NYql::NProto::Timestamp: - case NYql::NProto::Date32: - case NYql::NProto::Datetime64: - case NYql::NProto::Timestamp64: - case NYql::NProto::Interval64: - case NYql::NProto::Decimal: - return true; - default: - return false; - } + switch (typeId) { + case NYql::NProto::Bool: + case NYql::NProto::Interval: + case NYql::NProto::DyNumber: + return false; + default: + break; } + return true; +} + +bool TOlapColumnBase::IsAllowedPgType(ui32 pgTypeId) { + switch (pgTypeId) { + case INT2OID: + case INT4OID: + case INT8OID: + case FLOAT4OID: + case FLOAT8OID: + return true; + default: + break; + } + return false; +} + +bool TOlapColumnBase::IsAllowedPkType(ui32 typeId) { + switch (typeId) { + case NYql::NProto::Int8: + case NYql::NProto::Uint8: // Byte + case NYql::NProto::Int16: + case NYql::NProto::Uint16: + case NYql::NProto::Int32: + case NYql::NProto::Uint32: + case NYql::NProto::Int64: + case NYql::NProto::Uint64: + case NYql::NProto::String: + case NYql::NProto::Utf8: + case NYql::NProto::Date: + case NYql::NProto::Datetime: + case NYql::NProto::Timestamp: + case NYql::NProto::Date32: + case NYql::NProto::Datetime64: + case NYql::NProto::Timestamp64: + case NYql::NProto::Interval64: + case NYql::NProto::Decimal: + return true; + default: + return false; + } +} + +bool TOlapColumnAdd::ParseFromRequest(const NKikimrSchemeOp::TOlapColumnDescription& columnSchema, IErrorCollector& errors) { + if (columnSchema.HasColumnFamilyName()) { + ColumnFamilyName = columnSchema.GetColumnFamilyName(); + } + return TBase::ParseFromRequest(columnSchema, errors); +} + +void TOlapColumnAdd::ParseFromLocalDB(const NKikimrSchemeOp::TOlapColumnDescription& columnSchema) { + if (columnSchema.HasColumnFamilyName()) { + ColumnFamilyName = columnSchema.GetColumnFamilyName(); + } + TBase::ParseFromLocalDB(columnSchema); +} bool TOlapColumnsUpdate::Parse(const NKikimrSchemeOp::TAlterColumnTableSchema& alterRequest, IErrorCollector& errors) { for (const auto& column : alterRequest.GetDropColumns()) { diff --git a/ydb/core/tx/schemeshard/olap/columns/update.h b/ydb/core/tx/schemeshard/olap/columns/update.h index 84a728829d6e..4f87cf014d37 100644 --- a/ydb/core/tx/schemeshard/olap/columns/update.h +++ b/ydb/core/tx/schemeshard/olap/columns/update.h @@ -1,13 +1,15 @@ #pragma once -#include -#include -#include -#include -#include #include +#include #include #include +#include +#include #include +#include +#include + +#include namespace NKikimr::NSchemeShard { @@ -19,40 +21,13 @@ class TOlapColumnDiff { YDB_READONLY_DEF(std::optional, StorageId); YDB_READONLY_DEF(std::optional, DefaultValue); YDB_READONLY_DEF(NArrow::NAccessor::TRequestedConstructorContainer, AccessorConstructor); + YDB_READONLY_DEF(std::optional, ColumnFamilyName); + public: - bool ParseFromRequest(const NKikimrSchemeOp::TOlapColumnDiff& columnSchema, IErrorCollector& errors) { - Name = columnSchema.GetName(); - if (!!columnSchema.GetStorageId()) { - StorageId = columnSchema.GetStorageId(); - } - if (!Name) { - errors.AddError("empty field name"); - return false; - } - if (columnSchema.HasDefaultValue()) { - DefaultValue = columnSchema.GetDefaultValue(); - } - if (columnSchema.HasDataAccessorConstructor()) { - if (!AccessorConstructor.DeserializeFromProto(columnSchema.GetDataAccessorConstructor())) { - errors.AddError("cannot parse accessor constructor from proto"); - return false; - } - } - if (columnSchema.HasSerializer()) { - if (!Serializer.DeserializeFromProto(columnSchema.GetSerializer())) { - errors.AddError("cannot parse serializer diff from proto"); - return false; - } - } - if (!DictionaryEncoding.DeserializeFromProto(columnSchema.GetDictionaryEncoding())) { - errors.AddError("cannot parse dictionary encoding diff from proto"); - return false; - } - return true; - } + bool ParseFromRequest(const NKikimrSchemeOp::TOlapColumnDiff& columnSchema, IErrorCollector& errors); }; -class TOlapColumnAdd { +class TOlapColumnBase { private: YDB_READONLY_DEF(std::optional, KeyOrder); YDB_READONLY_DEF(TString, Name); @@ -60,19 +35,24 @@ class TOlapColumnAdd { YDB_READONLY_DEF(NScheme::TTypeInfo, Type); YDB_READONLY_DEF(TString, StorageId); YDB_FLAG_ACCESSOR(NotNull, false); - YDB_READONLY_DEF(std::optional, Serializer); + YDB_ACCESSOR_DEF(std::optional, Serializer); YDB_READONLY_DEF(std::optional, DictionaryEncoding); YDB_READONLY_DEF(NOlap::TColumnDefaultScalarValue, DefaultValue); YDB_READONLY_DEF(NArrow::NAccessor::TConstructorContainer, AccessorConstructor); -public: - TOlapColumnAdd(const std::optional& keyOrder) - : KeyOrder(keyOrder) { + YDB_READONLY_PROTECT(std::optional, ColumnFamilyId, std::nullopt); +public: + TOlapColumnBase(const std::optional& keyOrder, const std::optional columnFamilyId = {}) + : KeyOrder(keyOrder) + , ColumnFamilyId(columnFamilyId) + { } bool ParseFromRequest(const NKikimrSchemeOp::TOlapColumnDescription& columnSchema, IErrorCollector& errors); void ParseFromLocalDB(const NKikimrSchemeOp::TOlapColumnDescription& columnSchema); void Serialize(NKikimrSchemeOp::TOlapColumnDescription& columnSchema) const; bool ApplyDiff(const TOlapColumnDiff& diffColumn, IErrorCollector& errors); + bool ApplySerializerFromColumnFamily(const TOlapColumnFamiliesDescription& columnFamilies, IErrorCollector& errors); + bool ApplyDiff(const TOlapColumnDiff& diffColumn, const TOlapColumnFamiliesDescription& columnFamilies, IErrorCollector& errors); bool IsKeyColumn() const { return !!KeyOrder; } @@ -81,6 +61,20 @@ class TOlapColumnAdd { static bool IsAllowedPgType(ui32 pgTypeId); }; +class TOlapColumnAdd: public TOlapColumnBase { +private: + using TBase = TOlapColumnBase; + YDB_READONLY_DEF(std::optional, ColumnFamilyName); + +public: + TOlapColumnAdd(const std::optional& keyOrder) + : TBase(keyOrder) + { + } + bool ParseFromRequest(const NKikimrSchemeOp::TOlapColumnDescription& columnSchema, IErrorCollector& errors); + void ParseFromLocalDB(const NKikimrSchemeOp::TOlapColumnDescription& columnSchema); +}; + class TOlapColumnsUpdate { private: YDB_READONLY_DEF(TVector, AddColumns); diff --git a/ydb/core/tx/schemeshard/olap/manager/manager.cpp b/ydb/core/tx/schemeshard/olap/manager/manager.cpp index 72a8c93c5f1c..109bcfe33b18 100644 --- a/ydb/core/tx/schemeshard/olap/manager/manager.cpp +++ b/ydb/core/tx/schemeshard/olap/manager/manager.cpp @@ -3,41 +3,17 @@ namespace NKikimr::NSchemeShard { void TTablesStorage::OnAddObject(const TPathId& pathId, TColumnTableInfo::TPtr object) { - const TString& tieringId = object->Description.GetTtlSettings().GetUseTiering(); - if (!!tieringId) { - PathsByTieringId[tieringId].emplace(pathId); - } for (auto&& s : object->GetColumnShards()) { - TablesByShard[s].AddId(pathId); + AFL_VERIFY(TablesByShard[s].AddId(pathId)); } } void TTablesStorage::OnRemoveObject(const TPathId& pathId, TColumnTableInfo::TPtr object) { - const TString& tieringId = object->Description.GetTtlSettings().GetUseTiering(); - if (!!tieringId) { - auto it = PathsByTieringId.find(tieringId); - if (PathsByTieringId.end() == it) { - return; - } - it->second.erase(pathId); - if (it->second.empty()) { - PathsByTieringId.erase(it); - } - } for (auto&& s : object->GetColumnShards()) { TablesByShard[s].RemoveId(pathId); } } -const THashSet& TTablesStorage::GetTablesWithTiering(const TString& tieringId) const { - auto it = PathsByTieringId.find(tieringId); - if (it != PathsByTieringId.end()) { - return it->second; - } else { - return Default>(); - } -} - TColumnTableInfo::TPtr TTablesStorage::ExtractPtr(const TPathId& id) { auto it = Tables.find(id); Y_ABORT_UNLESS(it != Tables.end()); @@ -78,13 +54,14 @@ TTablesStorage::TTableCreatedGuard TTablesStorage::BuildNew(const TPathId& id) { return TTableCreatedGuard(*this, id); } -size_t TTablesStorage::Drop(const TPathId& id) { +bool TTablesStorage::Drop(const TPathId& id) { auto it = Tables.find(id); if (it == Tables.end()) { - return 0; + return false; } else { OnRemoveObject(id, it->second); - return Tables.erase(id); + Tables.erase(it); + return true; } } @@ -120,7 +97,9 @@ void TTablesStorage::TTableExtractedGuard::UseAlterDataVerified() { TColumnTableInfo::TPtr alterInfo = Object->AlterData; Y_ABORT_UNLESS(alterInfo); alterInfo->AlterBody.Clear(); + auto stats = Object->Stats; Object = alterInfo; + Object->Stats = stats; } std::unordered_set TTablesStorage::GetAllPathIds() const { diff --git a/ydb/core/tx/schemeshard/olap/manager/manager.h b/ydb/core/tx/schemeshard/olap/manager/manager.h index 0873a12da22d..a2697cf5b593 100644 --- a/ydb/core/tx/schemeshard/olap/manager/manager.h +++ b/ydb/core/tx/schemeshard/olap/manager/manager.h @@ -9,7 +9,6 @@ namespace NKikimr::NSchemeShard { class TTablesStorage { private: THashMap Tables; - THashMap> PathsByTieringId; THashMap TablesByShard; void OnAddObject(const TPathId& pathId, TColumnTableInfo::TPtr object); @@ -20,7 +19,7 @@ class TTablesStorage { TColumnTablesLayout GetTablesLayout(const std::vector& tabletIds) const; - const THashSet& GetTablesWithTiering(const TString& tieringId) const; + const THashSet& GetTablesWithTier(const TString& storageId) const; class TTableReadGuard { protected: @@ -115,7 +114,7 @@ class TTablesStorage { TTableReadGuard at(const TPathId& id) const { return TTableReadGuard(Tables.at(id)); } - size_t Drop(const TPathId& id); + bool Drop(const TPathId& id); }; } diff --git a/ydb/core/tx/schemeshard/olap/operations/alter/abstract/converter.h b/ydb/core/tx/schemeshard/olap/operations/alter/abstract/converter.h index 7a45468961b1..f4687cd88b14 100644 --- a/ydb/core/tx/schemeshard/olap/operations/alter/abstract/converter.h +++ b/ydb/core/tx/schemeshard/olap/operations/alter/abstract/converter.h @@ -25,12 +25,10 @@ class TConverterModifyToAlter { if (enabled.HasColumnUnit()) { alterEnabled->SetColumnUnit(enabled.GetColumnUnit()); } + *alterEnabled->MutableTiers() = enabled.GetTiers(); } else if (tableTtl.HasDisabled()) { alterTtl->MutableDisabled(); } - if (tableTtl.HasUseTiering()) { - alterTtl->SetUseTiering(tableTtl.GetUseTiering()); - } } for (auto&& dsColumn : dsDescription.GetColumns()) { @@ -50,6 +48,12 @@ class TConverterModifyToAlter { return parse; } } + + for (auto&& family : dsDescription.GetPartitionConfig().GetColumnFamilies()) { + NKikimrSchemeOp::TAlterColumnTableSchema* alterSchema = olapDescription.MutableAlterSchema(); + alterSchema->AddAddColumnFamily()->CopyFrom(family); + } + return TConclusionStatus::Success(); } @@ -71,11 +75,27 @@ class TConverterModifyToAlter { if (dsColumn.HasDefaultFromSequence()) { return TConclusionStatus::Fail("DefaultFromSequence not supported"); } - if (dsColumn.HasFamilyName() || dsColumn.HasFamily()) { - return TConclusionStatus::Fail("FamilyName and Family not supported"); + if (dsColumn.HasFamilyName()) { + olapColumn.SetColumnFamilyName(dsColumn.GetFamilyName()); + } + if (dsColumn.HasFamily()) { + olapColumn.SetColumnFamilyId(dsColumn.GetFamily()); + } + return TConclusionStatus::Success(); + } + + TConclusionStatus ParseFromDSRequest( + const NKikimrSchemeOp::TColumnDescription& dsColumn, NKikimrSchemeOp::TOlapColumnDiff& olapColumn) const { + olapColumn.SetName(dsColumn.GetName()); + if (dsColumn.HasDefaultFromSequence()) { + return TConclusionStatus::Fail("DefaultFromSequence not supported"); + } + if (dsColumn.HasFamilyName()) { + olapColumn.SetColumnFamilyName(dsColumn.GetFamilyName()); } return TConclusionStatus::Success(); } + public: TConclusion Convert(const NKikimrSchemeOp::TModifyScheme& modify) { NKikimrSchemeOp::TAlterColumnTable result; diff --git a/ydb/core/tx/schemeshard/olap/operations/alter/common/update.cpp b/ydb/core/tx/schemeshard/olap/operations/alter/common/update.cpp index 442c67833557..e083706f1aaf 100644 --- a/ydb/core/tx/schemeshard/olap/operations/alter/common/update.cpp +++ b/ydb/core/tx/schemeshard/olap/operations/alter/common/update.cpp @@ -12,6 +12,27 @@ TConclusionStatus TColumnTableUpdate::DoStart(const TUpdateStartContext& context auto tableInfo = context.GetSSOperationContext()->SS->ColumnTables.TakeVerified(pathId); context.GetSSOperationContext()->SS->PersistColumnTableAlter(*context.GetDB(), pathId, *GetTargetTableInfoVerified()); tableInfo->AlterData = GetTargetTableInfoVerified(); + + { + THashSet oldDataSources = tableInfo->GetUsedTiers(); + THashSet newDataSources = GetTargetTableInfoVerified()->GetUsedTiers(); + for (const auto& tier : oldDataSources) { + if (!newDataSources.contains(tier)) { + auto tierPath = TPath::Resolve(tier, context.GetSSOperationContext()->SS); + AFL_VERIFY(tierPath.IsResolved())("path", tier); + context.GetSSOperationContext()->SS->PersistRemoveExternalDataSourceReference(*context.GetDB(), tierPath->PathId, pathId); + } + } + for (const auto& tier : newDataSources) { + if (!oldDataSources.contains(tier)) { + auto tierPath = TPath::Resolve(tier, context.GetSSOperationContext()->SS); + AFL_VERIFY(tierPath.IsResolved())("path", tier); + context.GetSSOperationContext()->SS->PersistExternalDataSourceReference( + *context.GetDB(), tierPath->PathId, TPath::Init(pathId, context.GetSSOperationContext()->SS)); + } + } + } + return TConclusionStatus::Success(); } diff --git a/ydb/core/tx/schemeshard/olap/operations/alter/in_store/schema/update.cpp b/ydb/core/tx/schemeshard/olap/operations/alter/in_store/schema/update.cpp index bbf1845ac1bb..4bef41d16282 100644 --- a/ydb/core/tx/schemeshard/olap/operations/alter/in_store/schema/update.cpp +++ b/ydb/core/tx/schemeshard/olap/operations/alter/in_store/schema/update.cpp @@ -41,7 +41,7 @@ NKikimr::TConclusionStatus TInStoreSchemaUpdate::DoInitializeImpl(const TUpdateI return patch; } TSimpleErrorCollector collector; - if (!originalSchema.ValidateTtlSettings(ttl.GetData(), collector)) { + if (!originalSchema.ValidateTtlSettings(ttl.GetData(), *context.GetSSOperationContext(), collector)) { return TConclusionStatus::Fail("ttl update error: " + collector->GetErrorMessage() + ". in alter constructor STANDALONE_UPDATE"); } *description.MutableTtlSettings() = ttl.SerializeToProto(); diff --git a/ydb/core/tx/schemeshard/olap/operations/alter/standalone/update.cpp b/ydb/core/tx/schemeshard/olap/operations/alter/standalone/update.cpp index 2902534fbb24..1b11cd53ff9a 100644 --- a/ydb/core/tx/schemeshard/olap/operations/alter/standalone/update.cpp +++ b/ydb/core/tx/schemeshard/olap/operations/alter/standalone/update.cpp @@ -65,7 +65,7 @@ NKikimr::TConclusionStatus TStandaloneSchemaUpdate::DoInitializeImpl(const TUpda } *description.MutableTtlSettings() = ttl.SerializeToProto(); } - if (!targetSchema.ValidateTtlSettings(ttl.GetData(), collector)) { + if (!targetSchema.ValidateTtlSettings(ttl.GetData(), *context.GetSSOperationContext(), collector)) { return TConclusionStatus::Fail("ttl update error: " + collector->GetErrorMessage() + ". in alter constructor STANDALONE_UPDATE"); } auto saSharding = originalTable.GetTableInfoVerified().GetStandaloneShardingVerified(); @@ -76,4 +76,4 @@ NKikimr::TConclusionStatus TStandaloneSchemaUpdate::DoInitializeImpl(const TUpda return TConclusionStatus::Success(); } -} \ No newline at end of file +} diff --git a/ydb/core/tx/schemeshard/olap/operations/alter_store.cpp b/ydb/core/tx/schemeshard/olap/operations/alter_store.cpp index 57f05068b162..de95b6bd6eee 100644 --- a/ydb/core/tx/schemeshard/olap/operations/alter_store.cpp +++ b/ydb/core/tx/schemeshard/olap/operations/alter_store.cpp @@ -526,6 +526,18 @@ class TAlterOlapStore: public TSubOperation { return result; } + for (auto&& tPathId: alterData->ColumnTables) { + auto table = context.SS->ColumnTables.GetVerifiedPtr(tPathId); + if (!table->Description.HasTtlSettings()) { + continue; + } + auto it = alterData->SchemaPresets.find(table->Description.GetSchemaPresetId()); + AFL_VERIFY(it != alterData->SchemaPresets.end())("preset_info", table->Description.DebugString()); + if (!it->second.ValidateTtlSettings(table->Description.GetTtlSettings(), context, errors)) { + return result; + } + } + if (!AppData()->FeatureFlags.GetEnableSparsedColumns()) { for (auto& [_, preset]: alterData->SchemaPresets) { for (auto& [_, column]: preset.GetColumns().GetColumns()) { diff --git a/ydb/core/tx/schemeshard/olap/operations/alter_table.cpp b/ydb/core/tx/schemeshard/olap/operations/alter_table.cpp index 4fb76b4a75a0..0409fad44a3c 100644 --- a/ydb/core/tx/schemeshard/olap/operations/alter_table.cpp +++ b/ydb/core/tx/schemeshard/olap/operations/alter_table.cpp @@ -271,13 +271,6 @@ class TAlterColumnTable: public TSubOperation { return result; } - const bool hasTiering = Transaction.HasAlterColumnTable() && Transaction.GetAlterColumnTable().HasAlterTtlSettings() && - Transaction.GetAlterColumnTable().GetAlterTtlSettings().HasUseTiering(); - if (hasTiering && HasAppData() && !AppDataVerified().FeatureFlags.GetEnableTieringInColumnShard()) { - result->SetError(NKikimrScheme::StatusPreconditionFailed, "Tiering functionality is disabled for OLAP tables"); - return result; - } - const TString& parentPathStr = Transaction.GetWorkingDir(); const TString& name = Transaction.HasAlterColumnTable() ? Transaction.GetAlterColumnTable().GetName() : Transaction.GetAlterTable().GetName(); LOG_NOTICE_S(context.Ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, diff --git a/ydb/core/tx/schemeshard/olap/operations/create_table.cpp b/ydb/core/tx/schemeshard/olap/operations/create_table.cpp index 0c155d68d761..bc5ef25fc14f 100644 --- a/ydb/core/tx/schemeshard/olap/operations/create_table.cpp +++ b/ydb/core/tx/schemeshard/olap/operations/create_table.cpp @@ -23,7 +23,7 @@ class TTableConstructorBase { protected: ui32 ShardsCount = 0; public: - bool Deserialize(const NKikimrSchemeOp::TColumnTableDescription& description, IErrorCollector& errors) { + bool Deserialize(const NKikimrSchemeOp::TColumnTableDescription& description, const TOperationContext& context, IErrorCollector& errors) { Name = description.GetName(); ShardsCount = std::max(description.GetColumnShardCount(), 1); @@ -33,7 +33,7 @@ class TTableConstructorBase { if (description.HasTtlSettings()) { TtlSettings = description.GetTtlSettings(); - if (!GetSchema().ValidateTtlSettings(description.GetTtlSettings(), errors)) { + if (!GetSchema().ValidateTtlSettings(description.GetTtlSettings(), context, errors)) { return false; } } @@ -49,7 +49,7 @@ class TTableConstructorBase { } FillDefaultSharding(*tableInfo->Description.MutableSharding()); - if (!Deserialize(description, errors)) { + if (!Deserialize(description, context, errors)) { return nullptr; } if (tableInfo->Description.GetSharding().HasHashSharding()) { @@ -192,7 +192,7 @@ class TOlapTableConstructor : public TTableConstructorBase { } TOlapSchemaUpdate schemaDiff; - if (!schemaDiff.Parse(description.GetSchema(), errors)) { + if (!schemaDiff.Parse(description.GetSchema(), errors, AppData()->ColumnShardConfig.GetAllowNullableColumnsInPK())) { return false; } @@ -558,13 +558,35 @@ class TCreateColumnTable: public TSubOperation { public: using TSubOperation::TSubOperation; + void AddDefaultFamilyIfNotExists(NKikimrSchemeOp::TColumnTableDescription& createDescription) { + auto schema = createDescription.GetSchema(); + for (const auto& family : schema.GetColumnFamilies()) { + if (family.GetName() == "default") { + return; + } + } + + auto mutableSchema = createDescription.MutableSchema(); + auto defaultFamily = mutableSchema->AddColumnFamilies(); + defaultFamily->SetName("default"); + defaultFamily->SetId(0); + defaultFamily->SetColumnCodec(NKikimrSchemeOp::EColumnCodec::ColumnCodecPlain); + + for (ui32 i = 0; i < schema.ColumnsSize(); i++) { + if (!schema.GetColumns(i).HasColumnFamilyName() || !schema.GetColumns(i).HasColumnFamilyId()) { + mutableSchema->MutableColumns(i)->SetColumnFamilyName("default"); + mutableSchema->MutableColumns(i)->SetColumnFamilyId(0); + } + } + } + THolder Propose(const TString& owner, TOperationContext& context) override { const TTabletId ssId = context.SS->SelfTabletId(); const auto acceptExisted = !Transaction.GetFailOnExist(); const TString& parentPathStr = Transaction.GetWorkingDir(); - // Copy CreateColumnTable for changes. Update default sharding if not set. + // Copy CreateColumnTable for changes. Update default sharding if not set and add default family if not set. auto createDescription = Transaction.GetCreateColumnTable(); if (!createDescription.HasColumnShardCount()) { createDescription.SetColumnShardCount(TTableConstructorBase::DEFAULT_SHARDS_COUNT); @@ -698,6 +720,7 @@ class TCreateColumnTable: public TSubOperation { result->SetError(NKikimrScheme::StatusSchemeError, errStr); return result; } + AddDefaultFamilyIfNotExists(createDescription); TOlapTableConstructor tableConstructor; tableInfo = tableConstructor.BuildTableInfo(createDescription, context, errors); } @@ -811,6 +834,13 @@ class TCreateColumnTable: public TSubOperation { } NIceDb::TNiceDb db(context.GetDB()); + + for (const auto& tier : tableInfo->GetUsedTiers()) { + auto tierPath = TPath::Resolve(tier, context.SS); + AFL_VERIFY(tierPath.IsResolved())("path", tier); + context.SS->PersistExternalDataSourceReference(db, tierPath->PathId, dstPath); + } + context.SS->PersistTxState(db, OperationId); context.OnComplete.ActivateTx(OperationId); diff --git a/ydb/core/tx/schemeshard/olap/operations/drop_table.cpp b/ydb/core/tx/schemeshard/olap/operations/drop_table.cpp index 35857de22a90..57c36c39660c 100644 --- a/ydb/core/tx/schemeshard/olap/operations/drop_table.cpp +++ b/ydb/core/tx/schemeshard/olap/operations/drop_table.cpp @@ -266,14 +266,21 @@ class TProposedDeleteParts: public TSubOperationState { Y_ABORT_UNLESS(txState); Y_ABORT_UNLESS(txState->TxType == TTxState::TxDropColumnTable); + NIceDb::TNiceDb db(context.GetDB()); + bool isStandalone = false; { Y_ABORT_UNLESS(context.SS->ColumnTables.contains(txState->TargetPathId)); auto tableInfo = context.SS->ColumnTables.GetVerified(txState->TargetPathId); isStandalone = tableInfo->IsStandalone(); + + for (const auto& tier : tableInfo->GetUsedTiers()) { + auto tierPath = TPath::Resolve(tier, context.SS); + AFL_VERIFY(tierPath.IsResolved())("path", tier); + context.SS->PersistRemoveExternalDataSourceReference(db, tierPath->PathId, txState->TargetPathId); + } } - NIceDb::TNiceDb db(context.GetDB()); context.SS->PersistColumnTableRemove(db, txState->TargetPathId); if (isStandalone) { diff --git a/ydb/core/tx/schemeshard/olap/options/schema.cpp b/ydb/core/tx/schemeshard/olap/options/schema.cpp index 675247686b7f..59f9df521f34 100644 --- a/ydb/core/tx/schemeshard/olap/options/schema.cpp +++ b/ydb/core/tx/schemeshard/olap/options/schema.cpp @@ -4,33 +4,42 @@ namespace NKikimr::NSchemeShard { bool TOlapOptionsDescription::ApplyUpdate(const TOlapOptionsUpdate& schemaUpdate, IErrorCollector& /*errors*/) { SchemeNeedActualization = schemaUpdate.GetSchemeNeedActualization(); - if (!!schemaUpdate.GetExternalGuaranteeExclusivePK()) { - ExternalGuaranteeExclusivePK = *schemaUpdate.GetExternalGuaranteeExclusivePK(); + if (!!schemaUpdate.GetScanReaderPolicyName()) { + ScanReaderPolicyName = *schemaUpdate.GetScanReaderPolicyName(); } if (schemaUpdate.GetCompactionPlannerConstructor().HasObject()) { CompactionPlannerConstructor = schemaUpdate.GetCompactionPlannerConstructor(); } + if (schemaUpdate.GetMetadataManagerConstructor().HasObject()) { + MetadataManagerConstructor = schemaUpdate.GetMetadataManagerConstructor(); + } return true; } void TOlapOptionsDescription::Parse(const NKikimrSchemeOp::TColumnTableSchema& tableSchema) { SchemeNeedActualization = tableSchema.GetOptions().GetSchemeNeedActualization(); - if (tableSchema.GetOptions().HasExternalGuaranteeExclusivePK()) { - ExternalGuaranteeExclusivePK = tableSchema.GetOptions().GetExternalGuaranteeExclusivePK(); + if (tableSchema.GetOptions().HasScanReaderPolicyName()) { + ScanReaderPolicyName = tableSchema.GetOptions().GetScanReaderPolicyName(); } if (tableSchema.GetOptions().HasCompactionPlannerConstructor()) { AFL_VERIFY(CompactionPlannerConstructor.DeserializeFromProto(tableSchema.GetOptions().GetCompactionPlannerConstructor())); } + if (tableSchema.GetOptions().HasMetadataManagerConstructor()) { + AFL_VERIFY(MetadataManagerConstructor.DeserializeFromProto(tableSchema.GetOptions().GetMetadataManagerConstructor())); + } } void TOlapOptionsDescription::Serialize(NKikimrSchemeOp::TColumnTableSchema& tableSchema) const { tableSchema.MutableOptions()->SetSchemeNeedActualization(SchemeNeedActualization); - if (ExternalGuaranteeExclusivePK) { - tableSchema.MutableOptions()->SetExternalGuaranteeExclusivePK(ExternalGuaranteeExclusivePK); + if (ScanReaderPolicyName) { + tableSchema.MutableOptions()->SetScanReaderPolicyName(*ScanReaderPolicyName); } if (CompactionPlannerConstructor.HasObject()) { CompactionPlannerConstructor.SerializeToProto(*tableSchema.MutableOptions()->MutableCompactionPlannerConstructor()); } + if (MetadataManagerConstructor.HasObject()) { + MetadataManagerConstructor.SerializeToProto(*tableSchema.MutableOptions()->MutableMetadataManagerConstructor()); + } } bool TOlapOptionsDescription::Validate(const NKikimrSchemeOp::TColumnTableSchema& /*opSchema*/, IErrorCollector& /*errors*/) const { diff --git a/ydb/core/tx/schemeshard/olap/options/schema.h b/ydb/core/tx/schemeshard/olap/options/schema.h index 7387018423d6..070bd16437e7 100644 --- a/ydb/core/tx/schemeshard/olap/options/schema.h +++ b/ydb/core/tx/schemeshard/olap/options/schema.h @@ -8,8 +8,9 @@ class TOlapSchema; class TOlapOptionsDescription { private: YDB_READONLY(bool, SchemeNeedActualization, false); - YDB_READONLY(bool, ExternalGuaranteeExclusivePK, false); + YDB_READONLY_DEF(std::optional, ScanReaderPolicyName); YDB_READONLY_DEF(NOlap::NStorageOptimizer::TOptimizerPlannerConstructorContainer, CompactionPlannerConstructor); + YDB_READONLY_DEF(NOlap::NDataAccessorControl::TMetadataManagerConstructorContainer, MetadataManagerConstructor); public: bool ApplyUpdate(const TOlapOptionsUpdate& schemaUpdate, IErrorCollector& errors); diff --git a/ydb/core/tx/schemeshard/olap/options/update.h b/ydb/core/tx/schemeshard/olap/options/update.h index 9e8360ec0dd0..14dbd96cbd33 100644 --- a/ydb/core/tx/schemeshard/olap/options/update.h +++ b/ydb/core/tx/schemeshard/olap/options/update.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -11,13 +12,22 @@ namespace NKikimr::NSchemeShard { class TOlapOptionsUpdate { private: YDB_ACCESSOR(bool, SchemeNeedActualization, false); - YDB_ACCESSOR_DEF(std::optional, ExternalGuaranteeExclusivePK); + YDB_ACCESSOR_DEF(std::optional, ScanReaderPolicyName); YDB_ACCESSOR_DEF(NOlap::NStorageOptimizer::TOptimizerPlannerConstructorContainer, CompactionPlannerConstructor); + YDB_ACCESSOR_DEF(NOlap::NDataAccessorControl::TMetadataManagerConstructorContainer, MetadataManagerConstructor); public: bool Parse(const NKikimrSchemeOp::TAlterColumnTableSchema& alterRequest, IErrorCollector& errors) { SchemeNeedActualization = alterRequest.GetOptions().GetSchemeNeedActualization(); - if (alterRequest.GetOptions().HasExternalGuaranteeExclusivePK()) { - ExternalGuaranteeExclusivePK = alterRequest.GetOptions().GetExternalGuaranteeExclusivePK(); + if (alterRequest.GetOptions().HasScanReaderPolicyName()) { + ScanReaderPolicyName = alterRequest.GetOptions().GetScanReaderPolicyName(); + } + if (alterRequest.GetOptions().HasMetadataManagerConstructor()) { + auto container = NOlap::NDataAccessorControl::TMetadataManagerConstructorContainer::BuildFromProto(alterRequest.GetOptions().GetMetadataManagerConstructor()); + if (container.IsFail()) { + errors.AddError(container.GetErrorMessage()); + return false; + } + MetadataManagerConstructor = container.DetachResult(); } if (alterRequest.GetOptions().HasCompactionPlannerConstructor()) { auto container = NOlap::NStorageOptimizer::TOptimizerPlannerConstructorContainer::BuildFromProto(alterRequest.GetOptions().GetCompactionPlannerConstructor()); @@ -31,12 +41,15 @@ class TOlapOptionsUpdate { } void SerializeToProto(NKikimrSchemeOp::TAlterColumnTableSchema& alterRequest) const { alterRequest.MutableOptions()->SetSchemeNeedActualization(SchemeNeedActualization); - if (ExternalGuaranteeExclusivePK) { - alterRequest.MutableOptions()->SetExternalGuaranteeExclusivePK(*ExternalGuaranteeExclusivePK); + if (ScanReaderPolicyName) { + alterRequest.MutableOptions()->SetScanReaderPolicyName(*ScanReaderPolicyName); } if (CompactionPlannerConstructor.HasObject()) { CompactionPlannerConstructor.SerializeToProto(*alterRequest.MutableOptions()->MutableCompactionPlannerConstructor()); } + if (MetadataManagerConstructor.HasObject()) { + MetadataManagerConstructor.SerializeToProto(*alterRequest.MutableOptions()->MutableMetadataManagerConstructor()); + } } }; } diff --git a/ydb/core/tx/schemeshard/olap/schema/schema.cpp b/ydb/core/tx/schemeshard/olap/schema/schema.cpp index dd1889779c1e..82e2a499bdd9 100644 --- a/ydb/core/tx/schemeshard/olap/schema/schema.cpp +++ b/ydb/core/tx/schemeshard/olap/schema/schema.cpp @@ -1,88 +1,22 @@ #include "schema.h" + #include +#include namespace NKikimr::NSchemeShard { -namespace { -static inline bool IsDropped(const TOlapColumnsDescription::TColumn& col) { - Y_UNUSED(col); - return false; -} - -static inline ui32 GetType(const TOlapColumnsDescription::TColumn& col) { - Y_ABORT_UNLESS(col.GetType().GetTypeId() != NScheme::NTypeIds::Pg, "pg types are not supported"); - return col.GetType().GetTypeId(); -} - -} - -static bool ValidateColumnTableTtl(const NKikimrSchemeOp::TColumnDataLifeCycle::TTtl& ttl, - const THashMap& sourceColumns, - const THashMap& alterColumns, - const THashMap& colName2Id, - IErrorCollector& errors) { - const TString colName = ttl.GetColumnName(); - - auto it = colName2Id.find(colName); - if (it == colName2Id.end()) { - errors.AddError(Sprintf("Cannot enable TTL on unknown column: '%s'", colName.data())); - return false; - } - - const TOlapColumnsDescription::TColumn* column = nullptr; - const ui32 colId = it->second; - if (alterColumns.contains(colId)) { - column = &alterColumns.at(colId); - } else if (sourceColumns.contains(colId)) { - column = &sourceColumns.at(colId); - } else { - Y_ABORT_UNLESS("Unknown column"); - } - - if (IsDropped(*column)) { - errors.AddError(Sprintf("Cannot enable TTL on dropped column: '%s'", colName.data())); - return false; - } - - if (ttl.HasExpireAfterBytes()) { - errors.AddError("TTL with eviction by size is not supported yet"); - return false; - } - - if (!ttl.HasExpireAfterSeconds()) { - errors.AddError("TTL without eviction time"); - return false; - } - - auto unit = ttl.GetColumnUnit(); - - switch (GetType(*column)) { - case NScheme::NTypeIds::DyNumber: - errors.AddError("Unsupported column type for TTL in column tables"); - return false; - default: - break; - } - - TString errStr; - if (!NValidation::TTTLValidator::ValidateUnit(GetType(*column), unit, errStr)) { - errors.AddError(errStr); - return false; - } - return true; -} - -bool TOlapSchema::ValidateTtlSettings(const NKikimrSchemeOp::TColumnDataLifeCycle& ttl, IErrorCollector& errors) const { +bool TOlapSchema::ValidateTtlSettings( + const NKikimrSchemeOp::TColumnDataLifeCycle& ttl, const TOperationContext& context, IErrorCollector& errors) const { using TTtlProto = NKikimrSchemeOp::TColumnDataLifeCycle; switch (ttl.GetStatusCase()) { - case TTtlProto::kEnabled: + case TTtlProto::kEnabled: { const auto* column = Columns.GetByName(ttl.GetEnabled().GetColumnName()); if (!column) { errors.AddError("Incorrect ttl column - not found in scheme"); return false; } - return ValidateColumnTableTtl(ttl.GetEnabled(), {}, Columns.GetColumns(), Columns.GetColumnsByName(), errors); + return TTTLValidator::ValidateColumnTableTtl(ttl.GetEnabled(), Indexes, {}, Columns.GetColumns(), Columns.GetColumnsByName(), context, errors); } case TTtlProto::kDisabled: default: @@ -93,25 +27,20 @@ bool TOlapSchema::ValidateTtlSettings(const NKikimrSchemeOp::TColumnDataLifeCycl } bool TOlapSchema::Update(const TOlapSchemaUpdate& schemaUpdate, IErrorCollector& errors) { - if (!Columns.ApplyUpdate(schemaUpdate.GetColumns(), errors, NextColumnId)) { + if (!ColumnFamilies.ApplyUpdate(schemaUpdate.GetColumnFamilies(), errors, NextColumnFamilyId)) { return false; } - if (!Indexes.ApplyUpdate(*this, schemaUpdate.GetIndexes(), errors, NextColumnId)) { + if (!Columns.ApplyUpdate(schemaUpdate.GetColumns(), ColumnFamilies, errors, NextColumnId)) { return false; } - if (!Options.ApplyUpdate(schemaUpdate.GetOptions(), errors)) { + if (!Indexes.ApplyUpdate(*this, schemaUpdate.GetIndexes(), errors, NextColumnId)) { return false; } - if (!HasEngine()) { - Engine = schemaUpdate.GetEngineDef(NKikimrSchemeOp::COLUMN_ENGINE_REPLACING_TIMESERIES); - } else { - if (schemaUpdate.HasEngine()) { - errors.AddError(NKikimrScheme::StatusSchemeError, "No engine updates supported"); - return false; - } + if (!Options.ApplyUpdate(schemaUpdate.GetOptions(), errors)) { + return false; } ++Version; @@ -120,10 +49,10 @@ bool TOlapSchema::Update(const TOlapSchemaUpdate& schemaUpdate, IErrorCollector& void TOlapSchema::ParseFromLocalDB(const NKikimrSchemeOp::TColumnTableSchema& tableSchema) { NextColumnId = tableSchema.GetNextColumnId(); + NextColumnFamilyId = tableSchema.GetNextColumnFamilyId(); Version = tableSchema.GetVersion(); - Y_ABORT_UNLESS(tableSchema.HasEngine()); - Engine = tableSchema.GetEngine(); + ColumnFamilies.Parse(tableSchema); Columns.Parse(tableSchema); Indexes.Parse(tableSchema); Options.Parse(tableSchema); @@ -132,11 +61,10 @@ void TOlapSchema::ParseFromLocalDB(const NKikimrSchemeOp::TColumnTableSchema& ta void TOlapSchema::Serialize(NKikimrSchemeOp::TColumnTableSchema& tableSchemaExt) const { NKikimrSchemeOp::TColumnTableSchema resultLocal; resultLocal.SetNextColumnId(NextColumnId); + resultLocal.SetNextColumnFamilyId(NextColumnFamilyId); resultLocal.SetVersion(Version); - Y_ABORT_UNLESS(HasEngine()); - resultLocal.SetEngine(GetEngineUnsafe()); - + ColumnFamilies.Serialize(resultLocal); Columns.Serialize(resultLocal); Indexes.Serialize(resultLocal); Options.Serialize(resultLocal); @@ -155,11 +83,6 @@ bool TOlapSchema::Validate(const NKikimrSchemeOp::TColumnTableSchema& opSchema, if (!Options.Validate(opSchema, errors)) { return false; } - - if (opSchema.GetEngine() != Engine) { - errors.AddError("Specified schema engine does not match schema preset"); - return false; - } return true; } diff --git a/ydb/core/tx/schemeshard/olap/schema/schema.h b/ydb/core/tx/schemeshard/olap/schema/schema.h index f800750341fa..9e950b36c99e 100644 --- a/ydb/core/tx/schemeshard/olap/schema/schema.h +++ b/ydb/core/tx/schemeshard/olap/schema/schema.h @@ -1,22 +1,29 @@ #pragma once -#include -#include +#include "update.h" + +#include #include +#include #include +#include #include -#include "update.h" + +namespace NKikimr::NSchemeShard { +struct TOperationContext; +} namespace NKikimr::NSchemeShard { class TOlapSchema { private: - YDB_READONLY_OPT(NKikimrSchemeOp::EColumnTableEngine, Engine); YDB_READONLY_DEF(TOlapColumnsDescription, Columns); YDB_READONLY_DEF(TOlapIndexesDescription, Indexes); YDB_READONLY_DEF(TOlapOptionsDescription, Options); + YDB_READONLY_DEF(TOlapColumnFamiliesDescription, ColumnFamilies); YDB_READONLY(ui32, NextColumnId, 1); YDB_READONLY(ui32, Version, 0); + YDB_READONLY(ui32, NextColumnFamilyId, 1); public: bool Update(const TOlapSchemaUpdate& schemaUpdate, IErrorCollector& errors); @@ -24,7 +31,7 @@ namespace NKikimr::NSchemeShard { void ParseFromLocalDB(const NKikimrSchemeOp::TColumnTableSchema& tableSchema); void Serialize(NKikimrSchemeOp::TColumnTableSchema& tableSchema) const; bool Validate(const NKikimrSchemeOp::TColumnTableSchema& opSchema, IErrorCollector& errors) const; - bool ValidateTtlSettings(const NKikimrSchemeOp::TColumnDataLifeCycle& ttlSettings, IErrorCollector& errors) const; + bool ValidateTtlSettings(const NKikimrSchemeOp::TColumnDataLifeCycle& ttlSettings, const TOperationContext& context, IErrorCollector& errors) const; }; class TOlapStoreSchemaPreset: public TOlapSchema { diff --git a/ydb/core/tx/schemeshard/olap/schema/update.cpp b/ydb/core/tx/schemeshard/olap/schema/update.cpp index 3b0087e3b756..0414d14bec47 100644 --- a/ydb/core/tx/schemeshard/olap/schema/update.cpp +++ b/ydb/core/tx/schemeshard/olap/schema/update.cpp @@ -2,31 +2,35 @@ namespace NKikimr::NSchemeShard { - bool TOlapSchemaUpdate::Parse(const NKikimrSchemeOp::TColumnTableSchema& tableSchema, IErrorCollector& errors, bool allowNullKeys) { - if (tableSchema.HasEngine()) { - Engine = tableSchema.GetEngine(); - } - - if (!Columns.Parse(tableSchema, errors, allowNullKeys)) { - return false; - } +bool TOlapSchemaUpdate::Parse(const NKikimrSchemeOp::TColumnTableSchema& tableSchema, IErrorCollector& errors, bool allowNullKeys) { + if (!ColumnFamilies.Parse(tableSchema, errors)) { + return false; + } - return true; + if (!Columns.Parse(tableSchema, errors, allowNullKeys)) { + return false; } - bool TOlapSchemaUpdate::Parse(const NKikimrSchemeOp::TAlterColumnTableSchema& alterRequest, IErrorCollector& errors) { - if (!Columns.Parse(alterRequest, errors)) { - return false; - } + return true; +} - if (!Indexes.Parse(alterRequest, errors)) { - return false; - } +bool TOlapSchemaUpdate::Parse(const NKikimrSchemeOp::TAlterColumnTableSchema& alterRequest, IErrorCollector& errors) { + if (!ColumnFamilies.Parse(alterRequest, errors)) { + return false; + } - if (!Options.Parse(alterRequest, errors)) { - return false; - } + if (!Columns.Parse(alterRequest, errors)) { + return false; + } - return true; + if (!Indexes.Parse(alterRequest, errors)) { + return false; } + + if (!Options.Parse(alterRequest, errors)) { + return false; + } + + return true; +} } diff --git a/ydb/core/tx/schemeshard/olap/schema/update.h b/ydb/core/tx/schemeshard/olap/schema/update.h index 0cd98c09b3c1..70137493c168 100644 --- a/ydb/core/tx/schemeshard/olap/schema/update.h +++ b/ydb/core/tx/schemeshard/olap/schema/update.h @@ -1,18 +1,21 @@ #pragma once -#include -#include +#include #include #include +#include + +#include namespace NKikimr::NSchemeShard { - class TOlapSchemaUpdate { - YDB_READONLY_DEF(TOlapColumnsUpdate, Columns); - YDB_READONLY_DEF(TOlapIndexesUpdate, Indexes); - YDB_READONLY_DEF(TOlapOptionsUpdate, Options); - YDB_READONLY_OPT(NKikimrSchemeOp::EColumnTableEngine, Engine); - public: - bool Parse(const NKikimrSchemeOp::TColumnTableSchema& tableSchema, IErrorCollector& errors, bool allowNullKeys = false); - bool Parse(const NKikimrSchemeOp::TAlterColumnTableSchema& alterRequest, IErrorCollector& errors); - }; +class TOlapSchemaUpdate { + YDB_READONLY_DEF(TOlapColumnsUpdate, Columns); + YDB_READONLY_DEF(TOlapIndexesUpdate, Indexes); + YDB_READONLY_DEF(TOlapOptionsUpdate, Options); + YDB_READONLY_DEF(TOlapColumnFamiliesUpdate, ColumnFamilies); + +public: + bool Parse(const NKikimrSchemeOp::TColumnTableSchema& tableSchema, IErrorCollector& errors, bool allowNullKeys); + bool Parse(const NKikimrSchemeOp::TAlterColumnTableSchema& alterRequest, IErrorCollector& errors); +}; } diff --git a/ydb/core/tx/schemeshard/olap/schema/ya.make b/ydb/core/tx/schemeshard/olap/schema/ya.make index 76b2d2d1c801..77e32c36b52c 100644 --- a/ydb/core/tx/schemeshard/olap/schema/ya.make +++ b/ydb/core/tx/schemeshard/olap/schema/ya.make @@ -6,6 +6,7 @@ SRCS( ) PEERDIR( + ydb/core/tx/schemeshard/olap/column_families ydb/core/tx/schemeshard/olap/columns ydb/core/tx/schemeshard/olap/indexes ydb/core/tx/schemeshard/olap/options diff --git a/ydb/core/tx/schemeshard/olap/store/store.cpp b/ydb/core/tx/schemeshard/olap/store/store.cpp index 29750028c122..32f01d231d70 100644 --- a/ydb/core/tx/schemeshard/olap/store/store.cpp +++ b/ydb/core/tx/schemeshard/olap/store/store.cpp @@ -132,6 +132,11 @@ bool TOlapStoreInfo::ParseFromRequest(const NKikimrSchemeOp::TColumnStoreDescrip return false; } + if (descriptionProto.SchemaPresetsSize() > 1) { + errors.AddError("trying to create an OLAP store with multiple schema presets (not supported yet)"); + return false; + } + Name = descriptionProto.GetName(); StorageConfig = descriptionProto.GetStorageConfig(); // Make it easier by having data channel count always specified internally @@ -153,7 +158,7 @@ bool TOlapStoreInfo::ParseFromRequest(const NKikimrSchemeOp::TColumnStoreDescrip preset.SetProtoIndex(protoIndex++); TOlapSchemaUpdate schemaDiff; - if (!schemaDiff.Parse(presetProto.GetSchema(), errors)) { + if (!schemaDiff.Parse(presetProto.GetSchema(), errors, AppData()->ColumnShardConfig.GetAllowNullableColumnsInPK())) { return false; } diff --git a/ydb/core/tx/schemeshard/olap/table/table.h b/ydb/core/tx/schemeshard/olap/table/table.h index a092e175e25d..8a4d665d6fc3 100644 --- a/ydb/core/tx/schemeshard/olap/table/table.h +++ b/ydb/core/tx/schemeshard/olap/table/table.h @@ -49,6 +49,16 @@ struct TColumnTableInfo { } } + THashSet GetUsedTiers() const { + THashSet tiers; + for (const auto& tier : Description.GetTtlSettings().GetEnabled().GetTiers()) { + if (tier.HasEvictToExternalStorage()) { + tiers.emplace(tier.GetEvictToExternalStorage().GetStorage()); + } + } + return tiers; + } + NKikimrSchemeOp::TColumnTableDescription Description; TMaybe StandaloneSharding; TMaybe AlterBody; diff --git a/ydb/core/tx/schemeshard/olap/ttl/schema.cpp b/ydb/core/tx/schemeshard/olap/ttl/schema.cpp index 379f35012d24..f1b6e73c93ff 100644 --- a/ydb/core/tx/schemeshard/olap/ttl/schema.cpp +++ b/ydb/core/tx/schemeshard/olap/ttl/schema.cpp @@ -5,9 +5,6 @@ namespace NKikimr::NSchemeShard::NOlap::NAlter { TConclusionStatus TOlapTTL::Update(const TOlapTTLUpdate& update) { const ui64 currentTtlVersion = Proto.GetVersion(); const auto& ttlUpdate = update.GetPatch(); - if (ttlUpdate.HasUseTiering()) { - Proto.SetUseTiering(ttlUpdate.GetUseTiering()); - } if (ttlUpdate.HasEnabled()) { *Proto.MutableEnabled() = ttlUpdate.GetEnabled(); } diff --git a/ydb/core/tx/schemeshard/olap/ttl/validator.cpp b/ydb/core/tx/schemeshard/olap/ttl/validator.cpp new file mode 100644 index 000000000000..9df36eead54b --- /dev/null +++ b/ydb/core/tx/schemeshard/olap/ttl/validator.cpp @@ -0,0 +1,127 @@ +#include "validator.h" + +#include +#include +#include + +namespace NKikimr::NSchemeShard { + +namespace { +static inline bool IsDropped(const TOlapColumnsDescription::TColumn& col) { + Y_UNUSED(col); + return false; +} + +static inline NScheme::TTypeInfo GetType(const TOlapColumnsDescription::TColumn& col) { + return col.GetType(); +} + +} + +bool TTTLValidator::ValidateColumnTableTtl(const NKikimrSchemeOp::TColumnDataLifeCycle::TTtl& ttl, const TOlapIndexesDescription& indexes, const THashMap& sourceColumns, const THashMap& alterColumns, const THashMap& colName2Id, const TOperationContext& context, IErrorCollector& errors) { + const TString colName = ttl.GetColumnName(); + + auto it = colName2Id.find(colName); + if (it == colName2Id.end()) { + errors.AddError(Sprintf("Cannot enable TTL on unknown column: '%s'", colName.data())); + return false; + } + + const TOlapColumnsDescription::TColumn* column = nullptr; + const ui32 colId = it->second; + if (alterColumns.contains(colId)) { + column = &alterColumns.at(colId); + } else if (sourceColumns.contains(colId)) { + column = &sourceColumns.at(colId); + } else { + Y_ABORT_UNLESS("Unknown column"); + } + + if (IsDropped(*column)) { + errors.AddError(Sprintf("Cannot enable TTL on dropped column: '%s'", colName.data())); + return false; + } + + if (ttl.HasExpireAfterBytes()) { + errors.AddError("TTL with eviction by size is not supported yet"); + return false; + } + + auto unit = ttl.GetColumnUnit(); + + const auto& columnType = GetType(*column); + switch (columnType.GetTypeId()) { + case NScheme::NTypeIds::DyNumber: + case NScheme::NTypeIds::Pg: + errors.AddError("Unsupported column type for TTL in column tables"); + return false; + default: + break; + } + + TString errStr; + if (!NValidation::TTTLValidator::ValidateUnit(columnType.GetTypeId(), unit, errStr)) { + errors.AddError(errStr); + return false; + } + if (!NValidation::TTTLValidator::ValidateTiers(ttl.GetTiers(), errStr)) { + errors.AddError(errStr); + return false; + } + if (!AppDataVerified().FeatureFlags.GetEnableTieringInColumnShard()) { + for (const auto& tier : ttl.GetTiers()) { + if (tier.HasEvictToExternalStorage()) { + errors.AddError(NKikimrScheme::StatusPreconditionFailed, "Tiering functionality is disabled for OLAP tables"); + return false; + } + } + } + { + bool correct = false; + if (column->GetKeyOrder() && *column->GetKeyOrder() == 0) { + correct = true; + } else { + for (auto&& [_, i] : indexes.GetIndexes()) { + auto proto = i.GetIndexMeta().SerializeToProto(); + if (proto.HasMaxIndex() && proto.GetMaxIndex().GetColumnId() == column->GetId()) { + correct = true; + break; + } + } + } + if (!correct) { + errors.AddError("Haven't MAX-index for TTL column and TTL column is not first column in primary key"); + return false; + } + } + + for (const auto& tier : ttl.GetTiers()) { + if (!tier.HasEvictToExternalStorage()) { + continue; + } + const TString& tierPathString = tier.GetEvictToExternalStorage().GetStorage(); + TPath tierPath = TPath::Resolve(tierPathString, context.SS); + if (!tierPath.IsResolved() || tierPath.IsDeleted() || tierPath.IsUnderDeleting()) { + errors.AddError("Object not found: " + tierPathString); + return false; + } + if (!tierPath->IsExternalDataSource()) { + errors.AddError("Not an external data source: " + tierPathString); + return false; + } + { + auto* findExternalDataSource = context.SS->ExternalDataSources.FindPtr(tierPath->PathId); + AFL_VERIFY(findExternalDataSource); + NKikimrSchemeOp::TExternalDataSourceDescription proto; + (*findExternalDataSource)->FillProto(proto); + if (auto status = NColumnShard::NTiers::TTierConfig().DeserializeFromProto(proto); status.IsFail()) { + errors.AddError("Cannot use external data source \"" + tierPathString + "\" for tiering: " + status.GetErrorMessage()); + return false; + } + } + } + + return true; +} + +} diff --git a/ydb/core/tx/schemeshard/olap/ttl/validator.h b/ydb/core/tx/schemeshard/olap/ttl/validator.h new file mode 100644 index 000000000000..39207bbaf71e --- /dev/null +++ b/ydb/core/tx/schemeshard/olap/ttl/validator.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include + +namespace NKikimr::NSchemeShard { +struct TOperationContext; +} + +namespace NKikimr::NSchemeShard { + class TTTLValidator { + public: + static bool ValidateColumnTableTtl(const NKikimrSchemeOp::TColumnDataLifeCycle::TTtl& ttl, const TOlapIndexesDescription& indexes, + const THashMap& sourceColumns, + const THashMap& alterColumns, + const THashMap& colName2Id, const TOperationContext& context, + IErrorCollector& errors); + + }; +} diff --git a/ydb/core/tx/schemeshard/olap/ttl/ya.make b/ydb/core/tx/schemeshard/olap/ttl/ya.make index 8aea246ebddf..3d84cb700fb2 100644 --- a/ydb/core/tx/schemeshard/olap/ttl/ya.make +++ b/ydb/core/tx/schemeshard/olap/ttl/ya.make @@ -3,11 +3,13 @@ LIBRARY() SRCS( schema.cpp update.cpp + validator.cpp ) PEERDIR( ydb/core/base ydb/core/protos + ydb/core/tx/tiering/tier ) YQL_LAST_ABI_VERSION() diff --git a/ydb/core/tx/schemeshard/olap/ya.make b/ydb/core/tx/schemeshard/olap/ya.make index 4fde54f9fbd0..24a993e32826 100644 --- a/ydb/core/tx/schemeshard/olap/ya.make +++ b/ydb/core/tx/schemeshard/olap/ya.make @@ -13,6 +13,7 @@ PEERDIR( ydb/core/tx/schemeshard/olap/store ydb/core/tx/schemeshard/olap/table ydb/core/tx/schemeshard/olap/ttl + ydb/core/tx/schemeshard/olap/column_families ) END() diff --git a/ydb/core/tx/schemeshard/schemeshard__conditional_erase.cpp b/ydb/core/tx/schemeshard/schemeshard__conditional_erase.cpp index 08b812a07dc8..62c4ed28ef76 100644 --- a/ydb/core/tx/schemeshard/schemeshard__conditional_erase.cpp +++ b/ydb/core/tx/schemeshard/schemeshard__conditional_erase.cpp @@ -141,8 +141,16 @@ struct TSchemeShard::TTxRunConditionalErase: public TSchemeShard::TRwTxBase { } const auto& settings = tableInfo->TTLSettings().GetEnabled(); - const TDuration expireAfter = TDuration::Seconds(settings.GetExpireAfterSeconds()); - const TInstant wallClock = ctx.Now() - expireAfter; + + auto expireAfter = GetExpireAfter(settings, true); + if (expireAfter.IsFail()) { + LOG_WARN_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "Invalid TTL settings: " << expireAfter.GetErrorMessage() + << ": shardIdx: " << tableShardInfo.ShardIdx << ": pathId: " << shardInfo.PathId + << ", at schemeshard: " << Self->TabletID()); + return false; + } + const TInstant wallClock = ctx.Now() - *expireAfter; NKikimrTxDataShard::TEvConditionalEraseRowsRequest request; request.SetTableId(shardInfo.PathId.LocalPathId); diff --git a/ydb/core/tx/schemeshard/schemeshard__operation_alter_external_data_source.cpp b/ydb/core/tx/schemeshard/schemeshard__operation_alter_external_data_source.cpp index 1009c5dff470..e80fe117bdd7 100644 --- a/ydb/core/tx/schemeshard/schemeshard__operation_alter_external_data_source.cpp +++ b/ydb/core/tx/schemeshard/schemeshard__operation_alter_external_data_source.cpp @@ -3,6 +3,8 @@ #include "schemeshard__operation_common.h" #include "schemeshard_impl.h" +#include + #include namespace { @@ -229,9 +231,7 @@ class TAlterExternalDataSource : public TSubOperation { RETURN_RESULT_UNLESS(IsDestinationPathValid(result, dstPath, acl)); RETURN_RESULT_UNLESS(IsApplyIfChecksPassed(result, context)); - RETURN_RESULT_UNLESS(IsDescriptionValid(result, - externalDataSourceDescription, - context.SS->ExternalSourceFactory)); + RETURN_RESULT_UNLESS(IsDescriptionValid(result, externalDataSourceDescription, context.SS->ExternalSourceFactory)); const auto oldExternalDataSourceInfo = context.SS->ExternalDataSources.Value(dstPath->PathId, nullptr); @@ -241,6 +241,24 @@ class TAlterExternalDataSource : public TSubOperation { oldExternalDataSourceInfo->AlterVersion + 1); Y_ABORT_UNLESS(externalDataSourceInfo); + { + bool isTieredStorage = false; + for (const auto& referrer : externalDataSourceInfo->ExternalTableReferences.GetReferences()) { + if (TPath::Init(PathIdFromPathId(referrer.GetPathId()), context.SS)->PathType == + NKikimrSchemeOp::EPathType::EPathTypeColumnTable) { + isTieredStorage = true; + break; + } + } + if (isTieredStorage) { + if (auto status = NColumnShard::NTiers::TTierConfig().DeserializeFromProto(externalDataSourceDescription); status.IsFail()) { + result->SetError(NKikimrScheme::StatusInvalidParameter, + "Cannot make this change while the external data source is used as a tiered storage: " + status.GetErrorMessage()); + return result; + } + } + } + AddPathInSchemeShard(result, dstPath); const TPathElement::TPtr externalDataSource = ReplaceExternalDataSourcePathElement(dstPath); diff --git a/ydb/core/tx/schemeshard/schemeshard_impl.cpp b/ydb/core/tx/schemeshard/schemeshard_impl.cpp index d3b4e50b9177..89e52d28efd8 100644 --- a/ydb/core/tx/schemeshard/schemeshard_impl.cpp +++ b/ydb/core/tx/schemeshard/schemeshard_impl.cpp @@ -2941,6 +2941,31 @@ void TSchemeShard::PersistRemoveExternalDataSource(NIceDb::TNiceDb& db, TPathId db.Table().Key(pathId.OwnerId, pathId.LocalPathId).Delete(); } +void TSchemeShard::PersistExternalDataSourceReference(NIceDb::TNiceDb& db, TPathId pathId, const TPath& referrer) { + auto findSource = ExternalDataSources.FindPtr(pathId); + Y_ABORT_UNLESS(findSource); + auto* ref = (*findSource)->ExternalTableReferences.AddReferences(); + ref->SetPath(referrer.PathString()); + PathIdFromPathId(referrer->PathId, ref->MutablePathId()); + db.Table() + .Key(pathId.OwnerId, pathId.LocalPathId) + .Update( + NIceDb::TUpdate{ (*findSource)->ExternalTableReferences.SerializeAsString() }); +} + +void TSchemeShard::PersistRemoveExternalDataSourceReference(NIceDb::TNiceDb& db, TPathId pathId, TPathId referrer) { + auto findSource = ExternalDataSources.FindPtr(pathId); + Y_ABORT_UNLESS(findSource); + EraseIf(*(*findSource)->ExternalTableReferences.MutableReferences(), + [referrer](const NKikimrSchemeOp::TExternalTableReferences::TReference& reference) { + return PathIdFromPathId(reference.GetPathId()) == referrer; + }); + db.Table() + .Key(pathId.OwnerId, pathId.LocalPathId) + .Update( + NIceDb::TUpdate{ (*findSource)->ExternalTableReferences.SerializeAsString() }); +} + void TSchemeShard::PersistView(NIceDb::TNiceDb &db, TPathId pathId) { Y_ABORT_UNLESS(IsLocalId(pathId)); diff --git a/ydb/core/tx/schemeshard/schemeshard_impl.h b/ydb/core/tx/schemeshard/schemeshard_impl.h index 3615c8932ddf..b594a6a4967d 100644 --- a/ydb/core/tx/schemeshard/schemeshard_impl.h +++ b/ydb/core/tx/schemeshard/schemeshard_impl.h @@ -813,6 +813,8 @@ class TSchemeShard // ExternalDataSource void PersistExternalDataSource(NIceDb::TNiceDb &db, TPathId pathId, const TExternalDataSourceInfo::TPtr externalDataSource); void PersistRemoveExternalDataSource(NIceDb::TNiceDb& db, TPathId pathId); + void PersistExternalDataSourceReference(NIceDb::TNiceDb &db, TPathId pathId, const TPath& referrer); + void PersistRemoveExternalDataSourceReference(NIceDb::TNiceDb &db, TPathId pathId, TPathId referrer); void PersistView(NIceDb::TNiceDb &db, TPathId pathId); void PersistRemoveView(NIceDb::TNiceDb& db, TPathId pathId); diff --git a/ydb/core/tx/schemeshard/schemeshard_info_types.h b/ydb/core/tx/schemeshard/schemeshard_info_types.h index 529373a01824..504e860012a7 100644 --- a/ydb/core/tx/schemeshard/schemeshard_info_types.h +++ b/ydb/core/tx/schemeshard/schemeshard_info_types.h @@ -3290,6 +3290,15 @@ struct TExternalDataSourceInfo: TSimpleRefCount { NKikimrSchemeOp::TAuth Auth; NKikimrSchemeOp::TExternalTableReferences ExternalTableReferences; NKikimrSchemeOp::TExternalDataSourceProperties Properties; + + void FillProto(NKikimrSchemeOp::TExternalDataSourceDescription& proto) const { + proto.SetVersion(AlterVersion); + proto.SetSourceType(SourceType); + proto.SetLocation(Location); + proto.SetInstallation(Installation); + proto.MutableAuth()->CopyFrom(Auth); + proto.MutableProperties()->CopyFrom(Properties); + } }; struct TViewInfo : TSimpleRefCount { @@ -3312,6 +3321,12 @@ bool ValidateTtlSettings(const NKikimrSchemeOp::TTTLSettings& ttl, const THashMap& alterColumns, const THashMap& colName2Id, const TSubDomainInfo& subDomain, TString& errStr); + +TConclusion GetExpireAfter(const NKikimrSchemeOp::TTTLSettings::TEnabled& settings, const bool allowNonDeleteTiers); + +std::optional> ValidateSequenceType(const TString& sequenceName, const TString& dataType, + const NKikimr::NScheme::TTypeRegistry& typeRegistry, bool pgTypesEnabled, TString& errStr); + } } diff --git a/ydb/core/tx/schemeshard/schemeshard_path_describer.cpp b/ydb/core/tx/schemeshard/schemeshard_path_describer.cpp index f3ec22bd1884..6c0b8bbc7ee6 100644 --- a/ydb/core/tx/schemeshard/schemeshard_path_describer.cpp +++ b/ydb/core/tx/schemeshard/schemeshard_path_describer.cpp @@ -1019,12 +1019,7 @@ void TPathDescriber::DescribeExternalDataSource(const TActorContext&, TPathId pa auto entry = Result->Record.MutablePathDescription()->MutableExternalDataSourceDescription(); entry->SetName(pathEl->Name); PathIdFromPathId(pathId, entry->MutablePathId()); - entry->SetVersion(externalDataSourceInfo->AlterVersion); - entry->SetSourceType(externalDataSourceInfo->SourceType); - entry->SetLocation(externalDataSourceInfo->Location); - entry->SetInstallation(externalDataSourceInfo->Installation); - entry->MutableAuth()->CopyFrom(externalDataSourceInfo->Auth); - entry->MutableProperties()->CopyFrom(externalDataSourceInfo->Properties); + externalDataSourceInfo->FillProto(*entry); } void TPathDescriber::DescribeView(const TActorContext&, TPathId pathId, TPathElement::TPtr pathEl) { diff --git a/ydb/core/tx/schemeshard/schemeshard_schema.h b/ydb/core/tx/schemeshard/schemeshard_schema.h index 792aea95a4cd..b644345fc2a8 100644 --- a/ydb/core/tx/schemeshard/schemeshard_schema.h +++ b/ydb/core/tx/schemeshard/schemeshard_schema.h @@ -1746,7 +1746,7 @@ struct Schema : NIceDb::Schema { struct Location : Column<5, NScheme::NTypeIds::Utf8> {}; struct Installation : Column<6, NScheme::NTypeIds::Utf8> {}; struct Auth : Column<7, NScheme::NTypeIds::String> {}; - struct ExternalTableReferences : Column<8, NScheme::NTypeIds::String> {}; + struct ExternalTableReferences : Column<8, NScheme::NTypeIds::String> {}; // references from any scheme objects struct Properties : Column<9, NScheme::NTypeIds::String> {}; using TKey = TableKey; diff --git a/ydb/core/tx/schemeshard/schemeshard_validate_ttl.cpp b/ydb/core/tx/schemeshard/schemeshard_validate_ttl.cpp index 997001bafdc7..2b84efff5933 100644 --- a/ydb/core/tx/schemeshard/schemeshard_validate_ttl.cpp +++ b/ydb/core/tx/schemeshard/schemeshard_validate_ttl.cpp @@ -1,7 +1,6 @@ #include "schemeshard_info_types.h" #include "common/validation.h" -#include "olap/columns/schema.h" #include @@ -59,8 +58,18 @@ bool ValidateTtlSettings(const NKikimrSchemeOp::TTTLSettings& ttl, return false; } + if (!NValidation::TTTLValidator::ValidateTiers(enabled.GetTiers(), errStr)) { + return false; + } + + const auto expireAfter = GetExpireAfter(enabled, false); + if (expireAfter.IsFail()) { + errStr = expireAfter.GetErrorMessage(); + return false; + } + const TInstant now = TInstant::Now(); - if (enabled.GetExpireAfterSeconds() > now.Seconds()) { + if (expireAfter->Seconds() > now.Seconds()) { errStr = Sprintf("TTL should be less than %" PRIu64 " seconds (%" PRIu64 " days, %" PRIu64 " years). The ttl behaviour is undefined before 1970.", now.Seconds(), now.Days(), now.Days() / 365); return false; } @@ -86,4 +95,20 @@ bool ValidateTtlSettings(const NKikimrSchemeOp::TTTLSettings& ttl, return true; } +TConclusion GetExpireAfter(const NKikimrSchemeOp::TTTLSettings::TEnabled& settings, const bool allowNonDeleteTiers) { + if (settings.TiersSize()) { + for (const auto& tier : settings.GetTiers()) { + if (tier.HasDelete()) { + return TDuration::Seconds(tier.GetApplyAfterSeconds()); + } else if (!allowNonDeleteTiers) { + return TConclusionStatus::Fail("Only DELETE via TTL is allowed for row-oriented tables"); + } + } + return TConclusionStatus::Fail("TTL settings does not contain DELETE action"); + } else { + // legacy format + return TDuration::Seconds(settings.GetExpireAfterSeconds()); + } +} + }} diff --git a/ydb/core/tx/schemeshard/ut_helpers/ls_checks.cpp b/ydb/core/tx/schemeshard/ut_helpers/ls_checks.cpp index fe7c69563602..85fc6e4b98e5 100644 --- a/ydb/core/tx/schemeshard/ut_helpers/ls_checks.cpp +++ b/ydb/core/tx/schemeshard/ut_helpers/ls_checks.cpp @@ -1098,6 +1098,9 @@ TCheckFunc HasTtlEnabled(const TString& columnName, const TDuration& expireAfter UNIT_ASSERT_VALUES_EQUAL(ttl.GetEnabled().GetColumnName(), columnName); UNIT_ASSERT_VALUES_EQUAL(ttl.GetEnabled().GetColumnUnit(), columnUnit); UNIT_ASSERT_VALUES_EQUAL(ttl.GetEnabled().GetExpireAfterSeconds(), expireAfter.Seconds()); + UNIT_ASSERT_VALUES_EQUAL(ttl.GetEnabled().TiersSize(), 1); + UNIT_ASSERT(ttl.GetEnabled().GetTiers(0).HasDelete()); + UNIT_ASSERT_VALUES_EQUAL(ttl.GetEnabled().GetTiers(0).GetApplyAfterSeconds(), expireAfter.Seconds()); }; } @@ -1171,12 +1174,22 @@ TCheckFunc HasColumnTableTtlSettingsDisabled() { }; } -TCheckFunc HasColumnTableTtlSettingsTiering(const TString& tieringName) { +TCheckFunc HasColumnTableTtlSettingsTier(const TString& columnName, const TDuration& evictAfter, const std::optional& storageName) { return [=] (const NKikimrScheme::TEvDescribeSchemeResult& record) { const auto& table = record.GetPathDescription().GetColumnTableDescription(); UNIT_ASSERT(table.HasTtlSettings()); const auto& ttl = table.GetTtlSettings(); - UNIT_ASSERT_EQUAL(ttl.GetUseTiering(), tieringName); + UNIT_ASSERT(ttl.HasEnabled()); + UNIT_ASSERT_VALUES_EQUAL(ttl.GetEnabled().GetColumnName(), columnName); + UNIT_ASSERT_VALUES_EQUAL(ttl.GetEnabled().TiersSize(), 1); + const auto& tier = ttl.GetEnabled().GetTiers(0); + UNIT_ASSERT_VALUES_EQUAL(tier.GetApplyAfterSeconds(), evictAfter.Seconds()); + if (storageName) { + UNIT_ASSERT(tier.HasEvictToExternalStorage()); + UNIT_ASSERT_VALUES_EQUAL(tier.GetEvictToExternalStorage().GetStorage(), storageName); + } else { + UNIT_ASSERT(tier.HasDelete()); + } }; } diff --git a/ydb/core/tx/schemeshard/ut_helpers/ls_checks.h b/ydb/core/tx/schemeshard/ut_helpers/ls_checks.h index 0f39c65f8513..899aef68f421 100644 --- a/ydb/core/tx/schemeshard/ut_helpers/ls_checks.h +++ b/ydb/core/tx/schemeshard/ut_helpers/ls_checks.h @@ -131,7 +131,7 @@ namespace NLs { TCheckFunc HasColumnTableTtlSettingsVersion(ui64 ttlSettingsVersion); TCheckFunc HasColumnTableTtlSettingsEnabled(const TString& columnName, const TDuration& expireAfter); TCheckFunc HasColumnTableTtlSettingsDisabled(); - TCheckFunc HasColumnTableTtlSettingsTiering(const TString& tierName); + TCheckFunc HasColumnTableTtlSettingsTier(const TString& columnName, const TDuration& evictAfter, const std::optional& storageName); TCheckFunc CheckPartCount(const TString& name, ui32 partCount, ui32 maxParts, ui32 tabletCount, ui32 groupCount, NKikimrSchemeOp::EPathState pathState = NKikimrSchemeOp::EPathState::EPathStateNoChanges); diff --git a/ydb/core/tx/schemeshard/ut_olap/ut_olap.cpp b/ydb/core/tx/schemeshard/ut_olap/ut_olap.cpp index 1a6a85e46e27..a37fcac7e553 100644 --- a/ydb/core/tx/schemeshard/ut_olap/ut_olap.cpp +++ b/ydb/core/tx/schemeshard/ut_olap/ut_olap.cpp @@ -69,7 +69,6 @@ Y_UNIT_TEST_SUITE(TOlap) { Columns { Name: "timestamp" Type: "Timestamp" NotNull: true } Columns { Name: "data" Type: "Utf8" } KeyColumnNames: "timestamp" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } } )"; @@ -91,7 +90,6 @@ Y_UNIT_TEST_SUITE(TOlap) { Columns { Name: "timestamp" Type: "Timestamp" NotNull: true } Columns { Name: "data" Type: "Utf8" } KeyColumnNames: "timestamp" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } } )"); @@ -100,6 +98,81 @@ Y_UNIT_TEST_SUITE(TOlap) { TestLs(runtime, "/MyRoot/DirA/DirB/OlapStore", false, NLs::PathExist); } + Y_UNIT_TEST(CreateTableWithNullableKeysNotAllowed) { + TTestBasicRuntime runtime; + TTestEnv env(runtime); + ui64 txId = 100; + + auto& appData = runtime.GetAppData(); + appData.ColumnShardConfig.SetAllowNullableColumnsInPK(false); + + TestCreateOlapStore(runtime, ++txId, "/MyRoot", R"( + Name: "MyStore" + ColumnShardCount: 1 + SchemaPresets { + Name: "default" + Schema { + Columns { Name: "timestamp" Type: "Timestamp" NotNull: true } + Columns { Name: "key1" Type: "Uint32" } + Columns { Name: "data" Type: "Utf8" } + KeyColumnNames: [ "timestamp", "key1" ] + } + } + )", {NKikimrScheme::StatusSchemeError}); + env.TestWaitNotification(runtime, txId); + } + + Y_UNIT_TEST(CreateTableWithNullableKeys) { + TTestBasicRuntime runtime; + TTestEnv env(runtime); + ui64 txId = 100; + + auto& appData = runtime.GetAppData(); + appData.ColumnShardConfig.SetAllowNullableColumnsInPK(true); + + TestCreateOlapStore(runtime, ++txId, "/MyRoot", R"( + Name: "MyStore" + ColumnShardCount: 1 + SchemaPresets { + Name: "default" + Schema { + Columns { Name: "timestamp" Type: "Timestamp" NotNull: true } + Columns { Name: "key1" Type: "Uint32" } + Columns { Name: "data" Type: "Utf8" } + KeyColumnNames: [ "timestamp", "key1" ] + } + } + )"); + env.TestWaitNotification(runtime, txId); + + TestLs(runtime, "/MyRoot/MyStore", false, NLs::PathExist); + + TestMkDir(runtime, ++txId, "/MyRoot", "MyDir"); + env.TestWaitNotification(runtime, txId); + + TestLs(runtime, "/MyRoot/MyDir", false, NLs::PathExist); + + TestCreateColumnTable(runtime, ++txId, "/MyRoot/MyDir", R"( + Name: "MyTable" + ColumnShardCount: 1 + Schema { + Columns { Name: "timestamp" Type: "Timestamp" NotNull: true } + Columns { Name: "key1" Type: "Uint32" } + Columns { Name: "data" Type: "Utf8" } + KeyColumnNames: [ "timestamp", "key1" ] + } + )"); + env.TestWaitNotification(runtime, txId); + + TestLsPathId(runtime, 4, NLs::PathStringEqual("/MyRoot/MyDir/MyTable")); + + TestDropColumnTable(runtime, ++txId, "/MyRoot/MyDir", "MyTable"); + env.TestWaitNotification(runtime, txId); + + TestLs(runtime, "/MyRoot/MyDir/MyTable", false, NLs::PathNotExist); + TestLsPathId(runtime, 4, NLs::PathStringEqual("")); + } + Y_UNIT_TEST(CreateTable) { TTestBasicRuntime runtime; TTestEnv env(runtime); @@ -136,7 +209,6 @@ Y_UNIT_TEST_SUITE(TOlap) { Schema { Columns { Name: "timestamp" Type: "Timestamp" } KeyColumnNames: "timestamp" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } )", {NKikimrScheme::StatusSchemeError}); @@ -149,7 +221,6 @@ Y_UNIT_TEST_SUITE(TOlap) { Columns { Name: "data" Type: "Utf8" } Columns { Name: "comment" Type: "Utf8" } KeyColumnNames: "timestamp" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } )", {NKikimrScheme::StatusSchemeError}); @@ -161,7 +232,6 @@ Y_UNIT_TEST_SUITE(TOlap) { Columns { Name: "data" Type: "Utf8" } Columns { Name: "timestamp" Type: "Timestamp" } KeyColumnNames: "timestamp" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } )", {NKikimrScheme::StatusSchemeError}); @@ -174,7 +244,6 @@ Y_UNIT_TEST_SUITE(TOlap) { Columns { Name: "data" Type: "Utf8" } KeyColumnNames: "timestamp" KeyColumnNames: "data" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } )", {NKikimrScheme::StatusSchemeError}); @@ -186,7 +255,6 @@ Y_UNIT_TEST_SUITE(TOlap) { Columns { Name: "timestamp" Type: "Timestamp" } Columns { Name: "data" Type: "Utf8" } KeyColumnNames: "nottimestamp" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } )", {NKikimrScheme::StatusSchemeError}); @@ -198,7 +266,6 @@ Y_UNIT_TEST_SUITE(TOlap) { Columns { Name: "timestamp" Type: "Timestamp" } Columns { Name: "data" Type: "String" } KeyColumnNames: "timestamp" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } )", {NKikimrScheme::StatusSchemeError}); @@ -210,7 +277,6 @@ Y_UNIT_TEST_SUITE(TOlap) { Columns { Name: "timestamp" Type: "Timestamp" } Columns { Name: "data" Type: "Utf8" } KeyColumnNames: "timestamp" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } )"); env.TestWaitNotification(runtime, txId); @@ -234,7 +300,6 @@ Y_UNIT_TEST_SUITE(TOlap) { Columns { Name: "timestamp" Type: "Timestamp" } Columns { Name: "data" Type: "Utf8" } KeyColumnNames: "timestamp" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } )", {NKikimrScheme::StatusAccepted}); } @@ -338,7 +403,6 @@ Y_UNIT_TEST_SUITE(TOlap) { Columns { Name: "data" Type: "Utf8" NotNull: true } KeyColumnNames: "some" KeyColumnNames: "data" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } Sharding { HashSharding { @@ -436,7 +500,9 @@ Y_UNIT_TEST_SUITE(TOlap) { Y_UNIT_TEST(CreateTableTtl) { TTestBasicRuntime runtime; - TTestEnv env(runtime); + TTestEnvOptions options; + options.EnableTieringInColumnShard(true); + TTestEnv env(runtime, options); ui64 txId = 100; TestCreateOlapStore(runtime, ++txId, "/MyRoot", defaultStoreSchema); @@ -476,11 +542,33 @@ Y_UNIT_TEST_SUITE(TOlap) { NLs::HasColumnTableTtlSettingsVersion(1), NLs::HasColumnTableTtlSettingsDisabled())); + TestCreateExternalDataSource(runtime, ++txId, "/MyRoot", R"( + Name: "Tier1" + SourceType: "ObjectStorage" + Location: "http://fake.fake/fake" + Auth: { + Aws: { + AwsAccessKeyIdSecretName: "secret" + AwsSecretAccessKeySecretName: "secret" + } + } + )"); + env.TestWaitNotification(runtime, txId); + TString tableSchema3 = R"( Name: "Table3" ColumnShardCount: 1 TtlSettings { - UseTiering : "Tiering1" + Enabled: { + ColumnName: "timestamp" + ColumnUnit: UNIT_AUTO + Tiers: { + ApplyAfterSeconds: 360 + EvictToExternalStorage { + Storage: "/MyRoot/Tier1" + } + } + } } )"; @@ -491,13 +579,22 @@ Y_UNIT_TEST_SUITE(TOlap) { NLs::HasColumnTableSchemaPreset("default"), NLs::HasColumnTableSchemaVersion(1), NLs::HasColumnTableTtlSettingsVersion(1), - NLs::HasColumnTableTtlSettingsTiering("Tiering1"))); + NLs::HasColumnTableTtlSettingsTier("timestamp", TDuration::Seconds(360), "/MyRoot/Tier1"))); TString tableSchema4 = R"( Name: "Table4" ColumnShardCount: 1 TtlSettings { - UseTiering : "Tiering1" + Enabled: { + ColumnName: "timestamp" + ColumnUnit: UNIT_AUTO + Tiers: { + ApplyAfterSeconds: 3600000000 + EvictToExternalStorage { + Storage: "/MyRoot/Tier1" + } + } + } } )"; @@ -586,7 +683,6 @@ Y_UNIT_TEST_SUITE(TOlap) { Columns { Name: "timestamp" Type: "Timestamp" NotNull: true } Columns { Name: "data" Type: "Utf8" } KeyColumnNames: "timestamp" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } } )"; @@ -639,10 +735,32 @@ Y_UNIT_TEST_SUITE(TOlap) { )"); env.TestWaitNotification(runtime, txId); + TestCreateExternalDataSource(runtime, ++txId, "/MyRoot", R"( + Name: "Tier1" + SourceType: "ObjectStorage" + Location: "http://fake.fake/fake" + Auth: { + Aws: { + AwsAccessKeyIdSecretName: "secret" + AwsSecretAccessKeySecretName: "secret" + } + } + )"); + env.TestWaitNotification(runtime, txId); + TestAlterColumnTable(runtime, ++txId, "/MyRoot/OlapStore", R"( Name: "ColumnTable" AlterTtlSettings { - UseTiering : "Tiering1" + Enabled: { + ColumnName: "timestamp" + ColumnUnit: UNIT_AUTO + Tiers: { + ApplyAfterSeconds: 3600000000 + EvictToExternalStorage { + Storage: "/MyRoot/Tier1" + } + } + } } )"); env.TestWaitNotification(runtime, txId); @@ -726,8 +844,9 @@ Y_UNIT_TEST_SUITE(TOlap) { TSet txIds; for (ui32 i = 0; i < 10; ++i) { std::vector writeIds; - NTxUT::WriteData(runtime, sender, shardId, ++writeId, pathId, data, defaultYdbSchema, &writeIds, NEvWrite::EModificationType::Upsert); - NTxUT::ProposeCommit(runtime, sender, shardId, ++txId, writeIds); + ++txId; + NTxUT::WriteData(runtime, sender, shardId, ++writeId, pathId, data, defaultYdbSchema, &writeIds, NEvWrite::EModificationType::Upsert, txId); + NTxUT::ProposeCommit(runtime, sender, shardId, txId, writeIds, txId); txIds.insert(txId); } @@ -738,9 +857,10 @@ Y_UNIT_TEST_SUITE(TOlap) { // trigger periodic stats at shard (after timeout) std::vector writeIds; - NTxUT::WriteData(runtime, sender, shardId, ++writeId, pathId, data, defaultYdbSchema, &writeIds, NEvWrite::EModificationType::Upsert); - NTxUT::ProposeCommit(runtime, sender, shardId, ++txId, writeIds); - NTxUT::PlanCommit(runtime, sender, shardId, ++planStep, {txId}); + ++txId; + NTxUT::WriteData(runtime, sender, shardId, ++writeId, pathId, data, defaultYdbSchema, &writeIds, NEvWrite::EModificationType::Upsert, txId); + NTxUT::ProposeCommit(runtime, sender, shardId, txId, writeIds, txId); + NTxUT::PlanCommit(runtime, sender, shardId, ++planStep, { txId }); } csController->WaitIndexation(TDuration::Seconds(5)); { diff --git a/ydb/core/tx/schemeshard/ut_olap_reboots/ut_olap_reboots.cpp b/ydb/core/tx/schemeshard/ut_olap_reboots/ut_olap_reboots.cpp index 47ee8880c567..f6f4a64bc9b6 100644 --- a/ydb/core/tx/schemeshard/ut_olap_reboots/ut_olap_reboots.cpp +++ b/ydb/core/tx/schemeshard/ut_olap_reboots/ut_olap_reboots.cpp @@ -14,7 +14,6 @@ static const TString defaultStoreSchema = R"( Columns { Name: "timestamp" Type: "Timestamp" NotNull: true } Columns { Name: "data" Type: "Utf8" } KeyColumnNames: "timestamp" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } } )"; @@ -26,7 +25,6 @@ static const TString defaultTableSchema = R"( Columns { Name: "timestamp" Type: "Timestamp" NotNull: true } Columns { Name: "data" Type: "Utf8" } KeyColumnNames: "timestamp" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } )"; diff --git a/ydb/core/tx/schemeshard/ut_restore/ut_restore.cpp b/ydb/core/tx/schemeshard/ut_restore/ut_restore.cpp index 6007cf9619d5..ca2dbfd0af18 100644 --- a/ydb/core/tx/schemeshard/ut_restore/ut_restore.cpp +++ b/ydb/core/tx/schemeshard/ut_restore/ut_restore.cpp @@ -1182,7 +1182,7 @@ value { const TString string = "test string"; const TString json = R"({"key": "value"})"; auto binaryJson = NBinaryJson::SerializeToBinaryJson(json); - Y_ABORT_UNLESS(binaryJson.Defined()); + Y_ABORT_UNLESS(binaryJson.IsSuccess()); const std::pair decimal = NYql::NDecimal::MakePair(NYql::NDecimal::FromString("16.17", NScheme::DECIMAL_PRECISION, NScheme::DECIMAL_SCALE)); const TString dynumber = *NDyNumber::ParseDyNumberString("18"); diff --git a/ydb/core/tx/schemeshard/ut_subdomain/ut_subdomain.cpp b/ydb/core/tx/schemeshard/ut_subdomain/ut_subdomain.cpp index 8fc34d9edbe0..f555b6b470ec 100644 --- a/ydb/core/tx/schemeshard/ut_subdomain/ut_subdomain.cpp +++ b/ydb/core/tx/schemeshard/ut_subdomain/ut_subdomain.cpp @@ -2638,7 +2638,6 @@ Y_UNIT_TEST_SUITE(TSchemeShardSubDomainTest) { Columns { Name: "Value0" Type: "Utf8" } Columns { Name: "Value1" Type: "Utf8" } KeyColumnNames: "RowId" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } )", {NKikimrScheme::StatusAccepted}); env.TestWaitNotification(runtime, txId - 1); @@ -2671,7 +2670,6 @@ Y_UNIT_TEST_SUITE(TSchemeShardSubDomainTest) { Columns { Name: "Value1" Type: "Utf8" } Columns { Name: "Value2" Type: "Utf8" } KeyColumnNames: "RowId" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } )", {NKikimrScheme::StatusSchemeError}); @@ -2684,7 +2682,6 @@ Y_UNIT_TEST_SUITE(TSchemeShardSubDomainTest) { Columns { Name: "timestamp" Type: "Timestamp" NotNull: true } Columns { Name: "data" Type: "Utf8" } KeyColumnNames: "timestamp" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } } )"; @@ -2703,7 +2700,6 @@ Y_UNIT_TEST_SUITE(TSchemeShardSubDomainTest) { Columns { Name: "data2" Type: "Utf8" } Columns { Name: "data3" Type: "Utf8" } KeyColumnNames: "timestamp" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } } )"; diff --git a/ydb/core/tx/schemeshard/ut_ttl/ut_ttl.cpp b/ydb/core/tx/schemeshard/ut_ttl/ut_ttl.cpp index 1accb55c269b..0b86ebb1390a 100644 --- a/ydb/core/tx/schemeshard/ut_ttl/ut_ttl.cpp +++ b/ydb/core/tx/schemeshard/ut_ttl/ut_ttl.cpp @@ -10,10 +10,21 @@ using namespace NSchemeShardUT_Private; namespace { template -void CheckTtlSettings(const TTtlSettings& ttl, const char* ttlColumnName) { +void CheckTtlSettings(const TTtlSettings& ttl, const char* ttlColumnName, bool legacyTiering = false) { UNIT_ASSERT(ttl.HasEnabled()); UNIT_ASSERT_VALUES_EQUAL(ttl.GetEnabled().GetColumnName(), ttlColumnName); UNIT_ASSERT_VALUES_EQUAL(ttl.GetEnabled().GetExpireAfterSeconds(), 3600); + if (legacyTiering) { + UNIT_ASSERT_VALUES_EQUAL(ttl.GetEnabled().TiersSize(), 0); + } else { + UNIT_ASSERT_VALUES_EQUAL(ttl.GetEnabled().TiersSize(), 1); + UNIT_ASSERT(ttl.GetEnabled().GetTiers(0).HasDelete()); + UNIT_ASSERT_VALUES_EQUAL(ttl.GetEnabled().GetTiers(0).GetApplyAfterSeconds(), 3600); + } +} + +void LegacyOltpTtlChecker(const NKikimrScheme::TEvDescribeSchemeResult& record) { + CheckTtlSettings(record.GetPathDescription().GetTable().GetTTLSettings(), "modified_at", true); } void OltpTtlChecker(const NKikimrScheme::TEvDescribeSchemeResult& record) { @@ -54,6 +65,10 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { ColumnName: "modified_at" ExpireAfterSeconds: 3600 ColumnUnit: %s + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } )", name, ttlColumnType, unit)); @@ -88,7 +103,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { ColumnName: "created_at" } } - )", {NKikimrScheme::StatusSchemeError}); + )", {{NKikimrScheme::StatusSchemeError, "Cannot enable TTL on unknown column: 'created_at'"}}); } Y_UNIT_TEST(CreateTableShouldFailOnWrongColumnType) { @@ -106,7 +121,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { ColumnName: "modified_at" } } - )", {NKikimrScheme::StatusSchemeError}); + )", {{NKikimrScheme::StatusSchemeError, "Unsupported column type"}}); } void CreateTableShouldFailOnWrongUnit(const char* ttlColumnType, const char* unit) { @@ -114,7 +129,8 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { TTestEnv env(runtime); ui64 txId = 100; - TestCreateTable(runtime, ++txId, "/MyRoot", Sprintf(R"( + TestCreateTable(runtime, ++txId, "/MyRoot", + Sprintf(R"( Name: "TTLEnabledTable" Columns { Name: "key" Type: "Uint64" } Columns { Name: "modified_at" Type: "%s" } @@ -125,7 +141,13 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { ColumnUnit: %s } } - )", ttlColumnType, unit), {NKikimrScheme::StatusSchemeError}); + )", + ttlColumnType, unit), { + { NKikimrScheme::StatusSchemeError, "To enable TTL on date PG type column 'DateTypeColumnModeSettings' should be specified" }, + { NKikimrScheme::StatusSchemeError, "To enable TTL on date type column 'DateTypeColumnModeSettings' should be specified" }, + { NKikimrScheme::StatusSchemeError, "To enable TTL on integral type column 'ValueSinceUnixEpochModeSettings' should be specified" }, + { NKikimrScheme::StatusSchemeError, "To enable TTL on integral PG type column 'ValueSinceUnixEpochModeSettings' should be specified" } + }); } Y_UNIT_TEST(CreateTableShouldFailOnWrongUnit) { @@ -152,7 +174,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { KeyColumnNames: ["key"] TTLSettings { } - )", {NKikimrScheme::StatusSchemeError}); + )", {{NKikimrScheme::StatusSchemeError, "TTL status must be specified"}}); } Y_UNIT_TEST(CreateTableShouldFailOnBeforeEpochTTL) { @@ -173,9 +195,13 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { Enabled { ColumnName: "modified_at" ExpireAfterSeconds: 3153600000 + Tiers: { + ApplyAfterSeconds: 3153600000 + Delete: {} + } } } - )", {NKikimrScheme::StatusSchemeError}); + )", {{NKikimrScheme::StatusSchemeError, "TTL should be less than"}}); } void CreateTableOnIndexedTable(NKikimrSchemeOp::EIndexType indexType) { @@ -193,6 +219,10 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { Enabled { ColumnName: "modified_at" ExpireAfterSeconds: 3600 + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } } @@ -234,6 +264,10 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { Enabled { ColumnName: "modified_at" ExpireAfterSeconds: 3600 + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } )"); @@ -243,7 +277,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { TestAlterTable(runtime, ++txId, "/MyRoot", R"( Name: "TTLEnabledTable" DropColumns { Name: "modified_at" } - )", {NKikimrScheme::StatusInvalidParameter}); + )", {{NKikimrScheme::StatusInvalidParameter, "Can't drop TTL column: 'modified_at', disable TTL first"}}); TestAlterTable(runtime, ++txId, "/MyRoot", R"( Name: "TTLEnabledTable" @@ -284,6 +318,10 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { Enabled { ColumnName: "modified_at" ExpireAfterSeconds: 3600 + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } )"); @@ -312,7 +350,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { ColumnName: "modified_at" } } - )", {NKikimrScheme::StatusInvalidParameter}); + )", {{NKikimrScheme::StatusInvalidParameter, "Cannot enable TTL on dropped column: 'modified_at'"}}); } void AlterTableOnIndexedTable(NKikimrSchemeOp::EIndexType indexType) { @@ -341,6 +379,10 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { Enabled { ColumnName: "modified_at" ExpireAfterSeconds: 3600 + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } )"); @@ -372,6 +414,10 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { Enabled { ColumnName: "modified_at" ExpireAfterSeconds: 3600 + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } )"); @@ -512,6 +558,10 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { Enabled { ColumnName: "ts" ExpireAfterSeconds: 3600 + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } )"); @@ -773,6 +823,10 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { Enabled { ColumnName: "modified_at" ExpireAfterSeconds: 3600 + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } )"); @@ -791,6 +845,10 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { SysSettings { RunInterval: 1800000000 } + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } )"); @@ -809,9 +867,13 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { SysSettings { RunInterval: 1799999999 } + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } - )", {NKikimrScheme::StatusSchemeError}); + )", {{NKikimrScheme::StatusSchemeError, "TTL run interval cannot be less than limit"}}); } Y_UNIT_TEST(ShouldSkipDroppedColumn) { @@ -855,6 +917,53 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { WaitForCondErase(runtime); } + Y_UNIT_TEST(LegacyTtlSettingsNoTiers) { + TTestBasicRuntime runtime; + TTestEnv env(runtime); + ui64 txId = 100; + + TestCreateTable(runtime, ++txId, "/MyRoot", Sprintf(R"( + Name: "TTLEnabledTable" + Columns { Name: "key" Type: "Uint64" } + Columns { Name: "modified_at" Type: "Timestamp" } + KeyColumnNames: ["key"] + TTLSettings { + Enabled { + ColumnName: "modified_at" + ExpireAfterSeconds: 3600 + } + } + )")); + env.TestWaitNotification(runtime, txId); + CheckTtlSettings(runtime, LegacyOltpTtlChecker); + } + + Y_UNIT_TEST(LegacyTtlSettingsNoTiersAlterTable) { + TTestBasicRuntime runtime; + TTestEnv env(runtime); + ui64 txId = 100; + + TestCreateTable(runtime, ++txId, "/MyRoot", Sprintf(R"( + Name: "TTLEnabledTable" + Columns { Name: "key" Type: "Uint64" } + Columns { Name: "modified_at" Type: "Timestamp" } + KeyColumnNames: ["key"] + )")); + env.TestWaitNotification(runtime, txId); + + TestAlterTable(runtime, ++txId, "/MyRoot", R"( + Name: "TTLEnabledTable" + TTLSettings { + Enabled { + ColumnName: "modified_at" + ExpireAfterSeconds: 3600 + } + } + )"); + env.TestWaitNotification(runtime, txId); + CheckTtlSettings(runtime, LegacyOltpTtlChecker); + } + NKikimrTabletBase::TEvGetCountersResponse GetCounters(TTestBasicRuntime& runtime) { const auto sender = runtime.AllocateEdgeActor(); runtime.SendToPipe(TTestTxConfig::SchemeShard, sender, new TEvTablet::TEvGetCounters); @@ -1057,6 +1166,56 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { WaitForStats(runtime, 2); CheckPercentileCounter(runtime, "SchemeShard/NumShardsByTtlLag", {{"900", 2}, {"1800", 0}, {"inf", 0}}); } + + Y_UNIT_TEST(TtlTiersValidation) { + TTestBasicRuntime runtime; + TTestEnv env(runtime); + ui64 txId = 100; + + TestCreateTable(runtime, ++txId, "/MyRoot", Sprintf(R"( + Name: "TTLEnabledTable" + Columns { Name: "key" Type: "Uint64" } + Columns { Name: "modified_at" Type: "Timestamp" } + KeyColumnNames: ["key"] + )")); + env.TestWaitNotification(runtime, txId); + + TestAlterTable(runtime, ++txId, "/MyRoot", R"( + Name: "TTLEnabledTable" + TTLSettings { + Enabled { + ColumnName: "modified_at" + Tiers { + Delete {} + ApplyAfterSeconds: 3600 + } + Tiers { + Delete {} + ApplyAfterSeconds: 7200 + } + } + } + )", {{NKikimrScheme::StatusInvalidParameter, "Tier 0: only the last tier in TTL settings can have Delete action"}}); + + TestAlterTable(runtime, ++txId, "/MyRoot", R"( + Name: "TTLEnabledTable" + TTLSettings { + Enabled { + ColumnName: "modified_at" + Tiers { + EvictToExternalStorage { + Storage: "/Root/abc" + } + ApplyAfterSeconds: 3600 + } + Tiers { + Delete {} + ApplyAfterSeconds: 7200 + } + } + } + )", {{NKikimrScheme::StatusInvalidParameter, "Only DELETE via TTL is allowed for row-oriented tables"}}); + } } Y_UNIT_TEST_SUITE(TSchemeShardColumnTableTTL) { @@ -1069,14 +1228,18 @@ Y_UNIT_TEST_SUITE(TSchemeShardColumnTableTTL) { Name: "%s" Schema { Columns { Name: "key" Type: "Uint64" NotNull: true } - Columns { Name: "modified_at" Type: "%s" } - KeyColumnNames: ["key"] + Columns { Name: "modified_at" Type: "%s" NotNull: true } + KeyColumnNames: ["modified_at"] } TtlSettings { Enabled { ColumnName: "modified_at" ExpireAfterSeconds: 3600 ColumnUnit: %s + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } )", name, ttlColumnType, unit)); @@ -1102,7 +1265,8 @@ Y_UNIT_TEST_SUITE(TSchemeShardColumnTableTTL) { ui64 txId = 100; for (auto ct : {"String", "DyNumber"}) { - TestCreateColumnTable(runtime, ++txId, "/MyRoot", Sprintf(R"( + TestCreateColumnTable(runtime, ++txId, "/MyRoot", + Sprintf(R"( Name: "TTLEnabledTable" Schema { Columns { Name: "key" Type: "Uint64" NotNull: true } @@ -1113,9 +1277,17 @@ Y_UNIT_TEST_SUITE(TSchemeShardColumnTableTTL) { Enabled { ColumnName: "modified_at" ExpireAfterSeconds: 3600 + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } - )", ct), {NKikimrScheme::StatusSchemeError}); + )", + ct), { + { NKikimrScheme::StatusSchemeError, "Type 'DyNumber' specified for column 'modified_at' is not supported" }, + { NKikimrScheme::StatusSchemeError, "Unsupported column type" + } }); } } @@ -1136,7 +1308,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardColumnTableTTL) { ColumnName: "created_at" } } - )", {NKikimrScheme::StatusSchemeError}); + )", {{NKikimrScheme::StatusSchemeError, "Incorrect ttl column - not found in scheme"}}); } Y_UNIT_TEST(AlterColumnTable) { @@ -1148,10 +1320,10 @@ Y_UNIT_TEST_SUITE(TSchemeShardColumnTableTTL) { Name: "TTLEnabledTable" Schema { Columns { Name: "key" Type: "Uint64" NotNull: true } - Columns { Name: "modified_at" Type: "Timestamp" } + Columns { Name: "modified_at" Type: "Timestamp" NotNull: true } Columns { Name: "saved_at" Type: "Datetime" } Columns { Name: "data" Type: "Utf8" } - KeyColumnNames: ["key"] + KeyColumnNames: ["modified_at"] } )"); env.TestWaitNotification(runtime, txId); @@ -1171,24 +1343,16 @@ Y_UNIT_TEST_SUITE(TSchemeShardColumnTableTTL) { Enabled { ColumnName: "modified_at" ExpireAfterSeconds: 3600 + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } )"); env.TestWaitNotification(runtime, txId); CheckTtlSettings(runtime, OlapTtlChecker()); - TestAlterColumnTable(runtime, ++txId, "/MyRoot", R"( - Name: "TTLEnabledTable" - AlterTtlSettings { - Enabled { - ColumnName: "saved_at" - ExpireAfterSeconds: 3600 - } - } - )"); - env.TestWaitNotification(runtime, txId); - CheckTtlSettings(runtime, OlapTtlChecker("saved_at")); - TestAlterColumnTable(runtime, ++txId, "/MyRoot", R"( Name: "TTLEnabledTable" AlterTtlSettings { @@ -1212,7 +1376,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardColumnTableTTL) { AlterSchema { AlterColumns {Name: "data" DefaultValue: "10"} } - )", {NKikimrScheme::StatusSchemeError}); + )", {{NKikimrScheme::StatusSchemeError, "sparsed columns are disabled"}}); env.TestWaitNotification(runtime, txId); } @@ -1247,9 +1411,13 @@ Y_UNIT_TEST_SUITE(TSchemeShardColumnTableTTL) { Enabled { ColumnName: "str" ExpireAfterSeconds: 3600 + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } - )", {NKikimrScheme::StatusSchemeError}); + )", {{NKikimrScheme::StatusSchemeError, "Unsupported column type"}}); } } @@ -1266,6 +1434,10 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTestsWithReboots) { Enabled { ColumnName: "modified_at" ExpireAfterSeconds: 3600 + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } )"); @@ -1298,6 +1470,10 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTestsWithReboots) { Enabled { ColumnName: "modified_at" ExpireAfterSeconds: 3600 + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } )"); @@ -1324,6 +1500,10 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTestsWithReboots) { Enabled { ColumnName: "modified_at" ExpireAfterSeconds: 3600 + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } )"); @@ -1354,6 +1534,10 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTestsWithReboots) { Enabled { ColumnName: "modified_at" ExpireAfterSeconds: 3600 + Tiers: { + ApplyAfterSeconds: 3600 + Delete: {} + } } } )"); diff --git a/ydb/core/tx/schemeshard/ut_ttl/ut_ttl_utility.cpp b/ydb/core/tx/schemeshard/ut_ttl/ut_ttl_utility.cpp new file mode 100644 index 000000000000..3547876037bd --- /dev/null +++ b/ydb/core/tx/schemeshard/ut_ttl/ut_ttl_utility.cpp @@ -0,0 +1,100 @@ +#include +#include + +#include + +using namespace NKikimr; +using namespace NSchemeShard; + +Y_UNIT_TEST_SUITE(TSchemeShardTTLUtility) { + void TestValidateTiers(const std::vector& tiers, const TConclusionStatus& expectedResult) { + google::protobuf::RepeatedPtrField input; + for (const auto& tier : tiers) { + input.Add()->CopyFrom(tier); + } + + TString error; + UNIT_ASSERT_VALUES_EQUAL(NValidation::TTTLValidator::ValidateTiers(input, error), expectedResult.IsSuccess()); + if (expectedResult.IsFail()) { + UNIT_ASSERT_STRING_CONTAINS(error, expectedResult.GetErrorMessage()); + } + } + + Y_UNIT_TEST(ValidateTiers) { + NKikimrSchemeOp::TTTLSettings::TTier tierNoAction; + tierNoAction.SetApplyAfterSeconds(60); + NKikimrSchemeOp::TTTLSettings::TTier tierNoDuration; + tierNoDuration.MutableDelete(); + auto makeDeleteTier = [](const ui32 seconds) { + NKikimrSchemeOp::TTTLSettings::TTier tier; + tier.MutableDelete(); + tier.SetApplyAfterSeconds(seconds); + return tier; + }; + auto makeEvictTier = [](const ui32 seconds) { + NKikimrSchemeOp::TTTLSettings::TTier tier; + tier.MutableEvictToExternalStorage()->SetStorage("/Root/abc"); + tier.SetApplyAfterSeconds(seconds); + return tier; + }; + + TestValidateTiers({ tierNoAction }, TConclusionStatus::Fail("Tier 0: missing Action")); + TestValidateTiers({ tierNoDuration }, TConclusionStatus::Fail("Tier 0: missing ApplyAfterSeconds")); + TestValidateTiers({ makeDeleteTier(1) }, TConclusionStatus::Success()); + TestValidateTiers({ makeEvictTier(1) }, TConclusionStatus::Success()); + TestValidateTiers({ makeEvictTier(1), makeDeleteTier(2) }, TConclusionStatus::Success()); + TestValidateTiers({ makeEvictTier(1), makeEvictTier(2), makeDeleteTier(3) }, TConclusionStatus::Success()); + TestValidateTiers({ makeEvictTier(1), makeEvictTier(2) }, TConclusionStatus::Success()); + TestValidateTiers({ makeEvictTier(2), makeEvictTier(1) }, TConclusionStatus::Fail("Tiers in the sequence must have increasing ApplyAfterSeconds: 2 (tier 0) >= 1 (tier 1)")); + TestValidateTiers({ makeDeleteTier(1), makeEvictTier(2) }, TConclusionStatus::Fail("Tier 0: only the last tier in TTL settings can have Delete action")); + TestValidateTiers({ makeDeleteTier(1), makeDeleteTier(2) }, TConclusionStatus::Fail("Tier 0: only the last tier in TTL settings can have Delete action")); + } + + void ValidateGetExpireAfter(const NKikimrSchemeOp::TTTLSettings::TEnabled& ttlSettings, const bool allowNonDeleteTiers, const TConclusion& expectedResult) { + auto result = GetExpireAfter(ttlSettings, allowNonDeleteTiers); + UNIT_ASSERT_VALUES_EQUAL(result.IsSuccess(), expectedResult.IsSuccess()); + if (expectedResult.IsFail()) { + UNIT_ASSERT_STRING_CONTAINS(result.GetErrorMessage(), expectedResult.GetErrorMessage()); + } + } + + Y_UNIT_TEST(GetExpireAfter) { + NKikimrSchemeOp::TTTLSettings::TTier evictTier; + evictTier.MutableEvictToExternalStorage()->SetStorage("/Root/abc"); + evictTier.SetApplyAfterSeconds(1800); + NKikimrSchemeOp::TTTLSettings::TTier deleteTier; + deleteTier.MutableDelete(); + deleteTier.SetApplyAfterSeconds(3600); + + { + NKikimrSchemeOp::TTTLSettings::TEnabled input; + ValidateGetExpireAfter(input, true, TDuration::Zero()); + ValidateGetExpireAfter(input, false, TDuration::Zero()); + } + { + NKikimrSchemeOp::TTTLSettings::TEnabled input; + input.SetExpireAfterSeconds(60); + ValidateGetExpireAfter(input, true, TDuration::Seconds(60)); + ValidateGetExpireAfter(input, false, TDuration::Seconds(60)); + } + { + NKikimrSchemeOp::TTTLSettings::TEnabled input; + *input.AddTiers() = deleteTier; + ValidateGetExpireAfter(input, true, TDuration::Seconds(3600)); + ValidateGetExpireAfter(input, false, TDuration::Seconds(3600)); + } + { + NKikimrSchemeOp::TTTLSettings::TEnabled input; + *input.AddTiers() = evictTier; + *input.AddTiers() = deleteTier; + ValidateGetExpireAfter(input, true, TDuration::Seconds(3600)); + ValidateGetExpireAfter(input, false, TConclusionStatus::Fail("Only DELETE via TTL is allowed for row-oriented tables")); + } + { + NKikimrSchemeOp::TTTLSettings::TEnabled input; + *input.AddTiers() = evictTier; + ValidateGetExpireAfter(input, true, TConclusionStatus::Fail("TTL settings does not contain DELETE action")); + ValidateGetExpireAfter(input, false, TConclusionStatus::Fail("Only DELETE via TTL is allowed for row-oriented tables")); + } + } +} diff --git a/ydb/core/tx/schemeshard/ut_ttl/ya.make b/ydb/core/tx/schemeshard/ut_ttl/ya.make index 339b61bb95c9..d23ab069a593 100644 --- a/ydb/core/tx/schemeshard/ut_ttl/ya.make +++ b/ydb/core/tx/schemeshard/ut_ttl/ya.make @@ -21,6 +21,7 @@ PEERDIR( SRCS( ut_ttl.cpp + ut_ttl_utility.cpp ) YQL_LAST_ABI_VERSION() diff --git a/ydb/core/tx/schemeshard/ya.make b/ydb/core/tx/schemeshard/ya.make index e17c0a11b792..4ffd62fd004b 100644 --- a/ydb/core/tx/schemeshard/ya.make +++ b/ydb/core/tx/schemeshard/ya.make @@ -287,6 +287,7 @@ PEERDIR( ydb/library/yql/providers/common/proto ydb/services/bg_tasks ydb/core/tx/columnshard/bg_tasks/manager + ydb/core/tx/tiering/tier ) YQL_LAST_ABI_VERSION() diff --git a/ydb/core/tx/tiering/common.h b/ydb/core/tx/tiering/common.h index 1eb341b78b80..cd7fa14b82bc 100644 --- a/ydb/core/tx/tiering/common.h +++ b/ydb/core/tx/tiering/common.h @@ -13,6 +13,11 @@ enum EEvents { EvSSFetchingProblem, EvTimeout, EvTiersManagerReadyForUsage, + EvWatchSchemeObject, + EvNotifySchemeObjectUpdated, + EvNotifySchemeObjectDeleted, + EvSchemeObjectResulutionFailed, + EvListTieredStoragesResult, EvEnd }; diff --git a/ydb/core/tx/tiering/external_data.cpp b/ydb/core/tx/tiering/external_data.cpp deleted file mode 100644 index b6616bc760aa..000000000000 --- a/ydb/core/tx/tiering/external_data.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "external_data.h" - -#include -#include -#include - -#include -#include - -#include - -namespace NKikimr::NColumnShard::NTiers { - -void TSnapshotConstructor::EnrichSnapshotData(ISnapshot::TPtr original, NMetadata::NFetcher::ISnapshotAcceptorController::TPtr controller) const { - controller->OnSnapshotEnriched(original); -} - -TSnapshotConstructor::TSnapshotConstructor() { -} - -std::vector TSnapshotConstructor::DoGetManagers() const { - std::vector result = { - TTierConfig::GetBehaviour(), - TTieringRule::GetBehaviour() - }; - return result; -} - -} diff --git a/ydb/core/tx/tiering/external_data.h b/ydb/core/tx/tiering/external_data.h deleted file mode 100644 index 02b963ab6d4b..000000000000 --- a/ydb/core/tx/tiering/external_data.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -#include "snapshot.h" - -#include -#include -#include - -#include - -namespace NKikimr::NColumnShard::NTiers { - -class TSnapshotConstructor: public NMetadata::NFetcher::TSnapshotsFetcher { -private: - using TNavigate = NSchemeCache::TSchemeCacheNavigate; - using TBaseActor = TActor; - using ISnapshot = NMetadata::NFetcher::ISnapshot; -protected: - virtual std::vector DoGetManagers() const override; -public: - virtual void EnrichSnapshotData(ISnapshot::TPtr original, NMetadata::NFetcher::ISnapshotAcceptorController::TPtr controller) const override; - - TSnapshotConstructor(); -}; - -} diff --git a/ydb/core/tx/tiering/fetcher.cpp b/ydb/core/tx/tiering/fetcher.cpp new file mode 100644 index 000000000000..ddb51424448a --- /dev/null +++ b/ydb/core/tx/tiering/fetcher.cpp @@ -0,0 +1,3 @@ +#include "fetcher.h" + +namespace NKikimr::NColumnShard {} diff --git a/ydb/core/tx/tiering/fetcher.h b/ydb/core/tx/tiering/fetcher.h new file mode 100644 index 000000000000..38fd08655374 --- /dev/null +++ b/ydb/core/tx/tiering/fetcher.h @@ -0,0 +1,198 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include + +namespace NKikimr::NColumnShard { + +namespace NTiers { + +class TEvWatchSchemeObject: public TEventLocal { +private: + YDB_READONLY_DEF(std::vector, ObjectPaths); + +public: + TEvWatchSchemeObject(std::vector paths) + : ObjectPaths(std::move(paths)) { + } +}; + +class TEvNotifySchemeObjectUpdated: public TEventLocal { +private: + YDB_READONLY_DEF(TString, ObjectPath); + YDB_READONLY_DEF(NKikimrSchemeOp::TPathDescription, Description); + +public: + TEvNotifySchemeObjectUpdated(const TString& path, NKikimrSchemeOp::TPathDescription description) + : ObjectPath(path) + , Description(std::move(description)) { + } +}; + +class TEvNotifySchemeObjectDeleted: public TEventLocal { +private: + YDB_READONLY_DEF(TString, ObjectPath); + +public: + TEvNotifySchemeObjectDeleted(TString path) + : ObjectPath(std::move(path)) { + } +}; + +class TEvSchemeObjectResolutionFailed: public TEventLocal { +public: + enum EReason { + NOT_FOUND = 0, + LOOKUP_ERROR = 1 + }; + +private: + YDB_READONLY_DEF(TString, ObjectPath); + YDB_READONLY_DEF(EReason, Reason); + +public: + TEvSchemeObjectResolutionFailed(TString path, const EReason reason) + : ObjectPath(std::move(path)) + , Reason(reason) { + } +}; + +} // namespace NTiers + +class TSchemeObjectWatcher: public TActorBootstrapped { +private: + TActorId Owner; + THashSet WatchedPathIds; + +private: + THolder BuildSchemeCacheNavigateRequest( + const TVector>& paths, TIntrusiveConstPtr userToken) { + auto request = MakeHolder(); + request->DatabaseName = AppDataVerified().TenantName; + if (userToken && !userToken->GetSerializedToken().empty()) { + request->UserToken = userToken; + } + + for (const auto& pathComponents : paths) { + auto& entry = request->ResultSet.emplace_back(); + entry.Operation = NSchemeCache::TSchemeCacheNavigate::OpPath; + entry.RequestType = NSchemeCache::TSchemeCacheNavigate::TEntry::ERequestType::ByPath; + entry.ShowPrivatePath = true; + entry.Path = pathComponents; + } + + return request; + } + + void WatchObjects(const std::vector& paths) { + TVector> splitPaths; + for (const TString& path : paths) { + splitPaths.emplace_back(SplitPath(path)); + } + + auto event = BuildSchemeCacheNavigateRequest( + std::move(splitPaths), MakeIntrusive(BUILTIN_ACL_METADATA, TVector{})); + Send(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(event.Release()), IEventHandle::FlagTrackDelivery); + } + + void WatchPathId(const TPathId& pathId) { + if (WatchedPathIds.emplace(pathId).second) { + Send(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvWatchPathId(pathId), IEventHandle::FlagTrackDelivery); + } else { + AFL_DEBUG(NKikimrServices::TX_TIERING)("event", "skip_watch_path_id")("reason", "already_subscribed")("path", pathId.ToString()); + } + } + + void Handle(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) { + const NSchemeCache::TSchemeCacheNavigate* result = ev->Get()->Request.Get(); + for (auto entry : result->ResultSet) { + AFL_DEBUG(NKikimrServices::TX_TIERING)("component", "TSchemeObjectWatcher")("event", ev->ToString())("path", JoinPath(entry.Path)); + switch (entry.Status) { + case NSchemeCache::TSchemeCacheNavigate::EStatus::Ok: + WatchPathId(entry.TableId.PathId); + break; + + case NSchemeCache::TSchemeCacheNavigate::EStatus::PathErrorUnknown: + case NSchemeCache::TSchemeCacheNavigate::EStatus::RootUnknown: + Send(Owner, new NTiers::TEvSchemeObjectResolutionFailed( + JoinPath(entry.Path), NTiers::TEvSchemeObjectResolutionFailed::EReason::NOT_FOUND)); + break; + + case NSchemeCache::TSchemeCacheNavigate::EStatus::RedirectLookupError: + case NSchemeCache::TSchemeCacheNavigate::EStatus::LookupError: + Send(Owner, new NTiers::TEvSchemeObjectResolutionFailed( + JoinPath(entry.Path), NTiers::TEvSchemeObjectResolutionFailed::EReason::LOOKUP_ERROR)); + break; + + case NSchemeCache::TSchemeCacheNavigate::EStatus::AccessDenied: + case NSchemeCache::TSchemeCacheNavigate::EStatus::PathNotTable: + case NSchemeCache::TSchemeCacheNavigate::EStatus::PathNotPath: + case NSchemeCache::TSchemeCacheNavigate::EStatus::TableCreationNotComplete: + case NSchemeCache::TSchemeCacheNavigate::EStatus::Unknown: + AFL_VERIFY(false)("entry", entry.ToString()); + } + } + } + + void Handle(TEvTxProxySchemeCache::TEvWatchNotifyUpdated::TPtr& ev) { + AFL_DEBUG(NKikimrServices::TX_TIERING)("event", "object_fetched")("path", ev->Get()->Path); + const auto& describeResult = *ev->Get()->Result; + Send(Owner, new NTiers::TEvNotifySchemeObjectUpdated(describeResult.GetPath(), describeResult.GetPathDescription())); + } + + void Handle(TEvTxProxySchemeCache::TEvWatchNotifyDeleted::TPtr& ev) { + const auto& record = ev->Get(); + const TString name = TString(ExtractBase(record->Path)); + const TString storageDir = TString(ExtractParent(record->Path)); + AFL_DEBUG(NKikimrServices::TX_TIERING)("event", "object_deleted")("path", record->Path); + AFL_VERIFY(WatchedPathIds.erase(record->PathId)); + Send(Owner, new NTiers::TEvNotifySchemeObjectDeleted(record->Path)); + } + + void Handle(NTiers::TEvWatchSchemeObject::TPtr& ev) { + AFL_DEBUG(NKikimrServices::TX_TIERING)("event", "watch_scheme_objects")( + "names", JoinStrings(ev->Get()->GetObjectPaths().begin(), ev->Get()->GetObjectPaths().end(), ",")); + WatchObjects(ev->Get()->GetObjectPaths()); + } + + void Handle(NActors::TEvents::TEvPoison::TPtr& /*ev*/) { + Send(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvWatchRemove()); + PassAway(); + } + + void Handle(NActors::TEvents::TEvUndelivered::TPtr& ev) { + AFL_WARN(NKikimrServices::TX_TIERING)("error", "event_undelivered_to_scheme_cache")("reason", ev->Get()->Reason); + } + +public: + TSchemeObjectWatcher(TActorId owner) + : Owner(owner) { + } + + STATEFN(StateMain) { + switch (ev->GetTypeRewrite()) { + hFunc(TEvTxProxySchemeCache::TEvNavigateKeySetResult, Handle); + hFunc(TEvTxProxySchemeCache::TEvWatchNotifyUpdated, Handle); + hFunc(TEvTxProxySchemeCache::TEvWatchNotifyDeleted, Handle); + IgnoreFunc(TEvTxProxySchemeCache::TEvWatchNotifyUnavailable); + hFunc(NTiers::TEvWatchSchemeObject, Handle); + hFunc(NActors::TEvents::TEvPoison, Handle); + hFunc(NActors::TEvents::TEvUndelivered, Handle); + default: + break; + } + } + + void Bootstrap() { + Become(&TSchemeObjectWatcher::StateMain); + } +}; + +} // namespace NKikimr::NColumnShard diff --git a/ydb/core/tx/tiering/manager.cpp b/ydb/core/tx/tiering/manager.cpp index 57462d745d3a..d937aaa95420 100644 --- a/ydb/core/tx/tiering/manager.cpp +++ b/ydb/core/tx/tiering/manager.cpp @@ -1,109 +1,174 @@ #include "common.h" #include "manager.h" -#include "external_data.h" #include +#include + +#include #include +#include +#include + namespace NKikimr::NColumnShard { class TTiersManager::TActor: public TActorBootstrapped { private: + using IRetryPolicy = IRetryPolicy<>; + std::shared_ptr Owner; + IRetryPolicy::TPtr RetryPolicy; + THashMap RetryStateByObject; NMetadata::NFetcher::ISnapshotsFetcher::TPtr SecretsFetcher; - std::shared_ptr SecretsSnapshot; - std::shared_ptr ConfigsSnapshot; + TActorId TiersFetcher; + +private: TActorId GetExternalDataActorId() const { return NMetadata::NProvider::MakeServiceId(SelfId().NodeId()); } -public: - TActor(std::shared_ptr owner) - : Owner(owner) - , SecretsFetcher(std::make_shared()) - { + void OnInvalidTierConfig(const TString& path) { + if (!Owner->TierRefCount.contains(path)) { + ResetRetryState(path); + return; + } + AFL_DEBUG(NKikimrServices::TX_TIERING)("component", "tiers_manager")("event", "retry_watch_objects"); + auto findRetryState = RetryStateByObject.find(path); + if (!findRetryState) { + findRetryState = RetryStateByObject.emplace(path, RetryPolicy->CreateRetryState()).first; + } + auto retryDelay = findRetryState->second->GetNextRetryDelay(); + AFL_VERIFY(retryDelay)("object", path); + ActorContext().Schedule(*retryDelay, std::make_unique(SelfId(), TiersFetcher, new NTiers::TEvWatchSchemeObject(std::vector({ path })))); } - ~TActor() { - Owner->Stop(false); + + void ResetRetryState(const TString& path) { + RetryStateByObject.erase(path); + } + + void OnFetchingFailure(const TString& path) { + if (Owner->TierRefCount.contains(path)) { + OnInvalidTierConfig(path); + } } STATEFN(StateMain) { switch (ev->GetTypeRewrite()) { hFunc(NMetadata::NProvider::TEvRefreshSubscriberData, Handle); hFunc(NActors::TEvents::TEvPoison, Handle); + hFunc(NTiers::TEvNotifySchemeObjectUpdated, Handle); + hFunc(NTiers::TEvNotifySchemeObjectDeleted, Handle); + hFunc(NTiers::TEvSchemeObjectResolutionFailed, Handle); + hFunc(NTiers::TEvWatchSchemeObject, Handle); default: break; } } - void Bootstrap() { - Become(&TThis::StateMain); - AFL_INFO(NKikimrServices::TX_TIERING)("event", "start_subscribing_metadata"); - Send(GetExternalDataActorId(), new NMetadata::NProvider::TEvSubscribeExternal(Owner->GetExternalDataManipulation())); - Send(GetExternalDataActorId(), new NMetadata::NProvider::TEvSubscribeExternal(SecretsFetcher)); - } - void Handle(NMetadata::NProvider::TEvRefreshSubscriberData::TPtr& ev) { auto snapshot = ev->Get()->GetSnapshot(); - if (auto configs = std::dynamic_pointer_cast(snapshot)) { - AFL_DEBUG(NKikimrServices::TX_TIERING)("event", "TEvRefreshSubscriberData")("snapshot", "configs"); - ConfigsSnapshot = configs; - if (SecretsSnapshot) { - Owner->TakeConfigs(ConfigsSnapshot, SecretsSnapshot); - } else { - ALS_DEBUG(NKikimrServices::TX_TIERING) << "Waiting secrets for update at tablet " << Owner->TabletId; - } - } else if (auto secrets = std::dynamic_pointer_cast(snapshot)) { + if (auto secrets = std::dynamic_pointer_cast(snapshot)) { AFL_DEBUG(NKikimrServices::TX_TIERING)("event", "TEvRefreshSubscriberData")("snapshot", "secrets"); - SecretsSnapshot = secrets; - if (ConfigsSnapshot) { - Owner->TakeConfigs(ConfigsSnapshot, SecretsSnapshot); - } else { - ALS_DEBUG(NKikimrServices::TX_TIERING) << "Waiting configs for update at tablet " << Owner->TabletId; - } + Owner->UpdateSecretsSnapshot(secrets); } else { - Y_ABORT_UNLESS(false, "unexpected behaviour"); + AFL_VERIFY(false); } } void Handle(NActors::TEvents::TEvPoison::TPtr& /*ev*/) { - Send(GetExternalDataActorId(), new NMetadata::NProvider::TEvUnsubscribeExternal(Owner->GetExternalDataManipulation())); Send(GetExternalDataActorId(), new NMetadata::NProvider::TEvUnsubscribeExternal(SecretsFetcher)); PassAway(); } + + void Handle(NTiers::TEvNotifySchemeObjectUpdated::TPtr& ev) { + AFL_DEBUG(NKikimrServices::TX_TIERING)("component", "tiering_manager")("event", "object_updated")("path", ev->Get()->GetObjectPath()); + const TString& objectPath = ev->Get()->GetObjectPath(); + const auto& description = ev->Get()->GetDescription(); + ResetRetryState(objectPath); + if (description.GetSelf().GetPathType() == NKikimrSchemeOp::EPathTypeExternalDataSource) { + NTiers::TTierConfig tier; + if (const auto status = tier.DeserializeFromProto(description.GetExternalDataSourceDescription()); status.IsFail()) { + AFL_WARN(NKikimrServices::TX_TIERING)("event", "fetched_invalid_tier_settings")("error", status.GetErrorMessage()); + OnInvalidTierConfig(objectPath); + return; + } + Owner->UpdateTierConfig(tier, objectPath); + } else { + AFL_WARN(false)("error", "invalid_object_type")("type", static_cast(description.GetSelf().GetPathType()))("path", objectPath); + OnInvalidTierConfig(objectPath); + } + } + + void Handle(NTiers::TEvNotifySchemeObjectDeleted::TPtr& ev) { + AFL_DEBUG(NKikimrServices::TX_TIERING)("component", "tiering_manager")("event", "object_deleted")("name", ev->Get()->GetObjectPath()); + OnInvalidTierConfig(ev->Get()->GetObjectPath()); + } + + void Handle(NTiers::TEvSchemeObjectResolutionFailed::TPtr& ev) { + const TString objectPath = ev->Get()->GetObjectPath(); + AFL_WARN(NKikimrServices::TX_TIERING)("event", "object_resolution_failed")("path", objectPath)( + "reason", static_cast(ev->Get()->GetReason())); + OnInvalidTierConfig(objectPath); + } + + void Handle(NTiers::TEvWatchSchemeObject::TPtr& ev) { + Send(TiersFetcher, ev->Release()); + } + +public: + TActor(std::shared_ptr owner) + : Owner(owner) + , RetryPolicy(IRetryPolicy::GetExponentialBackoffPolicy( + []() { + return ERetryErrorClass::ShortRetry; + }, + TDuration::MilliSeconds(10), TDuration::MilliSeconds(200), TDuration::Seconds(30), 10)) + , SecretsFetcher(std::make_shared()) { + } + + void Bootstrap() { + AFL_INFO(NKikimrServices::TX_TIERING)("event", "start_subscribing_metadata"); + TiersFetcher = Register(new TSchemeObjectWatcher(SelfId())); + Send(GetExternalDataActorId(), new NMetadata::NProvider::TEvSubscribeExternal(SecretsFetcher)); + Become(&TThis::StateMain); + } + + ~TActor() { + Owner->Stop(false); + } }; namespace NTiers { TManager& TManager::Restart(const TTierConfig& config, std::shared_ptr secrets) { - ALS_DEBUG(NKikimrServices::TX_TIERING) << "Restarting tier '" << GetTierName() << "' at tablet " << TabletId; - if (Config.IsSame(config)) { - return *this; - } + ALS_DEBUG(NKikimrServices::TX_TIERING) << "Restarting tier '" << TierName << "' at tablet " << TabletId; Stop(); - Config = config; - Start(secrets); + Start(config, secrets); return *this; } bool TManager::Stop() { S3Settings.reset(); - ALS_DEBUG(NKikimrServices::TX_TIERING) << "Tier '" << GetTierName() << "' stopped at tablet " << TabletId; + ALS_DEBUG(NKikimrServices::TX_TIERING) << "Tier '" << TierName << "' stopped at tablet " << TabletId; return true; } -bool TManager::Start(std::shared_ptr secrets) { - AFL_VERIFY(!S3Settings)("tier", GetTierName())("event", "already started"); - S3Settings = Config.GetPatchedConfig(secrets); - ALS_DEBUG(NKikimrServices::TX_TIERING) << "Tier '" << GetTierName() << "' started at tablet " << TabletId; +bool TManager::Start(const TTierConfig& config, std::shared_ptr secrets) { + AFL_VERIFY(!S3Settings)("tier", TierName)("event", "already started"); + auto patchedConfig = config.GetPatchedConfig(secrets); + if (patchedConfig.IsFail()) { + AFL_ERROR(NKikimrServices::TX_TIERING)("error", "cannot_read_secrets")("reason", patchedConfig.GetErrorMessage()); + return false; + } + S3Settings = patchedConfig.DetachResult(); + ALS_DEBUG(NKikimrServices::TX_TIERING) << "Tier '" << TierName << "' started at tablet " << TabletId; return true; } -TManager::TManager(const ui64 tabletId, const NActors::TActorId& tabletActorId, const TTierConfig& config) +TManager::TManager(const ui64 tabletId, const NActors::TActorId& tabletActorId, const TString& tierName) : TabletId(tabletId) , TabletActorId(tabletActorId) - , Config(config) -{ + , TierName(tierName) { } NArrow::NSerialization::TSerializerContainer ConvertCompression(const NKikimrSchemeOp::TCompressionOptions& compressionProto) { @@ -119,40 +184,68 @@ NArrow::NSerialization::TSerializerContainer ConvertCompression(const NKikimrSch } } -void TTiersManager::TakeConfigs(NMetadata::NFetcher::ISnapshot::TPtr snapshotExt, std::shared_ptr secrets) { - ALS_INFO(NKikimrServices::TX_TIERING) << "Take configs:" - << (snapshotExt ? " snapshots" : "") << (secrets ? " secrets" : "") << " at tablet " << TabletId; +TTiersManager::TTierRefGuard::TTierRefGuard(const TString& tierName, TTiersManager& owner) + : TierName(tierName) + , Owner(&owner) { + if (!Owner->TierRefCount.contains(TierName)) { + Owner->RegisterTier(tierName); + } + ++Owner->TierRefCount[TierName]; +} - auto snapshotPtr = std::dynamic_pointer_cast(snapshotExt); - Y_ABORT_UNLESS(snapshotPtr); - Snapshot = snapshotExt; - Secrets = secrets; - auto& snapshot = *snapshotPtr; - for (auto itSelf = Managers.begin(); itSelf != Managers.end(); ) { - auto it = snapshot.GetTierConfigs().find(itSelf->first); - if (it == snapshot.GetTierConfigs().end()) { - itSelf->second.Stop(); - itSelf = Managers.erase(itSelf); - } else { - itSelf->second.Restart(it->second, Secrets); - ++itSelf; +TTiersManager::TTierRefGuard::~TTierRefGuard() { + if (Owner) { + auto findTier = Owner->TierRefCount.FindPtr(TierName); + AFL_VERIFY(findTier); + AFL_VERIFY(*findTier); + --*findTier; + if (!*findTier) { + AFL_VERIFY(Owner->TierRefCount.erase(TierName)); + Owner->UnregisterTier(TierName); } } - for (auto&& i : snapshot.GetTierConfigs()) { - auto tierName = i.second.GetTierName(); - ALS_DEBUG(NKikimrServices::TX_TIERING) << "Take config for tier '" << tierName << "' at tablet " << TabletId; - if (Managers.contains(tierName)) { - ALS_DEBUG(NKikimrServices::TX_TIERING) << "Ignore tier '" << tierName << "' at tablet " << TabletId; - continue; +} + +void TTiersManager::OnConfigsUpdated(bool notifyShard) { + for (auto& [tierName, manager] : Managers) { + auto* findTierConfig = TierConfigs.FindPtr(tierName); + if (Secrets && findTierConfig) { + if (manager.IsReady()) { + manager.Restart(*findTierConfig, Secrets); + } else { + manager.Start(*findTierConfig, Secrets); + } + } else { + AFL_DEBUG(NKikimrServices::TX_TIERING)("event", "skip_tier_manager_reloading")("tier", tierName)("has_secrets", !!Secrets)( + "found_tier_config", !!findTierConfig); } - NTiers::TManager localManager(TabletId, TabletActorId, i.second); - auto itManager = Managers.emplace(tierName, std::move(localManager)).first; - itManager->second.Start(Secrets); } - if (ShardCallback && TlsActivationContext) { + if (notifyShard && ShardCallback && TlsActivationContext) { ShardCallback(TActivationContext::AsActorContext()); } + + AFL_DEBUG(NKikimrServices::TX_TIERING)("event", "configs_updated")("configs", DebugString()); +} + +void TTiersManager::RegisterTier(const TString& name) { + auto emplaced = Managers.emplace(name, NTiers::TManager(TabletId, TabletActorId, name)); + AFL_VERIFY(emplaced.second); + + auto* findConfig = TierConfigs.FindPtr(name); + if (Secrets && findConfig) { + emplaced.first->second.Start(*findConfig, Secrets); + } else { + AFL_DEBUG(NKikimrServices::TX_TIERING)("event", "skip_tier_manager_start")("tier", name)("has_secrets", !!Secrets)( + "found_tier_config", !!findConfig); + } +} + +void TTiersManager::UnregisterTier(const TString& name) { + auto findManager = Managers.find(name); + AFL_VERIFY(findManager != Managers.end()); + findManager->second.Stop(); + Managers.erase(findManager); } TTiersManager& TTiersManager::Start(std::shared_ptr ownerPtr) { @@ -185,37 +278,48 @@ const NTiers::TManager* TTiersManager::GetManagerOptional(const TString& tierId) } } -NMetadata::NFetcher::ISnapshotsFetcher::TPtr TTiersManager::GetExternalDataManipulation() const { - if (!ExternalDataManipulation) { - ExternalDataManipulation = std::make_shared(); +void TTiersManager::EnablePathId(const ui64 pathId, const THashSet& usedTiers) { + AFL_VERIFY(Actor)("error", "tiers_manager_is_not_started"); + auto& tierRefs = UsedTiers[pathId]; + tierRefs.clear(); + for (const TString& tierName : usedTiers) { + AFL_VERIFY(tierName == CanonizePath(tierName))("current", tierName)("canonized", CanonizePath(tierName)); + tierRefs.emplace_back(tierName, *this); + if (!TierConfigs.contains(tierName)) { + const auto& actorContext = NActors::TActivationContext::AsActorContext(); + AFL_VERIFY(&actorContext)("error", "no_actor_context"); + actorContext.Send(Actor->SelfId(), new NTiers::TEvWatchSchemeObject({ tierName })); + } } - return ExternalDataManipulation; + OnConfigsUpdated(false); } -THashMap TTiersManager::GetTiering() const { - THashMap result; - AFL_VERIFY(IsReady()); - auto snapshotPtr = std::dynamic_pointer_cast(Snapshot); - Y_ABORT_UNLESS(snapshotPtr); - auto& tierConfigs = snapshotPtr->GetTierConfigs(); - for (auto&& i : PathIdTiering) { - auto* tieringRule = snapshotPtr->GetTieringById(i.second); - if (tieringRule) { - AFL_DEBUG(NKikimrServices::TX_COLUMNSHARD)("path_id", i.first)("tiering_name", i.second)("event", "activation"); - NOlap::TTiering tiering = tieringRule->BuildOlapTiers(); - for (auto& [name, tier] : tiering.GetTierByName()) { - AFL_VERIFY(name != NOlap::NTiering::NCommon::DeleteTierName); - auto it = tierConfigs.find(name); - if (it != tierConfigs.end()) { - tier->SetSerializer(NTiers::ConvertCompression(it->second.GetCompression())); - } - } - result.emplace(i.first, std::move(tiering)); - } else { - AFL_ERROR(NKikimrServices::TX_COLUMNSHARD)("path_id", i.first)("tiering_name", i.second)("event", "not_found"); +void TTiersManager::DisablePathId(const ui64 pathId) { + UsedTiers.erase(pathId); + OnConfigsUpdated(false); +} + +void TTiersManager::UpdateSecretsSnapshot(std::shared_ptr secrets) { + AFL_INFO(NKikimrServices::TX_TIERING)("event", "update_secrets")("tablet", TabletId); + AFL_VERIFY(secrets); + Secrets = secrets; + OnConfigsUpdated(); +} + +void TTiersManager::UpdateTierConfig(const NTiers::TTierConfig& config, const TString& tierName, const bool notifyShard) { + AFL_INFO(NKikimrServices::TX_TIERING)("event", "update_tier_config")("name", tierName)("tablet", TabletId); + AFL_VERIFY(tierName == CanonizePath(tierName))("current", tierName)("canonized", CanonizePath(tierName)); + TierConfigs[tierName] = config; + OnConfigsUpdated(notifyShard); +} + +bool TTiersManager::AreConfigsComplete() const { + for (const auto& [tier, cnt] : TierRefCount) { + if (!TierConfigs.contains(tier)) { + return false; } } - return result; + return true; } TActorId TTiersManager::GetActorId() const { @@ -226,4 +330,36 @@ TActorId TTiersManager::GetActorId() const { } } +TString TTiersManager::DebugString() { + TStringBuilder sb; + sb << "TIERS="; + if (TierConfigs) { + sb << "{"; + for (const auto& [name, config] : TierConfigs) { + sb << name << ";"; + } + sb << "}"; + } + sb << ";USED_TIERS="; + { + sb << "{"; + for (const auto& [pathId, tiers] : UsedTiers) { + sb << pathId << ":{"; + for (const auto& tierRef : tiers) { + sb << tierRef.GetTierName() << ";"; + } + sb << "}"; + } + sb << "}"; + } + sb << ";SECRETS="; + if (Secrets) { + sb << "{"; + for (const auto& [name, config] : Secrets->GetSecrets()) { + sb << name.SerializeToString() << ";"; + } + sb << "}"; + } + return sb; +} } diff --git a/ydb/core/tx/tiering/manager.h b/ydb/core/tx/tiering/manager.h index 147ee27f54f8..2aa78f597b7d 100644 --- a/ydb/core/tx/tiering/manager.h +++ b/ydb/core/tx/tiering/manager.h @@ -1,17 +1,15 @@ #pragma once -#include "external_data.h" +#include "common.h" #include "abstract/manager.h" -#include -#include +#include +#include #include #include #include -#include - #include namespace NKikimr::NColumnShard { @@ -23,9 +21,9 @@ NArrow::NSerialization::TSerializerContainer ConvertCompression(const NKikimrSch class TManager { private: ui64 TabletId = 0; - YDB_READONLY_DEF(NActors::TActorId, TabletActorId); - YDB_READONLY_DEF(TTierConfig, Config); - YDB_READONLY_DEF(NActors::TActorId, StorageActorId); + NActors::TActorId TabletActorId; + TString TierName; + NActors::TActorId StorageActorId; std::optional S3Settings; public: const NKikimrSchemeOp::TS3Settings& GetS3Settings() const { @@ -33,64 +31,89 @@ class TManager { return *S3Settings; } - TManager(const ui64 tabletId, const NActors::TActorId& tabletActorId, const TTierConfig& config); + TManager(const ui64 tabletId, const NActors::TActorId& tabletActorId, const TString& tierName); + bool IsReady() const { + return !!S3Settings; + } TManager& Restart(const TTierConfig& config, std::shared_ptr secrets); bool Stop(); - bool Start(std::shared_ptr secrets); - - TString GetTierName() const { - return GetConfig().GetTierName(); - } + bool Start(const TTierConfig& config, std::shared_ptr secrets); }; } class TTiersManager: public ITiersManager { +private: + friend class TTierRef; + class TTierRefGuard: public TMoveOnly { + private: + YDB_READONLY_DEF(TString, TierName); + TTiersManager* Owner; + + public: + TTierRefGuard(const TString& tierName, TTiersManager& owner); + ~TTierRefGuard(); + + TTierRefGuard(TTierRefGuard&& other) + : TierName(other.TierName) + , Owner(other.Owner) { + other.Owner = nullptr; + } + TTierRefGuard& operator=(TTierRefGuard&& other) { + std::swap(Owner, other.Owner); + std::swap(TierName, other.TierName); + return *this; + } + }; + private: class TActor; + friend class TActor; using TManagers = std::map; + ui64 TabletId = 0; const TActorId TabletActorId; std::function ShardCallback; - TActor* Actor = nullptr; - std::unordered_map PathIdTiering; + IActor* Actor = nullptr; TManagers Managers; - std::shared_ptr Secrets; - NMetadata::NFetcher::ISnapshot::TPtr Snapshot; - mutable NMetadata::NFetcher::ISnapshotsFetcher::TPtr ExternalDataManipulation; + using TTierRefCount = THashMap; + using TTierRefsByPathId = THashMap>; + YDB_READONLY_DEF(TTierRefCount, TierRefCount); + YDB_READONLY_DEF(TTierRefsByPathId, UsedTiers); + + using TTierByName = THashMap; + YDB_READONLY_DEF(TTierByName, TierConfigs); + YDB_READONLY_DEF(std::shared_ptr, Secrets); + +private: + void OnConfigsUpdated(bool notifyShard = true); + void RegisterTier(const TString& name); + void UnregisterTier(const TString& name); public: - TTiersManager(const ui64 tabletId, const TActorId& tabletActorId, - std::function shardCallback = {}) + TTiersManager(const ui64 tabletId, const TActorId& tabletActorId, std::function shardCallback = {}) : TabletId(tabletId) , TabletActorId(tabletActorId) , ShardCallback(shardCallback) - { + , Secrets(std::make_shared(TInstant::Zero())) { } TActorId GetActorId() const; - THashMap GetTiering() const; - void TakeConfigs(NMetadata::NFetcher::ISnapshot::TPtr snapshot, std::shared_ptr secrets); - void EnablePathId(const ui64 pathId, const TString& tieringId) { - PathIdTiering.emplace(pathId, tieringId); - } - void DisablePathId(const ui64 pathId) { - PathIdTiering.erase(pathId); - } + void EnablePathId(const ui64 pathId, const THashSet& usedTiers); + void DisablePathId(const ui64 pathId); - bool IsReady() const { - return !!Snapshot; - } + void UpdateSecretsSnapshot(std::shared_ptr secrets); + void UpdateTierConfig(const NTiers::TTierConfig& config, const TString& tierName, const bool notifyShard = true); + bool AreConfigsComplete() const; + + TString DebugString(); TTiersManager& Start(std::shared_ptr ownerPtr); TTiersManager& Stop(const bool needStopActor); virtual const std::map& GetManagers() const override { - AFL_VERIFY(IsReady()); return Managers; } virtual const NTiers::TManager* GetManagerOptional(const TString& tierId) const override; - NMetadata::NFetcher::ISnapshotsFetcher::TPtr GetExternalDataManipulation() const; - }; } diff --git a/ydb/core/tx/tiering/rule/behaviour.cpp b/ydb/core/tx/tiering/rule/behaviour.cpp deleted file mode 100644 index df7ad1973101..000000000000 --- a/ydb/core/tx/tiering/rule/behaviour.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "behaviour.h" -#include "initializer.h" -#include "checker.h" -#include "manager.h" - -namespace NKikimr::NColumnShard::NTiers { - -TTieringRuleBehaviour::TFactory::TRegistrator TTieringRuleBehaviour::Registrator(TTieringRule::GetTypeId()); - -TString TTieringRuleBehaviour::GetInternalStorageTablePath() const { - return "tiering/rules"; -} - -NMetadata::NInitializer::IInitializationBehaviour::TPtr TTieringRuleBehaviour::ConstructInitializer() const { - return std::make_shared(); -} - -NMetadata::NModifications::IOperationsManager::TPtr TTieringRuleBehaviour::ConstructOperationsManager() const { - return std::make_shared(); -} - -TString TTieringRuleBehaviour::GetTypeId() const { - return TTieringRule::GetTypeId(); -} - -} diff --git a/ydb/core/tx/tiering/rule/behaviour.h b/ydb/core/tx/tiering/rule/behaviour.h deleted file mode 100644 index c10f2f24d73f..000000000000 --- a/ydb/core/tx/tiering/rule/behaviour.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "object.h" -#include - -namespace NKikimr::NColumnShard::NTiers { - -class TTieringRuleBehaviour: public NMetadata::TClassBehaviour { -private: - static TFactory::TRegistrator Registrator; -protected: - virtual std::shared_ptr ConstructInitializer() const override; - virtual std::shared_ptr ConstructOperationsManager() const override; - - virtual TString GetInternalStorageTablePath() const override; - virtual TString GetTypeId() const override; - -}; - -} diff --git a/ydb/core/tx/tiering/rule/checker.cpp b/ydb/core/tx/tiering/rule/checker.cpp deleted file mode 100644 index 1210b66ed0b4..000000000000 --- a/ydb/core/tx/tiering/rule/checker.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "checker.h" -#include "ss_checker.h" - -#include -#include -#include -#include -#include -#include - -namespace NKikimr::NColumnShard::NTiers { - -void TRulePreparationActor::StartChecker() { - if (!Tierings || !Secrets || !SSCheckResult) { - return; - } - auto g = PassAwayGuard(); - if (!SSCheckResult->GetContent().GetOperationAllow()) { - Controller->OnPreparationProblem(SSCheckResult->GetContent().GetDenyReason()); - return; - } - - for (auto&& tiering : Objects) { - for (auto&& interval : tiering.GetIntervals()) { - auto tier = Tierings->GetTierById(interval.GetTierName()); - if (!tier) { - Controller->OnPreparationProblem("unknown tier usage: " + interval.GetTierName()); - return; - } else if (!Secrets->CheckSecretAccess(tier->GetAccessKey(), Context.GetExternalData().GetUserToken())) { - Controller->OnPreparationProblem("no access for secret: " + tier->GetAccessKey().DebugString()); - return; - } else if (!Secrets->CheckSecretAccess(tier->GetSecretKey(), Context.GetExternalData().GetUserToken())) { - Controller->OnPreparationProblem("no access for secret: " + tier->GetSecretKey().DebugString()); - return; - } - } - } - Controller->OnPreparationFinished(std::move(Objects)); -} - -void TRulePreparationActor::Handle(NSchemeShard::TEvSchemeShard::TEvProcessingResponse::TPtr& ev) { - auto& proto = ev->Get()->Record; - if (proto.HasError()) { - Controller->OnPreparationProblem(proto.GetError().GetErrorMessage()); - PassAway(); - } else if (proto.HasContent()) { - SSCheckResult = SSFetcher->UnpackResult(ev->Get()->Record.GetContent().GetData()); - if (!SSCheckResult) { - Controller->OnPreparationProblem("cannot unpack ss-fetcher result for class " + SSFetcher->GetClassName()); - PassAway(); - } else { - StartChecker(); - } - } else { - Y_ABORT_UNLESS(false); - } -} - -void TRulePreparationActor::Handle(NMetadata::NProvider::TEvRefreshSubscriberData::TPtr& ev) { - if (auto snapshot = ev->Get()->GetSnapshotPtrAs()) { - Tierings = snapshot; - } else if (auto snapshot = ev->Get()->GetSnapshotPtrAs()) { - Secrets = snapshot; - } else { - Y_ABORT_UNLESS(false); - } - StartChecker(); -} - -void TRulePreparationActor::Bootstrap() { - Become(&TThis::StateMain); - Send(NMetadata::NProvider::MakeServiceId(SelfId().NodeId()), - new NMetadata::NProvider::TEvAskSnapshot(std::make_shared())); - Send(NMetadata::NProvider::MakeServiceId(SelfId().NodeId()), - new NMetadata::NProvider::TEvAskSnapshot(std::make_shared())); - { - SSFetcher = std::make_shared(); - SSFetcher->SetUserToken(Context.GetExternalData().GetUserToken()); - SSFetcher->SetActivityType(Context.GetActivityType()); - for (auto&& i : Objects) { - SSFetcher->MutableTieringRuleIds().emplace(i.GetTieringRuleId()); - } - Register(new TSSFetchingActor(SSFetcher, std::make_shared(SelfId()), TDuration::Seconds(10))); - } -} - -TRulePreparationActor::TRulePreparationActor(std::vector&& objects, - NMetadata::NModifications::IAlterPreparationController::TPtr controller, - const NMetadata::NModifications::IOperationsManager::TInternalModificationContext& context) - : Objects(std::move(objects)) - , Controller(controller) - , Context(context) -{ - -} - -} diff --git a/ydb/core/tx/tiering/rule/checker.h b/ydb/core/tx/tiering/rule/checker.h deleted file mode 100644 index ec6e0f3d66e7..000000000000 --- a/ydb/core/tx/tiering/rule/checker.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once -#include "object.h" -#include "ss_fetcher.h" - -#include -#include - -#include -#include -#include -#include - -namespace NKikimr::NColumnShard::NTiers { - -class TRulePreparationActor: public NActors::TActorBootstrapped { -private: - std::vector Objects; - NMetadata::NModifications::IAlterPreparationController::TPtr Controller; - NMetadata::NModifications::IOperationsManager::TInternalModificationContext Context; - std::shared_ptr Tierings; - std::shared_ptr Secrets; - std::shared_ptr SSFetcher; - std::optional SSCheckResult; - void StartChecker(); -protected: - void Handle(NMetadata::NProvider::TEvRefreshSubscriberData::TPtr& ev); - void Handle(NSchemeShard::TEvSchemeShard::TEvProcessingResponse::TPtr& ev); -public: - STATEFN(StateMain) { - switch (ev->GetTypeRewrite()) { - hFunc(NMetadata::NProvider::TEvRefreshSubscriberData, Handle); - hFunc(NSchemeShard::TEvSchemeShard::TEvProcessingResponse, Handle); - default: - break; - } - } - void Bootstrap(); - - TRulePreparationActor(std::vector&& objects, - NMetadata::NModifications::IAlterPreparationController::TPtr controller, - const NMetadata::NModifications::IOperationsManager::TInternalModificationContext& context); -}; - -} diff --git a/ydb/core/tx/tiering/rule/initializer.cpp b/ydb/core/tx/tiering/rule/initializer.cpp deleted file mode 100644 index 96c1c3cff550..000000000000 --- a/ydb/core/tx/tiering/rule/initializer.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "initializer.h" -#include "object.h" - -namespace NKikimr::NColumnShard::NTiers { - -TVector TTierRulesInitializer::BuildModifiers() const { - TVector result; - { - Ydb::Table::CreateTableRequest request; - request.set_session_id(""); - request.set_path(TTieringRule::GetBehaviour()->GetStorageTablePath()); - request.add_primary_key("tieringRuleId"); - { - auto& column = *request.add_columns(); - column.set_name("tieringRuleId"); - column.mutable_type()->mutable_optional_type()->mutable_item()->set_type_id(Ydb::Type::UTF8); - } - { - auto& column = *request.add_columns(); - column.set_name("defaultColumn"); - column.mutable_type()->mutable_optional_type()->mutable_item()->set_type_id(Ydb::Type::UTF8); - } - { - auto& column = *request.add_columns(); - column.set_name("description"); - column.mutable_type()->mutable_optional_type()->mutable_item()->set_type_id(Ydb::Type::UTF8); - } - result.emplace_back(new NMetadata::NInitializer::TGenericTableModifier(request, "create")); - auto hRequest = TTieringRule::AddHistoryTableScheme(request); - result.emplace_back(new NMetadata::NInitializer::TGenericTableModifier(hRequest, "create_history")); - } - result.emplace_back(NMetadata::NInitializer::TACLModifierConstructor::GetReadOnlyModifier(TTieringRule::GetBehaviour()->GetStorageTablePath(), "acl")); - result.emplace_back(NMetadata::NInitializer::TACLModifierConstructor::GetReadOnlyModifier(TTieringRule::GetBehaviour()->GetStorageHistoryTablePath(), "acl_history")); - return result; -} - -void TTierRulesInitializer::DoPrepare(NMetadata::NInitializer::IInitializerInput::TPtr controller) const { - controller->OnPreparationFinished(BuildModifiers()); -} - -} diff --git a/ydb/core/tx/tiering/rule/initializer.h b/ydb/core/tx/tiering/rule/initializer.h deleted file mode 100644 index 93f15e78f9c4..000000000000 --- a/ydb/core/tx/tiering/rule/initializer.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once -#include -#include -#include - -namespace NKikimr::NColumnShard::NTiers { - -class TTierRulesInitializer: public NMetadata::NInitializer::IInitializationBehaviour { -protected: - TVector BuildModifiers() const; - virtual void DoPrepare(NMetadata::NInitializer::IInitializerInput::TPtr controller) const override; -public: -}; - -} diff --git a/ydb/core/tx/tiering/rule/manager.cpp b/ydb/core/tx/tiering/rule/manager.cpp deleted file mode 100644 index a97ba742467a..000000000000 --- a/ydb/core/tx/tiering/rule/manager.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "manager.h" -#include "initializer.h" -#include "checker.h" - -namespace NKikimr::NColumnShard::NTiers { - -void TTieringRulesManager::DoPrepareObjectsBeforeModification(std::vector&& objects, - NMetadata::NModifications::IAlterPreparationController::TPtr controller, - const TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& /*alterContext*/) const { - TActivationContext::Register(new TRulePreparationActor(std::move(objects), controller, context)); -} - -NMetadata::NModifications::TOperationParsingResult TTieringRulesManager::DoBuildPatchFromSettings( - const NYql::TObjectSettingsImpl& settings, - TInternalModificationContext& /*context*/) const { - if (HasAppData() && !AppDataVerified().FeatureFlags.GetEnableTieringInColumnShard()) { - return TConclusionStatus::Fail("Tiering functionality is disabled for OLAP tables."); - } - - NMetadata::NInternal::TTableRecord result; - result.SetColumn(TTieringRule::TDecoder::TieringRuleId, NMetadata::NInternal::TYDBValue::Utf8(settings.GetObjectId())); - if (settings.GetObjectId().StartsWith("$") || settings.GetObjectId().StartsWith("_")) { - return TConclusionStatus::Fail("tiering rule cannot start with '$', '_' characters"); - } - { - auto fValue = settings.GetFeaturesExtractor().Extract(TTieringRule::TDecoder::DefaultColumn); - if (fValue) { - if (fValue->Empty()) { - return TConclusionStatus::Fail("defaultColumn cannot be empty"); - } - result.SetColumn(TTieringRule::TDecoder::DefaultColumn, NMetadata::NInternal::TYDBValue::Utf8(*fValue)); - } - } - { - auto fValue = settings.GetFeaturesExtractor().Extract(TTieringRule::TDecoder::Description); - if (fValue) { - result.SetColumn(TTieringRule::TDecoder::Description, NMetadata::NInternal::TYDBValue::Utf8(*fValue)); - } - } - return result; -} - -} diff --git a/ydb/core/tx/tiering/rule/manager.h b/ydb/core/tx/tiering/rule/manager.h deleted file mode 100644 index d5646dbf3002..000000000000 --- a/ydb/core/tx/tiering/rule/manager.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include "object.h" - -#include - -namespace NKikimr::NColumnShard::NTiers { - -class TTieringRulesManager: public NMetadata::NModifications::TGenericOperationsManager { -protected: - virtual void DoPrepareObjectsBeforeModification(std::vector&& objects, - NMetadata::NModifications::IAlterPreparationController::TPtr controller, - const TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& alterContext) const override; - - virtual NMetadata::NModifications::TOperationParsingResult DoBuildPatchFromSettings(const NYql::TObjectSettingsImpl& settings, - TInternalModificationContext& context) const override; -}; - -} diff --git a/ydb/core/tx/tiering/rule/object.cpp b/ydb/core/tx/tiering/rule/object.cpp deleted file mode 100644 index a596b56890ca..000000000000 --- a/ydb/core/tx/tiering/rule/object.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "object.h" -#include "behaviour.h" - -#include -#include - -#include - -namespace NKikimr::NColumnShard::NTiers { - -NJson::TJsonValue TTieringRule::GetDebugJson() const { - NJson::TJsonValue result = NJson::JSON_MAP; - result.InsertValue(TDecoder::TieringRuleId, TieringRuleId); - result.InsertValue(TDecoder::DefaultColumn, DefaultColumn); - result.InsertValue(TDecoder::Description, SerializeDescriptionToJson()); - return result; -} - -NJson::TJsonValue TTieringRule::SerializeDescriptionToJson() const { - NJson::TJsonValue result = NJson::JSON_MAP; - auto& jsonRules = result.InsertValue("rules", NJson::JSON_ARRAY); - for (auto&& i : Intervals) { - jsonRules.AppendValue(i.SerializeToJson()); - } - return result; -} - -bool TTieringRule::DeserializeDescriptionFromJson(const NJson::TJsonValue& jsonInfo) { - const NJson::TJsonValue::TArray* rules; - if (!jsonInfo["rules"].GetArrayPointer(&rules)) { - return false; - } - if (rules->empty()) { - AFL_INFO(NKikimrServices::TX_COLUMNSHARD)("event", "tiering_rule_deserialization_failed")("reason", "empty_rules"); - return false; - } - for (auto&& i : *rules) { - TTieringInterval interval; - if (!interval.DeserializeFromJson(i)) { - return false; - } - Intervals.emplace_back(std::move(interval)); - } - std::sort(Intervals.begin(), Intervals.end()); - return true; -} - -NMetadata::NInternal::TTableRecord TTieringRule::SerializeToRecord() const { - NMetadata::NInternal::TTableRecord result; - result.SetColumn(TDecoder::TieringRuleId, NMetadata::NInternal::TYDBValue::Utf8(TieringRuleId)); - result.SetColumn(TDecoder::DefaultColumn, NMetadata::NInternal::TYDBValue::Utf8(DefaultColumn)); - { - auto jsonDescription = SerializeDescriptionToJson(); - NJsonWriter::TBuf sout; - sout.WriteJsonValue(&jsonDescription, true); - result.SetColumn(TDecoder::Description, NMetadata::NInternal::TYDBValue::Utf8(sout.Str())); - } - return result; -} - -bool TTieringRule::DeserializeFromRecord(const TDecoder& decoder, const Ydb::Value& r) { - if (!decoder.Read(decoder.GetTieringRuleIdIdx(), TieringRuleId, r)) { - return false; - } - if (!decoder.Read(decoder.GetDefaultColumnIdx(), DefaultColumn, r)) { - return false; - } - if (DefaultColumn.Empty()) { - return false; - } - NJson::TJsonValue jsonDescription; - if (!decoder.ReadJson(decoder.GetDescriptionIdx(), jsonDescription, r)) { - return false; - } - if (!DeserializeDescriptionFromJson(jsonDescription)) { - return false; - } - return true; -} - -NKikimr::NOlap::TTiering TTieringRule::BuildOlapTiers() const { - AFL_VERIFY(!Intervals.empty()); - NOlap::TTiering result; - for (auto&& r : Intervals) { - AFL_VERIFY(result.Add(std::make_shared(r.GetTierName(), r.GetDurationForEvict(), GetDefaultColumn()))); - } - return result; -} - -bool TTieringRule::ContainsTier(const TString& tierName) const { - for (auto&& i : Intervals) { - if (i.GetTierName() == tierName) { - return true; - } - } - return false; -} - -NMetadata::IClassBehaviour::TPtr TTieringRule::GetBehaviour() { - static std::shared_ptr result = std::make_shared(); - return result; -} - -} diff --git a/ydb/core/tx/tiering/rule/object.h b/ydb/core/tx/tiering/rule/object.h deleted file mode 100644 index 566f10e5efc8..000000000000 --- a/ydb/core/tx/tiering/rule/object.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once -#include -#include - -#include -#include -#include -#include - -#include - -namespace NKikimr::NColumnShard::NTiers { - -class TTieringInterval { -private: - YDB_ACCESSOR_DEF(TString, TierName); - YDB_ACCESSOR_DEF(TDuration, DurationForEvict); -public: - TTieringInterval() = default; - TTieringInterval(const TString& name, const TDuration d) - : TierName(name) - , DurationForEvict(d) - { - - } - - bool operator<(const TTieringInterval& item) const { - return DurationForEvict < item.DurationForEvict; - } - - NJson::TJsonValue SerializeToJson() const { - NJson::TJsonValue result; - result.InsertValue("tierName", TierName); - result.InsertValue("durationForEvict", DurationForEvict.ToString()); - return result; - } - - bool DeserializeFromJson(const NJson::TJsonValue& jsonInfo) { - if (!jsonInfo["tierName"].GetString(&TierName)) { - return false; - } - const TString dStr = jsonInfo["durationForEvict"].GetStringRobust(); - if (!TDuration::TryParse(dStr, DurationForEvict)) { - return false; - } - return true; - } -}; - -class TTieringRule: public NMetadata::NModifications::TObject { -private: - YDB_ACCESSOR_DEF(TString, TieringRuleId); - YDB_ACCESSOR_DEF(TString, DefaultColumn); - YDB_ACCESSOR_DEF(TVector, Intervals); -protected: - NJson::TJsonValue SerializeDescriptionToJson() const; - bool DeserializeDescriptionFromJson(const NJson::TJsonValue& jsonInfo); -public: - static NMetadata::IClassBehaviour::TPtr GetBehaviour(); - - bool ContainsTier(const TString& tierName) const; - - void AddInterval(const TString& name, const TDuration evDuration) { - Intervals.emplace_back(TTieringInterval(name, evDuration)); - } - - static TString GetTypeId() { - return "TIERING_RULE"; - } - - NJson::TJsonValue GetDebugJson() const; - - class TDecoder: public NMetadata::NInternal::TDecoderBase { - private: - YDB_READONLY(i32, TieringRuleIdIdx, -1); - YDB_READONLY(i32, DefaultColumnIdx, -1); - YDB_READONLY(i32, DescriptionIdx, -1); - public: - static inline const TString TieringRuleId = "tieringRuleId"; - static inline const TString DefaultColumn = "defaultColumn"; - static inline const TString Description = "description"; - - TDecoder(const Ydb::ResultSet& rawData) { - TieringRuleIdIdx = GetFieldIndex(rawData, TieringRuleId); - DefaultColumnIdx = GetFieldIndex(rawData, DefaultColumn); - DescriptionIdx = GetFieldIndex(rawData, Description); - } - }; - NMetadata::NInternal::TTableRecord SerializeToRecord() const; - bool DeserializeFromRecord(const TDecoder& decoder, const Ydb::Value& r); - NKikimr::NOlap::TTiering BuildOlapTiers() const; -}; - -} diff --git a/ydb/core/tx/tiering/rule/ss_checker.cpp b/ydb/core/tx/tiering/rule/ss_checker.cpp deleted file mode 100644 index 7626a13a309e..000000000000 --- a/ydb/core/tx/tiering/rule/ss_checker.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "ss_checker.h" - -#include -#include - -namespace NKikimr::NColumnShard::NTiers { - -void TSSFetchingActor::Handle(NSchemeShard::TEvSchemeShard::TEvProcessingResponse::TPtr& ev) { - auto g = PassAwayGuard(); - Controller->FetchingResult(ev->Get()->Record); -} - -TSSFetchingActor::TSSFetchingActor(NSchemeShard::ISSDataProcessor::TPtr processor, - ISSFetchingController::TPtr controller, const TDuration livetime) - : TBase(livetime) - , Processor(processor) - , Controller(controller) -{ - -} - -constexpr NKikimrServices::TActivity::EType TSSFetchingActor::ActorActivityType() { - return NKikimrServices::TActivity::SS_FETCHING_ACTOR; -} - -} diff --git a/ydb/core/tx/tiering/rule/ss_checker.h b/ydb/core/tx/tiering/rule/ss_checker.h deleted file mode 100644 index d1e9e777e5f5..000000000000 --- a/ydb/core/tx/tiering/rule/ss_checker.h +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once -#include "object.h" - -#include -#include -#include -#include -#include -#include - -namespace NKikimr::NColumnShard::NTiers { - -class ISSFetchingController { -public: - using TPtr = std::shared_ptr; - virtual ~ISSFetchingController() = default; - virtual void FetchingProblem(const TString& errorMessage) const = 0; - virtual void FetchingResult(const NKikimrScheme::TEvProcessingResponse& result) const = 0; -}; - -class TSSFetchingController: public ISSFetchingController { -private: - const TActorIdentity ActorId; -public: - TSSFetchingController(const TActorIdentity& actorId) - : ActorId(actorId) { - - } - - virtual void FetchingProblem(const TString& errorMessage) const override { - ActorId.Send(ActorId, new NSchemeShard::TEvSchemeShard::TEvProcessingResponse(errorMessage)); - } - virtual void FetchingResult(const NKikimrScheme::TEvProcessingResponse& result) const override { - ActorId.Send(ActorId, new NSchemeShard::TEvSchemeShard::TEvProcessingResponse(result)); - } -}; - -class TSSFetchingActor: public NMetadata::NInternal::TSSDialogActor { -private: - using TBase = NMetadata::NInternal::TSSDialogActor; - NSchemeShard::ISSDataProcessor::TPtr Processor; - ISSFetchingController::TPtr Controller; - void Handle(NSchemeShard::TEvSchemeShard::TEvProcessingResponse::TPtr& ev); -protected: - virtual void OnBootstrap() override { - UnsafeBecome(&TSSFetchingActor::StateMain); - TBase::OnBootstrap(); - } - virtual void OnFail(const TString& errorMessage) override { - Controller->FetchingProblem(errorMessage); - } - virtual void Execute() override { - auto req = std::make_unique(*Processor); - Send(SchemeShardPipe, new TEvPipeCache::TEvForward(req.release(), SchemeShardId, false)); - } -public: - static constexpr NKikimrServices::TActivity::EType ActorActivityType(); - STFUNC(StateMain) { - switch (ev->GetTypeRewrite()) { - hFunc(NSchemeShard::TEvSchemeShard::TEvProcessingResponse, Handle); - default: - TBase::StateMain(ev); - } - } - TSSFetchingActor(NSchemeShard::ISSDataProcessor::TPtr processor, ISSFetchingController::TPtr controller, const TDuration livetime); -}; - -} diff --git a/ydb/core/tx/tiering/rule/ss_fetcher.cpp b/ydb/core/tx/tiering/rule/ss_fetcher.cpp deleted file mode 100644 index e822ace4c5a8..000000000000 --- a/ydb/core/tx/tiering/rule/ss_fetcher.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "ss_fetcher.h" -#include - -namespace NKikimr::NColumnShard::NTiers { - -TFetcherCheckUserTieringPermissions::TFactory::TRegistrator - TFetcherCheckUserTieringPermissions::Registrator(TFetcherCheckUserTieringPermissions::GetTypeIdStatic()); - -void TFetcherCheckUserTieringPermissions::DoProcess(NSchemeShard::TSchemeShard& schemeShard, NKikimrScheme::TEvProcessingResponse& result) const { - TResult content; - content.MutableContent().SetOperationAllow(true); - ui32 access = 0; - access |= NACLib::EAccessRights::AlterSchema; - - if (ActivityType == NMetadata::NModifications::IOperationsManager::EActivityType::Undefined) { - content.Deny("undefined activity type"); - } else { - bool denied = false; - for (auto&& i : TieringRuleIds) { - const auto& pathIds = schemeShard.ColumnTables.GetTablesWithTiering(i); - for (auto&& pathId : pathIds) { - auto path = NSchemeShard::TPath::Init(pathId, &schemeShard); - if (!path.IsResolved() || path.IsUnderDeleting() || path.IsDeleted()) { - continue; - } - if (ActivityType == NMetadata::NModifications::IOperationsManager::EActivityType::Drop) { - denied = true; - content.Deny("tiering in using by table"); - break; - } else if (ActivityType == NMetadata::NModifications::IOperationsManager::EActivityType::Alter) { - if (!UserToken) { - continue; - } - TSecurityObject sObject(path->Owner, path->ACL, path->IsContainer()); - if (!sObject.CheckAccess(access, *UserToken)) { - denied = true; - content.Deny("no alter permissions for affected table"); - break; - } - } - } - if (denied) { - break; - } - } - } - result.MutableContent()->SetData(content.SerializeToString()); -} - -bool TFetcherCheckUserTieringPermissions::DoDeserializeFromProto(const TProtoClass& protoData) { - if (!TryFromString(protoData.GetActivityType(), ActivityType)) { - ALS_ERROR(0) << "Cannot parse activity type: undefined value = " << protoData.GetActivityType(); - return false; - } - if (protoData.GetUserToken()) { - NACLib::TUserToken uToken(protoData.GetUserToken()); - UserToken = uToken; - } - for (auto&& i : protoData.GetTieringRuleIds()) { - TieringRuleIds.emplace(i); - } - return true; -} - -NKikimr::NColumnShard::NTiers::TFetcherCheckUserTieringPermissions::TProtoClass TFetcherCheckUserTieringPermissions::DoSerializeToProto() const { - TProtoClass result; - result.SetActivityType(::ToString(ActivityType)); - if (UserToken) { - result.SetUserToken(UserToken->SerializeAsString()); - } - for (auto&& i : TieringRuleIds) { - *result.AddTieringRuleIds() = i; - } - return result; -} - -} diff --git a/ydb/core/tx/tiering/rule/ss_fetcher.h b/ydb/core/tx/tiering/rule/ss_fetcher.h deleted file mode 100644 index 8d0891d7362d..000000000000 --- a/ydb/core/tx/tiering/rule/ss_fetcher.h +++ /dev/null @@ -1,79 +0,0 @@ -#pragma once -#include "object.h" - -#include -#include -#include - -#include - -namespace NKikimr::NColumnShard::NTiers { - -class TFetcherCheckUserTieringPermissionsResult: public NBackgroundTasks::IProtoStringSerializable< - NKikimrScheme::TFetcherCheckUserTieringPermissionsResult, NBackgroundTasks::IStringSerializable> { -private: - using TProtoClass = NKikimrScheme::TFetcherCheckUserTieringPermissionsResult; - YDB_ACCESSOR_DEF(TProtoClass, Content); -protected: - virtual TProtoClass DoSerializeToProto() const override { - return Content; - } - virtual bool DoDeserializeFromProto(const TProtoClass& protoData) override { - Content = protoData; - return true; - } -public: - void Deny(const TString& reason) { - Content.SetOperationAllow(false); - Content.SetDenyReason(reason); - } -}; - -class TFetcherCheckUserTieringPermissions: public NBackgroundTasks::IProtoStringSerializable< - NKikimrScheme::TFetcherCheckUserTieringPermissions, NSchemeShard::ISSDataProcessor> { -private: - using TBase = NBackgroundTasks::IProtoStringSerializable< - NKikimrScheme::TFetcherCheckUserTieringPermissions, NSchemeShard::ISSDataProcessor>; - using TBase::TFactory; - using TProtoClass = NKikimrScheme::TFetcherCheckUserTieringPermissions; - static TFactory::TRegistrator Registrator; - YDB_ACCESSOR_DEF(std::set, TieringRuleIds); - YDB_ACCESSOR_DEF(std::optional, UserToken); - YDB_ACCESSOR(NMetadata::NModifications::IOperationsManager::EActivityType, ActivityType, - NMetadata::NModifications::IOperationsManager::EActivityType::Undefined); -protected: - virtual TProtoClass DoSerializeToProto() const override; - virtual bool DoDeserializeFromProto(const TProtoClass& protoData) override; - virtual void DoProcess(NSchemeShard::TSchemeShard& schemeShard, NKikimrScheme::TEvProcessingResponse& result) const override; -public: - using TResult = TFetcherCheckUserTieringPermissionsResult; - std::optional UnpackResult(const TString& content) const { - TFetcherCheckUserTieringPermissionsResult result; - if (!result.DeserializeFromString(content)) { - return {}; - } else { - return result; - } - } - - TFetcherCheckUserTieringPermissions() = default; - - virtual TString DebugString() const override { - TStringBuilder sb; - sb << "USID=" << (UserToken ? UserToken->GetUserSID() : "nobody") << ";"; - sb << "tierings="; - for (auto&& i : TieringRuleIds) { - sb << i << ","; - } - sb << ";"; - return sb; - } - virtual TString GetClassName() const override { - return GetTypeIdStatic(); - } - static TString GetTypeIdStatic() { - return "ss_fetcher_tiering_permissions"; - } -}; - -} diff --git a/ydb/core/tx/tiering/rule/ya.make b/ydb/core/tx/tiering/rule/ya.make deleted file mode 100644 index 80b625857c1f..000000000000 --- a/ydb/core/tx/tiering/rule/ya.make +++ /dev/null @@ -1,24 +0,0 @@ -LIBRARY() - -SRCS( - manager.cpp - object.cpp - GLOBAL behaviour.cpp - initializer.cpp - checker.cpp - ss_checker.cpp - GLOBAL ss_fetcher.cpp -) - -PEERDIR( - ydb/services/metadata/abstract - ydb/services/metadata/common - ydb/services/metadata/initializer - ydb/services/metadata/manager - ydb/services/bg_tasks/abstract - ydb/core/tx/schemeshard -) - -YQL_LAST_ABI_VERSION() - -END() diff --git a/ydb/core/tx/tiering/snapshot.cpp b/ydb/core/tx/tiering/snapshot.cpp deleted file mode 100644 index cfc003d7ba7b..000000000000 --- a/ydb/core/tx/tiering/snapshot.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "snapshot.h" - -#include - -#include -#include - -#include - -namespace NKikimr::NColumnShard::NTiers { - -bool TConfigsSnapshot::DoDeserializeFromResultSet(const Ydb::Table::ExecuteQueryResult& rawDataResult) { - Y_ABORT_UNLESS(rawDataResult.result_sets().size() == 2); - ParseSnapshotObjects(rawDataResult.result_sets()[0], [this](TTierConfig&& s) {TierConfigs.emplace(s.GetTierName(), s); }); - ParseSnapshotObjects(rawDataResult.result_sets()[1], [this](TTieringRule&& s) {TableTierings.emplace(s.GetTieringRuleId(), s); }); - return true; -} - -std::optional TConfigsSnapshot::GetTierById(const TString& tierName) const { - auto it = TierConfigs.find(tierName); - if (it == TierConfigs.end()) { - return {}; - } else { - return it->second; - } -} - -const TTieringRule* TConfigsSnapshot::GetTieringById(const TString& tieringId) const { - auto it = TableTierings.find(tieringId); - if (it == TableTierings.end()) { - return nullptr; - } else { - return &it->second; - } -} - -std::set TConfigsSnapshot::GetTieringIdsForTier(const TString& tierName) const { - std::set result; - for (auto&& i : TableTierings) { - for (auto&& t : i.second.GetIntervals()) { - if (t.GetTierName() == tierName) { - result.emplace(i.second.GetTieringRuleId()); - break; - } - } - } - return result; -} - -TString NTiers::TConfigsSnapshot::DoSerializeToString() const { - NJson::TJsonValue result = NJson::JSON_MAP; - auto& jsonTiers = result.InsertValue("tiers", NJson::JSON_MAP); - for (auto&& i : TierConfigs) { - jsonTiers.InsertValue(i.first, i.second.GetDebugJson()); - } - auto& jsonTiering = result.InsertValue("rules", NJson::JSON_MAP); - for (auto&& i : TableTierings) { - jsonTiering.InsertValue(i.first, i.second.GetDebugJson()); - } - return result.GetStringRobust(); -} - -} diff --git a/ydb/core/tx/tiering/snapshot.h b/ydb/core/tx/tiering/snapshot.h deleted file mode 100644 index db323d11f253..000000000000 --- a/ydb/core/tx/tiering/snapshot.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include -#include -#include -#include - -#include - -#include - -namespace NKikimr::NColumnShard::NTiers { - -class TConfigsSnapshot: public NMetadata::NFetcher::ISnapshot { -private: - using TBase = NMetadata::NFetcher::ISnapshot; - using TConfigsMap = TMap; - YDB_ACCESSOR_DEF(TConfigsMap, TierConfigs); - using TTieringMap = TMap; - YDB_ACCESSOR_DEF(TTieringMap, TableTierings); -protected: - virtual bool DoDeserializeFromResultSet(const Ydb::Table::ExecuteQueryResult& rawData) override; - virtual TString DoSerializeToString() const override; -public: - - std::set GetTieringIdsForTier(const TString& tierName) const; - const TTieringRule* GetTieringById(const TString& tieringId) const; - std::optional GetTierById(const TString& tierName) const; - using TBase::TBase; -}; - -} diff --git a/ydb/core/tx/tiering/tier/behaviour.cpp b/ydb/core/tx/tiering/tier/behaviour.cpp deleted file mode 100644 index 01b3d13290f1..000000000000 --- a/ydb/core/tx/tiering/tier/behaviour.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "behaviour.h" -#include "manager.h" -#include "initializer.h" - -#include - -#include -#include - -#include -#include - -namespace NKikimr::NColumnShard::NTiers { - -TTierConfigBehaviour::TFactory::TRegistrator TTierConfigBehaviour::Registrator(TTierConfig::GetTypeId()); - -TString TTierConfigBehaviour::GetInternalStorageTablePath() const { - return "tiering/tiers"; -} - -NMetadata::NModifications::IOperationsManager::TPtr TTierConfigBehaviour::ConstructOperationsManager() const { - return std::make_shared(); -} - -NMetadata::NInitializer::IInitializationBehaviour::TPtr TTierConfigBehaviour::ConstructInitializer() const { - return std::make_shared(); -} - -TString TTierConfigBehaviour::GetTypeId() const { - return TTierConfig::GetTypeId(); -} - -} diff --git a/ydb/core/tx/tiering/tier/behaviour.h b/ydb/core/tx/tiering/tier/behaviour.h deleted file mode 100644 index fd231708fffe..000000000000 --- a/ydb/core/tx/tiering/tier/behaviour.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "object.h" -#include - -namespace NKikimr::NColumnShard::NTiers { - -class TTierConfigBehaviour: public NMetadata::TClassBehaviour { -private: - static TFactory::TRegistrator Registrator; -protected: - virtual std::shared_ptr ConstructInitializer() const override; - virtual std::shared_ptr ConstructOperationsManager() const override; - - virtual TString GetInternalStorageTablePath() const override; - virtual TString GetTypeId() const override; - -}; - -} diff --git a/ydb/core/tx/tiering/tier/checker.cpp b/ydb/core/tx/tiering/tier/checker.cpp deleted file mode 100644 index 1fd719069d43..000000000000 --- a/ydb/core/tx/tiering/tier/checker.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "checker.h" - -#include -#include -#include - -namespace NKikimr::NColumnShard::NTiers { - -void TTierPreparationActor::StartChecker() { - if (!Tierings || !Secrets || !SSCheckResult) { - return; - } - auto g = PassAwayGuard(); - if (!SSCheckResult->GetContent().GetOperationAllow()) { - Controller->OnPreparationProblem(SSCheckResult->GetContent().GetDenyReason()); - return; - } - for (auto&& tier : Objects) { - if (Context.GetActivityType() == NMetadata::NModifications::IOperationsManager::EActivityType::Drop) { - std::set tieringsWithTiers; - for (auto&& i : Tierings->GetTableTierings()) { - if (i.second.ContainsTier(tier.GetTierName())) { - tieringsWithTiers.emplace(i.first); - if (tieringsWithTiers.size() > 10) { - break; - } - } - } - if (tieringsWithTiers.size()) { - Controller->OnPreparationProblem("tier in usage for tierings: " + JoinSeq(", ", tieringsWithTiers)); - return; - } - } - if (!Secrets->CheckSecretAccess(tier.GetAccessKey(), Context.GetExternalData().GetUserToken())) { - Controller->OnPreparationProblem("no access for secret: " + tier.GetAccessKey().DebugString()); - return; - } else if (!Secrets->CheckSecretAccess(tier.GetSecretKey(), Context.GetExternalData().GetUserToken())) { - Controller->OnPreparationProblem("no access for secret: " + tier.GetSecretKey().DebugString()); - return; - } - } - Controller->OnPreparationFinished(std::move(Objects)); -} - -void TTierPreparationActor::Handle(NSchemeShard::TEvSchemeShard::TEvProcessingResponse::TPtr& ev) { - auto& proto = ev->Get()->Record; - if (proto.HasError()) { - Controller->OnPreparationProblem(proto.GetError().GetErrorMessage()); - PassAway(); - } else if (proto.HasContent()) { - SSCheckResult = SSFetcher->UnpackResult(ev->Get()->Record.GetContent().GetData()); - if (!SSCheckResult) { - Controller->OnPreparationProblem("cannot unpack ss-fetcher result for class " + SSFetcher->GetClassName()); - PassAway(); - } else { - StartChecker(); - } - } else { - Y_ABORT_UNLESS(false); - } -} - -void TTierPreparationActor::Handle(NMetadata::NProvider::TEvRefreshSubscriberData::TPtr& ev) { - if (auto snapshot = ev->Get()->GetSnapshotPtrAs()) { - Secrets = snapshot; - } else if (auto snapshot = ev->Get()->GetSnapshotPtrAs()) { - Tierings = snapshot; - std::set tieringIds; - std::set tiersChecked; - for (auto&& tier : Objects) { - if (!tiersChecked.emplace(tier.GetTierName()).second) { - continue; - } - auto tIds = Tierings->GetTieringIdsForTier(tier.GetTierName()); - if (tieringIds.empty()) { - tieringIds = std::move(tIds); - } else { - tieringIds.insert(tIds.begin(), tIds.end()); - } - } - { - SSFetcher = std::make_shared(); - SSFetcher->SetUserToken(Context.GetExternalData().GetUserToken()); - SSFetcher->SetActivityType(Context.GetActivityType()); - SSFetcher->MutableTieringRuleIds() = tieringIds; - Register(new TSSFetchingActor(SSFetcher, std::make_shared(SelfId()), TDuration::Seconds(10))); - } - } else { - Y_ABORT_UNLESS(false); - } - StartChecker(); -} - -void TTierPreparationActor::Bootstrap() { - Become(&TThis::StateMain); - Send(NMetadata::NProvider::MakeServiceId(SelfId().NodeId()), - new NMetadata::NProvider::TEvAskSnapshot(std::make_shared())); - Send(NMetadata::NProvider::MakeServiceId(SelfId().NodeId()), - new NMetadata::NProvider::TEvAskSnapshot(std::make_shared())); -} - -TTierPreparationActor::TTierPreparationActor(std::vector&& objects, - NMetadata::NModifications::IAlterPreparationController::TPtr controller, - const NMetadata::NModifications::IOperationsManager::TInternalModificationContext& context) - : Objects(std::move(objects)) - , Controller(controller) - , Context(context) -{ - -} - -} diff --git a/ydb/core/tx/tiering/tier/checker.h b/ydb/core/tx/tiering/tier/checker.h deleted file mode 100644 index 2b1d5ffd2e7a..000000000000 --- a/ydb/core/tx/tiering/tier/checker.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once -#include "object.h" - -#include -#include -#include - -#include -#include -#include -#include - -namespace NKikimr::NColumnShard::NTiers { - -class TTierPreparationActor: public NActors::TActorBootstrapped { -private: - std::vector Objects; - NMetadata::NModifications::IAlterPreparationController::TPtr Controller; - NMetadata::NModifications::IOperationsManager::TInternalModificationContext Context; - std::shared_ptr Secrets; - std::shared_ptr Tierings; - std::shared_ptr SSFetcher; - std::optional SSCheckResult; - void StartChecker(); -protected: - void Handle(NMetadata::NProvider::TEvRefreshSubscriberData::TPtr& ev); - void Handle(NSchemeShard::TEvSchemeShard::TEvProcessingResponse::TPtr& ev); -public: - STATEFN(StateMain) { - switch (ev->GetTypeRewrite()) { - hFunc(NMetadata::NProvider::TEvRefreshSubscriberData, Handle); - hFunc(NSchemeShard::TEvSchemeShard::TEvProcessingResponse, Handle); - default: - break; - } - } - void Bootstrap(); - - TTierPreparationActor(std::vector&& objects, - NMetadata::NModifications::IAlterPreparationController::TPtr controller, - const NMetadata::NModifications::IOperationsManager::TInternalModificationContext& context); -}; - -} diff --git a/ydb/core/tx/tiering/tier/initializer.cpp b/ydb/core/tx/tiering/tier/initializer.cpp deleted file mode 100644 index 9bf517856a9a..000000000000 --- a/ydb/core/tx/tiering/tier/initializer.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "initializer.h" -#include "object.h" - -namespace NKikimr::NColumnShard::NTiers { - -TVector TTiersInitializer::BuildModifiers() const { - TVector result; - { - Ydb::Table::CreateTableRequest request; - request.set_session_id(""); - request.set_path(TTierConfig::GetBehaviour()->GetStorageTablePath()); - request.add_primary_key("tierName"); - { - auto& column = *request.add_columns(); - column.set_name("tierName"); - column.mutable_type()->mutable_optional_type()->mutable_item()->set_type_id(Ydb::Type::UTF8); - } - { - auto& column = *request.add_columns(); - column.set_name("tierConfig"); - column.mutable_type()->mutable_optional_type()->mutable_item()->set_type_id(Ydb::Type::UTF8); - } - result.emplace_back(new NMetadata::NInitializer::TGenericTableModifier(request, "create")); - auto hRequest = TTierConfig::AddHistoryTableScheme(request); - result.emplace_back(new NMetadata::NInitializer::TGenericTableModifier(hRequest, "create_history")); - } - result.emplace_back(NMetadata::NInitializer::TACLModifierConstructor::GetReadOnlyModifier(TTierConfig::GetBehaviour()->GetStorageTablePath(), "acl")); - result.emplace_back(NMetadata::NInitializer::TACLModifierConstructor::GetReadOnlyModifier(TTierConfig::GetBehaviour()->GetStorageHistoryTablePath(), "acl_history")); - return result; -} - -void TTiersInitializer::DoPrepare(NMetadata::NInitializer::IInitializerInput::TPtr controller) const { - controller->OnPreparationFinished(BuildModifiers()); -} - -} diff --git a/ydb/core/tx/tiering/tier/initializer.h b/ydb/core/tx/tiering/tier/initializer.h deleted file mode 100644 index e3d99fffea0a..000000000000 --- a/ydb/core/tx/tiering/tier/initializer.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once -#include -#include -#include - -namespace NKikimr::NColumnShard::NTiers { - -class TTiersInitializer: public NMetadata::NInitializer::IInitializationBehaviour { -protected: - TVector BuildModifiers() const; - virtual void DoPrepare(NMetadata::NInitializer::IInitializerInput::TPtr controller) const override; -public: -}; - -} diff --git a/ydb/core/tx/tiering/tier/manager.cpp b/ydb/core/tx/tiering/tier/manager.cpp deleted file mode 100644 index 8d60219624b4..000000000000 --- a/ydb/core/tx/tiering/tier/manager.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "manager.h" -#include "initializer.h" -#include "checker.h" - -namespace NKikimr::NColumnShard::NTiers { - -NMetadata::NModifications::TOperationParsingResult TTiersManager::DoBuildPatchFromSettings( - const NYql::TObjectSettingsImpl& settings, - TInternalModificationContext& context) const -{ - if (HasAppData() && !AppDataVerified().FeatureFlags.GetEnableTieringInColumnShard()) { - return TConclusionStatus::Fail("Tiering functionality is disabled for OLAP tables."); - } - - NMetadata::NInternal::TTableRecord result; - result.SetColumn(TTierConfig::TDecoder::TierName, NMetadata::NInternal::TYDBValue::Utf8(settings.GetObjectId())); - if (settings.GetObjectId().StartsWith("$") || settings.GetObjectId().StartsWith("_")) { - return TConclusionStatus::Fail("tier name cannot start with '$', '_' characters"); - } - { - auto fConfig = settings.GetFeaturesExtractor().Extract(TTierConfig::TDecoder::TierConfig); - if (fConfig) { - NKikimrSchemeOp::TStorageTierConfig proto; - if (!::google::protobuf::TextFormat::ParseFromString(*fConfig, &proto)) { - return TConclusionStatus::Fail("incorrect proto format"); - } else if (proto.HasObjectStorage()) { - TString defaultUserId; - if (context.GetExternalData().GetUserToken()) { - defaultUserId = context.GetExternalData().GetUserToken()->GetUserSID(); - } - - if (proto.GetObjectStorage().HasSecretableAccessKey()) { - auto accessKey = NMetadata::NSecret::TSecretIdOrValue::DeserializeFromProto(proto.GetObjectStorage().GetSecretableAccessKey(), defaultUserId); - if (!accessKey) { - return TConclusionStatus::Fail("AccessKey description is incorrect"); - } - *proto.MutableObjectStorage()->MutableSecretableAccessKey() = accessKey->SerializeToProto(); - } else if (proto.GetObjectStorage().HasAccessKey()) { - auto accessKey = NMetadata::NSecret::TSecretIdOrValue::DeserializeFromString(proto.GetObjectStorage().GetAccessKey(), defaultUserId); - if (!accessKey) { - return TConclusionStatus::Fail("AccessKey is incorrect: " + proto.GetObjectStorage().GetAccessKey() + " for userId: " + defaultUserId); - } - *proto.MutableObjectStorage()->MutableAccessKey() = accessKey->SerializeToString(); - } else { - return TConclusionStatus::Fail("AccessKey not configured"); - } - - if (proto.GetObjectStorage().HasSecretableSecretKey()) { - auto secretKey = NMetadata::NSecret::TSecretIdOrValue::DeserializeFromProto(proto.GetObjectStorage().GetSecretableSecretKey(), defaultUserId); - if (!secretKey) { - return TConclusionStatus::Fail("SecretKey description is incorrect"); - } - *proto.MutableObjectStorage()->MutableSecretableSecretKey() = secretKey->SerializeToProto(); - } else if (proto.GetObjectStorage().HasSecretKey()) { - auto secretKey = NMetadata::NSecret::TSecretIdOrValue::DeserializeFromString(proto.GetObjectStorage().GetSecretKey(), defaultUserId); - if (!secretKey) { - return TConclusionStatus::Fail("SecretKey is incorrect"); - } - *proto.MutableObjectStorage()->MutableSecretKey() = secretKey->SerializeToString(); - } else { - return TConclusionStatus::Fail("SecretKey not configured"); - } - } - result.SetColumn(TTierConfig::TDecoder::TierConfig, NMetadata::NInternal::TYDBValue::Utf8(proto.DebugString())); - } - } - return result; -} - -void TTiersManager::DoPrepareObjectsBeforeModification(std::vector&& patchedObjects, - NMetadata::NModifications::IAlterPreparationController::TPtr controller, - const TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& /*alterContext*/) const -{ - TActivationContext::Register(new TTierPreparationActor(std::move(patchedObjects), controller, context)); -} - -} diff --git a/ydb/core/tx/tiering/tier/manager.h b/ydb/core/tx/tiering/tier/manager.h deleted file mode 100644 index 7d8626c8c36c..000000000000 --- a/ydb/core/tx/tiering/tier/manager.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include "object.h" - -#include - -namespace NKikimr::NColumnShard::NTiers { - -class TTiersManager: public NMetadata::NModifications::TGenericOperationsManager { -protected: - virtual void DoPrepareObjectsBeforeModification(std::vector&& patchedObjects, - NMetadata::NModifications::IAlterPreparationController::TPtr controller, - const TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& alterContext) const override; - - virtual NMetadata::NModifications::TOperationParsingResult DoBuildPatchFromSettings(const NYql::TObjectSettingsImpl& settings, - TInternalModificationContext& context) const override; -public: -}; - -} diff --git a/ydb/core/tx/tiering/tier/object.cpp b/ydb/core/tx/tiering/tier/object.cpp index e5045a3ee887..37b0cddab5ab 100644 --- a/ydb/core/tx/tiering/tier/object.cpp +++ b/ydb/core/tx/tiering/tier/object.cpp @@ -1,62 +1,88 @@ #include "object.h" -#include "behaviour.h" - -#include - -#include -#include #include #include +#include namespace NKikimr::NColumnShard::NTiers { -NMetadata::IClassBehaviour::TPtr TTierConfig::GetBehaviour() { - static std::shared_ptr result = std::make_shared(); - return result; +TConclusion TTierConfig::GetPatchedConfig( + const std::shared_ptr& secrets) const { + auto config = ProtoConfig; + if (secrets) { + { + auto secretIdOrValue = NMetadata::NSecret::TSecretIdOrValue::DeserializeFromString(config.GetAccessKey()); + AFL_VERIFY(secretIdOrValue); + auto value = secrets->GetSecretValue(*secretIdOrValue); + if (value.IsFail()) { + return TConclusionStatus::Fail(TStringBuilder() << "Can't read access key: " << value.GetErrorMessage()); + } + config.SetAccessKey(value.DetachResult()); + } + { + auto secretIdOrValue = NMetadata::NSecret::TSecretIdOrValue::DeserializeFromString(config.GetSecretKey()); + AFL_VERIFY(secretIdOrValue); + auto value = secrets->GetSecretValue(*secretIdOrValue); + if (value.IsFail()) { + return TConclusionStatus::Fail(TStringBuilder() << "Can't read secret key: " << value.GetErrorMessage()); + } + config.SetSecretKey(value.DetachResult()); + } + } + return config; } -NJson::TJsonValue TTierConfig::GetDebugJson() const { - NJson::TJsonValue result = NJson::JSON_MAP; - result.InsertValue(TDecoder::TierName, TierName); - NProtobufJson::Proto2Json(ProtoConfig, result.InsertValue(TDecoder::TierConfig, NJson::JSON_MAP)); - return result; -} +TConclusionStatus TTierConfig::DeserializeFromProto(const NKikimrSchemeOp::TExternalDataSourceDescription& proto) { + if (!proto.GetAuth().HasAws()) { + return TConclusionStatus::Fail("AWS auth is not defined for storage tier"); + } -bool TTierConfig::IsSame(const TTierConfig& item) const { - return TierName == item.TierName && ProtoConfig.SerializeAsString() == item.ProtoConfig.SerializeAsString(); -} + ProtoConfig.SetAccessKey(NMetadata::NSecret::TSecretName(proto.GetAuth().GetAws().GetAwsAccessKeyIdSecretName()).SerializeToString()); + ProtoConfig.SetSecretKey( + NMetadata::NSecret::TSecretName(proto.GetAuth().GetAws().GetAwsSecretAccessKeySecretName()).SerializeToString()); -bool TTierConfig::DeserializeFromRecord(const TDecoder& decoder, const Ydb::Value& r) { - if (!decoder.Read(decoder.GetTierNameIdx(), TierName, r)) { - return false; + NUri::TUri url; + if (url.Parse(proto.GetLocation(), NUri::TFeature::FeaturesAll) != NUri::TState::EParsed::ParsedOK) { + return TConclusionStatus::Fail("Cannot parse url: " + proto.GetLocation()); } - if (!decoder.ReadDebugProto(decoder.GetTierConfigIdx(), ProtoConfig, r)) { - return false; + + switch (url.GetScheme()) { + case NUri::TScheme::SchemeEmpty: + break; + case NUri::TScheme::SchemeHTTP: + ProtoConfig.SetScheme(::NKikimrSchemeOp::TS3Settings_EScheme_HTTP); + break; + case NUri::TScheme::SchemeHTTPS: + ProtoConfig.SetScheme(::NKikimrSchemeOp::TS3Settings_EScheme_HTTPS); + break; + default: + return TConclusionStatus::Fail("Unknown schema in url"); } - return ProtoConfig.HasObjectStorage(); -} -NMetadata::NInternal::TTableRecord TTierConfig::SerializeToRecord() const { - NMetadata::NInternal::TTableRecord result; - result.SetColumn(TDecoder::TierName, NMetadata::NInternal::TYDBValue::Utf8(TierName)); - result.SetColumn(TDecoder::TierConfig, NMetadata::NInternal::TYDBValue::Utf8(ProtoConfig.DebugString())); - return result; -} + { + TStringBuf endpoint; + TStringBuf bucket; -NKikimrSchemeOp::TS3Settings TTierConfig::GetPatchedConfig( - std::shared_ptr secrets) const -{ - auto config = ProtoConfig.GetObjectStorage(); - if (secrets) { - if (!secrets->GetSecretValue(GetAccessKey(), *config.MutableAccessKey())) { - ALS_ERROR(NKikimrServices::TX_TIERING) << "cannot read access key secret for " << GetAccessKey().DebugString(); - } - if (!secrets->GetSecretValue(GetSecretKey(), *config.MutableSecretKey())) { - ALS_ERROR(NKikimrServices::TX_TIERING) << "cannot read secret key secret for " << GetSecretKey().DebugString(); + TStringBuf host = url.GetHost(); + TStringBuf path = url.GetField(NUri::TField::FieldPath); + if (!path.Empty()) { + endpoint = host; + bucket = path; + bucket.SkipPrefix("/"); + if (bucket.Contains("/")) { + return TConclusionStatus::Fail(TStringBuilder() << "Not a bucket (contains directories): " << bucket); + } + } else { + if (!path.TrySplit('.', endpoint, bucket)) { + return TConclusionStatus::Fail(TStringBuilder() << "Bucket is not specified in URL: " << path); + } } + + ProtoConfig.SetEndpoint(TString(endpoint)); + ProtoConfig.SetBucket(TString(bucket)); } - return config; + + return TConclusionStatus::Success(); } NJson::TJsonValue TTierConfig::SerializeConfigToJson() const { @@ -65,4 +91,8 @@ NJson::TJsonValue TTierConfig::SerializeConfigToJson() const { return result; } +bool TTierConfig::IsSame(const TTierConfig& item) const { + return ProtoConfig.SerializeAsString() == item.ProtoConfig.SerializeAsString(); +} + } diff --git a/ydb/core/tx/tiering/tier/object.h b/ydb/core/tx/tiering/tier/object.h index 96bdfc490ac2..9da470a01046 100644 --- a/ydb/core/tx/tiering/tier/object.h +++ b/ydb/core/tx/tiering/tier/object.h @@ -1,11 +1,10 @@ #pragma once #include -#include -#include -#include -#include -#include -#include + +#include +#include +#include +#include #include @@ -15,72 +14,26 @@ class TSnapshot; namespace NKikimr::NColumnShard::NTiers { -class TTierConfig: public NMetadata::NModifications::TObject { +class TTierConfig { private: - using TTierProto = NKikimrSchemeOp::TStorageTierConfig; - YDB_ACCESSOR_DEF(TString, TierName); - TTierProto ProtoConfig; -public: + using TTierProto = NKikimrSchemeOp::TS3Settings; + YDB_READONLY_DEF(TTierProto, ProtoConfig); + YDB_READONLY_DEF(NKikimrSchemeOp::TCompressionOptions, Compression); +public: TTierConfig() = default; - TTierConfig(const TString& tierName) - : TierName(tierName) { - - } - - TTierConfig(const TString& tierName, const TTierProto& config) - : TierName(tierName) - , ProtoConfig(config) - { - - } - - const NKikimrSchemeOp::TCompressionOptions& GetCompression() const { - return ProtoConfig.GetCompression(); - } - - NMetadata::NSecret::TSecretIdOrValue GetAccessKey() const { - auto accessKey = NMetadata::NSecret::TSecretIdOrValue::DeserializeFromOptional(ProtoConfig.GetObjectStorage().GetSecretableAccessKey(), ProtoConfig.GetObjectStorage().GetAccessKey()); - if (!accessKey) { - return NMetadata::NSecret::TSecretIdOrValue::BuildEmpty(); - } - return *accessKey; + TTierConfig(const TTierProto& config, const NKikimrSchemeOp::TCompressionOptions& compression) + : ProtoConfig(config) + , Compression(compression) { } - NMetadata::NSecret::TSecretIdOrValue GetSecretKey() const { - auto secretKey = NMetadata::NSecret::TSecretIdOrValue::DeserializeFromOptional(ProtoConfig.GetObjectStorage().GetSecretableSecretKey(), ProtoConfig.GetObjectStorage().GetSecretKey()); - if (!secretKey) { - return NMetadata::NSecret::TSecretIdOrValue::BuildEmpty(); - } - return *secretKey; - } + TConclusionStatus DeserializeFromProto(const NKikimrSchemeOp::TExternalDataSourceDescription& proto); NJson::TJsonValue SerializeConfigToJson() const; - - static NMetadata::IClassBehaviour::TPtr GetBehaviour(); - NKikimrSchemeOp::TS3Settings GetPatchedConfig(std::shared_ptr secrets) const; - - class TDecoder: public NMetadata::NInternal::TDecoderBase { - private: - YDB_READONLY(i32, TierNameIdx, -1); - YDB_READONLY(i32, TierConfigIdx, -1); - public: - static inline const TString TierName = "tierName"; - static inline const TString TierConfig = "tierConfig"; - TDecoder(const Ydb::ResultSet& rawData) { - TierNameIdx = GetFieldIndex(rawData, TierName); - TierConfigIdx = GetFieldIndex(rawData, TierConfig); - } - }; - bool DeserializeFromRecord(const TDecoder& decoder, const Ydb::Value& r); - NMetadata::NInternal::TTableRecord SerializeToRecord() const; + TConclusion GetPatchedConfig(const std::shared_ptr& secrets) const; bool IsSame(const TTierConfig& item) const; NJson::TJsonValue GetDebugJson() const; - static TString GetTypeId() { - return "TIER"; - } }; - } diff --git a/ydb/core/tx/tiering/tier/ya.make b/ydb/core/tx/tiering/tier/ya.make index f319e8e28af1..32f06144cbd8 100644 --- a/ydb/core/tx/tiering/tier/ya.make +++ b/ydb/core/tx/tiering/tier/ya.make @@ -1,18 +1,12 @@ LIBRARY() SRCS( - manager.cpp object.cpp - initializer.cpp - checker.cpp - GLOBAL behaviour.cpp ) PEERDIR( - ydb/services/metadata/initializer - ydb/services/metadata/abstract - ydb/services/metadata/secret - ydb/core/tx/schemeshard + ydb/library/conclusion + ydb/services/metadata/secret/accessor ) YQL_LAST_ABI_VERSION() diff --git a/ydb/core/tx/tiering/ut/ut_tiers.cpp b/ydb/core/tx/tiering/ut/ut_tiers.cpp index 21fa01b29a6e..59f3ddf8a177 100644 --- a/ydb/core/tx/tiering/ut/ut_tiers.cpp +++ b/ydb/core/tx/tiering/ut/ut_tiers.cpp @@ -1,15 +1,17 @@ #include +#include #include -#include #include +#include #include +#include #include -#include -#include -#include #include -#include +#include +#include + #include +#include #include #include #include @@ -17,10 +19,8 @@ #include #include -#include #include #include - #include namespace NKikimr { @@ -29,6 +29,9 @@ using namespace NColumnShard; class TFastTTLCompactionController: public NKikimr::NYDBTest::ICSController { public: + virtual bool CheckPortionForEvict(const NOlap::TPortionInfo& /*portion*/) const override { + return true; + } virtual bool NeedForceCompactionBacketsConstruction() const override { return true; } @@ -72,9 +75,6 @@ class TLocalHelper: public Tests::NCS::THelper { TBase::CreateTestOlapTable(sender, storeName, Sprintf(R"( Name: "%s" ColumnShardCount: %d - TtlSettings: { - UseTiering: "tiering1" - } Sharding { HashSharding { Function: %s @@ -122,187 +122,79 @@ class TLocalHelper: public Tests::NCS::THelper { } )", tableName.c_str(), tableShardsCount, shardingFunction.c_str(), shardingColumns.c_str())); } -}; - -Y_UNIT_TEST_SUITE(ColumnShardTiers) { - - TString GetConfigProtoWithName(const TString & tierName) { - return TStringBuilder() << "Name : \"" << tierName << "\"\n" << - R"( - ObjectStorage : { - Endpoint: "fake" - Bucket: "fake" - SecretableAccessKey: { - Value: { - Data: "secretAccessKey" - } - } - SecretableSecretKey: { - Value: { - Data: "secretSecretKey" - } - } - } - )"; + void CreateSecrets() const { + StartSchemaRequest(R"( + UPSERT OBJECT `accessKey` (TYPE SECRET) WITH (value = `secretAccessKey`); + UPSERT OBJECT `secretKey` (TYPE SECRET) WITH (value = `fakeSecret`); + )"); } - const TString ConfigTiering1Str = R"({ - "rules" : [ - { - "tierName" : "tier1", - "durationForEvict" : "10d" - }, - { - "tierName" : "tier2", - "durationForEvict" : "20d" - } - ] - })"; - - const TString ConfigTiering2Str = R"({ - "rules" : [ - { - "tierName" : "tier1", - "durationForEvict" : "10d" - } - ] - })"; - - const TString ConfigTieringNothingStr = R"({ - "rules" : [ - { - "tierName" : "tier1", - "durationForEvict" : "10000d" - }, - { - "tierName" : "tier2", - "durationForEvict" : "20000d" - } - ] - })"; - - class TJsonChecker { - private: - YDB_ACCESSOR_DEF(TString, Path); - YDB_ACCESSOR_DEF(TString, Expectation); - public: - TJsonChecker(const TString& path, const TString& expectation) - : Path(path) - , Expectation(expectation) - { + void CreateExternalDataSource(const TString& name, const TString& location = "http://fake.fake/fake") const { + StartSchemaRequest(R"( + CREATE EXTERNAL DATA SOURCE `)" + name + R"(` WITH ( + SOURCE_TYPE="ObjectStorage", + LOCATION=")" + location + R"(", + AUTH_METHOD="AWS", + AWS_ACCESS_KEY_ID_SECRET_NAME="accessKey", + AWS_SECRET_ACCESS_KEY_SECRET_NAME="secretKey", + AWS_REGION="ru-central1" + ); + )"); + } +}; - } - bool Check(const NJson::TJsonValue& jsonInfo) const { - auto* jsonPathValue = jsonInfo.GetValueByPath(Path); - if (!jsonPathValue) { - return Expectation == "__NULL"; - } - return jsonPathValue->GetStringRobust() == Expectation; - } - TString GetDebugString() const { - TStringBuilder sb; - sb << "path=" << Path << ";" - << "expectation=" << Expectation << ";"; - return sb; - } - }; +Y_UNIT_TEST_SUITE(ColumnShardTiers) { class TTestCSEmulator: public NActors::TActorBootstrapped { private: using TBase = NActors::TActorBootstrapped; - std::shared_ptr ExternalDataManipulation; - TActorId ProviderId; + THashSet ExpectedTiers; TInstant Start; - YDB_READONLY_FLAG(Found, false); - YDB_ACCESSOR(ui32, ExpectedTieringsCount, 1); - YDB_ACCESSOR(ui32, ExpectedTiersCount, 1); + std::shared_ptr Manager; - using TKeyCheckers = TMap; - YDB_ACCESSOR_DEF(TKeyCheckers, Checkers); public: - void ResetConditions() { - FoundFlag = false; - Checkers.clear(); - } - STATEFN(StateInit) { switch (ev->GetTypeRewrite()) { - hFunc(NMetadata::NProvider::TEvRefreshSubscriberData, Handle); default: Y_ABORT_UNLESS(false); } } void CheckRuntime(TTestActorRuntime& runtime) { - const auto pred = [this](TAutoPtr& event)->TTestActorRuntimeBase::EEventAction { - if (event->HasBuffer() && !event->HasEvent()) { - } else if (!event->HasEvent()) { - } else { - auto ptr = event->CastAsLocal(); - if (ptr) { - CheckFound(ptr); - } - } - return TTestActorRuntimeBase::EEventAction::PROCESS; - }; - - runtime.SetObserverFunc(pred); - for (const TInstant start = Now(); !IsFound() && Now() - start < TDuration::Seconds(30); ) { runtime.SimulateSleep(TDuration::Seconds(1)); } - runtime.SetObserverFunc(TTestActorRuntime::DefaultObserverFunc); Y_ABORT_UNLESS(IsFound()); } - void CheckFound(NMetadata::NProvider::TEvRefreshSubscriberData* event) { - auto snapshot = event->GetSnapshotAs(); - if (!snapshot) { - Cerr << "incorrect snapshot" << Endl; - return; - } - Cerr << "SNAPSHOT: " << snapshot->SerializeToString() << Endl; - const auto& tierings = snapshot->GetTableTierings(); - if (tierings.size() != ExpectedTieringsCount) { - Cerr << "TieringsCount incorrect: " << snapshot->SerializeToString() << ";expectation=" << ExpectedTieringsCount << Endl; - return; - } - if (ExpectedTiersCount != snapshot->GetTierConfigs().size()) { - Cerr << "TiersCount incorrect: " << snapshot->SerializeToString() << ";expectation=" << ExpectedTiersCount << Endl; - return; + bool IsFound() const { + if (!Manager) { + return false; } - for (auto&& i : Checkers) { - NJson::TJsonValue jsonData; - if (i.first.StartsWith("TIER.")) { - auto value = snapshot->GetTierById(i.first.substr(5)); - jsonData = value->SerializeConfigToJson(); - } else if (i.first.StartsWith("TIERING_RULE.")) { - auto value = snapshot->GetTierById(i.first.substr(13)); - jsonData = value->SerializeConfigToJson(); - } else { - Y_ABORT_UNLESS(false); - } - if (!i.second.Check(jsonData)) { - Cerr << "config value incorrect:" << snapshot->SerializeToString() << ";snapshot_check_path=" << i.first << Endl; - Cerr << "json path incorrect:" << jsonData << ";" << i.second.GetDebugString() << Endl; - return; - } + THashSet notFoundTiers = ExpectedTiers; + for (const auto& [id, config] : Manager->GetTierConfigs()) { + notFoundTiers.erase(id); } - FoundFlag = true; + return notFoundTiers.empty(); } - void Handle(NMetadata::NProvider::TEvRefreshSubscriberData::TPtr& ev) { - CheckFound(ev->Get()); + const THashMap& GetTierConfigs() { + return Manager->GetTierConfigs(); } void Bootstrap() { - ProviderId = NMetadata::NProvider::MakeServiceId(SelfId().NodeId()); - ExternalDataManipulation = std::make_shared(); Become(&TThis::StateInit); - Sender(ExternalDataManipulation).SendTo(ProviderId); Start = Now(); + Manager = std::make_shared(0, SelfId(), [](const TActorContext&) { + }); + Manager->Start(Manager); + Manager->EnablePathId(0, ExpectedTiers); + } + + TTestCSEmulator(THashSet expectedTiers) + : ExpectedTiers(std::move(expectedTiers)) { } }; @@ -332,6 +224,7 @@ Y_UNIT_TEST_SUITE(ColumnShardTiers) { .SetUseRealThreads(false) .SetEnableMetadataProvider(true) .SetEnableTieringInColumnShard(true) + .SetEnableExternalDataSources(true) ; Tests::TServer::TPtr server = new Tests::TServer(serverSettings); @@ -341,66 +234,31 @@ Y_UNIT_TEST_SUITE(ColumnShardTiers) { Tests::TClient client(serverSettings); auto& runtime = *server->GetRuntime(); + runtime.SetLogPriority(NKikimrServices::TX_TIERING, NLog::PRI_DEBUG); auto sender = runtime.AllocateEdgeActor(); server->SetupRootStoragePools(sender); TLocalHelper lHelper(*server); - lHelper.CreateTestOlapTable(); { - TTestCSEmulator* emulator = new TTestCSEmulator; - emulator->MutableCheckers().emplace("TIER.tier1", TJsonChecker("Name", "abc")); - emulator->SetExpectedTiersCount(2); - runtime.Register(emulator); - runtime.SimulateSleep(TDuration::Seconds(10)); - Cerr << "Initialization finished" << Endl; + lHelper.CreateTestOlapTable(); + lHelper.CreateSecrets(); + lHelper.CreateExternalDataSource("/Root/tier1", "http://fake.fake/abc"); + lHelper.CreateExternalDataSource("/Root/tier2", "http://fake.fake/abc"); + lHelper.StartSchemaRequest( + R"(ALTER TABLE `/Root/olapStore/olapTable` SET TTL Interval("P10D") TO EXTERNAL DATA SOURCE `/Root/tier1`, Interval("P20D") TO EXTERNAL DATA SOURCE `/Root/tier2` ON timestamp)"); - lHelper.StartSchemaRequest("CREATE OBJECT tier1 (TYPE TIER) WITH tierConfig = `" + GetConfigProtoWithName("abc") + "`"); - lHelper.StartSchemaRequest("CREATE OBJECT tiering1 (" - "TYPE TIERING_RULE) WITH (defaultColumn = timestamp, description = `" + ConfigTiering1Str + "` )", false); - lHelper.StartSchemaRequest("CREATE OBJECT tier2 (TYPE TIER) WITH tierConfig = `" + GetConfigProtoWithName("abc") + "`"); - lHelper.StartSchemaRequest("CREATE OBJECT tiering1 (" - "TYPE TIERING_RULE) WITH (defaultColumn = timestamp, description = `" + ConfigTiering1Str + "` )"); - { - const TInstant start = Now(); - while (!emulator->IsFound() && Now() - start < TDuration::Seconds(2000)) { - runtime.SimulateSleep(TDuration::Seconds(1)); - } - Y_ABORT_UNLESS(emulator->IsFound()); - } { - emulator->ResetConditions(); - emulator->SetExpectedTiersCount(2); - emulator->MutableCheckers().emplace("TIER.tier1", TJsonChecker("Name", "abc1")); - - lHelper.StartSchemaRequest("ALTER OBJECT tier1 (TYPE TIER) SET tierConfig = `" + GetConfigProtoWithName("abc1") + "`"); - - { - const TInstant start = Now(); - while (!emulator->IsFound() && Now() - start < TDuration::Seconds(2000)) { - runtime.SimulateSleep(TDuration::Seconds(1)); - } - Y_ABORT_UNLESS(emulator->IsFound()); - } + TTestCSEmulator* emulator = new TTestCSEmulator({ "/Root/tier1", "/Root/tier2" }); + runtime.Register(emulator); + emulator->CheckRuntime(runtime); + UNIT_ASSERT_EQUAL(emulator->GetTierConfigs().at("/Root/tier1").GetProtoConfig().GetBucket(), "abc"); } + Cerr << "Initialization finished" << Endl; { - emulator->ResetConditions(); - emulator->SetExpectedTieringsCount(0); - emulator->SetExpectedTiersCount(0); - - lHelper.StartSchemaRequest("DROP OBJECT tier1(TYPE TIER)", false); - lHelper.StartSchemaRequest("DROP OBJECT tiering1(TYPE TIERING_RULE)", false); + lHelper.StartSchemaRequest("DROP EXTERNAL DATA SOURCE `/Root/tier1`", false); lHelper.StartSchemaRequest("DROP TABLE `/Root/olapStore/olapTable`"); - lHelper.StartSchemaRequest("DROP OBJECT tiering1(TYPE TIERING_RULE)"); - lHelper.StartSchemaRequest("DROP OBJECT tier1(TYPE TIER)"); - lHelper.StartSchemaRequest("DROP OBJECT tier2(TYPE TIER)"); - - { - const TInstant start = Now(); - while (!emulator->IsFound() && Now() - start < TDuration::Seconds(20)) { - runtime.SimulateSleep(TDuration::Seconds(1)); - } - Y_ABORT_UNLESS(emulator->IsFound()); - } + lHelper.StartSchemaRequest("DROP EXTERNAL DATA SOURCE `/Root/tier1`"); + lHelper.StartSchemaRequest("DROP EXTERNAL DATA SOURCE `/Root/tier2`"); } } } @@ -422,6 +280,7 @@ Y_UNIT_TEST_SUITE(ColumnShardTiers) { .SetUseRealThreads(false) .SetEnableMetadataProvider(true) .SetEnableTieringInColumnShard(true) + .SetEnableExternalDataSources(true) .SetAppConfig(appConfig); Tests::TServer::TPtr server = new Tests::TServer(serverSettings); @@ -435,57 +294,45 @@ Y_UNIT_TEST_SUITE(ColumnShardTiers) { TLocalHelper lHelper(*server); lHelper.SetUseQueryService(useQueryService); - lHelper.CreateTestOlapTable("olapTable"); - runtime.SetLogPriority(NKikimrServices::TX_DATASHARD, NLog::PRI_NOTICE); runtime.SetLogPriority(NKikimrServices::TX_COLUMNSHARD, NLog::PRI_INFO); + runtime.SetLogPriority(NKikimrServices::TX_TIERING, NLog::PRI_DEBUG); // runtime.SetLogPriority(NKikimrServices::TX_PROXY_SCHEME_CACHE, NLog::PRI_DEBUG); runtime.SimulateSleep(TDuration::Seconds(10)); Cerr << "Initialization finished" << Endl; - lHelper.StartSchemaRequest("CREATE OBJECT tier1 (TYPE TIER) WITH tierConfig = `" + GetConfigProtoWithName("abc1") + "`", true, false); + lHelper.CreateSecrets(); + lHelper.CreateExternalDataSource("/Root/tier1", "http://fake.fake/abc1"); { - TTestCSEmulator emulator; - emulator.MutableCheckers().emplace("TIER.tier1", TJsonChecker("Name", "abc1")); - emulator.SetExpectedTieringsCount(0); - emulator.SetExpectedTiersCount(1); - emulator.CheckRuntime(runtime); - } - - lHelper.StartSchemaRequest("CREATE OBJECT tier2 (TYPE TIER) WITH tierConfig = `" + GetConfigProtoWithName("abc2") + "`"); - lHelper.StartSchemaRequest("CREATE OBJECT IF NOT EXISTS tiering1 (TYPE TIERING_RULE) " - "WITH (defaultColumn = timestamp, description = `" + ConfigTiering1Str + "`)"); - lHelper.StartSchemaRequest("CREATE OBJECT tiering2 (TYPE TIERING_RULE) " - "WITH (defaultColumn = timestamp, description = `" + ConfigTiering2Str + "` )", true, false); - { - TTestCSEmulator emulator; - emulator.MutableCheckers().emplace("TIER.tier1", TJsonChecker("Name", "abc1")); - emulator.MutableCheckers().emplace("TIER.tier2", TJsonChecker("Name", "abc2")); - emulator.SetExpectedTieringsCount(2); - emulator.SetExpectedTiersCount(2); - emulator.CheckRuntime(runtime); - } - - lHelper.StartSchemaRequest("DROP OBJECT tier2 (TYPE TIER)", false); - lHelper.StartSchemaRequest("DROP OBJECT tier1 (TYPE TIER)", false); - lHelper.StartSchemaRequest("DROP OBJECT tiering2 (TYPE TIERING_RULE)"); - lHelper.StartSchemaRequest("DROP OBJECT tiering1 (TYPE TIERING_RULE)", false); - lHelper.StartSchemaRequest("DROP TABLE `/Root/olapStore/olapTable`"); - lHelper.StartSchemaRequest("DROP OBJECT tiering1 (TYPE TIERING_RULE)", true, false); + TTestCSEmulator* emulator = new TTestCSEmulator({ "/Root/tier1" }); + runtime.Register(emulator); + emulator->CheckRuntime(runtime); + UNIT_ASSERT_EQUAL(emulator->GetTierConfigs().at("/Root/tier1").GetProtoConfig().GetBucket(), "abc1"); + } + + lHelper.CreateExternalDataSource("/Root/tier2", "http://fake.fake/abc2"); { - TTestCSEmulator emulator; - emulator.SetExpectedTieringsCount(0); - emulator.SetExpectedTiersCount(2); - emulator.CheckRuntime(runtime); + TTestCSEmulator* emulator = new TTestCSEmulator({ "/Root/tier1", "/Root/tier2" }); + runtime.Register(emulator); + emulator->CheckRuntime(runtime); + UNIT_ASSERT_EQUAL(emulator->GetTierConfigs().at("/Root/tier1").GetProtoConfig().GetBucket(), "abc1"); + UNIT_ASSERT_EQUAL(emulator->GetTierConfigs().at("/Root/tier2").GetProtoConfig().GetBucket(), "abc2"); } - lHelper.StartSchemaRequest("DROP OBJECT tier2 (TYPE TIER)"); - lHelper.StartSchemaRequest("DROP OBJECT tier1 (TYPE TIER)", true, false); + + lHelper.CreateTestOlapTable("olapTable"); + lHelper.StartSchemaRequest( + R"(ALTER TABLE `/Root/olapStore/olapTable` SET TTL Interval("P10D") TO EXTERNAL DATA SOURCE `/Root/tier1`, Interval("P20D") TO EXTERNAL DATA SOURCE `/Root/tier2` ON timestamp)"); + + lHelper.StartSchemaRequest("DROP EXTERNAL DATA SOURCE `/Root/tier2`", false); + lHelper.StartSchemaRequest("DROP EXTERNAL DATA SOURCE `/Root/tier1`", false); + lHelper.StartSchemaRequest("DROP TABLE `/Root/olapStore/olapTable`"); { - TTestCSEmulator emulator; - emulator.SetExpectedTieringsCount(0); - emulator.SetExpectedTiersCount(0); - emulator.CheckRuntime(runtime); + TTestCSEmulator* emulator = new TTestCSEmulator({ "/Root/tier1", "/Root/tier2" }); + runtime.Register(emulator); + emulator->CheckRuntime(runtime); } + lHelper.StartSchemaRequest("DROP EXTERNAL DATA SOURCE `/Root/tier2`"); + lHelper.StartSchemaRequest("DROP EXTERNAL DATA SOURCE `/Root/tier1`"); //runtime.SetLogPriority(NKikimrServices::TX_PROXY, NLog::PRI_TRACE); //runtime.SetLogPriority(NKikimrServices::KQP_YQL, NLog::PRI_TRACE); @@ -533,7 +380,7 @@ Y_UNIT_TEST_SUITE(ColumnShardTiers) { SecretKey: "SId:secretSecretKey" } )"; - const TString TierEndpoint = "fake"; + const TString TierEndpoint = "fake.fake"; #endif Y_UNIT_TEST(TieringUsage) { @@ -553,6 +400,7 @@ Y_UNIT_TEST_SUITE(ColumnShardTiers) { .SetUseRealThreads(false) .SetEnableMetadataProvider(true) .SetEnableTieringInColumnShard(true) + .SetEnableExternalDataSources(true) ; Tests::TServer::TPtr server = new Tests::TServer(serverSettings); @@ -570,35 +418,26 @@ Y_UNIT_TEST_SUITE(ColumnShardTiers) { // runtime.SetLogPriority(NKikimrServices::TX_DATASHARD, NLog::PRI_NOTICE); runtime.SetLogPriority(NKikimrServices::TX_COLUMNSHARD, NLog::PRI_DEBUG); runtime.SetLogPriority(NKikimrServices::BG_TASKS, NLog::PRI_DEBUG); + // runtime.SetLogPriority(NKikimrServices::TX_TIERING, NLog::PRI_DEBUG); // runtime.SetLogPriority(NKikimrServices::TX_PROXY_SCHEME_CACHE, NLog::PRI_DEBUG); TLocalHelper lHelper(*server); lHelper.SetOptionalStorageId("__DEFAULT"); - lHelper.StartSchemaRequest("CREATE OBJECT secretAccessKey ( " - "TYPE SECRET) WITH (value = ak)"); - lHelper.StartSchemaRequest("CREATE OBJECT secretSecretKey ( " - "TYPE SECRET) WITH (value = fakeSecret)"); + lHelper.CreateSecrets(); Singleton()->SetSecretKey("fakeSecret"); - lHelper.StartSchemaRequest("CREATE OBJECT tier1 ( " - "TYPE TIER) WITH (tierConfig = `" + TierConfigProtoStr + "`)"); - lHelper.StartSchemaRequest("CREATE OBJECT tier2 ( " - "TYPE TIER) WITH (tierConfig = `" + TierConfigProtoStr + "`)"); - - lHelper.StartSchemaRequest("CREATE OBJECT tiering1 (" - "TYPE TIERING_RULE) WITH (defaultColumn = timestamp, description = `" + ConfigTiering1Str + "` )"); - lHelper.StartSchemaRequest("CREATE OBJECT tiering2 (" - "TYPE TIERING_RULE) WITH (defaultColumn = timestamp, description = `" + ConfigTiering2Str + "` )"); + lHelper.CreateExternalDataSource("/Root/tier1", "http://" + TierEndpoint + "/fake"); + lHelper.CreateExternalDataSource("/Root/tier2", "http://" + TierEndpoint + "/fake"); { - TTestCSEmulator* emulator = new TTestCSEmulator; + TTestCSEmulator* emulator = new TTestCSEmulator({ "/Root/tier1", "/Root/tier2" }); runtime.Register(emulator); - emulator->MutableCheckers().emplace("TIER.tier1", TJsonChecker("Name", "fakeTier")); - emulator->MutableCheckers().emplace("TIER.tier2", TJsonChecker("ObjectStorage.Endpoint", TierEndpoint)); - emulator->SetExpectedTieringsCount(2); - emulator->SetExpectedTiersCount(2); emulator->CheckRuntime(runtime); + UNIT_ASSERT_VALUES_EQUAL(emulator->GetTierConfigs().at("/Root/tier1").GetProtoConfig().GetEndpoint(), TierEndpoint); } + lHelper.CreateTestOlapTable("olapTable", 2); + lHelper.StartSchemaRequest( + R"(ALTER TABLE `/Root/olapStore/olapTable` SET TTL Interval("P10D") TO EXTERNAL DATA SOURCE `/Root/tier1`, Interval("P20D") TO EXTERNAL DATA SOURCE `/Root/tier2` ON timestamp)"); Cerr << "Wait tables" << Endl; runtime.SimulateSleep(TDuration::Seconds(20)); Cerr << "Initialization tables" << Endl; @@ -606,9 +445,10 @@ Y_UNIT_TEST_SUITE(ColumnShardTiers) { runtime.UpdateCurrentTime(now); const TInstant pkStart = now - TDuration::Days(15); - auto batch = lHelper.TestArrowBatch(0, pkStart.GetValue(), 6000); + auto batch1 = lHelper.TestArrowBatch(0, pkStart.GetValue(), 6000); + auto batch2 = lHelper.TestArrowBatch(0, pkStart.GetValue() - 100, 6000); auto batchSmall = lHelper.TestArrowBatch(0, now.GetValue(), 1); - auto batchSize = NArrow::GetBatchDataSize(batch); + auto batchSize = NArrow::GetBatchDataSize(batch1); Cerr << "Inserting " << batchSize << " bytes..." << Endl; UNIT_ASSERT(batchSize > 4 * 1024 * 1024); // NColumnShard::TLimits::MIN_BYTES_TO_INSERT UNIT_ASSERT(batchSize < 8 * 1024 * 1024); @@ -617,7 +457,8 @@ Y_UNIT_TEST_SUITE(ColumnShardTiers) { TAtomic unusedPrev; runtime.GetAppData().Icb->SetValue("ColumnShardControls.GranuleIndexedPortionsCountLimit", 1, unusedPrev); } - lHelper.SendDataViaActorSystem("/Root/olapStore/olapTable", batch); + lHelper.SendDataViaActorSystem("/Root/olapStore/olapTable", batch1); + lHelper.SendDataViaActorSystem("/Root/olapStore/olapTable", batch2); { const TInstant start = Now(); bool check = false; @@ -642,8 +483,8 @@ Y_UNIT_TEST_SUITE(ColumnShardTiers) { lHelper.DropTable("/Root/olapStore/olapTable"); lHelper.StartDataRequest("DELETE FROM `/Root/olapStore/olapTable`"); */ - lHelper.StartSchemaRequest("UPSERT OBJECT tiering1 (" - "TYPE TIERING_RULE) WITH (defaultColumn = timestamp, description = `" + ConfigTieringNothingStr + "` )"); + lHelper.StartSchemaRequest( + R"(ALTER TABLE `/Root/olapStore/olapTable` SET TTL Interval("P10000D") TO EXTERNAL DATA SOURCE `/Root/tier1`, Interval("P20000D") TO EXTERNAL DATA SOURCE `/Root/tier2` ON timestamp)"); { const TInstant start = Now(); bool check = false; @@ -934,11 +775,11 @@ Y_UNIT_TEST_SUITE(ColumnShardTiers) { { TVector> result; lHelper.StartScanRequest("SELECT MAX(timestamp) as a, MIN(timestamp) as b, COUNT(*) as c FROM `/Root/olapStore/olapTable`", true, &result); - UNIT_ASSERT(result.size() == 1); - UNIT_ASSERT(result.front().size() == 3); - UNIT_ASSERT(GetValueResult(result.front(), "c")->GetProto().uint64_value() == 600000); - UNIT_ASSERT(GetValueResult(result.front(), "a")->GetProto().uint64_value() == 599999000000); - UNIT_ASSERT(GetValueResult(result.front(), "b")->GetProto().uint64_value() == 0); + UNIT_ASSERT_VALUES_EQUAL(result.size(), 1); + UNIT_ASSERT_VALUES_EQUAL(result.front().size(), 3); + UNIT_ASSERT_VALUES_EQUAL(GetValueResult(result.front(), "c")->GetProto().uint64_value(), 600000); + UNIT_ASSERT_VALUES_EQUAL(GetValueResult(result.front(), "a")->GetProto().uint64_value(), 599999000000); + UNIT_ASSERT_VALUES_EQUAL(GetValueResult(result.front(), "b")->GetProto().uint64_value(), 0); } const ui32 reduceStepsCount = 1; for (ui32 i = 0; i < reduceStepsCount; ++i) { diff --git a/ydb/core/tx/tiering/ya.make b/ydb/core/tx/tiering/ya.make index b7412d358938..f64ae8bed4ea 100644 --- a/ydb/core/tx/tiering/ya.make +++ b/ydb/core/tx/tiering/ya.make @@ -3,8 +3,7 @@ LIBRARY() SRCS( common.cpp manager.cpp - GLOBAL external_data.cpp - snapshot.cpp + fetcher.cpp ) IF (OS_WINDOWS) @@ -18,8 +17,8 @@ PEERDIR( library/cpp/json/writer ydb/core/blobstorage ydb/core/protos + ydb/core/tx/columnshard/hooks/abstract ydb/core/tx/schemeshard - ydb/core/tx/tiering/rule ydb/core/tx/tiering/tier ydb/core/tablet_flat/protos ydb/core/wrappers @@ -28,6 +27,8 @@ PEERDIR( ydb/services/metadata ) +YQL_LAST_ABI_VERSION() + END() RECURSE_FOR_TESTS( diff --git a/ydb/core/tx/tx_proxy/rpc_long_tx.cpp b/ydb/core/tx/tx_proxy/rpc_long_tx.cpp index 11650948fe93..435dd536bec6 100644 --- a/ydb/core/tx/tx_proxy/rpc_long_tx.cpp +++ b/ydb/core/tx/tx_proxy/rpc_long_tx.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -21,7 +22,8 @@ using namespace NLongTxService; // Common logic of LongTx Write that takes care of splitting the data according to the sharding scheme, // sending it to shards and collecting their responses template -class TLongTxWriteBase: public TActorBootstrapped { +class TLongTxWriteBase: public TActorBootstrapped, + NColumnShard::TMonitoringObjectsCounter> { using TBase = TActorBootstrapped; static inline TAtomicCounter MemoryInFlight = 0; @@ -37,8 +39,7 @@ class TLongTxWriteBase: public TActorBootstrapped { , Path(path) , DedupId(dedupId) , LongTxId(longTxId) - , ActorSpan(0, NWilson::TTraceId::NewTraceId(0, Max()), "TLongTxWriteBase") - { + , ActorSpan(0, NWilson::TTraceId::NewTraceId(0, Max()), "TLongTxWriteBase") { if (token) { UserToken.emplace(token); } @@ -95,7 +96,11 @@ class TLongTxWriteBase: public TActorBootstrapped { accessor.reset(); const auto& splittedData = shardsSplitter->GetSplitData(); - InternalController = std::make_shared(splittedData.GetShardRequestsCount(), this->SelfId(), LongTxId, NoTxWrite); + const auto& shardsInRequest = splittedData.GetShardRequestsCount(); + InternalController = + std::make_shared(shardsInRequest, this->SelfId(), LongTxId, NoTxWrite); + + InternalController->GetCounters()->OnSplitByShards(shardsInRequest); ui32 sumBytes = 0; ui32 rowsCount = 0; ui32 writeIdx = 0; @@ -104,9 +109,9 @@ class TLongTxWriteBase: public TActorBootstrapped { InternalController->GetCounters()->OnRequest(shardInfo->GetRowsCount(), shardInfo->GetBytes()); sumBytes += shardInfo->GetBytes(); rowsCount += shardInfo->GetRowsCount(); - this->Register(new NEvWrite::TShardWriter(shard, shardsSplitter->GetTableId(), shardsSplitter->GetSchemaVersion(), DedupId, shardInfo, - ActorSpan, InternalController, - ++writeIdx, NEvWrite::EModificationType::Replace, NoTxWrite)); + this->Register( + new NEvWrite::TShardWriter(shard, shardsSplitter->GetTableId(), shardsSplitter->GetSchemaVersion(), DedupId, shardInfo, + ActorSpan, InternalController, ++writeIdx, NEvWrite::EModificationType::Replace, NoTxWrite, TDuration::Seconds(20))); } } pSpan.Attribute("affected_shards_count", (long)splittedData.GetShardsInfo().size()); @@ -235,8 +240,7 @@ class TLongTxWriteInternal: public TLongTxWriteBase { , ReplyTo(replyTo) , NavigateResult(navigateResult) , Batch(batch) - , Issues(issues) - { + , Issues(issues) { Y_ABORT_UNLESS(Issues); DataAccessor = std::make_unique(Batch); } diff --git a/ydb/core/tx/tx_proxy/upload_rows_common_impl.cpp b/ydb/core/tx/tx_proxy/upload_rows_common_impl.cpp index 281d8bf05bc7..41a5433a773b 100644 --- a/ydb/core/tx/tx_proxy/upload_rows_common_impl.cpp +++ b/ydb/core/tx/tx_proxy/upload_rows_common_impl.cpp @@ -1,28 +1,3 @@ #include "upload_rows_common_impl.h" - -namespace NKikimr { - - TUploadCounters::TUploadCounters() - : TBase("BulkUpsert") - { - RequestsCount = TBase::GetDeriviative("Requests/Count"); - ReplyDuration = TBase::GetHistogram("Replies/Duration", NMonitoring::ExponentialHistogram(15, 2, 10)); - - RowsCount = TBase::GetDeriviative("Rows/Count"); - PackageSizeRecordsByRecords = TBase::GetHistogram("ByRecords/PackageSize/Records", NMonitoring::ExponentialHistogram(15, 2, 10)); - PackageSizeCountByRecords = TBase::GetHistogram("ByRecords/PackageSize/Count", NMonitoring::ExponentialHistogram(15, 2, 10)); - - PreparingDuration = TBase::GetHistogram("Preparing/DurationMs", NMonitoring::ExponentialHistogram(15, 2, 10)); - WritingDuration = TBase::GetHistogram("Writing/DurationMs", NMonitoring::ExponentialHistogram(15, 2, 10)); - CommitDuration = TBase::GetHistogram("Commit/DurationMs", NMonitoring::ExponentialHistogram(15, 2, 10)); - PrepareReplyDuration = TBase::GetHistogram("ToReply/DurationMs", NMonitoring::ExponentialHistogram(15, 2, 10)); - - const google::protobuf::EnumDescriptor* descriptor = ::Ydb::StatusIds::StatusCode_descriptor(); - for (ui32 i = 0; i < (ui32)descriptor->value_count(); ++i) { - auto vDescription = descriptor->value(i); - CodesCount.emplace(vDescription->name(), CreateSubGroup("reply_code", vDescription->name()).GetDeriviative("Replies/Count")); - } - } - -} +namespace NKikimr {} diff --git a/ydb/core/tx/tx_proxy/upload_rows_common_impl.h b/ydb/core/tx/tx_proxy/upload_rows_common_impl.h index 12b62be92ca5..779e5944c2ca 100644 --- a/ydb/core/tx/tx_proxy/upload_rows_common_impl.h +++ b/ydb/core/tx/tx_proxy/upload_rows_common_impl.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -30,93 +31,13 @@ #include #include +#include +#include #include #include -#include namespace NKikimr { -class TUploadCounters: public NColumnShard::TCommonCountersOwner { -private: - using TBase = NColumnShard::TCommonCountersOwner; - NMonitoring::TDynamicCounters::TCounterPtr RequestsCount; - NMonitoring::THistogramPtr ReplyDuration; - - NMonitoring::TDynamicCounters::TCounterPtr RowsCount; - NMonitoring::THistogramPtr PackageSizeRecordsByRecords; - NMonitoring::THistogramPtr PackageSizeCountByRecords; - - NMonitoring::THistogramPtr PreparingDuration; - NMonitoring::THistogramPtr WritingDuration; - NMonitoring::THistogramPtr CommitDuration; - NMonitoring::THistogramPtr PrepareReplyDuration; - - THashMap CodesCount; -public: - TUploadCounters(); - - class TGuard: TMoveOnly { - private: - TMonotonic Start = TMonotonic::Now(); - std::optional WritingStarted; - std::optional CommitStarted; - std::optional CommitFinished; - std::optional ReplyFinished; - TUploadCounters& Owner; - public: - TGuard(const TMonotonic start, TUploadCounters& owner) - : Start(start) - , Owner(owner) - { - - } - - void OnWritingStarted() { - WritingStarted = TMonotonic::Now(); - Owner.PreparingDuration->Collect((*WritingStarted - Start).MilliSeconds()); - } - - void OnCommitStarted() { - CommitStarted = TMonotonic::Now(); - AFL_VERIFY(WritingStarted); - Owner.WritingDuration->Collect((*CommitStarted - *WritingStarted).MilliSeconds()); - } - - void OnCommitFinished() { - CommitFinished = TMonotonic::Now(); - AFL_VERIFY(CommitStarted); - Owner.CommitDuration->Collect((*CommitFinished - *CommitStarted).MilliSeconds()); - } - - void OnReply(const ::Ydb::StatusIds::StatusCode code) { - ReplyFinished = TMonotonic::Now(); - if (CommitFinished) { - Owner.PrepareReplyDuration->Collect((*ReplyFinished - *CommitFinished).MilliSeconds()); - } - Owner.ReplyDuration->Collect((*ReplyFinished - Start).MilliSeconds()); - - const TString name = ::Ydb::StatusIds::StatusCode_Name(code); - auto it = Owner.CodesCount.find(name); - Y_ABORT_UNLESS(it != Owner.CodesCount.end()); - it->second->Add(1); - } - }; - - TGuard BuildGuard(const TMonotonic start) { - return TGuard(start, *this); - } - - void OnRequest(const ui64 rowsCount) const { - RequestsCount->Add(1); - RowsCount->Add(rowsCount); - PackageSizeRecordsByRecords->Collect((i64)rowsCount, rowsCount); - PackageSizeCountByRecords->Collect(rowsCount); - } - - void OnReply(const TDuration dFull, const TDuration dDelta, const ::Ydb::StatusIds::StatusCode code) const; -}; - - using namespace NActors; struct TUpsertCost { @@ -218,8 +139,7 @@ class TUploadRowsBase : public TActorBootstrapped ShardRepliesLeft; THashMap ShardUploadRetryStates; - Ydb::StatusIds::StatusCode Status; - TString ErrorMessage; + TUploadStatus Status; std::shared_ptr Issues = std::make_shared(); NLongTxService::TLongTxId LongTxId; TUploadCounters UploadCounters; @@ -655,8 +575,8 @@ class TUploadRowsBase : public TActorBootstrappedNow() - StartTime).Seconds() << " sec", ctx); + TStringBuilder() << "longTx " << LongTxId.ToString() + << " timed out, duration: " << (TAppData::TimeProvider->Now() - StartTime).Seconds() << " sec", + ctx); } void Handle(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev, const TActorContext& ctx) { @@ -683,37 +604,22 @@ class TUploadRowsBase : public TActorBootstrappedGet()->Request.Release()); @@ -721,20 +627,20 @@ class TUploadRowsBase : public TActorBootstrappedGetLongTxId(); - LOG_DEBUG_S(ctx, NKikimrServices::RPC_REQUEST, LogPrefix() << "started LongTx '" << LongTxId.ToString() << "'"); + LOG_DEBUG_S(ctx, NKikimrServices::RPC_REQUEST, TStringBuilder() << "started LongTx '" << LongTxId.ToString() << "'"); auto outputColumns = GetOutputColumns(ctx); if (!outputColumns.empty()) { if (!Batch) { - return ReplyWithError(Ydb::StatusIds::BAD_REQUEST, - LogPrefix() << "no data or conversion error", ctx); + return ReplyWithError(Ydb::StatusIds::BAD_REQUEST, "no data or conversion error", ctx); } auto batch = NArrow::TColumnOperator().ErrorIfAbsent().Extract(Batch, outputColumns); if (!batch) { for (auto& columnName : outputColumns) { if (Batch->schema()->GetFieldIndex(columnName) < 0) { - return ReplyWithError(Ydb::StatusIds::SCHEME_ERROR, - LogPrefix() << "no expected column '" << columnName << "' in data", ctx); + return ReplyWithError( + Ydb::StatusIds::SCHEME_ERROR, TStringBuilder() << "no expected column '" << columnName << "' in data", ctx); } } - return ReplyWithError(Ydb::StatusIds::SCHEME_ERROR, LogPrefix() << "cannot prepare data", ctx); + return ReplyWithError(Ydb::StatusIds::SCHEME_ERROR, "cannot prepare data", ctx); } Y_ABORT_UNLESS(batch); @@ -866,9 +773,8 @@ class TUploadRowsBase : public TActorBootstrapped long tx -> shard insert) auto validationInfo = batch->ValidateFull(); if (!validationInfo.ok()) { - return ReplyWithError(Ydb::StatusIds::SCHEME_ERROR, LogPrefix() - << "bad batch in data: " + validationInfo.message() - << "; order:" + JoinSeq(", ", outputColumns), ctx); + return ReplyWithError(Ydb::StatusIds::SCHEME_ERROR, + TStringBuilder() << "bad batch in data: " + validationInfo.message() << "; order:" + JoinSeq(", ", outputColumns), ctx); } #endif @@ -882,19 +788,19 @@ class TUploadRowsBase : public TActorBootstrappedErrorCount > 0) { - ReplyWithError(Ydb::StatusIds::SCHEME_ERROR, LogPrefix() << "failed to get table schema", ctx); + ReplyWithError(Ydb::StatusIds::SCHEME_ERROR, "failed to get table schema", ctx); return {}; } auto& entry = ResolveNamesResult->ResultSet[0]; if (entry.Kind != NSchemeCache::TSchemeCacheNavigate::KindColumnTable) { - ReplyWithError(Ydb::StatusIds::SCHEME_ERROR, LogPrefix() << "specified path is not a column table", ctx); + ReplyWithError(Ydb::StatusIds::SCHEME_ERROR, "specified path is not a column table", ctx); return {}; } if (!entry.ColumnTableInfo || !entry.ColumnTableInfo->Description.HasSchema()) { - ReplyWithError(Ydb::StatusIds::SCHEME_ERROR, LogPrefix() << "column table has no schema", ctx); + ReplyWithError(Ydb::StatusIds::SCHEME_ERROR, "column table has no schema", ctx); return {}; } @@ -929,8 +835,7 @@ class TUploadRowsBase : public TActorBootstrappedRequest; if (ResolvePartitionsResult->ErrorCount > 0) { - return ReplyWithError(Ydb::StatusIds::SCHEME_ERROR, LogPrefix() << "unknown table", ctx); + return ReplyWithError(Ydb::StatusIds::SCHEME_ERROR, "unknown table", ctx); } TString accessCheckError; if (!CheckAccess(accessCheckError)) { - return ReplyWithError(Ydb::StatusIds::UNAUTHORIZED, LogPrefix() << accessCheckError, ctx); + return ReplyWithError(Ydb::StatusIds::UNAUTHORIZED, accessCheckError, ctx); } auto getShardsString = [] (const TVector& partitions) { @@ -1201,7 +1106,8 @@ class TUploadRowsBase : public TActorBootstrappedTableId, TActorId()), 0, 0, Span.GetTraceId()); - SetError(Ydb::StatusIds::UNAVAILABLE, Sprintf("Failed to connect to shard %" PRIu64, ev->Get()->TabletId)); + SetError(TUploadStatus(Ydb::StatusIds::UNAVAILABLE, TUploadStatus::ECustomSubcode::DELIVERY_PROBLEM, + Sprintf("Failed to connect to shard %" PRIu64, ev->Get()->TabletId))); ShardRepliesLeft.erase(ev->Get()->TabletId); return ReplyIfDone(ctx); @@ -1241,28 +1148,10 @@ class TUploadRowsBase : public TActorBootstrappedTableId, TActorId()), 0, 0, Span.GetTraceId()); - status = Ydb::StatusIds::OVERLOADED; - break; - case NKikimrTxDataShard::TError::DISK_SPACE_EXHAUSTED: - case NKikimrTxDataShard::TError::OUT_OF_SPACE: - status = Ydb::StatusIds::UNAVAILABLE; - break; - case NKikimrTxDataShard::TError::SCHEME_ERROR: - status = Ydb::StatusIds::SCHEME_ERROR; - break; - case NKikimrTxDataShard::TError::BAD_ARGUMENT: - status = Ydb::StatusIds::BAD_REQUEST; - break; - case NKikimrTxDataShard::TError::EXECUTION_CANCELLED: - status = Ydb::StatusIds::TIMEOUT; - break; - }; + } if (auto* state = ShardUploadRetryStates.FindPtr(shardId)) { if (!shardResponse.HasOverloadSubscribed()) { @@ -1275,7 +1164,8 @@ class TUploadRowsBase : public TActorBootstrapped(shardResponse.GetStatus()), shardResponse.GetErrorDescription())); } // Notify the cache that we are done with the pipe @@ -1299,13 +1189,12 @@ class TUploadRowsBase : public TActorBootstrappedsecond; +} + +TUploadCounters::TUploadCounters() + : TBase("BulkUpsert") { + RequestsCount = TBase::GetDeriviative("Requests/Count"); + ReplyDuration = TBase::GetHistogram("Replies/Duration", NMonitoring::ExponentialHistogram(15, 2, 10)); + + RowsCount = TBase::GetDeriviative("Rows/Count"); + PackageSizeRecordsByRecords = TBase::GetHistogram("ByRecords/PackageSize/Records", NMonitoring::ExponentialHistogram(15, 2, 10)); + PackageSizeCountByRecords = TBase::GetHistogram("ByRecords/PackageSize/Count", NMonitoring::ExponentialHistogram(15, 2, 10)); + + PreparingDuration = TBase::GetHistogram("Preparing/DurationMs", NMonitoring::ExponentialHistogram(15, 2, 10)); + WritingDuration = TBase::GetHistogram("Writing/DurationMs", NMonitoring::ExponentialHistogram(15, 2, 10)); + CommitDuration = TBase::GetHistogram("Commit/DurationMs", NMonitoring::ExponentialHistogram(15, 2, 10)); + PrepareReplyDuration = TBase::GetHistogram("ToReply/DurationMs", NMonitoring::ExponentialHistogram(15, 2, 10)); +} +} diff --git a/ydb/core/tx/tx_proxy/upload_rows_counters.h b/ydb/core/tx/tx_proxy/upload_rows_counters.h new file mode 100644 index 000000000000..4839e76da172 --- /dev/null +++ b/ydb/core/tx/tx_proxy/upload_rows_counters.h @@ -0,0 +1,136 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include + +namespace NKikimr { + +class TUploadStatus { +private: + YDB_READONLY_DEF(Ydb::StatusIds::StatusCode, Code); + YDB_READONLY_DEF(std::optional, Subcode); + YDB_READONLY_DEF(std::optional, ErrorMessage); + +public: + enum class ECustomSubcode { + DISK_QUOTA_EXCEEDED, + DELIVERY_PROBLEM, + }; + +public: + TUploadStatus(const Ydb::StatusIds::StatusCode code) + : Code(code) { + } + TUploadStatus(const Ydb::StatusIds::StatusCode code, const TString& errorMessage) + : Code(code) + , ErrorMessage(errorMessage) { + AFL_VERIFY(code != Ydb::StatusIds::SUCCESS); + } + TUploadStatus(const Ydb::StatusIds::StatusCode code, const ECustomSubcode& subcode, const TString& errorMessage) + : Code(code) + , Subcode(ToString(subcode)) + , ErrorMessage(errorMessage) { + AFL_VERIFY(code != Ydb::StatusIds::SUCCESS); + } + TUploadStatus(const NSchemeCache::TSchemeCacheNavigate::EStatus status); + TUploadStatus(const NKikimrTxDataShard::TError::EKind status, const TString& errorDescription); + + struct THasher { + ui64 operator()(const TUploadStatus& object) const { + return MultiHash(object.GetCode(), object.GetSubcode().has_value(), object.GetSubcode().value_or("")); + } + }; + + bool operator==(const TUploadStatus& other) const { + return Code == other.Code && Subcode == other.Subcode; + } + + TString GetCodeString() const { + return Ydb::StatusIds::StatusCode_Name(Code); + } +}; + +class TUploadCounters: public NColumnShard::TCommonCountersOwner { +private: + using TBase = NColumnShard::TCommonCountersOwner; + NMonitoring::TDynamicCounters::TCounterPtr RequestsCount; + NMonitoring::THistogramPtr ReplyDuration; + + NMonitoring::TDynamicCounters::TCounterPtr RowsCount; + NMonitoring::THistogramPtr PackageSizeRecordsByRecords; + NMonitoring::THistogramPtr PackageSizeCountByRecords; + + NMonitoring::THistogramPtr PreparingDuration; + NMonitoring::THistogramPtr WritingDuration; + NMonitoring::THistogramPtr CommitDuration; + NMonitoring::THistogramPtr PrepareReplyDuration; + + THashMap CodesCount; + + NMonitoring::TDynamicCounters::TCounterPtr GetCodeCounter(const TUploadStatus& status); + +public: + TUploadCounters(); + + class TGuard: TMoveOnly { + private: + TMonotonic Start = TMonotonic::Now(); + std::optional WritingStarted; + std::optional CommitStarted; + std::optional CommitFinished; + std::optional ReplyFinished; + TUploadCounters& Owner; + + public: + TGuard(const TMonotonic start, TUploadCounters& owner) + : Start(start) + , Owner(owner) { + } + + void OnWritingStarted() { + WritingStarted = TMonotonic::Now(); + Owner.PreparingDuration->Collect((*WritingStarted - Start).MilliSeconds()); + } + + void OnCommitStarted() { + CommitStarted = TMonotonic::Now(); + AFL_VERIFY(WritingStarted); + Owner.WritingDuration->Collect((*CommitStarted - *WritingStarted).MilliSeconds()); + } + + void OnCommitFinished() { + CommitFinished = TMonotonic::Now(); + AFL_VERIFY(CommitStarted); + Owner.CommitDuration->Collect((*CommitFinished - *CommitStarted).MilliSeconds()); + } + + void OnReply(const TUploadStatus& status) { + ReplyFinished = TMonotonic::Now(); + if (CommitFinished) { + Owner.PrepareReplyDuration->Collect((*ReplyFinished - *CommitFinished).MilliSeconds()); + } + Owner.ReplyDuration->Collect((*ReplyFinished - Start).MilliSeconds()); + Owner.GetCodeCounter(status)->Add(1); + } + }; + + TGuard BuildGuard(const TMonotonic start) { + return TGuard(start, *this); + } + + void OnRequest(const ui64 rowsCount) const { + RequestsCount->Add(1); + RowsCount->Add(rowsCount); + PackageSizeRecordsByRecords->Collect((i64)rowsCount, rowsCount); + PackageSizeCountByRecords->Collect(rowsCount); + } +}; + +} // namespace NKikimr diff --git a/ydb/core/tx/tx_proxy/ya.make b/ydb/core/tx/tx_proxy/ya.make index d592810a65b2..a5042ac58bd9 100644 --- a/ydb/core/tx/tx_proxy/ya.make +++ b/ydb/core/tx/tx_proxy/ya.make @@ -12,12 +12,14 @@ SRCS( rpc_long_tx.cpp snapshotreq.cpp commitreq.cpp + upload_rows_counters.cpp upload_rows_common_impl.cpp upload_rows.cpp global.cpp ) GENERATE_ENUM_SERIALIZATION(read_table_impl.h) +GENERATE_ENUM_SERIALIZATION(upload_rows_counters.h) PEERDIR( ydb/library/actors/core diff --git a/ydb/core/wrappers/abstract.cpp b/ydb/core/wrappers/abstract.cpp index 0f3681beec77..5f1a452db048 100644 --- a/ydb/core/wrappers/abstract.cpp +++ b/ydb/core/wrappers/abstract.cpp @@ -11,7 +11,7 @@ IExternalStorageOperator::TPtr IExternalStorageConfig::ConstructStorageOperator( } IExternalStorageConfig::TPtr IExternalStorageConfig::Construct(const NKikimrSchemeOp::TS3Settings& settings) { - if (settings.GetEndpoint() == "fake") { + if (settings.GetEndpoint() == "fake.fake") { return std::make_shared(settings.GetBucket(), settings.GetSecretKey()); } else { return std::make_shared(settings); diff --git a/ydb/core/wrappers/fake_storage.h b/ydb/core/wrappers/fake_storage.h index c672835c9874..222c9ad30461 100644 --- a/ydb/core/wrappers/fake_storage.h +++ b/ydb/core/wrappers/fake_storage.h @@ -142,7 +142,7 @@ class TFakeExternalStorageOperator: public IExternalStorageOperator { template void ExecuteImpl(TEvent& ev) const { ev->Get()->MutableRequest().WithBucket(Bucket); - Y_ABORT_UNLESS(SecretKey == Singleton()->GetSecretKey()); + Y_ABORT_UNLESS(SecretKey == Singleton()->GetSecretKey(), "%s != %s", SecretKey.data(), Singleton()->GetSecretKey().data()); if (OwnedStorage) { OwnedStorage->Execute(ev, ReplyAdapter); } else { diff --git a/ydb/core/ydb_convert/column_families.h b/ydb/core/ydb_convert/column_families.h index 14f6c8517da1..809399b5f2f0 100644 --- a/ydb/core/ydb_convert/column_families.h +++ b/ydb/core/ydb_convert/column_families.h @@ -147,6 +147,12 @@ namespace NKikimr { return false; } + if (familySettings.has_compression_level()) { + *code = Ydb::StatusIds::BAD_REQUEST; + *error = "Field `COMPRESSION_LEVEL` is not supported for OLTP tables"; + return false; + } + auto* family = MutableNamedFamily(familySettings.name()); if (familySettings.has_data()) { @@ -216,6 +222,15 @@ namespace NKikimr { return false; } + for (size_t index = 0; index < PartitionConfig->ColumnFamiliesSize(); ++index) { + auto columnFamily = PartitionConfig->GetColumnFamilies(index); + if (columnFamily.HasColumnCodecLevel()) { + *code = Ydb::StatusIds::BAD_REQUEST; + *error = "Field `COMPRESSION_LEVEL` is not supported for OLTP tables"; + return false; + } + } + if (!defaultFamily->HasStorageConfig() || !defaultFamily->GetStorageConfig().HasSysLog() || !defaultFamily->GetStorageConfig().HasLog()) diff --git a/ydb/core/ydb_convert/table_description.cpp b/ydb/core/ydb_convert/table_description.cpp index f8b5b51c3321..56e31e2dde35 100644 --- a/ydb/core/ydb_convert/table_description.cpp +++ b/ydb/core/ydb_convert/table_description.cpp @@ -45,8 +45,6 @@ THashSet GetAlterOperationKinds(const Ydb::Table::AlterTabl req->alter_columns_size() || req->ttl_action_case() != Ydb::Table::AlterTableRequest::TTL_ACTION_NOT_SET || - req->tiering_action_case() != - Ydb::Table::AlterTableRequest::TIERING_ACTION_NOT_SET || req->has_alter_storage_settings() || req->add_column_families_size() || req->alter_column_families_size() || req->set_compaction_policy() || req->has_alter_partitioning_settings() || @@ -469,38 +467,6 @@ Ydb::Type* AddColumn(Ydb::Table::ColumnMeta return columnType; } -template -static void AddTtl(TYdbProto& out, const TTtl& inTTL) { - switch (inTTL.GetColumnUnit()) { - case NKikimrSchemeOp::TTTLSettings::UNIT_AUTO: { - auto& outTTL = *out.mutable_ttl_settings()->mutable_date_type_column(); - outTTL.set_column_name(inTTL.GetColumnName()); - outTTL.set_expire_after_seconds(inTTL.GetExpireAfterSeconds()); - break; - } - - case NKikimrSchemeOp::TTTLSettings::UNIT_SECONDS: - case NKikimrSchemeOp::TTTLSettings::UNIT_MILLISECONDS: - case NKikimrSchemeOp::TTTLSettings::UNIT_MICROSECONDS: - case NKikimrSchemeOp::TTTLSettings::UNIT_NANOSECONDS: { - auto& outTTL = *out.mutable_ttl_settings()->mutable_value_since_unix_epoch(); - outTTL.set_column_name(inTTL.GetColumnName()); - outTTL.set_column_unit(static_cast(inTTL.GetColumnUnit())); - outTTL.set_expire_after_seconds(inTTL.GetExpireAfterSeconds()); - break; - } - - default: - break; - } - - if constexpr (std::is_same_v) { - if (inTTL.HasSysSettings() && inTTL.GetSysSettings().HasRunInterval()) { - out.mutable_ttl_settings()->set_run_interval_seconds(TDuration::FromValue(inTTL.GetSysSettings().GetRunInterval()).Seconds()); - } - } -} - template void FillColumnDescriptionImpl(TYdbProto& out, NKikimrMiniKQL::TType& splitKeyType, const NKikimrSchemeOp::TTableDescription& in) { @@ -532,11 +498,11 @@ void FillColumnDescriptionImpl(TYdbProto& out, if (in.HasTTLSettings()) { if (in.GetTTLSettings().HasEnabled()) { - AddTtl(out, in.GetTTLSettings().GetEnabled()); - } - - if (in.GetTTLSettings().HasUseTiering()) { - out.set_tiering(in.GetTTLSettings().GetUseTiering()); + Ydb::StatusIds::StatusCode code; + TString error; + if (!FillTtlSettings(*out.mutable_ttl_settings(), in.GetTTLSettings().GetEnabled(), code, error)) { + ythrow yexception() << "invalid TTL settings: " << error; + } } } } @@ -572,11 +538,11 @@ void FillColumnDescription(Ydb::Table::DescribeTableResult& out, const NKikimrSc if (in.HasTtlSettings()) { if (in.GetTtlSettings().HasEnabled()) { - AddTtl(out, in.GetTtlSettings().GetEnabled()); - } - - if (in.GetTtlSettings().HasUseTiering()) { - out.set_tiering(in.GetTtlSettings().GetUseTiering()); + Ydb::StatusIds::StatusCode status; + TString error; + if (!FillTtlSettings(*out.mutable_ttl_settings(), in.GetTtlSettings().GetEnabled(), status, error)) { + ythrow yexception() << "invalid TTL settings: " << error; + } } } @@ -697,10 +663,17 @@ bool FillColumnDescription(NKikimrSchemeOp::TTableDescription& out, return true; } -bool FillColumnDescription(NKikimrSchemeOp::TColumnTableDescription& out, - const google::protobuf::RepeatedPtrField& in, Ydb::StatusIds::StatusCode& status, TString& error) { - auto* schema = out.MutableSchema(); +NKikimrSchemeOp::TOlapColumnDescription* GetAddColumn(NKikimrSchemeOp::TColumnTableDescription& out) { + return out.MutableSchema()->AddColumns(); +} + +NKikimrSchemeOp::TOlapColumnDescription* GetAddColumn(NKikimrSchemeOp::TAlterColumnTable& out) { + return out.MutableAlterSchema()->AddAddColumns(); +} +template +bool FillColumnDescriptionImpl(TColumnTable& out, const google::protobuf::RepeatedPtrField& in, + Ydb::StatusIds::StatusCode& status, TString& error) { for (const auto& column : in) { if (column.type().has_pg_type()) { status = Ydb::StatusIds::BAD_REQUEST; @@ -708,7 +681,7 @@ bool FillColumnDescription(NKikimrSchemeOp::TColumnTableDescription& out, return false; } - auto* columnDesc = schema->AddColumns(); + auto* columnDesc = GetAddColumn(out); columnDesc->SetName(column.name()); NScheme::TTypeInfo typeInfo; @@ -718,11 +691,141 @@ bool FillColumnDescription(NKikimrSchemeOp::TColumnTableDescription& out, } columnDesc->SetType(NScheme::TypeName(typeInfo, typeMod)); columnDesc->SetNotNull(column.not_null()); + + if (!column.Getfamily().empty()) { + columnDesc->SetColumnFamilyName(column.Getfamily()); + } } return true; } +bool FillColumnDescription(NKikimrSchemeOp::TColumnTableDescription& out, const google::protobuf::RepeatedPtrField& in, + Ydb::StatusIds::StatusCode& status, TString& error) { + return FillColumnDescriptionImpl(out, in, status, error); +} + +bool FillColumnDescription(NKikimrSchemeOp::TAlterColumnTable& out, const google::protobuf::RepeatedPtrField& in, + Ydb::StatusIds::StatusCode& status, TString& error) { + return FillColumnDescriptionImpl(out, in, status, error); +} + +bool FillColumnFamily( + const Ydb::Table::ColumnFamily& from, NKikimrSchemeOp::TFamilyDescription* to, Ydb::StatusIds::StatusCode& status, TString& error) { + to->SetName(from.name()); + if (from.has_data()) { + status = Ydb::StatusIds::BAD_REQUEST; + error = TStringBuilder() << "Field `DATA` is not supported for OLAP tables in column family '" << from.name() << "'"; + return false; + } + switch (from.compression()) { + case Ydb::Table::ColumnFamily::COMPRESSION_UNSPECIFIED: + break; + case Ydb::Table::ColumnFamily::COMPRESSION_NONE: + to->SetColumnCodec(NKikimrSchemeOp::ColumnCodecPlain); + break; + case Ydb::Table::ColumnFamily::COMPRESSION_LZ4: + to->SetColumnCodec(NKikimrSchemeOp::ColumnCodecLZ4); + break; + case Ydb::Table::ColumnFamily::COMPRESSION_ZSTD: + to->SetColumnCodec(NKikimrSchemeOp::ColumnCodecZSTD); + break; + default: + status = Ydb::StatusIds::BAD_REQUEST; + error = TStringBuilder() << "Unsupported compression value " << (ui32)from.compression() << " in column family '" << from.name() + << "'"; + return false; + } + if (from.has_compression_level()) { + to->SetColumnCodecLevel(from.compression_level()); + } + return true; +} + +bool BuildAlterColumnTableModifyScheme(const TString& path, const Ydb::Table::AlterTableRequest* req, + NKikimrSchemeOp::TModifyScheme* modifyScheme, Ydb::StatusIds::StatusCode& status, TString& error) { + const auto ops = GetAlterOperationKinds(req); + if (ops.empty()) { + status = Ydb::StatusIds::BAD_REQUEST; + error = "Empty alter"; + return false; + } + + if (ops.size() > 1) { + status = Ydb::StatusIds::UNSUPPORTED; + error = "Mixed alter is unsupported"; + return false; + } + + const auto OpType = *ops.begin(); + + std::pair pathPair; + try { + pathPair = SplitPathIntoWorkingDirAndName(path); + } catch (const std::exception&) { + status = Ydb::StatusIds::BAD_REQUEST; + return false; + } + + const auto& workingDir = pathPair.first; + const auto& name = pathPair.second; + modifyScheme->SetWorkingDir(workingDir); + + if (OpType == EAlterOperationKind::Common) { + auto alterColumnTable = modifyScheme->MutableAlterColumnTable(); + alterColumnTable->SetName(name); + modifyScheme->SetOperationType(NKikimrSchemeOp::EOperationType::ESchemeOpAlterColumnTable); + + for (const auto& drop : req->drop_columns()) { + alterColumnTable->MutableAlterSchema()->AddDropColumns()->SetName(drop); + } + + if (!FillColumnDescription(*alterColumnTable, req->add_columns(), status, error)) { + return false; + } + + for (const auto& alter : req->alter_columns()) { + auto alterColumn = alterColumnTable->MutableAlterSchema()->AddAlterColumns(); + alterColumn->SetName(alter.Getname()); + + if (!alter.family().empty()) { + alterColumn->SetColumnFamilyName(alter.family()); + } + } + + for (const auto& add : req->add_column_families()) { + if (add.compression() == Ydb::Table::ColumnFamily::COMPRESSION_UNSPECIFIED) { + status = Ydb::StatusIds::BAD_REQUEST; + error = TStringBuilder() << "Compression value is not set for column family '" << add.name() << "'"; + } + if (!FillColumnFamily(add, alterColumnTable->MutableAlterSchema()->AddAddColumnFamily(), status, error)) { + return false; + } + } + + for (const auto& alter : req->alter_column_families()) { + if (!FillColumnFamily(alter, alterColumnTable->MutableAlterSchema()->AddAlterColumnFamily(), status, error)) { + return false; + } + } + + if (req->has_set_ttl_settings()) { + if (!FillTtlSettings(*alterColumnTable->MutableAlterTtlSettings()->MutableEnabled(), req->Getset_ttl_settings(), status, error)) { + return false; + } + } else if (req->has_drop_ttl_settings()) { + alterColumnTable->MutableAlterTtlSettings()->MutableDisabled(); + } + } + + return true; +} + +bool BuildAlterColumnTableModifyScheme( + const Ydb::Table::AlterTableRequest* req, NKikimrSchemeOp::TModifyScheme* modifyScheme, Ydb::StatusIds::StatusCode& code, TString& error) { + return BuildAlterColumnTableModifyScheme(req->path(), req, modifyScheme, code, error); +} + template void FillTableBoundaryImpl(TYdbProto& out, const NKikimrSchemeOp::TTableDescription& in, const NKikimrMiniKQL::TType& splitKeyType) { @@ -1521,4 +1624,4 @@ bool FillSequenceDescription(NKikimrSchemeOp::TSequenceDescription& out, const Y return true; } -} // namespace NKikimr +} // namespace NKikimr diff --git a/ydb/core/ydb_convert/table_description.h b/ydb/core/ydb_convert/table_description.h index aada5d1f767c..636e6461eb6b 100644 --- a/ydb/core/ydb_convert/table_description.h +++ b/ydb/core/ydb_convert/table_description.h @@ -37,6 +37,10 @@ bool BuildAlterTableModifyScheme(const TString& path, const Ydb::Table::AlterTab bool BuildAlterTableModifyScheme(const Ydb::Table::AlterTableRequest* req, NKikimrSchemeOp::TModifyScheme* modifyScheme, const TTableProfiles& profiles, const TPathId& resolvedPathId, Ydb::StatusIds::StatusCode& status, TString& error); +bool BuildAlterColumnTableModifyScheme(const TString& path, const Ydb::Table::AlterTableRequest* req, + NKikimrSchemeOp::TModifyScheme* modifyScheme, Ydb::StatusIds::StatusCode& status, TString& error); +bool BuildAlterColumnTableModifyScheme( + const Ydb::Table::AlterTableRequest* req, NKikimrSchemeOp::TModifyScheme* modifyScheme, Ydb::StatusIds::StatusCode& status, TString& error); bool FillAlterTableSettingsDesc(NKikimrSchemeOp::TTableDescription& out, const Ydb::Table::AlterTableRequest& in, const TTableProfiles& profiles, @@ -55,8 +59,10 @@ void FillColumnDescription(Ydb::Table::DescribeTableResult& out, const NKikimrSc // in bool FillColumnDescription(NKikimrSchemeOp::TTableDescription& out, const google::protobuf::RepeatedPtrField& in, Ydb::StatusIds::StatusCode& status, TString& error); -bool FillColumnDescription(NKikimrSchemeOp::TColumnTableDescription& out, - const google::protobuf::RepeatedPtrField& in, Ydb::StatusIds::StatusCode& status, TString& error); +bool FillColumnDescription(NKikimrSchemeOp::TColumnTableDescription& out, const google::protobuf::RepeatedPtrField& in, + Ydb::StatusIds::StatusCode& status, TString& error); +bool FillColumnDescription(NKikimrSchemeOp::TAlterColumnTable& out, const google::protobuf::RepeatedPtrField& in, + Ydb::StatusIds::StatusCode& status, TString& error); bool ExtractColumnTypeInfo(NScheme::TTypeInfo& outTypeInfo, TString& outTypeMod, const Ydb::Type& inType, Ydb::StatusIds::StatusCode& status, TString& error); diff --git a/ydb/core/ydb_convert/table_settings.cpp b/ydb/core/ydb_convert/table_settings.cpp index 9ee281f3f648..f28c7206facd 100644 --- a/ydb/core/ydb_convert/table_settings.cpp +++ b/ydb/core/ydb_convert/table_settings.cpp @@ -1,9 +1,11 @@ +#include "column_families.h" #include "table_description.h" #include "table_settings.h" -#include "column_families.h" #include +#include + #include #include @@ -228,10 +230,6 @@ bool FillCreateTableSettingsDesc(NKikimrSchemeOp::TTableDescription& tableDesc, } } - if (proto.tiering().size()) { - tableDesc.MutableTTLSettings()->SetUseTiering(proto.tiering()); - } - if (proto.has_storage_settings()) { TColumnFamilyManager families(tableDesc.MutablePartitionConfig()); if (!families.ApplyStorageSettings(proto.storage_settings(), &code, &error)) { @@ -391,12 +389,6 @@ bool FillAlterTableSettingsDesc(NKikimrSchemeOp::TTableDescription& tableDesc, tableDesc.MutableTTLSettings()->MutableDisabled(); } - if (proto.has_set_tiering()) { - tableDesc.MutableTTLSettings()->SetUseTiering(proto.set_tiering()); - } else if (proto.has_drop_tiering()) { - tableDesc.MutableTTLSettings()->SetUseTiering(""); - } - if (!changed && !hadPartitionConfig) { tableDesc.ClearPartitionConfig(); } @@ -449,4 +441,267 @@ bool FillIndexTablePartitioning( return true; } +namespace { + +template +TConclusionStatus FillTtlExpressionImpl(MutableDateTypeColumn&& mutable_date_type_column, + MutableValueSinceUnixEpoch&& mutable_value_since_unix_epoch, const TString& column, const NKikimrSchemeOp::TTTLSettings::EUnit unit, + const ui32 expireAfterSeconds) { + switch (unit) { + case NKikimrSchemeOp::TTTLSettings::UNIT_AUTO: { + auto* mode = mutable_date_type_column(); + mode->set_column_name(column); + mode->set_expire_after_seconds(expireAfterSeconds); + } break; + + case NKikimrSchemeOp::TTTLSettings::UNIT_SECONDS: + case NKikimrSchemeOp::TTTLSettings::UNIT_MILLISECONDS: + case NKikimrSchemeOp::TTTLSettings::UNIT_MICROSECONDS: + case NKikimrSchemeOp::TTTLSettings::UNIT_NANOSECONDS: { + auto* mode = mutable_value_since_unix_epoch(); + mode->set_column_name(column); + mode->set_column_unit(static_cast(unit)); + mode->set_expire_after_seconds(expireAfterSeconds); + } break; + + default: + return TConclusionStatus::Fail("Undefined column unit"); + } + return TConclusionStatus::Success(); +}; + +TConclusionStatus FillLegacyTtlMode( + Ydb::Table::TtlSettings& out, const TString& column, const NKikimrSchemeOp::TTTLSettings::EUnit unit, const ui32 expireAfterSeconds) { + return FillTtlExpressionImpl( + [&out]() mutable { + return out.mutable_date_type_column(); + }, + [&out]() mutable { + return out.mutable_value_since_unix_epoch(); + }, + column, unit, expireAfterSeconds); +} + +} // namespace + +template +bool FillPublicTtlSettingsImpl(Ydb::Table::TtlSettings& out, const TTtl& in, Ydb::StatusIds::StatusCode& code, TString& error) { + auto bad_request = [&code, &error](const TString& message) -> bool { + code = Ydb::StatusIds::UNSUPPORTED; + error = message; + return false; + }; + + if (!in.TiersSize()) { + // handle legacy input format for backwards-compatibility + const auto status = FillLegacyTtlMode(out, in.GetColumnName(), in.GetColumnUnit(), in.GetExpireAfterSeconds()); + if (status.IsFail()) { + return bad_request(status.GetErrorMessage()); + } + } else if (in.TiersSize() == 1 && in.GetTiers(0).HasDelete()) { + // convert delete-only TTL to legacy mode for backwards-compatibility + const auto& tier = in.GetTiers(0); + const auto status = FillLegacyTtlMode(out, in.GetColumnName(), in.GetColumnUnit(), + tier.GetApplyAfterSeconds()); + if (status.IsFail()) { + return bad_request(status.GetErrorMessage()); + } + } else { + for (const auto& inTier : in.GetTiers()) { + auto& outTier = *out.mutable_tiered_ttl()->add_tiers(); + auto exprStatus = FillTtlExpressionImpl( + [&outTier]() mutable { + return outTier.mutable_date_type_column(); + }, + [&outTier]() mutable { + return outTier.mutable_value_since_unix_epoch(); + }, + in.GetColumnName(), in.GetColumnUnit(), inTier.GetApplyAfterSeconds()); + if (exprStatus.IsFail()) { + return bad_request(exprStatus.GetErrorMessage()); + } + + switch (inTier.GetActionCase()) { + case NKikimrSchemeOp::TTTLSettings::TTier::ActionCase::kDelete: + outTier.mutable_delete_(); + break; + case NKikimrSchemeOp::TTTLSettings::TTier::ActionCase::kEvictToExternalStorage: + outTier.mutable_evict_to_external_storage()->set_storage(inTier.GetEvictToExternalStorage().GetStorage()); + break; + case NKikimrSchemeOp::TTTLSettings::TTier::ActionCase::ACTION_NOT_SET: + return bad_request("Undefined tier action"); + } + } + } + + if constexpr (std::is_same_v) { + if (in.HasSysSettings() && in.GetSysSettings().HasRunInterval()) { + out.set_run_interval_seconds(TDuration::FromValue(in.GetSysSettings().GetRunInterval()).Seconds()); + } + } + + return true; +} + +template +bool FillSchemeTtlSettingsImpl(TTtl& out, const Ydb::Table::TtlSettings& in, Ydb::StatusIds::StatusCode& code, TString& error) { + auto unsupported = [&code, &error](const TString& message) -> bool { + code = Ydb::StatusIds::UNSUPPORTED; + error = message; + return false; + }; + auto bad_request = [&code, &error](const TString& message) -> bool { + code = Ydb::StatusIds::BAD_REQUEST; + error = message; + return false; + }; + + auto setColumnUnit = [&unsupported](TTtl& out, const Ydb::Table::ValueSinceUnixEpochModeSettings::Unit unit) -> bool { +#define CASE_UNIT(type) \ + case Ydb::Table::ValueSinceUnixEpochModeSettings::type: \ + out.SetColumnUnit(NKikimrSchemeOp::TTTLSettings::type); \ + break + + switch (unit) { + CASE_UNIT(UNIT_SECONDS); + CASE_UNIT(UNIT_MILLISECONDS); + CASE_UNIT(UNIT_MICROSECONDS); + CASE_UNIT(UNIT_NANOSECONDS); + default: + return unsupported(TStringBuilder() << "Unsupported unit: " << static_cast(unit)); + } + return true; + +#undef CASE_UNIT + }; + + switch (in.mode_case()) { + case Ydb::Table::TtlSettings::kDateTypeColumn: { + const auto& mode = in.date_type_column(); + auto* tier = out.AddTiers(); + tier->MutableDelete(); + tier->SetApplyAfterSeconds(mode.expire_after_seconds()); + out.SetColumnName(mode.column_name()); + } break; + + case Ydb::Table::TtlSettings::kValueSinceUnixEpoch: { + const auto& mode = in.value_since_unix_epoch(); + auto* tier = out.AddTiers(); + tier->MutableDelete(); + tier->SetApplyAfterSeconds(mode.expire_after_seconds()); + out.SetColumnName(mode.column_name()); + if (!setColumnUnit(out, mode.column_unit())) { + return false; + } + } break; + + case Ydb::Table::TtlSettings::kTieredTtl: { + if (!in.tiered_ttl().tiers_size()) { + return bad_request("No tiers in TTL settings"); + } + + std::optional columnName; + std::optional columnUnit; + std::optional expressionType; + for (const auto& inTier : in.tiered_ttl().tiers()) { + auto* outTier = out.AddTiers(); + TStringBuf tierColumnName; + switch (inTier.expression_case()) { + case Ydb::Table::TtlTier::kDateTypeColumn: { + const auto& mode = inTier.date_type_column(); + outTier->SetApplyAfterSeconds(mode.expire_after_seconds()); + tierColumnName = mode.column_name(); + } break; + case Ydb::Table::TtlTier::kValueSinceUnixEpoch: { + const auto& mode = inTier.value_since_unix_epoch(); + outTier->SetApplyAfterSeconds(mode.expire_after_seconds()); + tierColumnName = mode.column_name(); + if (columnUnit) { + if (*columnUnit != mode.column_unit()) { + return bad_request(TStringBuilder() + << "Unit of the TTL columns must be the same for all tiers: " + << Ydb::Table::ValueSinceUnixEpochModeSettings::Unit_Name(*columnUnit) + << " != " << Ydb::Table::ValueSinceUnixEpochModeSettings::Unit_Name(mode.column_unit())); + } + } else { + columnUnit = mode.column_unit(); + } + } break; + case Ydb::Table::TtlTier::EXPRESSION_NOT_SET: + return bad_request("Tier expression is undefined"); + } + + if (columnName) { + if (*columnName != tierColumnName) { + return bad_request(TStringBuilder() << "TTL columns must be the same for all tiers: " << *columnName << " != " << tierColumnName); + } + } else { + columnName = tierColumnName; + } + + if (expressionType) { + if (*expressionType != inTier.expression_case()) { + return bad_request("Expression type must be the same for all tiers"); + } + } else { + expressionType = inTier.expression_case(); + } + + switch (inTier.action_case()) { + case Ydb::Table::TtlTier::kDelete: + outTier->MutableDelete(); + break; + case Ydb::Table::TtlTier::kEvictToExternalStorage: + outTier->MutableEvictToExternalStorage()->SetStorage(inTier.evict_to_external_storage().storage()); + break; + case Ydb::Table::TtlTier::ACTION_NOT_SET: + return bad_request("Tier action is undefined"); + } + } + + out.SetColumnName(*columnName); + if (columnUnit) { + setColumnUnit(out, *columnUnit); + } + } break; + + case Ydb::Table::TtlSettings::MODE_NOT_SET: + return bad_request("TTL mode is undefined"); + } + + std::optional expireAfterSeconds; + for (const auto& tier : out.GetTiers()) { + if (tier.HasDelete()) { + expireAfterSeconds = tier.GetApplyAfterSeconds(); + } + } + out.SetExpireAfterSeconds(expireAfterSeconds.value_or(std::numeric_limits::max())); + + return true; +} + +bool FillTtlSettings(Ydb::Table::TtlSettings& out, const NKikimrSchemeOp::TTTLSettings::TEnabled& in, Ydb::StatusIds::StatusCode& code, TString& error) { + return FillPublicTtlSettingsImpl(out, in, code, error); +} + +bool FillTtlSettings(Ydb::Table::TtlSettings& out, const NKikimrSchemeOp::TColumnDataLifeCycle::TTtl& in, Ydb::StatusIds::StatusCode& code, TString& error) { + return FillPublicTtlSettingsImpl(out, in, code, error); +} + +bool FillTtlSettings(NKikimrSchemeOp::TTTLSettings::TEnabled& out, const Ydb::Table::TtlSettings& in, Ydb::StatusIds::StatusCode& code, TString& error) { + if (!FillSchemeTtlSettingsImpl(out, in, code, error)) { + return false; + } + + if (in.run_interval_seconds()) { + out.MutableSysSettings()->SetRunInterval(TDuration::Seconds(in.run_interval_seconds()).GetValue()); + } + + return true; +} + +bool FillTtlSettings(NKikimrSchemeOp::TColumnDataLifeCycle::TTtl& out, const Ydb::Table::TtlSettings& in, Ydb::StatusIds::StatusCode& code, TString& error) { + return FillSchemeTtlSettingsImpl(out, in, code, error); +} + } // namespace NKikimr diff --git a/ydb/core/ydb_convert/table_settings.h b/ydb/core/ydb_convert/table_settings.h index 46713ceefa36..48184d2bb29f 100644 --- a/ydb/core/ydb_convert/table_settings.h +++ b/ydb/core/ydb_convert/table_settings.h @@ -1,6 +1,7 @@ #pragma once #include + #include #include @@ -18,56 +19,13 @@ bool FillAlterTableSettingsDesc(NKikimrSchemeOp::TTableDescription& out, const Ydb::Table::AlterTableRequest& in, Ydb::StatusIds::StatusCode& code, TString& error, bool changed); -template -bool FillTtlSettings(TTtlSettingsEnabled& out, const Ydb::Table::TtlSettings& in, - Ydb::StatusIds::StatusCode& code, TString& error) -{ - auto unsupported = [&code, &error](const TString& message) -> bool { - code = Ydb::StatusIds::UNSUPPORTED; - error = message; - return false; - }; - - switch (in.mode_case()) { - case Ydb::Table::TtlSettings::kDateTypeColumn: - out.SetColumnName(in.date_type_column().column_name()); - out.SetExpireAfterSeconds(in.date_type_column().expire_after_seconds()); - break; - - case Ydb::Table::TtlSettings::kValueSinceUnixEpoch: - out.SetColumnName(in.value_since_unix_epoch().column_name()); - out.SetExpireAfterSeconds(in.value_since_unix_epoch().expire_after_seconds()); - - #define CASE_UNIT(type) \ - case Ydb::Table::ValueSinceUnixEpochModeSettings::type: \ - out.SetColumnUnit(NKikimrSchemeOp::TTTLSettings::type); \ - break - - switch (in.value_since_unix_epoch().column_unit()) { - CASE_UNIT(UNIT_SECONDS); - CASE_UNIT(UNIT_MILLISECONDS); - CASE_UNIT(UNIT_MICROSECONDS); - CASE_UNIT(UNIT_NANOSECONDS); - default: - return unsupported(TStringBuilder() << "Unsupported unit: " - << static_cast(in.value_since_unix_epoch().column_unit())); - } - - #undef CASE_UNIT - break; - - default: - return unsupported("Unsupported ttl settings"); - } - - if constexpr (std::is_same_v) { - if (in.run_interval_seconds()) { - out.MutableSysSettings()->SetRunInterval(TDuration::Seconds(in.run_interval_seconds()).GetValue()); - } - } - return true; -} +// out +bool FillTtlSettings(Ydb::Table::TtlSettings& out, const NKikimrSchemeOp::TTTLSettings::TEnabled& in, Ydb::StatusIds::StatusCode& code, TString& error); +bool FillTtlSettings(Ydb::Table::TtlSettings& out, const NKikimrSchemeOp::TColumnDataLifeCycle::TTtl& in, Ydb::StatusIds::StatusCode& code, TString& error); +// in +bool FillTtlSettings(NKikimrSchemeOp::TTTLSettings::TEnabled& out, const Ydb::Table::TtlSettings& in, Ydb::StatusIds::StatusCode& code, TString& error); +bool FillTtlSettings(NKikimrSchemeOp::TColumnDataLifeCycle::TTtl& out, const Ydb::Table::TtlSettings& in, Ydb::StatusIds::StatusCode& code, TString& error); bool FillIndexTablePartitioning( NKikimrSchemeOp::TTableDescription& out, diff --git a/ydb/core/ydb_convert/ya.make b/ydb/core/ydb_convert/ya.make index 3cf7bc08e6fd..953579f43ff2 100644 --- a/ydb/core/ydb_convert/ya.make +++ b/ydb/core/ydb_convert/ya.make @@ -19,6 +19,7 @@ PEERDIR( ydb/core/util ydb/library/binary_json ydb/library/dynumber + ydb/library/conclusion ydb/library/mkql_proto/protos ydb/library/yql/minikql/dom ydb/library/yql/public/udf diff --git a/ydb/core/ydb_convert/ydb_convert.cpp b/ydb/core/ydb_convert/ydb_convert.cpp index 8fb072d8f4fe..e3575d096d34 100644 --- a/ydb/core/ydb_convert/ydb_convert.cpp +++ b/ydb/core/ydb_convert/ydb_convert.cpp @@ -491,8 +491,8 @@ Y_FORCE_INLINE void ConvertData(NUdf::TDataTypeId typeId, const Ydb::Value& valu case NUdf::TDataType::Id: { CheckTypeId(value.value_case(), Ydb::Value::kTextValue, "JsonDocument"); const auto binaryJson = NBinaryJson::SerializeToBinaryJson(value.text_value()); - if (!binaryJson.Defined()) { - throw yexception() << "Invalid JsonDocument value"; + if (binaryJson.IsFail()) { + throw yexception() << "Invalid JsonDocument value: " << binaryJson.GetErrorMessage(); } res.SetBytes(binaryJson->Data(), binaryJson->Size()); break; @@ -1217,8 +1217,8 @@ bool CellFromProtoVal(NScheme::TTypeInfo type, i32 typmod, const Ydb::Value* vp, } case NScheme::NTypeIds::JsonDocument : { const auto binaryJson = NBinaryJson::SerializeToBinaryJson(val.Gettext_value()); - if (!binaryJson.Defined()) { - err = "Invalid JSON for JsonDocument provided"; + if (binaryJson.IsFail()) { + err = "Invalid JSON for JsonDocument provided: " + binaryJson.GetErrorMessage(); return false; } const auto binaryJsonInPool = valueDataPool.AppendString(TStringBuf(binaryJson->Data(), binaryJson->Size())); diff --git a/ydb/library/accessor/validator.h b/ydb/library/accessor/validator.h index 6182b524bfa7..5602005f3f77 100644 --- a/ydb/library/accessor/validator.h +++ b/ydb/library/accessor/validator.h @@ -10,8 +10,8 @@ class TValidator { return object; } template - static T& CheckNotNull(T& object) { + static T&& CheckNotNull(T&& object) { AFL_VERIFY(!!object); - return object; + return std::forward(object); } -}; \ No newline at end of file +}; diff --git a/ydb/library/binary_json/ut/entry_ut.cpp b/ydb/library/binary_json/ut/entry_ut.cpp index d9099b0f9fdc..097707aec22a 100644 --- a/ydb/library/binary_json/ut/entry_ut.cpp +++ b/ydb/library/binary_json/ut/entry_ut.cpp @@ -17,6 +17,7 @@ class TBinaryJsonEntryTest : public TBinaryJsonTestBase { UNIT_TEST(TestGetContainer); UNIT_TEST(TestGetString); UNIT_TEST(TestGetNumber); + UNIT_TEST(TestInvalidInput); UNIT_TEST_SUITE_END(); void TestGetType() { @@ -93,6 +94,18 @@ class TBinaryJsonEntryTest : public TBinaryJsonTestBase { UNIT_ASSERT_VALUES_EQUAL(container.GetElement(0).GetNumber(), testCase.second); } } + + void TestInvalidInput() { + const TVector> testCases = { + {"nul", "N_ATOM_ERROR: Problem while parsing an atom starting with the letter 'n'"}, + }; + + for (const auto& testCase : testCases) { + const auto parsingResult = SerializeToBinaryJson(testCase.first); + UNIT_ASSERT(parsingResult.IsFail()); + UNIT_ASSERT_VALUES_EQUAL(parsingResult.GetErrorMessage(), testCase.second); + } + } }; UNIT_TEST_SUITE_REGISTRATION(TBinaryJsonEntryTest); diff --git a/ydb/library/binary_json/ut_benchmark/write.cpp b/ydb/library/binary_json/ut_benchmark/write.cpp new file mode 100644 index 000000000000..8c63fbbfdacc --- /dev/null +++ b/ydb/library/binary_json/ut_benchmark/write.cpp @@ -0,0 +1,50 @@ +#include + +#include +#include +#include +#include + +#include + +// ya test -r -D BENCHMARK_MAKE_LARGE_PART +#ifndef BENCHMARK_MAKE_LARGE_PART +#define BENCHMARK_MAKE_LARGE_PART 0 +#endif + +using namespace NKikimr::NBinaryJson; + +namespace { + +static ui64 seed = 0; + +NJson::TJsonValue GetTestJson(ui64 depth = 10, ui64 nChildren = 2) { + NJson::TJsonValue value; + if (depth == 1) { + value.SetValue(NUnitTest::RandomString(10, seed++)); + return value; + } + for (ui64 i = 0; i < nChildren; ++i) { + value.InsertValue(NUnitTest::RandomString(10, seed++), GetTestJson(depth - 1)); + } + return value; +} + +TString GetTestJsonString() { + seed = 42; + return NJson::WriteJson(GetTestJson(3, 50)); +} + +static void BenchWriteSimdJson(benchmark::State& state) { + TString value = GetTestJsonString(); + TStringBuf buf(value); + for (auto _ : state) { + auto result = SerializeToBinaryJson(buf); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); + } +} + +} + +BENCHMARK(BenchWriteSimdJson)->MinTime(1); diff --git a/ydb/library/binary_json/ut_benchmark/ya.make b/ydb/library/binary_json/ut_benchmark/ya.make new file mode 100644 index 000000000000..626cc4e426a2 --- /dev/null +++ b/ydb/library/binary_json/ut_benchmark/ya.make @@ -0,0 +1,30 @@ +G_BENCHMARK() + +TAG(ya:fat) +SIZE(LARGE) +TIMEOUT(600) + +IF (BENCHMARK_MAKE_LARGE_PART) + CFLAGS( + -DBENCHMARK_MAKE_LARGE_PART=1 + ) + TIMEOUT(1200) +ENDIF() + +SRCS( + write.cpp +) + +PEERDIR( + library/cpp/testing/unittest + ydb/library/binary_json + ydb/library/yql/minikql/dom + ydb/library/yql/minikql/invoke_builtins/llvm14 + ydb/library/yql/public/udf/service/exception_policy + ydb/library/yql/core/issue/protos + ydb/library/yql/sql/pg_dummy +) + +YQL_LAST_ABI_VERSION() + +END() diff --git a/ydb/library/binary_json/write.cpp b/ydb/library/binary_json/write.cpp index 88f6338797c0..b3583fe6b387 100644 --- a/ydb/library/binary_json/write.cpp +++ b/ydb/library/binary_json/write.cpp @@ -1,12 +1,18 @@ #include "write.h" +#include +#include +#include +#include +#include +#include +#include #include - -#include -#include -#include #include #include +#include +#include +#include #include @@ -71,41 +77,32 @@ struct TContainer { * container index instead. This is exactly how containers are stored in serialized BinaryJson (but with offsets instead of indices) */ struct TJsonIndex { - ui32 InternKey(const TStringBuf value) { + ui32 InternKey(const TStringBuf& value) { TotalKeysCount++; - const auto it = Keys.find(value); - if (it == Keys.end()) { - const ui32 currentIndex = LastFreeStringIndex++; - Keys[TString(value)] = currentIndex; + const auto [it, emplaced] = Keys.emplace(value, LastFreeStringIndex); + if (emplaced) { + ++LastFreeStringIndex; TotalKeyLength += value.length() + 1; - return currentIndex; - } else { - return it->second; } + return it->second; } - ui32 InternString(const TStringBuf value) { - const auto it = Strings.find(value); - if (it == Strings.end()) { - const ui32 currentIndex = LastFreeStringIndex++; - Strings[value] = currentIndex; + ui32 InternString(const TStringBuf& value) { + const auto [it, emplaced] = Strings.emplace(value, LastFreeStringIndex); + if (emplaced) { + ++LastFreeStringIndex; TotalStringLength += value.length() + 1; - return currentIndex; - } else { - return it->second; } + return it->second; } ui32 InternNumber(double value) { - const auto it = Numbers.find(value); - if (it == Numbers.end()) { - const ui32 currentIndex = LastFreeNumberIndex++; - Numbers[value] = currentIndex; - return currentIndex; - } else { - return it->second; + const auto [it, emplaced] = Numbers.emplace(value, LastFreeNumberIndex); + if (emplaced) { + ++LastFreeNumberIndex; } + return it->second; } void AddContainer(EContainerType type) { @@ -133,15 +130,15 @@ struct TJsonIndex { TStack ContainerIndex; TVector Containers; - TMap Keys; + TMap Keys; ui32 TotalKeyLength = 0; ui32 TotalKeysCount = 0; - THashMap Strings; + absl::flat_hash_map Strings; ui32 LastFreeStringIndex = 0; ui32 TotalStringLength = 0; - THashMap Numbers; + absl::flat_hash_map Numbers; ui32 LastFreeNumberIndex = 0; ui32 TotalEntriesCount = 0; @@ -551,20 +548,193 @@ void DomToJsonIndex(const NUdf::TUnboxedValue& value, TBinaryJsonCallbacks& call } } +template + requires std::is_same_v || std::is_same_v +[[nodiscard]] simdjson::error_code SimdJsonToJsonIndex(TOnDemandValue& value, TBinaryJsonCallbacks& callbacks) { +#define RETURN_IF_NOT_SUCCESS(expr) \ + if (const auto& status = expr; Y_UNLIKELY(status != simdjson::SUCCESS)) { \ + return status; \ + } + + switch (value.type()) { + case simdjson::ondemand::json_type::string: { + std::string_view v; + RETURN_IF_NOT_SUCCESS(value.get(v)); + callbacks.OnString(v); + break; + } + case simdjson::ondemand::json_type::boolean: { + bool v; + RETURN_IF_NOT_SUCCESS(value.get(v)); + callbacks.OnBoolean(v); + break; + } + case simdjson::ondemand::json_type::number: { + switch (value.get_number_type()) { + case simdjson::builtin::number_type::floating_point_number: { + double v; + RETURN_IF_NOT_SUCCESS(value.get(v)); + callbacks.OnDouble(v); + break; + } + case simdjson::builtin::number_type::signed_integer: { + int64_t v; + RETURN_IF_NOT_SUCCESS(value.get(v)); + callbacks.OnInteger(v); + break; + } + case simdjson::builtin::number_type::unsigned_integer: { + uint64_t v; + RETURN_IF_NOT_SUCCESS(value.get(v)); + callbacks.OnUInteger(v); + break; + } + case simdjson::builtin::number_type::big_integer: + double v; + RETURN_IF_NOT_SUCCESS(value.get(v)); + callbacks.OnDouble(v); + break; + } + break; + } + case simdjson::ondemand::json_type::null: { + auto is_null = value.is_null(); + RETURN_IF_NOT_SUCCESS(is_null.error()); + if (Y_UNLIKELY(!is_null.value_unsafe())) { + return simdjson::error_code::N_ATOM_ERROR; + } + callbacks.OnNull(); + break; + } + case simdjson::ondemand::json_type::array: { + callbacks.OnOpenArray(); + + simdjson::ondemand::array v; + RETURN_IF_NOT_SUCCESS(value.get(v)); + for (auto item : v) { + RETURN_IF_NOT_SUCCESS(item.error()); + RETURN_IF_NOT_SUCCESS(SimdJsonToJsonIndex(item.value_unsafe(), callbacks)); + } + + callbacks.OnCloseArray(); + break; + } + case simdjson::ondemand::json_type::object: { + callbacks.OnOpenMap(); + + simdjson::ondemand::object v; + RETURN_IF_NOT_SUCCESS(value.get(v)); + for (auto item : v) { + RETURN_IF_NOT_SUCCESS(item.error()); + auto& keyValue = item.value_unsafe(); + const auto key = keyValue.unescaped_key(); + RETURN_IF_NOT_SUCCESS(key.error()); + callbacks.OnMapKey(key.value_unsafe()); + RETURN_IF_NOT_SUCCESS(SimdJsonToJsonIndex(keyValue.value(), callbacks)); + } + + callbacks.OnCloseMap(); + break; + } + } + + return simdjson::SUCCESS; + +#undef RETURN_IF_NOT_SUCCESS } -TMaybe SerializeToBinaryJsonImpl(const TStringBuf json) { - TMemoryInput input(json.data(), json.size()); +// unused, left for performance comparison +[[maybe_unused]] [[nodiscard]] simdjson::error_code SimdJsonToJsonIndexImpl(const simdjson::dom::element& value, TBinaryJsonCallbacks& callbacks) { +#define RETURN_IF_NOT_SUCCESS(status) \ + if (Y_UNLIKELY(status != simdjson::SUCCESS)) { \ + return status; \ + } + + switch (value.type()) { + case simdjson::dom::element_type::STRING: { + std::string_view v; + RETURN_IF_NOT_SUCCESS(value.get(v)); + callbacks.OnString(v); + break; + } + case simdjson::dom::element_type::BOOL: { + bool v; + RETURN_IF_NOT_SUCCESS(value.get(v)); + callbacks.OnBoolean(v); + break; + } + case simdjson::dom::element_type::INT64: { + int64_t v; + RETURN_IF_NOT_SUCCESS(value.get(v)); + callbacks.OnInteger(v); + break; + } + case simdjson::dom::element_type::UINT64: { + uint64_t v; + RETURN_IF_NOT_SUCCESS(value.get(v)); + callbacks.OnUInteger(v); + break; + } + case simdjson::dom::element_type::DOUBLE: { + double v; + RETURN_IF_NOT_SUCCESS(value.get(v)); + callbacks.OnDouble(v); + break; + } + case simdjson::dom::element_type::NULL_VALUE: + callbacks.OnNull(); + break; + case simdjson::dom::element_type::ARRAY: { + callbacks.OnOpenArray(); + + simdjson::dom::array v; + RETURN_IF_NOT_SUCCESS(value.get(v)); + for (const auto& item : v) { + RETURN_IF_NOT_SUCCESS(SimdJsonToJsonIndexImpl(item, callbacks)); + } + + callbacks.OnCloseArray(); + break; + } + case simdjson::dom::element_type::OBJECT: { + callbacks.OnOpenMap(); + + simdjson::dom::object v; + RETURN_IF_NOT_SUCCESS(value.get(v)); + for (const auto& item : v) { + callbacks.OnMapKey(item.key); + RETURN_IF_NOT_SUCCESS(SimdJsonToJsonIndexImpl(item.value, callbacks)); + } + + callbacks.OnCloseMap(); + break; + } + } + return simdjson::SUCCESS; +#undef RETURN_IF_NOT_SUCCESS +} +} + +TConclusion SerializeToBinaryJsonImpl(const TStringBuf json) { TBinaryJsonCallbacks callbacks(/* throwException */ false); - if (!ReadJson(&input, &callbacks)) { - return Nothing(); + const simdjson::padded_string paddedJson(json); + simdjson::ondemand::parser parser; + try { + auto doc = parser.iterate(paddedJson); + if (auto status = doc.error(); status != simdjson::SUCCESS) { + return TConclusionStatus::Fail(simdjson::error_message(status)); + } + if (auto status = SimdJsonToJsonIndex(doc.value_unsafe(), callbacks); status != simdjson::SUCCESS) { + return TConclusionStatus::Fail(simdjson::error_message(status)); + } + } catch (const simdjson::simdjson_error& e) { + return TConclusionStatus::Fail(e.what()); } TBinaryJsonSerializer serializer(std::move(callbacks).GetResult()); return std::move(serializer).Serialize(); - } -TMaybe SerializeToBinaryJson(const TStringBuf json) { +TConclusion SerializeToBinaryJson(const TStringBuf json) { return SerializeToBinaryJsonImpl(json); } diff --git a/ydb/library/binary_json/write.h b/ydb/library/binary_json/write.h index f1d4dad7cdc1..814f4a549d56 100644 --- a/ydb/library/binary_json/write.h +++ b/ydb/library/binary_json/write.h @@ -2,6 +2,7 @@ #include "format.h" +#include #include #include @@ -11,7 +12,7 @@ namespace NKikimr::NBinaryJson { /** * @brief Translates textual JSON into BinaryJson */ -TMaybe SerializeToBinaryJson(const TStringBuf json); +TConclusion SerializeToBinaryJson(const TStringBuf json); /** * @brief Translates DOM layout from `yql/library/dom` library into BinaryJson diff --git a/ydb/library/binary_json/ya.make b/ydb/library/binary_json/ya.make index 93b3032fd223..d287346c9895 100644 --- a/ydb/library/binary_json/ya.make +++ b/ydb/library/binary_json/ya.make @@ -7,8 +7,11 @@ YQL_ABI_VERSION( ) PEERDIR( + library/cpp/containers/absl_flat_hash library/cpp/json + ydb/library/conclusion ydb/library/yql/minikql/dom + contrib/libs/simdjson ) SRCS( @@ -19,8 +22,13 @@ SRCS( GENERATE_ENUM_SERIALIZATION(format.h) +CFLAGS( + -Wno-assume +) + END() RECURSE_FOR_TESTS( ut + ut_benchmark ) diff --git a/ydb/library/conclusion/generic/result.h b/ydb/library/conclusion/generic/result.h new file mode 100644 index 000000000000..b0d93d3a404d --- /dev/null +++ b/ydb/library/conclusion/generic/result.h @@ -0,0 +1,112 @@ +#pragma once +#include +#include + +#include +#include + +namespace NKikimr { + +template +class TConclusionImpl { +private: + std::variant Result; + +public: + TConclusionImpl(TStatus&& status) + : Result(std::move(status)) { + auto* resStatus = std::get_if(&Result); + Y_ABORT_UNLESS(resStatus->IsFail()); + } + + bool IsFail() const { + return std::holds_alternative(Result); + } + + bool IsSuccess() const { + return std::holds_alternative(Result); + } + + TConclusionImpl(const TStatus& status) + : Result(status) { + Y_ABORT_UNLESS(IsFail()); + } + + template + TConclusionImpl(TResultArg&& result) + : Result(std::move(result)) { + } + + template + TConclusionImpl(const TResultArg& result) + : Result(result) { + } + + template + TConclusionImpl(TResultArg& result) + : Result(result) { + } + + const TStatus& GetError() const { + auto result = std::get_if(&Result); + Y_ABORT_UNLESS(result, "incorrect object for error request"); + return *result; + } + + const TResult& GetResult() const { + auto result = std::get_if(&Result); + Y_ABORT_UNLESS(result, "incorrect object for result request"); + return *result; + } + + TResult& MutableResult() { + auto result = std::get_if(&Result); + Y_ABORT_UNLESS(result, "incorrect object for result request"); + return *result; + } + + TResult&& DetachResult() { + auto result = std::get_if(&Result); + Y_ABORT_UNLESS(result, "incorrect object for result request: %s", GetErrorMessage().data()); + return std::move(*result); + } + + const TResult* operator->() const { + return &GetResult(); + } + + TResult* operator->() { + return &MutableResult(); + } + + const TResult& operator*() const { + return GetResult(); + } + + bool operator!() const { + return IsFail(); + } + + operator TStatus() const { + return GetError(); + } + + const TString& GetErrorMessage() const { + auto* status = std::get_if(&Result); + if (!status) { + return Default(); + } else { + return status->GetErrorMessage(); + } + } + + auto GetStatus() const { + auto* status = std::get_if(&Result); + if (!status) { + return TStatus::Success().GetStatus(); + } else { + return status->GetStatus(); + } + } +}; +} diff --git a/ydb/library/conclusion/generic/status.h b/ydb/library/conclusion/generic/status.h new file mode 100644 index 000000000000..26be88712b50 --- /dev/null +++ b/ydb/library/conclusion/generic/status.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include + +#include + +namespace NKikimr { + +template +class TConclusionStatusImpl { +private: + std::optional ErrorMessage; + TStatus Status = StatusOk; + TConclusionStatusImpl() = default; + TConclusionStatusImpl(const TString& errorMessage, TStatus status = DefaultError) + : ErrorMessage(errorMessage) + , Status(status) { + Y_ABORT_UNLESS(!!ErrorMessage); + } + + TConclusionStatusImpl(const char* errorMessage, TStatus status = DefaultError) + : ErrorMessage(errorMessage) + , Status(status) { + Y_ABORT_UNLESS(!!ErrorMessage); + } + + TConclusionStatusImpl(const std::string& errorMessage, TStatus status = DefaultError) + : ErrorMessage(TString(errorMessage.data(), errorMessage.size())) + , Status(status) { + Y_ABORT_UNLESS(!!ErrorMessage); + } + +public: + void Validate(const TString& processInfo = Default()) const { + if (processInfo) { + Y_ABORT_UNLESS(Ok(), "error=%s, processInfo=%s", GetErrorMessage().c_str(), processInfo.c_str()); + } else { + Y_ABORT_UNLESS(Ok(), "error=%s", GetErrorMessage().c_str()); + } + } + + [[nodiscard]] const TString& GetErrorMessage() const { + return ErrorMessage ? *ErrorMessage : Default(); + } + + [[nodiscard]] TStatus GetStatus() const { + return Status; + } + + [[nodiscard]] static TConclusionStatusImpl Fail(const char* errorMessage) { + return TConclusionStatusImpl(errorMessage); + } + + [[nodiscard]] static TConclusionStatusImpl Fail(const TString& errorMessage) { + return TConclusionStatusImpl(errorMessage); + } + + [[nodiscard]] static TConclusionStatusImpl Fail(const std::string& errorMessage) { + return TConclusionStatusImpl(errorMessage); + } + + [[nodiscard]] static TConclusionStatusImpl Fail(const TStatus& status, const char* errorMessage) { + Y_ABORT_UNLESS(DefaultError == StatusOk || status != StatusOk); + return TConclusionStatusImpl(errorMessage, status); + } + + [[nodiscard]] static TConclusionStatusImpl Fail(const TStatus& status, const TString& errorMessage) { + Y_ABORT_UNLESS(DefaultError == StatusOk || status != StatusOk); + return TConclusionStatusImpl(errorMessage, status); + } + + [[nodiscard]] bool IsFail() const { + return !Ok(); + } + + [[nodiscard]] bool IsSuccess() const { + return Ok(); + } + + [[nodiscard]] bool Ok() const { + return !ErrorMessage; + } + + [[nodiscard]] bool operator!() const { + return !!ErrorMessage; + } + + [[nodiscard]] static TConclusionStatusImpl Success() { + return TConclusionStatusImpl(); + } +}; + +} // namespace NKikimr diff --git a/ydb/library/conclusion/generic/ya.make b/ydb/library/conclusion/generic/ya.make new file mode 100644 index 000000000000..1e614b2bfb5c --- /dev/null +++ b/ydb/library/conclusion/generic/ya.make @@ -0,0 +1,9 @@ +LIBRARY() + +SRCS() + +PEERDIR( + util +) + +END() diff --git a/ydb/library/conclusion/result.h b/ydb/library/conclusion/result.h index 3e0cde0c7da2..2839bb18cd22 100644 --- a/ydb/library/conclusion/result.h +++ b/ydb/library/conclusion/result.h @@ -1,111 +1,11 @@ #pragma once #include "status.h" -#include -#include -namespace NKikimr { - -template -class TConclusion { -private: - std::variant Result; -public: - - TConclusion(TConclusionStatus&& status) - : Result(std::move(status)) { - auto* resStatus = std::get_if(&Result); - Y_ABORT_UNLESS(resStatus->IsFail()); - } - - bool IsFail() const { - return std::get_if(&Result); - } - - bool IsSuccess() const { - return std::get_if(&Result); - } - - TConclusion(const TConclusionStatus& status) - : Result(status) { - Y_ABORT_UNLESS(IsFail()); - } - - template - TConclusion(TResultArg&& result) - : Result(std::move(result)) { - } - - template - TConclusion(const TResultArg& result) - : Result(result) { - } - - template - TConclusion(TResultArg& result) - : Result(result) { - } - - const TConclusionStatus& GetError() const { - auto result = std::get_if(&Result); - Y_ABORT_UNLESS(result, "incorrect object for error request"); - return *result; - } +#include - const TResult& GetResult() const { - auto result = std::get_if(&Result); - Y_ABORT_UNLESS(result, "incorrect object for result request"); - return *result; - } - - TResult& MutableResult() { - auto result = std::get_if(&Result); - Y_ABORT_UNLESS(result, "incorrect object for result request"); - return *result; - } - - TResult&& DetachResult() { - auto result = std::get_if(&Result); - Y_ABORT_UNLESS(result, "incorrect object for result request: %s", GetErrorMessage().data()); - return std::move(*result); - } - - const TResult* operator->() const { - return &GetResult(); - } - - TResult* operator->() { - return &MutableResult(); - } - - const TResult& operator*() const { - return GetResult(); - } - - bool operator!() const { - return IsFail(); - } - - operator TConclusionStatus() const { - return GetError(); - } - - const TString& GetErrorMessage() const { - auto* status = std::get_if(&Result); - if (!status) { - return Default(); - } else { - return status->GetErrorMessage(); - } - } +namespace NKikimr { - Ydb::StatusIds::StatusCode GetStatus() const { - auto* status = std::get_if(&Result); - if (!status) { - return Ydb::StatusIds::SUCCESS; - } else { - return status->GetStatus(); - } - } -}; +template +using TConclusion = TConclusionImpl; -} +} // namespace NKikimr diff --git a/ydb/library/conclusion/status.cpp b/ydb/library/conclusion/status.cpp index bc355d9ff02b..e946a122914b 100644 --- a/ydb/library/conclusion/status.cpp +++ b/ydb/library/conclusion/status.cpp @@ -1,14 +1,5 @@ #include "status.h" -#include namespace NKikimr { -void TConclusionStatus::Validate(const TString& processInfo) const { - if (processInfo) { - AFL_VERIFY(Ok())("problem", GetErrorMessage())("process_info", processInfo); - } else { - AFL_VERIFY(Ok())("problem", GetErrorMessage()); - } -} - } diff --git a/ydb/library/conclusion/status.h b/ydb/library/conclusion/status.h index 8af77479de13..b6a0830cf7a6 100644 --- a/ydb/library/conclusion/status.h +++ b/ydb/library/conclusion/status.h @@ -1,145 +1,11 @@ #pragma once -#include - -#include -#include +#include namespace NKikimr { -class TConclusionStatus { -private: - std::optional ErrorMessage; - Ydb::StatusIds::StatusCode Status = Ydb::StatusIds::SUCCESS; - TConclusionStatus() = default; - TConclusionStatus(const TString& errorMessage, Ydb::StatusIds::StatusCode status = Ydb::StatusIds::INTERNAL_ERROR) - : ErrorMessage(errorMessage) - , Status(status) - { - Y_ABORT_UNLESS(!!ErrorMessage); - } - - TConclusionStatus(const char* errorMessage, Ydb::StatusIds::StatusCode status = Ydb::StatusIds::INTERNAL_ERROR) - : ErrorMessage(errorMessage) - , Status(status) { - Y_ABORT_UNLESS(!!ErrorMessage); - } - - TConclusionStatus(const std::string& errorMessage, Ydb::StatusIds::StatusCode status = Ydb::StatusIds::INTERNAL_ERROR) - : ErrorMessage(TString(errorMessage.data(), errorMessage.size())) - , Status(status) { - Y_ABORT_UNLESS(!!ErrorMessage); - } -public: - void Validate(const TString& processInfo = Default()) const; - - [[nodiscard]] const TString& GetErrorMessage() const { - return ErrorMessage ? *ErrorMessage : Default(); - } - - [[nodiscard]] Ydb::StatusIds::StatusCode GetStatus() const { - return Status; - } - - [[nodiscard]] static TConclusionStatus Fail(const char* errorMessage) { - return TConclusionStatus(errorMessage); - } - - [[nodiscard]] static TConclusionStatus Fail(const TString& errorMessage) { - return TConclusionStatus(errorMessage); - } - - [[nodiscard]] static TConclusionStatus Fail(const std::string& errorMessage) { - return TConclusionStatus(errorMessage); - } - - [[nodiscard]] bool IsFail() const { - return !Ok(); - } - - [[nodiscard]] bool IsSuccess() const { - return Ok(); - } - - [[nodiscard]] bool Ok() const { - return !ErrorMessage; - } - - [[nodiscard]] bool operator!() const { - return !!ErrorMessage; - } - - [[nodiscard]] static TConclusionStatus Success() { - return TConclusionStatus(); - } -}; - -template -class TConclusionSpecialStatus { -private: - std::optional ErrorMessage; - TStatus SpecialStatus = StatusOk; - - TConclusionSpecialStatus() = default; - TConclusionSpecialStatus(const TStatus& status, const std::optional& errorMessage = {}) - : ErrorMessage(errorMessage) - , SpecialStatus(status) - { - Y_ABORT_UNLESS(!!ErrorMessage); - } - - TConclusionSpecialStatus(const TStatus& status,const char* errorMessage) - : ErrorMessage(errorMessage) - , SpecialStatus(status) - { - Y_ABORT_UNLESS(!!ErrorMessage); - } -public: - - const TString& GetErrorMessage() const { - return ErrorMessage ? *ErrorMessage : Default(); - } - - static TConclusionSpecialStatus Fail(const char* errorMessage) { - return Fail(DefaultError, errorMessage); - } - - static TConclusionSpecialStatus Fail(const TString& errorMessage) { - return Fail(DefaultError, errorMessage); - } - - static TConclusionSpecialStatus Fail(const TStatus& status, const char* errorMessage) { - Y_ABORT_UNLESS(status != StatusOk); - return TConclusionSpecialStatus(status, errorMessage); - } - - static TConclusionSpecialStatus Fail(const TStatus& status, const TString& errorMessage) { - Y_ABORT_UNLESS(status != StatusOk); - return TConclusionSpecialStatus(status, errorMessage); - } - - const TStatus& GetStatus() const { - return SpecialStatus; - } - - bool IsFail() const { - return !Ok(); - } - - bool Ok() const { - return SpecialStatus == StatusOk; - } - - bool operator!() const { - return !Ok(); - } - - explicit operator bool() const { - return Ok(); - } +using TConclusionStatus = TConclusionStatusImpl<::TNull, ::TNull{}, ::TNull{}>; - static TConclusionSpecialStatus Success() { - return TConclusionSpecialStatus(); - } -}; +template +using TConclusionSpecialStatus = TConclusionStatusImpl; } diff --git a/ydb/library/conclusion/ya.make b/ydb/library/conclusion/ya.make index e6e350a5a55a..41cf1944ba17 100644 --- a/ydb/library/conclusion/ya.make +++ b/ydb/library/conclusion/ya.make @@ -6,8 +6,8 @@ SRCS( ) PEERDIR( - ydb/public/api/protos - ydb/library/actors/core + util + ydb/library/conclusion/generic ) END() diff --git a/ydb/library/formats/arrow/accessor/abstract/accessor.h b/ydb/library/formats/arrow/accessor/abstract/accessor.h index 934ca2fddea4..6507201cb895 100644 --- a/ydb/library/formats/arrow/accessor/abstract/accessor.h +++ b/ydb/library/formats/arrow/accessor/abstract/accessor.h @@ -253,6 +253,8 @@ class IChunkedArray { if (position < chunkCurrent->GetFinishPosition()) { return accessor.OnArray( chunkCurrent->GetChunkIndex(), chunkCurrent->GetStartPosition()); + } else if (position == chunkCurrent->GetFinishPosition() && chunkCurrent->GetChunkIndex() + 1 < accessor.GetChunksCount()) { + return accessor.OnArray(chunkCurrent->GetChunkIndex() + 1, position); } AFL_VERIFY(chunkCurrent->GetChunkIndex() < accessor.GetChunksCount()); startIndex = chunkCurrent->GetChunkIndex(); @@ -267,6 +269,11 @@ class IChunkedArray { } } else { AFL_VERIFY(chunkCurrent->GetChunkIndex() > 0); + if (position + 1 == chunkCurrent->GetStartPosition()) { + const ui32 chunkIndex = chunkCurrent->GetChunkIndex() - 1; + return accessor.OnArray(chunkIndex, chunkCurrent->GetStartPosition() - accessor.GetChunkLength(chunkIndex)); + } + ui64 idx = chunkCurrent->GetStartPosition(); for (i32 i = chunkCurrent->GetChunkIndex() - 1; i >= 0; --i) { AFL_VERIFY(idx >= accessor.GetChunkLength(i))("idx", idx)("length", accessor.GetChunkLength(i)); diff --git a/ydb/library/formats/arrow/accessor/abstract/ya.make b/ydb/library/formats/arrow/accessor/abstract/ya.make index c3ebb89dace4..9aa7e7fda3f8 100644 --- a/ydb/library/formats/arrow/accessor/abstract/ya.make +++ b/ydb/library/formats/arrow/accessor/abstract/ya.make @@ -5,6 +5,7 @@ PEERDIR( ydb/library/formats/arrow/accessor/common contrib/libs/apache/arrow ydb/library/conclusion + ydb/library/actors/core ) SRCS( diff --git a/ydb/library/formats/arrow/arrow_helpers.cpp b/ydb/library/formats/arrow/arrow_helpers.cpp index d27b18af5bc9..294f8c4931eb 100644 --- a/ydb/library/formats/arrow/arrow_helpers.cpp +++ b/ydb/library/formats/arrow/arrow_helpers.cpp @@ -237,12 +237,18 @@ std::vector ColumnNames(const std::shared_ptr& schema) { return out; } -std::shared_ptr MakeUI64Array(ui64 value, i64 size) { +std::shared_ptr MakeUI64Array(const ui64 value, const i64 size) { auto res = arrow::MakeArrayFromScalar(arrow::UInt64Scalar(value), size); Y_ABORT_UNLESS(res.ok()); return std::static_pointer_cast(*res); } +std::shared_ptr MakeStringArray(const TString& value, const i64 size) { + auto res = arrow::MakeArrayFromScalar(arrow::StringScalar(value), size); + Y_ABORT_UNLESS(res.ok()); + return std::static_pointer_cast(*res); +} + std::pair FindMinMaxPosition(const std::shared_ptr& array) { if (array->length() == 0) { return {-1, -1}; diff --git a/ydb/library/formats/arrow/arrow_helpers.h b/ydb/library/formats/arrow/arrow_helpers.h index 8bceee2d836e..43d49dc1481f 100644 --- a/ydb/library/formats/arrow/arrow_helpers.h +++ b/ydb/library/formats/arrow/arrow_helpers.h @@ -57,7 +57,8 @@ std::vector> MakeBuilders(const std::shared size_t reserve = 0, const std::map& sizeByColumn = {}); std::vector> Finish(std::vector>&& builders); -std::shared_ptr MakeUI64Array(ui64 value, i64 size); +std::shared_ptr MakeUI64Array(const ui64 value, const i64 size); +std::shared_ptr MakeStringArray(const TString& value, const i64 size); std::vector ColumnNames(const std::shared_ptr& schema); bool ReserveData(arrow::ArrayBuilder& builder, const size_t size); bool MergeBatchColumns(const std::vector>& batches, std::shared_ptr& result, const std::vector& columnsOrder = {}, const bool orderFieldsAreNecessary = true); diff --git a/ydb/library/formats/arrow/common/iterator.cpp b/ydb/library/formats/arrow/common/iterator.cpp new file mode 100644 index 000000000000..d2ae67542269 --- /dev/null +++ b/ydb/library/formats/arrow/common/iterator.cpp @@ -0,0 +1,3 @@ +#include "iterator.h" + +namespace NKikimr::NArrow::NUtil {} diff --git a/ydb/library/formats/arrow/common/iterator.h b/ydb/library/formats/arrow/common/iterator.h new file mode 100644 index 000000000000..9fee8c3441cc --- /dev/null +++ b/ydb/library/formats/arrow/common/iterator.h @@ -0,0 +1,84 @@ +#pragma once + +namespace NKikimr::NArrow::NUtil { + +template +class TRandomAccessIteratorClone { +private: + TBase Base; + +public: + using iterator_category = TBase::iterator_category; + using difference_type = TBase::difference_type; + using value_type = TBase::value_type; + using pointer = TBase::pointer; + using reference = TBase::reference; + + TRandomAccessIteratorClone() = default; + TRandomAccessIteratorClone(const TBase& base) + : Base(base) { + } + + bool operator==(const TDerived& other) const { + return Base == other.Base; + } + bool operator!=(const TDerived& other) const { + return Base != other.Base; + } + + TDerived& operator+=(const difference_type& diff) { + Base += diff; + return *static_cast(this); + } + TDerived& operator-=(const difference_type& diff) { + Base -= diff; + return *static_cast(this); + } + TDerived& operator++() { + ++Base; + return *static_cast(this); + } + TDerived& operator--() { + --Base; + return *static_cast(this); + } + TDerived operator++(int) { + auto ret = *static_cast(this); + ++Base; + return ret; + } + TDerived operator--(int) { + auto ret = *static_cast(this); + --Base; + return ret; + } + TDerived operator+(const difference_type& diff) { + return Base + diff; + } + TDerived operator-(const difference_type& diff) { + return Base - diff; + } + + difference_type operator-(const TDerived& other) { + return Base - other.Base; + } + + reference operator*() { + return *Base; + } + const reference operator*() const { + return *Base; + } + pointer operator->() { + return &*Base; + } + + pointer getPtr() const { + return Base.getPtr(); + } + const pointer getConstPtr() const { + return Base.getConstPtr(); + } +}; + +} // namespace NKikimr::NArrow::NUtil diff --git a/ydb/library/formats/arrow/hash/xx_hash.cpp b/ydb/library/formats/arrow/hash/xx_hash.cpp index bc69a160f535..d6b7b654857c 100644 --- a/ydb/library/formats/arrow/hash/xx_hash.cpp +++ b/ydb/library/formats/arrow/hash/xx_hash.cpp @@ -2,6 +2,18 @@ namespace NKikimr::NArrow::NHash::NXX64 { +void TStreamStringHashCalcer_H3::Start() { + XXH3_64bits_reset_withSeed(&HashState, Seed); +} + +void TStreamStringHashCalcer_H3::Update(const ui8* data, const ui32 size) { + XXH3_64bits_update(&HashState, data, size); +} + +ui64 TStreamStringHashCalcer_H3::Finish() { + return XXH3_64bits_digest(&HashState); +} + void TStreamStringHashCalcer::Start() { XXH64_reset(&HashState, Seed); } diff --git a/ydb/library/formats/arrow/hash/xx_hash.h b/ydb/library/formats/arrow/hash/xx_hash.h index 25903f47c6d5..d0cfed68b549 100644 --- a/ydb/library/formats/arrow/hash/xx_hash.h +++ b/ydb/library/formats/arrow/hash/xx_hash.h @@ -21,4 +21,18 @@ class TStreamStringHashCalcer { ui64 Finish(); }; +class TStreamStringHashCalcer_H3 { +private: + const ui64 Seed; + XXH3_state_t HashState; +public: + TStreamStringHashCalcer_H3(const ui64 seed) + : Seed(seed) { + } + + void Start(); + void Update(const ui8* data, const ui32 size); + ui64 Finish(); +}; + } diff --git a/ydb/library/formats/arrow/modifier/schema.h b/ydb/library/formats/arrow/modifier/schema.h index 1d90167c0979..7dc06a40626e 100644 --- a/ydb/library/formats/arrow/modifier/schema.h +++ b/ydb/library/formats/arrow/modifier/schema.h @@ -1,7 +1,9 @@ #pragma once #include +#include #include #include +#include #include #include @@ -14,9 +16,16 @@ class TSchemaLite { public: TSchemaLite() = default; - TSchemaLite(const std::shared_ptr& schema) { - AFL_VERIFY(schema); - Fields = schema->fields(); + TSchemaLite(const std::shared_ptr& schema) + : Fields(TValidator::CheckNotNull(schema)->fields()) { + } + + TSchemaLite(std::vector>&& fields) + : Fields(std::move(fields)) { + } + + TSchemaLite(const std::vector>& fields) + : Fields(fields) { } const std::shared_ptr& field(const ui32 index) const { @@ -78,14 +87,80 @@ class TSchemaLite { } return Default>(); } +}; - TSchemaLite(std::vector>&& fields) - : Fields(std::move(fields)) { +class TSchemaLiteView: private TNonCopyable { +private: + using TFields = std::span>; + TFields Fields; + + class TIterator: public NUtil::TRandomAccessIteratorClone { + using TBase = NUtil::TRandomAccessIteratorClone; + public: + using TBase::TRandomAccessIteratorClone; + }; + +public: + TSchemaLiteView() = default; + TSchemaLiteView(const TSchemaLite& schema) + : Fields(schema.fields()) { } - TSchemaLite(const std::vector>& fields) + TSchemaLiteView(const std::span>& fields) : Fields(fields) { } + + std::shared_ptr field(const ui32 index) const { + return GetFieldByIndexVerified(index); + } + + TIterator begin() const { + return Fields.begin(); + } + + TIterator end() const { + return Fields.end(); + } + + int num_fields() const { + return Fields.size(); + } + + std::vector field_names() const { + std::vector result; + result.reserve(Fields.size()); + for (auto&& f : Fields) { + result.emplace_back(f->name()); + } + return result; + } + + TString DebugString() const { + TStringBuilder sb; + sb << "["; + for (auto&& f : Fields) { + sb << f->ToString() << ";"; + } + sb << "]"; + + return sb; + } + + TString ToString() const { + return DebugString(); + } + + const std::shared_ptr& GetFieldByIndexVerified(const ui32 index) const { + AFL_VERIFY(index < Fields.size()); + return Fields[index]; + } + + const std::shared_ptr& GetFieldByIndexOptional(const ui32 index) const { + if (index < Fields.size()) { + return Fields[index]; + } + return Default>(); + } }; } // namespace NKikimr::NArrow diff --git a/ydb/library/formats/arrow/modifier/subset.h b/ydb/library/formats/arrow/modifier/subset.h index 23430af5524f..49ef50500fcf 100644 --- a/ydb/library/formats/arrow/modifier/subset.h +++ b/ydb/library/formats/arrow/modifier/subset.h @@ -1,7 +1,9 @@ #pragma once -#include -#include #include +#include +#include + +#include namespace NKikimr::NArrow { @@ -14,29 +16,35 @@ class TSchemaSubset { TSchemaSubset() = default; TSchemaSubset(const std::set& fieldsIdx, const ui32 fieldsCount); + ui64 GetTxVolume() const { + return FieldBits.size() + FieldIdx.size() * sizeof(ui32) + 1; + } + static TSchemaSubset AllFieldsAccepted() { TSchemaSubset result; result.Exclude = true; return result; } - template - std::vector Apply(const std::vector& fullSchema) const { + template + std::vector::value_type> Apply(TIterator begin, TIterator end) const { + using TValue = std::iterator_traits::value_type; if (FieldIdx.empty()) { - return fullSchema; + return {std::move(begin), std::move(end)}; } - std::vector fields; + std::vector fields; + const ui64 size = end - begin; if (!Exclude) { for (auto&& i : FieldIdx) { - AFL_VERIFY(i < fullSchema.size()); - fields.emplace_back(fullSchema[i]); + AFL_VERIFY(i < size); + fields.emplace_back(*(begin + i)); } } else { auto it = FieldIdx.begin(); - for (ui32 i = 0; i < fullSchema.size(); ++i) { + for (ui32 i = 0; i < size; ++i) { if (it == FieldIdx.end() || i < *it) { - AFL_VERIFY(i < fullSchema.size()); - fields.emplace_back(fullSchema[i]); + AFL_VERIFY(i < size); + fields.emplace_back(*(begin + i)); } else if (i == *it) { ++it; } else { diff --git a/ydb/library/formats/arrow/protos/ssa.proto b/ydb/library/formats/arrow/protos/ssa.proto index 193c759a3a80..38a0bb14805b 100644 --- a/ydb/library/formats/arrow/protos/ssa.proto +++ b/ydb/library/formats/arrow/protos/ssa.proto @@ -45,6 +45,10 @@ message TProgram { repeated uint64 HashValues = 1; } + message TBloomNGrammFilterChecker { + repeated uint64 HashValues = 1; + } + message TCountMinSketchChecker { } @@ -60,6 +64,7 @@ message TProgram { TBloomFilterChecker BloomFilter = 40; TCompositeChecker Composite = 41; TCountMinSketchChecker CountMinSketch = 42; + TBloomNGrammFilterChecker BloomNGrammFilter = 43; } } diff --git a/ydb/library/formats/arrow/replace_key.h b/ydb/library/formats/arrow/replace_key.h index 1dda69cec323..159915f944a5 100644 --- a/ydb/library/formats/arrow/replace_key.h +++ b/ydb/library/formats/arrow/replace_key.h @@ -50,6 +50,19 @@ class TReplaceKeyTemplate { return sb; } + NJson::TJsonValue DebugJson() const { + NJson::TJsonValue result = NJson::JSON_ARRAY; + for (auto&& i : *Columns) { + auto res = i->GetScalar(Position); + if (!res.ok()) { + result.AppendValue(res.status().ToString()); + } else { + result.AppendValue((*res)->ToString()); + } + } + return result; + } + TReplaceKeyTemplate(TArrayVecPtr columns, const ui64 position) : Columns(columns) , Position(position) diff --git a/ydb/library/mkql_proto/mkql_proto.cpp b/ydb/library/mkql_proto/mkql_proto.cpp index 3f986048a222..0eee18e052ac 100644 --- a/ydb/library/mkql_proto/mkql_proto.cpp +++ b/ydb/library/mkql_proto/mkql_proto.cpp @@ -1601,7 +1601,7 @@ Y_FORCE_INLINE NUdf::TUnboxedValue KindDataImport(const TType* type, const Ydb:: case NUdf::TDataType::Id: { CheckTypeId(value.value_case(), Ydb::Value::kTextValue, "JsonDocument"); const auto binaryJson = NBinaryJson::SerializeToBinaryJson(value.text_value()); - if (!binaryJson.Defined()) { + if (binaryJson.IsFail()) { throw yexception() << "Invalid JsonDocument value"; } return MakeString(TStringBuf(binaryJson->Data(), binaryJson->Size())); diff --git a/ydb/library/services/services.proto b/ydb/library/services/services.proto index 6a7dfe033522..5e0279aea3db 100644 --- a/ydb/library/services/services.proto +++ b/ydb/library/services/services.proto @@ -297,6 +297,12 @@ enum EServiceKikimr { S3_WRAPPER = 803; CONTINUOUS_BACKUP = 804; + TX_COLUMNSHARD_ACTUALIZATION = 850; + TX_COLUMNSHARD_COMPACTION = 851; + TX_COLUMNSHARD_WRITE = 852; + TX_COLUMNSHARD_TX = 853; + TX_COLUMNSHARD_RESTORE = 854; + // System views SYSTEM_VIEWS = 900; @@ -396,6 +402,10 @@ enum EServiceKikimr { BS_REQUEST_COST = 2500; GROUPED_MEMORY_LIMITER = 2700; + + DATA_INTEGRITY = 3000; + + TX_PRIORITIES_QUEUE = 3100; }; message TActivity { diff --git a/ydb/library/yql/core/issue/protos/issue_id.proto b/ydb/library/yql/core/issue/protos/issue_id.proto index 5d8812c6402c..f1854ee90f48 100644 --- a/ydb/library/yql/core/issue/protos/issue_id.proto +++ b/ydb/library/yql/core/issue/protos/issue_id.proto @@ -75,6 +75,9 @@ message TIssuesIds { KIKIMR_UNSUPPORTED = 2030; KIKIMR_BAD_COLUMN_TYPE = 2031; KIKIMR_NO_COLUMN_DEFAULT_VALUE = 2032; + KIKIMR_DISK_SPACE_EXHAUSTED = 2033; + KIKIMR_SCHEMA_CHANGED = 2034; + KIKIMR_INTERNAL_ERROR = 2035; // kikimr warnings KIKIMR_READ_MODIFIED_TABLE = 2500; diff --git a/ydb/library/yql/core/issue/yql_issue.txt b/ydb/library/yql/core/issue/yql_issue.txt index d9ecca2db9e6..22e798c371aa 100644 --- a/ydb/library/yql/core/issue/yql_issue.txt +++ b/ydb/library/yql/core/issue/yql_issue.txt @@ -291,6 +291,18 @@ ids { code: KIKIMR_NO_COLUMN_DEFAULT_VALUE severity: S_ERROR } +ids { + code: KIKIMR_DISK_SPACE_EXHAUSTED + severity: S_ERROR +} +ids { + code: KIKIMR_SCHEMA_CHANGED + severity: S_ERROR +} +ids { + code: KIKIMR_INTERNAL_ERROR + severity: S_ERROR +} ids { code: YQL_PRAGMA_WARNING_MSG severity: S_WARNING diff --git a/ydb/library/yql/minikql/invoke_builtins/mkql_builtins_convert.cpp b/ydb/library/yql/minikql/invoke_builtins/mkql_builtins_convert.cpp index 909526e21493..6cbf8efe5c7f 100644 --- a/ydb/library/yql/minikql/invoke_builtins/mkql_builtins_convert.cpp +++ b/ydb/library/yql/minikql/invoke_builtins/mkql_builtins_convert.cpp @@ -573,7 +573,7 @@ struct TStringConvert { NUdf::TUnboxedValuePod JsonToJsonDocument(const NUdf::TUnboxedValuePod value) { auto binaryJson = NKikimr::NBinaryJson::SerializeToBinaryJson(value.AsStringRef()); - if (!binaryJson.Defined()) { + if (!binaryJson.IsSuccess()) { // JSON parse error happened, return NULL return NUdf::TUnboxedValuePod(); } diff --git a/ydb/library/yql/minikql/invoke_builtins/ya.make b/ydb/library/yql/minikql/invoke_builtins/ya.make index fa7a96b61a0f..846c6063f750 100644 --- a/ydb/library/yql/minikql/invoke_builtins/ya.make +++ b/ydb/library/yql/minikql/invoke_builtins/ya.make @@ -3,8 +3,7 @@ LIBRARY() SRCS( ) -PEERDIR( -) +PEERDIR() YQL_LAST_ABI_VERSION() diff --git a/ydb/library/yql/minikql/jsonpath/ut/common_ut.cpp b/ydb/library/yql/minikql/jsonpath/ut/common_ut.cpp index 087759f769b5..a32389a76898 100644 --- a/ydb/library/yql/minikql/jsonpath/ut/common_ut.cpp +++ b/ydb/library/yql/minikql/jsonpath/ut/common_ut.cpp @@ -339,7 +339,7 @@ class TJsonPathCommonTest : public TJsonPathTestBase { "array": [1, 2, 3, 4] })", "$.array[+$.range.from to +$.range.to]", {"2", "3"}}, {R"([1, 2, 3])", "-$[*]", {"-1", "-2", "-3"}}, - {"10000000000000000000000000", "-$", {"-9.999999999999999e+24"}}, + {"30000000000000000000000000", "-$", {"-3e+25"}}, }; for (const auto& testCase : testCases) { diff --git a/ydb/library/yql/minikql/mkql_type_ops.cpp b/ydb/library/yql/minikql/mkql_type_ops.cpp index 455db9894c1c..50f480b7344f 100644 --- a/ydb/library/yql/minikql/mkql_type_ops.cpp +++ b/ydb/library/yql/minikql/mkql_type_ops.cpp @@ -2246,7 +2246,7 @@ NUdf::TUnboxedValuePod ValueFromString(NUdf::EDataSlot type, NUdf::TStringRef bu case NUdf::EDataSlot::JsonDocument: { auto binaryJson = NKikimr::NBinaryJson::SerializeToBinaryJson(buf); - if (!binaryJson.Defined()) { + if (binaryJson.IsFail()) { // JSON parse error happened, return NULL return NUdf::TUnboxedValuePod(); } diff --git a/ydb/library/yql/providers/yt/codec/codegen/yt_codec_cg.cpp b/ydb/library/yql/providers/yt/codec/codegen/yt_codec_cg.cpp index 628e3f311255..7a9e2afbf5ad 100644 --- a/ydb/library/yql/providers/yt/codec/codegen/yt_codec_cg.cpp +++ b/ydb/library/yql/providers/yt/codec/codegen/yt_codec_cg.cpp @@ -67,7 +67,7 @@ extern "C" void YtCodecReadJsonDocument(void* vbuf, void* vpod) { buf.ReadMany(json.AsStringRef().Data(), size); const auto binaryJson = NBinaryJson::SerializeToBinaryJson(json.AsStringRef()); - if (!binaryJson.Defined()) { + if (binaryJson.IsFail()) { YQL_ENSURE(false, "Invalid JSON stored for JsonDocument type"); } diff --git a/ydb/library/yql/sql/v1/SQLv1.g.in b/ydb/library/yql/sql/v1/SQLv1.g.in index f92b02c8babd..d00ec4e3137d 100644 --- a/ydb/library/yql/sql/v1/SQLv1.g.in +++ b/ydb/library/yql/sql/v1/SQLv1.g.in @@ -748,14 +748,23 @@ table_setting_value: | STRING_VALUE | integer | split_boundaries - | expr ON an_id (AS (SECONDS | MILLISECONDS | MICROSECONDS | NANOSECONDS))? + | ttl_tier_list ON an_id (AS (SECONDS | MILLISECONDS | MICROSECONDS | NANOSECONDS))? | bool_value ; +ttl_tier_list: expr (ttl_tier_action (COMMA expr ttl_tier_action)*)?; +ttl_tier_action: + TO EXTERNAL DATA SOURCE an_id + | DELETE +; + family_entry: FAMILY an_id family_settings; family_settings: LPAREN (family_settings_entry (COMMA family_settings_entry)*)? RPAREN; family_settings_entry: an_id EQUALS family_setting_value; -family_setting_value: STRING_VALUE; +family_setting_value: + STRING_VALUE + | integer +; split_boundaries: LPAREN literal_value_list (COMMA literal_value_list)* RPAREN diff --git a/ydb/library/yql/sql/v1/format/sql_format.cpp b/ydb/library/yql/sql/v1/format/sql_format.cpp index 9c03df7f5de0..bb320b18753c 100644 --- a/ydb/library/yql/sql/v1/format/sql_format.cpp +++ b/ydb/library/yql/sql/v1/format/sql_format.cpp @@ -2436,6 +2436,66 @@ friend struct TStaticData; Visit(msg.GetToken5()); } + void VisitTableSettingValue(const TRule_table_setting_value& msg) { + switch (msg.GetAltCase()) { + case TRule_table_setting_value::kAltTableSettingValue5: { + // | ttl_tier_list ON an_id (AS (SECONDS | MILLISECONDS | MICROSECONDS | NANOSECONDS))? + const auto& ttlSettings = msg.GetAlt_table_setting_value5(); + const auto& tierList = ttlSettings.GetRule_ttl_tier_list1(); + const bool needIndent = tierList.HasBlock2() && tierList.GetBlock2().Block2Size() > 0; // more then one tier + if (needIndent) { + NewLine(); + PushCurrentIndent(); + Visit(tierList.GetRule_expr1()); + VisitTtlTierAction(tierList.GetBlock2().GetRule_ttl_tier_action1()); + + for (const auto& tierEntry : tierList.GetBlock2().GetBlock2()) { + Visit(tierEntry.GetToken1()); // comma + NewLine(); + Visit(tierEntry.GetRule_expr2()); + VisitTtlTierAction(tierEntry.GetRule_ttl_tier_action3()); + } + + PopCurrentIndent(); + NewLine(); + } else { + Visit(tierList.GetRule_expr1()); + if (tierList.HasBlock2()) { + VisitTtlTierAction(tierList.GetBlock2().GetRule_ttl_tier_action1()); + } + } + + VisitKeyword(ttlSettings.GetToken2()); + Visit(ttlSettings.GetRule_an_id3()); + if (ttlSettings.HasBlock4()) { + VisitKeyword(ttlSettings.GetBlock4().GetToken1()); + VisitKeyword(ttlSettings.GetBlock4().GetToken2()); + } + } break; + default: + VisitAllFields(TRule_table_setting_value::GetDescriptor(), msg); + } + } + + void VisitTtlTierAction(const TRule_ttl_tier_action& msg) { + switch (msg.GetAltCase()) { + case TRule_ttl_tier_action::kAltTtlTierAction1: + // | TO EXTERNAL DATA SOURCE an_id + VisitKeyword(msg.GetAlt_ttl_tier_action1().GetToken1()); + VisitKeyword(msg.GetAlt_ttl_tier_action1().GetToken2()); + VisitKeyword(msg.GetAlt_ttl_tier_action1().GetToken3()); + VisitKeyword(msg.GetAlt_ttl_tier_action1().GetToken4()); + Visit(msg.GetAlt_ttl_tier_action1().GetRule_an_id5()); + break; + case TRule_ttl_tier_action::kAltTtlTierAction2: + // | DELETE + VisitKeyword(msg.GetAlt_ttl_tier_action2().GetToken1()); + break; + case TRule_ttl_tier_action::ALT_NOT_SET: + break; + } + } + void VisitExpr(const TRule_expr& msg) { if (msg.HasAlt_expr2()) { Visit(msg.GetAlt_expr2()); @@ -2724,6 +2784,8 @@ TStaticData::TStaticData() {TRule_case_expr::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitCaseExpr)}, {TRule_when_expr::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitWhenExpr)}, {TRule_with_table_settings::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitWithTableSettingsExpr)}, + {TRule_table_setting_value::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitTableSettingValue)}, + {TRule_ttl_tier_action::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitTtlTierAction)}, {TRule_expr::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitExpr)}, {TRule_or_subexpr::GetDescriptor(), MakePrettyFunctor(&TPrettyVisitor::VisitOrSubexpr)}, diff --git a/ydb/library/yql/sql/v1/node.cpp b/ydb/library/yql/sql/v1/node.cpp index 66482fc50e0c..dc2d2249ef5c 100644 --- a/ydb/library/yql/sql/v1/node.cpp +++ b/ydb/library/yql/sql/v1/node.cpp @@ -1853,9 +1853,14 @@ TMaybe StringContentOrIdContent(TContext& ctx, TPosition pos, co (ctx.AnsiQuotedIdentifiers && input.StartsWith('"'))? EStringContentMode::AnsiIdent : EStringContentMode::Default); } -TTtlSettings::TTtlSettings(const TIdentifier& columnName, const TNodePtr& expr, const TMaybe& columnUnit) +TTtlSettings::TTierSettings::TTierSettings(const TNodePtr& evictionDelay, const std::optional& storageName) + : EvictionDelay(evictionDelay) + , StorageName(storageName) { +} + +TTtlSettings::TTtlSettings(const TIdentifier& columnName, const std::vector& tiers, const TMaybe& columnUnit) : ColumnName(columnName) - , Expr(expr) + , Tiers(tiers) , ColumnUnit(columnUnit) { } @@ -3225,7 +3230,7 @@ TSourcePtr TryMakeSourceFromExpression(TPosition pos, TContext& ctx, const TStri return nullptr; } - auto wrappedNode = new TAstListNodeImpl(pos, { + auto wrappedNode = new TAstListNodeImpl(pos, { new TAstAtomNodeImpl(pos, "EvaluateAtom", TNodeFlags::Default), node }); @@ -3254,7 +3259,7 @@ void MakeTableFromExpression(TPosition pos, TContext& ctx, TNodePtr node, TDefer node = node->Y("Concat", node->Y("String", node->Q(prefix)), node); } - auto wrappedNode = new TAstListNodeImpl(pos, { + auto wrappedNode = new TAstListNodeImpl(pos, { new TAstAtomNodeImpl(pos, "EvaluateAtom", TNodeFlags::Default), node }); @@ -3271,7 +3276,7 @@ TDeferredAtom MakeAtomFromExpression(TPosition pos, TContext& ctx, TNodePtr node node = node->Y("Concat", node->Y("String", node->Q(prefix)), node); } - auto wrappedNode = new TAstListNodeImpl(pos, { + auto wrappedNode = new TAstListNodeImpl(pos, { new TAstAtomNodeImpl(pos, "EvaluateAtom", TNodeFlags::Default), node }); diff --git a/ydb/library/yql/sql/v1/node.h b/ydb/library/yql/sql/v1/node.h index 198ac249907a..6fa3e3a7b6fa 100644 --- a/ydb/library/yql/sql/v1/node.h +++ b/ydb/library/yql/sql/v1/node.h @@ -1098,11 +1098,18 @@ namespace NSQLTranslationV1 { Nanoseconds /* "nanoseconds" */, }; + struct TTierSettings { + TNodePtr EvictionDelay; + std::optional StorageName; + + TTierSettings(const TNodePtr& evictionDelay, const std::optional& storageName = std::nullopt); + }; + TIdentifier ColumnName; - TNodePtr Expr; + std::vector Tiers; TMaybe ColumnUnit; - TTtlSettings(const TIdentifier& columnName, const TNodePtr& expr, const TMaybe& columnUnit = {}); + TTtlSettings(const TIdentifier& columnName, const std::vector& tiers, const TMaybe& columnUnit = {}); }; struct TTableSettings { @@ -1143,6 +1150,7 @@ namespace NSQLTranslationV1 { TIdentifier Name; TNodePtr Data; TNodePtr Compression; + TNodePtr CompressionLevel; }; struct TIndexDescription { diff --git a/ydb/library/yql/sql/v1/query.cpp b/ydb/library/yql/sql/v1/query.cpp index 85b3bc3149eb..e60182097e0e 100644 --- a/ydb/library/yql/sql/v1/query.cpp +++ b/ydb/library/yql/sql/v1/query.cpp @@ -239,7 +239,17 @@ static INode::TPtr CreateTableSettings(const TTableSettings& tableSettings, ETab auto opts = Y(); opts = L(opts, Q(Y(Q("columnName"), BuildQuotedAtom(ttlSettings.ColumnName.Pos, ttlSettings.ColumnName.Name)))); - opts = L(opts, Q(Y(Q("expireAfter"), ttlSettings.Expr))); + + auto tiersDesc = Y(); + for (const auto& tier : ttlSettings.Tiers) { + auto tierDesc = Y(); + tierDesc = L(tierDesc, Q(Y(Q("evictionDelay"), tier.EvictionDelay))); + if (tier.StorageName) { + tierDesc = L(tierDesc, Q(Y(Q("storageName"), BuildQuotedAtom(tier.StorageName->Pos, tier.StorageName->Name)))); + } + tiersDesc = L(tiersDesc, Q(tierDesc)); + } + opts = L(opts, Q(Y(Q("tiers"), Q(tiersDesc)))); if (ttlSettings.ColumnUnit) { opts = L(opts, Q(Y(Q("columnUnit"), Q(ToString(*ttlSettings.ColumnUnit))))); @@ -1072,6 +1082,9 @@ class TCreateTableNode final: public TAstListNode { if (family.Compression) { familyDesc = L(familyDesc, Q(Y(Q("compression"), family.Compression))); } + if (family.CompressionLevel) { + familyDesc = L(familyDesc, Q(Y(Q("compression_level"), family.CompressionLevel))); + } columnFamilies = L(columnFamilies, Q(familyDesc)); } opts = L(opts, Q(Y(Q("columnFamilies"), Q(columnFamilies)))); @@ -1357,6 +1370,9 @@ class TAlterTableNode final: public TAstListNode { if (family.Compression) { familyDesc = L(familyDesc, Q(Y(Q("compression"), family.Compression))); } + if (family.CompressionLevel) { + familyDesc = L(familyDesc, Q(Y(Q("compression_level"), family.CompressionLevel))); + } columnFamilies = L(columnFamilies, Q(familyDesc)); } actions = L(actions, Q(Y(Q("addColumnFamilies"), Q(columnFamilies)))); @@ -1373,6 +1389,9 @@ class TAlterTableNode final: public TAstListNode { if (family.Compression) { familyDesc = L(familyDesc, Q(Y(Q("compression"), family.Compression))); } + if (family.CompressionLevel) { + familyDesc = L(familyDesc, Q(Y(Q("compression_level"), family.CompressionLevel))); + } columnFamilies = L(columnFamilies, Q(familyDesc)); } actions = L(actions, Q(Y(Q("alterColumnFamilies"), Q(columnFamilies)))); diff --git a/ydb/library/yql/sql/v1/sql_query.cpp b/ydb/library/yql/sql/v1/sql_query.cpp index 40c1b6727204..b54fce1379b0 100644 --- a/ydb/library/yql/sql/v1/sql_query.cpp +++ b/ydb/library/yql/sql/v1/sql_query.cpp @@ -1810,16 +1810,29 @@ bool TSqlQuery::AlterTableAlterFamily(const TRule_alter_table_alter_column_famil << "' in one alter"; return false; } - const TString stringValue(Ctx.Token(value.GetToken1())); - entry->Data = BuildLiteralSmartString(Ctx, stringValue); + if (!StoreString(value, entry->Data, Ctx)) { + Ctx.Error() << to_upper(settingName.Name) << " value should be a string literal"; + return false; + } } else if (to_lower(settingName.Name) == "compression") { if (entry->Compression) { Ctx.Error() << "Redefinition of 'compression' setting for column family '" << name.Name << "' in one alter"; return false; } - const TString stringValue(Ctx.Token(value.GetToken1())); - entry->Compression = BuildLiteralSmartString(Ctx, stringValue); + if (!StoreString(value, entry->Compression, Ctx)) { + Ctx.Error() << to_upper(settingName.Name) << " value should be a string literal"; + return false; + } + } else if (to_lower(settingName.Name) == "compression_level") { + if (entry->CompressionLevel) { + Ctx.Error() << "Redefinition of 'compression_level' setting for column family '" << name.Name << "' in one alter"; + return false; + } + if (!StoreInt(value, entry->CompressionLevel, Ctx)) { + Ctx.Error() << to_upper(settingName.Name) << " value should be an integer"; + return false; + } } else { Ctx.Error() << "Unknown table setting: " << settingName.Name; return false; diff --git a/ydb/library/yql/sql/v1/sql_translation.cpp b/ydb/library/yql/sql/v1/sql_translation.cpp index 570dd98202d3..bd16923b1c2e 100644 --- a/ydb/library/yql/sql/v1/sql_translation.cpp +++ b/ydb/library/yql/sql/v1/sql_translation.cpp @@ -1406,15 +1406,59 @@ TNodePtr TSqlTranslation::SerialTypeNode(const TRule_type_name_or_bind& node) { return nullptr; } +bool StoreString(const TRule_family_setting_value& from, TNodePtr& to, TContext& ctx) { + switch (from.Alt_case()) { + case TRule_family_setting_value::kAltFamilySettingValue1: { + // STRING_VALUE + const TString stringValue(ctx.Token(from.GetAlt_family_setting_value1().GetToken1())); + TNodePtr literal = BuildLiteralSmartString(ctx, stringValue); + if (!literal) { + return false; + } + to = literal; + break; + } + default: + return false; + } + return true; +} + +bool StoreInt(const TRule_family_setting_value& from, TNodePtr& to, TContext& ctx) { + switch (from.Alt_case()) { + case TRule_family_setting_value::kAltFamilySettingValue2: { + // integer + TNodePtr literal = LiteralNumber(ctx, from.GetAlt_family_setting_value2().GetRule_integer1()); + if (!literal) { + return false; + } + to = literal; + break; + } + default: + return false; + } + return true; +} + bool TSqlTranslation::FillFamilySettingsEntry(const TRule_family_settings_entry& settingNode, TFamilyEntry& family) { TIdentifier id = IdEx(settingNode.GetRule_an_id1(), *this); const TRule_family_setting_value& value = settingNode.GetRule_family_setting_value3(); if (to_lower(id.Name) == "data") { - const TString stringValue(Ctx.Token(value.GetToken1())); - family.Data = BuildLiteralSmartString(Ctx, stringValue); + if (!StoreString(value, family.Data, Ctx)) { + Ctx.Error() << to_upper(id.Name) << " value should be a string literal"; + return false; + } } else if (to_lower(id.Name) == "compression") { - const TString stringValue(Ctx.Token(value.GetToken1())); - family.Compression = BuildLiteralSmartString(Ctx, stringValue); + if (!StoreString(value, family.Compression, Ctx)) { + Ctx.Error() << to_upper(id.Name) << " value should be a string literal"; + return false; + } + } else if (to_lower(id.Name) == "compression_level") { + if (!StoreInt(value, family.CompressionLevel, Ctx)) { + Ctx.Error() << to_upper(id.Name) << " value should be an integer"; + return false; + } } else { Ctx.Error() << "Unknown table setting: " << id.Name; return false; @@ -1758,19 +1802,68 @@ namespace { return true; } - bool StoreTtlSettings(const TRule_table_setting_value& from, TResetableSetting& to, - TSqlExpression& expr, TContext& ctx, TTranslation& txc) { + bool FillTieringInterval(const TRule_expr& from, TNodePtr& tieringInterval, TSqlExpression& expr, TContext& ctx) { + auto exprNode = expr.Build(from); + if (!exprNode) { + return false; + } + + if (exprNode->GetOpName() != "Interval") { + ctx.Error() << "Literal of Interval type is expected for TTL"; + return false; + } + + tieringInterval = exprNode; + return true; + } + + bool FillTierAction(const TRule_ttl_tier_action& from, std::optional& storageName, TTranslation& txc) { + switch (from.GetAltCase()) { + case TRule_ttl_tier_action::kAltTtlTierAction1: + storageName = IdEx(from.GetAlt_ttl_tier_action1().GetRule_an_id5(), txc); + break; + case TRule_ttl_tier_action::kAltTtlTierAction2: + storageName.reset(); + break; + case TRule_ttl_tier_action::ALT_NOT_SET: + Y_ABORT("You should change implementation according to grammar changes"); + } + return true; + } + + bool StoreTtlSettings(const TRule_table_setting_value& from, TResetableSetting& to, TSqlExpression& expr, TContext& ctx, + TTranslation& txc) { switch (from.Alt_case()) { case TRule_table_setting_value::kAltTableSettingValue5: { auto columnName = IdEx(from.GetAlt_table_setting_value5().GetRule_an_id3(), txc); - auto exprNode = expr.Build(from.GetAlt_table_setting_value5().GetRule_expr1()); - if (!exprNode) { + auto tiersLiteral = from.GetAlt_table_setting_value5().GetRule_ttl_tier_list1(); + + TNodePtr firstInterval; + if (!FillTieringInterval(tiersLiteral.GetRule_expr1(), firstInterval, expr, ctx)) { return false; } - if (exprNode->GetOpName() != "Interval") { - ctx.Error() << "Literal of Interval type is expected for TTL"; - return false; + std::vector tiers; + if (!tiersLiteral.HasBlock2()) { + tiers.emplace_back(firstInterval); + } else { + std::optional firstStorageName; + if (!FillTierAction(tiersLiteral.GetBlock2().GetRule_ttl_tier_action1(), firstStorageName, txc)) { + return false; + } + tiers.emplace_back(firstInterval, firstStorageName); + + for (const auto& tierLiteral : tiersLiteral.GetBlock2().GetBlock2()) { + TNodePtr intervalExpr; + if (!FillTieringInterval(tierLiteral.GetRule_expr2(), intervalExpr, expr, ctx)) { + return false; + } + std::optional storageName; + if (!FillTierAction(tierLiteral.GetRule_ttl_tier_action3(), storageName, txc)) { + return false; + } + tiers.emplace_back(intervalExpr, storageName); + } } TMaybe columnUnit; @@ -1783,7 +1876,7 @@ namespace { } } - to.Set(TTtlSettings(columnName, exprNode, columnUnit)); + to.Set(TTtlSettings(columnName, tiers, columnUnit)); break; } default: diff --git a/ydb/library/yql/sql/v1/sql_translation.h b/ydb/library/yql/sql/v1/sql_translation.h index 07fc6764840d..41d5bb69eee2 100644 --- a/ydb/library/yql/sql/v1/sql_translation.h +++ b/ydb/library/yql/sql/v1/sql_translation.h @@ -266,6 +266,9 @@ class TSqlTranslation: public TTranslation { TNodePtr LiteralNumber(TContext& ctx, const TRule_integer& node); +bool StoreString(const TRule_family_setting_value& from, TNodePtr& to, TContext& ctx); +bool StoreInt(const TRule_family_setting_value& from, TNodePtr& to, TContext& ctx); + template struct TPatternComponent { TBasicString Prefix; diff --git a/ydb/library/yql/sql/v1/sql_ut.cpp b/ydb/library/yql/sql/v1/sql_ut.cpp index 1570fabfc4b7..68c3c534bfcd 100644 --- a/ydb/library/yql/sql/v1/sql_ut.cpp +++ b/ydb/library/yql/sql/v1/sql_ut.cpp @@ -2026,7 +2026,8 @@ Y_UNIT_TEST_SUITE(SqlParsingOnly) { TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { if (word == "Write") { UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("setTtlSettings")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("expireAfter")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiers")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("evictionDelay")); UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("86400000")); } }; @@ -2048,7 +2049,8 @@ Y_UNIT_TEST_SUITE(SqlParsingOnly) { TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { if (word == "Write") { UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("setTtlSettings")); - UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("expireAfter")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiers")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("evictionDelay")); UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("86400000")); UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("columnUnit")); UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("seconds")); @@ -2061,6 +2063,80 @@ Y_UNIT_TEST_SUITE(SqlParsingOnly) { UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); } + Y_UNIT_TEST(TtlTieringParseCorrect) { + NYql::TAstParseResult res = SqlToYql( + R"( USE plato; + CREATE TABLE tableName (Key Uint32, CreatedAt Uint32, PRIMARY KEY (Key)) + WITH (TTL = + Interval("P1D") TO EXTERNAL DATA SOURCE Tier1, + Interval("P2D") TO EXTERNAL DATA SOURCE Tier2, + Interval("P30D") DELETE + ON CreatedAt AS SECONDS);)" + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("setTtlSettings")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiers")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("evictionDelay")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("storageName")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Tier1")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Tier2")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("86400000")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("172800000")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("2592000000")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("columnUnit")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("seconds")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + + Y_UNIT_TEST(TtlTieringWithOtherActionsParseCorrect) { + NYql::TAstParseResult res = SqlToYql( + R"( USE plato; + ALTER TABLE tableName + ADD FAMILY cold (DATA = "rot"), + SET TTL + Interval("P1D") TO EXTERNAL DATA SOURCE Tier1, + Interval("P2D") TO EXTERNAL DATA SOURCE Tier2, + Interval("P30D") DELETE + ON CreatedAt, + ALTER COLUMN payload_v2 SET FAMILY cold, + ALTER FAMILY default SET DATA "ssd" + ;)" + ); + UNIT_ASSERT(res.Root); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("addColumnFamilies")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("cold")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("alterColumnFamilies")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("default")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("setTtlSettings")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("tiers")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("evictionDelay")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("storageName")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Tier1")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("Tier2")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("86400000")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("172800000")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("2592000000")); + } + }; + + TWordCountHive elementStat = { {TString("Write"), 0} }; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + } + Y_UNIT_TEST(TieringParseCorrect) { NYql::TAstParseResult res = SqlToYql( R"( USE plato; @@ -7051,3 +7127,124 @@ Y_UNIT_TEST_SUITE(ResourcePoolClassifier) { UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); } } + +Y_UNIT_TEST_SUITE(ColumnFamily) { + Y_UNIT_TEST(CompressionLevelCorrectUsage) { + NYql::TAstParseResult res = SqlToYql(R"( use plato; + CREATE TABLE tableName ( + Key Uint32 FAMILY default, + Value String FAMILY family1, + PRIMARY KEY (Key), + FAMILY default ( + DATA = "test", + COMPRESSION = "lz4", + COMPRESSION_LEVEL = 5 + ), + FAMILY family1 ( + DATA = "test", + COMPRESSION = "lz4", + COMPRESSION_LEVEL = 3 + ) + ); + )"); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 0); + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("compression_level")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("5")); + UNIT_ASSERT_VALUES_UNEQUAL(TString::npos, line.find("3")); + } + }; + + TWordCountHive elementStat = { { TString("Write"), 0 }, { TString("compression_level"), 0 } }; + VerifyProgram(res, elementStat, verifyLine); + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); + UNIT_ASSERT_VALUES_EQUAL(2, elementStat["compression_level"]); + } + + Y_UNIT_TEST(FieldDataIsNotString) { + NYql::TAstParseResult res = SqlToYql(R"( use plato; + CREATE TABLE tableName ( + Key Uint32 FAMILY default, + PRIMARY KEY (Key), + FAMILY default ( + DATA = 1, + COMPRESSION = "lz4", + COMPRESSION_LEVEL = 5 + ) + ); + )"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 1); + UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "DATA value should be a string literal"); + } + + Y_UNIT_TEST(FieldCompressionIsNotString) { + NYql::TAstParseResult res = SqlToYql(R"( use plato; + CREATE TABLE tableName ( + Key Uint32 FAMILY default, + PRIMARY KEY (Key), + FAMILY default ( + DATA = "test", + COMPRESSION = 2, + COMPRESSION_LEVEL = 5 + ), + ); + )"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 1); + UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "COMPRESSION value should be a string literal"); + } + + Y_UNIT_TEST(FieldCompressionLevelIsNotInteger) { + NYql::TAstParseResult res = SqlToYql(R"( use plato; + CREATE TABLE tableName ( + Key Uint32 FAMILY default, + PRIMARY KEY (Key), + FAMILY default ( + DATA = "test", + COMPRESSION = "lz4", + COMPRESSION_LEVEL = "5" + ) + ); + )"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 1); + UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "COMPRESSION_LEVEL value should be an integer"); + } + + Y_UNIT_TEST(AlterCompressionCorrectUsage) { + NYql::TAstParseResult res = SqlToYql(R"( use plato; + ALTER TABLE tableName ALTER FAMILY default SET COMPRESSION "lz4"; + )"); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 0); + } + + Y_UNIT_TEST(AlterCompressionFieldIsNotString) { + NYql::TAstParseResult res = SqlToYql(R"( use plato; + ALTER TABLE tableName ALTER FAMILY default SET COMPRESSION lz4; + )"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 1); + UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "Unexpected token 'lz4' : cannot match to any predicted input"); + } + + Y_UNIT_TEST(AlterCompressionLevelCorrectUsage) { + NYql::TAstParseResult res = SqlToYql(R"( use plato; + ALTER TABLE tableName ALTER FAMILY default SET COMPRESSION_LEVEL 5; + )"); + UNIT_ASSERT(res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 0); + } + + Y_UNIT_TEST(AlterCompressionLevelFieldIsNotInteger) { + NYql::TAstParseResult res = SqlToYql(R"( use plato; + ALTER TABLE tableName ALTER FAMILY default SET COMPRESSION_LEVEL "5"; + )"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT(res.Issues.Size() == 1); + UNIT_ASSERT_STRING_CONTAINS(res.Issues.ToString(), "COMPRESSION_LEVEL value should be an integer"); + } +} diff --git a/ydb/mvp/meta/meta_cloud.h b/ydb/mvp/meta/meta_cloud.h index ddfe0edfab31..00cfb5acddc1 100644 --- a/ydb/mvp/meta/meta_cloud.h +++ b/ydb/mvp/meta/meta_cloud.h @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include diff --git a/ydb/mvp/meta/meta_cluster.h b/ydb/mvp/meta/meta_cluster.h index ceb793a351db..5bec6b8dd096 100644 --- a/ydb/mvp/meta/meta_cluster.h +++ b/ydb/mvp/meta/meta_cluster.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include diff --git a/ydb/mvp/meta/meta_clusters.h b/ydb/mvp/meta/meta_clusters.h index b001bc162ffb..e2ebdca0e541 100644 --- a/ydb/mvp/meta/meta_clusters.h +++ b/ydb/mvp/meta/meta_clusters.h @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include diff --git a/ydb/mvp/meta/meta_cp_databases.h b/ydb/mvp/meta/meta_cp_databases.h index 0f73ea71c8ed..fd3ef8fb7d27 100644 --- a/ydb/mvp/meta/meta_cp_databases.h +++ b/ydb/mvp/meta/meta_cp_databases.h @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include diff --git a/ydb/mvp/meta/meta_db_clusters.h b/ydb/mvp/meta/meta_db_clusters.h index 6c9b549d2a1b..d96309461479 100644 --- a/ydb/mvp/meta/meta_db_clusters.h +++ b/ydb/mvp/meta/meta_db_clusters.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include diff --git a/ydb/public/api/protos/draft/ydb_logstore.proto b/ydb/public/api/protos/draft/ydb_logstore.proto index 8c0a24c53aaa..34705aa0f593 100644 --- a/ydb/public/api/protos/draft/ydb_logstore.proto +++ b/ydb/public/api/protos/draft/ydb_logstore.proto @@ -60,10 +60,6 @@ message Tier { Ydb.Table.TtlSettings eviction = 2; // When to evict data to the next tier (or remove if none) } -message TieringSettings { - optional string tiering_id = 2; -} - message CreateLogStoreRequest { Ydb.Operations.OperationParams operation_params = 1; @@ -135,8 +131,8 @@ message CreateLogTableRequest { }; oneof ttl_specification { Ydb.Table.TtlSettings ttl_settings = 5; - TieringSettings tiering_settings = 6; }; + reserved 6; // Specifies the desired number of ColumnShards for this table uint32 shards_count = 7; @@ -160,9 +156,9 @@ message DescribeLogTableResult { string schema_preset_name = 2; Schema schema = 3; + reserved 4; oneof ttl_specification { Ydb.Table.TtlSettings ttl_settings = 5; - TieringSettings tiering_settings = 4; } // Specifies the desired number of ColumnShards for this table @@ -195,9 +191,9 @@ message AlterLogTableRequest { oneof ttl_action { google.protobuf.Empty drop_ttl_settings = 3; Ydb.Table.TtlSettings set_ttl_settings = 4; - TieringSettings set_tiering_settings = 5; google.protobuf.Empty drop_tiering_settings = 6; } + reserved 5; } message AlterLogTableResponse { diff --git a/ydb/public/api/protos/ydb_table.proto b/ydb/public/api/protos/ydb_table.proto index 875bbf882d19..37d6ad5e1e1b 100644 --- a/ydb/public/api/protos/ydb_table.proto +++ b/ydb/public/api/protos/ydb_table.proto @@ -387,8 +387,13 @@ message ColumnMeta { } } +message EvictionToExternalStorageSettings { + // Path to external data source + string storage = 1; +} + message DateTypeColumnModeSettings { - // The row will be considered as expired at the moment of time, when the value + // The row will be considered as expired or assigned a tier at the moment of time, when the value // stored in is less than or equal to the current time (in epoch // time format), and has passed since that moment; // i.e. the expiration threshold is the value of plus . @@ -425,10 +430,27 @@ message ValueSinceUnixEpochModeSettings { uint32 expire_after_seconds = 3; } +message TtlTier { + oneof expression { + DateTypeColumnModeSettings date_type_column = 1; + ValueSinceUnixEpochModeSettings value_since_unix_epoch = 2; + } + + oneof action { + google.protobuf.Empty delete = 3; + EvictionToExternalStorageSettings evict_to_external_storage = 4; + } +} + +message TieredModeSettings { + repeated TtlTier tiers = 1; +} + message TtlSettings { oneof mode { DateTypeColumnModeSettings date_type_column = 1; ValueSinceUnixEpochModeSettings value_since_unix_epoch = 2; + TieredModeSettings tiered_ttl = 4; } // There is no guarantee that expired row will be deleted immediately upon @@ -472,6 +494,7 @@ message ColumnFamily { COMPRESSION_UNSPECIFIED = 0; COMPRESSION_NONE = 1; COMPRESSION_LZ4 = 2; + COMPRESSION_ZSTD = 3; } // Name of the column family, the name "default" must be used for the @@ -487,6 +510,10 @@ message ColumnFamily { // When enabled table data will be kept in memory // WARNING: DO NOT USE Ydb.FeatureFlag.Status keep_in_memory = 4; + + // Not all compression algorithms support + // Set if want to change default value + optional int32 compression_level = 5; } message PartitioningSettings { @@ -574,8 +601,7 @@ message CreateTableRequest { Ydb.FeatureFlag.Status key_bloom_filter = 16; // Read replicas settings for table ReadReplicasSettings read_replicas_settings = 17; - // Tiering rules name. It specifies how data migrates from one tier (logical storage) to another. - string tiering = 18; + reserved 18; // Is temporary table bool temporary = 19; // Is table column or row oriented @@ -654,11 +680,7 @@ message AlterTableRequest { repeated string drop_changefeeds = 20; // Rename existed index repeated RenameIndexItem rename_indexes = 21; - // Setup or remove tiering - oneof tiering_action { - string set_tiering = 22; - google.protobuf.Empty drop_tiering = 23; - } + reserved 22, 23; } message AlterTableResponse { diff --git a/ydb/public/lib/experimental/ydb_logstore.cpp b/ydb/public/lib/experimental/ydb_logstore.cpp index 7b7c3329751a..53c73d47bbf2 100644 --- a/ydb/public/lib/experimental/ydb_logstore.cpp +++ b/ydb/public/lib/experimental/ydb_logstore.cpp @@ -16,23 +16,10 @@ namespace NYdb { namespace NLogStore { TMaybe TtlSettingsFromProto(const Ydb::Table::TtlSettings& proto) { - switch (proto.mode_case()) { - case Ydb::Table::TtlSettings::kDateTypeColumn: - return TTtlSettings( - proto.date_type_column(), - proto.run_interval_seconds() - ); - - case Ydb::Table::TtlSettings::kValueSinceUnixEpoch: - return TTtlSettings( - proto.value_since_unix_epoch(), - proto.run_interval_seconds() - ); - - default: - break; + if (auto settings = TTtlSettings::FromProto(proto)) { + return *settings; } - return {}; + return Nothing(); } static TCompression CompressionFromProto(const Ydb::LogStore::Compression& compression) { @@ -199,8 +186,6 @@ void TLogTableDescription::SerializeTo(Ydb::LogStore::CreateLogTableRequest& req if (TtlSettings) { TtlSettings->SerializeTo(*request.mutable_ttl_settings()); - } else if (TieringSettings) { - TieringSettings->SerializeTo(*request.mutable_tiering_settings()); } } diff --git a/ydb/public/lib/experimental/ydb_logstore.h b/ydb/public/lib/experimental/ydb_logstore.h index 633730e015da..be8148b023f3 100644 --- a/ydb/public/lib/experimental/ydb_logstore.h +++ b/ydb/public/lib/experimental/ydb_logstore.h @@ -152,21 +152,6 @@ struct TLogTableSharding { TLogTableSharding(const Ydb::LogStore::DescribeLogTableResult& desc); }; -class TTieringSettings { -private: - TString TieringId; -public: - TTieringSettings(const TString& tieringId) - : TieringId(tieringId) { - - } - - void SerializeTo(Ydb::LogStore::TieringSettings& proto) const { - proto.set_tiering_id(TieringId); - } - -}; - class TLogTableDescription { public: TLogTableDescription(const TString& schemaPresetName, const TLogTableSharding& sharding); @@ -200,16 +185,11 @@ class TLogTableDescription { TtlSettings = settings; return *this; } - TLogTableDescription& SetTieringSettings(const TTieringSettings& settings) { - TieringSettings = settings; - return *this; - } private: const TString SchemaPresetName; const TSchema Schema; const TLogTableSharding Sharding; TMaybe TtlSettings; - TMaybe TieringSettings; TString Owner; TVector Permissions; TVector EffectivePermissions; diff --git a/ydb/public/sdk/cpp/client/ydb_table/table.cpp b/ydb/public/sdk/cpp/client/ydb_table/table.cpp index 047526adabaa..ad207af8df26 100644 --- a/ydb/public/sdk/cpp/client/ydb_table/table.cpp +++ b/ydb/public/sdk/cpp/client/ydb_table/table.cpp @@ -24,6 +24,7 @@ #include +#include #include #include #include @@ -324,28 +325,8 @@ class TTableDescription::TImpl { } // ttl settings - switch (proto.ttl_settings().mode_case()) { - case Ydb::Table::TtlSettings::kDateTypeColumn: - TtlSettings_ = TTtlSettings( - proto.ttl_settings().date_type_column(), - proto.ttl_settings().run_interval_seconds() - ); - break; - - case Ydb::Table::TtlSettings::kValueSinceUnixEpoch: - TtlSettings_ = TTtlSettings( - proto.ttl_settings().value_since_unix_epoch(), - proto.ttl_settings().run_interval_seconds() - ); - break; - - default: - break; - } - - // tiering - if (proto.tiering().size()) { - Tiering_ = proto.tiering(); + if (auto ttlSettings = TTtlSettings::FromProto(proto.ttl_settings())) { + TtlSettings_ = std::move(*ttlSettings); } if (proto.store_type()) { @@ -570,10 +551,6 @@ class TTableDescription::TImpl { return TtlSettings_; } - const TMaybe& GetTiering() const { - return Tiering_; - } - EStoreType GetStoreType() const { return StoreType_; } @@ -654,7 +631,6 @@ class TTableDescription::TImpl { TVector Indexes_; TVector Changefeeds_; TMaybe TtlSettings_; - TMaybe Tiering_; TString Owner_; TVector Permissions_; TVector EffectivePermissions_; @@ -721,7 +697,7 @@ TMaybe TTableDescription::GetTtlSettings() const { } TMaybe TTableDescription::GetTiering() const { - return Impl_->GetTiering(); + return Nothing(); } EStoreType TTableDescription::GetStoreType() const { @@ -936,10 +912,6 @@ void TTableDescription::SerializeTo(Ydb::Table::CreateTableRequest& request) con ttl->SerializeTo(*request.mutable_ttl_settings()); } - if (const auto& tiering = Impl_->GetTiering()) { - request.set_tiering(*tiering); - } - if (Impl_->GetStoreType() == EStoreType::Column) { request.set_store_type(Ydb::Table::StoreType::STORE_TYPE_COLUMN); } @@ -2743,14 +2715,72 @@ bool operator!=(const TChangefeedDescription& lhs, const TChangefeedDescription& //////////////////////////////////////////////////////////////////////////////// -TDateTypeColumnModeSettings::TDateTypeColumnModeSettings(const TString& columnName, const TDuration& expireAfter) +TTtlTierSettings::TTtlTierSettings(const TExpression& expression, const TAction& action) + : Expression_(expression) + , Action_(action) +{ } + +std::optional TTtlTierSettings::FromProto(const Ydb::Table::TtlTier& tier) { + std::optional expression; + switch (tier.expression_case()) { + case Ydb::Table::TtlTier::kDateTypeColumn: + expression = TDateTypeColumnModeSettings( + tier.date_type_column().column_name(), TDuration::Seconds(tier.date_type_column().expire_after_seconds())); + break; + case Ydb::Table::TtlTier::kValueSinceUnixEpoch: + expression = TValueSinceUnixEpochModeSettings(tier.value_since_unix_epoch().column_name(), + TProtoAccessor::FromProto(tier.value_since_unix_epoch().column_unit()), + TDuration::Seconds(tier.value_since_unix_epoch().expire_after_seconds())); + break; + case Ydb::Table::TtlTier::EXPRESSION_NOT_SET: + return std::nullopt; + } + + TAction action; + switch (tier.action_case()) { + case Ydb::Table::TtlTier::kDelete: + action = TTtlDeleteAction(); + break; + case Ydb::Table::TtlTier::kEvictToExternalStorage: + action = TTtlEvictToExternalStorageAction(tier.evict_to_external_storage().storage()); + break; + case Ydb::Table::TtlTier::ACTION_NOT_SET: + return std::nullopt; + } + + return TTtlTierSettings(std::move(*expression), std::move(action)); +} + +void TTtlTierSettings::SerializeTo(Ydb::Table::TtlTier& proto) const { + std::visit(TOverloaded{ + [&proto](const TDateTypeColumnModeSettings& expr) { expr.SerializeTo(*proto.mutable_date_type_column()); }, + [&proto](const TValueSinceUnixEpochModeSettings& expr) { expr.SerializeTo(*proto.mutable_value_since_unix_epoch()); }, + }, + Expression_); + + std::visit(TOverloaded{ + [&proto](const TTtlDeleteAction&) { proto.mutable_delete_(); }, + [&proto](const TTtlEvictToExternalStorageAction& action) { action.SerializeTo(*proto.mutable_evict_to_external_storage()); }, + }, + Action_); +} + +const TTtlTierSettings::TExpression& TTtlTierSettings::GetExpression() const { + return Expression_; +} + +const TTtlTierSettings::TAction& TTtlTierSettings::GetAction() const { + return Action_; +} + +TDateTypeColumnModeSettings::TDateTypeColumnModeSettings(const TString& columnName, const TDuration& applyAfter) : ColumnName_(columnName) - , ExpireAfter_(expireAfter) + , ApplyAfter_(applyAfter) {} void TDateTypeColumnModeSettings::SerializeTo(Ydb::Table::DateTypeColumnModeSettings& proto) const { proto.set_column_name(ColumnName_); - proto.set_expire_after_seconds(ExpireAfter_.Seconds()); + proto.set_expire_after_seconds(ApplyAfter_.Seconds()); } const TString& TDateTypeColumnModeSettings::GetColumnName() const { @@ -2758,19 +2788,19 @@ const TString& TDateTypeColumnModeSettings::GetColumnName() const { } const TDuration& TDateTypeColumnModeSettings::GetExpireAfter() const { - return ExpireAfter_; + return ApplyAfter_; } -TValueSinceUnixEpochModeSettings::TValueSinceUnixEpochModeSettings(const TString& columnName, EUnit columnUnit, const TDuration& expireAfter) +TValueSinceUnixEpochModeSettings::TValueSinceUnixEpochModeSettings(const TString& columnName, EUnit columnUnit, const TDuration& applyAfter) : ColumnName_(columnName) , ColumnUnit_(columnUnit) - , ExpireAfter_(expireAfter) + , ApplyAfter_(applyAfter) {} void TValueSinceUnixEpochModeSettings::SerializeTo(Ydb::Table::ValueSinceUnixEpochModeSettings& proto) const { proto.set_column_name(ColumnName_); proto.set_column_unit(TProtoAccessor::GetProto(ColumnUnit_)); - proto.set_expire_after_seconds(ExpireAfter_.Seconds()); + proto.set_expire_after_seconds(ApplyAfter_.Seconds()); } const TString& TValueSinceUnixEpochModeSettings::GetColumnName() const { @@ -2782,7 +2812,7 @@ TValueSinceUnixEpochModeSettings::EUnit TValueSinceUnixEpochModeSettings::GetCol } const TDuration& TValueSinceUnixEpochModeSettings::GetExpireAfter() const { - return ExpireAfter_; + return ApplyAfter_; } void TValueSinceUnixEpochModeSettings::Out(IOutputStream& out, EUnit unit) { @@ -2825,42 +2855,85 @@ TValueSinceUnixEpochModeSettings::EUnit TValueSinceUnixEpochModeSettings::UnitFr return EUnit::Unknown; } +TTtlEvictToExternalStorageAction::TTtlEvictToExternalStorageAction(const TString& storageName) + : Storage_(storageName) +{} + +void TTtlEvictToExternalStorageAction::SerializeTo(Ydb::Table::EvictionToExternalStorageSettings& proto) const { + proto.set_storage(Storage_); +} + +TString TTtlEvictToExternalStorageAction::GetStorage() const { + return Storage_; +} + +TTtlSettings::TTtlSettings(const TVector& tiers) + : Tiers_(tiers) +{} + TTtlSettings::TTtlSettings(const TString& columnName, const TDuration& expireAfter) - : Mode_(TDateTypeColumnModeSettings(columnName, expireAfter)) + : TTtlSettings({TTtlTierSettings(TDateTypeColumnModeSettings(columnName, expireAfter), TTtlDeleteAction())}) {} TTtlSettings::TTtlSettings(const Ydb::Table::DateTypeColumnModeSettings& mode, ui32 runIntervalSeconds) - : TTtlSettings(mode.column_name(), TDuration::Seconds(mode.expire_after_seconds())) -{ + : TTtlSettings(mode.column_name(), TDuration::Seconds(mode.expire_after_seconds())) { RunInterval_ = TDuration::Seconds(runIntervalSeconds); } const TDateTypeColumnModeSettings& TTtlSettings::GetDateTypeColumn() const { - return std::get(Mode_); + return std::get(Tiers_.front().GetExpression()); } TTtlSettings::TTtlSettings(const TString& columnName, EUnit columnUnit, const TDuration& expireAfter) - : Mode_(TValueSinceUnixEpochModeSettings(columnName, columnUnit, expireAfter)) + : TTtlSettings({TTtlTierSettings(TValueSinceUnixEpochModeSettings(columnName, columnUnit, expireAfter), TTtlDeleteAction())}) {} TTtlSettings::TTtlSettings(const Ydb::Table::ValueSinceUnixEpochModeSettings& mode, ui32 runIntervalSeconds) - : TTtlSettings(mode.column_name(), TProtoAccessor::FromProto(mode.column_unit()), TDuration::Seconds(mode.expire_after_seconds())) -{ + : TTtlSettings(mode.column_name(), TProtoAccessor::FromProto(mode.column_unit()), TDuration::Seconds(mode.expire_after_seconds())) { RunInterval_ = TDuration::Seconds(runIntervalSeconds); } const TValueSinceUnixEpochModeSettings& TTtlSettings::GetValueSinceUnixEpoch() const { - return std::get(Mode_); + return std::get(Tiers_.front().GetExpression()); +} + +std::optional TTtlSettings::FromProto(const Ydb::Table::TtlSettings& proto) { + switch(proto.mode_case()) { + case Ydb::Table::TtlSettings::kDateTypeColumn: + return TTtlSettings(proto.date_type_column(), proto.run_interval_seconds()); + case Ydb::Table::TtlSettings::kValueSinceUnixEpoch: + return TTtlSettings(proto.value_since_unix_epoch(), proto.run_interval_seconds()); + case Ydb::Table::TtlSettings::kTieredTtl: { + TVector tiers; + for (const auto& tier : proto.tiered_ttl().tiers()) { + if (auto deserialized = TTtlTierSettings::FromProto(tier)) { + tiers.emplace_back(std::move(*deserialized)); + } else { + return std::nullopt; + } + } + auto settings = TTtlSettings(std::move(tiers)); + settings.SetRunInterval(TDuration::Seconds(proto.run_interval_seconds())); + return settings; + } + case Ydb::Table::TtlSettings::MODE_NOT_SET: + return std::nullopt; + break; + } } void TTtlSettings::SerializeTo(Ydb::Table::TtlSettings& proto) const { - switch (GetMode()) { - case EMode::DateTypeColumn: - GetDateTypeColumn().SerializeTo(*proto.mutable_date_type_column()); - break; - case EMode::ValueSinceUnixEpoch: - GetValueSinceUnixEpoch().SerializeTo(*proto.mutable_value_since_unix_epoch()); - break; + if (Tiers_.size() == 1 && std::holds_alternative(Tiers_.back().GetAction())) { + // serialize DELETE-only TTL to legacy format for backwards-compatibility + std::visit(TOverloaded{ + [&proto](const TDateTypeColumnModeSettings& expr) { expr.SerializeTo(*proto.mutable_date_type_column()); }, + [&proto](const TValueSinceUnixEpochModeSettings& expr) { expr.SerializeTo(*proto.mutable_value_since_unix_epoch()); }, + }, + Tiers_.front().GetExpression()); + } else { + for (const auto& tier : Tiers_) { + tier.SerializeTo(*proto.mutable_tiered_ttl()->add_tiers()); + } } if (RunInterval_) { @@ -2869,7 +2942,7 @@ void TTtlSettings::SerializeTo(Ydb::Table::TtlSettings& proto) const { } TTtlSettings::EMode TTtlSettings::GetMode() const { - return static_cast(Mode_.index()); + return static_cast(Tiers_.front().GetExpression().index()); } TTtlSettings& TTtlSettings::SetRunInterval(const TDuration& value) { @@ -2881,6 +2954,10 @@ const TDuration& TTtlSettings::GetRunInterval() const { return RunInterval_; } +const TVector& TTtlSettings::GetTiers() const { + return Tiers_; +} + TAlterTtlSettings::EAction TAlterTtlSettings::GetAction() const { return static_cast(Action_.index()); } diff --git a/ydb/public/sdk/cpp/client/ydb_table/table.h b/ydb/public/sdk/cpp/client/ydb_table/table.h index de2b387ab4e6..2acd278b3c5e 100644 --- a/ydb/public/sdk/cpp/client/ydb_table/table.h +++ b/ydb/public/sdk/cpp/client/ydb_table/table.h @@ -28,9 +28,11 @@ class GlobalIndexSettings; class PartitioningSettings; class DateTypeColumnModeSettings; class TtlSettings; +class TtlTier; class TableIndex; class TableIndexDescription; class ValueSinceUnixEpochModeSettings; +class EvictionToExternalStorageSettings; } // namespace Table } // namespace Ydb @@ -359,7 +361,7 @@ struct TPartitionStats { class TDateTypeColumnModeSettings { public: - explicit TDateTypeColumnModeSettings(const TString& columnName, const TDuration& expireAfter); + explicit TDateTypeColumnModeSettings(const TString& columnName, const TDuration& applyAfter); void SerializeTo(Ydb::Table::DateTypeColumnModeSettings& proto) const; const TString& GetColumnName() const; @@ -367,7 +369,7 @@ class TDateTypeColumnModeSettings { private: TString ColumnName_; - TDuration ExpireAfter_; + TDuration ApplyAfter_; }; class TValueSinceUnixEpochModeSettings { @@ -382,7 +384,7 @@ class TValueSinceUnixEpochModeSettings { }; public: - explicit TValueSinceUnixEpochModeSettings(const TString& columnName, EUnit columnUnit, const TDuration& expireAfter); + explicit TValueSinceUnixEpochModeSettings(const TString& columnName, EUnit columnUnit, const TDuration& applyAfter); void SerializeTo(Ydb::Table::ValueSinceUnixEpochModeSettings& proto) const; const TString& GetColumnName() const; @@ -396,11 +398,56 @@ class TValueSinceUnixEpochModeSettings { private: TString ColumnName_; EUnit ColumnUnit_; - TDuration ExpireAfter_; + TDuration ApplyAfter_; +}; + +class TTtlDeleteAction {}; + +class TTtlEvictToExternalStorageAction { +public: + TTtlEvictToExternalStorageAction(const TString& storageName); + void SerializeTo(Ydb::Table::EvictionToExternalStorageSettings& proto) const; + + TString GetStorage() const; + +private: + TString Storage_; +}; + +class TTtlTierSettings { +public: + using TExpression = std::variant< + TDateTypeColumnModeSettings, + TValueSinceUnixEpochModeSettings + >; + + using TAction = std::variant< + TTtlDeleteAction, + TTtlEvictToExternalStorageAction + >; + +public: + explicit TTtlTierSettings(const TExpression& expression, const TAction& action); + + static std::optional FromProto(const Ydb::Table::TtlTier& tier); + void SerializeTo(Ydb::Table::TtlTier& proto) const; + + const TExpression& GetExpression() const; + const TAction& GetAction() const; + +private: + TExpression Expression_; + TAction Action_; }; //! Represents ttl settings class TTtlSettings { +private: + using TMode = std::variant< + TDateTypeColumnModeSettings, + TValueSinceUnixEpochModeSettings + >; + public: using EUnit = TValueSinceUnixEpochModeSettings::EUnit; @@ -409,25 +456,27 @@ class TTtlSettings { ValueSinceUnixEpoch = 1, }; + explicit TTtlSettings(const TVector& tiers); + explicit TTtlSettings(const TString& columnName, const TDuration& expireAfter); - explicit TTtlSettings(const Ydb::Table::DateTypeColumnModeSettings& mode, ui32 runIntervalSeconds); const TDateTypeColumnModeSettings& GetDateTypeColumn() const; + explicit TTtlSettings(const Ydb::Table::DateTypeColumnModeSettings& mode, ui32 runIntervalSeconds); explicit TTtlSettings(const TString& columnName, EUnit columnUnit, const TDuration& expireAfter); - explicit TTtlSettings(const Ydb::Table::ValueSinceUnixEpochModeSettings& mode, ui32 runIntervalSeconds); const TValueSinceUnixEpochModeSettings& GetValueSinceUnixEpoch() const; + explicit TTtlSettings(const Ydb::Table::ValueSinceUnixEpochModeSettings& mode, ui32 runIntervalSeconds); + static std::optional FromProto(const Ydb::Table::TtlSettings& proto); void SerializeTo(Ydb::Table::TtlSettings& proto) const; EMode GetMode() const; TTtlSettings& SetRunInterval(const TDuration& value); const TDuration& GetRunInterval() const; + const TVector& GetTiers() const; + private: - std::variant< - TDateTypeColumnModeSettings, - TValueSinceUnixEpochModeSettings - > Mode_; + TVector Tiers_; TDuration RunInterval_ = TDuration::Zero(); }; @@ -546,6 +595,7 @@ class TTableDescription { TVector GetIndexDescriptions() const; TVector GetChangefeedDescriptions() const; TMaybe GetTtlSettings() const; + // Deprecated. Use GetTtlSettings() instead TMaybe GetTiering() const; EStoreType GetStoreType() const; diff --git a/ydb/services/bg_tasks/abstract/interface.h b/ydb/services/bg_tasks/abstract/interface.h index 75f6d44c7ce6..6e57260ee4ac 100644 --- a/ydb/services/bg_tasks/abstract/interface.h +++ b/ydb/services/bg_tasks/abstract/interface.h @@ -103,36 +103,21 @@ class IProtoStringSerializable: public TBaseClass { }; template -class TCommonInterfaceContainer { +class TControlInterfaceContainer { protected: std::shared_ptr Object; - using TFactory = typename IInterface::TFactory; public: - TCommonInterfaceContainer() = default; - TCommonInterfaceContainer(std::shared_ptr object) + TControlInterfaceContainer() = default; + TControlInterfaceContainer(std::shared_ptr object) : Object(object) { } template - TCommonInterfaceContainer(std::shared_ptr object) + TControlInterfaceContainer(std::shared_ptr object) : Object(object) { static_assert(std::is_base_of::value); } - bool Initialize(const TString& className, const bool maybeExists = false) { - AFL_VERIFY(maybeExists || !Object)("problem", "initialize for not-empty-object"); - Object.reset(TFactory::Construct(className)); - if (!Object) { - ALS_ERROR(NKikimrServices::BG_TASKS) << "incorrect class name: " << className << " for " << typeid(IInterface).name(); - return false; - } - return true; - } - - TString GetClassName() const { - return Object ? Object->GetClassName() : "UNDEFINED"; - } - bool HasObject() const { return !!Object; } @@ -163,6 +148,12 @@ class TCommonInterfaceContainer { return result; } + template + std::shared_ptr GetObjectPtrOptionalAs() const { + auto result = std::dynamic_pointer_cast(Object); + return result; + } + const IInterface& GetObjectVerified() const { AFL_VERIFY(Object); return *Object; @@ -188,6 +179,32 @@ class TCommonInterfaceContainer { }; +template +class TCommonInterfaceContainer: public TControlInterfaceContainer { +private: + using TBase = TControlInterfaceContainer; +protected: + using TFactory = typename IInterface::TFactory; + using TBase::Object; +public: + using TBase::TBase; + + bool Initialize(const TString& className, const bool maybeExists = false) { + AFL_VERIFY(maybeExists || !Object)("problem", "initialize for not-empty-object"); + Object.reset(TFactory::Construct(className)); + if (!Object) { + ALS_ERROR(NKikimrServices::BG_TASKS) << "incorrect class name: " << className << " for " << typeid(IInterface).name(); + return false; + } + return true; + } + + TString GetClassName() const { + return Object ? Object->GetClassName() : "UNDEFINED"; + } + +}; + class TStringContainerProcessor { public: static bool DeserializeFromContainer(const TString& data, TString& className, TString& binary); diff --git a/ydb/services/ext_index/metadata/object.cpp b/ydb/services/ext_index/metadata/object.cpp index 609e0357cc19..5e7ac93ac6f2 100644 --- a/ydb/services/ext_index/metadata/object.cpp +++ b/ydb/services/ext_index/metadata/object.cpp @@ -1,5 +1,8 @@ -#include "object.h" #include "behaviour.h" +#include "object.h" + +#include + #include #include @@ -73,9 +76,14 @@ bool TObject::TryProvideTtl(const NKikimrSchemeOp::TColumnTableDescription& csDe return false; } if (cRequest) { - auto& newTtl = *cRequest->mutable_ttl_settings()->mutable_date_type_column(); - newTtl.set_column_name("pk_" + ttl.GetColumnName()); - newTtl.set_expire_after_seconds(ttl.GetExpireAfterSeconds()); + auto newTtl = ttl; + newTtl.SetColumnName("pk_" + ttl.GetColumnName()); + + Ydb::StatusIds::StatusCode status; + TString error; + if (!FillTtlSettings(*cRequest->mutable_ttl_settings(), newTtl, status, error)) { + return false; + } } } return true; diff --git a/ydb/services/ext_index/metadata/ya.make b/ydb/services/ext_index/metadata/ya.make index 3f3dbbc69192..9d2cf0e9b8fa 100644 --- a/ydb/services/ext_index/metadata/ya.make +++ b/ydb/services/ext_index/metadata/ya.make @@ -15,6 +15,7 @@ PEERDIR( ydb/core/grpc_services/local_rpc ydb/core/grpc_services/base ydb/core/grpc_services + ydb/core/ydb_convert ydb/services/metadata/request ydb/services/ext_index/metadata/extractor ) diff --git a/ydb/services/ext_index/ut/ut_ext_index.cpp b/ydb/services/ext_index/ut/ut_ext_index.cpp index ec67f99c8478..2083968943aa 100644 --- a/ydb/services/ext_index/ut/ut_ext_index.cpp +++ b/ydb/services/ext_index/ut/ut_ext_index.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -25,8 +24,6 @@ namespace NKikimr { -using namespace NColumnShard; - class TLocalHelper: public Tests::NCS::THelper { private: using TBase = Tests::NCS::THelper; diff --git a/ydb/services/metadata/initializer/ut/ut_init.cpp b/ydb/services/metadata/initializer/ut/ut_init.cpp index bce2dd7a12f7..5207e20e9910 100644 --- a/ydb/services/metadata/initializer/ut/ut_init.cpp +++ b/ydb/services/metadata/initializer/ut/ut_init.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -28,8 +27,6 @@ namespace NKikimr { -using namespace NColumnShard; - Y_UNIT_TEST_SUITE(Initializer) { class TTestInitializer: public NMetadata::NInitializer::IInitializationBehaviour { diff --git a/ydb/services/metadata/manager/abstract.h b/ydb/services/metadata/manager/abstract.h index 65b6759628bc..1e8e5262d3e3 100644 --- a/ydb/services/metadata/manager/abstract.h +++ b/ydb/services/metadata/manager/abstract.h @@ -4,13 +4,15 @@ #include #include + #include #include -#include +#include #include - +#include #include #include +#include #include #include @@ -168,6 +170,11 @@ class IObjectOperationsManager: public IOperationsManager { const TInternalModificationContext& context, const TAlterOperationContext& alterContext) const { return DoPrepareObjectsBeforeModification(std::move(patchedObjects), controller, context, alterContext); } + + virtual std::vector GetPreconditions( + const std::vector& /*objects*/, const IOperationsManager::TInternalModificationContext& /*context*/) const { + return {}; + } }; class IObjectModificationCommand { diff --git a/ydb/services/metadata/manager/alter.h b/ydb/services/metadata/manager/alter.h index 481675db202d..326c9d8d32de 100644 --- a/ydb/services/metadata/manager/alter.h +++ b/ydb/services/metadata/manager/alter.h @@ -15,8 +15,8 @@ class TUpdateObjectActor: public TModificationActor { using TBase = TModificationActor; protected: virtual bool ProcessPreparedObjects(NInternal::TTableRecords&& records) const override { - TBase::Register(new TUpdateObjectsActor(std::move(records), TBase::UserToken, - TBase::InternalController, TBase::SessionId, TBase::TransactionId, TBase::Context.GetExternalData().GetUserToken())); + TBase::Register(new TUpdateObjectsActor(std::move(records), TBase::UserToken, TBase::InternalController, TBase::SessionId, + TBase::TransactionId, TBase::Context.GetExternalData().GetUserToken(), TBase::Preconditions)); return true; } @@ -33,9 +33,8 @@ class TUpsertObjectActor: public TModificationActor { using TBase = TModificationActor; protected: virtual bool ProcessPreparedObjects(NInternal::TTableRecords&& records) const override { - TBase::Register(new TUpsertObjectsActor(std::move(records), TBase::UserToken, - TBase::InternalController, TBase::SessionId, TBase::TransactionId, - TBase::Context.GetExternalData().GetUserToken())); + TBase::Register(new TUpsertObjectsActor(std::move(records), TBase::UserToken, TBase::InternalController, TBase::SessionId, + TBase::TransactionId, TBase::Context.GetExternalData().GetUserToken(), TBase::Preconditions)); return true; } @@ -53,9 +52,8 @@ class TCreateObjectActor: public TModificationActor { bool ExistingOk = false; protected: virtual bool ProcessPreparedObjects(NInternal::TTableRecords&& records) const override { - TBase::Register(new TInsertObjectsActor(std::move(records), TBase::UserToken, - TBase::InternalController, TBase::SessionId, TBase::TransactionId, - TBase::Context.GetExternalData().GetUserToken(), ExistingOk)); + TBase::Register(new TInsertObjectsActor(std::move(records), TBase::UserToken, TBase::InternalController, TBase::SessionId, + TBase::TransactionId, TBase::Context.GetExternalData().GetUserToken(), TBase::Preconditions, ExistingOk)); return true; } @@ -103,8 +101,8 @@ class TDeleteObjectActor: public TModificationActor { using TBase::TBase; virtual bool ProcessPreparedObjects(NInternal::TTableRecords&& records) const override { - TBase::Register(new TDeleteObjectsActor(std::move(records), TBase::UserToken, - TBase::InternalController, TBase::SessionId, TBase::TransactionId, TBase::Context.GetExternalData().GetUserToken())); + TBase::Register(new TDeleteObjectsActor(std::move(records), TBase::UserToken, TBase::InternalController, TBase::SessionId, + TBase::TransactionId, TBase::Context.GetExternalData().GetUserToken(), TBase::Preconditions)); return true; } diff --git a/ydb/services/metadata/manager/alter_impl.h b/ydb/services/metadata/manager/alter_impl.h index 434ab1c8b7f0..7894e685ecb2 100644 --- a/ydb/services/metadata/manager/alter_impl.h +++ b/ydb/services/metadata/manager/alter_impl.h @@ -58,6 +58,7 @@ class TModificationActorImpl: public NActors::TActorBootstrapped::TPtr Manager; const IOperationsManager::TInternalModificationContext Context; std::vector Patches; + std::vector Preconditions; NInternal::TTableRecords RestoreObjectIds; const NACLib::TUserToken UserToken = NACLib::TSystemUsers::Metadata(); virtual bool PrepareRestoredObjects(std::vector& objects) const = 0; @@ -179,6 +180,7 @@ class TModificationActorImpl: public NActors::TActorBootstrapped::TPtr& ev) { + Preconditions = Manager->GetPreconditions(ev->Get()->GetObjects(), Context); NInternal::TTableRecords records; records.InitColumns(Manager->GetSchema().GetYDBColumns()); records.ReserveRows(ev->Get()->GetObjects().size()); diff --git a/ydb/services/metadata/manager/modification.h b/ydb/services/metadata/manager/modification.h index 382fd23f6f27..17dbd306a5f5 100644 --- a/ydb/services/metadata/manager/modification.h +++ b/ydb/services/metadata/manager/modification.h @@ -10,6 +10,32 @@ namespace NKikimr::NMetadata::NModifications { +class TModificationStage { +private: + YDB_ACCESSOR_DEF(Ydb::Table::ExecuteDataQueryRequest, Request); + +public: + using TPtr = std::shared_ptr; + + virtual TConclusionStatus HandleResult(const Ydb::Table::ExecuteQueryResult& /*result*/) const { + return TConclusionStatus::Success(); + } + + virtual TConclusionStatus HandleError(const NRequest::TEvRequestFailed& ev) const { + return TConclusionStatus::Fail(ev.GetErrorMessage()); + } + + void SetCommit() { + Request.mutable_tx_control()->set_commit_tx(true); + } + + TModificationStage(Ydb::Table::ExecuteDataQueryRequest request) + : Request(std::move(request)) { + } + + virtual ~TModificationStage() = default; +}; + template class TModifyObjectsActor: public NActors::TActorBootstrapped> { private: @@ -19,17 +45,44 @@ class TModifyObjectsActor: public NActors::TActorBootstrapped UserToken; + + void FillRequestSettings(Ydb::Table::ExecuteDataQueryRequest& request) { + request.set_session_id(SessionId); + request.mutable_tx_control()->set_tx_id(TransactionId); + } + + void AdvanceStage() { + AFL_VERIFY(!Stages.empty()); + Stages.pop_front(); + if (Stages.size()) { + TBase::Register( + new NRequest::TYDBCallbackRequest(Stages.front()->GetRequest(), SystemUserToken, TBase::SelfId())); + } else { + Controller->OnModificationFinished(); + TBase::PassAway(); + } + } + protected: - std::deque Requests; + std::deque Stages; NInternal::TTableRecords Objects; virtual Ydb::Table::ExecuteDataQueryRequest BuildModifyQuery() const = 0; virtual TString GetModifyType() const = 0; + virtual TModificationStage::TPtr DoBuildRequestDirect(Ydb::Table::ExecuteDataQueryRequest query) const { + return std::make_shared(std::move(query)); + } + + void BuildPreconditionStages(const std::vector& stages) { + for (auto&& stage : stages) { + FillRequestSettings(stage->MutableRequest()); + Stages.emplace_back(std::move(stage)); + } + } void BuildRequestDirect() { Ydb::Table::ExecuteDataQueryRequest request = BuildModifyQuery(); - request.set_session_id(SessionId); - request.mutable_tx_control()->set_tx_id(TransactionId); - Requests.emplace_back(std::move(request)); + FillRequestSettings(request); + Stages.emplace_back(DoBuildRequestDirect(request)); } void BuildRequestHistory() { @@ -42,31 +95,39 @@ class TModifyObjectsActor: public NActors::TActorBootstrappedGetStorageHistoryTablePath()); - request.set_session_id(SessionId); - request.mutable_tx_control()->set_tx_id(TransactionId); - Requests.emplace_back(std::move(request)); + FillRequestSettings(request); + Stages.emplace_back(std::make_shared(std::move(request))); } - void Handle(NRequest::TEvRequestResult::TPtr& /*ev*/) { - if (Requests.size()) { - TBase::Register(new NRequest::TYDBCallbackRequest( - Requests.front(), SystemUserToken, TBase::SelfId())); - Requests.pop_front(); - } else { - Controller->OnModificationFinished(); + void Handle(NRequest::TEvRequestResult::TPtr& ev) { + const auto& operation = ev->Get()->GetResult().operation(); + AFL_VERIFY(operation.ready()); + + Ydb::Table::ExecuteQueryResult result; + operation.result().UnpackTo(&result); + + if (auto status = Stages.front()->HandleResult(result); status.IsFail()) { + Controller->OnModificationProblem(status.GetErrorMessage()); TBase::PassAway(); + return; } + + AdvanceStage(); } - virtual void Handle(NRequest::TEvRequestFailed::TPtr& ev) { + void Handle(NRequest::TEvRequestFailed::TPtr& ev) { auto g = TBase::PassAwayGuard(); - Controller->OnModificationProblem("cannot execute yql request for " + GetModifyType() + - " objects: " + ev->Get()->GetErrorMessage()); + if (auto status = Stages.front()->HandleError(*ev->Get()); status.IsFail()) { + Controller->OnModificationProblem(status.GetErrorMessage()); + } else { + Controller->OnModificationFinished(); + } } public: - TModifyObjectsActor(NInternal::TTableRecords&& objects, const NACLib::TUserToken& systemUserToken, IModificationObjectsController::TPtr controller, const TString& sessionId, - const TString& transactionId, const std::optional& userToken) + TModifyObjectsActor(NInternal::TTableRecords&& objects, const NACLib::TUserToken& systemUserToken, + IModificationObjectsController::TPtr controller, const TString& sessionId, const TString& transactionId, + const std::optional& userToken, const std::vector& preconditions) : Controller(controller) , SessionId(sessionId) , TransactionId(transactionId) @@ -75,6 +136,7 @@ class TModifyObjectsActor: public NActors::TActorBootstrappedset_commit_tx(true); + Y_ABORT_UNLESS(Stages.size()); + Stages.back()->SetCommit(); - TBase::Register(new NRequest::TYDBCallbackRequest( - Requests.front(), SystemUserToken, TBase::SelfId())); - Requests.pop_front(); + TBase::Register(new NRequest::TYDBCallbackRequest(Stages.front()->GetRequest(), SystemUserToken, TBase::SelfId())); } }; @@ -148,6 +208,23 @@ class TDeleteObjectsActor: public TModifyObjectsActor { using TBase::TBase; }; +class TStageInsertObjects: public NModifications::TModificationStage { +private: + const bool ExistingOk; + +public: + TConclusionStatus HandleError(const NRequest::TEvRequestFailed& ev) const override { + if (ExistingOk && ev.GetStatus() == Ydb::StatusIds::PRECONDITION_FAILED) { + return TConclusionStatus::Success(); + } + return TConclusionStatus::Fail(ev.GetErrorMessage()); + } + + TStageInsertObjects(Ydb::Table::ExecuteDataQueryRequest request, const bool existingOk) + : TModificationStage(std::move(request)), ExistingOk(existingOk) { + } +}; + template class TInsertObjectsActor: public TModifyObjectsActor { private: @@ -161,19 +238,13 @@ class TInsertObjectsActor: public TModifyObjectsActor { return "insert"; } - void Handle(NRequest::TEvRequestFailed::TPtr& ev) override { - if (ev->Get()->GetStatus() == Ydb::StatusIds::PRECONDITION_FAILED && ExistingOk) { - NRequest::TDialogYQLRequest::TResponse resp; - this->Send(this->SelfId(), new NRequest::TEvRequestResult(std::move(resp))); - this->Requests.clear(); // Remove history request - return; - } - TBase::Handle(ev); + TModificationStage::TPtr DoBuildRequestDirect(Ydb::Table::ExecuteDataQueryRequest query) const override { + return std::make_shared(std::move(query), ExistingOk); } public: TInsertObjectsActor(NInternal::TTableRecords&& objects, const NACLib::TUserToken& systemUserToken, IModificationObjectsController::TPtr controller, const TString& sessionId, - const TString& transactionId, const std::optional& userToken, bool existingOk) - : TBase(std::move(objects), systemUserToken, std::move(controller), sessionId, transactionId, userToken) + const TString& transactionId, const std::optional& userToken, const std::vector& preconditions, bool existingOk) + : TBase(std::move(objects), systemUserToken, std::move(controller), sessionId, transactionId, userToken, preconditions) , ExistingOk(existingOk) { } diff --git a/ydb/services/metadata/request/request_actor_cb.h b/ydb/services/metadata/request/request_actor_cb.h index 07bea21c9992..71792903f7a3 100644 --- a/ydb/services/metadata/request/request_actor_cb.h +++ b/ydb/services/metadata/request/request_actor_cb.h @@ -10,7 +10,9 @@ #include #include #include -#include +#include +#include +#include namespace NKikimr::NMetadata::NRequest { @@ -64,15 +66,18 @@ class IChainController: public IExternalController { std::shared_ptr NextController; const NACLib::TUserToken UserToken; protected: - TConclusion BuildNextRequest(typename TCurrentDialogPolicy::TResponse&& result) const { + using TYdbConclusionStatus = TConclusionSpecialStatus; + using TRequestConclusion = TConclusionImpl; + + TRequestConclusion BuildNextRequest(typename TCurrentDialogPolicy::TResponse&& result) const { return DoBuildNextRequest(std::move(result)); } - virtual TConclusion DoBuildNextRequest(typename TCurrentDialogPolicy::TResponse&& result) const = 0; + virtual TRequestConclusion DoBuildNextRequest(typename TCurrentDialogPolicy::TResponse&& result) const = 0; public: using TDialogPolicy = TCurrentDialogPolicy; virtual void OnRequestResult(typename TCurrentDialogPolicy::TResponse&& result) override { - TConclusion nextRequest = BuildNextRequest(std::move(result)); + TRequestConclusion nextRequest = BuildNextRequest(std::move(result)); if (!nextRequest) { OnRequestFailed(nextRequest.GetStatus(), nextRequest.GetErrorMessage()); } else { @@ -113,14 +118,14 @@ class TSessionedChainController: public IChainController DoBuildNextRequest(TDialogCreateSession::TResponse&& response) const override { + virtual TBase::TRequestConclusion DoBuildNextRequest(TDialogCreateSession::TResponse&& response) const override { auto result = ProtoRequest; Ydb::Table::CreateSessionResponse currentFullReply = std::move(response); Ydb::Table::CreateSessionResult session; currentFullReply.operation().result().UnpackTo(&session); const TString sessionId = session.session_id(); if (!sessionId) { - return TConclusionStatus::Fail("cannot build session for request"); + return TBase::TYdbConclusionStatus::Fail("cannot build session for request"); } result.set_session_id(sessionId); SessionContext->SetSessionId(sessionId); diff --git a/ydb/services/metadata/secret/accessor/secret_id.cpp b/ydb/services/metadata/secret/accessor/secret_id.cpp new file mode 100644 index 000000000000..0dc4e50e8a8d --- /dev/null +++ b/ydb/services/metadata/secret/accessor/secret_id.cpp @@ -0,0 +1,32 @@ +#include "secret_id.h" + +#include +#include + +namespace NKikimr::NMetadata::NSecret { + +TString TSecretId::SerializeToString() const { + TStringBuilder sb; + sb << "USId:" << OwnerUserId << ":" << SecretId; + return sb; +} + +TString TSecretIdOrValue::DebugString() const { + return std::visit(TOverloaded( + [](std::monostate) -> TString{ + return "__NONE__"; + }, + [](const TSecretId& id) -> TString{ + return id.SerializeToString(); + }, + [](const TSecretName& name) -> TString{ + return name.SerializeToString(); + }, + [](const TString& value) -> TString{ + return MD5::Calc(value); + } + ), + State); +} + +} diff --git a/ydb/services/metadata/secret/accessor/secret_id.h b/ydb/services/metadata/secret/accessor/secret_id.h new file mode 100644 index 000000000000..f972bd6c59ae --- /dev/null +++ b/ydb/services/metadata/secret/accessor/secret_id.h @@ -0,0 +1,223 @@ +#pragma once +#include + +#include +#include + +namespace NKikimr::NMetadata::NSecret { + +class TSecretId { +private: + YDB_READONLY_PROTECT_DEF(TString, OwnerUserId); + YDB_READONLY_PROTECT_DEF(TString, SecretId); + +public: + inline static const TString PrefixWithUser = "USId:"; + + TSecretId() = default; + TSecretId(const TString& ownerUserId, const TString& secretId) + : OwnerUserId(ownerUserId) + , SecretId(secretId) { + } + + TSecretId(const TStringBuf ownerUserId, const TStringBuf secretId) + : OwnerUserId(ownerUserId) + , SecretId(secretId) { + } + + TString SerializeToString() const; + + template + TString BuildSecretAccessString(const TProto& proto, const TString& defaultOwnerId) { + if (proto.HasValue()) { + return proto.GetValue(); + } else { + return TStringBuilder() << PrefixWithUser << (proto.GetSecretOwnerId() ? proto.GetSecretOwnerId() : defaultOwnerId) << ":" << SecretId; + } + } + + bool operator<(const TSecretId& item) const { + return std::tie(OwnerUserId, SecretId) < std::tie(item.OwnerUserId, item.SecretId); + } + bool operator==(const TSecretId& item) const { + return std::tie(OwnerUserId, SecretId) == std::tie(item.OwnerUserId, item.SecretId); + } +}; + +class TSecretName { +private: + YDB_READONLY_DEF(TString, SecretId); + +public: + inline static const TString PrefixNoUser = "SId:"; + + TSecretName() = default; + TSecretName(const TString& secretId) : SecretId(secretId) {} + + TString SerializeToString() const { + return TStringBuilder() << "SId:" << SecretId; + } + + bool DeserializeFromString(const TString& secretString) { + if (secretString.StartsWith(PrefixNoUser)) { + SecretId = secretString.substr(PrefixNoUser.size()); + return true; + } + return false; + } +}; + +class TSecretIdOrValue { +private: + using TState = std::variant; + YDB_READONLY_DEF(TState, State); + +private: + TSecretIdOrValue() = default; + + bool DeserializeFromStringImpl(const TString& info, const TString& defaultUserId = "") { + if (info.StartsWith(TSecretId::PrefixWithUser)) { + TStringBuf sb(info.data(), info.size()); + sb.Skip(TSecretId::PrefixWithUser.size()); + TStringBuf uId; + TStringBuf sId; + if (!sb.TrySplit(':', uId, sId)) { + return false; + } + if (!uId || !sId) { + return false; + } + State = TSecretId(uId, sId); + } else if (info.StartsWith(TSecretName::PrefixNoUser)) { + TStringBuf sb(info.data(), info.size()); + sb.Skip(TSecretName::PrefixNoUser.size()); + if (!sb) { + return false; + } + if (defaultUserId) { + State = TSecretId(defaultUserId, TString(sb)); + } else { + State = TSecretName(TString(sb)); + } + } else { + State = info; + } + return true; + } + + explicit TSecretIdOrValue(const TSecretId& id) + : State(id) { + } + explicit TSecretIdOrValue(const TSecretName& id) + : State(id) { + } + explicit TSecretIdOrValue(const TString& value) + : State(value) { + } + +public: + bool operator!() const { + return std::holds_alternative(State); + } + + static TSecretIdOrValue BuildAsValue(const TString& value) { + return TSecretIdOrValue(value); + } + + static TSecretIdOrValue BuildEmpty() { + return TSecretIdOrValue(); + } + + static TSecretIdOrValue BuildAsId(const TSecretId& id) { + return TSecretIdOrValue(id); + } + + static TSecretIdOrValue BuildAsId(const TSecretName& id) { + return TSecretIdOrValue(id); + } + + static std::optional DeserializeFromOptional( + const NKikimrSchemeOp::TSecretableVariable& proto, const TString& secretInfo, const TString& defaultOwnerId = Default()) { + if (proto.HasSecretId()) { + return DeserializeFromProto(proto, defaultOwnerId); + } else if (proto.HasValue()) { + return DeserializeFromString(proto.GetValue().GetData()); + } + if (secretInfo) { + return DeserializeFromString(secretInfo, defaultOwnerId); + } else { + return {}; + } + } + + NKikimrSchemeOp::TSecretableVariable SerializeToProto() const { + NKikimrSchemeOp::TSecretableVariable result; + std::visit(TOverloaded( + [](std::monostate){ }, + [&result](const TSecretId& id){ + result.MutableSecretId()->SetId(id.GetSecretId()); + result.MutableSecretId()->SetOwnerId(id.GetOwnerUserId()); + }, + [&result](const TSecretName& name){ + result.MutableSecretId()->SetId(name.GetSecretId()); + }, + [&result](const TString& value){ + result.MutableValue()->SetData(value); + } + ), + State); + return result; + } + + static std::optional DeserializeFromProto( + const NKikimrSchemeOp::TSecretableVariable& proto, const TString& defaultOwnerId = Default()) { + if (proto.HasSecretId()) { + TString ownerId; + TString secretId; + if (!proto.GetSecretId().HasOwnerId() || !proto.GetSecretId().GetOwnerId()) { + ownerId = defaultOwnerId; + } else { + ownerId = proto.GetSecretId().GetOwnerId(); + } + secretId = proto.GetSecretId().GetId(); + if (!ownerId || !secretId) { + return {}; + } + return TSecretIdOrValue::BuildAsId(TSecretId(ownerId, secretId)); + } else if (proto.HasValue()) { + return TSecretIdOrValue::BuildAsValue(proto.GetValue().GetData()); + } else { + return {}; + } + } + + static std::optional DeserializeFromString(const TString& info, const TString& defaultOwnerId = Default()) { + TSecretIdOrValue result; + if (!result.DeserializeFromStringImpl(info, defaultOwnerId)) { + return {}; + } else { + return result; + } + } + + TString SerializeToString() const { + return std::visit(TOverloaded( + [](std::monostate) -> TString{ + return ""; + }, + [](const TSecretId& id) -> TString{ + return TStringBuilder() << TSecretId::PrefixWithUser << id.GetOwnerUserId() << ":" << id.GetSecretId(); + }, + [](const TSecretName& name) -> TString{ + return TStringBuilder() << TSecretName::PrefixNoUser << name.GetSecretId(); + }, + [](const TString& value) -> TString{ + return value; + } + ), + State); + } + + TString DebugString() const; +}; +} // namespace NKikimr::NMetadata::NSecret diff --git a/ydb/services/metadata/secret/accessor/snapshot.h b/ydb/services/metadata/secret/accessor/snapshot.h new file mode 100644 index 000000000000..1c8d0179519c --- /dev/null +++ b/ydb/services/metadata/secret/accessor/snapshot.h @@ -0,0 +1,18 @@ +#pragma once + +#include "secret_id.h" + +#include +#include + +namespace NKikimr::NMetadata::NSecret { + +class ISecretAccessor { +public: + virtual bool CheckSecretAccess(const TSecretIdOrValue& sIdOrValue, const NACLib::TUserToken& userToken) const = 0; + virtual bool PatchString(TString& stringForPath) const = 0; + virtual TConclusion GetSecretValue(const TSecretIdOrValue& secretId) const = 0; + virtual std::vector GetSecretIds(const std::optional& userToken, const TString& secretId) const = 0; +}; + +} // namespace NKikimr::NMetadata::NSecret diff --git a/ydb/services/metadata/secret/accessor/ya.make b/ydb/services/metadata/secret/accessor/ya.make new file mode 100644 index 000000000000..5c748f1c2bdd --- /dev/null +++ b/ydb/services/metadata/secret/accessor/ya.make @@ -0,0 +1,13 @@ +LIBRARY() + +SRCS( + secret_id.cpp +) + +PEERDIR( + ydb/core/base + ydb/library/actors/core + ydb/library/aclib +) + +END() diff --git a/ydb/services/metadata/secret/checker_secret.cpp b/ydb/services/metadata/secret/checker_secret.cpp index b9d54935ea5a..e612d291aa35 100644 --- a/ydb/services/metadata/secret/checker_secret.cpp +++ b/ydb/services/metadata/secret/checker_secret.cpp @@ -8,21 +8,31 @@ namespace NKikimr::NMetadata::NSecret { void TSecretPreparationActor::StartChecker() { Y_ABORT_UNLESS(Secrets); auto g = PassAwayGuard(); - for (auto&& i : Objects) { + THashMap secretNameToOwner; + for (auto&& object : Objects) { + if (Context.GetActivityType() == NModifications::IOperationsManager::EActivityType::Create || + Context.GetActivityType() == NModifications::IOperationsManager::EActivityType::Upsert) { + const auto* findSecret = secretNameToOwner.FindPtr(object.GetSecretId()); + if (findSecret && *findSecret != object.GetOwnerUserId()) { + Controller->OnPreparationProblem("cannot create multiple secrets with same id: " + object.GetSecretId()); + return; + } + } if (Context.GetActivityType() == NModifications::IOperationsManager::EActivityType::Alter) { - if (!Secrets->GetSecrets().contains(i)) { - Controller->OnPreparationProblem("secret " + i.GetSecretId() + " not found for alter"); + if (!Secrets->GetSecrets().contains(object)) { + Controller->OnPreparationProblem("secret " + object.GetSecretId() + " not found for alter"); return; } } for (auto&& sa : Secrets->GetAccess()) { if (Context.GetActivityType() == NModifications::IOperationsManager::EActivityType::Drop) { - if (sa.GetOwnerUserId() == i.GetOwnerUserId() && sa.GetSecretId() == i.GetSecretId()) { - Controller->OnPreparationProblem("secret " + i.GetSecretId() + " using in access for " + sa.GetAccessSID()); + if (sa.GetOwnerUserId() == object.GetOwnerUserId() && sa.GetSecretId() == object.GetSecretId()) { + Controller->OnPreparationProblem("secret " + object.GetSecretId() + " using in access for " + sa.GetAccessSID()); return; } } } + secretNameToOwner.emplace(object.GetSecretId(), object.GetOwnerUserId()); } Controller->OnPreparationFinished(std::move(Objects)); } diff --git a/ydb/services/metadata/secret/initializer.cpp b/ydb/services/metadata/secret/initializer.cpp index 045f83898de3..4be1f480128d 100644 --- a/ydb/services/metadata/secret/initializer.cpp +++ b/ydb/services/metadata/secret/initializer.cpp @@ -33,6 +33,19 @@ void TSecretInitializer::DoPrepare(NInitializer::IInitializerInput::TPtr control } result.emplace_back(NInitializer::TACLModifierConstructor::GetNoAccessModifier(TSecret::GetBehaviour()->GetStorageTablePath(), "acl")); result.emplace_back(NInitializer::TACLModifierConstructor::GetNoAccessModifier(TSecret::GetBehaviour()->GetStorageHistoryTablePath(), "acl_history")); + { + Ydb::Table::AlterTableRequest request; + request.set_session_id(""); + request.set_path(TSecret::GetBehaviour()->GetStorageTablePath()); + { + auto& index = *request.add_add_indexes(); + index.set_name("index_by_secret_id"); + index.add_index_columns(TSecret::TDecoder::SecretId); + index.add_index_columns(TSecret::TDecoder::OwnerUserId); + index.mutable_global_index(); + } + result.emplace_back(new NInitializer::TGenericTableModifier(request, "add_index_by_secret_id")); + } controller->OnPreparationFinished(result); } diff --git a/ydb/services/metadata/secret/manager.cpp b/ydb/services/metadata/secret/manager.cpp index 882c282064f4..5d08d2e2c52e 100644 --- a/ydb/services/metadata/secret/manager.cpp +++ b/ydb/services/metadata/secret/manager.cpp @@ -1,10 +1,62 @@ #include "checker_access.h" #include "checker_secret.h" #include "manager.h" + #include +#include + namespace NKikimr::NMetadata::NSecret { +class TCheckSecretNameUnique: public NModifications::TModificationStage { +private: + THashMap SecretNameToOwner; + + static Ydb::Table::ExecuteDataQueryRequest BuildRequest(std::vector secrets) { + std::vector secretNameLiterals; + for (const auto& id : secrets) { + secretNameLiterals.push_back(TStringBuilder() << '"' << id.GetSecretId() << '"'); + } + + Ydb::Table::ExecuteDataQueryRequest request; + TStringBuilder sb; + sb << "SELECT " + TSecret::TDecoder::SecretId + ", " + TSecret::TDecoder::OwnerUserId + ", " + TSecret::TDecoder::Value << Endl; + sb << "FROM `" + TSecret::GetBehaviour()->GetStorageTablePath() + "`" << Endl; + sb << "VIEW index_by_secret_id" << Endl; + sb << "WHERE " + TSecret::TDecoder::SecretId + " IN (" + JoinStrings(secretNameLiterals.begin(), secretNameLiterals.end(), ", ") + ")" + << Endl; + AFL_DEBUG(NKikimrServices::METADATA_SECRET)("event", "build_precondition")("sql", sb); + request.mutable_query()->set_yql_text(sb); + return request; + } + +public: + TConclusionStatus HandleResult(const Ydb::Table::ExecuteQueryResult& result) const override { + AFL_VERIFY(result.result_sets_size() == 1)("size", result.result_sets_size()); + const auto& resultSet = result.result_sets(0); + TSecret::TDecoder decoder(resultSet); + + for (const auto& row : resultSet.rows()) { + TSecret secret; + AFL_VERIFY(secret.DeserializeFromRecord(decoder, row)); + auto findOwner = SecretNameToOwner.FindPtr(secret.GetSecretId()); + AFL_VERIFY(findOwner); + if (*findOwner != secret.GetOwnerUserId()) { + return TConclusionStatus::Fail("Secret already exists: " + secret.GetSecretId()); + } + } + + return TConclusionStatus::Success(); + } + + TCheckSecretNameUnique(std::vector secrets) + : TModificationStage(BuildRequest(secrets)) { + for (const auto& id : secrets) { + AFL_VERIFY(SecretNameToOwner.emplace(id.GetSecretId(), id.GetOwnerUserId()).second); + } + } +}; + void TAccessManager::DoPrepareObjectsBeforeModification(std::vector&& patchedObjects, NModifications::IAlterPreparationController::TPtr controller, const TInternalModificationContext& context, const NMetadata::NModifications::TAlterOperationContext& /*alterContext*/) const { if (context.GetActivityType() == IOperationsManager::EActivityType::Alter) { @@ -100,4 +152,16 @@ void TSecretManager::DoPrepareObjectsBeforeModification(std::vector&& p TActivationContext::Register(new TSecretPreparationActor(std::move(patchedObjects), controller, context)); } +std::vector TSecretManager::GetPreconditions( + const std::vector& objects, const IOperationsManager::TInternalModificationContext& context) const { + if (context.GetActivityType() == NModifications::IOperationsManager::EActivityType::Create || + context.GetActivityType() == NModifications::IOperationsManager::EActivityType::Upsert) { + std::vector secretIds; + for (const auto& secret : objects) { + secretIds.emplace_back(TSecretId(secret.GetOwnerUserId(), secret.GetSecretId())); + } + return { std::make_shared(std::move(secretIds)) }; + } + return {}; +} } diff --git a/ydb/services/metadata/secret/manager.h b/ydb/services/metadata/secret/manager.h index df231cdb65f0..a415f6c331e8 100644 --- a/ydb/services/metadata/secret/manager.h +++ b/ydb/services/metadata/secret/manager.h @@ -14,6 +14,9 @@ class TSecretManager: public NModifications::TGenericOperationsManager virtual NModifications::TOperationParsingResult DoBuildPatchFromSettings( const NYql::TObjectSettingsImpl& settings, TInternalModificationContext& context) const override; + + virtual std::vector GetPreconditions( + const std::vector& objects, const IOperationsManager::TInternalModificationContext& context) const override; }; class TAccessManager: public NModifications::TGenericOperationsManager { diff --git a/ydb/services/metadata/secret/secret.cpp b/ydb/services/metadata/secret/secret.cpp index ec447c1d0ed6..ddab3fdaf3cd 100644 --- a/ydb/services/metadata/secret/secret.cpp +++ b/ydb/services/metadata/secret/secret.cpp @@ -32,20 +32,4 @@ IClassBehaviour::TPtr TSecret::GetBehaviour() { return TSecretBehaviour::GetInstance(); } -TString TSecretId::SerializeToString() const { - TStringBuilder sb; - sb << "USId:" << OwnerUserId << ":" << SecretId; - return sb; -} - - -TString TSecretIdOrValue::DebugString() const { - if (SecretId) { - return SecretId->SerializeToString(); - } else if (Value) { - return MD5::Calc(*Value); - } - return ""; -} - } diff --git a/ydb/services/metadata/secret/secret.h b/ydb/services/metadata/secret/secret.h index 70920091bf31..8cc936766c2d 100644 --- a/ydb/services/metadata/secret/secret.h +++ b/ydb/services/metadata/secret/secret.h @@ -5,169 +5,10 @@ #include #include #include +#include namespace NKikimr::NMetadata::NSecret { -class TSecretId { -private: - YDB_READONLY_PROTECT_DEF(TString, OwnerUserId); - YDB_READONLY_PROTECT_DEF(TString, SecretId); -public: - TSecretId() = default; - TSecretId(const TString& ownerUserId, const TString& secretId) - : OwnerUserId(ownerUserId) - , SecretId(secretId) { - } - - TSecretId(const TStringBuf ownerUserId, const TStringBuf secretId) - : OwnerUserId(ownerUserId) - , SecretId(secretId) { - } - - TString SerializeToString() const; - - template - TString BuildSecretAccessString(const TProto& proto, const TString& defaultOwnerId) { - if (proto.HasValue()) { - return proto.GetValue(); - } else { - return TStringBuilder() << "USId:" << (proto.GetSecretOwnerId() ? proto.GetSecretOwnerId() : defaultOwnerId) << ":" << SecretId; - } - } - - bool operator<(const TSecretId& item) const { - return std::tie(OwnerUserId, SecretId) < std::tie(item.OwnerUserId, item.SecretId); - } - bool operator==(const TSecretId& item) const { - return std::tie(OwnerUserId, SecretId) == std::tie(item.OwnerUserId, item.SecretId); - } -}; - -class TSecretIdOrValue { -private: - YDB_READONLY_DEF(std::optional, SecretId); - YDB_READONLY_DEF(std::optional, Value); - TSecretIdOrValue() = default; - - bool DeserializeFromStringImpl(const TString& info, const TString& defaultUserId) { - static const TString prefixWithUser = "USId:"; - static const TString prefixNoUser = "SId:"; - if (info.StartsWith(prefixWithUser)) { - TStringBuf sb(info.data(), info.size()); - sb.Skip(prefixWithUser.size()); - TStringBuf uId; - TStringBuf sId; - if (!sb.TrySplit(':', uId, sId)) { - return false; - } - if (!uId || !sId) { - return false; - } - SecretId = TSecretId(uId, sId); - } else if (info.StartsWith(prefixNoUser)) { - TStringBuf sb(info.data(), info.size()); - sb.Skip(prefixNoUser.size()); - SecretId = TSecretId(defaultUserId, TString(sb)); - if (!sb || !defaultUserId) { - return false; - } - } else { - Value = info; - } - return true; - } - explicit TSecretIdOrValue(const TSecretId& id) - : SecretId(id) { - - } - - explicit TSecretIdOrValue(const TString& value) - : Value(value) { - - } - -public: - bool operator!() const { - return !Value && !SecretId; - } - - static TSecretIdOrValue BuildAsValue(const TString& value) { - return TSecretIdOrValue(value); - } - - static TSecretIdOrValue BuildEmpty() { - return TSecretIdOrValue(); - } - - static TSecretIdOrValue BuildAsId(const TSecretId& id) { - return TSecretIdOrValue(id); - } - - static std::optional DeserializeFromOptional(const NKikimrSchemeOp::TSecretableVariable& proto, const TString& secretInfo, const TString& defaultOwnerId = Default()) { - if (proto.HasSecretId()) { - return DeserializeFromProto(proto, defaultOwnerId); - } else if (proto.HasValue()) { - return DeserializeFromString(proto.GetValue().GetData()); - } if (secretInfo) { - return DeserializeFromString(secretInfo, defaultOwnerId); - } else { - return {}; - } - } - - NKikimrSchemeOp::TSecretableVariable SerializeToProto() const { - NKikimrSchemeOp::TSecretableVariable result; - if (SecretId) { - result.MutableSecretId()->SetId(SecretId->GetSecretId()); - result.MutableSecretId()->SetOwnerId(SecretId->GetOwnerUserId()); - } else if (Value) { - result.MutableValue()->SetData(*Value); - } - return result; - } - - static std::optional DeserializeFromProto(const NKikimrSchemeOp::TSecretableVariable& proto, const TString& defaultOwnerId = Default()) { - if (proto.HasSecretId()) { - TString ownerId; - TString secretId; - if (!proto.GetSecretId().HasOwnerId() || !proto.GetSecretId().GetOwnerId()) { - ownerId = defaultOwnerId; - } else { - ownerId = proto.GetSecretId().GetOwnerId(); - } - secretId = proto.GetSecretId().GetId(); - if (!ownerId || !secretId) { - return {}; - } - return TSecretIdOrValue::BuildAsId(TSecretId(ownerId, secretId)); - } else if (proto.HasValue()) { - return TSecretIdOrValue::BuildAsValue(proto.GetValue().GetData()); - } else { - return {}; - } - } - - static std::optional DeserializeFromString(const TString& info, const TString& defaultOwnerId = Default()) { - TSecretIdOrValue result; - if (!result.DeserializeFromStringImpl(info, defaultOwnerId)) { - return {}; - } else { - return result; - } - } - - TString SerializeToString() const { - if (SecretId) { - return SecretId->SerializeToString(); - } else if (Value) { - return *Value; - } - return ""; - } - - TString DebugString() const; -}; - class TSecret: public TSecretId, public NModifications::TObject { private: using TBase = TSecretId; diff --git a/ydb/services/metadata/secret/snapshot.cpp b/ydb/services/metadata/secret/snapshot.cpp index 26aa9ca7bd57..60397660b14f 100644 --- a/ydb/services/metadata/secret/snapshot.cpp +++ b/ydb/services/metadata/secret/snapshot.cpp @@ -2,10 +2,18 @@ namespace NKikimr::NMetadata::NSecret { +void TSnapshot::BuildIndex() { + IndexByName.clear(); + for (const auto& [id, secret] : Secrets) { + IndexByName[id.GetSecretId()].emplace_back(id); + } +} + bool TSnapshot::DoDeserializeFromResultSet(const Ydb::Table::ExecuteQueryResult& rawDataResult) { Y_ABORT_UNLESS(rawDataResult.result_sets().size() == 2); ParseSnapshotObjects(rawDataResult.result_sets()[0], [this](TSecret&& s) {Secrets.emplace(s, s); }); ParseSnapshotObjects(rawDataResult.result_sets()[1], [this](TAccess&& s) {Access.emplace_back(std::move(s)); }); + BuildIndex(); return true; } @@ -27,52 +35,102 @@ bool TSnapshot::PatchString(TString& stringForPath) const { if (!sId) { return false; } - return GetSecretValue(*sId, stringForPath); -} - -bool TSnapshot::CheckSecretAccess(const TSecretIdOrValue& sIdOrValue, const std::optional& userToken) const { - if (!userToken || !sIdOrValue) { + if (auto value = GetSecretValue(*sId); value.IsSuccess()) { + stringForPath = value.DetachResult(); return true; } - if (sIdOrValue.GetValue()) { + return false; +} + +bool TSnapshot::CheckSecretAccess(const TSecretIdOrValue& sIdOrValue, const NACLib::TUserToken& userToken) const { + if (std::holds_alternative(sIdOrValue.GetState()) || std::holds_alternative(sIdOrValue.GetState())) { return true; } - if (!sIdOrValue.GetSecretId()) { - return false; - } - const auto sId = *sIdOrValue.GetSecretId(); - auto it = Secrets.find(sId); + + auto findId = std::visit(TOverloaded( + [](std::monostate) -> const TSecretId* { + Y_ABORT(); + }, + [](const TSecretId& id) -> const TSecretId*{ + return &id; + }, + [this](const TSecretName& name) -> const TSecretId*{ + const auto findSecrets = IndexByName.FindPtr(name.GetSecretId()); + if (!findSecrets) { + return nullptr; + } + AFL_VERIFY(!findSecrets->empty()); + if (findSecrets->size() > 1) { + return nullptr; + } + return &*findSecrets->begin(); + }, + [](const TString& value) -> const TSecretId*{ + Y_UNUSED(value); + Y_ABORT(); + } + ), + sIdOrValue.GetState()); + + auto it = Secrets.find(*findId); if (it == Secrets.end()) { return false; } - if (it->second.GetOwnerUserId() == userToken->GetUserSID()) { + if (it->second.GetOwnerUserId() == userToken.GetUserSID()) { return true; } for (auto&& i : Access) { - if (i != sId) { + if (i != *findId) { continue; } - if (userToken->IsExist(i.GetAccessSID())) { + if (userToken.IsExist(i.GetAccessSID())) { return true; } } return false; } -bool TSnapshot::GetSecretValue(const TSecretIdOrValue& sId, TString& result) const { - if (sId.GetValue()) { - result = *sId.GetValue(); - return true; - } - if (!sId.GetSecretId()) { - return false;; - } - auto it = Secrets.find(*sId.GetSecretId()); - if (it == Secrets.end()) { - return false; - } - result = it->second.GetValue(); - return true; +TConclusion TSnapshot::GetSecretValue(const TSecretIdOrValue& sId) const { + return std::visit(TOverloaded( + [](std::monostate) -> TConclusion{ + return TConclusionStatus::Fail("Empty secret id"); + }, + [this](const TSecretId& id) -> TConclusion{ + if (const auto findSecret = Secrets.find(id); findSecret != Secrets.end()) { + return findSecret->second.GetValue(); + } + return TConclusionStatus::Fail(TStringBuilder() << "No such secret: " << id.SerializeToString()); + }, + [this](const TSecretName& name) -> TConclusion{ + if (const auto findSecrets = IndexByName.FindPtr(name.GetSecretId())) { + AFL_VERIFY(!findSecrets->empty()); + if (findSecrets->size() > 1) { + return TConclusionStatus::Fail(TStringBuilder() << "Can't identify secret: More than 1 secret found with such name: " << name.GetSecretId()); + } + auto secret = Secrets.find(*findSecrets->begin()); + AFL_VERIFY(secret != Secrets.end())("secret", findSecrets->begin()->SerializeToString()); + return secret->second.GetValue(); + } + return TConclusionStatus::Fail(TStringBuilder() << "No such secret: " << name.SerializeToString()); + }, + [](const TString& value) -> TConclusion{ + return value; + } + ), + sId.GetState()); } +std::vector TSnapshot::GetSecretIds(const std::optional& userToken, const TString& secretId) const { + std::vector secretIds; + for (const auto& [key, value]: Secrets) { + if (key.GetSecretId() != secretId) { + continue; + } + if (userToken && !CheckSecretAccess(NMetadata::NSecret::TSecretIdOrValue::BuildAsId(key), *userToken)) { + continue; + } + secretIds.push_back(key); + } + return secretIds; +} } diff --git a/ydb/services/metadata/secret/snapshot.h b/ydb/services/metadata/secret/snapshot.h index 9acff259c7ae..b53ce0c2c4f2 100644 --- a/ydb/services/metadata/secret/snapshot.h +++ b/ydb/services/metadata/secret/snapshot.h @@ -3,24 +3,30 @@ #include "access.h" #include +#include #include namespace NKikimr::NMetadata::NSecret { -class TSnapshot: public NFetcher::ISnapshot { +class TSnapshot: public NFetcher::ISnapshot, public ISecretAccessor { private: using TBase = NFetcher::ISnapshot; using TSecrets = std::map; + using TIdsByName = THashMap>; YDB_READONLY_DEF(TSecrets, Secrets); YDB_READONLY_DEF(std::vector, Access); + YDB_READONLY_DEF(TIdsByName, IndexByName); +private: + void BuildIndex(); protected: virtual bool DoDeserializeFromResultSet(const Ydb::Table::ExecuteQueryResult& rawData) override; virtual TString DoSerializeToString() const override; public: using TBase::TBase; - bool CheckSecretAccess(const TSecretIdOrValue& sIdOrValue, const std::optional& userToken) const; - bool PatchString(TString& stringForPath) const; - bool GetSecretValue(const TSecretIdOrValue& secretId, TString& result) const; + bool CheckSecretAccess(const TSecretIdOrValue& sIdOrValue, const NACLib::TUserToken& userToken) const override; + bool PatchString(TString& stringForPath) const override; + TConclusion GetSecretValue(const TSecretIdOrValue& secretId) const override; + std::vector GetSecretIds(const std::optional& userToken, const TString& secretId) const override; }; } diff --git a/ydb/services/metadata/secret/ut/ut_secret.cpp b/ydb/services/metadata/secret/ut/ut_secret.cpp index b7b215659432..039cd2c3a8a6 100644 --- a/ydb/services/metadata/secret/ut/ut_secret.cpp +++ b/ydb/services/metadata/secret/ut/ut_secret.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -26,8 +25,6 @@ namespace NKikimr { -using namespace NColumnShard; - Y_UNIT_TEST_SUITE(Secret) { class TJsonChecker { @@ -197,7 +194,7 @@ Y_UNIT_TEST_SUITE(Secret) { { TString resultData; lHelper.StartDataRequest("SELECT COUNT(*) FROM `/Root/.metadata/initialization/migrations`", true, &resultData); - UNIT_ASSERT_EQUAL_C(resultData, "[6u]", resultData); + UNIT_ASSERT_EQUAL_C(resultData, "[7u]", resultData); } emulator->SetExpectedSecretsCount(2).SetExpectedAccessCount(0).CheckFound(); @@ -214,7 +211,7 @@ Y_UNIT_TEST_SUITE(Secret) { { TString resultData; lHelper.StartDataRequest("SELECT COUNT(*) FROM `/Root/.metadata/initialization/migrations`", true, &resultData); - UNIT_ASSERT_EQUAL_C(resultData, "[10u]", resultData); + UNIT_ASSERT_EQUAL_C(resultData, "[11u]", resultData); } emulator->SetExpectedSecretsCount(2).SetExpectedAccessCount(1).CheckFound(); @@ -296,10 +293,17 @@ Y_UNIT_TEST_SUITE(Secret) { lHelper.StartSchemaRequest("CREATE OBJECT IF NOT EXISTS `secret1:test@test1` (TYPE SECRET_ACCESS)"); lHelper.StartSchemaRequest("DROP OBJECT `secret1` (TYPE SECRET)", false); lHelper.StartDataRequest("SELECT * FROM `/Root/.metadata/secrets/values`", false); + + lHelper.SetAuthToken("test@test1"); + lHelper.StartSchemaRequest("CREATE OBJECT secret1 (TYPE SECRET) WITH value = `100`", false); + lHelper.StartSchemaRequest("UPSERT OBJECT secret1 (TYPE SECRET) WITH value = `100`", false); + lHelper.StartSchemaRequest("CREATE OBJECT secret2 (TYPE SECRET) WITH value = `100`"); + lHelper.ResetAuthToken(); + { TString resultData; lHelper.StartDataRequest("SELECT COUNT(*) FROM `/Root/.metadata/initialization/migrations`", true, &resultData); - UNIT_ASSERT_EQUAL_C(resultData, "[10u]", resultData); + UNIT_ASSERT_EQUAL_C(resultData, "[11u]", resultData); } } } diff --git a/ydb/services/metadata/secret/ya.make b/ydb/services/metadata/secret/ya.make index e44e3e3152dc..6cfd391a7365 100644 --- a/ydb/services/metadata/secret/ya.make +++ b/ydb/services/metadata/secret/ya.make @@ -21,6 +21,7 @@ PEERDIR( ydb/core/grpc_services/base ydb/core/grpc_services ydb/services/metadata/request + ydb/services/metadata/secret/accessor ) END() diff --git a/ydb/services/ydb/ydb_common_ut.h b/ydb/services/ydb/ydb_common_ut.h index ec837f9730d2..74f16e9ad07b 100644 --- a/ydb/services/ydb/ydb_common_ut.h +++ b/ydb/services/ydb/ydb_common_ut.h @@ -270,7 +270,6 @@ struct TTestOlap { Columns { Name: "request_id" Type: "Utf8" } KeyColumnNames: "timestamp" KeyColumnNames: "uid" - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } } )", storeName.c_str()); diff --git a/ydb/services/ydb/ydb_olapstore_ut.cpp b/ydb/services/ydb/ydb_olapstore_ut.cpp index d570e9cc73cc..a14e1c5e79db 100644 --- a/ydb/services/ydb/ydb_olapstore_ut.cpp +++ b/ydb/services/ydb/ydb_olapstore_ut.cpp @@ -90,7 +90,6 @@ Y_UNIT_TEST_SUITE(YdbOlapStore) { Columns { Name: "saved_at" Type: "Timestamp" } Columns { Name: "request_id" Type: "Utf8" } KeyColumnNames: ["timestamp", "resource_type", "resource_id", "uid"] - Engine: COLUMN_ENGINE_REPLACING_TIMESERIES } } )", notNullStr, notNullStr, allowedTypes[opts.TsType].c_str(), notNullStr, notNullStr); diff --git a/ydb/tests/library/harness/kikimr_config.py b/ydb/tests/library/harness/kikimr_config.py index 3baa281fdd75..c3bc302687a6 100644 --- a/ydb/tests/library/harness/kikimr_config.py +++ b/ydb/tests/library/harness/kikimr_config.py @@ -166,6 +166,7 @@ def __init__( pg_compatible_expirement=False, generic_connector_config=None, # typing.Optional[TGenericConnectorConfig] pgwire_port=None, + column_shard_config=None, ): if extra_feature_flags is None: extra_feature_flags = [] @@ -291,11 +292,12 @@ def __init__( self.yaml_config['pqconfig']['require_credentials_in_new_protocol'] = False self.yaml_config['pqconfig']['root'] = '/Root/PQ' self.yaml_config['pqconfig']['quoting_config']['enable_quoting'] = False - if pq_client_service_types: self.yaml_config['pqconfig']['client_service_type'] = [] for service_type in pq_client_service_types: self.yaml_config['pqconfig']['client_service_type'].append({'name': service_type}) + if column_shard_config: + self.yaml_config["column_shard_config"] = column_shard_config self.yaml_config['grpc_config']['services'].extend(extra_grpc_services) diff --git a/ydb/tests/olap/lib/ydb_cluster.py b/ydb/tests/olap/lib/ydb_cluster.py index 6b56fb4b9ca1..3bc2f630f6b5 100644 --- a/ydb/tests/olap/lib/ydb_cluster.py +++ b/ydb/tests/olap/lib/ydb_cluster.py @@ -92,6 +92,13 @@ def _create_ydb_driver(endpoint, database, oauth=None, iam_file=None): ) raise + @classmethod + def reset(cls, ydb_endpoint, ydb_database, ydb_mon_port): + cls.ydb_endpoint = ydb_endpoint + cls.ydb_database = ydb_database + cls.ydb_mon_port = ydb_mon_port + cls._ydb_driver = None + @classmethod def get_ydb_driver(cls): if cls._ydb_driver is None: diff --git a/ydb/tests/olap/scenario/conftest.py b/ydb/tests/olap/scenario/conftest.py index 533375e989d4..2ec8a6fb888d 100644 --- a/ydb/tests/olap/scenario/conftest.py +++ b/ydb/tests/olap/scenario/conftest.py @@ -4,14 +4,58 @@ import time from ydb.tests.olap.lib.results_processor import ResultsProcessor from ydb.tests.olap.scenario.helpers.scenario_tests_helper import TestContext, ScenarioTestHelper +from ydb.tests.olap.lib.ydb_cluster import YdbCluster from ydb.tests.olap.lib.utils import external_param_is_true +from ydb.tests.olap.lib.utils import get_external_param from ydb.tests.olap.lib.allure_utils import allure_test_description +from ydb.tests.library.harness.kikimr_runner import KiKiMR +from ydb.tests.library.harness.kikimr_config import KikimrConfigGenerator + LOGGER = logging.getLogger() SCENARIO_PREFIX = 'scenario_' +class YdbClusterInstance(): + ''' + Represents either long-running external cluster or create temporary cluster for local run + ''' + _temp_ydb_cluster = None + _endpoint = None + _database = None + + def __init__(self, endpoint, database): + if endpoint is not None: + self._endpoint = endpoint + self._database = database + self._mon_port = 8765 + else: + config = KikimrConfigGenerator() + cluster = KiKiMR(configurator=config) + cluster.start() + node = cluster.nodes[1] + self._endpoint = "grpc://%s:%d" % (node.host, node.port) + self._database = config.domain_name + self._mon_port = node.mon_port + self._temp_ydb_cluster = cluster + LOGGER.info(f'Using YDB, endpoint:{self._endpoint}, database:{self._database}') + + def endpoint(self): + return self._endpoint + + def database(self): + return self._database + + def mon_port(self): + return self._mon_port + + def stop(self): + if self._temp_ydb_cluster is not None: + self._temp_ydb_cluster.stop() + self._temp_ydb_cluster = None + + class BaseTestSet: @classmethod def get_suite_name(cls): @@ -19,6 +63,10 @@ def get_suite_name(cls): @classmethod def setup_class(cls): + ydb_endpoint = get_external_param('ydb-endpoint', None) + ydb_database = get_external_param('ydb-db', "").lstrip('/') + cls._ydb_instance = YdbClusterInstance(ydb_endpoint, ydb_database) + YdbCluster.reset(cls._ydb_instance.endpoint(), cls._ydb_instance.database(), cls._ydb_instance.mon_port()) if not external_param_is_true('reuse-tables'): ScenarioTestHelper(None).remove_path(cls.get_suite_name()) @@ -26,6 +74,7 @@ def setup_class(cls): def teardown_class(cls): if not external_param_is_true('keep-tables'): ScenarioTestHelper(None).remove_path(cls.get_suite_name()) + cls._ydb_instance.stop() def test(self, ctx: TestContext): allure_test_description(ctx.suite, ctx.test) diff --git a/ydb/tests/olap/scenario/helpers/data_generators.py b/ydb/tests/olap/scenario/helpers/data_generators.py index 286f4d8347a0..08e0d614169e 100644 --- a/ydb/tests/olap/scenario/helpers/data_generators.py +++ b/ydb/tests/olap/scenario/helpers/data_generators.py @@ -5,6 +5,7 @@ from ydb import PrimitiveType from typing import override, Any, List, Dict import random +import string class IColumnValueGenerator(ABC): @@ -90,6 +91,11 @@ def __init__(self, null_probability: float = 0.5) -> None: super().__init__() self._null_propabitity = null_probability + @staticmethod + def random_utf8_string(length): + characters = string.ascii_letters + string.digits + string.punctuation + ' ' + return ''.join(random.choices(characters, k=length)).encode('utf-8') + @override def generate_value(self, column: ScenarioTestHelper.Column) -> Any: if not column.not_null and random.random() <= self._null_propabitity: @@ -114,8 +120,10 @@ def generate_value(self, column: ScenarioTestHelper.Column) -> Any: return random.randint(0, 2**64 - 1) elif column.type in {PrimitiveType.Float, PrimitiveType.Double}: return random.uniform(-1e6, 1e6) - elif column.type in {PrimitiveType.String, PrimitiveType.Utf8}: + elif column.type == PrimitiveType.String: return random.randbytes(15) + elif column.type == PrimitiveType.Utf8: + return self.random_utf8_string(15) elif column.type in {PrimitiveType.Json, PrimitiveType.JsonDocument}: return f'"{random.randbytes(15)}"' elif column.type == PrimitiveType.Timestamp: diff --git a/ydb/tests/olap/scenario/helpers/scenario_tests_helper.py b/ydb/tests/olap/scenario/helpers/scenario_tests_helper.py index ad1cab7da4db..249171dc9eef 100644 --- a/ydb/tests/olap/scenario/helpers/scenario_tests_helper.py +++ b/ydb/tests/olap/scenario/helpers/scenario_tests_helper.py @@ -1,4 +1,5 @@ from __future__ import annotations +import enum import os import allure import allure_commons @@ -59,26 +60,79 @@ class ScenarioTestHelper: sth.execute_scheme_query(DropTable(table_name)) """ + DEFAULT_RETRIABLE_ERRORS = { + ydb.StatusCode.OVERLOADED, + ydb.StatusCode.BAD_SESSION, + ydb.StatusCode.CONNECTION_LOST, + ydb.StatusCode.UNAVAILABLE, + } + + @enum.unique + class Compression(enum.IntEnum): + OFF = 1 + LZ4 = 2 + ZSTD = 3 + + class ColumnFamily: + """A class that describes a column family.""" + + def __init__(self, name: str, compression: ScenarioTestHelper.Compression, compression_level: Optional[int]): + """Constructor. + + Args: + name: Column family name. + compression: Compression codec. + compression_level: Compression codec level. + """ + + self._name = name + self._compression = compression + self._compression_level = compression_level + + def to_yql(self) -> str: + """Convert to YQL""" + return f'FAMILY {self._name} (COMPRESSION = "{self._compression.name}"{", COMPRESSION_LEVEL = " + str(self._compression_level) if self._compression_level is not None else ""})' + + @property + def name(self) -> str: + """Column family name.""" + + return self._name + + @property + def compression(self) -> ScenarioTestHelper.Compression: + """Compression""" + + return self._compression + + @property + def compression_level(self) -> Optional[int]: + """Compression level.""" + + return self._compression_level + class Column: """A class that describes a table column.""" - def __init__(self, name: str, type: ydb.PrimitiveType, not_null: bool = False) -> None: + def __init__(self, name: str, type: ydb.PrimitiveType, column_family_name: str = "", not_null: bool = False) -> None: """Constructor. Args: name: Column name. type: Column type. + column_family_name: Column Family name. not_null: Whether the entry in the column can be NULL. """ self._name = name self._type = type + self._column_family_name = column_family_name self._not_null = not_null def to_yql(self) -> str: """Convert to YQL""" - return f'{self._name} {self._type}{" NOT NULL" if self._not_null else ""}' + return f'{self._name} {self._type}{"" if not self._column_family_name else f" FAMILY {self._column_family_name}"}{" NOT NULL" if self._not_null else ""}' @property def bulk_upsert_type(self) -> ydb.OptionalType | ydb.PrimitiveType: @@ -100,6 +154,11 @@ def type(self) -> ydb.PrimitiveType: return self._type + def column_family(self) -> str: + """Colum family name""" + + return "default" if not self._column_family_name else self._column_family_name + @property def not_null(self) -> bool: """Whether the entry in the column can be NULL.""" @@ -113,8 +172,9 @@ class Schema: schema = ( ScenarioTestHelper.Schema() .with_column(name='id', type=PrimitiveType.Int32, not_null=True) - .with_column(name='level', type=PrimitiveType.Uint32) + .with_column(name='level', type=PrimitiveType.Uint32, column_family_name="family1") .with_key_columns('id') + .with_column_family(name="family1", compression=ScenarioTestHelper.Compression.LZ4, compression_level=None) ) """ @@ -123,6 +183,7 @@ def __init__(self) -> None: self.columns = [] self.key_columns = [] + self.column_families = [] def with_column(self, *vargs, **kargs) -> ScenarioTestHelper.Schema: """Add a column. @@ -148,6 +209,18 @@ def with_key_columns(self, *vargs: str) -> ScenarioTestHelper.Schema: self.key_columns += vargs return self + def with_column_family(self, *vargs, **kargs) -> ScenarioTestHelper.Schema: + """Add a column family. + + The method arguments are the same as {ScenarioTestHelper.ColumnFamily.__init__}. + + Returns: + self. + """ + + self.column_families.append(ScenarioTestHelper.ColumnFamily(*vargs, **kargs)) + return self + def build_bulk_columns_types(self) -> ydb.BulkUpsertColumns: """Convert to ydb.BulkUpsertColumns""" @@ -369,6 +442,26 @@ def execute_scan_query( allure.attach(json.dumps(rows), 'result', allure.attachment_type.JSON) return ret + @allure.step('Execute query') + def execute_query( + self, yql: str, expected_status: ydb.StatusCode | Set[ydb.StatusCode] = ydb.StatusCode.SUCCESS + ): + """Run a query on the tested database. + + Args: + yql: Query text. + expected_status: Expected status or set of database response statuses. If the response status is not in the expected set, an exception is thrown. + + Example: + tablename = 'testTable' + sth = ScenarioTestHelper(ctx) + sth.execute_query(f'INSERT INTO `{sth.get_full_path("tablename") }` (key, c) values(1, 100)') + """ + + allure.attach(yql, 'request', allure.attachment_type.TEXT) + with ydb.QuerySessionPool(YdbCluster.get_ydb_driver()) as pool: + self._run_with_expected_status(lambda: pool.execute_with_retries(yql), expected_status) + def drop_if_exist(self, names: List[str], operation) -> None: """Erase entities in the tested database, if it exists. @@ -560,15 +653,18 @@ def list_path(self, path: str) -> List[ydb.SchemeEntry]: """ root_path = self.get_full_path('') - result = [] - self_descr = self._describe_path_impl(os.path.join(root_path, path)) - if self_descr is not None: - self_descr.name = path - if self_descr.is_directory(): - result = self._list_directory_impl(root_path, path) - result.append(self_descr) - allure.attach('\n'.join([f'{e.name}: {repr(e.type)}' for e in result]), 'result', allure.attachment_type.TEXT) - return result + try: + self_descr = YdbCluster._describe_path_impl(os.path.join(root_path, path)) + except ydb.issues.SchemeError: + return [] + + if self_descr is None: + return [] + + if self_descr.is_directory(): + return list(reversed(YdbCluster.list_directory(root_path, path))) + [self_descr] + else: + return self_descr @allure.step('Remove path {path}') def remove_path(self, path: str) -> None: @@ -599,3 +695,15 @@ def remove_path(self, path: str) -> None: ) else: pytest.fail(f'Cannot remove type {repr(e.type)} for path {os.path.join(root_path, e.name)}') + + def get_volumes_columns(self, table_name: str, name_column: str) -> tuple[int, int]: + query = f'''SELECT * FROM `{ScenarioTestHelper(self.test_context).get_full_path(table_name)}/.sys/primary_index_stats` WHERE Activity == 1''' + if (len(name_column)): + query += f' AND EntityName = \"{name_column}\"' + result_set = self.execute_scan_query(query, {ydb.StatusCode.SUCCESS}).result_set + raw_bytes = 0 + bytes = 0 + for row in result_set.rows: + raw_bytes += row["RawBytes"] + bytes += row["BlobRangeSize"] + return raw_bytes, bytes diff --git a/ydb/tests/olap/scenario/helpers/table_helper.py b/ydb/tests/olap/scenario/helpers/table_helper.py index 8b1963fd13e0..a6a7b726d610 100644 --- a/ydb/tests/olap/scenario/helpers/table_helper.py +++ b/ydb/tests/olap/scenario/helpers/table_helper.py @@ -58,10 +58,12 @@ def title(self): @override def to_yql(self, ctx: TestContext) -> str: schema_str = ',\n '.join([c.to_yql() for c in self._schema.columns]) + column_families_str = ',\n'.join([c.to_yql() for c in self._schema.column_families]) keys = ', '.join(self._schema.key_columns) return f'''CREATE {self._type().upper()} `{ScenarioTestHelper(ctx).get_full_path(self._name)}` ( {schema_str}, PRIMARY KEY({keys}) + {"" if not column_families_str else f", {column_families_str}"} ) {self._partition_by()} WITH( @@ -135,20 +137,34 @@ def _partition_by(self) -> str: return '' -class AlterTableAction(ABC): +class Action(ABC): + """The base class for all actions.""" + + @abstractmethod + def to_yql(self) -> str: + """Convert to YQL.""" + pass + + @abstractmethod + def title(self) -> str: + """Title to display in Allure.""" + pass + + +class AlterTableAction(Action): """The base class for all actions when changing table-like objects. Table-like objects are Tables and TableStore. See {AlterTableLikeObject}. """ - @abstractmethod + @override def to_yql(self) -> str: """Convert to YQL.""" pass - @abstractmethod + @override def title(self) -> str: """Title to display in Allure.""" @@ -187,6 +203,70 @@ def title(self) -> str: return f'add column `{self._column.name}`' +class AlterColumnBase(Action): + """The base class for all actions on a column.""" + + @override + def to_yql(self) -> str: + """Convert to YQL.""" + pass + + @override + def title(self) -> str: + """Title to display in Allure.""" + pass + + +class AlterFamily(AlterColumnBase): + """Alter family for a column.""" + + def __init__(self, colum_family_name: str) -> None: + super().__init__() + self._colum_family_name = colum_family_name + + @override + def to_yql(self) -> str: + return f'SET FAMILY {self._colum_family_name}' + + @override + def title(self) -> str: + return f'set family {self._colum_family_name}' + + +class AlterColumn(AlterTableAction): + """Alter a column in a table-like object. + + Table-like objects are Tables and TableStore. + See {AlterTableLikeObject}. + + Example: + sth = ScenarioTestHelper(ctx) + sth.execute_scheme_query( + AlterTable('testTable') + .action(AlterColumn("column", AlterFamily("family2"))) + ) + """ + + def __init__(self, column_name: str, action: AlterColumnBase) -> None: + """Constructor. + + Args: + column_name: Column name for alter + action: Action description.""" + + super().__init__() + self._column_name = column_name + self._action = action + + @override + def to_yql(self) -> str: + return f'ALTER COLUMN {self._column_name} {self._action.to_yql()}' + + @override + def title(self) -> str: + return f'altert column {self._column_name}`{self._action.title()}`' + + class DropColumn(AlterTableAction): """Remove a column from a table-like object. @@ -281,6 +361,123 @@ def title(self) -> str: return f'reset {self._setting}' +class AddColumnFamily(AlterTableAction): + """Add a column family to a table-like object. + + Table-like objects are Tables and TableStore. + See {AlterTableLikeObject}. + + Example: + sth = ScenarioTestHelper(ctx) + sth.execute_scheme_query( + AlterTable('testTable') + .action(AddColumnFamily(sth.ColumnFamily('family1', ScenarioTestHelper.Compression.LZ4, None)) + .action(AddColumnFamily(sth.ColumnFamily('family2', ScenarioTestHelper.Compression.ZSTD, 4)) + ) + ) + """ + + def __init__(self, column_family: ScenarioTestHelper.ColumnFamily) -> None: + """Constructor. + + Args: + column_family: Column family description.""" + + super().__init__() + self._column_family = column_family + + @override + def to_yql(self) -> str: + return f'ADD {self._column_family.to_yql()}' + + @override + def title(self) -> str: + return f'add family `{self._column_family.name}`' + + +class ColumnFamilyAction(Action): + """The base class for all actions when changing colum family.""" + + @override + def to_yql(self) -> str: + """Convert to YQL.""" + + pass + + @override + def title(self) -> str: + """Title to display in Allure.""" + + pass + + +class AlterCompression(AlterTableAction): + """Alter compression codec for a column family.""" + + def __init__(self, compression: ScenarioTestHelper.Compression) -> None: + super().__init__() + self._compression = compression + + @override + def to_yql(self) -> str: + return f'SET COMPRESSION "{self._compression.name}"' + + @override + def title(self) -> str: + return f'set compression "{self._compression.name}"' + + +class AlterCompressionLevel(AlterTableAction): + """Alter compression codec level for a column family.""" + + def __init__(self, compression_level: int) -> None: + super().__init__() + self._compression_level = compression_level + + @override + def to_yql(self) -> str: + return f'SET COMPRESSION_LEVEL {self._compression_level}' + + @override + def title(self) -> str: + return f'set compression level {self._compression_level}' + + +class AlterColumnFamily(AlterTableAction): + """Alter a column family to a table-like object. + + Table-like objects are Tables and TableStore. + See {AlterTableLikeObject}. + + Example: + sth = ScenarioTestHelper(ctx) + sth.execute_scheme_query( + AlterTable('testTable') + .action(AlterColumnFamily('family1', AlterCompression(ScenarioTestHelper.Compression.ZSTD))) + .action(AlterColumnFamily('family2', AlterCompressionLevel(9))) + ) + ) + """ + + def __init__(self, column_family_name: str, action: ColumnFamilyAction) -> None: + """Constructor. + + Args: + column: Column description.""" + + super().__init__() + self._column_family_name = column_family_name + self._action = action + + @override + def to_yql(self) -> str: + return f'ALTER FAMILY {self._column_family_name} {self._action.to_yql()}' + + @override + def title(self) -> str: + return f'alter family `{self._column_family_name}` {self._action.title()}' + + class AlterTableLikeObject(ScenarioTestHelper.IYqlble): """The base class for all requests to change table-like objects. @@ -376,6 +573,19 @@ def set_ttl(self, interval: str, column: str) -> AlterTableLikeObject: return self(SetSetting('TTL', f'Interval("{interval}") ON `{column}`')) + def add_column_family(self, column_family: ScenarioTestHelper.ColumnFamily) -> AlterTableLikeObject: + """Add a column_family. + + The method is similar to calling {AlterTableLikeObject.action} with an {AddColumnFamily} instance. + + Args: + column: Description of the column_family. + + Returns: + self.""" + + return self(AddColumnFamily(column_family)) + @override def params(self) -> Dict[str, str]: return {self._type(): self._name, 'actions': ', '.join([a.title() for a in self._actions])} @@ -387,7 +597,7 @@ def title(self): @override def to_yql(self, ctx: TestContext) -> str: actions = ', '.join([a.to_yql() for a in self._actions]) - return f'ALTER {self._type().upper()} `{ScenarioTestHelper(ctx).get_full_path(self._name)}` {actions}' + return f'ALTER {self._type().upper()} `{ScenarioTestHelper(ctx).get_full_path(self._name)}` {actions};' @abstractmethod def _type(self) -> str: diff --git a/ydb/tests/olap/scenario/helpers/thread_helper.py b/ydb/tests/olap/scenario/helpers/thread_helper.py new file mode 100644 index 000000000000..c95d3aba3480 --- /dev/null +++ b/ydb/tests/olap/scenario/helpers/thread_helper.py @@ -0,0 +1,16 @@ +import threading + + +class TestThread(threading.Thread): + def run(self) -> None: + self.exc = None + try: + self.ret = self._target(*self._args, **self._kwargs) + except BaseException as e: + self.exc = e + + def join(self, timeout=None): + super().join(timeout) + if self.exc: + raise self.exc + return self.ret diff --git a/ydb/tests/olap/scenario/helpers/ya.make b/ydb/tests/olap/scenario/helpers/ya.make index f97b1f294e6c..eb7cd77f6d82 100644 --- a/ydb/tests/olap/scenario/helpers/ya.make +++ b/ydb/tests/olap/scenario/helpers/ya.make @@ -6,6 +6,7 @@ PY3_LIBRARY() data_generators.py table_helper.py drop_helper.py + thread_helper.py ) PEERDIR( diff --git a/ydb/tests/olap/scenario/test_alter_compression.py b/ydb/tests/olap/scenario/test_alter_compression.py new file mode 100644 index 000000000000..08c47c7e8c3f --- /dev/null +++ b/ydb/tests/olap/scenario/test_alter_compression.py @@ -0,0 +1,359 @@ +from conftest import BaseTestSet +from ydb.tests.olap.scenario.helpers import ( + ScenarioTestHelper, + TestContext, + CreateTable, + CreateTableStore, + AlterTableLikeObject, + AlterTable, + AlterTableStore, + AlterColumnFamily, + AlterCompression, + AlterCompressionLevel, + AddColumnFamily, + AlterColumn, + AlterFamily, +) +from helpers.thread_helper import TestThread +from typing import List, Dict, Any +from ydb import PrimitiveType +from ydb.tests.olap.lib.utils import get_external_param +from datetime import datetime, timedelta +from string import ascii_lowercase + +import random +import threading +import copy +import logging +import time + + +class TestAlterCompression(BaseTestSet): + schema1 = ( + ScenarioTestHelper.Schema() + .with_column(name="Key", type=PrimitiveType.Uint64, not_null=True) + .with_column(name="Field", type=PrimitiveType.Utf8, not_null=True) + .with_column(name="Doub", type=PrimitiveType.Double, not_null=True) + .with_key_columns("Key") + ) + + def _loop_upsert( + self, + ctx: TestContext, + table_path: str, + start_index: int, + count_rows: int, + duration: timedelta, + ): + sth = ScenarioTestHelper(ctx) + rows_written = 0 + deadline = datetime.now() + duration + while datetime.now() < deadline: + data: List[Dict[str, Any]] = [] + for i in range(rows_written, rows_written + count_rows): + data.append( + { + "Key": i + start_index, + "Field": f"Field_{i + start_index}", + "Doub": (i + start_index) + 0.1 * ((i + start_index) % 10), + } + ) + sth.bulk_upsert_data(table_path, self.schema1, data) + rows_written += count_rows + # assert sth.get_table_rows_count(table_path) == rows_written + + def _loop_alter_table( + self, + ctx: TestContext, + action: AlterTableLikeObject, + table: str, + column_families: list[str], + duration: timedelta, + ): + data_types = [ + PrimitiveType.Double, + PrimitiveType.Int32, + PrimitiveType.Uint64, + PrimitiveType.Datetime, + PrimitiveType.Utf8, + PrimitiveType.String, + ] + deadline = datetime.now() + duration + sth = ScenarioTestHelper(ctx) + compressions: list = list(ScenarioTestHelper.Compression) + column_names: list[str] = [column.name for column in self.schema1.columns] + while datetime.now() < deadline: + column_name = f"tmp_column_{threading.get_ident()}_" + "".join( + random.choice(ascii_lowercase) for _ in range(8) + ) + sth.execute_scheme_query( + copy.deepcopy(action).add_column( + sth.Column(column_name, random.choice(data_types)) + ), + retries=10, + ) + sth.execute_scheme_query( + copy.deepcopy(action).drop_column(column_name), retries=10 + ) + + column_name: str = random.choice(column_names) + family: str = random.choice(column_families) + index_compression_type: int = random.randint(0, len(compressions) - 1) + compression: ScenarioTestHelper.Compression = compressions[ + index_compression_type + ] + sth.execute_scheme_query( + AlterTable(table).action( + AlterColumnFamily(family, AlterCompression(compression)) + ) + ) + if compression == ScenarioTestHelper.Compression.ZSTD: + compression_level: int = random.randint(0, 10) + sth.execute_scheme_query( + AlterTable(table).action( + AlterColumnFamily( + family, AlterCompressionLevel(compression_level) + ) + ) + ) + sth.execute_scheme_query( + AlterTable(table).action(AlterColumn(column_name, AlterFamily(family))) + ) + + def _upsert_and_alter( + self, + ctx: TestContext, + is_standalone_tables: bool, + table_store: str, + tables: list[str], + count_rows: int, + duration: timedelta, + column_families: list[str], + ): + sth = ScenarioTestHelper(ctx) + threads = [] + if not is_standalone_tables: + threads.append( + TestThread( + target=self._loop_alter_table, + args=[ctx, AlterTableStore(table_store), duration], + ) + ) + + for table in tables: + start_index = sth.get_table_rows_count(table) + if is_standalone_tables: + threads.append( + TestThread( + target=self._loop_alter_table, + args=[ctx, AlterTable(table), table, column_families, duration], + ) + ) + threads.append( + TestThread( + target=self._loop_upsert, + args=[ctx, table, start_index, count_rows, duration], + ) + ) + + for thread in threads: + thread.start() + for thread in threads: + thread.join() + + def _get_volumes_column( + self, ctx: TestContext, table_name: str, column_name: str + ) -> tuple[int, int]: + sth = ScenarioTestHelper(ctx) + pred_raw_bytes, pred_bytes = 0, 0 + raw_bytes, bytes = sth.get_volumes_columns(table_name, column_name) + while pred_raw_bytes != raw_bytes and pred_bytes != bytes: + pred_raw_bytes = raw_bytes + pred_bytes = bytes + time.sleep(5) + raw_bytes, bytes = sth.get_volumes_columns(table_name, column_name) + return raw_bytes, bytes + + def _volumes_columns( + self, ctx: TestContext, tables: list[str], column_names: list[str] + ) -> dict[str, dict[str, tuple[int, int]]]: + volumes: dict[str, dict[str, tuple[int, int]]] = dict() + for table in tables: + volumes.setdefault(table, dict()) + for column_name in column_names: + raw_bytes, bytes = self._get_volumes_column( + ctx=ctx, table_name=table, column_name=column_name + ) + volumes[table][column_name] = raw_bytes, bytes + logging.info( + f"Table: `{table}` Column: `{column_name}` raw_bytes = {raw_bytes}, bytes = {bytes}" + ) + return volumes + + def _read_data( + self, ctx: TestContext, tables: list[str], column_names: list[str] + ) -> bool: + sth = ScenarioTestHelper(ctx) + columns = ", ".join(column_names) + for table in tables: + count_rows: int = sth.get_table_rows_count(table) + scan_result = sth.execute_scan_query( + f"SELECT {columns} FROM `{sth.get_full_path(table)}` ORDER BY Key" + ) + for i in range(count_rows): + if not ( + scan_result.result_set.rows[i]["Key"] == i + and scan_result.result_set.rows[i]["Field"] == f"Field_{i}" + and scan_result.result_set.rows[i]["Doub"] == i + 0.1 * (i % 10) + ): + return False + return True + + def _scenario( + self, + ctx: TestContext, + tables: list[str], + alter_action: AlterTable, + column_family_names: list[str], + ): + sth = ScenarioTestHelper(ctx) + self._upsert_and_alter( + ctx=ctx, + is_standalone_tables=self.is_standalone_tables, + table_store=self.table_store, + tables=tables, + count_rows=self.count_rows_for_bulk_upsert, + duration=self.duration_alter_and_insert, + column_families=column_family_names, + ) + column_names: list[str] = [column.name for column in self.schema1.columns] + assert self._read_data(ctx=ctx, tables=tables, column_names=column_names) + # prev_volumes: dict[str, dict[str, tuple[int, int]]] = self._volumes_columns(ctx=ctx, tables=tables, column_names=column_names) + sth.execute_scheme_query(alter_action) + # current_volumes: dict[str, dict[str, tuple[int, int]]] = self._volumes_columns(ctx=ctx, tables=tables, column_names=column_names) + assert self._read_data(ctx, tables=tables, column_names=column_names) + + # working with the table store is not supported yet, so is_standalone_tables = True + def scenario_alter_compression(self, ctx: TestContext): + random.seed(2) + n_tables = int(get_external_param("n_tables", "2")) + # is_standalone_tables = external_param_is_true('test-standalone-tables') + self.is_standalone_tables = True + self.duration_alter_and_insert = timedelta( + seconds=int(get_external_param("duration_alter_and_insert", "2")) + ) + self.count_rows_for_bulk_upsert = int( + get_external_param("count_rows_for_bulk_upsert", "1000") + ) + self.table_store = "TableStore" + + sth = ScenarioTestHelper(ctx) + + if not self.is_standalone_tables: + sth.execute_scheme_query( + CreateTableStore(self.table_store).with_schema(self.schema1) + ) + + tables: list[str] = [] + for i in range(n_tables): + table_name = f"Table{i}" + tables.append( + table_name + if self.is_standalone_tables + else f"{self.table_store}/{table_name}" + ) + sth.execute_scheme_query(CreateTable(tables[-1]).with_schema(self.schema1)) + column_names: list[str] = [column.name for column in self.schema1.columns] + column_families: list[ScenarioTestHelper.ColumnFamily] = [ + sth.ColumnFamily( + name="family1", + compression=ScenarioTestHelper.Compression.LZ4, + compression_level=None, + ), + sth.ColumnFamily( + name="family2", + compression=ScenarioTestHelper.Compression.ZSTD, + compression_level=1, + ), + ] + + column_family_names: list[str] = [] + column_family_names.append("default") + for family in column_families: + column_family_names.append(family.name) + + for table_name in tables: + add_family_action = AlterTable(table_name) + for family in column_families: + add_family_action.action(AddColumnFamily(family)) + sth.execute_scheme_query(add_family_action) + + assert self._read_data(ctx=ctx, tables=tables, column_names=column_names) + + for column_name in column_names: + prev_compression_level = column_families[-1].compression_level + + for family in column_families: + self._scenario( + ctx=ctx, + tables=tables, + alter_action=AlterTable(table_name).action( + AlterColumn(column_name, AlterFamily(family.name)) + ), + column_family_names=column_family_names, + ) + + self._scenario( + ctx=ctx, + tables=tables, + alter_action=AlterTable(table_name) + .action( + AlterColumnFamily( + column_families[-1].name, + AlterCompression(column_families[-1].compression), + ) + ) + .action( + AlterColumnFamily( + column_families[-1].name, AlterCompressionLevel(9) + ) + ), + column_family_names=column_family_names, + ) + + self._scenario( + ctx=ctx, + tables=tables, + alter_action=AlterTable(table_name) + .action( + AlterColumnFamily( + column_families[-1].name, + AlterCompression(column_families[-1].compression), + ) + ) + .action( + AlterColumnFamily( + column_families[-1].name, AlterCompressionLevel(0) + ) + ), + column_family_names=column_family_names, + ) + + self._scenario( + ctx=ctx, + tables=tables, + alter_action=AlterTable(table_name) + .action( + AlterColumnFamily( + column_families[-1].name, + AlterCompression(column_families[-1].compression), + ) + ) + .action( + AlterColumnFamily( + column_families[-1].name, + AlterCompressionLevel(prev_compression_level), + ) + ), + column_family_names=column_family_names, + ) diff --git a/ydb/tests/olap/scenario/test_alter_tiering.py b/ydb/tests/olap/scenario/test_alter_tiering.py index d82087e7a217..82282995fe42 100644 --- a/ydb/tests/olap/scenario/test_alter_tiering.py +++ b/ydb/tests/olap/scenario/test_alter_tiering.py @@ -5,7 +5,9 @@ CreateTable, CreateTableStore, DropTable, + DropTableStore, ) +from helpers.thread_helper import TestThread from helpers.tiering_helper import ( ObjectStorageParams, AlterTier, @@ -19,15 +21,19 @@ DropTieringRule, ) import helpers.data_generators as dg -from helpers.table_helper import AlterTable +from helpers.table_helper import ( + AlterTable, + AlterTableStore +) -from ydb.tests.olap.lib.utils import get_external_param -from ydb import PrimitiveType +from ydb.tests.olap.lib.utils import get_external_param, external_param_is_true +from ydb import PrimitiveType, StatusCode +import boto3 import datetime import random -import threading -from typing import Iterable -import time +from typing import Iterable, Optional +import itertools +from string import ascii_lowercase class TestAlterTiering(BaseTestSet): @@ -37,30 +43,18 @@ class TestAlterTiering(BaseTestSet): .with_column(name='writer', type=PrimitiveType.Uint32, not_null=True) .with_column(name='value', type=PrimitiveType.Uint64, not_null=True) .with_column(name='data', type=PrimitiveType.String, not_null=True) + .with_column(name='timestamp2', type=PrimitiveType.Timestamp, not_null=True) .with_key_columns('timestamp', 'writer', 'value') ) - class TestThread(threading.Thread): - def run(self) -> None: - self.exc = None - try: - self.ret = self._target(*self._args, **self._kwargs) - except BaseException as e: - self.exc = e - - def join(self, timeout=None): - super().join(timeout) - if self.exc: - raise self.exc - return self.ret - def _drop_tables(self, prefix: str, count: int, ctx: TestContext): sth = ScenarioTestHelper(ctx) for i in range(count): sth.execute_scheme_query(DropTable(f'store/{prefix}_{i}')) - def _upsert(self, ctx: TestContext, table: str, writer_id: int, duration: datetime.timedelta): + def _loop_upsert(self, ctx: TestContext, table: str, writer_id: int, duration: datetime.timedelta, allow_scan_errors: bool = False): deadline = datetime.datetime.now() + duration + expected_scan_status = {StatusCode.SUCCESS, StatusCode.GENERIC_ERROR} if allow_scan_errors else {StatusCode.SUCCESS} sth = ScenarioTestHelper(ctx) rows_written = 0 i = 0 @@ -68,33 +62,91 @@ def _upsert(self, ctx: TestContext, table: str, writer_id: int, duration: dateti sth.bulk_upsert( table, dg.DataGeneratorPerColumn(self.schema1, 1000) - .with_column('timestamp', dg.ColumnValueGeneratorRandom(null_probability=0)) + .with_column('timestamp', dg.ColumnValueGeneratorLambda(lambda: int(datetime.datetime.now().timestamp() * 1000000))) .with_column('writer', dg.ColumnValueGeneratorConst(writer_id)) .with_column('value', dg.ColumnValueGeneratorSequential(rows_written)) .with_column('data', dg.ColumnValueGeneratorConst(random.randbytes(1024))) + .with_column('timestamp2', dg.ColumnValueGeneratorRandom(null_probability=0)) ) rows_written += 1000 i += 1 - if rows_written > 100000 and i % 10 == 0: - scan_result = sth.execute_scan_query(f'SELECT COUNT(*) FROM `{sth.get_full_path("store/table")}` WHERE writer == {writer_id}') - assert scan_result.result_set.rows[0][0] == rows_written + scan_result = sth.execute_scan_query(f'SELECT COUNT(*) FROM `{sth.get_full_path(table)}` WHERE writer == {writer_id}', expected_status=expected_scan_status) + assert scan_result.result_set.rows[0][0] == rows_written - def _change_tiering_rule(self, ctx: TestContext, table: str, tiering_rules: Iterable[str], duration: datetime.timedelta): + def _loop_change_tiering_rule(self, ctx: TestContext, table: str, tiering_rules: Iterable[str], duration: datetime.timedelta): deadline = datetime.datetime.now() + duration sth = ScenarioTestHelper(ctx) while datetime.datetime.now() < deadline: for tiering_rule in tiering_rules: - sth.execute_scheme_query(AlterTable(table).set_tiering(tiering_rule)) - sth.execute_scheme_query(AlterTable(table).reset_tiering()) + if tiering_rule is not None: + sth.execute_scheme_query(AlterTable(table).set_tiering(tiering_rule), retries=10) + else: + sth.execute_scheme_query(AlterTable(table).reset_tiering(), retries=10) + sth.execute_scheme_query(AlterTable(table).reset_tiering(), retries=10) - def scenario_alter_tiering_rule_while_writing(self, ctx: TestContext): - test_duration = datetime.timedelta(seconds=400) + def _loop_alter_tiering_rule(self, ctx: TestContext, tiering_rule: str, default_column_values: Iterable[str], config_values: Iterable[TieringPolicy], duration: datetime.timedelta): + deadline = datetime.datetime.now() + duration + sth = ScenarioTestHelper(ctx) + for default_column, config in zip(itertools.cycle(default_column_values), itertools.cycle(config_values)): + if datetime.datetime.now() >= deadline: + break + sth.execute_scheme_query(AlterTieringRule(tiering_rule, default_column, config), retries=10) + + def _loop_alter_column(self, ctx: TestContext, store: str, duration: datetime.timedelta): + column_name = 'tmp_column_' + ''.join(random.choice(ascii_lowercase) for _ in range(8)) + data_types = [PrimitiveType.Int8, PrimitiveType.Uint64, PrimitiveType.Datetime, PrimitiveType.Utf8] - s3_endpoint = get_external_param('s3-endpoint', 'storage.yandexcloud.net') + deadline = datetime.datetime.now() + duration + sth = ScenarioTestHelper(ctx) + while datetime.datetime.now() < deadline: + sth.execute_scheme_query(AlterTableStore(store).add_column(sth.Column(column_name, random.choice(data_types))), retries=10) + sth.execute_scheme_query(AlterTableStore(store).drop_column(column_name), retries=10) + + def _override_tier(self, sth, name, config): + sth.execute_scheme_query(CreateTierIfNotExists(name, config)) + sth.execute_scheme_query(AlterTier(name, config)) + + def _override_tiering_rule(self, sth, name, default_column, config): + sth.execute_scheme_query(CreateTieringRuleIfNotExists(name, default_column, config)) + sth.execute_scheme_query(AlterTieringRule(name, default_column, config)) + + def _make_s3_client(self, access_key, secret_key, endpoint): + session = boto3.Session( + aws_access_key_id=(access_key), + aws_secret_access_key=(secret_key), + region_name='ru-central1', + ) + return session.client('s3', endpoint_url=endpoint) + + def _count_objects(self, bucket_config: ObjectStorageParams): + s3 = self._make_s3_client(bucket_config.access_key, bucket_config.secret_key, bucket_config.endpoint) + paginator = s3.get_paginator('list_objects_v2') + page_iterator = paginator.paginate(Bucket=bucket_config.bucket) + + object_count = 0 + for page in page_iterator: + if 'Contents' in page: + object_count += len(page['Contents']) + + return object_count + + def scenario_many_tables(self, ctx: TestContext): + random.seed(42) + n_tables = 4 + + test_duration = datetime.timedelta(seconds=int(get_external_param('test-duration-seconds', '4600'))) + n_tables = int(get_external_param('tables', '4')) + n_writers = int(get_external_param('writers-per-table', '4')) + allow_s3_unavailability = external_param_is_true('allow-s3-unavailability') + is_standalone_tables = external_param_is_true('test-standalone-tables') + + s3_endpoint = get_external_param('s3-endpoint', 'http://storage.yandexcloud.net') s3_access_key = get_external_param('s3-access-key', 'YCAJEM3Pg9fMyuX9ZUOJ_fake') s3_secret_key = get_external_param('s3-secret-key', 'YCM7Ovup55wDkymyEtO8pw5F10_L5jtVY8w_fake') s3_buckets = get_external_param('s3-buckets', 'ydb-tiering-test-1,ydb-tiering-test-2').split(',') + assert len(s3_buckets) == 2, f'expected 2 bucket configs, got {len(s3_buckets)}' + s3_configs = [ ObjectStorageParams( scheme='HTTP', @@ -109,44 +161,84 @@ def scenario_alter_tiering_rule_while_writing(self, ctx: TestContext): sth = ScenarioTestHelper(ctx) tiers: list[str] = [] - tiering_rules: list[str] = [] for i, s3_config in enumerate(s3_configs): tiers.append(f'TestAlterTiering:tier{i}') + self._override_tier(sth, tiers[-1], TierConfig(tiers[-1], s3_config)) + + tiering_policy_configs: list[TieringPolicy] = [] + tiering_policy_configs.append(TieringPolicy().with_rule(TieringRule(tiers[0], '1s'))) + tiering_policy_configs.append(TieringPolicy().with_rule(TieringRule(tiers[1], '1s'))) + tiering_policy_configs.append(TieringPolicy().with_rule(TieringRule(tiers[0], '100000d'))) + tiering_policy_configs.append(TieringPolicy().with_rule(TieringRule(tiers[1], '100000d'))) + tiering_policy_configs.append(TieringPolicy().with_rule(TieringRule(tiers[0], '1s')).with_rule(TieringRule(tiers[1], '100000d'))) + tiering_policy_configs.append(TieringPolicy().with_rule(TieringRule(tiers[1], '1s')).with_rule(TieringRule(tiers[0], '100000d'))) + + tiering_rules: list[Optional[str]] = [] + for i, config in enumerate(tiering_policy_configs): tiering_rules.append(f'TestAlterTiering:tiering_rule{i}') - - tier_config = TierConfig(tiers[-1], s3_config) - tiering_config = TieringPolicy().with_rule(TieringRule(tiers[-1], '1s')) - - sth.execute_scheme_query(CreateTierIfNotExists(tiers[-1], tier_config)) - sth.execute_scheme_query(CreateTieringRuleIfNotExists(tiering_rules[-1], 'timestamp', tiering_config)) - - sth.execute_scheme_query(AlterTier(tiers[-1], tier_config)) - sth.execute_scheme_query(AlterTieringRule(tiering_rules[-1], 'timestamp', tiering_config)) - - sth.execute_scheme_query(CreateTableStore('store').with_schema(self.schema1)) - sth.execute_scheme_query(CreateTable('store/table').with_schema(self.schema1)) + self._override_tiering_rule(sth, tiering_rules[-1], 'timestamp', config) + tiering_rules.append(None) + + if not is_standalone_tables: + sth.execute_scheme_query(CreateTableStore('store').with_schema(self.schema1)) + + tables: list[str] = [] + tables_for_tiering_modification: list[str] = [] + for i in range(n_tables): + if is_standalone_tables: + tables.append(f'table{i}') + else: + tables.append(f'store/table{i}') + tables_for_tiering_modification.append(tables[-1]) + sth.execute_scheme_query(CreateTable(tables[-1]).with_schema(self.schema1)) + for i, tiering_rule in enumerate([tiering_rules[0], tiering_rules[1]]): + if is_standalone_tables: + tables.append(f'extra_table{i}') + else: + tables.append(f'store/extra_table{i}') + sth.execute_scheme_query(CreateTable(tables[-1]).with_schema(self.schema1)) + sth.execute_scheme_query(AlterTable(tables[-1]).set_tiering(tiering_rule)) + + if any(self._count_objects(bucket) != 0 for bucket in s3_configs): + assert any(sth.get_table_rows_count(table) != 0 for table in tables), \ + 'unrelated data in object storage: all tables are empty, but S3 is not' threads = [] - threads.append(self.TestThread( - target=self._change_tiering_rule, - args=[ctx, 'store/table', tiering_rules, test_duration] - )) - writer_id_offset = random.randint(0, 1 << 30) - for i in range(4): - threads.append(self.TestThread(target=self._upsert, args=[ctx, 'store/table', writer_id_offset + i, test_duration])) + # "Alter table drop column" causes scan failures + threads.append(TestThread(target=self._loop_alter_column, args=[ctx, 'store', test_duration])) + for table in tables_for_tiering_modification: + threads.append(TestThread( + target=self._loop_change_tiering_rule, + args=[ctx, table, random.sample(tiering_rules, len(tiering_rules)), test_duration] + )) + for i, table in enumerate(tables): + for writer in range(n_writers): + threads.append(TestThread(target=self._loop_upsert, args=[ctx, table, i * n_writers + writer, test_duration, allow_s3_unavailability])) + for tiering_rule in tiering_rules: + threads.append(TestThread( + target=self._loop_alter_tiering_rule, + args=[ctx, tiering_rule, random.sample(['timestamp', 'timestamp2'], 2), random.sample(tiering_policy_configs, len(tiering_policy_configs)), test_duration] + )) for thread in threads: thread.start() for thread in threads: thread.join() + assert any(self._count_objects(bucket) != 0 for bucket in s3_configs) + + for table in tables: + sth.execute_scheme_query(AlterTable(table).reset_tiering()) + for tiering in tiering_rules: sth.execute_scheme_query(DropTieringRule(tiering)) for tier in tiers: sth.execute_scheme_query(DropTier(tier)) - sth.execute_scheme_query(AlterTable('store/table').set_ttl('P1D', 'timestamp')) + for table in tables: + sth.execute_scheme_query(DropTable(table)) + if not is_standalone_tables: + sth.execute_scheme_query(DropTableStore('store')) - while sth.execute_scan_query(f'SELECT COUNT(*) FROM `{sth.get_full_path("store/table")}`').result_set.rows[0][0]: - time.sleep(10) + assert all(self._count_objects(bucket) == 0 for bucket in s3_configs) diff --git a/ydb/tests/olap/scenario/test_insert.py b/ydb/tests/olap/scenario/test_insert.py new file mode 100644 index 000000000000..deee48050c23 --- /dev/null +++ b/ydb/tests/olap/scenario/test_insert.py @@ -0,0 +1,86 @@ +from conftest import BaseTestSet +from ydb.tests.olap.scenario.helpers import ( + ScenarioTestHelper, + TestContext, + CreateTable, +) +from helpers.thread_helper import TestThread +from ydb import PrimitiveType +from typing import List, Dict, Any +from ydb.tests.olap.lib.utils import get_external_param + + +class TestInsert(BaseTestSet): + schema_cnt = ( + ScenarioTestHelper.Schema() + .with_column(name="key", type=PrimitiveType.Int32, not_null=True) + .with_column(name="c", type=PrimitiveType.Int64) + .with_key_columns("key") + ) + + schema_log = ( + ScenarioTestHelper.Schema() + .with_column(name="key", type=PrimitiveType.Int32, not_null=True) + .with_key_columns("key") + ) + + def _loop_upsert(self, ctx: TestContext, data: list): + sth = ScenarioTestHelper(ctx) + for batch in data: + sth.bulk_upsert_data("log", self.schema_log, batch) + + def _loop_insert(self, ctx: TestContext, rows_count: int): + sth = ScenarioTestHelper(ctx) + log: str = sth.get_full_path("log") + cnt: str = sth.get_full_path("cnt") + for i in range(rows_count): + sth.execute_query( + f'$cnt = SELECT CAST(COUNT(*) AS INT64) from `{log}`; INSERT INTO `{cnt}` (key, c) values({i}, $cnt)' + ) + + def scenario_read_data_during_bulk_upsert(self, ctx: TestContext): + sth = ScenarioTestHelper(ctx) + cnt_table_name: str = "cnt" + log_table_name: str = "log" + batches_count = int(get_external_param("batches_count", "10")) + rows_count = int(get_external_param("rows_count", "1000")) + inserts_count = int(get_external_param("inserts_count", "200")) + sth.execute_scheme_query( + CreateTable(cnt_table_name).with_schema(self.schema_cnt) + ) + sth.execute_scheme_query( + CreateTable(log_table_name).with_schema(self.schema_log) + ) + data: List = [] + for i in range(batches_count): + batch: List[Dict[str, Any]] = [] + for j in range(rows_count): + batch.append({"key": j + rows_count * i}) + data.append(batch) + + thread1 = TestThread(target=self._loop_upsert, args=[ctx, data]) + thread2 = TestThread(target=self._loop_insert, args=[ctx, inserts_count]) + + thread1.start() + thread2.start() + + thread2.join() + thread1.join() + + rows: int = sth.get_table_rows_count(cnt_table_name) + assert rows == inserts_count + scan_result = sth.execute_scan_query( + f"SELECT key, c FROM `{sth.get_full_path(cnt_table_name)}` ORDER BY key" + ) + for i in range(rows): + if scan_result.result_set.rows[i]["key"] != i: + assert False, f"{i} ?= {scan_result.result_set.rows[i]['key']}" + + rows: int = sth.get_table_rows_count(log_table_name) + assert rows == rows_count * batches_count + scan_result = sth.execute_scan_query( + f"SELECT key FROM `{sth.get_full_path(log_table_name)}` ORDER BY key" + ) + for i in range(rows): + if scan_result.result_set.rows[i]["key"] != i: + assert False, f"{i} ?= {scan_result.result_set.rows[i]['key']}" diff --git a/ydb/tests/olap/scenario/test_scheme_load.py b/ydb/tests/olap/scenario/test_scheme_load.py index b3e0dfebd35d..fa73c1c96bbb 100644 --- a/ydb/tests/olap/scenario/test_scheme_load.py +++ b/ydb/tests/olap/scenario/test_scheme_load.py @@ -7,7 +7,7 @@ DropTable, ) from ydb import PrimitiveType -import threading +from helpers.thread_helper import TestThread import allure @@ -19,20 +19,6 @@ class TestSchemeLoad(BaseTestSet): .with_key_columns('id') ) - class TestThread(threading.Thread): - def run(self) -> None: - self.exc = None - try: - self.ret = self._target(*self._args, **self._kwargs) - except BaseException as e: - self.exc = e - - def join(self, timeout=None): - super().join(timeout) - if self.exc: - raise self.exc - return self.ret - def _create_tables(self, prefix: str, count: int, ctx: TestContext): sth = ScenarioTestHelper(ctx) for i in range(count): @@ -44,7 +30,7 @@ def _drop_tables(self, prefix: str, count: int, ctx: TestContext): sth.execute_scheme_query(DropTable(f'store/{prefix}_{i}')) def scenario_create_and_drop_tables(self, ctx: TestContext): - tables_count = 100000 + tables_count = 100 threads_count = 20 ScenarioTestHelper(ctx).execute_scheme_query(CreateTableStore('store').with_schema(self.schema1)) @@ -52,7 +38,7 @@ def scenario_create_and_drop_tables(self, ctx: TestContext): threads = [] for t in range(threads_count): threads.append( - self.TestThread(target=self._create_tables, args=[str(t), int(tables_count / threads_count), ctx]) + TestThread(target=self._create_tables, args=[str(t), int(tables_count / threads_count), ctx]) ) threads[-1].start() for t in threads: @@ -62,7 +48,7 @@ def scenario_create_and_drop_tables(self, ctx: TestContext): threads = [] for t in range(threads_count): threads.append( - self.TestThread(target=self._drop_tables, args=[str(t), int(tables_count / threads_count), ctx]) + TestThread(target=self._drop_tables, args=[str(t), int(tables_count / threads_count), ctx]) ) threads[-1].start() for t in threads: diff --git a/ydb/tests/olap/scenario/ya.make b/ydb/tests/olap/scenario/ya.make index 58a33a89fdba..4a47da7ae920 100644 --- a/ydb/tests/olap/scenario/ya.make +++ b/ydb/tests/olap/scenario/ya.make @@ -1,9 +1,5 @@ PY3TEST() - TAG(ya:manual) - - TIMEOUT(600) - PY_SRCS ( conftest.py ) @@ -12,6 +8,13 @@ PY3TEST() test_simple.py test_scheme_load.py test_alter_tiering.py + test_insert.py + test_alter_compression.py + ) + + ENV(YDB_DRIVER_BINARY="ydb/apps/ydbd/ydbd") + DEPENDS( + ydb/apps/ydbd ) PEERDIR( @@ -22,8 +25,11 @@ PY3TEST() ydb/public/sdk/python ydb/public/sdk/python/enable_v3_new_behavior ydb/tests/olap/lib + ydb/tests/library ydb/tests/olap/scenario/helpers library/python/testing/yatest_common ) + SIZE(MEDIUM) + END() diff --git a/ydb/tests/workloads/olap_workload/__main__.py b/ydb/tests/workloads/olap_workload/__main__.py new file mode 100644 index 000000000000..60848ce91da1 --- /dev/null +++ b/ydb/tests/workloads/olap_workload/__main__.py @@ -0,0 +1,340 @@ +# -*- coding: utf-8 -*- +import argparse +import ydb +import time +import os +import random +import threading + +ydb.interceptor.monkey_patch_event_handler() + + +class YdbClient: + def __init__(self, endpoint, database, use_query_service=False): + self.driver = ydb.Driver(endpoint=endpoint, database=database, oauth=None) + self.database = database + self.use_query_service = use_query_service + self.session_pool = ydb.QuerySessionPool(self.driver) if use_query_service else ydb.SessionPool(self.driver) + + def wait_connection(self, timeout=5): + self.driver.wait(timeout, fail_fast=True) + + def query(self, statement, is_ddl): + if self.use_query_service: + return self.session_pool.execute_with_retries(statement) + else: + if is_ddl: + return self.session_pool.retry_operation_sync(lambda session: session.execute_scheme(statement)) + else: + raise "Unsuppported dml" # TODO implement me + + def drop_table(self, path_to_table): + if self.use_query_service: + self.session_pool.execute_with_retries(f"DROP TABLE `{path_to_table}`") + else: + self.session_pool.retry_operation_sync(lambda session: session.drop_table(path_to_table)) + + def describe(self, path): + try: + return self.driver.scheme_client.describe_path(path) + except ydb.issues.SchemeError as e: + if "Path not found" in e.message: + return None + raise e + + def _remove_recursively(self, path): + deleted = 0 + d = self.driver.scheme_client.list_directory(path) + for entry in d.children: + entry_path = "/".join([path, entry.name]) + if entry.is_directory(): + deleted += self._remove_recursively(entry_path) + elif entry.is_column_table() or entry.is_table(): + self.drop_table(entry_path) + deleted += 1 + else: + raise f"Scheme entry {entry_path} of unexpected type" + self.driver.scheme_client.remove_directory(path) + return deleted + + def remove_recursively(self, path): + d = self.describe(path) + if d is None: + return + if not d.is_directory(): + raise f"{path} has unexpected type" + return self._remove_recursively(path) + + +class WorkloadBase: + def __init__(self, client, tables_prefix, workload_name, stop): + self.client = client + self.table_prefix = tables_prefix + '/' + workload_name + self.name = workload_name + self.stop = stop + self.workload_threads = [] + + def name(self): + return self.name + + def get_table_path(self, table_name): + return "/".join([self.client.database, self.table_prefix, table_name]) + + def is_stop_requested(self): + return self.stop.is_set() + + def start(self): + funcs = self.get_workload_thread_funcs() + + def wrapper(f): + try: + f() + except Exception as e: + print(f"FATAL: {e}") + os._exit(1) + + for f in funcs: + t = threading.Thread(target=lambda: wrapper(f)) + t.start() + self.workload_threads.append(t) + + def join(self): + for t in self.workload_threads: + t.join() + + +supported_pk_types = [ + # Bool https://github.com/ydb-platform/ydb/issues/13037 + "Int8", + "Int16", + "Int32", + "Int64", + "Uint8", + "Uint16", + "Uint32", + "Uint64", + "Decimal(22,9)", + # "DyNumber", https://github.com/ydb-platform/ydb/issues/13048 + + "String", + "Utf8", + # Uuid", https://github.com/ydb-platform/ydb/issues/13047 + + "Date", + "Datetime", + "Datetime64", + "Timestamp", + # "Interval", https://github.com/ydb-platform/ydb/issues/13050 +] + +supported_types = supported_pk_types + [ + "Float", + "Double", + "Json", + "JsonDocument", + "Yson" +] + + +class WorkloadTablesCreateDrop(WorkloadBase): + def __init__(self, client, prefix, stop, allow_nullables_in_pk): + super().__init__(client, prefix, "create_drop", stop) + self.allow_nullables_in_pk = allow_nullables_in_pk + self.created = 0 + self.deleted = 0 + self.tables = set() + self.lock = threading.Lock() + + def get_stat(self): + with self.lock: + return f"Created: {self.created}, Deleted: {self.deleted}, Exists: {len(self.tables)}" + + def _generate_new_table_n(self): + while True: + r = random.randint(1, 40000) + with self.lock: + if r not in self.tables: + return r + + def _get_existing_table_n(self): + with self.lock: + if len(self.tables) == 0: + return None + return next(iter(self.tables)) + + def create_table(self, table): + path = self.get_table_path(table) + column_n = random.randint(1, 10000) + primary_key_column_n = random.randint(1, column_n) + partition_key_column_n = random.randint(1, primary_key_column_n) + column_defs = [] + for i in range(column_n): + if i < primary_key_column_n: + c = random.choice(supported_pk_types) + if not self.allow_nullables_in_pk or random.choice([False, True]): + c += " NOT NULL" + else: + c = random.choice(supported_types) + if random.choice([False, True]): + c += " NOT NULL" + column_defs.append(c) + + stmt = f""" + CREATE TABLE `{path}` ( + {", ".join(["c" + str(i) + " " + column_defs[i] for i in range(column_n)])}, + PRIMARY KEY({", ".join(["c" + str(i) for i in range(primary_key_column_n)])}) + ) + PARTITION BY HASH({", ".join(["c" + str(i) for i in range(partition_key_column_n)])}) + WITH ( + STORE = COLUMN + ) + """ + self.client.query(stmt, True) + + def _create_tables_loop(self): + while not self.is_stop_requested(): + n = self._generate_new_table_n() + self.create_table(str(n)) + with self.lock: + self.tables.add(n) + self.created += 1 + + def _delete_tables_loop(self): + while not self.is_stop_requested(): + n = self._get_existing_table_n() + if n is None: + print("create_drop: No tables to delete") + time.sleep(10) + continue + self.client.drop_table(self.get_table_path(str(n))) + with self.lock: + self.tables.remove(n) + self.deleted += 1 + + def get_workload_thread_funcs(self): + r = [self._create_tables_loop for x in range(0, 10)] + r.append(self._delete_tables_loop) + return r + + +class WorkloadInsertDelete(WorkloadBase): + def __init__(self, client, prefix, stop): + super().__init__(client, prefix, "insert_delete", stop) + self.inserted = 0 + self.current = 0 + self.table_name = "table" + self.lock = threading.Lock() + + def get_stat(self): + with self.lock: + return f"Inserted: {self.inserted}, Current: {self.current}" + + def _loop(self): + table_path = self.get_table_path(self.table_name) + self.client.query( + f""" + CREATE TABLE `{table_path}` ( + id Int64 NOT NULL, + i64Val Int64, + PRIMARY KEY(id) + ) + PARTITION BY HASH(id) + WITH ( + STORE = COLUMN + ) + """, + True, + ) + i = 1 + while not self.is_stop_requested(): + self.client.query( + f""" + INSERT INTO `{table_path}` (`id`, `i64Val`) + VALUES + ({i * 2}, {i * 10}), + ({i * 2 + 1}, {i * 10 + 1}) + """, + False, + ) + + self.client.query( + f""" + DELETE FROM `{table_path}` + WHERE i64Val % 2 == 1 + """, + False, + ) + + actual = self.client.query( + f""" + SELECT COUNT(*) as cnt, SUM(i64Val) as vals, SUM(id) as ids FROM `{table_path}` + """, + False, + )[0].rows[0] + expected = {"cnt": i, "vals": i * (i + 1) * 5, "ids": i * (i + 1)} + if actual != expected: + raise Exception(f"Incorrect result: expected:{expected}, actual:{actual}") + i += 1 + with self.lock: + self.inserted += 2 + self.current = actual["cnt"] + + def get_workload_thread_funcs(self): + return [self._loop] + + +class WorkloadRunner: + def __init__(self, client, name, duration, allow_nullables_in_pk): + self.client = client + self.name = args.path + self.tables_prefix = "/".join([self.client.database, self.name]) + self.duration = args.duration + self.allow_nullables_in_pk = allow_nullables_in_pk + + def __enter__(self): + self._cleanup() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self._cleanup() + + def _cleanup(self): + print(f"Cleaning up {self.tables_prefix}...") + deleted = client.remove_recursively(self.tables_prefix) + print(f"Cleaning up {self.tables_prefix}... done, {deleted} tables deleted") + + def run(self): + stop = threading.Event() + workloads = [ + WorkloadTablesCreateDrop(self.client, self.name, stop, self.allow_nullables_in_pk), + # WorkloadInsertDelete(self.client, self.name, stop), TODO fix https://github.com/ydb-platform/ydb/issues/12871 + ] + for w in workloads: + w.start() + started_at = started_at = time.time() + while time.time() - started_at < self.duration: + print(f"Elapsed {(int)(time.time() - started_at)} seconds, stat:") + for w in workloads: + print(f"\t{w.name}: {w.get_stat()}") + time.sleep(10) + stop.set() + print("Waiting for stop...") + for w in workloads: + w.join() + print("Waiting for stop... stopped") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="olap stability workload", formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument("--endpoint", default="localhost:2135", help="An endpoint to be used") + parser.add_argument("--database", default="Root/test", help="A database to connect") + parser.add_argument("--path", default="olap_workload", help="A path prefix for tables") + parser.add_argument("--duration", default=10 ** 9, type=lambda x: int(x), help="A duration of workload in seconds.") + parser.add_argument("--allow-nullables-in-pk", default=False, help="Allow nullable types for columns in a Primary Key.") + args = parser.parse_args() + client = YdbClient(args.endpoint, args.database, True) + client.wait_connection() + with WorkloadRunner(client, args.path, args.duration, args.allow_nullables_in_pk) as runner: + runner.run() diff --git a/ydb/tests/workloads/olap_workload/tests/test_workload.py b/ydb/tests/workloads/olap_workload/tests/test_workload.py new file mode 100644 index 000000000000..b50f644353de --- /dev/null +++ b/ydb/tests/workloads/olap_workload/tests/test_workload.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +import yatest + +from ydb.tests.library.harness.kikimr_runner import KiKiMR +from ydb.tests.library.harness.kikimr_config import KikimrConfigGenerator +from ydb.tests.library.common.types import Erasure + + +class TestYdbWorkload(object): + @classmethod + def setup_class(cls): + cls.cluster = KiKiMR(KikimrConfigGenerator( + erasure=Erasure.MIRROR_3_DC, + column_shard_config={ + "allow_nullable_columns_in_pk": True, + } + )) + cls.cluster.start() + + @classmethod + def teardown_class(cls): + cls.cluster.stop() + + def test(self): + workload_path = yatest.common.build_path("ydb/tests/workloads/olap_workload/olap_workload") + yatest.common.execute( + [ + workload_path, + "--endpoint", f"grpc://localhost:{self.cluster.nodes[1].grpc_port}", + "--database=/Root", + "--duration", "120", + "--allow-nullables-in-pk", "1", + ], + wait=True + ) diff --git a/ydb/tests/workloads/olap_workload/tests/ya.make b/ydb/tests/workloads/olap_workload/tests/ya.make new file mode 100644 index 000000000000..b8d79d44e671 --- /dev/null +++ b/ydb/tests/workloads/olap_workload/tests/ya.make @@ -0,0 +1,25 @@ +PY3TEST() +ENV(YDB_DRIVER_BINARY="ydb/apps/ydbd/ydbd") + +TEST_SRCS( + test_workload.py +) + +IF (SANITIZER_TYPE) + REQUIREMENTS(ram:32) +ENDIF() + +SIZE(MEDIUM) + +DEPENDS( + ydb/apps/ydbd + ydb/apps/ydb + ydb/tests/workloads/olap_workload +) + +PEERDIR( + ydb/tests/library +) + + +END() diff --git a/ydb/tools/olap_workload/ya.make b/ydb/tests/workloads/olap_workload/ya.make similarity index 60% rename from ydb/tools/olap_workload/ya.make rename to ydb/tests/workloads/olap_workload/ya.make index 939ecf1af94b..442bc7d4a585 100644 --- a/ydb/tools/olap_workload/ya.make +++ b/ydb/tests/workloads/olap_workload/ya.make @@ -6,7 +6,12 @@ PY_SRCS( PEERDIR( ydb/public/sdk/python + ydb/public/sdk/python/enable_v3_new_behavior library/python/monlib ) END() + +RECURSE_FOR_TESTS( + tests +) diff --git a/ydb/tools/olap_workload/__main__.py b/ydb/tools/olap_workload/__main__.py deleted file mode 100644 index 02ee03f4f231..000000000000 --- a/ydb/tools/olap_workload/__main__.py +++ /dev/null @@ -1,199 +0,0 @@ -# -*- coding: utf-8 -*- -import argparse -import ydb -import time -import os -import random -import string - -ydb.interceptor.monkey_patch_event_handler() - - -def timestamp(): - return int(1000 * time.time()) - - -def table_name_with_timestamp(): - return os.path.join("column_table_" + str(timestamp())) - - -def random_string(length): - letters = string.ascii_lowercase - return bytes(''.join(random.choice(letters) for i in range(length)), encoding='utf8') - - -def random_type(): - return random.choice([ydb.PrimitiveType.Int64, ydb.PrimitiveType.String]) - - -def random_value(type): - if isinstance(type, ydb.OptionalType): - return random_value(type.item) - if type == ydb.PrimitiveType.Int64: - return random.randint(0, 1 << 31) - if type == ydb.PrimitiveType.String: - return random_string(random.randint(1, 32)) - - -class Workload(object): - def __init__(self, endpoint, database, duration, batch_size): - self.database = database - self.driver = ydb.Driver(ydb.DriverConfig(endpoint, database)) - self.pool = ydb.SessionPool(self.driver, size=200) - self.duration = duration - self.batch_size = batch_size - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.pool.stop() - self.driver.stop() - - def run_query_ignore_errors(self, callee): - try: - self.pool.retry_operation_sync(callee) - except Exception as e: - print(type(e), e) - - def create_table(self, table_name): - print(f"Create table {table_name}") - - def callee(session): - session.execute_scheme( - f""" - CREATE TABLE {table_name} ( - id Int64 NOT NULL, - i64Val Int64, - PRIMARY KEY(id) - ) - PARTITION BY HASH(id) - WITH ( - STORE = COLUMN - ) - """ - ) - - self.run_query_ignore_errors(callee) - - def drop_table(self, table_name): - print(f"Drop table {table_name}") - - def callee(session): - session.drop_table(self.database + "/" + table_name) - - self.run_query_ignore_errors(callee) - - def add_column(self, table_name, col_name, col_type): - print(f"Add column {table_name}.{col_name} {str(col_type)}") - - def callee(session): - session.execute_scheme(f"ALTER TABLE {table_name} ADD COLUMN {col_name} {str(col_type)}") - - self.run_query_ignore_errors(callee) - - def drop_column(self, table_name, col_name): - print(f"Drop column {table_name}.{col_name}") - - def callee(session): - session.execute_scheme(f"ALTER TABLE {table_name} DROP COLUMN {col_name}") - - self.run_query_ignore_errors(callee) - - def generate_batch(self, schema): - data = [] - - for i in range(self.batch_size): - data.append({c.name: random_value(c.type) for c in schema}) - - return data - - def add_batch(self, table_name, schema): - print(f"Add batch {table_name}") - - column_types = ydb.BulkUpsertColumns() - - for c in schema: - column_types.add_column(c.name, c.type) - - batch = self.generate_batch(schema) - - self.driver.table_client.bulk_upsert(self.database + "/" + table_name, batch, column_types) - - def list_tables(self): - db = self.driver.scheme_client.list_directory(self.database) - return [t.name for t in db.children if t.type == ydb.SchemeEntryType.COLUMN_TABLE] - - def list_columns(self, table_name): - path = self.database + "/" + table_name - - def callee(session): - return session.describe_table(path).columns - - return self.pool.retry_operation_sync(callee) - - def rows_count(self, table_name): - return self.driver.table_client.scan_query(f"SELECT count(*) FROM {table_name}").next().result_set.rows[0][0] - - def select_n(self, table_name, limit): - print(f"Select {limit} from {table_name}") - self.driver.table_client.scan_query(f"SELECT * FROM {table_name} limit {limit}").next() - - def drop_all_tables(self): - for t in self.list_tables(): - if t.startswith("column_table_"): - self.drop_table(t) - - def drop_all_columns(self, table_name): - for c in self.list_columns(table_name): - if c.name != "id": - self.drop_column(table_name, c.name) - - def queries_while_alter(self): - table_name = "queries_while_alter" - - schema = self.list_columns(table_name) - - self.select_n(table_name, 1000) - self.add_batch(table_name, schema) - self.select_n(table_name, 100) - self.add_batch(table_name, schema) - self.select_n(table_name, 300) - - if len(schema) > 50: - self.drop_all_columns(table_name) - - if self.rows_count(table_name) > 100000: - self.drop_table(table_name) - - col = "col_" + str(timestamp()) - self.add_column(table_name, col, random_type()) - - def run(self): - started_at = time.time() - - while time.time() - started_at < self.duration: - try: - self.create_table("queries_while_alter") - - self.drop_all_tables() - - self.queries_while_alter() - - table_name = table_name_with_timestamp() - self.create_table(table_name) - except Exception as e: - print(type(e), e) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser( - description="olap stability workload", formatter_class=argparse.RawDescriptionHelpFormatter - ) - parser.add_argument('--endpoint', default='localhost:2135', help="An endpoint to be used") - parser.add_argument('--database', default=None, required=True, help='A database to connect') - parser.add_argument('--duration', default=120, type=lambda x: int(x), help='A duration of workload in seconds.') - parser.add_argument('--batch_size', default=1000, help='Batch size for bulk insert') - args = parser.parse_args() - with Workload(args.endpoint, args.database, args.duration, args.batch_size) as workload: - workload.run() diff --git a/ydb/tools/ya.make b/ydb/tools/ya.make index 375abee36446..e209a795f207 100644 --- a/ydb/tools/ya.make +++ b/ydb/tools/ya.make @@ -4,7 +4,7 @@ RECURSE( query_replay query_replay_yt simple_queue - olap_workload + stress_tool tsserver tstool ydbd_slice