Skip to content

Commit 87f5923

Browse files
committed
Add some path optimmization for reverse and first/rest chains involving path*.
Fixes #49.
1 parent d9420ad commit 87f5923

File tree

6 files changed

+71
-26
lines changed

6 files changed

+71
-26
lines changed

bin/sparql

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ def run(input, **options)
4646
SPARQL::Grammar.parse(input, **options)
4747
end
4848

49+
query = query.optimize if options[:optimize]
50+
4951
puts ("\nSSE:\n" + query.to_sse) if options[:debug]
5052

5153
if options[:parse_only]
@@ -70,37 +72,41 @@ rescue LoadError
7072
$stderr.puts "Running SPARQL server requires Rack and Sinatra to be in environment: #{$!.message}"
7173
end
7274

75+
cmd, input = ARGV.shift, nil
76+
77+
OPT_ARGS = [
78+
["--dataset", GetoptLong::REQUIRED_ARGUMENT, "File containing RDF graph or dataset"],
79+
["--debug", GetoptLong::NO_ARGUMENT, "Debugging output"],
80+
["--execute", "-e", GetoptLong::REQUIRED_ARGUMENT, "Run against source in argument"],
81+
["--format", GetoptLong::REQUIRED_ARGUMENT, "Output format for results (json, xml, csv, tsv, html, sparql, sse, or another RDF format)"],
82+
["--help", "-?", GetoptLong::NO_ARGUMENT, "print this message"],
83+
["--optimize", GetoptLong::NO_ARGUMENT, "Perform query optimizations"],
84+
["--port", "-p", GetoptLong::REQUIRED_ARGUMENT, "Port on which to run server; defaults to 9292"],
85+
["--sse", GetoptLong::NO_ARGUMENT, "Query input is in SSE format"],
86+
["--update", GetoptLong::NO_ARGUMENT, "Process query as a SPARQL Update"],
87+
["--verbose", GetoptLong::NO_ARGUMENT, "Verbose output"],
88+
]
89+
7390
def usage
7491
puts "Usage: #{File.basename($0)} execute [options] query-file Execute a query against the specified dataset"
7592
puts " #{File.basename($0)} parse [options] query-file Parse a query into SPARQL S-Expressions (SSE)"
7693
puts " #{File.basename($0)} query [options] end-point query-file Run the query against a remote end-point"
7794
puts " #{File.basename($0)} server [options] dataset-file Start a server initialized from the specified dataset"
7895
puts "Options:"
79-
puts " --dataset: File containing RDF graph or dataset"
80-
puts " --debug: Display detailed debug output"
81-
puts " --execute,-e: Use option argument as the SPARQL input if no query-file given"
82-
puts " --format: Output format for results (json, xml, csv, tsv, html, sparql, sse, or another RDF format)"
83-
puts " --port,-p Port on which to run server; defaults to 9292"
84-
puts " --sse: Query input is in SSE format"
85-
puts " --update: Process query as a SPARQL Update"
86-
puts " --verbose: Display details of processing"
87-
puts " --help,-?: This message"
96+
width = OPT_ARGS.map do |o|
97+
l = o.first.length
98+
l += o[1].length + 2 if o[1].is_a?(String)
99+
l
100+
end.max
101+
OPT_ARGS.each do |o|
102+
s = " %-*s " % [width, (o[1].is_a?(String) ? "#{o[0,2].join(', ')}" : o[0])]
103+
s += o.last
104+
puts s
105+
end
88106
exit(0)
89107
end
90108

91-
cmd, input = ARGV.shift, nil
92-
93-
opts = GetoptLong.new(
94-
["--dataset", GetoptLong::REQUIRED_ARGUMENT],
95-
["--debug", GetoptLong::NO_ARGUMENT],
96-
["--execute", "-e", GetoptLong::REQUIRED_ARGUMENT],
97-
["--format", GetoptLong::REQUIRED_ARGUMENT],
98-
["--port", "-p", GetoptLong::REQUIRED_ARGUMENT],
99-
["--sse", GetoptLong::NO_ARGUMENT],
100-
["--update", GetoptLong::NO_ARGUMENT],
101-
["--verbose", GetoptLong::NO_ARGUMENT],
102-
["--help", "-?", GetoptLong::NO_ARGUMENT]
103-
)
109+
opts = GetoptLong.new(*OPT_ARGS.map {|o| o[0..-2]})
104110

