dynomite 1.2.7 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +17 -2
  3. data/CHANGELOG.md +18 -0
  4. data/Gemfile +1 -5
  5. data/LICENSE.txt +22 -0
  6. data/README.md +6 -190
  7. data/Rakefile +13 -1
  8. data/dynomite.gemspec +9 -2
  9. data/exe/dynomite +14 -0
  10. data/lib/dynomite/associations/association.rb +126 -0
  11. data/lib/dynomite/associations/belongs_to.rb +35 -0
  12. data/lib/dynomite/associations/has_and_belongs_to_many.rb +19 -0
  13. data/lib/dynomite/associations/has_many.rb +19 -0
  14. data/lib/dynomite/associations/has_one.rb +19 -0
  15. data/lib/dynomite/associations/many_association.rb +257 -0
  16. data/lib/dynomite/associations/single_association.rb +157 -0
  17. data/lib/dynomite/associations.rb +248 -0
  18. data/lib/dynomite/autoloader.rb +25 -0
  19. data/lib/dynomite/cli.rb +48 -0
  20. data/lib/dynomite/client.rb +118 -0
  21. data/lib/dynomite/command.rb +89 -0
  22. data/lib/dynomite/completer/script.rb +6 -0
  23. data/lib/dynomite/completer/script.sh +10 -0
  24. data/lib/dynomite/completer.rb +159 -0
  25. data/lib/dynomite/config.rb +39 -0
  26. data/lib/dynomite/core.rb +18 -19
  27. data/lib/dynomite/engine.rb +45 -0
  28. data/lib/dynomite/erb.rb +5 -3
  29. data/lib/dynomite/error.rb +12 -0
  30. data/lib/dynomite/help/completion.md +20 -0
  31. data/lib/dynomite/help/completion_script.md +3 -0
  32. data/lib/dynomite/help/migrate.md +3 -0
  33. data/lib/dynomite/help.rb +9 -0
  34. data/lib/dynomite/install.rb +4 -0
  35. data/lib/dynomite/item/abstract.rb +15 -0
  36. data/lib/dynomite/item/components.rb +33 -0
  37. data/lib/dynomite/item/dsl.rb +101 -0
  38. data/lib/dynomite/item/id.rb +41 -0
  39. data/lib/dynomite/item/indexes/finder.rb +58 -0
  40. data/lib/dynomite/item/indexes/index.rb +21 -0
  41. data/lib/dynomite/item/indexes/primary_index.rb +18 -0
  42. data/lib/dynomite/item/indexes.rb +25 -0
  43. data/lib/dynomite/item/locking.rb +53 -0
  44. data/lib/dynomite/item/magic_fields.rb +66 -0
  45. data/lib/dynomite/item/primary_key.rb +85 -0
  46. data/lib/dynomite/item/query/delegates.rb +28 -0
  47. data/lib/dynomite/item/query/params/base.rb +42 -0
  48. data/lib/dynomite/item/query/params/expression_attribute.rb +79 -0
  49. data/lib/dynomite/item/query/params/filter.rb +41 -0
  50. data/lib/dynomite/item/query/params/function/attribute_exists.rb +21 -0
  51. data/lib/dynomite/item/query/params/function/attribute_type.rb +30 -0
  52. data/lib/dynomite/item/query/params/function/base.rb +33 -0
  53. data/lib/dynomite/item/query/params/function/begins_with.rb +32 -0
  54. data/lib/dynomite/item/query/params/function/contains.rb +7 -0
  55. data/lib/dynomite/item/query/params/function/size_fn.rb +37 -0
  56. data/lib/dynomite/item/query/params/helpers.rb +94 -0
  57. data/lib/dynomite/item/query/params/key_condition.rb +34 -0
  58. data/lib/dynomite/item/query/params.rb +115 -0
  59. data/lib/dynomite/item/query/partiql/executer.rb +72 -0
  60. data/lib/dynomite/item/query/partiql.rb +67 -0
  61. data/lib/dynomite/item/query/relation/chain.rb +125 -0
  62. data/lib/dynomite/item/query/relation/comparision_expression.rb +21 -0
  63. data/lib/dynomite/item/query/relation/comparision_map.rb +19 -0
  64. data/lib/dynomite/item/query/relation/delete.rb +38 -0
  65. data/lib/dynomite/item/query/relation/ids.rb +21 -0
  66. data/lib/dynomite/item/query/relation/math.rb +19 -0
  67. data/lib/dynomite/item/query/relation/where_field.rb +32 -0
  68. data/lib/dynomite/item/query/relation/where_group.rb +78 -0
  69. data/lib/dynomite/item/query/relation.rb +127 -0
  70. data/lib/dynomite/item/query.rb +7 -0
  71. data/lib/dynomite/item/read/find.rb +196 -0
  72. data/lib/dynomite/item/read/find_with_event.rb +42 -0
  73. data/lib/dynomite/item/read.rb +90 -0
  74. data/lib/dynomite/item/sti.rb +43 -0
  75. data/lib/dynomite/item/table_namespace.rb +43 -0
  76. data/lib/dynomite/item/typecaster.rb +106 -0
  77. data/lib/dynomite/item/waiter_methods.rb +18 -0
  78. data/lib/dynomite/item/write/base.rb +15 -0
  79. data/lib/dynomite/item/write/delete_item.rb +14 -0
  80. data/lib/dynomite/item/write/put_item.rb +99 -0
  81. data/lib/dynomite/item/write/update_item.rb +73 -0
  82. data/lib/dynomite/item/write.rb +204 -0
  83. data/lib/dynomite/item.rb +113 -286
  84. data/lib/dynomite/migration/dsl/accessor.rb +19 -0
  85. data/lib/dynomite/migration/dsl/index/base.rb +42 -0
  86. data/lib/dynomite/migration/dsl/index/gsi.rb +59 -0
  87. data/lib/dynomite/migration/dsl/index/lsi.rb +27 -0
  88. data/lib/dynomite/migration/dsl/index.rb +72 -0
  89. data/lib/dynomite/migration/dsl/primary_key.rb +62 -0
  90. data/lib/dynomite/migration/dsl/provisioned_throughput.rb +38 -0
  91. data/lib/dynomite/migration/dsl.rb +89 -142
  92. data/lib/dynomite/migration/file_info.rb +28 -0
  93. data/lib/dynomite/migration/generator.rb +30 -16
  94. data/lib/dynomite/migration/helpers.rb +7 -0
  95. data/lib/dynomite/migration/internal/migrate/create_schema_migrations.rb +17 -0
  96. data/lib/dynomite/migration/internal/models/schema_migration.rb +6 -0
  97. data/lib/dynomite/migration/runner.rb +178 -0
  98. data/lib/dynomite/migration/templates/create_table.rb +7 -23
  99. data/lib/dynomite/migration/templates/delete_table.rb +7 -0
  100. data/lib/dynomite/migration/templates/update_table.rb +3 -18
  101. data/lib/dynomite/migration.rb +53 -10
  102. data/lib/dynomite/reserved_words.rb +13 -3
  103. data/lib/dynomite/seed.rb +12 -0
  104. data/lib/dynomite/types.rb +22 -0
  105. data/lib/dynomite/version.rb +1 -1
  106. data/lib/dynomite/waiter.rb +40 -0
  107. data/lib/dynomite.rb +11 -17
  108. data/lib/generators/application_item/application_item_generator.rb +30 -0
  109. data/lib/generators/application_item/templates/application_item.rb.tt +4 -0
  110. data/lib/jets/commands/dynamodb_command.rb +29 -0
  111. data/lib/jets/commands/help/generate.md +33 -0
  112. data/lib/jets/commands/help/migrate.md +3 -0
  113. metadata +201 -17
  114. data/docs/migrations/long-example.rb +0 -127
  115. data/docs/migrations/short-example.rb +0 -40
  116. data/lib/dynomite/db_config.rb +0 -121
  117. data/lib/dynomite/errors.rb +0 -15
  118. data/lib/dynomite/log.rb +0 -15
  119. data/lib/dynomite/migration/common.rb +0 -86
  120. data/lib/dynomite/migration/dsl/base_secondary_index.rb +0 -73
  121. data/lib/dynomite/migration/dsl/global_secondary_index.rb +0 -4
  122. data/lib/dynomite/migration/dsl/local_secondary_index.rb +0 -8
  123. data/lib/dynomite/migration/executor.rb +0 -38
