dynomite 1.2.7 → 2.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.
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