@@ -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;
0 commit comments