Skip to content

Commit 067b26d

Browse files
committed
Feature #8439 - Syntax rules for ambiguous name resolution.
1 parent 4706458 commit 067b26d

22 files changed

+452
-333
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Name resolution (FB 6.0)
2+
3+
With the introduction of schemas in Firebird 6.0, the syntax `<name>.<name>` - used for tables, views, procedures,
4+
and functions (both standalone and packaged) - introduces ambiguity when resolving object names using the schema
5+
search path. The ambiguity arises between:
6+
7+
- `<schema>.<object>` (a schema and its object)
8+
- `<package>.<object>` (a package and its object)
9+
10+
This document focuses on name resolution rules for tables, views, procedures, and functions within queries and
11+
code blocks.
12+
13+
## Scope specifier (`%`)
14+
15+
To resolve these ambiguities, Firebird introduces a **scope specifier**, represented by the `%` symbol. This
16+
allows unambiguous referencing of objects.
17+
18+
### Syntax
19+
20+
```sql
21+
<name> % { SCHEMA | PACKAGE } . <name>
22+
```
23+
24+
### Examples
25+
26+
```sql
27+
select *
28+
from plg$profiler%schema.plg$prof_sessions;
29+
30+
execute procedure rdb$profiler%package.pause_session;
31+
32+
call rdb$profiler%package.pause_session();
33+
34+
select rdb$time_zone_util%package.database_version()
35+
from system%schema.rdb$database;
36+
37+
select *
38+
from rdb$time_zone_util%package.transitions('America/Sao_Paulo', timestamp '2017-01-01', timestamp '2019-01-01');
39+
```
40+
41+
## Detailed name resolution rules
42+
43+
Firebird resolves object names following a structured sequence of rules. Once an object is located, the resolution
44+
process halts, ensuring no ambiguity errors occur.
45+
46+
- **`name1.name2.name3`**
47+
1. Look for routine `name3` inside package `name2`, inside schema `name1`.
48+
49+
- **`name1%schema.name2`**
50+
1. Look for object `name2` inside schema `name1`.
51+
52+
- **`name1%package.name2`**
53+
1. Look for object `name2` inside a package `name1` using the schema search path.
54+
55+
- **`name1.name2`**
56+
1. If inside a package named `name1`, look for routine `name2` in the same package.
57+
2. Look in schema `name1` for object `name2`.
58+
3. Look for object `name2` inside a package `name1` using the schema search path.
59+
60+
- **`name`**
61+
1. Look for subroutine `name`.
62+
2. If inside a package, look for routine `name` in the same package.
63+
3. Look for object `name` using the schema search path.
64+
65+
> **_Note:_** Object resolution also depends on the context in which they are used. For example, in
66+
`select * from name1.name2`, `name2` could be a table, view, or procedure. However, in
67+
`execute procedure name1.name2`, `name2` must be a procedure. This distinction means that an `execute procedure`
68+
command versus a `select` command can resolve to different objects.

doc/sql.extensions/README.schemas.md

+1-6
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,7 @@ end;
162162
### Resolving between `PACKAGE.OBJECT` and `SCHEMA.OBJECT`
163163

164164
The syntax `<name>.<name>` introduces ambiguity between `<package>.<object>` and `<schema>.<object>` when referring to
165-
procedures and functions.
166-
167-
In such cases, the system first searches for a package name using the search path. If a matching package is found, the
168-
name is resolved as `<package>.<object>` within the schema where the package resides.
169-
170-
If no package is found, the name is interpreted as a fully qualified `<schema>.<object>` instead.
165+
procedures and functions. See [name resolution](README.name_resolution.md) for details.
171166

172167
## Permissions
173168

