class RDF::Transaction

An RDF transaction.

Transactions provide an ACID scope for queries and mutations.

Repository implementations may provide support for transactional updates by providing an atomic implementation of {Mutable#apply_changeset} and responding to `#supports?(:atomic_write)` with `true`.

We carefully distinguish between read-only and read/write transactions, in order to enable repository implementations to take out the appropriate locks for concurrency control. Transactions are read-only by default; mutability must be explicitly requested on construction in order to obtain a read/write transaction.

Individual repositories may make their own sets of guarantees within the transaction's scope. In case repository implementations should be unable to provide full ACID guarantees for transactions, that must be clearly indicated in their documentation. If update atomicity is not provided, `#supports?(:atomic_write)` must respond `false`.

@example Executing a read-only transaction

repository = RDF::Repository.new

RDF::Transaction.begin(repository) do |tx|
  tx.query(predicate: RDF::Vocab::DOAP.developer) do |statement|
    puts statement.inspect
  end
end

@example Executing a read/write transaction

repository = RDF::Repository.new

RDF::Transaction.begin(repository, mutable: true) do |tx|
  subject = RDF::URI("http://example.org/article")
  tx.delete [subject, RDF::RDFS.label, "Old title"]
  tx.insert [subject, RDF::RDFS.label, "New title"]
end

The base class provides an atomic write implementation depending on `RDF::Changeset` and using `Changeset#apply`. Custom `Repositories` can implement a minimial write-atomic transactions by overriding `#apply_changeset`.

Reads within a transaction run against the live repository by default (`#isolation_level' is `:read_committed`). Repositories may provide support for snapshots by implementing `Repository#snapshot` and responding `true` to `#supports?(:snapshots)`. In this case, the transaction will use the `RDF::Dataset` returned by `#snapshot` for reads (`:repeatable_read`).

For datastores that support transactions natively, implementation of a custom `Transaction` subclass is recommended. The `Repository` is responsible for specifying snapshot support and isolation level as appropriate. Note that repositories may provide the snapshot isolation level without implementing `#snapshot`.

@example A repository with a custom transaction class

class MyRepository < RDF::Repository
  DEFAULT_TX_CLASS = MyTransaction
  # ...
  # custom repository logic
  # ...
end

@see RDF::Changeset @see RDF::Mutable#apply_changeset @since 0.3.0

Attributes

changes[R]

RDF statement mutations to apply when executed.

@return [RDF::Changeset] @since 2.0.0

graph_name[R]

The default graph name to apply to statements inserted or deleted by the transaction.

@return [RDF::Resource, nil] @since 2.0.0

options[R]

Any additional options for this transaction.

@return [Hash{Symbol => Object}]

repository[R]

The repository being operated upon.

@return [RDF::Repository] @since 2.0.0

Public Class Methods

begin(repository, mutable: false, **options, &block) click to toggle source

Executes a transaction against the given RDF repository.

@param [RDF::Repository] repository @param [Boolean] mutable (false)

Whether this is a read-only or read/write transaction.

@param [Hash{Symbol => Object}] options @yield [tx] @yieldparam [RDF::Transaction] tx @return [void]

# File lib/rdf/transaction.rb, line 90
def self.begin(repository, mutable: false, **options, &block)
  self.new(repository, options.merge(mutable: mutable), &block)
end
new(repository, graph_name: nil, mutable: false, **options, &block) click to toggle source

Initializes this transaction.

@param [Hash{Symbol => Object}] options @param [Boolean] mutable (false)

Whether this is a read-only or read/write transaction.

@yield [tx] @yieldparam [RDF::Transaction] tx

# File lib/rdf/transaction.rb, line 130
def initialize(repository, graph_name: nil, mutable: false, **options, &block)
  @repository = repository
  @snapshot = 
    repository.supports?(:snapshots) ? repository.snapshot : repository
  @options    = options.dup
  @mutable    = mutable
  @graph_name = graph_name

  raise TransactionError, 
        'Tried to open a mutable transaction on an immutable repository' if
    @mutable && !@repository.mutable?

  @changes = RDF::Changeset.new
  
  if block_given?
    case block.arity
      when 1 then block.call(self)
      else self.instance_eval(&block)
    end
  end
end

Public Instance Methods

each(*args, &block) click to toggle source

@see RDF::Enumerable#each

# File lib/rdf/transaction.rb, line 76
def each(*args, &block)
  read_target.each(*args, &block)
end
execute() click to toggle source

Executes the transaction

@return [Boolean] `true` if the changes are successfully applied. @raise [TransactionError] if the transaction can't be applied

# File lib/rdf/transaction.rb, line 215
def execute
  raise TransactionError, 'Cannot execute a rolled back transaction. '                                'Open a new one instead.' if @rolledback
  @changes.apply(@repository)
end
has_statement?(statement) click to toggle source

@see RDF::Enumerable#has_statement?

# File lib/rdf/transaction.rb, line 188
def has_statement?(statement)
  read_target.has_statement?(statement)
end
inspect() click to toggle source

Returns a developer-friendly representation of this transaction.

@return [String]

# File lib/rdf/transaction.rb, line 196
def inspect
  sprintf("#<%s:%#0x(changes: -%d/+%d)>", self.class.name,
    self.__id__, self.changes.deletes.count, self.changes.inserts.count)
end
inspect!() click to toggle source

Outputs a developer-friendly representation of this transaction to `stderr`.

@return [void]

# File lib/rdf/transaction.rb, line 206
def inspect!
  $stderr.puts(inspect)
end
isolation_level() click to toggle source

@see RDF::Dataset#isolation_level

# File lib/rdf/transaction.rb, line 154
def isolation_level
  return :repeatable_read if repository.supports?(:snapshots)
  :read_committed
end
mutable?() click to toggle source

Returns `true` if this is a read/write transaction, `false` otherwise.

@return [Boolean] @see RDF::Writable#mutable?

# File lib/rdf/transaction.rb, line 173
def mutable?
  @mutable
end
readable?() click to toggle source

Returns `true` to indicate that this transaction is readable.

@return [Boolean] @see RDF::Readable#readable?

# File lib/rdf/transaction.rb, line 182
def readable?
  true
end
rollback() click to toggle source

Rolls back the transaction

@note: the base class simply replaces its current `Changeset` with a

fresh one. Other implementations may need to explictly rollback 
at the supporting datastore.

@note: clients should not rely on using same transaction instance after

rollback.

@return [Boolean] `true` if the changes are successfully applied.

# File lib/rdf/transaction.rb, line 232
def rollback
  @changes = RDF::Changeset.new
  @rolledback = true
end
writable?() click to toggle source

Returns `true` if this is a read/write transaction, `false` otherwise.

@return [Boolean] @see RDF::Writable#writable?

# File lib/rdf/transaction.rb, line 164
def writable?
  @mutable
end

Protected Instance Methods

delete_statement(statement) click to toggle source

Appends an RDF statement to the sequence to delete when executed.

@param [RDF::Statement] statement @return [void] @see RDF::Mutable#delete_statement

# File lib/rdf/transaction.rb, line 255
def delete_statement(statement)
  @changes.delete(process_statement(statement))
end
insert_statement(statement) click to toggle source

Appends an RDF statement to the sequence to insert when executed.

@param [RDF::Statement] statement @return [void] @see RDF::Writable#insert_statement

# File lib/rdf/transaction.rb, line 245
def insert_statement(statement)
  @changes.insert(process_statement(statement))
end
query_execute(*args, &block) click to toggle source
# File lib/rdf/transaction.rb, line 263
def query_execute(*args, &block)
  read_target.send(:query_execute, *args, &block)
end
query_pattern(*args, &block) click to toggle source
# File lib/rdf/transaction.rb, line 259
def query_pattern(*args, &block)
  read_target.send(:query_pattern, *args, &block)
end

Private Instance Methods

process_statement(statement) click to toggle source

@private Adds the default #graph_name to the statement, when one it does

not already have one.

@param statement [RDF::Statement] @return [RDF::Statement]

# File lib/rdf/transaction.rb, line 277
def process_statement(statement)
  if graph_name
    statement = statement.dup
    statement.graph_name = graph_name
  end
  statement
end
read_target() click to toggle source
# File lib/rdf/transaction.rb, line 285
def read_target
  return @snapshot if graph_name.nil?
  return @snapshot.project_graph(nil) if graph_name == false
  @snapshot.project_graph(graph_name)
end