Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
55 changes: 55 additions & 0 deletions doc/sql.extensions/README.create_table_as_query.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# `CREATE TABLE ... AS <query>` (FB 6.0)

Tables may be created from a query result using the following syntax:

```sql
CREATE [{GLOBAL | LOCAL} TEMPORARY TABLE] [IF NOT EXISTS] TABLE <table name>
[ (<column name> [, <column name> ...]) ]
AS <query expression>
[WITH [NO] DATA]
[ON COMMIT {DELETE | PRESERVE} ROWS]
```

The new table columns are derived from the query select list. If a column name list is specified, it must have
the same number of columns as the query result.

If no column name list is specified, column names are taken from the query output names. Unnamed expressions must
be explicitly aliased.

`WITH DATA` is the default and inserts the query result into the newly created table. `WITH NO DATA` creates only
the table definition.

For global and local temporary tables, normal temporary-table data lifetime rules apply. Package temporary tables
do not support this syntax.

## Character Sets

For `CHAR` and `VARCHAR` columns, the database default character set is not used. The character set and collation
of the new column are copied from the corresponding query expression.

String literals use the connection character set, so columns created from string literals inherit the connection
character set.

## Examples

```sql
CREATE TABLE employee_copy AS
SELECT emp_no, first_name, last_name
FROM employee;

CREATE TABLE employee_names (id, full_name) AS
SELECT emp_no, first_name || ' ' || last_name
FROM employee
WITH NO DATA;

CREATE GLOBAL TEMPORARY TABLE session_report AS
SELECT emp_no, salary
FROM employee
WITH DATA
ON COMMIT PRESERVE ROWS;

CREATE LOCAL TEMPORARY TABLE tx_work AS
SELECT emp_no, salary
FROM employee
WITH NO DATA;
```
188 changes: 188 additions & 0 deletions src/dsql/DdlNodes.epp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
#include "../jrd/vio_proto.h"
#include "../jrd/idx_proto.h"
#include "../dsql/ddl_proto.h"
#include "../dsql/dsql_proto.h"
#include "../dsql/errd_proto.h"
#include "../dsql/gen_proto.h"
#include "../dsql/make_proto.h"
Expand Down Expand Up @@ -9573,6 +9574,10 @@ string CreateRelationNode::internalPrint(NodePrinter& printer) const
NODE_PRINT(printer, externalFile);
NODE_PRINT(printer, tempFlag);
NODE_PRINT(printer, tempRowsFlag);
NODE_PRINT(printer, queryColumns);
NODE_PRINT(printer, querySelectExpr);
NODE_PRINT(printer, querySource);
NODE_PRINT(printer, withData);

return "CreateRelationNode";
}
Expand All @@ -9596,6 +9601,9 @@ void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScrat
if (createIfNotExistsOnly && !DYN_UTIL_check_unique_name_nothrow(tdbb, name, obj_relation))
return;

if (querySelectExpr)
defineQueryColumns(tdbb, dsqlScratch);

saveRelation(tdbb, dsqlScratch, name, false, true);

if (externalFile)
Expand Down Expand Up @@ -9628,6 +9636,9 @@ void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScrat
// Update DSQL cache
METD_drop_relation(transaction, name);

if (withData)
executeInsert(tdbb, dsqlScratch, transaction);

return;
}

Expand Down Expand Up @@ -9849,9 +9860,186 @@ void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScrat
// Update DSQL cache
METD_drop_relation(transaction, name);

if (withData)
executeInsert(tdbb, dsqlScratch, transaction);

savePoint.release(); // everything is ok
}

void CreateRelationNode::defineQueryColumns(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch)
{
fb_assert(querySelectExpr);

dsqlScratch->resetContextStack();
dsqlScratch->unionContext.clear();
dsqlScratch->derivedContext.clear();

const auto rse = PASS1_rse(dsqlScratch, querySelectExpr);
const auto items = rse->dsqlSelectList;

if (queryColumns && queryColumns->items.getCount() != items->items.getCount())
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_num_field_err));
}

