sql_munger 0.0.7

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.
@@ -0,0 +1,12 @@
1
+ module SqlMunger
2
+
3
+ # Quote a value in default SQL standard style. Probably
4
+ # won't work for many backends, so the quote
5
+ # method from, for example, DBI should be used.
6
+ module IdentifierQuoter
7
+ def quote_ident( identifier )
8
+ %Q{"#{identifier}"}
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,290 @@
1
+ require 'sql_munger/sql_parser.rb'
2
+
3
+ module TableNode
4
+ def fields
5
+ defns.select {|x| x.field? }
6
+ end
7
+
8
+ def indices
9
+ @indices ||= []
10
+ end
11
+
12
+ def constraints
13
+ defns.select {|x| x.constraint?}
14
+ end
15
+
16
+ def defns
17
+ table_element_list.elements.map do |node|
18
+ node.table_element
19
+ end
20
+ end
21
+
22
+ # Return the set of primary key field objects for this table.
23
+ # May be empty, or contain only one field.
24
+ def primary_key
25
+ single_key = fields.select{|f| f.clauses.any? {|c| c.clause_type == :primary_key } }
26
+ multi_key = constraints.select{ |x| x.type == :primary_key }
27
+
28
+ if single_key.size > 1
29
+ raise "Can't have more than one primary key field specifier"
30
+ end
31
+
32
+ if single_key.size > 0 && multi_key.size > 0
33
+ raise "Can't specify both single primary key and multi-key constraint"
34
+ end
35
+
36
+ if single_key.size == 1
37
+ # return single key field name
38
+ [ single_key.first ]
39
+ elsif multi_key.size == 1
40
+ fields.select {|f| multi_key.first.fields.include?( f.name ) }
41
+ else
42
+ []
43
+ end
44
+ end
45
+
46
+ # return the set of primary key field names for this table.
47
+ def primary_key_fields
48
+ primary_key.map{|f| f.name}
49
+ end
50
+
51
+ def name
52
+ table_name.parts.last
53
+ end
54
+ end
55
+
56
+ module IndexNode
57
+ def unique?
58
+ !unique.text_value.empty?
59
+ end
60
+
61
+ def qualified_fields
62
+ parenthesised_list.qualified_identifier_list.elements.map do |node|
63
+ node.qualified_identifier.parts
64
+ end
65
+ end
66
+
67
+ def fields
68
+ qualified_fields.map {|x| x.last}
69
+ end
70
+
71
+ def name
72
+ index_name.parts.last
73
+ end
74
+ end
75
+
76
+ module FieldNode
77
+ def field?; true; end
78
+ def constraint?; false; end
79
+
80
+ def name
81
+ qualified_identifier.parts.last
82
+ end
83
+
84
+ def sql_type
85
+ if type_specifier.terminal?
86
+ type_specifier.text_value
87
+ else
88
+ type_specifier.sql_type.text_value
89
+ end
90
+ end
91
+
92
+ def primary_key?
93
+ clauses.any? {|x| x.clause_type == :primary_key }
94
+ end
95
+
96
+ def rails_type
97
+ type_specifier.rails_type rescue sql_type
98
+ end
99
+
100
+ def default?
101
+ !default_clause.nil?
102
+ end
103
+
104
+ def default_clause
105
+ clauses.find {|x| x.clause_type == :default }
106
+ end
107
+
108
+ def default
109
+ # do we have a default clause?
110
+ if default?
111
+ # fetch the default value
112
+ retval = default_clause.parenthesised.value
113
+
114
+ # don't return a default of "null" because it's redundant
115
+ retval unless retval == "null"
116
+ end
117
+ end
118
+
119
+ def null_clause
120
+ clauses.find {|x| x.clause_type == :not_null }
121
+ end
122
+
123
+ def null
124
+ null_clause.nil?
125
+ end
126
+
127
+ def rails_options
128
+ if @rails_options.nil?
129
+ @rails_options ||= {}
130
+ @rails_options[:limit] = length.to_i unless length.nil?
131
+ @rails_options[:precision] = precision.to_i unless precision.nil?
132
+ @rails_options[:scale] = scale.to_i unless scale.nil?
133
+ # only include a null specifier if it's false, ie we need a "not null" SQL clause
134
+ @rails_options[:null] = null unless ( null.nil? || null )
135
+ @rails_options[:default] = default unless default.nil?
136
+ end
137
+ @rails_options
138
+ end
139
+
140
+ def formatted_rails_opts
141
+ if rails_options.empty?
142
+ ''
143
+ else
144
+ ", " + rails_options.inspect[1..-2].gsub( '=>', ' => ' )
145
+ end
146
+ end
147
+
148
+ def length
149
+ type_specifier.length.text_value rescue nil
150
+ end
151
+
152
+ def precision
153
+ type_specifier.args.precision.text_value rescue nil
154
+ end
155
+
156
+ def scale
157
+ type_specifier.args.scale.text_value rescue nil
158
+ end
159
+
160
+ # return text values of each clause
161
+ def clauses
162
+ if field_clauses.empty?
163
+ []
164
+ else
165
+ field_clauses.elements.map{|node| node.field_clause}
166
+ end
167
+ end
168
+ end
169
+
170
+ module ClauseNode
171
+ def clause_type
172
+ name.downcase.gsub( /\s+/, '_' ).to_sym
173
+ end
174
+ end
175
+
176
+ module SimpleFieldClauseNode
177
+ include ClauseNode
178
+ def name
179
+ text_value
180
+ end
181
+ end
182
+
183
+ module FieldClauseNode
184
+ include ClauseNode
185
+ def name
186
+ elements.first.text_value
187
+ end
188
+ end
189
+
190
+ module QualifiedIdentifierNode
191
+ # return an array of parts, each stripped of delimiters
192
+ def parts
193
+ elements.map do |node|
194
+ if node.identifier.respond_to?( :identifier )
195
+ node.identifier.identifier.text_value
196
+ else
197
+ node.identifier.text_value
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ # Rails migration types are
204
+ # integer, float, datetime, date, timestamp
205
+ # time, text, string, binary and boolean.
206
+ module TypeNode
207
+ end
208
+
209
+ module TableConstraintNode
210
+ def field?; false; end
211
+ def constraint?; true; end
212
+
213
+ def type
214
+ constraint_spec.constraint_clause.text_value.downcase.gsub( /\s+/, '_' ).to_sym
215
+ end
216
+
217
+ def name
218
+ constraint_name.parts.last
219
+ end
220
+
221
+ def fields
222
+ constraint_spec.parenthesised_list.qualified_identifier_list.elements.map do |node|
223
+ node.qualified_identifier.text_value
224
+ end
225
+ end
226
+
227
+ end
228
+
229
+ module ParenthesisedNode
230
+
231
+ # this is actually def value, but it mustn't override
232
+ # the value methods for the literal nodes.
233
+ def method_missing( symbol, *args, &block )
234
+ if symbol == :value
235
+ parenthesised_value
236
+ else
237
+ super
238
+ end
239
+ end
240
+
241
+ # this will end up being called for anything that's not handled by
242
+ # one of the XXXLiteralNode modules. Right now it basically applies
243
+ # to function calls only.
244
+ def parenthesised_value
245
+ node = self
246
+ while node.respond_to?( :parenthesised ) && !node.respond_to?( :function )
247
+ node = node.parenthesised
248
+ end
249
+
250
+ unless node == self
251
+ node.value
252
+ else
253
+ text_value
254
+ end
255
+ end
256
+ end
257
+
258
+ module StringLiteral
259
+ def value
260
+ # strip delimiters and unescape
261
+ text_value[1..-2].gsub( "''", "'" )
262
+ end
263
+ end
264
+
265
+ module NumericLiteral
266
+ def value
267
+ if /[\.e]/ =~ text_value
268
+ text_value.to_f
269
+ else
270
+ text_value.to_i
271
+ end
272
+ end
273
+ end
274
+
275
+ module NullLiteral
276
+ def value
277
+ "null"
278
+ end
279
+ end
280
+
281
+ module BooleanLiteral
282
+ def value
283
+ eval text_value
284
+ end
285
+ end
286
+
287
+ class ParenthesisedList < Treetop::Runtime::SyntaxNode
288
+ def values
289
+ end
290
+ end
@@ -0,0 +1,42 @@
1
+ require 'sql_munger/identifier_quoter.rb'
2
+ require 'sql_munger/value_quoter.rb'
3
+
4
+ module SqlMunger
5
+
6
+ module Quoter
7
+ class DefaultQuoter
8
+ include ValueQuoter
9
+ include IdentifierQuoter
10
+ def escape( st )
11
+ st.gsub( "'", "\\'" )
12
+ end
13
+ end
14
+
15
+ attr_writer :quoter
16
+
17
+ def quoter
18
+ @quoter ||= self.class.default_quoter
19
+ end
20
+
21
+ def value_quoter
22
+ @value_quoter ||= quoter
23
+ end
24
+
25
+ def identifier_quoter
26
+ @identifier_quoter ||= quoter
27
+ end
28
+
29
+ module ClassMethods
30
+ def default_quoter
31
+ @default_quoter ||= DefaultQuoter.new
32
+ end
33
+
34
+ attr_writer :default_quoter
35
+ end
36
+
37
+ def self.included( base )
38
+ base.extend( ClassMethods )
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,214 @@
1
+ grammar Sql
2
+ rule data_defintion
3
+ ( s* statement s* terminator? s* )* {
4
+ def statements
5
+ elements.map { |node| node.elements[1] }
6
+ end
7
+
8
+ def tables
9
+ statements.select {|x| x.is_a? TableNode }
10
+ end
11
+
12
+ def indices
13
+ statements.select {|x| x.is_a? IndexNode }
14
+ end
15
+ }
16
+ end
17
+
18
+ rule terminator
19
+ ';' / 'go'
20
+ end
21
+
22
+ rule statement
23
+ create_table <TableNode>
24
+ / drop_table
25
+ / create_index <IndexNode>
26
+ / drop_index
27
+ / alter_table
28
+ end
29
+
30
+ rule drop_table
31
+ 'drop' s+ 'table' s+ table_name:qualified_identifier ( s+ drop_behaviour )?
32
+ end
33
+
34
+ rule drop_behaviour
35
+ 'cascade' / 'restrict'
36
+ end
37
+
38
+ rule create_table
39
+ 'create' s+ 'table' s+
40
+ # ( ( 'global' / 'local' ) sp 'temporary' )?
41
+ table_name:qualified_identifier s* '(' s* table_element_list s* ')'
42
+ #[ on commit { delete | preserve } rows ]
43
+ end
44
+
45
+ rule alter_table
46
+ 'alter' s+ 'table' s+ table_name:qualified_identifier s+
47
+ 'add' s+ table_constraint
48
+ end
49
+
50
+ rule table_constraint
51
+ 'constraint' s+ constraint_name:qualified_identifier s+
52
+ constraint_spec:( primary_key_constraint / foreign_key_constraint ) <TableConstraintNode>
53
+ end
54
+
55
+ rule primary_key_constraint
56
+ constraint_clause:( 'primary' s+ 'key' )
57
+ s* parenthesised_list
58
+ end
59
+
60
+ rule foreign_key_constraint
61
+ constraint_clause:( 'foreign' s+ 'key' ) s*
62
+ referencing_columns:parenthesised_list s+
63
+ 'references' s+ referenced_table:qualified_identifier s*
64
+ referenced_fields:parenthesised_list
65
+ on_delete:( s+ 'on' s+ 'delete' s+ referential_action )?
66
+ on_update:( s+ 'on' s+ 'update' s+ referential_action )?
67
+ end
68
+
69
+ rule referential_action
70
+ 'cascade' / ( 'set' s+ 'null' ) / ( 'set' s+ 'default' ) / ( 'no' s+ 'action' )
71
+ end
72
+
73
+ rule create_index
74
+ 'create' s+ unique:( 'unique' s+ )? 'index' s+
75
+ index_name:qualified_identifier s+ 'on' s+ table_name:qualified_identifier s* parenthesised_list
76
+ end
77
+
78
+ rule drop_index
79
+ 'drop' s+ 'index' identifier ( s+ drop_behaviour )?
80
+ end
81
+
82
+ # actually whitespace or comment
83
+ rule s
84
+ [ \t\r\n] / comment
85
+ end
86
+
87
+ rule comment
88
+ '--' (![\r\n] .)* [\r\n]
89
+ end
90
+
91
+ rule qualified_identifier
92
+ ( '.'? identifier )+ <QualifiedIdentifierNode>
93
+ end
94
+
95
+ rule qualified_identifier_list
96
+ ( s* ','? s* qualified_identifier )+
97
+ end
98
+
99
+ rule table_element_list
100
+ ( s* ','? s* table_element s* )+
101
+ end
102
+
103
+ rule table_element
104
+ field_defn <FieldNode> / table_constraint
105
+ end
106
+
107
+ rule field_defn
108
+ qualified_identifier s+ type_specifier field_clauses:(s+ field_clause)*
109
+ end
110
+
111
+ rule unqualified_type_specifier
112
+ ( 'bit' / 'boolean' ) {
113
+ def rails_type; :boolean; end
114
+ }
115
+
116
+ / ( 'smallint' / 'int' / 'tinyint' / 'bigint' / 'integer' ) {
117
+ def rails_type; :integer; end
118
+ }
119
+
120
+ / ( 'datetime' / 'date' / 'time' / 'timestamp' ) {
121
+ def rails_type; text_value.to_sym; end
122
+ }
123
+
124
+ / ( 'real' / 'double precision' ) {
125
+ def rails_type; :decimal; end
126
+ }
127
+
128
+ / ( 'blob' / 'clob' / 'image' ) {
129
+ def rails_type; :binary; end
130
+ }
131
+
132
+ / ( 'text' ) {
133
+ def rails_type; :text; end
134
+ }
135
+ end
136
+
137
+ rule type_specifier
138
+ ( unqualified_type_specifier / length_type_specifier / numeric_type_specifier ) <TypeNode>
139
+ end
140
+
141
+ rule length_type_specifier
142
+ sql_type:( 'n'? 'var'? 'char' ) s* '(' s* length:digit+ s* ')'
143
+ { def rails_type; :string; end }
144
+ end
145
+
146
+ rule precision_scale
147
+ s* '(' s* precision:digit+ s* comma_scale:( ',' s* scale:digit+ )? s* ')' {
148
+ def scale
149
+ comma_scale.scale
150
+ end
151
+ }
152
+ end
153
+
154
+ rule precision
155
+ s* '(' s* precision:digit+ s* ')'
156
+ end
157
+
158
+ rule numeric_type_specifier
159
+ sql_type:('dec' 'imal'? / 'numeric' ) args:precision_scale?
160
+ { def rails_type; :decimal; end }
161
+ / sql_type:'float' args:precision? { def rails_type; :float; end }
162
+ end
163
+
164
+ rule identifier
165
+ alpha ( alpha / digit / id_punct )* / delimiter identifier delimiter
166
+ end
167
+
168
+ rule delimiter
169
+ ["\[\]`]
170
+ end
171
+
172
+ rule word
173
+ alpha ( alpha / digit / id_punct )*
174
+ end
175
+
176
+ rule digit
177
+ [0-9]
178
+ end
179
+
180
+ rule id_punct
181
+ [_]
182
+ end
183
+
184
+ rule alpha
185
+ [a-zA-Z_]
186
+ end
187
+
188
+ rule simple_field_clause
189
+ 'identity' / 'primary' s+ 'key'/ 'unique'/ 'not' s+ 'null'
190
+ end
191
+
192
+ rule field_clause
193
+ simple_field_clause <SimpleFieldClauseNode>
194
+ / 'check' s* parenthesised <FieldClauseNode>
195
+ / 'default' s* parenthesised <FieldClauseNode>
196
+ end
197
+
198
+ rule parenthesised
199
+ ( '(' s* parenthesised s* ')' / ( function:word s* parenthesised ) / literal / s* ) <ParenthesisedNode>
200
+ end
201
+
202
+ rule literal
203
+ "'" ( "''" / (!"'" .) )* "'" <StringLiteral>
204
+ / [+-]? digit+ ( '.' digit+ )? ('e' digit+ )? <NumericLiteral>
205
+ / 'null' <NullLiteral>
206
+ / 'true' <BooleanLiteral>
207
+ / 'false' <BooleanLiteral>
208
+ end
209
+
210
+ rule parenthesised_list
211
+ '(' s* qualified_identifier_list s* ')' <ParenthesisedList>
212
+ end
213
+
214
+ end