From dafaa3efb75ce1aae2e6dbefaf6f3a889dea0d21 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Mon, 7 Feb 2011 23:46:51 +0200 Subject: [PATCH] Implement genuine serializable isolation level. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Until now, our Serializable mode has in fact been what's called Snapshot Isolation, which allows some anomalies that could not occur in any serialized ordering of the transactions. This patch fixes that using a method called Serializable Snapshot Isolation, based on research papers by Michael J. Cahill (see README-SSI for full references). In Serializable Snapshot Isolation, transactions run like they do in Snapshot Isolation, but a predicate lock manager observes the reads and writes performed and aborts transactions if it detects that an anomaly might occur. This method produces some false positives, ie. it sometimes aborts transactions even though there is no anomaly. To track reads we implement predicate locking, see storage/lmgr/predicate.c. Whenever a tuple is read, a predicate lock is acquired on the tuple. Shared memory is finite, so when a transaction takes many tuple-level locks on a page, the locks are promoted to a single page-level lock, and further to a single relation level lock if necessary. To lock key values with no matching tuple, a sequential scan always takes a relation-level lock, and an index scan acquires a page-level lock that covers the search key, whether or not there are any matching keys at the moment. A predicate lock doesn't conflict with any regular locks or with another predicate locks in the normal sense. They're only used by the predicate lock manager to detect the danger of anomalies. Only serializable transactions participate in predicate locking, so there should be no extra overhead for for other transactions. Predicate locks can't be released at commit, but must be remembered until all the transactions that overlapped with it have completed. That means that we need to remember an unbounded amount of predicate locks, so we apply a lossy but conservative method of tracking locks for committed transactions. If we run short of shared memory, we overflow to a new "pg_serial" SLRU pool. We don't currently allow Serializable transactions in Hot Standby mode. That would be hard, because even read-only transactions can cause anomalies that wouldn't otherwise occur. Serializable isolation mode now means the new fully serializable level. Repeatable Read gives you the old Snapshot Isolation level that we have always had. Kevin Grittner and Dan Ports, reviewed by Jeff Davis, Heikki Linnakangas and Anssi Kääriäinen --- doc/src/sgml/catalogs.sgml | 9 +- doc/src/sgml/config.sgml | 70 + doc/src/sgml/high-availability.sgml | 9 + doc/src/sgml/indexam.sgml | 13 + doc/src/sgml/lobj.sgml | 2 +- doc/src/sgml/mvcc.sgml | 542 +- doc/src/sgml/ref/begin.sgml | 9 +- doc/src/sgml/ref/lock.sgml | 6 +- doc/src/sgml/ref/pg_dump.sgml | 35 + doc/src/sgml/ref/select.sgml | 2 +- doc/src/sgml/ref/set_transaction.sgml | 60 +- doc/src/sgml/ref/start_transaction.sgml | 11 +- doc/src/sgml/spi.sgml | 2 +- src/backend/access/heap/heapam.c | 90 +- src/backend/access/index/indexam.c | 25 +- src/backend/access/nbtree/nbtinsert.c | 12 + src/backend/access/nbtree/nbtpage.c | 7 + src/backend/access/nbtree/nbtree.c | 2 + src/backend/access/nbtree/nbtsearch.c | 13 + src/backend/access/transam/twophase.c | 3 + src/backend/access/transam/twophase_rmgr.c | 5 + src/backend/access/transam/varsup.c | 5 + src/backend/access/transam/xact.c | 21 + src/backend/commands/variable.c | 38 + src/backend/executor/nodeBitmapHeapscan.c | 3 +- src/backend/executor/nodeSeqscan.c | 5 + src/backend/parser/gram.y | 6 + src/backend/storage/ipc/ipci.c | 7 + src/backend/storage/ipc/shmem.c | 2 +- src/backend/storage/ipc/shmqueue.c | 8 +- src/backend/storage/lmgr/Makefile | 2 +- src/backend/storage/lmgr/README | 4 +- src/backend/storage/lmgr/README-SSI | 537 ++ src/backend/storage/lmgr/lwlock.c | 4 + src/backend/storage/lmgr/predicate.c | 4439 +++++++++++++++++ src/backend/tcop/utility.c | 4 + src/backend/utils/adt/lockfuncs.c | 79 + src/backend/utils/misc/guc.c | 37 + src/backend/utils/misc/postgresql.conf.sample | 4 +- src/backend/utils/resowner/resowner.c | 4 + src/backend/utils/time/snapmgr.c | 15 +- src/bin/initdb/initdb.c | 1 + src/bin/pg_dump/pg_dump.c | 35 +- src/include/access/heapam.h | 4 +- src/include/access/relscan.h | 1 + src/include/access/twophase_rmgr.h | 5 +- src/include/access/xact.h | 15 +- src/include/catalog/pg_am.h | 42 +- src/include/commands/variable.h | 2 + src/include/storage/lwlock.h | 12 +- src/include/storage/predicate.h | 67 + src/include/storage/predicate_internals.h | 476 ++ src/include/storage/shmem.h | 7 +- src/test/isolation/.gitignore | 12 + src/test/isolation/Makefile | 74 + src/test/isolation/README | 65 + .../expected/classroom-scheduling.out | 299 ++ .../expected/multiple-row-versions.out | 24 + src/test/isolation/expected/partial-index.out | 641 +++ .../isolation/expected/project-manager.out | 299 ++ .../isolation/expected/receipt-report.out | 3379 +++++++++++++ .../expected/referential-integrity.out | 629 +++ src/test/isolation/expected/ri-trigger.out | 111 + .../isolation/expected/simple-write-skew.out | 41 + .../expected/temporal-range-integrity.out | 299 ++ src/test/isolation/expected/total-cash.out | 281 ++ src/test/isolation/expected/two-ids.out | 1007 ++++ src/test/isolation/isolation_main.c | 89 + src/test/isolation/isolation_schedule | 11 + src/test/isolation/isolationtester.c | 372 ++ src/test/isolation/isolationtester.h | 59 + src/test/isolation/specparse.y | 188 + .../isolation/specs/classroom-scheduling.spec | 29 + .../specs/multiple-row-versions.spec | 48 + src/test/isolation/specs/partial-index.spec | 32 + src/test/isolation/specs/project-manager.spec | 30 + src/test/isolation/specs/receipt-report.spec | 47 + .../specs/referential-integrity.spec | 32 + src/test/isolation/specs/ri-trigger.spec | 53 + .../isolation/specs/simple-write-skew.spec | 30 + .../specs/temporal-range-integrity.spec | 38 + src/test/isolation/specs/total-cash.spec | 28 + src/test/isolation/specs/two-ids.spec | 40 + src/test/isolation/specscanner.l | 103 + src/test/regress/expected/prepared_xacts.out | 12 +- .../regress/expected/prepared_xacts_1.out | 12 +- src/test/regress/expected/transactions.out | 2 +- src/test/regress/sql/prepared_xacts.sql | 12 +- src/test/regress/sql/transactions.sql | 2 +- src/tools/pgindent/typedefs.list | 18 + 90 files changed, 14995 insertions(+), 271 deletions(-) create mode 100644 src/backend/storage/lmgr/README-SSI create mode 100644 src/backend/storage/lmgr/predicate.c create mode 100644 src/include/storage/predicate.h create mode 100644 src/include/storage/predicate_internals.h create mode 100644 src/test/isolation/.gitignore create mode 100644 src/test/isolation/Makefile create mode 100644 src/test/isolation/README create mode 100644 src/test/isolation/expected/classroom-scheduling.out create mode 100644 src/test/isolation/expected/multiple-row-versions.out create mode 100644 src/test/isolation/expected/partial-index.out create mode 100644 src/test/isolation/expected/project-manager.out create mode 100644 src/test/isolation/expected/receipt-report.out create mode 100644 src/test/isolation/expected/referential-integrity.out create mode 100644 src/test/isolation/expected/ri-trigger.out create mode 100644 src/test/isolation/expected/simple-write-skew.out create mode 100644 src/test/isolation/expected/temporal-range-integrity.out create mode 100644 src/test/isolation/expected/total-cash.out create mode 100644 src/test/isolation/expected/two-ids.out create mode 100644 src/test/isolation/isolation_main.c create mode 100644 src/test/isolation/isolation_schedule create mode 100644 src/test/isolation/isolationtester.c create mode 100644 src/test/isolation/isolationtester.h create mode 100644 src/test/isolation/specparse.y create mode 100644 src/test/isolation/specs/classroom-scheduling.spec create mode 100644 src/test/isolation/specs/multiple-row-versions.spec create mode 100644 src/test/isolation/specs/partial-index.spec create mode 100644 src/test/isolation/specs/project-manager.spec create mode 100644 src/test/isolation/specs/receipt-report.spec create mode 100644 src/test/isolation/specs/referential-integrity.spec create mode 100644 src/test/isolation/specs/ri-trigger.spec create mode 100644 src/test/isolation/specs/simple-write-skew.spec create mode 100644 src/test/isolation/specs/temporal-range-integrity.spec create mode 100644 src/test/isolation/specs/total-cash.spec create mode 100644 src/test/isolation/specs/two-ids.spec create mode 100644 src/test/isolation/specscanner.l diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index eda82c5f34..be132f2eb7 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -490,6 +490,13 @@ Can an index of this type be clustered on? + + ampredlocks + bool + + Does an index of this type manage fine-grained predicate locks? + + amkeytype oid @@ -6577,7 +6584,7 @@ text Name of the lock mode held or desired by this process (see ) + linkend="locking-tables"> and ) granted diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index d2a6445af3..2d8396e4e9 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -4456,6 +4456,7 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; transaction isolation level + setting default default_transaction_isolation configuration parameter @@ -4481,6 +4482,7 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; read-only transaction + setting default default_transaction_read_only configuration parameter @@ -4500,6 +4502,41 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; + + + deferrable transaction + setting default + + + default_transaction_deferrable configuration parameter + + + default_transaction_deferrable (boolean) + + + When running at the serializable isolation level, + a deferrable read-only SQL transaction may be delayed before + it is allowed to proceed. However, once it begins executing + it does not incur any of the overhead required to ensure + serializability; so serialization code will have no reason to + force it to abort because of concurrent updates, making this + option suitable for long-running read-only transactions. + + + + This parameter controls the default deferrable status of each + new transaction. It currently has no effect on read-write + transactions or those operating at isolation levels lower + than serializable. The default is off. + + + + Consult for more information. + + + + + session_replication_role (enum) @@ -5125,6 +5162,39 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir' + + max_predicate_locks_per_transaction (integer) + + max_predicate_locks_per_transaction configuration parameter + + + + The shared predicate lock table tracks locks on + max_predicate_locks_per_transaction * ( + ) objects (e.g., tables); + hence, no more than this many distinct objects can be locked at + any one time. This parameter controls the average number of object + locks allocated for each transaction; individual transactions + can lock more objects as long as the locks of all transactions + fit in the lock table. This is not the number of + rows that can be locked; that value is unlimited. The default, + 64, has generally been sufficient in testing, but you might need to + raise this value if you have clients that touch many different + tables in a single serializable transaction. This parameter can + only be set at server start. + + + + Increasing this parameter might cause PostgreSQL + to request more System V shared + memory than your operating system's default configuration + allows. See for information on how to + adjust those parameters, if necessary. + + + + diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml index 94d5ae8d35..a89296905b 100644 --- a/doc/src/sgml/high-availability.sgml +++ b/doc/src/sgml/high-availability.sgml @@ -1916,6 +1916,15 @@ LOG: database system is ready to accept read only connections your setting of max_prepared_transactions is 0. + + + The Serializable transaction isolation level is not yet available in hot + standby. (See and + for details.) + An attempt to set a transaction to the serializable isolation level in + hot standby mode will generate an error. + + diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml index 241064a40f..c7e997793d 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -705,6 +705,19 @@ amrestrpos (IndexScanDesc scan); it is only safe to use such scans with MVCC-compliant snapshots. + + When the ampredlocks flag is not set, any scan using that + index access method within a serializable transaction will acquire a + non-blocking predicate lock on the full index. This will generate a + read-write conflict with the insert of any tuple into that index by a + concurrent serializable transaction. If certain patterns of read-write + conflicts are detected among a set of concurrent serializable + transactions, one of those transactions may be cancelled to protect data + integrity. When the flag is set, it indicates that the index access + method implements finer-grained predicate locking, which will tend to + reduce the frequency of such transaction cancellations. + + diff --git a/doc/src/sgml/lobj.sgml b/doc/src/sgml/lobj.sgml index eebc0d6aca..0e6b705835 100644 --- a/doc/src/sgml/lobj.sgml +++ b/doc/src/sgml/lobj.sgml @@ -256,7 +256,7 @@ int lo_open(PGconn *conn, Oid lobjId, int mode); from a descriptor opened with INV_WRITE returns data that reflects all writes of other committed transactions as well as writes of the current transaction. This is similar to the behavior - of SERIALIZABLE versus READ COMMITTED transaction + of REPEATABLE READ versus READ COMMITTED transaction modes for ordinary SQL SELECT commands. diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml index 579425dad6..f71f978ef2 100644 --- a/doc/src/sgml/mvcc.sgml +++ b/doc/src/sgml/mvcc.sgml @@ -20,10 +20,22 @@ Introduction + + Multiversion Concurrency Control + + MVCC + + Serializable Snapshot Isolation + + + + SSI + + PostgreSQL provides a rich set of tools for developers to manage concurrent access to data. Internally, @@ -37,7 +49,7 @@ could be caused by (other) concurrent transaction updates on the same data rows, providing transaction isolation for each database session. MVCC, by eschewing - explicit locking methodologies of traditional database systems, + the locking methodologies of traditional database systems, minimizes lock contention in order to allow for reasonable performance in multiuser environments. @@ -48,12 +60,17 @@ MVCC locks acquired for querying (reading) data do not conflict with locks acquired for writing data, and so reading never blocks writing and writing never blocks reading. + PostgreSQL maintains this guarantee + even when providing the strictest level of transaction + isolation through the use of an innovative Serializable + Snapshot Isolation (SSI) level. Table- and row-level locking facilities are also available in - PostgreSQL for applications that cannot - adapt easily to MVCC behavior. However, proper + PostgreSQL for applications which don't + generally need full transaction isolation and prefer to explicitly + manage particular points of conflict. However, proper use of MVCC will generally provide better performance than locks. In addition, application-defined advisory locks provide a mechanism for acquiring locks that are not tied @@ -70,9 +87,21 @@ The SQL standard defines four levels of - transaction isolation in terms of three phenomena that must be - prevented between concurrent transactions. These undesirable - phenomena are: + transaction isolation. The most strict is Serializable, + which is defined by the standard in a paragraph which says that any + concurrent execution of a set of Serializable transactions is guaranteed + to produce the same effect as running them one at a time in some order. + The other three levels are defined in terms of phenomena, resulting from + interaction between concurrent transactions, which must not occur at + each level. The standard notes that due to the definition of + Serializable, none of these phenomena are possible at that level. (This + is hardly surprising -- if the effect of the transactions must be + consistent with having been run one at a time, how could you see any + phenomena caused by interactions?) + + + + The phenomena which are prohibited are various levels are: @@ -211,15 +240,16 @@ In PostgreSQL, you can request any of the four standard transaction isolation levels. But internally, there are - only two distinct isolation levels, which correspond to the levels Read - Committed and Serializable. When you select the level Read - Uncommitted you really get Read Committed, and when you select - Repeatable Read you really get Serializable, so the actual + only three distinct isolation levels, which correspond to the levels Read + Committed, Repeatable Read, and Serializable. When you select the level Read + Uncommitted you really get Read Committed, and phantom reads are not possible + in the PostgreSQL implementation of Repeatable + Read, so the actual isolation level might be stricter than what you select. This is permitted by the SQL standard: the four isolation levels only define which phenomena must not happen, they do not define which phenomena must happen. The reason that PostgreSQL - only provides two isolation levels is that this is the only + only provides three isolation levels is that this is the only sensible way to map the standard isolation levels to the multiversion concurrency control architecture. The behavior of the available isolation levels is detailed in the following subsections. @@ -238,6 +268,10 @@ read committed + + read committed + + Read Committed is the default isolation level in PostgreSQL. When a transaction @@ -345,39 +379,46 @@ COMMIT; - - Serializable Isolation Level + + Repeatable Read Isolation Level transaction isolation level - serializable + repeatable read + + + + repeatable read - The Serializable isolation level provides the strictest transaction - isolation. This level emulates serial transaction execution, - as if transactions had been executed one after another, serially, - rather than concurrently. However, applications using this level must - be prepared to retry transactions due to serialization failures. + The Repeatable Read isolation level only sees + data committed before the transaction began; it never sees either + uncommitted data or changes committed during transaction execution + by concurrent transactions. (However, the query does see the + effects of previous updates executed within its own transaction, + even though they are not yet committed.) This is a stronger + guarantee than is required by the SQL standard + for this isolation level, and prevents all of the phenomena described + in . As mentioned above, this is + specifically allowed by the standard, which only describes the + minimum protections each isolation level must + provide. - When a transaction is using the serializable level, - a SELECT query only sees data committed before the - transaction began; it never sees either uncommitted data or changes - committed - during transaction execution by concurrent transactions. (However, - the query does see the effects of previous updates - executed within its own transaction, even though they are not yet - committed.) This is different from Read Committed in that - a query in a serializable transaction - sees a snapshot as of the start of the transaction, - not as of the start + This level is different from Read Committed in that a query in a + repeatable read transaction sees a snapshot as of the start of the + transaction, not as of the start of the current query within the transaction. Thus, successive SELECT commands within a single transaction see the same data, i.e., they do not see changes made by other transactions that committed after their own transaction started. - (This behavior can be ideal for reporting applications.) + + + + Applications using this level must be prepared to retry transactions + due to serialization failures. @@ -386,22 +427,21 @@ COMMIT; behave the same as SELECT in terms of searching for target rows: they will only find target rows that were committed as of the transaction start time. However, such a - target - row might have already been updated (or deleted or locked) by + target row might have already been updated (or deleted or locked) by another concurrent transaction by the time it is found. In this case, the - serializable transaction will wait for the first updating transaction to commit or + repeatable read transaction will wait for the first updating transaction to commit or roll back (if it is still in progress). If the first updater rolls back, - then its effects are negated and the serializable transaction can proceed + then its effects are negated and the repeatable read transaction can proceed with updating the originally found row. But if the first updater commits (and actually updated or deleted the row, not just locked it) - then the serializable transaction will be rolled back with the message + then the repeatable read transaction will be rolled back with the message ERROR: could not serialize access due to concurrent update - because a serializable transaction cannot modify or lock rows changed by - other transactions after the serializable transaction began. + because a repeatable read transaction cannot modify or lock rows changed by + other transactions after the repeatable read transaction began. @@ -419,39 +459,70 @@ ERROR: could not serialize access due to concurrent update - The Serializable mode provides a rigorous guarantee that each - transaction sees a wholly consistent view of the database. However, - the application has to be prepared to retry transactions when concurrent - updates make it impossible to sustain the illusion of serial execution. - Since the cost of redoing complex transactions can be significant, - serializable mode is recommended only when updating transactions contain logic - sufficiently complex that they might give wrong answers in Read - Committed mode. Most commonly, Serializable mode is necessary when - a transaction executes several successive commands that must see - identical views of the database. + The Repeatable Read mode provides a rigorous guarantee that each + transaction sees a completely stable view of the database. However, + this view will not necessarily always be consistent with some serial + (one at a time) execution of concurrent transactions of the same level. + For example, even a read only transaction at this level may see a + control record updated to show that a batch has been completed but + not see one of the detail records which is logically + part of the batch because it read an earlier revision of the control + record. Attempts to enforce business rules by transactions running at + this isolation level are not likely to work correctly without careful use + of explicit locks to block conflicting transactions. - - Serializable Isolation Versus True Serializability + + + Prior to PostgreSQL version 9.1, a request + for the Serializable transaction isolation level provided exactly the + same behavior described here. To retain the legacy Serializable + behavior, Repeatable Read should now be requested. + + + + + + Serializable Isolation Level - serializability + transaction isolation level + serializable + + + + serializable predicate locking + + serialization anomaly + + - The intuitive meaning (and mathematical definition) of - serializable execution is that any two successfully committed - concurrent transactions will appear to have executed strictly serially, - one after the other — although which one appeared to occur first might - not be predictable in advance. It is important to realize that forbidding - the undesirable behaviors listed in - is not sufficient to guarantee true serializability, and in fact - PostgreSQL's Serializable mode does - not guarantee serializable execution in this sense. As an example, + The Serializable isolation level provides the strictest transaction + isolation. This level emulates serial transaction execution, + as if transactions had been executed one after another, serially, + rather than concurrently. However, like the Repeatable Read level, + applications using this level must + be prepared to retry transactions due to serialization failures. + In fact, this isolation level works exactly the same as Repeatable + Read except that it monitors for conditions which could make + execution of a concurrent set of serializable transactions behave + in a manner inconsistent with all possible serial (one at a time) + executions of those transactions. This monitoring does not + introduce any blocking beyond that present in repeatable read, but + there is some overhead to the monitoring, and detection of the + conditions which could cause a + serialization anomaly will trigger a + serialization failure. + + + + As an example, consider a table mytab, initially containing: class | value @@ -472,48 +543,137 @@ SELECT SUM(value) FROM mytab WHERE class = 1; SELECT SUM(value) FROM mytab WHERE class = 2; and obtains the result 300, which it inserts in a new row with - class = 1. Then both transactions commit. None of - the listed undesirable behaviors have occurred, yet we have a result - that could not have occurred in either order serially. If A had + class = 1. Then both transactions try to commit. + If either transaction were running at the Repeatable Read isolation level, + both would be allowed to commit; but since there is no serial order of execution + consistent with the result, using Serializable transactions will allow one + transaction to commit and and will roll the other back with this message: + + +ERROR: could not serialize access due to read/write dependencies among transactions + + + This is because if A had executed before B, B would have computed the sum 330, not 300, and similarly the other order would have resulted in a different sum computed by A. - To guarantee true mathematical serializability, it is necessary for - a database system to enforce predicate locking, which - means that a transaction cannot insert or modify a row that would - have matched the WHERE condition of a query in another concurrent - transaction. For example, once transaction A has executed the query - SELECT ... WHERE class = 1, a predicate-locking system - would forbid transaction B from inserting any new row with class 1 - until A has committed. - - - Essentially, a predicate-locking system prevents phantom reads - by restricting what is written, whereas MVCC prevents them by - restricting what is read. - - - Such a locking system is complex to - implement and extremely expensive in execution, since every session must - be aware of the details of every query executed by every concurrent - transaction. And this large expense is mostly wasted, since in - practice most applications do not do the sorts of things that could - result in problems. (Certainly the example above is rather contrived - and unlikely to represent real software.) For these reasons, - PostgreSQL does not implement predicate - locking. + To guarantee true serializability PostgreSQL + uses predicate locking, which means that it keeps locks + which allow it to determine when a write would have had an impact on + the result of a previous read from a concurrent transaction, had it run + first. In PostgreSQL these locks do not + cause any blocking and therefore can not play any part in + causing a deadlock. They are used to identify and flag dependencies + among concurrent serializable transactions which in certain combinations + can lead to serialization anomalies. In contrast, a Read Committed or + Repeatable Read transaction which wants to ensure data consistency may + need to take out a lock on an entire table, which could block other + users attempting to use that table, or it may use SELECT FOR + UPDATE or SELECT FOR SHARE which not only + can block other transactions but cause disk access. - In cases where the possibility of non-serializable execution - is a real hazard, problems can be prevented by appropriate use of - explicit locking. Further discussion appears in the following - sections. + Predicate locks in PostgreSQL, like in most + other database systems, are based on data actually accessed by a + transaction. These will show up in the + pg_locks + system view with a mode of SIReadLock. The + particular locks + acquired during execution of a query will depend on the plan used by + the query, and multiple finer-grained locks (e.g., tuple locks) may be + combined into fewer coarser-grained locks (e.g., page locks) during the + course of the transaction to prevent exhaustion of the memory used to + track the locks. A READ ONLY transaction may be able to + release its SIRead locks before completion, if it detects that no + conflicts can still occur which could lead to a serialization anomaly. + In fact, READ ONLY transactions will often be able to + establish that fact at startup and avoid taking any predicate locks. + If you explicitly request a SERIALIZABLE READ ONLY DEFERRABLE + transaction, it will block until it can establish this fact. (This is + the only case where Serializable transactions block but + Repeatable Read transactions don't.) On the other hand, SIRead locks + often need to be kept past transaction commit, until overlapping read + write transactions complete. - + + + Consistent use of Serializable transactions can simplify development. + The guarantee that any set of concurrent serializable transactions will + have the same effect as if they were run one at a time means that if + you can demonstrate that a singe transaction, as written, will do the + right thing when run by itself, you can have confidence that it will + do the right thing in any mix of serializable transactions, even without + any information about what those other transactions might do. It is + important that an environment which uses this technique have a + generalized way of handling serialization failures (which always return + with a SQLSTATE value of '40001'), because it will be very hard to + predict exactly which transactions might contribute to the read/write + dependencies and need to be rolled back to prevent serialization + anomalies. The monitoring of read/write dependences has a cost, as does + the restart of transactions which are terminated with a serialization + failure, but balanced against the cost and blocking involved in use of + explicit locks and SELECT FOR UPDATE or SELECT FOR + SHARE, Serializable transactions are the best performance choice + for some environments. + + + + For optimal performance when relying on Serializable transactions for + concurrency control, these issues should be considered: + + + + + Declare transactions as READ ONLY when possible. + + + + + Control the number of active connections, using a connection pool if + needed. This is always an important performance consideration, but + it can be paricularly important in a busy system using Serializable + transactions. + + + + + Don't put more into a single transaction than needed for integrity + purposes. + + + + + Don't leave connections dangling idle in transaction + longer than necessary. + + + + + Eliminate explicit locks, SELECT FOR UPDATE, and + SELECT FOR SHARE where no longer needed due to the + protections automatically provided by Serializable transactions. + + + + + + + + Support for the Serializable transaction isolation level has not yet + been added to Hot Standby replication targets (described in + ). The strictest isolation level currently + supported in hot standby mode is Repeatable Read. While performing all + permanent database writes within Serializable transactions on the + master will ensure that all standbys will eventually reach a consistent + state, a Repeatable Read transaction run on the standby can sometimes + see a transient state which in inconsistent with any serial execution + of serializable transactions on the master. + + @@ -1109,80 +1269,148 @@ SELECT pg_advisory_lock(q.id) FROM Data Consistency Checks at the Application Level - Because readers in PostgreSQL - do not lock data, regardless of - transaction isolation level, data read by one transaction can be - overwritten by another concurrent transaction. In other words, - if a row is returned by SELECT it doesn't mean that - the row is still current at the instant it is returned (i.e., sometime - after the current query began). The row might have been modified or - deleted by an already-committed transaction that committed after - the SELECT started. - Even if the row is still valid now, it could be changed or - deleted - before the current transaction does a commit or rollback. + It is very difficult to enforce business rules regarding data integrity + using Read Committed transactions because the view of the data is + shifting with each statement, and even a single statement may not + restrict itself to the statement's snapshot if a write conflict occurs. - Another way to think about it is that each - transaction sees a snapshot of the database contents, and concurrently - executing transactions might very well see different snapshots. So the - whole concept of now is somewhat ill-defined anyway. - This is not normally - a big problem if the client applications are isolated from each other, - but if the clients can communicate via channels outside the database - then serious confusion might ensue. + While a Repeatable Read transaction has a stable view of the data + throughout its execution, there is a subtle issue with using + MVCC snapshots for data consistency checks, involving + something known as read/write conflicts. + If one transaction writes data and a concurrent transaction attempts + to read the same data (whether before or after the write), it cannot + see the work of the other transaction. The reader then appears to have + executed first regardless of which started first or which committed + first. If that is as far as it goes, there is no problem, but + if the reader also writes data which is read by a concurrent transaction + there is now a transaction which appears to have run before either of + the previously mentioned transactions. If the transaction which appears + to have executed last actually commits first, it is very easy for a + cycle to appear in a graph of the order of execution of the transactions. + When such a cycle appears, integrity checks will not work correctly + without some help. - To ensure the current validity of a row and protect it against - concurrent updates one must use SELECT FOR UPDATE, - SELECT FOR SHARE, or an appropriate LOCK - TABLE statement. (SELECT FOR UPDATE - and SELECT FOR SHARE lock just the - returned rows against concurrent updates, while LOCK - TABLE locks the whole table.) This should be taken into - account when porting applications to - PostgreSQL from other environments. + As mentioned in , Serializable + transactions are just Repeatable Read transactions which add + non-blocking monitoring for dangerous patterns of read/write conflicts. + When a pattern is detected which could cause a cycle in the apparent + order of execution, one of the transactions involved is rolled back to + break the cycle. - - Global validity checks require extra thought under MVCC. - For example, a banking application might wish to check that the sum of - all credits in one table equals the sum of debits in another table, - when both tables are being actively updated. Comparing the results of two - successive SELECT sum(...) commands will not work reliably in - Read Committed mode, since the second query will likely include the results - of transactions not counted by the first. Doing the two sums in a - single serializable transaction will give an accurate picture of only the - effects of transactions that committed before the serializable transaction - started — but one might legitimately wonder whether the answer is still - relevant by the time it is delivered. If the serializable transaction - itself applied some changes before trying to make the consistency check, - the usefulness of the check becomes even more debatable, since now it - includes some but not all post-transaction-start changes. In such cases - a careful person might wish to lock all tables needed for the check, - in order to get an indisputable picture of current reality. A - SHARE mode (or higher) lock guarantees that there are no - uncommitted changes in the locked table, other than those of the current - transaction. - + + Enforcing Consistency With Serializable Transactions - - Note also that if one is relying on explicit locking to prevent concurrent - changes, one should either use Read Committed mode, or in Serializable - mode be careful to obtain - locks before performing queries. A lock obtained by a - serializable transaction guarantees that no other transactions modifying - the table are still running, but if the snapshot seen by the - transaction predates obtaining the lock, it might predate some now-committed - changes in the table. A serializable transaction's snapshot is actually - frozen at the start of its first query or data-modification command - (SELECT, INSERT, - UPDATE, or DELETE), so - it is possible to obtain locks explicitly before the snapshot is - frozen. - + + If the Serializable transaction isolation level is used for all writes + and for all reads which need a consistent view of the data, no other + effort is required to ensure consistency. Software from other + environments which is written to use serializable transactions to + ensure consistency should just work in this regard in + PostgreSQL. + + + + When using this technique, it will avoid creating an unnecessary burden + for application programmers if the application software goes through a + framework which automatically retries transactions which are rolled + back with a serialization failure. It may be a good idea to set + default_transaction_isolation to serializable. + It would also be wise to take some action to ensure that no other + transaction isolation level is used, either inadvertently or to + subvert integrity checks, through checks of the transaction isolation + level in triggers. + + + + See for performance suggestions. + + + + + This level of integrity protection using Serializable transactions + does not yet extend to hot standby mode (). + Because of that, those using hot standby may want to use Repeatable + Read and explicit locking.on the master. + + + + + + Enforcing Consistency With Explicit Blocking Locks + + + When non-serializable writes are possible, + to ensure the current validity of a row and protect it against + concurrent updates one must use SELECT FOR UPDATE, + SELECT FOR SHARE, or an appropriate LOCK + TABLE statement. (SELECT FOR UPDATE + and SELECT FOR SHARE lock just the + returned rows against concurrent updates, while LOCK + TABLE locks the whole table.) This should be taken into + account when porting applications to + PostgreSQL from other environments. + + + + Also of note to those converting from other environments is the fact + that SELECT FOR UPDATE does not ensure that a + concurrent transaction will not update or delete a selected row. + To do that in PostgreSQL you must actually + update the row, even if no values need to be changed. + SELECT FOR UPDATE temporarily blocks + other transactions from acquiring the same lock or executing an + UPDATE or DELETE which would + affect the locked row, but once the transaction holding this lock + commits or rolls back, a blocked transaction will proceed with the + conflicting operation unless an actual UPDATE of + the row was performed while the lock was held. + + + + Global validity checks require extra thought under + non-serializable MVCC. + For example, a banking application might wish to check that the sum of + all credits in one table equals the sum of debits in another table, + when both tables are being actively updated. Comparing the results of two + successive SELECT sum(...) commands will not work reliably in + Read Committed mode, since the second query will likely include the results + of transactions not counted by the first. Doing the two sums in a + single repeatable read transaction will give an accurate picture of only the + effects of transactions that committed before the repeatable read transaction + started — but one might legitimately wonder whether the answer is still + relevant by the time it is delivered. If the repeatable read transaction + itself applied some changes before trying to make the consistency check, + the usefulness of the check becomes even more debatable, since now it + includes some but not all post-transaction-start changes. In such cases + a careful person might wish to lock all tables needed for the check, + in order to get an indisputable picture of current reality. A + SHARE mode (or higher) lock guarantees that there are no + uncommitted changes in the locked table, other than those of the current + transaction. + + + + Note also that if one is relying on explicit locking to prevent concurrent + changes, one should either use Read Committed mode, or in Repeatable Read + mode be careful to obtain + locks before performing queries. A lock obtained by a + repeatable read transaction guarantees that no other transactions modifying + the table are still running, but if the snapshot seen by the + transaction predates obtaining the lock, it might predate some now-committed + changes in the table. A repeatable read transaction's snapshot is actually + frozen at the start of its first query or data-modification command + (SELECT, INSERT, + UPDATE, or DELETE), so + it is possible to obtain locks explicitly before the snapshot is + frozen. + + diff --git a/doc/src/sgml/ref/begin.sgml b/doc/src/sgml/ref/begin.sgml index c4d90ef8c6..4c6a2b4a2c 100644 --- a/doc/src/sgml/ref/begin.sgml +++ b/doc/src/sgml/ref/begin.sgml @@ -27,6 +27,7 @@ BEGIN [ WORK | TRANSACTION ] [ transaction_mode @@ -57,7 +58,7 @@ BEGIN [ WORK | TRANSACTION ] [ transaction_mode - If the isolation level or read/write mode is specified, the new + If the isolation level, read/write mode, or deferrable mode is specified, the new transaction has those characteristics, as if was executed. @@ -135,6 +136,12 @@ BEGIN; contains additional compatibility information. + + The DEFERRABLE + transaction_mode + is a PostgreSQL language extension. + + Incidentally, the BEGIN key word is used for a different purpose in embedded SQL. You are advised to be careful diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml index 86cd744ea4..9b464580aa 100644 --- a/doc/src/sgml/ref/lock.sgml +++ b/doc/src/sgml/ref/lock.sgml @@ -67,10 +67,12 @@ LOCK [ TABLE ] [ ONLY ] name [, ... - To achieve a similar effect when running a transaction at the Serializable + To achieve a similar effect when running a transaction at the + REPEATABLE READ or SERIALIZABLE isolation level, you have to execute the LOCK TABLE statement before executing any SELECT or data modification statement. - A serializable transaction's view of data will be frozen when its first + A REPEATABLE READ or SERIALIZABLE transaction's + view of data will be frozen when its first SELECT or data modification statement begins. A LOCK TABLE later in the transaction will still prevent concurrent writes — but it won't ensure that what the transaction reads corresponds to diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index f90d669995..25dc2a7014 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -646,6 +646,41 @@ PostgreSQL documentation + + + + + Use a serializable transaction for the dump, to + ensure that the snapshot used is consistent with later database + states; but do this by waiting for a point in the transaction stream + at which no anomalies can be present, so that there isn't a risk of + the dump failing or causing other transactions to roll back with a + serialization_failure. See + for more information about transaction isolation and concurrency + control. + + + + This option is not beneficial for a dump which is intended only for + disaster recovery. It could be useful for a dump used to load a + copy of the database for reporting or other read-only load sharing + while the original database continues to be updated. Without it the + dump may reflect a state which is not consistent with any serial + execution of the transactions eventually committed. For example, if + batch processing techniques are used, a batch may show as closed in + the dump without all of the items which are in the batch appearing. + + + + This option will make no difference if there are no read-write + transactions active when pg_dump is started. If read-write + transactions are active, the start of the dump may be delayed for an + indeterminate length of time. Once running, performance with or + without the switch is the same. + + + + diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml index 24f8249713..92e47d1279 100644 --- a/doc/src/sgml/ref/select.sgml +++ b/doc/src/sgml/ref/select.sgml @@ -1144,7 +1144,7 @@ FOR SHARE [ OF table_name [, ...] ] has already locked a selected row or rows, SELECT FOR UPDATE will wait for the other transaction to complete, and will then lock and return the updated row (or no row, if the - row was deleted). Within a SERIALIZABLE transaction, + row was deleted). Within a REPEATABLE READ or SERIALIZABLE transaction, however, an error will be thrown if a row to be locked has changed since the transaction started. For further discussion see . diff --git a/doc/src/sgml/ref/set_transaction.sgml b/doc/src/sgml/ref/set_transaction.sgml index 57ab38b685..2c57f45511 100644 --- a/doc/src/sgml/ref/set_transaction.sgml +++ b/doc/src/sgml/ref/set_transaction.sgml @@ -15,6 +15,21 @@ SET TRANSACTION + + transaction isolation level + setting + + + + read-only transaction + setting + + + + deferrable transaction + setting + + SET TRANSACTION transaction_mode [, ...] @@ -24,6 +39,7 @@ SET SESSION CHARACTERISTICS AS TRANSACTION transa ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED } READ WRITE | READ ONLY + [ NOT ] DEFERRABLE @@ -42,8 +58,8 @@ SET SESSION CHARACTERISTICS AS TRANSACTION transa The available transaction characteristics are the transaction - isolation level and the transaction access mode (read/write or - read-only). + isolation level, the transaction access mode (read/write or + read-only), and the deferrable mode. @@ -62,7 +78,7 @@ SET SESSION CHARACTERISTICS AS TRANSACTION transa - SERIALIZABLE + REPEATABLE READ All statements of the current transaction can only see rows committed @@ -71,14 +87,27 @@ SET SESSION CHARACTERISTICS AS TRANSACTION transa + + + SERIALIZABLE + + + All statements of the current transaction can only see rows committed + before the first query or data-modification statement was executed in + this transaction. If a pattern of reads and writes among concurrent + serializable transactions would create a situation which could not + have occurred for any serial (one-at-a-time) execution of those + transactions, one of them will be rolled back with a + serialization_failure SQLSTATE. + + + - The SQL standard defines two additional levels, READ - UNCOMMITTED and REPEATABLE READ. + The SQL standard defines one additional level, READ + UNCOMMITTED. In PostgreSQL READ - UNCOMMITTED is treated as - READ COMMITTED, while REPEATABLE - READ is treated as SERIALIZABLE. + UNCOMMITTED is treated as READ COMMITTED. @@ -127,8 +156,9 @@ SET SESSION CHARACTERISTICS AS TRANSACTION transa The session default transaction modes can also be set by setting the - configuration parameters - and . + configuration parameters , + , and + . (In fact SET SESSION CHARACTERISTICS is just a verbose equivalent for setting these variables with SET.) This means the defaults can be set in the configuration file, via @@ -146,9 +176,7 @@ SET SESSION CHARACTERISTICS AS TRANSACTION transa isolation level in the standard. In PostgreSQL the default is ordinarily READ COMMITTED, but you can change it as - mentioned above. Because of lack of predicate locking, the - SERIALIZABLE level is not truly - serializable. See for details. + mentioned above. @@ -158,6 +186,12 @@ SET SESSION CHARACTERISTICS AS TRANSACTION transa not implemented in the PostgreSQL server. + + The DEFERRABLE + transaction_mode + is a PostgreSQL language extension. + + The SQL standard requires commas between successive transaction_modes, but for historical diff --git a/doc/src/sgml/ref/start_transaction.sgml b/doc/src/sgml/ref/start_transaction.sgml index ffa4976279..f25a3e9536 100644 --- a/doc/src/sgml/ref/start_transaction.sgml +++ b/doc/src/sgml/ref/start_transaction.sgml @@ -27,6 +27,7 @@ START TRANSACTION [ transaction_mode @@ -34,8 +35,8 @@ START TRANSACTION [ transaction_modeDescription - This command begins a new transaction block. If the isolation level or - read/write mode is specified, the new transaction has those + This command begins a new transaction block. If the isolation level, + read/write mode, or deferrable mode is specified, the new transaction has those characteristics, as if was executed. This is the same as the command. @@ -64,6 +65,12 @@ START TRANSACTION [ transaction_mode + + The DEFERRABLE + transaction_mode + is a PostgreSQL language extension. + + The SQL standard requires commas between successive transaction_modes, but for historical diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml index fcee74f605..e2dec39244 100644 --- a/doc/src/sgml/spi.sgml +++ b/doc/src/sgml/spi.sgml @@ -340,7 +340,7 @@ SPI_execute("INSERT INTO foo SELECT * FROM bar", false, 5); SPI_execute increments the command counter and computes a new snapshot before executing each command in the string. The snapshot does not actually change if the - current transaction isolation level is SERIALIZABLE, but in + current transaction isolation level is SERIALIZABLE or REPEATABLE READ, but in READ COMMITTED mode the snapshot update allows each command to see the results of newly committed transactions from other sessions. This is essential for consistent behavior when the commands are modifying diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 25d9fdea3a..7dcc6015de 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -57,6 +57,7 @@ #include "storage/bufmgr.h" #include "storage/freespace.h" #include "storage/lmgr.h" +#include "storage/predicate.h" #include "storage/procarray.h" #include "storage/smgr.h" #include "storage/standby.h" @@ -261,20 +262,20 @@ heapgetpage(HeapScanDesc scan, BlockNumber page) { if (ItemIdIsNormal(lpp)) { + HeapTupleData loctup; bool valid; + loctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp); + loctup.t_len = ItemIdGetLength(lpp); + ItemPointerSet(&(loctup.t_self), page, lineoff); + if (all_visible) valid = true; else - { - HeapTupleData loctup; - - loctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp); - loctup.t_len = ItemIdGetLength(lpp); - ItemPointerSet(&(loctup.t_self), page, lineoff); - valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer); - } + + CheckForSerializableConflictOut(valid, scan->rs_rd, &loctup, buffer); + if (valid) scan->rs_vistuples[ntup++] = lineoff; } @@ -468,12 +469,16 @@ heapgettup(HeapScanDesc scan, snapshot, scan->rs_cbuf); + CheckForSerializableConflictOut(valid, scan->rs_rd, tuple, scan->rs_cbuf); + if (valid && key != NULL) HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd), nkeys, key, valid); if (valid) { + if (!scan->rs_relpredicatelocked) + PredicateLockTuple(scan->rs_rd, tuple); LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK); return; } @@ -741,12 +746,16 @@ heapgettup_pagemode(HeapScanDesc scan, nkeys, key, valid); if (valid) { + if (!scan->rs_relpredicatelocked) + PredicateLockTuple(scan->rs_rd, tuple); scan->rs_cindex = lineindex; return; } } else { + if (!scan->rs_relpredicatelocked) + PredicateLockTuple(scan->rs_rd, tuple); scan->rs_cindex = lineindex; return; } @@ -1213,6 +1222,7 @@ heap_beginscan_internal(Relation relation, Snapshot snapshot, scan->rs_strategy = NULL; /* set in initscan */ scan->rs_allow_strat = allow_strat; scan->rs_allow_sync = allow_sync; + scan->rs_relpredicatelocked = false; /* * we can use page-at-a-time mode if it's an MVCC-safe snapshot @@ -1459,8 +1469,13 @@ heap_fetch(Relation relation, */ valid = HeapTupleSatisfiesVisibility(tuple, snapshot, buffer); + if (valid) + PredicateLockTuple(relation, tuple); + LockBuffer(buffer, BUFFER_LOCK_UNLOCK); + CheckForSerializableConflictOut(valid, relation, tuple, buffer); + if (valid) { /* @@ -1506,13 +1521,15 @@ heap_fetch(Relation relation, * heap_fetch, we do not report any pgstats count; caller may do so if wanted. */ bool -heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot, - bool *all_dead) +heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, + Snapshot snapshot, bool *all_dead) { Page dp = (Page) BufferGetPage(buffer); TransactionId prev_xmax = InvalidTransactionId; OffsetNumber offnum; bool at_chain_start; + bool valid; + bool match_found; if (all_dead) *all_dead = true; @@ -1522,6 +1539,7 @@ heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot, Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer)); offnum = ItemPointerGetOffsetNumber(tid); at_chain_start = true; + match_found = false; /* Scan through possible multiple members of HOT-chain */ for (;;) @@ -1552,6 +1570,8 @@ heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot, heapTuple.t_data = (HeapTupleHeader) PageGetItem(dp, lp); heapTuple.t_len = ItemIdGetLength(lp); + heapTuple.t_tableOid = relation->rd_id; + heapTuple.t_self = *tid; /* * Shouldn't see a HEAP_ONLY tuple at chain start. @@ -1569,12 +1589,18 @@ heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot, break; /* If it's visible per the snapshot, we must return it */ - if (HeapTupleSatisfiesVisibility(&heapTuple, snapshot, buffer)) + valid = HeapTupleSatisfiesVisibility(&heapTuple, snapshot, buffer); + CheckForSerializableConflictOut(valid, relation, &heapTuple, buffer); + if (valid) { ItemPointerSetOffsetNumber(tid, offnum); + PredicateLockTuple(relation, &heapTuple); if (all_dead) *all_dead = false; - return true; + if (IsolationIsSerializable()) + match_found = true; + else + return true; } /* @@ -1603,7 +1629,7 @@ heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot, break; /* end of chain */ } - return false; + return match_found; } /* @@ -1622,7 +1648,7 @@ heap_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot, buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid)); LockBuffer(buffer, BUFFER_LOCK_SHARE); - result = heap_hot_search_buffer(tid, buffer, snapshot, all_dead); + result = heap_hot_search_buffer(tid, relation, buffer, snapshot, all_dead); LockBuffer(buffer, BUFFER_LOCK_UNLOCK); ReleaseBuffer(buffer); return result; @@ -1729,6 +1755,7 @@ heap_get_latest_tid(Relation relation, * result candidate. */ valid = HeapTupleSatisfiesVisibility(&tp, snapshot, buffer); + CheckForSerializableConflictOut(valid, relation, &tp, buffer); if (valid) *tid = ctid; @@ -1893,6 +1920,13 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, buffer = RelationGetBufferForTuple(relation, heaptup->t_len, InvalidBuffer, options, bistate); + /* + * We're about to do the actual insert -- check for conflict at the + * relation or buffer level first, to avoid possibly having to roll + * back work we've just done. + */ + CheckForSerializableConflictIn(relation, NULL, buffer); + /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); @@ -2193,6 +2227,12 @@ l1: return result; } + /* + * We're about to do the actual delete -- check for conflict first, + * to avoid possibly having to roll back work we've just done. + */ + CheckForSerializableConflictIn(relation, &tp, buffer); + /* replace cid with a combo cid if necessary */ HeapTupleHeaderAdjustCmax(tp.t_data, &cid, &iscombo); @@ -2546,6 +2586,12 @@ l2: return result; } + /* + * We're about to do the actual update -- check for conflict first, + * to avoid possibly having to roll back work we've just done. + */ + CheckForSerializableConflictIn(relation, &oldtup, buffer); + /* Fill in OID and transaction status data for newtup */ if (relation->rd_rel->relhasoids) { @@ -2690,6 +2736,16 @@ l2: heaptup = newtup; } + /* + * We're about to create the new tuple -- check for conflict first, + * to avoid possibly having to roll back work we've just done. + * + * NOTE: For a tuple insert, we only need to check for table locks, since + * predicate locking at the index level will cover ranges for anything + * except a table scan. Therefore, only provide the relation. + */ + CheckForSerializableConflictIn(relation, NULL, InvalidBuffer); + /* * At this point newbuf and buffer are both pinned and locked, and newbuf * has enough space for the new tuple. If they are the same buffer, only @@ -2799,6 +2855,12 @@ l2: END_CRIT_SECTION(); + /* + * Any existing SIREAD locks on the old tuple must be linked to the new + * tuple for conflict detection purposes. + */ + PredicateLockTupleRowVersionLink(relation, &oldtup, newtup); + if (newbuf != buffer) LockBuffer(newbuf, BUFFER_LOCK_UNLOCK); LockBuffer(buffer, BUFFER_LOCK_UNLOCK); diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c index 32af32a206..6e0db79517 100644 --- a/src/backend/access/index/indexam.c +++ b/src/backend/access/index/indexam.c @@ -64,9 +64,11 @@ #include "access/relscan.h" #include "access/transam.h" +#include "access/xact.h" #include "pgstat.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" +#include "storage/predicate.h" #include "utils/relcache.h" #include "utils/snapmgr.h" #include "utils/tqual.h" @@ -192,6 +194,11 @@ index_insert(Relation indexRelation, RELATION_CHECKS; GET_REL_PROCEDURE(aminsert); + if (!(indexRelation->rd_am->ampredlocks)) + CheckForSerializableConflictIn(indexRelation, + (HeapTuple) NULL, + InvalidBuffer); + /* * have the am's insert proc do all the work. */ @@ -266,6 +273,9 @@ index_beginscan_internal(Relation indexRelation, RELATION_CHECKS; GET_REL_PROCEDURE(ambeginscan); + if (!(indexRelation->rd_am->ampredlocks)) + PredicateLockRelation(indexRelation); + /* * We hold a reference count to the relcache entry throughout the scan. */ @@ -523,6 +533,7 @@ index_getnext(IndexScanDesc scan, ScanDirection direction) { ItemId lp; ItemPointer ctid; + bool valid; /* check for bogus TID */ if (offnum < FirstOffsetNumber || @@ -577,8 +588,13 @@ index_getnext(IndexScanDesc scan, ScanDirection direction) break; /* If it's visible per the snapshot, we must return it */ - if (HeapTupleSatisfiesVisibility(heapTuple, scan->xs_snapshot, - scan->xs_cbuf)) + valid = HeapTupleSatisfiesVisibility(heapTuple, scan->xs_snapshot, + scan->xs_cbuf); + + CheckForSerializableConflictOut(valid, scan->heapRelation, + heapTuple, scan->xs_cbuf); + + if (valid) { /* * If the snapshot is MVCC, we know that it could accept at @@ -586,7 +602,8 @@ index_getnext(IndexScanDesc scan, ScanDirection direction) * any more members. Otherwise, check for continuation of the * HOT-chain, and set state for next time. */ - if (IsMVCCSnapshot(scan->xs_snapshot)) + if (IsMVCCSnapshot(scan->xs_snapshot) + && !IsolationIsSerializable()) scan->xs_next_hot = InvalidOffsetNumber; else if (HeapTupleIsHotUpdated(heapTuple)) { @@ -598,6 +615,8 @@ index_getnext(IndexScanDesc scan, ScanDirection direction) else scan->xs_next_hot = InvalidOffsetNumber; + PredicateLockTuple(scan->heapRelation, heapTuple); + LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK); pgstat_count_heap_fetch(scan->indexRelation); diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c index 91b72b8f91..0dd745f19a 100644 --- a/src/backend/access/nbtree/nbtinsert.c +++ b/src/backend/access/nbtree/nbtinsert.c @@ -21,6 +21,7 @@ #include "miscadmin.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" +#include "storage/predicate.h" #include "utils/inval.h" #include "utils/tqual.h" @@ -174,6 +175,14 @@ top: if (checkUnique != UNIQUE_CHECK_EXISTING) { + /* + * The only conflict predicate locking cares about for indexes is when + * an index tuple insert conflicts with an existing lock. Since the + * actual location of the insert is hard to predict because of the + * random search used to prevent O(N^2) performance when there are many + * duplicate entries, we can just use the "first valid" page. + */ + CheckForSerializableConflictIn(rel, NULL, buf); /* do the insertion */ _bt_findinsertloc(rel, &buf, &offset, natts, itup_scankey, itup, heapRel); _bt_insertonpg(rel, buf, stack, itup, offset, false); @@ -696,6 +705,9 @@ _bt_insertonpg(Relation rel, /* split the buffer into left and right halves */ rbuf = _bt_split(rel, buf, firstright, newitemoff, itemsz, itup, newitemonleft); + PredicateLockPageSplit(rel, + BufferGetBlockNumber(buf), + BufferGetBlockNumber(rbuf)); /*---------- * By here, diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c index db86ec9a1a..27964455f7 100644 --- a/src/backend/access/nbtree/nbtpage.c +++ b/src/backend/access/nbtree/nbtpage.c @@ -29,6 +29,7 @@ #include "storage/freespace.h" #include "storage/indexfsm.h" #include "storage/lmgr.h" +#include "storage/predicate.h" #include "utils/inval.h" #include "utils/snapmgr.h" @@ -1183,6 +1184,12 @@ _bt_pagedel(Relation rel, Buffer buf, BTStack stack) rightsib, opaque->btpo_prev, target, RelationGetRelationName(rel)); + /* + * Any insert which would have gone on the target block will now go to the + * right sibling block. + */ + PredicateLockPageCombine(rel, target, rightsib); + /* * Next find and write-lock the current parent of the target page. This is * essentially the same as the corresponding step of splitting. diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index d66bdd47e4..558ace1562 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -29,6 +29,7 @@ #include "storage/indexfsm.h" #include "storage/ipc.h" #include "storage/lmgr.h" +#include "storage/predicate.h" #include "storage/smgr.h" #include "utils/memutils.h" @@ -822,6 +823,7 @@ restart: if (_bt_page_recyclable(page)) { /* Okay to recycle this page */ + Assert(!PageIsPredicateLocked(rel, blkno)); RecordFreeIndexPage(rel, blkno); vstate->totFreePages++; stats->pages_deleted++; diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c index 42d956c6ea..cf74f7776c 100644 --- a/src/backend/access/nbtree/nbtsearch.c +++ b/src/backend/access/nbtree/nbtsearch.c @@ -21,6 +21,7 @@ #include "miscadmin.h" #include "pgstat.h" #include "storage/bufmgr.h" +#include "storage/predicate.h" #include "utils/lsyscache.h" #include "utils/rel.h" @@ -63,7 +64,10 @@ _bt_search(Relation rel, int keysz, ScanKey scankey, bool nextkey, /* If index is empty and access = BT_READ, no root page is created. */ if (!BufferIsValid(*bufP)) + { + PredicateLockRelation(rel); /* Nothing finer to lock exists. */ return (BTStack) NULL; + } /* Loop iterates once per level descended in the tree */ for (;;) @@ -88,7 +92,11 @@ _bt_search(Relation rel, int keysz, ScanKey scankey, bool nextkey, page = BufferGetPage(*bufP); opaque = (BTPageOpaque) PageGetSpecialPointer(page); if (P_ISLEAF(opaque)) + { + if (access == BT_READ) + PredicateLockPage(rel, BufferGetBlockNumber(*bufP)); break; + } /* * Find the appropriate item on the internal page, and get the child @@ -1142,6 +1150,7 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir) opaque = (BTPageOpaque) PageGetSpecialPointer(page); if (!P_IGNORE(opaque)) { + PredicateLockPage(rel, blkno); /* see if there are any matches on this page */ /* note that this will clear moreRight if we can stop */ if (_bt_readpage(scan, dir, P_FIRSTDATAKEY(opaque))) @@ -1189,6 +1198,7 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir) opaque = (BTPageOpaque) PageGetSpecialPointer(page); if (!P_IGNORE(opaque)) { + PredicateLockPage(rel, BufferGetBlockNumber(so->currPos.buf)); /* see if there are any matches on this page */ /* note that this will clear moreLeft if we can stop */ if (_bt_readpage(scan, dir, PageGetMaxOffsetNumber(page))) @@ -1352,6 +1362,7 @@ _bt_get_endpoint(Relation rel, uint32 level, bool rightmost) if (!BufferIsValid(buf)) { /* empty index... */ + PredicateLockRelation(rel); /* Nothing finer to lock exists. */ return InvalidBuffer; } @@ -1431,10 +1442,12 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir) if (!BufferIsValid(buf)) { /* empty index... */ + PredicateLockRelation(rel); /* Nothing finer to lock exists. */ so->currPos.buf = InvalidBuffer; return false; } + PredicateLockPage(rel, BufferGetBlockNumber(buf)); page = BufferGetPage(buf); opaque = (BTPageOpaque) PageGetSpecialPointer(page); Assert(P_ISLEAF(opaque)); diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index 4fee9c3244..287ad26698 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -57,6 +57,7 @@ #include "pgstat.h" #include "replication/walsender.h" #include "storage/fd.h" +#include "storage/predicate.h" #include "storage/procarray.h" #include "storage/sinvaladt.h" #include "storage/smgr.h" @@ -1357,6 +1358,8 @@ FinishPreparedTransaction(const char *gid, bool isCommit) else ProcessRecords(bufptr, xid, twophase_postabort_callbacks); + PredicateLockTwoPhaseFinish(xid, isCommit); + /* Count the prepared xact as committed or aborted */ AtEOXact_PgStat(isCommit); diff --git a/src/backend/access/transam/twophase_rmgr.c b/src/backend/access/transam/twophase_rmgr.c index 02de1e8526..47c15af241 100644 --- a/src/backend/access/transam/twophase_rmgr.c +++ b/src/backend/access/transam/twophase_rmgr.c @@ -18,12 +18,14 @@ #include "access/twophase_rmgr.h" #include "pgstat.h" #include "storage/lock.h" +#include "storage/predicate.h" const TwoPhaseCallback twophase_recover_callbacks[TWOPHASE_RM_MAX_ID + 1] = { NULL, /* END ID */ lock_twophase_recover, /* Lock */ + predicatelock_twophase_recover, /* PredicateLock */ NULL, /* pgstat */ multixact_twophase_recover /* MultiXact */ }; @@ -32,6 +34,7 @@ const TwoPhaseCallback twophase_postcommit_callbacks[TWOPHASE_RM_MAX_ID + 1] = { NULL, /* END ID */ lock_twophase_postcommit, /* Lock */ + NULL, /* PredicateLock */ pgstat_twophase_postcommit, /* pgstat */ multixact_twophase_postcommit /* MultiXact */ }; @@ -40,6 +43,7 @@ const TwoPhaseCallback twophase_postabort_callbacks[TWOPHASE_RM_MAX_ID + 1] = { NULL, /* END ID */ lock_twophase_postabort, /* Lock */ + NULL, /* PredicateLock */ pgstat_twophase_postabort, /* pgstat */ multixact_twophase_postabort /* MultiXact */ }; @@ -48,6 +52,7 @@ const TwoPhaseCallback twophase_standby_recover_callbacks[TWOPHASE_RM_MAX_ID + 1 { NULL, /* END ID */ lock_twophase_standby_recover, /* Lock */ + NULL, /* PredicateLock */ NULL, /* pgstat */ NULL /* MultiXact */ }; diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c index a03ec85f8f..a828b3de48 100644 --- a/src/backend/access/transam/varsup.c +++ b/src/backend/access/transam/varsup.c @@ -21,6 +21,7 @@ #include "miscadmin.h" #include "postmaster/autovacuum.h" #include "storage/pmsignal.h" +#include "storage/predicate.h" #include "storage/proc.h" #include "utils/builtins.h" #include "utils/syscache.h" @@ -161,6 +162,10 @@ GetNewTransactionId(bool isSubXact) ExtendCLOG(xid); ExtendSUBTRANS(xid); + /* If it's top level, the predicate locking system also needs to know. */ + if (!isSubXact) + RegisterPredicateLockingXid(xid); + /* * Now advance the nextXid counter. This must not happen until after we * have successfully completed ExtendCLOG() --- if that routine fails, we diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 1e31e07ec9..a0170b42e2 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -40,6 +40,7 @@ #include "storage/bufmgr.h" #include "storage/fd.h" #include "storage/lmgr.h" +#include "storage/predicate.h" #include "storage/procarray.h" #include "storage/sinvaladt.h" #include "storage/smgr.h" @@ -63,6 +64,9 @@ int XactIsoLevel; bool DefaultXactReadOnly = false; bool XactReadOnly; +bool DefaultXactDeferrable = false; +bool XactDeferrable; + bool XactSyncCommit = true; int CommitDelay = 0; /* precommit delay in microseconds */ @@ -1640,6 +1644,7 @@ StartTransaction(void) s->startedInRecovery = false; XactReadOnly = DefaultXactReadOnly; } + XactDeferrable = DefaultXactDeferrable; XactIsoLevel = DefaultXactIsoLevel; forceSyncCommit = false; MyXactAccessedTempRel = false; @@ -1786,6 +1791,13 @@ CommitTransaction(void) /* close large objects before lower-level cleanup */ AtEOXact_LargeObject(true); + /* + * Mark serializable transaction as complete for predicate locking + * purposes. This should be done as late as we can put it and still + * allow errors to be raised for failure patterns found at commit. + */ + PreCommit_CheckForSerializationFailure(); + /* * Insert notifications sent by NOTIFY commands into the queue. This * should be late in the pre-commit sequence to minimize time spent @@ -1980,6 +1992,13 @@ PrepareTransaction(void) /* close large objects before lower-level cleanup */ AtEOXact_LargeObject(true); + /* + * Mark serializable transaction as complete for predicate locking + * purposes. This should be done as late as we can put it and still + * allow errors to be raised for failure patterns found at commit. + */ + PreCommit_CheckForSerializationFailure(); + /* NOTIFY will be handled below */ /* @@ -2044,6 +2063,7 @@ PrepareTransaction(void) AtPrepare_Notify(); AtPrepare_Locks(); + AtPrepare_PredicateLocks(); AtPrepare_PgStat(); AtPrepare_MultiXact(); AtPrepare_RelationMap(); @@ -2103,6 +2123,7 @@ PrepareTransaction(void) PostPrepare_MultiXact(xid); PostPrepare_Locks(xid); + PostPrepare_PredicateLocks(xid); ResourceOwnerRelease(TopTransactionResourceOwner, RESOURCE_RELEASE_LOCKS, diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c index 139148eec2..2a61ea3bc7 100644 --- a/src/backend/commands/variable.c +++ b/src/backend/commands/variable.c @@ -616,6 +616,15 @@ assign_XactIsoLevel(const char *value, bool doit, GucSource source) errmsg("SET TRANSACTION ISOLATION LEVEL must not be called in a subtransaction"))); return NULL; } + /* Can't go to serializable mode while recovery is still active */ + if (RecoveryInProgress() && strcmp(value, "serializable") == 0) + { + ereport(GUC_complaint_elevel(source), + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot use serializable mode in a hot standby"), + errhint("You can use REPEATABLE READ instead."))); + return false; + } } if (strcmp(value, "serializable") == 0) @@ -667,6 +676,35 @@ show_XactIsoLevel(void) } } +/* + * SET TRANSACTION [NOT] DEFERRABLE + */ + +bool +assign_transaction_deferrable(bool newval, bool doit, GucSource source) +{ + /* source == PGC_S_OVERRIDE means do it anyway, eg at xact abort */ + if (source == PGC_S_OVERRIDE) + return true; + + if (IsSubTransaction()) + { + ereport(GUC_complaint_elevel(source), + (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), + errmsg("SET TRANSACTION [NOT] DEFERRABLE cannot be called within a subtransaction"))); + return false; + } + + if (FirstSnapshotSet) + { + ereport(GUC_complaint_elevel(source), + (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), + errmsg("SET TRANSACTION [NOT] DEFERRABLE must be called before any query"))); + return false; + } + + return true; +} /* * Random number seed diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c index 7178b4c17d..20d5eb14bf 100644 --- a/src/backend/executor/nodeBitmapHeapscan.c +++ b/src/backend/executor/nodeBitmapHeapscan.c @@ -42,6 +42,7 @@ #include "executor/nodeBitmapHeapscan.h" #include "pgstat.h" #include "storage/bufmgr.h" +#include "storage/predicate.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/tqual.h" @@ -351,7 +352,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres) ItemPointerData tid; ItemPointerSet(&tid, page, offnum); - if (heap_hot_search_buffer(&tid, buffer, snapshot, NULL)) + if (heap_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot, NULL)) scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid); } } diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c index 0f3438d063..1e566b2d50 100644 --- a/src/backend/executor/nodeSeqscan.c +++ b/src/backend/executor/nodeSeqscan.c @@ -28,6 +28,7 @@ #include "access/relscan.h" #include "executor/execdebug.h" #include "executor/nodeSeqscan.h" +#include "storage/predicate.h" static void InitScanRelation(SeqScanState *node, EState *estate); static TupleTableSlot *SeqNext(SeqScanState *node); @@ -105,11 +106,15 @@ SeqRecheck(SeqScanState *node, TupleTableSlot *slot) * tuple. * We call the ExecScan() routine and pass it the appropriate * access method functions. + * For serializable transactions, we first acquire a predicate + * lock on the entire relation. * ---------------------------------------------------------------- */ TupleTableSlot * ExecSeqScan(SeqScanState *node) { + PredicateLockRelation(node->ss_currentRelation); + node->ss_currentScanDesc->rs_relpredicatelocked = true; return ExecScan((ScanState *) node, (ExecScanAccessMtd) SeqNext, (ExecScanRecheckMtd) SeqRecheck); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 456db5c50e..21782824ca 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -6768,6 +6768,12 @@ transaction_mode_item: | READ WRITE { $$ = makeDefElem("transaction_read_only", makeIntConst(FALSE, @1)); } + | DEFERRABLE + { $$ = makeDefElem("transaction_deferrable", + makeIntConst(TRUE, @1)); } + | NOT DEFERRABLE + { $$ = makeDefElem("transaction_deferrable", + makeIntConst(FALSE, @1)); } ; /* Syntax with commas is SQL-spec, without commas is Postgres historical */ diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index 2dbac56c25..56c0bd8d49 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -32,6 +32,7 @@ #include "storage/ipc.h" #include "storage/pg_shmem.h" #include "storage/pmsignal.h" +#include "storage/predicate.h" #include "storage/procarray.h" #include "storage/procsignal.h" #include "storage/sinvaladt.h" @@ -105,6 +106,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port) sizeof(ShmemIndexEnt))); size = add_size(size, BufferShmemSize()); size = add_size(size, LockShmemSize()); + size = add_size(size, PredicateLockShmemSize()); size = add_size(size, ProcGlobalShmemSize()); size = add_size(size, XLOGShmemSize()); size = add_size(size, CLOGShmemSize()); @@ -199,6 +201,11 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port) */ InitLocks(); + /* + * Set up predicate lock manager + */ + InitPredicateLocks(); + /* * Set up process table */ diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c index 6280957799..0811da7b6f 100644 --- a/src/backend/storage/ipc/shmem.c +++ b/src/backend/storage/ipc/shmem.c @@ -198,7 +198,7 @@ ShmemAlloc(Size size) * Returns TRUE if the pointer points within the shared memory segment. */ bool -ShmemAddrIsValid(void *addr) +ShmemAddrIsValid(const void *addr) { return (addr >= ShmemBase) && (addr < ShmemEnd); } diff --git a/src/backend/storage/ipc/shmqueue.c b/src/backend/storage/ipc/shmqueue.c index 0277c3f9f4..1cf69a09c8 100644 --- a/src/backend/storage/ipc/shmqueue.c +++ b/src/backend/storage/ipc/shmqueue.c @@ -43,14 +43,12 @@ SHMQueueInit(SHM_QUEUE *queue) * SHMQueueIsDetached -- TRUE if element is not currently * in a queue. */ -#ifdef NOT_USED bool -SHMQueueIsDetached(SHM_QUEUE *queue) +SHMQueueIsDetached(const SHM_QUEUE *queue) { Assert(ShmemAddrIsValid(queue)); return (queue->prev == NULL); } -#endif /* * SHMQueueElemInit -- clear an element's links @@ -146,7 +144,7 @@ SHMQueueInsertAfter(SHM_QUEUE *queue, SHM_QUEUE *elem) *-------------------- */ Pointer -SHMQueueNext(SHM_QUEUE *queue, SHM_QUEUE *curElem, Size linkOffset) +SHMQueueNext(const SHM_QUEUE *queue, const SHM_QUEUE *curElem, Size linkOffset) { SHM_QUEUE *elemPtr = curElem->next; @@ -162,7 +160,7 @@ SHMQueueNext(SHM_QUEUE *queue, SHM_QUEUE *curElem, Size linkOffset) * SHMQueueEmpty -- TRUE if queue head is only element, FALSE otherwise */ bool -SHMQueueEmpty(SHM_QUEUE *queue) +SHMQueueEmpty(const SHM_QUEUE *queue) { Assert(ShmemAddrIsValid(queue)); diff --git a/src/backend/storage/lmgr/Makefile b/src/backend/storage/lmgr/Makefile index 9aa9a5c086..e12a8549f7 100644 --- a/src/backend/storage/lmgr/Makefile +++ b/src/backend/storage/lmgr/Makefile @@ -12,7 +12,7 @@ subdir = src/backend/storage/lmgr top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o +OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o spin.o s_lock.o predicate.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/storage/lmgr/README b/src/backend/storage/lmgr/README index 87cae18cb6..40779d2e35 100644 --- a/src/backend/storage/lmgr/README +++ b/src/backend/storage/lmgr/README @@ -3,7 +3,7 @@ src/backend/storage/lmgr/README Locking Overview ================ -Postgres uses three types of interprocess locks: +Postgres uses four types of interprocess locks: * Spinlocks. These are intended for *very* short-term locks. If a lock is to be held more than a few dozen instructions, or across any sort of @@ -34,6 +34,8 @@ supports a variety of lock modes with table-driven semantics, and it has full deadlock detection and automatic release at transaction end. Regular locks should be used for all user-driven lock requests. +* SIReadLock predicate locks. See separate README-SSI file for details. + Acquisition of either a spinlock or a lightweight lock causes query cancel and die() interrupts to be held off until all such locks are released. No such restriction exists for regular locks, however. Also diff --git a/src/backend/storage/lmgr/README-SSI b/src/backend/storage/lmgr/README-SSI new file mode 100644 index 0000000000..a2bb63e3f8 --- /dev/null +++ b/src/backend/storage/lmgr/README-SSI @@ -0,0 +1,537 @@ +src/backend/storage/lmgr/README-SSI + +Serializable Snapshot Isolation (SSI) and Predicate Locking +=========================================================== + +This is currently sitting in the lmgr directory because about 90% of +the code is an implementation of predicate locking, which is required +for SSI, rather than being directly related to SSI itself. When +another use for predicate locking justifies the effort to tease these +two things apart, this README file should probably be split. + + +Credits +------- + +This feature was developed by Kevin Grittner and Dan R. K. Ports, +with review and suggestions from Joe Conway, Heikki Linnakangas, and +Jeff Davis. It is based on work published in these papers: + + Michael J. Cahill, Uwe Röhm, and Alan D. Fekete. 2008. + Serializable isolation for snapshot databases. + In SIGMOD ’08: Proceedings of the 2008 ACM SIGMOD + international conference on Management of data, + pages 729–738, New York, NY, USA. ACM. + http://doi.acm.org/10.1145/1376616.1376690 + + Michael James Cahill. 2009. + Serializable Isolation for Snapshot Databases. + Sydney Digital Theses. + University of Sydney, School of Information Technologies. + http://hdl.handle.net/2123/5353 + + +Overview +-------- + +With true serializable transactions, if you can show that your +transaction will do the right thing if there are no concurrent +transactions, it will do the right thing in any mix of serializable +transactions or be rolled back with a serialization failure. This +feature has been implemented in PostgreSQL using SSI. + + +Serializable and Snapshot Transaction Isolation Levels +------------------------------------------------------ + +Serializable transaction isolation is attractive for shops with +active development by many programmers against a complex schema +because it guarantees data integrity with very little staff time -- +if a transaction can be shown to always do the right thing when it is +run alone (before or after any other transaction), it will always do +the right thing in any mix of concurrent serializable transactions. +Where conflicts with other transactions would result in an +inconsistent state within the database, or an inconsistent view of +the data, a serializable transaction will block or roll back to +prevent the anomaly. The SQL standard provides a specific SQLSTATE +for errors generated when a transaction rolls back for this reason, +so that transactions can be retried automatically. + +Before version 9.1 PostgreSQL did not support a full serializable +isolation level. A request for serializable transaction isolation +actually provided snapshot isolation. This has well known anomalies +which can allow data corruption or inconsistent views of the data +during concurrent transactions; although these anomalies only occur +when certain patterns of read-write dependencies exist within a set +of concurrent transactions. Where these patterns exist, the anomalies +can be prevented by introducing conflicts through explicitly +programmed locks or otherwise unnecessary writes to the database. +Snapshot isolation is popular because performance is better than +serializable isolation and the integrity guarantees which it does +provide allow anomalies to be avoided or managed with reasonable +effort in many environments. + + +Serializable Isolation Implementation Strategies +------------------------------------------------ + +Techniques for implementing full serializable isolation have been +published and in use in many database products for decades. The +primary technique which has been used is Strict 2 Phase Locking +(S2PL), which operates by blocking writes against data which has been +read by concurrent transactions and blocking any access (read or +write) against data which has been written by concurrent +transactions. A cycle in a graph of blocking indicates a deadlock, +requiring a rollback. Blocking and deadlocks under S2PL in high +contention workloads can be debilitating, crippling throughput and +response time. + +A new technique for implementing full serializable isolation in an +MVCC database appears in the literature beginning in 2008. This +technique, known as Serializable Snapshot Isolation (SSI) has many of +the advantages of snapshot isolation. In particular, reads don't +block anything and writes don't block reads. Essentially, it runs +snapshot isolation but monitors the read-write conflicts between +transactions to identify dangerous structures in the transaction +graph which indicate that a set of concurrent transactions might +produce an anomaly, and rolls back transactions to ensure that no +anomalies occur. It will produce some false positives (where a +transaction is rolled back even though there would not have been an +anomaly), but will never let an anomaly occur. In the two known +prototype implementations, performance for many workloads (even with +the need to restart transactions which are rolled back) is very close +to snapshot isolation and generally far better than an S2PL +implementation. + + +Apparent Serial Order of Execution +---------------------------------- + +One way to understand when snapshot anomalies can occur, and to +visualize the difference between the serializable implementations +described above, is to consider that among transactions executing at +the serializable transaction isolation level, the results are +required to be consistent with some serial (one-at-a-time) execution +of the transactions[1]. How is that order determined in each? + +S2PL locks rows used by the transaction in a way which blocks +conflicting access, so that at the moment of a successful commit it +is certain that no conflicting access has occurred. Some transactions +may have blocked, essentially being partially serialized with the +committing transaction, to allow this. Some transactions may have +been rolled back, due to cycles in the blocking. But with S2PL, +transactions can always be viewed as having occurred serially, in the +order of successful commit. + +With snapshot isolation, reads never block writes, nor vice versa, so +there is much less actual serialization. The order in which +transactions appear to have executed is determined by something more +subtle than in S2PL: read/write dependencies. If a transaction +attempts to read data which is not visible to it because the +transaction which wrote it (or will later write it) is concurrent +(one of them was running when the other acquired its snapshot), then +the reading transaction appears to have executed first, regardless of +the actual sequence of transaction starts or commits (since it sees a +database state prior to that in which the other transaction leaves +it). If one transaction has both rw-dependencies in (meaning that a +concurrent transaction attempts to read data it writes) and out +(meaning it attempts to read data a concurrent transaction writes), +and a couple other conditions are met, there can appear to be a cycle +in execution order of the transactions. This is when the anomalies +occur. + +SSI works by watching for the conditions mentioned above, and rolling +back a transaction when needed to prevent any anomaly. The apparent +order of execution will always be consistent with any actual +serialization (i.e., a transaction which run by itself can always be +considered to have run after any transactions committed before it +started and before any transacton which starts after it commits); but +among concurrent transactions it will appear that the transaction on +the read side of a rw-dependency executed before the transaction on +the write side. + + +PostgreSQL Implementation +------------------------- + +The implementation of serializable transactions for PostgreSQL is +accomplished through Serializable Snapshot Isolation (SSI), based on +the work of Cahill, et al. Fundamentally, this allows snapshot +isolation to run as it has, while monitoring for conditions which +could create a serialization anomaly. + + * Since this technique is based on Snapshot Isolation (SI), those +areas in PostgreSQL which don't use SI can't be brought under SSI. +This includes system tables, temporary tables, sequences, hint bit +rewrites, etc. SSI can not eliminate existing anomalies in these +areas. + + * Any transaction which is run at a transaction isolation level +other than SERIALIZABLE will not be affected by SSI. If you want to +enforce business rules through SSI, all transactions should be run at +the SERIALIZABLE transaction isolation level, and that should +probably be set as the default. + + * If all transactions are run at the SERIALIZABLE transaction +isolation level, business rules can be enforced in triggers or +application code without ever having a need to acquire an explicit +lock or to use SELECT FOR SHARE or SELECT FOR UPDATE. + + * Those who want to continue to use snapshot isolation without +the additional protections of SSI (and the associated costs of +enforcing those protections), can use the REPEATABLE READ transaction +isolation level. This level will retain its legacy behavior, which +is identical to the old SERIALIZABLE implementation and fully +consistent with the standard's requirements for the REPEATABLE READ +transaction isolation level. + + * Performance under this SSI implementation will be significantly +improved if transactions which don't modify permanent tables are +declared to be READ ONLY before they begin reading data. + + * Performance under SSI will tend to degrade more rapidly with a +large number of active database transactions than under less strict +isolation levels. Limiting the number of active transactions through +use of a connection pool or similar techniques may be necessary to +maintain good performance. + + * Any transaction which must be rolled back to prevent +serialization anomalies will fail with SQLSTATE 40001, which has a +standard meaning of "serialization failure". + + * This SSI implementation makes an effort to choose the +transaction to be cancelled such that an immediate retry of the +transaction will not fail due to conflicts with exactly the same +transactions. Pursuant to this goal, no transaction is cancelled +until one of the other transactions in the set of conflicts which +could generate an anomaly has successfully committed. This is +conceptually similar to how write conflicts are handled. To fully +implement this guarantee there needs to be a way to roll back the +active transaction for another process with a serialization failure +SQLSTATE, even if it is "idle in transaction". + + +Predicate Locking +----------------- + +Both S2PL and SSI require some form of predicate locking to handle +situations where reads conflict with later inserts or with later +updates which move data into the selected range. PostgreSQL didn't +already have predicate locking, so it needed to be added to support +full serializable transactions under either strategy. Practical +implementations of predicate locking generally involve acquiring +locks against data as it is accessed, using multiple granularities +(tuple, page, table, etc.) with escalation as needed to keep the lock +count to a number which can be tracked within RAM structures, and +this was used in PostgreSQL. Coarse granularities can cause some +false positive indications of conflict. The number of false positives +can be influenced by plan choice. + + +Implementation overview +----------------------- + +New RAM structures, inspired by those used to track traditional locks +in PostgreSQL, but tailored to the needs of SIREAD predicate locking, +are used. These refer to physical objects actually accessed in the +course of executing the query, to model the predicates through +inference. Anyone interested in this subject should review the +Hellerstein, Stonebraker and Hamilton paper[2], along with the +locking papers referenced from that and the Cahill papers. + +Because the SIREAD locks don't block, traditional locking techniques +were be modified. Intent locking (locking higher level objects +before locking lower level objects) doesn't work with non-blocking +"locks" (which are, in some respects, more like flags than locks). + +A configurable amount of shared memory is reserved at postmaster +start-up to track predicate locks. This size cannot be changed +without a restart. + + * To prevent resource exhaustion, multiple fine-grained locks may +be promoted to a single coarser-grained lock as needed. + + * An attempt to acquire an SIREAD lock on a tuple when the same +transaction already holds an SIREAD lock on the page or the relation +will be ignored. Likewise, an attempt to lock a page when the +relation is locked will be ignored, and the acquisition of a coarser +lock will result in the automatic release of all finer-grained locks +it covers. + + +Heap locking +------------ + +Predicate locks will be acquired for the heap based on the following: + + * For a table scan, the entire relation will be locked. + + * Each tuple read which is visible to the reading transaction +will be locked, whether or not it meets selection criteria; except +that there is no need to acquire an SIREAD lock on a tuple when the +transaction already holds a write lock on any tuple representing the +row, since a rw-dependency would also create a ww-dependency which +has more aggressive enforcement and will thus prevent any anomaly. + + +Index AM implementations +------------------------ + +Since predicate locks only exist to detect writes which conflict with +earlier reads, and heap tuple locks are acquired to cover all heap +tuples actually read, including those read through indexes, the index +tuples which were actually scanned are not of interest in themselves; +we only care about their "new neighbors" -- later inserts into the +index which would have been included in the scan had they existed at +the time. Conceptually, we want to lock the gaps between and +surrounding index entries within the scanned range. + +Correctness requires that any insert into an index generates a +rw-conflict with a concurrent serializable transaction if, after that +insert, re-execution of any index scan of the other transaction would +access the heap for a row not accessed during the previous execution. +Note that a non-HOT update which expires an old index entry covered +by the scan and adds a new entry for the modified row's new tuple +need not generate a conflict, although an update which "moves" a row +into the scan must generate a conflict. While correctness allows +false positives, they should be minimized for performance reasons. + +Several optimizations are possible: + + * An index scan which is just finding the right position for an +index insertion or deletion need not acquire a predicate lock. + + * An index scan which is comparing for equality on the entire key +for a unique index need not acquire a predicate lock as long as a key +is found corresponding to a visible tuple which has not been modified +by another transaction -- there are no "between or around" gaps to +cover. + + * As long as built-in foreign key enforcement continues to use +its current "special tricks" to deal with MVCC issues, predicate +locks should not be needed for scans done by enforcement code. + + * If a search determines that no rows can be found regardless of +index contents because the search conditions are contradictory (e.g., +x = 1 AND x = 2), then no predicate lock is needed. + +Other index AM implementation considerations: + + * If a btree search discovers that no root page has yet been +created, a predicate lock on the index relation is required; +otherwise btree searches must get to the leaf level to determine +which tuples match, so predicate locks go there. + + * GiST searches can determine that there are no matches at any +level of the index, so there must be a predicate lock at each index +level during a GiST search. An index insert at the leaf level can +then be trusted to ripple up to all levels and locations where +conflicting predicate locks may exist. + + * The effects of page splits, overflows, consolidations, and +removals must be carefully reviewed to ensure that predicate locks +aren't "lost" during those operations, or kept with pages which could +get re-used for different parts of the index. + + +Innovations +----------- + +The PostgreSQL implementation of Serializable Snapshot Isolation +differs from what is described in the cited papers for several +reasons: + + 1. PostgreSQL didn't have any existing predicate locking. It had +to be added from scratch. + + 2. The existing in-memory lock structures were not suitable for +tracking SIREAD locks. + * The database products used for the prototype +implementations for the papers used update-in-place with a rollback +log for their MVCC implementations, while PostgreSQL leaves the old +version of a row in place and adds a new tuple to represent the row +at a new location. + * In PostgreSQL, tuple level locks are not held in RAM for +any length of time; lock information is written to the tuples +involved in the transactions. + * In PostgreSQL, existing lock structures have pointers to +memory which is related to a connection. SIREAD locks need to persist +past the end of the originating transaction and even the connection +which ran it. + * PostgreSQL needs to be able to tolerate a large number of +transactions executing while one long-running transaction stays open +-- the in-RAM techniques discussed in the papers wouldn't support +that. + + 3. Unlike the database products used for the prototypes described +in the papers, PostgreSQL didn't already have a true serializable +isolation level distinct from snapshot isolation. + + 4. PostgreSQL supports subtransactions -- an issue not mentioned +in the papers. + + 5. PostgreSQL doesn't assign a transaction number to a database +transaction until and unless necessary. + + 6. PostgreSQL has pluggable data types with user-definable +operators, as well as pluggable index types, not all of which are +based around data types which support ordering. + + 7. Some possible optimizations became apparent during development +and testing. + +Differences from the implementation described in the papers are +listed below. + + * New structures needed to be created in shared memory to track +the proper information for serializable transactions and their SIREAD +locks. + + * Because PostgreSQL does not have the same concept of an "oldest +transaction ID" for all serializable transactions as assumed in the +Cahill these, we track the oldest snapshot xmin among serializable +transactions, and a count of how many active transactions use that +xmin. When the count hits zero we find the new oldest xmin and run a +clean-up based on that. + + * Because reads in a subtransaction may cause that subtransaction +to roll back, thereby affecting what is written by the top level +transaction, predicate locks must survive a subtransaction rollback. +As a consequence, all xid usage in SSI, including predicate locking, +is based on the top level xid. When looking at an xid that comes +from a tuple's xmin or xmax, for example, we always call +SubTransGetTopmostTransaction() before doing much else with it. + + * Predicate locking in PostgreSQL will start at the tuple level +when possible, with automatic conversion of multiple fine-grained +locks to coarser granularity as need to avoid resource exhaustion. +The amount of memory used for these structures will be configurable, +to balance RAM usage against SIREAD lock granularity. + + * A process-local copy of locks held by a process and the coarser +covering locks with counts, are kept to support granularity promotion +decisions with low CPU and locking overhead. + + * Conflicts will be identified by looking for predicate locks +when tuples are written and looking at the MVCC information when +tuples are read. There is no matching between two RAM-based locks. + + * Because write locks are stored in the heap tuples rather than a +RAM-based lock table, the optimization described in the Cahill thesis +which eliminates an SIREAD lock where there is a write lock is +implemented by the following: + 1. When checking a heap write for conflicts against existing +predicate locks, a tuple lock on the tuple being written is removed. + 2. When acquiring a predicate lock on a heap tuple, we +return quickly without doing anything if it is a tuple written by the +reading transaction. + + * Rather than using conflictIn and conflictOut pointers which use +NULL to indicate no conflict and a self-reference to indicate +multiple conflicts or conflicts with committed transactions, we use a +list of rw-conflicts. With the more complete information, false +positives are reduced and we have sufficient data for more aggressive +clean-up and other optimizations. + o We can avoid ever rolling back a transaction until and +unless there is a pivot where a transaction on the conflict *out* +side of the pivot committed before either of the other transactions. + o We can avoid ever rolling back a transaction when the +transaction on the conflict *in* side of the pivot is explicitly or +implicitly READ ONLY unless the transaction on the conflict *out* +side of the pivot committed before the READ ONLY transaction acquired +its snapshot. (An implicit READ ONLY transaction is one which +committed without writing, even though it was not explicitly declared +to be READ ONLY.) + o We can more aggressively clean up conflicts, predicate +locks, and SSI transaction information. + + * Allow a READ ONLY transaction to "opt out" of SSI if there are +no READ WRITE transactions which could cause the READ ONLY +transaction to ever become part of a "dangerous structure" of +overlapping transaction dependencies. + + * Allow the user to request that a READ ONLY transaction wait +until the conditions are right for it to start in the "opt out" state +described above. We add a DEFERRABLE state to transactions, which is +specified and maintained in a way similar to READ ONLY. It is +ignored for transactions which are not SERIALIZABLE and READ ONLY. + + * When a transaction must be rolled back, we pick among the +active transactions such that an immediate retry will not fail again +on conflicts with the same transactions. + + * We use the PostgreSQL SLRU system to hold summarized +information about older committed transactions to put an upper bound +on RAM used. Beyond that limit, information spills to disk. +Performance can degrade in a pessimal situation, but it should be +tolerable, and transactions won't need to be cancelled or blocked +from starting. + + +R&D Issues +---------- + +This is intended to be the place to record specific issues which need +more detailed review or analysis. + + * WAL file replay. While serializable implementations using S2PL +can guarantee that the write-ahead log contains commits in a sequence +consistent with some serial execution of serializable transactions, +SSI cannot make that guarantee. While the WAL replay is no less +consistent than under snapshot isolation, it is possible that under +PITR recovery or hot standby a database could reach a readable state +where some transactions appear before other transactions which would +have had to precede them to maintain serializable consistency. In +essence, if we do nothing, WAL replay will be at snapshot isolation +even for serializable transactions. Is this OK? If not, how do we +address it? + + * External replication. Look at how this impacts external +replication solutions, like Postgres-R, Slony, pgpool, HS/SR, etc. +This is related to the "WAL file replay" issue. + + * Weak-memory-ordering machines. Make sure that shared memory +access which involves visibility across multiple transactions uses +locks as needed to avoid problems. On the other hand, ensure that we +really need volatile where we're using it. +http://archives.postgresql.org/pgsql-committers/2008-06/msg00228.php + + * UNIQUE btree search for equality on all columns. Since a search +of a UNIQUE index using equality tests on all columns will lock the +heap tuple if an entry is found, it appears that there is no need to +get a predicate lock on the index in that case. A predicate lock is +still needed for such a search if a matching index entry which points +to a visible tuple is not found. + + * Planner index probes. To avoid problems with data skew at the +ends of an index which have historically caused bad plans, the +planner now probes the end of an index to see what the maximum or +minimum value is when a query appears to be requesting a range of +data outside what statistics shows is present. These planner checks +don't require predicate locking, but there's currently no easy way to +avoid it. What can we do to avoid predicate locking for such planner +activity? + + * Minimize touching of shared memory. Should lists in shared +memory push entries which have just been returned to the front of the +available list, so they will be popped back off soon and some memory +might never be touched, or should we keep adding returned items to +the end of the available list? + + +Footnotes +--------- + +[1] http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt +Search for serial execution to find the relevant section. + +[2] http://db.cs.berkeley.edu/papers/fntdb07-architecture.pdf +Joseph M. Hellerstein, Michael Stonebraker and James Hamilton. 2007. +Architecture of a Database System. Foundations and Trends(R) in +Databases Vol. 1, No. 2 (2007) 141–259. + Of particular interest: + * 6.1 A Note on ACID + * 6.2 A Brief Review of Serializability + * 6.3 Locking and Latching + * 6.3.1 Transaction Isolation Levels + * 6.5.3 Next-Key Locking: Physical Surrogates for Logical diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c index 61621a4d0a..0fe7ce45cd 100644 --- a/src/backend/storage/lmgr/lwlock.c +++ b/src/backend/storage/lmgr/lwlock.c @@ -28,6 +28,7 @@ #include "miscadmin.h" #include "pg_trace.h" #include "storage/ipc.h" +#include "storage/predicate.h" #include "storage/proc.h" #include "storage/spin.h" @@ -178,6 +179,9 @@ NumLWLocks(void) /* async.c needs one per Async buffer */ numLocks += NUM_ASYNC_BUFFERS; + /* predicate.c needs one per old serializable xid buffer */ + numLocks += NUM_OLDSERXID_BUFFERS; + /* * Add any requested by loadable modules; for backwards-compatibility * reasons, allocate at least NUM_USER_DEFINED_LWLOCKS of them even if diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c new file mode 100644 index 0000000000..5e62ba9e4d --- /dev/null +++ b/src/backend/storage/lmgr/predicate.c @@ -0,0 +1,4439 @@ +/*------------------------------------------------------------------------- + * + * predicate.c + * POSTGRES predicate locking + * to support full serializable transaction isolation + * + * + * The approach taken is to implement Serializable Snapshot Isolation (SSI) + * as initially described in this paper: + * + * Michael J. Cahill, Uwe Röhm, and Alan D. Fekete. 2008. + * Serializable isolation for snapshot databases. + * In SIGMOD ’08: Proceedings of the 2008 ACM SIGMOD + * international conference on Management of data, + * pages 729–738, New York, NY, USA. ACM. + * http://doi.acm.org/10.1145/1376616.1376690 + * + * and further elaborated in Cahill's doctoral thesis: + * + * Michael James Cahill. 2009. + * Serializable Isolation for Snapshot Databases. + * Sydney Digital Theses. + * University of Sydney, School of Information Technologies. + * http://hdl.handle.net/2123/5353 + * + * + * Predicate locks for Serializable Snapshot Isolation (SSI) are SIREAD + * locks, which are so different from normal locks that a distinct set of + * structures is required to handle them. They are needed to detect + * rw-conflicts when the read happens before the write. (When the write + * occurs first, the reading transaction can check for a conflict by + * examining the MVCC data.) + * + * (1) Besides tuples actually read, they must cover ranges of tuples + * which would have been read based on the predicate. This will + * require modelling the predicates through locks against database + * objects such as pages, index ranges, or entire tables. + * + * (2) They must be kept in RAM for quick access. Because of this, it + * isn't possible to always maintain tuple-level granularity -- when + * the space allocated to store these approaches exhaustion, a + * request for a lock may need to scan for situations where a single + * transaction holds many fine-grained locks which can be coalesced + * into a single coarser-grained lock. + * + * (3) They never block anything; they are more like flags than locks + * in that regard; although they refer to database objects and are + * used to identify rw-conflicts with normal write locks. + * + * (4) While they are associated with a transaction, they must survive + * a successful COMMIT of that transaction, and remain until all + * overlapping transactions complete. This even means that they + * must survive termination of the transaction's process. If a + * top level transaction is rolled back, however, it is immediately + * flagged so that it can be ignored, and its SIREAD locks can be + * released any time after that. + * + * (5) The only transactions which create SIREAD locks or check for + * conflicts with them are serializable transactions. + * + * (6) When a write lock for a top level transaction is found to cover + * an existing SIREAD lock for the same transaction, the SIREAD lock + * can be deleted. + * + * (7) A write from a serializable transaction must ensure that a xact + * record exists for the transaction, with the same lifespan (until + * all concurrent transaction complete or the transaction is rolled + * back) so that rw-dependencies to that transaction can be + * detected. + * + * We use an optimization for read-only transactions. Under certain + * circumstances, a read-only transaction's snapshot can be shown to + * never have conflicts with other transactions. This is referred to + * as a "safe" snapshot (and one known not to be is "unsafe"). + * However, it can't be determined whether a snapshot is safe until + * all concurrent read/write transactions complete. + * + * Once a read-only transaction is known to have a safe snapshot, it + * can release its predicate locks and exempt itself from further + * predicate lock tracking. READ ONLY DEFERRABLE transactions run only + * on safe snapshots, waiting as necessary for one to be available. + * + * + * Lightweight locks to manage access to the predicate locking shared + * memory objects must be taken in this order, and should be released in + * reverse order: + * + * SerializableFinishedListLock + * - Protects the list of transactions which have completed but which + * may yet matter because they overlap still-active transactions. + * + * SerializablePredicateLockListLock + * - Protects the linked list of locks held by a transaction. Note + * that the locks themselves are also covered by the partition + * locks of their respective lock targets; this lock only affects + * the linked list connecting the locks related to a transaction. + * - All transactions share this single lock (with no partitioning). + * - There is never a need for a process other than the one running + * an active transaction to walk the list of locks held by that + * transaction. + * - It is relatively infrequent that another process needs to + * modify the list for a transaction, but it does happen for such + * things as index page splits for pages with predicate locks and + * freeing of predicate locked pages by a vacuum process. When + * removing a lock in such cases, the lock itself contains the + * pointers needed to remove it from the list. When adding a + * lock in such cases, the lock can be added using the anchor in + * the transaction structure. Neither requires walking the list. + * - Cleaning up the list for a terminated transaction is sometimes + * not done on a retail basis, in which case no lock is required. + * - Due to the above, a process accessing its active transaction's + * list always uses a shared lock, regardless of whether it is + * walking or maintaining the list. This improves concurrency + * for the common access patterns. + * - A process which needs to alter the list of a transaction other + * than its own active transaction must acquire an exclusive + * lock. + * + * FirstPredicateLockMgrLock based partition locks + * - The same lock protects a target, all locks on that target, and + * the linked list of locks on the target.. + * - When more than one is needed, acquire in ascending order. + * + * SerializableXactHashLock + * - Protects both PredXact and SerializableXidHash. + * + * PredicateLockNextRowLinkLock + * - Protects the priorVersionOfRow and nextVersionOfRow fields of + * PREDICATELOCKTARGET when linkage is being created or destroyed. + * + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/storage/lmgr/predicate.c + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * + * housekeeping for setting up shared memory predicate lock structures + * InitPredicateLocks(void) + * PredicateLockShmemSize(void) + * + * predicate lock reporting + * GetPredicateLockStatusData(void) + * PageIsPredicateLocked(Relation relation, BlockNumber blkno) + * + * predicate lock maintenance + * RegisterSerializableTransaction(Snapshot snapshot) + * RegisterPredicateLockingXid(void) + * PredicateLockRelation(Relation relation) + * PredicateLockPage(Relation relation, BlockNumber blkno) + * PredicateLockTuple(Relation relation, HeapTuple tuple) + * PredicateLockPageSplit(Relation relation, BlockNumber oldblkno, + * BlockNumber newblkno); + * PredicateLockPageCombine(Relation relation, BlockNumber oldblkno, + * BlockNumber newblkno); + * PredicateLockTupleRowVersionLink(const Relation relation, + * const HeapTuple oldTuple, + * const HeapTuple newTuple) + * ReleasePredicateLocks(bool isCommit) + * + * conflict detection (may also trigger rollback) + * CheckForSerializableConflictOut(bool visible, Relation relation, + * HeapTupleData *tup, Buffer buffer) + * CheckForSerializableConflictIn(Relation relation, HeapTupleData *tup, + * Buffer buffer) + * + * final rollback checking + * PreCommit_CheckForSerializationFailure(void) + * + * two-phase commit support + * AtPrepare_PredicateLocks(void); + * PostPrepare_PredicateLocks(TransactionId xid); + * PredicateLockTwoPhaseFinish(TransactionId xid, bool isCommit); + * predicatelock_twophase_recover(TransactionId xid, uint16 info, + * void *recdata, uint32 len); + */ + +#include "postgres.h" + +#include "access/slru.h" +#include "access/subtrans.h" +#include "access/transam.h" +#include "access/twophase.h" +#include "access/twophase_rmgr.h" +#include "access/xact.h" +#include "miscadmin.h" +#include "storage/bufmgr.h" +#include "storage/predicate.h" +#include "storage/predicate_internals.h" +#include "storage/procarray.h" +#include "utils/rel.h" +#include "utils/snapmgr.h" +#include "utils/tqual.h" + +/* Uncomment the next line to test the graceful degradation code. */ +/* #define TEST_OLDSERXID */ + +/* + * Test the most selective fields first, for performance. + * + * a is covered by b if all of the following hold: + * 1) a.database = b.database + * 2) a.relation = b.relation + * 3) b.offset is invalid (b is page-granularity or higher) + * 4) either of the following: + * 4a) a.offset is valid (a is tuple-granularity) and a.page = b.page + * or 4b) a.offset is invalid and b.page is invalid (a is + * page-granularity and b is relation-granularity + */ +#define TargetTagIsCoveredBy(covered_target, covering_target) \ + ((GET_PREDICATELOCKTARGETTAG_RELATION(covered_target) == /* (2) */ \ + GET_PREDICATELOCKTARGETTAG_RELATION(covering_target)) \ + && (GET_PREDICATELOCKTARGETTAG_OFFSET(covering_target) == \ + InvalidOffsetNumber) /* (3) */ \ + && (((GET_PREDICATELOCKTARGETTAG_OFFSET(covered_target) != \ + InvalidOffsetNumber) /* (4a) */ \ + && (GET_PREDICATELOCKTARGETTAG_PAGE(covering_target) == \ + GET_PREDICATELOCKTARGETTAG_PAGE(covered_target))) \ + || ((GET_PREDICATELOCKTARGETTAG_PAGE(covering_target) == \ + InvalidBlockNumber) /* (4b) */ \ + && (GET_PREDICATELOCKTARGETTAG_PAGE(covered_target) \ + != InvalidBlockNumber))) \ + && (GET_PREDICATELOCKTARGETTAG_DB(covered_target) == /* (1) */ \ + GET_PREDICATELOCKTARGETTAG_DB(covering_target))) + +/* + * The predicate locking target and lock shared hash tables are partitioned to + * reduce contention. To determine which partition a given target belongs to, + * compute the tag's hash code with PredicateLockTargetTagHashCode(), then + * apply one of these macros. + * NB: NUM_PREDICATELOCK_PARTITIONS must be a power of 2! + */ +#define PredicateLockHashPartition(hashcode) \ + ((hashcode) % NUM_PREDICATELOCK_PARTITIONS) +#define PredicateLockHashPartitionLock(hashcode) \ + ((LWLockId) (FirstPredicateLockMgrLock + PredicateLockHashPartition(hashcode))) + +#define NPREDICATELOCKTARGETENTS() \ + mul_size(max_predicate_locks_per_xact, add_size(MaxBackends, max_prepared_xacts)) + +#define SxactIsOnFinishedList(sxact) (!SHMQueueIsDetached(&((sxact)->finishedLink))) + +#define SxactIsPrepared(sxact) (((sxact)->flags & SXACT_FLAG_PREPARED) != 0) +#define SxactIsCommitted(sxact) (((sxact)->flags & SXACT_FLAG_COMMITTED) != 0) +#define SxactIsRolledBack(sxact) (((sxact)->flags & SXACT_FLAG_ROLLED_BACK) != 0) +#define SxactIsReadOnly(sxact) (((sxact)->flags & SXACT_FLAG_READ_ONLY) != 0) +#define SxactHasSummaryConflictIn(sxact) (((sxact)->flags & SXACT_FLAG_SUMMARY_CONFLICT_IN) != 0) +#define SxactHasSummaryConflictOut(sxact) (((sxact)->flags & SXACT_FLAG_SUMMARY_CONFLICT_OUT) != 0) +#define SxactHasConflictOut(sxact) (((sxact)->flags & SXACT_FLAG_CONFLICT_OUT) != 0) +#define SxactIsDeferrableWaiting(sxact) (((sxact)->flags & SXACT_FLAG_DEFERRABLE_WAITING) != 0) +#define SxactIsROSafe(sxact) (((sxact)->flags & SXACT_FLAG_RO_SAFE) != 0) +#define SxactIsROUnsafe(sxact) (((sxact)->flags & SXACT_FLAG_RO_UNSAFE) != 0) +#define SxactIsMarkedForDeath(sxact) (((sxact)->flags & SXACT_FLAG_MARKED_FOR_DEATH) != 0) + +/* + * When a public interface method is called for a split on an index relation, + * this is the test to see if we should do a quick return. + */ +#define SkipSplitTracking(relation) \ + (((relation)->rd_id < FirstBootstrapObjectId) \ + || RelationUsesLocalBuffers(relation)) + +/* + * When a public interface method is called for serializing a relation within + * the current transaction, this is the test to see if we should do a quick + * return. + */ +#define SkipSerialization(relation) \ + ((!IsolationIsSerializable()) \ + || ((MySerializableXact == InvalidSerializableXact)) \ + || ReleasePredicateLocksIfROSafe() \ + || SkipSplitTracking(relation)) + + +/* + * Compute the hash code associated with a PREDICATELOCKTARGETTAG. + * + * To avoid unnecessary recomputations of the hash code, we try to do this + * just once per function, and then pass it around as needed. Aside from + * passing the hashcode to hash_search_with_hash_value(), we can extract + * the lock partition number from the hashcode. + */ +#define PredicateLockTargetTagHashCode(predicatelocktargettag) \ + (tag_hash((predicatelocktargettag), sizeof(PREDICATELOCKTARGETTAG))) + +/* + * Given a predicate lock tag, and the hash for its target, + * compute the lock hash. + * + * To make the hash code also depend on the transaction, we xor the sxid + * struct's address into the hash code, left-shifted so that the + * partition-number bits don't change. Since this is only a hash, we + * don't care if we lose high-order bits of the address; use an + * intermediate variable to suppress cast-pointer-to-int warnings. + */ +#define PredicateLockHashCodeFromTargetHashCode(predicatelocktag, targethash) \ + ((targethash) ^ ((uint32) PointerGetDatum((predicatelocktag)->myXact)) \ + << LOG2_NUM_PREDICATELOCK_PARTITIONS) + + +/* + * The SLRU buffer area through which we access the old xids. + */ +static SlruCtlData OldSerXidSlruCtlData; + +#define OldSerXidSlruCtl (&OldSerXidSlruCtlData) + +#define OLDSERXID_PAGESIZE BLCKSZ +#define OLDSERXID_ENTRYSIZE sizeof(SerCommitSeqNo) +#define OLDSERXID_ENTRIESPERPAGE (OLDSERXID_PAGESIZE / OLDSERXID_ENTRYSIZE) +#define OLDSERXID_MAX_PAGE (SLRU_PAGES_PER_SEGMENT * 0x10000 - 1) + +#define OldSerXidNextPage(page) (((page) >= OLDSERXID_MAX_PAGE) ? 0 : (page) + 1) + +#define OldSerXidValue(slotno, xid) (*((SerCommitSeqNo *) \ + (OldSerXidSlruCtl->shared->page_buffer[slotno] + \ + ((((uint32) (xid)) % OLDSERXID_ENTRIESPERPAGE) * OLDSERXID_ENTRYSIZE)))) + +#define OldSerXidPage(xid) ((((uint32) (xid)) / OLDSERXID_ENTRIESPERPAGE) % (OLDSERXID_MAX_PAGE + 1)) +#define OldSerXidSegment(page) ((page) / SLRU_PAGES_PER_SEGMENT) + +typedef struct OldSerXidControlData +{ + int headPage; + int tailSegment; + TransactionId headXid; + TransactionId tailXid; + bool warningIssued; +} OldSerXidControlData; + +typedef struct OldSerXidControlData *OldSerXidControl; + +static OldSerXidControl oldSerXidControl; + +/* + * When the oldest committed transaction on the "finished" list is moved to + * SLRU, its predicate locks will be moved to this "dummy" transaction, + * collapsing duplicate targets. When a duplicate is found, the later + * commitSeqNo is used. + */ +static SERIALIZABLEXACT *OldCommittedSxact; + + +/* This configuration variable is used to set the predicate lock table size */ +int max_predicate_locks_per_xact; /* set by guc.c */ + +/* + * This provides a list of objects in order to track transactions + * participating in predicate locking. Entries in the list are fixed size, + * and reside in shared memory. The memory address of an entry must remain + * fixed during its lifetime. The list will be protected from concurrent + * update externally; no provision is made in this code to manage that. The + * number of entries in the list, and the size allowed for each entry is + * fixed upon creation. + */ +static PredXactList PredXact; + +/* + * This provides a pool of RWConflict data elements to use in conflict lists + * between transactions. + */ +static RWConflictPoolHeader RWConflictPool; + +/* + * The predicate locking hash tables are in shared memory. + * Each backend keeps pointers to them. + */ +static HTAB *SerializableXidHash; +static HTAB *PredicateLockTargetHash; +static HTAB *PredicateLockHash; +static SHM_QUEUE *FinishedSerializableTransactions; + +/* + * Tag for a reserved entry in PredicateLockTargetHash; used to ensure + * there's an element available for scratch space if we need it, + * e.g. in PredicateLockPageSplit. This is an otherwise-invalid tag. + */ +static const PREDICATELOCKTARGETTAG ReservedTargetTag = {0, 0, 0, 0, 0}; + +/* + * The local hash table used to determine when to combine multiple fine- + * grained locks into a single courser-grained lock. + */ +static HTAB *LocalPredicateLockHash = NULL; + +/* + * Keep a pointer to the currently-running serializable transaction (if any) + * for quick reference. + * TODO SSI: Remove volatile qualifier and the then-unnecessary casts? + */ +static volatile SERIALIZABLEXACT *MySerializableXact = InvalidSerializableXact; + +/* local functions */ + +static SERIALIZABLEXACT *CreatePredXact(void); +static void ReleasePredXact(SERIALIZABLEXACT *sxact); +static SERIALIZABLEXACT *FirstPredXact(void); +static SERIALIZABLEXACT *NextPredXact(SERIALIZABLEXACT *sxact); + +static bool RWConflictExists(const SERIALIZABLEXACT *reader, const SERIALIZABLEXACT *writer); +static void SetRWConflict(SERIALIZABLEXACT *reader, SERIALIZABLEXACT *writer); +static void SetPossibleUnsafeConflict(SERIALIZABLEXACT *roXact, SERIALIZABLEXACT *activeXact); +static void ReleaseRWConflict(RWConflict conflict); +static void FlagSxactUnsafe(SERIALIZABLEXACT *sxact); + +static bool OldSerXidPagePrecedesLogically(int p, int q); +static void OldSerXidInit(void); +static void OldSerXidAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo); +static SerCommitSeqNo OldSerXidGetMinConflictCommitSeqNo(TransactionId xid); +static void OldSerXidSetActiveSerXmin(TransactionId xid); + +static uint32 predicatelock_hash(const void *key, Size keysize); +static void SummarizeOldestCommittedSxact(void); +static Snapshot GetSafeSnapshot(Snapshot snapshot); +static Snapshot RegisterSerializableTransactionInt(Snapshot snapshot); +static bool PredicateLockExists(const PREDICATELOCKTARGETTAG *targettag); +static bool GetParentPredicateLockTag(const PREDICATELOCKTARGETTAG *tag, + PREDICATELOCKTARGETTAG *parent); +static bool CoarserLockCovers(const PREDICATELOCKTARGETTAG *newtargettag); +static void RemoveTargetIfNoLongerUsed(PREDICATELOCKTARGET *target, + uint32 targettaghash); +static void DeleteChildTargetLocks(const PREDICATELOCKTARGETTAG *newtargettag); +static int PredicateLockPromotionThreshold(const PREDICATELOCKTARGETTAG *tag); +static bool CheckAndPromotePredicateLockRequest(const PREDICATELOCKTARGETTAG *reqtag); +static void DecrementParentLocks(const PREDICATELOCKTARGETTAG *targettag); +static void CreatePredicateLock(const PREDICATELOCKTARGETTAG *targettag, + uint32 targettaghash, + SERIALIZABLEXACT *sxact); +static void DeleteLockTarget(PREDICATELOCKTARGET *target, uint32 targettaghash); +static bool TransferPredicateLocksToNewTarget(const PREDICATELOCKTARGETTAG oldtargettag, + const PREDICATELOCKTARGETTAG newtargettag, + bool removeOld); +static void PredicateLockAcquire(const PREDICATELOCKTARGETTAG *targettag); +static void SetNewSxactGlobalXmin(void); +static bool ReleasePredicateLocksIfROSafe(void); +static void ClearOldPredicateLocks(void); +static void ReleaseOneSerializableXact(SERIALIZABLEXACT *sxact, bool partial, + bool summarize); +static bool XidIsConcurrent(TransactionId xid); +static void CheckTargetForConflictsIn(PREDICATELOCKTARGETTAG *targettag); +static bool CheckSingleTargetForConflictsIn(PREDICATELOCKTARGETTAG *targettag, + PREDICATELOCKTARGETTAG *nexttargettag); +static void FlagRWConflict(SERIALIZABLEXACT *reader, SERIALIZABLEXACT *writer); +static void OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *reader, + SERIALIZABLEXACT *writer); + +/*------------------------------------------------------------------------*/ + +/* + * These functions are a simple implementation of a list for this specific + * type of struct. If there is ever a generalized shared memory list, we + * should probably switch to that. + */ +static SERIALIZABLEXACT * +CreatePredXact(void) +{ + PredXactListElement ptle; + + ptle = (PredXactListElement) + SHMQueueNext(&PredXact->availableList, + &PredXact->availableList, + offsetof(PredXactListElementData, link)); + if (!ptle) + return NULL; + + SHMQueueDelete(&ptle->link); + SHMQueueInsertBefore(&PredXact->activeList, &ptle->link); + return &ptle->sxact; +} + +static void +ReleasePredXact(SERIALIZABLEXACT *sxact) +{ + PredXactListElement ptle; + + Assert(ShmemAddrIsValid(sxact)); + + ptle = (PredXactListElement) + (((char *) sxact) + - offsetof(PredXactListElementData, sxact) + +offsetof(PredXactListElementData, link)); + SHMQueueDelete(&ptle->link); + SHMQueueInsertBefore(&PredXact->availableList, &ptle->link); +} + +static SERIALIZABLEXACT * +FirstPredXact(void) +{ + PredXactListElement ptle; + + ptle = (PredXactListElement) + SHMQueueNext(&PredXact->activeList, + &PredXact->activeList, + offsetof(PredXactListElementData, link)); + if (!ptle) + return NULL; + + return &ptle->sxact; +} + +static SERIALIZABLEXACT * +NextPredXact(SERIALIZABLEXACT *sxact) +{ + PredXactListElement ptle; + + Assert(ShmemAddrIsValid(sxact)); + + ptle = (PredXactListElement) + (((char *) sxact) + - offsetof(PredXactListElementData, sxact) + +offsetof(PredXactListElementData, link)); + ptle = (PredXactListElement) + SHMQueueNext(&PredXact->activeList, + &ptle->link, + offsetof(PredXactListElementData, link)); + if (!ptle) + return NULL; + + return &ptle->sxact; +} + +/*------------------------------------------------------------------------*/ + +/* + * These functions manage primitive access to the RWConflict pool and lists. + */ +static bool +RWConflictExists(const SERIALIZABLEXACT *reader, const SERIALIZABLEXACT *writer) +{ + RWConflict conflict; + + Assert(reader != writer); + + /* Check the ends of the purported conflict first. */ + if (SxactIsRolledBack(reader) + || SxactIsRolledBack(writer) + || SHMQueueEmpty(&reader->outConflicts) + || SHMQueueEmpty(&writer->inConflicts)) + return false; + + /* A conflict is possible; walk the list to find out. */ + conflict = (RWConflict) + SHMQueueNext(&reader->outConflicts, + &reader->outConflicts, + offsetof(RWConflictData, outLink)); + while (conflict) + { + if (conflict->sxactIn == writer) + return true; + conflict = (RWConflict) + SHMQueueNext(&reader->outConflicts, + &conflict->outLink, + offsetof(RWConflictData, outLink)); + } + + /* No conflict found. */ + return false; +} + +static void +SetRWConflict(SERIALIZABLEXACT *reader, SERIALIZABLEXACT *writer) +{ + RWConflict conflict; + + Assert(reader != writer); + Assert(!RWConflictExists(reader, writer)); + + conflict = (RWConflict) + SHMQueueNext(&RWConflictPool->availableList, + &RWConflictPool->availableList, + offsetof(RWConflictData, outLink)); + if (!conflict) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("not enough elements in RWConflictPool to record a rw-conflict"), + errhint("You might need to run fewer transactions at a time or increase max_connections."))); + + SHMQueueDelete(&conflict->outLink); + + conflict->sxactOut = reader; + conflict->sxactIn = writer; + SHMQueueInsertBefore(&reader->outConflicts, &conflict->outLink); + SHMQueueInsertBefore(&writer->inConflicts, &conflict->inLink); +} + +static void +SetPossibleUnsafeConflict(SERIALIZABLEXACT *roXact, + SERIALIZABLEXACT *activeXact) +{ + RWConflict conflict; + + Assert(roXact != activeXact); + Assert(SxactIsReadOnly(roXact)); + Assert(!SxactIsReadOnly(activeXact)); + + conflict = (RWConflict) + SHMQueueNext(&RWConflictPool->availableList, + &RWConflictPool->availableList, + offsetof(RWConflictData, outLink)); + if (!conflict) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("not enough elements in RWConflictPool to record a potential rw-conflict"), + errhint("You might need to run fewer transactions at a time or increase max_connections."))); + + SHMQueueDelete(&conflict->outLink); + + conflict->sxactOut = activeXact; + conflict->sxactIn = roXact; + SHMQueueInsertBefore(&activeXact->possibleUnsafeConflicts, + &conflict->outLink); + SHMQueueInsertBefore(&roXact->possibleUnsafeConflicts, + &conflict->inLink); +} + +static void +ReleaseRWConflict(RWConflict conflict) +{ + SHMQueueDelete(&conflict->inLink); + SHMQueueDelete(&conflict->outLink); + SHMQueueInsertBefore(&RWConflictPool->availableList, &conflict->outLink); +} + +static void +FlagSxactUnsafe(SERIALIZABLEXACT *sxact) +{ + RWConflict conflict, + nextConflict; + + Assert(SxactIsReadOnly(sxact)); + Assert(!SxactIsROSafe(sxact)); + + sxact->flags |= SXACT_FLAG_RO_UNSAFE; + + /* + * We know this isn't a safe snapshot, so we can stop looking for other + * potential conflicts. + */ + conflict = (RWConflict) + SHMQueueNext(&sxact->possibleUnsafeConflicts, + &sxact->possibleUnsafeConflicts, + offsetof(RWConflictData, inLink)); + while (conflict) + { + nextConflict = (RWConflict) + SHMQueueNext(&sxact->possibleUnsafeConflicts, + &conflict->inLink, + offsetof(RWConflictData, inLink)); + + Assert(!SxactIsReadOnly(conflict->sxactOut)); + Assert(sxact == conflict->sxactIn); + + ReleaseRWConflict(conflict); + + conflict = nextConflict; + } +} + +/*------------------------------------------------------------------------*/ + +/* + * We will work on the page range of 0..OLDSERXID_MAX_PAGE. + * Compares using wraparound logic, as is required by slru.c. + */ +static bool +OldSerXidPagePrecedesLogically(int p, int q) +{ + int diff; + + /* + * We have to compare modulo (OLDSERXID_MAX_PAGE+1)/2. Both inputs should + * be in the range 0..OLDSERXID_MAX_PAGE. + */ + Assert(p >= 0 && p <= OLDSERXID_MAX_PAGE); + Assert(q >= 0 && q <= OLDSERXID_MAX_PAGE); + + diff = p - q; + if (diff >= ((OLDSERXID_MAX_PAGE + 1) / 2)) + diff -= OLDSERXID_MAX_PAGE + 1; + else if (diff < -((OLDSERXID_MAX_PAGE + 1) / 2)) + diff += OLDSERXID_MAX_PAGE + 1; + return diff < 0; +} + +/* + * Initialize for the tracking of old serializable committed xids. + */ +static void +OldSerXidInit(void) +{ + bool found; + + /* + * Set up SLRU management of the pg_serial data. + */ + OldSerXidSlruCtl->PagePrecedes = OldSerXidPagePrecedesLogically; + SimpleLruInit(OldSerXidSlruCtl, "OldSerXid SLRU Ctl", NUM_OLDSERXID_BUFFERS, 0, + OldSerXidLock, "pg_serial"); + /* Override default assumption that writes should be fsync'd */ + OldSerXidSlruCtl->do_fsync = false; + + /* + * Create or attach to the OldSerXidControl structure. + */ + oldSerXidControl = (OldSerXidControl) + ShmemInitStruct("OldSerXidControlData", sizeof(OldSerXidControlData), &found); + + if (!found) + { + /* + * Set control information to reflect empty SLRU. + */ + oldSerXidControl->headPage = -1; + oldSerXidControl->tailSegment = -1; + oldSerXidControl->headXid = InvalidTransactionId; + oldSerXidControl->tailXid = InvalidTransactionId; + oldSerXidControl->warningIssued = false; + } +} + +/* + * Record a committed read write serializable xid and the minimum + * commitSeqNo of any transactions to which this xid had a rw-conflict out. + * A zero seqNo means that there were no conflicts out from xid. + * + * The return value is normally false -- true means that we're about to + * wrap around our space for tracking these xids, so the caller might want + * to take action to prevent that. + */ +static void +OldSerXidAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo) +{ + TransactionId tailXid; + int targetPage; + int slotno; + int page; + int xidSpread; + bool isNewPage; + + Assert(TransactionIdIsValid(xid)); + + targetPage = OldSerXidPage(xid); + + LWLockAcquire(OldSerXidLock, LW_EXCLUSIVE); + + /* + * If no serializable transactions are active, there shouldn't be anything + * to push out to this SLRU. Hitting this assert would mean there's + * something wrong with the earlier cleanup logic. + */ + tailXid = oldSerXidControl->tailXid; + Assert(TransactionIdIsValid(tailXid)); + + if (oldSerXidControl->headPage < 0) + { + page = OldSerXidPage(tailXid); + oldSerXidControl->tailSegment = OldSerXidSegment(page); + page = oldSerXidControl->tailSegment * OLDSERXID_ENTRIESPERPAGE; + isNewPage = true; + } + else + { + page = OldSerXidNextPage(oldSerXidControl->headPage); + isNewPage = OldSerXidPagePrecedesLogically(oldSerXidControl->headPage, targetPage); + } + + if (!TransactionIdIsValid(oldSerXidControl->headXid) + || TransactionIdFollows(xid, oldSerXidControl->headXid)) + oldSerXidControl->headXid = xid; + if (oldSerXidControl->headPage < 0 + || OldSerXidPagePrecedesLogically(oldSerXidControl->headPage, targetPage)) + oldSerXidControl->headPage = targetPage; + + xidSpread = (((uint32) xid) - ((uint32) tailXid)); + if (oldSerXidControl->warningIssued) + { + if (xidSpread < 800000000) + oldSerXidControl->warningIssued = false; + } + else if (xidSpread >= 1000000000) + { + oldSerXidControl->warningIssued = true; + ereport(WARNING, + (errmsg("memory for serializable conflict tracking is nearly exhausted"), + errhint("There may be an idle transaction or a forgotten prepared transaction causing this."))); + } + + if (isNewPage) + { + /* Initialize intervening pages. */ + while (page != targetPage) + { + (void) SimpleLruZeroPage(OldSerXidSlruCtl, page); + page = OldSerXidNextPage(page); + } + slotno = SimpleLruZeroPage(OldSerXidSlruCtl, targetPage); + } + else + slotno = SimpleLruReadPage(OldSerXidSlruCtl, targetPage, true, xid); + + OldSerXidValue(slotno, xid) = minConflictCommitSeqNo; + + LWLockRelease(OldSerXidLock); +} + +/* + * Get the minimum commitSeqNo for any conflict out for the given xid. For + * a transaction which exists but has no conflict out, InvalidSerCommitSeqNo + * will be returned. + */ +static SerCommitSeqNo +OldSerXidGetMinConflictCommitSeqNo(TransactionId xid) +{ + TransactionId headXid; + TransactionId tailXid; + SerCommitSeqNo val; + int slotno; + + Assert(TransactionIdIsValid(xid)); + + LWLockAcquire(OldSerXidLock, LW_SHARED); + headXid = oldSerXidControl->headXid; + tailXid = oldSerXidControl->tailXid; + LWLockRelease(OldSerXidLock); + + if (!TransactionIdIsValid(headXid)) + return 0; + + Assert(TransactionIdIsValid(tailXid)); + + if (TransactionIdPrecedes(xid, tailXid) + || TransactionIdFollows(xid, headXid)) + return 0; + + /* + * The following function must be called without holding OldSerXidLock, + * but will return with that lock held, which must then be released. + */ + slotno = SimpleLruReadPage_ReadOnly(OldSerXidSlruCtl, + OldSerXidPage(xid), xid); + val = OldSerXidValue(slotno, xid); + LWLockRelease(OldSerXidLock); + return val; +} + +/* + * Call this whenever there is a new xmin for active serializable + * transactions. We don't need to keep information on transactions which + * preceed that. InvalidTransactionId means none active, so everything in + * the SLRU should be discarded. + */ +static void +OldSerXidSetActiveSerXmin(TransactionId xid) +{ + int newTailPage; + int newTailSegment; + + LWLockAcquire(OldSerXidLock, LW_EXCLUSIVE); + + /* + * When no sxacts are active, nothing overlaps, set the xid values to + * invalid to show that there are no valid entries. Don't clear the + * segment/page information, though. A new xmin might still land in an + * existing segment, and we don't want to repeatedly delete and re-create + * the same segment file. + */ + if (!TransactionIdIsValid(xid)) + { + if (TransactionIdIsValid(oldSerXidControl->tailXid)) + { + oldSerXidControl->headXid = InvalidTransactionId; + oldSerXidControl->tailXid = InvalidTransactionId; + } + LWLockRelease(OldSerXidLock); + return; + } + + /* + * When we're recovering prepared transactions, the global xmin might move + * backwards depending on the order they're recovered. Normally that's not + * OK, but during recovery no serializable transactions will commit, so + * the SLRU is empty and we can get away with it. + */ + if (RecoveryInProgress()) + { + Assert(oldSerXidControl->headPage < 0); + if (!TransactionIdIsValid(oldSerXidControl->tailXid) + || TransactionIdPrecedes(xid, oldSerXidControl->tailXid)) + oldSerXidControl->tailXid = xid; + LWLockRelease(OldSerXidLock); + return; + } + + Assert(!TransactionIdIsValid(oldSerXidControl->tailXid) + || TransactionIdFollows(xid, oldSerXidControl->tailXid)); + + oldSerXidControl->tailXid = xid; + + /* Exit quickly if there are no segments active. */ + if (oldSerXidControl->headPage < 0) + { + LWLockRelease(OldSerXidLock); + return; + } + + newTailPage = OldSerXidPage(xid); + newTailSegment = OldSerXidSegment(newTailPage); + + /* Exit quickly if we're still on the same segment. */ + if (newTailSegment == oldSerXidControl->tailSegment) + { + LWLockRelease(OldSerXidLock); + return; + } + + oldSerXidControl->tailSegment = newTailSegment; + + /* See if that has cleared the last segment. */ + if (OldSerXidPagePrecedesLogically(oldSerXidControl->headPage, + newTailSegment * SLRU_PAGES_PER_SEGMENT)) + { + oldSerXidControl->headXid = InvalidTransactionId; + oldSerXidControl->headPage = -1; + oldSerXidControl->tailSegment = -1; + } + + LWLockRelease(OldSerXidLock); + + SimpleLruTruncate(OldSerXidSlruCtl, newTailPage); +} + +/*------------------------------------------------------------------------*/ + +/* + * InitPredicateLocks -- Initialize the predicate locking data structures. + * + * This is called from CreateSharedMemoryAndSemaphores(), which see for + * more comments. In the normal postmaster case, the shared hash tables + * are created here. Backends inherit the pointers + * to the shared tables via fork(). In the EXEC_BACKEND case, each + * backend re-executes this code to obtain pointers to the already existing + * shared hash tables. + */ +void +InitPredicateLocks(void) +{ + HASHCTL info; + int hash_flags; + long init_table_size, + max_table_size; + Size requestSize; + bool found; + + /* + * Compute init/max size to request for predicate lock target hashtable. + * Note these calculations must agree with PredicateLockShmemSize! + */ + max_table_size = NPREDICATELOCKTARGETENTS(); + init_table_size = max_table_size / 2; + + /* + * Allocate hash table for PREDICATELOCKTARGET structs. This stores + * per-predicate-lock-target information. + */ + MemSet(&info, 0, sizeof(info)); + info.keysize = sizeof(PREDICATELOCKTARGETTAG); + info.entrysize = sizeof(PREDICATELOCKTARGET); + info.hash = tag_hash; + info.num_partitions = NUM_PREDICATELOCK_PARTITIONS; + hash_flags = (HASH_ELEM | HASH_FUNCTION | HASH_PARTITION); + + PredicateLockTargetHash = ShmemInitHash("PREDICATELOCKTARGET hash", + init_table_size, + max_table_size, + &info, + hash_flags); + + /* Assume an average of 2 xacts per target */ + max_table_size *= 2; + init_table_size *= 2; + + /* + * Reserve an entry in the hash table; we use it to make sure there's + * always one entry available when we need to split or combine a page, + * because running out of space there could mean aborting a + * non-serializable transaction. + */ + hash_search(PredicateLockTargetHash, &ReservedTargetTag, + HASH_ENTER, NULL); + + + /* + * Allocate hash table for PREDICATELOCK structs. This stores per + * xact-lock-of-a-target information. + */ + MemSet(&info, 0, sizeof(info)); + info.keysize = sizeof(PREDICATELOCKTAG); + info.entrysize = sizeof(PREDICATELOCK); + info.hash = predicatelock_hash; + info.num_partitions = NUM_PREDICATELOCK_PARTITIONS; + hash_flags = (HASH_ELEM | HASH_FUNCTION | HASH_PARTITION); + + PredicateLockHash = ShmemInitHash("PREDICATELOCK hash", + init_table_size, + max_table_size, + &info, + hash_flags); + + /* + * Compute init/max size to request for serializable transaction + * hashtable. Note these calculations must agree with + * PredicateLockShmemSize! + */ + max_table_size = (MaxBackends + max_prepared_xacts); + init_table_size = max_table_size / 2; + + /* + * Allocate a list to hold information on transactions participating in + * predicate locking. + * + * Assume an average of 10 predicate locking transactions per backend. + * This allows aggressive cleanup while detail is present before data must + * be summarized for storage in SLRU and the "dummy" transaction. + */ + max_table_size *= 10; + init_table_size *= 10; + + PredXact = ShmemInitStruct("PredXactList", + PredXactListDataSize, + &found); + if (!found) + { + int i; + + SHMQueueInit(&PredXact->availableList); + SHMQueueInit(&PredXact->activeList); + PredXact->SxactGlobalXmin = InvalidTransactionId; + PredXact->SxactGlobalXminCount = 0; + PredXact->WritableSxactCount = 0; + PredXact->LastSxactCommitSeqNo = FirstNormalSerCommitSeqNo - 1; + PredXact->CanPartialClearThrough = 0; + PredXact->HavePartialClearedThrough = 0; + PredXact->NeedTargetLinkCleanup = false; + requestSize = mul_size((Size) max_table_size, + PredXactListElementDataSize); + PredXact->element = ShmemAlloc(requestSize); + if (PredXact->element == NULL) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("not enough shared memory for elements of data structure" + " \"%s\" (%lu bytes requested)", + "PredXactList", (unsigned long) requestSize))); + /* Add all elements to available list, clean. */ + memset(PredXact->element, 0, requestSize); + for (i = 0; i < max_table_size; i++) + { + SHMQueueInsertBefore(&(PredXact->availableList), + &(PredXact->element[i].link)); + } + PredXact->OldCommittedSxact = CreatePredXact(); + SetInvalidVirtualTransactionId(PredXact->OldCommittedSxact->vxid); + PredXact->OldCommittedSxact->commitSeqNo = 0; + PredXact->OldCommittedSxact->SeqNo.lastCommitBeforeSnapshot = 0; + SHMQueueInit(&PredXact->OldCommittedSxact->outConflicts); + SHMQueueInit(&PredXact->OldCommittedSxact->inConflicts); + SHMQueueInit(&PredXact->OldCommittedSxact->predicateLocks); + SHMQueueInit(&PredXact->OldCommittedSxact->finishedLink); + SHMQueueInit(&PredXact->OldCommittedSxact->possibleUnsafeConflicts); + PredXact->OldCommittedSxact->topXid = InvalidTransactionId; + PredXact->OldCommittedSxact->finishedBefore = InvalidTransactionId; + PredXact->OldCommittedSxact->xmin = InvalidTransactionId; + PredXact->OldCommittedSxact->flags = SXACT_FLAG_COMMITTED; + PredXact->OldCommittedSxact->pid = 0; + } + /* This never changes, so let's keep a local copy. */ + OldCommittedSxact = PredXact->OldCommittedSxact; + + /* + * Allocate hash table for SERIALIZABLEXID structs. This stores per-xid + * information for serializable transactions which have accessed data. + */ + MemSet(&info, 0, sizeof(info)); + info.keysize = sizeof(SERIALIZABLEXIDTAG); + info.entrysize = sizeof(SERIALIZABLEXID); + info.hash = tag_hash; + hash_flags = (HASH_ELEM | HASH_FUNCTION); + + SerializableXidHash = ShmemInitHash("SERIALIZABLEXID hash", + init_table_size, + max_table_size, + &info, + hash_flags); + + /* + * Allocate space for tracking rw-conflicts in lists attached to the + * transactions. + * + * Assume an average of 5 conflicts per transaction. Calculations suggest + * that this will prevent resource exhaustion in even the most pessimal + * loads up to max_connections = 200 with all 200 connections pounding the + * database with serializable transactions. Beyond that, there may be + * occassional transactions canceled when trying to flag conflicts. That's + * probably OK. + */ + max_table_size *= 5; + + RWConflictPool = ShmemInitStruct("RWConflictPool", + RWConflictPoolHeaderDataSize, + &found); + if (!found) + { + int i; + + SHMQueueInit(&RWConflictPool->availableList); + requestSize = mul_size((Size) max_table_size, + PredXactListElementDataSize); + RWConflictPool->element = ShmemAlloc(requestSize); + if (RWConflictPool->element == NULL) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("not enough shared memory for elements of data structure" + " \"%s\" (%lu bytes requested)", + "RWConflictPool", (unsigned long) requestSize))); + /* Add all elements to available list, clean. */ + memset(RWConflictPool->element, 0, requestSize); + for (i = 0; i < max_table_size; i++) + { + SHMQueueInsertBefore(&(RWConflictPool->availableList), + &(RWConflictPool->element[i].outLink)); + } + } + + /* + * Create or attach to the header for the list of finished serializable + * transactions. + */ + FinishedSerializableTransactions = (SHM_QUEUE *) + ShmemInitStruct("FinishedSerializableTransactions", + sizeof(SHM_QUEUE), + &found); + if (!found) + SHMQueueInit(FinishedSerializableTransactions); + + /* + * Initialize the SLRU storage for old committed serializable + * transactions. + */ + OldSerXidInit(); +} + +/* + * Estimate shared-memory space used for predicate lock table + */ +Size +PredicateLockShmemSize(void) +{ + Size size = 0; + long max_table_size; + + /* predicate lock target hash table */ + max_table_size = NPREDICATELOCKTARGETENTS(); + size = add_size(size, hash_estimate_size(max_table_size, + sizeof(PREDICATELOCKTARGET))); + + /* predicate lock hash table */ + max_table_size *= 2; + size = add_size(size, hash_estimate_size(max_table_size, + sizeof(PREDICATELOCK))); + + /* + * Since NPREDICATELOCKTARGETENTS is only an estimate, add 10% safety + * margin. + */ + size = add_size(size, size / 10); + + /* transaction list */ + max_table_size = MaxBackends + max_prepared_xacts; + max_table_size *= 10; + size = add_size(size, PredXactListDataSize); + size = add_size(size, mul_size((Size) max_table_size, + PredXactListElementDataSize)); + + /* transaction xid table */ + size = add_size(size, hash_estimate_size(max_table_size, + sizeof(SERIALIZABLEXID))); + + /* Head for list of finished serializable transactions. */ + size = add_size(size, sizeof(SHM_QUEUE)); + + /* Shared memory structures for SLRU tracking of old committed xids. */ + size = add_size(size, sizeof(OldSerXidControl)); + size = add_size(size, SimpleLruShmemSize(NUM_OLDSERXID_BUFFERS, 0)); + + return size; +} + + +/* + * Compute the hash code associated with a PREDICATELOCKTAG. + * + * Because we want to use just one set of partition locks for both the + * PREDICATELOCKTARGET and PREDICATELOCK hash tables, we have to make sure + * that PREDICATELOCKs fall into the same partition number as their + * associated PREDICATELOCKTARGETs. dynahash.c expects the partition number + * to be the low-order bits of the hash code, and therefore a + * PREDICATELOCKTAG's hash code must have the same low-order bits as the + * associated PREDICATELOCKTARGETTAG's hash code. We achieve this with this + * specialized hash function. + */ +static uint32 +predicatelock_hash(const void *key, Size keysize) +{ + const PREDICATELOCKTAG *predicatelocktag = (const PREDICATELOCKTAG *) key; + uint32 targethash; + + Assert(keysize == sizeof(PREDICATELOCKTAG)); + + /* Look into the associated target object, and compute its hash code */ + targethash = PredicateLockTargetTagHashCode(&predicatelocktag->myTarget->tag); + + return PredicateLockHashCodeFromTargetHashCode(predicatelocktag, targethash); +} + + +/* + * GetPredicateLockStatusData + * Return a table containing the internal state of the predicate + * lock manager for use in pg_lock_status. + * + * Like GetLockStatusData, this function tries to hold the partition LWLocks + * for as short a time as possible by returning two arrays that simply + * contain the PREDICATELOCKTARGETTAG and SERIALIZABLEXACT for each lock + * table entry. Multiple copies of the same PREDICATELOCKTARGETTAG and + * SERIALIZABLEXACT will likely appear. + */ +PredicateLockData * +GetPredicateLockStatusData(void) +{ + PredicateLockData *data; + int i; + int els, + el; + HASH_SEQ_STATUS seqstat; + PREDICATELOCK *predlock; + + data = (PredicateLockData *) palloc(sizeof(PredicateLockData)); + + /* + * To ensure consistency, take simultaneous locks on all partition locks + * in ascending order, then SerializableXactHashLock. + */ + for (i = 0; i < NUM_PREDICATELOCK_PARTITIONS; i++) + LWLockAcquire(FirstPredicateLockMgrLock + i, LW_SHARED); + LWLockAcquire(SerializableXactHashLock, LW_SHARED); + + /* Get number of locks and allocate appropriately-sized arrays. */ + els = hash_get_num_entries(PredicateLockHash); + data->nelements = els; + data->locktags = (PREDICATELOCKTARGETTAG *) + palloc(sizeof(PREDICATELOCKTARGETTAG) * els); + data->xacts = (SERIALIZABLEXACT *) + palloc(sizeof(SERIALIZABLEXACT) * els); + + + /* Scan through PredicateLockHash and copy contents */ + hash_seq_init(&seqstat, PredicateLockHash); + + el = 0; + + while ((predlock = (PREDICATELOCK *) hash_seq_search(&seqstat))) + { + data->locktags[el] = predlock->tag.myTarget->tag; + data->xacts[el] = *predlock->tag.myXact; + el++; + } + + Assert(el == els); + + /* Release locks in reverse order */ + LWLockRelease(SerializableXactHashLock); + for (i = NUM_PREDICATELOCK_PARTITIONS - 1; i >= 0; i--) + LWLockRelease(FirstPredicateLockMgrLock + i); + + return data; +} + +/* + * Free up shared memory structures by pushing the oldest sxact (the one at + * the front of the SummarizeOldestCommittedSxact queue) into summary form. + * Each call will free exactly one SERIALIZABLEXACT structure and may also + * free one or more of these structures: SERIALIZABLEXID, PREDICATELOCK, + * PREDICATELOCKTARGET, RWConflictData. + */ +static void +SummarizeOldestCommittedSxact(void) +{ + SERIALIZABLEXACT *sxact; + + LWLockAcquire(SerializableFinishedListLock, LW_EXCLUSIVE); + +#ifdef TEST_OLDSERXID + if (SHMQueueEmpty(FinishedSerializableTransactions)) + { + LWLockRelease(SerializableFinishedListLock); + return; + } +#else + Assert(!SHMQueueEmpty(FinishedSerializableTransactions)); +#endif + + /* + * Grab the first sxact off the finished list -- this will be the earliest + * commit. Remove it from the list. + */ + sxact = (SERIALIZABLEXACT *) + SHMQueueNext(FinishedSerializableTransactions, + FinishedSerializableTransactions, + offsetof(SERIALIZABLEXACT, finishedLink)); + SHMQueueDelete(&(sxact->finishedLink)); + + /* Add to SLRU summary information. */ + if (TransactionIdIsValid(sxact->topXid) && !SxactIsReadOnly(sxact)) + OldSerXidAdd(sxact->topXid, SxactHasConflictOut(sxact) + ? sxact->SeqNo.earliestOutConflictCommit : InvalidSerCommitSeqNo); + + /* Summarize and release the detail. */ + ReleaseOneSerializableXact(sxact, false, true); + + LWLockRelease(SerializableFinishedListLock); +} + +/* + * GetSafeSnapshot + * Obtain and register a snapshot for a READ ONLY DEFERRABLE + * transaction. Ensures that the snapshot is "safe", i.e. a + * read-only transaction running on it can execute serializably + * without further checks. This requires waiting for concurrent + * transactions to complete, and retrying with a new snapshot if + * one of them could possibly create a conflict. + */ +static Snapshot +GetSafeSnapshot(Snapshot origSnapshot) +{ + Snapshot snapshot; + + Assert(XactReadOnly && XactDeferrable); + + while (true) + { + /* + * RegisterSerializableTransactionInt is going to call + * GetSnapshotData, so we need to provide it the static snapshot our + * caller passed to us. It returns a copy of that snapshot and + * registers it on TopTransactionResourceOwner. + */ + snapshot = RegisterSerializableTransactionInt(origSnapshot); + + if (MySerializableXact == InvalidSerializableXact) + return snapshot; /* no concurrent r/w xacts; it's safe */ + + MySerializableXact->flags |= SXACT_FLAG_DEFERRABLE_WAITING; + + /* + * Wait for concurrent transactions to finish. Stop early if one of + * them marked us as conflicted. + */ + while (!(SHMQueueEmpty((SHM_QUEUE *) + &MySerializableXact->possibleUnsafeConflicts) || + SxactIsROUnsafe(MySerializableXact))) + ProcWaitForSignal(); + + MySerializableXact->flags &= ~SXACT_FLAG_DEFERRABLE_WAITING; + if (!SxactIsROUnsafe(MySerializableXact)) + break; /* success */ + + /* else, need to retry... */ + ereport(DEBUG2, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("deferrable snapshot was unsafe; trying a new one"))); + ReleasePredicateLocks(false); + UnregisterSnapshotFromOwner(snapshot, + TopTransactionResourceOwner); + } + + /* + * Now we have a safe snapshot, so we don't need to do any further checks. + */ + Assert(SxactIsROSafe(MySerializableXact)); + ReleasePredicateLocks(false); + + return snapshot; +} + +/* + * Acquire and register a snapshot which can be used for this transaction.. + * Make sure we have a SERIALIZABLEXACT reference in MySerializableXact. + * It should be current for this process and be contained in PredXact. + */ +Snapshot +RegisterSerializableTransaction(Snapshot snapshot) +{ + Assert(IsolationIsSerializable()); + + /* + * A special optimization is available for SERIALIZABLE READ ONLY + * DEFERRABLE transactions -- we can wait for a suitable snapshot and + * thereby avoid all SSI overhead once it's running.. + */ + if (XactReadOnly && XactDeferrable) + return GetSafeSnapshot(snapshot); + + return RegisterSerializableTransactionInt(snapshot); +} + +static Snapshot +RegisterSerializableTransactionInt(Snapshot snapshot) +{ + PGPROC *proc; + VirtualTransactionId vxid; + SERIALIZABLEXACT *sxact, + *othersxact; + HASHCTL hash_ctl; + + /* We only do this for serializable transactions. Once. */ + Assert(MySerializableXact == InvalidSerializableXact); + + Assert(!RecoveryInProgress()); + + proc = MyProc; + Assert(proc != NULL); + GET_VXID_FROM_PGPROC(vxid, *proc); + + /* + * First we get the sxact structure, which may involve looping and access + * to the "finished" list to free a structure for use. + */ +#ifdef TEST_OLDSERXID + SummarizeOldestCommittedSxact(); +#endif + LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE); + do + { + sxact = CreatePredXact(); + /* If null, push out committed sxact to SLRU summary & retry. */ + if (!sxact) + { + LWLockRelease(SerializableXactHashLock); + SummarizeOldestCommittedSxact(); + LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE); + } + } while (!sxact); + + /* Get and register a snapshot */ + snapshot = GetSnapshotData(snapshot); + snapshot = RegisterSnapshotOnOwner(snapshot, TopTransactionResourceOwner); + + /* + * If there are no serializable transactions which are not read-only, we + * can "opt out" of predicate locking and conflict checking for a + * read-only transaction. + * + * The reason this is safe is that a read-only transaction can only become + * part of a dangerous structure if it overlaps a writable transaction + * which in turn overlaps a writable transaction which committed before + * the read-only transaction started. A new writable transaction can + * overlap this one, but it can't meet the other condition of overlapping + * a transaction which committed before this one started. + */ + if (XactReadOnly && PredXact->WritableSxactCount == 0) + { + ReleasePredXact(sxact); + LWLockRelease(SerializableXactHashLock); + return snapshot; + } + + /* Maintain serializable global xmin info. */ + if (!TransactionIdIsValid(PredXact->SxactGlobalXmin)) + { + Assert(PredXact->SxactGlobalXminCount == 0); + PredXact->SxactGlobalXmin = snapshot->xmin; + PredXact->SxactGlobalXminCount = 1; + OldSerXidSetActiveSerXmin(snapshot->xmin); + } + else if (TransactionIdEquals(snapshot->xmin, PredXact->SxactGlobalXmin)) + { + Assert(PredXact->SxactGlobalXminCount > 0); + PredXact->SxactGlobalXminCount++; + } + else + { + Assert(TransactionIdFollows(snapshot->xmin, PredXact->SxactGlobalXmin)); + } + + /* Initialize the structure. */ + sxact->vxid = vxid; + sxact->SeqNo.lastCommitBeforeSnapshot = PredXact->LastSxactCommitSeqNo; + sxact->commitSeqNo = InvalidSerCommitSeqNo; + SHMQueueInit(&(sxact->outConflicts)); + SHMQueueInit(&(sxact->inConflicts)); + SHMQueueInit(&(sxact->possibleUnsafeConflicts)); + sxact->topXid = GetTopTransactionIdIfAny(); + sxact->finishedBefore = InvalidTransactionId; + sxact->xmin = snapshot->xmin; + sxact->pid = MyProcPid; + SHMQueueInit(&(sxact->predicateLocks)); + SHMQueueElemInit(&(sxact->finishedLink)); + sxact->flags = 0; + if (XactReadOnly) + { + sxact->flags |= SXACT_FLAG_READ_ONLY; + + /* + * Register all concurrent r/w transactions as possible conflicts; if + * all of them commit without any outgoing conflicts to earlier + * transactions then this snapshot can be deemed safe (and we can run + * without tracking predicate locks). + */ + for (othersxact = FirstPredXact(); + othersxact != NULL; + othersxact = NextPredXact(othersxact)) + { + if (!SxactIsOnFinishedList(othersxact) && + !SxactIsReadOnly(othersxact)) + { + SetPossibleUnsafeConflict(sxact, othersxact); + } + } + } + else + { + ++(PredXact->WritableSxactCount); + Assert(PredXact->WritableSxactCount <= + (MaxBackends + max_prepared_xacts)); + } + + MySerializableXact = sxact; + + LWLockRelease(SerializableXactHashLock); + + /* Initialize the backend-local hash table of parent locks */ + Assert(LocalPredicateLockHash == NULL); + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(PREDICATELOCKTARGETTAG); + hash_ctl.entrysize = sizeof(LOCALPREDICATELOCK); + hash_ctl.hash = tag_hash; + LocalPredicateLockHash = hash_create("Local predicate lock", + max_predicate_locks_per_xact, + &hash_ctl, + HASH_ELEM | HASH_FUNCTION); + + return snapshot; +} + +/* + * Register the top level XID in SerializableXidHash. + * Also store it for easy reference in MySerializableXact. + */ +void +RegisterPredicateLockingXid(const TransactionId xid) +{ + SERIALIZABLEXIDTAG sxidtag; + SERIALIZABLEXID *sxid; + bool found; + + /* + * If we're not tracking predicate lock data for this transaction, we + * should ignore the request and return quickly. + */ + if (MySerializableXact == InvalidSerializableXact) + return; + + /* This should only be done once per transaction. */ + Assert(MySerializableXact->topXid == InvalidTransactionId); + + /* We should have a valid XID and be at the top level. */ + Assert(TransactionIdIsValid(xid)); + + MySerializableXact->topXid = xid; + + sxidtag.xid = xid; + LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE); + sxid = (SERIALIZABLEXID *) hash_search(SerializableXidHash, + &sxidtag, + HASH_ENTER, &found); + if (!sxid) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of shared memory"), + errhint("You might need to increase max_predicate_locks_per_transaction."))); + + Assert(!found); + + /* Initialize the structure. */ + sxid->myXact = (SERIALIZABLEXACT *) MySerializableXact; + LWLockRelease(SerializableXactHashLock); +} + + +/* + * Check whether there are any predicate locks held by any transaction + * for the page at the given block number. + * + * Note that the transaction may be completed but not yet subject to + * cleanup due to overlapping serializable transactions. This must + * return valid information regardless of transaction isolation level. + * + * Also note that this doesn't check for a conflicting relation lock, + * just a lock specifically on the given page. + * + * One use is to support proper behavior during GiST index vacuum. + */ +bool +PageIsPredicateLocked(const Relation relation, const BlockNumber blkno) +{ + PREDICATELOCKTARGETTAG targettag; + uint32 targettaghash; + LWLockId partitionLock; + PREDICATELOCKTARGET *target; + + SET_PREDICATELOCKTARGETTAG_PAGE(targettag, + relation->rd_node.dbNode, + relation->rd_id, + blkno); + + targettaghash = PredicateLockTargetTagHashCode(&targettag); + partitionLock = PredicateLockHashPartitionLock(targettaghash); + LWLockAcquire(partitionLock, LW_SHARED); + target = (PREDICATELOCKTARGET *) + hash_search_with_hash_value(PredicateLockTargetHash, + &targettag, targettaghash, + HASH_FIND, NULL); + LWLockRelease(partitionLock); + + return (target != NULL); +} + + +/* + * Check whether a particular lock is held by this transaction. + * + * Important note: this function may return false even if the lock is + * being held, because it uses the local lock table which is not + * updated if another transaction modifies our lock list (e.g. to + * split an index page). However, it will never return true if the + * lock is not held. We only use this function in circumstances where + * such false negatives are acceptable. + */ +static bool +PredicateLockExists(const PREDICATELOCKTARGETTAG *targettag) +{ + LOCALPREDICATELOCK *lock; + + /* check local hash table */ + lock = (LOCALPREDICATELOCK *) hash_search(LocalPredicateLockHash, + targettag, + HASH_FIND, NULL); + + if (!lock) + return false; + + /* + * Found entry in the table, but still need to check whether it's actually + * held -- it could just be a parent of some held lock. + */ + return lock->held; +} + +/* + * Return the parent lock tag in the lock hierarchy: the next coarser + * lock that covers the provided tag. + * + * Returns true and sets *parent to the parent tag if one exists, + * returns false if none exists. + */ +static bool +GetParentPredicateLockTag(const PREDICATELOCKTARGETTAG *tag, + PREDICATELOCKTARGETTAG *parent) +{ + switch (GET_PREDICATELOCKTARGETTAG_TYPE(*tag)) + { + case PREDLOCKTAG_RELATION: + /* relation locks have no parent lock */ + return false; + + case PREDLOCKTAG_PAGE: + /* parent lock is relation lock */ + SET_PREDICATELOCKTARGETTAG_RELATION(*parent, + GET_PREDICATELOCKTARGETTAG_DB(*tag), + GET_PREDICATELOCKTARGETTAG_RELATION(*tag)); + + return true; + + case PREDLOCKTAG_TUPLE: + /* parent lock is page lock */ + SET_PREDICATELOCKTARGETTAG_PAGE(*parent, + GET_PREDICATELOCKTARGETTAG_DB(*tag), + GET_PREDICATELOCKTARGETTAG_RELATION(*tag), + GET_PREDICATELOCKTARGETTAG_PAGE(*tag)); + return true; + } + + /* not reachable */ + Assert(false); + return false; +} + +/* + * Check whether the lock we are considering is already covered by a + * coarser lock for our transaction. + */ +static bool +CoarserLockCovers(const PREDICATELOCKTARGETTAG *newtargettag) +{ + PREDICATELOCKTARGETTAG targettag, + parenttag; + + targettag = *newtargettag; + + /* check parents iteratively until no more */ + while (GetParentPredicateLockTag(&targettag, &parenttag)) + { + targettag = parenttag; + if (PredicateLockExists(&targettag)) + return true; + } + + /* no more parents to check; lock is not covered */ + return false; +} + +/* + * Check whether both the list of related predicate locks and the pointer to + * a prior version of the row (if this is a tuple lock target) are empty for + * a predicate lock target, and remove the target if they are. + */ +static void +RemoveTargetIfNoLongerUsed(PREDICATELOCKTARGET *target, uint32 targettaghash) +{ + PREDICATELOCKTARGET *rmtarget; + PREDICATELOCKTARGET *next; + + Assert(LWLockHeldByMe(SerializablePredicateLockListLock)); + + /* Can't remove it until no locks at this target. */ + if (!SHMQueueEmpty(&target->predicateLocks)) + return; + + /* Can't remove it if there are locks for a prior row version. */ + LWLockAcquire(PredicateLockNextRowLinkLock, LW_EXCLUSIVE); + if (target->priorVersionOfRow != NULL) + { + LWLockRelease(PredicateLockNextRowLinkLock); + return; + } + + /* + * We are going to release this target, This requires that we let the + * next version of the row (if any) know that it's previous version is + * done. + * + * It might be that the link was all that was keeping the other target + * from cleanup, but we can't clean that up here -- LW locking is all + * wrong for that. We'll pass the HTAB in the general cleanup function to + * get rid of such "dead" targets. + */ + next = target->nextVersionOfRow; + if (next != NULL) + { + next->priorVersionOfRow = NULL; + if (SHMQueueEmpty(&next->predicateLocks)) + PredXact->NeedTargetLinkCleanup = true; + } + LWLockRelease(PredicateLockNextRowLinkLock); + + /* Actually remove the target. */ + rmtarget = hash_search_with_hash_value(PredicateLockTargetHash, + &target->tag, + targettaghash, + HASH_REMOVE, NULL); + Assert(rmtarget == target); +} + +/* + * Delete child target locks owned by this process. + * This implementation is assuming that the usage of each target tag field + * is uniform. No need to make this hard if we don't have to. + * + * We aren't acquiring lightweight locks for the predicate lock or lock + * target structures associated with this transaction unless we're going + * to modify them, because no other process is permitted to modify our + * locks. + */ +static void +DeleteChildTargetLocks(const PREDICATELOCKTARGETTAG *newtargettag) +{ + SERIALIZABLEXACT *sxact; + PREDICATELOCK *predlock; + + LWLockAcquire(SerializablePredicateLockListLock, LW_SHARED); + sxact = (SERIALIZABLEXACT *) MySerializableXact; + predlock = (PREDICATELOCK *) + SHMQueueNext(&(sxact->predicateLocks), + &(sxact->predicateLocks), + offsetof(PREDICATELOCK, xactLink)); + while (predlock) + { + SHM_QUEUE *predlocksxactlink; + PREDICATELOCK *nextpredlock; + PREDICATELOCKTAG oldlocktag; + PREDICATELOCKTARGET *oldtarget; + PREDICATELOCKTARGETTAG oldtargettag; + + predlocksxactlink = &(predlock->xactLink); + nextpredlock = (PREDICATELOCK *) + SHMQueueNext(&(sxact->predicateLocks), + predlocksxactlink, + offsetof(PREDICATELOCK, xactLink)); + + oldlocktag = predlock->tag; + Assert(oldlocktag.myXact == sxact); + oldtarget = oldlocktag.myTarget; + oldtargettag = oldtarget->tag; + + if (TargetTagIsCoveredBy(oldtargettag, *newtargettag)) + { + uint32 oldtargettaghash; + LWLockId partitionLock; + PREDICATELOCK *rmpredlock; + + oldtargettaghash = PredicateLockTargetTagHashCode(&oldtargettag); + partitionLock = PredicateLockHashPartitionLock(oldtargettaghash); + + LWLockAcquire(partitionLock, LW_EXCLUSIVE); + + SHMQueueDelete(predlocksxactlink); + SHMQueueDelete(&(predlock->targetLink)); + rmpredlock = hash_search_with_hash_value + (PredicateLockHash, + &oldlocktag, + PredicateLockHashCodeFromTargetHashCode(&oldlocktag, + oldtargettaghash), + HASH_REMOVE, NULL); + Assert(rmpredlock == predlock); + + RemoveTargetIfNoLongerUsed(oldtarget, oldtargettaghash); + + LWLockRelease(partitionLock); + + DecrementParentLocks(&oldtargettag); + } + + predlock = nextpredlock; + } + LWLockRelease(SerializablePredicateLockListLock); +} + +/* + * Returns the promotion threshold for a given predicate lock + * target. This is the number of descendant locks required to promote + * to the specified tag. Note that the threshold includes non-direct + * descendants, e.g. both tuples and pages for a relation lock. + * + * TODO SSI: We should do something more intelligent about what the + * thresholds are, either making it proportional to the number of + * tuples in a page & pages in a relation, or at least making it a + * GUC. Currently the threshold is 3 for a page lock, and + * max_predicate_locks_per_transaction/2 for a relation lock, chosen + * entirely arbitrarily (and without benchmarking). + */ +static int +PredicateLockPromotionThreshold(const PREDICATELOCKTARGETTAG *tag) +{ + switch (GET_PREDICATELOCKTARGETTAG_TYPE(*tag)) + { + case PREDLOCKTAG_RELATION: + return max_predicate_locks_per_xact / 2; + + case PREDLOCKTAG_PAGE: + return 3; + + case PREDLOCKTAG_TUPLE: + + /* + * not reachable: nothing is finer-granularity than a tuple, so we + * should never try to promote to it. + */ + Assert(false); + return 0; + } + + /* not reachable */ + Assert(false); + return 0; +} + +/* + * For all ancestors of a newly-acquired predicate lock, increment + * their child count in the parent hash table. If any of them have + * more descendants than their promotion threshold, acquire the + * coarsest such lock. + * + * Returns true if a parent lock was acquired and false otherwise. + */ +static bool +CheckAndPromotePredicateLockRequest(const PREDICATELOCKTARGETTAG *reqtag) +{ + PREDICATELOCKTARGETTAG targettag, + nexttag, + promotiontag; + LOCALPREDICATELOCK *parentlock; + bool found, + promote; + + promote = false; + + targettag = *reqtag; + + /* check parents iteratively */ + while (GetParentPredicateLockTag(&targettag, &nexttag)) + { + targettag = nexttag; + parentlock = (LOCALPREDICATELOCK *) hash_search(LocalPredicateLockHash, + &targettag, + HASH_ENTER, + &found); + if (!found) + { + parentlock->held = false; + parentlock->childLocks = 1; + } + else + parentlock->childLocks++; + + if (parentlock->childLocks >= + PredicateLockPromotionThreshold(&targettag)) + { + /* + * We should promote to this parent lock. Continue to check its + * ancestors, however, both to get their child counts right and to + * check whether we should just go ahead and promote to one of + * them. + */ + promotiontag = targettag; + promote = true; + } + } + + if (promote) + { + /* acquire coarsest ancestor eligible for promotion */ + PredicateLockAcquire(&promotiontag); + return true; + } + else + return false; +} + +/* + * When releasing a lock, decrement the child count on all ancestor + * locks. + * + * This is called only when releasing a lock via + * DeleteChildTargetLocks (i.e. when a lock becomes redundant because + * we've acquired its parent, possibly due to promotion) or when a new + * MVCC write lock makes the predicate lock unnecessary. There's no + * point in calling it when locks are released at transaction end, as + * this information is no longer needed. + */ +static void +DecrementParentLocks(const PREDICATELOCKTARGETTAG *targettag) +{ + PREDICATELOCKTARGETTAG parenttag, + nexttag; + + parenttag = *targettag; + + while (GetParentPredicateLockTag(&parenttag, &nexttag)) + { + uint32 targettaghash; + LOCALPREDICATELOCK *parentlock, + *rmlock; + + parenttag = nexttag; + targettaghash = PredicateLockTargetTagHashCode(&parenttag); + parentlock = (LOCALPREDICATELOCK *) + hash_search_with_hash_value(LocalPredicateLockHash, + &parenttag, targettaghash, + HASH_FIND, NULL); + + /* + * There's a small chance the parent lock doesn't exist in the lock + * table. This can happen if we prematurely removed it because an + * index split caused the child refcount to be off. + */ + if (parentlock == NULL) + continue; + + parentlock->childLocks--; + + /* + * Under similar circumstances the parent lock's refcount might be + * zero. This only happens if we're holding that lock (otherwise we + * would have removed the entry). + */ + if (parentlock->childLocks < 0) + { + Assert(parentlock->held); + parentlock->childLocks = 0; + } + + if ((parentlock->childLocks == 0) && (!parentlock->held)) + { + rmlock = (LOCALPREDICATELOCK *) + hash_search_with_hash_value(LocalPredicateLockHash, + &parenttag, targettaghash, + HASH_REMOVE, NULL); + Assert(rmlock == parentlock); + } + } +} + +/* + * Indicate that a predicate lock on the given target is held by the + * specified transaction. Has no effect if the lock is already held. + * + * This updates the lock table and the sxact's lock list, and creates + * the lock target if necessary, but does *not* do anything related to + * granularity promotion or the local lock table. See + * PredicateLockAcquire for that. + */ +static void +CreatePredicateLock(const PREDICATELOCKTARGETTAG *targettag, + uint32 targettaghash, + SERIALIZABLEXACT *sxact) +{ + PREDICATELOCKTARGET *target; + PREDICATELOCKTAG locktag; + PREDICATELOCK *lock; + LWLockId partitionLock; + bool found; + + partitionLock = PredicateLockHashPartitionLock(targettaghash); + + LWLockAcquire(SerializablePredicateLockListLock, LW_SHARED); + LWLockAcquire(partitionLock, LW_EXCLUSIVE); + + /* Make sure that the target is represented. */ + target = (PREDICATELOCKTARGET *) + hash_search_with_hash_value(PredicateLockTargetHash, + targettag, targettaghash, + HASH_ENTER, &found); + if (!target) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of shared memory"), + errhint("You might need to increase max_predicate_locks_per_transaction."))); + if (!found) + { + SHMQueueInit(&(target->predicateLocks)); + target->priorVersionOfRow = NULL; + target->nextVersionOfRow = NULL; + } + + /* We've got the sxact and target, make sure they're joined. */ + locktag.myTarget = target; + locktag.myXact = sxact; + lock = (PREDICATELOCK *) + hash_search_with_hash_value(PredicateLockHash, &locktag, + PredicateLockHashCodeFromTargetHashCode(&locktag, targettaghash), + HASH_ENTER, &found); + if (!lock) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of shared memory"), + errhint("You might need to increase max_predicate_locks_per_transaction."))); + + if (!found) + { + SHMQueueInsertBefore(&(target->predicateLocks), &(lock->targetLink)); + SHMQueueInsertBefore(&(sxact->predicateLocks), + &(lock->xactLink)); + lock->commitSeqNo = 0; + } + + LWLockRelease(partitionLock); + LWLockRelease(SerializablePredicateLockListLock); +} + +/* + * Acquire a predicate lock on the specified target for the current + * connection if not already held. This updates the local lock table + * and uses it to implement granularity promotion. It will consolidate + * multiple locks into a coarser lock if warranted, and will release + * any finer-grained locks covered by the new one. + */ +static void +PredicateLockAcquire(const PREDICATELOCKTARGETTAG *targettag) +{ + uint32 targettaghash; + bool found; + LOCALPREDICATELOCK *locallock; + + /* Do we have the lock already, or a covering lock? */ + if (PredicateLockExists(targettag)) + return; + + if (CoarserLockCovers(targettag)) + return; + + /* the same hash and LW lock apply to the lock target and the local lock. */ + targettaghash = PredicateLockTargetTagHashCode(targettag); + + /* Acquire lock in local table */ + locallock = (LOCALPREDICATELOCK *) + hash_search_with_hash_value(LocalPredicateLockHash, + targettag, targettaghash, + HASH_ENTER, &found); + /* We should not hold the lock (but its entry might still exist) */ + Assert(!found || !locallock->held); + locallock->held = true; + if (!found) + locallock->childLocks = 0; + + /* Actually create the lock */ + CreatePredicateLock(targettag, targettaghash, + (SERIALIZABLEXACT *) MySerializableXact); + + /* + * Lock has been acquired. Check whether it should be promoted to a + * coarser granularity, or whether there are finer-granularity locks to + * clean up. + */ + if (CheckAndPromotePredicateLockRequest(targettag)) + { + /* + * Lock request was promoted to a coarser-granularity lock, and that + * lock was acquired. It will delete this lock and any of its + * children, so we're done. + */ + } + else + { + /* Clean up any finer-granularity locks */ + if (GET_PREDICATELOCKTARGETTAG_TYPE(*targettag) != PREDLOCKTAG_TUPLE) + DeleteChildTargetLocks(targettag); + } +} + + +/* + * PredicateLockRelation + * + * Gets a predicate lock at the relation level. + * Skip if not in full serializable transaction isolation level. + * Skip if this is a temporary table. + * Clear any finer-grained predicate locks this session has on the relation. + */ +void +PredicateLockRelation(const Relation relation) +{ + PREDICATELOCKTARGETTAG tag; + + if (SkipSerialization(relation)) + return; + + SET_PREDICATELOCKTARGETTAG_RELATION(tag, + relation->rd_node.dbNode, + relation->rd_id); + PredicateLockAcquire(&tag); +} + +/* + * PredicateLockPage + * + * Gets a predicate lock at the page level. + * Skip if not in full serializable transaction isolation level. + * Skip if this is a temporary table. + * Skip if a coarser predicate lock already covers this page. + * Clear any finer-grained predicate locks this session has on the relation. + */ +void +PredicateLockPage(const Relation relation, const BlockNumber blkno) +{ + PREDICATELOCKTARGETTAG tag; + + if (SkipSerialization(relation)) + return; + + SET_PREDICATELOCKTARGETTAG_PAGE(tag, + relation->rd_node.dbNode, + relation->rd_id, + blkno); + PredicateLockAcquire(&tag); +} + +/* + * PredicateLockTuple + * + * Gets a predicate lock at the tuple level. + * Skip if not in full serializable transaction isolation level. + * Skip if this is a temporary table. + */ +void +PredicateLockTuple(const Relation relation, const HeapTuple tuple) +{ + PREDICATELOCKTARGETTAG tag; + ItemPointer tid; + + if (SkipSerialization(relation)) + return; + + /* + * If it's a heap tuple, return if this xact wrote it. + */ + if (relation->rd_index == NULL) + { + TransactionId myxid = GetTopTransactionIdIfAny(); + + if (TransactionIdIsValid(myxid)) + { + TransactionId xid = HeapTupleHeaderGetXmin(tuple->t_data); + + if (TransactionIdFollowsOrEquals(xid, TransactionXmin)) + { + xid = SubTransGetTopmostTransaction(xid); + if (TransactionIdEquals(xid, myxid)) + { + /* We wrote it; we already have a write lock. */ + return; + } + } + } + } + + /* + * Do quick-but-not-definitive test for a relation lock first. This will + * never cause a return when the relation is *not* locked, but will + * occasionally let the check continue when there really *is* a relation + * level lock. + */ + SET_PREDICATELOCKTARGETTAG_RELATION(tag, + relation->rd_node.dbNode, + relation->rd_id); + if (PredicateLockExists(&tag)) + return; + + tid = &(tuple->t_self); + SET_PREDICATELOCKTARGETTAG_TUPLE(tag, + relation->rd_node.dbNode, + relation->rd_id, + ItemPointerGetBlockNumber(tid), + ItemPointerGetOffsetNumber(tid)); + PredicateLockAcquire(&tag); +} + +/* + * If the old tuple has any predicate locks, create a lock target for the + * new tuple and point them at each other. Conflict detection needs to + * look for locks against prior versions of the row. + */ +void +PredicateLockTupleRowVersionLink(const Relation relation, + const HeapTuple oldTuple, + const HeapTuple newTuple) +{ + PREDICATELOCKTARGETTAG oldtargettag; + PREDICATELOCKTARGETTAG newtargettag; + PREDICATELOCKTARGET *oldtarget; + PREDICATELOCKTARGET *newtarget; + PREDICATELOCKTARGET *next; + uint32 oldtargettaghash; + LWLockId oldpartitionLock; + uint32 newtargettaghash; + LWLockId newpartitionLock; + bool found; + + SET_PREDICATELOCKTARGETTAG_TUPLE(oldtargettag, + relation->rd_node.dbNode, + relation->rd_id, + ItemPointerGetBlockNumber(&(oldTuple->t_self)), + ItemPointerGetOffsetNumber(&(oldTuple->t_self))); + oldtargettaghash = PredicateLockTargetTagHashCode(&oldtargettag); + oldpartitionLock = PredicateLockHashPartitionLock(oldtargettaghash); + + SET_PREDICATELOCKTARGETTAG_TUPLE(newtargettag, + relation->rd_node.dbNode, + relation->rd_id, + ItemPointerGetBlockNumber(&(newTuple->t_self)), + ItemPointerGetOffsetNumber(&(newTuple->t_self))); + newtargettaghash = PredicateLockTargetTagHashCode(&newtargettag); + newpartitionLock = PredicateLockHashPartitionLock(newtargettaghash); + + /* Lock lower numbered partition first. */ + if (oldpartitionLock < newpartitionLock) + { + LWLockAcquire(oldpartitionLock, LW_SHARED); + LWLockAcquire(newpartitionLock, LW_EXCLUSIVE); + } + else if (newpartitionLock < oldpartitionLock) + { + LWLockAcquire(newpartitionLock, LW_EXCLUSIVE); + LWLockAcquire(oldpartitionLock, LW_SHARED); + } + else + LWLockAcquire(newpartitionLock, LW_EXCLUSIVE); + + oldtarget = (PREDICATELOCKTARGET *) + hash_search_with_hash_value(PredicateLockTargetHash, + &oldtargettag, oldtargettaghash, + HASH_FIND, NULL); + + /* Only need to link if there is an old target already. */ + if (oldtarget) + { + LWLockAcquire(PredicateLockNextRowLinkLock, LW_EXCLUSIVE); + + /* Guard against stale pointers from rollback. */ + next = oldtarget->nextVersionOfRow; + if (next != NULL) + { + next->priorVersionOfRow = NULL; + oldtarget->nextVersionOfRow = NULL; + } + + /* Find or create the new target, and link old and new. */ + newtarget = (PREDICATELOCKTARGET *) + hash_search_with_hash_value(PredicateLockTargetHash, + &newtargettag, newtargettaghash, + HASH_ENTER, &found); + if (!newtarget) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of shared memory"), + errhint("You might need to increase max_predicate_locks_per_transaction."))); + if (!found) + { + SHMQueueInit(&(newtarget->predicateLocks)); + newtarget->nextVersionOfRow = NULL; + } + else + Assert(newtarget->priorVersionOfRow == NULL); + + newtarget->priorVersionOfRow = oldtarget; + oldtarget->nextVersionOfRow = newtarget; + + LWLockRelease(PredicateLockNextRowLinkLock); + } + + /* Release lower number partition last. */ + if (oldpartitionLock < newpartitionLock) + { + LWLockRelease(newpartitionLock); + LWLockRelease(oldpartitionLock); + } + else if (newpartitionLock < oldpartitionLock) + { + LWLockRelease(oldpartitionLock); + LWLockRelease(newpartitionLock); + } + else + LWLockRelease(newpartitionLock); +} + + +/* + * DeleteLockTarget + * + * Remove a predicate lock target along with any locks held for it. + * + * Caller must hold SerializablePredicateLockListLock and the + * appropriate hash partition lock for the target. + */ +static void +DeleteLockTarget(PREDICATELOCKTARGET *target, uint32 targettaghash) +{ + PREDICATELOCK *predlock; + SHM_QUEUE *predlocktargetlink; + PREDICATELOCK *nextpredlock; + bool found; + + Assert(LWLockHeldByMe(SerializablePredicateLockListLock)); + Assert(LWLockHeldByMe(PredicateLockHashPartitionLock(targettaghash))); + + predlock = (PREDICATELOCK *) + SHMQueueNext(&(target->predicateLocks), + &(target->predicateLocks), + offsetof(PREDICATELOCK, targetLink)); + LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE); + while (predlock) + { + predlocktargetlink = &(predlock->targetLink); + nextpredlock = (PREDICATELOCK *) + SHMQueueNext(&(target->predicateLocks), + predlocktargetlink, + offsetof(PREDICATELOCK, targetLink)); + + SHMQueueDelete(&(predlock->xactLink)); + SHMQueueDelete(&(predlock->targetLink)); + + hash_search_with_hash_value + (PredicateLockHash, + &predlock->tag, + PredicateLockHashCodeFromTargetHashCode(&predlock->tag, + targettaghash), + HASH_REMOVE, &found); + Assert(found); + + predlock = nextpredlock; + } + LWLockRelease(SerializableXactHashLock); + + /* Remove the target itself, if possible. */ + RemoveTargetIfNoLongerUsed(target, targettaghash); +} + + +/* + * TransferPredicateLocksToNewTarget + * + * Move or copy all the predicate locks for a lock target, for use by + * index page splits/combines and other things that create or replace + * lock targets. If 'removeOld' is true, the old locks and the target + * will be removed. + * + * Returns true on success, or false if we ran out of shared memory to + * allocate the new target or locks. Guaranteed to always succeed if + * removeOld is set (by using the reserved entry in + * PredicateLockTargetHash for scratch space). + * + * Caller must hold SerializablePredicateLockListLock. + */ +static bool +TransferPredicateLocksToNewTarget(const PREDICATELOCKTARGETTAG oldtargettag, + const PREDICATELOCKTARGETTAG newtargettag, + bool removeOld) +{ + uint32 oldtargettaghash; + LWLockId oldpartitionLock; + PREDICATELOCKTARGET *oldtarget; + uint32 newtargettaghash; + LWLockId newpartitionLock; + bool found; + bool outOfShmem = false; + uint32 reservedtargettaghash; + LWLockId reservedpartitionLock; + + + Assert(LWLockHeldByMe(SerializablePredicateLockListLock)); + + oldtargettaghash = PredicateLockTargetTagHashCode(&oldtargettag); + newtargettaghash = PredicateLockTargetTagHashCode(&newtargettag); + oldpartitionLock = PredicateLockHashPartitionLock(oldtargettaghash); + newpartitionLock = PredicateLockHashPartitionLock(newtargettaghash); + + reservedtargettaghash = 0; /* Quiet compiler warnings. */ + reservedpartitionLock = 0; /* Quiet compiler warnings. */ + + if (removeOld) + { + /* + * Remove the reserved entry to give us scratch space, so we know + * we'll be able to create the new lock target. + */ + reservedtargettaghash = PredicateLockTargetTagHashCode(&ReservedTargetTag); + reservedpartitionLock = PredicateLockHashPartitionLock(reservedtargettaghash); + LWLockAcquire(reservedpartitionLock, LW_EXCLUSIVE); + hash_search_with_hash_value(PredicateLockTargetHash, + &ReservedTargetTag, + reservedtargettaghash, + HASH_REMOVE, &found); + Assert(found); + LWLockRelease(reservedpartitionLock); + } + + /* + * We must get the partition locks in ascending sequence to avoid + * deadlocks. If old and new partitions are the same, we must request the + * lock only once. + */ + if (oldpartitionLock < newpartitionLock) + { + LWLockAcquire(oldpartitionLock, + (removeOld ? LW_EXCLUSIVE : LW_SHARED)); + LWLockAcquire(newpartitionLock, LW_EXCLUSIVE); + } + else if (oldpartitionLock > newpartitionLock) + { + LWLockAcquire(newpartitionLock, LW_EXCLUSIVE); + LWLockAcquire(oldpartitionLock, + (removeOld ? LW_EXCLUSIVE : LW_SHARED)); + } + else + LWLockAcquire(newpartitionLock, LW_EXCLUSIVE); + + /* + * Look for the old target. If not found, that's OK; no predicate locks + * are affected, so we can just clean up and return. If it does exist, + * walk its list of predicate locks and move or copy them to the new + * target. + */ + oldtarget = hash_search_with_hash_value(PredicateLockTargetHash, + &oldtargettag, + oldtargettaghash, + HASH_FIND, NULL); + + if (oldtarget) + { + PREDICATELOCKTARGET *newtarget; + PREDICATELOCK *oldpredlock; + PREDICATELOCKTAG newpredlocktag; + + newtarget = hash_search_with_hash_value(PredicateLockTargetHash, + &newtargettag, + newtargettaghash, + HASH_ENTER_NULL, &found); + + if (!newtarget) + { + /* Failed to allocate due to insufficient shmem */ + outOfShmem = true; + goto exit; + } + + /* If we created a new entry, initialize it */ + if (!found) + { + SHMQueueInit(&(newtarget->predicateLocks)); + newpredlocktag.myTarget = newtarget; + } + + oldpredlock = (PREDICATELOCK *) + SHMQueueNext(&(oldtarget->predicateLocks), + &(oldtarget->predicateLocks), + offsetof(PREDICATELOCK, targetLink)); + LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE); + while (oldpredlock) + { + SHM_QUEUE *predlocktargetlink; + PREDICATELOCK *nextpredlock; + PREDICATELOCK *newpredlock; + + predlocktargetlink = &(oldpredlock->targetLink); + nextpredlock = (PREDICATELOCK *) + SHMQueueNext(&(oldtarget->predicateLocks), + predlocktargetlink, + offsetof(PREDICATELOCK, targetLink)); + newpredlocktag.myXact = oldpredlock->tag.myXact; + + if (removeOld) + { + SHMQueueDelete(&(oldpredlock->xactLink)); + SHMQueueDelete(&(oldpredlock->targetLink)); + + hash_search_with_hash_value + (PredicateLockHash, + &oldpredlock->tag, + PredicateLockHashCodeFromTargetHashCode(&oldpredlock->tag, + oldtargettaghash), + HASH_REMOVE, &found); + Assert(found); + } + + + newpredlock = (PREDICATELOCK *) + hash_search_with_hash_value + (PredicateLockHash, + &newpredlocktag, + PredicateLockHashCodeFromTargetHashCode(&newpredlocktag, + newtargettaghash), + HASH_ENTER_NULL, &found); + if (!newpredlock) + { + /* Out of shared memory. Undo what we've done so far. */ + LWLockRelease(SerializableXactHashLock); + DeleteLockTarget(newtarget, newtargettaghash); + outOfShmem = true; + goto exit; + } + SHMQueueInsertBefore(&(newtarget->predicateLocks), + &(newpredlock->targetLink)); + SHMQueueInsertBefore(&(newpredlocktag.myXact->predicateLocks), + &(newpredlock->xactLink)); + + oldpredlock = nextpredlock; + } + LWLockRelease(SerializableXactHashLock); + + if (removeOld) + { + Assert(SHMQueueEmpty(&oldtarget->predicateLocks)); + RemoveTargetIfNoLongerUsed(oldtarget, oldtargettaghash); + } + } + + +exit: + /* Release partition locks in reverse order of acquisition. */ + if (oldpartitionLock < newpartitionLock) + { + LWLockRelease(newpartitionLock); + LWLockRelease(oldpartitionLock); + } + else if (oldpartitionLock > newpartitionLock) + { + LWLockRelease(oldpartitionLock); + LWLockRelease(newpartitionLock); + } + else + LWLockRelease(newpartitionLock); + + if (removeOld) + { + /* We shouldn't run out of memory if we're moving locks */ + Assert(!outOfShmem); + + /* Put the reserved entry back */ + LWLockAcquire(reservedpartitionLock, LW_EXCLUSIVE); + hash_search_with_hash_value(PredicateLockTargetHash, + &ReservedTargetTag, + reservedtargettaghash, + HASH_ENTER, &found); + Assert(!found); + LWLockRelease(reservedpartitionLock); + } + + return !outOfShmem; +} + + +/* + * PredicateLockPageSplit + * + * Copies any predicate locks for the old page to the new page. + * Skip if this is a temporary table or toast table. + * + * NOTE: A page split (or overflow) affects all serializable transactions, + * even if it occurs in the context of another transaction isolation level. + * + * NOTE: This currently leaves the local copy of the locks without + * information on the new lock which is in shared memory. This could cause + * problems if enough page splits occur on locked pages without the processes + * which hold the locks getting in and noticing. + */ +void +PredicateLockPageSplit(const Relation relation, const BlockNumber oldblkno, + const BlockNumber newblkno) +{ + PREDICATELOCKTARGETTAG oldtargettag; + PREDICATELOCKTARGETTAG newtargettag; + bool success; + + if (SkipSplitTracking(relation)) + return; + + Assert(oldblkno != newblkno); + Assert(BlockNumberIsValid(oldblkno)); + Assert(BlockNumberIsValid(newblkno)); + + SET_PREDICATELOCKTARGETTAG_PAGE(oldtargettag, + relation->rd_node.dbNode, + relation->rd_id, + oldblkno); + SET_PREDICATELOCKTARGETTAG_PAGE(newtargettag, + relation->rd_node.dbNode, + relation->rd_id, + newblkno); + + LWLockAcquire(SerializablePredicateLockListLock, LW_EXCLUSIVE); + + /* + * Try copying the locks over to the new page's tag, creating it if + * necessary. + */ + success = TransferPredicateLocksToNewTarget(oldtargettag, + newtargettag, + false); + + if (!success) + { + /* + * No more predicate lock entries are available. Failure isn't an + * option here, so promote the page lock to a relation lock. + */ + + /* Get the parent relation lock's lock tag */ + success = GetParentPredicateLockTag(&oldtargettag, + &newtargettag); + Assert(success); + + /* Move the locks to the parent. This shouldn't fail. */ + success = TransferPredicateLocksToNewTarget(oldtargettag, + newtargettag, + true); + Assert(success); + } + + LWLockRelease(SerializablePredicateLockListLock); +} + +/* + * PredicateLockPageCombine + * + * Combines predicate locks for two existing pages. + * Skip if this is a temporary table or toast table. + * + * NOTE: A page combine affects all serializable transactions, even if it + * occurs in the context of another transaction isolation level. + */ +void +PredicateLockPageCombine(const Relation relation, const BlockNumber oldblkno, + const BlockNumber newblkno) +{ + PREDICATELOCKTARGETTAG oldtargettag; + PREDICATELOCKTARGETTAG newtargettag; + bool success; + + + if (SkipSplitTracking(relation)) + return; + + Assert(oldblkno != newblkno); + Assert(BlockNumberIsValid(oldblkno)); + Assert(BlockNumberIsValid(newblkno)); + + SET_PREDICATELOCKTARGETTAG_PAGE(oldtargettag, + relation->rd_node.dbNode, + relation->rd_id, + oldblkno); + SET_PREDICATELOCKTARGETTAG_PAGE(newtargettag, + relation->rd_node.dbNode, + relation->rd_id, + newblkno); + + LWLockAcquire(SerializablePredicateLockListLock, LW_EXCLUSIVE); + + /* Move the locks. This shouldn't fail. */ + success = TransferPredicateLocksToNewTarget(oldtargettag, + newtargettag, + true); + Assert(success); + + LWLockRelease(SerializablePredicateLockListLock); +} + +/* + * Walk the hash table and find the new xmin. + */ +static void +SetNewSxactGlobalXmin(void) +{ + SERIALIZABLEXACT *sxact; + + Assert(LWLockHeldByMe(SerializableXactHashLock)); + + PredXact->SxactGlobalXmin = InvalidTransactionId; + PredXact->SxactGlobalXminCount = 0; + + for (sxact = FirstPredXact(); sxact != NULL; sxact = NextPredXact(sxact)) + { + if (!SxactIsRolledBack(sxact) + && !SxactIsCommitted(sxact) + && sxact != OldCommittedSxact) + { + Assert(sxact->xmin != InvalidTransactionId); + if (!TransactionIdIsValid(PredXact->SxactGlobalXmin) + || TransactionIdPrecedes(sxact->xmin, + PredXact->SxactGlobalXmin)) + { + PredXact->SxactGlobalXmin = sxact->xmin; + PredXact->SxactGlobalXminCount = 1; + } + else if (TransactionIdEquals(sxact->xmin, + PredXact->SxactGlobalXmin)) + PredXact->SxactGlobalXminCount++; + } + } + + OldSerXidSetActiveSerXmin(PredXact->SxactGlobalXmin); +} + +/* + * ReleasePredicateLocks + * + * Releases predicate locks based on completion of the current transaction, + * whether committed or rolled back. It can also be called for a read only + * transaction when it becomes impossible for the transaction to become + * part of a dangerous structure. + * + * We do nothing unless this is a serializable transaction. + * + * This method must ensure that shared memory hash tables are cleaned + * up in some relatively timely fashion. + * + * If this transaction is committing and is holding any predicate locks, + * it must be added to a list of completed serializable transaction still + * holding locks. + */ +void +ReleasePredicateLocks(const bool isCommit) +{ + bool needToClear; + RWConflict conflict, + nextConflict, + possibleUnsafeConflict; + SERIALIZABLEXACT *roXact; + + /* + * We can't trust XactReadOnly here, because a transaction which started + * as READ WRITE can show as READ ONLY later, e.g., within + * substransactions. We want to flag a transaction as READ ONLY if it + * commits without writing so that de facto READ ONLY transactions get the + * benefit of some RO optimizations, so we will use this local variable to + * get some cleanup logic right which is based on whether the transaction + * was declared READ ONLY at the top level. + */ + bool topLevelIsDeclaredReadOnly; + + if (MySerializableXact == InvalidSerializableXact) + { + Assert(LocalPredicateLockHash == NULL); + return; + } + + Assert(!isCommit || SxactIsPrepared(MySerializableXact)); + Assert(!SxactIsRolledBack(MySerializableXact)); + Assert(!SxactIsCommitted(MySerializableXact)); + + /* may not be serializable during COMMIT/ROLLBACK PREPARED */ + if (MySerializableXact->pid != 0) + Assert(IsolationIsSerializable()); + + /* We'd better not already be on the cleanup list. */ + Assert(!SxactIsOnFinishedList((SERIALIZABLEXACT *) MySerializableXact)); + + topLevelIsDeclaredReadOnly = SxactIsReadOnly(MySerializableXact); + + LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE); + + /* + * We don't hold a lock here, assuming that TransactionId is atomic! + * + * If this value is changing, we don't care that much whether we get the + * old or new value -- it is just used to determine how far + * GlobalSerizableXmin must advance before this transaction can be cleaned + * fully cleaned up. The worst that could happen is we wait for ome more + * transaction to complete before freeing some RAM; correctness of visible + * behavior is not affected. + */ + MySerializableXact->finishedBefore = ShmemVariableCache->nextXid; + + /* + * If it's not a commit it's a rollback, and we can clear our locks + * immediately. + */ + if (isCommit) + { + MySerializableXact->flags |= SXACT_FLAG_COMMITTED; + MySerializableXact->commitSeqNo = ++(PredXact->LastSxactCommitSeqNo); + /* Recognize implicit read-only transaction (commit without write). */ + if (!(MySerializableXact->flags & SXACT_FLAG_DID_WRITE)) + MySerializableXact->flags |= SXACT_FLAG_READ_ONLY; + } + else + { + MySerializableXact->flags |= SXACT_FLAG_ROLLED_BACK; + } + + if (!topLevelIsDeclaredReadOnly) + { + Assert(PredXact->WritableSxactCount > 0); + if (--(PredXact->WritableSxactCount) == 0) + { + /* + * Release predicate locks and rw-conflicts in for all committed + * transactions. There are no longer any transactions which might + * conflict with the locks and no chance for new transactions to + * overlap. Similarly, existing conflicts in can't cause pivots, + * and any conflicts in which could have completed a dangerous + * structure would already have caused a rollback, so any + * remaining ones must be benign. + */ + PredXact->CanPartialClearThrough = PredXact->LastSxactCommitSeqNo; + } + } + else + { + /* + * Read-only transactions: clear the list of transactions that might + * make us unsafe. Note that we use 'inLink' for the iteration as + * opposed to 'outLink' for the r/w xacts. + */ + possibleUnsafeConflict = (RWConflict) + SHMQueueNext((SHM_QUEUE *) &MySerializableXact->possibleUnsafeConflicts, + (SHM_QUEUE *) &MySerializableXact->possibleUnsafeConflicts, + offsetof(RWConflictData, inLink)); + while (possibleUnsafeConflict) + { + nextConflict = (RWConflict) + SHMQueueNext((SHM_QUEUE *) &MySerializableXact->possibleUnsafeConflicts, + &possibleUnsafeConflict->inLink, + offsetof(RWConflictData, inLink)); + + Assert(!SxactIsReadOnly(possibleUnsafeConflict->sxactOut)); + Assert(MySerializableXact == possibleUnsafeConflict->sxactIn); + + ReleaseRWConflict(possibleUnsafeConflict); + + possibleUnsafeConflict = nextConflict; + } + } + + /* Check for conflict out to old committed transactions. */ + if (isCommit + && !SxactIsReadOnly(MySerializableXact) + && SxactHasSummaryConflictOut(MySerializableXact)) + { + MySerializableXact->SeqNo.earliestOutConflictCommit = + FirstNormalSerCommitSeqNo; + MySerializableXact->flags |= SXACT_FLAG_CONFLICT_OUT; + } + + /* + * Release all outConflicts to committed transactions. If we're rolling + * back clear them all. Set SXACT_FLAG_CONFLICT_OUT if any point to + * previously committed transactions. + */ + conflict = (RWConflict) + SHMQueueNext((SHM_QUEUE *) &MySerializableXact->outConflicts, + (SHM_QUEUE *) &MySerializableXact->outConflicts, + offsetof(RWConflictData, outLink)); + while (conflict) + { + nextConflict = (RWConflict) + SHMQueueNext((SHM_QUEUE *) &MySerializableXact->outConflicts, + &conflict->outLink, + offsetof(RWConflictData, outLink)); + + if (isCommit + && !SxactIsReadOnly(MySerializableXact) + && SxactIsCommitted(conflict->sxactIn)) + { + if ((MySerializableXact->flags & SXACT_FLAG_CONFLICT_OUT) == 0 + || conflict->sxactIn->commitSeqNo < MySerializableXact->SeqNo.earliestOutConflictCommit) + MySerializableXact->SeqNo.earliestOutConflictCommit = conflict->sxactIn->commitSeqNo; + MySerializableXact->flags |= SXACT_FLAG_CONFLICT_OUT; + } + + if (!isCommit + || SxactIsCommitted(conflict->sxactIn) + || (conflict->sxactIn->SeqNo.lastCommitBeforeSnapshot >= PredXact->LastSxactCommitSeqNo)) + ReleaseRWConflict(conflict); + + conflict = nextConflict; + } + + /* + * Release all inConflicts from committed and read-only transactions. If + * we're rolling back, clear them all. + */ + conflict = (RWConflict) + SHMQueueNext((SHM_QUEUE *) &MySerializableXact->inConflicts, + (SHM_QUEUE *) &MySerializableXact->inConflicts, + offsetof(RWConflictData, inLink)); + while (conflict) + { + nextConflict = (RWConflict) + SHMQueueNext((SHM_QUEUE *) &MySerializableXact->inConflicts, + &conflict->inLink, + offsetof(RWConflictData, inLink)); + + if (!isCommit + || SxactIsCommitted(conflict->sxactOut) + || SxactIsReadOnly(conflict->sxactOut)) + ReleaseRWConflict(conflict); + + conflict = nextConflict; + } + + if (!topLevelIsDeclaredReadOnly) + { + /* + * Remove ourselves from the list of possible conflicts for concurrent + * READ ONLY transactions, flagging them as unsafe if we have a + * conflict out. If any are waiting DEFERRABLE transactions, wake them + * up if they are known safe or known unsafe. + */ + possibleUnsafeConflict = (RWConflict) + SHMQueueNext((SHM_QUEUE *) &MySerializableXact->possibleUnsafeConflicts, + (SHM_QUEUE *) &MySerializableXact->possibleUnsafeConflicts, + offsetof(RWConflictData, outLink)); + while (possibleUnsafeConflict) + { + nextConflict = (RWConflict) + SHMQueueNext((SHM_QUEUE *) &MySerializableXact->possibleUnsafeConflicts, + &possibleUnsafeConflict->outLink, + offsetof(RWConflictData, outLink)); + + roXact = possibleUnsafeConflict->sxactIn; + Assert(MySerializableXact == possibleUnsafeConflict->sxactOut); + Assert(SxactIsReadOnly(roXact)); + + /* Mark conflicted if necessary. */ + if (isCommit + && (MySerializableXact->flags & SXACT_FLAG_DID_WRITE) + && SxactHasConflictOut(MySerializableXact) + && (MySerializableXact->SeqNo.earliestOutConflictCommit + <= roXact->SeqNo.lastCommitBeforeSnapshot)) + { + /* + * This releases possibleUnsafeConflict (as well as all other + * possible conflicts for roXact) + */ + FlagSxactUnsafe(roXact); + } + else + { + ReleaseRWConflict(possibleUnsafeConflict); + + /* + * If we were the last possible conflict, flag it safe. The + * transaction can now safely release its predicate locks (but + * that transaction's backend has to do that itself). + */ + if (SHMQueueEmpty(&roXact->possibleUnsafeConflicts)) + roXact->flags |= SXACT_FLAG_RO_SAFE; + } + + /* + * Wake up the process for a waiting DEFERRABLE transaction if we + * now know it's either safe or conflicted. + */ + if (SxactIsDeferrableWaiting(roXact) && + (SxactIsROUnsafe(roXact) || SxactIsROSafe(roXact))) + ProcSendSignal(roXact->pid); + + possibleUnsafeConflict = nextConflict; + } + } + + /* + * Check whether it's time to clean up old transactions. This can only be + * done when the last serializable transaction with the oldest xmin among + * serializable transactions completes. We then find the "new oldest" + * xmin and purge any transactions which finished before this transaction + * was launched. + */ + needToClear = false; + if (TransactionIdEquals(MySerializableXact->xmin, PredXact->SxactGlobalXmin)) + { + Assert(PredXact->SxactGlobalXminCount > 0); + if (--(PredXact->SxactGlobalXminCount) == 0) + { + SetNewSxactGlobalXmin(); + needToClear = true; + } + } + + LWLockRelease(SerializableXactHashLock); + + LWLockAcquire(SerializableFinishedListLock, LW_EXCLUSIVE); + + /* Add this to the list of transactions to check for later cleanup. */ + if (isCommit) + SHMQueueInsertBefore(FinishedSerializableTransactions, + (SHM_QUEUE *) &(MySerializableXact->finishedLink)); + + if (!isCommit) + ReleaseOneSerializableXact((SERIALIZABLEXACT *) MySerializableXact, + false, false); + + LWLockRelease(SerializableFinishedListLock); + + if (needToClear) + ClearOldPredicateLocks(); + + MySerializableXact = InvalidSerializableXact; + + /* Delete per-transaction lock table */ + if (LocalPredicateLockHash != NULL) + { + hash_destroy(LocalPredicateLockHash); + LocalPredicateLockHash = NULL; + } +} + +/* + * ReleasePredicateLocksIfROSafe + * Check if the current transaction is read only and operating on + * a safe snapshot. If so, release predicate locks and return + * true. + * + * A transaction is flagged as RO_SAFE if all concurrent R/W + * transactions commit without having conflicts out to an earlier + * snapshot, thus ensuring that no conflicts are possible for this + * transaction. Thus, we call this function as part of the + * SkipSerialization check on all public interface methods. + */ +static bool +ReleasePredicateLocksIfROSafe(void) +{ + if (SxactIsROSafe(MySerializableXact)) + { + ReleasePredicateLocks(false); + return true; + } + else + return false; +} + +/* + * Clear old predicate locks. + */ +static void +ClearOldPredicateLocks(void) +{ + SERIALIZABLEXACT *finishedSxact; + PREDICATELOCK *predlock; + int i; + HASH_SEQ_STATUS seqstat; + PREDICATELOCKTARGET *locktarget; + + LWLockAcquire(SerializableFinishedListLock, LW_EXCLUSIVE); + finishedSxact = (SERIALIZABLEXACT *) + SHMQueueNext(FinishedSerializableTransactions, + FinishedSerializableTransactions, + offsetof(SERIALIZABLEXACT, finishedLink)); + LWLockAcquire(SerializableXactHashLock, LW_SHARED); + while (finishedSxact) + { + SERIALIZABLEXACT *nextSxact; + + nextSxact = (SERIALIZABLEXACT *) + SHMQueueNext(FinishedSerializableTransactions, + &(finishedSxact->finishedLink), + offsetof(SERIALIZABLEXACT, finishedLink)); + if (!TransactionIdIsValid(PredXact->SxactGlobalXmin) + || TransactionIdPrecedesOrEquals(finishedSxact->finishedBefore, + PredXact->SxactGlobalXmin)) + { + LWLockRelease(SerializableXactHashLock); + SHMQueueDelete(&(finishedSxact->finishedLink)); + ReleaseOneSerializableXact(finishedSxact, false, false); + LWLockAcquire(SerializableXactHashLock, LW_SHARED); + } + else if (finishedSxact->commitSeqNo > PredXact->HavePartialClearedThrough + && finishedSxact->commitSeqNo <= PredXact->CanPartialClearThrough) + { + LWLockRelease(SerializableXactHashLock); + ReleaseOneSerializableXact(finishedSxact, + !SxactIsReadOnly(finishedSxact), + false); + PredXact->HavePartialClearedThrough = finishedSxact->commitSeqNo; + LWLockAcquire(SerializableXactHashLock, LW_SHARED); + } + else + break; + finishedSxact = nextSxact; + } + LWLockRelease(SerializableXactHashLock); + + /* + * Loop through predicate locks on dummy transaction for summarized data. + */ + predlock = (PREDICATELOCK *) + SHMQueueNext(&OldCommittedSxact->predicateLocks, + &OldCommittedSxact->predicateLocks, + offsetof(PREDICATELOCK, xactLink)); + LWLockAcquire(SerializablePredicateLockListLock, LW_SHARED); + while (predlock) + { + PREDICATELOCK *nextpredlock; + bool canDoPartialCleanup; + + nextpredlock = (PREDICATELOCK *) + SHMQueueNext(&OldCommittedSxact->predicateLocks, + &predlock->xactLink, + offsetof(PREDICATELOCK, xactLink)); + + LWLockAcquire(SerializableXactHashLock, LW_SHARED); + canDoPartialCleanup = (predlock->commitSeqNo <= PredXact->CanPartialClearThrough); + LWLockRelease(SerializableXactHashLock); + + if (canDoPartialCleanup) + { + PREDICATELOCKTAG tag; + SHM_QUEUE *targetLink; + PREDICATELOCKTARGET *target; + PREDICATELOCKTARGETTAG targettag; + uint32 targettaghash; + LWLockId partitionLock; + + tag = predlock->tag; + targetLink = &(predlock->targetLink); + target = tag.myTarget; + targettag = target->tag; + targettaghash = PredicateLockTargetTagHashCode(&targettag); + partitionLock = PredicateLockHashPartitionLock(targettaghash); + + LWLockAcquire(partitionLock, LW_EXCLUSIVE); + + SHMQueueDelete(targetLink); + SHMQueueDelete(&(predlock->xactLink)); + + hash_search_with_hash_value(PredicateLockHash, &tag, + PredicateLockHashCodeFromTargetHashCode(&tag, + targettaghash), + HASH_REMOVE, NULL); + RemoveTargetIfNoLongerUsed(target, targettaghash); + + LWLockRelease(partitionLock); + } + + predlock = nextpredlock; + } + + LWLockRelease(SerializablePredicateLockListLock); + LWLockRelease(SerializableFinishedListLock); + + if (!PredXact->NeedTargetLinkCleanup) + return; + + /* + * Clean up any targets which were disconnected from a prior version with + * no predicate locks attached. + */ + for (i = 0; i < NUM_PREDICATELOCK_PARTITIONS; i++) + LWLockAcquire(FirstPredicateLockMgrLock + i, LW_EXCLUSIVE); + LWLockAcquire(PredicateLockNextRowLinkLock, LW_SHARED); + + hash_seq_init(&seqstat, PredicateLockTargetHash); + while ((locktarget = (PREDICATELOCKTARGET *) hash_seq_search(&seqstat))) + { + if (SHMQueueEmpty(&locktarget->predicateLocks) + && locktarget->priorVersionOfRow == NULL + && locktarget->nextVersionOfRow == NULL) + { + hash_search(PredicateLockTargetHash, &locktarget->tag, + HASH_REMOVE, NULL); + } + } + + PredXact->NeedTargetLinkCleanup = false; + + LWLockRelease(PredicateLockNextRowLinkLock); + for (i = NUM_PREDICATELOCK_PARTITIONS - 1; i >= 0; i--) + LWLockRelease(FirstPredicateLockMgrLock + i); +} + +/* + * This is the normal way to delete anything from any of the predicate + * locking hash tables. Given a transaction which we know can be deleted: + * delete all predicate locks held by that transaction and any predicate + * lock targets which are now unreferenced by a lock; delete all conflicts + * for the transaction; delete all xid values for the transaction; then + * delete the transaction. + * + * When the partial flag is set, we can release all predicate locks and + * out-conflict information -- we've established that there are no longer + * any overlapping read write transactions for which this transaction could + * matter. + * + * When the summarize flag is set, we've run short of room for sxact data + * and must summarize to the SLRU. Predicate locks are transferred to a + * dummy "old" transaction, with duplicate locks on a single target + * collapsing to a single lock with the "latest" commitSeqNo from among + * the conflicting locks.. + */ +static void +ReleaseOneSerializableXact(SERIALIZABLEXACT *sxact, bool partial, + bool summarize) +{ + PREDICATELOCK *predlock; + SERIALIZABLEXIDTAG sxidtag; + RWConflict conflict, + nextConflict; + + Assert(sxact != NULL); + Assert(SxactIsRolledBack(sxact) || SxactIsCommitted(sxact)); + Assert(LWLockHeldByMe(SerializableFinishedListLock)); + + LWLockAcquire(SerializablePredicateLockListLock, LW_SHARED); + predlock = (PREDICATELOCK *) + SHMQueueNext(&(sxact->predicateLocks), + &(sxact->predicateLocks), + offsetof(PREDICATELOCK, xactLink)); + while (predlock) + { + PREDICATELOCK *nextpredlock; + PREDICATELOCKTAG tag; + SHM_QUEUE *targetLink; + PREDICATELOCKTARGET *target; + PREDICATELOCKTARGETTAG targettag; + uint32 targettaghash; + LWLockId partitionLock; + + nextpredlock = (PREDICATELOCK *) + SHMQueueNext(&(sxact->predicateLocks), + &(predlock->xactLink), + offsetof(PREDICATELOCK, xactLink)); + + tag = predlock->tag; + targetLink = &(predlock->targetLink); + target = tag.myTarget; + targettag = target->tag; + targettaghash = PredicateLockTargetTagHashCode(&targettag); + partitionLock = PredicateLockHashPartitionLock(targettaghash); + + LWLockAcquire(partitionLock, LW_EXCLUSIVE); + + SHMQueueDelete(targetLink); + + hash_search_with_hash_value(PredicateLockHash, &tag, + PredicateLockHashCodeFromTargetHashCode(&tag, + targettaghash), + HASH_REMOVE, NULL); + if (summarize) + { + bool found; + + /* Fold into dummy transaction list. */ + tag.myXact = OldCommittedSxact; + predlock = hash_search_with_hash_value(PredicateLockHash, &tag, + PredicateLockHashCodeFromTargetHashCode(&tag, + targettaghash), + HASH_ENTER, &found); + if (!predlock) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of shared memory"), + errhint("You might need to increase max_predicate_locks_per_transaction."))); + if (found) + { + if (predlock->commitSeqNo < sxact->commitSeqNo) + predlock->commitSeqNo = sxact->commitSeqNo; + } + else + { + SHMQueueInsertBefore(&(target->predicateLocks), + &(predlock->targetLink)); + SHMQueueInsertBefore(&(OldCommittedSxact->predicateLocks), + &(predlock->xactLink)); + predlock->commitSeqNo = sxact->commitSeqNo; + } + } + else + RemoveTargetIfNoLongerUsed(target, targettaghash); + + LWLockRelease(partitionLock); + + predlock = nextpredlock; + } + + /* + * Rather than retail removal, just re-init the head after we've run + * through the list. + */ + SHMQueueInit(&sxact->predicateLocks); + + LWLockRelease(SerializablePredicateLockListLock); + + sxidtag.xid = sxact->topXid; + LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE); + + if (!partial) + { + /* Release all outConflicts. */ + conflict = (RWConflict) + SHMQueueNext(&sxact->outConflicts, + &sxact->outConflicts, + offsetof(RWConflictData, outLink)); + while (conflict) + { + nextConflict = (RWConflict) + SHMQueueNext(&sxact->outConflicts, + &conflict->outLink, + offsetof(RWConflictData, outLink)); + if (summarize) + conflict->sxactIn->flags |= SXACT_FLAG_SUMMARY_CONFLICT_IN; + ReleaseRWConflict(conflict); + conflict = nextConflict; + } + } + + /* Release all inConflicts. */ + conflict = (RWConflict) + SHMQueueNext(&sxact->inConflicts, + &sxact->inConflicts, + offsetof(RWConflictData, inLink)); + while (conflict) + { + nextConflict = (RWConflict) + SHMQueueNext(&sxact->inConflicts, + &conflict->inLink, + offsetof(RWConflictData, inLink)); + if (summarize) + conflict->sxactOut->flags |= SXACT_FLAG_SUMMARY_CONFLICT_OUT; + ReleaseRWConflict(conflict); + conflict = nextConflict; + } + + if (!partial) + { + /* Get rid of the xid and the record of the transaction itself. */ + if (sxidtag.xid != InvalidTransactionId) + hash_search(SerializableXidHash, &sxidtag, HASH_REMOVE, NULL); + ReleasePredXact(sxact); + } + + LWLockRelease(SerializableXactHashLock); +} + +/* + * Tests whether the given top level transaction is concurrent with + * (overlaps) our current transaction. + * + * We need to identify the top level transaction for SSI, anyway, so pass + * that to this function to save the overhead of checking the snapshot's + * subxip array. + */ +static bool +XidIsConcurrent(TransactionId xid) +{ + Snapshot snap; + uint32 i; + + Assert(TransactionIdIsValid(xid)); + Assert(!TransactionIdEquals(xid, GetTopTransactionIdIfAny())); + + snap = GetTransactionSnapshot(); + + if (TransactionIdPrecedes(xid, snap->xmin)) + return false; + + if (TransactionIdFollowsOrEquals(xid, snap->xmax)) + return true; + + for (i = 0; i < snap->xcnt; i++) + { + if (xid == snap->xip[i]) + return true; + } + + return false; +} + +/* + * CheckForSerializableConflictOut + * We are reading a tuple which has been modified. If it is visible to + * us but has been deleted, that indicates a rw-conflict out. If it's + * not visible and was created by a concurrent (overlapping) + * serializable transaction, that is also a rw-conflict out, + * + * We will determine the top level xid of the writing transaction with which + * we may be in conflict, and check for overlap with our own transaction. + * If the transactions overlap (i.e., they cannot see each other's writes), + * then we have a conflict out. + * + * This function should be called just about anywhere in heapam.c that a + * tuple has been read. There is currently no known reason to call this + * function from an index AM. + */ +void +CheckForSerializableConflictOut(const bool visible, const Relation relation, + const HeapTuple tuple, const Buffer buffer) +{ + TransactionId xid; + SERIALIZABLEXIDTAG sxidtag; + SERIALIZABLEXID *sxid; + SERIALIZABLEXACT *sxact; + HTSV_Result htsvResult; + + if (SkipSerialization(relation)) + return; + + if (SxactIsMarkedForDeath(MySerializableXact)) + { + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to read/write dependencies among transactions"), + errdetail("Cancelled on identification as a pivot, during conflict out checking."), + errhint("The transaction might succeed if retried."))); + } + + /* + * Check to see whether the tuple has been written to by a concurrent + * transaction, either to create it not visible to us, or to delete it + * while it is visible to us. The "visible" bool indicates whether the + * tuple is visible to us, while HeapTupleSatisfiesVacuum checks what else + * is going on with it. + */ + htsvResult = HeapTupleSatisfiesVacuum(tuple->t_data, TransactionXmin, buffer); + switch (htsvResult) + { + case HEAPTUPLE_LIVE: + if (visible) + return; + xid = HeapTupleHeaderGetXmin(tuple->t_data); + break; + case HEAPTUPLE_RECENTLY_DEAD: + if (!visible) + return; + xid = HeapTupleHeaderGetXmax(tuple->t_data); + break; + case HEAPTUPLE_DELETE_IN_PROGRESS: + xid = HeapTupleHeaderGetXmax(tuple->t_data); + break; + case HEAPTUPLE_INSERT_IN_PROGRESS: + xid = HeapTupleHeaderGetXmin(tuple->t_data); + break; + case HEAPTUPLE_DEAD: + return; + default: + + /* + * The only way to get to this default clause is if a new value is + * added to the enum type without adding it to this switch + * statement. That's a bug, so elog. + */ + elog(ERROR, "unrecognized return value from HeapTupleSatisfiesVacuum: %u", htsvResult); + + /* + * In spite of having all enum values covered and calling elog on + * this default, some compilers think this is a code path which + * allows xid to be used below without initialization. Silence + * that warning. + */ + xid = InvalidTransactionId; + } + Assert(TransactionIdIsValid(xid)); + Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin)); + + /* + * Find top level xid. Bail out if xid is too early to be a conflict, or + * if it's our own xid. + */ + if (TransactionIdEquals(xid, GetTopTransactionIdIfAny())) + return; + xid = SubTransGetTopmostTransaction(xid); + if (TransactionIdPrecedes(xid, TransactionXmin)) + return; + if (TransactionIdEquals(xid, GetTopTransactionIdIfAny())) + return; + + /* + * Find sxact or summarized info for the top level xid. + */ + sxidtag.xid = xid; + LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE); + sxid = (SERIALIZABLEXID *) + hash_search(SerializableXidHash, &sxidtag, HASH_FIND, NULL); + if (!sxid) + { + /* + * Transaction not found in "normal" SSI structures. Check whether it + * got pushed out to SLRU storage for "old committed" transactions. + */ + SerCommitSeqNo conflictCommitSeqNo; + + conflictCommitSeqNo = OldSerXidGetMinConflictCommitSeqNo(xid); + if (conflictCommitSeqNo != 0) + { + if (conflictCommitSeqNo != InvalidSerCommitSeqNo + && (!SxactIsReadOnly(MySerializableXact) + || conflictCommitSeqNo + <= MySerializableXact->SeqNo.lastCommitBeforeSnapshot)) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to read/write dependencies among transactions"), + errdetail("Cancelled on conflict out to old pivot %u.", xid), + errhint("The transaction might succeed if retried."))); + + if (SxactHasSummaryConflictIn(MySerializableXact) + || !SHMQueueEmpty((SHM_QUEUE *) &MySerializableXact->inConflicts)) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to read/write dependencies among transactions"), + errdetail("Cancelled on identification as a pivot, with conflict out to old committed transaction %u.", xid), + errhint("The transaction might succeed if retried."))); + + MySerializableXact->flags |= SXACT_FLAG_SUMMARY_CONFLICT_OUT; + } + + /* It's not serializable or otherwise not important. */ + LWLockRelease(SerializableXactHashLock); + return; + } + sxact = sxid->myXact; + Assert(TransactionIdEquals(sxact->topXid, xid)); + if (sxact == MySerializableXact + || SxactIsRolledBack(sxact) + || SxactIsMarkedForDeath(sxact)) + { + /* We can't conflict with our own transaction or one rolled back. */ + LWLockRelease(SerializableXactHashLock); + return; + } + + /* + * We have a conflict out to a transaction which has a conflict out to a + * summarized transaction. That summarized transaction must have + * committed first, and we can't tell when it committed in relation to our + * snapshot acquisition, so something needs to be cancelled. + */ + if (SxactHasSummaryConflictOut(sxact)) + { + if (!SxactIsPrepared(sxact)) + { + sxact->flags |= SXACT_FLAG_MARKED_FOR_DEATH; + LWLockRelease(SerializableXactHashLock); + return; + } + else + { + LWLockRelease(SerializableXactHashLock); + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to read/write dependencies among transactions"), + errdetail("Cancelled on conflict out to old pivot."), + errhint("The transaction might succeed if retried."))); + } + } + + /* + * If this is a read-only transaction and the writing transaction has + * committed, and it doesn't have a rw-conflict to a transaction which + * committed before it, no conflict. + */ + if (SxactIsReadOnly(MySerializableXact) + && SxactIsCommitted(sxact) + && !SxactHasSummaryConflictOut(sxact) + && (!SxactHasConflictOut(sxact) + || MySerializableXact->SeqNo.lastCommitBeforeSnapshot < sxact->SeqNo.earliestOutConflictCommit)) + { + /* Read-only transaction will appear to run first. No conflict. */ + LWLockRelease(SerializableXactHashLock); + return; + } + + if (!XidIsConcurrent(xid)) + { + /* This write was already in our snapshot; no conflict. */ + LWLockRelease(SerializableXactHashLock); + return; + } + + if (RWConflictExists((SERIALIZABLEXACT *) MySerializableXact, sxact)) + { + /* We don't want duplicate conflict records in the list. */ + LWLockRelease(SerializableXactHashLock); + return; + } + + /* + * Flag the conflict. But first, if this conflict creates a dangerous + * structure, ereport an error. + */ + FlagRWConflict((SERIALIZABLEXACT *) MySerializableXact, sxact); + LWLockRelease(SerializableXactHashLock); +} + +/* + * Check a particular target for rw-dependency conflict in. This will + * also check prior versions of a tuple, if any. + */ +static void +CheckTargetForConflictsIn(PREDICATELOCKTARGETTAG *targettag) +{ + PREDICATELOCKTARGETTAG nexttargettag; + PREDICATELOCKTARGETTAG thistargettag; + + for (;;) + { + if (!CheckSingleTargetForConflictsIn(targettag, &nexttargettag)) + break; + thistargettag = nexttargettag; + targettag = &thistargettag; + } +} + +/* + * Check a particular target for rw-dependency conflict in. If the tuple + * has prior versions, returns true and *nexttargettag is set to the tag + * of the prior tuple version. + */ +static bool +CheckSingleTargetForConflictsIn(PREDICATELOCKTARGETTAG *targettag, + PREDICATELOCKTARGETTAG *nexttargettag) +{ + uint32 targettaghash; + LWLockId partitionLock; + PREDICATELOCKTARGET *target; + PREDICATELOCK *predlock; + bool hasnexttarget = false; + + Assert(MySerializableXact != InvalidSerializableXact); + + /* + * The same hash and LW lock apply to the lock target and the lock itself. + */ + targettaghash = PredicateLockTargetTagHashCode(targettag); + partitionLock = PredicateLockHashPartitionLock(targettaghash); + LWLockAcquire(partitionLock, LW_SHARED); + LWLockAcquire(PredicateLockNextRowLinkLock, LW_SHARED); + target = (PREDICATELOCKTARGET *) + hash_search_with_hash_value(PredicateLockTargetHash, + targettag, targettaghash, + HASH_FIND, NULL); + if (!target) + { + /* Nothing has this target locked; we're done here. */ + LWLockRelease(PredicateLockNextRowLinkLock); + LWLockRelease(partitionLock); + return false; + } + + /* + * If the target is linked to a prior version of the row, save the tag so + * that it can be used for iterative calls to this function. + */ + if (target->priorVersionOfRow != NULL) + { + *nexttargettag = target->priorVersionOfRow->tag; + hasnexttarget = true; + } + LWLockRelease(PredicateLockNextRowLinkLock); + + /* + * Each lock for an overlapping transaction represents a conflict: a + * rw-dependency in to this transaction. + */ + predlock = (PREDICATELOCK *) + SHMQueueNext(&(target->predicateLocks), + &(target->predicateLocks), + offsetof(PREDICATELOCK, targetLink)); + LWLockAcquire(SerializableXactHashLock, LW_SHARED); + while (predlock) + { + SHM_QUEUE *predlocktargetlink; + PREDICATELOCK *nextpredlock; + SERIALIZABLEXACT *sxact; + + predlocktargetlink = &(predlock->targetLink); + nextpredlock = (PREDICATELOCK *) + SHMQueueNext(&(target->predicateLocks), + predlocktargetlink, + offsetof(PREDICATELOCK, targetLink)); + + sxact = predlock->tag.myXact; + if (sxact == MySerializableXact) + { + /* + * If we're getting a write lock on the tuple, we don't need a + * predicate (SIREAD) lock. At this point our transaction already + * has an ExclusiveRowLock on the relation, so we are OK to drop + * the predicate lock on the tuple, if found, without fearing that + * another write against the tuple will occur before the MVCC + * information makes it to the buffer. + */ + if (GET_PREDICATELOCKTARGETTAG_OFFSET(*targettag)) + { + uint32 predlockhashcode; + PREDICATELOCKTARGET *rmtarget = NULL; + PREDICATELOCK *rmpredlock; + LOCALPREDICATELOCK *locallock, + *rmlocallock; + + /* + * This is a tuple on which we have a tuple predicate lock. We + * only have shared LW locks now; release those, and get + * exclusive locks only while we modify things. + */ + LWLockRelease(SerializableXactHashLock); + LWLockRelease(partitionLock); + LWLockAcquire(SerializablePredicateLockListLock, LW_SHARED); + LWLockAcquire(partitionLock, LW_EXCLUSIVE); + LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE); + + /* + * Remove the predicate lock from shared memory, if it wasn't + * removed while the locks were released. One way that could + * happen is from autovacuum cleaning up an index. + */ + predlockhashcode = PredicateLockHashCodeFromTargetHashCode + (&(predlock->tag), targettaghash); + rmpredlock = (PREDICATELOCK *) + hash_search_with_hash_value(PredicateLockHash, + &(predlock->tag), + predlockhashcode, + HASH_FIND, NULL); + if (rmpredlock) + { + Assert(rmpredlock == predlock); + + SHMQueueDelete(predlocktargetlink); + SHMQueueDelete(&(predlock->xactLink)); + + rmpredlock = (PREDICATELOCK *) + hash_search_with_hash_value(PredicateLockHash, + &(predlock->tag), + predlockhashcode, + HASH_REMOVE, NULL); + Assert(rmpredlock == predlock); + + RemoveTargetIfNoLongerUsed(target, targettaghash); + + LWLockRelease(SerializableXactHashLock); + LWLockRelease(partitionLock); + LWLockRelease(SerializablePredicateLockListLock); + + locallock = (LOCALPREDICATELOCK *) + hash_search_with_hash_value(LocalPredicateLockHash, + targettag, targettaghash, + HASH_FIND, NULL); + Assert(locallock != NULL); + Assert(locallock->held); + locallock->held = false; + + if (locallock->childLocks == 0) + { + rmlocallock = (LOCALPREDICATELOCK *) + hash_search_with_hash_value(LocalPredicateLockHash, + targettag, targettaghash, + HASH_REMOVE, NULL); + Assert(rmlocallock == locallock); + } + + DecrementParentLocks(targettag); + + /* + * If we've cleaned up the last of the predicate locks for + * the target, bail out before re-acquiring the locks. + */ + if (rmtarget) + return hasnexttarget; + + /* + * The list has been altered. Start over at the front. + */ + LWLockAcquire(partitionLock, LW_SHARED); + nextpredlock = (PREDICATELOCK *) + SHMQueueNext(&(target->predicateLocks), + &(target->predicateLocks), + offsetof(PREDICATELOCK, targetLink)); + + LWLockAcquire(SerializableXactHashLock, LW_SHARED); + } + else + { + /* + * The predicate lock was cleared while we were attempting + * to upgrade our lightweight locks. Revert to the shared + * locks. + */ + LWLockRelease(SerializableXactHashLock); + LWLockRelease(partitionLock); + LWLockRelease(SerializablePredicateLockListLock); + LWLockAcquire(partitionLock, LW_SHARED); + LWLockAcquire(SerializableXactHashLock, LW_SHARED); + } + } + } + else if (!SxactIsRolledBack(sxact) + && (!SxactIsCommitted(sxact) + || TransactionIdPrecedes(GetTransactionSnapshot()->xmin, + sxact->finishedBefore)) + && !RWConflictExists(sxact, (SERIALIZABLEXACT *) MySerializableXact)) + { + LWLockRelease(SerializableXactHashLock); + LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE); + + FlagRWConflict(sxact, (SERIALIZABLEXACT *) MySerializableXact); + + LWLockRelease(SerializableXactHashLock); + LWLockAcquire(SerializableXactHashLock, LW_SHARED); + } + + predlock = nextpredlock; + } + LWLockRelease(SerializableXactHashLock); + LWLockRelease(partitionLock); + + return hasnexttarget; +} + +/* + * CheckForSerializableConflictIn + * We are writing the given tuple. If that indicates a rw-conflict + * in from another serializable transaction, take appropriate action. + * + * Skip checking for any granularity for which a parameter is missing. + * + * A tuple update or delete is in conflict if we have a predicate lock + * against the relation or page in which the tuple exists, or against the + * tuple itself. + */ +void +CheckForSerializableConflictIn(const Relation relation, const HeapTuple tuple, + const Buffer buffer) +{ + PREDICATELOCKTARGETTAG targettag; + + if (SkipSerialization(relation)) + return; + + if (SxactIsMarkedForDeath(MySerializableXact)) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to read/write dependencies among transactions"), + errdetail("Cancelled on identification as a pivot, during conflict in checking."), + errhint("The transaction might succeed if retried."))); + + MySerializableXact->flags |= SXACT_FLAG_DID_WRITE; + + /* + * It is important that we check for locks from the finest granularity to + * the coarsest granularity, so that granularity promotion doesn't cause + * us to miss a lock. The new (coarser) lock will be acquired before the + * old (finer) locks are released. + * + * It is not possible to take and hold a lock across the checks for all + * granularities because each target could be in a separate partition. + */ + if (tuple != NULL) + { + SET_PREDICATELOCKTARGETTAG_TUPLE(targettag, + relation->rd_node.dbNode, + relation->rd_id, + ItemPointerGetBlockNumber(&(tuple->t_data->t_ctid)), + ItemPointerGetOffsetNumber(&(tuple->t_data->t_ctid))); + CheckTargetForConflictsIn(&targettag); + } + + if (BufferIsValid(buffer)) + { + SET_PREDICATELOCKTARGETTAG_PAGE(targettag, + relation->rd_node.dbNode, + relation->rd_id, + BufferGetBlockNumber(buffer)); + CheckTargetForConflictsIn(&targettag); + } + + SET_PREDICATELOCKTARGETTAG_RELATION(targettag, + relation->rd_node.dbNode, + relation->rd_id); + CheckTargetForConflictsIn(&targettag); +} + +/* + * Flag a rw-dependency between two serializable transactions. + * + * The caller is responsible for ensuring that we have a LW lock on + * the transaction hash table. + */ +static void +FlagRWConflict(SERIALIZABLEXACT *reader, SERIALIZABLEXACT *writer) +{ + Assert(reader != writer); + + /* First, see if this conflict causes failure. */ + OnConflict_CheckForSerializationFailure(reader, writer); + + /* Actually do the conflict flagging. */ + if (reader == OldCommittedSxact) + writer->flags |= SXACT_FLAG_SUMMARY_CONFLICT_IN; + else if (writer == OldCommittedSxact) + reader->flags |= SXACT_FLAG_SUMMARY_CONFLICT_OUT; + else + SetRWConflict(reader, writer); +} + +/* + * Check whether we should roll back one of these transactions + * instead of flagging a new rw-conflict. + */ +static void +OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *reader, + SERIALIZABLEXACT *writer) +{ + bool failure; + RWConflict conflict; + + Assert(LWLockHeldByMe(SerializableXactHashLock)); + + failure = false; + + /* + * Check for already-committed writer with rw-conflict out flagged. This + * means that the reader must immediately fail. + */ + if (SxactIsCommitted(writer) + && (SxactHasConflictOut(writer) || SxactHasSummaryConflictOut(writer))) + failure = true; + + /* + * Check whether the reader has become a pivot with a committed writer. If + * so, we must roll back unless every in-conflict either committed before + * the writer committed or is READ ONLY and overlaps the writer. + */ + if (!failure && SxactIsCommitted(writer) && !SxactIsReadOnly(reader)) + { + if (SxactHasSummaryConflictIn(reader)) + { + failure = true; + conflict = NULL; + } + else + conflict = (RWConflict) + SHMQueueNext(&reader->inConflicts, + &reader->inConflicts, + offsetof(RWConflictData, inLink)); + while (conflict) + { + if (!SxactIsRolledBack(conflict->sxactOut) + && (!SxactIsCommitted(conflict->sxactOut) + || conflict->sxactOut->commitSeqNo >= writer->commitSeqNo) + && (!SxactIsReadOnly(conflict->sxactOut) + || conflict->sxactOut->SeqNo.lastCommitBeforeSnapshot >= writer->commitSeqNo)) + { + failure = true; + break; + } + conflict = (RWConflict) + SHMQueueNext(&reader->inConflicts, + &conflict->inLink, + offsetof(RWConflictData, inLink)); + } + } + + /* + * Check whether the writer has become a pivot with an out-conflict + * committed transaction, while neither reader nor writer is committed. If + * the reader is a READ ONLY transaction, there is only a serialization + * failure if an out-conflict transaction causing the pivot committed + * before the reader acquired its snapshot. (That is, the reader must not + * have been concurrent with the out-conflict transaction.) + */ + if (!failure && !SxactIsCommitted(writer)) + { + if (SxactHasSummaryConflictOut(reader)) + { + failure = true; + conflict = NULL; + } + else + conflict = (RWConflict) + SHMQueueNext(&writer->outConflicts, + &writer->outConflicts, + offsetof(RWConflictData, outLink)); + while (conflict) + { + if ((reader == conflict->sxactIn && SxactIsCommitted(reader)) + || (SxactIsCommitted(conflict->sxactIn) + && !SxactIsCommitted(reader) + && (!SxactIsReadOnly(reader) + || conflict->sxactIn->commitSeqNo <= reader->SeqNo.lastCommitBeforeSnapshot))) + { + failure = true; + break; + } + conflict = (RWConflict) + SHMQueueNext(&writer->outConflicts, + &conflict->outLink, + offsetof(RWConflictData, outLink)); + } + } + + if (failure) + { + if (MySerializableXact == writer) + { + LWLockRelease(SerializableXactHashLock); + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to read/write dependencies among transactions"), + errdetail("Cancelled on identification as pivot, during write."), + errhint("The transaction might succeed if retried."))); + } + else if (SxactIsPrepared(writer)) + { + LWLockRelease(SerializableXactHashLock); + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to read/write dependencies among transactions"), + errdetail("Cancelled on conflict out to pivot %u, during read.", writer->topXid), + errhint("The transaction might succeed if retried."))); + } + writer->flags |= SXACT_FLAG_MARKED_FOR_DEATH; + } +} + +/* + * PreCommit_CheckForSerializableConflicts + * Check for dangerous structures in a serializable transaction + * at commit. + * + * We're checking for a dangerous structure as each conflict is recorded. + * The only way we could have a problem at commit is if this is the "out" + * side of a pivot, and neither the "in" side nor the pivot has yet + * committed. + * + * If a dangerous structure is found, the pivot (the near conflict) is + * marked for death, because rolling back another transaction might mean + * that we flail without ever making progress. This transaction is + * committing writes, so letting it commit ensures progress. If we + * cancelled the far conflict, it might immediately fail again on retry. + */ +void +PreCommit_CheckForSerializationFailure(void) +{ + RWConflict nearConflict; + + if (MySerializableXact == InvalidSerializableXact) + return; + + Assert(IsolationIsSerializable()); + + LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE); + + if (SxactIsMarkedForDeath(MySerializableXact)) + { + LWLockRelease(SerializableXactHashLock); + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to read/write dependencies among transactions"), + errdetail("Cancelled on identification as a pivot, during commit attempt."), + errhint("The transaction might succeed if retried."))); + } + + nearConflict = (RWConflict) + SHMQueueNext((SHM_QUEUE *) &MySerializableXact->inConflicts, + (SHM_QUEUE *) &MySerializableXact->inConflicts, + offsetof(RWConflictData, inLink)); + while (nearConflict) + { + if (!SxactIsCommitted(nearConflict->sxactOut) + && !SxactIsRolledBack(nearConflict->sxactOut) + && !SxactIsMarkedForDeath(nearConflict->sxactOut)) + { + RWConflict farConflict; + + farConflict = (RWConflict) + SHMQueueNext(&nearConflict->sxactOut->inConflicts, + &nearConflict->sxactOut->inConflicts, + offsetof(RWConflictData, inLink)); + while (farConflict) + { + if (farConflict->sxactOut == MySerializableXact + || (!SxactIsCommitted(farConflict->sxactOut) + && !SxactIsReadOnly(farConflict->sxactOut) + && !SxactIsRolledBack(farConflict->sxactOut) + && !SxactIsMarkedForDeath(farConflict->sxactOut))) + { + nearConflict->sxactOut->flags |= SXACT_FLAG_MARKED_FOR_DEATH; + break; + } + farConflict = (RWConflict) + SHMQueueNext(&nearConflict->sxactOut->inConflicts, + &farConflict->inLink, + offsetof(RWConflictData, inLink)); + } + } + + nearConflict = (RWConflict) + SHMQueueNext((SHM_QUEUE *) &MySerializableXact->inConflicts, + &nearConflict->inLink, + offsetof(RWConflictData, inLink)); + } + + MySerializableXact->flags |= SXACT_FLAG_PREPARED; + + LWLockRelease(SerializableXactHashLock); +} + +/*------------------------------------------------------------------------*/ + +/* + * Two-phase commit support + */ + +/* + * AtPrepare_Locks + * Do the preparatory work for a PREPARE: make 2PC state file + * records for all predicate locks currently held. + */ +void +AtPrepare_PredicateLocks(void) +{ + PREDICATELOCK *predlock; + SERIALIZABLEXACT *sxact; + TwoPhasePredicateRecord record; + TwoPhasePredicateXactRecord *xactRecord; + TwoPhasePredicateLockRecord *lockRecord; + + sxact = (SERIALIZABLEXACT *) MySerializableXact; + xactRecord = &(record.data.xactRecord); + lockRecord = &(record.data.lockRecord); + + if (MySerializableXact == InvalidSerializableXact) + return; + + /* Generate a xact record for our SERIALIZABLEXACT */ + record.type = TWOPHASEPREDICATERECORD_XACT; + xactRecord->xmin = MySerializableXact->xmin; + xactRecord->flags = MySerializableXact->flags; + + /* + * Tweak the flags. Since we're not going to output the inConflicts and + * outConflicts lists, if they're non-empty we'll represent that by + * setting the appropriate summary conflict flags. + */ + if (!SHMQueueEmpty((SHM_QUEUE *) &MySerializableXact->inConflicts)) + xactRecord->flags |= SXACT_FLAG_SUMMARY_CONFLICT_IN; + if (!SHMQueueEmpty((SHM_QUEUE *) &MySerializableXact->outConflicts)) + xactRecord->flags |= SXACT_FLAG_SUMMARY_CONFLICT_OUT; + + RegisterTwoPhaseRecord(TWOPHASE_RM_PREDICATELOCK_ID, 0, + &record, sizeof(record)); + + /* + * Generate a lock record for each lock. + * + * To do this, we need to walk the predicate lock list in our sxact rather + * than using the local predicate lock table because the latter is not + * guaranteed to be accurate. + */ + LWLockAcquire(SerializablePredicateLockListLock, LW_SHARED); + + predlock = (PREDICATELOCK *) + SHMQueueNext(&(sxact->predicateLocks), + &(sxact->predicateLocks), + offsetof(PREDICATELOCK, xactLink)); + + while (predlock != NULL) + { + record.type = TWOPHASEPREDICATERECORD_LOCK; + lockRecord->target = predlock->tag.myTarget->tag; + + RegisterTwoPhaseRecord(TWOPHASE_RM_PREDICATELOCK_ID, 0, + &record, sizeof(record)); + + predlock = (PREDICATELOCK *) + SHMQueueNext(&(sxact->predicateLocks), + &(predlock->xactLink), + offsetof(PREDICATELOCK, xactLink)); + } + + LWLockRelease(SerializablePredicateLockListLock); +} + +/* + * PostPrepare_Locks + * Clean up after successful PREPARE. Unlike the non-predicate + * lock manager, we do not need to transfer locks to a dummy + * PGPROC because our SERIALIZABLEXACT will stay around + * anyway. We only need to clean up our local state. + */ +void +PostPrepare_PredicateLocks(TransactionId xid) +{ + if (MySerializableXact == InvalidSerializableXact) + return; + + Assert(SxactIsPrepared(MySerializableXact)); + + MySerializableXact->pid = 0; + + hash_destroy(LocalPredicateLockHash); + LocalPredicateLockHash = NULL; + + MySerializableXact = InvalidSerializableXact; +} + +/* + * PredicateLockTwoPhaseFinish + * Release a prepared transaction's predicate locks once it + * commits or aborts. + */ +void +PredicateLockTwoPhaseFinish(TransactionId xid, bool isCommit) +{ + SERIALIZABLEXID *sxid; + SERIALIZABLEXIDTAG sxidtag; + + sxidtag.xid = xid; + + LWLockAcquire(SerializableXactHashLock, LW_SHARED); + sxid = (SERIALIZABLEXID *) + hash_search(SerializableXidHash, &sxidtag, HASH_FIND, NULL); + LWLockRelease(SerializableXactHashLock); + + /* xid will not be found if it wasn't a serializable transaction */ + if (sxid == NULL) + return; + + /* Release its locks */ + MySerializableXact = sxid->myXact; + ReleasePredicateLocks(isCommit); +} + +/* + * Re-acquire a predicate lock belonging to a transaction that was prepared. + */ +void +predicatelock_twophase_recover(TransactionId xid, uint16 info, + void *recdata, uint32 len) +{ + TwoPhasePredicateRecord *record; + + Assert(len == sizeof(TwoPhasePredicateRecord)); + + record = (TwoPhasePredicateRecord *) recdata; + + Assert((record->type == TWOPHASEPREDICATERECORD_XACT) || + (record->type == TWOPHASEPREDICATERECORD_LOCK)); + + if (record->type == TWOPHASEPREDICATERECORD_XACT) + { + /* Per-transaction record. Set up a SERIALIZABLEXACT. */ + TwoPhasePredicateXactRecord *xactRecord; + SERIALIZABLEXACT *sxact; + SERIALIZABLEXID *sxid; + SERIALIZABLEXIDTAG sxidtag; + bool found; + + xactRecord = (TwoPhasePredicateXactRecord *) &record->data.xactRecord; + + LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE); + sxact = CreatePredXact(); + if (!sxact) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of shared memory"))); + + /* vxid for a prepared xact is InvalidBackendId/xid; no pid */ + sxact->vxid.backendId = InvalidBackendId; + sxact->vxid.localTransactionId = (LocalTransactionId) xid; + sxact->pid = 0; + + /* a prepared xact hasn't committed yet */ + sxact->commitSeqNo = InvalidSerCommitSeqNo; + sxact->finishedBefore = InvalidTransactionId; + + sxact->SeqNo.lastCommitBeforeSnapshot = RecoverySerCommitSeqNo; + + + /* + * We don't need the details of a prepared transaction's conflicts, + * just whether it had conflicts in or out (which we get from the + * flags) + */ + SHMQueueInit(&(sxact->outConflicts)); + SHMQueueInit(&(sxact->inConflicts)); + + /* + * Don't need to track this; no transactions running at the time the + * recovered xact started are still active, except possibly other + * prepared xacts and we don't care whether those are RO_SAFE or not. + */ + SHMQueueInit(&(sxact->possibleUnsafeConflicts)); + + SHMQueueInit(&(sxact->predicateLocks)); + SHMQueueElemInit(&(sxact->finishedLink)); + + sxact->topXid = xid; + sxact->xmin = xactRecord->xmin; + sxact->flags = xactRecord->flags; + Assert(SxactIsPrepared(sxact)); + if (!SxactIsReadOnly(sxact)) + { + ++(PredXact->WritableSxactCount); + Assert(PredXact->WritableSxactCount <= + (MaxBackends + max_prepared_xacts)); + } + + /* Register the transaction's xid */ + sxidtag.xid = xid; + sxid = (SERIALIZABLEXID *) hash_search(SerializableXidHash, + &sxidtag, + HASH_ENTER, &found); + if (!sxid) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of shared memory"))); + Assert(!found); + sxid->myXact = (SERIALIZABLEXACT *) sxact; + + /* + * Update global xmin. Note that this is a special case compared to + * registering a normal transaction, because the global xmin might go + * backwards. That's OK, because until recovery is over we're not + * going to complete any transactions or create any non-prepared + * transactions, so there's no danger of throwing away. + */ + if ((!TransactionIdIsValid(PredXact->SxactGlobalXmin)) || + (TransactionIdFollows(PredXact->SxactGlobalXmin, sxact->xmin))) + { + PredXact->SxactGlobalXmin = sxact->xmin; + PredXact->SxactGlobalXminCount = 1; + OldSerXidSetActiveSerXmin(sxact->xmin); + } + else if (TransactionIdEquals(sxact->xmin, PredXact->SxactGlobalXmin)) + { + Assert(PredXact->SxactGlobalXminCount > 0); + PredXact->SxactGlobalXminCount++; + } + + LWLockRelease(SerializableXactHashLock); + } + else if (record->type == TWOPHASEPREDICATERECORD_LOCK) + { + /* Lock record. Recreate the PREDICATELOCK */ + TwoPhasePredicateLockRecord *lockRecord; + SERIALIZABLEXID *sxid; + SERIALIZABLEXACT *sxact; + SERIALIZABLEXIDTAG sxidtag; + uint32 targettaghash; + + lockRecord = (TwoPhasePredicateLockRecord *) &record->data.lockRecord; + targettaghash = PredicateLockTargetTagHashCode(&lockRecord->target); + + LWLockAcquire(SerializableXactHashLock, LW_SHARED); + sxidtag.xid = xid; + sxid = (SERIALIZABLEXID *) + hash_search(SerializableXidHash, &sxidtag, HASH_FIND, NULL); + LWLockRelease(SerializableXactHashLock); + + Assert(sxid != NULL); + sxact = sxid->myXact; + Assert(sxact != InvalidSerializableXact); + + CreatePredicateLock(&lockRecord->target, targettaghash, sxact); + } +} diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 9500037770..af2eba01d6 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -374,6 +374,10 @@ standard_ProcessUtility(Node *parsetree, SetPGVariable("transaction_read_only", list_make1(item->arg), true); + else if (strcmp(item->defname, "transaction_deferrable") == 0) + SetPGVariable("transaction_deferrable", + list_make1(item->arg), + true); } } break; diff --git a/src/backend/utils/adt/lockfuncs.c b/src/backend/utils/adt/lockfuncs.c index 78cafada2c..8e369826ce 100644 --- a/src/backend/utils/adt/lockfuncs.c +++ b/src/backend/utils/adt/lockfuncs.c @@ -15,6 +15,7 @@ #include "catalog/pg_type.h" #include "funcapi.h" #include "miscadmin.h" +#include "storage/predicate_internals.h" #include "storage/proc.h" #include "utils/builtins.h" @@ -32,11 +33,20 @@ static const char *const LockTagTypeNames[] = { "advisory" }; +/* This must match enum PredicateLockTargetType (predicate_internals.h) */ +static const char *const PredicateLockTagTypeNames[] = { + "relation", + "page", + "tuple" +}; + /* Working status for pg_lock_status */ typedef struct { LockData *lockData; /* state data from lmgr */ int currIdx; /* current PROCLOCK index */ + PredicateLockData *predLockData; /* state data for pred locks */ + int predLockIdx; /* current index for pred lock */ } PG_Lock_Status; @@ -69,6 +79,7 @@ pg_lock_status(PG_FUNCTION_ARGS) FuncCallContext *funcctx; PG_Lock_Status *mystatus; LockData *lockData; + PredicateLockData *predLockData; if (SRF_IS_FIRSTCALL()) { @@ -126,6 +137,8 @@ pg_lock_status(PG_FUNCTION_ARGS) mystatus->lockData = GetLockStatusData(); mystatus->currIdx = 0; + mystatus->predLockData = GetPredicateLockStatusData(); + mystatus->predLockIdx = 0; MemoryContextSwitchTo(oldcontext); } @@ -303,6 +316,72 @@ pg_lock_status(PG_FUNCTION_ARGS) SRF_RETURN_NEXT(funcctx, result); } + /* + * Have returned all regular locks. Now start on the SIREAD predicate + * locks. + */ + predLockData = mystatus->predLockData; + if (mystatus->predLockIdx < predLockData->nelements) + { + PredicateLockTargetType lockType; + + PREDICATELOCKTARGETTAG *predTag = &(predLockData->locktags[mystatus->predLockIdx]); + SERIALIZABLEXACT *xact = &(predLockData->xacts[mystatus->predLockIdx]); + Datum values[14]; + bool nulls[14]; + HeapTuple tuple; + Datum result; + + mystatus->predLockIdx++; + + /* + * Form tuple with appropriate data. + */ + MemSet(values, 0, sizeof(values)); + MemSet(nulls, false, sizeof(nulls)); + + /* lock type */ + lockType = GET_PREDICATELOCKTARGETTAG_TYPE(*predTag); + + values[0] = CStringGetTextDatum(PredicateLockTagTypeNames[lockType]); + + /* lock target */ + values[1] = GET_PREDICATELOCKTARGETTAG_DB(*predTag); + values[2] = GET_PREDICATELOCKTARGETTAG_RELATION(*predTag); + if (lockType == PREDLOCKTAG_TUPLE) + values[4] = GET_PREDICATELOCKTARGETTAG_OFFSET(*predTag); + else + nulls[4] = true; + if ((lockType == PREDLOCKTAG_TUPLE) || + (lockType == PREDLOCKTAG_PAGE)) + values[3] = GET_PREDICATELOCKTARGETTAG_PAGE(*predTag); + else + nulls[3] = true; + + /* these fields are targets for other types of locks */ + nulls[5] = true; /* virtualxid */ + nulls[6] = true; /* transactionid */ + nulls[7] = true; /* classid */ + nulls[8] = true; /* objid */ + nulls[9] = true; /* objsubid */ + + /* lock holder */ + values[10] = VXIDGetDatum(xact->vxid.backendId, + xact->vxid.localTransactionId); + nulls[11] = true; /* pid */ + + /* + * Lock mode. Currently all predicate locks are SIReadLocks, which are + * always held (never waiting) + */ + values[12] = CStringGetTextDatum("SIReadLock"); + values[13] = BoolGetDatum(true); + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + SRF_RETURN_NEXT(funcctx, result); + } + SRF_RETURN_DONE(funcctx); } diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 2c95ef80c4..216236b529 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -59,6 +59,7 @@ #include "storage/bufmgr.h" #include "storage/standby.h" #include "storage/fd.h" +#include "storage/predicate.h" #include "tcop/tcopprot.h" #include "tsearch/ts_cache.h" #include "utils/builtins.h" @@ -1096,6 +1097,23 @@ static struct config_bool ConfigureNamesBool[] = &XactReadOnly, false, assign_transaction_read_only, NULL }, + { + {"default_transaction_deferrable", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default deferrable status of new transactions."), + NULL + }, + &DefaultXactDeferrable, + false, NULL, NULL + }, + { + {"transaction_deferrable", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Whether to defer a read-only serializable transaction until it can be executed with no possible serialization failures."), + NULL, + GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &XactDeferrable, + false, assign_transaction_deferrable, NULL + }, { {"check_function_bodies", PGC_USERSET, CLIENT_CONN_STATEMENT, gettext_noop("Check function bodies during CREATE FUNCTION."), @@ -1695,6 +1713,17 @@ static struct config_int ConfigureNamesInt[] = 64, 10, INT_MAX, NULL, NULL }, + { + {"max_predicate_locks_per_transaction", PGC_POSTMASTER, LOCK_MANAGEMENT, + gettext_noop("Sets the maximum number of predicate locks per transaction."), + gettext_noop("The shared predicate lock table is sized on the assumption that " + "at most max_predicate_locks_per_transaction * max_connections distinct " + "objects will need to be locked at any one time.") + }, + &max_predicate_locks_per_xact, + 64, 10, INT_MAX, NULL, NULL + }, + { {"authentication_timeout", PGC_SIGHUP, CONN_AUTH_SECURITY, gettext_noop("Sets the maximum allowed time to complete client authentication."), @@ -3460,6 +3489,8 @@ InitializeGUCOptions(void) PGC_POSTMASTER, PGC_S_OVERRIDE); SetConfigOption("transaction_read_only", "no", PGC_POSTMASTER, PGC_S_OVERRIDE); + SetConfigOption("transaction_deferrable", "no", + PGC_POSTMASTER, PGC_S_OVERRIDE); /* * For historical reasons, some GUC parameters can receive defaults from @@ -5699,6 +5730,9 @@ ExecSetVariableStmt(VariableSetStmt *stmt) else if (strcmp(item->defname, "transaction_read_only") == 0) SetPGVariable("transaction_read_only", list_make1(item->arg), stmt->is_local); + else if (strcmp(item->defname, "transaction_deferrable") == 0) + SetPGVariable("transaction_deferrable", + list_make1(item->arg), stmt->is_local); else elog(ERROR, "unexpected SET TRANSACTION element: %s", item->defname); @@ -5718,6 +5752,9 @@ ExecSetVariableStmt(VariableSetStmt *stmt) else if (strcmp(item->defname, "transaction_read_only") == 0) SetPGVariable("default_transaction_read_only", list_make1(item->arg), stmt->is_local); + else if (strcmp(item->defname, "transaction_deferrable") == 0) + SetPGVariable("default_transaction_deferrable", + list_make1(item->arg), stmt->is_local); else elog(ERROR, "unexpected SET SESSION element: %s", item->defname); diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 6c6f9a9a0d..fe80c4dc23 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -450,6 +450,7 @@ #check_function_bodies = on #default_transaction_isolation = 'read committed' #default_transaction_read_only = off +#default_transaction_deferrable = off #session_replication_role = 'origin' #statement_timeout = 0 # in milliseconds, 0 is disabled #vacuum_freeze_min_age = 50000000 @@ -501,7 +502,8 @@ # Note: Each lock table slot uses ~270 bytes of shared memory, and there are # max_locks_per_transaction * (max_connections + max_prepared_transactions) # lock table slots. - +#max_predicate_locks_per_transaction = 64 # min 10 + # (change requires restart) #------------------------------------------------------------------------------ # VERSION/PLATFORM COMPATIBILITY diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c index ef1cac0147..c1ba5ad8e6 100644 --- a/src/backend/utils/resowner/resowner.c +++ b/src/backend/utils/resowner/resowner.c @@ -22,6 +22,7 @@ #include "access/hash.h" #include "storage/bufmgr.h" +#include "storage/predicate.h" #include "storage/proc.h" #include "utils/memutils.h" #include "utils/rel.h" @@ -261,7 +262,10 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, * the top of the recursion. */ if (owner == TopTransactionResourceOwner) + { ProcReleaseLocks(isCommit); + ReleasePredicateLocks(isCommit); + } } else { diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c index 45b92a0ef8..c2ff5e542b 100644 --- a/src/backend/utils/time/snapmgr.c +++ b/src/backend/utils/time/snapmgr.c @@ -27,6 +27,7 @@ #include "access/transam.h" #include "access/xact.h" +#include "storage/predicate.h" #include "storage/proc.h" #include "storage/procarray.h" #include "utils/memutils.h" @@ -126,9 +127,6 @@ GetTransactionSnapshot(void) { Assert(RegisteredSnapshots == 0); - CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData); - FirstSnapshotSet = true; - /* * In transaction-snapshot mode, the first snapshot must live until * end of xact regardless of what the caller does with it, so we must @@ -136,11 +134,20 @@ GetTransactionSnapshot(void) */ if (IsolationUsesXactSnapshot()) { - CurrentSnapshot = RegisterSnapshotOnOwner(CurrentSnapshot, + if (IsolationIsSerializable()) + CurrentSnapshot = RegisterSerializableTransaction(&CurrentSnapshotData); + else + { + CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData); + CurrentSnapshot = RegisterSnapshotOnOwner(CurrentSnapshot, TopTransactionResourceOwner); + } registered_xact_snapshot = true; } + else + CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData); + FirstSnapshotSet = true; return CurrentSnapshot; } diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index 73d5a62d85..b903b7b057 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -2299,6 +2299,7 @@ main(int argc, char *argv[]) "pg_xlog/archive_status", "pg_clog", "pg_notify", + "pg_serial", "pg_subtrans", "pg_twophase", "pg_multixact/members", diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index e844b5b062..d3eb766288 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -11,14 +11,14 @@ * script that reproduces the schema in terms of SQL that is understood * by PostgreSQL * - * Note that pg_dump runs in a serializable transaction, so it sees a - * consistent snapshot of the database including system catalogs. - * However, it relies in part on various specialized backend functions - * like pg_get_indexdef(), and those things tend to run on SnapshotNow - * time, ie they look at the currently committed state. So it is - * possible to get 'cache lookup failed' error if someone performs DDL - * changes while a dump is happening. The window for this sort of thing - * is from the beginning of the serializable transaction to + * Note that pg_dump runs in a transaction-snapshot mode transaction, + * so it sees a consistent snapshot of the database including system + * catalogs. However, it relies in part on various specialized backend + * functions like pg_get_indexdef(), and those things tend to run on + * SnapshotNow time, ie they look at the currently committed state. So + * it is possible to get 'cache lookup failed' error if someone + * performs DDL changes while a dump is happening. The window for this + * sort of thing is from the acquisition of the transaction snapshot to * getSchemaData() (when pg_dump acquires AccessShareLock on every * table it intends to dump). It isn't very large, but it can happen. * @@ -135,6 +135,7 @@ static int dump_inserts = 0; static int column_inserts = 0; static int no_security_label = 0; static int no_unlogged_table_data = 0; +static int serializable_deferrable = 0; static void help(const char *progname); @@ -318,6 +319,7 @@ main(int argc, char **argv) {"no-tablespaces", no_argument, &outputNoTablespaces, 1}, {"quote-all-identifiers", no_argument, "e_all_identifiers, 1}, {"role", required_argument, NULL, 3}, + {"serializable-deferrable", no_argument, &serializable_deferrable, 1}, {"use-set-session-authorization", no_argument, &use_setsessauth, 1}, {"no-security-label", no_argument, &no_security_label, 1}, {"no-unlogged-table-data", no_argument, &no_unlogged_table_data, 1}, @@ -669,11 +671,21 @@ main(int argc, char **argv) no_security_label = 1; /* - * Start serializable transaction to dump consistent data. + * Start transaction-snapshot mode transaction to dump consistent data. */ do_sql_command(g_conn, "BEGIN"); - - do_sql_command(g_conn, "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"); + if (g_fout->remoteVersion >= 90100) + { + if (serializable_deferrable) + do_sql_command(g_conn, + "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, " + "READ ONLY, DEFERRABLE"); + else + do_sql_command(g_conn, + "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ"); + } + else + do_sql_command(g_conn, "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"); /* Select the appropriate subquery to convert user IDs to names */ if (g_fout->remoteVersion >= 80100) @@ -864,6 +876,7 @@ help(const char *progname) printf(_(" --disable-triggers disable triggers during data-only restore\n")); printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --quote-all-identifiers quote all identifiers, even if not keywords\n")); + printf(_(" --serializable-deferrable wait until the dump can run without anomalies\n")); printf(_(" --role=ROLENAME do SET ROLE before dump\n")); printf(_(" --no-security-label do not dump security label assignments\n")); printf(_(" --no-unlogged-table-data do not dump unlogged table data\n")); diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 9efab4c500..4dbc393709 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -82,8 +82,8 @@ extern HeapTuple heap_getnext(HeapScanDesc scan, ScanDirection direction); extern bool heap_fetch(Relation relation, Snapshot snapshot, HeapTuple tuple, Buffer *userbuf, bool keep_buf, Relation stats_relation); -extern bool heap_hot_search_buffer(ItemPointer tid, Buffer buffer, - Snapshot snapshot, bool *all_dead); +extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation, + Buffer buffer, Snapshot snapshot, bool *all_dead); extern bool heap_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot, bool *all_dead); diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h index 989dc57685..f703280b27 100644 --- a/src/include/access/relscan.h +++ b/src/include/access/relscan.h @@ -35,6 +35,7 @@ typedef struct HeapScanDescData BlockNumber rs_startblock; /* block # to start at */ BufferAccessStrategy rs_strategy; /* access strategy for reads */ bool rs_syncscan; /* report location to syncscan logic? */ + bool rs_relpredicatelocked; /* predicate lock on relation exists */ /* scan current state */ bool rs_inited; /* false = scan not init'd yet */ diff --git a/src/include/access/twophase_rmgr.h b/src/include/access/twophase_rmgr.h index a541d0fce7..1c7d8bb4c0 100644 --- a/src/include/access/twophase_rmgr.h +++ b/src/include/access/twophase_rmgr.h @@ -23,8 +23,9 @@ typedef uint8 TwoPhaseRmgrId; */ #define TWOPHASE_RM_END_ID 0 #define TWOPHASE_RM_LOCK_ID 1 -#define TWOPHASE_RM_PGSTAT_ID 2 -#define TWOPHASE_RM_MULTIXACT_ID 3 +#define TWOPHASE_RM_PREDICATELOCK_ID 2 +#define TWOPHASE_RM_PGSTAT_ID 3 +#define TWOPHASE_RM_MULTIXACT_ID 4 #define TWOPHASE_RM_MAX_ID TWOPHASE_RM_MULTIXACT_ID extern const TwoPhaseCallback twophase_recover_callbacks[]; diff --git a/src/include/access/xact.h b/src/include/access/xact.h index 902e99e815..1685a0167f 100644 --- a/src/include/access/xact.h +++ b/src/include/access/xact.h @@ -32,15 +32,26 @@ extern int DefaultXactIsoLevel; extern int XactIsoLevel; /* - * We only implement two isolation levels internally. This macro should - * be used to check which one is selected. + * We implement three isolation levels internally. + * The two stronger ones use one snapshot per database transaction; + * the others use one snapshot per statement. + * Serializable uses predicate locks in addition to snapshots. + * These macros should be used to check which isolation level is selected. */ #define IsolationUsesXactSnapshot() (XactIsoLevel >= XACT_REPEATABLE_READ) +#define IsolationIsSerializable() (XactIsoLevel == XACT_SERIALIZABLE) /* Xact read-only state */ extern bool DefaultXactReadOnly; extern bool XactReadOnly; +/* + * Xact is deferrable -- only meaningful (currently) for read only + * SERIALIZABLE transactions + */ +extern bool DefaultXactDeferrable; +extern bool XactDeferrable; + /* Asynchronous commits */ extern bool XactSyncCommit; diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h index 16fbdd629a..566d4fac5d 100644 --- a/src/include/catalog/pg_am.h +++ b/src/include/catalog/pg_am.h @@ -49,6 +49,7 @@ CATALOG(pg_am,2601) bool amsearchnulls; /* can AM search for NULL/NOT NULL entries? */ bool amstorage; /* can storage type differ from column type? */ bool amclusterable; /* does AM support cluster command? */ + bool ampredlocks; /* does AM handle predicate locks? */ Oid amkeytype; /* type of data in index, or InvalidOid */ regproc aminsert; /* "insert this tuple" function */ regproc ambeginscan; /* "prepare for index scan" function */ @@ -77,7 +78,7 @@ typedef FormData_pg_am *Form_pg_am; * compiler constants for pg_am * ---------------- */ -#define Natts_pg_am 27 +#define Natts_pg_am 28 #define Anum_pg_am_amname 1 #define Anum_pg_am_amstrategies 2 #define Anum_pg_am_amsupport 3 @@ -90,37 +91,38 @@ typedef FormData_pg_am *Form_pg_am; #define Anum_pg_am_amsearchnulls 10 #define Anum_pg_am_amstorage 11 #define Anum_pg_am_amclusterable 12 -#define Anum_pg_am_amkeytype 13 -#define Anum_pg_am_aminsert 14 -#define Anum_pg_am_ambeginscan 15 -#define Anum_pg_am_amgettuple 16 -#define Anum_pg_am_amgetbitmap 17 -#define Anum_pg_am_amrescan 18 -#define Anum_pg_am_amendscan 19 -#define Anum_pg_am_ammarkpos 20 -#define Anum_pg_am_amrestrpos 21 -#define Anum_pg_am_ambuild 22 -#define Anum_pg_am_ambuildempty 23 -#define Anum_pg_am_ambulkdelete 24 -#define Anum_pg_am_amvacuumcleanup 25 -#define Anum_pg_am_amcostestimate 26 -#define Anum_pg_am_amoptions 27 +#define Anum_pg_am_ampredlocks 13 +#define Anum_pg_am_amkeytype 14 +#define Anum_pg_am_aminsert 15 +#define Anum_pg_am_ambeginscan 16 +#define Anum_pg_am_amgettuple 17 +#define Anum_pg_am_amgetbitmap 18 +#define Anum_pg_am_amrescan 19 +#define Anum_pg_am_amendscan 20 +#define Anum_pg_am_ammarkpos 21 +#define Anum_pg_am_amrestrpos 22 +#define Anum_pg_am_ambuild 23 +#define Anum_pg_am_ambuildempty 24 +#define Anum_pg_am_ambulkdelete 25 +#define Anum_pg_am_amvacuumcleanup 26 +#define Anum_pg_am_amcostestimate 27 +#define Anum_pg_am_amoptions 28 /* ---------------- * initial contents of pg_am * ---------------- */ -DATA(insert OID = 403 ( btree 5 1 t f t t t t t f t 0 btinsert btbeginscan btgettuple btgetbitmap btrescan btendscan btmarkpos btrestrpos btbuild btbuildempty btbulkdelete btvacuumcleanup btcostestimate btoptions )); +DATA(insert OID = 403 ( btree 5 1 t f t t t t t f t t 0 btinsert btbeginscan btgettuple btgetbitmap btrescan btendscan btmarkpos btrestrpos btbuild btbuildempty btbulkdelete btvacuumcleanup btcostestimate btoptions )); DESCR("b-tree index access method"); #define BTREE_AM_OID 403 -DATA(insert OID = 405 ( hash 1 1 f f t f f f f f f 23 hashinsert hashbeginscan hashgettuple hashgetbitmap hashrescan hashendscan hashmarkpos hashrestrpos hashbuild hashbuildempty hashbulkdelete hashvacuumcleanup hashcostestimate hashoptions )); +DATA(insert OID = 405 ( hash 1 1 f f t f f f f f f f 23 hashinsert hashbeginscan hashgettuple hashgetbitmap hashrescan hashendscan hashmarkpos hashrestrpos hashbuild hashbuildempty hashbulkdelete hashvacuumcleanup hashcostestimate hashoptions )); DESCR("hash index access method"); #define HASH_AM_OID 405 -DATA(insert OID = 783 ( gist 0 8 f t f f t t t t t 0 gistinsert gistbeginscan gistgettuple gistgetbitmap gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbuildempty gistbulkdelete gistvacuumcleanup gistcostestimate gistoptions )); +DATA(insert OID = 783 ( gist 0 8 f t f f t t t t t f 0 gistinsert gistbeginscan gistgettuple gistgetbitmap gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbuildempty gistbulkdelete gistvacuumcleanup gistcostestimate gistoptions )); DESCR("GiST index access method"); #define GIST_AM_OID 783 -DATA(insert OID = 2742 ( gin 0 5 f f f f t t f t f 0 gininsert ginbeginscan - gingetbitmap ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbuildempty ginbulkdelete ginvacuumcleanup gincostestimate ginoptions )); +DATA(insert OID = 2742 ( gin 0 5 f f f f t t f t f f 0 gininsert ginbeginscan - gingetbitmap ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbuildempty ginbulkdelete ginvacuumcleanup gincostestimate ginoptions )); DESCR("GIN index access method"); #define GIN_AM_OID 2742 diff --git a/src/include/commands/variable.h b/src/include/commands/variable.h index 2fc144e1e1..39bccbd5bf 100644 --- a/src/include/commands/variable.h +++ b/src/include/commands/variable.h @@ -26,6 +26,8 @@ extern bool assign_transaction_read_only(bool value, extern const char *assign_XactIsoLevel(const char *value, bool doit, GucSource source); extern const char *show_XactIsoLevel(void); +extern bool assign_transaction_deferrable(bool newval, bool doit, + GucSource source); extern bool assign_random_seed(double value, bool doit, GucSource source); extern const char *show_random_seed(void); diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h index f5e267c988..99fe37ef83 100644 --- a/src/include/storage/lwlock.h +++ b/src/include/storage/lwlock.h @@ -27,6 +27,10 @@ #define LOG2_NUM_LOCK_PARTITIONS 4 #define NUM_LOCK_PARTITIONS (1 << LOG2_NUM_LOCK_PARTITIONS) +/* Number of partitions the shared predicate lock tables are divided into */ +#define LOG2_NUM_PREDICATELOCK_PARTITIONS 4 +#define NUM_PREDICATELOCK_PARTITIONS (1 << LOG2_NUM_PREDICATELOCK_PARTITIONS) + /* * We have a number of predefined LWLocks, plus a bunch of LWLocks that are * dynamically assigned (e.g., for shared buffers). The LWLock structures @@ -70,12 +74,18 @@ typedef enum LWLockId RelationMappingLock, AsyncCtlLock, AsyncQueueLock, + SerializableXactHashLock, + SerializableFinishedListLock, + SerializablePredicateLockListLock, + OldSerXidLock, + PredicateLockNextRowLinkLock, /* Individual lock IDs end here */ FirstBufMappingLock, FirstLockMgrLock = FirstBufMappingLock + NUM_BUFFER_PARTITIONS, + FirstPredicateLockMgrLock = FirstLockMgrLock + NUM_LOCK_PARTITIONS, /* must be last except for MaxDynamicLWLock: */ - NumFixedLWLocks = FirstLockMgrLock + NUM_LOCK_PARTITIONS, + NumFixedLWLocks = FirstPredicateLockMgrLock + NUM_PREDICATELOCK_PARTITIONS, MaxDynamicLWLock = 1000000000 } LWLockId; diff --git a/src/include/storage/predicate.h b/src/include/storage/predicate.h new file mode 100644 index 0000000000..163d8cb3ff --- /dev/null +++ b/src/include/storage/predicate.h @@ -0,0 +1,67 @@ +/*------------------------------------------------------------------------- + * + * predicate.h + * POSTGRES public predicate locking definitions. + * + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/storage/predicate.h + * + *------------------------------------------------------------------------- + */ +#ifndef PREDICATE_H +#define PREDICATE_H + +#include "utils/relcache.h" +#include "utils/snapshot.h" + + +/* + * GUC variables + */ +extern int max_predicate_locks_per_xact; + + +/* Number of SLRU buffers to use for predicate locking */ +#define NUM_OLDSERXID_BUFFERS 16 + + +/* + * function prototypes + */ + +/* housekeeping for shared memory predicate lock structures */ +extern void InitPredicateLocks(void); +extern Size PredicateLockShmemSize(void); + +/* predicate lock reporting */ +extern bool PageIsPredicateLocked(const Relation relation, const BlockNumber blkno); + +/* predicate lock maintenance */ +extern Snapshot RegisterSerializableTransaction(Snapshot snapshot); +extern void RegisterPredicateLockingXid(const TransactionId xid); +extern void PredicateLockRelation(const Relation relation); +extern void PredicateLockPage(const Relation relation, const BlockNumber blkno); +extern void PredicateLockTuple(const Relation relation, const HeapTuple tuple); +extern void PredicateLockTupleRowVersionLink(const Relation relation, const HeapTuple oldTuple, const HeapTuple newTuple); +extern void PredicateLockPageSplit(const Relation relation, const BlockNumber oldblkno, const BlockNumber newblkno); +extern void PredicateLockPageCombine(const Relation relation, const BlockNumber oldblkno, const BlockNumber newblkno); +extern void ReleasePredicateLocks(const bool isCommit); + +/* conflict detection (may also trigger rollback) */ +extern void CheckForSerializableConflictOut(const bool valid, const Relation relation, const HeapTuple tuple, const Buffer buffer); +extern void CheckForSerializableConflictIn(const Relation relation, const HeapTuple tuple, const Buffer buffer); + +/* final rollback checking */ +extern void PreCommit_CheckForSerializationFailure(void); + +/* two-phase commit support */ +extern void AtPrepare_PredicateLocks(void); +extern void PostPrepare_PredicateLocks(TransactionId xid); +extern void PredicateLockTwoPhaseFinish(TransactionId xid, bool isCommit); +extern void predicatelock_twophase_recover(TransactionId xid, uint16 info, + void *recdata, uint32 len); + +#endif /* PREDICATE_H */ diff --git a/src/include/storage/predicate_internals.h b/src/include/storage/predicate_internals.h new file mode 100644 index 0000000000..41aa70fdfa --- /dev/null +++ b/src/include/storage/predicate_internals.h @@ -0,0 +1,476 @@ +/*------------------------------------------------------------------------- + * + * predicate_internals.h + * POSTGRES internal predicate locking definitions. + * + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/storage/predicate_internals.h + * + *------------------------------------------------------------------------- + */ +#ifndef PREDICATE_INTERNALS_H +#define PREDICATE_INTERNALS_H + +#include "storage/lock.h" + +/* + * Commit number. + */ +typedef uint64 SerCommitSeqNo; + +/* + * Reserved commit sequence numbers: + * - 0 is reserved to indicate a non-existent SLRU entry; it cannot be + * used as a SerCommitSeqNo, even an invalid one + * - InvalidSerCommitSeqNo is used to indicate a transaction that + * hasn't committed yet, so use a number greater than all valid + * ones to make comparison do the expected thing + * - RecoverySerCommitSeqNo is used to refer to transactions that + * happened before a crash/recovery, since we restart the sequence + * at that point. It's earlier than all normal sequence numbers, + * and is only used by recovered prepared transactions + */ +#define InvalidSerCommitSeqNo UINT64_MAX +#define RecoverySerCommitSeqNo ((SerCommitSeqNo) 1) +#define FirstNormalSerCommitSeqNo ((SerCommitSeqNo) 2) + +/* + * The SERIALIZABLEXACT struct contains information needed for each + * serializable database transaction to support SSI techniques. + * + * A home-grown list is maintained in shared memory to manage these. + * An entry is used when the serializable transaction acquires a snapshot. + * Unless the transaction is rolled back, this entry must generally remain + * until all concurrent transactions have completed. (There are special + * optimizations for READ ONLY transactions which often allow them to be + * cleaned up earlier.) A transaction which is rolled back is cleaned up + * as soon as possible. + * + * Eligibility for cleanup of committed transactions is generally determined + * by comparing the transaction's finishedBefore field to + * SerializableGlobalXmin. + */ +typedef struct SERIALIZABLEXACT +{ + VirtualTransactionId vxid; /* The executing process always has one of + * these. */ + SerCommitSeqNo commitSeqNo; + union /* these values are not both interesting at + * the same time */ + { + SerCommitSeqNo earliestOutConflictCommit; /* when committed with + * conflict out */ + SerCommitSeqNo lastCommitBeforeSnapshot; /* when not committed or + * no conflict out */ + } SeqNo; + SHM_QUEUE outConflicts; /* list of write transactions whose data we + * couldn't read. */ + SHM_QUEUE inConflicts; /* list of read transactions which couldn't + * see our write. */ + SHM_QUEUE predicateLocks; /* list of associated PREDICATELOCK objects */ + SHM_QUEUE finishedLink; /* list link in + * FinishedSerializableTransactions */ + + /* + * for r/o transactions: list of concurrent r/w transactions that we could + * potentially have conflicts with, and vice versa for r/w transactions + */ + SHM_QUEUE possibleUnsafeConflicts; + + TransactionId topXid; /* top level xid for the transaction, if one + * exists; else invalid */ + TransactionId finishedBefore; /* invalid means still running; else + * the struct expires when no + * serializable xids are before this. */ + TransactionId xmin; /* the transaction's snapshot xmin */ + uint32 flags; /* OR'd combination of values defined below */ + int pid; /* pid of associated process */ +} SERIALIZABLEXACT; + +#define SXACT_FLAG_ROLLED_BACK 0x00000001 +#define SXACT_FLAG_COMMITTED 0x00000002 +#define SXACT_FLAG_CONFLICT_OUT 0x00000004 +#define SXACT_FLAG_READ_ONLY 0x00000008 +#define SXACT_FLAG_DID_WRITE 0x00000010 +#define SXACT_FLAG_MARKED_FOR_DEATH 0x00000020 +#define SXACT_FLAG_DEFERRABLE_WAITING 0x00000040 +#define SXACT_FLAG_RO_SAFE 0x00000080 +#define SXACT_FLAG_RO_UNSAFE 0x00000100 +#define SXACT_FLAG_SUMMARY_CONFLICT_IN 0x00000200 +#define SXACT_FLAG_SUMMARY_CONFLICT_OUT 0x00000400 +#define SXACT_FLAG_PREPARED 0x00000800 + +/* + * The following types are used to provide an ad hoc list for holding + * SERIALIZABLEXACT objects. An HTAB is overkill, since there is no need to + * access these by key -- there are direct pointers to these objects where + * needed. If a shared memory list is created, these types can probably be + * eliminated in favor of using the general solution. + */ +typedef struct PredXactListElementData +{ + SHM_QUEUE link; + SERIALIZABLEXACT sxact; +} PredXactListElementData; + +typedef struct PredXactListElementData *PredXactListElement; + +#define PredXactListElementDataSize \ + ((Size)MAXALIGN(sizeof(PredXactListElementData))) + +typedef struct PredXactListData +{ + SHM_QUEUE availableList; + SHM_QUEUE activeList; + + /* + * These global variables are maintained when registering and cleaning up + * serializable transactions. They must be global across all backends, + * but are not needed outside the predicate.c source file. + */ + TransactionId SxactGlobalXmin; /* global xmin for active serializable + * transactions */ + int SxactGlobalXminCount; /* how many active serializable + * transactions have this xmin */ + int WritableSxactCount; /* how many non-read-only serializable + * transactions are active */ + SerCommitSeqNo LastSxactCommitSeqNo; /* a strictly monotonically + * increasing number for + * commits of serializable + * transactions */ + /* Protected by SerializableXactHashLock. */ + SerCommitSeqNo CanPartialClearThrough; /* can clear predicate locks + * and inConflicts for + * committed transactions + * through this seq no */ + /* Protected by SerializableFinishedListLock. */ + SerCommitSeqNo HavePartialClearedThrough; /* have cleared through this + * seq no */ + SERIALIZABLEXACT *OldCommittedSxact; /* shared copy of dummy sxact */ + bool NeedTargetLinkCleanup; /* to save cleanup effort for rare + * case */ + + PredXactListElement element; +} PredXactListData; + +typedef struct PredXactListData *PredXactList; + +#define PredXactListDataSize \ + ((Size)MAXALIGN(sizeof(PredXactListData))) + + +/* + * The following types are used to provide lists of rw-conflicts between + * pairs of transactions. Since exactly the same information is needed, + * they are also used to record possible unsafe transaction relationships + * for purposes of identifying safe snapshots for read-only transactions. + * + * When a RWConflictData is not in use to record either type of relationship + * between a pair of transactions, it is kept on an "available" list. The + * outLink field is used for maintaining that list. + */ +typedef struct RWConflictData +{ + SHM_QUEUE outLink; /* link for list of conflicts out from a sxact */ + SHM_QUEUE inLink; /* link for list of conflicts in to a sxact */ + SERIALIZABLEXACT *sxactOut; + SERIALIZABLEXACT *sxactIn; +} RWConflictData; + +typedef struct RWConflictData *RWConflict; + +#define RWConflictDataSize \ + ((Size)MAXALIGN(sizeof(RWConflictData))) + +typedef struct RWConflictPoolHeaderData +{ + SHM_QUEUE availableList; + RWConflict element; +} RWConflictPoolHeaderData; + +typedef struct RWConflictPoolHeaderData *RWConflictPoolHeader; + +#define RWConflictPoolHeaderDataSize \ + ((Size)MAXALIGN(sizeof(RWConflictPoolHeaderData))) + + +/* + * The SERIALIZABLEXIDTAG struct identifies an xid assigned to a serializable + * transaction or any of its subtransactions. + */ +typedef struct SERIALIZABLEXIDTAG +{ + TransactionId xid; +} SERIALIZABLEXIDTAG; + +/* + * The SERIALIZABLEXID struct provides a link from a TransactionId for a + * serializable transaction to the related SERIALIZABLEXACT record, even if + * the transaction has completed and its connection has been closed. + * + * These are created as new top level transaction IDs are first assigned to + * transactions which are participating in predicate locking. This may + * never happen for a particular transaction if it doesn't write anything. + * They are removed with their related serializable transaction objects. + * + * The SubTransGetTopmostTransaction method is used where necessary to get + * from an XID which might be from a subtransaction to the top level XID. + */ +typedef struct SERIALIZABLEXID +{ + /* hash key */ + SERIALIZABLEXIDTAG tag; + + /* data */ + SERIALIZABLEXACT *myXact; /* pointer to the top level transaction data */ +} SERIALIZABLEXID; + + +/* + * The PREDICATELOCKTARGETTAG struct identifies a database object which can + * be the target of predicate locks. It is designed to fit into 16 bytes + * with no padding. Note that this would need adjustment if we widen Oid or + * BlockNumber to more than 32 bits. + * + * TODO SSI: If we always use the same fields for the same type of value, we + * should rename these. Holding off until it's clear there are no exceptions. + * Since indexes are relations with blocks and tuples, it's looking likely that + * the rename will be possible. If not, we may need to divide the last field + * and use part of it for a target type, so that we know how to interpret the + * data.. + */ +typedef struct PREDICATELOCKTARGETTAG +{ + uint32 locktag_field1; /* a 32-bit ID field */ + uint32 locktag_field2; /* a 32-bit ID field */ + uint32 locktag_field3; /* a 32-bit ID field */ + uint16 locktag_field4; /* a 16-bit ID field */ + uint16 locktag_field5; /* a 16-bit ID field */ +} PREDICATELOCKTARGETTAG; + +/* + * The PREDICATELOCKTARGET struct represents a database object on which there + * are predicate locks. + * + * A hash list of these objects is maintained in shared memory. An entry is + * added when a predicate lock is requested on an object which doesn't + * already have one. An entry is removed when the last lock is removed from + * its list. + * + * Because a check for predicate locks on a tuple target should also find + * locks on previous versions of the same row, if there are any created by + * overlapping transactions, we keep a pointer to the target for the prior + * version of the row. We also keep a pointer to the next version of the + * row, so that when we no longer have any predicate locks and the back + * pointer is clear, we can clean up the prior pointer for the next version. + */ +typedef struct PREDICATELOCKTARGET PREDICATELOCKTARGET; + +struct PREDICATELOCKTARGET +{ + /* hash key */ + PREDICATELOCKTARGETTAG tag; /* unique identifier of lockable object */ + + /* data */ + SHM_QUEUE predicateLocks; /* list of PREDICATELOCK objects assoc. with + * predicate lock target */ + + /* + * The following two pointers are only used for tuple locks, and are only + * consulted for conflict detection and cleanup; not for granularity + * promotion. + */ + PREDICATELOCKTARGET *priorVersionOfRow; /* what other locks to check */ + PREDICATELOCKTARGET *nextVersionOfRow; /* who has pointer here for + * more targets */ +}; + + +/* + * The PREDICATELOCKTAG struct identifies an individual predicate lock. + * + * It is the combination of predicate lock target (which is a lockable + * object) and a serializable transaction which has acquired a lock on that + * target. + */ +typedef struct PREDICATELOCKTAG +{ + PREDICATELOCKTARGET *myTarget; + SERIALIZABLEXACT *myXact; +} PREDICATELOCKTAG; + +/* + * The PREDICATELOCK struct represents an individual lock. + * + * An entry can be created here when the related database object is read, or + * by promotion of multiple finer-grained targets. All entries related to a + * serializable transaction are removed when that serializable transaction is + * cleaned up. Entries can also be removed when they are combined into a + * single coarser-grained lock entry. + */ +typedef struct PREDICATELOCK +{ + /* hash key */ + PREDICATELOCKTAG tag; /* unique identifier of lock */ + + /* data */ + SHM_QUEUE targetLink; /* list link in PREDICATELOCKTARGET's list of + * predicate locks */ + SHM_QUEUE xactLink; /* list link in SERIALIZABLEXACT's list of + * predicate locks */ + SerCommitSeqNo commitSeqNo; /* only used for summarized predicate locks */ +} PREDICATELOCK; + + +/* + * The LOCALPREDICATELOCK struct represents a local copy of data which is + * also present in the PREDICATELOCK table, organized for fast access without + * needing to acquire a LWLock. It is strictly for optimization. + * + * Each serializable transaction creates its own local hash table to hold a + * collection of these. This information is used to determine when a number + * of fine-grained locks should be promoted to a single coarser-grained lock. + * The information is maintained more-or-less in parallel to the + * PREDICATELOCK data, but because this data is not protected by locks and is + * only used in an optimization heuristic, it is allowed to drift in a few + * corner cases where maintaining exact data would be expensive. + * + * The hash table is created when the serializable transaction acquires its + * snapshot, and its memory is released upon completion of the transaction. + */ +typedef struct LOCALPREDICATELOCK +{ + /* hash key */ + PREDICATELOCKTARGETTAG tag; /* unique identifier of lockable object */ + + /* data */ + bool held; /* is lock held, or just its children? */ + int childLocks; /* number of child locks currently held */ +} LOCALPREDICATELOCK; + + +/* + * The types of predicate locks which can be acquired. + */ +typedef enum PredicateLockTargetType +{ + PREDLOCKTAG_RELATION, + PREDLOCKTAG_PAGE, + PREDLOCKTAG_TUPLE + /* TODO SSI: Other types may be needed for index locking */ +} PredicateLockTargetType; + + +/* + * This structure is used to quickly capture a copy of all predicate + * locks. This is currently used only by the pg_lock_status function, + * which in turn is used by the pg_locks view. + */ +typedef struct PredicateLockData +{ + int nelements; + PREDICATELOCKTARGETTAG *locktags; + SERIALIZABLEXACT *xacts; +} PredicateLockData; + + +/* + * These macros define how we map logical IDs of lockable objects into the + * physical fields of PREDICATELOCKTARGETTAG. Use these to set up values, + * rather than accessing the fields directly. Note multiple eval of target! + */ +#define SET_PREDICATELOCKTARGETTAG_RELATION(locktag,dboid,reloid) \ + ((locktag).locktag_field1 = (dboid), \ + (locktag).locktag_field2 = (reloid), \ + (locktag).locktag_field3 = InvalidBlockNumber, \ + (locktag).locktag_field4 = InvalidOffsetNumber, \ + (locktag).locktag_field5 = 0) + +#define SET_PREDICATELOCKTARGETTAG_PAGE(locktag,dboid,reloid,blocknum) \ + ((locktag).locktag_field1 = (dboid), \ + (locktag).locktag_field2 = (reloid), \ + (locktag).locktag_field3 = (blocknum), \ + (locktag).locktag_field4 = InvalidOffsetNumber, \ + (locktag).locktag_field5 = 0) + +#define SET_PREDICATELOCKTARGETTAG_TUPLE(locktag,dboid,reloid,blocknum,offnum) \ + ((locktag).locktag_field1 = (dboid), \ + (locktag).locktag_field2 = (reloid), \ + (locktag).locktag_field3 = (blocknum), \ + (locktag).locktag_field4 = (offnum), \ + (locktag).locktag_field5 = 0) + +#define GET_PREDICATELOCKTARGETTAG_DB(locktag) \ + ((locktag).locktag_field1) +#define GET_PREDICATELOCKTARGETTAG_RELATION(locktag) \ + ((locktag).locktag_field2) +#define GET_PREDICATELOCKTARGETTAG_PAGE(locktag) \ + ((locktag).locktag_field3) +#define GET_PREDICATELOCKTARGETTAG_OFFSET(locktag) \ + ((locktag).locktag_field4) +#define GET_PREDICATELOCKTARGETTAG_TYPE(locktag) \ + (((locktag).locktag_field4 != InvalidOffsetNumber) ? PREDLOCKTAG_TUPLE : \ + (((locktag).locktag_field3 != InvalidBlockNumber) ? PREDLOCKTAG_PAGE : \ + PREDLOCKTAG_RELATION)) + +/* + * Two-phase commit statefile records. There are two types: for each + * transaction, we generate one per-transaction record and a variable + * number of per-predicate-lock records. + */ +typedef enum TwoPhasePredicateRecordType +{ + TWOPHASEPREDICATERECORD_XACT, + TWOPHASEPREDICATERECORD_LOCK +} TwoPhasePredicateRecordType; + +/* + * Per-transaction information to reconstruct a SERIALIZABLEXACT. Not + * much is needed because most of it not meaningful for a recovered + * prepared transaction. + * + * In particular, we do not record the in and out conflict lists for a + * prepared transaction because the associated SERIALIZABLEXACTs will + * not be available after recovery. Instead, we simply record the + * existence of each type of conflict by setting the transaction's + * summary conflict in/out flag. + */ +typedef struct TwoPhasePredicateXactRecord +{ + TransactionId xmin; + uint32 flags; +} TwoPhasePredicateXactRecord; + +/* Per-lock state */ +typedef struct TwoPhasePredicateLockRecord +{ + PREDICATELOCKTARGETTAG target; +} TwoPhasePredicateLockRecord; + +typedef struct TwoPhasePredicateRecord +{ + TwoPhasePredicateRecordType type; + union + { + TwoPhasePredicateXactRecord xactRecord; + TwoPhasePredicateLockRecord lockRecord; + } data; +} TwoPhasePredicateRecord; + +/* + * Define a macro to use for an "empty" SERIALIZABLEXACT reference. + */ +#define InvalidSerializableXact ((SERIALIZABLEXACT *) NULL) + + +/* + * Function definitions for functions needing awareness of predicate + * locking internals. + */ +extern PredicateLockData *GetPredicateLockStatusData(void); + + +#endif /* PREDICATE_INTERNALS_H */ diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h index 61e3886bd3..f23740c9e3 100644 --- a/src/include/storage/shmem.h +++ b/src/include/storage/shmem.h @@ -35,7 +35,7 @@ typedef struct SHM_QUEUE extern void InitShmemAccess(void *seghdr); extern void InitShmemAllocation(void); extern void *ShmemAlloc(Size size); -extern bool ShmemAddrIsValid(void *addr); +extern bool ShmemAddrIsValid(const void *addr); extern void InitShmemIndex(void); extern HTAB *ShmemInitHash(const char *name, long init_size, long max_size, HASHCTL *infoP, int hash_flags); @@ -67,8 +67,9 @@ extern void SHMQueueInit(SHM_QUEUE *queue); extern void SHMQueueElemInit(SHM_QUEUE *queue); extern void SHMQueueDelete(SHM_QUEUE *queue); extern void SHMQueueInsertBefore(SHM_QUEUE *queue, SHM_QUEUE *elem); -extern Pointer SHMQueueNext(SHM_QUEUE *queue, SHM_QUEUE *curElem, +extern Pointer SHMQueueNext(const SHM_QUEUE *queue, const SHM_QUEUE *curElem, Size linkOffset); -extern bool SHMQueueEmpty(SHM_QUEUE *queue); +extern bool SHMQueueEmpty(const SHM_QUEUE *queue); +extern bool SHMQueueIsDetached(const SHM_QUEUE *queue); #endif /* SHMEM_H */ diff --git a/src/test/isolation/.gitignore b/src/test/isolation/.gitignore new file mode 100644 index 0000000000..42ee945744 --- /dev/null +++ b/src/test/isolation/.gitignore @@ -0,0 +1,12 @@ +# Local binaries +/isolationtester +/pg_isolation_regress + +# Local generated source files +/specparse.c +/specscanner.c + +# Generated subdirectories +/results/ +/log/ +/tmp_check/ diff --git a/src/test/isolation/Makefile b/src/test/isolation/Makefile new file mode 100644 index 0000000000..1d4c7db8bc --- /dev/null +++ b/src/test/isolation/Makefile @@ -0,0 +1,74 @@ +# +# Makefile for isolation tests +# + +subdir = src/test/isolation +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +ifeq ($(PORTNAME), win32) +LDLIBS += -lws2_32 +endif + +override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS) +override LDLIBS := $(libpq_pgport) $(LDLIBS) + +OBJS = specparse.o isolationtester.o + +submake-regress: + $(MAKE) -C $(top_builddir)/src/test/regress pg_regress.o + +pg_regress.o: | submake-regress + rm -f $@ && $(LN_S) $(top_builddir)/src/test/regress/pg_regress.o . + +pg_isolation_regress: isolation_main.o pg_regress.o + $(CC) $(CFLAGS) $^ $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) + +all: isolationtester pg_isolation_regress + +isolationtester: $(OBJS) | submake-libpq submake-libpgport + $(CC) $(CFLAGS) $(OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) + +distprep: specparse.c + +# There is no correct way to write a rule that generates two files. +# Rules with two targets don't have that meaning, they are merely +# shorthand for two otherwise separate rules. To be safe for parallel +# make, we must chain the dependencies like this. The semicolon is +# important, otherwise make will choose the built-in rule for +# gram.y=>gram.c. + +all: isolationtester$(X) pg_isolation_regress$(X) + +specparse.h: specparse.c ; + +# specscanner is compiled as part of specparse +specparse.o: specscanner.c + +specparse.c: specparse.y +ifdef BISON + $(BISON) $(BISONFLAGS) -o $@ $< +else + @$(missing) bison $< $@ +endif + +specscanner.c: specscanner.l +ifdef FLEX + $(FLEX) $(FLEXFLAGS) -o'$@' $< +else + @$(missing) flex $< $@ +endif +# specparse.c is in the distribution tarball, so is not cleaned here +clean distclean: + rm -f isolationtester$(X) pg_isolation_regress$(X) $(OBJS) isolation_main.o + rm -f pg_regress.o + rm -rf results + +maintainer-clean: distclean + rm -f specparse.c specscanner.c + +installcheck: all + ./pg_isolation_regress --schedule=$(srcdir)/isolation_schedule + +check: all + ./pg_isolation_regress --temp-install=./tmp_check --top-builddir=$(top_builddir) --schedule=$(srcdir)/isolation_schedule diff --git a/src/test/isolation/README b/src/test/isolation/README new file mode 100644 index 0000000000..f6984b0bee --- /dev/null +++ b/src/test/isolation/README @@ -0,0 +1,65 @@ +src/test/isolation/README + +Isolation tests +=============== + +This directory contains a set of tests for the serializable isolation level. +Testing isolation requires running multiple overlapping transactions, so +which requires multiple concurrent connections, and can't therefore be +tested using the normal pg_regress program. + +To represent a test with overlapping transactions, we use a test specification +file with a custom syntax, described in the next section. + +isolationtester is program that uses libpq to open multiple connections, +and executes a test specified by a spec file. A libpq connection string +to specify the server and database to connect to, the defaults derived from +environment variables are used otherwise. + +pg_isolation_regress is a tool identical to pg_regress, but instead of using +psql to execute a test, it uses isolationtester. + +To run the tests, you need to have a server up and running. Run + gmake installcheck + +Test specification +================== + +Each isolation test is defined by a specification file, stored in the specs +subdirectory. A test specification consists of five parts, in this order: + +setup { } + + The given SQL block is executed once, in one session only, before running + the test. Create any test tables or such objects here. This part is + optional. + +teardown { } + + The teardown SQL block is executed once after the test is finished. Use + this to clean up, e.g dropping any test tables. This part is optional. + +session "" + + Each session is executed in a separate connection. A session consists + of four parts: setup, teardown and one or more steps. The per-session + setup and teardown parts have the same syntax as the per-test setup and + teardown described above, but they are executed in every session, + before and after each permutation. The setup part typically contains a + "BEGIN" command to begin a transaction. + + Each step has a syntax of + + step "" { } + + where is a unique name identifying this step, and SQL is a SQL + statement (or statements, separated by semicolons) that is executed in the + step. + +permutation "" ... + + A permutation line specifies a list of steps that are ran in that order. + If no permutation lines are given, the test program automatically generates + all possible overlapping orderings of the given sessions. + +Lines beginning with a # are considered comments. diff --git a/src/test/isolation/expected/classroom-scheduling.out b/src/test/isolation/expected/classroom-scheduling.out new file mode 100644 index 0000000000..faae14f45a --- /dev/null +++ b/src/test/isolation/expected/classroom-scheduling.out @@ -0,0 +1,299 @@ +Parsed test spec with 2 sessions + +starting permutation: rx1 wy1 c1 ry2 wx2 c2 +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +step c1: COMMIT; +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +1 +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +step c2: COMMIT; + +starting permutation: rx1 wy1 ry2 c1 wx2 c2 +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step c1: COMMIT; +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: rx1 wy1 ry2 wx2 c1 c2 +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 wy1 ry2 wx2 c2 c1 +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 ry2 wy1 c1 wx2 c2 +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +step c1: COMMIT; +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: rx1 ry2 wy1 wx2 c1 c2 +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 ry2 wy1 wx2 c2 c1 +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 ry2 wx2 wy1 c1 c2 +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 ry2 wx2 wy1 c2 c1 +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 ry2 wx2 c2 wy1 c1 +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +step c2: COMMIT; +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: ry2 rx1 wy1 c1 wx2 c2 +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +step c1: COMMIT; +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: ry2 rx1 wy1 wx2 c1 c2 +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 rx1 wy1 wx2 c2 c1 +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 rx1 wx2 wy1 c1 c2 +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 rx1 wx2 wy1 c2 c1 +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 rx1 wx2 c2 wy1 c1 +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +step c2: COMMIT; +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: ry2 wx2 rx1 wy1 c1 c2 +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 wx2 rx1 wy1 c2 c1 +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 wx2 rx1 c2 wy1 c1 +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +0 +step c2: COMMIT; +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: ry2 wx2 c2 rx1 wy1 c1 +step ry2: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; +count + +0 +step wx2: UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; +step c2: COMMIT; +step rx1: SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; +count + +1 +step wy1: INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); +step c1: COMMIT; diff --git a/src/test/isolation/expected/multiple-row-versions.out b/src/test/isolation/expected/multiple-row-versions.out new file mode 100644 index 0000000000..cd31029d17 --- /dev/null +++ b/src/test/isolation/expected/multiple-row-versions.out @@ -0,0 +1,24 @@ +Parsed test spec with 4 sessions + +starting permutation: rx1 wx2 c2 wx3 ry3 wy4 rz4 c4 c3 wz1 c1 +step rx1: SELECT * FROM t WHERE id = 1000000; +id txt + +1000000 +step wx2: UPDATE t SET txt = 'b' WHERE id = 1000000; +step c2: COMMIT; +step wx3: UPDATE t SET txt = 'c' WHERE id = 1000000; +step ry3: SELECT * FROM t WHERE id = 500000; +id txt + +500000 +step wy4: UPDATE t SET txt = 'd' WHERE id = 500000; +step rz4: SELECT * FROM t WHERE id = 1; +id txt + +1 +step c4: COMMIT; +step c3: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions +step wz1: UPDATE t SET txt = 'a' WHERE id = 1; +step c1: COMMIT; diff --git a/src/test/isolation/expected/partial-index.out b/src/test/isolation/expected/partial-index.out new file mode 100644 index 0000000000..1230513675 --- /dev/null +++ b/src/test/isolation/expected/partial-index.out @@ -0,0 +1,641 @@ +Parsed test spec with 2 sessions + +starting permutation: rxy1 wx1 c1 wy2 rxy2 c2 +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +step c1: COMMIT; +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step rxy2: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +step c2: COMMIT; + +starting permutation: rxy1 wx1 wy2 c1 rxy2 c2 +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step c1: COMMIT; +step rxy2: select * from test_t where val2 = 1; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: rxy1 wx1 wy2 rxy2 c1 c2 +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step rxy2: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +10 a 1 +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rxy1 wx1 wy2 rxy2 c2 c1 +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step rxy2: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +10 a 1 +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rxy1 wy2 wx1 c1 rxy2 c2 +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +step c1: COMMIT; +step rxy2: select * from test_t where val2 = 1; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: rxy1 wy2 wx1 rxy2 c1 c2 +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +step rxy2: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +10 a 1 +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rxy1 wy2 wx1 rxy2 c2 c1 +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +step rxy2: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +10 a 1 +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rxy1 wy2 rxy2 wx1 c1 c2 +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step rxy2: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +10 a 1 +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rxy1 wy2 rxy2 wx1 c2 c1 +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step rxy2: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +10 a 1 +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rxy1 wy2 rxy2 c2 wx1 c1 +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step rxy2: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +10 a 1 +step c2: COMMIT; +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: wy2 rxy1 wx1 c1 rxy2 c2 +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +step c1: COMMIT; +step rxy2: select * from test_t where val2 = 1; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: wy2 rxy1 wx1 rxy2 c1 c2 +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +step rxy2: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +10 a 1 +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wy2 rxy1 wx1 rxy2 c2 c1 +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +step rxy2: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +10 a 1 +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wy2 rxy1 rxy2 wx1 c1 c2 +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step rxy2: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +10 a 1 +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wy2 rxy1 rxy2 wx1 c2 c1 +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step rxy2: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +10 a 1 +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wy2 rxy1 rxy2 c2 wx1 c1 +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step rxy2: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +10 a 1 +step c2: COMMIT; +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: wy2 rxy2 rxy1 wx1 c1 c2 +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step rxy2: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +10 a 1 +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wy2 rxy2 rxy1 wx1 c2 c1 +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step rxy2: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +10 a 1 +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wy2 rxy2 rxy1 c2 wx1 c1 +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step rxy2: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +10 a 1 +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +9 a 1 +10 a 1 +step c2: COMMIT; +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: wy2 rxy2 c2 rxy1 wx1 c1 +step wy2: update test_t set val2 = 2 where val2 = 1 and id = 9; +step rxy2: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +10 a 1 +step c2: COMMIT; +step rxy1: select * from test_t where val2 = 1; +id val1 val2 + +0 a 1 +1 a 1 +2 a 1 +3 a 1 +4 a 1 +5 a 1 +6 a 1 +7 a 1 +8 a 1 +10 a 1 +step wx1: update test_t set val2 = 2 where val2 = 1 and id = 10; +step c1: COMMIT; diff --git a/src/test/isolation/expected/project-manager.out b/src/test/isolation/expected/project-manager.out new file mode 100644 index 0000000000..0050a744a8 --- /dev/null +++ b/src/test/isolation/expected/project-manager.out @@ -0,0 +1,299 @@ +Parsed test spec with 2 sessions + +starting permutation: rx1 wy1 c1 ry2 wx2 c2 +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +step c1: COMMIT; +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +1 +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +step c2: COMMIT; + +starting permutation: rx1 wy1 ry2 c1 wx2 c2 +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step c1: COMMIT; +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: rx1 wy1 ry2 wx2 c1 c2 +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 wy1 ry2 wx2 c2 c1 +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 ry2 wy1 c1 wx2 c2 +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +step c1: COMMIT; +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: rx1 ry2 wy1 wx2 c1 c2 +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 ry2 wy1 wx2 c2 c1 +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 ry2 wx2 wy1 c1 c2 +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 ry2 wx2 wy1 c2 c1 +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 ry2 wx2 c2 wy1 c1 +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +step c2: COMMIT; +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: ry2 rx1 wy1 c1 wx2 c2 +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +step c1: COMMIT; +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: ry2 rx1 wy1 wx2 c1 c2 +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 rx1 wy1 wx2 c2 c1 +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 rx1 wx2 wy1 c1 c2 +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 rx1 wx2 wy1 c2 c1 +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 rx1 wx2 c2 wy1 c1 +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +step c2: COMMIT; +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: ry2 wx2 rx1 wy1 c1 c2 +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 wx2 rx1 wy1 c2 c1 +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 wx2 rx1 c2 wy1 c1 +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +1 +step c2: COMMIT; +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: ry2 wx2 c2 rx1 wy1 c1 +step ry2: SELECT count(*) FROM project WHERE project_manager = 1; +count + +0 +step wx2: UPDATE person SET is_project_manager = false WHERE person_id = 1; +step c2: COMMIT; +step rx1: SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; +count + +0 +step wy1: INSERT INTO project VALUES (101, 'Build Great Wall', 1); +step c1: COMMIT; diff --git a/src/test/isolation/expected/receipt-report.out b/src/test/isolation/expected/receipt-report.out new file mode 100644 index 0000000000..bcab10ea95 --- /dev/null +++ b/src/test/isolation/expected/receipt-report.out @@ -0,0 +1,3379 @@ +Parsed test spec with 3 sessions + +starting permutation: rxwy1 c1 wx2 c2 rx3 ry3 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c3: COMMIT; + +starting permutation: rxwy1 c1 wx2 rx3 c2 ry3 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c3: COMMIT; + +starting permutation: rxwy1 c1 wx2 rx3 ry3 c2 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy1 c1 wx2 rx3 ry3 c3 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy1 c1 rx3 wx2 c2 ry3 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c3: COMMIT; + +starting permutation: rxwy1 c1 rx3 wx2 ry3 c2 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy1 c1 rx3 wx2 ry3 c3 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy1 c1 rx3 ry3 wx2 c2 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy1 c1 rx3 ry3 wx2 c3 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy1 c1 rx3 ry3 c3 wx2 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c3: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; + +starting permutation: rxwy1 wx2 c1 c2 rx3 ry3 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step c2: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c3: COMMIT; + +starting permutation: rxwy1 wx2 c1 rx3 c2 ry3 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c3: COMMIT; + +starting permutation: rxwy1 wx2 c1 rx3 ry3 c2 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy1 wx2 c1 rx3 ry3 c3 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy1 wx2 c2 c1 rx3 ry3 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c1: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c3: COMMIT; + +starting permutation: rxwy1 wx2 c2 rx3 c1 ry3 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +ERROR: could not serialize access due to read/write dependencies among transactions +step c3: COMMIT; + +starting permutation: rxwy1 wx2 c2 rx3 ry3 c1 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions +step c3: COMMIT; + +starting permutation: rxwy1 wx2 c2 rx3 ry3 c3 c1 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rxwy1 wx2 rx3 c1 c2 ry3 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c1: COMMIT; +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: rxwy1 wx2 rx3 c1 ry3 c2 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy1 wx2 rx3 c1 ry3 c3 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy1 wx2 rx3 c2 c1 ry3 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c2: COMMIT; +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: rxwy1 wx2 rx3 c2 ry3 c1 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy1 wx2 rx3 c2 ry3 c3 c1 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy1 wx2 rx3 ry3 c1 c2 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy1 wx2 rx3 ry3 c1 c3 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy1 wx2 rx3 ry3 c2 c1 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy1 wx2 rx3 ry3 c2 c3 c1 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy1 wx2 rx3 ry3 c3 c1 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy1 wx2 rx3 ry3 c3 c2 c1 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy1 rx3 c1 wx2 c2 ry3 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: rxwy1 rx3 c1 wx2 ry3 c2 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy1 rx3 c1 wx2 ry3 c3 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy1 rx3 c1 ry3 wx2 c2 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy1 rx3 c1 ry3 wx2 c3 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy1 rx3 c1 ry3 c3 wx2 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; + +starting permutation: rxwy1 rx3 wx2 c1 c2 ry3 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: rxwy1 rx3 wx2 c1 ry3 c2 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy1 rx3 wx2 c1 ry3 c3 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy1 rx3 wx2 c2 c1 ry3 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: rxwy1 rx3 wx2 c2 ry3 c1 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy1 rx3 wx2 c2 ry3 c3 c1 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy1 rx3 wx2 ry3 c1 c2 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy1 rx3 wx2 ry3 c1 c3 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy1 rx3 wx2 ry3 c2 c1 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy1 rx3 wx2 ry3 c2 c3 c1 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy1 rx3 wx2 ry3 c3 c1 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy1 rx3 wx2 ry3 c3 c2 c1 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy1 rx3 ry3 c1 wx2 c2 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy1 rx3 ry3 c1 wx2 c3 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy1 rx3 ry3 c1 c3 wx2 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; + +starting permutation: rxwy1 rx3 ry3 wx2 c1 c2 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy1 rx3 ry3 wx2 c1 c3 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy1 rx3 ry3 wx2 c2 c1 c3 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy1 rx3 ry3 wx2 c2 c3 c1 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy1 rx3 ry3 wx2 c3 c1 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy1 rx3 ry3 wx2 c3 c2 c1 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy1 rx3 ry3 c3 c1 wx2 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; + +starting permutation: rxwy1 rx3 ry3 c3 wx2 c1 c2 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy1 rx3 ry3 c3 wx2 c2 c1 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: wx2 rxwy1 c1 c2 rx3 ry3 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c2: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c3: COMMIT; + +starting permutation: wx2 rxwy1 c1 rx3 c2 ry3 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c3: COMMIT; + +starting permutation: wx2 rxwy1 c1 rx3 ry3 c2 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: wx2 rxwy1 c1 rx3 ry3 c3 c2 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: wx2 rxwy1 c2 c1 rx3 ry3 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step c1: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +3 12-22-2008 4.00 +step c3: COMMIT; + +starting permutation: wx2 rxwy1 c2 rx3 c1 ry3 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +ERROR: could not serialize access due to read/write dependencies among transactions +step c3: COMMIT; + +starting permutation: wx2 rxwy1 c2 rx3 ry3 c1 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions +step c3: COMMIT; + +starting permutation: wx2 rxwy1 c2 rx3 ry3 c3 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wx2 rxwy1 rx3 c1 c2 ry3 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c1: COMMIT; +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: wx2 rxwy1 rx3 c1 ry3 c2 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: wx2 rxwy1 rx3 c1 ry3 c3 c2 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: wx2 rxwy1 rx3 c2 c1 ry3 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c2: COMMIT; +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: wx2 rxwy1 rx3 c2 ry3 c1 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: wx2 rxwy1 rx3 c2 ry3 c3 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: wx2 rxwy1 rx3 ry3 c1 c2 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: wx2 rxwy1 rx3 ry3 c1 c3 c2 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: wx2 rxwy1 rx3 ry3 c2 c1 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: wx2 rxwy1 rx3 ry3 c2 c3 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: wx2 rxwy1 rx3 ry3 c3 c1 c2 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: wx2 rxwy1 rx3 ry3 c3 c2 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: wx2 c2 rxwy1 c1 rx3 ry3 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: wx2 c2 rxwy1 rx3 c1 ry3 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: wx2 c2 rxwy1 rx3 ry3 c1 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: wx2 c2 rxwy1 rx3 ry3 c3 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: wx2 c2 rx3 rxwy1 c1 ry3 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: wx2 c2 rx3 rxwy1 ry3 c1 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: wx2 c2 rx3 rxwy1 ry3 c3 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: wx2 c2 rx3 ry3 rxwy1 c1 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: wx2 c2 rx3 ry3 rxwy1 c3 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: wx2 c2 rx3 ry3 c3 rxwy1 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-23-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; + +starting permutation: wx2 rx3 rxwy1 c1 c2 ry3 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: wx2 rx3 rxwy1 c1 ry3 c2 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: wx2 rx3 rxwy1 c1 ry3 c3 c2 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: wx2 rx3 rxwy1 c2 c1 ry3 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: wx2 rx3 rxwy1 c2 ry3 c1 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: wx2 rx3 rxwy1 c2 ry3 c3 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: wx2 rx3 rxwy1 ry3 c1 c2 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: wx2 rx3 rxwy1 ry3 c1 c3 c2 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: wx2 rx3 rxwy1 ry3 c2 c1 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: wx2 rx3 rxwy1 ry3 c2 c3 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: wx2 rx3 rxwy1 ry3 c3 c1 c2 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: wx2 rx3 rxwy1 ry3 c3 c2 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: wx2 rx3 c2 rxwy1 c1 ry3 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: wx2 rx3 c2 rxwy1 ry3 c1 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: wx2 rx3 c2 rxwy1 ry3 c3 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: wx2 rx3 c2 ry3 rxwy1 c1 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: wx2 rx3 c2 ry3 rxwy1 c3 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: wx2 rx3 c2 ry3 c3 rxwy1 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; + +starting permutation: wx2 rx3 ry3 rxwy1 c1 c2 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: wx2 rx3 ry3 rxwy1 c1 c3 c2 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: wx2 rx3 ry3 rxwy1 c2 c1 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: wx2 rx3 ry3 rxwy1 c2 c3 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: wx2 rx3 ry3 rxwy1 c3 c1 c2 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: wx2 rx3 ry3 rxwy1 c3 c2 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: wx2 rx3 ry3 c2 rxwy1 c1 c3 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: wx2 rx3 ry3 c2 rxwy1 c3 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: wx2 rx3 ry3 c2 c3 rxwy1 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c3: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; + +starting permutation: wx2 rx3 ry3 c3 rxwy1 c1 c2 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: wx2 rx3 ry3 c3 rxwy1 c2 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: wx2 rx3 ry3 c3 c2 rxwy1 c1 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; + +starting permutation: rx3 rxwy1 c1 wx2 c2 ry3 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: rx3 rxwy1 c1 wx2 ry3 c2 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 rxwy1 c1 wx2 ry3 c3 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 rxwy1 c1 ry3 wx2 c2 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 rxwy1 c1 ry3 wx2 c3 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 rxwy1 c1 ry3 c3 wx2 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; + +starting permutation: rx3 rxwy1 wx2 c1 c2 ry3 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: rx3 rxwy1 wx2 c1 ry3 c2 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 rxwy1 wx2 c1 ry3 c3 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 rxwy1 wx2 c2 c1 ry3 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: rx3 rxwy1 wx2 c2 ry3 c1 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 rxwy1 wx2 c2 ry3 c3 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 rxwy1 wx2 ry3 c1 c2 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 rxwy1 wx2 ry3 c1 c3 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 rxwy1 wx2 ry3 c2 c1 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 rxwy1 wx2 ry3 c2 c3 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 rxwy1 wx2 ry3 c3 c1 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 rxwy1 wx2 ry3 c3 c2 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 rxwy1 ry3 c1 wx2 c2 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 rxwy1 ry3 c1 wx2 c3 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 rxwy1 ry3 c1 c3 wx2 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; + +starting permutation: rx3 rxwy1 ry3 wx2 c1 c2 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 rxwy1 ry3 wx2 c1 c3 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 rxwy1 ry3 wx2 c2 c1 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 rxwy1 ry3 wx2 c2 c3 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 rxwy1 ry3 wx2 c3 c1 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 rxwy1 ry3 wx2 c3 c2 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 rxwy1 ry3 c3 c1 wx2 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; + +starting permutation: rx3 rxwy1 ry3 c3 wx2 c1 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 rxwy1 ry3 c3 wx2 c2 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 wx2 rxwy1 c1 c2 ry3 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: rx3 wx2 rxwy1 c1 ry3 c2 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 wx2 rxwy1 c1 ry3 c3 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 wx2 rxwy1 c2 c1 ry3 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: rx3 wx2 rxwy1 c2 ry3 c1 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 wx2 rxwy1 c2 ry3 c3 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 wx2 rxwy1 ry3 c1 c2 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 wx2 rxwy1 ry3 c1 c3 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 wx2 rxwy1 ry3 c2 c1 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 wx2 rxwy1 ry3 c2 c3 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 wx2 rxwy1 ry3 c3 c1 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 wx2 rxwy1 ry3 c3 c2 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 wx2 c2 rxwy1 c1 ry3 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; + +starting permutation: rx3 wx2 c2 rxwy1 ry3 c1 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 wx2 c2 rxwy1 ry3 c3 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 wx2 c2 ry3 rxwy1 c1 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 wx2 c2 ry3 rxwy1 c3 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 wx2 c2 ry3 c3 rxwy1 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; + +starting permutation: rx3 wx2 ry3 rxwy1 c1 c2 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 wx2 ry3 rxwy1 c1 c3 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 wx2 ry3 rxwy1 c2 c1 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 wx2 ry3 rxwy1 c2 c3 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 wx2 ry3 rxwy1 c3 c1 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 wx2 ry3 rxwy1 c3 c2 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 wx2 ry3 c2 rxwy1 c1 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 wx2 ry3 c2 rxwy1 c3 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 wx2 ry3 c2 c3 rxwy1 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c2: COMMIT; +step c3: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; + +starting permutation: rx3 wx2 ry3 c3 rxwy1 c1 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 wx2 ry3 c3 rxwy1 c2 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 wx2 ry3 c3 c2 rxwy1 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; + +starting permutation: rx3 ry3 rxwy1 c1 wx2 c2 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 ry3 rxwy1 c1 wx2 c3 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 ry3 rxwy1 c1 c3 wx2 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c3: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; + +starting permutation: rx3 ry3 rxwy1 wx2 c1 c2 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 ry3 rxwy1 wx2 c1 c3 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 ry3 rxwy1 wx2 c2 c1 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 ry3 rxwy1 wx2 c2 c3 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 ry3 rxwy1 wx2 c3 c1 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 ry3 rxwy1 wx2 c3 c2 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 ry3 rxwy1 c3 c1 wx2 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c3: COMMIT; +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; + +starting permutation: rx3 ry3 rxwy1 c3 wx2 c1 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c3: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 ry3 rxwy1 c3 wx2 c2 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c3: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 ry3 wx2 rxwy1 c1 c2 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 ry3 wx2 rxwy1 c1 c3 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 ry3 wx2 rxwy1 c2 c1 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 ry3 wx2 rxwy1 c2 c3 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 ry3 wx2 rxwy1 c3 c1 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 ry3 wx2 rxwy1 c3 c2 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 ry3 wx2 c2 rxwy1 c1 c3 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rx3 ry3 wx2 c2 rxwy1 c3 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 ry3 wx2 c2 c3 rxwy1 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c3: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; + +starting permutation: rx3 ry3 wx2 c3 rxwy1 c1 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c3: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 ry3 wx2 c3 rxwy1 c2 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c3: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 ry3 wx2 c3 c2 rxwy1 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c3: COMMIT; +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; + +starting permutation: rx3 ry3 c3 rxwy1 c1 wx2 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; + +starting permutation: rx3 ry3 c3 rxwy1 wx2 c1 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 ry3 c3 rxwy1 wx2 c2 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 ry3 c3 wx2 rxwy1 c1 c2 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rx3 ry3 c3 wx2 rxwy1 c2 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rx3 ry3 c3 wx2 c2 rxwy1 c1 +step rx3: SELECT * FROM ctl WHERE k = 'receipt'; +k deposit_date + +receipt 12-22-2008 +step ry3: SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; +receipt_no deposit_date amount + +1 12-22-2008 1.00 +2 12-22-2008 2.00 +step c3: COMMIT; +step wx2: UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; +step c2: COMMIT; +step rxwy1: INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); +step c1: COMMIT; diff --git a/src/test/isolation/expected/referential-integrity.out b/src/test/isolation/expected/referential-integrity.out new file mode 100644 index 0000000000..569d0347cc --- /dev/null +++ b/src/test/isolation/expected/referential-integrity.out @@ -0,0 +1,629 @@ +Parsed test spec with 2 sessions + +starting permutation: rx1 wy1 c1 rx2 ry2 wx2 c2 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step c1: COMMIT; +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +1 +step wx2: DELETE FROM a WHERE i = 1; +step c2: COMMIT; + +starting permutation: rx1 wy1 rx2 c1 ry2 wx2 c2 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step c1: COMMIT; +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: rx1 wy1 rx2 ry2 c1 wx2 c2 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step c1: COMMIT; +step wx2: DELETE FROM a WHERE i = 1; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: rx1 wy1 rx2 ry2 wx2 c1 c2 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 wy1 rx2 ry2 wx2 c2 c1 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 rx2 wy1 c1 ry2 wx2 c2 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step c1: COMMIT; +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: rx1 rx2 wy1 ry2 c1 wx2 c2 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step c1: COMMIT; +step wx2: DELETE FROM a WHERE i = 1; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: rx1 rx2 wy1 ry2 wx2 c1 c2 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 rx2 wy1 ry2 wx2 c2 c1 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 rx2 ry2 wy1 c1 wx2 c2 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wy1: INSERT INTO b VALUES (1); +step c1: COMMIT; +step wx2: DELETE FROM a WHERE i = 1; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: rx1 rx2 ry2 wy1 wx2 c1 c2 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wy1: INSERT INTO b VALUES (1); +step wx2: DELETE FROM a WHERE i = 1; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 rx2 ry2 wy1 wx2 c2 c1 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wy1: INSERT INTO b VALUES (1); +step wx2: DELETE FROM a WHERE i = 1; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 rx2 ry2 wx2 wy1 c1 c2 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +step wy1: INSERT INTO b VALUES (1); +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 rx2 ry2 wx2 wy1 c2 c1 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +step wy1: INSERT INTO b VALUES (1); +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 rx2 ry2 wx2 c2 wy1 c1 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +step c2: COMMIT; +step wy1: INSERT INTO b VALUES (1); +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: rx2 rx1 wy1 c1 ry2 wx2 c2 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step c1: COMMIT; +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: rx2 rx1 wy1 ry2 c1 wx2 c2 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step c1: COMMIT; +step wx2: DELETE FROM a WHERE i = 1; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: rx2 rx1 wy1 ry2 wx2 c1 c2 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx2 rx1 wy1 ry2 wx2 c2 c1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx2 rx1 ry2 wy1 c1 wx2 c2 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wy1: INSERT INTO b VALUES (1); +step c1: COMMIT; +step wx2: DELETE FROM a WHERE i = 1; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: rx2 rx1 ry2 wy1 wx2 c1 c2 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wy1: INSERT INTO b VALUES (1); +step wx2: DELETE FROM a WHERE i = 1; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx2 rx1 ry2 wy1 wx2 c2 c1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wy1: INSERT INTO b VALUES (1); +step wx2: DELETE FROM a WHERE i = 1; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx2 rx1 ry2 wx2 wy1 c1 c2 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +step wy1: INSERT INTO b VALUES (1); +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx2 rx1 ry2 wx2 wy1 c2 c1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +step wy1: INSERT INTO b VALUES (1); +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx2 rx1 ry2 wx2 c2 wy1 c1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +step c2: COMMIT; +step wy1: INSERT INTO b VALUES (1); +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: rx2 ry2 rx1 wy1 c1 wx2 c2 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step c1: COMMIT; +step wx2: DELETE FROM a WHERE i = 1; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: rx2 ry2 rx1 wy1 wx2 c1 c2 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step wx2: DELETE FROM a WHERE i = 1; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx2 ry2 rx1 wy1 wx2 c2 c1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step wx2: DELETE FROM a WHERE i = 1; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx2 ry2 rx1 wx2 wy1 c1 c2 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step wx2: DELETE FROM a WHERE i = 1; +step wy1: INSERT INTO b VALUES (1); +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx2 ry2 rx1 wx2 wy1 c2 c1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step wx2: DELETE FROM a WHERE i = 1; +step wy1: INSERT INTO b VALUES (1); +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx2 ry2 rx1 wx2 c2 wy1 c1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step wx2: DELETE FROM a WHERE i = 1; +step c2: COMMIT; +step wy1: INSERT INTO b VALUES (1); +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: rx2 ry2 wx2 rx1 wy1 c1 c2 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx2 ry2 wx2 rx1 wy1 c2 c1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step wy1: INSERT INTO b VALUES (1); +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx2 ry2 wx2 rx1 c2 wy1 c1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +step rx1: SELECT i FROM a WHERE i = 1; +i + +1 +step c2: COMMIT; +step wy1: INSERT INTO b VALUES (1); +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: rx2 ry2 wx2 c2 rx1 wy1 c1 +step rx2: SELECT i FROM a WHERE i = 1; +i + +1 +step ry2: SELECT a_id FROM b WHERE a_id = 1; +a_id + +step wx2: DELETE FROM a WHERE i = 1; +step c2: COMMIT; +step rx1: SELECT i FROM a WHERE i = 1; +i + +step wy1: INSERT INTO b VALUES (1); +step c1: COMMIT; diff --git a/src/test/isolation/expected/ri-trigger.out b/src/test/isolation/expected/ri-trigger.out new file mode 100644 index 0000000000..9709e77195 --- /dev/null +++ b/src/test/isolation/expected/ri-trigger.out @@ -0,0 +1,111 @@ +Parsed test spec with 2 sessions + +starting permutation: wxry1 c1 r2 wyrx2 c2 +step wxry1: INSERT INTO child (parent_id) VALUES (0); +step c1: COMMIT; +step r2: SELECT TRUE; +bool + +t +step wyrx2: DELETE FROM parent WHERE parent_id = 0; +ERROR: child row exists +step c2: COMMIT; + +starting permutation: wxry1 r2 c1 wyrx2 c2 +step wxry1: INSERT INTO child (parent_id) VALUES (0); +step r2: SELECT TRUE; +bool + +t +step c1: COMMIT; +step wyrx2: DELETE FROM parent WHERE parent_id = 0; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: wxry1 r2 wyrx2 c1 c2 +step wxry1: INSERT INTO child (parent_id) VALUES (0); +step r2: SELECT TRUE; +bool + +t +step wyrx2: DELETE FROM parent WHERE parent_id = 0; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wxry1 r2 wyrx2 c2 c1 +step wxry1: INSERT INTO child (parent_id) VALUES (0); +step r2: SELECT TRUE; +bool + +t +step wyrx2: DELETE FROM parent WHERE parent_id = 0; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: r2 wxry1 c1 wyrx2 c2 +step r2: SELECT TRUE; +bool + +t +step wxry1: INSERT INTO child (parent_id) VALUES (0); +step c1: COMMIT; +step wyrx2: DELETE FROM parent WHERE parent_id = 0; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: r2 wxry1 wyrx2 c1 c2 +step r2: SELECT TRUE; +bool + +t +step wxry1: INSERT INTO child (parent_id) VALUES (0); +step wyrx2: DELETE FROM parent WHERE parent_id = 0; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: r2 wxry1 wyrx2 c2 c1 +step r2: SELECT TRUE; +bool + +t +step wxry1: INSERT INTO child (parent_id) VALUES (0); +step wyrx2: DELETE FROM parent WHERE parent_id = 0; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: r2 wyrx2 wxry1 c1 c2 +step r2: SELECT TRUE; +bool + +t +step wyrx2: DELETE FROM parent WHERE parent_id = 0; +step wxry1: INSERT INTO child (parent_id) VALUES (0); +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: r2 wyrx2 wxry1 c2 c1 +step r2: SELECT TRUE; +bool + +t +step wyrx2: DELETE FROM parent WHERE parent_id = 0; +step wxry1: INSERT INTO child (parent_id) VALUES (0); +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: r2 wyrx2 c2 wxry1 c1 +step r2: SELECT TRUE; +bool + +t +step wyrx2: DELETE FROM parent WHERE parent_id = 0; +step c2: COMMIT; +step wxry1: INSERT INTO child (parent_id) VALUES (0); +ERROR: parent row missing +step c1: COMMIT; diff --git a/src/test/isolation/expected/simple-write-skew.out b/src/test/isolation/expected/simple-write-skew.out new file mode 100644 index 0000000000..5896beec33 --- /dev/null +++ b/src/test/isolation/expected/simple-write-skew.out @@ -0,0 +1,41 @@ +Parsed test spec with 2 sessions + +starting permutation: rwx1 c1 rwx2 c2 +step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear'; +step c1: COMMIT; +step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple' +step c2: COMMIT; + +starting permutation: rwx1 rwx2 c1 c2 +step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear'; +step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple' +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rwx1 rwx2 c2 c1 +step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear'; +step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple' +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rwx2 rwx1 c1 c2 +step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple' +step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear'; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rwx2 rwx1 c2 c1 +step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple' +step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear'; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rwx2 c2 rwx1 c1 +step rwx2: UPDATE test SET t = 'pear' WHERE t = 'apple' +step c2: COMMIT; +step rwx1: UPDATE test SET t = 'apple' WHERE t = 'pear'; +step c1: COMMIT; diff --git a/src/test/isolation/expected/temporal-range-integrity.out b/src/test/isolation/expected/temporal-range-integrity.out new file mode 100644 index 0000000000..3e7fb98690 --- /dev/null +++ b/src/test/isolation/expected/temporal-range-integrity.out @@ -0,0 +1,299 @@ +Parsed test spec with 2 sessions + +starting permutation: rx1 wy1 c1 ry2 wx2 c2 +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +step c1: COMMIT; +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +1 +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +step c2: COMMIT; + +starting permutation: rx1 wy1 ry2 c1 wx2 c2 +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step c1: COMMIT; +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: rx1 wy1 ry2 wx2 c1 c2 +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 wy1 ry2 wx2 c2 c1 +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 ry2 wy1 c1 wx2 c2 +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +step c1: COMMIT; +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: rx1 ry2 wy1 wx2 c1 c2 +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 ry2 wy1 wx2 c2 c1 +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 ry2 wx2 wy1 c1 c2 +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 ry2 wx2 wy1 c2 c1 +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rx1 ry2 wx2 c2 wy1 c1 +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +step c2: COMMIT; +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: ry2 rx1 wy1 c1 wx2 c2 +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +step c1: COMMIT; +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: ry2 rx1 wy1 wx2 c1 c2 +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 rx1 wy1 wx2 c2 c1 +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 rx1 wx2 wy1 c1 c2 +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 rx1 wx2 wy1 c2 c1 +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 rx1 wx2 c2 wy1 c1 +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +step c2: COMMIT; +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: ry2 wx2 rx1 wy1 c1 c2 +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 wx2 rx1 wy1 c2 c1 +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry2 wx2 rx1 c2 wy1 c1 +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +1 +step c2: COMMIT; +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: ry2 wx2 c2 rx1 wy1 c1 +step ry2: SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; +count + +0 +step wx2: DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; +step c2: COMMIT; +step rx1: SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); +count + +0 +step wy1: INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); +step c1: COMMIT; diff --git a/src/test/isolation/expected/total-cash.out b/src/test/isolation/expected/total-cash.out new file mode 100644 index 0000000000..df1950843b --- /dev/null +++ b/src/test/isolation/expected/total-cash.out @@ -0,0 +1,281 @@ +Parsed test spec with 2 sessions + +starting permutation: wx1 rxy1 c1 wy2 rxy2 c2 +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step rxy1: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c1: COMMIT; +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step rxy2: SELECT SUM(balance) FROM accounts; +sum + +800 +step c2: COMMIT; + +starting permutation: wx1 rxy1 wy2 c1 rxy2 c2 +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step rxy1: SELECT SUM(balance) FROM accounts; +sum + +1000 +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step c1: COMMIT; +step rxy2: SELECT SUM(balance) FROM accounts; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: wx1 rxy1 wy2 rxy2 c1 c2 +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step rxy1: SELECT SUM(balance) FROM accounts; +sum + +1000 +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step rxy2: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wx1 rxy1 wy2 rxy2 c2 c1 +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step rxy1: SELECT SUM(balance) FROM accounts; +sum + +1000 +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step rxy2: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wx1 wy2 rxy1 c1 rxy2 c2 +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step rxy1: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c1: COMMIT; +step rxy2: SELECT SUM(balance) FROM accounts; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: wx1 wy2 rxy1 rxy2 c1 c2 +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step rxy1: SELECT SUM(balance) FROM accounts; +sum + +1000 +step rxy2: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wx1 wy2 rxy1 rxy2 c2 c1 +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step rxy1: SELECT SUM(balance) FROM accounts; +sum + +1000 +step rxy2: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wx1 wy2 rxy2 rxy1 c1 c2 +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step rxy2: SELECT SUM(balance) FROM accounts; +sum + +1000 +step rxy1: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wx1 wy2 rxy2 rxy1 c2 c1 +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step rxy2: SELECT SUM(balance) FROM accounts; +sum + +1000 +step rxy1: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wx1 wy2 rxy2 c2 rxy1 c1 +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step rxy2: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c2: COMMIT; +step rxy1: SELECT SUM(balance) FROM accounts; +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: wy2 wx1 rxy1 c1 rxy2 c2 +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step rxy1: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c1: COMMIT; +step rxy2: SELECT SUM(balance) FROM accounts; +ERROR: could not serialize access due to read/write dependencies among transactions +step c2: COMMIT; + +starting permutation: wy2 wx1 rxy1 rxy2 c1 c2 +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step rxy1: SELECT SUM(balance) FROM accounts; +sum + +1000 +step rxy2: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wy2 wx1 rxy1 rxy2 c2 c1 +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step rxy1: SELECT SUM(balance) FROM accounts; +sum + +1000 +step rxy2: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wy2 wx1 rxy2 rxy1 c1 c2 +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step rxy2: SELECT SUM(balance) FROM accounts; +sum + +1000 +step rxy1: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wy2 wx1 rxy2 rxy1 c2 c1 +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step rxy2: SELECT SUM(balance) FROM accounts; +sum + +1000 +step rxy1: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wy2 wx1 rxy2 c2 rxy1 c1 +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step rxy2: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c2: COMMIT; +step rxy1: SELECT SUM(balance) FROM accounts; +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: wy2 rxy2 wx1 rxy1 c1 c2 +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step rxy2: SELECT SUM(balance) FROM accounts; +sum + +1000 +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step rxy1: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wy2 rxy2 wx1 rxy1 c2 c1 +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step rxy2: SELECT SUM(balance) FROM accounts; +sum + +1000 +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step rxy1: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c2: COMMIT; +step c1: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wy2 rxy2 wx1 c2 rxy1 c1 +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step rxy2: SELECT SUM(balance) FROM accounts; +sum + +1000 +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step c2: COMMIT; +step rxy1: SELECT SUM(balance) FROM accounts; +ERROR: could not serialize access due to read/write dependencies among transactions +step c1: COMMIT; + +starting permutation: wy2 rxy2 c2 wx1 rxy1 c1 +step wy2: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; +step rxy2: SELECT SUM(balance) FROM accounts; +sum + +1000 +step c2: COMMIT; +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; +step rxy1: SELECT SUM(balance) FROM accounts; +sum + +800 +step c1: COMMIT; diff --git a/src/test/isolation/expected/two-ids.out b/src/test/isolation/expected/two-ids.out new file mode 100644 index 0000000000..81e6139680 --- /dev/null +++ b/src/test/isolation/expected/two-ids.out @@ -0,0 +1,1007 @@ +Parsed test spec with 3 sessions + +starting permutation: wx1 c1 rxwy2 c2 ry3 c3 +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step ry3: select id from D2; +id + +3 +step c3: COMMIT; + +starting permutation: wx1 c1 rxwy2 ry3 c2 c3 +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: wx1 c1 rxwy2 ry3 c3 c2 +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: wx1 c1 ry3 rxwy2 c2 c3 +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: wx1 c1 ry3 rxwy2 c3 c2 +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: wx1 c1 ry3 c3 rxwy2 c2 +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; + +starting permutation: wx1 rxwy2 c1 c2 ry3 c3 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step c1: COMMIT; +step c2: COMMIT; +step ry3: select id from D2; +id + +2 +step c3: COMMIT; + +starting permutation: wx1 rxwy2 c1 ry3 c2 c3 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step c1: COMMIT; +step ry3: select id from D2; +id + +1 +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions +step c3: COMMIT; + +starting permutation: wx1 rxwy2 c1 ry3 c3 c2 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step c1: COMMIT; +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wx1 rxwy2 c2 c1 ry3 c3 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step c1: COMMIT; +step ry3: select id from D2; +id + +2 +step c3: COMMIT; + +starting permutation: wx1 rxwy2 c2 ry3 c1 c3 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step ry3: select id from D2; +id + +2 +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: wx1 rxwy2 c2 ry3 c3 c1 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step ry3: select id from D2; +id + +2 +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: wx1 rxwy2 ry3 c1 c2 c3 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions +step c3: COMMIT; + +starting permutation: wx1 rxwy2 ry3 c1 c3 c2 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wx1 rxwy2 ry3 c2 c1 c3 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: wx1 rxwy2 ry3 c2 c3 c1 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: wx1 rxwy2 ry3 c3 c1 c2 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: wx1 rxwy2 ry3 c3 c2 c1 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: wx1 ry3 c1 rxwy2 c2 c3 +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step c1: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: wx1 ry3 c1 rxwy2 c3 c2 +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step c1: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: wx1 ry3 c1 c3 rxwy2 c2 +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step c1: COMMIT; +step c3: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; + +starting permutation: wx1 ry3 rxwy2 c1 c2 c3 +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions +step c3: COMMIT; + +starting permutation: wx1 ry3 rxwy2 c1 c3 c2 +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: wx1 ry3 rxwy2 c2 c1 c3 +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: wx1 ry3 rxwy2 c2 c3 c1 +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: wx1 ry3 rxwy2 c3 c1 c2 +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: wx1 ry3 rxwy2 c3 c2 c1 +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: wx1 ry3 c3 c1 rxwy2 c2 +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step c1: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; + +starting permutation: wx1 ry3 c3 rxwy2 c1 c2 +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: wx1 ry3 c3 rxwy2 c2 c1 +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy2 wx1 c1 c2 ry3 c3 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step c2: COMMIT; +step ry3: select id from D2; +id + +2 +step c3: COMMIT; + +starting permutation: rxwy2 wx1 c1 ry3 c2 c3 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step ry3: select id from D2; +id + +1 +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions +step c3: COMMIT; + +starting permutation: rxwy2 wx1 c1 ry3 c3 c2 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rxwy2 wx1 c2 c1 ry3 c3 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step c2: COMMIT; +step c1: COMMIT; +step ry3: select id from D2; +id + +2 +step c3: COMMIT; + +starting permutation: rxwy2 wx1 c2 ry3 c1 c3 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step c2: COMMIT; +step ry3: select id from D2; +id + +2 +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy2 wx1 c2 ry3 c3 c1 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step c2: COMMIT; +step ry3: select id from D2; +id + +2 +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy2 wx1 ry3 c1 c2 c3 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions +step c3: COMMIT; + +starting permutation: rxwy2 wx1 ry3 c1 c3 c2 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rxwy2 wx1 ry3 c2 c1 c3 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy2 wx1 ry3 c2 c3 c1 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy2 wx1 ry3 c3 c1 c2 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy2 wx1 ry3 c3 c2 c1 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy2 c2 wx1 c1 ry3 c3 +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step ry3: select id from D2; +id + +2 +step c3: COMMIT; + +starting permutation: rxwy2 c2 wx1 ry3 c1 c3 +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +2 +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy2 c2 wx1 ry3 c3 c1 +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step wx1: update D1 set id = id + 1; +step ry3: select id from D2; +id + +2 +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy2 c2 ry3 wx1 c1 c3 +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step ry3: select id from D2; +id + +2 +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy2 c2 ry3 wx1 c3 c1 +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step ry3: select id from D2; +id + +2 +step wx1: update D1 set id = id + 1; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy2 c2 ry3 c3 wx1 c1 +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step ry3: select id from D2; +id + +2 +step c3: COMMIT; +step wx1: update D1 set id = id + 1; +step c1: COMMIT; + +starting permutation: rxwy2 ry3 wx1 c1 c2 c3 +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions +step c3: COMMIT; + +starting permutation: rxwy2 ry3 wx1 c1 c3 c2 +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: rxwy2 ry3 wx1 c2 c1 c3 +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy2 ry3 wx1 c2 c3 c1 +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy2 ry3 wx1 c3 c1 c2 +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy2 ry3 wx1 c3 c2 c1 +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy2 ry3 c2 wx1 c1 c3 +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step c2: COMMIT; +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: rxwy2 ry3 c2 wx1 c3 c1 +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step c2: COMMIT; +step wx1: update D1 set id = id + 1; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy2 ry3 c2 c3 wx1 c1 +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step c2: COMMIT; +step c3: COMMIT; +step wx1: update D1 set id = id + 1; +step c1: COMMIT; + +starting permutation: rxwy2 ry3 c3 wx1 c1 c2 +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: rxwy2 ry3 c3 wx1 c2 c1 +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step wx1: update D1 set id = id + 1; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: rxwy2 ry3 c3 c2 wx1 c1 +step rxwy2: update D2 set id = (select id+1 from D1); +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step c2: COMMIT; +step wx1: update D1 set id = id + 1; +step c1: COMMIT; + +starting permutation: ry3 wx1 c1 rxwy2 c2 c3 +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step c3: COMMIT; + +starting permutation: ry3 wx1 c1 rxwy2 c3 c2 +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step c3: COMMIT; +step c2: COMMIT; + +starting permutation: ry3 wx1 c1 c3 rxwy2 c2 +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step c3: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; + +starting permutation: ry3 wx1 rxwy2 c1 c2 c3 +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions +step c3: COMMIT; + +starting permutation: ry3 wx1 rxwy2 c1 c3 c2 +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry3 wx1 rxwy2 c2 c1 c3 +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: ry3 wx1 rxwy2 c2 c3 c1 +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: ry3 wx1 rxwy2 c3 c1 c2 +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: ry3 wx1 rxwy2 c3 c2 c1 +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: ry3 wx1 c3 c1 rxwy2 c2 +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step c3: COMMIT; +step c1: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; + +starting permutation: ry3 wx1 c3 rxwy2 c1 c2 +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step c3: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: ry3 wx1 c3 rxwy2 c2 c1 +step ry3: select id from D2; +id + +1 +step wx1: update D1 set id = id + 1; +step c3: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: ry3 rxwy2 wx1 c1 c2 c3 +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions +step c3: COMMIT; + +starting permutation: ry3 rxwy2 wx1 c1 c3 c2 +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step c3: COMMIT; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions + +starting permutation: ry3 rxwy2 wx1 c2 c1 c3 +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step c2: COMMIT; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: ry3 rxwy2 wx1 c2 c3 c1 +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step c2: COMMIT; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: ry3 rxwy2 wx1 c3 c1 c2 +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step c3: COMMIT; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: ry3 rxwy2 wx1 c3 c2 c1 +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step c3: COMMIT; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: ry3 rxwy2 c2 wx1 c1 c3 +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step c3: COMMIT; + +starting permutation: ry3 rxwy2 c2 wx1 c3 c1 +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step wx1: update D1 set id = id + 1; +step c3: COMMIT; +step c1: COMMIT; + +starting permutation: ry3 rxwy2 c2 c3 wx1 c1 +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step c3: COMMIT; +step wx1: update D1 set id = id + 1; +step c1: COMMIT; + +starting permutation: ry3 rxwy2 c3 wx1 c1 c2 +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step c3: COMMIT; +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: ry3 rxwy2 c3 wx1 c2 c1 +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step c3: COMMIT; +step wx1: update D1 set id = id + 1; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: ry3 rxwy2 c3 c2 wx1 c1 +step ry3: select id from D2; +id + +1 +step rxwy2: update D2 set id = (select id+1 from D1); +step c3: COMMIT; +step c2: COMMIT; +step wx1: update D1 set id = id + 1; +step c1: COMMIT; + +starting permutation: ry3 c3 wx1 c1 rxwy2 c2 +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; + +starting permutation: ry3 c3 wx1 rxwy2 c1 c2 +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: ry3 c3 wx1 rxwy2 c2 c1 +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step wx1: update D1 set id = id + 1; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: ry3 c3 rxwy2 wx1 c1 c2 +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step c1: COMMIT; +step c2: COMMIT; + +starting permutation: ry3 c3 rxwy2 wx1 c2 c1 +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step wx1: update D1 set id = id + 1; +step c2: COMMIT; +step c1: COMMIT; + +starting permutation: ry3 c3 rxwy2 c2 wx1 c1 +step ry3: select id from D2; +id + +1 +step c3: COMMIT; +step rxwy2: update D2 set id = (select id+1 from D1); +step c2: COMMIT; +step wx1: update D1 set id = id + 1; +step c1: COMMIT; diff --git a/src/test/isolation/isolation_main.c b/src/test/isolation/isolation_main.c new file mode 100644 index 0000000000..7d2cfa2ea8 --- /dev/null +++ b/src/test/isolation/isolation_main.c @@ -0,0 +1,89 @@ +/*------------------------------------------------------------------------- + * + * isolation_main --- pg_regress test launcher for isolation tests + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/test/isolation/isolation_main.c + * + *------------------------------------------------------------------------- + */ + +#include "../regress/pg_regress.h" + +/* + * start an isolation tester process for specified file (including + * redirection), and return process ID + */ +static PID_TYPE +isolation_start_test(const char *testname, + _stringlist ** resultfiles, + _stringlist ** expectfiles, + _stringlist ** tags) +{ + PID_TYPE pid; + char infile[MAXPGPATH]; + char outfile[MAXPGPATH]; + char expectfile[MAXPGPATH]; + char psql_cmd[MAXPGPATH * 3]; + size_t offset = 0; + + /* + * Look for files in the output dir first, consistent with a vpath search. + * This is mainly to create more reasonable error messages if the file is + * not found. It also allows local test overrides when running pg_regress + * outside of the source tree. + */ + snprintf(infile, sizeof(infile), "%s/specs/%s.spec", + outputdir, testname); + if (!file_exists(infile)) + snprintf(infile, sizeof(infile), "%s/specs/%s.spec", + inputdir, testname); + + snprintf(outfile, sizeof(outfile), "%s/results/%s.out", + outputdir, testname); + + snprintf(expectfile, sizeof(expectfile), "%s/expected/%s.out", + outputdir, testname); + if (!file_exists(expectfile)) + snprintf(expectfile, sizeof(expectfile), "%s/expected/%s.out", + inputdir, testname); + + add_stringlist_item(resultfiles, outfile); + add_stringlist_item(expectfiles, expectfile); + + if (launcher) + offset += snprintf(psql_cmd + offset, sizeof(psql_cmd) - offset, + "%s ", launcher); + + snprintf(psql_cmd + offset, sizeof(psql_cmd) - offset, + SYSTEMQUOTE "./isolationtester \"dbname=%s\" < \"%s\" > \"%s\" 2>&1" SYSTEMQUOTE, + dblist->str, + infile, + outfile); + + pid = spawn_process(psql_cmd); + + if (pid == INVALID_PID) + { + fprintf(stderr, _("could not start process for test %s\n"), + testname); + exit_nicely(2); + } + + return pid; +} + +static void +isolation_init(void) +{ + /* set default regression database name */ + add_stringlist_item(&dblist, "isolationtest"); +} + +int +main(int argc, char *argv[]) +{ + return regression_main(argc, argv, isolation_init, isolation_start_test); +} diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule new file mode 100644 index 0000000000..6ea8a29f49 --- /dev/null +++ b/src/test/isolation/isolation_schedule @@ -0,0 +1,11 @@ +test: simple-write-skew +test: receipt-report +test: temporal-range-integrity +test: project-manager +test: classroom-scheduling +test: total-cash +test: referential-integrity +test: ri-trigger +test: partial-index +test: two-ids +test: multiple-row-versions diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c new file mode 100644 index 0000000000..9946051945 --- /dev/null +++ b/src/test/isolation/isolationtester.c @@ -0,0 +1,372 @@ +/* + * src/test/isolation/isolationtester.c + * + * isolationtester.c + * Runs an isolation test specified by a spec file. + */ + +#ifdef WIN32 +#include +#endif + +#include +#include +#include +#include +#include "libpq-fe.h" + +#include "isolationtester.h" + +static PGconn **conns = NULL; +static int nconns = 0; + +static void run_all_permutations(TestSpec *testspec); +static void run_all_permutations_recurse(TestSpec *testspec, int nsteps, Step **steps); +static void run_named_permutations(TestSpec *testspec); +static void run_permutation(TestSpec *testspec, int nsteps, Step **steps); + +static int step_qsort_cmp(const void *a, const void *b); +static int step_bsearch_cmp(const void *a, const void *b); + +static void printResultSet(PGresult *res); + +/* close all connections and exit */ +static void +exit_nicely(void) +{ + int i; + for (i = 0; i < nconns; i++) + PQfinish(conns[i]); + exit(1); +} + +int +main(int argc, char **argv) +{ + const char *conninfo; + TestSpec *testspec; + int i; + + /* + * If the user supplies a parameter on the command line, use it as the + * conninfo string; otherwise default to setting dbname=postgres and + * using environment variables or defaults for all other connection + * parameters. + */ + if (argc > 1) + conninfo = argv[1]; + else + conninfo = "dbname = postgres"; + + /* Read the test spec from stdin */ + spec_yyparse(); + testspec = &parseresult; + printf("Parsed test spec with %d sessions\n", testspec->nsessions); + + /* Establish connections to the database, one for each session */ + nconns = testspec->nsessions; + conns = calloc(nconns, sizeof(PGconn *)); + for (i = 0; i < testspec->nsessions; i++) + { + PGresult *res; + + conns[i] = PQconnectdb(conninfo); + if (PQstatus(conns[i]) != CONNECTION_OK) + { + fprintf(stderr, "Connection %d to database failed: %s", + i, PQerrorMessage(conns[i])); + exit_nicely(); + } + + /* + * Suppress NOTIFY messages, which otherwise pop into results at odd + * places. + */ + res = PQexec(conns[i], "SET client_min_messages = warning;"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + fprintf(stderr, "message level setup failed: %s", PQerrorMessage(conns[i])); + exit_nicely(); + } + PQclear(res); + } + + /* Set the session index fields in steps. */ + for (i = 0; i < testspec->nsessions; i++) + { + Session *session = testspec->sessions[i]; + int stepindex; + for (stepindex = 0; stepindex < session->nsteps; stepindex++) + session->steps[stepindex]->session = i; + } + + /* + * Run the permutations specified in the spec, or all if none were + * explicitly specified. + */ + if (testspec->permutations) + run_named_permutations(testspec); + else + run_all_permutations(testspec); + + /* Clean up and exit */ + for (i = 0; i < nconns; i++) + PQfinish(conns[i]); + return 0; +} + +static int *piles; + +/* + * Run all permutations of the steps and sessions. + */ +static void +run_all_permutations(TestSpec *testspec) +{ + int nsteps; + int i; + Step **steps; + + /* Count the total number of steps in all sessions */ + nsteps = 0; + for (i = 0; i < testspec->nsessions; i++) + nsteps += testspec->sessions[i]->nsteps; + + steps = malloc(sizeof(Step *) * nsteps); + + /* + * To generate the permutations, we conceptually put the steps of + * each session on a pile. To generate a permuation, we pick steps + * from the piles until all piles are empty. By picking steps from + * piles in different order, we get different permutations. + * + * A pile is actually just an integer which tells how many steps + * we've already picked from this pile. + */ + piles = malloc(sizeof(int) * testspec->nsessions); + for (i = 0; i < testspec->nsessions; i++) + piles[i] = 0; + + run_all_permutations_recurse(testspec, 0, steps); +} + +static void +run_all_permutations_recurse(TestSpec *testspec, int nsteps, Step **steps) +{ + int i; + int found = 0; + + for (i = 0; i < testspec->nsessions; i++) + { + /* If there's any more steps in this pile, pick it and recurse */ + if (piles[i] < testspec->sessions[i]->nsteps) + { + steps[nsteps] = testspec->sessions[i]->steps[piles[i]]; + piles[i]++; + + run_all_permutations_recurse(testspec, nsteps + 1, steps); + + piles[i]--; + + found = 1; + } + } + + /* If all the piles were empty, this permutation is completed. Run it */ + if (!found) + run_permutation(testspec, nsteps, steps); +} + +/* + * Run permutations given in the test spec + */ +static void +run_named_permutations(TestSpec *testspec) +{ + int i, j; + int n; + int nallsteps; + Step **allsteps; + + /* First create a lookup table of all steps */ + nallsteps = 0; + for (i = 0; i < testspec->nsessions; i++) + nallsteps += testspec->sessions[i]->nsteps; + + allsteps = malloc(nallsteps * sizeof(Step *)); + + n = 0; + for (i = 0; i < testspec->nsessions; i++) + { + for (j = 0; j < testspec->sessions[i]->nsteps; j++) + allsteps[n++] = testspec->sessions[i]->steps[j]; + } + + qsort(allsteps, nallsteps, sizeof(Step *), &step_qsort_cmp); + + for (i = 0; i < testspec->npermutations; i++) + { + Permutation *p = testspec->permutations[i]; + Step **steps; + + steps = malloc(p->nsteps * sizeof(Step *)); + + /* Find all the named steps from the lookup table */ + for (j = 0; j < p->nsteps; j++) + { + steps[j] = *((Step **) bsearch(p->stepnames[j], allsteps, nallsteps, + sizeof(Step *), &step_bsearch_cmp)); + if (steps[j] == NULL) + { + fprintf(stderr, "undefined step \"%s\" specified in permutation\n", p->stepnames[j]); + exit_nicely(); + } + } + + run_permutation(testspec, p->nsteps, steps); + free(steps); + } +} + +static int +step_qsort_cmp(const void *a, const void *b) +{ + Step *stepa = *((Step **) a); + Step *stepb = *((Step **) b); + + return strcmp(stepa->name, stepb->name); +} + +static int +step_bsearch_cmp(const void *a, const void *b) +{ + char *stepname = (char *) a; + Step *step = *((Step **) b); + + return strcmp(stepname, step->name); +} + +/* + * Run one permutation + */ +static void +run_permutation(TestSpec *testspec, int nsteps, Step **steps) +{ + PGresult *res; + int i; + + printf("\nstarting permutation:"); + for (i = 0; i < nsteps; i++) + printf(" %s", steps[i]->name); + printf("\n"); + + /* Perform setup */ + if (testspec->setupsql) + { + res = PQexec(conns[0], testspec->setupsql); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + fprintf(stderr, "setup failed: %s", PQerrorMessage(conns[0])); + exit_nicely(); + } + PQclear(res); + } + + /* Perform per-session setup */ + for (i = 0; i < testspec->nsessions; i++) + { + if (testspec->sessions[i]->setupsql) + { + res = PQexec(conns[i], testspec->sessions[i]->setupsql); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + fprintf(stderr, "setup of session %s failed: %s", + testspec->sessions[i]->name, + PQerrorMessage(conns[0])); + exit_nicely(); + } + PQclear(res); + } + } + + /* Perform steps */ + for (i = 0; i < nsteps; i++) + { + Step *step = steps[i]; + printf("step %s: %s\n", step->name, step->sql); + res = PQexec(conns[step->session], step->sql); + + switch(PQresultStatus(res)) + { + case PGRES_COMMAND_OK: + break; + + case PGRES_TUPLES_OK: + printResultSet(res); + break; + + case PGRES_FATAL_ERROR: + /* Detail may contain xid values, so just show primary. */ + printf("%s: %s\n", PQresultErrorField(res, PG_DIAG_SEVERITY), + PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY)); + break; + + default: + printf("unexpected result status: %s\n", + PQresStatus(PQresultStatus(res))); + } + PQclear(res); + } + + /* Perform per-session teardown */ + for (i = 0; i < testspec->nsessions; i++) + { + if (testspec->sessions[i]->teardownsql) + { + res = PQexec(conns[i], testspec->sessions[i]->teardownsql); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + fprintf(stderr, "teardown of session %s failed: %s", + testspec->sessions[i]->name, + PQerrorMessage(conns[0])); + /* don't exit on teardown failure */ + } + PQclear(res); + } + } + + /* Perform teardown */ + if (testspec->teardownsql) + { + res = PQexec(conns[0], testspec->teardownsql); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + fprintf(stderr, "teardown failed: %s", + PQerrorMessage(conns[0])); + /* don't exit on teardown failure */ + + } + PQclear(res); + } +} + +static void +printResultSet(PGresult *res) +{ + int nFields; + int i, j; + + /* first, print out the attribute names */ + nFields = PQnfields(res); + for (i = 0; i < nFields; i++) + printf("%-15s", PQfname(res, i)); + printf("\n\n"); + + /* next, print out the rows */ + for (i = 0; i < PQntuples(res); i++) + { + for (j = 0; j < nFields; j++) + printf("%-15s", PQgetvalue(res, i, j)); + printf("\n"); + } +} diff --git a/src/test/isolation/isolationtester.h b/src/test/isolation/isolationtester.h new file mode 100644 index 0000000000..b092ed16a0 --- /dev/null +++ b/src/test/isolation/isolationtester.h @@ -0,0 +1,59 @@ +/*------------------------------------------------------------------------- + * + * bootstrap.h + * include file for the bootstrapping code + * + * + * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL: pgsql/src/include/bootstrap/bootstrap.h,v 1.44 2006/10/04 00:30:07 momjian Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef ISOLATIONTESTER_H +#define ISOLATIONTESTER_H + +typedef struct Session Session; +typedef struct Step Step; + +struct Session +{ + char *name; + char *setupsql; + char *teardownsql; + Step **steps; + int nsteps; +}; + +struct Step +{ + int session; + char *name; + char *sql; +}; + +typedef struct +{ + int nsteps; + char **stepnames; +} Permutation; + +typedef struct +{ + char *setupsql; + char *teardownsql; + Session **sessions; + int nsessions; + Permutation **permutations; + int npermutations; +} TestSpec; + +extern TestSpec parseresult; + +extern int spec_yyparse(void); + +extern int spec_yylex(void); +extern void spec_yyerror(const char *str); + +#endif /* ISOLATIONTESTER_H */ diff --git a/src/test/isolation/specparse.y b/src/test/isolation/specparse.y new file mode 100644 index 0000000000..c684780216 --- /dev/null +++ b/src/test/isolation/specparse.y @@ -0,0 +1,188 @@ +%{ +/*------------------------------------------------------------------------- + * + * specparse.y + * bison grammar for the isolation test file format + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + *------------------------------------------------------------------------- + */ + +#include +#include +#include +#include + +#include "isolationtester.h" + + +TestSpec parseresult; /* result of parsing is left here */ + +%} + +%expect 0 +%name-prefix="spec_yy" + +%union +{ + char *str; + Session *session; + Step *step; + Permutation *permutation; + struct + { + void **elements; + int nelements; + } ptr_list; +} + +%type opt_setup opt_teardown +%type step_list session_list permutation_list opt_permutation_list +%type string_list +%type session +%type step +%type permutation + +%token sqlblock string +%token PERMUTATION SESSION SETUP STEP TEARDOWN TEST + +%% + +TestSpec: + opt_setup + opt_teardown + session_list + opt_permutation_list + { + parseresult.setupsql = $1; + parseresult.teardownsql = $2; + parseresult.sessions = (Session **) $3.elements; + parseresult.nsessions = $3.nelements; + parseresult.permutations = (Permutation **) $4.elements; + parseresult.npermutations = $4.nelements; + } + ; + +opt_setup: + /* EMPTY */ { $$ = NULL; } + | SETUP sqlblock { $$ = $2; } + ; + +opt_teardown: + /* EMPTY */ { $$ = NULL; } + | TEARDOWN sqlblock { $$ = $2; } + ; + +session_list: + session_list session + { + $$.elements = realloc($1.elements, + ($1.nelements + 1) * sizeof(void *)); + $$.elements[$1.nelements] = $2; + $$.nelements = $1.nelements + 1; + } + | session + { + $$.nelements = 1; + $$.elements = malloc(sizeof(void *)); + $$.elements[0] = $1; + } + ; + +session: + SESSION string opt_setup step_list opt_teardown + { + $$ = malloc(sizeof(Session)); + $$->name = $2; + $$->setupsql = $3; + $$->steps = (Step **) $4.elements; + $$->nsteps = $4.nelements; + $$->teardownsql = $5; + } + ; + +step_list: + step_list step + { + $$.elements = realloc($1.elements, + ($1.nelements + 1) * sizeof(void *)); + $$.elements[$1.nelements] = $2; + $$.nelements = $1.nelements + 1; + } + | step + { + $$.nelements = 1; + $$.elements = malloc(sizeof(void *)); + $$.elements[0] = $1; + } + ; + + +step: + STEP string sqlblock + { + $$ = malloc(sizeof(Step)); + $$->name = $2; + $$->sql = $3; + } + ; + + +opt_permutation_list: + permutation_list + { + $$ = $1; + } + | /* EMPTY */ + { + $$.elements = NULL; + $$.nelements = 0; + } + +permutation_list: + permutation_list permutation + { + $$.elements = realloc($1.elements, + ($1.nelements + 1) * sizeof(void *)); + $$.elements[$1.nelements] = $2; + $$.nelements = $1.nelements + 1; + } + | permutation + { + $$.nelements = 1; + $$.elements = malloc(sizeof(void *)); + $$.elements[0] = $1; + } + ; + + +permutation: + PERMUTATION string_list + { + $$ = malloc(sizeof(Permutation)); + $$->stepnames = (char **) $2.elements; + $$->nsteps = $2.nelements; + } + ; + +string_list: + string_list string + { + $$.elements = realloc($1.elements, + ($1.nelements + 1) * sizeof(void *)); + $$.elements[$1.nelements] = $2; + $$.nelements = $1.nelements + 1; + } + | string + { + $$.nelements = 1; + $$.elements = malloc(sizeof(void *)); + $$.elements[0] = $1; + } + ; + +%% + +#include "specscanner.c" diff --git a/src/test/isolation/specs/classroom-scheduling.spec b/src/test/isolation/specs/classroom-scheduling.spec new file mode 100644 index 0000000000..a31565b9cd --- /dev/null +++ b/src/test/isolation/specs/classroom-scheduling.spec @@ -0,0 +1,29 @@ +# Classroom Scheduling test +# +# Ensure that the classroom is not scheduled more than once +# for any moment in time. +# +# Any overlap between the transactions must cause a serialization failure. + +setup +{ + CREATE TABLE room_reservation (room_id text NOT NULL, start_time timestamp with time zone NOT NULL, end_time timestamp with time zone NOT NULL, description text NOT NULL, CONSTRAINT room_reservation_pkey PRIMARY KEY (room_id, start_time)); + INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 10:00', TIMESTAMP WITH TIME ZONE '2010-04-01 11:00', 'Bob'); +} + +teardown +{ + DROP TABLE room_reservation; +} + +session "s1" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "rx1" { SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:00' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:00'; } +step "wy1" { INSERT INTO room_reservation VALUES ('101', TIMESTAMP WITH TIME ZONE '2010-04-01 13:00', TIMESTAMP WITH TIME ZONE '2010-04-01 14:00', 'Carol'); } +step "c1" { COMMIT; } + +session "s2" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "ry2" { SELECT count(*) FROM room_reservation WHERE room_id = '101' AND start_time < TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' AND end_time > TIMESTAMP WITH TIME ZONE '2010-04-01 13:30'; } +step "wx2" { UPDATE room_reservation SET start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 13:30', end_time = TIMESTAMP WITH TIME ZONE '2010-04-01 14:30' WHERE room_id = '101' AND start_time = TIMESTAMP WITH TIME ZONE '2010-04-01 10:00'; } +step "c2" { COMMIT; } diff --git a/src/test/isolation/specs/multiple-row-versions.spec b/src/test/isolation/specs/multiple-row-versions.spec new file mode 100644 index 0000000000..8cfe3a44dc --- /dev/null +++ b/src/test/isolation/specs/multiple-row-versions.spec @@ -0,0 +1,48 @@ +# Multiple Row Versions test +# +# This test is designed to ensure that predicate locks taken on one version +# of a row are detected as conflicts when a later version of the row is +# updated or deleted by a transaction concurrent to the reader. +# +# Due to long permutation setup time, we are only testing one specific +# permutation, which should get a serialization error. + +setup +{ + CREATE TABLE t (id int NOT NULL, txt text) WITH (fillfactor=50); + INSERT INTO t (id) + SELECT x FROM (SELECT * FROM generate_series(1, 1000000)) a(x); + ALTER TABLE t ADD PRIMARY KEY (id); +} + +teardown +{ + DROP TABLE t; +} + +session "s1" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "rx1" { SELECT * FROM t WHERE id = 1000000; } +# delay until after T3 commits +step "wz1" { UPDATE t SET txt = 'a' WHERE id = 1; } +step "c1" { COMMIT; } + +session "s2" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "wx2" { UPDATE t SET txt = 'b' WHERE id = 1000000; } +step "c2" { COMMIT; } + +session "s3" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "wx3" { UPDATE t SET txt = 'c' WHERE id = 1000000; } +step "ry3" { SELECT * FROM t WHERE id = 500000; } +# delay until after T4 commits +step "c3" { COMMIT; } + +session "s4" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "wy4" { UPDATE t SET txt = 'd' WHERE id = 500000; } +step "rz4" { SELECT * FROM t WHERE id = 1; } +step "c4" { COMMIT; } + +permutation "rx1" "wx2" "c2" "wx3" "ry3" "wy4" "rz4" "c4" "c3" "wz1" "c1" diff --git a/src/test/isolation/specs/partial-index.spec b/src/test/isolation/specs/partial-index.spec new file mode 100644 index 0000000000..74e72645d9 --- /dev/null +++ b/src/test/isolation/specs/partial-index.spec @@ -0,0 +1,32 @@ +# Partial Index test +# +# Make sure that an update which moves a row out of a partial index +# is handled correctly. In early versions, an attempt at optimization +# broke this behavior, allowing anomalies. +# +# Any overlap between the transactions must cause a serialization failure. + +setup +{ + create table test_t (id integer, val1 text, val2 integer); + create index test_idx on test_t(id) where val2 = 1; + insert into test_t (select generate_series(0, 10000), 'a', 2); + insert into test_t (select generate_series(0, 10), 'a', 1); +} + +teardown +{ + DROP TABLE test_t; +} + +session "s1" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "rxy1" { select * from test_t where val2 = 1; } +step "wx1" { update test_t set val2 = 2 where val2 = 1 and id = 10; } +step "c1" { COMMIT; } + +session "s2" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "wy2" { update test_t set val2 = 2 where val2 = 1 and id = 9; } +step "rxy2" { select * from test_t where val2 = 1; } +step "c2" { COMMIT; } diff --git a/src/test/isolation/specs/project-manager.spec b/src/test/isolation/specs/project-manager.spec new file mode 100644 index 0000000000..884012dd89 --- /dev/null +++ b/src/test/isolation/specs/project-manager.spec @@ -0,0 +1,30 @@ +# Project Manager test +# +# Ensure that the person who is on the project as a manager +# is flagged as a project manager in the person table. +# +# Any overlap between the transactions must cause a serialization failure. + +setup +{ + CREATE TABLE person (person_id int NOT NULL PRIMARY KEY, name text NOT NULL, is_project_manager bool NOT NULL); + INSERT INTO person VALUES (1, 'Robert Haas', true); + CREATE TABLE project (project_no int NOT NULL PRIMARY KEY, description text NOT NULL, project_manager int NOT NULL); +} + +teardown +{ + DROP TABLE person, project; +} + +session "s1" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "rx1" { SELECT count(*) FROM person WHERE person_id = 1 AND is_project_manager; } +step "wy1" { INSERT INTO project VALUES (101, 'Build Great Wall', 1); } +step "c1" { COMMIT; } + +session "s2" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "ry2" { SELECT count(*) FROM project WHERE project_manager = 1; } +step "wx2" { UPDATE person SET is_project_manager = false WHERE person_id = 1; } +step "c2" { COMMIT; } diff --git a/src/test/isolation/specs/receipt-report.spec b/src/test/isolation/specs/receipt-report.spec new file mode 100644 index 0000000000..1e214960d1 --- /dev/null +++ b/src/test/isolation/specs/receipt-report.spec @@ -0,0 +1,47 @@ +# Daily Report of Receipts test. +# +# This test doesn't persist a bad state in the database; rather, it +# provides a view of the data which is not consistent with any +# order of execution of the serializable transactions. It +# demonstrates a situation where the deposit date for receipts could +# be changed and a report of the closed day's receipts subsequently +# run which will miss a receipt from the date which has been closed. +# +# There are only six permuations which must cause a serialization failure. +# Failure cases are where s1 overlaps both s2 and s3, but s2 commits before +# s3 executes its first SELECT. +# +# As long as s3 is declared READ ONLY there should be no false positives. +# If s3 were changed to READ WRITE, we would currently expect 42 false +# positives. Further work dealing with de facto READ ONLY transactions +# may be able to reduce or eliminate those false positives. + +setup +{ + CREATE TABLE ctl (k text NOT NULL PRIMARY KEY, deposit_date date NOT NULL); + INSERT INTO ctl VALUES ('receipt', DATE '2008-12-22'); + CREATE TABLE receipt (receipt_no int NOT NULL PRIMARY KEY, deposit_date date NOT NULL, amount numeric(13,2)); + INSERT INTO receipt VALUES (1, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 1.00); + INSERT INTO receipt VALUES (2, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 2.00); +} + +teardown +{ + DROP TABLE ctl, receipt; +} + +session "s1" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "rxwy1" { INSERT INTO receipt VALUES (3, (SELECT deposit_date FROM ctl WHERE k = 'receipt'), 4.00); } +step "c1" { COMMIT; } + +session "s2" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "wx2" { UPDATE ctl SET deposit_date = DATE '2008-12-23' WHERE k = 'receipt'; } +step "c2" { COMMIT; } + +session "s3" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE, READ ONLY; } +step "rx3" { SELECT * FROM ctl WHERE k = 'receipt'; } +step "ry3" { SELECT * FROM receipt WHERE deposit_date = DATE '2008-12-22'; } +step "c3" { COMMIT; } diff --git a/src/test/isolation/specs/referential-integrity.spec b/src/test/isolation/specs/referential-integrity.spec new file mode 100644 index 0000000000..0bea3eab97 --- /dev/null +++ b/src/test/isolation/specs/referential-integrity.spec @@ -0,0 +1,32 @@ +# Referential Integrity test +# +# The assumption here is that the application code issuing the SELECT +# to test for the presence or absence of a related record would do the +# right thing -- this script doesn't include that logic. +# +# Any overlap between the transactions must cause a serialization failure. + +setup +{ + CREATE TABLE a (i int PRIMARY KEY); + CREATE TABLE b (a_id int); + INSERT INTO a VALUES (1); +} + +teardown +{ + DROP TABLE a, b; +} + +session "s1" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "rx1" { SELECT i FROM a WHERE i = 1; } +step "wy1" { INSERT INTO b VALUES (1); } +step "c1" { COMMIT; } + +session "s2" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "rx2" { SELECT i FROM a WHERE i = 1; } +step "ry2" { SELECT a_id FROM b WHERE a_id = 1; } +step "wx2" { DELETE FROM a WHERE i = 1; } +step "c2" { COMMIT; } diff --git a/src/test/isolation/specs/ri-trigger.spec b/src/test/isolation/specs/ri-trigger.spec new file mode 100644 index 0000000000..78d1f2f226 --- /dev/null +++ b/src/test/isolation/specs/ri-trigger.spec @@ -0,0 +1,53 @@ +# RI Trigger test +# +# Test trigger-based referential integrity enforcement. +# +# Any overlap between the transactions must cause a serialization failure. + +setup +{ + CREATE TABLE parent (parent_id SERIAL NOT NULL PRIMARY KEY); + CREATE TABLE child (child_id SERIAL NOT NULL PRIMARY KEY, parent_id INTEGER NOT NULL); + CREATE FUNCTION ri_parent() RETURNS TRIGGER LANGUAGE PLPGSQL AS $body$ + BEGIN + PERFORM TRUE FROM child WHERE parent_id = OLD.parent_id; + IF FOUND THEN + RAISE SQLSTATE '23503' USING MESSAGE = 'child row exists'; + END IF; + IF TG_OP = 'DELETE' THEN + RETURN OLD; + END IF; + RETURN NEW; + END; + $body$; + CREATE TRIGGER ri_parent BEFORE UPDATE OR DELETE ON parent FOR EACH ROW EXECUTE PROCEDURE ri_parent(); + CREATE FUNCTION ri_child() RETURNS TRIGGER LANGUAGE PLPGSQL AS $body$ + BEGIN + PERFORM TRUE FROM parent WHERE parent_id = NEW.parent_id; + IF NOT FOUND THEN + RAISE SQLSTATE '23503' USING MESSAGE = 'parent row missing'; + END IF; + RETURN NEW; + END; + $body$; + CREATE TRIGGER ri_child BEFORE INSERT OR UPDATE ON child FOR EACH ROW EXECUTE PROCEDURE ri_child(); + INSERT INTO parent VALUES(0); +} + +teardown +{ + DROP TABLE parent, child; + DROP FUNCTION ri_parent(); + DROP FUNCTION ri_child(); +} + +session "s1" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "wxry1" { INSERT INTO child (parent_id) VALUES (0); } +step "c1" { COMMIT; } + +session "s2" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "r2" { SELECT TRUE; } +step "wyrx2" { DELETE FROM parent WHERE parent_id = 0; } +step "c2" { COMMIT; } diff --git a/src/test/isolation/specs/simple-write-skew.spec b/src/test/isolation/specs/simple-write-skew.spec new file mode 100644 index 0000000000..0aee43f38e --- /dev/null +++ b/src/test/isolation/specs/simple-write-skew.spec @@ -0,0 +1,30 @@ +# Write skew test. +# +# This test has two serializable transactions: one which updates all +# 'apple' rows to 'pear' and one which updates all 'pear' rows to +# 'apple'. If these were serialized (run one at a time) either +# value could be present, but not both. One must be rolled back to +# prevent the write skew anomaly. +# +# Any overlap between the transactions must cause a serialization failure. + +setup +{ + CREATE TABLE test (i int PRIMARY KEY, t text); + INSERT INTO test VALUES (5, 'apple'), (7, 'pear'), (11, 'banana'); +} + +teardown +{ + DROP TABLE test; +} + +session "s1" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "rwx1" { UPDATE test SET t = 'apple' WHERE t = 'pear'; } +step "c1" { COMMIT; } + +session "s2" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "rwx2" { UPDATE test SET t = 'pear' WHERE t = 'apple'} +step "c2" { COMMIT; } diff --git a/src/test/isolation/specs/temporal-range-integrity.spec b/src/test/isolation/specs/temporal-range-integrity.spec new file mode 100644 index 0000000000..63784ce0a7 --- /dev/null +++ b/src/test/isolation/specs/temporal-range-integrity.spec @@ -0,0 +1,38 @@ +# Temporal Range Integrity test +# +# Snapshot integrity fails with simple referential integrity tests, +# but those don't make for good demonstrations because people just +# say that foreign key definitions should be used instead. There +# are many integrity tests which are conceptually very similar but +# don't have built-in support which will fail when used in triggers. +# This is intended to illustrate such cases. It is obviously very +# hard to exercise all these permutations when the code is actually +# in a trigger; this test pulls what would normally be inside of +# triggers out to the top level to control the permutations. +# +# Any overlap between the transactions must cause a serialization failure. + + +setup +{ + CREATE TABLE statute (statute_cite text NOT NULL, eff_date date NOT NULL, exp_date date, CONSTRAINT statute_pkey PRIMARY KEY (statute_cite, eff_date)); + INSERT INTO statute VALUES ('123.45(1)a', DATE '2008-01-01', NULL); + CREATE TABLE offense (offense_no int NOT NULL, statute_cite text NOT NULL, offense_date date NOT NULL, CONSTRAINT offense_pkey PRIMARY KEY (offense_no)); +} + +teardown +{ + DROP TABLE statute, offense; +} + +session "s1" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "rx1" { SELECT count(*) FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date <= DATE '2009-05-15' AND (exp_date IS NULL OR exp_date > DATE '2009-05-15'); } +step "wy1" { INSERT INTO offense VALUES (1, '123.45(1)a', DATE '2009-05-15'); } +step "c1" { COMMIT; } + +session "s2" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "ry2" { SELECT count(*) FROM offense WHERE statute_cite = '123.45(1)a' AND offense_date >= DATE '2008-01-01'; } +step "wx2" { DELETE FROM statute WHERE statute_cite = '123.45(1)a' AND eff_date = DATE '2008-01-01'; } +step "c2" { COMMIT; } diff --git a/src/test/isolation/specs/total-cash.spec b/src/test/isolation/specs/total-cash.spec new file mode 100644 index 0000000000..843f41c77f --- /dev/null +++ b/src/test/isolation/specs/total-cash.spec @@ -0,0 +1,28 @@ +# Total Cash test +# +# Another famous test of snapshot isolation anomaly. +# +# Any overlap between the transactions must cause a serialization failure. + +setup +{ + CREATE TABLE accounts (accountid text NOT NULL PRIMARY KEY, balance numeric not null); + INSERT INTO accounts VALUES ('checking', 600),('savings',600); +} + +teardown +{ + DROP TABLE accounts; +} + +session "s1" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "wx1" { UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; } +step "rxy1" { SELECT SUM(balance) FROM accounts; } +step "c1" { COMMIT; } + +session "s2" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "wy2" { UPDATE accounts SET balance = balance - 200 WHERE accountid = 'savings'; } +step "rxy2" { SELECT SUM(balance) FROM accounts; } +step "c2" { COMMIT; } diff --git a/src/test/isolation/specs/two-ids.spec b/src/test/isolation/specs/two-ids.spec new file mode 100644 index 0000000000..d67064068e --- /dev/null +++ b/src/test/isolation/specs/two-ids.spec @@ -0,0 +1,40 @@ +# Two IDs test +# +# Small, simple test showing read-only anomalies. +# +# There are only four permuations which must cause a serialization failure. +# Required failure cases are where s2 overlaps both s1 and s3, but s1 +# commits before s3 executes its first SELECT. +# +# If s3 were declared READ ONLY there would be no false positives. +# With s3 defaulting to READ WRITE, we currently expect 12 false +# positives. Further work dealing with de facto READ ONLY transactions +# may be able to reduce or eliminate those false positives. + +setup +{ + create table D1 (id int not null); + create table D2 (id int not null); + insert into D1 values (1); + insert into D2 values (1); +} + +teardown +{ + DROP TABLE D1, D2; +} + +session "s1" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "wx1" { update D1 set id = id + 1; } +step "c1" { COMMIT; } + +session "s2" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "rxwy2" { update D2 set id = (select id+1 from D1); } +step "c2" { COMMIT; } + +session "s3" +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "ry3" { select id from D2; } +step "c3" { COMMIT; } diff --git a/src/test/isolation/specscanner.l b/src/test/isolation/specscanner.l new file mode 100644 index 0000000000..6752aca82d --- /dev/null +++ b/src/test/isolation/specscanner.l @@ -0,0 +1,103 @@ +%{ +/*------------------------------------------------------------------------- + * + * specscanner.l + * a lexical scanner for an isolation test specification + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + *------------------------------------------------------------------------- + */ + +static int yyline = 1; /* line number for error reporting */ + +static char litbuf[1024]; +static int litbufpos = 0; + +static void addlitchar(const char c); + +%} + +%option 8bit +%option never-interactive +%option nodefault +%option noinput +%option nounput +%option noyywrap +%option prefix="spec_yy" + + +%x sql +%x qstr + +non_newline [^\n\r] +space [ \t\n\r\f] + +comment ("#"{non_newline}*) +whitespace ({space}+|{comment}) + +%% + +permutation { return(PERMUTATION); } +session { return(SESSION); } +setup { return(SETUP); } +step { return(STEP); } +teardown { return(TEARDOWN); } + +[\n] { yyline++; } +{whitespace} { + /* ignore */ + } + +\" { + litbufpos = 0; + BEGIN(qstr); + } +\" { + litbuf[litbufpos] = '\0'; + yylval.str = strdup(litbuf); + BEGIN(INITIAL); + return(string); + } +. { addlitchar(yytext[0]); } + +"{" { + + litbufpos = 0; + BEGIN(sql); + } + +"}" { + litbuf[litbufpos] = '\0'; + yylval.str = strdup(litbuf); + BEGIN(INITIAL); + return(sqlblock); + } +[^}] { addlitchar(yytext[0]);} + + +. { + fprintf(stderr, "syntax error at line %d: unexpected character \"%s\"\n", yyline, yytext); + exit(1); + } + +%% + +static void +addlitchar(const char c) +{ + if (litbufpos >= sizeof(litbuf) - 1) + { + fprintf(stderr, "SQL step too long\n"); + exit(1); + } + litbuf[litbufpos++] = c; +} + +void +yyerror(const char *message) +{ + fprintf(stderr, "%s at line %d\n", message, yyline); + exit(1); +} diff --git a/src/test/regress/expected/prepared_xacts.out b/src/test/regress/expected/prepared_xacts.out index 292962ab7b..1a6b4ce1d9 100644 --- a/src/test/regress/expected/prepared_xacts.out +++ b/src/test/regress/expected/prepared_xacts.out @@ -9,7 +9,7 @@ CREATE TABLE pxtest1 (foobar VARCHAR(10)); INSERT INTO pxtest1 VALUES ('aaa'); -- Test PREPARE TRANSACTION -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; UPDATE pxtest1 SET foobar = 'bbb' WHERE foobar = 'aaa'; SELECT * FROM pxtest1; foobar @@ -45,7 +45,7 @@ SELECT gid FROM pg_prepared_xacts; (0 rows) -- Test COMMIT PREPARED -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; INSERT INTO pxtest1 VALUES ('ddd'); SELECT * FROM pxtest1; foobar @@ -70,7 +70,7 @@ SELECT * FROM pxtest1; (2 rows) -- Test duplicate gids -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd'; SELECT * FROM pxtest1; foobar @@ -86,7 +86,7 @@ SELECT gid FROM pg_prepared_xacts; foo3 (1 row) -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; INSERT INTO pxtest1 VALUES ('fff'); SELECT * FROM pxtest1; foobar @@ -117,7 +117,7 @@ SELECT * FROM pxtest1; -- Clean up DROP TABLE pxtest1; -- Test subtransactions -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; CREATE TABLE pxtest2 (a int); INSERT INTO pxtest2 VALUES (1); SAVEPOINT a; @@ -128,7 +128,7 @@ BEGIN; PREPARE TRANSACTION 'regress-one'; CREATE TABLE pxtest3(fff int); -- Test shared invalidation -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; DROP TABLE pxtest3; CREATE TABLE pxtest4 (a int); INSERT INTO pxtest4 VALUES (1); diff --git a/src/test/regress/expected/prepared_xacts_1.out b/src/test/regress/expected/prepared_xacts_1.out index 991a11bdfa..5051eaabd7 100644 --- a/src/test/regress/expected/prepared_xacts_1.out +++ b/src/test/regress/expected/prepared_xacts_1.out @@ -9,7 +9,7 @@ CREATE TABLE pxtest1 (foobar VARCHAR(10)); INSERT INTO pxtest1 VALUES ('aaa'); -- Test PREPARE TRANSACTION -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; UPDATE pxtest1 SET foobar = 'bbb' WHERE foobar = 'aaa'; SELECT * FROM pxtest1; foobar @@ -47,7 +47,7 @@ SELECT gid FROM pg_prepared_xacts; (0 rows) -- Test COMMIT PREPARED -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; INSERT INTO pxtest1 VALUES ('ddd'); SELECT * FROM pxtest1; foobar @@ -74,7 +74,7 @@ SELECT * FROM pxtest1; (1 row) -- Test duplicate gids -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd'; SELECT * FROM pxtest1; foobar @@ -90,7 +90,7 @@ SELECT gid FROM pg_prepared_xacts; ----- (0 rows) -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; INSERT INTO pxtest1 VALUES ('fff'); SELECT * FROM pxtest1; foobar @@ -120,7 +120,7 @@ SELECT * FROM pxtest1; -- Clean up DROP TABLE pxtest1; -- Test subtransactions -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; CREATE TABLE pxtest2 (a int); INSERT INTO pxtest2 VALUES (1); SAVEPOINT a; @@ -133,7 +133,7 @@ ERROR: prepared transactions are disabled HINT: Set max_prepared_transactions to a nonzero value. CREATE TABLE pxtest3(fff int); -- Test shared invalidation -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; DROP TABLE pxtest3; CREATE TABLE pxtest4 (a int); INSERT INTO pxtest4 VALUES (1); diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out index b91c9d8e23..f49ec0effe 100644 --- a/src/test/regress/expected/transactions.out +++ b/src/test/regress/expected/transactions.out @@ -44,7 +44,7 @@ SELECT * FROM aggtest; CREATE TABLE writetest (a int); CREATE TEMPORARY TABLE temptest (a int); BEGIN; -SET TRANSACTION READ ONLY; -- ok +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ ONLY, DEFERRABLE; -- ok SELECT * FROM writetest; -- ok a --- diff --git a/src/test/regress/sql/prepared_xacts.sql b/src/test/regress/sql/prepared_xacts.sql index 39d323a15b..2bdbb0d189 100644 --- a/src/test/regress/sql/prepared_xacts.sql +++ b/src/test/regress/sql/prepared_xacts.sql @@ -14,7 +14,7 @@ INSERT INTO pxtest1 VALUES ('aaa'); -- Test PREPARE TRANSACTION -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; UPDATE pxtest1 SET foobar = 'bbb' WHERE foobar = 'aaa'; SELECT * FROM pxtest1; PREPARE TRANSACTION 'foo1'; @@ -33,7 +33,7 @@ SELECT gid FROM pg_prepared_xacts; -- Test COMMIT PREPARED -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; INSERT INTO pxtest1 VALUES ('ddd'); SELECT * FROM pxtest1; PREPARE TRANSACTION 'foo2'; @@ -45,14 +45,14 @@ COMMIT PREPARED 'foo2'; SELECT * FROM pxtest1; -- Test duplicate gids -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd'; SELECT * FROM pxtest1; PREPARE TRANSACTION 'foo3'; SELECT gid FROM pg_prepared_xacts; -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; INSERT INTO pxtest1 VALUES ('fff'); SELECT * FROM pxtest1; @@ -69,7 +69,7 @@ SELECT * FROM pxtest1; DROP TABLE pxtest1; -- Test subtransactions -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; CREATE TABLE pxtest2 (a int); INSERT INTO pxtest2 VALUES (1); SAVEPOINT a; @@ -82,7 +82,7 @@ PREPARE TRANSACTION 'regress-one'; CREATE TABLE pxtest3(fff int); -- Test shared invalidation -BEGIN; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; DROP TABLE pxtest3; CREATE TABLE pxtest4 (a int); INSERT INTO pxtest4 VALUES (1); diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql index 7c9638f05e..23271c8eab 100644 --- a/src/test/regress/sql/transactions.sql +++ b/src/test/regress/sql/transactions.sql @@ -40,7 +40,7 @@ CREATE TABLE writetest (a int); CREATE TEMPORARY TABLE temptest (a int); BEGIN; -SET TRANSACTION READ ONLY; -- ok +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ ONLY, DEFERRABLE; -- ok SELECT * FROM writetest; -- ok SET TRANSACTION READ WRITE; --fail COMMIT; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 7b3787a66e..b73262d862 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -795,6 +795,7 @@ LINE LOCALLOCK LOCALLOCKOWNER LOCALLOCKTAG +LOCALPREDICATELOCK LOCK LOCKMASK LOCKMETHODID @@ -931,6 +932,7 @@ OffsetNumber OffsetVarNodes_context Oid OidOptions +OldSerXidControlData OldToNewMapping OldToNewMappingData OldTriggerInfo @@ -1081,6 +1083,10 @@ PQconninfoOption PQnoticeProcessor PQnoticeReceiver PQprintOpt +PREDICATELOCK +PREDICATELOCKTAG +PREDICATELOCKTARGET +PREDICATELOCKTARGETTAG PROC PROCESS_INFORMATION PROCLOCK @@ -1202,6 +1208,9 @@ PreParseColumnRefHook PredClass PredIterInfo PredIterInfoData +PredXactListData +PredXactListElementData +PredicateLockData PrepareStmt PreparedParamsData PreparedStatement @@ -1268,6 +1277,8 @@ RSA RTEKind RUHashEntry RUHashEntryData +RWConflictData +RWConflictPoolHeaderData RangeFunction RangeQueryClause RangeSubselect @@ -1350,6 +1361,10 @@ RunningTransactionsData SC_HANDLE SECURITY_ATTRIBUTES SEG +SERIALIZABLEXACT +SERIALIZABLEXACTTAG +SERIALIZABLEXID +SERIALIZABLEXIDTAG SERVICE_STATUS SERVICE_STATUS_HANDLE SERVICE_TABLE_ENTRY @@ -1595,6 +1610,9 @@ TwoPhaseCallback TwoPhaseFileHeader TwoPhaseLockRecord TwoPhasePgStatRecord +TwoPhasePredicateLockRecord +TwoPhasePredicateRecord +TwoPhasePredicateXactRecord TwoPhaseRecordOnDisk TwoPhaseRmgrId TwoPhaseStateData