Class: MongoRecord::SQL::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/mongo_record/sql.rb

Overview

Only parses simple WHERE clauses right now. The parser returns a query Hash suitable for use by Mongo.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tokenizer) ⇒ Parser

Returns a new instance of Parser.



137
138
139
# File 'lib/mongo_record/sql.rb', line 137

def initialize(tokenizer)
  @tokenizer = tokenizer
end

Class Method Details

.parse_where(sql, remove_table_names = false) ⇒ Object

Parse a WHERE clause (without the “WHERE”) ane return a query Hash suitable for use by Mongo.



133
134
135
# File 'lib/mongo_record/sql.rb', line 133

def self.parse_where(sql, remove_table_names=false)
  Parser.new(Tokenizer.new(sql)).parse_where(remove_table_names)
end

Instance Method Details

#parse_where(remove_table_names = false) ⇒ Object

Parse a WHERE clause (without the “WHERE”) and return a query Hash suitable for use by Mongo.



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/mongo_record/sql.rb', line 160

def parse_where(remove_table_names=false)
  filters = {}
  done = false
  while !done && @tokenizer.more?
    name = @tokenizer.next_token
    raise "sql parser can't handle nested stuff yet: #{@tokenizer.sql}" if name == '('
    name.sub!(/.*\./, '') if remove_table_names # Remove "schema.table." from "schema.table.col"

    op = @tokenizer.next_token
    op += (' ' + @tokenizer.next_token) if op.downcase == 'not'
    op = op.downcase

    val = @tokenizer.next_token

    case op
    when "="
      filters[name] = val
    when "<"
      filters[name] = { :$lt => val }
    when "<="
      filters[name] = { :$lte => val }
    when ">"
      filters[name] = { :$gt => val }
    when ">="
      filters[name] = { :$gte  => val }
    when "<>", "!="
      filters[name] = { :$ne => val }
    when "like"
      filters[name] = regexp_from_string(val)
    when "in"
      raise "'in' must be followed by a list of values: #{@tokenizer.sql}" unless val == '('
      filters[name] = { :$in => read_array }
    when "between"
      conjunction = @tokenizer.next_token.downcase
      raise "syntax error: expected 'between X and Y', but saw '" + conjunction + "' instead of 'and'" unless conjunction == 'and'
      val2 = @tokenizer.next_token
      val2, val = val, val2 if val > val2 # Make sure val <= val2
      filters[name] = { :$gte => val, :$lte => val2 }
    else
      raise "can't handle sql operator [#{op}] yet: #{@tokenizer.sql}"
    end

    break unless @tokenizer.more?

    tok = @tokenizer.next_token.downcase
    case tok
    when 'and'
      next
    when 'or'
        raise "sql parser can't handle ors yet: #{@tokenizer.sql}"
    when 'order', 'group', 'limit'
      @tokenizer.add_extra_token(tok)
      done = true
    else
      raise "can't handle [#{tok}] yet"
    end
  end
  filters
end

#regexp_from_string(str) ⇒ Object

Given a regexp string like ‘%foo%’, return a Regexp object. We set Regexp::IGNORECASE so that all regex matches are case-insensitive.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/mongo_record/sql.rb', line 143

def regexp_from_string(str)
  if str[0,1] == '%'
    str = str[1..-1]
  else
    str = '^' + str
  end

  if str[-1,1] == '%'
    str = str[0..-2]
  else
    str = str + '$'
  end
  Regexp.new(str, Regexp::IGNORECASE)
end