dynamoid 3.9.0 → 3.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -6
- data/README.md +202 -25
- data/dynamoid.gemspec +5 -6
- data/lib/dynamoid/adapter.rb +19 -13
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +2 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/filter_expression_convertor.rb +113 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +21 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/projection_expression_convertor.rb +40 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +46 -61
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +34 -28
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/transact.rb +31 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +95 -66
- data/lib/dynamoid/associations/belongs_to.rb +6 -6
- data/lib/dynamoid/associations.rb +1 -1
- data/lib/dynamoid/components.rb +1 -0
- data/lib/dynamoid/config/options.rb +12 -12
- data/lib/dynamoid/config.rb +3 -0
- data/lib/dynamoid/criteria/chain.rb +149 -142
- data/lib/dynamoid/criteria/key_fields_detector.rb +6 -7
- data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +2 -2
- data/lib/dynamoid/criteria/where_conditions.rb +36 -0
- data/lib/dynamoid/dirty.rb +87 -12
- data/lib/dynamoid/document.rb +1 -1
- data/lib/dynamoid/dumping.rb +38 -16
- data/lib/dynamoid/errors.rb +14 -2
- data/lib/dynamoid/fields/declare.rb +6 -6
- data/lib/dynamoid/fields.rb +6 -8
- data/lib/dynamoid/finders.rb +23 -32
- data/lib/dynamoid/indexes.rb +6 -7
- data/lib/dynamoid/loadable.rb +3 -2
- data/lib/dynamoid/persistence/inc.rb +6 -7
- data/lib/dynamoid/persistence/item_updater_with_casting_and_dumping.rb +36 -0
- data/lib/dynamoid/persistence/item_updater_with_dumping.rb +33 -0
- data/lib/dynamoid/persistence/save.rb +17 -18
- data/lib/dynamoid/persistence/update_fields.rb +7 -5
- data/lib/dynamoid/persistence/update_validations.rb +1 -1
- data/lib/dynamoid/persistence/upsert.rb +5 -4
- data/lib/dynamoid/persistence.rb +77 -21
- data/lib/dynamoid/transaction_write/base.rb +47 -0
- data/lib/dynamoid/transaction_write/create.rb +49 -0
- data/lib/dynamoid/transaction_write/delete_with_instance.rb +60 -0
- data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +59 -0
- data/lib/dynamoid/transaction_write/destroy.rb +79 -0
- data/lib/dynamoid/transaction_write/save.rb +164 -0
- data/lib/dynamoid/transaction_write/update_attributes.rb +46 -0
- data/lib/dynamoid/transaction_write/update_fields.rb +102 -0
- data/lib/dynamoid/transaction_write/upsert.rb +96 -0
- data/lib/dynamoid/transaction_write.rb +464 -0
- data/lib/dynamoid/type_casting.rb +18 -15
- data/lib/dynamoid/undumping.rb +14 -3
- data/lib/dynamoid/validations.rb +1 -1
- data/lib/dynamoid/version.rb +1 -1
- data/lib/dynamoid.rb +7 -0
- metadata +30 -16
- data/lib/dynamoid/criteria/ignored_conditions_detector.rb +0 -41
- data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +0 -40
data/lib/dynamoid/config.rb
CHANGED
@@ -48,7 +48,9 @@ module Dynamoid
|
|
48
48
|
option :dynamodb_timezone, default: :utc # available values - :utc, :local, time zone name like "Hawaii"
|
49
49
|
option :store_datetime_as_string, default: false # store Time fields in ISO 8601 string format
|
50
50
|
option :store_date_as_string, default: false # store Date fields in ISO 8601 string format
|
51
|
+
option :store_empty_string_as_nil, default: true # store attribute's empty String value as null
|
51
52
|
option :store_boolean_as_native, default: true
|
53
|
+
option :store_binary_as_native, default: false
|
52
54
|
option :backoff, default: nil # callable object to handle exceeding of table throughput limit
|
53
55
|
option :backoff_strategies, default: {
|
54
56
|
constant: BackoffStrategies::ConstantBackoff,
|
@@ -59,6 +61,7 @@ module Dynamoid
|
|
59
61
|
option :http_idle_timeout, default: nil # - default: 5
|
60
62
|
option :http_open_timeout, default: nil # - default: 15
|
61
63
|
option :http_read_timeout, default: nil # - default: 60
|
64
|
+
option :create_table_on_save, default: true
|
62
65
|
|
63
66
|
# The default logger for Dynamoid: either the Rails logger or just stdout.
|
64
67
|
#
|
@@ -1,29 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'key_fields_detector'
|
4
|
-
require_relative 'ignored_conditions_detector'
|
5
|
-
require_relative 'overwritten_conditions_detector'
|
6
4
|
require_relative 'nonexistent_fields_detector'
|
5
|
+
require_relative 'where_conditions'
|
7
6
|
|
8
7
|
module Dynamoid
|
9
8
|
module Criteria
|
10
9
|
# The criteria chain is equivalent to an ActiveRecord relation (and realistically I should change the name from
|
11
10
|
# chain to relation). It is a chainable object that builds up a query and eventually executes it by a Query or Scan.
|
12
11
|
class Chain
|
13
|
-
attr_reader :
|
12
|
+
attr_reader :source, :consistent_read, :key_fields_detector
|
14
13
|
|
15
14
|
include Enumerable
|
15
|
+
|
16
|
+
ALLOWED_FIELD_OPERATORS = Set.new(
|
17
|
+
%w[
|
18
|
+
eq ne gt lt gte lte between begins_with in contains not_contains null not_null
|
19
|
+
]
|
20
|
+
).freeze
|
21
|
+
|
16
22
|
# Create a new criteria chain.
|
17
23
|
#
|
18
24
|
# @param [Class] source the class upon which the ultimate query will be performed.
|
19
25
|
def initialize(source)
|
20
|
-
@
|
26
|
+
@where_conditions = WhereConditions.new
|
21
27
|
@source = source
|
22
28
|
@consistent_read = false
|
23
29
|
@scan_index_forward = true
|
24
30
|
|
25
|
-
# we should re-initialize keys detector every time we change
|
26
|
-
@key_fields_detector = KeyFieldsDetector.new(@
|
31
|
+
# we should re-initialize keys detector every time we change @where_conditions
|
32
|
+
@key_fields_detector = KeyFieldsDetector.new(@where_conditions, @source)
|
27
33
|
end
|
28
34
|
|
29
35
|
# Returns a chain which is a result of filtering current chain with the specified conditions.
|
@@ -89,30 +95,27 @@ module Dynamoid
|
|
89
95
|
#
|
90
96
|
# Internally +where+ performs either +Scan+ or +Query+ operation.
|
91
97
|
#
|
98
|
+
# Conditions can be specified as an expression as well:
|
99
|
+
#
|
100
|
+
# Post.where('links_count = :v', v: 2)
|
101
|
+
#
|
102
|
+
# This way complex expressions can be constructed (e.g. with AND, OR, and NOT
|
103
|
+
# keyword):
|
104
|
+
#
|
105
|
+
# Address.where('city = :c AND (post_code = :pc1 OR post_code = :pc2)', city: 'A', pc1: '001', pc2: '002')
|
106
|
+
#
|
107
|
+
# See documentation for condition expression's syntax and examples:
|
108
|
+
# - https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html
|
109
|
+
# - https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.FilterExpression.html
|
110
|
+
#
|
92
111
|
# @return [Dynamoid::Criteria::Chain]
|
93
112
|
# @since 0.2.0
|
94
|
-
def where(
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
detector = OverwrittenConditionsDetector.new(@query, args)
|
101
|
-
if detector.found?
|
102
|
-
Dynamoid.logger.warn(detector.warning_message)
|
103
|
-
end
|
104
|
-
|
105
|
-
detector = NonexistentFieldsDetector.new(args, @source)
|
106
|
-
if detector.found?
|
107
|
-
Dynamoid.logger.warn(detector.warning_message)
|
113
|
+
def where(conditions, placeholders = nil)
|
114
|
+
if conditions.is_a?(Hash)
|
115
|
+
where_with_hash(conditions)
|
116
|
+
else
|
117
|
+
where_with_string(conditions, placeholders)
|
108
118
|
end
|
109
|
-
|
110
|
-
query.update(args.symbolize_keys)
|
111
|
-
|
112
|
-
# we should re-initialize keys detector every time we change query
|
113
|
-
@key_fields_detector = KeyFieldsDetector.new(@query, @source, forced_index_name: @forced_index_name)
|
114
|
-
|
115
|
-
self
|
116
119
|
end
|
117
120
|
|
118
121
|
# Turns on strongly consistent reads.
|
@@ -187,7 +190,7 @@ module Dynamoid
|
|
187
190
|
def first(*args)
|
188
191
|
n = args.first || 1
|
189
192
|
|
190
|
-
return dup.scan_limit(n).to_a.first(*args) if @
|
193
|
+
return dup.scan_limit(n).to_a.first(*args) if @where_conditions.empty?
|
191
194
|
return super if @key_fields_detector.non_key_present?
|
192
195
|
|
193
196
|
dup.record_limit(n).to_a.first(*args)
|
@@ -230,12 +233,12 @@ module Dynamoid
|
|
230
233
|
ranges = []
|
231
234
|
|
232
235
|
if @key_fields_detector.key_present?
|
233
|
-
Dynamoid.adapter.query(source.table_name,
|
236
|
+
Dynamoid.adapter.query(source.table_name, query_key_conditions, query_non_key_conditions, query_options).flat_map { |i| i }.collect do |hash|
|
234
237
|
ids << hash[source.hash_key.to_sym]
|
235
238
|
ranges << hash[source.range_key.to_sym] if source.range_key
|
236
239
|
end
|
237
240
|
else
|
238
|
-
Dynamoid.adapter.scan(source.table_name,
|
241
|
+
Dynamoid.adapter.scan(source.table_name, scan_conditions, scan_options).flat_map { |i| i }.collect do |hash|
|
239
242
|
ids << hash[source.hash_key.to_sym]
|
240
243
|
ranges << hash[source.range_key.to_sym] if source.range_key
|
241
244
|
end
|
@@ -384,7 +387,7 @@ module Dynamoid
|
|
384
387
|
raise Dynamoid::Errors::InvalidIndex, "Unknown index #{index_name}" unless @source.find_index_by_name(index_name)
|
385
388
|
|
386
389
|
@forced_index_name = index_name
|
387
|
-
@key_fields_detector = KeyFieldsDetector.new(@
|
390
|
+
@key_fields_detector = KeyFieldsDetector.new(@where_conditions, @source, forced_index_name: index_name)
|
388
391
|
self
|
389
392
|
end
|
390
393
|
|
@@ -451,9 +454,9 @@ module Dynamoid
|
|
451
454
|
# It takes one or more field names and returns a collection of models with only
|
452
455
|
# these fields set.
|
453
456
|
#
|
454
|
-
# Post.where('views_count.gt' => 1000).
|
455
|
-
# Post.where('views_count.gt' => 1000).
|
456
|
-
# Post.
|
457
|
+
# Post.where('views_count.gt' => 1000).project(:title)
|
458
|
+
# Post.where('views_count.gt' => 1000).project(:title, :created_at)
|
459
|
+
# Post.project(:id)
|
457
460
|
#
|
458
461
|
# It can be used to avoid loading large field values and to decrease a
|
459
462
|
# memory footprint.
|
@@ -487,6 +490,8 @@ module Dynamoid
|
|
487
490
|
def pluck(*args)
|
488
491
|
fields = args.map(&:to_sym)
|
489
492
|
|
493
|
+
# `project` has a side effect - it sets `@project` instance variable.
|
494
|
+
# So use a duplicate to not pollute original chain.
|
490
495
|
scope = dup
|
491
496
|
scope.project(*fields)
|
492
497
|
|
@@ -502,6 +507,29 @@ module Dynamoid
|
|
502
507
|
|
503
508
|
private
|
504
509
|
|
510
|
+
def where_with_hash(conditions)
|
511
|
+
detector = NonexistentFieldsDetector.new(conditions, @source)
|
512
|
+
if detector.found?
|
513
|
+
Dynamoid.logger.warn(detector.warning_message)
|
514
|
+
end
|
515
|
+
|
516
|
+
@where_conditions.update_with_hash(conditions.symbolize_keys)
|
517
|
+
|
518
|
+
# we should re-initialize keys detector every time we change @where_conditions
|
519
|
+
@key_fields_detector = KeyFieldsDetector.new(@where_conditions, @source, forced_index_name: @forced_index_name)
|
520
|
+
|
521
|
+
self
|
522
|
+
end
|
523
|
+
|
524
|
+
def where_with_string(query, placeholders)
|
525
|
+
@where_conditions.update_with_string(query, placeholders)
|
526
|
+
|
527
|
+
# we should re-initialize keys detector every time we change @where_conditions
|
528
|
+
@key_fields_detector = KeyFieldsDetector.new(@where_conditions, @source, forced_index_name: @forced_index_name)
|
529
|
+
|
530
|
+
self
|
531
|
+
end
|
532
|
+
|
505
533
|
# The actual records referenced by the association.
|
506
534
|
#
|
507
535
|
# @return [Enumerator] an iterator of the found records.
|
@@ -535,7 +563,7 @@ module Dynamoid
|
|
535
563
|
if @key_fields_detector.key_present?
|
536
564
|
raw_pages_via_query
|
537
565
|
else
|
538
|
-
issue_scan_warning if Dynamoid::Config.warn_on_scan &&
|
566
|
+
issue_scan_warning if Dynamoid::Config.warn_on_scan && !@where_conditions.empty?
|
539
567
|
raw_pages_via_scan
|
540
568
|
end
|
541
569
|
end
|
@@ -547,7 +575,7 @@ module Dynamoid
|
|
547
575
|
# @since 3.1.0
|
548
576
|
def raw_pages_via_query
|
549
577
|
Enumerator.new do |y|
|
550
|
-
Dynamoid.adapter.query(source.table_name,
|
578
|
+
Dynamoid.adapter.query(source.table_name, query_key_conditions, query_non_key_conditions, query_options).each do |items, metadata|
|
551
579
|
options = metadata.slice(:last_evaluated_key)
|
552
580
|
|
553
581
|
y.yield items, options
|
@@ -562,7 +590,7 @@ module Dynamoid
|
|
562
590
|
# @since 3.1.0
|
563
591
|
def raw_pages_via_scan
|
564
592
|
Enumerator.new do |y|
|
565
|
-
Dynamoid.adapter.scan(source.table_name,
|
593
|
+
Dynamoid.adapter.scan(source.table_name, scan_conditions, scan_options).each do |items, metadata|
|
566
594
|
options = metadata.slice(:last_evaluated_key)
|
567
595
|
|
568
596
|
y.yield items, options
|
@@ -575,121 +603,94 @@ module Dynamoid
|
|
575
603
|
Dynamoid.logger.warn "You can index this query by adding index declaration to #{source.to_s.underscore}.rb:"
|
576
604
|
Dynamoid.logger.warn "* global_secondary_index hash_key: 'some-name', range_key: 'some-another-name'"
|
577
605
|
Dynamoid.logger.warn "* local_secondary_index range_key: 'some-name'"
|
578
|
-
Dynamoid.logger.warn "Not indexed attributes: #{
|
606
|
+
Dynamoid.logger.warn "Not indexed attributes: #{@where_conditions.keys.sort.collect { |name| ":#{name}" }.join(', ')}"
|
579
607
|
end
|
580
608
|
|
581
609
|
def count_via_query
|
582
|
-
Dynamoid.adapter.query_count(source.table_name,
|
610
|
+
Dynamoid.adapter.query_count(source.table_name, query_key_conditions, query_non_key_conditions, query_options)
|
583
611
|
end
|
584
612
|
|
585
613
|
def count_via_scan
|
586
|
-
Dynamoid.adapter.scan_count(source.table_name,
|
587
|
-
end
|
588
|
-
|
589
|
-
def range_hash(key)
|
590
|
-
name, operation = key.to_s.split('.')
|
591
|
-
val = type_cast_condition_parameter(name, query[key])
|
592
|
-
|
593
|
-
case operation
|
594
|
-
when 'gt'
|
595
|
-
{ range_greater_than: val }
|
596
|
-
when 'lt'
|
597
|
-
{ range_less_than: val }
|
598
|
-
when 'gte'
|
599
|
-
{ range_gte: val }
|
600
|
-
when 'lte'
|
601
|
-
{ range_lte: val }
|
602
|
-
when 'between'
|
603
|
-
{ range_between: val }
|
604
|
-
when 'begins_with'
|
605
|
-
{ range_begins_with: val }
|
606
|
-
end
|
614
|
+
Dynamoid.adapter.scan_count(source.table_name, scan_conditions, scan_options)
|
607
615
|
end
|
608
616
|
|
609
|
-
def
|
610
|
-
name,
|
611
|
-
|
612
|
-
|
613
|
-
hash = case operation
|
614
|
-
when 'ne'
|
615
|
-
{ ne: val }
|
616
|
-
when 'gt'
|
617
|
-
{ gt: val }
|
618
|
-
when 'lt'
|
619
|
-
{ lt: val }
|
620
|
-
when 'gte'
|
621
|
-
{ gte: val }
|
622
|
-
when 'lte'
|
623
|
-
{ lte: val }
|
624
|
-
when 'between'
|
625
|
-
{ between: val }
|
626
|
-
when 'begins_with'
|
627
|
-
{ begins_with: val }
|
628
|
-
when 'in'
|
629
|
-
{ in: val }
|
630
|
-
when 'contains'
|
631
|
-
{ contains: val }
|
632
|
-
when 'not_contains'
|
633
|
-
{ not_contains: val }
|
634
|
-
# NULL/NOT_NULL operators don't have parameters
|
635
|
-
# So { null: true } means NULL check and { null: false } means NOT_NULL one
|
636
|
-
# The same logic is used for { not_null: BOOL }
|
637
|
-
when 'null'
|
638
|
-
val ? { null: nil } : { not_null: nil }
|
639
|
-
when 'not_null'
|
640
|
-
val ? { not_null: nil } : { null: nil }
|
641
|
-
end
|
642
|
-
|
643
|
-
{ name.to_sym => hash }
|
644
|
-
end
|
645
|
-
|
646
|
-
def consistent_opts
|
647
|
-
{ consistent_read: consistent_read }
|
648
|
-
end
|
649
|
-
|
650
|
-
def range_query
|
651
|
-
opts = {}
|
652
|
-
query = self.query
|
617
|
+
def field_condition(key, value_before_type_casting)
|
618
|
+
name, operator = key.to_s.split('.')
|
619
|
+
value = type_cast_condition_parameter(name, value_before_type_casting)
|
620
|
+
operator ||= 'eq'
|
653
621
|
|
654
|
-
|
655
|
-
|
656
|
-
@key_fields_detector.hash_key.to_sym != @source.inheritance_field.to_sym
|
657
|
-
query.update(sti_condition)
|
622
|
+
unless operator.in? ALLOWED_FIELD_OPERATORS
|
623
|
+
raise Dynamoid::Errors::Error, "Unsupported operator #{operator} in #{key}"
|
658
624
|
end
|
659
625
|
|
626
|
+
condition =
|
627
|
+
case operator
|
628
|
+
# NULL/NOT_NULL operators don't have parameters
|
629
|
+
# So { null: true } means NULL check and { null: false } means NOT_NULL one
|
630
|
+
# The same logic is used for { not_null: BOOL }
|
631
|
+
when 'null'
|
632
|
+
value ? [:null, nil] : [:not_null, nil]
|
633
|
+
when 'not_null'
|
634
|
+
value ? [:not_null, nil] : [:null, nil]
|
635
|
+
else
|
636
|
+
[operator.to_sym, value]
|
637
|
+
end
|
638
|
+
|
639
|
+
[name.to_sym, condition]
|
640
|
+
end
|
641
|
+
|
642
|
+
def query_key_conditions
|
643
|
+
opts = {}
|
644
|
+
|
660
645
|
# Add hash key
|
661
|
-
|
662
|
-
|
646
|
+
# TODO: always have hash key in @where_conditions?
|
647
|
+
_, condition = field_condition(@key_fields_detector.hash_key, @where_conditions[@key_fields_detector.hash_key])
|
648
|
+
opts[@key_fields_detector.hash_key] = [condition]
|
663
649
|
|
664
650
|
# Add range key
|
665
651
|
if @key_fields_detector.range_key
|
666
|
-
|
667
|
-
|
652
|
+
if @where_conditions[@key_fields_detector.range_key].present?
|
653
|
+
_, condition = field_condition(@key_fields_detector.range_key, @where_conditions[@key_fields_detector.range_key])
|
654
|
+
opts[@key_fields_detector.range_key] = [condition]
|
655
|
+
end
|
668
656
|
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
opts.update(field_hash(key))
|
674
|
-
else
|
675
|
-
value = type_cast_condition_parameter(key, query[key])
|
676
|
-
opts[key] = { eq: value }
|
657
|
+
@where_conditions.keys.select { |k| k.to_s =~ /^#{@key_fields_detector.range_key}\./ }.each do |key|
|
658
|
+
name, condition = field_condition(key, @where_conditions[key])
|
659
|
+
opts[name] ||= []
|
660
|
+
opts[name] << condition
|
677
661
|
end
|
678
662
|
end
|
679
663
|
|
680
|
-
opts
|
664
|
+
opts
|
681
665
|
end
|
682
666
|
|
683
|
-
def
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
667
|
+
def query_non_key_conditions
|
668
|
+
hash_conditions = {}
|
669
|
+
|
670
|
+
# Honor STI and :type field if it presents
|
671
|
+
if @source.attributes.key?(@source.inheritance_field) &&
|
672
|
+
@key_fields_detector.hash_key.to_sym != @source.inheritance_field.to_sym
|
673
|
+
@where_conditions.update_with_hash(sti_condition)
|
674
|
+
end
|
675
|
+
|
676
|
+
# TODO: Separate key conditions and non-key conditions properly:
|
677
|
+
# only =, >, >=, <, <=, between and begins_with
|
678
|
+
# could be used for sort key in KeyConditionExpression
|
679
|
+
keys = (@where_conditions.keys.map(&:to_sym) - [@key_fields_detector.hash_key.to_sym, @key_fields_detector.range_key.try(:to_sym)])
|
680
|
+
.reject { |k, _| k.to_s =~ /^#{@key_fields_detector.range_key}\./ }
|
681
|
+
keys.each do |key|
|
682
|
+
name, condition = field_condition(key, @where_conditions[key])
|
683
|
+
hash_conditions[name] ||= []
|
684
|
+
hash_conditions[name] << condition
|
688
685
|
end
|
689
686
|
|
690
|
-
|
691
|
-
|
687
|
+
string_conditions = []
|
688
|
+
@where_conditions.string_conditions.each do |query, placeholders|
|
689
|
+
placeholders ||= {}
|
690
|
+
string_conditions << [query, placeholders]
|
692
691
|
end
|
692
|
+
|
693
|
+
[hash_conditions] + string_conditions
|
693
694
|
end
|
694
695
|
|
695
696
|
# TODO: casting should be operator aware
|
@@ -737,7 +738,7 @@ module Dynamoid
|
|
737
738
|
key
|
738
739
|
end
|
739
740
|
|
740
|
-
def
|
741
|
+
def query_options
|
741
742
|
opts = {}
|
742
743
|
# Don't specify select = ALL_ATTRIBUTES option explicitly because it's
|
743
744
|
# already a default value of Select statement. Explicite Select value
|
@@ -749,30 +750,35 @@ module Dynamoid
|
|
749
750
|
opts[:exclusive_start_key] = start_key if @start
|
750
751
|
opts[:scan_index_forward] = @scan_index_forward
|
751
752
|
opts[:project] = @project
|
753
|
+
opts[:consistent_read] = true if @consistent_read
|
752
754
|
opts
|
753
755
|
end
|
754
756
|
|
755
|
-
def
|
756
|
-
query = self.query
|
757
|
-
|
757
|
+
def scan_conditions
|
758
758
|
# Honor STI and :type field if it presents
|
759
759
|
if sti_condition
|
760
|
-
|
760
|
+
@where_conditions.update_with_hash(sti_condition)
|
761
761
|
end
|
762
762
|
|
763
|
-
{}
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
opts[key] = { eq: value }
|
770
|
-
end
|
763
|
+
hash_conditions = {}
|
764
|
+
hash_conditions.tap do |opts|
|
765
|
+
@where_conditions.keys.map(&:to_sym).each do |key|
|
766
|
+
name, condition = field_condition(key, @where_conditions[key])
|
767
|
+
opts[name] ||= []
|
768
|
+
opts[name] << condition
|
771
769
|
end
|
772
770
|
end
|
771
|
+
|
772
|
+
string_conditions = []
|
773
|
+
@where_conditions.string_conditions.each do |query, placeholders|
|
774
|
+
placeholders ||= {}
|
775
|
+
string_conditions << [query, placeholders]
|
776
|
+
end
|
777
|
+
|
778
|
+
[hash_conditions] + string_conditions
|
773
779
|
end
|
774
780
|
|
775
|
-
def
|
781
|
+
def scan_options
|
776
782
|
opts = {}
|
777
783
|
opts[:index_name] = @key_fields_detector.index_name if @key_fields_detector.index_name
|
778
784
|
opts[:record_limit] = @record_limit if @record_limit
|
@@ -784,6 +790,7 @@ module Dynamoid
|
|
784
790
|
opts
|
785
791
|
end
|
786
792
|
|
793
|
+
# TODO: return Array, not String
|
787
794
|
def sti_condition
|
788
795
|
condition = {}
|
789
796
|
type = @source.inheritance_field
|
@@ -5,10 +5,10 @@ module Dynamoid
|
|
5
5
|
# @private
|
6
6
|
class KeyFieldsDetector
|
7
7
|
class Query
|
8
|
-
def initialize(
|
9
|
-
@
|
10
|
-
@fields_with_operator =
|
11
|
-
@fields =
|
8
|
+
def initialize(where_conditions)
|
9
|
+
@where_conditions = where_conditions
|
10
|
+
@fields_with_operator = where_conditions.keys.map(&:to_s)
|
11
|
+
@fields = where_conditions.keys.map(&:to_s).map { |s| s.split('.').first }
|
12
12
|
end
|
13
13
|
|
14
14
|
def contain_only?(field_names)
|
@@ -24,10 +24,9 @@ module Dynamoid
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
def initialize(
|
28
|
-
@query = query
|
27
|
+
def initialize(where_conditions, source, forced_index_name: nil)
|
29
28
|
@source = source
|
30
|
-
@query = Query.new(
|
29
|
+
@query = Query.new(where_conditions)
|
31
30
|
@forced_index_name = forced_index_name
|
32
31
|
@result = find_keys_in_query
|
33
32
|
end
|
@@ -20,8 +20,8 @@ module Dynamoid
|
|
20
20
|
fields_list = @nonexistent_fields.map { |s| "`#{s}`" }.join(', ')
|
21
21
|
count = @nonexistent_fields.size
|
22
22
|
|
23
|
-
'where conditions contain nonexistent' \
|
24
|
-
"
|
23
|
+
'where conditions contain nonexistent ' \
|
24
|
+
"field #{'name'.pluralize(count)} #{fields_list}"
|
25
25
|
end
|
26
26
|
|
27
27
|
private
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dynamoid
|
4
|
+
module Criteria
|
5
|
+
# @private
|
6
|
+
class WhereConditions
|
7
|
+
attr_reader :string_conditions
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@hash_conditions = []
|
11
|
+
@string_conditions = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def update_with_hash(hash)
|
15
|
+
@hash_conditions << hash.symbolize_keys
|
16
|
+
end
|
17
|
+
|
18
|
+
def update_with_string(query, placeholders)
|
19
|
+
@string_conditions << [query, placeholders]
|
20
|
+
end
|
21
|
+
|
22
|
+
def keys
|
23
|
+
@hash_conditions.flat_map(&:keys)
|
24
|
+
end
|
25
|
+
|
26
|
+
def empty?
|
27
|
+
@hash_conditions.empty? && @string_conditions.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def [](key)
|
31
|
+
hash = @hash_conditions.find { |h| h.key?(key) }
|
32
|
+
hash[key] if hash
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|