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
checkin-list@monetdb.org
https://www.monetdb.org/mailman/listinfo/checkin-list

Reply via email to