@@ -0,0 +1,42 @@
1
+ module Dynomite::Migration::Dsl::Index
2
+ class Base
3
+ include Dynomite::Types
4
+
5
+ def partition_key_attribute_name
6
+ get_attribute_name(:partition_key)
7
+ end
8
+
9
+ def sort_key_attribute_name
10
+ get_attribute_name(:sort_key)
11
+ end
12
+
13
+ def get_attribute_name(field=:partition_key)
14
+ value = instance_variable_get("@#{field}") # IE: @partition_key
15
+ value.to_s.split(':').first if value
16
+ end
17
+
18
+ def partition_key_attribute_type
19
+ get_attribute_type(:partition_key)
20
+ end
21
+
22
+ def sort_key_attribute_type
23
+ get_attribute_type(:sort_key)
24
+ end
25
+
26
+ def get_attribute_type(field=:partition_key)
27
+ value = instance_variable_get("@#{field}") # IE: @partition_key
28
+ name, type = value.to_s.split(':')
29
+ type ||= "string"
30
+ type_map(type)
31
+ end
32
+
33
+ def conventional_index_name
34
+ # DynamoDB requires index names to be at least 3 characters long, otherwise:
35
+ # Error: Unable to : 1 validation error detected: Value 'id' at 'globalSecondaryIndexes.1.member.indexName' failed to satisfy constraint: Member must have length greater than or equal to 3
36
+ # The id valid is too short sadly.
37
+ # Adding -index to the end of the index name is a safe way to ensure that.
38
+ # Annoying that the index_name("id-index") is going to be a little longer.
39
+ [partition_key_attribute_name, sort_key_attribute_name, 'index'].compact.join('-')
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,59 @@
1
+ module Dynomite::Migration::Dsl::Index
2
+ class Gsi < Base
3
+ attr_reader :action, :attrs
4
+ def initialize(action, attrs)
5
+ @action = action
6
+ @params = attrs.dup
7
+ # Delete the special DSL keys. Keep the rest and pass through to AWS create_table or update_table
8
+ @partition_key = @params.delete(:partition_key) || @params.delete(:hash_key) # required for create, optional for update (only index needed)
9
+ @sort_key = @params.delete(:sort_key) || @params.delete(:range_key) # optional for create
10
+ end
11
+
12
+ def params
13
+ send("params_#{@action}")
14
+ end
15
+
16
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/DynamoDB/Client.html#create_table-instance_method
17
+ def params_create
18
+ @params[:key_schema] ||= []
19
+ # HASH - add to beginning of array
20
+ @params[:key_schema].unshift(attribute_name: partition_key_attribute_name, key_type: "HASH") unless @partition_key.blank?
21
+ # RANGE - add to end of array
22
+ @params[:key_schema] << {attribute_name: sort_key_attribute_name, key_type: "RANGE"} unless @sort_key.blank?
23
+ @params[:index_name] ||= conventional_index_name
24
+ @params[:projection] ||= {projection_type: "ALL"}
25
+ @params[:provisioned_throughput] = normalize_provisioned_throughput(@params[:provisioned_throughput]) if @params[:provisioned_throughput]
26
+ @params
27
+ end
28
+
29
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/DynamoDB/Client.html#update_table-instance_method
30
+ def params_update
31
+ @params[:index_name] ||= conventional_index_name
32
+ @params[:provisioned_throughput] = normalize_provisioned_throughput(@params[:provisioned_throughput]) if @params[:provisioned_throughput]
33
+ @params
34
+ end
35
+
36
+ def params_delete
37
+ @params[:index_name] ||= conventional_index_name
38
+ @params
39
+ end
40
+
41
+ def attribute_definitions
42
+ definitions = []
43
+ definitions << {attribute_name: partition_key_attribute_name, attribute_type: partition_key_attribute_type} unless @partition_key.blank?
44
+ definitions << {attribute_name: sort_key_attribute_name, attribute_type: sort_key_attribute_type} unless @sort_key.blank?
45
+ definitions
46
+ end
47
+
48
+ def normalize_provisioned_throughput(value)
49
+ if value.is_a?(Hash)
50
+ value
51
+ else
52
+ {
53
+ read_capacity_units: value,
54
+ write_capacity_units: value,
55
+ }
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,27 @@
1
+ module Dynomite::Migration::Dsl::Index
2
+ class Lsi < Base
3
+ attr_reader :attrs
4
+ def initialize(partition_key_name, attrs)
5
+ @partition_key_name = partition_key_name
6
+ @params = attrs.dup
7
+ # Delete the special DSL keys. Keep the rest and pass through to AWS create_table or update_table
8
+ @sort_key = @params.delete(:sort_key) || @params.delete(:range_key) # require for LSI index since it must be a composite key
9
+ end
10
+
11
+ def params
12
+ @params[:key_schema] ||= []
13
+ @params[:key_schema] << {attribute_name: @partition_key_name, key_type: "HASH"}
14
+ @params[:key_schema] << {attribute_name: sort_key_attribute_name, key_type: "RANGE"}
15
+ @params[:index_name] ||= conventional_index_name
16
+ @params[:projection] ||= {projection_type: "ALL"}
17
+ @params
18
+ end
19
+
20
+ def attribute_definitions
21
+ definitions = []
22
+ definitions << {attribute_name: partition_key_attribute_name, attribute_type: partition_key_attribute_type} unless @partition_key.blank?
23
+ definitions << {attribute_name: sort_key_attribute_name, attribute_type: sort_key_attribute_type} unless @sort_key.blank?
24
+ definitions
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,72 @@
1
+ class Dynomite::Migration::Dsl
2
+ module Index
3
+ def add_gsi(attrs={})
4
+ attrs = normalize_index_attrs(attrs, :partition_key)
5
+ @gsi_indexes << Gsi.new(:create, attrs) # store in @gsi_indexes for the parent Dsl to use
6
+ end
7
+ alias create_gsi add_gsi
8
+
9
+ def update_gsi(attrs={})
10
+ attrs = normalize_index_attrs(attrs, :partition_key)
11
+ @gsi_indexes << Gsi.new(:update, attrs) # store in @gsi_indexes for the parent Dsl to use
12
+ end
13
+
14
+ def remove_gsi(attrs={})
15
+ attrs = normalize_index_attrs(attrs, :partition_key)
16
+ @gsi_indexes << Gsi.new(:delete, attrs) # store in @gsi_indexes for the parent Dsl to use
17
+ end
18
+ alias delete_gsi remove_gsi
19
+
20
+ def add_lsi(attrs={})
21
+ # dont need create with lsi indexes, they are created as part of create_table
22
+ partition_key_name = @partition_key_identifier.split(':').first
23
+ attrs = normalize_index_attrs(attrs, :sort_key)
24
+ @lsi_indexes << Lsi.new(partition_key_name, attrs) # store in @lsi_indexes for the parent Dsl to use
25
+ end
26
+ alias create_lsi add_lsi
27
+
28
+ def normalize_index_attrs(attrs, type=:partition_key)
29
+ if attrs.is_a?(String) || attrs.is_a?(Symbol)
30
+ {type => attrs.to_s}
31
+ else
32
+ attrs
33
+ end
34
+ end
35
+
36
+ # maps each lsi to the hash structure expected by dynamodb update_table
37
+ # under the lsi_secondary_index_creates key:
38
+ #
39
+ # { create: {...} }
40
+ # { update: {...} }
41
+ # { delete: {...} }
42
+ def lsi_secondary_index_creates
43
+ @lsi_indexes.map do |lsi|
44
+ lsi.params
45
+ end
46
+ end
47
+
48
+ # maps each lsi to the hash structure expected by dynamodb update_table
49
+ # under the gsi_secondary_index_creates key:
50
+ #
51
+ # { create: {...} }
52
+ # { update: {...} }
53
+ # { delete: {...} }
54
+ def gsi_secondary_index_creates
55
+ @gsi_indexes.map do |gsi|
56
+ gsi.params
57
+ end
58
+ end
59
+
60
+ # maps each gsi to the hash structure expected by dynamodb update_table
61
+ # under the global_secondary_index_updates key:
62
+ #
63
+ # { create: {...} }
64
+ # { update: {...} }
65
+ # { delete: {...} }
66
+ def global_secondary_index_updates
67
+ @gsi_indexes.map do |gsi|
68
+ { gsi.action => gsi.params }
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,62 @@
1
+ # Common methods to the *SecondaryIndex classes that handle gsi and lsi methods
2
+ # as well a the Dsl class that handles create_table and update_table methods.
3
+ class Dynomite::Migration::Dsl
4
+ module PrimaryKey
5
+ include Dynomite::Types
6
+
7
+ # http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Types/KeySchemaElement.html
8
+ # partition_key is required
9
+ attr_reader :partition_key_identifier
10
+ def partition_key(identifier)
11
+ identifier = identifier.to_s
12
+ @partition_key_identifier = identifier # for later use. useful for conventional_index_name
13
+ adjust_schema_and_attributes(identifier, "HASH")
14
+ end
15
+
16
+ # sort_key is optional
17
+ attr_reader :sort_key_identifier
18
+ def sort_key(identifier)
19
+ identifier = identifier.to_s
20
+ @sort_key_identifier = identifier # for later use. useful for conventional_index_name
21
+ adjust_schema_and_attributes(identifier, "RANGE")
22
+ end
23
+
24
+ # Use class variable to share across all instances of GlobalSecondaryIndex
25
+ # Note: Even though dynomite merges attribute defintions correctly for
26
+ # multiple indexes, DynamoDB does not allow creating multiple indexes
27
+ # at the same time. Example error:
28
+ # Unable to update table: Subscriber limit exceeded: Only 1 online index can be created or deleted simultaneously per table
29
+ # Leaving the logic in place in case AWS changes this in the future.
30
+
31
+ # Parameters:
32
+ # identifier: "id:string" or "id"
33
+ # key_type: "HASH" OR "RANGE"
34
+ #
35
+ # Adjusts the parameters for create_table to add the
36
+ # partition_key and sort_key
37
+ def adjust_schema_and_attributes(identifier, key_type)
38
+ name, attribute_type = identifier.split(':')
39
+ attribute_type ||= "string" # default to string
40
+
41
+ partition_key = {
42
+ attribute_name: name,
43
+ key_type: key_type.upcase
44
+ }
45
+
46
+ if partition_key[:key_type] == "RANGE"
47
+ @key_schema << partition_key unless @key_schema.include?(partition_key)
48
+ else # HASH - add to beginning
49
+ @key_schema.unshift(partition_key) unless @key_schema.include?(partition_key)
50
+ end
51
+
52
+ attribute_definition = {
53
+ attribute_name: name,
54
+ attribute_type: type_map(attribute_type)
55
+ }
56
+ unless @attribute_definitions.include?(attribute_definition)
57
+ @attribute_definitions << attribute_definition
58
+ end
59
+ @attribute_definitions
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,38 @@
1
+ class Dynomite::Migration::Dsl
2
+ module ProvisionedThroughput
3
+ def billing_mode(v)
4
+ @billing_mode = v
5
+ @provisioned_throughput = nil if @billing_mode.to_s.upcase == "PAY_PER_REQUEST"
6
+ end
7
+
8
+ # t.provisioned_throughput(1) # both
9
+ # t.provisioned_throughput(1,1) # read, write
10
+ # t.provisioned_throughput({
11
+ # read_capacity_units: 1,
12
+ # write_capacity_units: 1
13
+ # }
14
+ def provisioned_throughput(*args)
15
+ if args.size == 0 # reader method
16
+ @provisioned_throughput # early return
17
+ elsif args.first.is_a?(Hash)
18
+ # @provisioned_throughput_set_called useful for update_table
19
+ # only provide a provisioned_throughput settings if explicitly called for update_table
20
+ @provisioned_throughput_set_called = true
21
+ # Case:
22
+ # provisioned_throughput(
23
+ # read_capacity_units: 1,
24
+ # write_capacity_units: 1
25
+ # )
26
+ @provisioned_throughput = arg.first # set directly
27
+ else # assume parameter is an Integer or [Integer, Integer]
28
+ # Case: provisioned_throughput(1)
29
+ # Case: provisioned_throughput(1, 1)
30
+ read_capacity_units, write_capacity_units = args
31
+ @provisioned_throughput = {
32
+ read_capacity_units: read_capacity_units,
33
+ write_capacity_units: write_capacity_units || read_capacity_units,
34
+ }
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,35 +1,27 @@
1
1
  class Dynomite::Migration
