Class: RDF::Transaction

Inherits:
Object
  • Object
show all
Includes:
Enumerable, Mutable, Queryable
Defined in:
lib/rdf/transaction.rb

Overview

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.

The base class provides an atomic write implementation depending on Changeset and using Changeset#apply. Custom Repository classes can implement a minimial write-atomic transactions by overriding Mutable#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 Dataset returned by Mutable#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 Mutable#snapshot.

Examples:

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

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

A repository with a custom transaction class

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

See Also:

Since:

  • 0.3.0

Defined Under Namespace

Classes: TransactionError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Queryable

#first, #first_literal, #first_object, #first_predicate, #first_subject, #first_value, #query

Methods included from Enumerable

#dump, #each_graph, #each_object, #each_predicate, #each_quad, #each_statement, #each_subject, #each_term, #each_triple, #enum_graph, #enum_object, #enum_predicate, #enum_quad, #enum_statement, #enum_subject, #enum_term, #enum_triple, #graph_names, #has_graph?, #has_object?, #has_predicate?, #has_quad?, #has_subject?, #has_term?, #has_triple?, #invalid?, #method_missing, #objects, #predicates, #project_graph, #quads, #respond_to_missing?, #statements, #subjects, #supports?, #terms, #to_a, #to_h, #to_set, #triples, #valid?, #validate!

Methods included from Util::Aliasing::LateBound

#alias_method

Methods included from Countable

#count, #empty?

Methods included from Mutable

#<<, #apply_changeset, #clear, #delete, #delete_insert, #delete_statements, #immutable?, #insert, #load, #method_missing, #respond_to_missing?, #snapshot, #update

Methods included from Writable

#<<, #insert, #insert_graph, #insert_reader, #insert_statements

Constructor Details

#initialize(repository, graph_name: nil, mutable: false, **options) {|tx| ... } ⇒ Transaction

Initializes this transaction.

Parameters:

  • options (Hash{Symbol => Object})
  • mutable (Boolean)

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

Yields:

  • (tx)

Yield Parameters:

Raises:

Since:

  • 0.3.0



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# 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

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class RDF::Enumerable

Instance Attribute Details

#changesRDF::Changeset (readonly)

RDF statement mutations to apply when executed.

Returns:

Since:

  • 2.0.0



114
115
116
# File 'lib/rdf/transaction.rb', line 114

def changes
  @changes
end

#graph_nameRDF::Resource? (readonly)

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

Returns:

Since:

  • 2.0.0



107
108
109
# File 'lib/rdf/transaction.rb', line 107

def graph_name
  @graph_name
end

#optionsHash{Symbol => Object} (readonly)

Any additional options for this transaction.

Returns:

  • (Hash{Symbol => Object})

Since:

  • 0.3.0



120
121
122
# File 'lib/rdf/transaction.rb', line 120

def options
  @options
end

#repositoryRDF::Repository (readonly)

The repository being operated upon.

Returns:

Since:

  • 2.0.0



99
100
101
# File 'lib/rdf/transaction.rb', line 99

def repository
  @repository
end

Class Method Details

.begin(repository, mutable: false, **options) {|tx| ... }

This method returns an undefined value.

Executes a transaction against the given RDF repository.

Parameters:

  • repository (RDF::Repository)
  • mutable (Boolean)

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

  • options (Hash{Symbol => Object})

Yields:

  • (tx)

Yield Parameters:

Since:

  • 0.3.0



90
91
92
# File 'lib/rdf/transaction.rb', line 90

def self.begin(repository, mutable: false, **options, &block)
  self.new(repository, mutable: mutable, **options, &block)
end

Instance Method Details

#delete_statement(statement) (protected)

This method returns an undefined value.

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

Parameters:

See Also:

Since:

  • 0.3.0



278
279
280
# File 'lib/rdf/transaction.rb', line 278

def delete_statement(statement)
  @changes.delete(process_statement(statement))
end

#each(*args, &block) ⇒ Object

See Also:

  • Enumerable#each

Since:

  • 0.3.0



76
77
78
# File 'lib/rdf/transaction.rb', line 76

def each(*args, &block)
  read_target.each(*args, &block)
