scheman 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,267 @@
1
+ module Scheman
2
+ module Views
3
+ class Mysql < Base
4
+ def self.transform
5
+ @transform ||= Transform.new
6
+ end
7
+
8
+ # @param diff [Hash]
9
+ def initialize(diff)
10
+ @diff = diff
11
+ end
12
+
13
+ # @return [String]
14
+ def to_s
15
+ self.class.transform.apply(
16
+ root: @diff
17
+ )
18
+ end
19
+
20
+ class Transform < Parslet::Transform
21
+ rule(root: subtree(:root)) do
22
+ [
23
+ "BEGIN;",
24
+ "SET foreign_key_checks=0;",
25
+ root,
26
+ "SET foreign_key_checks=1;",
27
+ "COMMIT;",
28
+ ].join("\n\n") + "\n"
29
+ end
30
+
31
+ rule(
32
+ alter_tables: sequence(:alter_tables),
33
+ create_tables: sequence(:create_tables),
34
+ drop_tables: sequence(:drop_tables),
35
+ ) do
36
+ [
37
+ CreateTables.new(create_tables).to_s.presence,
38
+ AlterTables.new(alter_tables).to_s.presence,
39
+ DropTables.new(drop_tables).to_s.presence,
40
+ ].compact.join("\n\n")
41
+ end
42
+
43
+ rule(drop_table: subtree(:drop_table)) do
44
+ DropTable.new(drop_table)
45
+ end
46
+
47
+ rule(create_table: subtree(:create_table)) do
48
+ CreateTable.new(create_table)
49
+ end
50
+
51
+ rule(field: subtree(:field)) do
52
+ Field.new(field)
53
+ end
54
+
55
+ rule(qualifier: subtree(:qualifier)) do
56
+ Qualifier.new(qualifier)
57
+ end
58
+
59
+ rule(index: subtree(:index)) do
60
+ Index.new(index)
61
+ end
62
+
63
+ rule(add_field: subtree(:add_field)) do
64
+ AddField.new(add_field)
65
+ end
66
+
67
+ rule(drop_field: subtree(:drop_field)) do
68
+ DropField.new(drop_field)
69
+ end
70
+ end
71
+
72
+ class Node
73
+ def initialize(element)
74
+ @element = element
75
+ end
76
+ end
77
+
78
+ class Statements < Node
79
+ def to_s
80
+ @element.join("\n\n")
81
+ end
82
+ end
83
+
84
+ class AlterTables < Statements
85
+ end
86
+
87
+ class DropTables < Statements
88
+ end
89
+
90
+ class CreateTables < Statements
91
+ end
92
+
93
+ class DropTable < Node
94
+ def to_s
95
+ "DROP TABLE `#{table_name}`;"
96
+ end
97
+
98
+ private
99
+
100
+ def table_name
101
+ @element[:name]
102
+ end
103
+ end
104
+
105
+ class AlterTable < Node
106
+ def to_s
107
+ "TODO"
108
+ end
109
+ end
110
+
111
+ class AlterField < Node
112
+ private
113
+
114
+ def table_name
115
+ @element[:table_name]
116
+ end
117
+ end
118
+
119
+ class AddField < AlterField
120
+ def to_s
121
+ "ALTER TABLE `#{table_name}` ADD COLUMN #{field};"
122
+ end
123
+
124
+ private
125
+
126
+ def field
127
+ Field.new(@element)
128
+ end
129
+ end
130
+
131
+ class DropField < AlterField
132
+ def to_s
133
+ "ALTER TABLE `#{table_name}` DROP COLUMN `#{field_name}`;"
134
+ end
135
+
136
+ private
137
+
138
+ def field_name
139
+ @element[:name]
140
+ end
141
+ end
142
+
143
+ class CreateTable < Node
144
+ def to_s
145
+ str = ""
146
+ str << "CREATE TABLE `#{table_name}` (\n"
147
+ str << definitions.join(",\n").indent(2) + "\n"
148
+ str << ");"
149
+ end
150
+
151
+ private
152
+
153
+ def table_name
154
+ @element[:name]
155
+ end
156
+
157
+ def definitions
158
+ @element[:fields] + @element[:indices]
159
+ end
160
+ end
161
+
162
+ class Field < Node
163
+ def to_s
164
+ str = "`#{name}` #{type}"
165
+ str << "(#{values})" if has_values?
166
+ str << " #{qualifiers}" if has_qualifiers?
167
+ str
168
+ end
169
+
170
+ private
171
+
172
+ # @example
173
+ # "id"
174
+ def name
175
+ @element[:name]
176
+ end
177
+
178
+ # @example
179
+ # "INTEGER"
180
+ def type
181
+ @element[:type].upcase
182
+ end
183
+
184
+ def qualifiers
185
+ @element[:qualifiers].map(&:to_s).join(" ")
186
+ end
187
+
188
+ def values
189
+ @element[:values].join(", ")
190
+ end
191
+
192
+ def has_qualifiers?
193
+ !@element[:qualifiers].empty?
194
+ end
195
+
196
+ def has_values?
197
+ !@element[:values].empty?
198
+ end
199
+ end
200
+
201
+ class Qualifier < Node
202
+ def to_s
203
+ str = type
204
+ str << " #{value}" if has_value?
205
+ str
206
+ end
207
+
208
+ private
209
+
210
+ # @example
211
+ # "NOT NULL"
212
+ def type
213
+ @element[:type].upcase.gsub("_", " ")
214
+ end
215
+
216
+ # @example
217
+ # "utf8"
218
+ def value
219
+ @element[:value]
220
+ end
221
+
222
+ def has_value?
223
+ @element[:value]
224
+ end
225
+ end
226
+
227
+ class Index < Node
228
+ def to_s
229
+ "#{prefix} (`#{column}`)"
230
+ end
231
+
232
+ def primary_key?
233
+ !!@element[:primary]
234
+ end
235
+
236
+ def fulltext?
237
+ @element[:type] == "fulltext"
238
+ end
239
+
240
+ def spatial?
241
+ @element[:type] == "spatial"
242
+ end
243
+
244
+ # @example
245
+ # "id"
246
+ def column
247
+ @element[:column]
248
+ end
249
+
250
+ # @example
251
+ # "PRIMARY KEY"
252
+ def prefix
253
+ case
254
+ when primary_key?
255
+ "PRIMARY KEY"
256
+ when fulltext?
257
+ "FULLTEXT"
258
+ when spatial?
259
+ "SPATIAL"
260
+ else
261
+ "KEY"
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
267
+ end
data/lib/scheman.rb ADDED
@@ -0,0 +1,17 @@
1
+ require "active_support/core_ext/array/wrap"
2
+ require "active_support/core_ext/enumerable"
3
+ require "active_support/core_ext/hash/slice"
4
+ require "active_support/core_ext/object/blank"
5
+ require "active_support/core_ext/object/try"
6
+ require "active_support/core_ext/string/indent"
7
+ require "parslet"
8
+
9
+ require "scheman/diff"
10
+ require "scheman/errors"
11
+ require "scheman/parser_builder"
12
+ require "scheman/parsers/base"
13
+ require "scheman/parsers/mysql"
14
+ require "scheman/schema"
15
+ require "scheman/version"
16
+ require "scheman/views/base"
17
+ require "scheman/views/mysql"
data/scheman.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "scheman/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "scheman"
7
+ spec.version = Scheman::VERSION
8
+ spec.authors = ["Ryo Nakamura"]
9
+ spec.email = ["r7kamura@gmail.com"]
10
+ spec.summary = "Manage database schema based on schema definition file."
11
+ spec.homepage = "https://github.com/r7kamura/scheman"
12
+ spec.license = "MIT"
13
+
14
+ spec.files = `git ls-files -z`.split("\x0")
15
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.add_dependency "activesupport"
20
+ spec.add_dependency "parslet"
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "pry"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "rspec", "2.14.1"
25
+ end
@@ -0,0 +1,76 @@
1
+ require "spec_helper"
2
+
3
+ describe Scheman::Diff do
4
+ let(:instance) do
5
+ described_class.new(args)
6
+ end
7
+
8
+ let(:args) do
9
+ {
10
+ before: before_schema,
11
+ after: after_schema,
12
+ type: type,
13
+ }
14
+ end
15
+
16
+ let(:type) do
17
+ "mysql"
18
+ end
19
+
20
+ let(:before_schema) do
21
+ <<-EOS.strip_heredoc
22
+ CREATE TABLE `table1` (
23
+ `column1` INTEGER NOT NULL AUTO INCREMENT,
24
+ PRIMARY KEY (`column1`)
25
+ );
26
+
27
+ CREATE TABLE `table2` (
28
+ `column1` INTEGER NOT NULL AUTO INCREMENT,
29
+ PRIMARY KEY (`column1`)
30
+ );
31
+ EOS
32
+ end
33
+
34
+ let(:after_schema) do
35
+ <<-EOS.strip_heredoc
36
+ CREATE TABLE `table1` (
37
+ `column2` VARCHAR(255) NOT NULL,
38
+ PRIMARY KEY (`column1`)
39
+ );
40
+
41
+ CREATE TABLE `table3` (
42
+ `column1` INTEGER NOT NULL AUTO INCREMENT,
43
+ PRIMARY KEY (`column1`)
44
+ );
45
+ EOS
46
+ end
47
+
48
+ describe "#to_s" do
49
+ subject do
50
+ instance.to_s
51
+ end
52
+
53
+ it "returns a diff in SQL" do
54
+ should == <<-EOS.strip_heredoc
55
+ BEGIN;
56
+
57
+ SET foreign_key_checks=0;
58
+
59
+ CREATE TABLE `table3` (
60
+ `column1` INTEGER NOT NULL AUTO INCREMENT,
61
+ PRIMARY KEY (`column1`)
62
+ );
63
+
64
+ ALTER TABLE `table1` ADD COLUMN `column2` VARCHAR(255) NOT NULL;
65
+
66
+ ALTER TABLE `table1` DROP COLUMN `column1`;
67
+
68
+ DROP TABLE `table2`;
69
+
70
+ SET foreign_key_checks=1;
71
+
72
+ COMMIT;
73
+ EOS
74
+ end
75
+ end
76
+ end