Commit fcef3d5d4c5 for woocommerce

commit fcef3d5d4c56628bc2e8160d80387489bda5d75e
Author: Daniel Mallory <daniel.mallory@automattic.com>
Date:   Mon Sep 7 15:43:17 2026 +0100

    Fix product list SQL errors when extensions join the lookup table (#68094)

    * fix: keep the wc_product_meta_lookup join when extensions join the table under another alias

    The guard deciding whether a products query already joins the
    wc_product_meta_lookup table checked with strstr(), so any join to the
    table satisfied it, including one an extension added under its own
    alias. WooCommerce then skipped its own join while its filters and
    sorts kept referencing the wc_product_meta_lookup alias, turning the
    admin Products list (stock, virtual and downloadable filters, every
    sortable column) into a hard SQL error. The same guard was duplicated
    in seven places, so front-end catalog sorting, the Product Collection
    block, the Store API and the analytics product endpoints could fail
    the same way.

    Replace the substring test with a word-boundary match on the alias and
    centralise the guard in ProductUtil, with the seven former copies
    delegating to it. The alias cannot match inside the prefixed table
    name because wpdb prefixes contain only word characters, and a clause
    that already defines the alias is still left alone, so an extension
    adding that same alias does not produce a duplicate-alias error.

    Refs #67886

    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

    * fix: preserve non-string product lookup join clauses

    * fix: normalize non-string product lookup joins

    ---------

    Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
    Co-authored-by: Oleksandr Aratovskyi <79862886+oaratovskyi@users.noreply.github.com>

diff --git a/plugins/woocommerce/changelog/fix-67886-product-meta-lookup-join-guard b/plugins/woocommerce/changelog/fix-67886-product-meta-lookup-join-guard
new file mode 100644
index 00000000000..d39ed7ed96c
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-67886-product-meta-lookup-join-guard
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix product list filters and sorting breaking when an extension joins the wc_product_meta_lookup table under its own alias.
diff --git a/plugins/woocommerce/includes/admin/list-tables/class-wc-admin-list-table-products.php b/plugins/woocommerce/includes/admin/list-tables/class-wc-admin-list-table-products.php
index 8b386efddd9..620f3f87dec 100644
--- a/plugins/woocommerce/includes/admin/list-tables/class-wc-admin-list-table-products.php
+++ b/plugins/woocommerce/includes/admin/list-tables/class-wc-admin-list-table-products.php
@@ -1142,20 +1142,7 @@ class WC_Admin_List_Table_Products extends WC_Admin_List_Table {
 	 * @return string
 	 */
 	private function append_product_sorting_table_join( $sql ) {
-		global $wpdb;
-
-		// Another posts_clauses callback produced this clause, so it is not guaranteed to be a string.
-		// Keep anything that can become one: discarding a join while the WHERE that depends on it
-		// survives would leave a broken query rather than a merely unfiltered one.
-		if ( ! is_string( $sql ) ) {
-			$stringable = is_scalar( $sql ) || ( is_object( $sql ) && method_exists( $sql, '__toString' ) );
-			$sql        = $stringable ? (string) $sql : '';
-		}
-
-		if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) {
-			$sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id ";
-		}
-		return $sql;
+		return wc_get_container()->get( ProductUtil::class )->append_product_sorting_table_join( $sql );
 	}

 	/**
diff --git a/plugins/woocommerce/includes/class-wc-query.php b/plugins/woocommerce/includes/class-wc-query.php
index d216b890313..caccc8d62f3 100644
--- a/plugins/woocommerce/includes/class-wc-query.php
+++ b/plugins/woocommerce/includes/class-wc-query.php
@@ -8,6 +8,7 @@

 use Automattic\WooCommerce\Internal\ProductAttributesLookup\Filterer;
 use Automattic\WooCommerce\Internal\ProductFilters\Params;
+use Automattic\WooCommerce\Internal\Utilities\ProductUtil;
 use Automattic\WooCommerce\Enums\ProductStockStatus;
 use Automattic\WooCommerce\Enums\TaxDisplayMode;

@@ -877,12 +878,7 @@ class WC_Query {
 	 * @return string
 	 */
 	private function append_product_sorting_table_join( $sql ) {
-		global $wpdb;
-
-		if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) {
-			$sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id ";
-		}
-		return $sql;
+		return wc_get_container()->get( ProductUtil::class )->append_product_sorting_table_join( $sql );
 	}

 	/**
diff --git a/plugins/woocommerce/src/Admin/API/Products.php b/plugins/woocommerce/src/Admin/API/Products.php
index 16d9e981128..3a1009af337 100644
--- a/plugins/woocommerce/src/Admin/API/Products.php
+++ b/plugins/woocommerce/src/Admin/API/Products.php
@@ -9,6 +9,8 @@ declare( strict_types = 1 );

 namespace Automattic\WooCommerce\Admin\API;

+use Automattic\WooCommerce\Internal\Utilities\ProductUtil;
+
 defined( 'ABSPATH' ) || exit;

 /**
@@ -305,12 +307,7 @@ class Products extends \WC_REST_Products_Controller {
 	 * @return string
 	 */
 	protected static function append_product_sorting_table_join( $sql ) {
-		global $wpdb;
-
-		if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) {
-			$sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id ";
-		}
-		return $sql;
+		return wc_get_container()->get( ProductUtil::class )->append_product_sorting_table_join( $sql );
 	}

 	/**
diff --git a/plugins/woocommerce/src/Admin/API/Reports/Stock/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Stock/Controller.php
index eb550aa16b8..0fd3fbec377 100644
--- a/plugins/woocommerce/src/Admin/API/Reports/Stock/Controller.php
+++ b/plugins/woocommerce/src/Admin/API/Reports/Stock/Controller.php
@@ -15,6 +15,7 @@ use Automattic\WooCommerce\Enums\ProductType;
 use WP_REST_Request;
 use WP_REST_Response;
 use Automattic\WooCommerce\Enums\ProductStockStatus;
+use Automattic\WooCommerce\Internal\Utilities\ProductUtil;

 /**
  * REST API Reports stock controller class.
@@ -220,12 +221,7 @@ class Controller extends GenericController implements ExportableInterface {
 	 * @return string
 	 */
 	protected static function append_product_sorting_table_join( $sql ) {
-		global $wpdb;
-
-		if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) {
-			$sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id ";
-		}
-		return $sql;
+		return wc_get_container()->get( ProductUtil::class )->append_product_sorting_table_join( $sql );
 	}

 	/**
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection/QueryBuilder.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection/QueryBuilder.php
index 3343bc8457c..03240cc519a 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection/QueryBuilder.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection/QueryBuilder.php
@@ -11,6 +11,7 @@ use WP_Query;
 use WC_Tax;
 use Automattic\WooCommerce\Enums\CatalogSortOrder;
 use Automattic\WooCommerce\Enums\ProductStockStatus;
+use Automattic\WooCommerce\Internal\Utilities\ProductUtil;
 use Automattic\WooCommerce\Enums\TaxDisplayMode;

 /**
@@ -1239,12 +1240,7 @@ class QueryBuilder {
 	 * @return string
 	 */
 	protected function append_product_sorting_table_join( $sql ) {
-		global $wpdb;
-
-		if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) {
-			$sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id ";
-		}
-		return $sql;
+		return wc_get_container()->get( ProductUtil::class )->append_product_sorting_table_join( $sql );
 	}

 	/**
diff --git a/plugins/woocommerce/src/Internal/ProductFilters/QueryClauses.php b/plugins/woocommerce/src/Internal/ProductFilters/QueryClauses.php
index f60c317413b..2c458ad33e6 100644
--- a/plugins/woocommerce/src/Internal/ProductFilters/QueryClauses.php
+++ b/plugins/woocommerce/src/Internal/ProductFilters/QueryClauses.php
@@ -11,6 +11,7 @@ use Automattic\WooCommerce\Enums\TaxDisplayMode;
 use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore;
 use Automattic\WooCommerce\Internal\ProductFilters\Interfaces\QueryClausesGenerator;
 use Automattic\WooCommerce\Internal\ProductFilters\Interfaces\MainQueryClausesGenerator;
+use Automattic\WooCommerce\Internal\Utilities\ProductUtil;
 use WC_Tax;
 use WC_Cache_Helper;

@@ -419,12 +420,7 @@ class QueryClauses implements QueryClausesGenerator, MainQueryClausesGenerator {
 	 * @return string
 	 */
 	private function append_product_sorting_table_join( string $sql ): string {
-		global $wpdb;
-
-		if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) {
-			$sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id ";
-		}
-		return $sql;
+		return wc_get_container()->get( ProductUtil::class )->append_product_sorting_table_join( $sql );
 	}

 	/**
diff --git a/plugins/woocommerce/src/Internal/Utilities/ProductUtil.php b/plugins/woocommerce/src/Internal/Utilities/ProductUtil.php
index e380ce8683e..e0f5547de88 100644
--- a/plugins/woocommerce/src/Internal/Utilities/ProductUtil.php
+++ b/plugins/woocommerce/src/Internal/Utilities/ProductUtil.php
@@ -144,6 +144,31 @@ class ProductUtil {
 		}
 	}

+	/**
+	 * Append the product meta lookup join unless the clause mentions wc_product_meta_lookup as a standalone token.
+	 * References, SQL literals and comments also count as mentions; this is not an SQL parser.
+	 *
+	 * @since 11.2.0
+	 *
+	 * @param mixed $join SQL JOIN clause supplied by query filters.
+	 * @return string
+	 */
+	public function append_product_sorting_table_join( $join ): string {
+		global $wpdb;
+
+		// Preserve stringable clauses: discarding a join can break a WHERE clause that still references it.
+		if ( ! is_string( $join ) ) {
+			$stringable = is_object( $join ) && method_exists( $join, '__toString' );
+			$join       = $stringable ? (string) $join : '';
+		}
+
+		// A non-empty wpdb prefix prevents a table-name match. Empty prefixes retain the old guard's limitation.
+		if ( ! preg_match( '/\bwc_product_meta_lookup\b/', $join ) ) {
+			$join .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id ";
+		}
+		return $join;
+	}
+
 	/**
 	 * Counts per-status number of products of a given post type.
 	 *
diff --git a/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php b/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php
index 450e2b2a721..4536d2404ad 100644
--- a/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php
+++ b/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php
@@ -624,11 +624,6 @@ class ProductQuery implements QueryClausesGenerator {
 	 * @return string
 	 */
 	protected function append_product_sorting_table_join( $sql ) {
-		global $wpdb;
-
-		if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) {
-			$sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id ";
-		}
-		return $sql;
+		return wc_get_container()->get( ProductUtil::class )->append_product_sorting_table_join( $sql );
 	}
 }
