Repository: duckdb/duckdb (language: python)

Issue:
Namespace resolution for VIEW does not work for views from ATTACH-ed database
### What happens?

Persistent `.duckdb` database exist, in which two views are defined:
- `first_view` is defined as a select over a  `read_parquet(...)` function
- `second_view` is defined as a view on top of `first_view`

Both these views can be queried when the `.duckdb` database is opened with the CLI tool.

After using `ATTACH` from another database (eg :memory) attempts to query `second_view` fails with:
```
CatalogException
This cell raised an exception: CatalogException('Catalog Error: Table with name first_view does not exist! Did you mean "demo.first_view"?')
``` 

Details:
```
Traceback (most recent call last):
  File "C:\Users\jbi\Repos\marimo\bugreport\.venv\Lib\site-packages\marimo\_runtime\executor.py", line 170, in execute_cell
    exec(cell.body, glbls)
  Cell marimo://C:\Users\jbi\Repos\marimo\bugreport\notebooks\Myname.py#cell=cell-3, line 1, in <module>
    _df = mo.sql(
          ^^^^^^^
  File "C:\Users\jbi\Repos\marimo\bugreport\.venv\Lib\site-packages\marimo\_sql\sql.py", line 40, in sql
    relation = duckdb.sql(query=query)
               ^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\jbi\Repos\marimo\bugreport\.venv\Lib\site-packages\duckdb\__init__.py", line 457, in sql
    return conn.sql(query, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
duckdb.duckdb.CatalogException: Catalog Error: Table with name first_view does not exist!
Did you mean "demo.first_view"?
``` 


### To Reproduce


DuckDB CLI is first used to create a .parquet file. Start CLI in memory mode:
```shell
duckdb
``` 

Execute statements:
```
CREATE TABLE mytable (id INTEGER PRIMARY KEY, j VARCHAR);
INSERT INTO mytable VALUES (1, 'a');
INSERT INTO mytable VALUES (b, '2');

COPY
  (SELECT * FROM mytable)
  TO 'demo.parquet'
  (FORMAT 'parquet');
``` 

Exit CLI tool. 

Next step is to create the persistent .duckdb database. Open/create DB:
```shell
duckdb demo.duckdb
``` 

Execute statements to create view. Note that full-path to demo.parquet file is required. 
```
CREATE VIEW first_view AS SELECT id, j FROM read_parquet('C:\Temp\demo.parquet');
CREATE VIEW second_view AS SELECT * FROM first_view;
``` 

Edit by @szarnyasg – Bash one-liner:

```bash
duckdb demo.duckdb -c "CREATE VIEW first_view AS SELECT id, j FROM read_parquet('`pwd`/demo.parquet'); CREATE VIEW second_view AS SELECT * FROM first_view;"
```

These views can both be queried as one would expect. 

Exit CLI tool, and open new a transient duck-db database:
```shell
duckdb
```

Attach the .duckdb database
```
ATTACH 'demo.duckdb';
```

Query of `first_view` works:
```
SELECT * FROM demo.main.first_view;
```

Query of `second_view` **fails**:
```
SELECT * FROM demo.main.second_view;
``` 

Changing the namespace makes the second query work:
```
USE demo.main;
```



### OS:

Windows 11 x64

### DuckDB Version:

1.0.0

### DuckDB Client:

duckdb cli

### Full Name:

Jannick Bitsch

### Affiliation:

Danske Commodities A/S

### What is the latest build you tested with? If possible, we recommend testing with the latest nightly build.

I have tested with a stable release

### 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
Namespace resolution for VIEW does not work for views from ATTACH-ed database
### What happens?

Persistent `.duckdb` database exist, in which two views are defined:
- `first_view` is defined as a select over a  `read_parquet(...)` function
- `second_view` is defined as a view on top of `first_view`

Both these views can be queried when the `.duckdb` database is opened with the CLI tool.

After using `ATTACH` from another database (eg :memory) attempts to query `second_view` fails with:
```
CatalogException
This cell raised an exception: CatalogException('Catalog Error: Table with name first_view does not exist! Did you mean "demo.first_view"?')
``` 

Details:
```
Traceback (most recent call last):
  File "C:\Users\jbi\Repos\marimo\bugreport\.venv\Lib\site-packages\marimo\_runtime\executor.py", line 170, in execute_cell
    exec(cell.body, glbls)
  Cell marimo://C:\Users\jbi\Repos\marimo\bugreport\notebooks\Myname.py#cell=cell-3, line 1, in <module>
    _df = mo.sql(
          ^^^^^^^
  File "C:\Users\jbi\Repos\marimo\bugreport\.venv\Lib\site-packages\marimo\_sql\sql.py", line 40, in sql
    relation = duckdb.sql(query=query)
               ^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\jbi\Repos\marimo\bugreport\.venv\Lib\site-packages\duckdb\__init__.py", line 457, in sql
    return conn.sql(query, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
duckdb.duckdb.CatalogException: Catalog Error: Table with name first_view does not exist!
Did you mean "demo.first_view"?
``` 


### To Reproduce


DuckDB CLI is first used to create a .parquet file. Start CLI in memory mode:
```shell
duckdb
``` 

Execute statements:
```
CREATE TABLE mytable (id INTEGER PRIMARY KEY, j VARCHAR);
INSERT INTO mytable VALUES (1, 'a');
INSERT INTO mytable VALUES (b, '2');

COPY
  (SELECT * FROM mytable)
  TO 'demo.parquet'
  (FORMAT 'parquet');
``` 

Exit CLI tool. 

Next step is to create the persistent .duckdb database. Open/create DB:
```shell
duckdb demo.duckdb
``` 

Execute statements to create view. Note that full-path to demo.parquet file is required. 
```
CREATE VIEW first_view AS SELECT id, j FROM read_parquet('C:\Temp\demo.parquet');
CREATE VIEW second_view AS SELECT * FROM first_view;
``` 

Edit by @szarnyasg – Bash one-liner:

```bash
duckdb demo.duckdb -c "CREATE VIEW first_view AS SELECT id, j FROM read_parquet('`pwd`/demo.parquet'); CREATE VIEW second_view AS SELECT * FROM first_view;"
```

These views can both be queried as one would expect. 

Exit CLI tool, and open new a transient duck-db database:
```shell
duckdb
```

Attach the .duckdb database
```
ATTACH 'demo.duckdb';
```

Query of `first_view` works:
```
SELECT * FROM demo.main.first_view;
```

Query of `second_view` **fails**:
```
SELECT * FROM demo.main.second_view;
``` 

Changing the namespace makes the second query work:
```
USE demo.main;
```



### OS:

Windows 11 x64

### DuckDB Version:

1.0.0

### DuckDB Client:

duckdb cli

### Full Name:

Jannick Bitsch

### Affiliation:

Danske Commodities A/S

