ld-patch 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/AUTHORS +1 -0
- data/LICENSE +25 -0
- data/README.md +81 -0
- data/VERSION +1 -0
- data/bin/ldpatch +91 -0
- data/lib/ld/patch.rb +79 -0
- data/lib/ld/patch/algebra.rb +24 -0
- data/lib/ld/patch/algebra/add.rb +59 -0
- data/lib/ld/patch/algebra/bind.rb +90 -0
- data/lib/ld/patch/algebra/constraint.rb +56 -0
- data/lib/ld/patch/algebra/cut.rb +56 -0
- data/lib/ld/patch/algebra/delete.rb +59 -0
- data/lib/ld/patch/algebra/index.rb +34 -0
- data/lib/ld/patch/algebra/patch.rb +40 -0
- data/lib/ld/patch/algebra/path.rb +71 -0
- data/lib/ld/patch/algebra/prefix.rb +48 -0
- data/lib/ld/patch/algebra/reverse.rb +39 -0
- data/lib/ld/patch/algebra/update_list.rb +77 -0
- data/lib/ld/patch/meta.rb +2666 -0
- data/lib/ld/patch/parser.rb +621 -0
- data/lib/ld/patch/terminals.rb +91 -0
- data/lib/ld/patch/version.rb +18 -0
- metadata +275 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2eb0185c5e3d87a311c57b6460363bf5e479fd0a
|
4
|
+
data.tar.gz: fd9772612616488a2ce9c595fa01ff3ebfff3879
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9a0aece9207a9d91d34706b19051f54c95934ca4cd017555eefda8aa0a032f1c2c4cbbddd4cdb2492ba966f3866e3ba8af30df806b420c8b76612a54d73b19d2
|
7
|
+
data.tar.gz: 98914c762b9fc8152c5399727c374dce01364d9ce6416e829c1ca3c4fbef686ef6dc78b4558cf9ce917b6f7f22da560f064bf0d4f1d2e18951a487978eac760f
|
data/AUTHORS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
* Gregg Kellogg <gregg@greggkellogg.net>
|
data/LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
4
|
+
distribute this software, either in source code form or as a compiled
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
6
|
+
means.
|
7
|
+
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
9
|
+
of this software dedicate any and all copyright interest in the
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
11
|
+
of the public at large and to the detriment of our heirs and
|
12
|
+
successors. We intend this dedication to be an overt act of
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
14
|
+
software under copyright law.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
For more information, please refer to <http://unlicense.org>
|
25
|
+
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# LD Patch for RDF.rb
|
2
|
+
|
3
|
+
This is a [Ruby][] implementation of [LD Patch][] for [RDF.rb][].
|
4
|
+
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/ld-patch.png)](http://badge.fury.io/rb/ld-patch)
|
6
|
+
[![Build Status](https://travis-ci.org/ruby-rdf/ld-patch.png?branch=master)](http://travis-ci.org/ruby-rdf/ld-patch)
|
7
|
+
[![Coverage Status](https://coveralls.io/repos/ruby-rdf/ld-patch/badge.svg)](https://coveralls.io/r/ruby-rdf/ld-patch)
|
8
|
+
|
9
|
+
## Description
|
10
|
+
|
11
|
+
This gem implements the [LD Patch][] specification with a couple of changes and/or limitations:
|
12
|
+
|
13
|
+
* The `INDEX` terminal was replaced by `INTEGER`. Having two terminals matching the same token strings causes a conflict. As a result, a _slice_ may contain positive integers, as well as unsigned and negative-integers.
|
14
|
+
* The _graph_ rule is changed to the following:
|
15
|
+
|
16
|
+
[18] graph ::= triples ('.' triples?)*
|
17
|
+
|
18
|
+
This is necessary as the specified production is not context-free. As a result, it is possible for a graph to contain multiple trailing "`.`".
|
19
|
+
|
20
|
+
[LD Patch][] is useful inside a Rack container where it can respond to `POST` messages to affect the modification of a _target graph_ identified using the URL of the `POST`.
|
21
|
+
|
22
|
+
## Features
|
23
|
+
|
24
|
+
* 100% free and unencumbered [public domain](http://unlicense.org/) software.
|
25
|
+
* Complete [Linked Data Patch Format][LD Patch] parsing and execution
|
26
|
+
* Implementation Report: {file:etc/earl.html EARL}
|
27
|
+
* Compatible with Ruby >= 2.0.0.
|
28
|
+
|
29
|
+
## Documentation
|
30
|
+
Full documentation available on [Rubydoc.info][LD-Patch doc]
|
31
|
+
|
32
|
+
## Implementation Notes
|
33
|
+
The reader uses the [EBNF][] gem to generate first, follow and branch tables, and uses the `Parser` and `Lexer` modules to implement the LD Patch parser.
|
34
|
+
|
35
|
+
The parser takes branch and follow tables generated from the [LD Patch Grammar](file.ld-patch.html) described in the [specification][LD Patch]. Branch and Follow tables are specified in the generated {LD::Patch::Meta}.
|
36
|
+
|
37
|
+
## Dependencies
|
38
|
+
|
39
|
+
* [Ruby](http://ruby-lang.org/) (>= 2.0.0)
|
40
|
+
* [RDF.rb](http://rubygems.org/gems/rdf) (>= 1.1.15)
|
41
|
+
* [EBNF][] (>= 0.3.0)
|
42
|
+
* [SPARQL][] (>= 1.1.7)
|
43
|
+
* [SXP][] (>= 0.1.5)
|
44
|
+
* [RDF::XSD][] (>= 1.1.4)
|
45
|
+
|
46
|
+
## Mailing List
|
47
|
+
* <http://lists.w3.org/Archives/Public/public-rdf-ruby/>
|
48
|
+
|
49
|
+
## Author
|
50
|
+
* [Gregg Kellogg](http://github.com/gkellogg) - <http://greggkellogg.net/>
|
51
|
+
|
52
|
+
## Contributing
|
53
|
+
This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange development and release activity. All submissions _must_ be on a feature branch based on the _develop_ branch to ease staging and integration.
|
54
|
+
|
55
|
+
* Do your best to adhere to the existing coding conventions and idioms.
|
56
|
+
* Don't use hard tabs, and don't leave trailing whitespace on any line.
|
57
|
+
* Do document every method you add using [YARD][] annotations. Read the [tutorial][YARD-GS] or just look at the existing code for examples.
|
58
|
+
* Don't touch the `.gemspec`, `VERSION` or `AUTHORS` files. If you need to change them, do so on your private branch only.
|
59
|
+
* Do feel free to add yourself to the `CREDITS` file and the corresponding list in the the `README`. Alphabetical order applies.
|
60
|
+
* Do note that in order for us to merge any non-trivial changes (as a rule of thumb, additions larger than about 15 lines of code), we need an explicit [public domain dedication][PDD] on record from you.
|
61
|
+
|
62
|
+
## License
|
63
|
+
This is free and unencumbered public domain software. For more information,
|
64
|
+
see <http://unlicense.org/> or the accompanying {file:LICENSE} file.
|
65
|
+
|
66
|
+
A copy of the [LD Patch EBNF](file:etc/ld-patch.ebnf) and derived parser files are included in the repository, which are not covered under the UNLICENSE. These files are covered via the [W3C Document License](http://www.w3.org/Consortium/Legal/2002/copyright-documents-20021231).
|
67
|
+
|
68
|
+
[Ruby]: http://ruby-lang.org/
|
69
|
+
[RDF]: http://www.w3.org/RDF/
|
70
|
+
[YARD]: http://yardoc.org/
|
71
|
+
[YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md
|
72
|
+
[PDD]: http://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html
|
73
|
+
[RDF.rb]: http://rubygems.org/gems/rdf
|
74
|
+
[RDF::XSD]: http://rubygems.org/gems/rdf-xsd
|
75
|
+
[EBNF]: http://rubygems.org/gems/ebnf
|
76
|
+
[SPARQL]: http://rubygems.org/gems/sparql
|
77
|
+
[Linked Data]: http://rubygems.org/gems/linkeddata
|
78
|
+
[SSE]: http://openjena.org/wiki/SSE
|
79
|
+
[SXP]: http://rubygems.org/gems/sxp-ruby
|
80
|
+
[LD Patch]: http://www.w3.org/TR/ldpatch/
|
81
|
+
[LD-Patch doc]: http://rubydoc.info/github/ruby-rdf/ld-patch
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bin/ldpatch
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", 'lib')))
|
4
|
+
require 'ld/patch'
|
5
|
+
begin
|
6
|
+
require 'linkeddata'
|
7
|
+
rescue LoadError
|
8
|
+
require 'rdf/ntriples'
|
9
|
+
require 'rdf/turtle'
|
10
|
+
end
|
11
|
+
require 'getoptlong'
|
12
|
+
|
13
|
+
def run(input, options = {})
|
14
|
+
if options[:debug]
|
15
|
+
puts "input graph:\n#{options[:graph].dump(:ttl, standard_prefixes: true)}\n" if options[:graph]
|
16
|
+
puts "query:\n#{input}\n"
|
17
|
+
end
|
18
|
+
options[:graph] ||= RDF::Graph.new
|
19
|
+
|
20
|
+
if options[:verbose]
|
21
|
+
puts ("\nPATCH:\n" + input)
|
22
|
+
end
|
23
|
+
|
24
|
+
patch = if options[:sse]
|
25
|
+
SPARQL::Algebra.parse(input, options)
|
26
|
+
else
|
27
|
+
# Only do grammar debugging if we're generating SSE
|
28
|
+
LD::Patch.parse(input, options)
|
29
|
+
end
|
30
|
+
|
31
|
+
puts ("\nSSE:\n" + patch.to_sse) if options[:debug] || options[:to_sse]
|
32
|
+
|
33
|
+
unless options[:to_sse]
|
34
|
+
res = patch.execute(options[:graph], debug: options[:debug])
|
35
|
+
puts res.inspect if options[:verbose]
|
36
|
+
puts res.dump(:ttl, base_uri: patch.base_uri, prefixes: patch.prefixes, standard_prefixes: true)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
opts = GetoptLong.new(
|
41
|
+
["--debug", GetoptLong::NO_ARGUMENT],
|
42
|
+
["--dump", GetoptLong::NO_ARGUMENT],
|
43
|
+
["--execute", "-e", GetoptLong::REQUIRED_ARGUMENT],
|
44
|
+
["--progress", GetoptLong::NO_ARGUMENT],
|
45
|
+
["--sse", GetoptLong::NO_ARGUMENT],
|
46
|
+
["--to-sse", GetoptLong::NO_ARGUMENT],
|
47
|
+
["--validate", GetoptLong::NO_ARGUMENT],
|
48
|
+
["--verbose", GetoptLong::NO_ARGUMENT],
|
49
|
+
["--help", "-?", GetoptLong::NO_ARGUMENT]
|
50
|
+
)
|
51
|
+
|
52
|
+
options = {
|
53
|
+
graph: RDF::Repository.new,
|
54
|
+
}
|
55
|
+
|
56
|
+
input = nil
|
57
|
+
|
58
|
+
opts.each do |opt, arg|
|
59
|
+
case opt
|
60
|
+
when '--debug' then options[:debug] = true
|
61
|
+
when '--dump' then $dump = true
|
62
|
+
when '--execute' then input = arg
|
63
|
+
when '--progress' then options[:debug] ||= 2
|
64
|
+
when '--sse' then options[:sse] = true
|
65
|
+
when '--to-sse' then options[:to_sse] = true
|
66
|
+
when '--validate' then options[:validate] = true
|
67
|
+
when '--verbose' then options[:verbose] = true
|
68
|
+
when "--help"
|
69
|
+
puts "Usage: #{$0} [options] file-or-uri ..."
|
70
|
+
puts "Options:"
|
71
|
+
puts " --execute,-e: Use option argument as the SPARQL input if no files are given"
|
72
|
+
puts " --dump: Dump raw output, otherwise serialize to SSE"
|
73
|
+
puts " --debug: Display detailed debug output"
|
74
|
+
puts " --sse: Input is in SSE format"
|
75
|
+
puts " --to-sse: Generate SSE instead of running query"
|
76
|
+
puts " --verbose: Display details of processing"
|
77
|
+
puts " --help,-?: This message"
|
78
|
+
exit(0)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
if ARGV.empty?
|
83
|
+
s = input ? input : $stdin.read
|
84
|
+
run(s, options)
|
85
|
+
else
|
86
|
+
ARGV.each do |test_file|
|
87
|
+
puts "parse #{test_file}"
|
88
|
+
run(RDF::Util::File.open_file(test_file).read, options.merge(base_uri: RDF::URI(test_file)))
|
89
|
+
end
|
90
|
+
end
|
91
|
+
puts
|
data/lib/ld/patch.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
$:.unshift(File.expand_path("../..", __FILE__))
|
2
|
+
require 'rdf'
|
3
|
+
require 'sparql'
|
4
|
+
require 'sparql/algebra'
|
5
|
+
require 'sxp'
|
6
|
+
|
7
|
+
module LD
|
8
|
+
# **`LD::Patch`** is a Linked Data Patch extension for RDF.rb.
|
9
|
+
#
|
10
|
+
# @author [Gregg Kellogg](http://greggkellogg.net/)
|
11
|
+
module Patch
|
12
|
+
autoload :Algebra, 'ld/patch/algebra'
|
13
|
+
autoload :Meta, 'ld/patch/meta'
|
14
|
+
autoload :Parser, 'ld/patch/parser'
|
15
|
+
autoload :Terminals, 'ld/patch/terminals'
|
16
|
+
autoload :Version, 'ld/patch/version'
|
17
|
+
|
18
|
+
##
|
19
|
+
# Parse the given LD Patch `input` string.
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# query = LD::Patch.parse("Add { <http://example.org/s2> <http://example.org/p2> <http://example.org/o2> } .")
|
23
|
+
#
|
24
|
+
# @param [IO, StringIO, String, #to_s] input
|
25
|
+
# @param [Hash{Symbol => Object}] options
|
26
|
+
# @return [Object]
|
27
|
+
# The parsed Patch
|
28
|
+
def self.parse(input, options = {})
|
29
|
+
LD::Patch::Parser.new(input, options).parse
|
30
|
+
end
|
31
|
+
|
32
|
+
class Error < StandardError
|
33
|
+
# The status code associated with this error
|
34
|
+
attr_reader :code
|
35
|
+
|
36
|
+
##
|
37
|
+
# Initializes a new patch error instance.
|
38
|
+
#
|
39
|
+
# @param [String, #to_s] message
|
40
|
+
# @param [Hash{Symbol => Object}] options
|
41
|
+
# @option options [Integer] :code (422)
|
42
|
+
def initialize(message, options = {})
|
43
|
+
@code = options.fetch(:status_code, 422)
|
44
|
+
super(message.to_s)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Indicates bad syntax found in LD Patch document
|
49
|
+
class ParseError < Error
|
50
|
+
##
|
51
|
+
# The invalid token which triggered the error.
|
52
|
+
#
|
53
|
+
# @return [String]
|
54
|
+
attr_reader :token
|
55
|
+
|
56
|
+
##
|
57
|
+
# The line number where the error occurred.
|
58
|
+
#
|
59
|
+
# @return [Integer]
|
60
|
+
attr_reader :lineno
|
61
|
+
|
62
|
+
##
|
63
|
+
# Initializes a new parser error instance.
|
64
|
+
#
|
65
|
+
# @param [String, #to_s] message
|
66
|
+
# @param [Hash{Symbol => Object}] options
|
67
|
+
# @option options [String] :token (nil)
|
68
|
+
# @option options [Integer] :lineno (nil)
|
69
|
+
# @option options [Integer] :code (400)
|
70
|
+
def initialize(message, options = {})
|
71
|
+
@token = options[:token]
|
72
|
+
@lineno = options[:lineno] || (@token.lineno if @token.respond_to?(:lineno))
|
73
|
+
super(message.to_s, code: options.fetch(:code, 400))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
$:.unshift(File.expand_path("../..", __FILE__))
|
2
|
+
require 'sparql/algebra'
|
3
|
+
require 'sxp'
|
4
|
+
|
5
|
+
module LD::Patch
|
6
|
+
# Based on the SPARQL Algebra, operators for executing a patch
|
7
|
+
#
|
8
|
+
# @author [Gregg Kellogg](http://greggkellogg.net/)
|
9
|
+
module Algebra
|
10
|
+
autoload :Add, 'ld/patch/algebra/add'
|
11
|
+
autoload :Bind, 'ld/patch/algebra/bind'
|
12
|
+
autoload :Constraint, 'ld/patch/algebra/constraint'
|
13
|
+
autoload :Cut, 'ld/patch/algebra/cut'
|
14
|
+
autoload :Delete, 'ld/patch/algebra/delete'
|
15
|
+
autoload :Index, 'ld/patch/algebra/index'
|
16
|
+
autoload :Patch, 'ld/patch/algebra/patch'
|
17
|
+
autoload :Path, 'ld/patch/algebra/path'
|
18
|
+
autoload :Prefix, 'ld/patch/algebra/prefix'
|
19
|
+
autoload :Reverse, 'ld/patch/algebra/reverse'
|
20
|
+
autoload :UpdateList, 'ld/patch/algebra/update_list'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module LD::Patch::Algebra
|
2
|
+
|
3
|
+
##
|
4
|
+
# The LD Patch `add` operator.
|
5
|
+
#
|
6
|
+
# The Add operation is used to add triples to the target graph with or without allowing duplicates.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# (add ((<a> <b> <c>)))
|
10
|
+
#
|
11
|
+
class Add < SPARQL::Algebra::Operator::Unary
|
12
|
+
include SPARQL::Algebra::Update
|
13
|
+
include SPARQL::Algebra::Evaluatable
|
14
|
+
|
15
|
+
NAME = :add
|
16
|
+
|
17
|
+
##
|
18
|
+
# Executes this upate on the given `writable` graph or repository.
|
19
|
+
#
|
20
|
+
# @param [RDF::Queryable] queryable
|
21
|
+
# the graph or repository to write
|
22
|
+
# @param [Hash{Symbol => Object}] options
|
23
|
+
# any additional options
|
24
|
+
# @option options [Boolean] :new
|
25
|
+
# Specifies that triples may not already exist in the target graph
|
26
|
+
# @return [RDF::Query::Solutions] A single solution including passed bindings with `var` bound to the solution.
|
27
|
+
# @raise [Error]
|
28
|
+
# If the :new option is specified and any triples already exist in queryable or if unbound variables are used
|
29
|
+
# @see http://www.w3.org/TR/sparql11-update/
|
30
|
+
def execute(queryable, options = {})
|
31
|
+
debug(options) {"Add"}
|
32
|
+
bindings = options.fetch(:bindings)
|
33
|
+
solution = bindings.first
|
34
|
+
|
35
|
+
# Bind variables to triples
|
36
|
+
triples = operand(0).dup.replace_vars! do |var|
|
37
|
+
case var
|
38
|
+
when RDF::Query::Pattern
|
39
|
+
s = var.bind(solution)
|
40
|
+
raise LD::Patch::Error.new("Operand uses unbound pattern #{var.inspect}", code: 400) if s.variable?
|
41
|
+
s
|
42
|
+
when RDF::Query::Variable
|
43
|
+
raise LD::Patch::Error.new("Operand uses unbound variable #{var.inspect}", code: 400) unless solution.bound?(var)
|
44
|
+
solution[var]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# If `:new` is specified, verify that no triple in triples exists in queryable
|
49
|
+
if @options[:new]
|
50
|
+
triples.each do |triple|
|
51
|
+
raise LD::Patch::Error, "Target graph contains added triple #{triple.to_ntriples}" if queryable.has_statement?(triple)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
queryable.insert(*triples)
|
56
|
+
bindings
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module LD::Patch::Algebra
|
2
|
+
|
3
|
+
##
|
4
|
+
# The LD Patch `bind` operator.
|
5
|
+
#
|
6
|
+
# The Bind operation is used to bind a single term to a variable.
|
7
|
+
#
|
8
|
+
# @example simple value to variable binding
|
9
|
+
# Bind ?x <http://example.org/s> . #=>
|
10
|
+
# (bind ?x <http://example.org/s> ())
|
11
|
+
#
|
12
|
+
# @example constant path (filter-forward.ldpatch)
|
13
|
+
# Bind ?x <http://example.org/s> / <http://example.org/p1> . #=>
|
14
|
+
# (bind ?x <http://example.org/s> (<http://example.org/p1>))
|
15
|
+
#
|
16
|
+
# @example list index (path-at.ldpatch)
|
17
|
+
# Bind ?x <http://example.org/s> / 1 . #=>
|
18
|
+
# (bind ?x <http://example.org/s> ((index 1)))
|
19
|
+
#
|
20
|
+
# @example reverse (path-backward.ldpath)
|
21
|
+
# Bind ?x <http://example.org/s> / ^<http://example.org/p1> . #=>
|
22
|
+
# (bind ?x <http://example.org/s> ((reverse <http://example.org/p1>)))
|
23
|
+
#
|
24
|
+
# @example constraint (path-filter-equal.ldpatch)
|
25
|
+
# Bind ?x <http://example.org/s> / <http://example.org/p2> [ / <http://example.org/l> = "b" ] . #=>
|
26
|
+
# (bind ?x <http://example.org/s> (
|
27
|
+
# <http://example.org/p2>
|
28
|
+
# (constraint (<http://example.org/l>) "b")))
|
29
|
+
#
|
30
|
+
# @example constraint (path-filter.ldpatch)
|
31
|
+
# Bind ?x <http://example.org/s> / <http://example.org/p2> [ / <http://example.org/p1> ] . #=>
|
32
|
+
# (bind ?x <http://example.org/s> (
|
33
|
+
# <http://example.org/p2>
|
34
|
+
# (constraint (<http://example.org/l>))))
|
35
|
+
#
|
36
|
+
# @example starting with a literal
|
37
|
+
# Bind ?x "a" / ^<http://example.org/l> / ^<http://example.org/p2> . #=>
|
38
|
+
# (bind ?x "a" (
|
39
|
+
# (reverse <http://example.org/l>)
|
40
|
+
# (reverse <http://example.org/p2>)))
|
41
|
+
#
|
42
|
+
# @example unicity (path-unicity.ldpath)
|
43
|
+
# Bind ?x <http://example.org/s> / <http://example.org/p1> ! . #=>
|
44
|
+
# SELECT ?x
|
45
|
+
# WHERE {<http//example.org/s> <http://example.org/p1> ?x}
|
46
|
+
# GROUP BY ?x
|
47
|
+
# HAVING COUNT(?x) = 1
|
48
|
+
# (bind ?x ?0
|
49
|
+
# ((pattern <http://example.org/s> <http://example.org/p1> ??0)
|
50
|
+
# (unique ??0)))
|
51
|
+
class Bind < SPARQL::Algebra::Operator::Ternary
|
52
|
+
include SPARQL::Algebra::Query
|
53
|
+
include SPARQL::Algebra::Evaluatable
|
54
|
+
|
55
|
+
NAME = :bind
|
56
|
+
|
57
|
+
##
|
58
|
+
# Maps the value to a single output term by executing the path and updates `bindings` with `var` set to that output term
|
59
|
+
#
|
60
|
+
# @param [RDF::Queryable] queryable
|
61
|
+
# the graph or repository to write
|
62
|
+
# @param [Hash{Symbol => Object}] options
|
63
|
+
# any additional options
|
64
|
+
# @option options [RDF::Query::Solutions] :bindings
|
65
|
+
# @return [RDF::Query::Solutions] A single solution including passed bindings with `var` bound to the solution.
|
66
|
+
# @raise [Error]
|
67
|
+
# If path does not evaluate to a single term or if unbound variables are used.
|
68
|
+
# @see http://www.w3.org/TR/sparql11-update/
|
69
|
+
def execute(queryable, options = {})
|
70
|
+
debug(options) {"Bind"}
|
71
|
+
bindings = options.fetch(:bindings)
|
72
|
+
solution = bindings.first
|
73
|
+
var, value, path = operands
|
74
|
+
|
75
|
+
# Bind variables to path
|
76
|
+
if value.variable?
|
77
|
+
raise LD::Patch::Error.new("Operand uses unbound variable #{value.inspect}", code: 400) unless solution.bound?(value)
|
78
|
+
value = solution[value]
|
79
|
+
end
|
80
|
+
|
81
|
+
path = path.dup.replace_vars! do |v|
|
82
|
+
raise Error, "Operand uses unbound variable #{v.inspect}" unless solution.bound?(v)
|
83
|
+
solution[v]
|
84
|
+
end
|
85
|
+
results = path.execute(queryable, terms: [value])
|
86
|
+
raise LD::Patch::Error, "Bind path bound to #{results.length} terms, expected just one" unless results.length == 1
|
87
|
+
RDF::Query::Solutions.new [solution.merge(var.to_sym => results.first.path)]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|