diff --git a/plugins/woocommerce/tests/php/includes/admin/list-tables/class-wc-admin-list-table-products-test.php b/plugins/woocommerce/tests/php/includes/admin/list-tables/class-wc-admin-list-table-products-test.php
index 8c43b655850..045cd1e7f90 100644
--- a/plugins/woocommerce/tests/php/includes/admin/list-tables/class-wc-admin-list-table-products-test.php
+++ b/plugins/woocommerce/tests/php/includes/admin/list-tables/class-wc-admin-list-table-products-test.php
@@ -898,6 +898,60 @@ class WC_Admin_List_Table_Products_Test extends WC_Unit_Test_Case {
 		);
 	}

+	/**
+	 * @testdox An extension join to the lookup table under another alias does not suppress WooCommerce's aliased join.
+	 */
+	public function test_stock_status_filter_joins_despite_a_foreign_alias_lookup_join(): void {
+		global $wpdb;
+
+		$args = $this->filter_clauses_for_stock_status(
+			ProductStockStatus::IN_STOCK,
+			array(
+				'join'  => " LEFT JOIN {$wpdb->wc_product_meta_lookup} extension_lookup ON {$wpdb->posts}.ID = extension_lookup.product_id ",
+				'where' => '',
+			)
+		);
+
+		$this->assertStringContainsString(
+			"LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup",
+			$args['join'],
+			'A join to the lookup table under another alias must not suppress the wc_product_meta_lookup alias the filter references.'
+		);
+	}
+
+	/**
+	 * @testdox In-stock filtering still lists products when an earlier callback joins the lookup table under its own alias.
+	 */
+	public function test_stock_status_filter_survives_an_extension_join_under_another_alias(): void {
+		global $wpdb;
+
+		update_option( 'woocommerce_manage_stock', 'no' );
+
+		$in_stock = WC_Helper_Product::create_simple_product();
+		$in_stock->set_manage_stock( false );
+		$in_stock->set_stock_status( ProductStockStatus::IN_STOCK );
+		$in_stock->save();
+
+		// The callback stands in for an extension that joins the lookup table under its own alias before WooCommerce's callback runs.
+		$extension_join = static function ( $clauses ) use ( $wpdb ) {
+			$clauses['join'] .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} extension_lookup ON {$wpdb->posts}.ID = extension_lookup.product_id ";
+			return $clauses;
+		};
+		add_filter( 'posts_clauses', $extension_join, 5 );
+
+		try {
+			$ids = $this->query_product_ids_for_stock_status( ProductStockStatus::IN_STOCK );
+		} finally {
+			remove_filter( 'posts_clauses', $extension_join, 5 );
+		}
+
+		$this->assertContains(
+			$in_stock->get_id(),
+			$ids,
+			'The in-stock filter should keep producing a valid query when an extension has already joined the lookup table under a different alias.'
+		);
+	}
+
 	/**
 	 * Stock statuses offered by the products list filter.
 	 *
diff --git a/plugins/woocommerce/tests/php/src/Internal/Utilities/ProductUtilTest.php b/plugins/woocommerce/tests/php/src/Internal/Utilities/ProductUtilTest.php
index d057c41188d..45c99ed58e1 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Utilities/ProductUtilTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Utilities/ProductUtilTest.php
@@ -5,9 +5,12 @@ declare(strict_types=1);
 namespace Automattic\WooCommerce\Tests\Internal\Utilities;

 use Automattic\WooCommerce\Enums\ProductStatus;
+use Automattic\WooCommerce\Enums\ProductStockStatus;
+use Automattic\WooCommerce\Admin\API\Reports\Stock\Controller as StockController;
 use Automattic\WooCommerce\Caches\ProductCountCache;
 use Automattic\WooCommerce\Internal\Utilities\ProductUtil;
 use Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper;
+use Automattic\WooCommerce\StoreApi\Utilities\ProductQuery;

 /**
  * Tests for the internal ProductUtil class.
@@ -168,4 +171,172 @@ class ProductUtilTest extends \WC_Unit_Test_Case {

 		$this->assertSame( 1, $delete_attempts );
 	}
+
+	/**
+	 * Join clauses that do not contain the wc_product_meta_lookup alias.
+	 *
+	 * %1$s is the prefixed lookup table name and %2$s is the posts table name.
+	 *
+	 * @return array<string, array<string>>
+	 */
+	public function provider_joins_without_the_lookup_alias(): array {
+		return array(
+			'empty clause'                     => array( '' ),
+			'unrelated table join'             => array( ' LEFT JOIN unrelated_table unrelated ON %2$s.ID = unrelated.post_id ' ),
+			'lookup table under another alias' => array( ' LEFT JOIN %1$s extension_lookup ON %2$s.ID = extension_lookup.product_id ' ),
+		);
+	}
+
+	/**
+	 * @testdox append_product_sorting_table_join appends the aliased lookup join when the clause does not contain the alias.
+	 * @dataProvider provider_joins_without_the_lookup_alias
+	 *
+	 * @param string $join_template Join clause template.
+	 */
+	public function test_append_product_sorting_table_join_appends_when_the_alias_is_missing( string $join_template ): void {
+		global $wpdb;
+		$join = sprintf( $join_template, $wpdb->wc_product_meta_lookup, $wpdb->posts );
+
+		$result = wc_get_container()->get( ProductUtil::class )->append_product_sorting_table_join( $join );
+
+		if ( '' !== $join ) {
+			$this->assertStringStartsWith( $join, $result, 'Existing joins should be preserved.' );
+		}
+		$this->assertStringContainsString(
+			"LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON {$wpdb->posts}.ID = wc_product_meta_lookup.product_id",
+			$result,
+			'The wc_product_meta_lookup alias should be joined whenever the clause does not already define it, even when other joins to the table exist.'
+		);
+	}
+
+	/**
+	 * @testdox Non-string query clauses are normalized before appending the lookup join.
+	 * @testWith [null]
+	 *           [false]
+	 *           [0]
+	 *           [true]
+	 *           [1.5]
+	 *           [[]]
+	 *
+	 * @param mixed $join Filtered join clause.
+	 */
+	public function test_append_product_sorting_table_join_normalizes_non_strings( $join ): void {
+		global $wpdb;
+
+		$result = wc_get_container()->get( ProductUtil::class )->append_product_sorting_table_join( $join );
+
+		$this->assertSame(
+			" LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id ",
+			$result,
+			'Non-string input must not produce an invalid SQL prefix.'
+		);
+	}
+
+	/**
+	 * @testdox A stringable query clause retains its join without duplicating the lookup alias.
+	 */
+	public function test_append_product_sorting_table_join_preserves_stringable_clause(): void {
+		$join = new class() {
+			/**
+			 * Return an existing lookup join.
+			 *
+			 * @return string
+			 */
+			public function __toString(): string {
+				return ' LEFT JOIN lookup_table wc_product_meta_lookup ON posts.ID = wc_product_meta_lookup.product_id ';
+			}
+		};
+
+		$result = wc_get_container()->get( ProductUtil::class )->append_product_sorting_table_join( $join );
+
+		$this->assertSame( (string) $join, $result, 'Preserve the original join and do not append a duplicate alias.' );
+	}
+
+	/**
+	 * @testdox Product queries still return products when an earlier filter supplies a non-string join.
+	 * @testWith ["catalog_sort", null]
+	 *           ["stock_join", null]
+	 *           ["stock_sort", null]
+	 *           ["store_api", null]
+	 *           ["store_api", 0]
+	 *           ["store_api", true]
+	 *           ["store_api", 1.5]
+	 *
+	 * @param string $path Query path to exercise.
+	 * @param mixed  $join Filtered join clause.
+	 */
+	public function test_lookup_join_consumers_tolerate_non_strings_from_filters( string $path, $join ): void {
+		global $wpdb;
+
+		$product = \WC_Helper_Product::create_simple_product();
+
+		if ( 'stock_join' === $path ) {
+			add_filter( 'posts_join', '__return_null', 5 );
+			add_filter( 'posts_join', array( StockController::class, 'add_wp_query_join' ), 10, 2 );
+		} else {
+			add_filter(
+				'posts_clauses',
+				static function ( $clauses ) use ( $join ) {
+					$clauses['join'] = $join;
+					return $clauses;
+				},
+				5
+			);
+			$callback = 'catalog_sort' === $path
+				? array( WC()->query, 'order_by_price_asc_post_clauses' )
+				: array( StockController::class, 'add_wp_query_orderby' );
+			if ( 'store_api' === $path ) {
+				$callback = array( new ProductQuery(), 'add_query_clauses' );
+			}
+			add_filter( 'posts_clauses', $callback, 10, 2 );
+		}
+
+		$query = new \WP_Query(
+			array(
+				'post_type'    => 'product',
+				'post__in'     => array( $product->get_id() ),
+				'fields'       => 'ids',
+				'orderby'      => 'stock_quantity',
+				'stock_status' => 'store_api' === $path ? array( ProductStockStatus::IN_STOCK ) : ProductStockStatus::IN_STOCK,
+			)
+		);
+
+		$this->assertSame( '', $wpdb->last_error, 'The lookup join must produce valid SQL.' );
+		$this->assertStringContainsString(
+			"LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup",
+			$query->request,
+			'The query must include the lookup join after normalizing the filtered clause.'
+		);
+		$this->assertSame( array( $product->get_id() ), $query->posts, 'The product must remain visible after a non-string join from an earlier filter.' );
+	}
+
+	/**
+	 * Join clauses that already define the wc_product_meta_lookup alias.
+	 *
+	 * %1$s is the prefixed lookup table name and %2$s is the posts table name.
+	 *
+	 * @return array<string, array<string>>
+	 */
+	public function provider_joins_with_the_lookup_alias(): array {
+		return array(
+			'implicit alias'   => array( ' LEFT JOIN %1$s wc_product_meta_lookup ON %2$s.ID = wc_product_meta_lookup.product_id ' ),
+			'AS keyword'       => array( ' LEFT JOIN %1$s AS wc_product_meta_lookup ON %2$s.ID = wc_product_meta_lookup.product_id ' ),
+			'backticked alias' => array( ' LEFT JOIN %1$s `wc_product_meta_lookup` ON %2$s.ID = `wc_product_meta_lookup`.product_id ' ),
+		);
+	}
+
+	/**
+	 * @testdox append_product_sorting_table_join leaves the clause alone when it already defines the alias.
+	 * @dataProvider provider_joins_with_the_lookup_alias
+	 *
+	 * @param string $join_template Join clause template.
+	 */
+	public function test_append_product_sorting_table_join_is_a_noop_when_the_alias_exists( string $join_template ): void {
+		global $wpdb;
+		$join = sprintf( $join_template, $wpdb->wc_product_meta_lookup, $wpdb->posts );
+
+		$result = wc_get_container()->get( ProductUtil::class )->append_product_sorting_table_join( $join );
+
+		$this->assertSame( $join, $result, 'A clause that already defines the wc_product_meta_lookup alias must be left alone; a second definition is a duplicate-alias SQL error.' );
+	}
 }