Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,5 @@ install -D -m 0644 %{sname}-sbom.json.asc %{buildroot}%{_datadir}/pgedge-%{sname
%{_datadir}/pgedge-%{sname}/%{sname}-sbom.json.asc

%changelog
* Tue Jul 01 2026 Muhammad Aqeel <muhammad.aqeel@pgedge.com> - 1.5.4-1
* Wed Jul 01 2026 Muhammad Aqeel <muhammad.aqeel@pgedge.com> - 1.5.4-1
- Initial build of the ColdFront patched DuckDB extensions (DuckDB 1.5.4)
2 changes: 1 addition & 1 deletion packaging/coldfront/rpm/coldfront.spec
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,5 @@ install -p -m 0644 %{_builddir}/%{sname}-%{version}/%{sname}-sbom.json.asc %{bui
%{pginstdir}/sbom/%{sname}-sbom.json.asc

%changelog
* Mon Jun 30 2026 Muhammad Aqeel <muhammad.aqeel@pgedge.com> - 1.0.0-1
* Tue Jun 30 2026 Muhammad Aqeel <muhammad.aqeel@pgedge.com> - 1.0.0-1
- Initial build of the pgEdge ColdFront extension
36 changes: 36 additions & 0 deletions packaging/pg_duckdb/build-deb.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,23 @@ CWD="$(pwd)"

export DEBIAN_FRONTEND=noninteractive

# Only the owner major (the latest — see common.sh) packages the shared DuckDB
# engine as pgedge-libduckdb; every other major deletes its identical copy.
# debian/rules reads both of these from the environment.
if [ "${PG_MAJOR_VERSION}" = "${LIBDUCKDB_OWNER_PG_MAJOR}" ]; then
export LIBDUCKDB_OWNER=yes
else
export LIBDUCKDB_OWNER=no
fi

# LIBDUCKDB_DIR comes from common.sh and is used for both the RPATH (below) and
# the install location (debian/rules reads it from the environment).

# Upper bound for the engine dependency — DuckDB's C++ ABI is tied to the
# upstream version, so 1.5.4 must never resolve against 1.5.5. Bumps the last
# component: 1.5.4 -> 1.5.5.
LIBDUCKDB_ENGINE_NEXT="$(awk -F. '{ $NF = $NF + 1; print }' OFS=. <<< "${PG_DUCKDB_ENGINE_VERSION}")"

prepare() {

setup_apt_build_env
Expand All @@ -35,13 +52,32 @@ prepare() {
# pgEdge propagates -fexcess-precision=standard into CXXFLAGS, which gcc
# rejects for C++. Strip it after PGXS is included (no-op if absent).
printf '\noverride CXXFLAGS := $(filter-out -fexcess-precision=standard,$(CXXFLAGS))\n' >> Makefile.global
# Teach pg_duckdb.so where the shared engine lives. Upstream links it with a
# plain -lduckdb plus -Wl,-rpath,$(PG_LIB), which only worked while
# libduckdb.so sat in the PG libdir; it now ships in LIBDUCKDB_DIR, so add
# that to the RPATH. Makefile.global is included AFTER $(PGXS), and PGXS
# expands SHLIB_LINK when it links, so appending here still lands.
printf '\nSHLIB_LINK += -Wl,-rpath,%s\n' "${LIBDUCKDB_DIR}" >> Makefile.global
)

echo "Moving Debian packaging into source directory..."
cp -rp "${CWD}/${COMPONENT_NAME}/deb/debian" "$SRC_DIR/"
cd "$SRC_DIR"
cp debian/control.in debian/control
# The shared-engine stanza is appended ONLY for the owner major; the other
# majors must not declare pgedge-libduckdb at all, or the release would push
# several same-version builds of it and reprepro would reject the second.
if [ "${LIBDUCKDB_OWNER}" = "yes" ]; then
echo "PG ${PG_MAJOR_VERSION} is the libduckdb owner — building pgedge-libduckdb here"
cat debian/control.libduckdb.in >> debian/control
else
echo "PG ${PG_MAJOR_VERSION} is not the libduckdb owner (${LIBDUCKDB_OWNER_PG_MAJOR} is) — engine comes from pgedge-libduckdb"
rm -f debian/pgedge-libduckdb.install
fi
rm -f debian/control.in debian/control.libduckdb.in
sed -i "s|PG_MAJOR_VERSION|${PG_MAJOR_VERSION}|g" debian/control
sed -i "s|DUCKDB_ENGINE_VERSION|${PG_DUCKDB_ENGINE_VERSION}|g;s|DUCKDB_ENGINE_NEXT|${LIBDUCKDB_ENGINE_NEXT}|g" \
debian/control
mv debian/pgedge-postgresql-pg-duckdb.install \
debian/pgedge-postgresql-${PG_MAJOR_VERSION}-pg-duckdb.install
sed -i "s|PG_MAJOR_VERSION|${PG_MAJOR_VERSION}|g" \
Expand Down
23 changes: 22 additions & 1 deletion packaging/pg_duckdb/build-rpm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ set -euo pipefail

RHEL="$(rpm --eval %rhel)"

# Only the owner major (the latest — see common.sh) packages the shared DuckDB
# engine; every other major deletes its identical copy and links against it.
# The comparison lives here, not in the spec, so RPM and DEB decide it the same
# way from the same variable.
if [ "${PG_MAJOR_VERSION}" = "${LIBDUCKDB_OWNER_PG_MAJOR}" ]; then
WITH_LIBDUCKDB=1
else
WITH_LIBDUCKDB=0
fi

prepare() {
setup_dnf_build_env

Expand Down Expand Up @@ -38,24 +48,35 @@ prepare() {
# This function is for debugging purpose if you have your own keys. GH workflow does not need it.
#import_gpg_keys

# with_libduckdb must be defined here too — builddep parses the same spec, and
# an undefined macro in the %if would abort the parse.
echo "🔧 Installing RPM build dependencies..."
dnf builddep -y \
--define "pgmajorversion ${PG_MAJOR_VERSION}" \
--define "pginstdir /usr/pgsql-${PG_MAJOR_VERSION}" \
--define "pg_duckdb_version ${PG_DUCKDB_VERSION}" \
--define "pg_duckdb_buildnum ${PG_DUCKDB_BUILDNUM}" \
--define "curl_version ${CURL_VERSION}" \
--define "with_libduckdb ${WITH_LIBDUCKDB}" \
--define "libduckdb_dir ${LIBDUCKDB_DIR}" \
~/rpmbuild/SPECS/pg_duckdb.spec
}

build() {
echo "Building RPM and SRPM..."
if [ "${WITH_LIBDUCKDB}" = "1" ]; then
echo " PG ${PG_MAJOR_VERSION} is the libduckdb owner — this cell also builds pgedge-libduckdb"
else
echo " PG ${PG_MAJOR_VERSION} is not the libduckdb owner (${LIBDUCKDB_OWNER_PG_MAJOR} is) — engine comes from pgedge-libduckdb"
fi
QA_RPATHS=$(( 0xffff )) rpmbuild -ba ~/rpmbuild/SPECS/pg_duckdb.spec \
--define "pgmajorversion ${PG_MAJOR_VERSION}" \
--define "pginstdir /usr/pgsql-${PG_MAJOR_VERSION}" \
--define "pg_duckdb_version ${PG_DUCKDB_VERSION}" \
--define "pg_duckdb_buildnum ${PG_DUCKDB_BUILDNUM}" \
--define "curl_version ${CURL_VERSION}"
--define "curl_version ${CURL_VERSION}" \
--define "with_libduckdb ${WITH_LIBDUCKDB}" \
--define "libduckdb_dir ${LIBDUCKDB_DIR}"
}

post_build() {
Expand Down
81 changes: 76 additions & 5 deletions packaging/pg_duckdb/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,84 @@ export PG_DUCKDB_COMMIT="${PG_DUCKDB_COMMIT:-c04e6a2dcf4e999abb921da1ba2f8335dad
export PG_DUCKDB_VERSION="${COMPONENT_VERSION:-1.5.4}"
export PG_DUCKDB_BUILDNUM="${COMPONENT_BUILDNUM:-1}"

# libcurl built from source and BUNDLED in the package. DuckDB 1.5.4 httpfs links
# CURLSSLOPT_AUTO_CLIENT_CERT (curl >= 7.77); el9 ships 7.76 and debian bullseye
# 7.74, so the system curl can't resolve pg_duckdb.so's symbols at load. 8.12.0
# also fixes CVE-2025-0665. Installed into the PG libdir with a $ORIGIN RUNPATH
# on pg_duckdb.so so it finds the co-located copy without touching the OS libcurl.
# libcurl built from source at BUILD TIME ONLY on the two distros whose system
# curl is too old to COMPILE against — el9 (7.76) and debian bullseye (7.74) —
# because DuckDB 1.5.4's httpfs uses CURLSSLOPT_AUTO_CLIENT_CERT (curl >= 7.77).
# 8.12.0 also fixes CVE-2025-0665. It is NOT shipped and NOT rpath'd: the built
# .so references only the libcurl.so.4 soname plus symbols the older system curl
# already provides, so at runtime the native system libcurl resolves it. Every
# other distro compiles against its own system libcurl and ignores this.
export CURL_VERSION="${CURL_VERSION:-8.12.0}"

# --- Shared DuckDB engine package ----------------------------------------------
# libduckdb.so is DuckDB itself, built from pg_duckdb's third_party/duckdb
# submodule. Nothing in it is PostgreSQL-specific — its DT_NEEDED set is only
# curl/ssl/crypto/stdc++/m/gcc_s/c — so every per-major build produces the same
# engine (verified: .text and .rodata are bit-identical across PG 16/17/18).
#
# Shipping that identical library from all three per-major packages made them
# mutually UNINSTALLABLE: identical binary -> identical build-id -> each package
# claimed the same /usr/lib/.build-id/<xx>/<hash>, but as a symlink pointing at
# its OWN PG libdir. rpm's transaction test and dpkg both reject that, so
# installing pg16+pg17+pg18 side by side failed. It only appeared to work on
# distros where an accidental per-major RUNPATH perturbed the binary enough to
# change its build-id.
#
# The engine now ships ONCE as `pgedge-libduckdb`, into a pgEdge-private
# directory (not the system libdir — the soname is unversioned, so a public
# install would squat a name any future distro duckdb package would want).
# pg_duckdb.so reaches it through an RPATH added at build time, so the per-major
# packages carry nothing but the extension itself.
#
# PER_PG_VERSION=true gives each major its own rpmbuild/dpkg-buildpackage run,
# so exactly ONE run may emit the shared package. If more than one did, the
# release would push N same-version artifacts built from N different runs:
# reprepro rejects the second outright ("already registered with different
# checksum") and the dnf side would silently replace the file, leaving clients
# with repo metadata pointing at a checksum that no longer matches.
#
# That one run is the LATEST major — it is the major that stays in the matrix
# as older ones age out, so the owner moves forward on its own. The list comes
# from release.yml's detect step for this component, read straight out of the
# workflow so the two can never drift (the duckdb-extensions component reads
# ICEBERG_REF from the Dockerfile the same way). $GITHUB_WORKSPACE is mounted
# into the build container, so the workflow is readable from inside the build.
CWD="${CWD:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
libduckdb_pg_majors="$(awk '
/component_name: packaging\/pg_duckdb[[:space:]]*$/ { found = 1 }
found && /pg_versions:/ { gsub(/[^0-9,]/, ""); print; exit }
' "${CWD}/.github/workflows/release.yml" 2>/dev/null)"

# Highest major in that list. Falls back to 18 when the list is unreadable — a
# build outside a repo checkout, or a workflow edit that moves the detect step
# beyond awk's reach. A wrong owner is self-announcing rather than silent:
# pgedge-libduckdb either goes missing (the promote install test fails on an
# unsatisfied dependency) or gets built twice (the apt push fails).
# LIBDUCKDB_OWNER_PG_MAJOR is the single override knob.
export LIBDUCKDB_OWNER_PG_MAJOR="${LIBDUCKDB_OWNER_PG_MAJOR:-$(
printf '%s' "${libduckdb_pg_majors:-18}" | tr ',' '\n' | grep -E '^[0-9]+$' | sort -n | tail -1
)}"
: "${LIBDUCKDB_OWNER_PG_MAJOR:=18}"
unset libduckdb_pg_majors

# Where the shared engine installs, and what pg_duckdb.so gets an RPATH to.
# Its own subdirectory alongside the duckdb extensions, under the same
# /usr/lib/pgedge/coldfront root (COLDFRONT_EXTDIR is
# /usr/lib/pgedge/coldfront/duckdb-extensions), so the package owns its
# directory outright and the ColdFront payload stays in one namespace. Same literal path on RPM and DEB:
# deliberately not %{_libdir} and not multiarch-qualified, since nothing here is
# ever co-installed for two architectures and one path keeps the RPATH identical
# across formats. Private rather than a public libdir because the soname is
# unversioned — a public install would squat a name any future distro duckdb
# package would want.
export LIBDUCKDB_DIR="${LIBDUCKDB_DIR:-/usr/lib/pgedge/coldfront/libduckdb}"

# The clean upstream DuckDB engine version, captured BEFORE the DEB pretag fold
# below can decorate PG_DUCKDB_VERSION (1.5.4 -> 1.5.4~beta2). The per-major
# packages depend on the shared engine by this version, so the dependency stays
# stable across rc/beta rebuilds while still pinning the C++ ABI to one engine.
export PG_DUCKDB_ENGINE_VERSION="${PG_DUCKDB_VERSION}"

# DEB only: move a pre-release pretag (e.g. BUILDNUM='rc1_1') into the upstream
# VERSION with a leading '~' (1.5.4~rc1, BUILDNUM=1) so '~' sorts pre-releases
# BELOW stable in dpkg/reprepro. Gated on apt-get so RPM keeps the pretag in
Expand Down
2 changes: 2 additions & 0 deletions packaging/pg_duckdb/deb/debian/control.in
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Depends:
${misc:Depends},
${shlibs:Depends},
pgedge-postgresql-PG_MAJOR_VERSION,
pgedge-libduckdb (>= DUCKDB_ENGINE_VERSION~),
pgedge-libduckdb (<< DUCKDB_ENGINE_NEXT~),
libcurl4,
Conflicts:
postgresql-PG_MAJOR_VERSION-pg-duckdb,
Expand Down
15 changes: 15 additions & 0 deletions packaging/pg_duckdb/deb/debian/control.libduckdb.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

Package: pgedge-libduckdb
Architecture: any
Section: libs
Depends:
${misc:Depends},
${shlibs:Depends},
Description: DuckDB engine shared library for pgEdge pg_duckdb
The DuckDB columnar-vectorized analytics engine, built from the
third_party/duckdb submodule pinned by pgEdge's pg_duckdb and shared by the
pg_duckdb extension for every PostgreSQL major version.
.
This library contains no PostgreSQL-specific code; the per-major
pgedge-postgresql-N-pg-duckdb packages link against it and find it at runtime
through an RPATH, so they ship nothing but the extension itself.
1 change: 1 addition & 0 deletions packaging/pg_duckdb/deb/debian/pgedge-libduckdb.install
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
usr/lib/pgedge/coldfront/libduckdb/libduckdb.so
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
debian/tmp/sbom/* usr/lib/postgresql/PG_MAJOR_VERSION/sbom/
# Everything `make install` produced, minus libduckdb.so: debian/rules has
# already moved that to the shared pgedge dir (owner build, where it becomes
# pgedge-libduckdb) or deleted it (every other major). Listed explicitly because
# dh_install only falls back to "install whatever is left in debian/tmp" when
# the source builds a SINGLE binary package, which stops being true in the
# owner build.
usr/lib/postgresql/
usr/share/postgresql/
48 changes: 47 additions & 1 deletion packaging/pg_duckdb/deb/debian/rules
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@
export DUCKDB_GEN = ninja
export CMAKE_BUILD_PARALLEL_LEVEL = 4

# --- Shared DuckDB engine (rationale in packaging/pg_duckdb/common.sh) ----------
# libduckdb.so is PG-major independent, so exactly one major — the owner, which
# build-deb.sh exports here from LIBDUCKDB_OWNER_PG_MAJOR — ships it as
# pgedge-libduckdb; every other major drops its identical copy. Shipping it from
# all of them gave every per-major dbgsym the same
# /usr/lib/debug/.build-id/<xx>/<hash>.debug path, so they refused to install
# side by side. pg_duckdb.so reaches the engine in its new home through an RPATH
# that build-deb.sh adds at build time, so no per-major link has to be managed.
LIBDUCKDB_OWNER ?= no
# Exported by build-deb.sh, which uses the same value for the RPATH; the default
# keeps a standalone `dpkg-buildpackage` working.
LIBDUCKDB_DIR ?= /usr/lib/pgedge/coldfront/libduckdb
# build-deb.sh puts the target major's bin dir first on PATH, so this resolves to
# that major's libdir — where `make install` drops both artifacts.
PG_PKGLIBDIR := $(shell pg_config --pkglibdir)
# dh_shlibdeps needs the engine to resolve pg_duckdb.so's NEEDED entry in the
# builds that no longer stage libduckdb.so themselves.
DUCKDB_BUILD_LIBDIR := $(CURDIR)/third_party/duckdb/build/release/src

%:
dh $@

Expand All @@ -14,7 +33,34 @@ override_dh_auto_build:
USE_PGXS=1 dh_auto_build -- -j4 with_llvm=no

override_dh_auto_install:
USE_PGXS=1 dh_auto_install -- with_llvm=no
# --destdir is pinned so the layout does not change between the owner build
# (two binary packages -> debian/tmp) and the others (one -> debian/<pkg>);
# the .install files depend on it being debian/tmp either way.
USE_PGXS=1 dh_auto_install --destdir=debian/tmp -- with_llvm=no
# The owner relocates the engine to the shared directory already on
# pg_duckdb.so's RPATH; everyone else drops their identical copy. Nothing is
# left in the PG libdir, so there is no per-major link to keep in step.
if [ "$(LIBDUCKDB_OWNER)" = "yes" ]; then \
mkdir -p debian/tmp$(LIBDUCKDB_DIR); \
mv debian/tmp$(PG_PKGLIBDIR)/libduckdb.so debian/tmp$(LIBDUCKDB_DIR)/libduckdb.so; \
else \
rm -f debian/tmp$(PG_PKGLIBDIR)/libduckdb.so; \
fi

override_dh_shlibdeps:
# -l: libduckdb.so is no longer beside pg_duckdb.so in the staging tree, so
# point dpkg-shlibdeps at the build tree to resolve the NEEDED entry.
# --ignore-missing-info: it comes from this build rather than an installed
# package, so it has no shlibs/symbols data. The dependency on the engine is
# declared explicitly in debian/control instead.
dh_shlibdeps -l$(DUCKDB_BUILD_LIBDIR) -- --ignore-missing-info

override_dh_missing:
# The .install globs above are the only thing routing files into packages in
# the owner build (dh_install's "install whatever is left" fallback applies
# to single-binary sources only), so fail loudly rather than silently
# shipping a package with a file missing.
dh_missing --fail-missing

override_dh_auto_test:
# pg_duckdb's `make check` drives a full regression harness that needs a
Expand Down
Loading
Loading