Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5aa9116
Implement package constants
Feb 11, 2026
251615c
Add "is constant" flag to nodes
Feb 11, 2026
2d35ecc
Fix blr parsing, generate unique names for constant FIELD_NAME, fix w…
Feb 11, 2026
75db5ab
Improve error message for non constant expression
Feb 11, 2026
e54c9e6
Add missing SCHEMA set to package constants, fix incorrect cached con…
Feb 12, 2026
45d62b7
Add missing resource allocation and transfer for constants
Feb 12, 2026
06411d7
Fix invalid constant and package name in store_dependencies
Feb 12, 2026
3dc1d3d
Fix invalid constant drop
Feb 12, 2026
d048560
Fix constant evaluating in BLR Parse
Feb 13, 2026
6231dbc
Fix constants collecting and dtype_dec64 parsing
Feb 13, 2026
05342aa
Fix incorrect ambiguity check
Feb 16, 2026
459354f
Update support for complex dtypes
Feb 16, 2026
3c2c61e
Cleanup
Feb 17, 2026
fd208d1
Rollback unneccecery changes
Feb 17, 2026
8a982ac
Move all constant related routines to Constant class
Feb 18, 2026
c42bd55
Add description for package constants
Feb 24, 2026
4a556aa
Cleanup
Feb 24, 2026
44a7050
Delete string in impure
Feb 24, 2026
c2caa1b
Merge remote-tracking branch 'upstream/master' into metacache_package…
Feb 24, 2026
4294bc4
Merge remote-tracking branch 'origin/master' into metacache_package_c…
Feb 24, 2026
3148971
Resolve incorrect merge with LTT
Feb 24, 2026
51fc8d7
One more cleanup (preparation) before PR
Feb 25, 2026
bd085c9
Add ability to add comment on package constant
Feb 25, 2026
4695050
Remove unneccecery namespace scope
Feb 26, 2026
e606d59
Make Constant value in reload state when MINISCAN is set
Feb 26, 2026
cf7929e
Remove not used variable and omit unneccecery namespace scope
Feb 26, 2026
12a7b2b
Update src/isql/show.epp
Noremos Feb 27, 2026
edca2c5
First part of fixes/corrections related to PR issues
Feb 27, 2026
cfdf444
Resolve more PR issues
Feb 27, 2026
816ad9c
Add missing search for FIELD SCHEMA
Feb 27, 2026
13b7df9
Use better name for method
Feb 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/sql.extensions/README.ddl.txt
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ COMMENT ON <basic_type> name IS {'txt'|NULL};
COMMENT ON COLUMN table_or_view_name.field_name IS {'txt'|NULL};
COMMENT ON {PROCEDURE | [EXTERNAL] FUNCTION} [<package_name> .] name.param_name IS {'txt'|NULL};
COMMENT ON [PROCEDURE | FUNCTION] PARAMETER [<package_name> .] name.param_name IS {'txt'|NULL};
COMMENT ON CONSTANT [<schema_name> .] <package_name> . name IS {'txt'|NULL};

An empty literal string '' will act as NULL since the internal code (DYN in this case)
works this way with blobs.
Expand Down
21 changes: 17 additions & 4 deletions doc/sql.extensions/README.packages.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,18 @@ Syntax:

<package_item> ::=
<function_decl> ; |
<procedure_decl> ;
<procedure_decl> ; |
<constant_decl> ;

<function_decl> ::=
FUNCTION <name> [( <parameters> )] RETURNS <type>

<procedure_decl> ::=
PROCEDURE <name> [( <parameters> ) [RETURNS ( <parameters> )]]

<constant_decl> ::=
CONSTANT <name> <type> = <constant expression>

<package_body> ::=
{ CREATE [OR ALTER] | ALTER | RECREATE } PACKAGE BODY <name>
AS
Expand All @@ -37,7 +41,8 @@ Syntax:

<package_body_item> ::=
<function_impl> |
<procedure_impl>
<procedure_impl> |
<constant_decl>

