dynamoid_advanced_where 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +100 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +19 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/Appraisals +8 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +119 -0
- data/README.md +375 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/dynamoid_advanced_where.gemspec +41 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/dynamoid_3.4.gemfile +8 -0
- data/gemfiles/dynamoid_3.4.gemfile.lock +118 -0
- data/gemfiles/dynamoid_latest.gemfile +8 -0
- data/gemfiles/dynamoid_latest.gemfile.lock +118 -0
- data/lib/dynamoid_advanced_where.rb +8 -0
- data/lib/dynamoid_advanced_where/batched_updater.rb +229 -0
- data/lib/dynamoid_advanced_where/filter_builder.rb +136 -0
- data/lib/dynamoid_advanced_where/integrations/model.rb +34 -0
- data/lib/dynamoid_advanced_where/nodes.rb +15 -0
- data/lib/dynamoid_advanced_where/nodes/and_node.rb +43 -0
- data/lib/dynamoid_advanced_where/nodes/base_node.rb +18 -0
- data/lib/dynamoid_advanced_where/nodes/equality_node.rb +37 -0
- data/lib/dynamoid_advanced_where/nodes/exists_node.rb +44 -0
- data/lib/dynamoid_advanced_where/nodes/field_node.rb +186 -0
- data/lib/dynamoid_advanced_where/nodes/greater_than_node.rb +25 -0
- data/lib/dynamoid_advanced_where/nodes/includes.rb +29 -0
- data/lib/dynamoid_advanced_where/nodes/less_than_node.rb +27 -0
- data/lib/dynamoid_advanced_where/nodes/literal_node.rb +28 -0
- data/lib/dynamoid_advanced_where/nodes/not.rb +35 -0
- data/lib/dynamoid_advanced_where/nodes/null_node.rb +25 -0
- data/lib/dynamoid_advanced_where/nodes/operation_node.rb +44 -0
- data/lib/dynamoid_advanced_where/nodes/or_node.rb +41 -0
- data/lib/dynamoid_advanced_where/nodes/root_node.rb +47 -0
- data/lib/dynamoid_advanced_where/nodes/subfield.rb +17 -0
- data/lib/dynamoid_advanced_where/query_builder.rb +47 -0
- data/lib/dynamoid_advanced_where/query_materializer.rb +73 -0
- data/lib/dynamoid_advanced_where/version.rb +3 -0
- metadata +216 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module DynamoidAdvancedWhere
|
2
|
+
module Nodes
|
3
|
+
class GreaterThanNode < OperationNode
|
4
|
+
self.operator = '>'
|
5
|
+
end
|
6
|
+
|
7
|
+
module Concerns
|
8
|
+
module SupportsGreaterThan
|
9
|
+
def gt(other_value)
|
10
|
+
val = if respond_to?(:parse_right_hand_side)
|
11
|
+
parse_right_hand_side(other_value)
|
12
|
+
else
|
13
|
+
other_value
|
14
|
+
end
|
15
|
+
|
16
|
+
GreaterThanNode.new(
|
17
|
+
lh_operation: self,
|
18
|
+
rh_operation: LiteralNode.new(val)
|
19
|
+
)
|
20
|
+
end
|
21
|
+
alias > gt
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module DynamoidAdvancedWhere
|
2
|
+
module Nodes
|
3
|
+
class IncludesNode < OperationNode
|
4
|
+
def to_expression
|
5
|
+
"contains(
|
6
|
+
#{lh_operation.to_expression},
|
7
|
+
#{rh_operation.to_expression}
|
8
|
+
)"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Concerns
|
13
|
+
module SupportsIncludes
|
14
|
+
def includes?(other_value)
|
15
|
+
val = if respond_to?(:parse_right_hand_side)
|
16
|
+
parse_right_hand_side(other_value)
|
17
|
+
else
|
18
|
+
other_value
|
19
|
+
end
|
20
|
+
|
21
|
+
IncludesNode.new(
|
22
|
+
lh_operation: self,
|
23
|
+
rh_operation: LiteralNode.new(val)
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DynamoidAdvancedWhere
|
4
|
+
module Nodes
|
5
|
+
class LessThanNode < OperationNode
|
6
|
+
self.operator = '<'
|
7
|
+
end
|
8
|
+
|
9
|
+
module Concerns
|
10
|
+
module SupportsGreaterThan
|
11
|
+
def lt(other_value)
|
12
|
+
val = if respond_to?(:parse_right_hand_side)
|
13
|
+
parse_right_hand_side(other_value)
|
14
|
+
else
|
15
|
+
other_value
|
16
|
+
end
|
17
|
+
|
18
|
+
LessThanNode.new(
|
19
|
+
lh_operation: self,
|
20
|
+
rh_operation: LiteralNode.new(val)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
alias < lt
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module DynamoidAdvancedWhere
|
6
|
+
module Nodes
|
7
|
+
class LiteralNode
|
8
|
+
attr_accessor :value, :attr_prefix
|
9
|
+
def initialize(value)
|
10
|
+
self.value = value
|
11
|
+
self.attr_prefix = SecureRandom.hex
|
12
|
+
freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_expression
|
16
|
+
":#{attr_prefix}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def expression_attribute_names
|
20
|
+
{}
|
21
|
+
end
|
22
|
+
|
23
|
+
def expression_attribute_values
|
24
|
+
{ ":#{attr_prefix}" => value }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module DynamoidAdvancedWhere
|
6
|
+
module Nodes
|
7
|
+
class NotNode
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
attr_accessor :sub_node
|
11
|
+
|
12
|
+
def_delegators :@sub_node,
|
13
|
+
:expression_attribute_names,
|
14
|
+
:expression_attribute_values
|
15
|
+
|
16
|
+
def initialize(sub_node:)
|
17
|
+
self.sub_node = sub_node
|
18
|
+
freeze
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_expression
|
22
|
+
"NOT(#{sub_node.to_expression})"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module Concerns
|
27
|
+
module Negatable
|
28
|
+
def negate
|
29
|
+
NotNode.new(sub_node: self)
|
30
|
+
end
|
31
|
+
alias ! negate
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module DynamoidAdvancedWhere
|
6
|
+
module Nodes
|
7
|
+
class NullNode
|
8
|
+
def initialize
|
9
|
+
freeze
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_expression
|
13
|
+
''
|
14
|
+
end
|
15
|
+
|
16
|
+
def expression_attribute_names
|
17
|
+
{}
|
18
|
+
end
|
19
|
+
|
20
|
+
def expression_attribute_values
|
21
|
+
{}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './not'
|
4
|
+
require_relative './and_node'
|
5
|
+
require_relative './or_node'
|
6
|
+
|
7
|
+
module DynamoidAdvancedWhere
|
8
|
+
module Nodes
|
9
|
+
class OperationNode < BaseNode
|
10
|
+
include Concerns::Negatable
|
11
|
+
include Concerns::SupportsLogicalAnd
|
12
|
+
include Concerns::SupportsLogicalOr
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_accessor :operator
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :lh_operation, :rh_operation
|
19
|
+
|
20
|
+
def initialize(lh_operation:, rh_operation:)
|
21
|
+
self.lh_operation = lh_operation
|
22
|
+
self.rh_operation = rh_operation
|
23
|
+
freeze
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_expression
|
27
|
+
"#{lh_operation.to_expression} #{self.class.operator} " \
|
28
|
+
"#{rh_operation.to_expression} "
|
29
|
+
end
|
30
|
+
|
31
|
+
def expression_attribute_names
|
32
|
+
lh_operation.expression_attribute_names.merge(
|
33
|
+
rh_operation.expression_attribute_names
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def expression_attribute_values
|
38
|
+
lh_operation.expression_attribute_values.merge(
|
39
|
+
rh_operation.expression_attribute_values
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module DynamoidAdvancedWhere
|
2
|
+
module Nodes
|
3
|
+
class OrNode < BaseNode
|
4
|
+
include Concerns::Negatable
|
5
|
+
attr_accessor :child_nodes
|
6
|
+
|
7
|
+
def initialize(*child_nodes)
|
8
|
+
self.child_nodes = child_nodes.freeze
|
9
|
+
freeze
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_expression
|
13
|
+
return if child_nodes.empty?
|
14
|
+
|
15
|
+
"(#{child_nodes.map(&:to_expression).join(') or (')})"
|
16
|
+
end
|
17
|
+
|
18
|
+
def expression_attribute_names
|
19
|
+
child_nodes.map(&:expression_attribute_names).inject({}, &:merge!)
|
20
|
+
end
|
21
|
+
|
22
|
+
def expression_attribute_values
|
23
|
+
child_nodes.map(&:expression_attribute_values).inject({}, &:merge!)
|
24
|
+
end
|
25
|
+
|
26
|
+
def or(other_value)
|
27
|
+
OrNode.new(other_value, *child_nodes)
|
28
|
+
end
|
29
|
+
alias | or
|
30
|
+
end
|
31
|
+
|
32
|
+
module Concerns
|
33
|
+
module SupportsLogicalOr
|
34
|
+
def or(other_value)
|
35
|
+
OrNode.new(self, other_value)
|
36
|
+
end
|
37
|
+
alias | or
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require_relative './null_node'
|
5
|
+
|
6
|
+
module DynamoidAdvancedWhere
|
7
|
+
module Nodes
|
8
|
+
class RootNode < BaseNode
|
9
|
+
extend Forwardable
|
10
|
+
attr_accessor :klass, :child_node
|
11
|
+
|
12
|
+
def initialize(klass:, &blk)
|
13
|
+
self.klass = klass
|
14
|
+
evaluate_block(blk) if blk
|
15
|
+
self.child_node ||= NullNode.new
|
16
|
+
freeze
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(method, *args, &blk)
|
20
|
+
if allowed_field?(method)
|
21
|
+
FieldNode.create_node(attr_config: klass.attributes[method], field_path: method)
|
22
|
+
else
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def respond_to_missing?(method, _i)
|
28
|
+
allowed_field?(method)
|
29
|
+
end
|
30
|
+
|
31
|
+
def allowed_field?(method)
|
32
|
+
klass.attributes.key?(method.to_sym)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def evaluate_block(blk)
|
38
|
+
self.child_node = if blk.arity.zero?
|
39
|
+
Dynamoid.logger.warn 'Using DynamoidAdvancedWhere builder without an argument is now deprecated'
|
40
|
+
instance_eval(&blk)
|
41
|
+
else
|
42
|
+
blk.call(self)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DynamoidAdvancedWhere
|
4
|
+
module Nodes
|
5
|
+
module Concerns
|
6
|
+
module SupportsSubFields
|
7
|
+
def sub_field(*path, options)
|
8
|
+
Nodes::FieldNode.create_node(
|
9
|
+
field_path: field_path + path,
|
10
|
+
attr_config: options
|
11
|
+
)
|
12
|
+
end
|
13
|
+
alias dig sub_field
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative './nodes'
|
2
|
+
require_relative './query_materializer'
|
3
|
+
require_relative './batched_updater'
|
4
|
+
|
5
|
+
module DynamoidAdvancedWhere
|
6
|
+
class QueryBuilder
|
7
|
+
attr_accessor :klass, :root_node
|
8
|
+
|
9
|
+
delegate :all, :each, to: :query_materializer
|
10
|
+
|
11
|
+
def initialize(klass:, &blk)
|
12
|
+
self.klass = klass
|
13
|
+
self.root_node = Nodes::RootNode.new(klass: klass, &blk)
|
14
|
+
end
|
15
|
+
|
16
|
+
def query_materializer
|
17
|
+
QueryMaterializer.new(query_builder: self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def batch_update
|
21
|
+
BatchedUpdater.new(query_builder: self)
|
22
|
+
end
|
23
|
+
|
24
|
+
def upsert(*args)
|
25
|
+
update_fields = args.extract_options!
|
26
|
+
batch_update.set_values(update_fields).apply(*args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def where(other_builder = nil, &blk)
|
30
|
+
raise 'cannot use a block and an argument' if other_builder && blk
|
31
|
+
|
32
|
+
other_builder = self.class.new(klass: klass, &blk) if blk
|
33
|
+
|
34
|
+
raise 'passed argument must be a query builder' unless other_builder.is_a?(self.class)
|
35
|
+
|
36
|
+
local_root_node = root_node
|
37
|
+
self.class.new(klass: klass) do
|
38
|
+
Nodes::AndNode.new(
|
39
|
+
other_builder.root_node.child_node,
|
40
|
+
local_root_node.child_node
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
alias and where
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require_relative './filter_builder'
|
2
|
+
|
3
|
+
module DynamoidAdvancedWhere
|
4
|
+
class QueryMaterializer
|
5
|
+
include Enumerable
|
6
|
+
attr_accessor :query_builder
|
7
|
+
|
8
|
+
|
9
|
+
delegate :klass, to: :query_builder
|
10
|
+
delegate :table_name, to: :klass
|
11
|
+
delegate :to_a, :first, to: :each
|
12
|
+
|
13
|
+
delegate :must_scan?, to: :filter_builder
|
14
|
+
|
15
|
+
def initialize(query_builder:)
|
16
|
+
self.query_builder = query_builder
|
17
|
+
end
|
18
|
+
|
19
|
+
def all
|
20
|
+
each.to_a
|
21
|
+
end
|
22
|
+
|
23
|
+
def each(&blk)
|
24
|
+
return enum_for(:each) unless blk
|
25
|
+
|
26
|
+
if must_scan?
|
27
|
+
each_via_scan(&blk)
|
28
|
+
else
|
29
|
+
each_via_query(&blk)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def each_via_query
|
34
|
+
query = {
|
35
|
+
table_name: table_name,
|
36
|
+
}.merge(filter_builder.to_query_filter)
|
37
|
+
|
38
|
+
results = client.query(query)
|
39
|
+
|
40
|
+
if results.items
|
41
|
+
results.items.each do |item|
|
42
|
+
yield klass.from_database(item.symbolize_keys)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def each_via_scan
|
48
|
+
query = {
|
49
|
+
table_name: table_name
|
50
|
+
}.merge(filter_builder.to_scan_filter)
|
51
|
+
|
52
|
+
results = client.scan(query)
|
53
|
+
|
54
|
+
if results.items
|
55
|
+
results.items.each do |item|
|
56
|
+
yield klass.from_database(item.symbolize_keys)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def filter_builder
|
62
|
+
@filter_builder ||= FilterBuilder.new(
|
63
|
+
root_node: query_builder.root_node,
|
64
|
+
klass: klass,
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def client
|
70
|
+
Dynamoid.adapter.client
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|