2
2
  class Dsl
3
- ATTRIBUTES = %i[
4
- key_schema
5
- attribute_definitions
6
- table_name
7
- billing_mode
8
- ].freeze
9
-
10
- autoload :Common, "dynomite/migration/common"
11
- autoload :BaseSecondaryIndex, "dynomite/migration/dsl/base_secondary_index"
12
- autoload :LocalSecondaryIndex, "dynomite/migration/dsl/local_secondary_index"
13
- autoload :GlobalSecondaryIndex, "dynomite/migration/dsl/global_secondary_index"
14
-
15
- include Dynomite::DbConfig
16
- include Common
17
-
18
- attr_accessor(*ATTRIBUTES)
19
-
3
+ extend Accessor
4
+ include Dynomite::Client
5
+ include ProvisionedThroughput
6
+ include PrimaryKey
7
+ include Index
8
+ include Helpers
9
+
10
+ attr_accessor :attribute_definitions,
11
+ :key_schema,
12
+ :table_name
20
13
  def initialize(method_name, table_name, &block)
21
14
  @method_name = method_name
22
15
  @table_name = table_name
23
16
  @block = block
24
17
 
25
- # Dsl fills in attributes in as methods are called within the block.
18
+ # Dsl fills in atttributes in as methods are called within the block
19
+ # when parition_key and sort_key are called.
26
20
  # Attributes for both create_table and updated_table:
