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.



135
136
137
# File 'lib/mongo_record/sql.rb', line 135

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.



131
132
133
# File 'lib/mongo_record/sql.rb', line 131

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.



158
159
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
# File 'lib/mongo_record/sql.rb', line 158

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.



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

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