Skip to content

Commit abd0c27

Browse files
authored
Feature #1181 - CREATE TABLE AS <query> (#9053)
1 parent 49b975e commit abd0c27

5 files changed

Lines changed: 353 additions & 2 deletions

File tree

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# `CREATE TABLE ... AS <query>` (FB 6.0)
2+
3+
Tables may be created from a query result using the following syntax:
4+
5+
```sql
6+
CREATE [{GLOBAL | LOCAL} TEMPORARY TABLE] [IF NOT EXISTS] TABLE <table name>
7+
[ (<column name> [, <column name> ...]) ]
8+
AS <query expression>
9+
[WITH [NO] DATA]
10+
[ON COMMIT {DELETE | PRESERVE} ROWS]
11+
```
12+
13+
The new table columns are derived from the query select list. If a column name list is specified, it must have
14+
the same number of columns as the query result.
15+
16+
If no column name list is specified, column names are taken from the query output names. Unnamed expressions must
17+
be explicitly aliased.
18+
19+
`WITH DATA` is the default and inserts the query result into the newly created table in the same transaction.
20+
`WITH NO DATA` creates only the table definition.
21+
22+
For global and local temporary tables, normal temporary-table data lifetime rules apply. Package temporary tables
23+
do not support this syntax.
24+
25+
## Character Sets
26+
27+
For `CHAR` and `VARCHAR` columns, the database default character set is not used. The character set and collation
28+
of the new column are copied from the corresponding query expression.
29+
30+
String literals use the connection character set, so columns created from string literals inherit the connection
31+
character set.
32+
33+
## Examples
34+
35+
```sql
36+
CREATE TABLE employee_copy AS
37+
SELECT emp_no, first_name, last_name
38+
FROM employee;
39+
40+
CREATE TABLE employee_names (id, full_name) AS
41+
SELECT emp_no, first_name || ' ' || last_name
42+
FROM employee
43+
WITH NO DATA;
44+
45+
CREATE GLOBAL TEMPORARY TABLE session_report AS
46+
SELECT emp_no, salary
47+
FROM employee
48+
WITH DATA
49+
ON COMMIT PRESERVE ROWS;
50+
51+
CREATE LOCAL TEMPORARY TABLE tx_work AS
52+
SELECT emp_no, salary
53+
FROM employee
54+
WITH NO DATA;
55+
```
56+
57+
## ISQL behavior
58+
59+
ISQL in AUTODDL mode (the default) uses separate transactions for DDL and DML statements.
60+
When `CREATE TABLE ... AS <query>` is executed with `WITH DATA`, the table creation and data population occur in the
61+
DDL transaction.
62+
63+
For regular tables, the inserted rows are not visible to the DML transaction until the DDL transaction is committed.
64+
For temporary tables, this behavior is even more surprising because the rows belong to the DDL transaction and are
65+
therefore not visible to the DML transaction at all.
66+
67+
For example:
68+
69+
```sql
70+
SQL> CREATE TABLE T1 AS SELECT 1 A FROM RDB$DATABASE;
71+
SQL> SELECT * FROM T1;
72+
73+
SQL> COMMIT;
74+
SQL> SELECT * FROM T1;
75+
76+
A
77+
============
78+
1
79+
```
80+
81+
With a temporary table:
82+
83+
```sql
84+
SQL> CREATE GLOBAL TEMPORARY TABLE T1 AS SELECT 1 A FROM RDB$DATABASE;
85+
SQL> SELECT * FROM T1;
86+
87+
SQL> COMMIT;
88+
SQL> SELECT * FROM T1;
89+
```
90+
91+
The table exists, but no rows are returned because the data was inserted in the DDL transaction and is not visible to
92+
the DML transaction.
93+
94+
To avoid this behavior, disable AUTODDL before executing the statement:
95+
96+
```sql
97+
SQL> SET AUTODDL OFF;
98+
99+
SQL> CREATE GLOBAL TEMPORARY TABLE T1 AS SELECT 1 A FROM RDB$DATABASE;
100+
SQL> SELECT * FROM T1;
101+
102+
A
103+
============
104+
1
105+
```
106+
107+
When AUTODDL is disabled, both the table creation and data population occur in the current transaction, making the
108+
inserted rows immediately visible.

src/dsql/DdlNodes.epp

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
#include "../jrd/vio_proto.h"
6161
#include "../jrd/idx_proto.h"
6262
#include "../dsql/ddl_proto.h"
63+
#include "../dsql/dsql_proto.h"
6364
#include "../dsql/errd_proto.h"
6465
#include "../dsql/gen_proto.h"
6566
#include "../dsql/make_proto.h"
@@ -9657,6 +9658,10 @@ string CreateRelationNode::internalPrint(NodePrinter& printer) const
96579658
NODE_PRINT(printer, externalFile);
96589659
NODE_PRINT(printer, tempFlag);
96599660
NODE_PRINT(printer, tempRowsFlag);
9661+
NODE_PRINT(printer, queryColumns);
9662+
NODE_PRINT(printer, querySelectExpr);
9663+
NODE_PRINT(printer, querySource);
9664+
NODE_PRINT(printer, withData);
96609665

