Skip to content

Commit 6c797c7

Browse files
committed
Copy source query exact datatypes when possible in CREATE TABLE AS SELECT.
1 parent afb9dc8 commit 6c797c7

3 files changed

Lines changed: 125 additions & 57 deletions

File tree

doc/sql.extensions/README.create_table_as_query.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,25 @@ be explicitly aliased.
2222
For global and local temporary tables, normal temporary-table data lifetime rules apply. Package temporary tables
2323
do not support this syntax.
2424

25+
## Datatypes and Domains
26+
27+
When a select list item is a direct reference to a source table or view column, the new column copies the source
28+
column's exact datatype instead of one derived only from the query's runtime result (which would lose information
29+
such as declared `NUMERIC`/`DECIMAL` precision).
30+
31+
* If the source column is based on a named domain, the new column references that domain directly, as if it had
32+
been declared `<column name> <domain name>`.
33+
* If the source column is based on an auto-generated (implicit) domain, its exact type (precision/scale, character
34+
set, collation, etc.) is copied.
35+
36+
For any other select list item (an expression, a literal, or an aggregate, for example), the new column's datatype
37+
is derived from the query result.
38+
2539
## Character Sets
2640

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.
41+
For `CHAR` and `VARCHAR` columns not covered by the direct column-reference rule above, the database default
42+
character set is not used. The character set and collation of the new column are copied from the corresponding
43+
query expression.
2944

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

src/dsql/DdlNodes.epp

