sql_munger 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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