class JunglePath::Query::Engine

Attributes

apply_limit_offset_to_sql[R]
db[R]
identity[R]
root[R]
tables[R]

Public Class Methods

add_array(symbols, hash) click to toggle source
# File lib/jungle_path/query/engine.rb, line 828
def self.add_array(symbols, hash)
  result = {}
  hash.each do |name, value|
    if value.class == Hash
      symbol = symbols.pop
      if symbol == "<="
        result[name] = []
        result[name] << add_array(symbols, value)
      else
        result[name] = add_array(symbols, value)
      end
    else
      result[name] = value
    end
  end
  result
end
add_arrays(query, rows) click to toggle source
# File lib/jungle_path/query/engine.rb, line 819
def self.add_arrays(query, rows)
  results = []
  rows.each do |hash|
    symbols = query.symbols.reverse
    results << add_array(symbols, hash)
  end
  results
end
calc_paths(query) click to toggle source
# File lib/jungle_path/query/engine.rb, line 695
def self.calc_paths(query)
  # For each sort field, find the path to its value in the nested hash.
  # Returns an array containing an arrays of hash keys. (One array of keys for each sort field.)
  sort_fields = query.root.sort
  paths = []
  sort_fields.each do |sort_field|
    keys = []
    paths << keys
    if sort_field.entities
      sort_field.entities.each do |e|
        keys << e.to_sym
      end
    end
    keys << sort_field.field_name
  end
  paths
end
calc_sort_orders(query) click to toggle source
# File lib/jungle_path/query/engine.rb, line 713
def self.calc_sort_orders(query)
  # For each sort field, add its ordering (:asc or :desc) into the returned array.
  sort_fields = query.root.sort
  sort_orders = []
  sort_fields.each do |sort_field|
    sort_orders << sort_field.sort.to_sym
  end
  sort_orders
end
combine_results(query, rows) click to toggle source
# File lib/jungle_path/query/engine.rb, line 868
def self.combine_results(query, rows)
  results = []

  aliases = {}
  query.aliases.values.each do |ai|
    aliases[ai.name] = ai
  end

  rows.each do |new_row|
    if results.length == 0
      results << new_row
    else
      begin
        previous_row = results[-1]
        if primary_keys_match?(previous_row, new_row, aliases.values[0].primary_key_columns_count)
          #merge the row...
          results[-1 ] = merge_hash(previous_row, new_row, aliases)
        else
          # add the row...
          results << new_row
        end
      rescue
        puts "rescue..."
        puts previous_row
        puts ""
        puts new_row
        puts ""
        raise
      end
    end
  end
  results
end
connectors() click to toggle source
# File lib/jungle_path/query/engine.rb, line 671
def self.connectors
  Set.new(["and", "or"])
end
dataset_to_array(query, dataset) click to toggle source
# File lib/jungle_path/query/engine.rb, line 679
def self.dataset_to_array(query, dataset)
  results = []
  dataset.each do |row|
    results << row
  end
  results
end
merge_hash(a_hash, b_hash, aliases) click to toggle source
# File lib/jungle_path/query/engine.rb, line 902
def self.merge_hash(a_hash, b_hash, aliases) #pk_fld_counts)
  h = {}
  a = a_hash.to_a # [[name, value], [name, value], ... ]
  b = b_hash.to_a # [[name, value], [name, value], ... ]
  a.each_index do |i|
    a_key = a[i][0] # name
    a_value = a[i][1] # value
    b_value = b[i][1] # value
    if a_value.class == Array

      pk_fld_count = aliases[a_key].primary_key_columns_count

      h[a_key] = a_value # a array
      a_hash = a_value[-1] # do we merge last element?
      b_hash = b_value[0] # compare to value from new row...
      if primary_keys_match?(a_hash, b_hash, pk_fld_count)
        h[a_key][-1] = merge_hash(a_hash,  b_hash, aliases)
      else
        h[a_key] << b_hash # add value from new row to array
      end
    elsif a_value.class == Hash
      pk_fld_count = aliases[a_key].primary_key_columns_count

      if primary_keys_match?(a_value, b_value, pk_fld_count)
        h[a_key] = merge_hash(a_value, b_value, aliases)
      else
        raise "unexpected multiple values found!!! Expected\na_value: '#{a_value}'\npk to match\nb_value: '#{b_value}'\npk."
      end
    else
      # regular field so a_value and b_value are the same.
      h[a_key] = a_value
    end
  end
  h
