Class: DataMapper::Adapters::AppEngineAdapter::QueryBuilder

Inherits:
Object
  • Object
show all
Includes:
Query::Conditions, Datastore::Query::Constants
Defined in:
lib/appengine_adapter.rb

Constant Summary collapse

@@OPERATORS =
{
EqualToComparison => EQUAL,
GreaterThanComparison => GREATER_THAN,
GreaterThanOrEqualToComparison => GREATER_THAN_OR_EQUAL,
LessThanComparison => LESS_THAN,
LessThanOrEqualToComparison => LESS_THAN_OR_EQUAL,
}.freeze
@@NEGATED_OPERATORS =
{
  EqualToComparison => NOT_EQUAL,
  GreaterThanComparison => LESS_THAN_OR_EQUAL,
  GreaterThanOrEqualToComparison => LESS_THAN,
  LessThanComparison => GREATER_THAN_OR_EQUAL,
  LessThanOrEqualToComparison => GREATER_THAN,
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(query, kind, adapter) ⇒ QueryBuilder

Returns a new instance of QueryBuilder.

Raises:

  • (NotImplementedError)


191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/appengine_adapter.rb', line 191

def initialize(query, kind, adapter)
  @model = query.model
  @kind = kind
  @limit = query.limit
  @offset = query.offset
  @maybe_get = true
  @must_be_get = false
  @keys = []
  @dm_query = query
  @adapter = adapter

  @query = Datastore::Query.new(kind)
  parse_order(query.order)
  parse_conditions(query.conditions)
  raise NotImplementedError if @must_be_get && !@maybe_get
end

Instance Method Details

#entity_to_hash(key_prop, entity) ⇒ Object



426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/appengine_adapter.rb', line 426

def entity_to_hash(key_prop, entity)
  # TODO: This is broken. We should be setting all properties
  return if entity.nil?
  key = entity.get_key
  hash = {}
  @dm_query.fields.each do |property|
    name = property.field
    if property.key?
      hash[name] = if property.kind_of? Property::Key
        key
      elsif property.serial?
        key.get_id
      elsif property.kind_of? Property::String
        key.get_name
      else
        key
      end
    else
      value = entity.get_property(name)
      if property.kind_of?(Property::Decimal) && value
        value = Lexidecimal.string_to_decimal(entity.get_property(name))
      end
      hash[name] = value
    end
  end
  hash
end

#get_entitiesObject



395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
# File 'lib/appengine_adapter.rb', line 395

def get_entities
  if is_get?
    Datastore.get(@keys)
  else
    begin
      chunk_size = FetchOptions::DEFAULT_CHUNK_SIZE
      options = FetchOptions::Builder.with_chunk_size(
          chunk_size)
      options.limit(@limit) if @limit
      options.offset(@offset) if @offset
      @query.iterator(options).collect {|e| e}
    rescue java.lang.IllegalArgumentException => ex
      raise ArgumentError, ex.message
    end
  end
end

#is_get?Boolean

Returns:

  • (Boolean)


391
392
393
# File 'lib/appengine_adapter.rb', line 391

def is_get?
  @maybe_get && @keys.size > 0
end

#keysObject



454
455
456
# File 'lib/appengine_adapter.rb', line 454

def keys
  @keys
end

#parse_and(op) ⇒ Object



304
305
306
307
308
309
310
311
312
# File 'lib/appengine_adapter.rb', line 304

def parse_and(op)
  if @maybe_get && (@found_and || op.operands.size > 1)
    @maybe_get = false
  end
  @found_and = true
  op.each do |conditions|
    parse_conditions(conditions)
  end
end

#parse_comparison(op) ⇒ Object



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/appengine_adapter.rb', line 314

def parse_comparison(op)
  if op.respond_to?(:relationship?) && op.relationship?
    return parse_conditions(op.foreign_key_mapping)
  elsif (respond_to?(:foreign_key_conditions) &&
         op.subject.kind_of?(Associations::Relationship))
    return parse_conditions(foreign_key_conditions(op))
  end
  property = op.subject
  value = op.value
  negated = op.negated?
  if @maybe_get
    if property.key? && !negated
      case op
      when EqualToComparison 
        @keys << parse_key(property, value)
      when InclusionComparison
        parse_key_inclusion(op)
        @must_be_get = true
        return
      else
        @maybe_get = false
      end
    else
      @maybe_get = false
    end
  end

  if op.kind_of?(InclusionComparison)
    parse_inclusion(op)
  elsif property.kind_of?(Property::AncestorKey)
    @query.ancestor = value
  else
    if negated
      filter_op = @@NEGATED_OPERATORS[op.class]
    else
      filter_op = @@OPERATORS[op.class]
    end
    if filter_op.nil?
      raise ArgumentError, "#{op.class} is not a supported comparison"
    end
    name = property_name(op.subject)
    value = property_value(op.subject, op.value)
    @query.filter(name, filter_op, value)
  end
end

#parse_conditions(conditions) ⇒ Object



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/appengine_adapter.rb', line 247

def parse_conditions(conditions)
  case conditions
  when NullOperation then
    return
  when NotOperation then
    if OrOperation === conditions.operand
      parse_and(conditions.operand)
    elsif AbstractComparison === conditions.operand
      parse_comparison(conditions.operand)
    else
      raise NotImplementedError, "NOT operator is not supported with #{conditions.operand.class.name}"
    end
  when AbstractComparison then
    parse_comparison(conditions)
  when OrOperation  then
    parse_or(conditions)
  when AndOperation then
    parse_and(conditions)
  else
    raise ArgumentError, "invalid conditions #{conditions.class}: #{conditions.inspect}"
  end
end

#parse_inclusion(op) ⇒ Object



360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/appengine_adapter.rb', line 360

def parse_inclusion(op)
  if Range === op.value
    parse_range(op)
  else
    name = property_name(op.subject)
    values = op.value.map {|x| property_value(op.subject, x)}
    if op.negated?
      values.each do |value|
        @query.filter(name, NOT_EQUAL, value)
      end
    else
      @query.filter(name, IN, java.util.ArrayList.new(values))
    end
  end
end

#parse_key(property, value) ⇒ Object



270
271
272
273
274
275
# File 'lib/appengine_adapter.rb', line 270

def parse_key(property, value)
  unless property.key?
    raise ArgumentError, "#{property_name(property)} is not the key"
  end
  Property::Key.typecast_to_primitive(property, value)
end

#parse_key_inclusion(op) ⇒ Object

Raises:

  • (NotImplementedError)


297
298
299
300
301
302
# File 'lib/appengine_adapter.rb', line 297

def parse_key_inclusion(op)
  raise NotImplementedError unless op.value.kind_of? Array
  op.value.each do |value|
    @keys << parse_key(op.subject, value)
  end
end

#parse_or(or_op) ⇒ Object



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/appengine_adapter.rb', line 277

def parse_or(or_op)
  if !@maybe_get
    raise NotImplementedError, "OR only supported with key equality comparisons"
  end
  @must_be_get = true
  or_op.each do |op|
    case op
      when OrOperation  then
        parse_or(op)
      when EqualToComparison then
        key = parse_key(op.subject, op.value)
        @keys << key
      when InclusionComparison then
        parse_key_inclusion(op)
      else
        raise NotImplementedError, "Unsupported condition #{op.class} inside OR"
    end
  end
end

#parse_order(order) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/appengine_adapter.rb', line 224

def parse_order(order)
  if order.size == 1 && order[0].operator != :desc
    if order[0].target.key?
      # omit the default key ordering.
      # This lets inequality filters work
      return
    end
  end
  order.map do |order|
    if order.operator == :desc
      direction = DESCENDING
    else
      direction = ASCENDING
    end
    name = if order.target.key?
      '__key__'
    else
      property_name(order.target)
    end
    @query.sort(name, direction)
  end
end

#parse_range(op) ⇒ Object

Raises:

  • (NotImplementedError)


376
377
378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/appengine_adapter.rb', line 376

def parse_range(op)
  range = op.value
  raise NotImplementedError unless range.is_a? Range
  raise NotImplementedError if op.negated?
  name = property_name(op.subject)
  begin_op = GREATER_THAN_OR_EQUAL
  end_op = if range.exclude_end?
    LESS_THAN
  else
    LESS_THAN_OR_EQUAL
  end
  @query.filter(name, begin_op, range.begin)
  @query.filter(name, end_op, range.end)
end

#property_name(property) ⇒ Object



208
209
210
211
212
213
214
# File 'lib/appengine_adapter.rb', line 208

def property_name(property)
  if property.key?
    '__key__'
  else
    property.field
  end
end

#property_value(property, value) ⇒ Object



216
217
218
219
220
221
222
# File 'lib/appengine_adapter.rb', line 216

def property_value(property, value)
  if property.key?
    parse_key(property, value)
  else
    @adapter.convert_value(property, value)
  end
end

#runObject



412
413
414
415
416
417
418
419
420
421
422
423
424
# File 'lib/appengine_adapter.rb', line 412

def run
  key_prop = @model.key(@adapter.name).first.field
  entities = get_entities
  hashes = entities.map do |entity|
    entity_to_hash(key_prop, entity)
  end
  hashes.compact!
  resources = hashes.empty? ? hashes : @model.load(hashes, @dm_query)
  resources.zip(entities) do |resource, entity|
    resource.instance_variable_set :@__entity__, entity
  end
  resources
end