Changeset: 2afe189425a1 for MonetDB
URL: https://dev.monetdb.org/hg/MonetDB/rev/2afe189425a1
Added Files:
sql/test/miscellaneous/Tests/transaction_isolation4.SQL.py
Modified Files:
sql/storage/bat/bat_storage.c
sql/test/miscellaneous/Tests/All
Branch: iso
Log Message:
Another transaction isolation case to fix. For tables that don't support
concurrent updates, hold the lock between checking segments and claiming them
diffs (164 lines):
diff --git a/sql/storage/bat/bat_storage.c b/sql/storage/bat/bat_storage.c
--- a/sql/storage/bat/bat_storage.c
+++ b/sql/storage/bat/bat_storage.c
@@ -3476,7 +3476,7 @@ add_offsets(BAT *pos, BUN slot, size_t n
}
static BAT *
-claim_segmentsV2(sql_trans *tr, sql_table *t, storage *s, size_t cnt)
+claim_segmentsV2(sql_trans *tr, sql_table *t, storage *s, size_t cnt, bool
locked)
{
int in_transaction = segments_in_transaction(tr, t), ok = LOG_OK;
assert(s->segs);
@@ -3485,7 +3485,8 @@ claim_segmentsV2(sql_trans *tr, sql_tabl
BAT *pos = NULL;
size_t total = cnt;
- lock_table(tr->store, t->base.id);
+ if (!locked)
+ lock_table(tr->store, t->base.id);
/* naive vacuum approach, iterator through segments, use deleted
segments or create new segment at the end */
for (segment *seg = s->segs->h, *p = NULL; seg && cnt && ok == LOG_OK;
p = seg, seg = seg->next) {
if (seg->deleted && seg->ts < oldest && seg->end > seg->start)
{ /* re-use old deleted or rolledback append */
@@ -3537,7 +3538,8 @@ claim_segmentsV2(sql_trans *tr, sql_tabl
if (!pos)
ok = LOG_ERR;
}
- unlock_table(tr->store, t->base.id);
+ if (!locked)
+ unlock_table(tr->store, t->base.id);
/* hard to only add this once per transaction (probably want to change
to once per new segment) */
if ((!inTransaction(tr, t) && !in_transaction && isGlobal(t)) ||
(!isNew(t) && isLocalTemp(t))) {
@@ -3558,17 +3560,18 @@ claim_segmentsV2(sql_trans *tr, sql_tabl
}
static BAT *
-claim_segments(sql_trans *tr, sql_table *t, storage *s, size_t cnt)
+claim_segments(sql_trans *tr, sql_table *t, storage *s, size_t cnt, bool
locked)
{
if (cnt > 1)
- return claim_segmentsV2(tr, t, s, cnt);
+ return claim_segmentsV2(tr, t, s, cnt, locked);
int in_transaction = segments_in_transaction(tr, t), ok = LOG_OK;
assert(s->segs);
ulng oldest = store_oldest(tr->store);
BUN slot = 0;
int reused = 0;
- lock_table(tr->store, t->base.id);
+ if (!locked)
+ lock_table(tr->store, t->base.id);
/* naive vacuum approach, iterator through segments, check for large
enough deleted segments
* or create new segment at the end */
for (segment *seg = s->segs->h, *p = NULL; seg && ok == LOG_OK; p =
seg, seg = seg->next) {
@@ -3606,7 +3609,8 @@ claim_segments(sql_trans *tr, sql_table
slot = s->segs->t->start;
}
}
- unlock_table(tr->store, t->base.id);
+ if (!locked)
+ unlock_table(tr->store, t->base.id);
/* hard to only add this once per transaction (probably want to change
to once per new segment) */
if ((!inTransaction(tr, t) && !in_transaction && isGlobal(t)) ||
(!isNew(t) && isLocalTemp(t))) {
@@ -3635,15 +3639,16 @@ claim_tab(sql_trans *tr, sql_table *t, s
if ((s = bind_del_data(tr, t, NULL)) == NULL)
return NULL;
- return claim_segments(tr, t, s, cnt); /* find slot(s) */
+ return claim_segments(tr, t, s, cnt, false); /* find slot(s) */
}
-/* some tables cannot be updates at the concurrently (user/roles etc) */
+/* some tables cannot be updated concurrently (user/roles etc) */
static void *
key_claim_tab(sql_trans *tr, sql_table *t, size_t cnt)
{
storage *s;
- int ok = LOG_OK;
+ int res = 0;
+ BAT *b = NULL;
/* we have a single segment structure for each persistent table
* for temporary tables each has its own */
@@ -3652,27 +3657,28 @@ key_claim_tab(sql_trans *tr, sql_table *
return NULL;
lock_table(tr->store, t->base.id);
- ok = segments_conflict(tr, s->segs, 1);
+ if ((res = segments_conflict(tr, s->segs, 1))) {
+ unlock_table(tr->store, t->base.id);
+ return NULL;
+ }
+ b = claim_segments(tr, t, s, cnt, true); /* find slot(s) */
unlock_table(tr->store, t->base.id);
-
- if (ok != LOG_OK)
- return NULL;
- return claim_segments(tr, t, s, cnt); /* find slot(s) */
+ return b;
}
static int
tab_validate(sql_trans *tr, sql_table *t, int uncommitted)
{
storage *s;
- int ok = LOG_OK;
+ int res = 0;
if ((s = bind_del_data(tr, t, NULL)) == NULL)
return LOG_ERR;
lock_table(tr->store, t->base.id);
- ok = segments_conflict(tr, s->segs, uncommitted);
+ res = segments_conflict(tr, s->segs, uncommitted);
unlock_table(tr->store, t->base.id);
- return ok;
+ return res ? LOG_CONFLICT : LOG_OK;
}
static BAT *
diff --git a/sql/test/miscellaneous/Tests/All b/sql/test/miscellaneous/Tests/All
--- a/sql/test/miscellaneous/Tests/All
+++ b/sql/test/miscellaneous/Tests/All
@@ -22,3 +22,4 @@ prepare
transaction_isolation
transaction_isolation2
transaction_isolation3
+transaction_isolation4
diff --git a/sql/test/miscellaneous/Tests/transaction_isolation4.SQL.py
b/sql/test/miscellaneous/Tests/transaction_isolation4.SQL.py
new file mode 100644
--- /dev/null
+++ b/sql/test/miscellaneous/Tests/transaction_isolation4.SQL.py
@@ -0,0 +1,30 @@
+from MonetDBtesting.sqltest import SQLTestCase
+
+with SQLTestCase() as mdb1:
+ with SQLTestCase() as mdb2:
+ mdb1.connect(username="monetdb", password="monetdb")
+ mdb2.connect(username="monetdb", password="monetdb")
+
+ mdb1.execute("create table myt (i int, j int);").assertSucceeded()
+ mdb1.execute('start transaction;').assertSucceeded()
+ mdb2.execute('start transaction;').assertSucceeded()
+ mdb1.execute("alter table myt add primary key (i);").assertSucceeded()
+ mdb2.execute("alter table myt add primary key
(j);").assertFailed(err_code="42000", err_message="NOT NULL CONSTRAINT:
transaction conflict detected") # only one pk per table
+ mdb1.execute('commit;').assertSucceeded()
+ mdb2.execute('rollback;').assertSucceeded()
+
+ mdb1.execute('CREATE schema mys;').assertSucceeded()
+ mdb1.execute("CREATE USER duser WITH PASSWORD 'ups' NAME 'ups' SCHEMA
mys;").assertSucceeded()
+ mdb1.execute("create table mys.myt2 (i int, j int);").assertSucceeded()
+
+ mdb1.execute('start transaction;').assertSucceeded()
+ mdb2.execute('start transaction;').assertSucceeded()
+ mdb1.execute("GRANT SELECT on table mys.myt2 to
duser;").assertSucceeded()
+ mdb2.execute('drop user duser;').assertSucceeded()
+ mdb1.execute('commit;').assertSucceeded()
+ mdb2.execute('commit;').assertFailed(err_code="40000",
err_message="COMMIT: transaction is aborted because of concurrency conflicts,
will ROLLBACK instead")
+
+ mdb1.execute('start transaction;').assertSucceeded()
+ mdb1.execute('drop table myt;').assertSucceeded()
+ mdb1.execute('drop schema mys cascade;').assertSucceeded()
+ mdb1.execute('commit;').assertSucceeded()
_______________________________________________
checkin-list mailing list
[email protected]
https://www.monetdb.org/mailman/listinfo/checkin-list