dynomite 1.2.7 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|