Skip to content

Commit

Permalink
Updates for RDF support for existential and universal as well as (non…
Browse files Browse the repository at this point in the history
…-)deterministic variables in serialization and creation.
  • Loading branch information
gkellogg committed Apr 19, 2019
1 parent 6381e1a commit f2938bc
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 30 deletions.
3 changes: 1 addition & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ source "https://rubygems.org"

gemspec

#gem "rdf", git: "https://github.com/ruby-rdf/rdf", branch: "develop"
gem "rdf", path: '../rdf'
gem "rdf", git: "https://github.com/ruby-rdf/rdf", branch: "develop"

group :development do
gem "rdf-spec", git: "https://github.com/ruby-rdf/rdf-spec", branch: "develop"
Expand Down
37 changes: 23 additions & 14 deletions lib/rdf/n3/algebra/formula.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class Formula < SPARQL::Algebra::Operator
##
# Yields solutions from patterns and other operands. Solutions are created by evaluating each pattern and other sub-operand against `queryable`.
#
# When executing, blank nodes are turned into non-distinguished existential variables, noted with `$$`. These variables are removed from the returned solutions, as they can't be bound outside of the formula.
#
# @param [RDF::Queryable] queryable
# the graph or repository to query
# @param [Hash{Symbol => Object}] options
Expand Down Expand Up @@ -44,7 +46,10 @@ def execute(queryable, **options, &block)
end
end
log_debug {"(formula solutions) #{@solutions.to_sxp}"}
@solutions

# Only return solutions with distinguished variables
variable_names = @solutions.variable_names.reject {|v| v.to_s.start_with?('$$')}
variable_names.empty? ? @solutions : @solutions.dup.project(*variable_names)
end

##
Expand All @@ -56,12 +61,8 @@ def execute(queryable, **options, &block)
# @yieldreturn [void] ignored
def each(&block)
@solutions ||= begin
# If there are no solutions, create bindings for all existential variables using the variable name as the bnode identifier
RDF::Query::Solutions.new(
[RDF::Query::Solution.new(
patterns.ndvars.inject({}) {|memo, v| memo.merge(v.name => RDF::Node.intern(v.name))}
)]
)
# If there are no solutions, create a single solution
RDF::Query::Solutions.new(RDF::Query::Solution.new)
end
log_debug {"formula #{graph_name} each #{@solutions.to_sxp}"}

Expand All @@ -72,7 +73,13 @@ def each(&block)
end

# Yield patterns by binding variables
# FIXME: do we need to do something with non-bound non-distinguished extistential variables?
@solutions.each do |solution|
# Bind blank nodes to the solution when it doesn't contain a solution for an existential variable
existential_vars.each do |var|
solution[var.name] ||= RDF::Node.intern(var.name.to_s.sub(/^\$+/, ''))
end

log_debug {"(formula apply) #{solution.to_sxp} to BGP"}
# Yield each variable statement which is constant after applying solution
patterns.each do |pattern|
Expand Down Expand Up @@ -117,17 +124,17 @@ def graph_name; @options[:graph_name]; end
##
# Statements memoizer
def statements
# BNodes in statements are existential variables
# BNodes in statements are non-distinguished existential variables
@statements ||= operands.
select {|op| op.is_a?(RDF::Statement)}.
map do |pattern|

# Map nodes to variables (except when in top-level formula)
# Map nodes to non-distinguished existential variables (except when in top-level formula)
if graph_name
terms = {}
[:subject, :predicate, :object].each do |r|
terms[r] = case o = pattern.send(r)
when RDF::Node then RDF::Query::Variable.new(o.id, distinguished: false)
when RDF::Node then RDF::Query::Variable.new(o.id, existential: true, distinguished: false)
else o
end
end
Expand Down Expand Up @@ -160,18 +167,20 @@ def sub_ops
@sub_ops ||= operands.reject {|op| op.is_a?(RDF::Statement)}
end

##
# Existential vars in this formula
def existential_vars
@existentials ||= patterns.vars.select(&:existential?)
end

##
# Reorder operands by graph_name or subject
def reorder_operands!
@operands = @operands.sort_by {|op| op.graph_name || op.subject}
end