27
- @billing_mode = 'PROVISIONED'
28
21
  @attribute_definitions = []
29
- @provisioned_throughput = {
30
- read_capacity_units: 5,
31
- write_capacity_units: 5
32
- }
22
+ # dont set billing_mode for update_table. otherwise creating an index can set billing_mode to PAY_PER_REQUEST without user knowing
23
+ @billing_mode = "PAY_PER_REQUEST" if @method_name == :create_table
24
+ @provisioned_throughput = nil
33
25
 
34
26
  # Attributes for create_table only:
35
27
  @key_schema = []
@@ -39,35 +31,10 @@ class Dynomite::Migration
39
31
  @lsi_indexes = []
40
32
  end
41
33
 
42
- # t.billing_mode(:pay_per_request)
43
- # t.billing_mode(:provisioned) # default value
44
- def billing_mode(mode = nil)
45
- return @billing_mode if mode.nil?
46
-
47
- @billing_mode = mode.to_s.upcase
34
+ def namespaced_table_name
35
+ table_name_with_namespace(table_name)
48
36
  end
49
37
 
50
- # t.gsi(:create) do |i|
51
- # i.partition_key "category:string"
52
- # i.sort_key "created_at:string" # optional
53
- # end
54
- def gsi(action=:create, index_name=nil, &block)
55
- gsi_index = GlobalSecondaryIndex.new(action, index_name, &block)
56
- @gsi_indexes << gsi_index # store @gsi_index for the parent Dsl to use
57
- end
58
- alias_method :global_secondary_index, :gsi
59
-
60
- # t.lsi(:create) do |i|
61
- # i.partition_key "category:string"
62
- # i.sort_key "created_at:string" # optional
63
- # end
64
- def lsi(action=:create, index_name=nil, &block)
65
- # dont need action create but have it to keep the lsi and gsi method consistent
66
- lsi_index = LocalSecondaryIndex.new(index_name, &block)
67
- @lsi_indexes << lsi_index # store @lsi_index for the parent Dsl to use
68
- end
69
- alias_method :local_secondary_index, :lsi
70
-
71
38
  def evaluate