ObjectsArray<MetaName> names;
FB_SIZE_T position = 0;

for (auto item : items->items)
{
MetaName fieldName;

if (queryColumns)
fieldName = nodeAs<FieldNode>(queryColumns->items[position])->dsqlName;
else
{
const ValueExprNode* nameNode = item;
const char* aliasName = nullptr;

while (nodeIs<DsqlAliasNode>(nameNode) || nodeIs<DerivedFieldNode>(nameNode) ||
nodeIs<DsqlMapNode>(nameNode) || nodeAs<PackageReferenceNode>(nameNode))
{
if (const auto aliasNode = nodeAs<DsqlAliasNode>(nameNode))
{
if (!aliasName)
aliasName = aliasNode->name.c_str();

nameNode = aliasNode->value;
}
else if (const auto mapNode = nodeAs<DsqlMapNode>(nameNode))
nameNode = mapNode->map->map_node;
else if (const auto derivedField = nodeAs<DerivedFieldNode>(nameNode))
{
if (!aliasName)
aliasName = derivedField->name.c_str();

nameNode = derivedField->value;
}
else if (const auto referenceNode = nodeAs<PackageReferenceNode>(nameNode))
{
if (!aliasName)
aliasName = referenceNode->getName();

nameNode = nullptr;
}
}

if (aliasName)
fieldName = aliasName;
else if (const auto fieldNode = nodeAs<FieldNode>(nameNode))
fieldName = fieldNode->dsqlField->fld_name;
else
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_specify_field_err));
}
}

for (const auto& name : names)
{
if (name == fieldName)
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_dsql_col_more_than_once_view) << Arg::Str(fieldName.c_str()));
}
}

names.add() = fieldName;

dsc desc;
DsqlDescMaker::fromNode(dsqlScratch, &desc, item);

if (!desc.dsc_dtype)
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_dsql_datatype_err));
}

auto clause = FB_NEW_POOL(dsqlScratch->getPool()) AddColumnClause(dsqlScratch->getPool());
clause->field = FB_NEW_POOL(dsqlScratch->getPool()) dsql_fld(dsqlScratch->getPool());

auto field = clause->field;
field->fld_name = fieldName;
field->dtype = desc.dsc_dtype;
field->length = desc.dsc_length;
field->scale = desc.dsc_scale;
field->subType = desc.dsc_sub_type;

if (desc.dsc_flags & DSC_nullable)
field->flags |= FLD_nullable;

if (desc.isText() || (desc.isBlob() && desc.getBlobSubType() == isc_blob_text))
{
field->charSetId = desc.getCharSet();
field->collationId = desc.getCollation();
}

if (desc.isText())
{
const USHORT adjust = (desc.dsc_dtype == dtype_varying) ? sizeof(USHORT) : 0;
const USHORT bpc = METD_get_charset_bpc(dsqlScratch->getTransaction(), field->charSetId.value_or(CS_NONE));
field->charLength = (field->length - adjust) / bpc;
}
else if (desc.isBlob())
field->segLength = 80;

field->setExactPrecision();
clauses.add(clause);

++position;
}

dsqlScratch->resetContextStack();
dsqlScratch->unionContext.clear();
dsqlScratch->derivedContext.clear();
}

void CreateRelationNode::executeInsert(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
string sql;
sql.printf("insert into %s (", name.toQuotedString().c_str());

bool first = true;

for (const auto& clause : clauses)
{
if (clause->type != Clause::TYPE_ADD_COLUMN)
continue;

const auto addColumnClause = static_cast<const AddColumnClause*>(clause.getObject());

if (!first)
sql += ", ";

first = false;
sql += addColumnClause->field->fld_name.toQuotedString();
}

sql += ") ";
sql += querySource;

jrd_tra* traHandle = transaction;
const auto attachment = tdbb->getAttachment();

AutoSetRestoreFlag autoLttReferences(&dsqlScratch->flags,
DsqlCompilerScratch::FLAG_ALLOW_CREATED_LTT_REFERENCE, tempFlag == REL_temp_ltt);

DSQL_execute_immediate(tdbb, attachment, &traHandle, sql.length(), sql.c_str(),
dsqlScratch->clientDialect, nullptr, nullptr, nullptr, nullptr, true);

fb_assert(traHandle == transaction);
}

