create_table 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +22 -0
  4. data/README.md +82 -0
  5. data/Rakefile +89 -0
  6. data/create_table.gemspec +28 -0
  7. data/features/parsing.feature +736 -0
  8. data/features/step_definitions/create_table/parsing_steps.rb +60 -0
  9. data/features/support/env.rb +28 -0
  10. data/lib/create_table.rb +2545 -0
  11. data/lib/create_table.rl +185 -0
  12. data/lib/create_table/column.rb +4920 -0
  13. data/lib/create_table/column.rl +338 -0
  14. data/lib/create_table/column_name_based_collection.rb +26 -0
  15. data/lib/create_table/common.rb +5 -0
  16. data/lib/create_table/common.rl +13 -0
  17. data/lib/create_table/index.rb +315 -0
  18. data/lib/create_table/index.rl +91 -0
  19. data/lib/create_table/mysql_reserved.txt +226 -0
  20. data/lib/create_table/parser.rb +36 -0
  21. data/lib/create_table/pg_reserved.txt +742 -0
  22. data/lib/create_table/unique.rb +7 -0
  23. data/lib/create_table/version.rb +3 -0
  24. data/spec/create_table_spec.rb +88 -0
  25. data/spec/generating/autoincrement_primary_key.yml +74 -0
  26. data/spec/generating/backticks.yml +22 -0
  27. data/spec/generating/charset.yml +21 -0
  28. data/spec/generating/comments.yml +34 -0
  29. data/spec/generating/doublequoted.yml +25 -0
  30. data/spec/generating/index.yml +37 -0
  31. data/spec/generating/named_unique.yml +37 -0
  32. data/spec/generating/reservedwords.yml +23 -0
  33. data/spec/generating/string_primary_key.yml +35 -0
  34. data/spec/generating/temporary.yml +25 -0
  35. data/spec/generating/unique.yml +35 -0
  36. data/spec/generating/unquoted.yml +25 -0
  37. data/spec/generating_spec.rb +27 -0
  38. data/spec/spec_helper.rb +66 -0
  39. metadata +263 -0