96619666
return "CreateRelationNode";
96629667
}
@@ -9680,6 +9685,9 @@ void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScrat
96809685
if (createIfNotExistsOnly && !DYN_UTIL_check_unique_name_nothrow(tdbb, name, obj_relation))
96819686
return;
96829687

9688+
if (querySelectExpr)
9689+
defineQueryColumns(tdbb, dsqlScratch);
9690+
96839691
saveRelation(tdbb, dsqlScratch, name, false, true);
96849692

96859693
if (externalFile)
@@ -9712,6 +9720,9 @@ void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScrat
97129720
// Update DSQL cache
97139721
METD_drop_relation(transaction, name);
97149722

9723+
if (withData)
9724+
executeInsert(tdbb, dsqlScratch, transaction);
9725+
97159726
return;
97169727
}
97179728

@@ -9933,9 +9944,190 @@ void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScrat
99339944
// Update DSQL cache
99349945
METD_drop_relation(transaction, name);
99359946

9947+
if (withData)
9948+
executeInsert(tdbb, dsqlScratch, transaction);
9949+
99369950
savePoint.release(); // everything is ok
99379951
}
99389952

9953+
void CreateRelationNode::defineQueryColumns(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch)
9954+
{
9955+
fb_assert(querySelectExpr);
9956+
9957+
dsqlScratch->resetContextStack();
9958+
dsqlScratch->unionContext.clear();
9959+
dsqlScratch->derivedContext.clear();
9960+
9961+
const auto rse = PASS1_rse(dsqlScratch, querySelectExpr);
9962+
const auto items = rse->dsqlSelectList;
9963+
9964+
if (queryColumns && queryColumns->items.getCount() != items->items.getCount())
9965+
{
9966+
status_exception::raise(
9967+
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
9968+
Arg::Gds(isc_dsql_command_err) <<
9969+
Arg::Gds(isc_num_field_err));
9970+
}
9971+
9972+
ObjectsArray<MetaName> names;
9973+
FB_SIZE_T position = 0;
9974+
9975+
for (auto item : items->items)
9976+
{
9977+
MetaName fieldName;
9978+
9979+
if (queryColumns)
9980+
fieldName = nodeAs<FieldNode>(queryColumns->items[position])->dsqlName;
9981+
else
9982+
{
9983+
const ValueExprNode* nameNode = item;
9984+
const char* aliasName = nullptr;
9985+
9986+
while (nodeIs<DsqlAliasNode>(nameNode) || nodeIs<DerivedFieldNode>(nameNode) ||
9987+
nodeIs<DsqlMapNode>(nameNode) || nodeAs<PackageReferenceNode>(nameNode))
9988+
{
9989+
if (const auto aliasNode = nodeAs<DsqlAliasNode>(nameNode))
9990+
{
9991+
if (!aliasName)
9992+
aliasName = aliasNode->name.c_str();
9993+
9994+
nameNode = aliasNode->value;
9995+
}
9996+
else if (const auto mapNode = nodeAs<DsqlMapNode>(nameNode))
9997+
nameNode = mapNode->map->map_node;
9998+
else if (const auto derivedField = nodeAs<DerivedFieldNode>(nameNode))
9999+
{
10000+
if (!aliasName)
10001+
aliasName = derivedField->name.c_str();
10002+
10003+
nameNode = derivedField->value;
10004+
}
10005+
else if (const auto referenceNode = nodeAs<PackageReferenceNode>(nameNode))
10006+
{
10007+
if (!aliasName)
10008+
aliasName = referenceNode->getName();
10009+
10010+
nameNode = nullptr;
10011+
}
10012+
}
10013+
10014+
if (aliasName)
10015+
fieldName = aliasName;
10016+
else if (const auto fieldNode = nodeAs<FieldNode>(nameNode))
10017+
fieldName = fieldNode->dsqlField->fld_name;
10018+
else
10019+
{
10020+
status_exception::raise(
10021+
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
10022+
Arg::Gds(isc_dsql_command_err) <<
10023+
Arg::Gds(isc_specify_field_err));
10024+
}
10025+
}
10026+
10027+
for (const auto& name : names)
10028+
{
10029+
if (name == fieldName)
10030+
{
10031+
status_exception::raise(
10032+
Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
10033+
Arg::Gds(isc_dsql_command_err) <<
10034+
Arg::Gds(isc_dsql_col_more_than_once_view) << Arg::Str(fieldName.c_str()));
10035+
}
10036+
}
10037+
10038+
names.add() = fieldName;
10039+
10040+
dsc desc;
10041+
DsqlDescMaker::fromNode(dsqlScratch, &desc, item);
10042+
10043+
if (!desc.dsc_dtype)
10044+
{
10045+
status_exception::raise(
10046+
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
10047+
Arg::Gds(isc_dsql_command_err) <<
10048+
Arg::Gds(isc_dsql_datatype_err));
10049+
}
10050+
10051+
auto clause = FB_NEW_POOL(dsqlScratch->getPool()) AddColumnClause(dsqlScratch->getPool());
10052+
clause->field = FB_NEW_POOL(dsqlScratch->getPool()) dsql_fld(dsqlScratch->getPool());
10053+
10054+
auto field = clause->field;
10055+
field->fld_name = fieldName;
10056+
field->dtype = desc.dsc_dtype;
10057+
field->length = desc.dsc_length;
10058+
field->scale = desc.dsc_scale;
10059+
field->subType = desc.dsc_sub_type;
10060+
10061+
if (desc.dsc_flags & DSC_nullable)
10062+
field->flags |= FLD_nullable;
10063+
10064+
if (desc.isText() || (desc.isBlob() && desc.getBlobSubType() == isc_blob_text))
10065+
{
10066+
field->charSetId = desc.getCharSet();
10067+
field->collationId = desc.getCollation();
10068+
}
10069+
10070+
if (desc.isText())
10071+
{
10072+
const USHORT adjust = (desc.dsc_dtype == dtype_varying) ? sizeof(USHORT) : 0;
10073+
const USHORT bpc = METD_get_charset_bpc(dsqlScratch->getTransaction(), field->charSetId.value_or(CS_NONE));
10074+
field->charLength = (field->length - adjust) / bpc;
10075+
}
10076+
else if (desc.isBlob())
10077+
field->segLength = 80;
10078+
10079+
field->setExactPrecision();
10080+
clauses.add(clause);
10081+
10082+
++position;
10083+
}
10084+
10085+
dsqlScratch->resetContextStack();
10086+
dsqlScratch->unionContext.clear();
10087+
dsqlScratch->derivedContext.clear();
10088+
}
10089+
10090+
void CreateRelationNode::executeInsert(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
10091+
jrd_tra* transaction)
10092+
{
10093+
string sql;
10094+
sql.printf("insert into %s (", name.toQuotedString().c_str());
10095+
10096+
bool first = true;
10097+
10098+
for (const auto& clause : clauses)
10099+
{
10100+
if (clause->type != Clause::TYPE_ADD_COLUMN)
10101+
continue;
10102+
10103+
const auto addColumnClause = static_cast<const AddColumnClause*>(clause.getObject());
10104+
10105+
if (!first)
10106+
sql += ", ";
10107+
10108+
first = false;
10109+
sql += addColumnClause->field->fld_name.toQuotedString();
10110+
}
10111+
10112+
sql += ") ";
10113+
sql += querySource;
10114+
10115+
jrd_tra* traHandle = transaction;
10116+
const auto attachment = tdbb->getAttachment();
10117+
10118+
AutoSetRestoreFlag autoLttReferences(&dsqlScratch->flags,
10119+
DsqlCompilerScratch::FLAG_ALLOW_CREATED_LTT_REFERENCE, tempFlag == REL_temp_ltt);
10120+
10121+
// Clear TDBB_use_db_page_space so the INSERT uses per-transaction temp pages for GTT/LTT,
10122+
// not the structural base pages that were used during table creation.
10123+
AutoSetRestoreFlag<ULONG> noDbPageSpace(&tdbb->tdbb_flags, TDBB_use_db_page_space, false);
10124+
10125+
DSQL_execute_immediate(tdbb, attachment, &traHandle, sql.length(), sql.c_str(),
10126+
dsqlScratch->clientDialect, nullptr, nullptr, nullptr, nullptr, true);
10127+
10128+
fb_assert(traHandle == transaction);
10129+
}
10130+
993910131
// Starting from the elements in a table definition, locate the PK columns if given in a
994010132
// separate table constraint declaration.
994110133
const ObjectsArray<MetaName>* CreateRelationNode::findPkColumns()

