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.
- data/.bnsignore +16 -0
- data/.gitignore +3 -0
- data/History.txt +3 -0
- data/README.txt +57 -0
- data/Rakefile +27 -0
- data/examples/sql_izer.rb +92 -0
- data/lib/sql_munger.rb +49 -0
- data/lib/sql_munger/field_set.rb +355 -0
- data/lib/sql_munger/identifier_quoter.rb +12 -0
- data/lib/sql_munger/nodes.rb +290 -0
- data/lib/sql_munger/quoter.rb +42 -0
- data/lib/sql_munger/sql.tt +214 -0
- data/lib/sql_munger/sql_parser.rb +47 -0
- data/lib/sql_munger/table_name.rb +47 -0
- data/lib/sql_munger/value_quoter.rb +30 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/sql_munger_spec.rb +7 -0
- data/test/mock_field.rb +2 -0
- data/test/test_field_set.rb +462 -0
- data/test/test_sql_munger.rb +9 -0
- data/test/test_table_name.rb +26 -0
- metadata +101 -0
@@ -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
|