dynamoid_advanced_where 1.0.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 +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
|