72
39
  return if @evaluated
73
40
  @block.call(self) if @block
@@ -79,136 +46,116 @@ class Dynomite::Migration
79
46
  # executor
80
47
  def params
81
48
  evaluate # lazy evaluation: wait until as long as possible before evaluating code block
82
-
83
- # Not using send because think its clearer in this case
84
- case @method_name
85
- when :create_table
86
- params_create_table
87
- when :update_table
88
- params_update_table
89
- end
49
+ send "params_#{@method_name}" # IE: params_create_table, params_update_table, params_delete_table
90
50
  end
91
51
 
52
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/DynamoDB/Client.html#create_table-instance_method
92
53
  def params_create_table
93
- merge_lsi_attribute_definitions!
94
- merge_gsi_attribute_definitions!
95
-
96
54
  params = {
55
+ billing_mode: @billing_mode.to_s.upcase,
97
56
  table_name: namespaced_table_name,
98
57
  key_schema: @key_schema,
99
- attribute_definitions: @attribute_definitions,
100
- billing_mode: @billing_mode
58
+ provisioned_throughput: @provisioned_throughput,
59
+ tags: @tags, # only for create_table
101
60
  }
61
+ params.reject! { |k,v| v.blank? }
102
62
 
103
- params[:provisioned_throughput] = @provisioned_throughput if @billing_mode == 'PROVISIONED'
104
63
  params[:local_secondary_indexes] = lsi_secondary_index_creates unless @lsi_indexes.empty?
