Skip to content

Commit 6f2f653

Browse files
committed
Front-ported pull request #8957 from FirebirdSQL/work/UpdateOverwriteMode
New setting AllowUpdateOverwrite : defines how UPDATE should handle the case when a record was updated by trigger.
1 parent 460a34e commit 6f2f653

4 files changed

Lines changed: 110 additions & 45 deletions

File tree

builds/install/misc/firebird.conf

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,31 @@
399399
#OptimizeForFirstRows = false
400400

401401

402+
# ----------------------------
403+
# Defines how UPDATE should handle the case when a record was already updated by
404+
# trigger or by the another positional UPDATE in the same cursor.
405+
#
406+
# It is possible that BEFORE or AFTER UPDATE trigger was fired previously by the
407+
# current UPDATE statement for the other record and has already changed the
408+
# current record being updated. Due to cursor stability, UPDATE should not see
409+
# such changes, and could silently overwrite them.
410+
#
411+
# Setting AllowUpdateOverwrite manage how engine should handle such cases:
412+
# - if set to true (default), prior changes will be overwritten,
413+
# - if set to false, error "UPDATE will overwrite changes made by the trigger or
414+
# by the another UPDATE in the same cursor" will be raised.
415+
#
416+
# CAUTION!
417+
# There is no guarantee that this setting will be available in future Firebird
418+
# versions.
419+
#
420+
# Per-database configurable.
421+
#
422+
# Type: integer
423+
#
424+
#AllowUpdateOverwrite = true
425+
426+
402427
# ============================
403428
# Plugin settings
404429
# ============================

src/common/config/config.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ enum ConfigKey
186186
KEY_PARALLEL_WORKERS,
187187
KEY_MAX_PARALLEL_WORKERS,
188188
KEY_OPTIMIZE_FOR_FIRST_ROWS,
189+
KEY_ALLOW_UPDATE_OVERWRITE,
189190
MAX_CONFIG_KEY // keep it last
190191
};
191192

@@ -298,7 +299,8 @@ inline constexpr ConfigEntry entries[MAX_CONFIG_KEY] =
298299
{TYPE_INTEGER, "MaxStatementCacheSize", false, 2 * 1048576}, // bytes
299300
{TYPE_INTEGER, "ParallelWorkers", true, 1},
300301
{TYPE_INTEGER, "MaxParallelWorkers", true, 1},
301-
{TYPE_BOOLEAN, "OptimizeForFirstRows", false, false}
302+
{TYPE_BOOLEAN, "OptimizeForFirstRows", false, false},
303+
{TYPE_BOOLEAN, "AllowUpdateOverwrite", false, true}
302304
};
303305

304306

@@ -619,6 +621,8 @@ class Config : public RefCounted, public GlobalStorage
619621
CONFIG_GET_GLOBAL_INT(getMaxParallelWorkers, KEY_MAX_PARALLEL_WORKERS);
620622

621623
CONFIG_GET_PER_DB_BOOL(getOptimizeForFirstRows, KEY_OPTIMIZE_FOR_FIRST_ROWS);
624+
625+
CONFIG_GET_PER_DB_BOOL(getAllowUpdateOverwrite, KEY_ALLOW_UPDATE_OVERWRITE);
622626
};
623627