Lines changed: 104 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9986,54 +9986,57 @@ void CreateRelationNode::defineQueryColumns(thread_db* tdbb, DsqlCompilerScratch
99869986
{
99879987
MetaName fieldName;
99889988

9989-
if (queryColumns)
9990-
fieldName = nodeAs<FieldNode>(queryColumns->items[position])->dsqlName;
9991-
else
9992-
{
9993-
const ValueExprNode* nameNode = item;
9994-
const char* aliasName = nullptr;
9989+
// Unwrap aliases/derived fields/union maps/package references to reach the underlying
9990+
// node. This is used to find the field name (when no explicit column list is given) and,
9991+
// in all cases, to detect a direct reference to a source table column so its exact
9992+
// datatype (and, for named domains, the domain itself) can be copied.
9993+
const ValueExprNode* nameNode = item;
9994+
const char* aliasName = nullptr;
99959995

9996-
while (nodeIs<DsqlAliasNode>(nameNode) || nodeIs<DerivedFieldNode>(nameNode) ||
9997-
nodeIs<DsqlMapNode>(nameNode) || nodeAs<PackageReferenceNode>(nameNode))
9996+
while (nodeIs<DsqlAliasNode>(nameNode) || nodeIs<DerivedFieldNode>(nameNode) ||
9997+
nodeIs<DsqlMapNode>(nameNode) || nodeAs<PackageReferenceNode>(nameNode))
9998+
{
9999+
if (const auto aliasNode = nodeAs<DsqlAliasNode>(nameNode))
999810000
{
9999-
if (const auto aliasNode = nodeAs<DsqlAliasNode>(nameNode))
10000-
{
10001-
if (!aliasName)
10002-
aliasName = aliasNode->name.c_str();
10003-
10004-
nameNode = aliasNode->value;
10005-
}
10006-
else if (const auto mapNode = nodeAs<DsqlMapNode>(nameNode))
10007-
nameNode = mapNode->map->map_node;
10008-
else if (const auto derivedField = nodeAs<DerivedFieldNode>(nameNode))
10009-
{
10010-
if (!aliasName)
10011-
aliasName = derivedField->name.c_str();
10012-
10013-
nameNode = derivedField->value;
10014-
}
10015-
else if (const auto referenceNode = nodeAs<PackageReferenceNode>(nameNode))
10016-
{
10017-
if (!aliasName)
10018-
aliasName = referenceNode->getName();
10001+
if (!aliasName)
10002+
aliasName = aliasNode->name.c_str();
1001910003

10020-
nameNode = nullptr;
10021-
}
10004+
nameNode = aliasNode->value;
1002210005
}
10006+
else if (const auto mapNode = nodeAs<DsqlMapNode>(nameNode))
10007+
nameNode = mapNode->map->map_node;
10008+
else if (const auto derivedField = nodeAs<DerivedFieldNode>(nameNode))
10009+
{
10010+
if (!aliasName)
10011+
aliasName = derivedField->name.c_str();
1002310012

10024-
if (aliasName)
10025-
fieldName = aliasName;
10026-
else if (const auto fieldNode = nodeAs<FieldNode>(nameNode))
10027-
fieldName = fieldNode->dsqlField->fld_name;
10028-
else
10013+
nameNode = derivedField->value;
10014+
}
10015+
else if (const auto referenceNode = nodeAs<PackageReferenceNode>(nameNode))
1002910016
{
10030-
status_exception::raise(
10031-
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
10032-
Arg::Gds(isc_dsql_command_err) <<
10033-
Arg::Gds(isc_specify_field_err));
10017+
if (!aliasName)
10018+
aliasName = referenceNode->getName();
10019+
10020+
nameNode = nullptr;
1003410021
}
1003510022
}
1003610023

10024+
const auto sourceFieldNode = nodeAs<FieldNode>(nameNode);
10025+
10026+
if (queryColumns)
10027+
fieldName = nodeAs<FieldNode>(queryColumns->items[position])->dsqlName;
10028+
else if (aliasName)
10029+
fieldName = aliasName;
10030+
else if (sourceFieldNode)
10031+
fieldName = sourceFieldNode->dsqlField->fld_name;
10032+
else
10033+
{
10034+
status_exception::raise(
10035+
Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
10036+
Arg::Gds(isc_dsql_command_err) <<
10037+
Arg::Gds(isc_specify_field_err));
10038+
}
10039+
1003710040
for (const auto& name : names)
1003810041
{
1003910042
if (name == fieldName)
@@ -10063,30 +10066,76 @@ void CreateRelationNode::defineQueryColumns(thread_db* tdbb, DsqlCompilerScratch
1006310066

1006410067
auto field = clause->field;
1006510068
field->fld_name = fieldName;
10066-
field->dtype = desc.dsc_dtype;
10067-
field->length = desc.dsc_length;
10068-
field->scale = desc.dsc_scale;
10069-
field->subType = desc.dsc_sub_type;
1007010069

10071-
if (desc.dsc_flags & DSC_nullable)
10072-
field->flags |= FLD_nullable;
10070+
// If the select item is a direct reference to a source table/view column, copy its
10071+
// exact datatype rather than relying only on the runtime descriptor. A column based on
10072+
// a named domain is copied as a full domain reference (inheriting its NOT NULL/DEFAULT/
10073+
// CHECK, like a hand-written column of that domain). A column based on an implicit
10074+
// (auto-generated) domain has its exact primitive type copied instead, since implicit
10075+
// domains must not be shared between tables (they are erased, unconditionally, when
10076+
// their owning column is dropped).
10077+
dsql_fld* sourceField = nullptr;
1007310078

10074-
if (desc.isText() || (desc.isBlob() && desc.getBlobSubType() == isc_blob_text))
10079+
if (sourceFieldNode && sourceFieldNode->dsqlField &&
10080+
sourceFieldNode->dsqlField->fld_relation &&
10081+
!(sourceFieldNode->dsqlField->flags & FLD_computed) &&
10082+
sourceFieldNode->dsqlField->fieldSource.object.hasData())
1007510083
{
10076-
field->charSetId = desc.getCharSet();
10077-
field->collationId = desc.getCollation();
10084+
sourceField = sourceFieldNode->dsqlField;
1007810085
}
1007910086

10080-
if (desc.isText())
10087+
if (sourceField && !fb_utils::implicit_domain(sourceField->fieldSource.object.c_str()))
1008110088
{
10082-
const USHORT adjust = (desc.dsc_dtype == dtype_varying) ? sizeof(USHORT) : 0;
10083-
const USHORT bpc = METD_get_charset_bpc(dsqlScratch->getTransaction(), field->charSetId.value_or(CS_NONE));
10084-
field->charLength = (field->length - adjust) / bpc;
10089+
// Named domain: reference it directly, like "<field name> <domain name>".
10090+
field->typeOfName = sourceField->fieldSource;
10091+
field->fullDomain = true;
10092+
}
10093+
else
10094+
{
10095+
if (sourceField)
10096+
{
10097+
// Implicit domain: copy its exact stored type (precision, charset, collation,
10098+
// dimensions, etc.) instead of sharing the domain object itself.
10099+
METD_get_domain(dsqlScratch->getTransaction(), field, sourceField->fieldSource);
10100+
field->flags &= ~(FLD_computed | FLD_system);
10101+
}
10102+
else
10103+
{
10104+
field->dtype = desc.dsc_dtype;
10105+
field->length = desc.dsc_length;
10106+
field->scale = desc.dsc_scale;
10107+
field->subType = desc.dsc_sub_type;
10108+
10109+
if (desc.isText() || (desc.isBlob() && desc.getBlobSubType() == isc_blob_text))
10110+
{
10111+
field->charSetId = desc.getCharSet();
10112+
field->collationId = desc.getCollation();
10113+
}
10114+
10115+
if (desc.isText())
10116+
{
10117+
const USHORT adjust = (desc.dsc_dtype == dtype_varying) ? sizeof(USHORT) : 0;
10118+
const USHORT bpc = METD_get_charset_bpc(dsqlScratch->getTransaction(), field->charSetId.value_or(CS_NONE));
10119+
field->charLength = (field->length - adjust) / bpc;
10120+
}
10121+
else if (desc.isBlob())
10122+
field->segLength = 80;
10123+
10124+
field->setExactPrecision();
10125+
}
10126+
10127+
field->flags &= ~FLD_nullable;
10128+
if (desc.dsc_flags & DSC_nullable)
10129+
field->flags |= FLD_nullable;
10130+
else
10131+
{
10132+
// A newly created (non-domain) column is nullable unless a NOT NULL constraint
10133+
// is explicitly added - field->flags alone does not drive this, see defineField().
10134+
auto& notNullConstraint = clause->constraints.add();
10135+
notNullConstraint.constraintType = AddConstraintClause::CTYPE_NOT_NULL;
10136+
}
1008510137
}
10086-
else if (desc.isBlob())
10087-
field->segLength = 80;
1008810138

10089-
field->setExactPrecision();
1009010139
clauses.add(clause);
1009110140

1009210141
++position;

src/dsql/metd.epp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,10 @@ bool METD_get_domain(jrd_tra* transaction, TypeClause* field, const QualifiedNam
509509
if (!FLX.RDB$CHARACTER_LENGTH.NULL)
510510
field->charLength = FLX.RDB$CHARACTER_LENGTH;
511511

512+
field->precision = 0;
513+
if (!FLX.RDB$FIELD_PRECISION.NULL)
514+
field->precision = FLX.RDB$FIELD_PRECISION;
515+
512516
if (!FLX.RDB$COMPUTED_BLR.NULL)
513517
field->flags |= FLD_computed;
514518

0 commit comments

Comments
 (0)