end
new(node_tree, identity=nil, apply_limit_offset_to_sql=true, db=nil) click to toggle source

def initialize(tables_hash, identity=nil, apply_limit_offset_to_sql=true)

# File lib/jungle_path/query/engine.rb, line 13
def initialize(node_tree, identity=nil, apply_limit_offset_to_sql=true, db=nil)
  @tables = node_tree.tables_hash
  @identity = identity
  @apply_limit_offset_to_sql = apply_limit_offset_to_sql
  @db = db
  #@root = Gen.gen_node_tree(tables_hash)
  @root = node_tree
end
operator_map() click to toggle source
# File lib/jungle_path/query/engine.rb, line 667
def self.operator_map
  {"==" => "=", "!=" => "!=", ">" => ">", "<" => "<", ">=" => ">=", "<=" => "<=", "~" => "ilike", "=~" => "regex", "~~" => "full text search", ">>" => "in", "<<" => "not in", "is" => "is", "is!" => "is not"}
end
operators() click to toggle source
# File lib/jungle_path/query/engine.rb, line 663
def self.operators
  Set.new(["==", "!=", ">", "<", ">=", "<=", "~", "=~", "~~", ">>", "<<", "is", "is!"])
end
primary_keys_match?(row_a, row_b, pk_fld_count) click to toggle source
# File lib/jungle_path/query/engine.rb, line 846
def self.primary_keys_match?(row_a, row_b, pk_fld_count)
  # most common:
  if pk_fld_count == 1
    if row_a.first[1] == row_b.first[1]
      return true
    end
  end

  # multiple PKs:
  values_a = row_a.values
  values_b = row_b.values
  values_a.each_index do |i|
    if i == pk_fld_count
      break
    end
    if values_a[i] != values_b[i]
      return false
    end
  end
  return true
end
run(query, db, level=7) click to toggle source
# File lib/jungle_path/query/engine.rb, line 22
def self.run(query, db, level=7)
  dataset = run_query(query, db)
  rows = dataset_to_array(query, dataset) if level > 0
  rows = sort_by_ids(query, rows) if level > 1
  rows = transform_results_to_hash_array(query, rows) if level > 2
  rows = add_arrays(query, rows) if level > 3
  rows = combine_results(query, rows) if level > 4
  rows = sort_by_original_sort(query, rows) if level > 5
  rows = wrap_outer_objects(query, rows) if level > 6
  if rows
    if query.apply_limit_offset_to_sql
      return rows
    else
      puts "query.root.limit: #{query.root.limit}."
      puts "query.root.offset: #{query.root.offset}."
      limit = 0
      offset = 0
      if query.root.limit and query.root.limit > 0
        limit = query.root.limit
      end
      if query.root.offset and query.root.offset > 0
        offset = query.root.offset
      end
      puts "rows.length: #{rows.length}."
      return rows if limit == 0 and offset == 0
      if offset > 0 and limit == 0
        rows = rows[offset, -1]
        rows = [] unless rows
        return rows
      end
      rows = rows[offset, limit]
      rows = [] unless rows
      return rows
    end
  else
    return dataset
  end
end
run_query(query, db) click to toggle source
# File lib/jungle_path/query/engine.rb, line 61
def self.run_query(query, db)
  ds = db[query.sql, *query.values]
end
sort_by_ids(query, results) click to toggle source
# File lib/jungle_path/query/engine.rb, line 687
def self.sort_by_ids(query, results)
  keys = query.sort_ids
  if results.length > 0
    results = results.sort_by {|h| (h.reject {|x| !keys.include?(x)}).values }
  end
  results
end
sort_by_original_sort(query, rows) click to toggle source
# File lib/jungle_path/query/engine.rb, line 727
def self.sort_by_original_sort(query, rows)
  if query.root.sort and query.root.sort.length > 0
    paths = calc_paths(query)
    sort_orders = calc_sort_orders(query)
    sorter = JunglePath::Query::NestedHashSorter.new(paths, sort_orders)
    begin
      #strange: when I added this debugging code, modes_of_action.uql sorting on code started working...
      #was getting: ArgumentError - comparison of Hash with Hash failed.
      #very strange!
      count = 0
      rows = rows.sort do |a, b|
        count += 1
        val = sorter.sort(a, b)
        val
      end
    rescue
      puts "count: #{count}."
      raise
    end
  end
  rows
