Skip to content

Commit 13f7cf8

Browse files
authored
Merge pull request #8957 from FirebirdSQL/work/UpdateOverwriteMode
New setting AllowUpdateOverwrite : defines how UPDATE should handle the case when a record was updated by trigger.
2 parents 4daf29e + a7b3f39 commit 13f7cf8

4 files changed

Lines changed: 96 additions & 30 deletions

File tree

builds/install/misc/firebird.conf

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,31 @@
523523
#SubQueryConversion = false
524524

525525

526+
# ----------------------------
527+
# Defines how UPDATE should handle the case when a record was already updated by
528+
# trigger or by the another positional UPDATE in the same cursor.
529+
#
530+
# It is possible that BEFORE or AFTER UPDATE trigger was fired previously by the
531+
# current UPDATE statement for the other record and has already changed the
532+
# current record being updated. Due to cursor stability, UPDATE should not see
533+
# such changes, and could silently overwrite them.
534+
#
535+
# Setting AllowUpdateOverwrite manage how engine should handle such cases:
536+
# - if set to true (default), prior changes will be overwritten,
537+
# - if set to false, error "UPDATE will overwrite changes made by the trigger or
538+
# by the another UPDATE in the same cursor" will be raised.
539+
#
540+
# CAUTION!
541+
# There is no guarantee that this setting will be available in future Firebird
542+
# versions.
543+
#
544+
# Per-database configurable.
545+
#
546+
# Type: integer
547+
#
548+
#AllowUpdateOverwrite = true
549+
550+
526551
# ============================
527552
# Plugin settings
528553
# ============================

src/common/config/config.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ enum ConfigKey
194194
KEY_OPTIMIZE_FOR_FIRST_ROWS,
195195
KEY_OUTER_JOIN_CONVERSION,
196196
KEY_SUBQUERY_CONVERSION,
197+
KEY_ALLOW_UPDATE_OVERWRITE,
197198
MAX_CONFIG_KEY // keep it last
198199
};
199200

@@ -314,7 +315,8 @@ constexpr ConfigEntry entries[MAX_CONFIG_KEY] =
314315
{TYPE_INTEGER, "MaxParallelWorkers", true, 1},
315316
{TYPE_BOOLEAN, "OptimizeForFirstRows", false, false},
316317
{TYPE_BOOLEAN, "OuterJoinConversion", false, true},
317-
{TYPE_BOOLEAN, "SubQueryConversion", false, false}
318+
{TYPE_BOOLEAN, "SubQueryConversion", false, false},
319+
{TYPE_BOOLEAN, "AllowUpdateOverwrite", false, true}
318320
};
319321

320322

@@ -652,6 +654,8 @@ class Config : public RefCounted, public GlobalStorage
652654
CONFIG_GET_PER_DB_BOOL(getOuterJoinConversion, KEY_OUTER_JOIN_CONVERSION);
653655

654656
CONFIG_GET_PER_DB_BOOL(getSubQueryConversion, KEY_SUBQUERY_CONVERSION);
657+
658+
CONFIG_GET_PER_DB_BOOL(getAllowUpdateOverwrite, KEY_ALLOW_UPDATE_OVERWRITE);
655659
};
656660

657661
// Implementation of interface to access master configuration file

