dynamoid 3.9.0 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|