end
sorts() click to toggle source
# File lib/jungle_path/query/engine.rb, line 675
def self.sorts
  Set.new(["asc", "desc"])
end
transform_results_to_hash_array(query, rows) click to toggle source
# File lib/jungle_path/query/engine.rb, line 750
def self.transform_results_to_hash_array(query, rows)
  # transform results into nested hashes (for converting to json later...)
  results = []
  rows.each do |row|
    #binding.pry
    a_stack = []
    h_stack = []
    current_alias = nil
    current_hash = nil
    row.each do |k, v| #each item in this row...
      new_alias, field_name = k.to_s.split(".").map{|a| a.to_sym}
      parent_alias = query.aliases[new_alias].parent_alias
      #parent_alias = parent_alias.to_sym if parent_alias

      #load stack:
      if current_alias
        a_stack.push(current_alias)
        h_stack.push(current_hash)
      end

      #unload stack:
      index = a_stack.index(new_alias)
      if index
        # We've seen this new alias before. Get it (and the related hash) out of the stacks:
        begin
          current_alias = a_stack.pop
          current_hash = h_stack.pop
        end until current_alias == new_alias
        current_hash[field_name] = v

      elsif current_alias
        if a_stack.include?(parent_alias)

          #get parent hash
          begin
            temp_parent_alias = a_stack.pop
            temp_parent_hash = h_stack.pop
          end until temp_parent_alias == parent_alias

          # put new hash into parent hash:
          new_hash = {}
          temp_parent_hash[query.aliases[new_alias.to_sym].name ] = new_hash # (convert alias to entity name)
          a_stack.push temp_parent_alias
          h_stack.push temp_parent_hash

          # we now have new currents:
          current_alias = new_alias
          current_hash = new_hash
          current_hash[field_name] = v
        else
          raise "parent_alias: #{parent_alias} was not found in a_stack: #{a_stack}. (h_stack: #{h_stack}).\nresults:\n#{results}."
        end
      else
        #completely new:
        current_alias = new_alias
        current_hash = {}
        current_hash[field_name] = v
      end
    end
    # get the earliest parent (which contains all of the rest) and add to results:
    while h_stack.length > 0
      current_hash = h_stack.pop
    end
    results << current_hash
    #break # <= for debugging only
  end
  results
end
wrap_outer_objects(query, rows) click to toggle source
# File lib/jungle_path/query/engine.rb, line 723
def self.wrap_outer_objects(query, rows)
  rows.map {|row| {query.root.name => row}}
end

Public Instance Methods

find_table_entity(name, parent_entity, level=0, entities=nil) click to toggle source
# File lib/jungle_path/query/engine.rb, line 492
def find_table_entity(name, parent_entity, level=0, entities=nil)
  #binding.pry
  entities = [] unless entities
  entity = nil
  parent_entity.fields.each do |field|
    if field.is_entity? and field.name == name
      entities << name
      entity = field
      break
    elsif field.is_entity?
      entities.push field.name
      entity, entities = find_table_entity(name, field, level + 1, entities)
      if entity
        break
      end
      entities.pop
    end
  end
  return entity, entities
end
get_entity_child_node(token, entity) click to toggle source
# File lib/jungle_path/query/engine.rb, line 470
def get_entity_child_node(token, entity)
  #puts "entity: #{entity.name}."
  #puts "token:  #{token}."
  #puts "entity.node: #{entity.node}."
  field_name = JunglePath::Query::Entity.get_name_from_token(token)
  #puts "field_name: #{field_name}."
  if entity.node.nodes.length > 0
    node = entity.node.nodes[field_name]
  else
    #puts "entity.node.child_table_name: #{entity.node.child_table_name}."
    node = @root.nodes[entity.node.child_table_name].nodes[field_name]
  end

  #new:
  if node and node.class == ::Array #see gen_node_tree.rb line 60+.
    # node is actually array of nodes, so pick correct one... token s/have column indicator, otherwise just grab first one.
    node = JunglePath::Query::Entity.select_node_from_array_by_token(node, token)
  end

  node
end
get_entity_node(token, parent_entity) click to toggle source
# File lib/jungle_path/query/engine.rb, line 448
def get_entity_node(token, parent_entity)
  name = JunglePath::Query::Entity.get_name_from_token(token)
  if parent_entity
    node = parent_entity.node.nodes[name]
    unless node
      if parent_entity.node.child_table_name
        node = @root.nodes[parent_entity.node.child_table_name].nodes[name]
      else
        node = @root.nodes[parent_entity.name].nodes[name]
      end
    end
  end
  node = @root.nodes[name] unless node

  #new:
  if node and node.class == ::Array
    node = JunglePath::Query::Entity.select_node_from_array_by_token(node, token)
  end

  node
