create_table 0.1.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.
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