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

group[R]

SQL GROUP BY expression

literal[R]

literal SQL expression

nodes[R]

blank variables control section

ns[R]

query namespaces mapping

order[R]

SQL order expression

order_dir[R]

direction of order, ASC or DESC

pattern[R]

query pattern graph as array of triples [ [p, s, o], … ]

variables[R]

list of variables defined in the query

Public Class Methods

new(config, query) click to toggle source

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
ns_shrink(uriref, namespaces) click to toggle source

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
uri_shrink!(uriref, prefix, uri) click to toggle source

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

ns_shrink(uriref) click to toggle source

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
substitute_literals(s) click to toggle source

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
to_s() click to toggle source

returns original string passed in for parsing

# File lib/graffiti/squish.rb, line 155
def to_s
  @query
end
validate_expression(string) click to toggle source

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

expression_value(expr, params={}) click to toggle source

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
get_literal_value(i) click to toggle source
# File lib/graffiti/squish.rb, line 260
def get_literal_value(i)
  "'" + @strings[i].gsub("'", "''") + "'"
end
get_parameter_value(name, params) click to toggle source
# 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_pattern(pattern) click to toggle source

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