def to_sxp_bin
@existentials = ndvars.uniq
@universals = vars.uniq - @existentials
[:formula, graph_name].compact +
(Array(universals).empty? ? [] : [universals.unshift(:universals)]) +
(Array(existentials).empty? ? [] : [existentials.unshift(:existentials)]) +
operands.map(&:to_sxp_bin)
end
end
Expand Down
8 changes: 5 additions & 3 deletions lib/rdf/n3/reader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ module RDF::N3
#
# Separate pass to create branch_table from n3-selectors.n3
#
# This implementation uses distinguished variables for both universal and explicit existential variables (defined with `@forSome`). Variables created from blank nodes are non-distinguished. Distinguished existential variables are tracked using `$`, internally, as the RDF `query_pattern` logic looses details of the variable definition in solutions, where the variable is represented using a symbol.
#
# @todo
# * Formulae as RDF::Query representations
# * Formula expansion similar to SPARQL Construct
Expand Down Expand Up @@ -244,7 +246,7 @@ def existentialFinish
pd = @prod_data.pop
forSome = Array(pd[:symbol])
forSome.each do |term|
var = univar(term, distinguished: false)
var = univar(term, existential: true)
add_var_to_formula(@formulae.last, term, var)
end
end
Expand Down Expand Up @@ -655,10 +657,10 @@ def bnode(label = nil)
end
end

def univar(label, distinguished: true)
def univar(label, existential: false)
# Label using any provided label, followed by seed, followed by incrementing index
value = "#{label}_#{unique_label}"
RDF::Query::Variable.new(value, distinguished: distinguished)
RDF::Query::Variable.new(value, existential: existential)
end

# add a pattern or statement
Expand Down
9 changes: 5 additions & 4 deletions lib/rdf/n3/writer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,9 @@ def preprocess
@options[:prefixes] = {} # Will define actual used when matched
repo.each {|statement| preprocess_statement(statement)}

@universals = repo.enum_term.to_a.select {|r| r.is_a?(RDF::Query::Variable) && r.distinguished?}.uniq
@existentials = repo.enum_term.to_a.select {|r| r.is_a?(RDF::Query::Variable) && !r.distinguished?}.uniq
vars = repo.enum_term.to_a.uniq.select {|r| r.is_a?(RDF::Query::Variable)}
@universals = vars.reject(&:existential?)
@existentials = vars - @universals
end

# Perform any statement preprocessing required. This is used to perform reference counts and determine required
Expand Down Expand Up @@ -453,7 +454,7 @@ def reset
# @return [String]
def quoted(string)
if string.to_s.match(/[\t\n\r]/)
string = string.gsub('\\', '\\\\\\\\').gsub('"""', '\\"""')
string = string.gsub('\\', '\\\\\\\\').gsub('"""', '\\"\\"\\"')
%("""#{string}""")
else
"\"#{escaped(string)}\""
Expand Down Expand Up @@ -499,7 +500,7 @@ def collection(node, position)
def p_term(resource, position)
#log_debug("p_term") {"#{resource.to_sxp}, #{position}"}
l = if resource.is_a?(RDF::Query::Variable)
format_term(RDF::URI(resource.name.to_s))
format_term(RDF::URI(resource.name.to_s.sub(/^\$/, '')))
elsif resource == RDF.nil
"()"
else
Expand Down
10 changes: 5 additions & 5 deletions spec/reader_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1277,17 +1277,17 @@
)
end

def parse(input, options = {})
def parse(input, **options)
options = {
logger: logger,
validate: false,
canonicalize: false,
}.merge(options)
graph = options[:repo] || RDF::Repository.new
RDF::N3::Reader.new(input, options).each_statement do |statement|
graph << statement
repo = options[:repo] || RDF::Repository.new
RDF::N3::Reader.new(input, **options).each_statement do |statement|
repo << statement
end
graph
repo
end

def test_file(filepath)
Expand Down
4 changes: 2 additions & 2 deletions spec/writer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@
case t.name
when *%w(n3_10003 n3_10004 n3_10008)
skip "Blank Node predicates"
when *%w(n3_10012 n3_10016 n3_10017)
when *%w(n3_10012 n3_10017)
pending "Investigate"
when *%w(n3_10013)
pending "Number syntax"
Expand All @@ -629,7 +629,7 @@
case t.name
when *%w(n3_10003 n3_10004 n3_10008)
skip "Blank Node predicates"
when *%w(n3_10012 n3_10016 n3_10017)
when *%w(n3_10012 n3_10017)
pending "Investigate"
when *%w(n3_10013)
pending "Number syntax"
Expand Down

0 comments on commit f2938bc

Please sign in to comment.