// Starting from the elements in a table definition, locate the PK columns if given in a
// separate table constraint declaration.
const ObjectsArray<MetaName>* CreateRelationNode::findPkColumns()
Expand Down
9 changes: 8 additions & 1 deletion src/dsql/DdlNodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -1767,7 +1767,8 @@ class CreateRelationNode final : public RelationNode
CreateRelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode,
const Firebird::string* aExternalFile = NULL)
: RelationNode(p, aDsqlNode),
externalFile(aExternalFile)
externalFile(aExternalFile),
querySource(p)
{
}

Expand Down Expand Up @@ -1803,12 +1804,18 @@ class CreateRelationNode final : public RelationNode

private:
const Firebird::ObjectsArray<MetaName>* findPkColumns();
void defineQueryColumns(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch);
void executeInsert(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction);
void defineLocalTempTable(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction);

public:
const Firebird::string* externalFile;
bool createIfNotExistsOnly = false;
bool packagePrivate = false;
NestConst<ValueListNode> queryColumns;
NestConst<SelectExprNode> querySelectExpr;
Firebird::string querySource;
bool withData = false;
};


Expand Down
2 changes: 1 addition & 1 deletion src/dsql/parse-conflicts.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
137 shift/reduce conflicts, 7 reduce/reduce conflicts.
142 shift/reduce conflicts, 7 reduce/reduce conflicts.
42 changes: 42 additions & 0 deletions src/dsql/parse.y
Original file line number Diff line number Diff line change
Expand Up @@ -2419,6 +2419,22 @@ table_clause
{
$$ = $3;
}
| simple_table_name column_parens_opt AS select_expr with_data_opt
{
const auto node = newNode<CreateRelationNode>($1);
node->queryColumns = $2;
node->querySelectExpr = $4;
node->querySource = makeParseStr(YYPOSNARG(4), YYPOSNARG(4));
node->withData = $5;
$$ = node;
}
;

%type <boolVal> with_data_opt
with_data_opt
: /* nothing */ { $$ = true; }
| WITH DATA { $$ = true; }
| WITH NO DATA { $$ = false; }
;

%type table_attributes(<relationNode>)
Expand Down Expand Up @@ -2458,6 +2474,19 @@ gtt_table_clause
{
$$ = $2;
}
| simple_table_name column_parens_opt AS select_expr with_data_opt
{
$<createRelationNode>$ = newNode<CreateRelationNode>($1);
$<createRelationNode>$->tempFlag = REL_temp_gtt;
$<createRelationNode>$->queryColumns = $2;
$<createRelationNode>$->querySelectExpr = $4;
$<createRelationNode>$->querySource = makeParseStr(YYPOSNARG(4), YYPOSNARG(4));
$<createRelationNode>$->withData = $5;
}
gtt_subclauses_opt($6)
{
$$ = $6;
}
;

%type gtt_subclauses_opt(<createRelationNode>)
Expand Down Expand Up @@ -2498,6 +2527,19 @@ ltt_table_clause
{
$$ = $2;
}
| simple_table_name column_parens_opt AS select_expr with_data_opt
{
$<createRelationNode>$ = newNode<CreateRelationNode>($1);
$<createRelationNode>$->tempFlag = REL_temp_ltt;
$<createRelationNode>$->queryColumns = $2;
$<createRelationNode>$->querySelectExpr = $4;
$<createRelationNode>$->querySource = makeParseStr(YYPOSNARG(4), YYPOSNARG(4));
$<createRelationNode>$->withData = $5;
}
ltt_subclause_opt($6)
{
$$ = $6;
}
;

%type <createRelationNode> packaged_table_clause
Expand Down
Loading