105
64
  params[:global_secondary_indexes] = gsi_secondary_index_creates unless @gsi_indexes.empty?
65
+ params[:attribute_definitions] = attribute_definitions
66
+ params.merge!(common_params)
106
67
  params
107
68
  end
108
69
 
70
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/DynamoDB/Client.html#update_table-instance_method
71
+ dsl_accessor :replica_updates
109
72
  def params_update_table
110
- merge_gsi_attribute_definitions!
111
-
112
73
  params = {
74
+ billing_mode: @billing_mode.to_s.upcase,
113
75
  table_name: namespaced_table_name,
114
- attribute_definitions: @attribute_definitions,
115
- billing_mode: @billing_mode
116
76
  # update table take values only some values for the "parent" table
117
77
  # no key_schema, update_table does not handle key_schema for the "parent" table
78
+ replica_updates: @replica_updates, # only for update_table
118
79
  }
119
- # only set "parent" table provisioned_throughput if user actually invoked
120
- # it in the dsl
121
- if @provisioned_throughput_set_called && @billing_mode == 'PROVISIONED'
122
- params[:provisioned_throughput] = @provisioned_throughput
123
- end
124
- params[:global_secondary_index_updates] = global_secondary_index_updates
80
+ params.reject! { |k,v| v.blank? }
81
+
82
+ # only set "parent" table provisioned_throughput if user actually invoked it in the dsl
83
+ params[:provisioned_throughput] = @provisioned_throughput if @provisioned_throughput_set_called
84
+ params[:global_secondary_index_updates] = global_secondary_index_updates unless @gsi_indexes.empty?
85
+ params[:attribute_definitions] = attribute_definitions
125
86
  params