@@ -0,0 +1,338 @@
1
+ # MAKE SURE YOU'RE EDITING THE .RL FILE !!!
2
+
3
+ =begin
4
+ %%{
5
+ machine parser;
6
+
7
+ include "common.rl";
8
+
9
+ action StartName {
10
+ start_name = p
11
+ }
12
+ action EndName {
13
+ self.name = read(data, start_name, p)
14
+ }
15
+
16
+ action StartDataType {
17
+ start_data_type = p
18
+ }
19
+
20
+ action MarkNotNull {
21
+ mark_not_null = p - 4
22
+ }
23
+ action Null {
24
+ start_default ||= nil
25
+ unless start_default # FIXME could this be excluded by the state machine instead?
26
+ mark_not_null ||= nil
27
+ if mark_not_null
28
+ self.null = false
29
+ end_data_type ||= mark_not_null
30
+ else
31
+ self.null = true
32
+ end_data_type ||= p - 4
33
+ end
34
+ end
35
+ }
36
+
37
+ action MarkDefault {
38
+ mark_default = p - 1
39
+ }
40
+ action StartDefault {
41
+ start_default = p
42
+ end_data_type ||= mark_default
43
+ }
44
+ action EndQuotedDefault {
45
+ end_default = p
46
+ self.default = read_quoted(data, start_default, end_default)
47
+ ended_quoted_default = true
48
+ }
49
+
50
+ action MarkPrimaryKey {
51
+ mark_primary_key = p - 1
52
+ }
53
+ action PrimaryKey {
54
+ primary_key!
55
+ end_data_type ||= mark_primary_key
56
+ end_default ||= mark_primary_key
57
+ }
58
+
59
+ action MarkUnique {
60
+ mark_unique = p - 5
61
+ }
62
+ action Unique {
63
+ unique!
64
+ end_data_type ||= mark_unique
65
+ end_default ||= mark_unique
66
+ }
67
+
68
+ action MarkAutoincrement {
69
+ mark_autoincrement = p - 1
70
+ }
71
+ action Autoincrement {
72
+ autoincrement!
73
+ end_data_type ||= mark_autoincrement
74
+ end_default ||= mark_autoincrement
75
+ }
76
+
77
+ action MarkCollate {
78
+ mark_collate = p - 1
79
+ }
80
+ action StartCollate {
81
+ start_collate = p
82
+ }
83
+ action EndCollate {
84
+ self.collate = read_quoted(data, start_collate, p)
85
+ end_data_type ||= mark_collate
86
+ end_default ||= mark_collate
87
+ }
88
+
89
+ action MarkCharset {
90
+ mark_charset = p - 5
91
+ }
92
+ action StartCharset {
93
+ start_charset = p
94
+ }
95
+ action EndCharset {
96
+ self.charset = read_quoted(data, start_charset, p)
97
+ end_data_type ||= mark_charset
98
+ end_default ||= mark_charset
99
+ }
100
+
101
+ action BitterEnd {
102
+ # EndUnquotedDefault
103
+ start_default ||= nil
104
+ ended_quoted_default ||= nil
105
+ if start_default and not ended_quoted_default
106
+ end_default ||= p
107
+ self.default = read_quoted(data, start_default, end_default)
108
+ end
109
+ # EndDataType
110
+ end_data_type ||= p
111
+ self.data_type = read(data, start_data_type, end_data_type)
112
+ }
113
+
114
+ name = quote_ident ident >StartName %EndName quote_ident;
115
+
116
+ primary_key = ('primary'i space+ 'key'i) >MarkPrimaryKey @PrimaryKey;
117
+
118
+ autoincrement = ('auto'i '_'? 'increment'i) >MarkAutoincrement @Autoincrement;
119
+
120
+ unique = 'uniq'i %MarkUnique 'ue'i @Unique;
121
+
122
+ quoted_default_value = quote_value (not_quote_or_escape | escaped_something | quoted_quote)+ >StartDefault %EndQuotedDefault quote_value;
123
+ unquoted_default_value = (alnum any*) >StartDefault; # space*;
124
+ default = ('default'i space+) >MarkDefault (quoted_default_value | unquoted_default_value);
125
+
126
+ _null = ('not'i %MarkNotNull)? space+ 'null'i @Null;
127
+
128
+ quoted_charset_value = quote_value (not_quote_or_escape | escaped_something | quoted_quote)+ >StartCharset %EndCharset quote_value;
129
+ unquoted_charset_value = (any - space)+ >StartCharset %EndCharset space*;
130
+ charset = 'char'i %MarkCharset ('set'i | ('acter'i space+ 'set'i)) space+ (quoted_charset_value | unquoted_charset_value);
131
+
132
+ quoted_collate_value = quote_value (not_quote_or_escape | escaped_something | quoted_quote)+ >StartCollate %EndCollate quote_value;
133
+ unquoted_collate_value = (any - space)+ >StartCollate %EndCollate space*;
134
+ collate = ('collate'i space+) >MarkCollate (quoted_collate_value | unquoted_collate_value);
135
+
136
+ data_type = any+;
137
+
138
+ main := space* name space+ data_type >StartDataType _null? default? primary_key? unique? autoincrement? charset? collate? %BitterEnd;
139
+ }%%
140
+ =end
141
+
142
+ class CreateTable
143
+ class Column
144
+ class << self
145
+ def munge_data_type(original, ansi)
146
+ if original =~ /\((.*)\)/
147
+ [ ansi, '(', $1, ')' ].join
148
+ else
149
+ ansi
150
+ end
151
+ end
152
+ end
153
+
154
+ BLANK_STRING = ''
155
+
156
+ include Parser
157
+
158
+ attr_reader :parent
159
+ attr_reader :name
160
+ attr_reader :data_type
161
+ attr_writer :default
162
+ attr_writer :null
163
+ attr_accessor :charset
164
+ attr_accessor :collate
165
+
166
+ def initialize(parent)
167
+ @parent = parent
168
+ parent.columns << self
169
+ end
170
+
171
+ def name=(name)
172
+ @name = name
173
+ end
174
+
175
+ def data_type=(str)
176
+ str = str.upcase
177
+ case str
178
+ when /SERIAL/
179
+ autoincrement!
180
+ @data_type = 'INTEGER'
181
+ when 'INT IDENTITY(1,1)'
182
+ # TODO is this correct?
183
+ autoincrement!
184
+ primary_key!
185
+ @data_type = 'INTEGER'
186
+ when 'TINYINT(1)'
187
+ @data_type = 'BOOLEAN'
188
+ when 'INT(11)'
189
+ @data_type = 'INTEGER'
190
+ when /\bINT\b/
191
+ @data_type = Column.munge_data_type str, 'INTEGER'
192
+ when /\bVARCHAR\b/
193
+ @data_type = Column.munge_data_type str, 'CHARACTER VARYING'
194
+ else
195
+ @data_type = str
196
+ end
197
+ end
198
+
199
+ def default
200
+ if defined?(@default)
201
+ @default
202
+ elsif primary_key and data_type =~ /char/i
203
+ BLANK_STRING
204
+ end
205
+ end
206
+
207
+ def null
208
+ if defined?(@null)
209
+ @null
210
+ elsif primary_key
211
+ false
212
+ else
213
+ true
214
+ end
215
+ end
216
+
217
+ alias :allow_null :null
218
+
219
+ def primary_key
220
+ parent.primary_key == self
221
+ end
222
+
223
+ def primary_key!
224
+ parent.primary_key = name
225
+ end
226
+
227
+ def unique
228
+ if primary_key
229
+ true
230
+ elsif index = parent.indexes[name]
231
+ index.unique
232
+ else
233
+ false
234
+ end
235
+ end
236
+
237
+ def named_unique
238
+ unique and parent.indexes[name].name
239
+ end
240
+
241
+ def unique!
242
+ parent.add_unique name
243
+ end
244
+
245
+ def index!
246
+ parent.add_index name
247
+ end
248
+
249
+ def indexed
250
+ primary_key or !!parent.indexes[name]
251
+ end
252
+
253
+ def autoincrement!
254
+ @autoincrement = true
255
+ end
256
+
257
+ def autoincrement
258
+ if defined?(@autoincrement)
259
+ @autoincrement
260
+ elsif default and default =~ /nextval/i
261
+ true
262
+ else
263
+ false
264
+ end
265
+ end
266
+
267
+ # @private
268
+ def column_names
269
+ [name]
270
+ end
271
+
272
+ def parse(str)
273
+ data = Parser.remove_comments(str).strip.unpack('c*')
274
+ %% write data;
275
+ # % (this fixes syntax highlighting)
276
+ parens = 0
277
+ p = item = 0
278
+ pe = eof = data.length
279
+ %% write init;
280
+ # % (this fixes syntax highlighting)
281
+ %% write exec;
282
+ # % (this fixes syntax highlighting)
283
+ self
284
+ end
285
+
286
+ # generating
287
+
288
+ def to_sql(format, options)
289
+ send "to_#{format}", options
290
+ end
291
+
292
+ def to_mysql(options)
293
+ parts = []
294
+ parts << CreateTable.quote_ident(name, options)
295
+ parts << data_type
296
+ if primary_key
297
+ parts << 'PRIMARY KEY'
298
+ elsif unique and not named_unique
299
+ parts << 'UNIQUE'
300
+ end
301
+ if autoincrement
302
+ parts << 'AUTO_INCREMENT'
303
+ end
304
+ parts.join ' '
305
+ end
306
+
307
+ def to_postgresql(options)
308
+ parts = []
309
+ parts << CreateTable.quote_ident(name, options)
310
+ if autoincrement and data_type =~ /integer/i
311
+ parts << 'SERIAL'
312
+ else
313
+ parts << data_type
314
+ end
315
+ if primary_key
316
+ parts << 'PRIMARY KEY'
317
+ elsif unique and not named_unique
318
+ parts << 'UNIQUE'
319
+ end
320
+ parts.join ' '
321
+ end
322
+
323
+ def to_sqlite3(options)
324
+ parts = []
325
+ parts << CreateTable.quote_ident(name, options)
326
+ parts << data_type
327
+ if primary_key
328
+ parts << 'PRIMARY KEY'
329
+ elsif unique and not named_unique
330
+ parts << 'UNIQUE'
331
+ end
332
+ if autoincrement
333
+ parts << 'AUTOINCREMENT'
334
+ end
335
+ parts.join ' '
336
+ end
337
+ end
338
+ end
@@ -0,0 +1,26 @@
1
+ require 'delegate'
2
+ class CreateTable
3
+ class ColumnNameBasedCollection < Delegator
4
+ class << self
5
+ def create
6
+ new([])
7
+ end
8
+ end
9
+
10
+ def __getobj__
11
+ @items
12
+ end
13
+
14
+ def __setobj__(items)
15
+ @items = items
16
+ end
17
+
18
+ def [](column_names)
19
+ return if column_names.nil?
20
+ k = [column_names].flatten
21
+ retval = @items.select { |i| i.column_names == k }
22
+ raise "oops #{k.inspect}: #{retval.map(&:column_names).inspect}" if retval.length > 1
23
+ retval.first
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+
2
+ # line 1 "/Users/seamusabshere/code/create_table/lib/create_table/common.rl.tmp"
3
+
4
+ # line 10 "/Users/seamusabshere/code/create_table/lib/create_table/common.rl.tmp"
5
+
@@ -0,0 +1,13 @@
1
+ %%{
2
+ machine parser;
3
+ ident = [_a-zA-Z][_a-zA-Z0-9]*;
4
+ quote_ident = ["`]?;
5
+ quote_value = ['"];
6
+ lparens = space* '(' space*;
7
+ rparens = space* ')' space*;
8
+ parens_counter = ( any | '(' @{parens+=1} | ')' @{parens-=1} )*;
9
+ with_parens = any+ & parens_counter;
10
+ not_quote_or_escape = [^'"\\];
11
+ escaped_something = /\\./;
12
+ quoted_quote = "''";
13
+ }%%
@@ -0,0 +1,315 @@
1
+
2
+ # MAKE SURE YOU'RE EDITING THE .RL FILE !!!
3
+
4
+ =begin
5
+
6
+
7
+ =end
8
+
9
+ class CreateTable
10
+ class Index
11
+ include Parser
12
+
13
+ attr_reader :parent
14
+ attr_reader :column_names
15
+ attr_accessor :name
16
+
17
+ def initialize(parent)
18
+ @parent = parent
19
+ @column_names = []
20
+ parent.indexes << self
21
+ end
22
+
23
+ def unique
24
+ false
25
+ end
26
+
27
+ def parse(str)
28
+ data = Parser.remove_comments(str).unpack('c*')
29
+
30
+ class << self
31
+ attr_accessor :_parser_actions
32
+ private :_parser_actions, :_parser_actions=
33
+ end
34
+ self._parser_actions = [
35
+ 0, 1, 0, 1, 1, 1, 2, 1,
36
+ 3
37
+ ]
38
+
39
+ class << self
40
+ attr_accessor :_parser_key_offsets
41
+ private :_parser_key_offsets, :_parser_key_offsets=
42
+ end
43
+ self._parser_key_offsets = [
44
+ 0, 0, 10, 15, 27, 31, 40, 45,
45
+ 55, 57, 67
46
+ ]
47
+
48
+ class << self
49
+ attr_accessor :_parser_trans_keys
50
+ private :_parser_trans_keys, :_parser_trans_keys=
51
+ end
52
+ self._parser_trans_keys = [
53
+ 32, 34, 40, 96, 9, 13, 65, 90,
54
+ 95, 122, 95, 65, 90, 97, 122, 32,
55
+ 34, 40, 96, 9, 13, 48, 57, 65,
56
+ 90, 95, 122, 32, 40, 9, 13, 32,
57
+ 34, 96, 9, 13, 65, 90, 95, 122,
58
+ 95, 65, 90, 97, 122, 34, 41, 44,
59
+ 96, 48, 57, 65, 90, 95, 122, 41,
60
+ 44, 32, 34, 41, 96, 9, 13, 65,
61
+ 90, 95, 122, 32, 9, 13, 0
62
+ ]
63
+
64
+ class << self
65
+ attr_accessor :_parser_single_lengths
66
+ private :_parser_single_lengths, :_parser_single_lengths=
67
+ end
68
+ self._parser_single_lengths = [
69
+ 0, 4, 1, 4, 2, 3, 1, 4,
70
+ 2, 4, 1
71
+ ]
72
+
73
+ class << self
74
+ attr_accessor :_parser_range_lengths
75
+ private :_parser_range_lengths, :_parser_range_lengths=
76
+ end
77
+ self._parser_range_lengths = [
78
+ 0, 3, 2, 4, 1, 3, 2, 3,
79
+ 0, 3, 1
80
+ ]
81
+
82
+ class << self
83
+ attr_accessor :_parser_index_offsets
84
+ private :_parser_index_offsets, :_parser_index_offsets=
85
+ end
86
+ self._parser_index_offsets = [
87
+ 0, 0, 8, 12, 21, 25, 32, 36,
88
+ 44, 47, 55
89
+ ]
90
+
91
+ class << self
92
+ attr_accessor :_parser_indicies
93
+ private :_parser_indicies, :_parser_indicies=
94
+ end
95
+ self._parser_indicies = [
96
+ 0, 2, 3, 2, 0, 4, 4, 1,
97
+ 4, 4, 4, 1, 5, 5, 6, 5,
98
+ 5, 7, 7, 7, 1, 8, 3, 8,
99
+ 1, 3, 9, 9, 3, 10, 10, 1,
100
+ 10, 10, 10, 1, 11, 12, 12, 11,
101
+ 13, 13, 13, 1, 14, 14, 1, 14,
102
+ 9, 15, 9, 14, 10, 10, 1, 15,
103
+ 15, 1, 0
104
+ ]
105
+
106
+ class << self
107
+ attr_accessor :_parser_trans_targs
108
+ private :_parser_trans_targs, :_parser_trans_targs=
109
+ end
110
+ self._parser_trans_targs = [
111
+ 1, 0, 2, 5, 3, 4, 5, 3,
112
+ 4, 6, 7, 8, 9, 7, 9, 10
113
+ ]
114
+
115
+ class << self
116
+ attr_accessor :_parser_trans_actions
117
+ private :_parser_trans_actions, :_parser_trans_actions=
118
+ end
119
+ self._parser_trans_actions = [
120
+ 0, 0, 0, 0, 1, 3, 3, 0,
121
+ 0, 0, 5, 7, 7, 0, 0, 0
122
+ ]
123
+
124
+ class << self
125
+ attr_accessor :parser_start
126
+ end
127
+ self.parser_start = 1;
128
+ class << self
129
+ attr_accessor :parser_first_final
130
+ end
131
+ self.parser_first_final = 10;
132
+ class << self
133
+ attr_accessor :parser_error
134
+ end
135
+ self.parser_error = 0;
136
+
137
+ class << self
138
+ attr_accessor :parser_en_main
139
+ end
140
+ self.parser_en_main = 1;
141
+
142
+
143
+ # % (this fixes syntax highlighting)
144
+ parens = 0
145
+ p = item = 0
146
+ pe = eof = data.length
147
+
148
+ begin
149
+ p ||= 0
150
+ pe ||= data.length
151
+ cs = parser_start
152
+ end
153
+
154
+ # % (this fixes syntax highlighting)
155
+
156
+ begin
157
+ _klen, _trans, _keys, _acts, _nacts = nil
158
+ _goto_level = 0
159
+ _resume = 10
160
+ _eof_trans = 15
161
+ _again = 20
162
+ _test_eof = 30
163
+ _out = 40
164
+ while true
165
+ _trigger_goto = false
166
+ if _goto_level <= 0
167
+ if p == pe
168
+ _goto_level = _test_eof
169
+ next
170
+ end
171
+ if cs == 0
172
+ _goto_level = _out
173
+ next
174
+ end
175
+ end
176
+ if _goto_level <= _resume
177
+ _keys = _parser_key_offsets[cs]
178
+ _trans = _parser_index_offsets[cs]
179
+ _klen = _parser_single_lengths[cs]
180
+ _break_match = false
181
+
182
+ begin
183
+ if _klen > 0
184
+ _lower = _keys
185
+ _upper = _keys + _klen - 1
186
+
187
+ loop do
188
+ break if _upper < _lower
189
+ _mid = _lower + ( (_upper - _lower) >> 1 )
190
+
191
+ if data[p].ord < _parser_trans_keys[_mid]
192
+ _upper = _mid - 1
193
+ elsif data[p].ord > _parser_trans_keys[_mid]
194
+ _lower = _mid + 1
195
+ else
196
+ _trans += (_mid - _keys)
197
+ _break_match = true
198
+ break
199
+ end
200
+ end # loop
201
+ break if _break_match
202
+ _keys += _klen
203
+ _trans += _klen
204
+ end
205
+ _klen = _parser_range_lengths[cs]
206
+ if _klen > 0
207
+ _lower = _keys
208
+ _upper = _keys + (_klen << 1) - 2
209
+ loop do
210
+ break if _upper < _lower
211
+ _mid = _lower + (((_upper-_lower) >> 1) & ~1)
212
+ if data[p].ord < _parser_trans_keys[_mid]
213
+ _upper = _mid - 2
214
+ elsif data[p].ord > _parser_trans_keys[_mid+1]
215
+ _lower = _mid + 2
216
+ else
217
+ _trans += ((_mid - _keys) >> 1)
218
+ _break_match = true
219
+ break
220
+ end
221
+ end # loop
222
+ break if _break_match
223
+ _trans += _klen
224
+ end
225
+ end while false
226
+ _trans = _parser_indicies[_trans]
227
+ cs = _parser_trans_targs[_trans]
228
+ if _parser_trans_actions[_trans] != 0
229
+ _acts = _parser_trans_actions[_trans]
230
+ _nacts = _parser_actions[_acts]
231
+ _acts += 1
232
+ while _nacts > 0
233
+ _nacts -= 1
234
+ _acts += 1
235
+ case _parser_actions[_acts - 1]
236
+ when 0 then
237
+ begin
238
+ start_name = p end
239
+ when 1 then
240
+ begin
241
+ self.name = read(data, start_name, p) end
242
+ when 2 then
243
+ begin
244
+ start_column_name = p end
245
+ when 3 then
246
+ begin
247
+ column_names << read(data, start_column_name, p) end
248
+ end # action switch
249
+ end
250
+ end
251
+ if _trigger_goto
252
+ next
253
+ end
254
+ end
255
+ if _goto_level <= _again
256
+ if cs == 0
257
+ _goto_level = _out
258
+ next
259
+ end
260
+ p += 1
261
+ if p != pe
262
+ _goto_level = _resume
263
+ next
264
+ end
265
+ end
266
+ if _goto_level <= _test_eof
267
+ end
268
+ if _goto_level <= _out
269
+ break
270
+ end
271
+ end
272
+ end
273
+
274
+ # % (this fixes syntax highlighting)
275
+ self
276
+ end
277
+
278
+ def column_names=(column_names)
279
+ @column_names = [column_names].compact.flatten
280
+ end
281
+
282
+ def to_sql(format, options)
283
+ return if primary_key
284
+ return if unique and name.nil?
285
+ parts = []
286
+ parts << 'CREATE'
287
+ parts << 'UNIQUE' if (unique and name)
288
+ parts << 'INDEX'
289
+ parts += [ quoted_name(options), 'ON', parent.quoted_table_name(options), '(', quoted_column_names(options), ')' ]
290
+ parts.join ' '
291
+ end
292
+
293
+ def primary_key
294
+ if pk = parent.primary_key
295
+ pk.column_names == column_names
296
+ end
297
+ end
298
+
299
+ def quoted_name(options)
300
+ if name
301
+ CreateTable.quote_ident name, options
302
+ elsif unique
303
+ "uidx_#{parent.table_name}_on_#{name}"
304
+ else
305
+ "idx_#{parent.table_name}_on_#{name}"
306
+ end
307
+ end
308
+
309
+ def quoted_column_names(options)
310
+ column_names.map do |column_name|
311
+ CreateTable.quote_ident column_name, options
312
+ end.join(', ')
313
+ end
314
+ end
315
+ end