105111
logger = Logger.new(STDERR)
106112
logger.level = Logger::WARN
@@ -117,6 +123,7 @@ opts.each do |opt, arg|
117123
when '--debug' then options[:debug] = true ; logger.level = Logger::DEBUG
118124
when '--execute' then input = arg
119125
when '--format' then options[:format] = arg.to_sym
126+
when '--optimize' then options[:optimize] = true
120127
when '--port' then options[:port] = arg.to_i
121128
when '--sse' then options[:sse] = true
122129
when '--update' then options[:update] = true

lib/sparql/algebra/operator/join.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def validate!
103103
# @return [self]
104104
# @see SPARQL::Algebra::Expression#optimize!
105105
def optimize!(**options)
106-
ops = operands.map {|o| o.optimize(**options) }.select {|o| o.respond_to?(:empty?) && !o.empty?}
106+
ops = operands.map {|o| o.optimize(**options) }.reject {|o| o.respond_to?(:empty?) && o.empty?}
107107
@operands = ops
108108
self
109109
end

lib/sparql/algebra/operator/left_join.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def validate!
138138
# FIXME
139139
def optimize!(**options)
140140
return self
141-
ops = operands.map {|o| o.optimize(**options) }.select {|o| o.respond_to?(:empty?) && !o.empty?}
141+
ops = operands.map {|o| o.optimize(**options) }.reject {|o| o.respond_to?(:empty?) && o.empty?}
142142
expr = ops.pop unless ops.last.executable?
143143
expr = nil if expr.respond_to?(:true?) && expr.true?
144144

lib/sparql/algebra/operator/minus.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def execute(queryable, **options, &block)
9191
# @return [self]
9292
# @see SPARQL::Algebra::Expression#optimize!
9393
def optimize!(**options)
94-
ops = operands.map {|o| o.optimize(**options) }.select {|o| o.respond_to?(:empty?) && !o.empty?}
94+
ops = operands.map {|o| o.optimize(**options) }.reject {|o| o.respond_to?(:empty?) && o.empty?}
9595
@operands = ops
9696
self
9797
end

lib/sparql/algebra/operator/path.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ class Operator
33
##
44
# The SPARQL Property Path `path` operator.
55
#
6+
# The second element represents a set of predicates which ar associated with the first (subject) and last (object) operands.
7+
#
68
# [88] Path ::= PathAlternative
79
#
810
# @example SPARQL Grammar
@@ -71,6 +73,42 @@ def to_sparql(top_level: true, **options)
7173
str = operands.to_sparql(top_level: false, **options) + " ."
7274
top_level ? Operator.to_sparql(str, **options) : str
7375
end
76+
77+
##
78+
# Special cases for optimizing a path based on its operands.
79+
#
80+
# @param [Hash{Symbol => Object}] options
81+
# any additional options for optimization
82+
# @return [SPARQL::Algebra::Operator]
83+
# May returnn a different operator
84+
# @see RDF::Query#optimize!
85+
def optimize(**options)
86+
op = super
87+
while true
88+
decon = op.to_sxp_bin
89+
op = case decon
90+
# Reverse
91+
in [:path, subject, [:reverse, path], object]
92+
Path.new(object, path, subject)
93+
# Path* (seq (seq p0 (path* p1)) p2)
94+
in [:path, subject, [:seq, [:seq, p0, [:'path*', p1]], p2], object]
95+
pp1 = Variable.new(nil, distinguished: false)
96+
pp2 = Variable.new(nil, distinguished: false)
97+
pp3 = Variable.new(nil, distinguished: false)
98+
# Bind variables used in Path*
99+
bgp = BGP.new(
100+
Triple.new(pp2, p2, subject),
101+
Triple.new(object, p1, pp3))
102+
# New path with pre-bound variables
103+
path = Path.new(pp3, PathStar.new(p2), pp2)
104+
Sequence.new(bgp, path)
105+
else
106+
# No matching patterns
107+
break
108+
end
109+
end
110+
op
111+
end
74112
end # Path
75113
end # Operator
76114
end; end # SPARQL::Algebra

lib/sparql/algebra/operator/union.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def validate!
6969
# @return [self]
7070
# @see SPARQL::Algebra::Expression#optimize!
7171
def optimize!(**options)
72-
ops = operands.map {|o| o.optimize(**options) }.select {|o| o.respond_to?(:empty?) && !o.empty?}
72+
ops = operands.map {|o| o.optimize(**options) }.reject {|o| o.respond_to?(:empty?) && o.empty?}
7373
@operands = ops
7474
self
7575
end

0 commit comments

Comments
 (0)