src/auth/SecureRemotePassword/manage/SrpManagement.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ class SrpManagement final : public Firebird::StdPlugin<Firebird::IManagementImpl
609609
" WHERE RDB$RELATION_NAME = 'RDB$ADMIN' AND RDB$PRIVILEGE = 'M' GROUP BY RDB$USER) "
610610
"SELECT PLG$USER_NAME, PLG$FIRST, PLG$MIDDLE, PLG$LAST, PLG$COMMENT, PLG$ATTRIBUTES, "
611611
" CASE WHEN RDB$USER IS NULL THEN FALSE ELSE TRUE END, PLG$ACTIVE "
612-
"FROM PLG$SRP.PLG$SRP_VIEW LEFT JOIN ADMINS "
612+
"FROM PLG$SRP%SCHEMA.PLG$SRP_VIEW LEFT JOIN ADMINS "
613613
" ON PLG$SRP_VIEW.PLG$USER_NAME = ADMINS.RDB$USER ";
614614
if (user->userName()->entered())
615615
{

src/auth/SecureRemotePassword/server/SrpServer.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ class SecurityDatabase : public VSecDb
196196
HANDSHAKE_DEBUG(fprintf(stderr, "Srv: SRP1: started transaction\n"));
197197

198198
const char* sql =
199-
"SELECT PLG$VERIFIER, PLG$SALT FROM PLG$SRP.PLG$SRP WHERE PLG$USER_NAME = ? AND PLG$ACTIVE";
199+
"SELECT PLG$VERIFIER, PLG$SALT FROM PLG$SRP%SCHEMA.PLG$SRP WHERE PLG$USER_NAME = ? AND PLG$ACTIVE";
200200
stmt = att->prepare(&status, tra, 0, sql, 3, IStatement::PREPARE_PREFETCH_METADATA);
201201
if (status->getState() & IStatus::STATE_ERRORS)
202202
{

src/common/classes/QualifiedMetaString.h

+19-3
Original file line numberDiff line numberDiff line change
@@ -52,22 +52,25 @@ class BaseQualifiedName
5252
BaseQualifiedName(MemoryPool& p, const BaseQualifiedName& src)
5353
: object(p, src.object),
5454
schema(p, src.schema),
55-
package(p, src.package)
55+
package(p, src.package),
56+
unambiguous(src.isUnambiguous())
5657
{
5758
}
5859

5960
BaseQualifiedName(const BaseQualifiedName& src)
6061
: object(src.object),
6162
schema(src.schema),
62-
package(src.package)
63+
package(src.package),
64+
unambiguous(src.isUnambiguous())
6365
{
6466
}
6567

6668
template <typename TT>
6769
BaseQualifiedName(const BaseQualifiedName<TT>& src)
6870
: object(src.object),
6971
schema(src.schema),
70-
package(src.package)
72+
package(src.package),
73+
unambiguous(src.isUnambiguous())
7174
{
7275
}
7376

@@ -222,6 +225,16 @@ class BaseQualifiedName
222225
}
223226

224227
public:
228+
bool isUnambiguous() const
229+
{
230+
return unambiguous;
231+
}
232+
233+
void setUnambiguous(bool value)
234+
{
235+
unambiguous = value;
236+
}
237+
225238
BaseQualifiedName getSchemaAndPackage() const
226239
{
227240
return BaseQualifiedName(package, schema);
@@ -263,6 +276,9 @@ class BaseQualifiedName
263276
T object;
264277
T schema;
265278
T package;
279+
280+
private:
281+
bool unambiguous = false;
266282
};
267283

268284
using QualifiedMetaString = Firebird::BaseQualifiedName<MetaString>;

src/dsql/DsqlCompilerScratch.cpp

+118-3
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
#include "../dsql/errd_proto.h"
3131
#include "../dsql/gen_proto.h"
3232
#include "../dsql/make_proto.h"
33+
#include "../dsql/metd_proto.h"
3334
#include "../dsql/pass1_proto.h"
35+
#include <unordered_set>
3436

3537
using namespace Firebird;
3638
using namespace Jrd;
@@ -72,7 +74,7 @@ void DsqlCompilerScratch::qualifyNewName(QualifiedName& name) const
7274
}
7375
}
7476

75-
void DsqlCompilerScratch::qualifyExistingName(QualifiedName& name, ObjectType objectType)
77+
void DsqlCompilerScratch::qualifyExistingName(QualifiedName& name, std::initializer_list<ObjectType> objectTypes)
7678
{
7779
if (!(name.schema.isEmpty() && name.object.hasData()))
7880
return;
@@ -96,10 +98,123 @@ void DsqlCompilerScratch::qualifyExistingName(QualifiedName& name, ObjectType ob
9698
}
9799
}
98100