src/include/firebird/impl/msg/jrd.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,3 +975,5 @@ FB_IMPL_MSG(JRD, 990, sweep_read_only, -901, "42", "000", "Database in read only
975975
FB_IMPL_MSG(JRD, 991, sweep_attach_no_cleanup, -901, "42", "000", "Attachment has no cleanup flag set")
976976
// Codes 992..997 are used in v6
977977
FB_IMPL_MSG(JRD, 998, no_user_att_while_restore, -901, "HY", "000", "User attachments are not allowed for the database being restored")
978+
// Codes 999..1004 are used in v6
979+
FB_IMPL_MSG(JRD, 1005, update_overwrite, -901, "27", "000", "UPDATE will overwrite changes made by the trigger or by the another UPDATE in the same cursor")

src/jrd/vio.cpp

Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ static void protect_system_table_delupd(thread_db* tdbb, const jrd_rel* relation
174174
bool force_flag = false);
175175
static void purge(thread_db*, record_param*);
176176
static void replace_record(thread_db*, record_param*, PageStack*, const jrd_tra*);
177-
static void refresh_fk_fields(thread_db*, Record*, record_param*, record_param*);
177+
static void refresh_changed_fields(thread_db*, Record*, record_param*, record_param*);
178178
static SSHORT set_metadata_id(thread_db*, Record*, USHORT, drq_type_t, const char*);
179179
static void set_nbackup_id(thread_db*, Record*, USHORT, drq_type_t, const char*);
180180
static void set_owner_name(thread_db*, Record*, USHORT);
@@ -3329,7 +3329,7 @@ bool VIO_modify(thread_db* tdbb, record_param* org_rpb, record_param* new_rpb, j
33293329
fb_assert(!(org_rpb->rpb_runtime_flags & RPB_undo_read));
33303330

33313331
if (undo_read)
3332-
refresh_fk_fields(tdbb, old_record, org_rpb, new_rpb);
3332+
refresh_changed_fields(tdbb, old_record, org_rpb, new_rpb);
33333333
}
33343334

33353335
// If we're the system transaction, modify stuff in place. This saves
@@ -6605,43 +6605,43 @@ static void replace_record(thread_db* tdbb,
66056605
}
66066606

66076607

6608-
static void refresh_fk_fields(thread_db* tdbb, Record* old_rec, record_param* cur_rpb,
6608+
static void refresh_changed_fields(thread_db* tdbb, Record* old_rec, record_param* cur_rpb,
66096609
record_param* new_rpb)
66106610
{
66116611
/**************************************
66126612
*
6613-
* r e f r e s h _ f k _ f i e l d s
6613+
* r e f r e s h _ c h a n g e d _ f i e l d s
66146614
*
66156615
**************************************
66166616
*
66176617
* Functional description
66186618
* Update new_rpb with foreign key fields values changed by cascade triggers.
66196619
* Consider self-referenced foreign keys only.
6620+
* Also, if AllowUpdateOverwrite is set to false, raise error when non
6621+
* self-referenced foreign key fields were changed by user triggers.
66206622
*
66216623
* old_rec - old record before modify
6622-
* cur_rpb - just read record with possibly changed FK fields
6624+
* cur_rpb - just read record with possibly changed fields
66236625
* new_rpb - new record evaluated by modify statement and before-triggers
66246626
*
66256627
**************************************/
6628+
const Database* dbb = tdbb->getDatabase();
6629+
const auto allowOverwrite = dbb->dbb_config->getAllowUpdateOverwrite();
6630+
66266631
jrd_rel* relation = cur_rpb->rpb_relation;
66276632

66286633
MET_scan_partners(tdbb, relation);
66296634

6630-
if (!(relation->rel_foreign_refs.frgn_relations))
6631-
return;
6632-
6633-
const FB_SIZE_T frgnCount = relation->rel_foreign_refs.frgn_relations->count();
6634-
if (!frgnCount)
6635-
return;
6635+
const FB_SIZE_T frgnCount = relation->rel_foreign_refs.frgn_relations ?
6636+
relation->rel_foreign_refs.frgn_relations->count() : 0;
66366637

66376638
RelationPages* relPages = cur_rpb->rpb_relation->getPages(tdbb);
66386639

6639-
// Collect all fields of all foreign keys
6640+
// Collect all fields of self-referenced foreign keys
66406641
SortedArray<int, InlineStorage<int, 16> > fields;
66416642

66426643
for (FB_SIZE_T i = 0; i < frgnCount; i++)
66436644
{
6644-
// We need self-referenced FK's only
66456645
if ((*relation->rel_foreign_refs.frgn_relations)[i] == relation->rel_id)
66466646
{
66476647
index_desc idx;
@@ -6663,26 +6663,61 @@ static void refresh_fk_fields(thread_db* tdbb, Record* old_rec, record_param* cu
66636663
}
66646664

66656665
if (fields.isEmpty())
6666-
return;
6666+
{
6667+
if (allowOverwrite)
6668+
return;
66676669

6668-
DSC desc1, desc2;
6669-
for (FB_SIZE_T idx = 0; idx < fields.getCount(); idx++)
6670+
if (cur_rpb->rpb_record->getFormat()->fmt_version == old_rec->getFormat()->fmt_version)
6671+
{
6672+
if (memcmp(cur_rpb->rpb_address, old_rec->getData(), cur_rpb->rpb_length) == 0)
6673+
return;
6674+
6675+
ERR_post(Arg::Gds(isc_update_overwrite)); // UPDATE will overwrite changes made by the trigger or by another UPDATE in the same cursor
6676+
}
6677+
// Else compare field-by-field
6678+
}
6679+
6680+
for (FB_SIZE_T fld = 0, frn = 0; fld < relation->rel_current_format->fmt_count; fld++)
66706681
{
6671-
// Detect if user changed FK field by himself.
6672-
const int fld = fields[idx];
6673-
const bool flag_old = EVL_field(relation, old_rec, fld, &desc1);
6674-
const bool flag_new = EVL_field(relation, new_rpb->rpb_record, fld, &desc2);
6682+
dsc dsc_old;
6683+
const bool flag_old = EVL_field(relation, old_rec, fld, &dsc_old);
66756684

6676-
// If field was not changed by user - pick up possible modification by
6677-
// system cascade trigger
6678-
if (flag_old == flag_new &&
6679-
(!flag_old || (flag_old && !MOV_compare(tdbb, &desc1, &desc2))))
6685+
const bool is_fk = (frn < fields.getCount() && fields[frn] == fld);
6686+
if (!is_fk)
66806687
{
6681-
const bool flag_tmp = EVL_field(relation, cur_rpb->rpb_record, fld, &desc1);
6682-
if (flag_tmp)
6683-
MOV_move(tdbb, &desc1, &desc2);
6684-
else
6685-
new_rpb->rpb_record->setNull(fld);
6688+
if (allowOverwrite)
6689+
continue;
6690+
6691+
dsc dsc_cur;
6692+
const bool flag_cur = EVL_field(relation, cur_rpb->rpb_record, fld, &dsc_cur);
6693+
6694+
// Check if current record differs from old record
6695+
if ((flag_cur != flag_old) ||
6696+
(flag_cur && flag_old && MOV_compare(tdbb, &dsc_old, &dsc_cur) != 0))
6697+
{
6698+
// Record was modified by trigger.
6699+
ERR_post(Arg::Gds(isc_update_overwrite)); // UPDATE will overwrite changes made by the trigger or by another UPDATE in the same cursor
6700+
}
6701+
}
6702+
else
6703+
{
6704+
dsc dsc_new;
6705+
const bool flag_new = EVL_field(relation, new_rpb->rpb_record, fld, &dsc_new);
6706+
6707+
// If field was not changed by user - pick up possible modification by
6708+
// system cascade trigger
6709+
if (flag_old == flag_new &&
6710+
(!flag_old || (flag_old && !MOV_compare(tdbb, &dsc_old, &dsc_new))))
6711+
{
6712+
dsc dsc_cur;
6713+
const bool flag_cur = EVL_field(relation, cur_rpb->rpb_record, fld, &dsc_cur);
6714+
if (flag_cur)
6715+
MOV_move(tdbb, &dsc_cur, &dsc_new);
6716+
else
6717+
new_rpb->rpb_record->setNull(fld);
6718+
}
6719+
6720+
frn++;
66866721
}
66876722
}
66886723
}

0 commit comments

Comments
 (0)