Note: This release is in preview. Features described here don’t become generally available until the latest general availability date that Salesforce announces for this release. Before then, and where features are noted as beta, pilot, or developer preview, we can’t guarantee general availability within any particular time frame or at all. Make your purchase decisions only on the basis of generally available products and features.
Prevent Field Name Collisions in Managed SOQL Queries
Understand Namespace Shadowing in Managed Packages
In managed Apex, if an unqualified custom field or relationship name matches both a packaged and a local field, the SOQL parser gives precedence to the package’s default namespace. This default assumption keeps packaged SOQL readable, but it can cause duplicate field name errors if a subscriber creates a local custom field with the same API name as a packaged field.
For example, suppose that your managed package uses the namespace ExPackageNS and includes an Age__c field on Account. A subscriber org also creates an unmanaged Age__c field on Account. In packaged Apex at API version 34.0 and later, this dynamic query fails with a duplicate field name error because both field references resolve to the packaged field.
1// Fails in a subscriber org that also has Age__c
2List<Account> results = Database.query(
3 'SELECT Id, Age__c, ExPackageNS__Age__c FROM Account'
4);How Explicit Namespace Resolution Works
The explicitNamespace property for Database.QueryOptions prevents namespace shadowing conflicts. You can query the subscriber’s local field, the packaged field, or both fields in the same dynamic SOQL query.
If you set explicitNamespace to true on a Database.QueryOptions object:
- An unqualified custom field or relationship name, such as Age__c, resolves to the subscriber’s local field.
- A namespace-qualified name, such as ExPackageNS__Age__c, resolves to the packaged field.
- If the subscriber org doesn’t have the local field, the query doesn’t fall back to the packaged field. To query the packaged field, use the namespace-qualified name.
That resolution applies across SELECT, WHERE, GROUP BY, HAVING, ORDER BY, foreign key traversal, aggregates, DISTANCE expressions, aliased expressions, relationship names, and subqueries or semi-joins that inherit options from the outer query.
Setting explicitNamespace to false is the same as leaving it unset. Package namespace precedence matching is applied.
Query Package and Subscriber Fields with Dynamic SOQL
Use dynamic SOQL when you need both the packaged field and the subscriber field in the same query, or when you reference a subscriber field that didn’t exist when the package was compiled.
Even when SET OPTIONS is present, static SOQL that selects both an unqualified field and its namespace-qualified counterpart fails at compile time because static SOQL can reference only fields that existed when the packaged class was compiled. Therefore, referencing a subscriber’s local field requires dynamic SOQL.
In this example, the subscriber���s local Age__c field doesn’t exist when ExPackageNS__Age__c is compiled, so the Database.query method is used to query the fields dynamically.
1Database.QueryOptions opts = Database.QueryOptions.builder()
2 .withExplicitNamespace(true)
3 .build();
4
5// Age__c -> subscriber-local field
6// ExPackageNS__Age__c -> packaged field
7List<Account> results = Database.query(
8 'SELECT Id, Name, Age__c, ExPackageNS__Age__c FROM Account SET OPTIONS :opts',
9 AccessLevel.USER_MODE
10);This example uses the Database.queryWithBinds method.
1Database.QueryOptions opts = Database.QueryOptions.builder()
2 .withExplicitNamespace(true)
3 .build();
4
5List<Account> results = Database.queryWithBinds(
6 'SELECT Id, Name, Age__c, ExPackageNS__Age__c FROM Account SET OPTIONS :opts',
7 new Map<String, Object>{ 'opts' => opts },
8 AccessLevel.USER_MODE
9);In this example, only the subscriber’s local field is queried.
1Database.QueryOptions opts = Database.QueryOptions.builder()
2 .withExplicitNamespace(true)
3 .build();
4
5// Age__c resolves to the subscriber field only.
6List<Account> results = Database.query(
7 'SELECT Id, Age__c FROM Account SET OPTIONS :opts'
8);You can also pass Database.QueryOptions through related dynamic SOQL methods that support binds, including Database.countQuery, Database.countQueryWithBinds, Database.getQueryLocator, and Database.getQueryLocatorWithBinds.
Considerations
Review these considerations when working with Database.QueryOptions and the SET OPTIONS clause in dynamic SOQL queries.
- Place SET OPTIONS after other query clauses, such as WHERE, GROUP BY, ORDER BY, LIMIT, and OFFSET, and before FOR UPDATE.
- Put SET OPTIONS on the outermost query only. Nested queries inherit the outer options for explicit namespace resolution.
- Use the :optionsVariable bind syntax in Apex only. It isn’t supported when you run SOQL directly through REST API or SOAP API.
- In dynamic SOQL, resolve the bind directly to a Database.QueryOptions variable. Don’t use expressions, such as :wrapper.opts or :getOpts().
- Construct Database.QueryOptions only through Database.QueryOptions.builder(). After build(), the object is immutable.
- Only the Database.QueryOptions properties that you explicitly set are applied.
Error Handling
This table details some possible exceptions that can occur when working with Database.QueryOptions and builder options in Apex.
| Error Type | Condition | Exception |
|---|---|---|
| Compile-time | Bind variable isn’t of type Database.QueryOptions. | Compile error |
| Runtime | Null Database.QueryOptions passed to SET OPTIONS. | System.QueryException |
| Runtime | Apex class API version is below the minimum required for a builder option. | System.QueryException |