Skip to content
Open
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
40 changes: 40 additions & 0 deletions .claude/skills/series-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,46 @@ and the commit that reaches `-dev` from it
arrives broken again on exactly the platform the patch was for.
Commit it onto `<S>-build` as well, in the same firing.

**The entry a firing did not write is carried by stage 4, not by hand.**
Stage 4's port brings whole `main` commits onto `-dev`,
`patch/` files included,
so an entry born as a pull request against `main`
reaches every `-dev` without any firing deciding to put it there —
and used to reach no buffer at all.
`scripts/series-port.sh --apply` now closes that as part of porting,
through `scripts/series-patch-sync.sh <S>`,
which is a read-only report on its own.
The drift it exists to prevent is quiet
while upstream leaves the patched file alone,
because the buffer is then internally consistent
and its commits carry no delta for that file;
it bites the first time upstream touches it,
and the fix is reverted on `-dev`
by a commit that looks like an ordinary vendor.

**The carry decides by test-applying, never by the file list.**
A buffer runs ahead of `main`,
so an entry `main` needs may not fit the engine the buffer has vendored,
and committing it there regardless breaks the next vendor run
(`vendor.sh`'s "patches moved" exit) rather than helping anything.
Three answers, three situations:
*carry* applies cleanly and is taken with its effect on the tree;
*satisfied* reverse-applies, so only the entry is taken,
to keep the two stacks named alike;
*stale* is neither — upstream moved the code out from under it,
and it reaches the buffer if and when the code it answers does.
A candidate whose files an entry the buffer has and `-dev` lacks
also touches is a **supersession** and is never carried:
applying both is how the next vendor run breaks,
and which of the two the buffer should end up with is judgement.

`scripts/series-check.sh` still prints a PATCH DRIFT line per series,
naming what the two stacks differ by in both directions —
a renamed entry is one of each.
It is the backstop, not the mechanism:
what it reports after a port is what the carry deliberately declined,
which is a stage 3 repair like any patch this stage writes.

### 4. Port from `main`

The goal is identity, not curation:
Expand Down
50 changes: 50 additions & 0 deletions patch/0034-Guard-explicit-producer-token-in-concurrent-queue.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kirill=20M=C3=BCller?= <kirill@cynkra.com>
Date: Sat, 19 Jul 2026 00:00:00 +0200
Subject: [PATCH] Guard explicit producer token in concurrent queue

The explicit-token enqueue paths in the vendored concurrent queue
(moodycamel) dereference token.producer without a null check, unlike
their implicit-producer siblings which return false when no producer is
available. Because the compiler cannot prove token.producer is non-null,
GCC 12+ (notably Rtools45's GCC 14.3.0 on Windows) assumes the atomic
object may live at address zero and emits a false-positive
-Wstringop-overflow ("writing 8 bytes into a region of size 0") for the
8-byte atomic load inlined from ExplicitProducer::enqueue_bulk into
EvictionQueue::PurgeIteration, which R CMD check reports as a
significant warning.

Add the same null guard already used by the implicit-producer overloads.
This both silences the false positive at the source (no diagnostic
pragma or compiler-flag override required) and makes the explicit-token
enqueue paths as robust as the rest of the API.
---
src/duckdb/third_party/concurrentqueue/concurrentqueue.h | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/duckdb/third_party/concurrentqueue/concurrentqueue.h b/src/duckdb/third_party/concurrentqueue/concurrentqueue.h
index b62b637..e613c6a 100644
--- a/src/duckdb/third_party/concurrentqueue/concurrentqueue.h
+++ b/src/duckdb/third_party/concurrentqueue/concurrentqueue.h
@@ -1306,7 +1306,8 @@ private:
template<AllocationMode canAlloc, typename U>
inline bool inner_enqueue(producer_token_t const& token, U&& element)
{
- return static_cast<ExplicitProducer*>(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue<canAlloc>(std::forward<U>(element));
+ auto producer = static_cast<ExplicitProducer*>(token.producer);
+ return producer == nullptr ? false : producer->ConcurrentQueue::ExplicitProducer::template enqueue<canAlloc>(std::forward<U>(element));
}

template<AllocationMode canAlloc, typename U>
@@ -1319,7 +1320,8 @@ private:
template<AllocationMode canAlloc, typename It>
inline bool inner_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count)
{
- return static_cast<ExplicitProducer*>(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue_bulk<canAlloc>(itemFirst, count);
+ auto producer = static_cast<ExplicitProducer*>(token.producer);
+ return producer == nullptr ? false : producer->ConcurrentQueue::ExplicitProducer::template enqueue_bulk<canAlloc>(itemFirst, count);
}

template<AllocationMode canAlloc, typename It>
--
2.52.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kirill=20M=C3=BCller?= <kirill@cynkra.com>
Date: Fri, 31 Jul 2026 00:00:00 +0000
Subject: [PATCH] Silence deprecated Catalog::GetEntry() self-delegation

duckdb/duckdb@6d4f5328 deprecated the schema- and catalog/schema-qualified
Catalog::GetEntry() overloads in favour of folding the qualification into
EntryLookupInfo, but kept the old overloads as compatibility shims that
delegate to one another. GCC warns on every one of those internal calls,
even though the caller is itself deprecated (clang does not), and R CMD
check reports them as significant warnings:

duckdb/src/catalog/catalog.cpp:1154:24: warning: '...GetEntry(...)' is
deprecated: Fold the schema into the EntryLookupInfo and use
GetEntry(retriever, EntryLookupInfo) [-Wdeprecated-declarations]

Scope the diagnostic off for the one translation unit that holds the
shims. Rewriting them to avoid the delegation would mean duplicating the
private lookup logic in the vendored tree, and the deprecation is advisory
only -- upstream keeps the overloads working.

---
src/duckdb/src/catalog/catalog.cpp | 16 ++++++++++++++++
1 file changed, 16 insertions(+)

diff --git a/src/duckdb/src/catalog/catalog.cpp b/src/duckdb/src/catalog/catalog.cpp
index 1cd024735..a91cc7a8a 100644
--- a/src/duckdb/src/catalog/catalog.cpp
+++ b/src/duckdb/src/catalog/catalog.cpp
@@ -43,6 +43,16 @@
#include "duckdb/main/settings.hpp"
#include <algorithm>

+// For CRAN
+// The deprecated Catalog::GetEntry() compatibility overloads defined below
+// delegate to one another, which GCC reports as a significant warning during
+// R CMD check. The delegation is upstream's own.
+#ifdef __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+// For CRAN
+
namespace duckdb {

Catalog::Catalog(AttachedDatabase &db) : db(db) {
@@ -1396,3 +1406,9 @@ bool Catalog::HasConflictingAttachOptions(const string &path, const AttachOption
}

} // namespace duckdb
+
+// For CRAN
+#ifdef __GNUC__
+# pragma GCC diagnostic pop
+#endif
+// For CRAN
--
2.54.0
1 change: 1 addition & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ Root of the documentation tree: [`handbook/`](/handbook/README.md).
| [`series-cutover.sh`](series-cutover.sh) | Atomically replace a series with its forward counterpart. |
| [`series-forward-build.sh`](series-forward-build.sh) | Populate `<S>-fwd-build`: replay every vendor commit of the old `<S>-build` onto HEAD, which must be the freshly flavored seed on current `main` (.claude/ski... |
| [`series-glue.sh`](series-glue.sh) | Every R-side glue adaptation a series carries, in one read. |
| [`series-patch-sync.sh`](series-patch-sync.sh) | Carry `patch/` entries from `<S>-dev` onto `<S>-build`, the way stage 5 carries tests and glue onto a forward series. |
| [`series-port.sh`](series-port.sh) | Bring a series' -dev branch level with `main` — stage 4 of the series loop (.claude/skills/series-loop.md). |

## [`testing/snapshots/`](/handbook/testing/snapshots/README.md)
Expand Down
46 changes: 46 additions & 0 deletions scripts/series-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
# gets a CUTOVER line: the command to run, for a human to run. The loop never
# swaps a serving green itself (.claude/skills/series-loop.md).
#
# A series whose `-dev` carries `patch/` entries its `-build` lacks gets a
# PATCH DRIFT line: the buffer regenerates its tree from its own patch stack, so
# an entry that never reached it is one the next vendor run will not apply.
#
# Classification is by positive evidence only (.claude/skills/series-loop.md);
# "Job is waiting for a hosted runner" appears in every log and means nothing.
#
Expand Down Expand Up @@ -299,6 +303,48 @@ for S in "${series[@]}"; do
echo " ADVANCE"
fi

# A `patch/` entry that only ever reached `-dev` is absent the next time the
# buffer vendors: `vendor-one.sh` applies the *buffer's* patch stack to every
# tree it regenerates (.claude/skills/series-loop.md stage 3). Stage 4's port
# is how such an entry arrives -- it carries whole `main` commits onto `-dev`,
# `patch/` files included -- and `-build` takes no ports by design, so nothing
# closes the gap by itself. The drift is quiet while upstream leaves the
# patched file alone, because the buffer is then internally consistent and its
# commits carry no delta for that file; it bites the first time upstream
# touches it, and the fix is reverted on `-dev` by a commit that looks like an
# ordinary vendor.
#
# Reported in both directions, because a renamed entry is one of each and
# neither half alone says so.
# Compared by blob, not by name: `main` also edits an entry in place, and the
# port brings the new content to `-dev` under a name the buffer already has,
# which a name-only comparison calls level.
only_dev=$(comm -23 \
<(git ls-tree --name-only "$dev" patch/ | sort) \
<(git ls-tree --name-only "$build" patch/ | sort))
only_build=$(comm -13 \
<(git ls-tree --name-only "$dev" patch/ | sort) \
<(git ls-tree --name-only "$build" patch/ | sort))
differs=$(for n in $(comm -12 \
<(git ls-tree --name-only "$dev" patch/ | sort) \
<(git ls-tree --name-only "$build" patch/ | sort)); do
[ "$(git rev-parse "$dev:$n")" = "$(git rev-parse "$build:$n")" ] || echo "$n"
done)
if [ -n "$only_dev$only_build$differs" ]; then
echo " PATCH DRIFT the two patch stacks of $S differ:"
if [ -n "$only_dev" ]; then
sed 's|^patch/| -dev only: |' <<<"$only_dev"
fi
if [ -n "$only_build" ]; then
sed 's|^patch/| -build only: |' <<<"$only_build"
fi
if [ -n "$differs" ]; then
sed 's|^patch/| both, differ: |' <<<"$differs"
fi
echo " scripts/series-patch-sync.sh $S says which of these it can carry;"
echo " after a port, what is left is what it declined — stage 3 work"
fi

# Suggested, never done: a firing reports a ready cutover and stops
# (.claude/skills/series-loop.md). Printed beside the verdict rather than as
# one, because it is orthogonal — a forward series that has caught up still
Expand Down
Loading
Loading