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
|
@@ -3,8 +3,6 @@ require "active_support/core_ext/string"
|
|
|
3
3
|
class Dynomite::Migration
|
|
4
4
|
# jets dynamodb:generate posts --partition-key id:string
|
|
5
5
|
class Generator
|
|
6
|
-
include Dynomite::DbConfig
|
|
7
|
-
|
|
8
6
|
attr_reader :migration_name, :table_name
|
|
9
7
|
def initialize(migration_name, options)
|
|
10
8
|
@migration_name = migration_name
|
|
@@ -12,21 +10,28 @@ class Dynomite::Migration
|
|
|
12
10
|
end
|
|
13
11
|
|
|
14
12
|
def generate
|
|
15
|
-
puts "Generating migration
|
|
16
|
-
return if
|
|
13
|
+
puts "Generating migration" unless @options[:quiet]
|
|
14
|
+
return if ENV['NOOP']
|
|
17
15
|
create_migration
|
|
18
16
|
end
|
|
19
17
|
|
|
20
18
|
def create_migration
|
|
21
19
|
FileUtils.mkdir_p(File.dirname(migration_path))
|
|
22
20
|
IO.write(migration_path, migration_code)
|
|
23
|
-
|
|
24
|
-
puts "
|
|
21
|
+
pretty_migration_path = migration_path.sub(/^\.\//,'') # remove leading ./
|
|
22
|
+
puts "Migration file created: #{pretty_migration_path}\nTo run:\n\n"
|
|
23
|
+
command = File.basename($0)
|
|
24
|
+
if command == "jets"
|
|
25
|
+
puts " #{command} dynamodb:migrate"
|
|
26
|
+
else
|
|
27
|
+
puts " #{command} migrate"
|
|
28
|
+
end
|
|
29
|
+
puts
|
|
25
30
|
end
|
|
26
31
|
|
|
27
32
|
def migration_code
|
|
28
|
-
path = File.expand_path("../templates/#{
|
|
29
|
-
|
|
33
|
+
path = File.expand_path("../templates/#{action}.rb", __FILE__)
|
|
34
|
+
Dynomite::Erb.result(path,
|
|
30
35
|
migration_class_name: migration_class_name,
|
|
31
36
|
table_name: table_name,
|
|
32
37
|
partition_key: @options[:partition_key],
|
|
@@ -35,30 +40,39 @@ class Dynomite::Migration
|
|
|
35
40
|
)
|
|
36
41
|
end
|
|
37
42
|
|
|
38
|
-
def
|
|
39
|
-
|
|
43
|
+
def action
|
|
44
|
+
# optoins[:table_action] is old and deprecated
|
|
45
|
+
action = @options[:action] || @options[:table_action] || conventional_action
|
|
46
|
+
case action
|
|
47
|
+
when /create/
|
|
48
|
+
"create_table"
|
|
49
|
+
when /delete/
|
|
50
|
+
"delete_table"
|
|
51
|
+
else
|
|
52
|
+
"update_table" # default and fallback
|
|
53
|
+
end
|
|
40
54
|
end
|
|
41
55
|
|
|
42
|
-
def
|
|
43
|
-
@migration_name.
|
|
56
|
+
def conventional_action
|
|
57
|
+
@migration_name.split("_").first
|
|
44
58
|
end
|
|
45
59
|
|
|
46
60
|
def table_name
|
|
47
|
-
@options[:table_name] || conventional_table_name
|
|
61
|
+
@options[:table_name] || conventional_table_name || "TABLE_NAME"
|
|
48
62
|
end
|
|
49
63
|
|
|
50
64
|
# create_posts => posts
|
|
51
65
|
# update_posts => posts
|
|
52
66
|
def conventional_table_name
|
|
53
|
-
@migration_name.sub(/^(create|update)_/, '')
|
|
67
|
+
@migration_name.sub(/^(create|update|delete)_/, '')
|
|
54
68
|
end
|
|
55
69
|
|
|
56
70
|
def migration_class_name
|
|
57
|
-
"#{@migration_name}
|
|
71
|
+
"#{@migration_name}".camelize # doesnt include timestamp
|
|
58
72
|
end
|
|
59
73
|
|
|
60
74
|
def migration_path
|
|
61
|
-
"#{Dynomite.
|
|
75
|
+
"#{Dynomite.root}/dynamodb/migrate/#{timestamp}-#{@migration_name}.rb"
|
|
62
76
|
end
|
|
63
77
|
|
|
64
78
|
def timestamp
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class CreateSchemaMigrations < Dynomite::Migration
|
|
2
|
+
include Dynomite::Client
|
|
3
|
+
|
|
4
|
+
def up
|
|
5
|
+
create_table :schema_migrations do |t|
|
|
6
|
+
t.partition_key "version:number" # required
|
|
7
|
+
t.billing_mode "PAY_PER_REQUEST"
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def table_exist?(full_table_name)
|
|
12
|
+
client.describe_table(table_name: full_table_name)
|
|
13
|
+
true
|
|
14
|
+
rescue Aws::DynamoDB::Errors::ResourceNotFoundException
|
|
15
|
+
false
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
require "timeout"
|
|
2
|
+
require_relative "internal/migrate/create_schema_migrations"
|
|
3
|
+
require_relative "internal/models/schema_migration"
|
|
4
|
+
|
|
5
|
+
class Dynomite::Migration
|
|
6
|
+
class Runner
|
|
7
|
+
include Dynomite::Item::WaiterMethods
|
|
8
|
+
|
|
9
|
+
def initialize(options={})
|
|
10
|
+
@options = options
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def run
|
|
14
|
+
puts "Running Dynomite migrations"
|
|
15
|
+
ensure_schema_migrations_exist!
|
|
16
|
+
clear_error_schema_migrations! if ENV['CLEAR_ERRORS']
|
|
17
|
+
check_for_migration_errors!
|
|
18
|
+
Dynomite::Migration::FileInfo.all_files.each do |path|
|
|
19
|
+
migrate(path)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def migrate(path)
|
|
24
|
+
# load migration class definition
|
|
25
|
+
# CreatePosts#up (up is called below)
|
|
26
|
+
file_info = FileInfo.new(path)
|
|
27
|
+
|
|
28
|
+
schema_migration = SchemaMigration.find_by(version: file_info.version)
|
|
29
|
+
if schema_migration
|
|
30
|
+
teriminal_statuses = %w[complete error]
|
|
31
|
+
if teriminal_statuses.include?(schema_migration.status)
|
|
32
|
+
return
|
|
33
|
+
else
|
|
34
|
+
action = with_timeout(message: "Timed out. You must respond within 60s.") do
|
|
35
|
+
uncompleted_migration_prompt(file_info, schema_migration)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
case action
|
|
41
|
+
when :skip
|
|
42
|
+
return
|
|
43
|
+
when :delete
|
|
44
|
+
schema_migration.delete
|
|
45
|
+
when :completed
|
|
46
|
+
schema_migration.status = "completed"
|
|
47
|
+
schema_migration.save
|
|
48
|
+
return
|
|
49
|
+
when :exit
|
|
50
|
+
puts "Exiting"
|
|
51
|
+
exit
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
puts "Running migration: #{file_info.pretty_path}"
|
|
55
|
+
load path
|
|
56
|
+
|
|
57
|
+
# INSERT schema_migration table - in_progress
|
|
58
|
+
unless schema_migration
|
|
59
|
+
schema_migration = SchemaMigration.new(version: file_info.version, status: "in_progress", path: file_info.pretty_path)
|
|
60
|
+
schema_migration.save
|
|
61
|
+
end
|
|
62
|
+
start_time = Time.now
|
|
63
|
+
|
|
64
|
+
# Run actual migration
|
|
65
|
+
migration_class = file_info.migration_class
|
|
66
|
+
error_message = nil
|
|
67
|
+
begin
|
|
68
|
+
# Runs migration up command. Example:
|
|
69
|
+
# CreatePosts#up
|
|
70
|
+
# Migration#create_table
|
|
71
|
+
# Migration#execute (wait happens here)
|
|
72
|
+
migration_class.new.up # wait happens within create_table or update_table
|
|
73
|
+
rescue Aws::DynamoDB::Errors::ServiceError => error
|
|
74
|
+
puts "Unable to #{@method_name.to_s.gsub('_',' ')}: #{error.message}".color(:red)
|
|
75
|
+
error_message = error.message
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# UPDATE schema_migrations table - complete status
|
|
79
|
+
if error_message
|
|
80
|
+
schema_migration.status = "error"
|
|
81
|
+
schema_migration.error_message = error_message
|
|
82
|
+
else
|
|
83
|
+
schema_migration.status = "complete"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
schema_migration.time_took = (Time.now - start_time).to_i
|
|
87
|
+
schema_migration.save
|
|
88
|
+
# schema_migration.delete # HACK
|
|
89
|
+
puts "Time took: #{pretty_time_took(schema_migration.time_took)}"
|
|
90
|
+
exit 1 if error_message # otherwise continue to next migration file
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def with_timeout(options={}, &block)
|
|
94
|
+
seconds = options[:seconds] || 60
|
|
95
|
+
message = options[:message] || "Timed out after #{seconds}s."
|
|
96
|
+
Timeout::timeout(seconds, &block)
|
|
97
|
+
rescue Timeout::Error => e
|
|
98
|
+
puts "#{e.class}: #{e.message}"
|
|
99
|
+
puts message
|
|
100
|
+
exit 1
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def uncompleted_migration_prompt(file_info, schema_migration)
|
|
104
|
+
choice = nil
|
|
105
|
+
until %w[s d c e].include?(choice)
|
|
106
|
+
puts(<<~EOL)
|
|
107
|
+
The #{file_info.pretty_path} migration status is incomplete. Status: #{schema_migration.status}
|
|
108
|
+
This can happen and if it was interrupted by a CTRL-C.
|
|
109
|
+
Please check the migration to help determine what to do next.
|
|
110
|
+
|
|
111
|
+
Options:
|
|
112
|
+
|
|
113
|
+
s - skip and continue. leaves schema_migrations item as-is
|
|
114
|
+
d - delete and continue. deletes the schema_migrations item
|
|
115
|
+
c - mark as successful completed and continue. updates the schema_migrations item as completed.
|
|
116
|
+
e - exit
|
|
117
|
+
|
|
118
|
+
EOL
|
|
119
|
+
print "Choose an option (s/d/c/e): "
|
|
120
|
+
choice = $stdin.gets.strip
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
map = {
|
|
124
|
+
"s" => :skip,
|
|
125
|
+
"d" => :delete,
|
|
126
|
+
"c" => :completed,
|
|
127
|
+
"e" => :exit,
|
|
128
|
+
}
|
|
129
|
+
map[choice]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def ensure_schema_migrations_exist!
|
|
133
|
+
migration = CreateSchemaMigrations.new
|
|
134
|
+
return if migration.table_exist?(SchemaMigration.table_name)
|
|
135
|
+
puts "Creating #{SchemaMigration.table_name} table for the first time"
|
|
136
|
+
migration.up
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def clear_error_schema_migrations!
|
|
140
|
+
SchemaMigration.warn_on_scan(false).where(status: "error").each do |schema_migration|
|
|
141
|
+
schema_migration.delete
|
|
142
|
+
puts "Deleted error schema_migration: #{schema_migration.path}"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def check_for_migration_errors!
|
|
147
|
+
errors = SchemaMigration.warn_on_scan(false).where(status: "error")
|
|
148
|
+
errors_info = SchemaMigration.warn_on_scan(false).where(status: "error").map do |schema_migration|
|
|
149
|
+
" #{schema_migration.path} - #{schema_migration.error_message}"
|
|
150
|
+
end.join("\n")
|
|
151
|
+
if errors.count > 0
|
|
152
|
+
puts <<~EOL
|
|
153
|
+
Found error migrations. Please review the migration erors fix before continuing.
|
|
154
|
+
You can clear them out manually by deleting them from the #{SchemaMigration.table_name} table.
|
|
155
|
+
|
|
156
|
+
#{errors_info}
|
|
157
|
+
|
|
158
|
+
You can also clear them with the following command:
|
|
159
|
+
|
|
160
|
+
CLEAR_ERRORS=1 jets dynamodb:migrate
|
|
161
|
+
|
|
162
|
+
EOL
|
|
163
|
+
exit 1
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# http://stackoverflow.com/questions/4175733/convert-duration-to-hoursminutesseconds-or-similar-in-rails-3-or-ruby
|
|
168
|
+
def pretty_time_took(total_seconds)
|
|
169
|
+
minutes = (total_seconds / 60) % 60
|
|
170
|
+
seconds = total_seconds % 60
|
|
171
|
+
if total_seconds < 60
|
|
172
|
+
"#{seconds.to_i}s"
|
|
173
|
+
else
|
|
174
|
+
"#{minutes.to_i}m #{seconds.to_i}s"
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
@@ -1,33 +1,17 @@
|
|
|
1
1
|
class <%= @migration_class_name %> < Dynomite::Migration
|
|
2
2
|
def up
|
|
3
3
|
create_table :<%= @table_name %> do |t|
|
|
4
|
-
t.partition_key
|
|
4
|
+
t.partition_key :<%= @partition_key %>
|
|
5
5
|
<% if @sort_key # so extra spaces are not added when generated -%>
|
|
6
6
|
t.sort_key "<%= @sort_key %>" # optional
|
|
7
7
|
<% end -%>
|
|
8
|
-
|
|
9
|
-
t.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# t.key_schema([
|
|
14
|
-
# {attribute_name: "id", :key_type=>"HASH"},
|
|
15
|
-
# {attribute_name: "created_at", :key_type=>"RANGE"}
|
|
16
|
-
# ])
|
|
17
|
-
# t.attribute_definitions([
|
|
18
|
-
# {attribute_name: "id", attribute_type: "N"},
|
|
19
|
-
# {attribute_name: "created_at", attribute_type: "S"}
|
|
20
|
-
# ])
|
|
21
|
-
|
|
22
|
-
# other ways to set provisioned_throughput
|
|
23
|
-
# t.provisioned_throughput(:read, 10)
|
|
24
|
-
# t.provisioned_throughput(:write, 10)
|
|
25
|
-
# t.provisioned_throughput(
|
|
26
|
-
# read_capacity_units: 5,
|
|
27
|
-
# write_capacity_units: 5
|
|
28
|
-
# )
|
|
8
|
+
<% if %w[id id:string].include?(@partition_key) -%>
|
|
9
|
+
t.add_gsi :updated_at
|
|
10
|
+
<% else %>
|
|
11
|
+
t.add_gsi :id
|
|
12
|
+
<% end -%>
|
|
29
13
|
end
|
|
30
14
|
end
|
|
31
15
|
end
|
|
32
16
|
|
|
33
|
-
# More examples: https://
|
|
17
|
+
# More examples: https://rubyonjets.com/docs/database/dynamodb/migration/
|
|
@@ -1,26 +1,11 @@
|
|
|
1
1
|
class <%= @migration_class_name %> < Dynomite::Migration
|
|
2
2
|
def up
|
|
3
3
|
update_table :<%= @table_name %> do |t|
|
|
4
|
-
t.
|
|
5
|
-
i.partition_key "<%= @partition_key %>" # required
|
|
6
|
-
<% if @sort_key # so extra spaces are not added when generated -%>
|
|
7
|
-
t.sort_key "<%= @sort_key %>" # optional
|
|
8
|
-
<% end -%>
|
|
4
|
+
t.add_gsi(partition_key: "<%= @partition_key %>", sort_key: "<%= @sort_key || 'updated_at' %>")
|
|
9
5
|
|
|
10
|
-
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
# Examples:
|
|
14
|
-
# t.gsi(:update, "update-me-index") do |i|
|
|
15
|
-
# i.provisioned_throughput(10)
|
|
16
|
-
# end
|
|
17
|
-
|
|
18
|
-
# t.gsi(:delete, "delete-me-index")
|
|
19
|
-
|
|
20
|
-
# Must use :create, :update, :delete one at a time in separate migration files.
|
|
21
|
-
# DynamoDB imposes this.
|
|
6
|
+
# t.remove_gsi(partition_key: "<%= @partition_key %>", sort_key: "<%= @sort_key || 'updated_at' %>")
|
|
22
7
|
end
|
|
23
8
|
end
|
|
24
9
|
end
|
|
25
10
|
|
|
26
|
-
# More examples: https://
|
|
11
|
+
# More examples: https://rubyonjets.com/docs/database/dynamodb/migration/
|
data/lib/dynomite/migration.rb
CHANGED
|
@@ -1,27 +1,70 @@
|
|
|
1
1
|
module Dynomite
|
|
2
2
|
class Migration
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
include Item::WaiterMethods
|
|
4
|
+
include Dynomite::Client
|
|
5
|
+
include Helpers
|
|
6
6
|
|
|
7
7
|
def up
|
|
8
8
|
puts "Should defined an up method for your migration: #{self.class.name}"
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def create_table(table_name, &block)
|
|
12
|
-
|
|
12
|
+
execute(table_name, :create_table, &block)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def update_table(table_name, &block)
|
|
16
|
-
|
|
16
|
+
execute(table_name, :update_table, &block)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
def delete_table(table_name, &block)
|
|
20
|
+
execute(table_name, :delete_table, &block)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add_gsi(table_name, *args)
|
|
24
|
+
update_table(table_name) do |t|
|
|
25
|
+
t.add_gsi(*args)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def remove_gsi(table_name, *args)
|
|
30
|
+
update_table(table_name) do |t|
|
|
31
|
+
t.remove_gsi(*args)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def update_gsi(table_name, *args)
|
|
36
|
+
update_table(table_name) do |t|
|
|
37
|
+
t.update_gsi(*args)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def update_time_to_live(table_name, time_to_live_specification={})
|
|
42
|
+
table_name = table_name_with_namespace(table_name)
|
|
43
|
+
client.update_time_to_live(
|
|
44
|
+
table_name: table_name,
|
|
45
|
+
time_to_live_specification: time_to_live_specification
|
|
46
|
+
)
|
|
47
|
+
# fast enough of an operation, no need to wait
|
|
48
|
+
end
|
|
49
|
+
alias update_ttl update_time_to_live
|
|
50
|
+
|
|
19
51
|
private
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
52
|
+
# execute with dsl params
|
|
53
|
+
# table name is short name and params[:table_name] is full namespaced name
|
|
54
|
+
def execute(table_name, method_name, &block)
|
|
55
|
+
params = Dsl.new(method_name, table_name, &block).params
|
|
56
|
+
|
|
57
|
+
puts "Running #{method_name} with:"
|
|
58
|
+
pp params
|
|
59
|
+
resp = client.send(method_name, params)
|
|
60
|
+
namespaced_table_name = params[:table_name] # full: demo-dev_posts short: posts
|
|
61
|
+
puts "DynamoDB Table: #{namespaced_table_name} Status: #{resp.table_description.table_status}"
|
|
62
|
+
|
|
63
|
+
if method_name.to_sym == :delete_table # already sym, to_sym just in case
|
|
64
|
+
waiter.wait_for_delete(params[:table_name])
|
|
65
|
+
else # create_table or update_table
|
|
66
|
+
waiter.wait(params[:table_name])
|
|
67
|
+
end
|
|
25
68
|
end
|
|
26
69
|
end
|
|
27
70
|
end
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
module Dynomite
|
|
2
2
|
RESERVED_WORDS = %w[
|
|
3
3
|
as_json
|
|
4
|
-
attrs
|
|
5
4
|
attributes
|
|
6
|
-
|
|
5
|
+
attrs
|
|
7
6
|
columns
|
|
7
|
+
destroy
|
|
8
|
+
fields
|
|
8
9
|
find
|
|
9
10
|
getter
|
|
11
|
+
hash_key
|
|
12
|
+
hash_key_field
|
|
13
|
+
key_schema
|
|
10
14
|
new_record
|
|
11
15
|
param_name
|
|
12
16
|
partition_key
|
|
17
|
+
partition_key_field
|
|
18
|
+
range_key
|
|
19
|
+
range_key_field
|
|
13
20
|
replace
|
|
14
|
-
|
|
21
|
+
save
|
|
15
22
|
scan
|
|
23
|
+
setter
|
|
24
|
+
sort_key_field
|
|
25
|
+
sort_key_field_field
|
|
16
26
|
table_name
|
|
17
27
|
].freeze
|
|
18
28
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Dynomite
|
|
2
|
+
module Types
|
|
3
|
+
TYPE_MAP = {
|
|
4
|
+
string: 'S',
|
|
5
|
+
number: 'N',
|
|
6
|
+
binary: 'B',
|
|
7
|
+
boolean: 'BOOL',
|
|
8
|
+
null: 'NULL',
|
|
9
|
+
map: 'M',
|
|
10
|
+
list: 'L',
|
|
11
|
+
string_set: 'SS',
|
|
12
|
+
number_set: 'NS',
|
|
13
|
+
binary_set: 'BS',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# https://rubyonjets.com/docs/database/dynamodb/types/
|
|
17
|
+
# https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypeDescriptors
|
|
18
|
+
def type_map(attribute_type)
|
|
19
|
+
TYPE_MAP[attribute_type.to_s.downcase.to_sym] || attribute_type
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/dynomite/version.rb
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Dynomite
|
|
2
|
+
class Waiter
|
|
3
|
+
include Client
|
|
4
|
+
|
|
5
|
+
def wait(table_name)
|
|
6
|
+
logger.info "Waiting for #{table_name} table to be ready."
|
|
7
|
+
statuses = [:initial]
|
|
8
|
+
until statuses.all? { |s| s == "ACTIVE" } do
|
|
9
|
+
resp = client.describe_table(table_name: table_name)
|
|
10
|
+
|
|
11
|
+
table = resp.table
|
|
12
|
+
# table_status: CREATING UPDATING DELETING ACTIVE INACCESSIBLE_ENCRYPTION_CREDENTIALS ARCHIVING ARCHIVED
|
|
13
|
+
table_status = table.table_status
|
|
14
|
+
statuses = [table_status]
|
|
15
|
+
# index_status: CREATING UPDATING DELETING ACTIVE
|
|
16
|
+
indexes = table.global_secondary_indexes || []
|
|
17
|
+
statuses += indexes.map { |i| i.index_status }
|
|
18
|
+
print '.'
|
|
19
|
+
sleep pause_time
|
|
20
|
+
end
|
|
21
|
+
puts
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def wait_for_delete(table_name)
|
|
25
|
+
logger.info "Waiting for #{table_name} table to be deleted."
|
|
26
|
+
begin
|
|
27
|
+
client.describe_table(table_name: table_name)
|
|
28
|
+
print '.'
|
|
29
|
+
sleep pause_time
|
|
30
|
+
rescue Aws::DynamoDB::Errors::ResourceNotFoundException => e
|
|
31
|
+
end
|
|
32
|
+
puts
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
def pause_time
|
|
37
|
+
1
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/dynomite.rb
CHANGED
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
$:.unshift(File.expand_path("../", __FILE__))
|
|
2
|
+
require "active_support"
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
require "active_support/core_ext/hash"
|
|
5
|
+
require "active_support/core_ext/string"
|
|
2
6
|
require "dynomite/version"
|
|
7
|
+
require "memoist"
|
|
3
8
|
require "rainbow/ext/string"
|
|
4
9
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
'string' => 'S',
|
|
8
|
-
'number' => 'N',
|
|
9
|
-
'binary' => 'B',
|
|
10
|
-
's' => 'S',
|
|
11
|
-
'n' => 'N',
|
|
12
|
-
'b' => 'B',
|
|
13
|
-
}
|
|
10
|
+
require "dynomite/autoloader"
|
|
11
|
+
Dynomite::Autoloader.setup
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
autoload :Dsl, "dynomite/dsl"
|
|
17
|
-
autoload :DbConfig, "dynomite/db_config"
|
|
18
|
-
autoload :Item, "dynomite/item"
|
|
19
|
-
autoload :Core, "dynomite/core"
|
|
20
|
-
autoload :Erb, "dynomite/erb"
|
|
21
|
-
autoload :Log, "dynomite/log"
|
|
22
|
-
autoload :Errors, "dynomite/errors"
|
|
13
|
+
require "dynomite/reserved_words"
|
|
23
14
|
|
|
15
|
+
module Dynomite
|
|
24
16
|
extend Core
|
|
25
17
|
end
|
|
18
|
+
|
|
19
|
+
require "dynomite/engine" if defined?(Jets)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
|
|
5
|
+
# Note: Currently have to use the Rails namespace to allow the Rails generator lookup to work.
|
|
6
|
+
# Would like to figure how to use Dynomite as the namespace instead
|
|
7
|
+
# Usage:
|
|
8
|
+
# jets generate application_item
|
|
9
|
+
module Rails
|
|
10
|
+
module Generators
|
|
11
|
+
class ApplicationItemGenerator < Rails::Generators::Base
|
|
12
|
+
source_root File.expand_path("templates", __dir__)
|
|
13
|
+
|
|
14
|
+
# FIXME: Change this file to a symlink once RubyGems 2.5.0 is required.
|
|
15
|
+
def create_application_item
|
|
16
|
+
template "application_item.rb", application_item_file_name
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
def application_item_file_name
|
|
21
|
+
@application_item_file_name ||=
|
|
22
|
+
if namespaced?
|
|
23
|
+
"app/models/#{namespaced_path}/application_item.rb"
|
|
24
|
+
else
|
|
25
|
+
"app/models/application_item.rb"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|