Repository: duckdb/duckdb (language: python)

Issue:
Binder error when manually materializing a CTE
### What happens?

The new automatic CTE materialization mechanism clashes with manually requesting a CTE to be materialized.



### To Reproduce

Run

```sql
create table batch (
    entity text,
    start_ts timestamptz,
    duration interval
)
;

create table active_events (
    entity text,
    start_ts timestamptz,
    end_ts timestamptz
)
;

create table active_events as
with
    new_events as materialized (  -- Does not make much sense in this example, but my original query was a union of a bunch of things
        select
            entity,
            start_ts as start_ts,
            start_ts + duration as end_ts
        from
            batch
    ), events_to_deduplicate as (
        select
            *
        from  -- We need to deduplicate events for old entites with new data
            active_events old
            semi join new_events new on
                old.entity = new.entity
        union all
        select *
        from
            new_events
    ), combined_deduplicated_events as (
        select
            entity,
            min(start_ts) as start_ts,
            max(end_ts) as end_ts
        from
            events_to_deduplicate
        group by
            entity
    ), all_events as (
        select
            entity,
            start_ts,
            end_ts
        from
            combined_deduplicated_events
        where
            end_ts > start_ts
        union all
        select
            *
        from  -- The data we did not need to deduplicate
            active_events old
            anti join new_events new on
                old.entity = new.entity
    )
select
    *
from
    combined_leases
;
```

and observe a Binder Error: Duplicate alias "new_events" in query!

Removing the `materialized` clause makes the query pass.



### OS:

Ubuntu x64 in WSL on Windows 11

### DuckDB Version:

v1.1.0 fa5c2fe15f

### DuckDB Client:

CLI

### Hardware:

_No response_

### Full Name:

Míma Hlaváček

### Affiliation:

Arista Networks

### What is the latest build you tested with? If possible, we recommend testing with the latest nightly build.

I have tested with a nightly build

### Did you include all relevant data sets for reproducing the issue?

Not applicable - the reproduction does not require a data set

### Did you include all code required to reproduce the issue?

- [X] Yes, I have

### Did you include all relevant configuration (e.g., CPU architecture, Python version, Linux distribution) to reproduce the issue?

- [X] Yes, I have
INTERNAL Error: Attempted to dereference unique_ptr that is NULL
### What happens?

