Implementation of nested databases using flexible locking mechanisms6772154Abstract Techniques have been developed whereby concurrency control mechanisms such as nested databases can be expressed in terms of operations implemented by various flexible transaction processing systems. For example, one such implementation of nested databases is particularly suitable for transaction processing systems that provides a lock delegation facility and which allow specification of ignore-conflict relationships between locking capabilities. By providing techniques that support movement of objects from a database to a subdatabase thereof, as well as termination (e.g., commit or abort) of transactions and databases (including subdatabases), transaction processing systems can provide advanced transaction models with unconventional concurrency control mechanisms. Some realizations allow specification of uses of parameters with lock modes and facilitate transformation of such uses into a form suitable for utilization in execution environments that support ignore-conflict relationships. Claims What is claimed is: Description BACKGROUND OF THE INVENTION
Creator(D) transaction that begun the database D
visited(T) database visited by transaction T
Superior(D) set of superiors of a database D
Inferior(D) set of inferiors of a database D
Ancestor(D) = Superior(D) .orgate. {D}
Descendant(D) = Inferior(D) .orgate. {D}
A transaction can modify the objects of the database it visits only. However, it can read objects from any descendant databases of its database, subject to lock compatibility, i.e., when its read locks do not conflict with the write locks of the descendant databases or any of their visiting transactions. Note that, read locks, write locks or both may be parameterized. Further note that, in any case, a transaction is confined to the sub-hierarchy of databases rooted by the database the transaction is visiting. In order to distinguish transactions that visit a database D from transactions of superior databases that can read objects from D, we call the latter observers of D. Nested databases form a hierarchy of disjoint logical sets of objects, in which objects can move downward the hierarchy upon explicit commands from transactions holding write locks on them, and upward upon a sub-database's commit or abort operation. More precisely, committing a database transfers the control of all the database's objects to the transaction that created that database. Aborting a database undoes all the updates performed by visiting transactions of the database and transfers control of the objects that were moved into the database back to the database's creator. For a transaction to commit, all its sub-databases must be either committed or aborted. A sub-database can commit only if no transaction is visiting it. When committing a sub-database with visiting transactions, the sub-database creator can be given the choice of either waiting for the ongoing visiting transactions of that sub-database to commit, or immediately aborting them. The physical object repository where objects physically reside is represented by a transaction processing system database created by an hypothetical transaction. This database is the root of all databases. Unless explicitly specified, a new transaction visits the root database. An object can belong to only one database at a time. Let the a transaction, D the database that T visits, and DT a sub-database begun by T There are only three ways for an object O to enter the database D.sub.T : 1. O belongs to D, T visits D and holds a write lock on O, and T explicitly moves O into D.sub.T. Note that moving O to D.sub.T removes it from D, and therefore prevents T from modifying O. T might however be able to continue reading O assuming it created D.sub.T with parameters compatible with the lock mode T uses to acquire read locks. 2. O is created by a visiting transaction of D.sub.T. 3. O belongs to a sub-database of a transaction T.sub.x that visits D.sub.T, and T.sub.x commits that sub-database. The conditions for committing a transaction's sub-database will be detailed later. Nested Databases and Parameterized Lock Modes Transactions may be assigned some lock mode parameters and must use them to acquire all their locks. Parameters may be specified separately for read and write lock modes. The following notation is used throughout this document to denote assignment of lock mode parameters to a transaction:
Locking Modes Used by T
Specification Read Access Write Access
T uses a R(a) W(a)
T uses R(a) R(a) W
T uses W(a) R W(a)
T uses R(a)W(b) R(a) W(b)
Parameterized lock modes are assigned upon transaction start and cannot be changed thereafter. A transaction can create a sub-database and assign it a set of write parameters. In the realizations described herein, read parameters are not employed for sub-databases; therefore, for a given sub-database D, the notation D uses a is interchangeable with D uses W(a). When the transaction moves an object on which it holds a write lock into its sub-database, the write lock is changed for a write lock with the sub-database's parameters. When later the sub-database is committed, the write locks of its objects are converted back to the write mode originally used by the sub-database's creator. There are several subtleties in this mechanism, related to the parameter chosen for a sub-database. Moving an object to a sub-database with write parameters different from the write parameters used by the sub-database's creator makes the lock of the object either more or less restrictive that the creator's lock. For instance, if the sub-database's creator uses for write parameters the set {a, b} while the sub-database uses {a}, then potentially more transactions can read the object. This may become a problem when the creator later tries to commit the sub-database and some observer transaction has acquired a read lock that is incompatible with the creator's lock, but which is compatible with the sub-database lock. How to treat this case? A first possibility is to postpone the commitment of the sub-database until all the observer transactions that hold a read lock that conflicts with a write lock of the creator of the database have completed. This approach is analogous to having the creator queuing a request for a lock on the objects of the sub-database. This approach might be unacceptable because the blocking observer transaction can last very long. Another possibility is to allow the creator of the sub-database to abort inopportune observers of the sub-database upon its commit. There again, such an approach might be unacceptable if the observer is a long-running transaction. A third possibility is to convert the parameters used by the sub-database's creator to the less restrictive parameters of the committed sub-database. Similarly, if the sub-database's creator uses for write parameters the set {a} while the sub-database uses {a, b}, then fewer transactions can read the object. The problem then is that an object cannot be moved to the sub-database until some observer transaction completes. By contrast, transactions.are free to use lock mode parameters that differ from those assigned to the database they visit. For instance, consider a database D that is assigned a set of parameters {a, b}, a transaction T, visiting D and using a set {a}, and a transaction T.sub.o visiting a superior of D and using the set {a} to parameterize its read locks. Although this configuration allows T.sub.o 's read lock to be compatible with T.sub.v write locks, D's write lock on every object of D would prevent T.sub.v from accessing any objects of D, including the one write locked by T.sub.o. To summarize, the following policy is assumed in this document. Transactions are allowed to create sub-databases with write parameters different from the one they use. Object can be moved to a sub-database only if no other reader transaction blocks that operation (i.e., if all the read locks on the object are compatible with a write lock parameterized with the sub-database's assigned parameters). To prevent blocking by long-lasting observer transactions that have read objects of a sub-database, the creator of the sub-database is given the right to abort any transaction that prevents it to commit its sub-databases. A transaction using some read parameters is aware that it risks being aborted if it is blocking the termination of another transaction's sub-database for an unreasonably long period of time. Transactions can use lock mode parameters different from their enclosing database. FIG. 2 graphically illustrates the notion of nested databases with a simple example. Two transactions T.sub.1 and T.sub.2 that visit a root database 201 have each created a sub-database (sub-databases D.sub.1 and D.sub.2, respectively) with the write parameter {a}. A transaction T.sub.1.1 that visits database D.sub.1 has in turn created a sub-database D.sub.1.1, but with no parameter. Lastly, a transaction T.sub.1.1.1, which visits sub-database D.sub.1.1, has created a sub-database D.sub.1.1.1 with write parameter {a, b}. Given the state illustrated in FIG. 2 and the foregoing definitions, the following properties can be shown: Transactions can only modify objects that pertain to the database they visit. Any transaction visiting the root database and using a superset of the set, {a}, for its read parameter can access objects of sub-databases D.sub.1 and D.sub.2 (i.e., can be an observer of sub-databases D.sub.1 and D.sub.2), provided that no visiting transaction of these databases holds a conflicting lock. Transactions visiting sub-database D.sub.1 cannot read objects from sub-database D.sub.2, irrespective of what parameter the visiting transaction uses, and vice-versa. No transactions other than the visiting transaction of sub-database D.sub.1.1 can read objects of sub-database D.sub.1.1. Any transactions visiting the root database or sub-databases of sub-database D.sub.1 (including sub-database D.sub.1) and using a superset of {a, b} can read objects from sub-database D.sub.1.1.1. (Note, however, that the object graph is such that only transactions visiting sub-database D.sub.1.1 can actually obtain a path to objects in sub-database D.sub.1.1.1). The description that follows demonstrates how the above-described model of nested databases can be implemented using flexible locking mechanisms. Flexible Locking Mechanisms The concurrency control of many transaction models tend to be characterized by: 1. the number of lock ownership required by each transactional entity to realize the isolation mechanism, 2. the conflict detection mechanism, 3. the conflict resolution mechanism, and 4. the use of atomic transfer of lock ownership between lock owners in transaction operations. In general, transaction models differ by the number of lock ownerships each transactional entity uses to realize the transaction model's concurrency control. For example, the original ACID transaction model just required one lock ownership per transaction. In general, the concurrency control of a nested transaction model supporting intra-transaction parallelism requires two lock ownerships per transaction: one for the locks the transaction needs for executing its program and one for retaining the locks of its committed sub-transactions. Some implementation of the transaction model described herein may employ up to three lock ownerships for its sub-databases. In general, a flexible lock manager should manage lock ownerships rather than transactions, and leave the mapping of lock ownerships to transactional entities to the specific transaction model implementation. A conflict can be determined by the compatibility of operations against an object. Advanced transaction models typically extend this basic conflict detection model with additional rules to determine when a transaction can ignore a given type of conflict with another transaction. Hence, the conflict detection mechanism of most transaction models can be expressed solely using "ignore-conflict" relationships between different lock owners. For example, the semantics of a nested transaction model can be obtained by defining ignore-all-conflict rules between a transaction and its ancestors in the hierarchy of nested transactions. In another example, as illustrated in the above-incorporated U.S. patent application Ser. No. 09/663,208, entitled "Implementation of Parameterized Locking Modes Using Ignore-Conflict Relationships," naming Laurent Daynes and Ole J.o slashed.rgen Anfindsen as inventors, filed 15 Sep. 2000, the effect of parameterized lock modes can be achieved by setting read-write and write-read ignore-conflict relationships between transactions that use parameter sets related by suitable boolean relationships. Accordingly, we believe that the conflict detection mechanism of a large variety of advanced transaction models can be modeled using ignore-conflict relationships between lock ownerships. Lastly, most advanced transaction models require support for atomic lock ownership transfers, also referred as lock delegation. Release of a lock can be treated as a special case of lock delegation, where all the locks are atomically transferred to a special transaction with which every transaction can ignore conflicts. Hence, one view of a transaction processing system is one where each resource is under the control of some transactional entity, and where the database is just a "passive" transaction (i.e., a transaction that does not execute any program). Locking Capabilities A locking capability is the principal abstraction of the exemplary flexible lock manager described herein. It defines lock ownership within the lock manager. The interface of the exemplary flexible lock manager includes functions for declaring ignore-conflict relationships between locking capabilities, for requesting locks for a locking capability, for delegating locks of one or more locking capabilities to one or more other locking capabilities and for releasing the lock(s) of a locking capability. A locking capability can generally be categorized as either active or passive. An active locking capability can be used to request locks, whereas a passive locking capability cannot. The only way for a passive locking capability to obtain a lock is via delegation of that lock from another locking capability. Conflict Detection with Ignore Conflict Relationships The state of a lock conceptually includes one owner set per lock mode. We denote Owners(l,A) the owner set of a lock l that correspond to the lock mode M. When considering read and write lock modes only, a lock state include the pair <Owners(l,Read), Owners(l, Write)>. A lock manager determines conflict between the current owners of a lock and a requester of the lock in a given mode using a compatibility function Compatible(m.sub.1,m.sub.2) that states whether a lock mode ml is compatible with a lock mode m.sub.2. Compatibility of locking modes is defined by the commutativity of the corresponding operations. The function Compatible is usually represented as a matrix, such as the one described earlier in FIG. 1B. An incompatibility of modes is considered a conflict. When considering read and write locking modes only, three types of conflict can occur: read/write (r/w), write/read (w/r), and write/write (w/w). More generally, a conflict m/m.sub.2 signifies that the requester of a lock 1 in mode m.sub.1 conflicts with owner(s) of lock l in mode m.sub.2. The exemplary lock manager supports a flexible conflict detection mechanism that allows a lock request to specify, in addition to the locking mode requested, a set of ignore-conflict relationships with other locking capabilities. An ignore-conflict relationship is a way to specify that one lock request can ignore an incompatible owner of a lock when evaluating conflicts for that request. For instance, a lock request issued from a locking capability C.sub.1 may specify an ignore-conflict relationship with C.sub.2. When deciding whether the requested lock can be granted to C.sub.1, the lock manager will ignore all conflicts (read-write, write-read, write-write) with C.sub.2. Ignore-conflicts relationships can be further refined to specify which kind of conflicts can be ignored, e.g., r/w, w/r, w/w, or any combination thereof. More specifically, an ignore-conflict relationship involves two locking capabilities, one of which must be an active locking capability. A passive locking capability cannot ignore conflicts with other locking capabilities since it cannot request a lock and generate a conflict. On the other hand, an active locking capability can ignore conflicts with either a passive or an active locking capability. We use ##EQU1## to denote an ignore-conflict relationship that allows an active locking capability C.sub.i to ignore a conflict of type m.sub.1 /m.sub.2 with a locking capability C.sub.j, where m.sub.1 /m.sub.2 can be any pair of potentially incompatible lock modes (e.g., r/w, w/r, or w/w) or set thereof. In an exemplary realization, ignoring a w/w conflict is subsumed into the case of ignoring all types of conflict and can be noted as all instead. Persons of ordinary skill in the art will recognize that these definitions can be refined for any given implementation, should it be necessary to include a richer variety of lock modes. The current framework only allows symmetric ignore-conflict relationships between two active locking capabilities. A symmetric ignore-conflict relationships is denoted as ##EQU2## and is equivalent to the pair of asymmetric ignore-conflict relationships ##EQU3## Each locking capability is associated with one set of ignore-conflict relationships per type of conflict. For a given locking capability C and conflict type m.sub.1 /m.sub.2, the set is denoted ICW(C,m.sub.1 /m.sub.2). For a locking capability C, we always have C.epsilon.ICW(C,m.sub.1 /m.sub.2) for all pairs m.sub.1 /m.sub.2 of incompatible lock modes. The flexible lock manager diagnoses a conflict for a request for a lock l in mode m by a locking capability C if the following condition is false: .A-inverted.m.sub.i, Compatible(m, m.sub.i) {character pullout}({character pullout}Compatible(m,m.sub.i).LAMBDA.(Owners(l,m.sub.i).OR right.ICW(C,m m/m.sub.i))) We note that this conflict detection mechanism does not prevent the use of more sophisticated lock modes than the basic read and write. However, it is sometimes the case that the semantics provided by such sophisticated lock modes can be implemented using just the read and write lock modes along with a particular set of ignore-conflict relationships between lock ownerships (however, the converse is false). Delegation of Locks A lock manager enables a locking capability to atomically delegate locks to another locking capability, that is, to atomically transfer the ownership of locks from one locking capability to another. In the case of delegation of C.sub.i 's locks to C.sub.j, C.sub.i is referred as the delegator and C.sub.j as the delegatee. In general, a request for delegating the lock of one locking capability to another can be blocking if other owners of the lock conflict with the delegatee. For instance, let us assume three locking capabilities C.sub.1, C.sub.2, and C.sub.3 such that ##EQU4## and a lock l such that Owners(l,r)={C.sub.1 }, Owners(l,w) )={C.sub.2 }. The request for delegating C.sub.1 's ownership of l to C.sub.3 is blocking because a read lock of C.sub.3 conflicts with a write lock of C.sub.2. In some implementations, blocking delegation requests may be appended to a special delegation queue. Pending requests from such a delegation queue are treated before standard pending lock requests. By default, the delegator of the lock is blocked until the conflict is resolved. A delegation operation between active locking capabilities can however specify that blocking performs lazily. In that case, the delegator is not blocked. Instead, a blocking request is still appended to the blocking delegation queue, and the delegatee is blocked only upon an attempt to either commit or access the object protected by the delegated lock before the conflict is resolved. As with other lock management events, the lock manager may be configured to notify blocking lock delegations so that transactions can customize their reaction (e.g., by refusing the delegation, negotiating an extension of the delegatee's ignore-conflict relationships so that the delegated lock can be granted, etc.) Lock delegation operations come in multiple flavors, listed below: delegate (l, C.sub.i, C.sub.j) C.sub.i delegates lock l to C.sub.j delegate (C.sub.i, C.sub.j) C.sub.i delegates all its locks to C.sub.j delegate (l, C.sub.i,{C.sub.j.sup.1, C.sub.j.sup.2, . . . C.sub.j.sup.n }) C.sub.i delegates lock l to {C.sub.j.sup.1, C.sub.j.sup.2, . . . C.sub.j.sup.n } delegate (C.sub.i, {C.sub.j.sup.1, C.sub.j.sup.2, . . . C.sub.j.sup.n }) C.sub.i delegates all its locks to {C.sub.j.sup.1, C.sub.j.sup.2, . . . C.sub.j.sup.n } Delegations to multiple locking capabilities are allowed only if the multiple delegatees do not conflict with each other. Otherwise, a deadlock situation is created. Nested Databases using Locking Capabilities We now discuss how locking capabilities can be used to implement nested databases. We start with some simplifying restrictions, e.g., (i) that a sub-database can only be created without parameters, and (ii) that transactions cannot themselves create new objects. We will eliminate these restrictions progressively. Basic Design The simplest model can be achieved by associating each transaction with a single active locking capability that will be used to acquire the lock of the transaction, and associating each database to a single passive locking capability (since they never acquire locks directly). We assume that a sub-database is always created empty, then filled with objects by the sub-database's creator using some command taking some enumeration of objects. For a transaction to move an object into a sub-database, two conditions must be met. First, the sub-database must have been created by the transaction, and second, the transaction must hold a write lock on the object it wants to move. Moving the object makes it unavailable to any transaction outside of the sub-database, including its creator. This goal can be accomplished using delegation since, in the absence of parameterized lock modes, no other transactions except the sub-database creator hold a lock on that object. The following scenario illustrates more precisely how delegation can be used. A transaction T.sub.i, associated with a locking capability C.sub.i, moves an object O to a sub-database D associated with a locking capability C.sub.D. Transaction T.sub.i holds a write lock without parameter on object O. Moving object O to sub-database D actually consists of delegating C.sub.i 's lock on object O to locking capability C.sub.D. The effect achieved is that no transaction outside of sub-database D, including transaction T.sub.i, can either write or read object O. What about sub-database D's visiting transactions? Allowing them to access object O is achieved by making them ignoring all type of conflicts with locking capability C.sub.D. In other words, let transaction TiD be a visiting transaction of sub-database D and let C.sub.i.sup.D be the locking capability of transaction T.sub.i.sup.D. Then, ##EQU5## However, this is not enough. What we forgot in this reasoning is that a transaction is always enclosed in a database, and the locking capability of a database always holds a write lock on every object of the database. This means that delegating the write lock of the sub-database's creator is not enough as demonstrated by the following example. Let us assume a database D and an object O of that database. Let us also assume that no transaction holds a lock on object O. In this case, the lock of object O is held in write mode by C.sub.D, the locking capability of database D. When a transaction T.sub.i visits database D, its locking capability, C.sub.i, can ignore all conflicts with C.sub.D. After transaction T.sub.i has acquired a write lock on object O, object O has two write lock owners: C.sub.i and C.sub.D. If transaction T.sub.i now creates a sub-database D.sub.i, and moves object O into it, then delegating C.sub.i 's lock on object O to C.sub.D, the locking capability of D.sub.i, is not enough. This is because locking capability C.sub.D still owns a write lock on object O and sub-database D.sub.i 's visiting transactions only ignore conflicts with locking capability C.sub.D.sub.i. To achieve the semantics of moving the object O from transaction T.sub.i 's visiting database to transaction T.sub.i 's sub-database D.sub.i, both the lock of T.sub.i and of its database D must be delegated to the sub-database D.sub.i. In other words, both C.sub.i 's and C.sub.D 's locks must be delegated to locking capability C.sub.D.sub..sub.i . Note that, by themselves, ignore conflict relationships do not provide the effect of nested databases. For instance, we cannot use ignore conflict relationships between the visiting transactions of a database and the parent database to achieve the logical movement of an object because the ignore conflict relationships would allow the visiting transactions to access the objects of the parent database as well. With the design described so far, when a visiting transaction terminates (e.g., by commit or abort), it releases the locks acquired with its locking capability. Database termination is more complex. Consider a sub-database D of a transaction T. Transaction T can commit sub-database D only when no transaction visits sub-database D. Committing sub-database D results in moving its objects up to the database that transaction T is visiting and makes the moved objects accessible to transaction T only. In order to achieve this, the lock of every object of sub-database D is converted into two locks: a write lock of transaction T and a write lock of the database visited by transaction T This conversion makes sub-database D's objects appear as if they were modified and/or created by transaction T. Conversion of one lock into two can be achieved by delegating D's locks simultaneously to transaction T and the database that transaction Tvisits (i.e., by performing delegate (C.sub.D, {C.sub.T, C.sub.visited(T) }). Aborting a sub-database must also result in setting the locks on the sub-database's objects back to their previous state. This objective can be achieved using the same sequence of locking manipulation that is used for a sub-database commit operation, i.e., delegation of the locks of the sub-database back to both the sub-database's creator and parent database. Note that a sub-database abort provokes an immediate abort of its visiting transactions, and proceeds only once all the sub-database's visiting transactions (and consequently, their sub-databases) are aborted. FIG. 3 illustrates operation of a transaction processing system implementing the techniques described above. In particular, FIG. 3 illustrates how the state of an object's lock evolves as it is moved in a hierarchy of nested databases and further illustrates use of locking capabilities in a transaction processing system in accordance with an exemplary embodiment of the present invention. FIG. 3 shows five successive states 1, 2, 3, 4 and 5. State 1 shows an initial database D1 with a single object O. The database D1 can be an arbitrary database in a nested database hierarchy. The lock of object O is held in write mode by the locking capability associated with the database D1, i.e., by locking capability C.sub.D1. State 2 illustrates the results of a visiting transaction T1 of database D1 that acquires a write lock on object O. First, the visiting transaction has an associated locking capability, C.sub.T1, that can ignore all conflicts with locking capability C.sub.D1 when acquiring locks. The lock of object O is now held by both locking capabilities C.sub.D1, and C.sub.T1. State 3 illustrates the creation of a sub-database of T1. An empty database, D2, is associated with a new locking capability, C.sub.D2. The state of the lock of object O remains unchanged. State 4, illustrates the results of movement, by transaction T1, of object O to transaction T1's sub-database D2. The state of object O's lock is changed as the result of delegation of both locking capability C.sub.T1 's lock and locking capability C.sub.D1 's lock on object O to locking capability C.sub.D2. Finally, state 5 illustrates what happens then when transaction T2 visits the sub-database D2 and requests a read lock on object O. The read lock is granted since T2 can ignore conflicts with sub-database D2. Later, when transaction T2 commits, it will release its locks, returning the database hierarchy to the state illustrated as state 4. Then, when sub-database D2 is committed by transaction T1, all of sub-database D2's locks are delegated to both transaction T1 and database D1, effectively returning the state of both the database hierarchy and O's lock to the state illustrated as state 2. Using Parameterized Lock Mode with Nested Databases Co-pending U.S. patent application Ser. No. 09/663,208, entitled "Implementation of Parameterized Locking Modes Using Ignore-Conflict Relationships," naming Laurent Daynes and Ole J.o slashed.rgen Anfindsen as inventors, filed 15 Sep. 2000, and which is incorporated herein by reference, showed how the effect of parameterized lock modes can be achieved using symmetric ignore-conflict relationships for non-nested databases. For example, the effect of using a parameter S for reads can be achieved by symmetrically ignoring read-write conflicts with every transaction using a sub-set of S for writes. Inversely, the effect of using a parameter set S for writes can be achieved by symmetrically ignoring write-read conflicts with every transaction using a super-set of S for reads. These ignore-relationships are set upon assignment of parameter sets to a transaction. The set of transactions with which to ignore-conflict can be determined using a table that maps parameter sets to the transactions that use them. The table need only keep track of active parameter sets, that is, parameter sets used by running transactions. In realizations that employ a particular boolean relation, assignment of a read (respectively, write) parameter set S to a transaction causes a search for sub-sets (respectively super-sets) of S. A symmetric ignore read-write (write-read) conflict relationship is established with every transaction using that sub-set (super-set), for parameterizing write (read) locks. Of course, as described in the above-incorporated patent application, other boolean relations may be employed with suitable modifications. Such parameterized lock modes can be extended to support nested databases as follows. Let The a transaction and O an object that currently resides in a database visited by T (denoted herein as visited(T)). A transaction processing system in accordance with the present invention may then define the following locking rules for nested databases with parameterized lock modes: 1. T can be granted a write lock on object O if (i) object O belongs to visited(T), (ii) no other transaction holds a write lock on object O, and (iii) all read locks on object O are parameterized with a super-set of transaction T's write lock mode parameter set. 2. Transaction Tcan be granted a read lock on object O if (i) object O belongs to a descendant database of visited(T) and (ii) all the write locks on object O are parameterized with a sub-set of transaction Ts read lock mode parameter set. More formally, such locking rules can be achieved by establishing the following ignore-conflict relationships (C, denotes the locking capability associated with transaction or database X) under the following conditions: ##EQU6## where the notation f.sub.1.function.f.sub.2.function.. . . .function.f.sub.n indicates the conjunction of Boolean clauses f.sub.1, f.sub.1, . . . f.sub.n and where the rules specifying ignore-conflict relationships include two parts: (i) a conjunction of Boolean clauses expressing a condition over two arbitrary transactions or over an arbitrary transaction and an arbitrary database, and (ii) an ignore-conflict relationship. The second part indicates the ignore-conflict relationship to be set in the event that the condition of the first part evaluates as true. In an exemplary implementation, these rules are re-evaluated each time a transaction is assigned a new set of parameters. Typically, parameter assignments are made at transaction initiation. Supporting Object Creation Some embodiments in accordance with the present invention are targeted toward cooperative applications, such as applications involving design activities (e.g., CAD, CASE, co-authoring tool). Therefore, it is desirable to allow transactions to create new objects and cooperate to refine the created objects. For example, certain cooperative applications may advantageously allow new objects to be observed by external transactions and/or allow new objects to be passed to sub-databases for refinement by transactions of other users. The design presented in the previous sections is insufficient to deal with the case of objects created by visiting transactions. For instance, consider the scenario illustrated in FIG. 3, and assume that transaction T.sub.2 creates an object O.sub.n. Object creation is a form of update, so the created object is initially locked by transaction T.sub.2 in exclusive mode. Note that database D.sub.2, i.e., Visited(T.sub.2), does not hold a lock on object O.sub.n. The previously described protocol specified that upon commit, a visiting transaction releases all its locks. However, doing so makes the objects created by transaction T.sub.2 available for updates by any transaction, including transactions that do not visit the database. This is because objects created by committed transactions are not covered by a write lock of their database. This problem can be corrected by changing the commit operation so that a committing transaction delegates all its locks to its visiting database instead of releasing them. This solves the mentioned problem, but introduces a new one: if the database is requested to abort, how can it distinguish its new objects from the one that were moved into it? Without this distinction, a sub-database lock on a created object will be delegated to both its parent database and creator transaction. A similar problem occurs when a transaction moves an object that it has just created into a sub-database. According to the previously described protocol, the lock of the moved object is delegated to the sub-database. When the sub-database completes (either by abort or commit), its lock is delegated both to its parent database and the transaction that created the sub-database. This is problematic if the transaction aborts. These examples indicate that supporting new objects requires changes to the original protocol, because new objects must be treated differently from other objects when a sub-database commits, aborts, or receives an object from the transaction that created the sub-database. First Approach One approach for properly handling object creation is to explicitly track objects moved into a sub-database. When an object is created, it is atomically associated with a write lock of its creator. Note that objects created by ongoing visiting transactions of a database differ from other objects of that database in that the database does not hold a lock on them. This does not violate the desired concurrency control because a write lock of a transaction is always stricter or equal than a lock of the database visited by that transaction. Hence, while the lock of the visited database is redundant with a write lock of a transaction visiting the database; it makes the locking protocol more regular and easier to implement and understand. Since a transaction always holds a write lock on the objects it creates, it can always move them into one of its sub-databases. Whereas the lock of an object not created by a transaction is held by both the transaction and the database it visits, the lock of an object created by a transaction is locked by that transaction only. This difference is important when moving an object because the former state of that object's lock will have to be re-established once the sub-database terminates. In order to handle this difference, each sub-database is extended with two tables of object references called, respectively, the pulled objects table (POT) and the pulled created objects table (POCT). The POT of a database records objects that were moved into the database and that pre-existed the transaction that moved them. The POCT records objects that were moved into the database and that were created by the transaction that moved them. Hence, the POT and POCT of a database are disjoint. When a transaction T moves an object O to a sub-database D, it first tests whether object O is locked by the database it visits. If it is the case, it means that object O was not created by transaction T. Transaction T then adds object O to sub-database D's POT and delegates both its lock and its database's lock on object O to sub-database D. Otherwise, object O is added to sub-database D's POCT and transaction T delegates its lock to sub-database D. A transaction commits by delegating all its locks to the database it visits, and aborts by releasing all its locks. As explained earlier, delegation upon transaction commit is necessary for the object created by the transaction to be protected by a lock of the database. Let D be a database, and T its creator. Transactions visiting a database D can access objects moved into database D, including those that were created by transaction T. These transactions can also augment database D with objects they create. Hence, within database D, three sets of objects can be distinguished: 1. The objects moved into database D and that were not created by transaction T These objects are recorded in the POT. 2. The objects that were created by transaction T. These objects are recorded in the POCT. 3. The objects that were created by committed visiting transactions of database D. These objects are neither in the POT or the POCT. When database D commits, all three type of objects are moved up to transaction T. The state of the locks protecting objects that were not created in database D must be re-established to the state they were before these objects moved into database D. That is, the write locks of objects that pre-existed transaction T should be held both by transaction T's database and transaction T. whereas the write locks on the objects created by transaction Tmust be held by transaction T only. Failing to achieve this will result in incorrect situation if transaction T later aborts. Lastly, objects that were created by committed visiting transactions of database D should look like they were created by transaction T From a locking perspective, this means that the locks of these objects must be held in write mode by transaction T only. The following sequence of locking operations achieves the desired effects of a database commit: 1. Delegate database D's locks on all objects of the POT to both transaction T and transaction T's database. 2. Delegate all remaining locks to transaction T only. When database D aborts, the locks of all objects that pre-existed database D must recover their state as of before database D's creation. The difference, when compared with commit handling, is that the objects created in database D are recovered (and therefore become garbage), and their lock must be cleaned up (i.e., released). The following sequence of locking operations achieves this goal: 1. Delegate database D's locks on all objects of the POT to both transaction T and transaction T's database. 2. Delegate database D's locks on all objects of the POCT to transaction T only. 3. Release all remaining locks of database D. FIG. 4 provides a detailed example of how this approach works. The example shows multiple steps of a scenario employing nested databases. For simplicity, lock parameters are unused in the scenario. This means that a transaction cannot observe any objects out of its database. The bottom of FIG. 4 indicates the write lock holders of each object at each step (no object is read-locked in the entire scenario). The scenario starts (at Step 1) with a transaction T.sub.1 that visits a database D.sub.1. The database already holds an object O.sub.1 from a parent database (not shown for simplicity). Transaction T.sub.1 has acquired a write lock on object O.sub.1, created a second object O.sub.2, and started a sub-database D.sub.2. Because object O.sub.1 was moved into database D.sub.1 by database D.sub.1 's creator, its reference is stored in D.sub.1 's POT. Step 2 shows the state of each database's tables and each object's lock after transaction T.sub.1 has moved all its objects (i.e., objects O.sub.1 and O.sub.2) to sub-database D.sub.2, and a transaction T.sub.2 that visits sub-database D.sub.2 has acquired the write locks on both moved objects O.sub.1 and O.sub.2 and a third object O.sub.3 created in subdatabase D.sub.2. Note that object O.sub.1 has been recorded in D.sub.2 's POT, whereas object O.sub.2 has been recorded to D.sub.2 's POCT since it was created by transaction T.sub.1. Step 3 shows the state of the system after transaction T.sub.2 has moved all its objects into a sub-database D.sub.3 it has created. Since both objects O.sub.1 and O.sub.2 pre-existed transaction T.sub.2, they are recorded in sub-database D.sub.3 's POT. Object O.sub.3 was created by transaction T.sub.2 (i.e., sub-database D.sub.3 's creator) and is therefore recorded in the POCT, according to the above-described rules for moving objects into a sub-database. When sub-database D.sub.3 is committed, the locks on objects O.sub.1, O.sub.2 and O.sub.3 will return to the state shown in Step 2. That is, sub-database D.sub.3 's write locks on objects recorded in its POT (namely, objects O.sub.1 and O.sub.2) are delegated to both its creator (namely, transaction T.sub.2) and its parent database (namely, sub-database D.sub.2), whereas sub-database D.sub.3 's write locks on objects recorded in its POCT (namely, object O.sub.3) are delegated only to sub-database D.sub.3 's creator (namely, transaction T.sub.2). Prior to Step 4, transaction T.sub.2 has committed, resulting in the above described state (not specifically shown) equivalent to that illustrated in Step 2. Then, a second transaction, T.sub.3, visits sub-database D.sub.2. Transaction T.sub.3 has acquired write locks on all three objects O.sub.1, O.sub.2, and O.sub.3, and has moved them into the sub-database D.sub.4 it has created. The resulting state is illustrated as Step 4. The main difference between this situation and the previous one is that all three objects are referenced from the POT, whereas, object O.sub.3 was referenced from the POCT in Step 3. The difference results from the fact that object O.sub.3 pre-existed transaction T.sub.3 and was already locked by sub-database D.sub.2. Accordingly, when transaction T.sub.3 moves object O.sub.3 into sub-database D.sub.3, it has to delegate both its lock and that of sub-database D.sub.2 to sub-database D.sub.3. As a result, object O.sub.3 's reference is stored to sub-database D.sub.3 's POT, so that a later commit of sub-database D.sub.3 will know that sub-database D.sub.3 's lock must be delegated both to sub-database D.sub.2 and transaction T.sub.3. Step 5 shows the system after all descendant databases of transaction T.sub.1 have committed. All objects created by visiting transactions of a committed sub-database of transaction T.sub.1 are treated as objects created by transaction T.sub.1. This result is reflected in the state of the system illustrated in Step 5. The locking status of objects O.sub.2 and O.sub.3 is the same, i.e., they are locked only by transaction T.sub.1 and not referenced from any table of database D.sub.1. Accordingly, when objects O.sub.2 and O.sub.3 are moved into a new sub-database of transaction T.sub.1, i.e., sub-database D.sub.5 (illustrated at Step 6), they are both recorded in the POCT of sub-database D.sub.5, as if they were both created by transaction T.sub.1, although object O.sub.3 was actually created by a committed transaction of an inferior database of transaction T.sub.1. Throughout the above-described scenario, it is notable that when a transaction moves an object to one of its sub-databases, it does not remove that object's reference from either the POT or the POCT table of the database it visits. For instance, when transaction T.sub.2 moved object O.sub.2 to sub-database D.sub.3, it does not remove object O.sub.2 's reference from sub-database D.sub.2 's POCT. If that were the case, the commit operation for sub-database D.sub.3 would be unable to identify the table of sub-database D.sub.2 to which object O.sub.2 's reference should be stored back. Note that, while not specifically illustrated, the design presented above is suitable for enforcement of the locking rules of a nested database when used with parameterized lock modes. This is because these locking rules define relationships between transactions, and are orthogonal to the status of an object (e.g., whether or not the object was created by a transaction). For instance, reconsider the scenario of FIG. 4 with the following assignment of sets of lock mode parameters, where S.sub.2.OR left.D S.sub.1 : T.sub.1 uses S.sub.1, D.sub.2 uses S.sub.2, and T.sub.2 uses S.sub.2 In this case, no ignore-conflict relationships will be set up between transaction T.sub.1 and both sub-database D.sub.2 and transaction T.sub.2. That is, since S.sub.2 is not a sub-set of S.sub.1, transaction T.sub.1 cannot ignore a read-write conflict with sub-database D.sub.2 or transaction T.sub.2). On the other hand, let us assume that a transaction T that visits database D.sub.1 and uses a set S such that S.OR left.D S.sub.2. Further assume that transaction T is begun at an execution state such as illustrated at Step 2 of FIG. 4. Then, transaction Twill be able to read both objects O.sub.2 and O.sub.3, since an ignore read-write conflict relationship has been established between transaction T and both sub-database D.sub.2 and transaction T.sub.2. Eliminating Tables The design described above included the maintenance of two object tables per nested database. These tables might be space-consuming if an application moves large sets of objects across nested databases. In addition, the tables contributed to meta-data that must be considered upon rollback. Therefore, in some implementations it is desirable to eliminate the pulled objects table and pulled objects created tables. One alternative is to rely on the lock manager to achieve the effect of discriminating pulled objects from pulled created objects. This goal can be achieved by defining two additional passive locking capabilities per nested database. These locking capabilities have a role similar to the object tables. More precisely, each nested database D is associated with three passive locking capabilities, namely C.sub.D, C.sub.D.sup.po and C.sub.D.sup.poc, such that: Locking capability C.sub.D holds a lock on every object of database D, except those objects created by ongoing visiting transactions of database D. Such objects are locked only by the creating visiting transaction until completion thereof. Locking capability C.sub.D.sup.po holds a lock on every object moved into database D that pre-existed database D's creator. Locking capability C.sub.D.sup.poc holds a lock on every object that is moved into database D and was created by database D's creator. In this way, the passive locking capabilities of a database encode the three sets of locked objects with the following relations: (C.sub.D.sup.po.andgate.C.sub.D.sup.poc)=.O slashed. C.sub.D.sup.po.andgate.C.sub.D C.sub.D.sup.poc.andgate.C.sub.D where C.sub.D.sup.po is the pulled objects (PO) capability of database D and C.sub.D.sup.poc is the pulled objects created (POC) capability of database D. Each plays an analogous role to the tables (POT and POTC, respectively) of the previously described implementation. As before, each transaction T is also associated with an active locking capability denoted C.sub.T. Given these new associations to locking capabilities, the concurrency control of an transaction processing system supporting nested databases with parameterized lock modes and object creation can be implemented with the rules and resulting ignore-conflict relationships listed in FIG. 5. The first two rules of FIG. 5 define the isolation imposed by databases, whereas the last three rules define the effect of parameterized lock modes and how they selectively relax the isolation imposed by the first two rules. As in the previous design, a transaction T visiting a database D commits by delegating all its locks to locking capability C.sub.D, and aborts by releasing all its locks. Using delegation for the commit operation allows objects created by the committed transaction to be isolated by the database. Illustrative implementations of move, commit and abort operations based on delegation and release of appropriate locking capabilities are illustrated in FIGS. 6A, 6B and 6C, respectively. Focusing on the move operation of FIG. 6A, when a transaction T moves an object O to one of its sub-databases D, it first determines whether it has created object O. In the illustrated implementation, this determination is performed by testing whether transaction T's visiting database holds a write lock on object O. If not, object O was created by transaction T and transaction T's lock on object O is delegated to both the sub-database D and sub-database D's POC capability, C.sub.D.sup.poc. Otherwise, both transaction T's lock and visited(T)'s lock on object O are delegated to the sub-database D and sub-database D's PO capability, C.sub.D.sup.po. Locks held by the PO and POC locking capabilities of a database are not delegated to a sub-database during a move operation, for the same reason that object references were not cleared from either a POT or a POCT table in the previous design. Note that the ignore-conflict relationships defined by rule 2 of FIG. 5 guarantee that moved objects will not create unwanted conflicts. Focusing on the operations of FIGS. 6B and 6C, when a sub-database either commits or aborts, the locks on objects that were moved to that database must return to their previous state. In case of a sub-database D commit, the objects created in the context of sub-database D should appear to have been created by sub-database D's creator. In case of a sub-database D abort, the new objects created within sub-database D will be "uncreated" as a result of roll-backing the sub-database D and their locks must be released. Depending on the implementation, such objects may be explicitly freed or garbage collected. As with the previous design, the effect of database completion is achieved using a sequence of lock delegation operations. However, instead of enumerating over each object that was moved into the committing database and performing a per-object lock delegation, global lock delegation operations are used. For example, a commit operation on sub-database D delegates all locks held by sub-database D to sub-database D's creator and delegates all locks held by the PO capability of sub-database D, i.e., by locking capability C.sub.D.sup.po, to the database visited by sub-database D's creator (i.e., to the locking capability C.sub.visited(Creator(D))). An abort operation on sub-database D delegates all locks held by the PO capability of sub-database D, i.e., by locking capability C.sub.D.sup.po, to both sub-database D's creator and the database it visits. In addition, the abort operation delegates all locks held by the POC capability of sub-database D, i.e., by locking capability C.sub.D.sup.poc, to sub-database D's creator. Since the set of locks owned by C.sub.D, C.sub.D.sup.po and C.sub.D.sup.poc overlap, a variety of sequences can be employed to achieve the same result. Choosing between approaches is somewhat arbitrary, although the sequence illustrated in FIG. 6B is attractive (when compared with alternatives) because redundant delegations are avoided and release of locks is likely to be handled efficiently by a lock manager implementation. Note that the root database only requires a single locking capability, i.e., no passive locking capabilities to track pulled objects and pulled created objects, since it has no parent database from which to receive objects. When the number of objects transferred between databases is large, implementations that employ locking capabilities rather than tables can perform better in lock manager implementations that optimize bulk lock operations, such as release or delegation of all the locks of a transaction at once. Such implementations are likely to perform better because the cost of bulk lock operations can be small and independent of the number of delegated locks. Exemplary Implementations Prototypes of the above-described concurrency control mechanisms can be implemented using a flexible lock manager offering locking capabilities. For example, one prototype implementation has been developed in the JAVA programming language. JAVA and all Java-based marks and logos are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. FIG. 7 illustrates the main classes of an implementation of the desired concurrency controls. These include the Transaction, Database, and ParameterSet classes. Each instance of a Transaction class has references to the active locking capability and read and write parameters assigned to that transaction, to the database visited by that transaction, and to a list of sub-databases created by that transaction. Each instance of the Database has references to three passive locking capability objects, to its creator (an instance of Transaction), to a linked list of visiting transactions, and to the parameter set that was assigned to the database. A bit-set records all the superiors of the database to enable fast testing of whether a transaction visits a superior of that database. Sets of parameters are represented as instances of the class ParameterSet. Each instance of that class includes a bit-set and three lists of users, i.e., one list of database using the set to parameterize their write locks and two lists of transactions, one for transactions using the set to parameterize read lock requests, and one for transactions using the set to parameterize w rite lock requests. Parameter values may be associated with a unique identifier each so they can be organized into bit-sets for fast set-inclusion tests between sets of parameters. A parameter value can be an arbitrary object, as long as it is mapped to a unique parameter identifier. The implementation of database and transaction operations is faithful to the design described in the previous sections. The only aspect that requires additional details is the maintenance of the ignore-conflict relationships upon the creation of either a new transaction or a new database. Each case is reviewed below. To start a new transaction, an application first constructs a new instance of the class Transaction, calls its begin method, and, before executing any code on behalf of that transaction, assigns it the parameters it must use to parameterize its read and write lock requests, if any, by calling its use method. Constructing a new transaction object associates it an active locking capability from the lock manager object, sets its visitedDB field to the database specified to the constructor, and updates that database's list of visiting transactions to include the new transaction. The begin method of a transaction can initialize the ignore-conflict relationships between the transaction's active locking capability and the locking capabilities of the database it visits. In particular, the begin method may define ignore-conflicts relationships in accordance with rule 1 of FIG. 5. Note that while a transaction that does not use parameterized lock modes does not gain access to objects of inferior databases of its database, use of parameterized lock modes is not essential. Rule 2 of FIG. 5 is unnecessary in that case. Accordingly, in some implementations, application of rule 2 is postponed until the parameters assigned to the transaction (if any) are known. If the transaction isn't assigned any parameters, no additional ignore-conflict relationships need to be set up, and the transaction can start executing and requesting locks. Otherwise, several additional ignore-conflict relationships might need to be set up, according to rules 2 through 5 of FIG. 5. Note that these rules concern transactions and databases that appear in the same branch of a nested database hierarchy, i.e., the branch of the database visited by the new transaction. In one implementation, these ignore-conflict relationships are established in a single pass over the table of active ParameterSet. Starting a new sub-database is very similar to starting a new transaction, i.e., the application first constructs a new instance of the Database class, calls its begin method, and, before performing any other database operations, or starting any visiting transactions, assigns the database with the parameter sets it must use for its write locks by calling its use method. Constructing an instance of Database initializes its parent and creator links as well as a bit-set of superiors. Beginning the sub-database registers the sub-database to its creator and associates three passive locking capabilities allocated by the lock manager. No ignore-conflict relationships need to be set up until some parameters are assigned to the database or some transactions visit it. The use method of a Database object traverses the hierarchy upward from the parent of the database up to the root database to find all transactions that use a super-set of the set of parameters assigned to the database to parameterize their read locks. An ignore read-write conflict relationship is set between each such transaction and the three locking capabilities of the new database, according to rule 3 of FIG. 5. All other rules concern transaction begin. Illustrative Variations While the invention has been described with reference to various embodiments, it will be understood that these embodiments are illustrative and that the scope of the invention is not limited to them. Many variations, modifications, additions, and improvements are possible. In addition, to those described above, two major sources of variation include (1) use of alternative relations between parameter sets to discriminate between compatible and incompatible modes and (2) differing and/or more complex sets of locking modes. Generalizing Relations between Parameter Sets In general, lock modes may be parameterized by association with a set of values from a parameter value domain and the particular parameterization by associated transactions affects the interpretation of compatibility between lock modes. In the illustrative context described above, a subset relation was used to affect the interpretation of compatibility between two lock modes based on contents of respective parameter sets. However more generally, the relation between parameter sets used to discriminate between compatible and incompatible modes of an associated lock may be any arbitrary boolean expression .PHI. over the parameter sets. Examples of suitable expressions include simple set relations such as .OR right., .OR left., .andgate. etc., but more generally can extend to arbitrary expressions over the respective parameter sets. While use of the subset relation was particularly intuitive, more generally, an application may assume any arbitrary expression in its assignment of parameterizations to achieve a desired concurrency control. Based on the particular relation selected, suitable modifications may be made to the corresponding relations employed in transforming parameterized uses to ignore-conflicts relations. In the context illustrated above, application or programming environments assumed the subset relation when using lock mode parameters to achieve desired concurrency controls. Use of a Boolean expression .PHI. over the parameter sets affects the previously illustrated ignore-conflicts rules (recall FIG. 5) as illustrated in FIG. 8A. Note that the Boolean expression .PHI. takes a lock mode with read semantics as its first argument and a lock mode with write semantics as its second argument. Based on the description herein, persons of ordinary skill in the art will appreciate a wide variety of suitable relations. Relevant Sets ofLock Modes For purposes of illustration, the description herein has focused on a simple set of locking modes (i.e., R and W). However, persons of ordinary skill in the art will appreciate that other sets of lock modes may be employed. Accordingly he previously illustrated ignore-conflicts rules can be further generalized. We use a dense notation ##EQU7## to denote a set of ignore-conflict relationships: ##EQU8## As described above, each lock mode can be classified either a lock mode for operations with read semantics or a lock mode for operations with write semantics. The difference between the two classifications is that the latter requires some recovery actions on user-visible data on rollback, whereas the former does not. Irrespective of the number of distinct lock modes employed in a given implementation, a transaction need specify at most two lock mode parameters, one that is associated with lock modes with read semantics and one that is associated with lock modes with write semantics. Accordingly, ignore-conflicts relationships that establish the previously described nested database semantics can be extended as illustrated in FIG. 8B where M.sub.r denotes a set of lock modes with read semantics (e.g., {R,IR}), where M.sub.w denotes a set of lock modes with read semantics (e.g., { W,U,IW}), and where S.sub.r and S.sub.w respectively denote lock mode parameters associated with the locks modes having read and write semantics. For simplicity of illustration, the assignment of lock mode parameters is assumed to be on a per lock mode semantics basis, i.e., for a given parameter S, uses R(S) specifies that lock modes with read semantics are assigned a parameter S and uses W(S) specifies that lock modes with write semantics are assigned the parameter S. However, persons of ordinary skill in the art will recognize that ignore-conflicts relationships suitable for establishing nested database semantics (e.g., as illustrated in FIG. 8B) can easily be altered to allow specification of parameters on a per lock mode basis (i.e., such that each individual lock mode is assigned different lock mode parameters): One alternative set of access modes includes browse (B), read (R), intention to read (IR), upgrade (U), write (W), intention to write (IW) and read with intention to write (RIW). Of these, R, B and IR can be classified as access modes having read semantics (Mr) and W, U, IW, and RIW can be classified as access modes having write semantics. Although a variety of conditional compatibility configurations are possible, in one exemplary configuration, the following modes may be considered conditionally compatible:
M.sub.r M.sub.w
R W
R U
R IW
R RIW
IR U
IR W
Other commonly employed lock modes include intention none (IN), share or shared (S), intent share (IS), update (U), exclusive (X), intent exclusive (IX) and share with intent exclusive (SIX), which generally correspond to the previously introduced browse, read, intention to read, upgrade, write, intention to write and read with intention to write lock modes. In general, the set of lock modes employed is implementation dependent and any suitable set may be employed. More generally, realizations in accordance with the present invention have been described in the context of particular embodiments. These embodiments are meant to be illustrative and not limiting. Accordingly, plural instances may be provided for components described herein as a single instance. Boundaries between various components, operations and data stores are somewhat arbitrary, and particular operations are illustrated in the context of specific illustrative configurations. Other allocations of functionality are envisioned and may fall within the scope of claims that follow. Finally, structures and functionality presented as discrete components in the exemplary configurations may be implemented as a combined structure or component. These and other variations, modifications, additions, and improvements may fall within the scope of the invention as defined in the claims that follow.
|
Same subclass Same class Consider this |
||||||||||