end
get_query_from_string(query_string) click to toggle source
# File lib/jungle_path/query/engine.rb, line 65
def get_query_from_string(query_string)
  tokens = tokenize(query_string).reverse
  entity_root, values = parse_tokens(tokens)
  #sql, aliases, symbols, sort_ids, primary_key_field_count = Engine.generate_sql @tables, (entity_root)
  sql, aliases, symbols, sort_ids, primary_key_field_count = JunglePath::Query::SQLString.generate( self, (entity_root) )
  JunglePath::Query::Query.new(entity_root, sql, values, aliases, symbols, sort_ids, primary_key_field_count, @apply_limit_offset_to_sql)
end
next_token(tokens) click to toggle source
# File lib/jungle_path/query/engine.rb, line 82
def next_token(tokens)
  token = tokens.pop
  token
end
parse_tokens(tokens) click to toggle source
# File lib/jungle_path/query/engine.rb, line 73
def parse_tokens(tokens)
  aliases = ('a'..'z').to_a.reverse
  doubles = aliases.map{|a| a + a} #also add in "aa", "bb", ... "zz"
  aliases = doubles.push(*aliases)
  values = []
  entity_root = process_entity(nil, nil, tokens, aliases, values)
  return entity_root, values
end
process_entity(parent_entity, token, tokens, aliases, values, symbol=nil, root_entity=nil, in_fields=false) click to toggle source
# File lib/jungle_path/query/engine.rb, line 87
def process_entity(parent_entity, token, tokens, aliases, values, symbol=nil, root_entity=nil, in_fields=false)
  #puts "process_entity - parent: #{parent_entity != nil}; token: #{token}."
  expect_entity = false
  expect_fields = false
  expect_filter = false
  expect_sort = false
  expect_limit = false
  expect_offset = false
  entity = nil
  token = next_token(tokens) unless token
  while token
    if !entity
      expect_entity = true
      if token =~ /^[a-zA-Z_]/ # starts with alpha or underscore
        #binding.pry
        node = get_entity_node(token, parent_entity)
        if node
          entity = JunglePath::Query::Entity.new(token, aliases.pop, node, parent_entity, symbol)
          root_entity = entity unless root_entity
          expect_entity = false
          expect_fields = true
        else
          raise "Entity #{token} was not found."
        end
      else
        raise "Expected token to be an entity name. Token: #{token}"
      end
    elsif expect_offset and token == "[" #token.start_with? "["
      expect_offset = false
      #entity.offset = JunglePath::Query::Limit.parse(token) #same format as Limit.
      entity.offset = process_limit(tokens) # same as limit
    elsif expect_limit and token == "[" #token.start_with? "["
      expect_limit = false
      #entity.limit = JunglePath::Query::Limit.parse(token)
      entity.limit = process_limit(tokens)
      expect_offset = true
    elsif expect_sort and token == "("
      if in_fields
        raise "Sorts need to go in outer query -- they cannot be nested. Qualify fields using entity names. No qualification is needed for the outer entity."
      end
      expect_sort = false
      entity.sort = process_sort(entity, tokens, aliases, root_entity)
      expect_limit = true
    elsif expect_filter and token == "("
      expect_filter = false
      entity.filter = process_filter(entity, tokens, aliases, values, root_entity)
      if in_fields and tokens.length > 0 and (tokens.last == ")" or tokens.last == ",")
        #puts "break"
        break
      end
      expect_sort = true
    elsif expect_fields and token == "[" # got parameters which should be passed to the entity in sql as a table function.
      entity.parameters = process_parameters(tokens)
    elsif expect_fields and token == "("
      expect_fields = false
      entity.fields = process_fields(entity, tokens, aliases, values, root_entity)
      #puts "back in entity"
      if in_fields and tokens.length > 0 and (tokens.last == ")" or tokens.last == ",")
        #puts "break"
        break
      end
      expect_filter = true
    else
      raise "Unexpected token: #{token}."
    end
    token = next_token(tokens)
  end
  entity
