Class: SPARQL::Algebra::Operator::LeftJoin
- Inherits:
-
SPARQL::Algebra::Operator
- Object
- SPARQL::Algebra::Operator
- SPARQL::Algebra::Operator::LeftJoin
- Includes:
- Query
- Defined in:
- lib/sparql/algebra/operator/left_join.rb
Overview
The SPARQL GraphPattern leftjoin
operator.
[57] OptionalGraphPattern ::= ‘OPTIONAL’ GroupGraphPattern
Constant Summary collapse
- NAME =
[:leftjoin]
Constants inherited from SPARQL::Algebra::Operator
Constants included from Expression
Instance Attribute Summary
Attributes included from Query
Attributes inherited from SPARQL::Algebra::Operator
Instance Method Summary collapse
-
#execute(queryable, **options) {|solution| ... } ⇒ RDF::Query::Solutions
Executes each operand with
queryable
and performs theleftjoin
operation by adding every solution from the left, merging compatible solutions from the right that match an optional filter. -
#optimize!(**options) ⇒ Object
Optimizes this query.
-
#to_sparql(top_level: true, filter_ops: [], extensions: {}, **options) ⇒ String
Returns a partial SPARQL grammar for this operator.
-
#validate! ⇒ Object
The same blank node label cannot be used in two different basic graph patterns in the same query.
Methods included from Query
#each_solution, #empty?, #failed?, #graph_name=, #matched?, #query_yields_boolean?, #query_yields_solutions?, #query_yields_statements?, #unshift, #variables
Methods inherited from SPARQL::Algebra::Operator
#aggregate?, arity, #base_uri, base_uri, base_uri=, #bind, #boolean, #constant?, #deep_dup, #each_descendant, #eql?, #evaluatable?, evaluate, #executable?, #first_ancestor, for, #initialize, #inspect, #ndvars, #node?, #operand, #optimize, #parent, #parent=, #prefixes, prefixes, prefixes=, #rewrite, #to_binary, to_sparql, #to_sxp, #to_sxp_bin, #variable?, #variables, #vars
Methods included from Expression
cast, #constant?, #evaluate, extension, extension?, extensions, for, #invalid?, new, #node?, open, #optimize, parse, register_extension, #to_sxp_bin, #valid?, #variable?
Constructor Details
This class inherits a constructor from SPARQL::Algebra::Operator
Instance Method Details
#execute(queryable, **options) {|solution| ... } ⇒ RDF::Query::Solutions
Executes each operand with queryable
and performs the leftjoin
operation by adding every solution from the left, merging compatible solutions from the right that match an optional filter.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/sparql/algebra/operator/left_join.rb', line 49 def execute(queryable, **, &block) filter = operand(2) raise ArgumentError, "leftjoin operator accepts at most two arguments with an optional filter" if operands.length < 2 || operands.length > 3 debug() {"LeftJoin"} left = queryable.query(operand(0), **.merge(depth: [:depth].to_i + 1)) debug() {"=>(leftjoin left) #{left.inspect}"} right = queryable.query(operand(1), **.merge(depth: [:depth].to_i + 1)) debug() {"=>(leftjoin right) #{right.inspect}"} # LeftJoin(Ω1, Ω2, expr) = @solutions = RDF::Query::Solutions() left.each do |s1| load_left = true right.each do |s2| s = s2.merge(s1) # Re-bind to bindings, if defined, as they might not be found in solution [:bindings].each_binding do |name, value| s[name] = value if filter.variables.include?(name) end if [:bindings] && filter.respond_to?(:variables) # See https://github.com/w3c/rdf-tests/pull/83#issuecomment-1324220844 for @afs's discussion of the simplified/not-simplified issue. # # The difference is when simplification is applied. It matters for OPTIONAL because OPTIONAL { ... FILTER(...) } puts the filter into the LeftJoin expressions. In LeftJoin, the FILTER can see the left-hand-side variables. (SQL: LEFT JOIN ... ON ...) # # For OPTIONAL { { ... FILTER(...) } }, the inner part is Join({}, {.... FILTER }). # # if simplify happens while coming back up the tree generating algebra operations, it removes the join i.e. the inner of {{ }}, and passes "... FILTER()" to the OPTIONAL. The effect of the extra nesting in {{ }} is lost and it exposes the filter to the OPTIONAL rule. # # if simplification happens as a step after the whole algebra is converted, this does not happen. Compiling the OPTIONAL see a join and the filter is not at the top level of the OPTIONAl block and so not handled in the LeftJoin. # # Use case: # # # Include name if person over 18 # SELECT * # { ?person :age ?age # OPTIONAL { ?person :name ?name. FILTER(?age > 18) } # } # Hindsight: a better syntax would be call out if the filter needed access to the LHS. # # OPTIONAL FILTER(....) { } # # But we are where we are. # # (a "no conditions on LeftJoin" approach would mean users having to duplicate parts of their query - possibly quite large parts.) expr = filter ? boolean(filter.evaluate(s)).true? : true rescue false debug() {"===>(evaluate) #{s.inspect}"} if filter if expr && s1.compatible?(s2) # { merge(μ1, μ2) | μ1 in Ω1 and μ2 in Ω2, and μ1 and μ2 are compatible and expr(merge(μ1, μ2)) is true } debug() {"=>(merge s1 s2) #{s.inspect}"} @solutions << s load_left = false # Left solution added one or more times due to merge end end if load_left debug() {"=>(add) #{s1.inspect}"} @solutions << s1 end end debug() {"=> #{@solutions.inspect}"} @solutions.each(&block) if block_given? @solutions end |
#optimize!(**options) ⇒ Object
Optimizes this query.
If optimize operands, and if the first two operands are both Queries, replace with the unique sum of the query elements
FIXME
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/sparql/algebra/operator/left_join.rb', line 139 def optimize!(**) return self ops = operands.map {|o| o.optimize(**) }.select {|o| o.respond_to?(:empty?) && !o.empty?} expr = ops.pop unless ops.last.executable? expr = nil if expr.respond_to?(:true?) && expr.true? # ops now is one or two executable operators # expr is a filter expression, which may have been optimized to 'true' case ops.length when 0 RDF::Query.new # Empty query, expr doesn't matter when 1 expr ? Filter.new(expr, ops.first) : ops.first else expr ? LeftJoin.new(ops[0], ops[1], expr) : LeftJoin.new(ops[0], ops[1]) end end |
#to_sparql(top_level: true, filter_ops: [], extensions: {}, **options) ⇒ String
Returns a partial SPARQL grammar for this operator.
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/sparql/algebra/operator/left_join.rb', line 168 def to_sparql(top_level: true, filter_ops: [], extensions: {}, **) str = "{\n" + operands[0].to_sparql(top_level: false, extensions: {}, **) str << "\nOPTIONAL {\n" + operands[1].to_sparql(top_level: false, extensions: {}, **) case operands[2] when SPARQL::Algebra::Operator::Exprlist operands[2].operands.each do |op| str << "\nFILTER (" + op.to_sparql(**) + ")" end when nil else str << "\nFILTER (" + operands[2].to_sparql(**) + ")" end str << "\n}}" top_level ? Operator.to_sparql(str, filter_ops: filter_ops, extensions: extensions, **) : str end |
#validate! ⇒ Object
The same blank node label cannot be used in two different basic graph patterns in the same query
120 121 122 123 124 125 126 127 128 |
# File 'lib/sparql/algebra/operator/left_join.rb', line 120 def validate! left_nodes, right_nodes = operand(0).ndvars, operand(1).ndvars unless (left_nodes.compact & right_nodes.compact).empty? raise ArgumentError, "sub-operands share non-distinguished variables: #{(left_nodes.compact & right_nodes.compact).to_sse}" end super end |