Skip to content

Commit 2220edf

Browse files
authored
Declared Local Temporary Tables in PSQL (#9058)
1 parent bb82bdc commit 2220edf

39 files changed

Lines changed: 1319 additions & 162 deletions
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
# Declared Local Temporary Tables in PSQL (FB 6.0)
2+
3+
Firebird 6.0 supports declaring local temporary tables inside PSQL blocks, procedures, functions and triggers.
4+
5+
Declared Local Temporary Tables are local to the compiled PSQL object or `EXECUTE BLOCK`. They are not stored in
6+
database metadata and they do not require an `ON COMMIT` clause. Their data is scoped to the current execution frame.
7+
8+
This feature is distinct from:
9+
10+
- SQL-created Local Temporary Tables (`CREATE LOCAL TEMPORARY TABLE ...`), whose definitions are attachment-private
11+
DDL objects.
12+
- Packaged temporary tables (`TEMPORARY TABLE ...` inside packages), whose definitions are persistent package-owned
13+
metadata.
14+
15+
## Syntax
16+
17+
Declared Local Temporary Tables are declared in the same declaration section as variables, cursors and subroutines.
18+
19+
```sql
20+
DECLARE [LOCAL] TEMPORARY TABLE <table_name>
21+
(
22+
<column_definition> [, ...]
23+
);
24+
```
25+
26+
There is no `ON COMMIT` clause.
27+
28+
Example in `EXECUTE BLOCK`:
29+
30+
```sql
31+
set term !;
32+
33+
execute block returns (n integer)
34+
as
35+
declare local temporary table t (
36+
id integer not null,
37+
val varchar(20)
38+
);
39+
begin
40+
insert into t(id, val) values (1, 'a');
41+
insert into t(id, val) values (2, 'b');
42+
43+
update t set id = id + 10 where id = 1;
44+
delete from t where id = 2;
45+
46+
select count(*) from t into n;
47+
suspend;
48+
end!
49+
50+
set term ;!
51+
```
52+
53+
Example in a procedure:
54+
55+
```sql
56+
set term !;
57+
58+
create procedure p_count_values returns (n integer)
59+
as
60+
declare local temporary table t (
61+
id integer not null
62+
);
63+
begin
64+
insert into t(id) values (1);
65+
insert into t(id) values (2);
66+
67+
select count(*) from t into n;
68+
suspend;
69+
end!
70+
71+
set term ;!
72+
```
73+
74+
## Semantics
75+
76+
Declared Local Temporary Tables have execution-frame data scope.
77+
78+
- The table structure is part of the compiled PSQL statement, procedure, function or trigger.
79+
- Rows are private to the current execution frame.
80+
- Rows are discarded when that execution frame finishes.
81+
- Transaction commit or rollback does not define the table lifetime; there is no `ON COMMIT` behavior.
82+
- An autonomous transaction block inside the same execution frame sees the same rows.
83+
- Local subroutines may access declared local temporary tables from the containing PSQL scope. They see and modify the
84+
rows of the current containing execution frame.
85+
- Local subroutines may also declare their own local temporary tables. Those rows are scoped to the subroutine execution
86+
frame.
87+
- Recursive calls reuse the same compiled table structure, but each recursive execution frame has separate rows.
88+
89+
Recursive example:
90+
91+
```sql
92+
set term !;
93+
94+
create procedure p_ltt_rec(d integer)
95+
returns (depth integer, cnt_before integer, cnt_after integer)
96+
as
97+
declare local temporary table t (
98+
id integer
99+
);
100+
begin
101+
insert into t values (:d);
102+
select count(*) from t into cnt_before;
103+
104+
if (d > 0) then
105+
begin
106+
for
107+
select depth, cnt_before, cnt_after
108+
from p_ltt_rec(:d - 1)
109+
into depth, cnt_before, cnt_after
110+
do
111+
suspend;
112+
end
113+
114+
select count(*) from t into cnt_after;
115+
depth = d;
116+
suspend;
117+
end!
118+
119+
set term ;!
120+
```
121+
122+
Each row returned by `p_ltt_rec(2)` reports `cnt_before = 1` and `cnt_after = 1`, because every recursive frame has
123+
its own table data.
124+
125+
Autonomous transaction example:
126+
127+
```sql
128+
set term !;
129+
130+
execute block returns (n integer)
131+
as
132+
declare local temporary table t (
133+
id integer
134+
);
135+
begin
136+
insert into t values (10);
137+
138+
in autonomous transaction do
139+
begin
140+
select count(*) from t into n;
141+
end
142+
143+
suspend;
144+
end!
145+
146+
set term ;!
147+
```
148+
149+
The autonomous block sees the row inserted by its parent execution frame.
150+
151+
Local subroutine example:
152+
153+
```sql
154+
set term !;
155+
156+
execute block returns (n integer, s integer)
157+
as
158+
declare local temporary table t (
159+
id integer
160+
);
161+
162+
declare procedure p_add(v integer)
163+
as
164+
begin
165+
insert into t values (:v);
166+
end
167+
168+
declare function f_count returns integer
169+
as
170+
declare variable ret integer;
171+
begin
172+
select count(*) from t into ret;
173+
return ret;
174+
end
175+
begin
176+
insert into t values (1);
177+
execute procedure p_add(2);
178+
179+
n = f_count();
180+
select sum(id) from t into s;
181+
suspend;
182+
end!
183+
184+
set term ;!
185+
```
186+
187+
The local procedure and function access `t` declared by the outer `EXECUTE BLOCK`. They use the same row set as the
188+
current outer execution frame.
189+
190+
## Visibility and Name Resolution
191+
192+
Declared Local Temporary Tables are visible only to SQL statements compiled inside the declaring PSQL scope and its
193+
local subroutines.
194+
195+
- Unqualified references to the declared name resolve to the declared local table.
196+
- SQL statements in local subroutines may also reference declared local temporary tables from the containing PSQL scope.
197+
- The declaration name cannot be schema-qualified or package-qualified.
198+
- The table is not present in `RDB$RELATIONS`, `RDB$RELATION_FIELDS`, monitoring metadata or other persistent metadata.
199+
- Dynamic SQL, including `EXECUTE STATEMENT`, is parsed separately and does not see declared local temporary tables.
200+
201+
The name shares the local declaration namespace. It cannot duplicate an input parameter, output parameter, local
202+
variable, cursor or another declared local table in the same declaration scope.
203+
204+
## Supported Operations
205+
206+
Declared Local Temporary Tables can be used by regular DML statements compiled in the same PSQL scope:
207+
208+
```sql
209+
insert into t (...) values (...);
210+
select ... from t ...;
211+
update t set ... where ...;
212+
delete from t where ...;
213+
```
214+
215+
They can be used in subqueries, joins and cursor loops like other record sources, subject to the restrictions below.
216+
217+
## Restrictions
218+
219+
Declared Local Temporary Tables intentionally support a small table definition surface.
220+
221+
Column restrictions:
222+
223+
- `DEFAULT` values are not supported.
224+
- `COMPUTED BY` columns are not supported.
225+
- `IDENTITY` columns are not supported.
226+
- Array columns are not supported.
227+
- Field-level `NOT NULL` is supported, but only as an unnamed constraint.
228+
229+
Constraint restrictions:
230+
231+
- Table constraints are not supported.
232+
- Primary keys are not supported.
233+
- Foreign keys are not supported.
234+
- Check constraints are not supported.
235+
- Named constraints are not supported.
236+
237+
Other restrictions:
238+
239+
- A single PSQL statement, procedure, function or trigger may declare at most 1024 local temporary tables.
240+
- Indexes are not supported.
241+
- Triggers on declared local temporary tables are not supported.
242+
- Explicit privileges are not supported.
243+
- `ALTER TABLE`, `DROP TABLE`, `CREATE INDEX`, `ALTER INDEX` and `DROP INDEX` are not valid for declared local
244+
temporary tables.
245+
- Declared local temporary tables cannot be referenced by persistent metadata objects outside the PSQL object where
246+
they are declared.
247+
248+
## Notes
249+
250+
Declared Local Temporary Tables are useful for temporary intermediate data that should not be exposed through database
251+
metadata and should be automatically isolated per PSQL execution frame.
252+
253+
Use SQL-created Local Temporary Tables when the temporary table definition must be created and addressed dynamically by
254+
the attachment. Use packaged temporary tables when the definition must be part of package metadata and visible through
255+
package name resolution.

src/dsql/DdlNodes.epp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10229,7 +10229,7 @@ void CreateRelationNode::defineLocalTempTable(thread_db* tdbb, DsqlCompilerScrat
1022910229
if (externalFile)
1023010230
status_exception::raise(Arg::Gds(isc_random) << "External file is not allowed for local temporary tables");
1023110231

10232-
if (attachment->att_local_temporary_tables.count() >= MAX_LTT_COUNT)
10232+
if (attachment->att_local_temporary_tables.count() >= MAX_CREATED_LTT_COUNT)
1023310233
{
1023410234
ERR_post(Arg::Gds(isc_imp_exc) <<
1023510235
Arg::Gds(isc_random) <<
@@ -10299,14 +10299,14 @@ void CreateRelationNode::defineLocalTempTable(thread_db* tdbb, DsqlCompilerScrat
1029910299
// Assign LTT relation ID
1030010300

1030110301
if (!attachment->att_next_ltt_id.has_value())
10302-
attachment->att_next_ltt_id = MAX_LTT_ID;
10302+
attachment->att_next_ltt_id = MAX_CREATED_LTT_ID;
1030310303

1030410304
const auto startLttId = attachment->att_next_ltt_id.value();
1030510305

1030610306
while (true)
1030710307
{
10308-
if (attachment->att_next_ltt_id.value() >= MAX_LTT_ID)
10309-
attachment->att_next_ltt_id = MIN_LTT_ID;
10308+
if (attachment->att_next_ltt_id.value() >= MAX_CREATED_LTT_ID)
10309+
attachment->att_next_ltt_id = MIN_CREATED_LTT_ID;
1031010310
else
1031110311
++attachment->att_next_ltt_id.value();
1031210312

src/dsql/DsqlCompilerScratch.cpp

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ void DsqlCompilerScratch::putLocalVariableInit(dsql_var* variable, const Declare
486486
// Put maps in subroutines for outer variables/parameters usage.
487487
void DsqlCompilerScratch::putOuterMaps()
488488
{
489-
if (!outerMessagesMap.count() && !outerVarsMap.count())
489+
if (outerMessagesMap.isEmpty() && outerVarsMap.isEmpty() && outerLocalTablesMap.isEmpty())
490490
return;
491491

492492
appendUChar(blr_outer_map);
@@ -505,6 +505,13 @@ void DsqlCompilerScratch::putOuterMaps()
505505
appendUShort(outer);
506506
}
507507

508+
for (auto& [outer, inner] : outerLocalTablesMap)
509+
{
510+
appendUChar(blr_outer_map_local_table);
511+
appendUShort(outer);
512+
appendUShort(inner);
513+
}
514+
508515
appendUChar(blr_end);
509516
}
510517

@@ -553,6 +560,41 @@ dsql_var* DsqlCompilerScratch::resolveVariable(const MetaName& varName)
553560
return NULL;
554561
}
555562

563+
DeclareLocalTableNode* DsqlCompilerScratch::getLocalTable(const MetaName& name, bool* outerDecl)
564+
{
565+
DeclareLocalTableNode* table = nullptr;
566+
localTableNames.get(name, table);
567+
568+
if (outerDecl)
569+
*outerDecl = false;
570+
571+
if (!table && mainScratch)
572+
{
573+
table = mainScratch->getLocalTable(name);
574+
575+
if (table && outerDecl)
576+
*outerDecl = true;
577+
}
578+
579+
return table;
580+
}
581+
582+
USHORT DsqlCompilerScratch::getOuterLocalTableNumber(USHORT tableNumber)
583+
{
584+
if (const auto innerNumber = outerLocalTablesMap.get(tableNumber))
585+
return *innerNumber;
586+
587+
const auto innerNumber = localTableNumber++;
588+
outerLocalTablesMap.put(tableNumber, innerNumber);
589+
590+
return innerNumber;
591+
}
592+
593+
void DsqlCompilerScratch::putLocalTable(DeclareLocalTableNode* table)
594+
{
595+
localTableNames.put(table->dsqlName, table);
596+
}
597+
556598
// Generate BLR for a return.
557599
void DsqlCompilerScratch::genReturn(bool eosFlag)
558600
{

src/dsql/DsqlCompilerScratch.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class DsqlCompilerScratch : public BlrDebugWriter
9797
labels(p),
9898
cursors(p),
9999
localTables(p),
100+
localTableNames(p),
100101
aliasRelationPrefix(p),
101102
package(p),
102103
currCtes(p),
@@ -106,6 +107,7 @@ class DsqlCompilerScratch : public BlrDebugWriter
106107
mainScratch(aMainScratch),
107108
outerMessagesMap(p),
108109
outerVarsMap(p),
110+
outerLocalTablesMap(p),
109111
ddlSchema(p),
110112
ctes(p),
111113
cteAliases(p),
@@ -198,6 +200,11 @@ class DsqlCompilerScratch : public BlrDebugWriter
198200
dsql_var* makeVariable(dsql_fld*, const char*, const dsql_var::Type type, USHORT,
199201
USHORT, std::optional<USHORT> = std::nullopt);
200202
dsql_var* resolveVariable(const MetaName& varName);
203+
204+
DeclareLocalTableNode* getLocalTable(const MetaName& name, bool* outerDecl = nullptr);
205+
USHORT getOuterLocalTableNumber(USHORT tableNumber);
206+
void putLocalTable(DeclareLocalTableNode* table);
207+
201208
void genReturn(bool eosFlag = false);
202209

203210
void genParameters(Firebird::Array<NestConst<ParameterClause> >& parameters,
@@ -323,6 +330,7 @@ class DsqlCompilerScratch : public BlrDebugWriter
323330
Firebird::Array<DeclareCursorNode*> cursors; // Cursors
324331
USHORT localTableNumber = 0; // Local table number
325332
Firebird::Array<DeclareLocalTableNode*> localTables; // Local tables
333+
Firebird::LeftPooledMap<MetaName, DeclareLocalTableNode*> localTableNames;
326334
USHORT inSelectList = 0; // now processing "select list"
327335
USHORT inWhereClause = 0; // processing "where clause"
328336
USHORT inGroupByClause = 0; // processing "group by clause"
@@ -350,6 +358,7 @@ class DsqlCompilerScratch : public BlrDebugWriter
350358
DsqlCompilerScratch* mainScratch = nullptr;
351359
Firebird::NonPooledMap<USHORT, USHORT> outerMessagesMap; // <outer, inner>
352360
Firebird::NonPooledMap<USHORT, USHORT> outerVarsMap; // <outer, inner>
361+
Firebird::NonPooledMap<USHORT, USHORT> outerLocalTablesMap; // <outer, inner>
353362
MetaName ddlSchema;
354363
Firebird::AutoPtr<Firebird::ObjectsArray<Firebird::MetaString>> cachedDdlSchemaSearchPath;
355364
dsql_msg* recordKeyMessage = nullptr; // Side message for positioned DML

0 commit comments

Comments
 (0)