Skip to content

Latest commit

 

History

History
149 lines (112 loc) · 9.99 KB

merge_transactions.markdown

File metadata and controls

149 lines (112 loc) · 9.99 KB

Merge Transactions in Project:M36

Introduction

Common database management systems offer one transaction "stream" whereby multiple database users are simultaneously contending for commits to access or change a single database state. Database management systems implement complex locking and caching in order to simulate concurrent access to this state.

Project:M36 allows for user-defined named "heads" which represent states (branches) onto which users can tack on additional transactions. This feature enables:

  • experimental changes which may or may not be merged back to the main state of the database
  • reduced contention over a single head
  • genuinely transaction-safe DDL (schema definition changes) without locking, caveats, or abstraction leaks
  • merge transactions according to business logic
  • elimination of "serialization errors"

Programmers are likely familiar with this workflow since this behavior is analogous to source version control branching and merging. Project:M36 uses terminology from git version control.

Command Summary

Command Explanation
:branch <branch name> Create a new branch.
:jumphead <head name> Jump to the named head.
:jump <transaction id> Jump to an arbitrarily-identified transaction. This allows the session's context to travel back in time to a part of the graph which is not a head.
:showgraph Display the transaction graph as a relation.
:mergetrans <merge strategy> <branchA> <branchB> Merge one branch onto another. This requires that the current context is on one of the heads. The "merge strategy" is one of "union", "unionpreferbranch <branch name>" , or "selectedbranch".

Merge Strategies

Selected Branch Merge

The naivest merge strategy is to pull in the database context of one branch while discarding the context of a second branch. This merge strategy is useful for marking the end of an experimental branch that is to be considered discarded or a dead end. This merge strategy always succeeds.

Union Merge

The union merge strategy attempts to merge the components of the two branches' database contexts. These components are:

  • inclusion dependencies (constraints)
  • atom functions
  • notifications
  • types
  • relation variables

If both branches have a atom function, etc. of the same name, then the merge fails with an unresolvable conflict.

As a special case for relation variables, if both branches have a relation variable of the same name and type (same attributes), then the resultant merged relation variable is a union of both branches' relation variables.

Union Prefer Branch Merge

The union-prefer-branch merge is identical to union merge except that, when encountering a conflict case as described above, the strategy chooses a component from the preferred branch. Thus, this merge strategy always succeeds.

Additional Strategies

Project:M36 will support more complex merge strategies in the future. In particular, business logic will likely dictate that Project:M36 support user-defined strategies, though this is not yet supported.

TutorialD Usage

Branching and merging are built-in to the Project:M36 tutd client.

First, let's create a new branch for some new sales data.

TutorialD (master/main): :branch add_sales_feature
TutorialD (add_sales_feature):

Note how the name in parentheses changes as we create and implicitly jump to the new "add_sales_feature" branch.

Next, we will add the sales data.

TutorialD (add_sales_feature): sales := relation{tuple{amount 45, product "peanuts"},tuple{amount 20, product "onions"}}
TutorialD (add_sales_feature): :showexpr sales
┌───────────┬─────────────┐
│amount::Int│product::Text│
├───────────┼─────────────┤
│45         │"peanuts"    │
│20         │"onions"     │
└───────────┴─────────────┘
TutorialD (add_sales_feature): :commit

Now, the relation variable "sales" is available on the "add_sales_feature" branch, but not the "master" branch. Let's verify this.

TutorialD (add_sales_feature): :jumphead master
TutorialD (master/main): :showexpr sales
ERR: RelVarNotDefinedError "sales"

Now that we are on the "master" branch, let's add a relation variables unrelated to sales.

TutorialD (master/main): employees := relation{tuple{name "Steve"},tuple{name "Cindy"}}
TutorialD (master/main): :commit

Note that the "employees" relation variable is only available on the "master" branch and not on the "add_sales_feature" branch.

Let's look at the transaction graph:

TutorialD (master/main): :showgraph
┌─────────────┬───────────────────┬──────────────────────────────────────┬────────────────────────────────────────┐
│current::Bool│head::Text         │id::Text                              │parents::relation {id::Text}            │
├─────────────┼───────────────────┼──────────────────────────────────────┼────────────────────────────────────────┤
│False        │""                 │"0d3bf914-1695-4f95-ab93-6c543d7b3dfa"│┌──────────────────────────────────────┐│
│             │                   │                                      ││id::Text                              ││
│             │                   │                                      │├──────────────────────────────────────┤│
│             │                   │                                      ││"0e51d95b-5578-4190-802b-9ef280a7f386"││
│             │                   │                                      │└──────────────────────────────────────┘│
│False        │"add_sales_feature"│"8b4d4eaf-31a3-4e48-85d3-bf6e8ac101ea"│┌──────────────────────────────────────┐│
│             │                   │                                      ││id::Text                              ││
│             │                   │                                      │├──────────────────────────────────────┤│
│             │                   │                                      ││"0d3bf914-1695-4f95-ab93-6c543d7b3dfa"││
│             │                   │                                      │└──────────────────────────────────────┘│
│False        │""                 │"0e51d95b-5578-4190-802b-9ef280a7f386"│┌────────┐                              │
│             │                   │                                      ││id::Text│                              │
│             │                   │                                      │└────────┘                              │
│True         │"master"           │"e4228cb8-e792-48c9-b987-7d78fb1abe04"│┌──────────────────────────────────────┐│
│             │                   │                                      ││id::Text                              ││
│             │                   │                                      │├──────────────────────────────────────┤│
│             │                   │                                      ││"0e51d95b-5578-4190-802b-9ef280a7f386"││
│             │                   │                                      │└──────────────────────────────────────┘│
└─────────────┴───────────────────┴──────────────────────────────────────┴────────────────────────────────────────┘

:showgraph shows us which database context is "current" and which parents are linked to each transaction.

Now, imagine that the work on the "add_sales_merge" branch is complete, so we will merge the "add_sales_feature" branch back into the "master" branch. We say we are merging "back" because both branches share a common ancestor transaction.

TutorialD (master/main): :mergetrans union add_sales_feature master
TutorialD (master/main): :showexpr employees
┌──────────┐
│name::Text│
├──────────┤
│"Steve"   │
│"Cindy"   │
└──────────┘
TutorialD (master/main): :showexpr sales
┌───────────┬─────────────┐
│amount::Int│product::Text│
├───────────┼─────────────┤
│45         │"peanuts"    │
│20         │"onions"     │
└───────────┴─────────────┘

After the merge, both relation variables appear in resultant merged transaction. This is because we used a "union merge strategy" which tries to mash both database contexts together.