diff --git a/.github/.licenserc.yaml b/.github/.licenserc.yaml index a667c903e..dcfcd64f6 100644 --- a/.github/.licenserc.yaml +++ b/.github/.licenserc.yaml @@ -29,6 +29,7 @@ header: - 'LICENSE' - 'NOTICE' - 'requirements.txt' + - 'src/iceberg/expected.h' - 'src/iceberg/util/murmurhash3_internal.*' - 'src/iceberg/test/resources/**' - 'src/iceberg/catalog/hive/gen-cpp/**' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 352da0d7a..524693b87 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,12 +78,19 @@ jobs: with: key-prefix: sccache-test-ubuntu-${{ matrix.cmake_build_type }} job-status: ${{ job.status }} - - name: Build Example + - name: Build Example (C++20) shell: bash env: CC: gcc-14 CXX: g++-14 run: ci/scripts/build_example.sh $(pwd)/example + - name: Build Example (C++23) + shell: bash + env: + CC: gcc-14 + CXX: g++-14 + ICEBERG_EXAMPLE_CXX_STANDARD: 23 + run: ci/scripts/build_example.sh $(pwd)/example hive: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} name: AMD64 Ubuntu 26.04 Hive @@ -153,9 +160,14 @@ jobs: with: key-prefix: sccache-test-macos job-status: ${{ job.status }} - - name: Build Example + - name: Build Example (C++20) shell: bash run: ci/scripts/build_example.sh $(pwd)/example + - name: Build Example (C++23) + shell: bash + env: + ICEBERG_EXAMPLE_CXX_STANDARD: 23 + run: ci/scripts/build_example.sh $(pwd)/example windows: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} name: AMD64 Windows 2025 @@ -199,8 +211,15 @@ jobs: with: key-prefix: sccache-test-windows job-status: ${{ job.status }} - - name: Build Example + - name: Build Example (C++20) + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + bash -lc 'ci/scripts/build_example.sh $(pwd)/example' + - name: Build Example (C++23) shell: pwsh + env: + ICEBERG_EXAMPLE_CXX_STANDARD: 23 run: | $ErrorActionPreference = "Stop" bash -lc 'ci/scripts/build_example.sh $(pwd)/example' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 27ff321da..4aba3e5d6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,3 +43,12 @@ repos: rev: v0.6.10 hooks: - id: cmake-format + + - repo: local + hooks: + - id: generate-public-headers + name: check src/iceberg/cpp20_compatibility_internal.h is up to date + entry: python ci/scripts/generate_public_headers.py + language: python + files: ^src/.*(\.(h)|CMakeLists\.txt)$ + pass_filenames: false diff --git a/LICENSE b/LICENSE index d0429e24b..6d0d88e91 100644 --- a/LICENSE +++ b/LICENSE @@ -230,6 +230,34 @@ License: https://www.apache.org/licenses/LICENSE-2.0 -------------------------------------------------------------------------------- +This product includes code from zeus-cpp/expected. + +* src/iceberg/expected.h is adapted from zeus-cpp/expected. + +Copyright: 2024 zeus-cpp. +Home page: https://github.com/zeus-cpp/expected +License: MIT + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +-------------------------------------------------------------------------------- + This product bundles utf8proc, which is available under the MIT License: utf8proc is a software package originally developed by Jan Behrens and the rest diff --git a/README.md b/README.md index 920b01a59..68ecf4500 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,20 @@ C++ implementation of [Apache Iceberg™](https://iceberg.apache.org/). - Python 3 and [pre-commit](https://pre-commit.com/) (for linting) +## C++20 Compatibility + +iceberg-cpp is built as C++23, but its installed public headers are also usable +from C++20. Applications that consume iceberg-cpp can therefore be compiled as +C++20 or later; C++20 is the minimum standard supported for the public headers. + +This contract is checked in two ways: `cpp20_compatibility_test` compiles every +public header as C++20 as part of the regular test suite, and CI builds the +[example](example) against the installed headers as both C++20 and C++23 (see +`ICEBERG_EXAMPLE_CXX_STANDARD` in [Build Examples](#build-examples)). The test's +header list, `src/iceberg/cpp20_compatibility_internal.h`, is generated by +`ci/scripts/generate_public_headers.py`; a pre-commit hook fails whenever it is +out of date, so new public headers cannot be missed. + ## Quick Start ```bash @@ -121,6 +135,14 @@ If you are using provided Apache Arrow, include `/path/to/arrow` in `CMAKE_PREFI cmake -S . -B build -G Ninja -DCMAKE_PREFIX_PATH="/path/to/install;/path/to/arrow" ``` +The examples build as C++20 by default, which is the minimum standard supported +by the public headers. Set `ICEBERG_EXAMPLE_CXX_STANDARD` to `23` to build them +as C++23 instead: + +```bash +cmake -S . -B build -G Ninja -DCMAKE_PREFIX_PATH=/path/to/install -DICEBERG_EXAMPLE_CXX_STANDARD=23 +``` + ## Customizing Dependency URLs If you experience network issues when downloading dependencies, you can customize the download URLs using environment variables: diff --git a/ci/scripts/build_example.sh b/ci/scripts/build_example.sh index 7ccdf4a8f..386019ad8 100755 --- a/ci/scripts/build_example.sh +++ b/ci/scripts/build_example.sh @@ -22,6 +22,7 @@ set -eux source_dir=${1} build_dir=${1}/build run_example=${ICEBERG_RUN_EXAMPLE:-OFF} +cxx_standard=${ICEBERG_EXAMPLE_CXX_STANDARD:-20} # Clean up before configuring. If Windows still holds a just-built exe/dll # after the retries, let mkdir fail rather than reuse a half-deleted tree. @@ -53,6 +54,7 @@ fi build_type="${ICEBERG_BUILD_TYPE:-Debug}" CMAKE_ARGS+=("-DCMAKE_BUILD_TYPE=${build_type}") +CMAKE_ARGS+=("-DICEBERG_EXAMPLE_CXX_STANDARD=${cxx_standard}") cmake "${CMAKE_ARGS[@]}" ${source_dir} cmake --build . diff --git a/ci/scripts/generate_public_headers.py b/ci/scripts/generate_public_headers.py new file mode 100755 index 000000000..56f196d87 --- /dev/null +++ b/ci/scripts/generate_public_headers.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +"""Regenerate src/iceberg/cpp20_compatibility_internal.h. + +The generated header includes every public header, i.e. every header that +iceberg_install_all_headers() installs: *.h files in a directory whose +CMakeLists.txt calls it, except those with "internal" in their name. This script +applies the same rule, so the list never has to be maintained by hand. The +generate-public-headers pre-commit hook runs it and fails when the checked-in +header is out of date. + +Headers of optional components are wrapped in a macro that +cpp20_compatibility_test defines only when that component is built. +""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +SRC = ROOT / "src" +OUTPUT = SRC / "iceberg" / "cpp20_compatibility_internal.h" + +# Install path prefix -> macro guarding it. The first match wins, so list more +# specific prefixes first. Anything unmatched is always built. +OPTIONAL_COMPONENTS = [ + ("iceberg/catalog/hive", "ICEBERG_PUBLIC_HEADERS_HIVE"), + ("iceberg/catalog/rest", "ICEBERG_PUBLIC_HEADERS_REST"), + ("iceberg/catalog/sql", "ICEBERG_PUBLIC_HEADERS_SQL_CATALOG"), + ("iceberg/arrow", "ICEBERG_PUBLIC_HEADERS_BUNDLE"), + ("iceberg/avro", "ICEBERG_PUBLIC_HEADERS_BUNDLE"), + ("iceberg/parquet", "ICEBERG_PUBLIC_HEADERS_BUNDLE"), +] + +INSTALL_CALL = re.compile(r"iceberg_install_all_headers\(\s*([^\s)]+)") + +LICENSE = """\ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +""" + + +def component_of(install_path: str) -> str | None: + for prefix, macro in OPTIONAL_COMPONENTS: + if install_path == prefix or install_path.startswith(prefix + "/"): + return macro + return None + + +def collect_public_headers() -> dict[str | None, list[str]]: + headers: dict[str | None, list[str]] = {} + for cmake_file in sorted(SRC.rglob("CMakeLists.txt")): + for install_path in INSTALL_CALL.findall(cmake_file.read_text()): + for header in cmake_file.parent.iterdir(): + if header.suffix != ".h" or "internal" in header.name: + continue + headers.setdefault(component_of(install_path), []).append( + f"{install_path}/{header.name}" + ) + return headers + + +def render(headers: dict[str | None, list[str]]) -> str: + lines = [ + LICENSE, + "// Generated by ci/scripts/generate_public_headers.py; do not edit by hand.", + "//", + "// Includes every public (installed) header. cpp20_compatibility_test compiles", + "// it as C++20 to keep the public headers usable from C++20.", + "", + "#pragma once", + "", + ] + lines += [f'#include "{h}"' for h in sorted(headers.get(None, []))] + for macro in sorted({m for _, m in OPTIONAL_COMPONENTS}): + if macro not in headers: + continue + lines += ["", f"#ifdef {macro}"] + lines += [f'# include "{h}"' for h in sorted(headers[macro])] + lines += [f"#endif // {macro}"] + return "\n".join(lines) + "\n" + + +def main() -> int: + content = render(collect_public_headers()) + if OUTPUT.exists() and OUTPUT.read_text() == content: + return 0 + OUTPUT.write_text(content) + print(f"Regenerated {OUTPUT.relative_to(ROOT)}; commit the updated file.") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index a9bf73cf0..5e565dfbc 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -20,13 +20,75 @@ cmake_minimum_required(VERSION 3.25) project(example) -set(CMAKE_CXX_STANDARD 23) +# C++20 is the minimum standard iceberg-cpp's public headers support, so the +# example builds as C++20 by default to keep that contract exercised. Set this to a +# newer standard (e.g. 23) to check the headers from a newer consumer as well. +set(ICEBERG_EXAMPLE_CXX_STANDARD + 20 + CACHE STRING "C++ standard used to build the example (20 or newer)") +if(ICEBERG_EXAMPLE_CXX_STANDARD MATCHES "^(98|11|14|17)$") + message(FATAL_ERROR "ICEBERG_EXAMPLE_CXX_STANDARD must be 20 or newer, got " + "'${ICEBERG_EXAMPLE_CXX_STANDARD}'") +endif() + +set(CMAKE_CXX_STANDARD ${ICEBERG_EXAMPLE_CXX_STANDARD}) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) find_package(iceberg CONFIG REQUIRED COMPONENTS bundle rest) +if(TARGET iceberg::iceberg_bundle_shared) + set(ICEBERG_BUNDLE_TARGET iceberg::iceberg_bundle_shared) +else() + set(ICEBERG_BUNDLE_TARGET iceberg::iceberg_bundle_static) +endif() + +if(TARGET iceberg::iceberg_rest_shared) + set(ICEBERG_REST_TARGET iceberg::iceberg_rest_shared) +else() + set(ICEBERG_REST_TARGET iceberg::iceberg_rest_static) +endif() + add_executable(demo_example demo_example.cc) -target_link_libraries(demo_example - PRIVATE "$,iceberg::iceberg_bundle_shared,iceberg::iceberg_bundle_static>" - "$,iceberg::iceberg_rest_shared,iceberg::iceberg_rest_static>" -) +target_link_libraries(demo_example PRIVATE ${ICEBERG_BUNDLE_TARGET} + ${ICEBERG_REST_TARGET}) + +# Compile every installed public header as a consumer using +# ICEBERG_EXAMPLE_CXX_STANDARD. The installed include +# tree is the public API contract: iceberg_install_all_headers excludes internal +# headers before packaging them. +get_target_property(ICEBERG_BUNDLE_INCLUDE_DIRS ${ICEBERG_BUNDLE_TARGET} + INTERFACE_INCLUDE_DIRECTORIES) +foreach(ICEBERG_INCLUDE_DIR IN LISTS ICEBERG_BUNDLE_INCLUDE_DIRS) + if(EXISTS "${ICEBERG_INCLUDE_DIR}/iceberg") + set(ICEBERG_PUBLIC_INCLUDE_DIR "${ICEBERG_INCLUDE_DIR}") + break() + endif() +endforeach() + +if(NOT ICEBERG_PUBLIC_INCLUDE_DIR) + message(FATAL_ERROR "Could not locate iceberg's installed public headers") +endif() + +file(GLOB_RECURSE ICEBERG_PUBLIC_HEADERS CONFIGURE_DEPENDS + "${ICEBERG_PUBLIC_INCLUDE_DIR}/iceberg/*.h") +list(SORT ICEBERG_PUBLIC_HEADERS) + +set(ICEBERG_PUBLIC_HEADER_CHECK_SOURCE + "// Generated from iceberg's installed public headers.\n") +foreach(ICEBERG_PUBLIC_HEADER IN LISTS ICEBERG_PUBLIC_HEADERS) + file(RELATIVE_PATH ICEBERG_PUBLIC_HEADER_RELATIVE_PATH "${ICEBERG_PUBLIC_INCLUDE_DIR}" + "${ICEBERG_PUBLIC_HEADER}") + string(APPEND ICEBERG_PUBLIC_HEADER_CHECK_SOURCE + "#include <${ICEBERG_PUBLIC_HEADER_RELATIVE_PATH}>\n") +endforeach() +string(APPEND ICEBERG_PUBLIC_HEADER_CHECK_SOURCE "\nint main() { return 0; }\n") + +file(GENERATE + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/public_headers_check.cc" + CONTENT "${ICEBERG_PUBLIC_HEADER_CHECK_SOURCE}") + +add_executable(public_headers_check "${CMAKE_CURRENT_BINARY_DIR}/public_headers_check.cc") +target_link_libraries(public_headers_check PRIVATE ${ICEBERG_BUNDLE_TARGET} + ${ICEBERG_REST_TARGET}) diff --git a/mkdocs/docs/getting-started.md b/mkdocs/docs/getting-started.md index 89ffd0a0b..8fa08cdc8 100644 --- a/mkdocs/docs/getting-started.md +++ b/mkdocs/docs/getting-started.md @@ -27,6 +27,20 @@ - CMake 3.25+ - [Ninja](https://ninja-build.org/) (recommended build backend) +## C++20 Compatibility + +iceberg-cpp is built as C++23, but its installed public headers are also usable +from C++20. Applications that consume iceberg-cpp can therefore be compiled as +C++20 or later; C++20 is the minimum standard supported for the public headers. + +This contract is checked in two ways: `cpp20_compatibility_test` compiles every +public header as C++20 as part of the regular test suite, and CI builds the +example against the installed headers as both C++20 and C++23 (see +`ICEBERG_EXAMPLE_CXX_STANDARD` in [Build Examples](#build-examples)). The test's +header list, `src/iceberg/cpp20_compatibility_internal.h`, is generated by +`ci/scripts/generate_public_headers.py`; a pre-commit hook fails whenever it is +out of date, so new public headers cannot be missed. + ## Quick Start ```bash @@ -112,6 +126,14 @@ If using provided Apache Arrow, include both paths: cmake -S . -B build -G Ninja -DCMAKE_PREFIX_PATH="/path/to/install;/path/to/arrow" ``` +The examples build as C++20 by default, which is the minimum standard supported +by the public headers. Set `ICEBERG_EXAMPLE_CXX_STANDARD` to `23` to build them +as C++23 instead: + +```bash +cmake -S . -B build -G Ninja -DCMAKE_PREFIX_PATH=/path/to/install -DICEBERG_EXAMPLE_CXX_STANDARD=23 +``` + ## Customizing Dependency URLs If you experience network issues when downloading dependencies, you can override the download URLs using environment variables: diff --git a/mkdocs/docs/index.md b/mkdocs/docs/index.md index 499583bad..73d92b283 100644 --- a/mkdocs/docs/index.md +++ b/mkdocs/docs/index.md @@ -25,7 +25,7 @@ iceberg-cpp is a C++ implementation of [Apache Iceberg™](https://iceberg.apach ## Key Features -- **Modern C++23** — Built with ranges, concepts, `std::expected`, and other modern idioms +- **Modern C++** — Built as C++23 with ranges, concepts, and other modern idioms; public headers require only C++20 - **Cross-Platform** — Builds and runs on Linux, macOS, and Windows - **Spec Compliance** — Full table spec support today; Puffin, View, and UDF specs are on the roadmap - **Arrow-Native** — Uses the Arrow C Data Interface as the primary data API diff --git a/src/iceberg/cpp20_compatibility_internal.h b/src/iceberg/cpp20_compatibility_internal.h new file mode 100644 index 000000000..d9162b924 --- /dev/null +++ b/src/iceberg/cpp20_compatibility_internal.h @@ -0,0 +1,244 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +// Generated by ci/scripts/generate_public_headers.py; do not edit by hand. +// +// Includes every public (installed) header. cpp20_compatibility_test compiles +// it as C++20 to keep the public headers usable from C++20. + +#pragma once + +#include "iceberg/arrow_c_data.h" +#include "iceberg/catalog.h" +#include "iceberg/catalog/catalog_util.h" +#include "iceberg/catalog/memory/in_memory_catalog.h" +#include "iceberg/catalog/session_catalog.h" +#include "iceberg/catalog/session_context.h" +#include "iceberg/constants.h" +#include "iceberg/data/data_writer.h" +#include "iceberg/data/delete_filter.h" +#include "iceberg/data/delete_loader.h" +#include "iceberg/data/equality_delete_writer.h" +#include "iceberg/data/file_scan_task_reader.h" +#include "iceberg/data/position_delete_writer.h" +#include "iceberg/data/writer.h" +#include "iceberg/delete_file_index.h" +#include "iceberg/deletes/dv_writer.h" +#include "iceberg/deletes/position_delete_index.h" +#include "iceberg/deletes/position_delete_range_consumer.h" +#include "iceberg/deletes/roaring_position_bitmap.h" +#include "iceberg/encryption/encrypted_key.h" +#include "iceberg/exception.h" +#include "iceberg/expected.h" +#include "iceberg/expression/aggregate.h" +#include "iceberg/expression/binder.h" +#include "iceberg/expression/evaluator.h" +#include "iceberg/expression/expression.h" +#include "iceberg/expression/expression_visitor.h" +#include "iceberg/expression/expressions.h" +#include "iceberg/expression/inclusive_metrics_evaluator.h" +#include "iceberg/expression/literal.h" +#include "iceberg/expression/manifest_evaluator.h" +#include "iceberg/expression/predicate.h" +#include "iceberg/expression/projections.h" +#include "iceberg/expression/residual_evaluator.h" +#include "iceberg/expression/rewrite_not.h" +#include "iceberg/expression/sanitize_expression.h" +#include "iceberg/expression/strict_metrics_evaluator.h" +#include "iceberg/expression/term.h" +#include "iceberg/file_format.h" +#include "iceberg/file_io.h" +#include "iceberg/file_io_registry.h" +#include "iceberg/file_reader.h" +#include "iceberg/file_writer.h" +#include "iceberg/geospatial.h" +#include "iceberg/iceberg_bundle_export.h" +#include "iceberg/iceberg_data_export.h" +#include "iceberg/iceberg_export.h" +#include "iceberg/inheritable_metadata.h" +#include "iceberg/inspect/history_table.h" +#include "iceberg/inspect/metadata_table.h" +#include "iceberg/inspect/snapshots_table.h" +#include "iceberg/location_provider.h" +#include "iceberg/logging/cerr_logger.h" +#include "iceberg/logging/log_level.h" +#include "iceberg/logging/log_macros.h" +#include "iceberg/logging/logger.h" +#include "iceberg/logging/loggers.h" +#include "iceberg/logging/short_log_macros.h" +#include "iceberg/manifest/manifest_entry.h" +#include "iceberg/manifest/manifest_filter_manager.h" +#include "iceberg/manifest/manifest_group.h" +#include "iceberg/manifest/manifest_list.h" +#include "iceberg/manifest/manifest_merge_manager.h" +#include "iceberg/manifest/manifest_reader.h" +#include "iceberg/manifest/manifest_writer.h" +#include "iceberg/manifest/rolling_manifest_writer.h" +#include "iceberg/metadata_columns.h" +#include "iceberg/metrics.h" +#include "iceberg/metrics/commit_report.h" +#include "iceberg/metrics/counter.h" +#include "iceberg/metrics/metrics_context.h" +#include "iceberg/metrics/metrics_reporter.h" +#include "iceberg/metrics/metrics_reporters.h" +#include "iceberg/metrics/metrics_types.h" +#include "iceberg/metrics/scan_report.h" +#include "iceberg/metrics/timer.h" +#include "iceberg/metrics_config.h" +#include "iceberg/name_mapping.h" +#include "iceberg/partition_field.h" +#include "iceberg/partition_spec.h" +#include "iceberg/puffin/file_metadata.h" +#include "iceberg/puffin/puffin_format.h" +#include "iceberg/puffin/puffin_reader.h" +#include "iceberg/puffin/puffin_writer.h" +#include "iceberg/puffin/type_fwd.h" +#include "iceberg/resolving_file_io.h" +#include "iceberg/result.h" +#include "iceberg/row/arrow_array_wrapper.h" +#include "iceberg/row/manifest_wrapper.h" +#include "iceberg/row/partition_values.h" +#include "iceberg/row/struct_like.h" +#include "iceberg/schema.h" +#include "iceberg/schema_field.h" +#include "iceberg/schema_util.h" +#include "iceberg/snapshot.h" +#include "iceberg/sort_field.h" +#include "iceberg/sort_order.h" +#include "iceberg/statistics_file.h" +#include "iceberg/storage_credential.h" +#include "iceberg/table.h" +#include "iceberg/table_identifier.h" +#include "iceberg/table_metadata.h" +#include "iceberg/table_properties.h" +#include "iceberg/table_requirement.h" +#include "iceberg/table_requirements.h" +#include "iceberg/table_scan.h" +#include "iceberg/table_update.h" +#include "iceberg/transaction.h" +#include "iceberg/transform.h" +#include "iceberg/transform_function.h" +#include "iceberg/type.h" +#include "iceberg/type_fwd.h" +#include "iceberg/update/delete_files.h" +#include "iceberg/update/expire_snapshots.h" +#include "iceberg/update/fast_append.h" +#include "iceberg/update/merge_append.h" +#include "iceberg/update/merging_snapshot_update.h" +#include "iceberg/update/overwrite_files.h" +#include "iceberg/update/pending_update.h" +#include "iceberg/update/replace_partitions.h" +#include "iceberg/update/rewrite_files.h" +#include "iceberg/update/row_delta.h" +#include "iceberg/update/set_snapshot.h" +#include "iceberg/update/snapshot_manager.h" +#include "iceberg/update/snapshot_update.h" +#include "iceberg/update/update_location.h" +#include "iceberg/update/update_partition_spec.h" +#include "iceberg/update/update_partition_statistics.h" +#include "iceberg/update/update_properties.h" +#include "iceberg/update/update_schema.h" +#include "iceberg/update/update_snapshot_reference.h" +#include "iceberg/update/update_sort_order.h" +#include "iceberg/update/update_statistics.h" +#include "iceberg/util/base64.h" +#include "iceberg/util/bucket_util.h" +#include "iceberg/util/checked_cast.h" +#include "iceberg/util/config.h" +#include "iceberg/util/content_file_util.h" +#include "iceberg/util/conversions.h" +#include "iceberg/util/data_file_set.h" +#include "iceberg/util/decimal.h" +#include "iceberg/util/endian.h" +#include "iceberg/util/error_collector.h" +#include "iceberg/util/executor.h" +#include "iceberg/util/formattable.h" +#include "iceberg/util/formatter.h" +#include "iceberg/util/functional.h" +#include "iceberg/util/int128.h" +#include "iceberg/util/lazy.h" +#include "iceberg/util/location_util.h" +#include "iceberg/util/macros.h" +#include "iceberg/util/partition_value_util.h" +#include "iceberg/util/property_util.h" +#include "iceberg/util/retry_util.h" +#include "iceberg/util/snapshot_util.h" +#include "iceberg/util/stream.h" +#include "iceberg/util/string_util.h" +#include "iceberg/util/struct_like_set.h" +#include "iceberg/util/task_group.h" +#include "iceberg/util/temporal_util.h" +#include "iceberg/util/timepoint.h" +#include "iceberg/util/transform_util.h" +#include "iceberg/util/truncate_util.h" +#include "iceberg/util/type_util.h" +#include "iceberg/util/unreachable.h" +#include "iceberg/util/url_encoder.h" +#include "iceberg/util/uuid.h" +#include "iceberg/util/visit_type.h" +#include "iceberg/util/visitor_generate.h" + +#ifdef ICEBERG_PUBLIC_HEADERS_BUNDLE +# include "iceberg/arrow/arrow_io_util.h" +# include "iceberg/arrow/arrow_register.h" +# include "iceberg/arrow/s3/s3_properties.h" +# include "iceberg/avro/avro_constants.h" +# include "iceberg/avro/avro_metrics.h" +# include "iceberg/avro/avro_reader.h" +# include "iceberg/avro/avro_register.h" +# include "iceberg/avro/avro_writer.h" +# include "iceberg/parquet/parquet_reader.h" +# include "iceberg/parquet/parquet_register.h" +# include "iceberg/parquet/parquet_writer.h" +#endif // ICEBERG_PUBLIC_HEADERS_BUNDLE + +#ifdef ICEBERG_PUBLIC_HEADERS_HIVE +# include "iceberg/catalog/hive/hive_catalog.h" +# include "iceberg/catalog/hive/hive_catalog_properties.h" +# include "iceberg/catalog/hive/hms_client.h" +# include "iceberg/catalog/hive/iceberg_hive_export.h" +#endif // ICEBERG_PUBLIC_HEADERS_HIVE + +#ifdef ICEBERG_PUBLIC_HEADERS_REST +# include "iceberg/catalog/rest/auth/auth_manager.h" +# include "iceberg/catalog/rest/auth/auth_managers.h" +# include "iceberg/catalog/rest/auth/auth_properties.h" +# include "iceberg/catalog/rest/auth/auth_session.h" +# include "iceberg/catalog/rest/auth/oauth2_util.h" +# include "iceberg/catalog/rest/auth/token_refresh_scheduler.h" +# include "iceberg/catalog/rest/catalog_properties.h" +# include "iceberg/catalog/rest/constant.h" +# include "iceberg/catalog/rest/endpoint.h" +# include "iceberg/catalog/rest/error_handlers.h" +# include "iceberg/catalog/rest/http_client.h" +# include "iceberg/catalog/rest/http_request.h" +# include "iceberg/catalog/rest/iceberg_rest_export.h" +# include "iceberg/catalog/rest/resource_paths.h" +# include "iceberg/catalog/rest/rest_catalog.h" +# include "iceberg/catalog/rest/rest_file_io.h" +# include "iceberg/catalog/rest/rest_util.h" +# include "iceberg/catalog/rest/type_fwd.h" +# include "iceberg/catalog/rest/types.h" +#endif // ICEBERG_PUBLIC_HEADERS_REST + +#ifdef ICEBERG_PUBLIC_HEADERS_SQL_CATALOG +# include "iceberg/catalog/sql/catalog_store.h" +# include "iceberg/catalog/sql/iceberg_sql_catalog_export.h" +# include "iceberg/catalog/sql/sql_catalog.h" +#endif // ICEBERG_PUBLIC_HEADERS_SQL_CATALOG diff --git a/src/iceberg/expected.h b/src/iceberg/expected.h new file mode 100644 index 000000000..743b79952 --- /dev/null +++ b/src/iceberg/expected.h @@ -0,0 +1,2460 @@ +/* + * MIT License + * + * Copyright (c) 2024 zeus-cpp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/// \file iceberg/expected.h +/// \brief A C++20 backport of C++23 `std::expected`, used as the storage type +/// behind `iceberg::Result`. +/// +/// Purpose: the library itself may be built as C++23, but its public headers +/// must stay consumable from C++20 translation units. `std::expected` is a +/// C++23 library feature, so `iceberg::Result` (see iceberg/result.h) is +/// defined in terms of `iceberg::expected` instead. The API mirrors +/// `std::expected` (including the monadic `and_then`, `or_else`, `transform` +/// and `transform_error`), so code can be written as if against the standard +/// type. +/// +/// History: +/// - apache/iceberg-cpp#40 vendored this header, adapted from +/// https://github.com/zeus-cpp/expected (MIT), while the project targeted +/// C++20. +/// +/// Upstream base: include/zeus/expected.hpp at tag v1.2.0 +/// (dea2a71cdbdb4cbdd1595a1b9d28a12ec49d0517). +/// +/// Local changes in #40: +/// * Namespace `zeus` and its versioned inline ABI namespace replaced by +/// `iceberg`; public classes annotated with `ICEBERG_EXPORT`. +/// * C++17 fallbacks dropped in favor of C++20: the version and +/// `__cplusplus` macros and the C++17-only blocks they guard, +/// `expected_detail::construct_at` (now `std::construct_at`), and +/// `expected_detail::remove_cvref_t` (now `std::remove_cvref_t`). +/// * `[[nodiscard]]` added to `unexpected` and `expected`. +/// * Style only: `typedef` changed to `using`, braces added to `if`/`else`, the +/// redundant `virtual` on `bad_expected_access::what()` removed, and +/// reformatted with clang-format. +/// - apache/iceberg-cpp#139 raised the project to C++23 and removed the header +/// in favor of `std::expected`, which made C++23 a requirement for users of +/// the public headers too. +/// - apache/iceberg-cpp#936 restored it so the public headers work in C++20 +/// again, with these changes on top of the #40 version: +/// * `unexpected`'s comparison operators go through `error()`, since a +/// friend of `unexpected` cannot read `unexpected`'s private member. +/// * The default constructor no longer requires `T` to be default +/// constructible, and the converting constructors start from `no_init` +/// rather than default constructing `T` first. This lets +/// `expected` hold types with no (or a private) default constructor, +/// which MSVC otherwise tried to instantiate. +/// * `expected` is marked `ICEBERG_TEMPLATE_CLASS_EXPORT` rather than +/// `ICEBERG_EXPORT`, so exporting a class template does not break the +/// Windows build. +/// * When `std::expected` is available, `iceberg::expected` can also be +/// constructed from `std::unexpected`, so library code compiled as C++23 +/// that still spells errors with `std::unexpected` keeps working. +/// +/// Once C++23 can be required of all consumers, this header can be dropped and +/// `iceberg::Result` aliased back to `std::expected`. + +#pragma once + +#include +#include +#include +#include +#include +#if defined(__cpp_lib_expected) && __cpp_lib_expected >= 202202L +# include +#endif + +#include "iceberg/iceberg_export.h" + +// NOLINTBEGIN + +namespace iceberg { + +namespace expected_detail { + +template class Template> +inline constexpr bool is_specialization_v = + false; // true if and only if T is a specialization of Template +template