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.
- checksums.yaml +4 -4
- data/.gitignore +17 -2
- data/CHANGELOG.md +18 -0
- data/Gemfile +1 -5
- data/LICENSE.txt +22 -0
- data/README.md +6 -190
- data/Rakefile +13 -1
- data/dynomite.gemspec +9 -2
- data/exe/dynomite +14 -0
- data/lib/dynomite/associations/association.rb +126 -0
- data/lib/dynomite/associations/belongs_to.rb +35 -0
- data/lib/dynomite/associations/has_and_belongs_to_many.rb +19 -0
- data/lib/dynomite/associations/has_many.rb +19 -0
- data/lib/dynomite/associations/has_one.rb +19 -0
- data/lib/dynomite/associations/many_association.rb +257 -0
- data/lib/dynomite/associations/single_association.rb +157 -0
- data/lib/dynomite/associations.rb +248 -0
- data/lib/dynomite/autoloader.rb +25 -0
- data/lib/dynomite/cli.rb +48 -0
- data/lib/dynomite/client.rb +118 -0
- data/lib/dynomite/command.rb +89 -0
- data/lib/dynomite/completer/script.rb +6 -0
- data/lib/dynomite/completer/script.sh +10 -0
- data/lib/dynomite/completer.rb +159 -0
- data/lib/dynomite/config.rb +39 -0
- data/lib/dynomite/core.rb +18 -19
- data/lib/dynomite/engine.rb +45 -0
- data/lib/dynomite/erb.rb +5 -3
- data/lib/dynomite/error.rb +12 -0
- data/lib/dynomite/help/completion.md +20 -0
- data/lib/dynomite/help/completion_script.md +3 -0
- data/lib/dynomite/help/migrate.md +3 -0
- data/lib/dynomite/help.rb +9 -0
- data/lib/dynomite/install.rb +4 -0
- data/lib/dynomite/item/abstract.rb +15 -0
- data/lib/dynomite/item/components.rb +33 -0
- data/lib/dynomite/item/dsl.rb +101 -0
- data/lib/dynomite/item/id.rb +41 -0
- data/lib/dynomite/item/indexes/finder.rb +58 -0
- data/lib/dynomite/item/indexes/index.rb +21 -0
- data/lib/dynomite/item/indexes/primary_index.rb +18 -0
- data/lib/dynomite/item/indexes.rb +25 -0
- data/lib/dynomite/item/locking.rb +53 -0
- data/lib/dynomite/item/magic_fields.rb +66 -0
- data/lib/dynomite/item/primary_key.rb +85 -0
- data/lib/dynomite/item/query/delegates.rb +28 -0
- data/lib/dynomite/item/query/params/base.rb +42 -0
- data/lib/dynomite/item/query/params/expression_attribute.rb +79 -0
- data/lib/dynomite/item/query/params/filter.rb +41 -0
- data/lib/dynomite/item/query/params/function/attribute_exists.rb +21 -0
- data/lib/dynomite/item/query/params/function/attribute_type.rb +30 -0
- data/lib/dynomite/item/query/params/function/base.rb +33 -0
- data/lib/dynomite/item/query/params/function/begins_with.rb +32 -0
- data/lib/dynomite/item/query/params/function/contains.rb +7 -0
- data/lib/dynomite/item/query/params/function/size_fn.rb +37 -0
- data/lib/dynomite/item/query/params/helpers.rb +94 -0
- data/lib/dynomite/item/query/params/key_condition.rb +34 -0
- data/lib/dynomite/item/query/params.rb +115 -0
- data/lib/dynomite/item/query/partiql/executer.rb +72 -0
- data/lib/dynomite/item/query/partiql.rb +67 -0
- data/lib/dynomite/item/query/relation/chain.rb +125 -0
- data/lib/dynomite/item/query/relation/comparision_expression.rb +21 -0
- data/lib/dynomite/item/query/relation/comparision_map.rb +19 -0
- data/lib/dynomite/item/query/relation/delete.rb +38 -0
- data/lib/dynomite/item/query/relation/ids.rb +21 -0
- data/lib/dynomite/item/query/relation/math.rb +19 -0
- data/lib/dynomite/item/query/relation/where_field.rb +32 -0
- data/lib/dynomite/item/query/relation/where_group.rb +78 -0
- data/lib/dynomite/item/query/relation.rb +127 -0
- data/lib/dynomite/item/query.rb +7 -0
- data/lib/dynomite/item/read/find.rb +196 -0
- data/lib/dynomite/item/read/find_with_event.rb +42 -0
- data/lib/dynomite/item/read.rb +90 -0
- data/lib/dynomite/item/sti.rb +43 -0
- data/lib/dynomite/item/table_namespace.rb +43 -0
- data/lib/dynomite/item/typecaster.rb +106 -0
- data/lib/dynomite/item/waiter_methods.rb +18 -0
- data/lib/dynomite/item/write/base.rb +15 -0
- data/lib/dynomite/item/write/delete_item.rb +14 -0
- data/lib/dynomite/item/write/put_item.rb +99 -0
- data/lib/dynomite/item/write/update_item.rb +73 -0
- data/lib/dynomite/item/write.rb +204 -0
- data/lib/dynomite/item.rb +113 -286
- data/lib/dynomite/migration/dsl/accessor.rb +19 -0
- data/lib/dynomite/migration/dsl/index/base.rb +42 -0
- data/lib/dynomite/migration/dsl/index/gsi.rb +59 -0
- data/lib/dynomite/migration/dsl/index/lsi.rb +27 -0
- data/lib/dynomite/migration/dsl/index.rb +72 -0
- data/lib/dynomite/migration/dsl/primary_key.rb +62 -0
- data/lib/dynomite/migration/dsl/provisioned_throughput.rb +38 -0
- data/lib/dynomite/migration/dsl.rb +89 -142
- data/lib/dynomite/migration/file_info.rb +28 -0
- data/lib/dynomite/migration/generator.rb +30 -16
- data/lib/dynomite/migration/helpers.rb +7 -0
- data/lib/dynomite/migration/internal/migrate/create_schema_migrations.rb +17 -0
- data/lib/dynomite/migration/internal/models/schema_migration.rb +6 -0
- data/lib/dynomite/migration/runner.rb +178 -0
- data/lib/dynomite/migration/templates/create_table.rb +7 -23
- data/lib/dynomite/migration/templates/delete_table.rb +7 -0
- data/lib/dynomite/migration/templates/update_table.rb +3 -18
- data/lib/dynomite/migration.rb +53 -10
- data/lib/dynomite/reserved_words.rb +13 -3
- data/lib/dynomite/seed.rb +12 -0
- data/lib/dynomite/types.rb +22 -0
- data/lib/dynomite/version.rb +1 -1
- data/lib/dynomite/waiter.rb +40 -0
- data/lib/dynomite.rb +11 -17
- data/lib/generators/application_item/application_item_generator.rb +30 -0
- data/lib/generators/application_item/templates/application_item.rb.tt +4 -0
- data/lib/jets/commands/dynamodb_command.rb +29 -0
- data/lib/jets/commands/help/generate.md +33 -0
- data/lib/jets/commands/help/migrate.md +3 -0
- metadata +201 -17
- data/docs/migrations/long-example.rb +0 -127
- data/docs/migrations/short-example.rb +0 -40
- data/lib/dynomite/db_config.rb +0 -121
- data/lib/dynomite/errors.rb +0 -15
- data/lib/dynomite/log.rb +0 -15
- data/lib/dynomite/migration/common.rb +0 -86
- data/lib/dynomite/migration/dsl/base_secondary_index.rb +0 -73
- data/lib/dynomite/migration/dsl/global_secondary_index.rb +0 -4
- data/lib/dynomite/migration/dsl/local_secondary_index.rb +0 -8
- 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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
if
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
params[:
|
|
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
|
-
|
|
129
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
#
|
|
159
|
-
#
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
189
|
-
@attribute_definitions = all_attrs
|
|
136
|
+
@attribute_definitions
|
|
190
137
|
end
|
|
191
138
|
|
|
192
|
-
|
|
193
|
-
#
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
211
|
-
|
|
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
|