end
process_fields(entity, tokens, aliases, values, root_entity) click to toggle source
# File lib/jungle_path/query/engine.rb, line 203
def process_fields(entity, tokens, aliases, values, root_entity)
  #puts "process_fields - entity.name: #{entity.name}."
  fields = []
  table = nil
  token = next_token(tokens)
  while token
    #puts "token: #{token}."
    if token == ")"
      break
    elsif token == ","
      # do nothing
    elsif token =~ /^[a-zA-Z_]/ # starts with alpha or underscore
      node = get_entity_child_node(token, entity)

      #Force PK fields onto field list:
      if fields.length == 0
        table_name = entity.node.child_table_name
        table_name = entity.name unless table_name
        #puts "#{entity.name}."
        #puts "#{entity.node.child_table_name}."
        table = tables[table_name]
        table.primary_key_columns.values.each do |pk_column|
          fields << JunglePath::Query::FieldPrimaryKey.new(pk_column.name, node, entity)
        end
      end

      if node and node.child_table_name
        # actually a table (entity).
        field = process_entity(entity, token, tokens, aliases, values, node.symbol, root_entity, true)
        field.field_node = node
        fields << field
      elsif node
        #puts "token, node, entity: #{token}, #{node.name}, #{entity.name}."
        field_symbol = token.downcase.to_sym
        is_secure = table.secure_columns.include? field_symbol
        fields << JunglePath::Query::Field.new(token, node, entity, is_secure) unless fields.map{|n| n.name}.include?(field_symbol)
      else
        raise "Token #{token} is not a valid table nor column name for entity: #{entity.name}.\nchild nodes: #{entity.node.nodes}."
      end
    else
      raise "Token #{token} is not a valid table nor column name."
    end
    token = next_token(tokens)
  end
  fields