99-
attachment->qualifyExistingName(tdbb, name, objectType, cachedDdlSchemaSearchPath);
101+
attachment->qualifyExistingName(tdbb, name, objectTypes, cachedDdlSchemaSearchPath);
100102
}
101103
else
102-
attachment->qualifyExistingName(tdbb, name, objectType);
104+
attachment->qualifyExistingName(tdbb, name, objectTypes);
105+
}
106+
107+
108+
std::variant<std::monostate, dsql_prc*, dsql_rel*, dsql_udf*> DsqlCompilerScratch::resolveRoutineOrRelation(
109+
QualifiedName& name, std::initializer_list<ObjectType> objectTypes)
110+
{
111+
const std::unordered_set<ObjectType> objectTypesSet(objectTypes);
112+
const bool searchProcedures = objectTypesSet.find(obj_procedure) != objectTypesSet.end();
113+
const bool searchRelations = objectTypesSet.find(obj_relation) != objectTypesSet.end();
114+
const bool searchFunctions = objectTypesSet.find(obj_udf) != objectTypesSet.end();
115+
fb_assert((searchProcedures || searchRelations) != searchFunctions);
116+
117+
std::variant<std::monostate, dsql_prc*, dsql_rel*, dsql_udf*> object;
118+
119+
const auto notFound = [&]()
120+
{
121+
return std::holds_alternative<std::monostate>(object);
122+
};
123+
124+
const auto setObject = [&](const auto value)
125+
{
126+
if (value)
127+
object = value;
128+
};
129+
130+
// search subroutine: name
131+
if (name.schema.isEmpty() &&
132+
name.package.isEmpty())
133+
{
134+
if (searchProcedures)
135+
{
136+
if (const auto subProcedure = getSubProcedure(name.object))
137+
setObject(subProcedure->dsqlProcedure);
138+
}
139+
140+
if (searchFunctions)
141+
{
142+
if (const auto subFunction = getSubFunction(name.object))
143+
setObject(subFunction->dsqlFunction);
144+
}
145+
}
146+
147+
// search packaged routine in the same package: name, same_package.name
148+
if (notFound() &&
149+
package.object.hasData() &&
150+
name.package.isEmpty() &&
151+
(name.schema.isEmpty() ||
152+
(!name.isUnambiguous() && name.schema == package.object)))
153+
{
154+
const QualifiedName routineName(name.object, package.schema, package.object);
155+
156+
if (searchProcedures)
157+
setObject(METD_get_procedure(getTransaction(), this, routineName));
158+
159+
if (searchFunctions)
160+
setObject(METD_get_function(getTransaction(), this, routineName));
161+
}
162+
163+
// search standalone routine or relation: name, name1%schema.name2, name1.name2
164+
if (notFound() &&
165+
name.package.isEmpty())
166+
{
167+
auto qualifiedName = name;
168+
qualifyExistingName(qualifiedName, objectTypes);
169+
170+
if (searchProcedures)
171+
setObject(METD_get_procedure(getTransaction(), this, qualifiedName));
172+
173+
if (searchRelations)
174+
setObject(METD_get_relation(getTransaction(), this, qualifiedName));
175+
176+
if (searchFunctions)
177+
setObject(METD_get_function(getTransaction(), this, qualifiedName));
178+
}
179+
180+
// search packaged routine: name1%package.name2, name1.name2.name3
181+
if (notFound() &&
182+
name.package.hasData())
183+
{
184+
auto qualifiedName = name;
185+
qualifyExistingName(qualifiedName, objectTypes);
186+
187+
if (searchProcedures)
188+
setObject(METD_get_procedure(getTransaction(), this, qualifiedName));
189+
190+
if (searchFunctions)
191+
setObject(METD_get_function(getTransaction(), this, qualifiedName));
192+
}
193+
194+
// search packaged routine: name1.name2
195+
if (notFound() &&
196+
!name.isUnambiguous() &&
197+
name.schema.hasData() &&
198+
name.package.isEmpty())
199+
{
200+
QualifiedName qualifiedName(name.object, {}, name.schema);
201+
qualifyExistingName(qualifiedName, objectTypes);
202+
203+
if (searchProcedures)
204+
setObject(METD_get_procedure(getTransaction(), this, qualifiedName));
205+
206+
if (searchFunctions)
207+
setObject(METD_get_function(getTransaction(), this, qualifiedName));
208+
}
209+
210+
if (const auto procedure = std::get_if<dsql_prc*>(&object))
211+
name = (*procedure)->prc_name;
212+
else if (const auto relation = std::get_if<dsql_rel*>(&object))
213+
name = (*relation)->rel_name;
214+
else if (const auto function = std::get_if<dsql_udf*>(&object))
215+
name = (*function)->udf_name;
216+
217+
return object;
103218
}
104219

