@@ -95,20 +95,27 @@ def initialize(source)
9595 #
9696 # Internally +where+ performs either +Scan+ or +Query+ operation.
9797 #
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+ #
98111 # @return [Dynamoid::Criteria::Chain]
99112 # @since 0.2.0
100- def where ( args )
101- detector = NonexistentFieldsDetector . new ( args , @source )
102- if detector . found?
103- 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 )
104118 end
105-
106- @where_conditions . update ( args . symbolize_keys )
107-
108- # we should re-initialize keys detector every time we change @where_conditions
109- @key_fields_detector = KeyFieldsDetector . new ( @where_conditions , @source , forced_index_name : @forced_index_name )
110-
111- self
112119 end
113120
114121 # Turns on strongly consistent reads.
@@ -500,6 +507,29 @@ def pluck(*args)
500507
501508 private
502509
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+
503533 # The actual records referenced by the association.
504534 #
505535 # @return [Enumerator] an iterator of the found records.
@@ -635,12 +665,12 @@ def query_key_conditions
635665 end
636666
637667 def query_non_key_conditions
638- opts = { }
668+ hash_conditions = { }
639669
640670 # Honor STI and :type field if it presents
641671 if @source . attributes . key? ( @source . inheritance_field ) &&
642672 @key_fields_detector . hash_key . to_sym != @source . inheritance_field . to_sym
643- @where_conditions . update ( sti_condition )
673+ @where_conditions . update_with_hash ( sti_condition )
644674 end
645675
646676 # TODO: Separate key conditions and non-key conditions properly:
@@ -650,11 +680,17 @@ def query_non_key_conditions
650680 . reject { |k , _ | k . to_s =~ /^#{ @key_fields_detector . range_key } \. / }
651681 keys . each do |key |
652682 name , condition = field_condition ( key , @where_conditions [ key ] )
653- opts [ name ] ||= [ ]
654- opts [ name ] << condition
683+ hash_conditions [ name ] ||= [ ]
684+ hash_conditions [ name ] << condition
655685 end
656686
657- opts
687+ string_conditions = [ ]
688+ @where_conditions . string_conditions . each do |query , placeholders |
689+ placeholders ||= { }
690+ string_conditions << [ query , placeholders ]
691+ end
692+
693+ [ hash_conditions ] + string_conditions
658694 end
659695
660696 # TODO: casting should be operator aware
@@ -721,16 +757,25 @@ def query_options
721757 def scan_conditions
722758 # Honor STI and :type field if it presents
723759 if sti_condition
724- @where_conditions . update ( sti_condition )
760+ @where_conditions . update_with_hash ( sti_condition )
725761 end
726762
727- { } . tap do |opts |
763+ hash_conditions = { }
764+ hash_conditions . tap do |opts |
728765 @where_conditions . keys . map ( &:to_sym ) . each do |key |
729766 name , condition = field_condition ( key , @where_conditions [ key ] )
730767 opts [ name ] ||= [ ]
731768 opts [ name ] << condition
732769 end
733770 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
734779 end
735780
736781 def scan_options
0 commit comments