This is an automated email from the ASF dual-hosted git repository.

fokko pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git


The following commit(s) were added to refs/heads/main by this push:
     new 57418d4  chore: add release script and github workflow (#193)
57418d4 is described below

commit 57418d46049d71d26f1880eb2ecfea1638f4d0b0
Author: Li Feiyang <lifeiy...@zju.edu.cn>
AuthorDate: Wed Sep 10 01:51:45 2025 +0800

    chore: add release script and github workflow (#193)
    
    This contribution introduces release automation tooling including:
    - `/dev/release/README.md`: Release process documentation
    - `/dev/release/*.sh`: Release script utilities
    - `/dev/release/check_rat_report.py`: Check Apache lisence script for
    release
    - `/dev/release/rat_exclude_files.txt`: List of files excluded from RAT
    license checks
    - `.github/workflows/rc.yml`: GitHub Actions automation for release
---
 .github/workflows/rc.yml          | 133 +++++++++++++++++++++++++++++++
 dev/release/README.md             | 110 ++++++++++++++++++++++++++
 dev/release/check_rat_report.py   |  65 ++++++++++++++++
 dev/release/rat_exclude_files.txt |  28 +++++++
 dev/release/release.sh            | 137 ++++++++++++++++++++++++++++++++
 dev/release/release_rc.sh         | 159 ++++++++++++++++++++++++++++++++++++++
 dev/release/run_rat.sh            |  54 +++++++++++++
 dev/release/verify_rc.sh          | 150 +++++++++++++++++++++++++++++++++++
 src/iceberg/transform.cc          |  24 ------
 src/iceberg/transform.h           |  25 +++++-
 10 files changed, 860 insertions(+), 25 deletions(-)

diff --git a/.github/workflows/rc.yml b/.github/workflows/rc.yml
new file mode 100644
index 0000000..ee0247e
--- /dev/null
+++ b/.github/workflows/rc.yml
@@ -0,0 +1,133 @@
+# 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.
+
+name: RC
+on:
+  push:
+    tags:
+      - '*-rc*'
+
+concurrency:
+  group: ${{ github.repository }}-${{ github.head_ref || github.sha }}-${{ 
github.workflow }}
+  cancel-in-progress: true
+permissions:
+  contents: read
+
+jobs:
+  archive:
+    name: Archive
+    runs-on: ubuntu-latest
+    timeout-minutes: 5
+    steps:
+      - name: Checkout
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 
v5.0.0
+
+      - name: Prepare for tag
+        if: github.ref_type == 'tag'
+        run: |
+          version=${GITHUB_REF_NAME#v}
+          version=${version%-rc*}
+          rc=${GITHUB_REF_NAME##*-rc}
+          echo "VERSION=${version}" >> ${GITHUB_ENV}
+          echo "RC=${rc}" >> ${GITHUB_ENV}
+          echo "VERSION=${version}"
+          echo "RC=${rc}"
+
+      - name: Archive
+        run: |
+          ARCHIVE_FILE_NAME="apache-iceberg-cpp-${VERSION}-rc${RC}"
+          tar_gz="${ARCHIVE_FILE_NAME}.tar.gz"
+          echo "TAR_GZ=${tar_gz}" >> ${GITHUB_ENV}
+          git archive HEAD --prefix "${ARCHIVE_FILE_NAME}/" --output 
"${tar_gz}"
+          sha512sum "${tar_gz}" > "${tar_gz}.sha512"
+
+      - name: Audit
+        run: |
+          dev/release/run_rat.sh "${TAR_GZ}"
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: archive
+          path: |
+            apache-iceberg-cpp-*
+
+  verify:
+    name: Verify
+    needs:
+      - archive
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os:
+          - macos-15
+          - ubuntu-24.04
+          # - windows-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 
v5.0.0
+
+      - uses: actions/download-artifact@v4
+        with:
+          name: archive
+
+      - name: Verify
+        run: |
+          tar_gz=$(echo apache-iceberg-cpp-*.tar.gz)
+          version=${tar_gz#apache-iceberg-cpp-}
+          version=${version%.tar.gz}
+          version=${version%%-rc*}
+          if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
+            rc=${GITHUB_REF_NAME##*-rc}
+          else
+            rc=100
+          fi
+          echo "VERSION=${version}"
+          echo "RC=${rc}"
+          # The verify_rc.sh script will untar and
+          # run cmake, build, tests (ctest) and install
+          VERIFY_SIGN=0 VERIFY_DOWNLOAD=0 dev/release/verify_rc.sh 
"${version}" "${rc}"
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+  upload:
+    name: Upload
+    if: github.ref_type == 'tag'
+    needs:
+      - verify
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write
+    steps:
+      - name: Checkout
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 
v5.0.0
+
+      - uses: actions/download-artifact@v4
+        with:
+          name: archive
+
+      - name: Upload
+        run: |
+          gh release create ${GITHUB_REF_NAME} \
+            --prerelease \
+            --title "Apache Iceberg C++ ${GITHUB_REF_NAME}" \
+            --generate-notes \
+            --verify-tag \
+            apache-iceberg-cpp-*.tar.gz \
+            apache-iceberg-cpp-*.tar.gz.sha*
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/dev/release/README.md b/dev/release/README.md
new file mode 100644
index 0000000..66ba1ec
--- /dev/null
+++ b/dev/release/README.md
@@ -0,0 +1,110 @@
+<!--
+  ~ 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.
+-->
+
+# Release
+
+## Overview
+
+    1. Test the revision to be released
+    2. Prepare RC and vote (detailed later)
+    3. Publish (detailed later)
+
+### Prepare RC and vote
+
+Run `dev/release/release_rc.sh` on a working copy of
+`g...@github.com:apache/iceberg-cpp` not from your fork:
+
+```console
+$ git clone g...@github.com:apache/iceberg-cpp.git && cd iceberg-cpp
+$ dev/release/release_rc.sh ${VERSION} ${RC}
+(Send a vote email to d...@iceberg.apache.org.
+ You can use a draft shown by release_rc.sh for the email.)
+```
+
+Here is an example to release RC0 of version 0.1.0:
+
+```console
+$ GH_TOKEN=${YOUR_GITHUB_TOKEN} dev/release/release_rc.sh 0.1.0 0
+```
+
+The arguments of `release_rc.sh` are the version and the RC number. If RC0 has 
a problem, we'll increment the RC number such as RC1, RC2 and so on.
+
+Requirements to run `release_rc.sh`:
+
+    * You must be an Apache Iceberg committer or PMC member
+    * You must prepare your PGP key for signing
+
+If you don't have a PGP key, 
https://infra.apache.org/release-signing.html#generate
+may be helpful.
+
+Your PGP key must be published in the KEYS file, which is hosted at:
+
+    * https://downloads.apache.org/iceberg/KEYS
+
+See the header comment of them for how to add a PGP key.
+
+If you are a first-time release manager, you need to add your public key to 
this file. To prevent formatting errors, please use the following commands 
instead of editing the file manually:
+
++ Check out the release distribution directory:
+
+```console
+$ svn co https://dist.apache.org/repos/dist/release/iceberg
+$ cd iceberg
+```
++ Append your GPG public key to the KEYS file. Replace <YOUR_KEY_ID> with your 
actual GPG key ID.
+
+```console
+$ echo "" >> KEYS   # append a newline
+$ gpg --list-sigs <YOUR_KEY_ID> >> KEYS  # append signatures
+$ gpg --armor --export <YOUR_KEY_ID> >> KEYS # append public key block
+$ svn commit -m "Add GPG key for <YOUR_NAME>"
+```
+
+### Publish
+
+We need to publish to apache.org to publish a new release.
+
+Run `dev/release/release.sh` to publish to apache.org:
+
+```console
+$ GH_TOKEN=${YOUR_GITHUB_TOKEN} dev/release/release.sh ${VERSION} ${RC}
+```
+
+Add the release to ASF's report database via [Apache Committee Report 
Helper](https://reporter.apache.org/addrelease.html?iceberg)
+
+### Verify
+
+We have a script for verifying a RC.
+
+You must install the following to run the script:
+
+    * `curl`
+    * `gpg`
+    * `shasum` or `sha512sum`
+    * `tar`
+    * `cmake` (3.25 or higher)
+    * C++23 compliant compiler (GCC 13+ or Clang 16+)
+
+To verify a RC, run the following:
+
+```console
+$ dev/release/verify_rc.sh ${VERSION} ${RC}
+```
+
+If the verification is successful, the message `RC looks good!` is shown.
diff --git a/dev/release/check_rat_report.py b/dev/release/check_rat_report.py
new file mode 100644
index 0000000..15d8b51
--- /dev/null
+++ b/dev/release/check_rat_report.py
@@ -0,0 +1,65 @@
+#!/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.
+
+import fnmatch
+import re
+import sys
+import xml.etree.ElementTree as ET
+
+if len(sys.argv) != 3:
+    sys.stderr.write("Usage: %s exclude_globs.lst rat_report.xml\n" %
+                     sys.argv[0])
+    sys.exit(1)
+
+exclude_globs_filename = sys.argv[1]
+xml_filename = sys.argv[2]
+
+globs = []
+with open(exclude_globs_filename, "r") as f:
+    for line in f:
+        stripped_line = line.strip()
+        if not stripped_line or stripped_line.startswith('#'):
+            continue
+        globs.append(stripped_line)
+
+tree = ET.parse(xml_filename)
+root = tree.getroot()
+resources = root.findall('resource')
+
+all_ok = True
+for r in resources:
+    approvals = r.findall('license-approval')
+    if not approvals or approvals[0].attrib['name'] == 'true':
+        continue
+    clean_name = re.sub('^[^/]+/', '', r.attrib['name'])
+    excluded = False
+    for g in globs:
+        if fnmatch.fnmatch(clean_name, g):
+            excluded = True
+            break
+    if not excluded:
+        sys.stdout.write("NOT APPROVED: %s (%s): %s\n" % (
+            clean_name, r.attrib['name'], approvals[0].attrib['name']))
+        all_ok = False
+
+if not all_ok:
+    sys.exit(1)
+
+print('OK')
+sys.exit(0)
diff --git a/dev/release/rat_exclude_files.txt 
b/dev/release/rat_exclude_files.txt
new file mode 100644
index 0000000..6b15545
--- /dev/null
+++ b/dev/release/rat_exclude_files.txt
@@ -0,0 +1,28 @@
+# 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.
+
+.gitignore
+LICENSE
+NOTICE
+build/**
+dist/**
+.git/**
+test/resources/**
+*.avro
+*.json
+*.parquet
+src/iceberg/util/murmurhash3_internal.*
diff --git a/dev/release/release.sh b/dev/release/release.sh
new file mode 100755
index 0000000..27e0a55
--- /dev/null
+++ b/dev/release/release.sh
@@ -0,0 +1,137 @@
+#!/usr/bin/env bash
+#
+# 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.
+
+set -eu
+
+for cmd in git gh svn; do
+  if ! command -v ${cmd} &> /dev/null; then
+    echo "This script requires '${cmd}' but it's not installed. Aborting."
+    exit 1
+  fi
+done
+
+if [ "$#" -ne 2 ]; then
+  echo "Usage: $0 <version> <rc>"
+  echo " e.g.: $0 0.1.0 1"
+  exit 1
+fi
+
+version=$1
+rc=$2
+
+git_origin_url="$(git remote get-url origin)"
+repository="${git_origin_url#*github.com?}"
+repository="${repository%.git}"
+
+if [ "${git_origin_url}" != "g...@github.com:apache/iceberg-cpp.git" ]; then
+  echo "This script must be ran with a working copy of apache/iceberg-cpp."
+  echo "The origin's URL: ${git_origin_url}"
+  exit 1
+fi
+
+tag="v${version}"
+rc_tag="${tag}-rc${rc}"
+echo "Tagging for release: ${tag}"
+git tag "${tag}" "${rc_tag}^{}" -m "Release ${tag}"
+git push origin "${tag}"
+
+release_id="apache-iceberg-cpp-${version}"
+dist_url="https://dist.apache.org/repos/dist/release/iceberg";
+dist_dev_url="https://dist.apache.org/repos/dist/dev/iceberg";
+
+svn \
+  mv "${dist_dev_url}/${release_id}-rc${rc}/" \
+  "${dist_url}/${release_id}" \
+  -m "Apache Iceberg C++ ${version}"
+
+svn co "${dist_url}/${release_id}"
+pushd "${release_id}"
+
+echo "Renaming artifacts to their final release names..."
+for fname in ./*; do
+  mv "${fname}" "${fname//-rc${rc}/}"
+done
+echo "Renamed files:"
+ls -l
+
+gh release create "${tag}" \
+  --repo "${repository}" \
+  --title "Apache Iceberg C++ ${version}" \
+  --generate-notes \
+  --verify-tag \
+  *.tar.gz \
+  *.tar.gz.asc \
+  *.tar.gz.sha512
+popd
+
+rm -rf "${release_id}"
+
+echo "Keep only the latest versions"
+old_releases=$(
+  svn ls "${dist_url}" |
+  grep -E '^apache-iceberg-cpp-' |
+  sort --version-sort --reverse |
+  tail -n +2
+)
+for old_release_version in ${old_releases}; do
+  echo "Remove old release ${old_release_version}"
+  svn \
+    delete \
+    -m "Remove old Apache Iceberg C++ release: ${old_release_version}" \
+    "https://dist.apache.org/repos/dist/release/iceberg/${old_release_version}";
+done
+
+echo "Success! The release is available here:"
+echo "  https://dist.apache.org/repos/dist/release/iceberg/${release_id}";
+echo
+echo "Add this release to ASF's report database:"
+echo "  https://reporter.apache.org/addrelease.html?iceberg";
+
+echo "Draft email for announcement"
+echo ""
+echo "---------------------------------------------------------"
+cat <<MAIL
+To: d...@iceberg.apache.org
+Cc: annou...@apache.org
+Hello everyone,
+
+I'm pleased to announce the release of Apache Iceberg C++ v${version}!
+
+Apache Iceberg is an open table format for huge analytic datasets,
+Iceberg delivers high query performance for tables with tens of
+petabytes of data, along with atomic commits, concurrent writes, and
+SQL-compatible table evolution.
+
+This release contains <COMMIT_COUNT> commits from <CONTRIBUTOR_COUNT> unique 
contributors. Among
+the changes in this release are the following highlights:
+
+- <FEATURE_1>
+- <FEATURE_2>
+- ...
+- <FEATURE_N>
+
+This release is hosted at: 
https://dist.apache.org/repos/dist/release/iceberg/apache-iceberg-cpp-${version}
+
+For release details and downloads, please visit: 
https://github.com/apache/iceberg-cpp/releases/tag/apache-iceberg-cpp-${version}
+
+Thanks to everyone for all your contributions!
+
+<AUTHOR>
+MAIL
+echo "---------------------------------------------------------"
diff --git a/dev/release/release_rc.sh b/dev/release/release_rc.sh
new file mode 100755
index 0000000..7ef1268
--- /dev/null
+++ b/dev/release/release_rc.sh
@@ -0,0 +1,159 @@
+#!/usr/bin/env bash
+#
+# 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.
+
+set -eu
+
+for cmd in git gh gpg svn; do
+  if ! command -v ${cmd} &> /dev/null; then
+    echo "This script requires '${cmd}' but it's not installed. Aborting."
+    exit 1
+  fi
+done
+
+SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+SOURCE_TOP_DIR="$(cd "${SOURCE_DIR}/../../" && pwd)"
+
+if [ "$#" -ne 2 ]; then
+  echo "Usage: $0 <version> <rc>"
+  echo " e.g.: $0 0.1.0 1"
+  exit 1
+fi
+
+# TODO: possibly use semantic versioning to auto generate the version?
+version=$1
+rc=$2
+
+: "${RELEASE_DEFAULT:=1}"
+: "${RELEASE_PULL:=${RELEASE_DEFAULT}}"
+: "${RELEASE_PUSH_TAG:=${RELEASE_DEFAULT}}"
+: "${RELEASE_SIGN:=${RELEASE_DEFAULT}}"
+: "${RELEASE_UPLOAD:=${RELEASE_DEFAULT}}"
+
+cd "${SOURCE_TOP_DIR}"
+
+if [ "${RELEASE_PULL}" -gt 0 ] || [ "${RELEASE_PUSH_TAG}" -gt 0 ]; then
+  git_origin_url="$(git remote get-url origin)"
+  if [ "${git_origin_url}" != "g...@github.com:apache/iceberg-cpp.git" ]; then
+    echo "This script must be ran with working copy of apache/iceberg-cpp."
+    echo "The origin's URL: ${git_origin_url}"
+    exit 1
+  fi
+fi
+
+if [ "${RELEASE_PULL}" -gt 0 ]; then
+  echo "Ensure using the latest commit"
+  git checkout main
+  git pull --rebase --prune
+fi
+
+rc_tag="v${version}-rc${rc}"
+if [ "${RELEASE_PUSH_TAG}" -gt 0 ]; then
+  echo "Tagging for RC: ${rc_tag}"
+  git tag -a -m "${version} RC${rc}" "${rc_tag}"
+  git push origin "${rc_tag}"
+fi
+
+rc_hash="$(git rev-list --max-count=1 "${rc_tag}")"
+
+id="apache-iceberg-cpp-${version}-rc${rc}"
+tar_gz="${id}.tar.gz"
+
+if [ "${RELEASE_SIGN}" -gt 0 ]; then
+  git_origin_url="$(git remote get-url origin)"
+  repository="${git_origin_url#*github.com?}"
+  repository="${repository%.git}"
+
+  echo "Looking for GitHub Actions workflow on ${repository}:${rc_tag}"
+  run_id=""
+  while [ -z "${run_id}" ]; do
+    echo "Waiting for run to start..."
+    run_id=$(gh run list \
+      --repo "${repository}" \
+      --workflow=rc.yml \
+      --json 'databaseId,event,headBranch,status' \
+      --jq ".[] | select(.event == \"push\" and .headBranch == \"${rc_tag}\") 
| .databaseId")
+    sleep 1
+  done
+
+  echo "Found GitHub Actions workflow with ID: ${run_id}"
+  gh run watch --repo "${repository}" --exit-status "${run_id}"
+
+  mkdir -p "${id}"
+
+  echo "Downloading .tar.gz and .sha512 from GitHub Releases"
+  gh release download "${rc_tag}" \
+    --dir "${id}" \
+    --pattern "${tar_gz}" \
+    --pattern "${tar_gz}.sha512" \
+    --repo "${repository}" \
+    --skip-existing
+
+  echo "Signing tar.gz"
+  cd "${id}"
+  gpg --armor --output "${tar_gz}.asc" --detach-sig "${tar_gz}"
+  echo "Add signature to GitHub release"
+  gh release upload "${rc_tag}" \
+    --clobber \
+    --repo "${repository}" \
+    "${tar_gz}".asc
+  cd ..
+fi
+
+if [ "${RELEASE_UPLOAD}" -gt 0 ]; then
+  echo "Uploading to ASF dist/dev..."
+  # rename files to remove -rc${rc} suffix before uploading
+  pushd "${id}"
+  for fname in ./*; do
+    mv "${fname}" "${fname//-rc${rc}/}"
+  done
+  popd
+  svn import "${id}" "https://dist.apache.org/repos/dist/dev/iceberg/${id}"; -m 
"Apache Iceberg C++ ${version} RC${rc}"
+fi
+
+echo "Draft email for d...@iceberg.apache.org mailing list"
+echo ""
+echo "---------------------------------------------------------"
+cat <<MAIL
+To: d...@iceberg.apache.org
+Subject: [VOTE][C++] Release Apache Iceberg C++ v${version} RC${rc}
+
+Hi,
+
+I would like to propose the following release candidate (RC${rc}) of
+Apache Iceberg C++ version v${version}.
+
+This release candidate is based on commit:
+${rc_hash} [1]
+
+The source release rc${rc} is hosted at [2].
+
+Please download, verify checksums and signatures, run the unit tests,
+and vote on the release. See [3] for how to validate a release candidate.
+
+The vote will be open for at least 72 hours.
+
+[ ] +1 Release this as Apache Iceberg C++ v${version}
+[ ] +0
+[ ] -1 Do not release this as Apache Iceberg C++ v${version} because...
+
+[1]: https://github.com/apache/iceberg-cpp/tree/${rc_hash}
+[2]: 
https://dist.apache.org/repos/dist/dev/iceberg/apache-iceberg-cpp-${version}-rc${rc}
+[3]: 
https://github.com/apache/iceberg-cpp/blob/main/dev/release/README.md#verify
+MAIL
+echo "---------------------------------------------------------"
diff --git a/dev/release/run_rat.sh b/dev/release/run_rat.sh
new file mode 100755
index 0000000..2104369
--- /dev/null
+++ b/dev/release/run_rat.sh
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+#
+# 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.
+
+set -eu
+
+RELEASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+RAT_VERSION=0.16.1
+
+RAT_JAR="${RELEASE_DIR}/apache-rat-${RAT_VERSION}.jar"
+if [ ! -f "${RAT_JAR}" ]; then
+  curl \
+    --fail \
+    --output "${RAT_JAR}" \
+    --show-error \
+    --silent \
+    
https://repo1.maven.org/maven2/org/apache/rat/apache-rat/${RAT_VERSION}/apache-rat-${RAT_VERSION}.jar
+fi
+
+RAT_XML="${RELEASE_DIR}/rat.xml"
+java \
+  -jar "${RAT_JAR}" \
+  --out "${RAT_XML}" \
+  --xml \
+  "$1"
+FILTERED_RAT_TXT="${RELEASE_DIR}/filtered_rat.txt"
+if ${PYTHON:-python3} \
+  "${RELEASE_DIR}/check_rat_report.py" \
+  "${RELEASE_DIR}/rat_exclude_files.txt" \
+  "${RAT_XML}" > \
+  "${FILTERED_RAT_TXT}"; then
+  echo "No unapproved licenses"
+else
+  cat "${FILTERED_RAT_TXT}"
+  N_UNAPPROVED=$(grep -c "NOT APPROVED" "${FILTERED_RAT_TXT}")
+  echo "${N_UNAPPROVED} unapproved licenses. Check Rat report: ${RAT_XML}"
+  exit 1
+fi
diff --git a/dev/release/verify_rc.sh b/dev/release/verify_rc.sh
new file mode 100755
index 0000000..f32626d
--- /dev/null
+++ b/dev/release/verify_rc.sh
@@ -0,0 +1,150 @@
+#!/usr/bin/env bash
+#
+# 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.
+
+set -eu
+
+for cmd in curl gpg cmake; do
+  if ! command -v ${cmd} &> /dev/null; then
+    echo "This script requires '${cmd}' but it's not installed. Aborting."
+    exit 1
+  fi
+done
+
+SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+TOP_SOURCE_DIR="$(dirname "$(dirname "${SOURCE_DIR}")")"
+
+if [ "$#" -ne 2 ]; then
+  echo "Usage: $0 <version> <rc>"
+  echo " e.g.: $0 0.1.0 1"
+  exit 1
+fi
+
+set -o pipefail
+set -x
+
+VERSION="$1"
+RC="$2"
+
+ICEBERG_DIST_BASE_URL="https://downloads.apache.org/iceberg";
+DOWNLOAD_RC_BASE_URL="https://dist.apache.org/repos/dist/dev/iceberg/apache-iceberg-cpp-${VERSION}-rc${RC}";
+ARCHIVE_BASE_NAME="apache-iceberg-cpp-${VERSION}-rc${RC}"
+
+: "${VERIFY_DEFAULT:=1}"
+: "${VERIFY_DOWNLOAD:=${VERIFY_DEFAULT}}"
+: "${VERIFY_SIGN:=${VERIFY_DEFAULT}}"
+
+VERIFY_SUCCESS=no
+
+setup_tmpdir() {
+  cleanup() {
+    if [ "${VERIFY_SUCCESS}" = "yes" ]; then
+      rm -rf "${VERIFY_TMPDIR}"
+    else
+      echo "Failed to verify release candidate. See ${VERIFY_TMPDIR} for 
details."
+    fi
+  }
+
+  if [ -z "${VERIFY_TMPDIR:-}" ]; then
+    VERIFY_TMPDIR="$(mktemp -d -t "$1.XXXXX")"
+    trap cleanup EXIT
+  else
+    mkdir -p "${VERIFY_TMPDIR}"
+  fi
+}
+
+download() {
+  curl \
+    --fail \
+    --location \
+    --remote-name \
+    --show-error \
+    --silent \
+    "$1"
+}
+
+download_rc_file() {
+  if [ "${VERIFY_DOWNLOAD}" -gt 0 ]; then
+    download "${DOWNLOAD_RC_BASE_URL}/$1"
+  else
+    cp "${TOP_SOURCE_DIR}/$1" "$1"
+  fi
+}
+
+import_gpg_keys() {
+  if [ "${VERIFY_SIGN}" -gt 0 ]; then
+    download "${ICEBERG_DIST_BASE_URL}/KEYS"
+    gpg --import KEYS
+  fi
+}
+
+if type shasum >/dev/null 2>&1; then
+  sha512_verify="shasum -a 512 -c"
+else
+  sha512_verify="sha512sum -c"
+fi
+
+fetch_archive() {
+  download_rc_file "${ARCHIVE_BASE_NAME}.tar.gz"
+  if [ "${VERIFY_SIGN}" -gt 0 ]; then
+    download_rc_file "${ARCHIVE_BASE_NAME}.tar.gz.asc"
+    gpg --verify "${ARCHIVE_BASE_NAME}.tar.gz.asc" 
"${ARCHIVE_BASE_NAME}.tar.gz"
+  fi
+  download_rc_file "${ARCHIVE_BASE_NAME}.tar.gz.sha512"
+  ${sha512_verify} "${ARCHIVE_BASE_NAME}.tar.gz.sha512"
+}
+
+ensure_source_directory() {
+  tar xf "${ARCHIVE_BASE_NAME}".tar.gz
+}
+
+test_source_distribution() {
+  echo "Building and testing Apache Iceberg C++..."
+
+  # Configure build
+  cmake -S . -B build \
+    -DCMAKE_BUILD_TYPE=Release \
+    -DICEBERG_BUILD_STATIC=ON \
+    -DICEBERG_BUILD_SHARED=ON
+
+  # Build
+  cmake --build build --parallel $(nproc || sysctl -n hw.ncpu || echo 4)
+
+  # Run tests
+  ctest --test-dir build --output-on-failure --parallel $(nproc || sysctl -n 
hw.ncpu || echo 4)
+
+  # Install
+  mkdir -p ./install_test
+  cmake --install build --prefix ./install_test
+
+  echo "Build, test and install completed successfully!"
+}
+
+setup_tmpdir "iceberg-cpp-${VERSION}-${RC}"
+echo "Working in sandbox ${VERIFY_TMPDIR}"
+cd "${VERIFY_TMPDIR}"
+
+import_gpg_keys
+fetch_archive
+ensure_source_directory
+pushd "${ARCHIVE_BASE_NAME}"
+test_source_distribution
+popd
+
+VERIFY_SUCCESS=yes
+echo "RC looks good!"
diff --git a/src/iceberg/transform.cc b/src/iceberg/transform.cc
index 7898fc6..1de38fc 100644
--- a/src/iceberg/transform.cc
+++ b/src/iceberg/transform.cc
@@ -39,30 +39,6 @@ constexpr std::string_view kHourName = "hour";
 constexpr std::string_view kVoidName = "void";
 }  // namespace
 
-constexpr std::string_view TransformTypeToString(TransformType type) {
-  switch (type) {
-    case TransformType::kUnknown:
-      return kUnknownName;
-    case TransformType::kIdentity:
-      return kIdentityName;
-    case TransformType::kBucket:
-      return kBucketName;
-    case TransformType::kTruncate:
-      return kTruncateName;
-    case TransformType::kYear:
-      return kYearName;
-    case TransformType::kMonth:
-      return kMonthName;
-    case TransformType::kDay:
-      return kDayName;
-    case TransformType::kHour:
-      return kHourName;
-    case TransformType::kVoid:
-      return kVoidName;
-  }
-  std::unreachable();
-}
-
 std::shared_ptr<Transform> Transform::Identity() {
   static auto instance =
       std::shared_ptr<Transform>(new Transform(TransformType::kIdentity));
diff --git a/src/iceberg/transform.h b/src/iceberg/transform.h
index 3e6709f..6c771fb 100644
--- a/src/iceberg/transform.h
+++ b/src/iceberg/transform.h
@@ -23,6 +23,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <utility>
 #include <variant>
 
 #include "iceberg/expression/literal.h"
@@ -57,7 +58,29 @@ enum class TransformType {
 };
 
 /// \brief Get the relative transform name
-ICEBERG_EXPORT constexpr std::string_view TransformTypeToString(TransformType 
type);
+ICEBERG_EXPORT constexpr std::string_view TransformTypeToString(TransformType 
type) {
+  switch (type) {
+    case TransformType::kUnknown:
+      return "unknown";
+    case TransformType::kIdentity:
+      return "identity";
+    case TransformType::kBucket:
+      return "bucket";
+    case TransformType::kTruncate:
+      return "truncate";
+    case TransformType::kYear:
+      return "year";
+    case TransformType::kMonth:
+      return "month";
+    case TransformType::kDay:
+      return "day";
+    case TransformType::kHour:
+      return "hour";
+    case TransformType::kVoid:
+      return "void";
+  }
+  std::unreachable();
+}
 
 /// \brief Represents a transform used in partitioning or sorting in Iceberg.
 ///

Reply via email to