src/dsql/DdlNodes.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1762,7 +1762,8 @@ class CreateRelationNode final : public RelationNode
17621762
CreateRelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode,
17631763
const Firebird::string* aExternalFile = NULL)
17641764
: RelationNode(p, aDsqlNode),
1765-
externalFile(aExternalFile)
1765+
externalFile(aExternalFile),
1766+
querySource(p)
17661767
{
17671768
}
17681769

@@ -1798,12 +1799,18 @@ class CreateRelationNode final : public RelationNode
17981799

17991800
private:
18001801
const Firebird::ObjectsArray<MetaName>* findPkColumns();
1802+
void defineQueryColumns(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch);
1803+
void executeInsert(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction);
18011804
void defineLocalTempTable(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction);
18021805

18031806
public:
18041807
const Firebird::string* externalFile;
18051808
bool createIfNotExistsOnly = false;
18061809
bool packagePrivate = false;
1810+
NestConst<ValueListNode> queryColumns;
1811+
NestConst<SelectExprNode> querySelectExpr;
1812+
Firebird::string querySource;
1813+
bool withData = false;
18071814
};
18081815

18091816

src/dsql/parse-conflicts.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
147 shift/reduce conflicts, 7 reduce/reduce conflicts.
1+
152 shift/reduce conflicts, 7 reduce/reduce conflicts.

0 commit comments

Comments
 (0)