### What is the latest build you tested with? If possible, we recommend testing with the latest nightly build.

I have tested with a stable release

### 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:
@jbit84 thanks, I could reproduce the issue!
@jbit84 thanks, I could reproduce the issue!

Fix patch:
diff --git a/Makefile b/Makefile
index e1c4d723eb22..f621e71b3cfb 100644
--- a/Makefile
+++ b/Makefile
@@ -231,6 +231,9 @@ endif
 ifeq (${DISABLE_EXTENSION_LOAD}, 1)
 	CMAKE_VARS:=${CMAKE_VARS} -DDISABLE_EXTENSION_LOAD=1
 endif
+ifeq (${DISABLE_SHELL}, 1)
+	CMAKE_VARS:=${CMAKE_VARS} -DBUILD_SHELL=0
+endif
 CMAKE_VARS:=${CMAKE_VARS} -DLOCAL_EXTENSION_REPO="${LOCAL_EXTENSION_REPO}"
 ifneq (${OSX_BUILD_ARCH}, )
 	CMAKE_VARS:=${CMAKE_VARS} -DOSX_BUILD_ARCH=${OSX_BUILD_ARCH}
diff --git a/src/catalog/catalog.cpp b/src/catalog/catalog.cpp
index 08a01d93d593..cd232c6d11ac 100644
--- a/src/catalog/catalog.cpp
+++ b/src/catalog/catalog.cpp
@@ -71,7 +71,12 @@ Catalog &Catalog::GetSystemCatalog(ClientContext &context) {
 	return Catalog::GetSystemCatalog(*context.db);
 }
 