<function_impl> ::=
FUNCTION <name> [( <parameters> )] RETURNS <type>
Expand Down Expand Up @@ -75,7 +80,7 @@ Objectives:
1) The grouping is not represented in the database metadata.
2) They all participate in a flat namespace and all routines are callable by everyone (not
talking about security permissions here).

- Facilitate dependency tracking between its internal routines and between other packaged and
unpackaged routines.

Expand All @@ -90,9 +95,17 @@ Objectives:
tables that the package body depends on that object. If you want to, for example, drop that
object, you first need to remove who depends on it. As who depends on it is a package body,
you can just drop it even if some other database object depends on this package. When the body
is dropped, the header remains, allowing you to create its body again after changing it based
is dropped, the header remains, allowing you to create its body again after changing it based
on the object removal.

A package constant is a value initialized by a constant expression.
A constant expression is defined by a simple rule: its value does not change after recompilation.
Constants declared in the package specification are publicly visible and can be referenced using
the [<schema>.]<package>.<constant_name> notation.
Constants declared in the package body are private and cannot be accessed from outside the package.
However, they can be referenced directly by <constant_name> within <procedure_impl> and <function_impl>.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears that package header constant cannot be referenced in its own body without the qualified package name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I corrected the desription

Header constants can also be used directly with just the name for package body elements.

- Facilitate permission management.

It's generally a good practice to create routines with a privileged database user and grant
Expand Down
1 change: 1 addition & 0 deletions src/burp/OdsDetection.epp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ namespace
{"RDB$PACKAGES", 0, DB_VERSION_DDL12}, // FB3
{"RDB$PUBLICATIONS", 0, DB_VERSION_DDL13}, // FB4
{"RDB$SCHEMAS", 0, DB_VERSION_DDL14}, // FB6
{"RDB$CONSTANTS", 0, DB_VERSION_DDL14}, // FB6
{0, 0, 0}
};

Expand Down
1 change: 1 addition & 0 deletions src/common/ParserTokens.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ PARSER_TOKEN(TOK_CONDITIONAL, "CONDITIONAL", true)
PARSER_TOKEN(TOK_CONNECT, "CONNECT", false)
PARSER_TOKEN(TOK_CONNECTIONS, "CONNECTIONS", true)
PARSER_TOKEN(TOK_CONSISTENCY, "CONSISTENCY", true)
PARSER_TOKEN(TOK_CONSTANT, "CONSTANT", true)
PARSER_TOKEN(TOK_CONSTRAINT, "CONSTRAINT", false)
PARSER_TOKEN(TOK_CONTAINING, "CONTAINING", true)
PARSER_TOKEN(TOK_CONTINUE, "CONTINUE", true)
Expand Down
50 changes: 37 additions & 13 deletions src/dsql/DdlNodes.epp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
#include "../jrd/ini.h"
#include "../jrd/GarbageCollector.h"
#include "../jrd/ProtectRelations.h"
#include "../dsql/PackageNodes.h"

namespace Jrd {

Expand Down Expand Up @@ -130,16 +131,6 @@ static rel_t relationType(SSHORT relationTypeNull, SSHORT relationType) noexcept
static void saveField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, const MetaName& fieldName);
static dsql_rel* saveRelation(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
const QualifiedName& relationName, bool view, bool creating);
static void updateRdbFields(const TypeClause* type,
SSHORT& fieldType,
SSHORT& fieldLength,
SSHORT& fieldSubTypeNull, SSHORT& fieldSubType,
SSHORT& fieldScaleNull, SSHORT& fieldScale,
SSHORT& characterSetIdNull, SSHORT& characterSetId,
SSHORT& characterLengthNull, SSHORT& characterLength,
SSHORT& fieldPrecisionNull, SSHORT& fieldPrecision,
SSHORT& collationIdNull, SSHORT& collationId,
SSHORT& segmentLengthNull, SSHORT& segmentLength);
static ISC_STATUS getErrorCodeByObjectType(int obj_type);