The below test case (after some seconds creduce) causes an INTERNAL error in DuckDB. This [test case](https://github.com/user-attachments/files/17001454/backup.txt) before further reduced could sometimes cause segmentation fault.


### To Reproduce

```sql
CREATE TABLE t0(c0 TIMESTAMP, c1 VARCHAR[]);
CREATE TABLE t1(c0 FLOAT, c1 TIMESTAMP, c2 FLOAT);
INSERT INTO t0 VALUES('2023-10-10 00:00:00+00:00', NULL);
INSERT INTO t0 VALUES('2025-12-25 12:00:00+02:00', []), ('2004-07-27 10:00:00+02', []);
INSERT INTO t0(c1, c0) VALUES([], '2023-01-01 00:00:00+00:00'), ([], '2021-01-01 00:00:00+01');
INSERT INTO t0(c1, c0) VALUES([], '2021-01-01 00:00:00+00');
INSERT INTO t1 VALUES(2.71, '1999-12-31 23:59:59', 1.41421356237);
INSERT INTO t1 VALUES(1.61803, '1970-01-01 00:00:00', 1.61803);
INSERT INTO t1(c0) VALUES(1064961652.34), (123.45);
INSERT INTO t1(c0) VALUES('514332609.12');
INSERT INTO t1(c0, c2, c1) VALUES(2.71828, 2.345, '1995-05-23 08:45:00'), ('1308880808', 12.34, '2021-01-01 15:30:45');
INSERT INTO t1(c0) VALUES(92857950), (840458867);
INSERT INTO t1 VALUES('3.14', '1999-12-31 23:59:59', 3.1415);

SELECT * FROM t0 RIGHT JOIN t1 ON(CAST(t1.c1 AS TIMESTAMP) BETWEEN t0.c0 AND t0.c0);
-- INTERNAL Error: Attempted to dereference unique_ptr that is NULL! This error signals an assertion failure within DuckDB. This usually occurs due to unexpected conditions or errors in the program's logic. For more information, see https://duckdb.org/docs/dev/internal_errors
```

### OS:

Ubuntu 22.04

### DuckDB Version:

v1.1.1-dev122 b369bcb4e0

### DuckDB Client:

CLI

### Hardware:

_No response_

### Full Name:

Suyang Zhong

### Affiliation:

NUS

### What is the latest build you tested with? If possible, we recommend testing with the latest nightly build.

I have tested with a nightly build

### Did you include all relevant data sets for reproducing the issue?

Yes

### Did you include all code required to reproduce the issue?

- [X] Yes, I have

### Did you include all relevant configuration (e.g., CPU architecture, Python version, Linux distribution) to reproduce the issue?

- [X] Yes, I have


Discussion / hints:

@suyZhong Reproduced it, thanks!

Fix patch:
diff --git a/extension/jemalloc/jemalloc/README.md b/extension/jemalloc/jemalloc/README.md
index 292d2e4bbae6..9bf08821e76a 100644
--- a/extension/jemalloc/jemalloc/README.md
+++ b/extension/jemalloc/jemalloc/README.md
@@ -81,9 +81,9 @@ jemalloc_constructor(void) {
 	// decay is in ms
 	unsigned long long decay = DUCKDB_JEMALLOC_DECAY * 1000;
 #ifdef DEBUG
-	snprintf(JE_MALLOC_CONF_BUFFER, JE_MALLOC_CONF_BUFFER_SIZE, "junk:true,oversize_threshold:268435456,dirty_decay_ms:%llu,muzzy_decay_ms:%llu,narenas:%llu,max_background_threads:%llu", decay, decay, cpu_count, bgt_count);
+	snprintf(JE_MALLOC_CONF_BUFFER, JE_MALLOC_CONF_BUFFER_SIZE, "junk:true,oversize_threshold:268435456,dirty_decay_ms:%llu,muzzy_decay_ms:%llu,narenas:%llu,max_background_threads:%llu", decay, decay, cpu_count / 2, bgt_count);
 #else
-	snprintf(JE_MALLOC_CONF_BUFFER, JE_MALLOC_CONF_BUFFER_SIZE, "oversize_threshold:268435456,dirty_decay_ms:%llu,muzzy_decay_ms:%llu,narenas:%llu,max_background_threads:%llu", decay, decay, cpu_count, bgt_count);
+	snprintf(JE_MALLOC_CONF_BUFFER, JE_MALLOC_CONF_BUFFER_SIZE, "oversize_threshold:268435456,dirty_decay_ms:%llu,muzzy_decay_ms:%llu,narenas:%llu,max_background_threads:%llu", decay, decay, cpu_count / 2, bgt_count);
 #endif
 	je_malloc_conf = JE_MALLOC_CONF_BUFFER;
 	malloc_init();
diff --git a/extension/jemalloc/jemalloc/src/jemalloc.c b/extension/jemalloc/jemalloc/src/jemalloc.c
index bac0880f9deb..9d57c2e42e73 100644
--- a/extension/jemalloc/jemalloc/src/jemalloc.c
+++ b/extension/jemalloc/jemalloc/src/jemalloc.c
@@ -4277,9 +4277,9 @@ jemalloc_constructor(void) {
 	// decay is in ms
 	unsigned long long decay = DUCKDB_JEMALLOC_DECAY * 1000;
 #ifdef DEBUG
-	snprintf(JE_MALLOC_CONF_BUFFER, JE_MALLOC_CONF_BUFFER_SIZE, "junk:true,oversize_threshold:268435456,dirty_decay_ms:%llu,muzzy_decay_ms:%llu,narenas:%llu,max_background_threads:%llu", decay, decay, cpu_count, bgt_count);
+	snprintf(JE_MALLOC_CONF_BUFFER, JE_MALLOC_CONF_BUFFER_SIZE, "junk:true,oversize_threshold:268435456,dirty_decay_ms:%llu,muzzy_decay_ms:%llu,narenas:%llu,max_background_threads:%llu", decay, decay, cpu_count / 2, bgt_count);
 #else
-	snprintf(JE_MALLOC_CONF_BUFFER, JE_MALLOC_CONF_BUFFER_SIZE, "oversize_threshold:268435456,dirty_decay_ms:%llu,muzzy_decay_ms:%llu,narenas:%llu,max_background_threads:%llu", decay, decay, cpu_count, bgt_count);
+	snprintf(JE_MALLOC_CONF_BUFFER, JE_MALLOC_CONF_BUFFER_SIZE, "oversize_threshold:268435456,dirty_decay_ms:%llu,muzzy_decay_ms:%llu,narenas:%llu,max_background_threads:%llu", decay, decay, cpu_count / 2, bgt_count);
 #endif
 	je_malloc_conf = JE_MALLOC_CONF_BUFFER;
 	malloc_init();
diff --git a/extension/json/include/json_common.hpp b/extension/json/include/json_common.hpp
index bcc8d7c23db7..872058023c22 100644
--- a/extension/json/include/json_common.hpp
+++ b/extension/json/include/json_common.hpp
@@ -241,12 +241,16 @@ struct JSONCommon {
 	};
 
 	//! Get JSON value using JSON path query (safe, checks the path query)
-	static inline yyjson_val *Get(yyjson_val *val, const string_t &path_str) {
+	static inline yyjson_val *Get(yyjson_val *val, const string_t &path_str, bool integral_argument) {
 		auto ptr = path_str.GetData();
 		auto len = path_str.GetSize();
 		if (len == 0) {
 			return GetUnsafe(val, ptr, len);
 		}
+		if (integral_argument) {
+			auto str = "$[" + path_str.GetString() + "]";
+			return GetUnsafe(val, str.c_str(), str.length());
+		}
 		switch (*ptr) {
 		case '/': {
 			// '/' notation must be '\0'-terminated
@@ -260,9 +264,15 @@ struct JSONCommon {
 			}
 			return GetUnsafe(val, ptr, len);
 		}
-		default:
-			auto str = "/" + string(ptr, len);
-			return GetUnsafe(val, str.c_str(), len + 1);
+		default: {
+			string path;
+			if (memchr(ptr, '"', len)) {
+				path = "/" + string(ptr, len);
+			} else {
+				path = "$.\"" + path_str.GetString() + "\"";
+			}
+			return GetUnsafe(val, path.c_str(), path.length());
+		}
 		}
 	}
 
diff --git a/extension/json/include/json_executors.hpp b/extension/json/include/json_executors.hpp
index 0eeff5e40bc6..3290a95ede6a 100644
--- a/extension/json/include/json_executors.hpp
+++ b/extension/json/include/json_executors.hpp
@@ -8,6 +8,7 @@
 
 #pragma once
 
+#include "duckdb/common/vector_operations/vector_operations.hpp"
 #include "duckdb/execution/expression_executor.hpp"
 #include "json_functions.hpp"
 
@@ -88,11 +89,18 @@ struct JSONExecutors {
 			}
 		} else { // Columnref path
 			D_ASSERT(info.path_type == JSONCommon::JSONPathType::REGULAR);
-			auto &paths = args.data[1];
+			unique_ptr<Vector> casted_paths;
+			if (args.data[1].GetType().id() == LogicalTypeId::VARCHAR) {
+				casted_paths = make_uniq<Vector>(args.data[1]);
+			} else {
+				casted_paths = make_uniq<Vector>(LogicalTypeId::VARCHAR);
+				VectorOperations::DefaultCast(args.data[1], *casted_paths, args.size(), true);
+			}
 			BinaryExecutor::ExecuteWithNulls<string_t, string_t, T>(
-			    inputs, paths, result, args.size(), [&](string_t input, string_t path, ValidityMask &mask, idx_t idx) {
+			    inputs, *casted_paths, result, args.size(),
+			    [&](string_t input, string_t path, ValidityMask &mask, idx_t idx) {
 				    auto doc = JSONCommon::ReadDocument(input, JSONCommon::READ_FLAG, lstate.json_allocator.GetYYAlc());
-				    auto val = JSONCommon::Get(doc->root, path);
+				    auto val = JSONCommon::Get(doc->root, path, args.data[1].GetType().IsIntegral());
 				    if (SET_NULL_IF_NOT_FOUND && !val) {
 					    mask.SetInvalid(idx);
 					    return T {};
diff --git a/extension/json/json_extension.cpp b/extension/json/json_extension.cpp
index b594c26d9c15..e0665260a5bf 100644
--- a/extension/json/json_extension.cpp
+++ b/extension/json/json_extension.cpp
@@ -27,7 +27,7 @@ static DefaultMacro json_macros[] = {
      "json_group_structure",
      {"x", nullptr},
      {{nullptr, nullptr}},
-     "json_structure(json_group_array(x))->'0'"},
+     "json_structure(json_group_array(x))->0"},
     {DEFAULT_SCHEMA, "json", {"x", nullptr}, {{nullptr, nullptr}}, "json_extract(x, '$')"},
     {nullptr, nullptr, {nullptr}, {{nullptr, nullptr}}, nullptr}};
 
diff --git a/extension/json/json_functions.cpp b/extension/json/json_functions.cpp
index 0ad6837690b6..2b8b7828e17c 100644
--- a/extension/json/json_functions.cpp
+++ b/extension/json/json_functions.cpp
@@ -21,21 +21,25 @@ static JSONPathType CheckPath(const Value &path_val, string &path, size_t &len)
 	const auto path_str_val = path_val.DefaultCastAs(LogicalType::VARCHAR);
 	auto path_str = path_str_val.GetValueUnsafe<string_t>();
 	len = path_str.GetSize();
-	auto ptr = path_str.GetData();
+	const auto ptr = path_str.GetData();
 	// Empty strings and invalid $ paths yield an error
 	if (len == 0) {
 		throw BinderException("Empty JSON path");
 	}
 	JSONPathType path_type = JSONPathType::REGULAR;
-	if (*ptr == '$') {
-		path_type = JSONCommon::ValidatePath(ptr, len, true);
-	}
 	// Copy over string to the bind data
 	if (*ptr == '/' || *ptr == '$') {
 		path = string(ptr, len);
-	} else {
+	} else if (path_val.type().IsIntegral()) {
+		path = "$[" + string(ptr, len) + "]";
+	} else if (memchr(ptr, '"', len)) {
 		path = "/" + string(ptr, len);
-		len++;
+	} else {
+		path = "$.\"" + string(ptr, len) + "\"";
+	}
+	len = path.length();
+	if (*path.c_str() == '$') {
+		path_type = JSONCommon::ValidatePath(path.c_str(), len, true);
 	}
 	return path_type;
 }
@@ -67,7 +71,11 @@ unique_ptr<FunctionData> JSONReadFunctionData::Bind(ClientContext &context, Scal
 			path_type = CheckPath(path_val, path, len);
 		}
 	}
-	bound_function.arguments[1] = LogicalType::VARCHAR;
+	if (arguments[1]->return_type.IsIntegral()) {
+		bound_function.arguments[1] = LogicalType::BIGINT;
+	} else {
+		bound_function.arguments[1] = LogicalType::VARCHAR;
+	}
 	if (path_type == JSONCommon::JSONPathType::WILDCARD) {
 		bound_function.return_type = LogicalType::LIST(bound_function.return_type);
 	}
@@ -117,6 +125,7 @@ unique_ptr<FunctionData> JSONReadManyFunctionData::Bind(ClientContext &context,
 
 JSONFunctionLocalState::JSONFunctionLocalState(Allocator &allocator) : json_allocator(allocator) {
 }
+
 JSONFunctionLocalState::JSONFunctionLocalState(ClientContext &context)
     : JSONFunctionLocalState(BufferAllocator::Get(context)) {
 }
diff --git a/src/common/types/column/column_data_allocator.cpp b/src/common/types/column/column_data_allocator.cpp
index 0f2c63849e6e..bec0751e0d6c 100644
--- a/src/common/types/column/column_data_allocator.cpp
+++ b/src/common/types/column/column_data_allocator.cpp
@@ -2,6 +2,7 @@
 
 #include "duckdb/common/types/column/column_data_collection_segment.hpp"
 #include "duckdb/storage/buffer/block_handle.hpp"
+#include "duckdb/storage/buffer/buffer_pool.hpp"
 #include "duckdb/storage/buffer_manager.hpp"
 
 namespace duckdb {
@@ -45,6 +46,21 @@ ColumnDataAllocator::ColumnDataAllocator(ColumnDataAllocator &other) {
 	}
 }
 
+ColumnDataAllocator::~ColumnDataAllocator() {
+	if (type == ColumnDataAllocatorType::IN_MEMORY_ALLOCATOR) {
+		return;
+	}
+	for (auto &block : blocks) {
+		block.handle->SetDestroyBufferUpon(DestroyBufferUpon::UNPIN);
+	}
+	const auto data_size = SizeInBytes();
+	blocks.clear();
+	if (Allocator::SupportsFlush() &&
+	    data_size > alloc.buffer_manager->GetBufferPool().GetAllocatorBulkDeallocationFlushThreshold()) {
+		Allocator::FlushAll();
+	}
+}
+
 BufferHandle ColumnDataAllocator::Pin(uint32_t block_id) {
 	D_ASSERT(type == ColumnDataAllocatorType::BUFFER_MANAGER_ALLOCATOR || type == ColumnDataAllocatorType::HYBRID);
 	shared_ptr<BlockHandle> handle;
diff --git a/src/common/types/row/tuple_data_segment.cpp b/src/common/types/row/tuple_data_segment.cpp
index 82e25001ee2b..ddec1323846e 100644
--- a/src/common/types/row/tuple_data_segment.cpp
+++ b/src/common/types/row/tuple_data_segment.cpp
@@ -1,6 +1,7 @@
 #include "duckdb/common/types/row/tuple_data_segment.hpp"
 
 #include "duckdb/common/types/row/tuple_data_allocator.hpp"
+#include "duckdb/storage/buffer/buffer_pool.hpp"
 
 namespace duckdb {
 
@@ -118,6 +119,10 @@ TupleDataSegment::~TupleDataSegment() {
 	}
 	pinned_row_handles.clear();
 	pinned_heap_handles.clear();
+	if (Allocator::SupportsFlush() && allocator &&
+	    data_size > allocator->GetBufferManager().GetBufferPool().GetAllocatorBulkDeallocationFlushThreshold()) {
+		Allocator::FlushAll();
+	}
 	allocator.reset();
 }
 
diff --git a/src/execution/operator/join/physical_piecewise_merge_join.cpp b/src/execution/operator/join/physical_piecewise_merge_join.cpp
index d7b30423e3a0..8216d91ab18b 100644
--- a/src/execution/operator/join/physical_piecewise_merge_join.cpp
+++ b/src/execution/operator/join/physical_piecewise_merge_join.cpp
@@ -618,7 +618,12 @@ OperatorResultType PhysicalPiecewiseMergeJoin::ResolveComplexJoin(ExecutionConte
 
 				if (tail_count < result_count) {
 					result_count = tail_count;
-					chunk.Slice(*sel, result_count);
+					if (result_count == 0) {
+						// Need to reset here otherwise we may use the non-flat chunk when constructing LEFT/OUTER
+						chunk.Reset();
+					} else {
+						chunk.Slice(*sel, result_count);
+					}
 				}
 			}
 
diff --git a/src/include/duckdb/common/types/column/column_data_allocator.hpp b/src/include/duckdb/common/types/column/column_data_allocator.hpp
index dc49d2db0ad9..194b40ca3ea1 100644
--- a/src/include/duckdb/common/types/column/column_data_allocator.hpp
+++ b/src/include/duckdb/common/types/column/column_data_allocator.hpp
@@ -32,6 +32,7 @@ class ColumnDataAllocator {
 	explicit ColumnDataAllocator(BufferManager &buffer_manager);
 	ColumnDataAllocator(ClientContext &context, ColumnDataAllocatorType allocator_type);
 	ColumnDataAllocator(ColumnDataAllocator &allocator);
+	~ColumnDataAllocator();
 
 	//! Returns an allocator object to allocate with. This returns the allocator in IN_MEMORY_ALLOCATOR, and a buffer
 	//! allocator in case of BUFFER_MANAGER_ALLOCATOR.
diff --git a/src/include/duckdb/storage/buffer/buffer_pool.hpp b/src/include/duckdb/storage/buffer/buffer_pool.hpp
index 955f1aa4ecee..50166a51fa05 100644
--- a/src/include/duckdb/storage/buffer/buffer_pool.hpp
+++ b/src/include/duckdb/storage/buffer/buffer_pool.hpp
@@ -50,6 +50,7 @@ class BufferPool {
 
 	//! If bulk deallocation larger than this occurs, flush outstanding allocations
 	void SetAllocatorBulkDeallocationFlushThreshold(idx_t threshold);
+	idx_t GetAllocatorBulkDeallocationFlushThreshold();
 
 	void UpdateUsedMemory(MemoryTag tag, int64_t size);
 
diff --git a/src/optimizer/filter_pushdown.cpp b/src/optimizer/filter_pushdown.cpp
index 0744c67091f8..4af2a075ff53 100644
--- a/src/optimizer/filter_pushdown.cpp
+++ b/src/optimizer/filter_pushdown.cpp
@@ -93,9 +93,14 @@ unique_ptr<LogicalOperator> FilterPushdown::Rewrite(unique_ptr<LogicalOperator>
 		// we can just push directly through these operations without any rewriting
 		op->children[0] = Rewrite(std::move(op->children[0]));
 		return op;
-	case LogicalOperatorType::LOGICAL_MATERIALIZED_CTE:
+	case LogicalOperatorType::LOGICAL_MATERIALIZED_CTE: {
+		// we can't push filters into the materialized CTE (LHS), but we do want to recurse into it
+		FilterPushdown pushdown(optimizer, convert_mark_joins);
+		op->children[0] = pushdown.Rewrite(std::move(op->children[0]));
+		// we can push filters into the rest of the query plan (RHS)
 		op->children[1] = Rewrite(std::move(op->children[1]));
 		return op;
+	}
 	case LogicalOperatorType::LOGICAL_GET:
 		return PushdownGet(std::move(op));
 	case LogicalOperatorType::LOGICAL_LIMIT:
diff --git a/src/optimizer/join_order/cardinality_estimator.cpp b/src/optimizer/join_order/cardinality_estimator.cpp
index 7b3151d4049d..fb65c0baf739 100644
--- a/src/optimizer/join_order/cardinality_estimator.cpp
+++ b/src/optimizer/join_order/cardinality_estimator.cpp
@@ -2,10 +2,10 @@
 #include "duckdb/common/enums/join_type.hpp"
 #include "duckdb/common/limits.hpp"
 #include "duckdb/common/printer.hpp"
-#include "duckdb/planner/expression_iterator.hpp"
 #include "duckdb/function/table/table_scan.hpp"
 #include "duckdb/optimizer/join_order/join_node.hpp"
 #include "duckdb/optimizer/join_order/query_graph_manager.hpp"
+#include "duckdb/planner/expression_iterator.hpp"
 #include "duckdb/planner/operator/logical_comparison_join.hpp"
 #include "duckdb/storage/data_table.hpp"
 
@@ -291,10 +291,18 @@ DenomInfo CardinalityEstimator::GetDenominator(JoinRelationSet &set) {
 	// and we start to choose the filters that join relations in the set.
 
 	// edges are guaranteed to be in order of largest tdom to smallest tdom.
+	unordered_set<idx_t> unused_edge_tdoms;
 	auto edges = GetEdges(relations_to_tdoms, set);
 	for (auto &edge : edges) {
-		auto subgraph_connections = SubgraphsConnectedByEdge(edge, subgraphs);
+		if (subgraphs.size() == 1 && subgraphs.at(0).relations->ToString() == set.ToString()) {
+			// the first subgraph has connected all the desired relations, just skip the rest of the edges
+			if (edge.has_tdom_hll) {
+				unused_edge_tdoms.insert(edge.tdom_hll);
+			}
+			continue;
+		}
 
+		auto subgraph_connections = SubgraphsConnectedByEdge(edge, subgraphs);
 		if (subgraph_connections.empty()) {
 			// create a subgraph out of left and right, then merge right into left and add left to subgraphs.
 			// this helps cover a case where there are no subgraphs yet, and the only join filter is a SEMI JOIN
@@ -342,13 +350,11 @@ DenomInfo CardinalityEstimator::GetDenominator(JoinRelationSet &set) {
 			                                   [](Subgraph2Denominator &s) { return !s.relations; });
 			subgraphs.erase(remove_start, subgraphs.end());
 		}
-		if (subgraphs.size() == 1 && subgraphs.at(0).relations->ToString() == set.ToString()) {
-			// the first subgraph has connected all the desired relations, no need to iterate
-			// through the rest of the edges.
-			break;
-		}
 	}
 
+	// Slight penalty to cardinality for unused edges
+	auto denom_multiplier = 1.0 + static_cast<double>(unused_edge_tdoms.size());
+
 	// It's possible cross-products were added and are not present in the filters in the relation_2_tdom
 	// structures. When that's the case, merge all remaining subgraphs.
 	if (subgraphs.size() > 1) {
@@ -367,7 +373,7 @@ DenomInfo CardinalityEstimator::GetDenominator(JoinRelationSet &set) {
 		// denominator is 1 and numerators are a cross product of cardinalities.
 		return DenomInfo(set, 1, 1);
 	}
-	return DenomInfo(*subgraphs.at(0).numerator_relations, 1, subgraphs.at(0).denom);
+	return DenomInfo(*subgraphs.at(0).numerator_relations, 1, subgraphs.at(0).denom * denom_multiplier);
 }
 
 template <>
diff --git a/src/planner/binder.cpp b/src/planner/binder.cpp
index ea751bade659..9f0abf3986ea 100644
--- a/src/planner/binder.cpp
+++ b/src/planner/binder.cpp
@@ -342,7 +342,8 @@ unique_ptr<BoundQueryNode> Binder::BindNode(QueryNode &node) {
 
 BoundStatement Binder::Bind(QueryNode &node) {
 	BoundStatement result;
-	if (context.db->config.options.disabled_optimizers.find(OptimizerType::MATERIALIZED_CTE) ==
+	if (node.type != QueryNodeType::CTE_NODE && // Issue #13850 - Don't auto-materialize if users materialize (for now)
+	    context.db->config.options.disabled_optimizers.find(OptimizerType::MATERIALIZED_CTE) ==
 	        context.db->config.options.disabled_optimizers.end() &&
 	    context.config.enable_optimizer && OptimizeCTEs(node)) {
 		switch (node.type) {
diff --git a/src/storage/buffer/buffer_pool.cpp b/src/storage/buffer/buffer_pool.cpp
index 3e7e11c496dc..9ed31f6f3262 100644
--- a/src/storage/buffer/buffer_pool.cpp
+++ b/src/storage/buffer/buffer_pool.cpp
@@ -413,6 +413,10 @@ void BufferPool::SetAllocatorBulkDeallocationFlushThreshold(idx_t threshold) {
 	allocator_bulk_deallocation_flush_threshold = threshold;
 }
 
+idx_t BufferPool::GetAllocatorBulkDeallocationFlushThreshold() {
+	return allocator_bulk_deallocation_flush_threshold;
+}
+
 BufferPool::MemoryUsage::MemoryUsage() {
 	for (auto &v : memory_usage) {
 		v = 0;


Test patch:
diff --git a/test/issues/general/test_13938.test b/test/issues/general/test_13938.test
new file mode 100644
index 000000000000..0d91b3f296f4
--- /dev/null
+++ b/test/issues/general/test_13938.test
@@ -0,0 +1,45 @@
+# name: test/issues/general/test_13938.test
+# description: Issue 13938 - INTERNAL Error: Attempted to dereference unique_ptr that is NULL
+# group: [general]
+
+statement ok
+CREATE TABLE t0(c0 TIMESTAMP, c1 VARCHAR[]);
+
+statement ok
+CREATE TABLE t1(c0 FLOAT, c1 TIMESTAMP, c2 FLOAT);
+
+statement ok
+INSERT INTO t0 VALUES('2023-10-10 00:00:00+00:00', NULL);
+
+statement ok
+INSERT INTO t0 VALUES('2025-12-25 12:00:00+02:00', []), ('2004-07-27 10:00:00+02', []);
+
+statement ok
+INSERT INTO t0(c1, c0) VALUES([], '2023-01-01 00:00:00+00:00'), ([], '2021-01-01 00:00:00+01');
+
+statement ok
+INSERT INTO t0(c1, c0) VALUES([], '2021-01-01 00:00:00+00');
+
+statement ok
+INSERT INTO t1 VALUES(2.71, '1999-12-31 23:59:59', 1.41421356237);
+
+statement ok
+INSERT INTO t1 VALUES(1.61803, '1970-01-01 00:00:00', 1.61803);
+
+statement ok
+INSERT INTO t1(c0) VALUES(1064961652.34), (123.45);
+
+statement ok
+INSERT INTO t1(c0) VALUES('514332609.12');
+
+statement ok
+INSERT INTO t1(c0, c2, c1) VALUES(2.71828, 2.345, '1995-05-23 08:45:00'), ('1308880808', 12.34, '2021-01-01 15:30:45');
+
+statement ok
+INSERT INTO t1(c0) VALUES(92857950), (840458867);
+
+statement ok
+INSERT INTO t1 VALUES('3.14', '1999-12-31 23:59:59', 3.1415);
+
+statement ok
+SELECT * FROM t0 RIGHT JOIN t1 ON(CAST(t1.c1 AS TIMESTAMP) BETWEEN t0.c0 AND t0.c0);
diff --git a/test/sql/cte/materialized/annotated_and_auto_materialized.test b/test/sql/cte/materialized/annotated_and_auto_materialized.test
new file mode 100644
index 000000000000..6ba4008d512c
--- /dev/null
+++ b/test/sql/cte/materialized/annotated_and_auto_materialized.test
@@ -0,0 +1,39 @@
+# name: test/sql/cte/materialized/annotated_and_auto_materialized.test
+# description: Issue 13850 - Binder error when manually materializing a CTE
+# group: [materialized]
+
+statement ok
+create table batch (
+    entity text,
+    start_ts timestamp,
+    duration interval
+);
+
+statement ok
+create table active_events (
+    entity text,
+    start_ts timestamp,
+    end_ts timestamp
+);
+
+statement ok
+explain create table new_active_events as
+with
+  new_events as materialized (  -- Does not make much sense in this example, but my original query was a union of a bunch of things
+      select * from batch
+  ), combined_deduplicated_events as (
+      select
+          entity,
+          min(start_ts) as start_ts,
+          max(end_ts) as end_ts
+      from
+          active_events
+      group by
+          entity
+  ), all_events as (
+      select  * from combined_deduplicated_events
+  )
+select
+  *
+from
+  new_events;
\ No newline at end of file
diff --git a/test/sql/json/issues/issue13948.test b/test/sql/json/issues/issue13948.test
new file mode 100644
index 000000000000..827ead417d4f
--- /dev/null
+++ b/test/sql/json/issues/issue13948.test
@@ -0,0 +1,79 @@
+# name: test/sql/json/issues/issue13948.test
+# description: Test issue 13948 - Json property name with special characters produce inconsistent results with json -> 'propertyname' and json_extract
+# group: [issues]
+
+require json
+
+statement ok
+pragma enable_verification
+
+query I
+SELECT '{"Status / SubStatus": "test"}' -> 'Status / SubStatus';
+----
+"test"
+
+query I
+WITH path AS (
+    SELECT 'Status / SubStatus' p
+)
+SELECT '{"Status / SubStatus": "test"}' -> p
+FROM path
+----
+"test"
+
+# TODO at some point we should escape supplied JSON paths automatically so that this works
+query I
+SELECT '{"\"Status / SubStatus\"": "test"}' -> '"Status / SubStatus"';
+----
+NULL
+
+query I
+WITH path AS (
+    SELECT NULL p
+)
+SELECT '{"\"Status / SubStatus\"": "test"}' -> p
+FROM path
+----
+NULL
+
+query I
+SELECT '{"Status / SubStatus": "test"}' -> '$."Status / SubStatus"';
+----
+"test"
+
+query I
+WITH path AS (
+    SELECT '$."Status / SubStatus"' p
+)
+SELECT '{"Status / SubStatus": "test"}' -> p
+FROM path
+----
+"test"
+
+query I
+SELECT '[1, 2, 3]'->0
+----
+1
+
+query I
+WITH path AS (
+    SELECT 0 p
+)
+SELECT '[1, 2, 3]' -> p
+FROM path
+----
+1
+
+query I
+SELECT '[1, 2, 3]'->'0'
+----
+NULL
+
+query I
+WITH path AS (
+    SELECT '0' p
+)
+SELECT '[1, 2, 3]' -> p
+FROM path
+----
+NULL
diff --git a/test/sql/json/scalar/test_json_extract.test b/test/sql/json/scalar/test_json_extract.test
index d41b6a4e909e..276cfc229e31 100644
--- a/test/sql/json/scalar/test_json_extract.test
+++ b/test/sql/json/scalar/test_json_extract.test
@@ -85,7 +85,7 @@ select '{"my_field": {"my_nested_field": ["goose", "duckduckduckduck"]}}'::JSON-
 duckduckduckduck
 
 query T
-select json_extract('[1, 2, 42]', '2')
+select json_extract('[1, 2, 42]', 2)
 ----
 42
 
diff --git a/test/sql/json/scalar/test_json_value.test b/test/sql/json/scalar/test_json_value.test
index 30e0c1acd873..d0ae5622b542 100644
--- a/test/sql/json/scalar/test_json_value.test
+++ b/test/sql/json/scalar/test_json_value.test
@@ -39,7 +39,7 @@ select json_value('{"my_field": {"my_nested_field": ["goose", "duckduckduckduck"
 "duckduckduckduck"
 
 query T
-select json_value('[1, 2, 42]', '2')
+select json_value('[1, 2, 42]', 2)
 ----
 42
 
diff --git a/test/sql/optimizer/plan/test_filter_pushdown_materialized_cte.test b/test/sql/optimizer/plan/test_filter_pushdown_materialized_cte.test
new file mode 100644
index 000000000000..d71d18a78a75
--- /dev/null
+++ b/test/sql/optimizer/plan/test_filter_pushdown_materialized_cte.test
@@ -0,0 +1,34 @@
+# name: test/sql/optimizer/plan/test_filter_pushdown_materialized_cte.test
+# description: Test filter pushdown in materialized CTEs (internal issue #3041)
+# group: [plan]
+
+require tpcds
+
+statement ok
+call dsdgen(sf=0.01)
+
+statement ok
+pragma explain_output='OPTIMIZED_ONLY'
+
+query II
+EXPLAIN WITH ss AS MATERIALIZED
+  ( SELECT i_manufact_id,
+           sum(ss_ext_sales_price) total_sales
+   FROM store_sales,
+        date_dim,
+        customer_address,
+        item
+   WHERE i_manufact_id IN
+       (SELECT i_manufact_id
+        FROM item
+        WHERE i_category IN ('Electronics'))
+     AND ss_item_sk = i_item_sk
+     AND ss_sold_date_sk = d_date_sk
+     AND d_year = 1998
+     AND d_moy = 5
+     AND ss_addr_sk = ca_address_sk
+     AND ca_gmt_offset = -5
+   GROUP BY i_manufact_id)
+FROM ss
+----
+logical_opt	<!REGEX>:.*CROSS_PRODUCT.*