diff --git a/doc/sql.extensions/README.identity_columns.txt b/doc/sql.extensions/README.identity_columns.txt index 5c5970ef8c..1f68eb1661 100644 --- a/doc/sql.extensions/README.identity_columns.txt +++ b/doc/sql.extensions/README.identity_columns.txt @@ -11,16 +11,20 @@ Description: Syntax: ::= - GENERATED BY DEFAULT AS IDENTITY [ ( ... ) ] + GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( ... ) ] ::= START WITH | INCREMENT [ BY ] ::= + [ ... ] | ... | DROP IDENTITY + ::= + SET GENERATED { ALWAYS | BY DEFAULT } + ::= RESTART [ WITH ] | SET INCREMENT [ BY ] @@ -39,10 +43,8 @@ Notes: Implementation: Two columns have been inserted in RDB$RELATION_FIELDS: RDB$GENERATOR_NAME and RDB$IDENTITY_TYPE. RDB$GENERATOR_NAME stores the automatically created generator for the column. In RDB$GENERATORS, - the value of RDB$SYSTEM_FLAG of that generator will be 6. RDB$IDENTITY_TYPE will currently - always store the value 1 (by default) for identity columns and NULL for non-identity columns. - In the future this column will store the value 0, too (for ALWAYS) when Firebird support this type - of identity column. + the value of RDB$SYSTEM_FLAG of that generator will be 6. RDB$IDENTITY_TYPE stores the value + 0 for GENERATED ALWAYS, 1 for GENERATED BY DEFAULT, and NULL for non-identity columns. Example: @@ -84,3 +86,21 @@ alter table objects alter table objects alter id drop identity; + + +--------------- +Override Clause +--------------- + +BY DEFAULT identity columns can be overriden in INSERT statements (INSERT, UPDATE OR INSERT, MERGE ... WHEN NOT MATCHED) +just specifying the value in the values list. However, for ALWAYS identity columns that is not allowed. + +To use the value passed in the INSERT statement for an ALWAYS column, you should pass OVERRIDING SYSTEM VALUE as +following: + +insert into objects (id, name) overriding system value values (11, 'Laptop'); + +OVERRIDING also supports a subclause to be used with BY DEFAULT columns, to ignore the value passed in INSERT and use +the defined sequence: + +insert into objects (id, name) overriding user value values (12, 'Laptop'); -- 12 is not used diff --git a/lang_helpers/gds_codes.ftn b/lang_helpers/gds_codes.ftn index 0ff1a37684..ca039252b0 100644 --- a/lang_helpers/gds_codes.ftn +++ b/lang_helpers/gds_codes.ftn @@ -1678,6 +1678,14 @@ C -- PARAMETER (GDS__att_shut_db_down = 335545132) INTEGER*4 GDS__att_shut_engine PARAMETER (GDS__att_shut_engine = 335545133) + INTEGER*4 GDS__overriding_without_identity + PARAMETER (GDS__overriding_without_identity = 335545134) + INTEGER*4 GDS__overriding_system_invalid + PARAMETER (GDS__overriding_system_invalid = 335545135) + INTEGER*4 GDS__overriding_user_invalid + PARAMETER (GDS__overriding_user_invalid = 335545136) + INTEGER*4 GDS__overriding_system_missing + PARAMETER (GDS__overriding_system_missing = 335545137) INTEGER*4 GDS__gfix_db_name PARAMETER (GDS__gfix_db_name = 335740929) INTEGER*4 GDS__gfix_invalid_sw diff --git a/lang_helpers/gds_codes.pas b/lang_helpers/gds_codes.pas index dc019b5f82..24ffb26fc9 100644 --- a/lang_helpers/gds_codes.pas +++ b/lang_helpers/gds_codes.pas @@ -1673,6 +1673,14 @@ const gds_att_shut_db_down = 335545132; isc_att_shut_engine = 335545133; gds_att_shut_engine = 335545133; + isc_overriding_without_identity = 335545134; + gds_overriding_without_identity = 335545134; + isc_overriding_system_invalid = 335545135; + gds_overriding_system_invalid = 335545135; + isc_overriding_user_invalid = 335545136; + gds_overriding_user_invalid = 335545136; + isc_overriding_system_missing = 335545137; + gds_overriding_system_missing = 335545137; isc_gfix_db_name = 335740929; gds_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; diff --git a/src/common/classes/Nullable.h b/src/common/classes/Nullable.h index 54352ec980..727caccc6a 100644 --- a/src/common/classes/Nullable.h +++ b/src/common/classes/Nullable.h @@ -35,7 +35,7 @@ class NullableClear public: static void clear(T& v) { - v = 0; + v = T(); } }; @@ -70,6 +70,11 @@ public: return (!specified && !o.specified) || (specified == o.specified && value == o.value); } + bool operator ==(const T& o) const + { + return specified && value == o; + } + void operator =(const T& v) { this->value = v; @@ -84,26 +89,6 @@ public: // NullableClear specializations. -template <> -class NullableClear // string especialization for NullableClear -{ -public: - static void clear(Firebird::string& v) - { - v = ""; - } -}; - -template <> -class NullableClear // MetaName especialization for NullableClear -{ -public: - static void clear(Firebird::MetaName& v) - { - v = ""; - } -}; - template class NullableClear > { diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp index 643dc06b0c..87d6a53476 100644 --- a/src/dsql/DdlNodes.epp +++ b/src/dsql/DdlNodes.epp @@ -5951,7 +5951,7 @@ void RelationNode::FieldDefinition::store(thread_db* tdbb, jrd_tra* transaction) strcpy(RFR.RDB$GENERATOR_NAME, identitySequence.c_str()); RFR.RDB$IDENTITY_TYPE.NULL = FALSE; - RFR.RDB$IDENTITY_TYPE = IDENT_TYPE_BY_DEFAULT; + RFR.RDB$IDENTITY_TYPE = identityType.value; } if (notNullFlag.specified) @@ -6296,6 +6296,7 @@ void RelationNode::defineField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch } DYN_UTIL_generate_generator_name(tdbb, fieldDefinition.identitySequence); + fieldDefinition.identityType = clause->identityOptions->type; CreateAlterSequenceNode::store(tdbb, transaction, fieldDefinition.identitySequence, fb_sysflag_identity_generator, @@ -7858,6 +7859,13 @@ void AlterRelationNode::modifyField(thread_db* tdbb, DsqlCompilerScratch* dsqlSc transaction->getGenIdCache()->put(id, val); } + if (clause->identityOptions->type.specified) + { + MODIFY RFR + RFR.RDB$IDENTITY_TYPE = clause->identityOptions->type.value; + END_MODIFY + } + if (clause->identityOptions->increment.specified) { if (clause->identityOptions->increment.value == 0) diff --git a/src/dsql/DdlNodes.h b/src/dsql/DdlNodes.h index a46b7b6db7..6725b5ee8a 100644 --- a/src/dsql/DdlNodes.h +++ b/src/dsql/DdlNodes.h @@ -1140,6 +1140,7 @@ public: Firebird::MetaName relationName; Firebird::MetaName fieldSource; Firebird::MetaName identitySequence; + Nullable identityType; Nullable collationId; Nullable notNullFlag; // true = NOT NULL / false = NULL Nullable position; @@ -1306,11 +1307,18 @@ public: struct IdentityOptions { + IdentityOptions(MemoryPool&, IdentityType aType) + : type(aType), + restart(false) + { + } + IdentityOptions(MemoryPool&) : restart(false) { } + Nullable type; Nullable startValue; Nullable increment; bool restart; // used in ALTER diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index 0b06fe776e..7cba81771b 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -94,6 +94,8 @@ static void postTriggerAccess(CompilerScratch* csb, jrd_rel* ownerRelation, ExternalAccess::exa_act operation, jrd_rel* view); static void preModifyEraseTriggers(thread_db* tdbb, TrigVector** trigs, StmtNode::WhichTrigger whichTrig, record_param* rpb, record_param* rec, TriggerAction op); +static void preprocessAssignments(thread_db* tdbb, CompilerScratch* csb, + StreamType stream, CompoundStmtNode* compoundNode, const Nullable* insertOverride); static void validateExpressions(thread_db* tdbb, const Array& validations); } // namespace Jrd @@ -5340,6 +5342,7 @@ StmtNode* MergeNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) store->dsqlRelation = relation; store->dsqlFields = notMatched->fields; store->dsqlValues = notMatched->values; + store->overrideClause = notMatched->overrideClause; bool needSavePoint; // unused thisIf->trueAction = store = store->internalDsqlPass(dsqlScratch, false, needSavePoint)->as(); @@ -5881,50 +5884,7 @@ void ModifyNode::genBlr(DsqlCompilerScratch* dsqlScratch) ModifyNode* ModifyNode::pass1(thread_db* tdbb, CompilerScratch* csb) { - CompoundStmtNode* compoundNode = statement->as(); - - // Remove assignments of DEFAULT to computed fields. - if (compoundNode) - { - for (size_t i = compoundNode->statements.getCount(); i--; ) - { - const AssignmentNode* assign = compoundNode->statements[i]->as(); - fb_assert(assign); - if (!assign) - continue; - - const ExprNode* assignFrom = assign->asgnFrom; - const FieldNode* assignToField = assign->asgnTo->as(); - - if (assignToField && assignFrom->is()) - { - jrd_rel* relation = csb->csb_rpt[newStream].csb_relation; - int fieldId = assignToField->fieldId; - - while (true) - { - jrd_fld* fld; - - if (assignToField->fieldStream == newStream && - relation && - relation->rel_fields && - (fld = (*relation->rel_fields)[fieldId])) - { - if (fld->fld_computation) - compoundNode->statements.remove(i); - else if (relation->rel_view_rse && fld->fld_source_rel_field.first.hasData()) - { - relation = MET_lookup_relation(tdbb, fld->fld_source_rel_field.first); - if ((fieldId = MET_lookup_field(tdbb, relation, fld->fld_source_rel_field.second)) >= 0) - continue; - } - } - - break; - } - } - } - } + preprocessAssignments(tdbb, csb, newStream, statement->as(), NULL); pass1Modify(tdbb, csb, this); @@ -6463,6 +6423,7 @@ const StmtNode* ReceiveNode::execute(thread_db* /*tdbb*/, jrd_req* request, ExeS static RegisterNode regStoreNode(blr_store); static RegisterNode regStoreNode2(blr_store2); +static RegisterNode regStoreNode3(blr_store3); // Parse a store statement. DmlNode* StoreNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp) @@ -6471,6 +6432,21 @@ DmlNode* StoreNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* cs AutoSetRestore autoCurrentDMLNode(&csb->csb_currentDMLNode, node); + if (blrOp == blr_store3) + { + node->overrideClause = static_cast(csb->csb_blr_reader.getByte()); + + switch (node->overrideClause.value) + { + case OverrideClause::USER_VALUE: + case OverrideClause::SYSTEM_VALUE: + break; + + default: + PAR_syntax_error(csb, "invalid blr_store3 override clause"); + } + } + const UCHAR* blrPos = csb->csb_blr_reader.getPos(); node->relationSource = PAR_parseRecordSource(tdbb, csb)->as(); @@ -6485,6 +6461,13 @@ DmlNode* StoreNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* cs if (blrOp == blr_store2) node->statement2 = PAR_parse_stmt(tdbb, csb); + else if (blrOp == blr_store3) + { + if (csb->csb_blr_reader.peekByte() == blr_null) + csb->csb_blr_reader.getByte(); + else + node->statement2 = PAR_parse_stmt(tdbb, csb); + } return node; } @@ -6498,6 +6481,7 @@ StmtNode* StoreNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch, dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_INSERT); StoreNode* node = FB_NEW_POOL(getPool()) StoreNode(getPool()); + node->overrideClause = overrideClause; // Process SELECT expression, if present @@ -6606,7 +6590,21 @@ StmtNode* StoreNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch, NestConst* ptr2 = values->items.begin(); for (const NestConst* end = fields.end(); ptr != end; ++ptr, ++ptr2) { - if (*ptr2) // it's NULL for DEFAULT + // *ptr2 is NULL for DEFAULT + + if (!*ptr2) + { + const FieldNode* field = (*ptr)->as(); + + if (field && field->dsqlField) + { + *ptr2 = FB_NEW_POOL(getPool()) DefaultNode(getPool(), + relation->rel_name, field->dsqlField->fld_name); + *ptr2 = doDsqlPass(dsqlScratch, *ptr2, false); + } + } + + if (*ptr2) { AssignmentNode* temp = FB_NEW_POOL(getPool()) AssignmentNode(getPool()); temp->asgnFrom = *ptr2; @@ -6691,12 +6689,19 @@ void StoreNode::genBlr(DsqlCompilerScratch* dsqlScratch) { const dsql_msg* message = dsqlGenDmlHeader(dsqlScratch, dsqlRse->as()); - dsqlScratch->appendUChar(statement2 ? blr_store2 : blr_store); + dsqlScratch->appendUChar(overrideClause.specified ? blr_store3 : (statement2 ? blr_store2 : blr_store)); + + if (overrideClause.specified) + dsqlScratch->appendUChar(UCHAR(overrideClause.value)); + GEN_expr(dsqlScratch, dsqlRelation); + statement->genBlr(dsqlScratch); if (statement2) statement2->genBlr(dsqlScratch); + else if (overrideClause.specified) + dsqlScratch->appendUChar(blr_null); if (message) dsqlScratch->appendUChar(blr_end); @@ -6704,6 +6709,8 @@ void StoreNode::genBlr(DsqlCompilerScratch* dsqlScratch) StoreNode* StoreNode::pass1(thread_db* tdbb, CompilerScratch* csb) { + preprocessAssignments(tdbb, csb, relationSource->getStream(), statement->as(), &overrideClause); + if (pass1Store(tdbb, csb, this)) makeDefaults(tdbb, csb); @@ -6839,7 +6846,6 @@ void StoreNode::makeDefaults(thread_db* tdbb, CompilerScratch* csb) } StmtNodeStack stack; - USHORT fieldId = 0; vec::iterator ptr1 = vector->begin(); @@ -6869,10 +6875,7 @@ void StoreNode::makeDefaults(thread_db* tdbb, CompilerScratch* csb) fb_assert(fieldNode); if (fieldNode && fieldNode->fieldStream == stream && fieldNode->fieldId == fieldId) - { - inList = true; break; - } } } @@ -8071,6 +8074,7 @@ StmtNode* UpdateOrInsertNode::dsqlPass(DsqlCompilerScratch* dsqlScratch) insert->dsqlFields = fields; insert->dsqlValues = values; insert->dsqlReturning = returning; + insert->overrideClause = overrideClause; insert = insert->internalDsqlPass(dsqlScratch, true, needSavePoint)->as(); fb_assert(insert); @@ -9324,6 +9328,99 @@ static void preModifyEraseTriggers(thread_db* tdbb, TrigVector** trigs, tdbb->getTransaction()->tra_rpblist->PopRpb(rpb, rpblevel); } +// 1. Remove assignments of DEFAULT to computed fields. +// 2. Remove assignments to identity column when OVERRIDING USER VALUE is specified in INSERT. +static void preprocessAssignments(thread_db* tdbb, CompilerScratch* csb, + StreamType stream, CompoundStmtNode* compoundNode, const Nullable* insertOverride) +{ + if (!compoundNode) + return; + + jrd_rel* relation = csb->csb_rpt[stream].csb_relation; + + fb_assert(relation); + if (!relation) + return; + + Nullable identityType; + + for (size_t i = compoundNode->statements.getCount(); i--; ) + { + const AssignmentNode* assign = compoundNode->statements[i]->as(); + fb_assert(assign); + if (!assign) + continue; + + const ExprNode* assignFrom = assign->asgnFrom; + const FieldNode* assignToField = assign->asgnTo->as(); + + if (assignToField) + { + int fieldId = assignToField->fieldId; + jrd_fld* fld; + + while (true) + { + if (assignToField->fieldStream == stream && + relation->rel_fields && + (fld = (*relation->rel_fields)[fieldId])) + { + if (insertOverride && fld->fld_identity_type.specified) + { + if (insertOverride->specified || !assignFrom->is()) + identityType = fld->fld_identity_type; + + if (*insertOverride == OverrideClause::USER_VALUE) + { + compoundNode->statements.remove(i); + break; + } + } + + if (fld->fld_computation) + { + if (assignFrom->is()) + compoundNode->statements.remove(i); + } + else if (relation->rel_view_rse && fld->fld_source_rel_field.first.hasData()) + { + relation = MET_lookup_relation(tdbb, fld->fld_source_rel_field.first); + + fb_assert(relation); + if (!relation) + return; + + if ((fieldId = MET_lookup_field(tdbb, relation, fld->fld_source_rel_field.second)) >= 0) + continue; + } + } + + break; + } + } + } + + if (!insertOverride) + return; + + if (insertOverride->specified) + { + if (!identityType.specified) + ERR_post(Arg::Gds(isc_overriding_without_identity) << relation->rel_name); + + if (identityType == IDENT_TYPE_BY_DEFAULT && *insertOverride == OverrideClause::SYSTEM_VALUE) + ERR_post(Arg::Gds(isc_overriding_system_invalid) << relation->rel_name); + + if (identityType == IDENT_TYPE_ALWAYS && *insertOverride == OverrideClause::USER_VALUE) + ERR_post(Arg::Gds(isc_overriding_user_invalid) << relation->rel_name); + } + else + { + if (identityType == IDENT_TYPE_ALWAYS) + ERR_post(Arg::Gds(isc_overriding_system_missing) << relation->rel_name); + } +} + // Execute a list of validation expressions. static void validateExpressions(thread_db* tdbb, const Array& validations) { diff --git a/src/dsql/StmtNodes.h b/src/dsql/StmtNodes.h index 0dd558bf64..f73f05b66e 100644 --- a/src/dsql/StmtNodes.h +++ b/src/dsql/StmtNodes.h @@ -112,6 +112,14 @@ struct ValidateInfo }; +enum OverrideClause : UCHAR +{ + // Warning: used in BLR + USER_VALUE = 1, + SYSTEM_VALUE +}; + + class AssignmentNode : public TypedNode { public: @@ -1048,6 +1056,7 @@ public: Firebird::Array > fields; NestConst values; NestConst condition; + Nullable overrideClause; }; explicit MergeNode(MemoryPool& pool) @@ -1267,6 +1276,7 @@ public: NestConst subStore; Firebird::Array validations; NestConst relationSource; + Nullable overrideClause; }; @@ -1609,6 +1619,7 @@ public: NestConst values; Firebird::Array > matching; NestConst returning; + Nullable overrideClause; }; diff --git a/src/dsql/parse-conflicts.txt b/src/dsql/parse-conflicts.txt index 20cc527f2d..9a7308c038 100644 --- a/src/dsql/parse-conflicts.txt +++ b/src/dsql/parse-conflicts.txt @@ -1 +1 @@ -45 shift/reduce conflicts, 17 reduce/reduce conflicts. +46 shift/reduce conflicts, 17 reduce/reduce conflicts. diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 01133660a8..634479f3f3 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -600,6 +600,7 @@ using namespace Firebird; %token MESSAGE %token NTILE %token OTHERS +%token OVERRIDING %token PERCENT_RANK %token PRECEDING %token PRIVILEGE @@ -648,6 +649,7 @@ using namespace Firebird; BaseNullable nullableIntVal; BaseNullable nullableBoolVal; BaseNullable nullableSqlSecurityVal; + BaseNullable nullableOverrideClause; bool boolVal; int intVal; unsigned uintVal; @@ -727,6 +729,7 @@ using namespace Firebird; Jrd::RelationNode::RefActionClause* refActionClause; Jrd::RelationNode::IndexConstraintClause* indexConstraintClause; Jrd::RelationNode::IdentityOptions* identityOptions; + IdentityType identityType; Jrd::CreateRelationNode* createRelationNode; Jrd::CreateAlterViewNode* createAlterViewNode; Jrd::CreateIndexNode* createIndexNode; @@ -2185,10 +2188,16 @@ column_def($relationNode) %type identity_clause identity_clause - : GENERATED BY DEFAULT AS IDENTITY - { $$ = newNode(); } - identity_clause_options_opt($6) - { $$ = $6; } + : GENERATED identity_clause_type AS IDENTITY + { $$ = newNode($2); } + identity_clause_options_opt($5) + { $$ = $5; } + ; + +%type identity_clause_type +identity_clause_type + : BY DEFAULT { $$ = IDENT_TYPE_BY_DEFAULT; } + | ALWAYS { $$ = IDENT_TYPE_ALWAYS; } ; %type identity_clause_options_opt() @@ -3935,7 +3944,7 @@ alter_op($relationNode) } | col_opt symbol_column_name { $$ = newNode(); } - alter_identity_clause_options($3) + alter_identity_clause_spec($3) { RelationNode::AlterColTypeClause* clause = newNode(); clause->field = newNode(); @@ -4081,6 +4090,24 @@ alter_data_type_or_domain } ; +%type alter_identity_clause_spec() +alter_identity_clause_spec($identityOptions) + : alter_identity_clause_generation($identityOptions) alter_identity_clause_options_opt($identityOptions) + | alter_identity_clause_options($identityOptions) + ; + +%type alter_identity_clause_generation() +alter_identity_clause_generation($identityOptions) + : SET GENERATED ALWAYS { $identityOptions->type = IDENT_TYPE_ALWAYS; } + | SET GENERATED BY DEFAULT { $identityOptions->type = IDENT_TYPE_BY_DEFAULT; } + ; + +%type alter_identity_clause_options_opt() +alter_identity_clause_options_opt($identityOptions) + : // nothing + | alter_identity_clause_options($identityOptions) + ; + %type alter_identity_clause_options() alter_identity_clause_options($identityOptions) : alter_identity_clause_options alter_identity_clause_option($identityOptions) @@ -5942,18 +5969,20 @@ fetch_first_clause // IBO hack: replace column_parens_opt by ins_column_parens_opt. %type insert insert - : insert_start ins_column_parens_opt(NOTRIAL(&$1->dsqlFields)) VALUES '(' value_or_default_list ')' + : insert_start ins_column_parens_opt(NOTRIAL(&$1->dsqlFields)) override_opt VALUES '(' value_or_default_list ')' returning_clause { StoreNode* node = $$ = $1; - node->dsqlValues = $5; - node->dsqlReturning = $7; + node->overrideClause = $3; + node->dsqlValues = $6; + node->dsqlReturning = $8; } - | insert_start ins_column_parens_opt(NOTRIAL(&$1->dsqlFields)) select_expr returning_clause + | insert_start ins_column_parens_opt(NOTRIAL(&$1->dsqlFields)) override_opt select_expr returning_clause { StoreNode* node = $$ = $1; - node->dsqlRse = $3; - node->dsqlReturning = $4; + node->overrideClause = $3; + node->dsqlRse = $4; + node->dsqlReturning = $5; $$ = node; } | insert_start DEFAULT VALUES returning_clause @@ -5974,6 +6003,13 @@ insert_start } ; +%type override_opt +override_opt + : /* nothing */ { $$ = Nullable::empty(); } + | OVERRIDING USER VALUE { $$ = Nullable::val(OverrideClause::USER_VALUE); } + | OVERRIDING SYSTEM VALUE { $$ = Nullable::val(OverrideClause::SYSTEM_VALUE); } + ; + %type value_or_default_list value_or_default_list : value_or_default { $$ = newNode($1); } @@ -6042,13 +6078,17 @@ merge_update_specification($mergeMatchedClause, $relationName) %type merge_insert_specification() merge_insert_specification($mergeNotMatchedClause) - : THEN INSERT ins_column_parens_opt(NOTRIAL(&$mergeNotMatchedClause->fields)) - VALUES '(' value_or_default_list ')' - { $mergeNotMatchedClause->values = $6; } - | AND search_condition THEN INSERT ins_column_parens_opt(NOTRIAL(&$mergeNotMatchedClause->fields)) + : THEN INSERT ins_column_parens_opt(NOTRIAL(&$mergeNotMatchedClause->fields)) override_opt VALUES '(' value_or_default_list ')' { - $mergeNotMatchedClause->values = $8; + $mergeNotMatchedClause->overrideClause = $4; + $mergeNotMatchedClause->values = $7; + } + | AND search_condition THEN INSERT ins_column_parens_opt(NOTRIAL(&$mergeNotMatchedClause->fields)) override_opt + VALUES '(' value_or_default_list ')' + { + $mergeNotMatchedClause->overrideClause = $6; + $mergeNotMatchedClause->values = $9; $mergeNotMatchedClause->condition = $2; } ; @@ -6138,12 +6178,13 @@ update_or_insert UpdateOrInsertNode* node = $$ = newNode(); node->relation = $5; } - ins_column_parens_opt(NOTRIAL(&$6->fields)) VALUES '(' value_or_default_list ')' + ins_column_parens_opt(NOTRIAL(&$6->fields)) override_opt VALUES '(' value_or_default_list ')' update_or_insert_matching_opt(NOTRIAL(&$6->matching)) returning_clause { UpdateOrInsertNode* node = $$ = $6; - node->values = $10; - node->returning = $13; + node->overrideClause = $8; + node->values = $11; + node->returning = $14; } ; @@ -8248,6 +8289,7 @@ non_reserved_word | MESSAGE | NTILE | OTHERS + | OVERRIDING | PERCENT_RANK | PRECEDING | PRIVILEGE diff --git a/src/include/gen/codetext.h b/src/include/gen/codetext.h index ad3cfc0f65..9b314540e1 100644 --- a/src/include/gen/codetext.h +++ b/src/include/gen/codetext.h @@ -835,6 +835,10 @@ static const struct { {"att_shut_idle", 335545131}, {"att_shut_db_down", 335545132}, {"att_shut_engine", 335545133}, + {"overriding_without_identity", 335545134}, + {"overriding_system_invalid", 335545135}, + {"overriding_user_invalid", 335545136}, + {"overriding_system_missing", 335545137}, {"gfix_db_name", 335740929}, {"gfix_invalid_sw", 335740930}, {"gfix_incmp_sw", 335740932}, diff --git a/src/include/gen/iberror.h b/src/include/gen/iberror.h index 2cb2f38751..36794b0a8d 100644 --- a/src/include/gen/iberror.h +++ b/src/include/gen/iberror.h @@ -869,6 +869,10 @@ const ISC_STATUS isc_att_shut_killed = 335545130L; const ISC_STATUS isc_att_shut_idle = 335545131L; const ISC_STATUS isc_att_shut_db_down = 335545132L; const ISC_STATUS isc_att_shut_engine = 335545133L; +const ISC_STATUS isc_overriding_without_identity = 335545134L; +const ISC_STATUS isc_overriding_system_invalid = 335545135L; +const ISC_STATUS isc_overriding_user_invalid = 335545136L; +const ISC_STATUS isc_overriding_system_missing = 335545137L; const ISC_STATUS isc_gfix_db_name = 335740929L; const ISC_STATUS isc_gfix_invalid_sw = 335740930L; const ISC_STATUS isc_gfix_incmp_sw = 335740932L; @@ -1343,7 +1347,7 @@ const ISC_STATUS isc_trace_switch_user_only = 337182757L; const ISC_STATUS isc_trace_switch_param_miss = 337182758L; const ISC_STATUS isc_trace_param_act_notcompat = 337182759L; const ISC_STATUS isc_trace_mandatory_switch_miss = 337182760L; -const ISC_STATUS isc_err_max = 1287; +const ISC_STATUS isc_err_max = 1291; #else /* c definitions */ @@ -2182,6 +2186,10 @@ const ISC_STATUS isc_err_max = 1287; #define isc_att_shut_idle 335545131L #define isc_att_shut_db_down 335545132L #define isc_att_shut_engine 335545133L +#define isc_overriding_without_identity 335545134L +#define isc_overriding_system_invalid 335545135L +#define isc_overriding_user_invalid 335545136L +#define isc_overriding_system_missing 335545137L #define isc_gfix_db_name 335740929L #define isc_gfix_invalid_sw 335740930L #define isc_gfix_incmp_sw 335740932L @@ -2656,7 +2664,7 @@ const ISC_STATUS isc_err_max = 1287; #define isc_trace_switch_param_miss 337182758L #define isc_trace_param_act_notcompat 337182759L #define isc_trace_mandatory_switch_miss 337182760L -#define isc_err_max 1287 +#define isc_err_max 1291 #endif diff --git a/src/include/gen/msgs.h b/src/include/gen/msgs.h index 38b86af912..8d8a3bc899 100644 --- a/src/include/gen/msgs.h +++ b/src/include/gen/msgs.h @@ -838,6 +838,10 @@ Data source : @4"}, /* eds_statement */ {335545131, "Idle timeout expired."}, /* att_shut_idle */ {335545132, "Database is shutdown."}, /* att_shut_db_down */ {335545133, "Engine is shutdown."}, /* att_shut_engine */ + {335545134, "OVERRIDING clause can be used only when an identity column is present in the INSERT's field list for table/view @1"}, /* overriding_without_identity */ + {335545135, "OVERRIDING SYSTEM VALUE can be used only for identity column defined as 'GENERATED ALWAYS' in INSERT for table/view @1"}, /* overriding_system_invalid */ + {335545136, "OVERRIDING USER VALUE can be used only for identity column defined as 'GENERATED BY DEFAULT' in INSERT for table/view"}, /* overriding_user_invalid */ + {335545137, "OVERRIDING SYSTEM VALUE should be used to override the value of an identity column defined as 'GENERATED ALWAYS' in ta"}, /* overriding_system_missing */ {335740929, "data base file name (@1) already given"}, /* gfix_db_name */ {335740930, "invalid switch @1"}, /* gfix_invalid_sw */ {335740932, "incompatible switch combination"}, /* gfix_incmp_sw */ diff --git a/src/include/gen/sql_code.h b/src/include/gen/sql_code.h index b05ac5fd46..092c9bbd6d 100644 --- a/src/include/gen/sql_code.h +++ b/src/include/gen/sql_code.h @@ -834,6 +834,10 @@ static const struct { {335545131, -902}, /* 811 att_shut_idle */ {335545132, -902}, /* 812 att_shut_db_down */ {335545133, -902}, /* 813 att_shut_engine */ + {335545134, -902}, /* 814 overriding_without_identity */ + {335545135, -902}, /* 815 overriding_system_invalid */ + {335545136, -902}, /* 816 overriding_user_invalid */ + {335545137, -902}, /* 817 overriding_system_missing */ {335740929, -901}, /* 1 gfix_db_name */ {335740930, -901}, /* 2 gfix_invalid_sw */ {335740932, -901}, /* 4 gfix_incmp_sw */ diff --git a/src/include/gen/sql_state.h b/src/include/gen/sql_state.h index 81feb82e24..06be5f599a 100644 --- a/src/include/gen/sql_state.h +++ b/src/include/gen/sql_state.h @@ -834,6 +834,10 @@ static const struct { {335545131, "08003"}, // 811 att_shut_idle {335545132, "08003"}, // 812 att_shut_db_down {335545133, "08003"}, // 813 att_shut_engine + {335545134, "42000"}, // 814 overriding_without_identity + {335545135, "42000"}, // 815 overriding_system_invalid + {335545136, "42000"}, // 816 overriding_user_invalid + {335545137, "42000"}, // 817 overriding_system_missing {335740929, "00000"}, // 1 gfix_db_name {335740930, "00000"}, // 2 gfix_invalid_sw {335740932, "00000"}, // 4 gfix_incmp_sw diff --git a/src/isql/extract.epp b/src/isql/extract.epp index f2ed253afa..bce30e9df8 100644 --- a/src/isql/extract.epp +++ b/src/isql/extract.epp @@ -496,7 +496,9 @@ int EXTRACT_list_table(const SCHAR* relation_name, FOR GEN IN RDB$GENERATORS WITH GEN.RDB$GENERATOR_NAME = RFR.RDB$GENERATOR_NAME { - isqlGlob.printf(" GENERATED BY DEFAULT AS IDENTITY"); + isqlGlob.printf(" GENERATED %s AS IDENTITY", + (RFR.RDB$IDENTITY_TYPE == IDENT_TYPE_BY_DEFAULT ? "BY DEFAULT" : + RFR.RDB$IDENTITY_TYPE == IDENT_TYPE_ALWAYS ? "ALWAYS" : "")); if (!GEN.RDB$INITIAL_VALUE.NULL && GEN.RDB$INITIAL_VALUE != 0) isqlGlob.printf(" (START WITH %" SQUADFORMAT ")", GEN.RDB$INITIAL_VALUE); diff --git a/src/isql/show.epp b/src/isql/show.epp index f631af5581..ef31406b40 100644 --- a/src/isql/show.epp +++ b/src/isql/show.epp @@ -5838,7 +5838,11 @@ static processing_state show_table(const SCHAR* relation_name, bool isView) } if (!RFR.RDB$GENERATOR_NAME.NULL) - isqlGlob.printf("Identity (by default)"); + { + isqlGlob.printf("Identity (%s)", + (RFR.RDB$IDENTITY_TYPE == IDENT_TYPE_BY_DEFAULT ? "by default" : + RFR.RDB$IDENTITY_TYPE == IDENT_TYPE_ALWAYS ? "always" : "")); + } // Handle defaults for columns diff --git a/src/jrd/Relation.h b/src/jrd/Relation.h index adf26a14a2..44d46ff1bb 100644 --- a/src/jrd/Relation.h +++ b/src/jrd/Relation.h @@ -485,6 +485,7 @@ public: Firebird::MetaName fld_security_name; // security class name for field Firebird::MetaName fld_generator_name; // identity generator name Firebird::MetaNamePair fld_source_rel_field; // Relation/field source name + Nullable fld_identity_type; public: explicit jrd_fld(MemoryPool& p) diff --git a/src/jrd/blp.h b/src/jrd/blp.h index b6602db173..2d56c1529d 100644 --- a/src/jrd/blp.h +++ b/src/jrd/blp.h @@ -243,5 +243,6 @@ static const struct {"gen_id2", gen_id2}, // 210 {"window_win", window_win}, {"default", relation_field}, + {"store3", store3}, {0, 0} }; diff --git a/src/jrd/blr.h b/src/jrd/blr.h index 3e17cb582a..b586a5e10f 100644 --- a/src/jrd/blr.h +++ b/src/jrd/blr.h @@ -421,5 +421,6 @@ #define blr_window_win_exclusion (unsigned char) 7 #define blr_default (unsigned char) 212 +#define blr_store3 (unsigned char) 213 #endif // JRD_BLR_H diff --git a/src/jrd/dfw.epp b/src/jrd/dfw.epp index 1395d6a521..e9ffc856cd 100644 --- a/src/jrd/dfw.epp +++ b/src/jrd/dfw.epp @@ -5754,6 +5754,10 @@ static bool make_version(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_ (UCHAR*) RFR.RDB$GENERATOR_NAME, n); } + n = RFR.RDB$IDENTITY_TYPE; + if (!RFR.RDB$IDENTITY_TYPE.NULL) + put_summary_record(tdbb, blob, RSR_field_identity_type, (UCHAR*) &n, sizeof(n)); + // Make a temporary field block TemporaryField* tfb = FB_NEW_POOL(*tdbb->getDefaultPool()) TemporaryField; diff --git a/src/jrd/filters.cpp b/src/jrd/filters.cpp index aa0a10d6ae..ecb5ab35cf 100644 --- a/src/jrd/filters.cpp +++ b/src/jrd/filters.cpp @@ -449,6 +449,10 @@ ISC_STATUS filter_runtime(USHORT action, BlobControl* control) sprintf(line, " field_generator_name: %s", p); break; + case RSR_field_identity_type: + sprintf(line, "Field identity type: %d", n); + break; + default: sprintf(line, "*** unknown verb %d ***", (int) buff[0]); } diff --git a/src/jrd/met.epp b/src/jrd/met.epp index 72cdf69009..cfa96ae4f6 100644 --- a/src/jrd/met.epp +++ b/src/jrd/met.epp @@ -4046,6 +4046,12 @@ void MET_scan_relation(thread_db* tdbb, jrd_rel* relation) case RSR_field_generator_name: field->fld_generator_name = (const TEXT*) p; + if (!field->fld_identity_type.specified) + field->fld_identity_type = IDENT_TYPE_BY_DEFAULT; + break; + + case RSR_field_identity_type: + field->fld_identity_type = static_cast(n); break; default: // Shut up compiler warning diff --git a/src/jrd/met.h b/src/jrd/met.h index 4896177efa..944d20b553 100644 --- a/src/jrd/met.h +++ b/src/jrd/met.h @@ -50,7 +50,8 @@ enum rsr_t { RSR_field_length, RSR_field_sub_type, RSR_field_not_null, - RSR_field_generator_name + RSR_field_generator_name, + RSR_field_identity_type }; // Temporary field block diff --git a/src/msgs/facilities2.sql b/src/msgs/facilities2.sql index 0d2969b0b2..cb5fa63889 100644 --- a/src/msgs/facilities2.sql +++ b/src/msgs/facilities2.sql @@ -1,7 +1,7 @@ /* MAX_NUMBER is the next number to be used, always one more than the highest message number. */ set bulk_insert INSERT INTO FACILITIES (LAST_CHANGE, FACILITY, FAC_CODE, MAX_NUMBER) VALUES (?, ?, ?, ?); -- -('2016-12-20 12:57:00', 'JRD', 0, 814) +('2017-02-24 22:00:00', 'JRD', 0, 818) ('2015-03-17 18:33:00', 'QLI', 1, 533) ('2015-01-07 18:01:51', 'GFIX', 3, 134) ('1996-11-07 13:39:40', 'GPRE', 4, 1) diff --git a/src/msgs/messages2.sql b/src/msgs/messages2.sql index dfad70f9ac..19a63d790b 100644 --- a/src/msgs/messages2.sql +++ b/src/msgs/messages2.sql @@ -921,6 +921,10 @@ Data source : @4', NULL, NULL) ('att_shut_idle', NULL, 'jrd.cpp', NULL, 0, 811, NULL, 'Idle timeout expired.', NULL, NULL); ('att_shut_db_down', NULL, 'jrd.cpp', NULL, 0, 812, NULL, 'Database is shutdown.', NULL, NULL); ('att_shut_engine', NULL, 'jrd.cpp', NULL, 0, 813, NULL, 'Engine is shutdown.', NULL, NULL); +('overriding_without_identity', NULL, 'StmtNodes.cpp', NULL, 0, 814, NULL, 'OVERRIDING clause can be used only when an identity column is present in the INSERT''s field list for table/view @1', NULL, NULL); +('overriding_system_invalid', NULL, 'StmtNodes.cpp', NULL, 0, 815, NULL, 'OVERRIDING SYSTEM VALUE can be used only for identity column defined as ''GENERATED ALWAYS'' in INSERT for table/view @1', NULL, NULL); +('overriding_user_invalid', NULL, 'StmtNodes.cpp', NULL, 0, 816, NULL, 'OVERRIDING USER VALUE can be used only for identity column defined as ''GENERATED BY DEFAULT'' in INSERT for table/view @1', NULL, NULL); +('overriding_system_missing', NULL, 'StmtNodes.cpp', NULL, 0, 817, NULL, 'OVERRIDING SYSTEM VALUE should be used to override the value of an identity column defined as ''GENERATED ALWAYS'' in table/view @1', NULL, NULL); -- QLI (NULL, NULL, NULL, NULL, 1, 0, NULL, 'expected type', NULL, NULL); (NULL, NULL, NULL, NULL, 1, 1, NULL, 'bad block type', NULL, NULL); diff --git a/src/msgs/system_errors2.sql b/src/msgs/system_errors2.sql index 1983084d0f..00a86ba11f 100644 --- a/src/msgs/system_errors2.sql +++ b/src/msgs/system_errors2.sql @@ -820,6 +820,10 @@ set bulk_insert INSERT INTO SYSTEM_ERRORS (SQL_CODE, SQL_CLASS, SQL_SUBCLASS, FA (-902, '08', '003', 0, 811, 'att_shut_idle', NULL, NULL) (-902, '08', '003', 0, 812, 'att_shut_db_down', NULL, NULL) (-902, '08', '003', 0, 813, 'att_shut_engine', NULL, NULL) +(-902, '42', '000', 0, 814, 'overriding_without_identity', NULL, NULL) +(-902, '42', '000', 0, 815, 'overriding_system_invalid', NULL, NULL) +(-902, '42', '000', 0, 816, 'overriding_user_invalid', NULL, NULL) +(-902, '42', '000', 0, 817, 'overriding_system_missing', NULL, NULL) -- GFIX (-901, '00', '000', 3, 1, 'gfix_db_name', NULL, NULL) (-901, '00', '000', 3, 2, 'gfix_invalid_sw', NULL, NULL) diff --git a/src/yvalve/gds.cpp b/src/yvalve/gds.cpp index 70b646588d..9df6d21897 100644 --- a/src/yvalve/gds.cpp +++ b/src/yvalve/gds.cpp @@ -344,7 +344,8 @@ static const UCHAR subfunc_decl[] = { op_subfunc_decl, 0}, window_win[] = { op_byte, op_window_win, 0}, relation_field[] = { op_line, op_indent, op_byte, op_literal, - op_line, op_indent, op_byte, op_literal, op_pad, op_line, 0}; + op_line, op_indent, op_byte, op_literal, op_pad, op_line, 0}, + store3[] = { op_line, op_byte, op_line, op_verb, op_verb, op_verb, 0}; #include "../jrd/blp.h" diff --git a/src/yvalve/keywords.cpp b/src/yvalve/keywords.cpp index 76929ae21d..1753775d5e 100644 --- a/src/yvalve/keywords.cpp +++ b/src/yvalve/keywords.cpp @@ -309,6 +309,7 @@ static const TOK tokens[] = {TOK_OVER, "OVER", false}, {TOK_OVERFLOW, "OVERFLOW", true}, {TOK_OVERLAY, "OVERLAY", true}, + {TOK_OVERRIDING, "OVERRIDING", true}, {TOK_PACKAGE, "PACKAGE", true}, {TOK_PAD, "PAD", true}, {TOK_PAGE, "PAGE", true},