end

#executeBoolean

Executes the transaction

Returns:

  • (Boolean)

    true if the changes are successfully applied.

Raises:

Since:

  • 0.3.0



238
239
240
241
242
# File 'lib/rdf/transaction.rb', line 238

def execute
  raise TransactionError, 'Cannot execute a rolled back transaction. ' \
                          'Open a new one instead.' if @rolledback
  @changes.apply(@repository)
end

#has_statement?(statement) ⇒ Boolean

Returns:

  • (Boolean)

See Also:

Since:

  • 0.3.0



211
212
213
# File 'lib/rdf/transaction.rb', line 211

def has_statement?(statement)
  read_target.has_statement?(statement)
end

#insert_statement(statement) (protected)

This method returns an undefined value.

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

Parameters:

See Also:

Since:

  • 0.3.0



268
269
270
# File 'lib/rdf/transaction.rb', line 268

def insert_statement(statement)
  @changes.insert(process_statement(statement))
end

#inspectString

Returns a developer-friendly representation of this transaction.

Returns:

  • (String)

Since:

  • 0.3.0



219
220
221
222
# File 'lib/rdf/transaction.rb', line 219

def inspect
  sprintf("#<%s:%#0x(changes: -%d/+%d)>", self.class.name,
    self.__id__, self.changes.deletes.count, self.changes.inserts.count)
end

#inspect!

This method returns an undefined value.

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

Since:

  • 0.3.0



229
230
231
# File 'lib/rdf/transaction.rb', line 229

def inspect!
  $stderr.puts(inspect)
end

#isolation_levelObject

See Also:

Since:

  • 0.3.0



154
155
156
157
# File 'lib/rdf/transaction.rb', line 154

def isolation_level
  return :repeatable_read if repository.supports?(:snapshots)
  :read_committed
end

#mutable?Boolean

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

Returns:

  • (Boolean)

See Also:

  • Writable#mutable?

Since:

  • 0.3.0



196
197
198
# File 'lib/rdf/transaction.rb', line 196

def mutable?
  @mutable
end

#mutated?Boolean

Note:

Transaction implementers may choose to NotImplementedError if the transaction implementation cannot be implemented efficiently.

Indicates whether the transaction includes changes relative to the target repository's state at transaction start time.

The response is guaranteed to be true if executing the transaction against the original repository state would cause a change. It may also return true in cases where the repository would not change (e.g. because the transaction would insert statements already present).

Returns:

  • (Boolean)

    true if the transaction has mutated (insert/delete) since transaction start time

Raises:

  • (NotImplementedError)

    if a mutation check is not implemented

Since:

  • 0.3.0



175
176
177
178
179
180
# File 'lib/rdf/transaction.rb', line 175

def mutated?
  return !changes.empty? if self.class == Transaction

  raise NotImplementedError, 
        '#mutated? is not implemented for #{self.class}'
end

#query_execute(*args, &block) ⇒ Object (protected)

Since:

  • 0.3.0



286
287
288
# File 'lib/rdf/transaction.rb', line 286

def query_execute(*args, &block)
  read_target.send(:query_execute, *args, &block)
end

#query_pattern(*args, &block) ⇒ Object (protected)

Since:

  • 0.3.0



282
283
284
# File 'lib/rdf/transaction.rb', line 282

def query_pattern(*args, &block)
  read_target.send(:query_pattern, *args, &block)
end

#readable?Boolean

Returns true to indicate that this transaction is readable.

Returns:

  • (Boolean)

See Also:

Since:

  • 0.3.0



205
206
207
# File 'lib/rdf/transaction.rb', line 205

def readable?
  true
end

#rollbackBoolean

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.

Returns:

  • (Boolean)

    true if the changes are successfully applied.

Since:

  • 0.3.0



255
256
257
258
# File 'lib/rdf/transaction.rb', line 255

def rollback
  @changes = RDF::Changeset.new
  @rolledback = true
end

#writable?Boolean

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

Returns:

  • (Boolean)

See Also:

Since:

  • 0.3.0



187
188
189
# File 'lib/rdf/transaction.rb', line 187

def writable?
  @mutable
end