Module: SPARQL::Algebra::Expression Abstract
- Includes:
- RDF::Util::Logger
- Included in:
- RDF::Query::Variable, RDF::Term, Operator
- Defined in:
- lib/sparql/algebra/expression.rb
Overview
A SPARQL algebra expression.
Constant Summary collapse
- PATTERN_PARENTS =
Operators for which
:triple
denotes a pattern, not a builtin [ Operator::BGP, Operator::Construct, Operator::Delete, Operator::DeleteData, Operator::DeleteWhere, Operator::Graph, Operator::Insert, Operator::InsertData, Operator::Path, ].freeze
Class Method Summary collapse
-
.cast(datatype, value) ⇒ RDF::Term
Casts operand as the specified datatype.
-
.extension(function, *args) ⇒ RDF::Term
Invoke an extension function.
-
.extension?(function) ⇒ Boolean
Is an extension function available?.
-
.extensions ⇒ Hash{RDF:URI: Proc}
Registered extensions.
- .for(*sse, **options) ⇒ Expression (also: [])
- .new(sse, parent_operator: nil, **options) ⇒ Expression
-
.open(filename, **options) {|expression| ... } ⇒ Expression
Parses input from the given file name or URL.
- .parse(sse, **options) {|expression| ... } ⇒ Expression
-
.register_extension(uri) {|*args| ... }
Register an extension function.
Instance Method Summary collapse
-
#constant? ⇒ Boolean
Returns
true
. -
#evaluate(bindings, **options) ⇒ Expression
Evaluates this expression using the given variable
bindings
. -
#invalid? ⇒ Boolean
Is this value invalid, or is it composed of any invalid components?.
-
#node? ⇒ Boolean
Returns
false
. -
#optimize(**options) ⇒ Expression
Returns an optimized version of this expression.
-
#optimize!(**options) ⇒ self
Optimizes this query.
-
#to_sxp_bin ⇒ Array
Returns the SPARQL S-Expression (SSE) representation of this expression.
-
#valid? ⇒ Boolean
Is this value valid, and composed only of valid components?.
-
#validate! ⇒ SPARQL::Algebra::Expression
(also: #validate)
Default validate! implementation, overridden in concrete classes.
-
#variable? ⇒ Boolean
Returns
false
.
Class Method Details
.cast(datatype, value) ⇒ RDF::Term
Casts operand as the specified datatype
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/sparql/algebra/expression.rb', line 243 def self.cast(datatype, value) case datatype when RDF::XSD.date, RDF::XSD.time, RDF::XSD.dateTime case value when RDF::Literal::DateTime, RDF::Literal::Date, RDF::Literal::Time RDF::Literal.new(value, datatype: datatype) when RDF::Literal::Numeric, RDF::Literal::Boolean, RDF::URI, RDF::Node raise TypeError, "Value #{value.inspect} cannot be cast as #{datatype}" else RDF::Literal.new(value.value, datatype: datatype, validate: true) end when RDF::XSD.duration, RDF::XSD.dayTimeDuration, RDF::XSD.yearMonthDuration case value when RDF::Literal::Duration, RDF::Literal::DayTimeDuration, RDF::Literal::YearMonthDuration RDF::Literal.new(value, datatype: datatype, validate: true, canonicalize: true) when RDF::Literal::Numeric, RDF::Literal::Boolean, RDF::URI, RDF::Node raise TypeError, "Value #{value.inspect} cannot be cast as #{datatype}" else RDF::Literal.new(value.value, datatype: datatype, validate: true, canonicalize: true) end when RDF::XSD.float, RDF::XSD.double case value when RDF::Literal::Boolean RDF::Literal.new(value.object ? 1 : 0, datatype: datatype) when RDF::Literal::Numeric RDF::Literal.new(value.to_f, datatype: datatype) when RDF::Literal::DateTime, RDF::Literal::Date, RDF::Literal::Time, RDF::URI, RDF::Node raise TypeError, "Value #{value.inspect} cannot be cast as #{datatype}" else RDF::Literal.new(value.value, datatype: datatype, validate: true) end when RDF::XSD.boolean case value when RDF::Literal::Boolean value when RDF::Literal::Numeric RDF::Literal::Boolean.new(value.object != 0) when RDF::Literal::DateTime, RDF::Literal::Date, RDF::Literal::Time, RDF::URI, RDF::Node raise TypeError, "Value #{value.inspect} cannot be cast as #{datatype}" else RDF::Literal::Boolean.new(value.value, datatype: datatype, validate: true) end when RDF::XSD.decimal, RDF::XSD.integer case value when RDF::Literal::Boolean RDF::Literal.new(value.object ? 1 : 0, datatype: datatype) when RDF::Literal::Numeric RDF::Literal.new(value.object, datatype: datatype) when RDF::Literal::DateTime, RDF::Literal::Date, RDF::Literal::Time, RDF::URI, RDF::Node raise TypeError, "Value #{value.inspect} cannot be cast as #{datatype}" else RDF::Literal.new(value.value, datatype: datatype, validate: true) end when RDF::XSD.string # Cast to string rules based on https://www.w3.org/TR/xpath-functions/#casting-to-string case value when RDF::Literal::Integer RDF::Literal.new(value.canonicalize.to_s, datatype: datatype) when RDF::Literal::Decimal if value == value.ceil RDF::Literal.new(value.ceil, datatype: datatype) else RDF::Literal.new(value.canonicalize.to_s, datatype: datatype) end when RDF::Literal::Float, RDF::Literal::Double if value.abs >= 0.000001 && value.abs < 1000000 # If SV has an absolute value that is greater than or equal to 0.000001 (one millionth) and less than 1000000 (one million), then the value is converted to an xs:decimal and the resulting xs:decimal is converted to an xs:string according to the rules above, as though using an implementation of xs:decimal that imposes no limits on the totalDigits or fractionDigits facets. cast(datatype, RDF::Literal::Decimal.new(value.object)) elsif value.object.zero? # If SV has the value positive or negative zero, TV is "0" or "-0" respectively. RDF::Literal.new(value.to_s.start_with?('-') ? '-0' : '0', datatype: datatype) else # If SV is positive or negative infinity, TV is the string "INF" or "-INF" respectively. # In other cases, the result consists of a mantissa, which has the lexical form of an xs:decimal, followed by the letter "E", followed by an exponent which has the lexical form of an xs:integer. Leading zeroes and "+" signs are prohibited in the exponent. For the mantissa, there must be a decimal point, and there must be exactly one digit before the decimal point, which must be non-zero. The "+" sign is prohibited. There must be at least one digit after the decimal point. Apart from this mandatory digit, trailing zero digits are prohibited. RDF::Literal.new(value.canonicalize.to_s, datatype: datatype) end else RDF::Literal.new(value.canonicalize.to_s, datatype: datatype) end else raise TypeError, "Expected datatype (#{datatype}) to be a recognized XPath function" end rescue raise TypeError, $!. end |
.extension(function, *args) ⇒ RDF::Term
Invoke an extension function.
Applies a registered extension function, if registered. Otherwise, if it is an XSD Constructor function, apply that.
222 223 224 225 226 227 228 229 230 |
# File 'lib/sparql/algebra/expression.rb', line 222 def self.extension(function, *args) if function.to_s.start_with?(RDF::XSD.to_s) self.cast(function, args.first) elsif extension_function = self.extensions[function] extension_function.call(*args) else raise TypeError, "Extension function #{function} not recognized" end end |
.extension?(function) ⇒ Boolean
Is an extension function available?
It’s either a registered extension, or an XSD casting function
206 207 208 |
# File 'lib/sparql/algebra/expression.rb', line 206 def self.extension?(function) function.to_s.start_with?(RDF::XSD.to_s) || self.extensions[function] end |
.extensions ⇒ Hash{RDF:URI: Proc}
Registered extensions
195 196 197 |
# File 'lib/sparql/algebra/expression.rb', line 195 def self.extensions @extensions ||= {} end |
.for(*sse, **options) ⇒ Expression Also known as: []
85 86 87 |
# File 'lib/sparql/algebra/expression.rb', line 85 def self.for(*sse, **) self.new(sse, **) end |
.new(sse, parent_operator: nil, **options) ⇒ Expression
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 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/sparql/algebra/expression.rb', line 100 def self.new(sse, parent_operator: nil, **) raise ArgumentError, "invalid SPARQL::Algebra::Expression form: #{sse.inspect}" unless sse.is_a?(Array) operator = Operator.for(sse.first, sse.length - 1) # If we don't find an operator, and sse.first is an extension IRI, use a function call if !operator && sse.first.is_a?(RDF::URI) && self.extension?(sse.first) operator = Operator.for(:function_call, sse.length) sse.unshift(:function_call) end unless operator return case sse.first when Array debug() {"Map array elements #{sse}"} sse.map {|s| self.new(s, parent_operator: parent_operator, **.merge(depth: [:depth].to_i + 1))} else debug() {"No operator found for #{sse.first}"} sse.map do |s| s.is_a?(Array) ? self.new(s, parent_operator: parent_operator, depth: [:depth].to_i + 1) : s end end end operands = sse[1..-1].map do |operand| debug() {"Operator=#{operator.inspect}, Operand=#{operand.inspect}"} case operand when Array self.new(operand, parent_operator: operator, **.merge(depth: [:depth].to_i + 1)) when Operator, Variable, RDF::Term, RDF::Query, Symbol operand when TrueClass, FalseClass, Numeric, String, DateTime, Date, Time RDF::Literal(operand) else raise TypeError, "invalid SPARQL::Algebra::Expression operand: #{operand.inspect}" end end debug() {"#{operator.inspect}(#{operands.map(&:inspect).join(',')})"} logger = [:logger] .delete_if {|k, v| [:debug, :logger, :depth, :prefixes, :base_uri, :update, :validate].include?(k) } begin # Due to confusion over (triple) and special-case for (qtriple) if operator == RDF::Query::Pattern = .merge(quoted: true) if sse.first == :qtriple elsif operator == Operator::Triple && PATTERN_PARENTS.include?(parent_operator) operator = RDF::Query::Pattern end operator.new(*operands, parent_operator: operator, **) rescue ArgumentError => e if logger logger.error("Operator=#{operator.inspect}: #{e}") else raise "Operator=#{operator.inspect}: #{e}" end end end |
.open(filename, **options) {|expression| ... } ⇒ Expression
Parses input from the given file name or URL.
68 69 70 71 72 73 |
# File 'lib/sparql/algebra/expression.rb', line 68 def self.open(filename, **, &block) RDF::Util::File.open_file(filename, **) do |file| [:base_uri] ||= filename Expression.parse(file, **, &block) end end |
.parse(sse, **options) {|expression| ... } ⇒ Expression
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/sparql/algebra/expression.rb', line 37 def self.parse(sse, **, &block) sse = sse.encode(Encoding::UTF_8) sxp = SXP::Reader::SPARQL.new(sse) do |reader| # Set base_uri if we have one reader.base_uri = [:base_uri] if [:base_uri] end sxp_result = sxp.read debug() {"base_uri: #{[:base_uri]}"} Operator.base_uri = .delete(:base_uri) if .has_key?(:base_uri) Operator.prefixes = sxp.prefixes || {} expression = self.new(sxp_result, **) yield(expression) if block_given? expression end |
.register_extension(uri) {|*args| ... }
This method returns an undefined value.
Register an extension function.
Extension functions take zero or more arguments of type RDF::Term
and return an argument of type RDF::Term
, or raise TypeError
.
Functions are identified using the uri
parameter and specified using a block.
Arguments are evaluated, and the block is called with argument values (if a variable was unbound, an error will have been generated).
It is possible to get unevaluated arguments but care must be taken not to violate the rules of function evaluation.
Normally, block should be a pure evaluation based on it’s arguments. It should not access a graph nor return different values for the same arguments (to allow expression optimization). Blocks can’t bind a variables.
185 186 187 188 189 |
# File 'lib/sparql/algebra/expression.rb', line 185 def self.register_extension(uri, &block) raise TypeError, "uri must be an IRI" unless uri.is_a?(RDF::URI) raise TypeError, "must pass a block" unless block_given? self.extensions[uri] = block end |
Instance Method Details
#constant? ⇒ Boolean
Returns true
.
351 352 353 |
# File 'lib/sparql/algebra/expression.rb', line 351 def constant? !(variable?) end |
#evaluate(bindings, **options) ⇒ Expression
Evaluates this expression using the given variable bindings
.
This is the default implementation, which simply returns self
. Subclasses can override this method in order to implement something more useful.
393 394 395 |
# File 'lib/sparql/algebra/expression.rb', line 393 def evaluate(bindings, **) self end |
#invalid? ⇒ Boolean
Is this value invalid, or is it composed of any invalid components?
422 423 424 |
# File 'lib/sparql/algebra/expression.rb', line 422 def invalid? !valid? end |
#node? ⇒ Boolean
Returns false
.
342 343 344 |
# File 'lib/sparql/algebra/expression.rb', line 342 def node? false end |
#optimize(**options) ⇒ Expression
Returns an optimized version of this expression.
This is the default implementation, which simply returns a copy of self
. Subclasses can override this method in order to implement something more useful.
366 367 368 |
# File 'lib/sparql/algebra/expression.rb', line 366 def optimize(**) self.deep_dup.optimize!(**) end |
#optimize!(**options) ⇒ self
Optimizes this query.
377 378 379 |
# File 'lib/sparql/algebra/expression.rb', line 377 def optimize!(**) self end |
#to_sxp_bin ⇒ Array
Returns the SPARQL S-Expression (SSE) representation of this expression.
This is the default implementation, which simply returns self
. Subclasses can override this method in order to implement something more useful.
406 407 408 |
# File 'lib/sparql/algebra/expression.rb', line 406 def to_sxp_bin self end |
#valid? ⇒ Boolean
Is this value valid, and composed only of valid components?
414 415 416 |
# File 'lib/sparql/algebra/expression.rb', line 414 def valid? true end |
#validate! ⇒ SPARQL::Algebra::Expression Also known as: validate
Default validate! implementation, overridden in concrete classes
430 431 432 433 |
# File 'lib/sparql/algebra/expression.rb', line 430 def validate! raise ArgumentError if invalid? self end |
#variable? ⇒ Boolean
Returns false
.
334 335 336 |
# File 'lib/sparql/algebra/expression.rb', line 334 def variable? false end |