class Graffiti::SquishQuery
parse Squish query and translate triples to relational conditions
provides access to internal representation of the parsed query and utility functions to deal with Squish syntax
Constants
- AGGREGATE
regexp for aggregate function
- BN
regexp for blank node mark and name
- BN_SCAN
regexp for scanning blank nodes inside a string
- INTERNAL
regexp for internal resource reference
- LITERAL
regexp for replaced string literal
- LITERAL_SCAN
regexp for scanning replaced string literals in a string
- NUMBER
regexp for number
- OPERATOR
regexp for operator
- PARAMETER
regexp for parametrized value
- PARAMETER_AND_LITERAL_SCAN
regexp for scanning query parameters inside a string
- PATTERN_SCAN
- QUERY
Attributes
SQL GROUP BY expression
literal SQL expression
blank variables control section
query namespaces mapping
SQL order expression
direction of order, ASC or DESC
query pattern graph as array of triples [ [p, s, o], … ]
list of variables defined in the query
Public Class Methods
extract common Squish query sections, perform namespace substitution, generate query pattern graph, call transform_pattern, determine query type and parse nodes section accordingly
# File lib/graffiti/squish.rb, line 71 def initialize(config, query) query.nil? and raise ProgrammingError, "SquishQuery: query can't be nil" if query.kind_of? Hash # pre-parsed query (used by SquishAssert) @nodes = query[:nodes] @pattern = query[:pattern] @negative = query[:negative] @optional = query[:optional] @strings = query[:strings] @literal = @group = @order = '' @sql_mapper = SqlMapper.new(config, @pattern) return self elsif not query.kind_of? String raise ProgrammingError, "Bad query initialization parameter class: #{query.class}" end debug { 'SquishQuery ' + query } @query = query # keep original string query = query.dup # replace string literals with 'n' placeholders (also see #substitute_literals) @strings = [] query.gsub!(/'((?:''|[^'])*)'/m) do @strings.push $1.gsub("''", "'") # keep unescaped string "'" + (@strings.size - 1).to_s + "'" end match = QUERY.match(query) or raise ProgrammingError, "Malformed query: are keywords SELECT, INSERT, UPDATE or WHERE missing?" match, @key, @nodes, @pattern, @negative, @optional, @literal, @group, @order, @order_dir, @ns = match.to_a.collect {|m| m.to_s } match = nil @key.upcase! @order_dir = @order_dir.upcase # can't mutate: ASC/DESC comes frozen from QUERY # namespaces # todo: validate ns @ns = (@ns.empty? or /\APRESET\s+NS\z/ =~ @ns) ? config.ns : Hash[*@ns.gsub(/\b(FOR|AS|AND)\b/i, '').scan(/\S+/)] @pattern = parse_pattern(@pattern) @optional = parse_pattern(@optional) @negative = parse_pattern(@negative) # validate SQL expressions validate_expression(@literal) @group.split(/\s*,\s*/).each {|group| validate_expression(group) } validate_expression(@order) @sql_mapper = SqlMapper.new( config, @pattern, @negative, @optional, @literal) # check that all variables can be bound @variables = query.scan(BN_SCAN) @variables.each {|node| @sql_mapper.bind(node) } return self end
replace schema uri with a prefix from a supplied namespaces hash
# File lib/graffiti/squish.rb, line 176 def SquishQuery.ns_shrink(uriref, namespaces) u = uriref.dup or return nil namespaces.each {|p, uri| SquishQuery.uri_shrink!(u, p, uri) and break } return u end
replace schema uri with namespace prefix
# File lib/graffiti/squish.rb, line 170 def SquishQuery.uri_shrink!(uriref, prefix, uri) uriref.gsub!(/\A#{uri}([^\/#]+)\z/) {"#{prefix}::#{$1}"} end
Public Instance Methods
replace schema uri with a prefix from query namespaces
# File lib/graffiti/squish.rb, line 184 def ns_shrink(uriref) SquishQuery.ns_shrink(uriref, @ns) end
replace 'n' substitutions with query string literals (see new, #LITERAL)
# File lib/graffiti/squish.rb, line 161 def substitute_literals(s) return s unless s.kind_of? String s.gsub(LITERAL_SCAN) do get_literal_value($1.to_i) end end
returns original string passed in for parsing
# File lib/graffiti/squish.rb, line 155 def to_s @query end
validate expression
expression := value [ operator expression ]
value := blank_node | literal_string | number | '(' expression ')'
whitespace between tokens (except inside parentheses) is mandatory
# File lib/graffiti/squish.rb, line 196 def validate_expression(string) # todo: lexical analyser string.split(/[\s(),]+/).each do |token| case token when '', BN, PARAMETER, LITERAL, NUMBER, OPERATOR, AGGREGATE else raise ProgrammingError, "Bad token '#{token}' in expression" end end string end
Private Instance Methods
replace RDF query parameters with their values
# File lib/graffiti/squish.rb, line 233 def expression_value(expr, params={}) case expr when 'NULL' nil when PARAMETER get_parameter_value($1, params) when LITERAL @strings[$1.to_i] else expr.gsub(PARAMETER_AND_LITERAL_SCAN) do if $1 # parameter get_parameter_value($1, params) else # literal get_literal_value($2.to_i) end end # fixme: make Sequel treat it as SQL expression, not a string value end end
# File lib/graffiti/squish.rb, line 260 def get_literal_value(i) "'" + @strings[i].gsub("'", "''") + "'" end
# File lib/graffiti/squish.rb, line 253 def get_parameter_value(name, params) key = name.to_sym params.has_key?(key) or raise ProgrammingError, 'Unknown parameter :' + name params[key] end
parse query pattern graph out of a string, expand URI namespaces
# File lib/graffiti/squish.rb, line 214 def parse_pattern(pattern) pattern.scan(/\(.*?\)(?=\s*(?:\(|\z))/).collect do |c| match, predicate, subject, object, filter, transitive = c.match(PATTERN_SCAN).to_a match = nil [predicate, subject, object].each do |u| u.sub!(/\A(\S+?)::/) do @ns[$1] or raise ProgrammingError, "Undefined namespace prefix #{$1}" end end validate_expression(filter.to_s) [predicate, subject, object, filter, 'TRANSITIVE' == transitive] end end