Simple Tutorial

This page provides a series of declarations and complex updates that tests the correctness and explains our TR implementation. The tutorial consists of 11 parts:


  1. Initially, the database is empty. The following commands declare and insert tuples on p/2, q/2 and r/1.

    |?- declare p/2 key 2.

    yes

    declare p/2 with the whole tuple as the key.
    |?- trans create(p(1,a)) sc create(p(2,b)).

    yes

    ?- p(X,Y).

    X = 1
    Y = a;

    X = 2
    Y = b;

    no

    |?- declare q/2 key 1.

    yes

    declare q/2 with the first argument as the key.
    |?- trans create(q(1,a)) sc create(q(2,b)).

    yes

    |?- q(X,Y).

    X = 1
    Y = a;

    X = 2
    Y = b;

    no

    |?- declare r/1.

    yes

    declare r/1. No key field is indicated in the transaction, so the whole tuple is used as the key.
    |?- trans create r(1) sc create r(2).

    yes

    |?- r(X).

    X = 1;

    X = 2;

    no

    |?- trans create(p(1,c)).

    yes

    p(1,c) successfully created.
    |?- trans create(q(1,c)).

    no

    create q(1,c) failed because the first argument is the key and q(1,a) is already in the database.
    ?- q(1,X).

    X = a;

    no

    q(1,c) is not in the database.


  2. Transaction rules and database rules can be added using loadtrans/1 and loadbase/1, as follows. File 1 is a transaction program file, and File 2 contains database rules only. The following points should be noticed in this example:
    • Compare the difference in syntax between database rules and transaction rules.
    • All database predicates must be declared.
    • All interactive commands are database queries, unless enclosed inside trans (.), in which case, they are transactions.
    • These files are not serial Horn, since p occurs in the body of a database rule and the head of a transaction rule.

    File 1:

    a(X,Y) <- p(X,Y) sc r(X).
    q(1,1).
    q(1,2).
    p(2,2).
    r(1).

    File 2:

    declare q/2 key 1.
    declare r/1.
    b(X,Y) :- p(X,Y), r(X).
    q(1,1).
    p(1,a).
    p(2,a).
    r(2).

    |?- loadbase(file2).

    yes

    |?- loadtrans(file1).

    yes

    Base predicates in the transaction base do not require declare statement because they are derived, not stored, and are not restricted by the key of database predicate with same name and number of arguments.
    |?- r(X).

    X = 2;

    no

    A database query considers data in database only.
    |?- trans r(X).

    X = 2;

    X = 1;

    no

    A transaction query considers data in both database and transaction base.
    |?- b(X,Y).

    X = 2
    Y = a;

    no

    A database rule accesses predicate in the database only.
    |?- trans b(X,Y).

    X = 2
    Y = a;

    no

    Even in a transaction query, database rule still only consider database items only. So, r(1) in the transaction base is not considered by in the database rule.
    |?- a(X,Y).

    no

    Transaction rules are invisible to database queries.
    |?- trans a(X,Y).

    X = 2
    Y = 2;

    X = 1
    Y = a;

    X = 2
    Y = a;

    no

    A transaction rule considers data in both transaction base and database.


  3. Test of destroy and set. The database contains p(1,a), p(2,b), p(1,c), q(1,a), q(2,b), q(3,a), r(1), r(2), r(3), where p/2 key 2, q/2 key 1, and r/1 key 1 are declared. The transaction base contains r(4), s(4).

    |?- trans destroy(r(4)).

    no

    Destroying q predicate in the transaction base is not allowed.
    |?- trans destroy(s(4)).
    Undefined predicate/function: destroy_1_r/1

    no

    The destory operation is defined only on tuples declared in the database. Since no tuple about s is declared in the database, the destroy operation is undefined here.
    |?- trans destroy(p(1,c)).

    yes

    removes p(1,c) from database
    |?- p(X,Y).

    X = 1
    Y = a;

    X = 2
    Y = b;

    no

    Now, p(1,a) and p(2,b) are left in database.
    |?- trans destroy(p(X,Y)).

    X = 1
    Y = a;

    X = 2
    Y = b

    yes

    We can select an atom to delete. First, p(X,Y) is unified to p(1,a), and delete it. Then, the deletion rolled back and delete p(2,b).
    |?- p(X,Y).

    X = 1
    Y = a;

    no

    p(2,b) was deleted, but p(1,a) was not.
    |?- trans destroy(q(X,Y)) sc write('aBc ') sc fail.
    aBc aBc aBc
    no
    The execution deletes an atom from q(X,Y), then printed the word ``aBc'', and then forced to fail. So, the number of times ``aBc'' shown indicated the number of times destroy(q(X,Y)) succeeded which is the same as number of backtracking, or the number of atoms of q(X,Y). Three ``aBc'' were shown, so q(X,Y) has three tuples.
    ?- q(X,Y).

    X = 1
    Y = a;

    X = 2
    Y = b;

    X = 3
    Y = a;

    no

    The extent and order of q(X,Y) was perserved.
    |?- trans destroy(q(X,Y)) sc destroy(r(Z)) sc write('aBc ') sc fail.
    aBc aBc aBc aBc aBc aBc aBc aBc aBc
    no
    There are 3*3 choices for backtracking p and q, i.e., (X,Y,Z) are (1,a,1), (1,a,2), (1,a,3), (2,b,1), (2,b,2), (2,b,3 (3,a,1), (3,a,3), (3,a,3). So, 9 ``aBc'' are shown.
    |?- trans set q(1,b).

    yes

    change the value of q(1,a) to q(1,b)
    ?- q(X,Y).

    X = 1
    Y = b;

    X = 2
    Y = b;

    X = 3
    Y = a;

    no

    |?- trans set q(4,a).

    no

    'set' is not equivalent to 'create'. If the key is not already in the database, then 'set' fails.
    |?- trans set p(1,b).
    Undefined predicate/function: set_2_p/2

    no

    'set' is not defined on p/2 because the whole predicate is the key (i.e., there are no non-key attributes whose values can be set).


  4. Test of bulk insertion and deletion. Database contains p(1,a), p(2,b), p(3,a), r(4). Transaction base contains p(5,5).

    |?- trans bulk\_ins(p(X,Y),r(X)).

    X = _1264924
    Y = _1264740

    yes

    bulk insertion of p(X,Y) into r(X), so r(1), r(2) and r(3) are added to the database. Also, it copies data from the database only.
    |?- r(X).

    X = 4;

    X = 1;

    X = 2;

    X = 3;

    no

    |?- trans bulk_ins(p(X,Y),q(X,Y)) sc fail.

    no

    Bulk insertion into q forced to fail and backtrack, so q(X,Y) should still contains no tuples.
    |?- q(X,Y).

    no

    |?- trans bulk_del(p(X,a)).

    X = _1264776

    yes

    bulk deletion of p(X,a), so p(1,a) and p(3,a) should be deleted.
    |?- p(X,Y).

    X = 2
    Y = b;

    no

    p(2,b) should be the only p-tuple left.
    |?- trans bulk\_del(p(X,Y)) sc fail.

    no

    A bulk deletion forced to fail and backtrack, so p(2,b) is still in the database after the execution.
    |?- p(X,Y).

    X = 2
    Y = b;

    no


  5. Bulk Assignment. Database contains p(1,a), p(1,b), p(2,b), q(3,c) and r(b). Transaction base contains p(1,1).

    |?- trans q(X,Y) := p(X,Y).

    X = _1267740
    Y = _1267744

    yes

    Since q/2 has the first argument as key, the assignment cannot copy both q(1,a) and q(1,b) to the database, so one of them is chosen non-deterministically.
    ?- q(X,Y).

    X = 1
    Y = a;

    X = 2
    Y = b;

    no

    Also, p(1,1) in the transaction base has no effect on the assignment.
    |?- trans q(X,Y) := (p(X,Y), r(Y)).

    X = _1268180
    Y = _1268188

    yes

    This test shows the use of a database formula (q(X,Y),r(Y)) rather than a single query in assignment. Since r has only one tuple in the database, i.e., r(b), Y is bound to b, and so p(1,b) and p(2,b) are satisfied.
    |?- q(X,Y).

    X = 1
    Y = b;

    X = 2
    Y = b;

    no

    q(1,b) and q(2,b) were inserted.


  6. Test of while loop and for loop. Transaction base contains p(4,4) and database contains p(1,a), p(2,b), p(3,a), and s/2 is declared with whole tuple as the key. The following cases are tested separately. Note that the transaction base predicate, p(4,4) has no effect in loop conditions.

    Case 1:

    |?- trans Y = a sc while p(X,Y) do destroy(p(X,Y)).

    Y = a
    X = _1265180

    yes

    Although Y is bound to a, inside the while loop, it is still considered to be a free variable. So, p is empty after the execution.
    |?- p(X,Y).

    no

    |?- trans p(X,Y).

    X = 4
    Y = 4;

    yes

    p(4,4) is still in transaction base.

    Case 2:

    |?- trans Y = a sc with [Y] while p(X,Y) do destroy(p(X,Y)).

    Y = a
    X = _1265312

    yes

    The term ``with [Y]'' in the loop causes Y to be imported into the loop. Since Y is bound to 1, database atoms of the form p(X,1) are destroyed during the execution of the loop.
    |?- p(X,Y).

    X = 2
    Y = b;

    no

    Case 3:

    |?- trans Y = a sc forall [X,Y] in p(X,Y) do create(s(X,Y)).

    Y = a
    X = _1265380

    yes

    As in case 1 above, Y is unified to a, but it is still a free variable inside the for-loop. So, the loop iterates on all pairs X,Y. So, when the loop terminates, s will contain the same information as p.
    |?- s(X,Y).

    X = 2
    Y = b;

    X = 1
    Y = a;

    X = 3
    Y = a;

    no

    X,Y unified with data in database only, so s(4,4) was not created.

    Case 4:

    |?- trans Y = a sc with [Y] forall [X,Y] in p(X,Y) do create(s(X,Y)).

    Y = a
    X = _1265512

    yes

    As in case 2 above, Y = a is imported into the for loop, so X binds to 1 and 3 during loop iteration, and s(1,a), s(3,a) are created.
    |?- s(X,Y).

    X = 1
    Y = a;

    X = 3
    Y = a;

    no


  7. Tests on different types of for-loop. The database contains p(1,a), p(2,b), p(3,a).

    |?- trans forall [Y] in p(X,Y) do write('aBc ').
    aBc aBc aBc
    Y = _1264920
    X = _1264964

    yes

    Y = a,b,a are chosen
    |?- trans forall unique [Y] in p(X,Y) do write('aBc ').
    aBc aBc
    Y = _1264936
    X = _1264980

    yes

    Y = a,b are chosen
    |?- trans forall possible [Y] in p(X,Y) do write('aBc ').
    aBc aBc
    Y = _1264936
    X = _1264980

    yes

    Y = a,b are chosen
    |?- trans forall [X,Y] in p(X,Y) do write('ac ') sc write('l ') sc fail.
    ac ac ac l
    no
    Forced failure of for loop but no attemp to re-execute it in a different way.
    |?- trans forall unique [X,Y] in p(X,Y) do write('ac ') sc write('l ') sc fail.
    ac ac ac l
    no
    Forced failure of for loop but no attemp to re-execute it in a different way
    |?- trans forall possible [X,Y] in p(X,Y) do write('ac ') sc write('l ') sc fail.
    ac ac ac l ac ac l ac ac ac l ac ac l ac ac ac l ac ac l
    no
    Loop passed sixth times (six '1' printed). Note that sometimes there are 3 iterations to execute a loop, but sometimes there are only two, because the transaction does not backtrack to the first iteration. For example, the order p(X,Y) in the first succeeded loop is p(1,a), p(2,b), p(3,a). When it was forced to fail, it backtracks and the second and the third iteration only, i.e., exchange the order to p(1,a), p(3,a), p(2,b). So, only 2 'ac' printed in the second loop. In the next backtracking, the iteration backtracks to the first iteration, and the order is p(2,b), p(1,a), p(3,a). Eventually, all possible combinations are chosen.


  8. Test on backtracking of for-loop and while-loop, database contains p(1,a), p(2,b), p(3,a).

    |?- trans while p(X,Y) do destroy(p(X,Y)) sc write('aBc ') sc fail.
    aBc aBc aBc aBc aBc aBc
    no
    Loop passed six times because there are 6 different orders to destroy p(X,Y).
    |?- p(X,Y).

    X = 1
    Y = a;

    X = 2
    Y = b;

    X = 3
    Y = a;

    no

    p(X,Y) unchanged.
    |?- trans forall [X,Y] in p(X,Y) do destroy(p(X,Y)) sc fail.

    no

    The for loop forced to fail. So, the destroy operations should be undone.
    |?- p(X,Y).

    X = 1
    Y = a;

    X = 2
    Y = b;

    X = 3
    Y = a;

    no

    p(X,Y) unchanged


  9. Test on if-then-else. Database contains: p(1,a), p(2,b), q(1), q(2).

    |?- trans q(X) sc if p(X,Y) then write(Y) else write(X).
    a
    X = 1
    Y = a;
    b
    X = 2
    Y = b;

    no

    X unifies to 1 first, then the condition p(1,Y) succeeds, unifying Y to a in the then-part, which writes a. The transaction backtracks to unify X to 2 and Y to b.
    |?- trans q(X) sc if p(X,Y) then write(Y).
    a
    X = 1
    Y = a;
    b
    X = 2
    Y = b;

    no

    Since in the above test, the else-part was not used, we can drop the else-part and obtain same result.
    |?- trans if p(1,1) then write(then) else write(else).
    else

    yes

    Since p(1,1) is not in the database, else-part is executed.
    |?- trans if p(1,1) then write(then).

    yes

    If the condition fails, an if-then formula is still true.


  10. Test of Nested Loops. Database contains: p(1,1), p(2,1), p(2,2), p(3,3), q(1), q(2).

    Case 1:
    |?- trans forall [X] in q(X) do (with [X] while p(X,Y) do destroy(p(X,Y))).

    X = _1265480
    Y = _1265012

    yes

    |?- p(X,Y).

    X = 3
    Y = 3;

    no

    In the forall loop, X is successfully unified to 1 and 2. So, all tuples of the form p(1,Y) and p(2,Y) are eventually deleted, leaving p(3,3) in database.

    Case 2:

    |?- trans while q(X) do (write(X) sc with [X] forall [Y] in p(X,Y) do destroy(q(Y))).
    122
    X = _1265488
    Y = _1265564

    yes

    |?- q(X).

    no

    Explanation : Normally, this execution should fail because in the first iteration of the outer loop, X is bound to 1, and q(1) is destroyed in the forall loop. In the next iteration of the outer loop, X is bound to 2. The inner loop becomes forall [Y] in p(2,Y) do destroy(q(Y)), and Y is unified to 1 and 2 in the iterations. The iteration fails because destroy(q(1)) fails in the execution of this inner loop. Therefore, the program backtracks and chooses other order to executing the outer while loop. During backtracking of the outer loop, the program chooses to bind X to 2 first. Then, both q(1) and q(2) are destroyed in the forall loop. Since there is no q(X) left in the database, the while loop succeeds.


  11. Test of Recursion Through Iteration.

    Database contains: p(1,a), p(1,b), p(1,2), p(2,a).
    Transaction base contains the rule:
    a(X) <- with [X] forall [Y] in p(X,Y) do (write(Y) sc a(Y)).

    |?- trans a(1).
    ab2a

    yes

    Explanation : During execution of the loop, Y is unified successively to a, b, and 2. In the first iteration, a is printed and a(a) is called, i.e., forall [Y] in p(a,Y) do destroy(q(Y)). Since there is no p(a,Y) in database, the loop succeeds without any iteration. In the second iteration, b is printed and a(b) is called. Similar, there is no p(b,Y) in the database, and so a(b) succeeds without any updates to database. In the third iteration, 2 is printed and a(2) is called. Since p(2,a) is in database, the loop iterates, binding Y to a, so a is printed.



More examples:


Back to the main page