static constexpr const char* CHECK_CONSTRAINT_EXCEPTION = "check_constraint";
Expand Down Expand Up @@ -887,7 +878,7 @@ static dsql_rel* saveRelation(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
}

// Update RDB$FIELDS received by reference.
static void updateRdbFields(const TypeClause* type,
void DdlNode::updateRdbFields(const TypeClause* type,
SSHORT& fieldType,
SSHORT& fieldLength,
SSHORT& fieldSubTypeNull, SSHORT& fieldSubType,
Expand Down Expand Up @@ -1486,6 +1477,20 @@ DdlNode* CommentOnNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
dsqlScratch->resolveRoutineOrRelation(name, {objType});
break;

case obj_package_constant:
{
QualifiedName constantName(subName, name.schema, name.object); // name is a package
if (constantName.schema.isEmpty())
constantName.schema = PUBLIC_SCHEMA;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was not necessary in any other command in DdlNodes.
Looks incorrect here.

Copy link
Contributor Author

@Noremos Noremos Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actuality, as I can see, something similar (but more more complicated) is happening for procedures and functions in resolveRoutineOrRelation(...). Without the check, scheme will remain NULL.


if (!PackageReferenceNode::constantExists(tdbb, transaction, constantName))
{
status_exception::raise(Arg::Gds(isc_bad_constant_name) << constantName.toQuotedString());
}
name = constantName;
break;
}

default:
dsqlScratch->qualifyExistingName(name, objType);
break;
Expand Down Expand Up @@ -1590,6 +1595,10 @@ void CommentOnNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
SCL_check_package(tdbb, name, SCL_alter);
break;

case obj_package_constant:
SCL_check_package(tdbb, name.getSchemaAndPackage(), SCL_alter);
break;

default:
fb_assert(false);
}
Expand Down Expand Up @@ -1753,6 +1762,12 @@ void CommentOnNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, j
status << Arg::Gds(isc_dyn_package_not_found) << Arg::Str(objNameStr);
break;

case obj_package_constant:
tableClause = "rdb$constants";
columnClause = "rdb$constant_name";
status << Arg::Gds(isc_bad_constant_name) << Arg::Str(objNameStr);
break;

default:
fb_assert(false);
return;
Expand All @@ -1776,7 +1791,7 @@ void CommentOnNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, j
sql << "and" << subColumnClause << "=" << subName;
}

if (objType == obj_procedure || objType == obj_udf)
if (objType == obj_procedure || objType == obj_udf || objType == obj_package_constant)
sql << "and rdb$package_name is not distinct from nullif(" << name.package << ", '')";

if (addWhereClause)
Expand Down Expand Up @@ -11562,11 +11577,13 @@ void CreateAlterViewNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScra
ValueExprNode* nameNode = fieldNode;
const char* aliasName = NULL;

while (nodeIs<DsqlAliasNode>(nameNode) || nodeIs<DerivedFieldNode>(nameNode) || nodeIs<DsqlMapNode>(nameNode))
while (nodeIs<DsqlAliasNode>(nameNode) || nodeIs<DerivedFieldNode>(nameNode) || nodeIs<DsqlMapNode>(nameNode) ||
nodeAs<PackageReferenceNode>(nameNode))
{
DsqlAliasNode* aliasNode;
DsqlMapNode* mapNode;
DerivedFieldNode* derivedField;
PackageReferenceNode* referenceNode;

if ((aliasNode = nodeAs<DsqlAliasNode>(nameNode)))
{
Expand All @@ -11582,6 +11599,13 @@ void CreateAlterViewNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScra
aliasName = derivedField->name.c_str();
nameNode = derivedField->value;
}
else if ((referenceNode = nodeAs<PackageReferenceNode>(nameNode)))
{
if (!aliasName)
aliasName = referenceNode->getName();

nameNode = nullptr;
}
}

const dsql_fld* nameField = NULL;
Expand Down
59 changes: 58 additions & 1 deletion src/dsql/ExprNodes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@
#include "../jrd/trace/TraceObjects.h"
#include "../jrd/trace/TraceJrdHelpers.h"

