Sequences Plugin

What the plugin does

The Sequences plugin provides transactional sequences for GraphDB. A sequence is a long counter that can be atomically incremented in a transaction to provide incremental IDs.

To deploy it, please follow the GitHub instructions.

Usage

The plugin supports multiple concurrent sequences where each sequence is identified by an IRI chosen by the user.

Creating a sequence

Choose an IRI for your sequence, for example http://example.com/my/seq1. Insert the following triple to create a sequence whose next value will be 1:

PREFIX seq: <http://www.ontotext.com/plugins/sequences#>
PREFIX my: <http://example.com/my/>

INSERT DATA {
    my:seq1 seq:create []
}

You can also create a sequence by providing the starting value, for example to create a sequence whose next value will be 10:

PREFIX seq: <http://www.ontotext.com/plugins/sequences#>
PREFIX my: <http://example.com/my/>

INSERT DATA {
    my:seq1 seq:create 10
}

When using the GraphDB cluster, you might get the following exception if the repository existed before registering the plugin: Update would affect a disabled plugin: sequences. You can activate the plugin with:

INSERT DATA { [] <http://www.ontotext.com/owlim/system#startplugin> "sequences".}

Using a sequence

Processing sequence values on the client

In this scenario, new and current sequence values can be retrieved on the client where they can be used to generate new data that can be added to GraphDB in the same transaction. For a workaround in the cluster, see here.

Note

Using the below examples will not work inside the GraphDB Workbench as they need to be executed in one single transaction, and if run one by one, they would be performed in separate transactions. See here how to execute them in one transaction.

To use any sequence, you must first start a transaction and then prepare the sequences for use by executing the following update:

PREFIX seq: <http://www.ontotext.com/plugins/sequences#>

INSERT DATA {
    [] seq:prepare []
}

Then you can request new values from any sequence by running a query like this (for the sequence http://example.com/my/seq1):

PREFIX seq: <http://www.ontotext.com/plugins/sequences#>
PREFIX my: <http://example.com/my/>

SELECT ?next {
    my:seq1 seq:nextValue ?next
}

To query the last new value without incrementing the counter, you can use a query like this:

PREFIX seq: <http://www.ontotext.com/plugins/sequences#>
PREFIX my: <http://example.com/my/>

SELECT ?current {
    my:seq1 seq:currentValue ?current
}

Use the obtained values to construct IRIs, assign IDs, or any other use case.

Using sequence values only on the server

In this scenario, new and current sequence values are available only within the execution context of a SPARQL INSERT update. New data using the sequence values can be generated by the same INSERT and added to GraphDB.

The following example prepares the sequences for use and inserts some new data using the sequence http://example.com/my/seq1 where the subject of the newly inserted data is created from a value obtained from the sequence.

The example will work both in:

  • the GraphDB cluster – as new sequence values do not need to be exposed to the client.

  • the GraphDB Workbench – as it performs everything in a single transaction by separating individual operations using a semicolon.

PREFIX seq: <http://www.ontotext.com/plugins/sequences#>
PREFIX my: <http://example.com/my/>

# Prepares sequences for use
INSERT DATA {
    [] seq:prepare []
};

# Obtains a new value from the sequence and creates an IRI based on it,
# then inserts new triples using that IRI
INSERT {
    ?subject rdfs:label "This is my new document" ;
            a my:Type1
} WHERE {
    my:seq1 seq:nextValue ?next
    BIND(IRI(CONCAT("http://example.com/my-data/test/", STR(?next))) as ?subject)
};

# Retrieves the last obtained value, recreates the same IRI,
# and adds more data using the same IRI
INSERT {
    ?subject rdfs:comment ?comment ;
} WHERE {
    my:seq1 seq:currentValue ?current
    BIND(IRI(CONCAT("http://example.com/my-data/test/", STR(?current))) as ?subject)
    BIND(CONCAT("The document ID is ", STR(?current)) as ?comment)
}

After that, commit the transaction.

Dropping a sequence

Dropping a sequence is similar to creating it. For example, to drop the sequence http://example.com/my/seq1, execute this:

PREFIX seq: <http://www.ontotext.com/plugins/sequences#>
PREFIX my: <http://example.com/my/>

INSERT DATA {
    my:seq1 seq:drop []
}

Resetting a sequence

In some cases, you might want to reset an existing sequence such that its next value will be a different number. Resetting is equivalent to dropping and recreating the sequence.

To reset a sequence such that its next value will be 1, execute this update:

PREFIX seq: <http://www.ontotext.com/plugins/sequences#>
PREFIX my: <http://example.com/my/>

INSERT DATA {
    my:seq1 seq:reset []
}

You can also reset a sequence by providing the starting value. For example, to reset a sequence such that its next value will be 10, execute:

PREFIX seq: <http://www.ontotext.com/plugins/sequences#>
PREFIX my: <http://example.com/my/>

INSERT DATA {
    my:seq1 seq:reset 10
}

Workaround for using sequence values on the client with the cluster

If you need to process your sequence values on the client in a GraphDB 9.x cluster environment, you can create a single-node (i.e., not part of a cluster) worker repository to provide the sequences. It is most convenient to have that repository on the same GraphDB instance as your primary master repository.

Let’s call the master repository where you will store your data master1 and the second worker repository where you will create and use your sequences seqrepo1.

Managing sequences

Execute all create, drop, and reset statements in seqrepo1.

The examples below assume that you have created a sequence http://example.com/my/seq1.

Using sequences on the client

  1. First, you need to obtain one or more new sequence values from the repository seqrepo1:

    1. Start a transaction in seqrepo1.

    2. Prepare the sequences for use by executing this in the same transaction:

      PREFIX seq: <http://www.ontotext.com/plugins/sequences#>
      
      INSERT DATA {
          [] seq:prepare []
      }
      
    3. Obtain one or more new sequence values from the sequence http://example.com/my/seq1:

      PREFIX seq: <http://www.ontotext.com/plugins/sequences#>
      PREFIX my: <http://example.com/my/>
      
      SELECT ?next {
          my:seq1 seq:nextValue ?next
      }
      
    4. Commit the transaction in seqrepo1.

  2. Then you can process the obtained values on the client, generate new data, and insert it into the master repository master1:

    1. Start a transaction in master1.

    2. Insert data using the obtained sequence values.

    3. Commit the transaction in master1.

Handling backups

To always ensure data consistency with backups, follow this order:

  • Backup

    1. Backup the master repository master1 first.

    2. Backup the sequence repository seqrepo1 second.

  • Restore

    1. Restore the sequence repository seqrepo1 first.

    2. Restore the master repository master1 second.

An alternative would be to not back up the seqrepo1 repository but simply recreate the repository and the sequence (or reset the sequence) with the next potential sequence value from the master1 repository. Here is a sample query that retrieves the next potential value (which is equal to the last used value + 1):

PREFIX ent: <http://www.ontotext.com/owlim/entity#>
PREFIX my: <http://example.com/my/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
SELECT ?nextValue WHERE {
    ?type a my:Type1;
          ent:id ?id .
    BIND(xsd:int(REPLACE(STR(?type), "http://example.com/my-data/test/", "")) + 1 as ?nextValue)
}
ORDER BY DESC(?id)
LIMIT 1

Note that this example assumes that sequence values were used to generate IRIs, and IRIs with higher values were used for the first time after IRIs with lower values were used.