-optional_ptr<Catalog> Catalog::GetCatalogEntry(ClientContext &context, const string &catalog_name) {
+const string &GetDefaultCatalog(CatalogEntryRetriever &retriever) {
+	return DatabaseManager::GetDefaultDatabase(retriever.GetContext());
+}
+
+optional_ptr<Catalog> Catalog::GetCatalogEntry(CatalogEntryRetriever &retriever, const string &catalog_name) {
+	auto &context = retriever.GetContext();
 	auto &db_manager = DatabaseManager::Get(context);
 	if (catalog_name == TEMP_CATALOG) {
 		return &ClientData::Get(context).temporary_objects->GetCatalog();
@@ -79,22 +84,32 @@ optional_ptr<Catalog> Catalog::GetCatalogEntry(ClientContext &context, const str
 	if (catalog_name == SYSTEM_CATALOG) {
 		return &GetSystemCatalog(context);
 	}
-	auto entry = db_manager.GetDatabase(
-	    context, IsInvalidCatalog(catalog_name) ? DatabaseManager::GetDefaultDatabase(context) : catalog_name);
+	auto entry =
+	    db_manager.GetDatabase(context, IsInvalidCatalog(catalog_name) ? GetDefaultCatalog(retriever) : catalog_name);
 	if (!entry) {
 		return nullptr;
 	}
 	return &entry->GetCatalog();
 }
 
-Catalog &Catalog::GetCatalog(ClientContext &context, const string &catalog_name) {
-	auto catalog = Catalog::GetCatalogEntry(context, catalog_name);
+optional_ptr<Catalog> Catalog::GetCatalogEntry(ClientContext &context, const string &catalog_name) {
+	CatalogEntryRetriever entry_retriever(context);
+	return GetCatalogEntry(entry_retriever, catalog_name);
+}
+
+Catalog &Catalog::GetCatalog(CatalogEntryRetriever &retriever, const string &catalog_name) {
+	auto catalog = Catalog::GetCatalogEntry(retriever, catalog_name);
 	if (!catalog) {
 		throw BinderException("Catalog \"%s\" does not exist!", catalog_name);
 	}
 	return *catalog;
 }
 
+Catalog &Catalog::GetCatalog(ClientContext &context, const string &catalog_name) {
+	CatalogEntryRetriever entry_retriever(context);
+	return GetCatalog(entry_retriever, catalog_name);
+}
+
 //===--------------------------------------------------------------------===//
 // Schema
 //===--------------------------------------------------------------------===//
@@ -321,8 +336,8 @@ void Catalog::DropEntry(ClientContext &context, DropInfo &info) {
 		return;
 	}
 
-	auto lookup = LookupEntry(context, info.type, info.schema, info.name, info.if_not_found);
-
+	CatalogEntryRetriever retriever(context);
+	auto lookup = LookupEntry(retriever, info.type, info.schema, info.name, info.if_not_found);
 	if (!lookup.Found()) {
 		return;
 	}
@@ -371,9 +386,11 @@ SimilarCatalogEntry Catalog::SimilarEntryInSchemas(ClientContext &context, const
 	return result;
 }
 
-vector<CatalogSearchEntry> GetCatalogEntries(ClientContext &context, const string &catalog, const string &schema) {
+vector<CatalogSearchEntry> GetCatalogEntries(CatalogEntryRetriever &retriever, const string &catalog,
+                                             const string &schema) {
+	auto &context = retriever.GetContext();
 	vector<CatalogSearchEntry> entries;
-	auto &search_path = *context.client_data->catalog_search_path;
+	auto &search_path = retriever.GetSearchPath();
 	if (IsInvalidCatalog(catalog) && IsInvalidSchema(schema)) {
 		// no catalog or schema provided - scan the entire search path
 		entries = search_path.Get();
@@ -400,11 +417,11 @@ vector<CatalogSearchEntry> GetCatalogEntries(ClientContext &context, const strin
 	return entries;
 }
 
-void FindMinimalQualification(ClientContext &context, const string &catalog_name, const string &schema_name,
+void FindMinimalQualification(CatalogEntryRetriever &retriever, const string &catalog_name, const string &schema_name,
                               bool &qualify_database, bool &qualify_schema) {
 	// check if we can we qualify ONLY the schema
 	bool found = false;
-	auto entries = GetCatalogEntries(context, INVALID_CATALOG, schema_name);
+	auto entries = GetCatalogEntries(retriever, INVALID_CATALOG, schema_name);
 	for (auto &entry : entries) {
 		if (entry.catalog == catalog_name && entry.schema == schema_name) {
 			found = true;
@@ -418,7 +435,7 @@ void FindMinimalQualification(ClientContext &context, const string &catalog_name
 	}
 	// check if we can qualify ONLY the catalog
 	found = false;
-	entries = GetCatalogEntries(context, catalog_name, INVALID_SCHEMA);
+	entries = GetCatalogEntries(retriever, catalog_name, INVALID_SCHEMA);
 	for (auto &entry : entries) {
 		if (entry.catalog == catalog_name && entry.schema == schema_name) {
 			found = true;
@@ -553,10 +570,11 @@ CatalogException Catalog::UnrecognizedConfigurationError(ClientContext &context,
 	throw CatalogException::MissingEntry("configuration parameter", name, potential_names);
 }
 
-CatalogException Catalog::CreateMissingEntryException(ClientContext &context, const string &entry_name,
+CatalogException Catalog::CreateMissingEntryException(CatalogEntryRetriever &retriever, const string &entry_name,
                                                       CatalogType type,
                                                       const reference_set_t<SchemaCatalogEntry> &schemas,
                                                       QueryErrorContext error_context) {
+	auto &context = retriever.GetContext();
 	auto entry = SimilarEntryInSchemas(context, entry_name, type, schemas);
 
 	reference_set_t<SchemaCatalogEntry> unseen_schemas;
@@ -646,7 +664,7 @@ CatalogException Catalog::CreateMissingEntryException(ClientContext &context, co
 		auto schema_name = unseen_entry.schema->name;
 		bool qualify_database;
 		bool qualify_schema;
-		FindMinimalQualification(context, catalog_name, schema_name, qualify_database, qualify_schema);
+		FindMinimalQualification(retriever, catalog_name, schema_name, qualify_database, qualify_schema);
 		did_you_mean = unseen_entry.GetQualifiedName(qualify_database, qualify_schema);
 	} else if (entry.Found()) {
 		did_you_mean = entry.name;
@@ -667,13 +685,14 @@ CatalogEntryLookup Catalog::TryLookupEntryInternal(CatalogTransaction transactio
 	return {schema_entry, entry, ErrorData()};
 }
 
-CatalogEntryLookup Catalog::TryLookupEntry(ClientContext &context, CatalogType type, const string &schema,
+CatalogEntryLookup Catalog::TryLookupEntry(CatalogEntryRetriever &retriever, CatalogType type, const string &schema,
                                            const string &name, OnEntryNotFound if_not_found,
                                            QueryErrorContext error_context) {
+	auto &context = retriever.GetContext();
 	reference_set_t<SchemaCatalogEntry> schemas;
 	if (IsInvalidSchema(schema)) {
 		// try all schemas for this catalog
-		auto entries = GetCatalogEntries(context, GetName(), INVALID_SCHEMA);
+		auto entries = GetCatalogEntries(retriever, GetName(), INVALID_SCHEMA);
 		for (auto &entry : entries) {
 			auto &candidate_schema = entry.schema;
 			auto transaction = GetCatalogTransaction(context);
@@ -699,15 +718,15 @@ CatalogEntryLookup Catalog::TryLookupEntry(ClientContext &context, CatalogType t
 	if (if_not_found == OnEntryNotFound::RETURN_NULL) {
 		return {nullptr, nullptr, ErrorData()};
 	} else {
-		auto except = CreateMissingEntryException(context, name, type, schemas, error_context);
+		auto except = CreateMissingEntryException(retriever, name, type, schemas, error_context);
 		return {nullptr, nullptr, ErrorData(except)};
 	}
 }
 
-CatalogEntryLookup Catalog::LookupEntry(ClientContext &context, CatalogType type, const string &schema,
+CatalogEntryLookup Catalog::LookupEntry(CatalogEntryRetriever &retriever, CatalogType type, const string &schema,
                                         const string &name, OnEntryNotFound if_not_found,
                                         QueryErrorContext error_context) {
-	auto res = TryLookupEntry(context, type, schema, name, if_not_found, error_context);
+	auto res = TryLookupEntry(retriever, type, schema, name, if_not_found, error_context);
 
 	if (res.error.HasError()) {
 		res.error.Throw();
@@ -716,9 +735,10 @@ CatalogEntryLookup Catalog::LookupEntry(ClientContext &context, CatalogType type
 	return res;
 }
 
-CatalogEntryLookup Catalog::TryLookupEntry(ClientContext &context, vector<CatalogLookup> &lookups, CatalogType type,
-                                           const string &name, OnEntryNotFound if_not_found,
+CatalogEntryLookup Catalog::TryLookupEntry(CatalogEntryRetriever &retriever, vector<CatalogLookup> &lookups,
+                                           CatalogType type, const string &name, OnEntryNotFound if_not_found,
                                            QueryErrorContext error_context) {
+	auto &context = retriever.GetContext();
 	reference_set_t<SchemaCatalogEntry> schemas;
 	for (auto &lookup : lookups) {
 		auto transaction = lookup.catalog.GetCatalogTransaction(context);
@@ -734,24 +754,24 @@ CatalogEntryLookup Catalog::TryLookupEntry(ClientContext &context, vector<Catalo
 	if (if_not_found == OnEntryNotFound::RETURN_NULL) {
 		return {nullptr, nullptr, ErrorData()};
 	} else {
-		auto except = CreateMissingEntryException(context, name, type, schemas, error_context);
+		auto except = CreateMissingEntryException(retriever, name, type, schemas, error_context);
 		return {nullptr, nullptr, ErrorData(except)};
 	}
 }
 
-CatalogEntryLookup Catalog::TryLookupEntry(ClientContext &context, CatalogType type, const string &catalog,
+CatalogEntryLookup Catalog::TryLookupEntry(CatalogEntryRetriever &retriever, CatalogType type, const string &catalog,
                                            const string &schema, const string &name, OnEntryNotFound if_not_found,
                                            QueryErrorContext error_context) {
-	auto entries = GetCatalogEntries(context, catalog, schema);
+	auto entries = GetCatalogEntries(retriever, catalog, schema);
 	vector<CatalogLookup> lookups;
 	vector<CatalogLookup> final_lookups;
 	lookups.reserve(entries.size());
 	for (auto &entry : entries) {
 		optional_ptr<Catalog> catalog_entry;
 		if (if_not_found == OnEntryNotFound::RETURN_NULL) {
-			catalog_entry = Catalog::GetCatalogEntry(context, entry.catalog);
+			catalog_entry = Catalog::GetCatalogEntry(retriever, entry.catalog);
 		} else {
-			catalog_entry = &Catalog::GetCatalog(context, entry.catalog);
+			catalog_entry = &Catalog::GetCatalog(retriever, entry.catalog);
 		}
 		if (!catalog_entry) {
 			return {nullptr, nullptr, ErrorData()};
@@ -767,18 +787,18 @@ CatalogEntryLookup Catalog::TryLookupEntry(ClientContext &context, CatalogType t
 	for (auto &lookup : final_lookups) {
 		lookups.emplace_back(std::move(lookup));
 	}
-	return Catalog::TryLookupEntry(context, lookups, type, name, if_not_found, error_context);
+	return Catalog::TryLookupEntry(retriever, lookups, type, name, if_not_found, error_context);
 }
 
-optional_ptr<CatalogEntry> Catalog::GetEntry(ClientContext &context, CatalogType type, const string &schema_name,
-                                             const string &name, OnEntryNotFound if_not_found,
-                                             QueryErrorContext error_context) {
-	auto lookup_entry = TryLookupEntry(context, type, schema_name, name, if_not_found, error_context);
+optional_ptr<CatalogEntry> Catalog::GetEntry(CatalogEntryRetriever &retriever, CatalogType type,
+                                             const string &schema_name, const string &name,
+                                             OnEntryNotFound if_not_found, QueryErrorContext error_context) {
+	auto lookup_entry = TryLookupEntry(retriever, type, schema_name, name, if_not_found, error_context);
 
 	// Try autoloading extension to resolve lookup
 	if (!lookup_entry.Found()) {
-		if (AutoLoadExtensionByCatalogEntry(*context.db, type, name)) {
-			lookup_entry = TryLookupEntry(context, type, schema_name, name, if_not_found, error_context);
+		if (AutoLoadExtensionByCatalogEntry(*retriever.GetContext().db, type, name)) {
+			lookup_entry = TryLookupEntry(retriever, type, schema_name, name, if_not_found, error_context);
 		}
 	}
 
@@ -789,20 +809,27 @@ optional_ptr<CatalogEntry> Catalog::GetEntry(ClientContext &context, CatalogType
 	return lookup_entry.entry.get();
 }
 
+optional_ptr<CatalogEntry> Catalog::GetEntry(ClientContext &context, CatalogType type, const string &schema_name,
+                                             const string &name, OnEntryNotFound if_not_found,
+                                             QueryErrorContext error_context) {
+	CatalogEntryRetriever retriever(context);
+	return GetEntry(retriever, type, schema_name, name, if_not_found, error_context);
+}
+
 CatalogEntry &Catalog::GetEntry(ClientContext &context, CatalogType type, const string &schema, const string &name,
                                 QueryErrorContext error_context) {
 	return *Catalog::GetEntry(context, type, schema, name, OnEntryNotFound::THROW_EXCEPTION, error_context);
 }
 
-optional_ptr<CatalogEntry> Catalog::GetEntry(ClientContext &context, CatalogType type, const string &catalog,
+optional_ptr<CatalogEntry> Catalog::GetEntry(CatalogEntryRetriever &retriever, CatalogType type, const string &catalog,
                                              const string &schema, const string &name, OnEntryNotFound if_not_found,
                                              QueryErrorContext error_context) {
-	auto result = TryLookupEntry(context, type, catalog, schema, name, if_not_found, error_context);
+	auto result = TryLookupEntry(retriever, type, catalog, schema, name, if_not_found, error_context);
 
 	// Try autoloading extension to resolve lookup
 	if (!result.Found()) {
-		if (AutoLoadExtensionByCatalogEntry(*context.db, type, name)) {
-			result = TryLookupEntry(context, type, catalog, schema, name, if_not_found, error_context);
+		if (AutoLoadExtensionByCatalogEntry(*retriever.GetContext().db, type, name)) {
+			result = TryLookupEntry(retriever, type, catalog, schema, name, if_not_found, error_context);
 		}
 	}
 
@@ -816,20 +843,26 @@ optional_ptr<CatalogEntry> Catalog::GetEntry(ClientContext &context, CatalogType
 	}
 	return result.entry.get();
 }
+optional_ptr<CatalogEntry> Catalog::GetEntry(ClientContext &context, CatalogType type, const string &catalog,
+                                             const string &schema, const string &name, OnEntryNotFound if_not_found,
+                                             QueryErrorContext error_context) {
+	CatalogEntryRetriever retriever(context);
+	return GetEntry(retriever, type, catalog, schema, name, if_not_found, error_context);
+}
 
 CatalogEntry &Catalog::GetEntry(ClientContext &context, CatalogType type, const string &catalog, const string &schema,
                                 const string &name, QueryErrorContext error_context) {
 	return *Catalog::GetEntry(context, type, catalog, schema, name, OnEntryNotFound::THROW_EXCEPTION, error_context);
 }
 
-optional_ptr<SchemaCatalogEntry> Catalog::GetSchema(ClientContext &context, const string &catalog_name,
+optional_ptr<SchemaCatalogEntry> Catalog::GetSchema(CatalogEntryRetriever &retriever, const string &catalog_name,
                                                     const string &schema_name, OnEntryNotFound if_not_found,
                                                     QueryErrorContext error_context) {
-	auto entries = GetCatalogEntries(context, catalog_name, schema_name);
+	auto entries = GetCatalogEntries(retriever, catalog_name, schema_name);
 	for (idx_t i = 0; i < entries.size(); i++) {
 		auto on_not_found = i + 1 == entries.size() ? if_not_found : OnEntryNotFound::RETURN_NULL;
-		auto &catalog = Catalog::GetCatalog(context, entries[i].catalog);
-		auto result = catalog.GetSchema(context, schema_name, on_not_found, error_context);
+		auto &catalog = Catalog::GetCatalog(retriever, entries[i].catalog);
+		auto result = catalog.GetSchema(retriever.GetContext(), schema_name, on_not_found, error_context);
 		if (result) {
 			return result;
 		}
@@ -837,20 +870,28 @@ optional_ptr<SchemaCatalogEntry> Catalog::GetSchema(ClientContext &context, cons
 	return nullptr;
 }
 
+optional_ptr<SchemaCatalogEntry> Catalog::GetSchema(ClientContext &context, const string &catalog_name,
+                                                    const string &schema_name, OnEntryNotFound if_not_found,
+                                                    QueryErrorContext error_context) {
+	CatalogEntryRetriever retriever(context);
+	return GetSchema(retriever, catalog_name, schema_name, if_not_found, error_context);
+}
+
 vector<reference<SchemaCatalogEntry>> Catalog::GetSchemas(ClientContext &context) {
 	vector<reference<SchemaCatalogEntry>> schemas;
 	ScanSchemas(context, [&](SchemaCatalogEntry &entry) { schemas.push_back(entry); });
 	return schemas;
 }
 
-vector<reference<SchemaCatalogEntry>> Catalog::GetSchemas(ClientContext &context, const string &catalog_name) {
+vector<reference<SchemaCatalogEntry>> Catalog::GetSchemas(CatalogEntryRetriever &retriever,
+                                                          const string &catalog_name) {
 	vector<reference<Catalog>> catalogs;
 	if (IsInvalidCatalog(catalog_name)) {
 		reference_set_t<Catalog> inserted_catalogs;
 
-		auto &search_path = *context.client_data->catalog_search_path;
+		auto &search_path = retriever.GetSearchPath();
 		for (auto &entry : search_path.Get()) {
-			auto &catalog = Catalog::GetCatalog(context, entry.catalog);
+			auto &catalog = Catalog::GetCatalog(retriever, entry.catalog);
 			if (inserted_catalogs.find(catalog) != inserted_catalogs.end()) {
 				continue;
 			}
@@ -858,16 +899,21 @@ vector<reference<SchemaCatalogEntry>> Catalog::GetSchemas(ClientContext &context
 			catalogs.push_back(catalog);
 		}
 	} else {
-		catalogs.push_back(Catalog::GetCatalog(context, catalog_name));
+		catalogs.push_back(Catalog::GetCatalog(retriever, catalog_name));
 	}
 	vector<reference<SchemaCatalogEntry>> result;
 	for (auto catalog : catalogs) {
-		auto schemas = catalog.get().GetSchemas(context);
+		auto schemas = catalog.get().GetSchemas(retriever.GetContext());
 		result.insert(result.end(), schemas.begin(), schemas.end());
 	}
 	return result;
 }
 
+vector<reference<SchemaCatalogEntry>> Catalog::GetSchemas(ClientContext &context, const string &catalog_name) {
+	CatalogEntryRetriever retriever(context);
+	return GetSchemas(retriever, catalog_name);
+}
+
 vector<reference<SchemaCatalogEntry>> Catalog::GetAllSchemas(ClientContext &context) {
 	vector<reference<SchemaCatalogEntry>> result;
 
@@ -896,8 +942,8 @@ vector<reference<SchemaCatalogEntry>> Catalog::GetAllSchemas(ClientContext &cont
 
 void Catalog::Alter(CatalogTransaction transaction, AlterInfo &info) {
 	if (transaction.HasContext()) {
-		auto lookup =
-		    LookupEntry(transaction.GetContext(), info.GetCatalogType(), info.schema, info.name, info.if_not_found);
+		CatalogEntryRetriever retriever(transaction.GetContext());
+		auto lookup = LookupEntry(retriever, info.GetCatalogType(), info.schema, info.name, info.if_not_found);
 		if (!lookup.Found()) {
 			return;
 		}
diff --git a/src/catalog/catalog_entry_retriever.cpp b/src/catalog/catalog_entry_retriever.cpp
index db0d0d423984..c37562d72144 100644
--- a/src/catalog/catalog_entry_retriever.cpp
+++ b/src/catalog/catalog_entry_retriever.cpp
@@ -7,6 +7,7 @@
 #include "duckdb/common/enums/catalog_type.hpp"
 #include "duckdb/common/optional_ptr.hpp"
 #include "duckdb/catalog/catalog_entry/type_catalog_entry.hpp"
+#include "duckdb/main/client_data.hpp"
 
 namespace duckdb {
 
@@ -36,14 +37,13 @@ optional_ptr<CatalogEntry> CatalogEntryRetriever::GetEntry(CatalogType type, con
                                                            const string &schema, const string &name,
                                                            OnEntryNotFound on_entry_not_found,
                                                            QueryErrorContext error_context) {
-	return GetEntryInternal(
-	    [&]() { return Catalog::GetEntry(context, type, catalog, schema, name, on_entry_not_found, error_context); });
+	return ReturnAndCallback(Catalog::GetEntry(*this, type, catalog, schema, name, on_entry_not_found, error_context));
 }
 
 optional_ptr<SchemaCatalogEntry> CatalogEntryRetriever::GetSchema(const string &catalog, const string &name,
                                                                   OnEntryNotFound on_entry_not_found,
                                                                   QueryErrorContext error_context) {
-	auto result = Catalog::GetSchema(context, catalog, name, on_entry_not_found, error_context);
+	auto result = Catalog::GetSchema(*this, catalog, name, on_entry_not_found, error_context);
 	if (!result) {
 		return result;
 	}
@@ -57,8 +57,63 @@ optional_ptr<SchemaCatalogEntry> CatalogEntryRetriever::GetSchema(const string &
 optional_ptr<CatalogEntry> CatalogEntryRetriever::GetEntry(CatalogType type, Catalog &catalog, const string &schema,
                                                            const string &name, OnEntryNotFound on_entry_not_found,
                                                            QueryErrorContext error_context) {
-	return GetEntryInternal(
-	    [&]() { return catalog.GetEntry(context, type, schema, name, on_entry_not_found, error_context); });
+	return ReturnAndCallback(catalog.GetEntry(*this, type, schema, name, on_entry_not_found, error_context));
+}
+
+optional_ptr<CatalogEntry> CatalogEntryRetriever::ReturnAndCallback(optional_ptr<CatalogEntry> result) {
+	if (!result) {
+		return result;
+	}
+	if (callback) {
+		// Call the callback if it's set
+		callback(*result);
+	}
+	return result;
+}
+
+void CatalogEntryRetriever::Inherit(const CatalogEntryRetriever &parent) {
+	this->callback = parent.callback;
+	this->search_path = parent.search_path;
+}
+
+CatalogSearchPath &CatalogEntryRetriever::GetSearchPath() {
+	if (search_path) {
+		return *search_path;
+	}
+	return *ClientData::Get(context).catalog_search_path;
+}
+
+void CatalogEntryRetriever::SetSearchPath(vector<CatalogSearchEntry> entries) {
+	vector<CatalogSearchEntry> new_path;
+	for (auto &entry : entries) {
+		if (IsInvalidCatalog(entry.catalog) || entry.catalog == SYSTEM_CATALOG || entry.catalog == TEMP_CATALOG) {
+			continue;
+		}
+		new_path.push_back(std::move(entry));
+	}
+	if (new_path.empty()) {
+		return;
+	}
+
+	// push the set paths from the ClientContext behind the provided paths
+	auto &client_search_path = *ClientData::Get(context).catalog_search_path;
+	auto &set_paths = client_search_path.GetSetPaths();
+	for (auto path : set_paths) {
+		if (IsInvalidCatalog(path.catalog)) {
+			path.catalog = DatabaseManager::GetDefaultDatabase(context);
+		}
+		new_path.push_back(std::move(path));
+	}
+
+	this->search_path = make_shared_ptr<CatalogSearchPath>(context, std::move(new_path));
+}
+
+void CatalogEntryRetriever::SetCallback(catalog_entry_callback_t callback) {
+	this->callback = std::move(callback);
+}
+
+catalog_entry_callback_t CatalogEntryRetriever::GetCallback() {
+	return callback;
 }
 
 } // namespace duckdb
diff --git a/src/catalog/catalog_search_path.cpp b/src/catalog/catalog_search_path.cpp
index 9368e300f4ad..fe314dde7478 100644
--- a/src/catalog/catalog_search_path.cpp
+++ b/src/catalog/catalog_search_path.cpp
@@ -123,13 +123,17 @@ vector<CatalogSearchEntry> CatalogSearchEntry::ParseList(const string &input) {
 	return result;
 }
 
-CatalogSearchPath::CatalogSearchPath(ClientContext &context_p) : context(context_p) {
-	Reset();
+CatalogSearchPath::CatalogSearchPath(ClientContext &context_p, vector<CatalogSearchEntry> entries)
+    : context(context_p) {
+	SetPathsInternal(std::move(entries));
+}
+
+CatalogSearchPath::CatalogSearchPath(ClientContext &context_p) : CatalogSearchPath(context_p, {}) {
 }
 
 void CatalogSearchPath::Reset() {
 	vector<CatalogSearchEntry> empty;
-	SetPaths(empty);
+	SetPathsInternal(empty);
 }
 
 string CatalogSearchPath::GetSetName(CatalogSetPathType set_type) {
@@ -176,8 +180,7 @@ void CatalogSearchPath::Set(vector<CatalogSearchEntry> new_paths, CatalogSetPath
 			                       new_paths[0].catalog);
 		}
 	}
-	this->set_paths = std::move(new_paths);
-	SetPaths(set_paths);
+	SetPathsInternal(std::move(new_paths));
 }
 
 void CatalogSearchPath::Set(CatalogSearchEntry new_value, CatalogSetPathType set_type) {
@@ -239,12 +242,14 @@ const CatalogSearchEntry &CatalogSearchPath::GetDefault() {
 	return paths[1];
 }
 
-void CatalogSearchPath::SetPaths(vector<CatalogSearchEntry> new_paths) {
+void CatalogSearchPath::SetPathsInternal(vector<CatalogSearchEntry> new_paths) {
+	this->set_paths = std::move(new_paths);
+
 	paths.clear();
-	paths.reserve(new_paths.size() + 3);
+	paths.reserve(set_paths.size() + 3);
 	paths.emplace_back(TEMP_CATALOG, DEFAULT_SCHEMA);
-	for (auto &path : new_paths) {
-		paths.push_back(std::move(path));
+	for (auto &path : set_paths) {
+		paths.push_back(path);
 	}
 	paths.emplace_back(INVALID_CATALOG, DEFAULT_SCHEMA);
 	paths.emplace_back(SYSTEM_CATALOG, DEFAULT_SCHEMA);
diff --git a/src/include/duckdb/catalog/catalog.hpp b/src/include/duckdb/catalog/catalog.hpp
index 278866020439..2bf7fad6e3c2 100644
--- a/src/include/duckdb/catalog/catalog.hpp
+++ b/src/include/duckdb/catalog/catalog.hpp
@@ -70,6 +70,7 @@ class LogicalInsert;
 class LogicalDelete;
 class LogicalUpdate;
 class CreateStatement;
+class CatalogEntryRetriever;
 
 //! The Catalog object represents the catalog of the database.
 class Catalog {
@@ -84,10 +85,15 @@ class Catalog {
 	DUCKDB_API static Catalog &GetSystemCatalog(DatabaseInstance &db);
 	//! Get the specified Catalog from the ClientContext
 	DUCKDB_API static Catalog &GetCatalog(ClientContext &context, const string &catalog_name);
+	//! Get the specified Catalog from the ClientContext
+	DUCKDB_API static Catalog &GetCatalog(CatalogEntryRetriever &retriever, const string &catalog_name);
 	//! Get the specified Catalog from the DatabaseInstance
 	DUCKDB_API static Catalog &GetCatalog(DatabaseInstance &db, const string &catalog_name);
 	//! Gets the specified Catalog from the database if it exists
 	DUCKDB_API static optional_ptr<Catalog> GetCatalogEntry(ClientContext &context, const string &catalog_name);
+	//! Gets the specified Catalog from the database if it exists
+	DUCKDB_API static optional_ptr<Catalog> GetCatalogEntry(CatalogEntryRetriever &retriever,
+	                                                        const string &catalog_name);
 	//! Get the specific Catalog from the AttachedDatabase
 	DUCKDB_API static Catalog &GetCatalog(AttachedDatabase &db);
 
@@ -214,6 +220,10 @@ class Catalog {
 	                                                             const string &schema_name,
 	                                                             OnEntryNotFound if_not_found,
 	                                                             QueryErrorContext error_context = QueryErrorContext());
+	DUCKDB_API static optional_ptr<SchemaCatalogEntry> GetSchema(CatalogEntryRetriever &retriever,
+	                                                             const string &catalog_name, const string &schema_name,
+	                                                             OnEntryNotFound if_not_found,
+	                                                             QueryErrorContext error_context = QueryErrorContext());
 	//! Scans all the schemas in the system one-by-one, invoking the callback for each entry
 	DUCKDB_API virtual void ScanSchemas(ClientContext &context, std::function<void(SchemaCatalogEntry &)> callback) = 0;
 
@@ -221,6 +231,10 @@ class Catalog {
 	DUCKDB_API optional_ptr<CatalogEntry> GetEntry(ClientContext &context, CatalogType type, const string &schema,
 	                                               const string &name, OnEntryNotFound if_not_found,
 	                                               QueryErrorContext error_context = QueryErrorContext());
+	DUCKDB_API optional_ptr<CatalogEntry> GetEntry(CatalogEntryRetriever &retriever, CatalogType type,
+	                                               const string &schema, const string &name,
+	                                               OnEntryNotFound if_not_found,
+	                                               QueryErrorContext error_context = QueryErrorContext());
 	DUCKDB_API CatalogEntry &GetEntry(ClientContext &context, CatalogType type, const string &schema,
 	                                  const string &name, QueryErrorContext error_context = QueryErrorContext());
 	//! Gets the "catalog.schema.name" entry of the specified type, if entry does not exist behavior depends on
@@ -229,6 +243,10 @@ class Catalog {
 	                                                      const string &catalog, const string &schema,
 	                                                      const string &name, OnEntryNotFound if_not_found,
 	                                                      QueryErrorContext error_context = QueryErrorContext());
+	DUCKDB_API static optional_ptr<CatalogEntry> GetEntry(CatalogEntryRetriever &retriever, CatalogType type,
+	                                                      const string &catalog, const string &schema,
+	                                                      const string &name, OnEntryNotFound if_not_found,
+	                                                      QueryErrorContext error_context = QueryErrorContext());
 	DUCKDB_API static CatalogEntry &GetEntry(ClientContext &context, CatalogType type, const string &catalog,
 	                                         const string &schema, const string &name,
 	                                         QueryErrorContext error_context = QueryErrorContext());
@@ -306,6 +324,8 @@ class Catalog {
 	DUCKDB_API vector<reference<SchemaCatalogEntry>> GetSchemas(ClientContext &context);
 	DUCKDB_API static vector<reference<SchemaCatalogEntry>> GetSchemas(ClientContext &context,
 	                                                                   const string &catalog_name);
+	DUCKDB_API static vector<reference<SchemaCatalogEntry>> GetSchemas(CatalogEntryRetriever &retriever,
+	                                                                   const string &catalog_name);
 	DUCKDB_API static vector<reference<SchemaCatalogEntry>> GetAllSchemas(ClientContext &context);
 
 	virtual void Verify();
@@ -329,21 +349,22 @@ class Catalog {
 	                                          const string &name);
 	//! Calls LookupEntryInternal on the schema, trying other schemas if the schema is invalid. Sets
 	//! CatalogEntryLookup->error depending on if_not_found when no entry is found
-	CatalogEntryLookup TryLookupEntry(ClientContext &context, CatalogType type, const string &schema,
+	CatalogEntryLookup TryLookupEntry(CatalogEntryRetriever &retriever, CatalogType type, const string &schema,
 	                                  const string &name, OnEntryNotFound if_not_found,
 	                                  QueryErrorContext error_context = QueryErrorContext());
 	//! Lookup an entry using TryLookupEntry, throws if entry not found and if_not_found == THROW_EXCEPTION
-	CatalogEntryLookup LookupEntry(ClientContext &context, CatalogType type, const string &schema, const string &name,
-	                               OnEntryNotFound if_not_found, QueryErrorContext error_context = QueryErrorContext());
-	static CatalogEntryLookup TryLookupEntry(ClientContext &context, vector<CatalogLookup> &lookups, CatalogType type,
-	                                         const string &name, OnEntryNotFound if_not_found,
+	CatalogEntryLookup LookupEntry(CatalogEntryRetriever &retriever, CatalogType type, const string &schema,
+	                               const string &name, OnEntryNotFound if_not_found,
+	                               QueryErrorContext error_context = QueryErrorContext());
+	static CatalogEntryLookup TryLookupEntry(CatalogEntryRetriever &retriever, vector<CatalogLookup> &lookups,
+	                                         CatalogType type, const string &name, OnEntryNotFound if_not_found,
 	                                         QueryErrorContext error_context = QueryErrorContext());
-	static CatalogEntryLookup TryLookupEntry(ClientContext &context, CatalogType type, const string &catalog,
+	static CatalogEntryLookup TryLookupEntry(CatalogEntryRetriever &retriever, CatalogType type, const string &catalog,
 	                                         const string &schema, const string &name, OnEntryNotFound if_not_found,
 	                                         QueryErrorContext error_context);
 
 	//! Return an exception with did-you-mean suggestion.
-	static CatalogException CreateMissingEntryException(ClientContext &context, const string &entry_name,
+	static CatalogException CreateMissingEntryException(CatalogEntryRetriever &retriever, const string &entry_name,
 	                                                    CatalogType type,
 	                                                    const reference_set_t<SchemaCatalogEntry> &schemas,
 	                                                    QueryErrorContext error_context);
diff --git a/src/include/duckdb/catalog/catalog_entry_retriever.hpp b/src/include/duckdb/catalog/catalog_entry_retriever.hpp
index 28b3874c4d7e..f51b63002350 100644
--- a/src/include/duckdb/catalog/catalog_entry_retriever.hpp
+++ b/src/include/duckdb/catalog/catalog_entry_retriever.hpp
@@ -1,3 +1,11 @@
+//===----------------------------------------------------------------------===//
+//                         DuckDB
+//
+// duckdb/catalog/catalog_entry_retriever.hpp
+//
+//
+//===----------------------------------------------------------------------===//
+
 #pragma once
 
 #include <functional>
@@ -6,6 +14,7 @@
 #include "duckdb/common/string.hpp"
 #include "duckdb/parser/query_error_context.hpp"
 #include "duckdb/catalog/catalog_entry/schema_catalog_entry.hpp"
+#include "duckdb/catalog/catalog_search_path.hpp"
 
 namespace duckdb {
 
@@ -24,6 +33,11 @@ class CatalogEntryRetriever {
 	}
 
 public:
+	void Inherit(const CatalogEntryRetriever &parent);
+	ClientContext &GetContext() {
+		return context;
+	}
+
 	optional_ptr<CatalogEntry> GetEntry(CatalogType type, const string &catalog, const string &schema,
 	                                    const string &name,
 	                                    OnEntryNotFound on_entry_not_found = OnEntryNotFound::THROW_EXCEPTION,
@@ -42,31 +56,20 @@ class CatalogEntryRetriever {
 	                                           OnEntryNotFound on_entry_not_found = OnEntryNotFound::THROW_EXCEPTION,
 	                                           QueryErrorContext error_context = QueryErrorContext());
 
-	void SetCallback(catalog_entry_callback_t callback) {
-		this->callback = std::move(callback);
-	}
-	catalog_entry_callback_t GetCallback() {
-		return callback;
-	}
+	CatalogSearchPath &GetSearchPath();
+	void SetSearchPath(vector<CatalogSearchEntry> entries);
+
+	void SetCallback(catalog_entry_callback_t callback);
+	catalog_entry_callback_t GetCallback();
 
 private:
-	using catalog_entry_retrieve_func_t = std::function<optional_ptr<CatalogEntry>()>;
-	optional_ptr<CatalogEntry> GetEntryInternal(const catalog_entry_retrieve_func_t &retriever) {
-		auto result = retriever();
-		if (!result) {
-			return result;
-		}
-		if (callback) {
-			// Call the callback if it's set
-			callback(*result);
-		}
-		return result;
-	}
+	optional_ptr<CatalogEntry> ReturnAndCallback(optional_ptr<CatalogEntry> result);
 
 private:
 	//! (optional) callback, called on every succesful entry retrieval
 	catalog_entry_callback_t callback = nullptr;
 	ClientContext &context;
+	shared_ptr<CatalogSearchPath> search_path;
 };
 
 } // namespace duckdb
diff --git a/src/include/duckdb/catalog/catalog_search_path.hpp b/src/include/duckdb/catalog/catalog_search_path.hpp
index 479d6c5298cf..ff0def0fcd30 100644
--- a/src/include/duckdb/catalog/catalog_search_path.hpp
+++ b/src/include/duckdb/catalog/catalog_search_path.hpp
@@ -41,6 +41,7 @@ enum class CatalogSetPathType { SET_SCHEMA, SET_SCHEMAS };
 class CatalogSearchPath {
 public:
 	DUCKDB_API explicit CatalogSearchPath(ClientContext &client_p);
+	DUCKDB_API CatalogSearchPath(ClientContext &client_p, vector<CatalogSearchEntry> entries);
 	CatalogSearchPath(const CatalogSearchPath &other) = delete;
 
 	DUCKDB_API void Set(CatalogSearchEntry new_value, CatalogSetPathType set_type);
@@ -61,8 +62,8 @@ class CatalogSearchPath {
 	DUCKDB_API bool SchemaInSearchPath(ClientContext &context, const string &catalog_name, const string &schema_name);
 
 private:
-	void SetPaths(vector<CatalogSearchEntry> new_paths);
-
+	//! Set paths without checking if they exist
+	void SetPathsInternal(vector<CatalogSearchEntry> new_paths);
 	string GetSetName(CatalogSetPathType set_type);
 
 private:
diff --git a/src/include/duckdb/main/database_manager.hpp b/src/include/duckdb/main/database_manager.hpp
index c06b5505aa78..2ac5c4ae5512 100644
--- a/src/include/duckdb/main/database_manager.hpp
+++ b/src/include/duckdb/main/database_manager.hpp
@@ -21,6 +21,7 @@
 namespace duckdb {
 class AttachedDatabase;
 class Catalog;
+class CatalogEntryRetriever;
 class CatalogSet;
 class ClientContext;
 class DatabaseInstance;
diff --git a/src/include/duckdb/parser/parsed_data/comment_on_column_info.hpp b/src/include/duckdb/parser/parsed_data/comment_on_column_info.hpp
index c274ffb5559b..60aa36ef01e0 100644
--- a/src/include/duckdb/parser/parsed_data/comment_on_column_info.hpp
+++ b/src/include/duckdb/parser/parsed_data/comment_on_column_info.hpp
@@ -12,9 +12,9 @@
 #include "duckdb/common/types/value.hpp"
 #include "duckdb/parser/parsed_data/alter_info.hpp"
 #include "duckdb/parser/qualified_name.hpp"
-#include "duckdb/catalog/catalog_entry_retriever.hpp"
 
 namespace duckdb {
+class CatalogEntryRetriever;
 class ClientContext;
 class CatalogEntry;
 
diff --git a/src/planner/binder.cpp b/src/planner/binder.cpp
index fa490303dcee..465ce2e220c2 100644
--- a/src/planner/binder.cpp
+++ b/src/planner/binder.cpp
@@ -59,7 +59,7 @@ Binder::Binder(ClientContext &context, shared_ptr<Binder> parent_p, BinderType b
     : context(context), bind_context(*this), parent(std::move(parent_p)), bound_tables(0), binder_type(binder_type),
       entry_retriever(context) {
 	if (parent) {
-		entry_retriever.SetCallback(parent->entry_retriever.GetCallback());
+		entry_retriever.Inherit(parent->entry_retriever);
 
 		// We have to inherit macro and lambda parameter bindings and from the parent binder, if there is a parent.
 		macro_binding = parent->macro_binding;
diff --git a/src/planner/binder/tableref/bind_basetableref.cpp b/src/planner/binder/tableref/bind_basetableref.cpp
index 40f2662d4500..a7ebe4335749 100644
--- a/src/planner/binder/tableref/bind_basetableref.cpp
+++ b/src/planner/binder/tableref/bind_basetableref.cpp
@@ -15,6 +15,7 @@
 #include "duckdb/planner/tableref/bound_cteref.hpp"
 #include "duckdb/planner/tableref/bound_dummytableref.hpp"
 #include "duckdb/planner/tableref/bound_subqueryref.hpp"
+#include "duckdb/catalog/catalog_search_path.hpp"
 
 namespace duckdb {
 
@@ -259,6 +260,16 @@ unique_ptr<BoundTableRef> Binder::Bind(BaseTableRef &ref) {
 			view_names.push_back(view_catalog_entry.names[n]);
 		}
 		subquery.column_name_alias = BindContext::AliasColumnNames(subquery.alias, view_names, ref.column_name_alias);
+
+		// when binding a view, we always look into the catalog/schema where the view is stored first
+		vector<CatalogSearchEntry> view_search_path;
+		auto &catalog_name = view_catalog_entry.ParentCatalog().GetName();
+		auto &schema_name = view_catalog_entry.ParentSchema().name;
+		view_search_path.emplace_back(catalog_name, schema_name);
+		if (schema_name != DEFAULT_SCHEMA) {
+			view_search_path.emplace_back(view_catalog_entry.ParentCatalog().GetName(), DEFAULT_SCHEMA);
+		}
+		view_binder->entry_retriever.SetSearchPath(std::move(view_search_path));
 		// bind the child subquery
 		view_binder->AddBoundView(view_catalog_entry);
 		auto bound_child = view_binder->Bind(subquery);


Test patch:
diff --git a/test/sql/attach/attach_view_search_path.test b/test/sql/attach/attach_view_search_path.test
new file mode 100644
index 000000000000..01a1f6eeddd8
--- /dev/null
+++ b/test/sql/attach/attach_view_search_path.test
@@ -0,0 +1,78 @@
+# name: test/sql/attach/attach_view_search_path.test
+# description: Test ATTACH with search path
+# group: [attach]
+
+# avoid loading a storage database because it changes the initial database name
+require noforcestorage
+
+statement ok
+PRAGMA enable_verification
+
+statement ok
+ATTACH DATABASE '__TEST_DIR__/view_search_path.db' AS view_search_path;
+
+statement ok
+USE view_search_path
+
+statement ok
+CREATE TABLE my_tbl(i INTEGER)
+
+statement ok
+INSERT INTO my_tbl VALUES (42)
+
+statement ok
+CREATE VIEW my_view AS FROM my_tbl
+
+query I
+FROM my_view
+----
+42
+
+statement ok
+CREATE SCHEMA my_schema
+
+statement ok
+USE my_schema
+
+statement ok
+CREATE TABLE my_tbl(i INTEGER)
+
+statement ok
+INSERT INTO my_tbl VALUES (84)
+
+statement ok
+CREATE VIEW my_view AS FROM my_tbl
+
+query I
+FROM my_view
+----
+84
+
+statement ok
+USE memory
+
+query I
+FROM view_search_path.my_view
+----
+42
+
+query I
+FROM view_search_path.my_schema.my_view
+----
+84
+
+statement ok
+DETACH view_search_path
+
+statement ok
+ATTACH DATABASE '__TEST_DIR__/view_search_path.db' AS view_search_path;
+
+query I
+FROM view_search_path.my_view
+----
+42
+
+query I
+FROM view_search_path.my_schema.my_view
+----
+84
diff --git a/test/sql/attach/attach_views.test b/test/sql/attach/attach_views.test
index 6311e9e702fb..c64c0fbf6505 100644
--- a/test/sql/attach/attach_views.test
+++ b/test/sql/attach/attach_views.test
@@ -2,7 +2,7 @@
 # description: Test views in an attached database
 # group: [attach]
 
-require skip_reload
+require noforcestorage
 
 statement ok
 PRAGMA enable_verification
@@ -46,7 +46,7 @@ SELECT * FROM ${prefix}.v1
 
 # reference tables from different databases in view
 statement ok
-CREATE OR REPLACE VIEW ${prefix}.v1 AS SELECT * FROM ${prefix}.t1 UNION ALL FROM t1 ORDER BY ALL
+CREATE OR REPLACE VIEW ${prefix}.v1 AS SELECT * FROM ${prefix}.t1 UNION ALL FROM memory.t1 ORDER BY ALL
 
 query I
 SELECT * FROM ${prefix}.v1