126
87
  end
127
88
 
128
- # Goes thorugh all the lsi_indexes that have been built up in memory.
129
- # Find the lsi object that creates an index and then grab the
130
- # attribute_definitions from it.
131
- def merge_lsi_attribute_definitions!
132
- lsi = @lsi_indexes.first # DynamoDB only supports adding one index at a time anyway. The reason @lsi_indexes is an Array is because we're sharing the same class code for LSI and GSI
133
- if lsi
134
- lsi.evaluate # force early evaluate since we need the params to
135
- # add: gsi_attribute_definitions + lsi_attrs
136
- lsi_attrs = lsi.attribute_definitions
137
- end
138
- all_attrs = if lsi_attrs
139
- @attribute_definitions + lsi_attrs
140
- else
141
- @attribute_definitions
142
- end
143
- @attribute_definitions = all_attrs.uniq
89
+ def params_delete_table
90
+ { table_name: namespaced_table_name }
144
91
  end
145
92
 
146
- # maps each lsi to the hash structure expected by dynamodb update_table
147
- # under the global_secondary_index_updates key:
148
- #
149
- # { create: {...} }
150
- # { update: {...} }
151
- # { delete: {...} }
152
- def lsi_secondary_index_creates
153
- @lsi_indexes.map do |lsi|
154
- lsi.params
93
+ def tags(values=nil)
94
+ if values.nil?
95
+ @tags
96
+ else
97
+ if values.is_a?(Hash)
98
+ @tags = values.map do |key,value|
99
+ {key: key.to_s, value: value.to_s}
100
+ end
101
+ else
102
+ @tags = values
103
+ end
155
104
  end
156
105
  end
157
106
 
158
- # maps each lsi to the hash structure expected by dynamodb update_table
159
- # under the global_secondary_index_updates key:
160
- #
161
- # { create: {...} }
162
- # { update: {...} }
163
- # { delete: {...} }
164
- def gsi_secondary_index_creates
165
- @gsi_indexes.map do |gsi|
166
- gsi.params
107
+ # @attribute_definitions only hold the partition_key and sort_key attributes
108
+ # We'll also added the attributes from: 1. lsi index 2. gsi indexes 3. existing attributes for update table
109
+ def attribute_definitions
110
+ # Goes through all the lsi_indexes that have been built up in memory.
111
+ # Find the lsi object that creates an index and then grab the attribute_definitions from it.
112
+ # All lsi indexes are create. There is no update or delete for lsi indexes.
113
+ @lsi_indexes.each do |lsi|
114
+ lsi.attribute_definitions.each do |definition|
115
+ @attribute_definitions << definition unless @attribute_definitions.include?(definition)
116
+ end
167
117
  end
168
- end
169
118
 