105220

src/dsql/DsqlCompilerScratch.h

+11-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
#include "../jrd/MetaName.h"
3131
#include "../common/classes/stack.h"
3232
#include "../common/classes/alloc.h"
33+
#include <initializer_list>
3334
#include <optional>
35+
#include <variant>
3436

3537
namespace Jrd
3638
{
@@ -153,7 +155,15 @@ class DsqlCompilerScratch : public BlrDebugWriter
153155
}
154156

155157
void qualifyNewName(QualifiedName& name) const;
156-
void qualifyExistingName(QualifiedName& name, ObjectType objectType);
158+
void qualifyExistingName(QualifiedName& name, std::initializer_list<ObjectType> objectTypes);
159+
160+
void qualifyExistingName(QualifiedName& name, ObjectType objectType)
161+
{
162+
qualifyExistingName(name, {objectType});
163+
}
164+
165+
std::variant<std::monostate, dsql_prc*, dsql_rel*, dsql_udf*> resolveRoutineOrRelation(QualifiedName& name,
166+
std::initializer_list<ObjectType> objectTypes);
157167

158168
void putBlrMarkers(ULONG marks);
159169
void putDtype(const TypeClause* field, bool useSubType);

src/dsql/ExprNodes.cpp

+3-32
Original file line numberDiff line numberDiff line change
@@ -13823,38 +13823,11 @@ dsc* UdfCallNode::execute(thread_db* tdbb, Request* request) const
1382313823

1382413824
ValueExprNode* UdfCallNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
1382513825
{
13826+
const auto resolvedObject = dsqlScratch->resolveRoutineOrRelation(name, {obj_udf});
1382613827
dsql_udf* function = nullptr;
1382713828

13828-
if (name.package.isEmpty())
13829-
{
13830-
if (name.schema.isEmpty())
13831-
{
13832-
if (const auto subFunction = dsqlScratch->getSubFunction(name.object))
13833-
function = subFunction->dsqlFunction;
13834-
else if (dsqlScratch->package.object.hasData())
13835-
{
13836-
const QualifiedName packagedName(name.object, dsqlScratch->package.schema, dsqlScratch->package.object);
13837-
function = METD_get_function(dsqlScratch->getTransaction(), dsqlScratch, packagedName);
13838-
}
13839-
13840-
if (!function)
13841-
dsqlScratch->qualifyExistingName(name, obj_udf);
13842-
}
13843-
else
13844-
{
13845-
QualifiedName packageName(name.schema);
13846-
dsqlScratch->qualifyExistingName(packageName, obj_package_header);
13847-
13848-
if (MET_check_package_exists(JRD_get_thread_data(), packageName))
13849-
{
13850-
name.schema = packageName.schema;
13851-
name.package = packageName.object;
13852-
}
13853-
}
13854-
}
13855-
13856-
if (!function)
13857-
function = METD_get_function(dsqlScratch->getTransaction(), dsqlScratch, name);
13829+
if (const auto resolvedFunction = std::get_if<dsql_udf*>(&resolvedObject))
13830+
function = *resolvedFunction;
1385813831

1385913832
if (!function)
1386013833
{
@@ -13871,8 +13844,6 @@ ValueExprNode* UdfCallNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
1387113844
function->udf_name.getSchemaAndPackage().toQuotedString());
1387213845
}
1387313846

13874-
name = function->udf_name;
13875-
1387613847
const auto node = FB_NEW_POOL(dsqlScratch->getPool()) UdfCallNode(dsqlScratch->getPool(), name,
1387713848
doDsqlPass(dsqlScratch, args),
1387813849
dsqlArgNames ?

0 commit comments

Comments
 (0)