Class: SPARQL::Algebra::Operator::Group
- Inherits:
-
SPARQL::Algebra::Operator
- Object
- SPARQL::Algebra::Operator
- SPARQL::Algebra::Operator::Group
- Includes:
- Query
- Defined in:
- lib/sparql/algebra/operator/group.rb
Overview
The SPARQL group
operator.
group
takes either two or three operands. The first operand is an array of grouped variables. The last operand is the query to be grouped. If three operands are provided, the second is an array of temporary bindings.
[19] GroupClause ::= ‘GROUP’ ‘BY’ GroupCondition+
Constant Summary collapse
- NAME =
[:group]
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
query
withqueryable
and groups results based on the first operand. -
#internal_variables ⇒ Hash{Symbol => RDF::Query::Variable}
The variables used within the query.
-
#to_sparql(extensions: {}, filter_ops: [], **options) ⇒ String
Returns a partial SPARQL grammar for this operator.
-
#validate! ⇒ Object
It is an error for aggregates to project variables with a name already used in other aggregate projections, or in the WHERE clause.
-
#variables ⇒ Hash{Symbol => RDF::Query::Variable}
The variables used in the extension.
Methods included from Query
#each_solution, #empty?, #failed?, #graph_name=, #matched?, #query_yields_boolean?, #query_yields_solutions?, #query_yields_statements?, #unshift
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, #optimize!, #parent, #parent=, #prefixes, prefixes, prefixes=, #rewrite, #to_binary, to_sparql, #to_sxp, #to_sxp_bin, #variable?, #vars
Methods included from Expression
cast, #constant?, #evaluate, extension, extension?, extensions, for, #invalid?, new, #node?, open, #optimize, #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 query
with queryable
and groups results based on the first operand.
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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/sparql/algebra/operator/group.rb', line 80 def execute(queryable, **, &block) debug() {"Group"} exprlist = operands.first query = operands.last aggregates = operands.length == 3 ? operand(1) : [] solutions = queryable.query(query, **.merge(depth: [:depth].to_i + 1)) groups = solutions.group_by do |solution| # Evaluate each exprlist operand to get groups where each key is a new solution # ListEval((expr1, ..., exprn), μ) returns a list (e1, ..., en), where ei = expri(μ) or error. soln = RDF::Query::Solution.new exprlist.each do |operand| begin if operand.is_a?(Array) # Form is [variable, expression] soln[operand.first] = operand.last.evaluate(solution, queryable: queryable, depth: [:depth].to_i + 1, **) else # Form is variable soln[operand] = operand.evaluate(solution, queryable: queryable, depth: [:depth].to_i + 1, **) end rescue TypeError # Ignore expression end end soln end debug() {"=>(groups) #{groups.inspect}"} # Aggregate solutions in each group using aggregates to get solutions @solutions = RDF::Query::Solutions(groups.map do |group_soln, solns| aggregates.each do |(var, aggregate)| begin group_soln[var] = aggregate.aggregate(solns, **) rescue TypeError # Ignored in output end end group_soln end) # If there exprlist is empty, make sure that's at least an empty solution if @solutions.empty? && exprlist.empty? soln = RDF::Query::Solution.new aggregates.each do |(var, aggregate)| begin soln[var] = aggregate.aggregate([], **) rescue TypeError # Ignored in output end end @solutions << soln end debug() {"=>(solutions) #{@solutions.inspect}"} @solutions.each(&block) if block_given? @solutions end |
#internal_variables ⇒ Hash{Symbol => RDF::Query::Variable}
The variables used within the query
189 190 191 |
# File 'lib/sparql/algebra/operator/group.rb', line 189 def internal_variables operands.last.variables end |
#to_sparql(extensions: {}, filter_ops: [], **options) ⇒ String
Returns a partial SPARQL grammar for this operator.
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/sparql/algebra/operator/group.rb', line 202 def to_sparql(extensions: {}, filter_ops: [], **) having_ops = [] if operands.length > 2 temp_bindings = operands[1].inject({}) {|memo, (var, op)| memo.merge(var => op)} # Replace extensions from temporary bindings temp_bindings.each do |var, op| # Update extensions using a temporarily bound variable with its binding extensions = extensions.inject({}) do |memo, (ext_var, ext_op)| if ext_op.is_a?(Operator) # Try to recursivley replace variable within operator new_op = ext_op.deep_dup.rewrite do |operand| if operand.is_a?(Variable) && operand.to_sym == var.to_sym op.dup else operand end end memo.merge(ext_var.to_s => new_op) elsif ext_op.is_a?(Variable) && ext_op.to_sym == var.to_sym memo.merge(ext_var.to_s => op) else # Doesn't match this variable, so don't change memo.merge(ext_var.to_s => ext_op) end end # Filter ops using temporary bindinds are used for HAVING clauses filter_ops.each do |fop| having_ops << fop if fop.descendants.include?(var) && !having_ops.include?(fop) end end # If used in a HAVING clause, it's not also a filter filter_ops -= having_ops # Replace each operand in having using var with it's corresponding operation having_ops = having_ops.map do |op| op.dup.rewrite do |operand| # Rewrite based on temporary bindings temp_bindings.fetch(operand, operand) end end end operands.last.to_sparql(extensions: extensions, group_ops: operands.first, having_ops: having_ops, **) end |
#validate! ⇒ Object
It is an error for aggregates to project variables with a name already used in other aggregate projections, or in the WHERE clause.
It is also an error to project ungrouped variables
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/sparql/algebra/operator/group.rb', line 147 def validate! group_vars = operand(0).map {|v| Array(v).first} ext = first_ancestor(Extend) extend_vars = ext ? ext.operand(0).map(&:first).select {|v| v.is_a?(RDF::Query::Variable)} : [] project = first_ancestor(Project) # If not projecting, were are effectively projecting all variables in the query project_vars = project ? project.operand(0) : operands.last.vars available_vars = (extend_vars + group_vars).compact # All variables must either be grouped or extended unless (project_vars - available_vars).empty? raise ArgumentError, "projecting ungrouped/extended variables: #{(project_vars.compact - available_vars.compact).to_sse}" end super end |
#variables ⇒ Hash{Symbol => RDF::Query::Variable}
The variables used in the extension. Includes grouped variables and temporary, but not those in the query, itself
170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/sparql/algebra/operator/group.rb', line 170 def variables group_vars = operands.first aggregate_vars = (operands.length == 3 ? operand(1) : []) # Extract first element of each and merge it's variables (group_vars + aggregate_vars). map do |o| v = Array(o).first v if v.is_a?(RDF::Query::Variable) end.compact. map(&:variables). inject({}) {|memo, h| memo.merge(h)} end |