#include "../dsql/PackageNodes.h"

using namespace Firebird;
using namespace Jrd;

Expand Down Expand Up @@ -466,6 +468,23 @@ void ExprNode::collectStreams(SortedStreamList& streamList) const
}
}

bool ExprNode::isChildrenConstant() const
{
NodeRefsHolder holder;
getChildren(holder, false);

for (auto i : holder.refs)
{
if (*i == nullptr)
continue;

if (!(*i)->constant())
return false;
}

return true;
}

bool ExprNode::computable(CompilerScratch* csb, StreamType stream,
bool allowOnlyCurrentStream, ValueExprNode* /*value*/)
{
Expand Down Expand Up @@ -6448,6 +6467,28 @@ ValueExprNode* FieldNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch, Rec
}
}

// Use context to check conflicts beween <relation>.<field> and <package>.<constant>
dsql_ctx packageContext(dsqlScratch->getPool());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why package becomes a context.

Copy link
Contributor Author

@Noremos Noremos Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to check name conflict between <relation>.<field> and <package>.<constant>. I added a comment about it

{ // Consatnts

const QualifiedName constantName(dsqlName,
dsqlQualifier.schema.hasData() ? dsqlQualifier.schema : (dsqlScratch->package.schema.hasData() ? dsqlScratch->package.schema : PUBLIC_SCHEMA),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think should use PUBLIC_SCHEMA here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without it, schema will be NULL. Or I am doing something wrong

dsqlQualifier.object.hasData() ? dsqlQualifier.object : dsqlScratch->package.object);

if (PackageReferenceNode::constantExists(tdbb, dsqlScratch->getTransaction(), constantName))
{
packageContext.ctx_relation = nullptr;
packageContext.ctx_procedure = nullptr;
// Alias is a package name, not a constant
packageContext.ctx_alias.push(QualifiedName(constantName.package, constantName.schema));
packageContext.ctx_flags |= CTX_package;
ambiguousCtxStack.push(&packageContext);

MemoryPool& pool = dsqlScratch->getPool();
node = FB_NEW_POOL(pool) PackageReferenceNode(pool, constantName);
}
}

// CVC: We can't return blindly if this is a check constraint, because there's
// the possibility of an invalid field that wasn't found. The multiple places that
// call this function pass1_field() don't expect a NULL pointer, hence will crash.
Expand Down Expand Up @@ -12463,7 +12504,12 @@ void SysFuncCallNode::make(DsqlCompilerScratch* dsqlScratch, dsc* desc)

bool SysFuncCallNode::deterministic(thread_db* tdbb) const
{
return ExprNode::deterministic(tdbb) && function->deterministic;
return ExprNode::deterministic(tdbb) && function->isDeterministic();
}

bool SysFuncCallNode::constant() const
{
return ExprNode::isChildrenConstant() && function->isConstant();
}

void SysFuncCallNode::getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc)
Expand Down Expand Up @@ -14151,6 +14197,17 @@ ValueExprNode* VariableNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
if (!node->dsqlVar ||
(node->dsqlVar->type == dsql_var::TYPE_LOCAL && !node->dsqlVar->initialized && !dsqlScratch->mainScratch))
{
if (dsqlScratch->package.object.hasData())
{
thread_db* tdbb = JRD_get_thread_data();
QualifiedName constantFullName(dsqlName, dsqlScratch->package.schema, dsqlScratch->package.object);
if (PackageReferenceNode::constantExists(tdbb, dsqlScratch->getTransaction(), constantFullName))
{
delete node;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird that it first creates a variable to later delete it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I don't want to change existing code too much in this case

return FB_NEW_POOL(dsqlScratch->getPool()) PackageReferenceNode(dsqlScratch->getPool(), constantFullName);
}
}

PASS1_field_unknown(NULL, dsqlName.toQuotedString().c_str(), this);
}

Expand Down
Loading
Loading