end
process_filter(entity, tokens, aliases, values, root_entity) click to toggle source
# File lib/jungle_path/query/engine.rb, line 250
def process_filter(entity, tokens, aliases, values, root_entity)
  #puts "process_filter..."
  filter = []
  nest_count = 0
  expect_field = true
  expect_value = false
  expect_operator = false
  expect_list = false
  expect_connector = false
  expect_comma = false
  token = next_token(tokens)
  #puts "token: #{token}"
  while token
    if expect_operator and JunglePath::Query::Engine.operators.include? token
      expect_operator = false
      filter << JunglePath::Query::Operator.parse(token)
      if token == ">>" or token == "<<"
        expect_list = true
      else
        expect_value = true
      end
    elsif expect_operator
      raise "Expected token #{token} to be one of the following operators: #{Engine.operators.inspect}."
    elsif expect_field and token == "("
      nest_count += 1
      filter << token
    elsif expect_field and token == ")" and nest_count > 0
      nest_count -= 1
      filter << token
    elsif expect_field and token == ")"
      break
    elsif expect_value and token == 'null'
      # values << token #skip this token will use the literal null in filter.
      filter << "null"
      expect_value = false
      expect_field = true
      expect_connector = true
    elsif expect_value and token =~ /^[0-9]/ # starts with number
      if token.include? "."
        values << JunglePath::Query::FloatValue.parse(token)
        filter << "?"
      else
        values << JunglePath::Query::IntValue.parse(token)
        filter << "?"
      end
      expect_value = false
      expect_field = true
      expect_connector = true
    elsif expect_value and token =~ /^["']/ # starts with quote
      values << JunglePath::Query::StringValue.parse(token)
      filter << "?"
      expect_value = false
      expect_field = true
      expect_connector = true
    elsif expect_value and token.downcase == "true"
      values << true
      filter << "?"
      expect_value = false
      expect_field = true
      expect_connector = true
    elsif expect_value and token.downcase == "false"
      values << false
      filter << "?"
      expect_value = false
      expect_field = true
      expect_connector = true
    elsif expect_list and token == "("
      filter << "("
      filter << process_value_list(entity, tokens, aliases, root_entity, values)
      expect_list = false
      expect_field = true
    elsif expect_field and expect_connector and JunglePath::Query::Engine.connectors.include? token.downcase
      filter << token.downcase
      expect_connector = false
    elsif expect_field and Engine.connectors.include? token.downcase
      raise "Did not expect connector token #{token}."
    elsif expect_field and token =~ /^[a-zA-Z_]/ # starts with alpha or underscore
      node = get_entity_child_node(token, entity)
      if node and !node.child_table_name
        #filter << Field.new(name, :column, node)
        filter << "#{entity.alias_}.#{node.name}"
        expect_field = false
        expect_connector = false
        expect_operator = true
      else
        raise "Token #{token} is not a valid column name."
      end
    else
      raise "Token #{token} is not a valid column name. filter: #{filter.inspect}; tokens: #{tokens.inspect}."
    end
    token = next_token(tokens)
  end
  filter
end
process_limit(tokens) click to toggle source
# File lib/jungle_path/query/engine.rb, line 157
def process_limit(tokens)
  limit = nil
  limit_contents = []
  token = next_token(tokens)
  while token
    if token == "]"
      limit = JunglePath::Query::Limit.parse(limit_contents.join(' '))
      break
    else
      limit_contents << token
    end
    token = next_token(tokens)
  end
  limit
end
process_parameters(tokens) click to toggle source
# File lib/jungle_path/query/engine.rb, line 173
def process_parameters(tokens)
  parameters = []
  token = next_token(tokens)
  while token
    if token == "]"
      break
    elsif token == ','
      # ignore a comma
    elsif token == 'null'
      parameters << nil
    elsif token =~ /^[0-9]/ # starts with number
      if token.include? "."
        parameters << JunglePath::Query::FloatValue.parse(token)
      else
        parameters << JunglePath::Query::IntValue.parse(token)
      end
    elsif token =~ /^["']/ # starts with quote
      parameters << JunglePath::Query::StringValue.parse(token)
    elsif token.downcase == "true"
      parameters << true
    elsif token.downcase == "false"
      parameters << false
    else
      raise "unexpected token: #{token}"
    end
    token = next_token(tokens)
  end
  parameters
end
process_sort(entity, tokens, aliases, root_entity) click to toggle source
# File lib/jungle_path/query/engine.rb, line 390
def process_sort(entity, tokens, aliases, root_entity)
  sort = []
  sort_field = nil
  expect_sort = false
  token = next_token(tokens)
  while token
    if token == ")"
      break
    elsif token == ","
      #do nothing
      #sort << ", "
    elsif expect_sort and JunglePath::Query::Engine.sorts.include? token.downcase
      expect_sort = false
      sort[-1].sort = token.downcase
    elsif token =~ /^[a-zA-Z_]/ # starts with alpha or underscore
      parts = token.downcase.split('.')
      if parts.length > 1
        table = parts[0].to_sym
        field = parts[1].to_sym
      else
        table = nil
        field = parts[0].to_sym
      end
      table_entity = nil
      entities = nil
      if table
        table_entity, entities = find_table_entity(table, root_entity)
        raise "Token #{token} leading part #{table} is not a valid table from your query." unless table_entity
      end
      if field
        table_entity = entity unless table_entity
        node = table_entity.node.nodes[field]
        #puts "table_entity.name: #{table_entity.name}."
        #puts "table_entity.node.name: #{table_entity.node.name}."
        #puts "table_entity.node.nodes: #{table_entity.node.nodes}."
        #puts "pp node:"
        #pp node
        #puts "zzz"
        if node and node.child_table_name
          raise "Field #{field} is not a valid field for table #{table} in your query,"
        elsif node == nil and !@root.nodes[table_entity.name].nodes[field]
          raise "Field #{field} is not a valid field for table #{table_entity.name} in your query,"
        else
          #sort_field = SortField.new(table, field, table_entity.node, node)
          #sort << sort_field
          #sort << "#{table_entity.alias_}.#{field}"
          sort << JunglePath::Query::SortField.new("#{table_entity.alias_}.#{field}", field, entities)
          expect_sort = true
        end
      end
    else
      raise "Token #{token} is not a valid table nor column name (or combination)."
    end
    token = next_token(tokens)
  end
  sort
end
process_value_list(entity, tokens, aliases, root_entity, values) click to toggle source
# File lib/jungle_path/query/engine.rb, line 345
def process_value_list(entity, tokens, aliases, root_entity, values)
    list = []
    expect_value = true
    expect_comma = false
    token = next_token(tokens)
    while token
      #puts "token: #{token}."
      #puts "list: #{list.inspect}."
      if list.empty? and token == ")"
        list << ")"
        break
      elsif expect_comma and token == ")"
        list << ")"
        break
      elsif expect_comma and token == ","
        list << ", "
        expect_comma = false
        expect_value =  true
      elsif expect_comma
        raise "Missing comma from 'in (list)'. list: #{list.inspect}; values: #{values.inspect}; tokens: #{tokens.inspect}."
      elsif expect_value and token == ")"
        raise "Found unexpected closing parenthisis: #{token} in value list following a comma: (,)."
      elsif expect_value and token =~ /^[0-9]/ # starts with number
        if token.include? "."
          values << JunglePath::Query::FloatValue.filter(token)
          list << "?"
        else
          values << JunglePath::Query::IntValue.parse(token)
          list << "?"
        end
        expect_value = false
        expect_comma = true
      elsif expect_value and token =~ /^["']/ # starts with quote
        values = JunglePath::Query::StringValue.parse(token)
        list << "?"
        expect_value = false
        expect_comma = true
      else
        raise "Found unexpected token: #{token} in value list."
      end
      token = next_token(tokens)
    end
    list
end
tokenize(query) click to toggle source
# File lib/jungle_path/query/engine.rb, line 513
def tokenize(query)
  tokens = []
  in_single_line_comment = nil
  in_comment = 0
  in_string = nil
  in_escape = false
  parens = 0
  in_bracket = false
  token = ""
  i = 0
  prev_char = nil
  next_char = nil
  query.each_char do |c|
    i += 1
    next_char = query[i]
    #puts "i: #{i}, c: #{c}, tokens: #{tokens}"
    if in_escape and c == "\\"
      in_escape = false
      token << "\\"
    elsif in_escape and c == "t"
      in_escape = false
      token << "\t"
    elsif in_escape and c == "n"
      in_escape = false
      token << "\n"
    elsif in_escape and c == "r"
      in_escape = false
      token << "\r"
    elsif in_escape and c == "\'"
      in_escape = false
      token << "\'"
    elsif in_escape and c == "\""
      in_escape = false
      token << "\""
    elsif in_escape
      raise "Invalid character #{c} was escaped. You can only escape as follows: \\\\ \\t \\n \\r \\\' \\\" got it? :)"
    elsif in_string and c.strip.empty? # is whitespace?
      token << c
    elsif in_string and in_string == c
      token = token << c
      tokens << token
      token = ""
      in_string = nil
    elsif in_string and c == "\\"
      in_escape = true
    elsif in_string
      token << c
    elsif in_comment > 0 and c == "*"
      #do nothing
    elsif in_comment > 0 and prev_char == "*" and c == "/"
      in_comment -= 1
    elsif in_single_line_comment and (c == "\n" or c == "\r")
      in_single_line_comment = false
    elsif in_single_line_comment
      #do nothing
    elsif c == "/" and next_char == "*"
      in_comment += 1
    elsif in_comment > 0
      #do nothing
    elsif c == "-" and next_char == "-"
      if token.length > 0
        tokens << token
        token = ""
      end
      in_single_line_comment = true
    #elsif in_bracket and c == "]"
    #  in_bracket = false
    #  token << c
    #  tokens << token
    #  token = ""
    #elsif in_bracket
    #     token << c
    elsif c == "\""
      if token.length > 0
        tokens << token
        token = ""
      end
      in_string = c
      token << c
    elsif c == "'"
      if token.length > 0
        tokens << token
        token = ""
      end
      in_string = c
      token << c
    elsif c.strip.empty? # is whitespace?
      if token.length > 0
        tokens << token
        token = ""
      end
    elsif c == ","
      if token.length > 0
        tokens << token
        token = ""
      end
      tokens << ","
    elsif c == "("
      parens += 1
      if token.length > 0
        tokens << token
        token = ""
      end
      tokens << "("
    elsif c == ")"
      parens -= 1
      if parens < 0
        raise "Extra ')' character found at #{i}."
      end
      if token.length > 0
        tokens << token
        token = ""
      end
      tokens << ")"
    elsif c == "["
      in_bracket = true
      if token.length > 0
        tokens << token
        token = ""
      end
      #token << c
      tokens << "["
    elsif in_bracket and c == "]"
      in_bracket = false
      if token.length > 0
        tokens << token
        token = ""
      end
      tokens << "]"
    else
      token << c
    end
    prev_char = c
  end
  if in_bracket
    raise "No closing ']' character was found."
  end
  if parens > 0
    raise "Missing #{parens} ')' characters."
  end
  if in_string
    raise "Unclosed string: missing #{in_string} character."
  end
  if token.length > 0
    tokens << token
    token = ""
  end
  tokens
end