624628
// Implementation of interface to access master configuration file

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,3 +1004,4 @@ FB_IMPL_MSG(JRD, 1001, sysf_argmustbe_range_inc0_1, -833, "42", "000", "Argument
10041004
FB_IMPL_MSG(JRD, 1002, argmustbe_numeric_function, -833, "42", "000", "Argument for @1 function must be numeric types")
10051005
FB_IMPL_MSG(JRD, 1003, percetile_only_one_sort_item, -833, "42", "000", "The PERCENTILE_DISC and PERENTILE_CONT functions support only one sort item in WITHIN GROUP")
10061006
FB_IMPL_MSG(JRD, 1004, argmustbe_const_within_group, -833, "42", "000", "Argument for @1 function must be constant within each group")
1007+
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: 79 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ static void protect_system_table_delupd(thread_db* tdbb, const jrd_rel* relation
178178
bool force_flag = false);
179179
static void purge(thread_db*, record_param*);
180180
static void replace_record(thread_db*, record_param*, PageStack*, const jrd_tra*);
181-
static void refresh_fk_fields(thread_db*, Record*, record_param*, record_param*);
181+
static void refresh_changed_fields(thread_db*, Record*, record_param*, record_param*);
182182
static SSHORT set_metadata_id(thread_db*, Record*, USHORT, drq_type_t, const char*);
183183
static void set_nbackup_id(thread_db*, Record*, USHORT, drq_type_t, const char*);
184184
static void set_owner_name(thread_db*, Record*, USHORT);
@@ -3317,7 +3317,7 @@ bool VIO_modify(thread_db* tdbb, record_param* org_rpb, record_param* new_rpb, j
33173317
fb_assert(!(org_rpb->rpb_runtime_flags & RPB_undo_read));
33183318

33193319
if (undo_read)
3320-
refresh_fk_fields(tdbb, old_record, org_rpb, new_rpb);
3320+
refresh_changed_fields(tdbb, old_record, org_rpb, new_rpb);
33213321
}
33223322

33233323
// If we're the system transaction, modify stuff in place. This saves
@@ -7014,83 +7014,118 @@ static void replace_record(thread_db* tdbb,
70147014
}
70157015

70167016

7017-
static void refresh_fk_fields(thread_db* tdbb, Record* old_rec, record_param* cur_rpb,
7017+
static void refresh_changed_fields(thread_db* tdbb, Record* old_rec, record_param* cur_rpb,
70187018
record_param* new_rpb)
70197019
{
70207020
/**************************************
70217021
*
7022-
* r e f r e s h _ f k _ f i e l d s
7022+
* r e f r e s h _ c h a n g e d _ f i e l d s
70237023
*
70247024
**************************************
70257025
*
70267026
* Functional description
70277027
* Update new_rpb with foreign key fields values changed by cascade triggers.
70287028
* Consider self-referenced foreign keys only.
7029+
* Also, if AllowUpdateOverwrite is set to false, raise error when non
7030+
* self-referenced foreign key fields were changed by user triggers.
70297031
*
70307032
* old_rec - old record before modify
7031-
* cur_rpb - just read record with possibly changed FK fields
7033+
* cur_rpb - just read record with possibly changed fields
70327034
* new_rpb - new record evaluated by modify statement and before-triggers
70337035
*
70347036
**************************************/
7035-
auto* relation = cur_rpb->rpb_relation->getPermanent();
7037+
const Database* dbb = tdbb->getDatabase();
7038+
const auto allowOverwrite = dbb->dbb_config->getAllowUpdateOverwrite();
70367039

7037-
relation->scanPartners(tdbb);
7040+
jrd_rel* relation = cur_rpb->rpb_relation;
7041+
auto* relPerm = relation->getPermanent();
70387042

7039-
const auto* frgn = relation->rel_foreign_refs;
7040-
if (!frgn)
7041-
return;
7042-
7043-
if (!frgn->getCount())
7044-
return;
7045-
7046-
RelationPages* relPages = cur_rpb->rpb_relation->getPages(tdbb);
7043+
relPerm->scanPartners(tdbb);
70477044

7048-
// Collect all fields of all foreign keys
7045+
// Collect all fields of self-referenced foreign keys
70497046
SortedArray<int, InlineStorage<int, 16> > fields;
70507047

7051-
for (auto& dep : *frgn)
7048+
if (const auto* frgn = relPerm->rel_foreign_refs)
70527049
{
7053-
// We need self-referenced FK's only
7054-
if (dep.dep_relation == relation->getId())
7050+
RelationPages* relPages = relation->getPages(tdbb);
7051+
for (auto& dep : *frgn)
70557052
{
7056-
index_desc idx;
7057-
idx.idx_id = idx_invalid;
7058-
7059-
if (BTR_lookup(tdbb, relation, dep.dep_reference_id, &idx, relPages))
7053+
if (dep.dep_relation == relPerm->getId())
70607054
{
7061-
fb_assert(idx.idx_flags & idx_foreign);
7055+
index_desc idx;
7056+
idx.idx_id = idx_invalid;
70627057

7063-
for (int fld = 0; fld < idx.idx_count; fld++)
7058+
if (BTR_lookup(tdbb, relPerm, dep.dep_reference_id, &idx, relPages))
70647059
{
7065-
const int fldNum = idx.idx_rpt[fld].idx_field;
7066-
if (!fields.exist(fldNum))
7067-
fields.add(fldNum);
7060+
fb_assert(idx.idx_flags & idx_foreign);
7061+
7062+
for (int fld = 0; fld < idx.idx_count; fld++)
7063+
{
7064+
const int fldNum = idx.idx_rpt[fld].idx_field;
7065+
if (!fields.exist(fldNum))
7066+
fields.add(fldNum);
7067+
}
70687068
}
70697069
}
70707070
}
70717071
}
70727072

70737073
if (fields.isEmpty())
7074-
return;
7074+
{
7075+
if (allowOverwrite)
7076+
return;
7077+
7078+
if (cur_rpb->rpb_record->getFormat()->fmt_version == old_rec->getFormat()->fmt_version)
7079+
{
7080+
if (memcmp(cur_rpb->rpb_address, old_rec->getData(), cur_rpb->rpb_length) == 0)
7081+
return;
70757082

7076-
DSC desc1, desc2;
7077-
for (FB_SIZE_T idx = 0; idx < fields.getCount(); idx++)
7083+
ERR_post(Arg::Gds(isc_update_overwrite)); // UPDATE will overwrite changes made by the trigger or by another UPDATE in the same cursor
7084+
}
7085+
// Else compare field-by-field
7086+
}
7087+
7088+
for (FB_SIZE_T fld = 0, frn = 0; fld < relation->rel_current_format->fmt_count; fld++)
70787089
{
7079-
// Detect if user changed FK field by himself.
7080-
const int fld = fields[idx];
7081-
const bool flag_old = EVL_field(cur_rpb->rpb_relation, old_rec, fld, &desc1);
7082-
const bool flag_new = EVL_field(cur_rpb->rpb_relation, new_rpb->rpb_record, fld, &desc2);
7090+
dsc dsc_old;
7091+
const bool flag_old = EVL_field(relation, old_rec, fld, &dsc_old);
70837092

7084-
// If field was not changed by user - pick up possible modification by
7085-
// system cascade trigger
7086-
if (flag_old == flag_new &&
7087-
(!flag_old || (flag_old && !MOV_compare(tdbb, &desc1, &desc2))))
7093+
const bool is_fk = (frn < fields.getCount() && fields[frn] == fld);
7094+
if (!is_fk)
70887095
{
7089-
const bool flag_tmp = EVL_field(cur_rpb->rpb_relation, cur_rpb->rpb_record, fld, &desc1);
7090-
if (flag_tmp)
7091-
MOV_move(tdbb, &desc1, &desc2);
7092-
else
7093-
new_rpb->rpb_record->setNull(fld);
7096+
if (allowOverwrite)
7097+
continue;
7098+
7099+
dsc dsc_cur;
7100+
const bool flag_cur = EVL_field(relation, cur_rpb->rpb_record, fld, &dsc_cur);
7101+
7102+
// Check if current record differs from old record
7103+
if ((flag_cur != flag_old) ||
7104+
(flag_cur && flag_old && MOV_compare(tdbb, &dsc_old, &dsc_cur) != 0))
7105+
{
7106+
// Record was modified by trigger.
7107+
ERR_post(Arg::Gds(isc_update_overwrite)); // UPDATE will overwrite changes made by the trigger or by another UPDATE in the same cursor
7108+
}
7109+
}
7110+
else
7111+
{
7112+
dsc dsc_new;
7113+
const bool flag_new = EVL_field(relation, new_rpb->rpb_record, fld, &dsc_new);
7114+
7115+
// If field was not changed by user - pick up possible modification by
7116+
// system cascade trigger
7117+
if (flag_old == flag_new &&
7118+
(!flag_old || (flag_old && !MOV_compare(tdbb, &dsc_old, &dsc_new))))
7119+
{
7120+
dsc dsc_cur;
7121+
const bool flag_cur = EVL_field(relation, cur_rpb->rpb_record, fld, &dsc_cur);
7122+
if (flag_cur)
7123+
MOV_move(tdbb, &dsc_cur, &dsc_new);
7124+
else
7125+
new_rpb->rpb_record->setNull(fld);
7126+
}
7127+
7128+
frn++;
70947129
}
70957130
}
70967131
}

0 commit comments

Comments
 (0)