Skip to content

Commit 27ae3df

Browse files
authored
Feature #1095 - LOCAL TEMPORARY TABLE (#8860)
* Feature #1095 - LOCAL TEMPORARY TABLE * Changes after Vlad's review * Add tables MON$LOCAL_TEMPORARY_TABLES and MON$LOCAL_TEMPORARY_TABLE_FIELDS * Fix problem with ISQL's set per_tab * Rename MON$LOCAL_TEMPORARY_TABLE_FIELDS to MON$LOCAL_TEMPORARY_TABLE_COLUMNS
1 parent 3d06c43 commit 27ae3df

52 files changed

Lines changed: 3107 additions & 442 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

doc/README.monitoring_tables

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@ Monitoring tables
242242
4: call
243243
- MON$TABLE_NAME (table name)
244244
- MON$RECORD_STAT_ID (record-level statistics ID, refers to MON$RECORD_STATS)
245+
- MON$TABLE_TYPE (table type)
246+
PERSISTENT: Regular persistent table
247+
GLOBAL TEMPORARY: Global temporary table
248+
LOCAL TEMPORARY: Local temporary table
245249

246250
MON$COMPILED_STATEMENTS (compiled statements)
247251
- MON$COMPILED_STATEMENT_ID (compiled statement ID)

doc/sql.extensions/README.ddl.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,7 @@ The following statements are supported:
691691
CREATE EXCEPTION [IF NOT EXISTS] ...
692692
CREATE INDEX [IF NOT EXISTS] ...
693693
CREATE PROCEDURE [IF NOT EXISTS] ...
694-
CREATE TABLE [IF NOT EXISTS] ...
694+
CREATE [{GLOBAL | LOCAL} TEMPORARY TABLE] TABLE [IF NOT EXISTS] ...
695695
CREATE TRIGGER [IF NOT EXISTS] ...
696696
CREATE VIEW [IF NOT EXISTS] ...
697697
CREATE FILTER [IF NOT EXISTS] ...
Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
# Created Local Temporary Tables (FB 6.0)
2+
3+
Firebird 6.0 introduces support for SQL Created Local Temporary Tables (LTT). Unlike Global Temporary Tables (GTT),
4+
which have permanent metadata stored in the system catalogue, Local Temporary Tables exist only within the connection
5+
that created them. The table definition is private to the creating connection and is automatically discarded when the
6+
connection ends. The data lifecycle depends on the `ON COMMIT` clause: with `ON COMMIT DELETE ROWS` (the default), data
7+
is private to each transaction and deleted when the transaction ends; with `ON COMMIT PRESERVE ROWS`, data is shared
8+
across all transactions in the connection and persists until the connection ends.
9+
10+
## Why Local Temporary Tables?
11+
12+
Local Temporary Tables are useful in scenarios where you need temporary storage without affecting the database metadata:
13+
14+
### Temporary storage in read-only databases
15+
16+
Since LTT definitions are not stored in the database, they can be created and used even when the database is read-only.
17+
18+
Global Temporary Tables (GTTs), on the other hand, cannot be created in read-only databases because their creation
19+
requires metadata changes. Regarding usage (DML operations), only ON DELETE ROWS GTTs are allowed in a read-only
20+
database.
21+
22+
### Session-private definitions
23+
24+
Each connection can create its own temporary tables with the same names without conflicts. The table definitions are
25+
completely isolated between connections.
26+
27+
### Ad-hoc temporary storage
28+
29+
LTTs provide a quick way to create temporary storage during a session for intermediate results, data transformations,
30+
or other temporary processing needs without leaving any trace in the database after disconnection.
31+
32+
## Comparison with Global Temporary Tables
33+
34+
| Feature | GTT | LTT |
35+
|-----------------------------|--------------------------------------|---------------------------------------|
36+
| Metadata storage | System tables (`RDB$RELATIONS`, etc.)| Connection memory only |
37+
| Visibility of definition | All connections | Creating connection only |
38+
| Persistence of definition | Permanent (until explicitly dropped) | Until connection ends |
39+
| Creation in read-only DB | No | Yes |
40+
| Used in read-only DB | ON DELETE ROWS only | ON COMMIT ROWS / ON DELETE ROWS |
41+
| Schema support | Yes | Yes |
42+
| Indexes | Full support | Basic support (no expression/partial) |
43+
| DDL triggers | Fire on CREATE/DROP/ALTER | Do not fire |
44+
| DML triggers support | Yes | Not supported |
45+
| Field level NOT NULL | Supported | Supported |
46+
| PK, FK, CHECK constraints | Supported | Not supported |
47+
| Named constraints | Supported | Not supported |
48+
| Explicit privileges | Supported | Not supported |
49+
50+
## Syntax
51+
52+
### CREATE LOCAL TEMPORARY TABLE
53+
54+
```sql
55+
{CREATE | RECREATE} LOCAL TEMPORARY TABLE [IF NOT EXISTS] [<schema name>.]<table name>
56+
(<column definitions>)
57+
[ON COMMIT {DELETE | PRESERVE} ROWS]
58+
```
59+
60+
The `ON COMMIT` clause determines the data lifecycle:
61+
62+
- `ON COMMIT DELETE ROWS` (default): Data is private to each transaction and deleted when the transaction ends.
63+
- `ON COMMIT PRESERVE ROWS`: Data persists until the connection ends.
64+
65+
Example:
66+
67+
```sql
68+
-- Create a simple LTT with default ON COMMIT DELETE ROWS
69+
create local temporary table temp_results (
70+
id integer not null,
71+
val varchar(100)
72+
);
73+
74+
-- Create an LTT that preserves data across transactions
75+
create local temporary table session_cache (
76+
key varchar(50) not null,
77+
data blob
78+
) on commit preserve rows;
79+
80+
-- Create an LTT in a specific schema
81+
create local temporary table my_schema.work_table (
82+
x integer,
83+
y integer
84+
);
85+
```
86+
87+
### ALTER TABLE
88+
89+
Local Temporary Tables support the following `ALTER TABLE` operations:
90+
91+
```sql
92+
-- Add a column
93+
ALTER TABLE <ltt name> ADD <column name> <data type> [NOT NULL];
94+
95+
-- Drop a column
96+
ALTER TABLE <ltt name> DROP <column name>;
97+
98+
-- Rename a column
99+
ALTER TABLE <ltt name> ALTER COLUMN <old name> TO <new name>;
100+
101+
-- Change column position
102+
ALTER TABLE <ltt name> ALTER COLUMN <column name> POSITION <new position>;
103+
104+
-- Change nullability
105+
ALTER TABLE <ltt name> ALTER COLUMN <column name> {DROP | SET} NOT NULL;
106+
107+
-- Change column type
108+
ALTER TABLE <ltt name> ALTER COLUMN <column name> TYPE <new data type>;
109+
```
110+
111+
Example:
112+
113+
```sql
114+
create local temporary table temp_data (id integer);
115+
116+
alter table temp_data add name varchar(50) not null;
117+
alter table temp_data alter column name position 1;
118+
alter table temp_data alter column name to full_name;
119+
alter table temp_data alter column id type bigint;
120+
```
121+
122+
### DROP TABLE
123+
124+
```sql
125+
DROP TABLE [IF EXISTS] [<schema name>.]<ltt name>
126+
```
127+
128+
The same `DROP TABLE` statement is used for both regular tables and Local Temporary Tables. If the table name matches
129+
an LTT in the current connection, the LTT is dropped.
130+
131+
### CREATE INDEX
132+
133+
```sql
134+
CREATE [UNIQUE] [ASC[ENDING] | DESC[ENDING]] INDEX [IF NOT EXISTS] [<schema name>.]<index name>
135+
ON [<schema name>.]<ltt name> (<column list>)
136+
```
137+
138+
Example:
139+
140+
```sql
141+
create local temporary table temp_orders (
142+
order_id integer not null,
143+
customer_id integer,
144+
order_date date
145+
);
146+
147+
create unique index idx_temp_orders_pk on temp_orders (order_id);
148+
create descending index idx_temp_orders_date on temp_orders (order_date);
149+
create index idx_temp_orders_cust on temp_orders (customer_id);
150+
```
151+
152+
### ALTER INDEX
153+
154+
```sql
155+
ALTER INDEX <index name> {ACTIVE | INACTIVE}
156+
```
157+
158+
Indexes on LTTs can be deactivated and reactivated, which may be useful when performing bulk inserts in
159+
`ON COMMIT PRESERVE ROWS` tables.
160+
161+
### DROP INDEX
162+
163+
```sql
164+
DROP INDEX [IF EXISTS] <index name>
165+
```
166+
167+
## Limitations
168+
169+
Local Temporary Tables have the following restrictions:
170+
171+
### Column restrictions
172+
173+
- **DEFAULT values**: Columns cannot have default values.
174+
- **COMPUTED BY columns**: Computed columns are not supported.
175+
- **IDENTITY columns**: Identity (auto-increment) columns are not supported.
176+
- **ARRAY types**: Array type columns are not supported.
177+
- **Domain changes**: Columns can be defined using domains, but changes to the domain definition are not propagated to
178+
existing LTT columns. The column retains the domain's characteristics as they were at the time the column was created.
179+
180+
### Constraint restrictions
181+
182+
- **PRIMARY KEY**: Primary key constraints are not supported.
183+
- **FOREIGN KEY**: Foreign key constraints are not supported.
184+
- **CHECK constraints**: Check constraints are not supported.
185+
- **NOT NULL constraints**: Only unnamed NOT NULL constraints are supported.
186+
187+
### Index restrictions
188+
189+
- **Expression-based indexes**: Indexes based on expressions are not supported.
190+
- **Partial indexes**: Partial (filtered) indexes are not supported.
191+
192+
### Other restrictions
193+
194+
- **EXTERNAL FILE**: LTTs cannot be linked to external files.
195+
- **SQL SECURITY clause**: The SQL SECURITY clause is not applicable to LTTs.
196+
- **Max number of LTTs**: The maximum number of active Local Temporary Tables per connection is 1024.
197+
- **Persistent metadata references**: LTTs cannot be directly referenced in stored procedures, triggers, views, or
198+
other persistent database objects. Attempting to do so will raise an error. However, LTTs can be accessed via
199+
`EXECUTE STATEMENT` inside persistent objects, since the SQL text is parsed at runtime.
200+
201+
## Data Lifecycle
202+
203+
### ON COMMIT DELETE ROWS
204+
205+
When a Local Temporary Table is created with `ON COMMIT DELETE ROWS` (the default), data is private to each transaction
206+
and automatically deleted when the transaction ends, whether by commit or rollback. Since Firebird supports multiple
207+
concurrent transactions within the same connection, each transaction has its own isolated view of the data.
208+
209+
```sql
210+
create local temporary table temp_work (id integer);
211+
212+
insert into temp_work values (1);
213+
insert into temp_work values (2);
214+
select count(*) from temp_work; -- Returns 2
215+
216+
commit;
217+
218+
select count(*) from temp_work; -- Returns 0 (data was deleted)
219+
```
220+
221+
### ON COMMIT PRESERVE ROWS
222+
223+
When created with `ON COMMIT PRESERVE ROWS`, data persists across transaction boundaries and remains available until
224+
the connection ends.
225+
226+
```sql
227+
create local temporary table session_data (id integer) on commit preserve rows;
228+
229+
insert into session_data values (1);
230+
commit;
231+
232+
insert into session_data values (2);
233+
commit;
234+
235+
select count(*) from session_data; -- Returns 2 (data preserved across commits)
236+
```
237+
238+
### COMMIT/ROLLBACK RETAINING
239+
240+
Similar to Global Temporary Tables, `COMMIT RETAINING` and `ROLLBACK RETAINING` preserve the data in LTTs with
241+
`ON COMMIT DELETE ROWS`.
242+
243+
## Transactional DDL
244+
245+
DDL operations on Local Temporary Tables behave the same as DDL on persistent tables with respect to transactions.
246+
Changes to the table structure (CREATE, ALTER, DROP) are only made permanent when the transaction commits. If the
247+
transaction is rolled back, any LTT structural changes made within that transaction are undone.
248+
249+
Savepoints are also supported. If a savepoint is rolled back, any LTT changes made after that savepoint are undone.
250+
251+
```sql
252+
set autoddl off; -- ISQL feature
253+
254+
create local temporary table t1 (id integer);
255+
256+
savepoint sp1;
257+
258+
alter table t1 add name varchar(50);
259+
260+
rollback to savepoint sp1;
261+
262+
-- The ALTER TABLE is undone; column 'name' does not exist
263+
```
264+
265+
This applies to all DDL operations on LTTs.
266+
267+
## Schema Integration
268+
269+
Local Temporary Tables are schema-bound objects, just like regular tables. They follow the same schema resolution
270+
rules:
271+
272+
- When creating an LTT without a schema qualifier, it is created in the current schema (the first valid schema in the
273+
search path).
274+
- When referencing an LTT without a schema qualifier, the search path is used to resolve the name.
275+
- Index names for LTTs must be unique within the schema and cannot conflict with persistent index names.
276+
277+
```sql
278+
set search_path to my_schema;
279+
280+
-- Creates LTT in my_schema
281+
create local temporary table temp_data (id integer);
282+
283+
-- References my_schema.temp_data
284+
select * from temp_data;
285+
286+
-- Explicit schema qualification
287+
create local temporary table other_schema.temp_work (x integer);
288+
```
289+
290+
## Monitoring
291+
292+
Since Local Temporary Tables are private to the connection and not stored in the system catalogue, they are not visible
293+
in `RDB$RELATIONS` or `RDB$RELATION_FIELDS`. Instead, two new monitoring tables are provided to inspect LTTs in the
294+
database.
295+
296+
### MON$LOCAL_TEMPORARY_TABLES
297+
298+
Provides information about active Local Temporary Tables across all connections.
299+
300+
| Column Name | Data Type | Description |
301+
|---------------------|--------------|----------------------------------------------------------|
302+
| MON$ATTACHMENT_ID | BIGINT | Attachment ID |
303+
| MON$TABLE_ID | INTEGER | Internal table ID (unique within the connection) |
304+
| MON$TABLE_NAME | CHAR(63) | Table name |
305+
| MON$SCHEMA_NAME | CHAR(63) | Schema name |
306+
| MON$TABLE_TYPE | VARCHAR(32) | Table type (`PRESERVE ROWS` or `DELETE ROWS`) |
307+
308+
### MON$LOCAL_TEMPORARY_TABLE_COLUMNS
309+
310+
Provides information about the columns of active Local Temporary Tables across all connections.
311+
312+
| Column Name | Data Type | Description |
313+
|--------------------------|-------------|-------------------------------------------------------|
314+
| MON$ATTACHMENT_ID | BIGINT | Attachment ID |
315+
| MON$TABLE_ID | INTEGER | Internal table ID |
316+
| MON$TABLE_NAME | CHAR(63) | Table name |
317+
| MON$SCHEMA_NAME | CHAR(63) | Schema name |
318+
| MON$FIELD_NAME | CHAR(63) | Field name |
319+
| MON$FIELD_POSITION | SMALLINT | Field position |
320+
| MON$FIELD_TYPE | SMALLINT | Field type |
321+
| MON$NOT_NULL | SMALLINT | Nullability flag (1 for NOT NULL, 0 for NULL) |
322+
| MON$CHARACTER_SET_ID | SMALLINT | Character set ID |
323+
| MON$COLLATION_ID | SMALLINT | Collation ID |
324+
| MON$FIELD_LENGTH | SMALLINT | Field length |
325+
| MON$FIELD_SCALE | SMALLINT | Field scale |
326+
| MON$FIELD_PRECISION | SMALLINT | Field precision |
327+
| MON$FIELD_SUB_TYPE | SMALLINT | Field sub-type |
328+
| MON$CHAR_LENGTH | INTEGER | Character length |
329+
330+
Example:
331+
332+
```sql
333+
-- See all LTTs in the database
334+
select * from MON$LOCAL_TEMPORARY_TABLES;
335+
336+
-- See fields of a specific LTT in the current connection
337+
select *
338+
from MON$LOCAL_TEMPORARY_TABLE_COLUMNS
339+
where MON$TABLE_NAME = 'TEMP_DATA' and MON$ATTACHMENT_ID = CURRENT_CONNECTION;
340+
```
341+
342+
## Implementation Notes
343+
344+
Local Temporary Tables store their data and indexes in temporary files, similar to Global Temporary Tables. Each
345+
connection has its own temporary file space for LTT data.
346+
347+
When a connection ends (either normally or due to an error), all Local Temporary Tables created by that connection
348+
are automatically discarded along with their data. No explicit cleanup is required.

0 commit comments

Comments
 (0)