From 8d8e713051f62c601f37c752b04f9ebcb9d21988 Mon Sep 17 00:00:00 2001 From: Philip Moore Date: Sun, 4 Jan 2026 16:44:03 -0500 Subject: [PATCH] Fix stack overflow from infinite recursion when binding views during Scan When DuckLakeSchemaEntry::Scan() iterates through catalog entries, it attempts to bind unbound views by calling view_entry.Bind(context). However, Bind() internally calls CreateViewInfo::FromCreateView() which parses the view's SQL. This can trigger catalog lookups (e.g., for GetSimilarEntry when resolving references), which in turn calls Scan() again - creating infinite recursion that exhausts the stack. This fix adds a thread-local recursion guard that prevents view binding during a recursive Scan() call. The guard uses RAII for exception safety. --- src/storage/ducklake_schema_entry.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/storage/ducklake_schema_entry.cpp b/src/storage/ducklake_schema_entry.cpp index 5b7a0923010..d1d75b0040c 100644 --- a/src/storage/ducklake_schema_entry.cpp +++ b/src/storage/ducklake_schema_entry.cpp @@ -15,6 +15,17 @@ namespace duckdb { +// Thread-local recursion guard to prevent infinite recursion when binding views during Scan. +// When Bind() is called, it may trigger catalog lookups (e.g., GetSimilarEntry) which call Scan() again. +// Without this guard, this can cause stack overflow. +static thread_local bool g_binding_views_in_scan = false; + +// RAII guard for exception-safe flag management +struct BindingGuard { + BindingGuard() { g_binding_views_in_scan = true; } + ~BindingGuard() { g_binding_views_in_scan = false; } +}; + DuckLakeSchemaEntry::DuckLakeSchemaEntry(Catalog &catalog, CreateSchemaInfo &info, SchemaIndex schema_id, string schema_uuid, string data_path_p) : SchemaCatalogEntry(catalog, info), schema_id(schema_id), schema_uuid(std::move(schema_uuid)), @@ -286,11 +297,15 @@ void DuckLakeSchemaEntry::Scan(ClientContext &context, CatalogType type, } if (entry.second->type == CatalogType::VIEW_ENTRY) { auto &view_entry = entry.second->Cast(); - if (!view_entry.IsBound()) { + // Only attempt to bind if we're not already in a binding operation. + // Binding can trigger catalog lookups which recursively call Scan(), + // leading to stack overflow without this guard. + if (!view_entry.IsBound() && !g_binding_views_in_scan) { + BindingGuard guard; try { view_entry.Bind(context); } catch (...) { - // + // Binding failed - view will remain unbound } } }