170
- # Goes thorugh all the gsi_indexes that have been built up in memory.
171
- # Find the gsi object that creates an index and then grab the
172
- # attribute_definitions from it.
173
- def merge_gsi_attribute_definitions!
174
- gsi_attrs = []
175
-
176
- gsi = @gsi_indexes.find { |i| i.action == :create }
177
- if gsi
178
- gsi.evaluate # force early evaluate since we need the params to
179
- # add: gsi_attribute_definitions + gsi_attrs
180
- gsi_attrs = gsi.attribute_definitions
119
+ # Goes through all the gsi_indexes that have been built up in memory.
120
+ # Find the gsi object that creates an index and then grab the attribute_definitions from it.
121
+ @gsi_indexes.select { |i| i.action == :create }.each do |gsi|
122
+ gsi.attribute_definitions.each do |definition|
123
+ @attribute_definitions << definition unless @attribute_definitions.include?(definition)
124
+ end
181
125
  end
182
126
 
183
127
  # Merge existing attributes for update table
184
- all_gsi_attrs = @method_name == :update_table ?
185
- gsi_attribute_definitions + gsi_attrs :
186
- gsi_attrs
128
+ if @method_name == :update_table
129
+ existing_attributes = desc_table(namespaced_table_name).attribute_definitions
130
+ existing_attributes.each do |attribute_definition|
131
+ definition = attribute_definition.to_h
132
+ @attribute_definitions << definition unless @attribute_definitions.include?(definition)
133
+ end
134
+ end
187
135
 
188
- all_attrs = (@attribute_definitions + all_gsi_attrs).uniq
189
- @attribute_definitions = all_attrs
136
+ @attribute_definitions
190
137
  end
191
138
 
192
- # maps each gsi to the hash structure expected by dynamodb update_table
193
- # under the global_secondary_index_updates key:
194
- #
195
- # { create: {...} }
196
- # { update: {...} }
197
- # { delete: {...} }
198
- def global_secondary_index_updates
199
- @gsi_indexes.map do |gsi|
200
- { gsi.action => gsi.params }
201
- end
139
+ dsl_accessor :stream_specification, :sse_specification
140
+ # common to create_table and update_table
141
+ def common_params
142
+ params = {
143
+ stream_specification: @stream_specification,
144
+ sse_specification: @sse_specification,
145
+ deletion_protection_enabled: deletion_protection_enabled_value,
146
+ }
147
+ params.reject! { |k,v| v.blank? }
202
148
  end
203
149
 
204
- # >> resp = Post.db.describe_table(table_name: "demo-dev-posts")
205
- # >> resp.table.attribute_definitions.map(&:to_h)
206
- # => [{:attribute_name=>"id", :attribute_type=>"S"}]
207
- def gsi_attribute_definitions
208
- return @gsi_attribute_definitions if @gsi_attribute_definitions
150
+ def deletion_protection_enabled(value=true)
151
+ @deletion_protection_enabled = value
152
+ end
153
+ alias deletion_protection deletion_protection_enabled
209
154
 
210
- resp = db.describe_table(table_name: namespaced_table_name)
211
- @gsi_attribute_definitions = resp.table.attribute_definitions.map(&:to_h)
155
+ # To avoid name conflict with DSL t.deletion_protection_enabled
156
+ def deletion_protection_enabled_value
157
+ default = Dynomite.config.migration.deletion_protection_enabled
158
+ @deletion_protection_enabled.nil? ? default : @deletion_protection_enabled
212
159
  end
213
160
  end
214
161
  end
@@ -0,0 +1,28 @@
1
+ class Dynomite::Migration
2
+ class FileInfo
3
+ attr_reader :path
4
+ def initialize(path)
5
+ @path = path
6
+ end
7
+
8
+ def pretty_path
9
+ @path.sub(/^\.\//,'') # remove leading ./
10
+ end
11
+
12
+ def migration_class
13
+ filename = File.basename(@path, '.rb')
14
+ filename = filename.sub(/\d+[-_]/, '') # strip leading timestsamp
15
+ filename.camelize.constantize
16
+ end
17
+
18
+ def version
19
+ filename = File.basename(@path, '.rb')
20
+ md = filename.match(/(\d+)[-_]/)
21
+ md[1].to_i
22
+ end
23
+
24
+ def self.all_files
25
+ Dir.glob("#{Dynomite.root}/dynamodb/migrate/*").to_a.sort
26
+ end
27
+ end
28
+ end