sql-maker 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +23 -0
- data/Rakefile +14 -0
- data/lib/sql-maker.rb +5 -0
- data/lib/sql/maker.rb +676 -0
- data/lib/sql/maker/condition.rb +378 -0
- data/lib/sql/maker/error.rb +3 -0
- data/lib/sql/maker/helper.rb +22 -0
- data/lib/sql/maker/quoting.rb +138 -0
- data/lib/sql/maker/select.rb +544 -0
- data/lib/sql/maker/select/oracle.rb +30 -0
- data/lib/sql/maker/select_set.rb +194 -0
- data/lib/sql/maker/util.rb +54 -0
- data/lib/sql/query_maker.rb +429 -0
- data/scripts/perl2ruby.rb +34 -0
- data/spec/maker/bind_param_spec.rb +62 -0
- data/spec/maker/condition/add_raw_spec.rb +29 -0
- data/spec/maker/condition/compose_empty_spec.rb +72 -0
- data/spec/maker/condition/empty_values_spec.rb +25 -0
- data/spec/maker/condition/make_term_spec.rb +38 -0
- data/spec/maker/condition/where_spec.rb +35 -0
- data/spec/maker/delete_spec.rb +116 -0
- data/spec/maker/insert_empty_spec.rb +23 -0
- data/spec/maker/insert_spec.rb +61 -0
- data/spec/maker/new_line_spec.rb +9 -0
- data/spec/maker/select/oracle/oracle_spec.rb +16 -0
- data/spec/maker/select/pod_select_spec.rb +34 -0
- data/spec/maker/select/statement_spec.rb +805 -0
- data/spec/maker/select_set_spec.rb +294 -0
- data/spec/maker/select_spec.rb +470 -0
- data/spec/maker/simple_spec.rb +54 -0
- data/spec/maker/strict_spec.rb +45 -0
- data/spec/maker/subquery_spec.rb +77 -0
- data/spec/maker/update_spec.rb +138 -0
- data/spec/maker/util_spec.rb +6 -0
- data/spec/maker/where_spec.rb +28 -0
- data/spec/query_maker/and_using_hash_spec.rb +13 -0
- data/spec/query_maker/cheatsheet_spec.rb +32 -0
- data/spec/query_maker/column_bind_spec.rb +55 -0
- data/spec/query_maker/refs_in_bind_spec.rb +19 -0
- data/spec/spec_helper.rb +15 -0
- data/sql-maker.gemspec +25 -0
- metadata +185 -0
@@ -0,0 +1,378 @@
|
|
1
|
+
require 'sql/maker/util'
|
2
|
+
require 'sql/query_maker'
|
3
|
+
|
4
|
+
class SQL::Maker::Condition
|
5
|
+
include SQL::Maker::Util
|
6
|
+
attr_accessor :sql, :bind, :strict, :name_sep, :quote_char
|
7
|
+
|
8
|
+
def initialize(args = {})
|
9
|
+
@sql = args[:sql] || []
|
10
|
+
@bind = args[:bind] || []
|
11
|
+
@strict = args[:strict].nil? ? false : args[:strict]
|
12
|
+
@name_sep = args[:name_sep] || ''
|
13
|
+
@quote_char = args[:quote_char] || ''
|
14
|
+
end
|
15
|
+
|
16
|
+
def &(other)
|
17
|
+
self.compose_and(other)
|
18
|
+
end
|
19
|
+
|
20
|
+
def |(other)
|
21
|
+
self.compose_or(other)
|
22
|
+
end
|
23
|
+
|
24
|
+
def _quote(label)
|
25
|
+
quote_identifier(label, self.quote_char, self.name_sep)
|
26
|
+
end
|
27
|
+
|
28
|
+
# _make_term(:x => 1)
|
29
|
+
def _make_term(*args)
|
30
|
+
col, val = parse_args(*args)
|
31
|
+
col = col.to_s
|
32
|
+
|
33
|
+
if val.is_a?(SQL::QueryMaker)
|
34
|
+
return [val.as_sql(col, self.method(:_quote)), val.bind]
|
35
|
+
elsif self.strict
|
36
|
+
croak("can pass only SQL::QueryMaker as an argument in strict mode")
|
37
|
+
end
|
38
|
+
|
39
|
+
if val.is_a?(Array)
|
40
|
+
if val.first.is_a?(Hash)
|
41
|
+
# {'foo'=>[{'>' => 'bar'},{'<' => 'baz'}]} => (`foo` > ?) OR (`foo` < ?)
|
42
|
+
return self._make_or_term(col, 'OR', val)
|
43
|
+
else
|
44
|
+
# {'foo'=>['bar','baz']} => `foo` IN (?, ?)
|
45
|
+
return self._make_in_term(col, 'IN', val)
|
46
|
+
end
|
47
|
+
elsif val.is_a?(Hash)
|
48
|
+
op, v = val.each.first
|
49
|
+
op = op.upcase.to_s
|
50
|
+
if ( op == 'AND' || op == 'OR' ) && v.is_a?(Array)
|
51
|
+
# {'foo'=>[{'>' => 'bar'},{'<' => 'baz'}]} => (`foo` > ?) OR (`foo` < ?)
|
52
|
+
return self._make_or_term(col, op, v)
|
53
|
+
elsif ( op == 'IN' || op == 'NOT IN' )
|
54
|
+
return self._make_in_term(col, op, v)
|
55
|
+
elsif ( op == 'BETWEEN' ) && v.is_a?(Array)
|
56
|
+
croak("USAGE: make_term(foo => {BETWEEN => [a, b]})") if v.size != 2
|
57
|
+
return [self._quote(col) + " BETWEEN ? AND ?", v]
|
58
|
+
else
|
59
|
+
# make_term(foo => { '<' => \"DATE_SUB(NOW(), INTERVAL 3 DAY)"}) => 'foo < DATE_SUB(NOW(), INTERVAL 3 DAY)'
|
60
|
+
# return [self._quote(col) + " op " + v, []]
|
61
|
+
# make_term(foo => { '<' => 3 }) => foo < 3
|
62
|
+
return [self._quote(col) + " #{op} ?", [v]]
|
63
|
+
end
|
64
|
+
elsif val
|
65
|
+
# make_term(foo => "3") => foo = 3
|
66
|
+
return [self._quote(col) + " = ?", [val]]
|
67
|
+
else
|
68
|
+
# make_term(foo => nil) => foo IS NULL
|
69
|
+
return [self._quote(col) + " IS NULL", []]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def _make_or_term(col, op, values)
|
74
|
+
binds = []
|
75
|
+
terms = []
|
76
|
+
values.each do |v|
|
77
|
+
term, bind = self._make_term(col => v)
|
78
|
+
terms.push "(#{term})"
|
79
|
+
binds.push bind
|
80
|
+
end
|
81
|
+
term = terms.join(" #{op} ")
|
82
|
+
bind = binds.flatten
|
83
|
+
return [term, bind]
|
84
|
+
end
|
85
|
+
|
86
|
+
def _make_in_term(col, op, v)
|
87
|
+
if v.respond_to?(:as_sql)
|
88
|
+
# make_term(foo => { 'IN' => sql_raw('SELECT foo FROM bar') }) => foo IN (SELECT foo FROM bar)
|
89
|
+
term = "#{self._quote(col)} #{op} (#{v.as_sql})"
|
90
|
+
[term, v.bind]
|
91
|
+
elsif v.is_a?(Array)
|
92
|
+
if v.size == 0
|
93
|
+
if op == 'IN'
|
94
|
+
# make_term(foo => {'IN' => []}) => 0=1
|
95
|
+
return ['0=1', []]
|
96
|
+
else
|
97
|
+
# make_term(foo => {'NOT IN' => []}) => 1=1
|
98
|
+
return ['1=1', []]
|
99
|
+
end
|
100
|
+
else
|
101
|
+
# make_term(foo => { 'IN' => [1,2,3] }) => [foo IN (?,?,?), [1,2,3]]
|
102
|
+
term = "#{self._quote(col)} #{op} (#{(['?'] * v.size).join(', ')})"
|
103
|
+
return [term, v]
|
104
|
+
end
|
105
|
+
else
|
106
|
+
croad("_make_in_term: arguments must be either of query instance or array")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def add(*args)
|
111
|
+
term, bind = self._make_term(*args)
|
112
|
+
self.sql.push "(#{term})" if term
|
113
|
+
self.bind += array_wrap(bind) if bind
|
114
|
+
|
115
|
+
return self # for influent interface
|
116
|
+
end
|
117
|
+
|
118
|
+
def add_raw(*args)
|
119
|
+
term, bind = parse_args(*args)
|
120
|
+
self.sql.push "(#{term})"
|
121
|
+
self.bind += array_wrap(bind) if bind
|
122
|
+
return self
|
123
|
+
end
|
124
|
+
|
125
|
+
def compose_and(other)
|
126
|
+
if self.sql.empty?
|
127
|
+
if other.sql.empty?
|
128
|
+
return SQL::Maker::Condition.new
|
129
|
+
end
|
130
|
+
return SQL::Maker::Condition.new(
|
131
|
+
:sql => ['(' + other.as_sql() + ')'],
|
132
|
+
:bind => other.bind,
|
133
|
+
)
|
134
|
+
end
|
135
|
+
if other.sql.empty?
|
136
|
+
return SQL::Maker::Condition.new(
|
137
|
+
:sql => ['(' + self.as_sql() + ')'],
|
138
|
+
:bind => self.bind,
|
139
|
+
)
|
140
|
+
end
|
141
|
+
|
142
|
+
return SQL::Maker::Condition.new(
|
143
|
+
:sql => ['(' + self.as_sql() + ') AND (' + other.as_sql() + ')'],
|
144
|
+
:bind => self.bind + other.bind,
|
145
|
+
)
|
146
|
+
end
|
147
|
+
|
148
|
+
def compose_or(other)
|
149
|
+
if self.sql.empty?
|
150
|
+
if other.sql.empty?
|
151
|
+
return SQL::Maker::Condition.new
|
152
|
+
end
|
153
|
+
return SQL::Maker::Condition.new(
|
154
|
+
:sql => ['(' + other.as_sql() + ')'],
|
155
|
+
:bind => other.bind,
|
156
|
+
)
|
157
|
+
end
|
158
|
+
if other.sql.empty?
|
159
|
+
return SQL::Maker::Condition.new(
|
160
|
+
:sql => ['(' + self.as_sql() + ')'],
|
161
|
+
:bind => self.bind,
|
162
|
+
)
|
163
|
+
end
|
164
|
+
|
165
|
+
# return value is enclosed with '()'.
|
166
|
+
# because 'OR' operator priority less than 'AND'.
|
167
|
+
return SQL::Maker::Condition.new(
|
168
|
+
:sql => ['((' + self.as_sql() + ') OR (' + other.as_sql() + '))'],
|
169
|
+
:bind => self.bind + other.bind,
|
170
|
+
)
|
171
|
+
end
|
172
|
+
|
173
|
+
def as_sql
|
174
|
+
self.sql.join(' AND ')
|
175
|
+
end
|
176
|
+
alias_method :to_s, :as_sql
|
177
|
+
end
|
178
|
+
|
179
|
+
__END__
|
180
|
+
|
181
|
+
=for test_synopsis
|
182
|
+
my (sql, @bind)
|
183
|
+
|
184
|
+
=head1 NAME
|
185
|
+
|
186
|
+
SQL::Maker::Condition - condition object for SQL::Maker
|
187
|
+
|
188
|
+
=head1 SYNOPSIS
|
189
|
+
|
190
|
+
my condition = SQL::Maker::Condition.new(
|
191
|
+
name_sep => '.',
|
192
|
+
quote_char => '`',
|
193
|
+
)
|
194
|
+
condition.add('foo_id' => 3)
|
195
|
+
condition.add('bar_id' => 4)
|
196
|
+
sql = condition.as_sql() # (`foo_id`=?) AND (`bar_id`=?)
|
197
|
+
@bind = condition.bind() # (3, 4)
|
198
|
+
|
199
|
+
# add_raw
|
200
|
+
my condition = SQL::Maker::Condition.new(
|
201
|
+
name_sep => '.',
|
202
|
+
quote_char => '`',
|
203
|
+
)
|
204
|
+
condition.add_raw('EXISTS(SELECT * FROM bar WHERE name = ?)' => ['john'])
|
205
|
+
condition.add_raw('type IS NOT NULL')
|
206
|
+
sql = condition.as_sql() # (EXISTS(SELECT * FROM bar WHERE name = ?)) AND (type IS NOT NULL)
|
207
|
+
@bind = condition.bind() # ('john')
|
208
|
+
|
209
|
+
# composite and
|
210
|
+
my other = SQL::Maker::Condition.new(
|
211
|
+
name_sep => '.',
|
212
|
+
quote_char => '`',
|
213
|
+
)
|
214
|
+
other.add('name' => 'john')
|
215
|
+
my $comp_and = condition & other
|
216
|
+
sql = $comp_and.as_sql() # ((`foo_id`=?) AND (`bar_id`=?)) AND (`name`=?)
|
217
|
+
@bind = $comp_and.bind() # (3, 4, 'john')
|
218
|
+
|
219
|
+
# composite or
|
220
|
+
my $comp_or = condition | other
|
221
|
+
sql = $comp_and.as_sql() # ((`foo_id`=?) AND (`bar_id`=?)) OR (`name`=?)
|
222
|
+
@bind = $comp_and.bind() # (3, 4, 'john')
|
223
|
+
|
224
|
+
|
225
|
+
=head1 CONDITION CHEAT SHEET
|
226
|
+
|
227
|
+
Here is a cheat sheet for conditions.
|
228
|
+
|
229
|
+
IN: {'foo'=>'bar'}
|
230
|
+
OUT QUERY: '`foo` = ?'
|
231
|
+
OUT BIND: ['bar']
|
232
|
+
|
233
|
+
IN: {'foo'=>['bar','baz']}
|
234
|
+
OUT QUERY: '`foo` IN (?, ?)'
|
235
|
+
OUT BIND: ['bar','baz']
|
236
|
+
|
237
|
+
IN: {'foo'=>{'IN' => ['bar','baz']}}
|
238
|
+
OUT QUERY: '`foo` IN (?, ?)'
|
239
|
+
OUT BIND: ['bar','baz']
|
240
|
+
|
241
|
+
IN: {'foo'=>{'not IN' => ['bar','baz']}}
|
242
|
+
OUT QUERY: '`foo` NOT IN (?, ?)'
|
243
|
+
OUT BIND: ['bar','baz']
|
244
|
+
|
245
|
+
IN: {'foo'=>{'!=' => 'bar'}}
|
246
|
+
OUT QUERY: '`foo` != ?'
|
247
|
+
OUT BIND: ['bar']
|
248
|
+
|
249
|
+
# IN: {'foo'=>\'IS NOT NULL'}
|
250
|
+
# OUT QUERY: '`foo` IS NOT NULL'
|
251
|
+
# OUT BIND: []
|
252
|
+
|
253
|
+
IN: {'foo'=>{'between' => ['1','2']}}
|
254
|
+
OUT QUERY: '`foo` BETWEEN ? AND ?'
|
255
|
+
OUT BIND: ['1','2']
|
256
|
+
|
257
|
+
IN: {'foo'=>{'like' => 'xaic%'}}
|
258
|
+
OUT QUERY: '`foo` LIKE ?'
|
259
|
+
OUT BIND: ['xaic%']
|
260
|
+
|
261
|
+
IN: {'foo'=>[{'>' => 'bar'},{'<' => 'baz'}]}
|
262
|
+
OUT QUERY: '(`foo` > ?) OR (`foo` < ?)'
|
263
|
+
OUT BIND: ['bar','baz']
|
264
|
+
|
265
|
+
IN: {'foo'=>{:AND => [{'>' => 'bar'},{'<' => 'baz'}]}}
|
266
|
+
OUT QUERY: '(`foo` > ?) AND (`foo` < ?)'
|
267
|
+
OUT BIND: ['bar','baz']
|
268
|
+
|
269
|
+
IN: {'foo'=>{:AND => ['foo','bar','baz']}}
|
270
|
+
OUT QUERY: '(`foo` = ?) AND (`foo` = ?) AND (`foo` = ?)'
|
271
|
+
OUT BIND: ['foo','bar','baz']
|
272
|
+
|
273
|
+
IN: {'foo_id'=>{'IN' => sql_raw('SELECT foo_id FROM bar WHERE t=?',44)}}
|
274
|
+
OUT QUERY: '`foo_id` IN (SELECT foo_id FROM bar WHERE t=?)'
|
275
|
+
OUT BIND: [44]
|
276
|
+
|
277
|
+
# IN: ['foo_id',\['MATCH (col1, col2) AGAINST (?)','apples']]
|
278
|
+
# OUT QUERY: '`foo_id` MATCH (col1, col2) AGAINST (?)'
|
279
|
+
# OUT BIND: ['apples']
|
280
|
+
|
281
|
+
IN: {'foo_id'=>nil}
|
282
|
+
OUT QUERY: '`foo_id` IS NULL'
|
283
|
+
OUT BIND: []
|
284
|
+
|
285
|
+
IN: {'foo_id'=>{'IN' => []}}
|
286
|
+
OUT QUERY: '0=1'
|
287
|
+
OUT BIND: []
|
288
|
+
|
289
|
+
IN: {'foo_id'=>{'NOT IN' => []}}
|
290
|
+
OUT QUERY: '1=1'
|
291
|
+
OUT BIND: []
|
292
|
+
|
293
|
+
# IN: ['foo_id', [123,sql_type(\3, SQL_INTEGER)]]
|
294
|
+
# OUT QUERY: '`foo_id` IN (?, ?)'
|
295
|
+
# OUT BIND: (123, sql_type(\3, SQL_INTEGER))
|
296
|
+
#
|
297
|
+
# IN: ['foo_id', sql_type(\3, SQL_INTEGER)]
|
298
|
+
# OUT QUERY: '`foo_id` = ?'
|
299
|
+
# OUT BIND: sql_type(\3, SQL_INTEGER)
|
300
|
+
#
|
301
|
+
# IN: ['created_on', { '>', \'DATE_SUB(NOW(), INTERVAL 1 DAY)' }]
|
302
|
+
# OUT QUERY: '`created_on` > DATE_SUB(NOW(), INTERVAL 1 DAY)'
|
303
|
+
# OUT BIND:
|
304
|
+
|
305
|
+
It is also possible to use the functions exported by C<SQL::QueryMaker> to define the conditions.
|
306
|
+
|
307
|
+
IN: {'foo' => sql_in(['bar','baz'])}
|
308
|
+
OUT QUERY: '`foo` IN (?,?)'
|
309
|
+
OUT BIND: ['bar','baz']
|
310
|
+
|
311
|
+
IN: {'foo' => sql_lt(3)}
|
312
|
+
OUT QUERY: '`foo` < ?'
|
313
|
+
OUT BIND: [3]
|
314
|
+
|
315
|
+
IN: {'foo' => sql_not_in(['bar','baz'])}
|
316
|
+
OUT QUERY: '`foo` NOT IN (?,?)'
|
317
|
+
OUT BIND: ['bar','baz']
|
318
|
+
|
319
|
+
IN: {'foo' => sql_ne('bar')}
|
320
|
+
OUT QUERY: '`foo` != ?'
|
321
|
+
OUT BIND: ['bar']
|
322
|
+
|
323
|
+
IN: {'foo' => sql_is_not_null()}
|
324
|
+
OUT QUERY: '`foo` IS NOT NULL'
|
325
|
+
OUT BIND: []
|
326
|
+
|
327
|
+
IN: {'foo' => sql_between('1','2')}
|
328
|
+
OUT QUERY: '`foo` BETWEEN ? AND ?'
|
329
|
+
OUT BIND: ['1','2']
|
330
|
+
|
331
|
+
IN: {'foo' => sql_like('xaic%')}
|
332
|
+
OUT QUERY: '`foo` LIKE ?'
|
333
|
+
OUT BIND: ['xaic%']
|
334
|
+
|
335
|
+
IN: {'foo' => sql_or([sql_gt('bar'), sql_lt('baz')])}
|
336
|
+
OUT QUERY: '(`foo` > ?) OR (`foo` < ?)'
|
337
|
+
OUT BIND: ['bar','baz']
|
338
|
+
|
339
|
+
IN: {'foo' => sql_and([sql_gt('bar'), sql_lt('baz')])}
|
340
|
+
OUT QUERY: '(`foo` > ?) AND (`foo` < ?)'
|
341
|
+
OUT BIND: ['bar','baz']
|
342
|
+
|
343
|
+
IN: {'foo_id' => sql_op('IN (SELECT foo_id FROM bar WHERE t=?)',[44])}
|
344
|
+
OUT QUERY: '`foo_id` IN (SELECT foo_id FROM bar WHERE t=?)'
|
345
|
+
OUT BIND: [44]
|
346
|
+
|
347
|
+
IN: {'foo_id' => sql_in([sql_raw('SELECT foo_id FROM bar WHERE t=?',44)])}
|
348
|
+
OUT QUERY: '`foo_id` IN ((SELECT foo_id FROM bar WHERE t=?))'
|
349
|
+
OUT BIND: [44]
|
350
|
+
|
351
|
+
IN: {'foo_id' => sql_op('MATCH (@) AGAINST (?)',['apples'])}
|
352
|
+
OUT QUERY: 'MATCH (`foo_id`) AGAINST (?)'
|
353
|
+
OUT BIND: ['apples']
|
354
|
+
|
355
|
+
IN: {'foo_id'=>sql_in([])}
|
356
|
+
OUT QUERY: '0=1'
|
357
|
+
OUT BIND: []
|
358
|
+
|
359
|
+
IN: {'foo_id'=>sql_not_in([])}
|
360
|
+
OUT QUERY: '1=1'
|
361
|
+
OUT BIND: []
|
362
|
+
|
363
|
+
# IN: ['foo_id', sql_type(\3, SQL_INTEGER)]
|
364
|
+
# OUT QUERY: '`foo_id` = ?'
|
365
|
+
# OUT BIND: sql_type(\3, SQL_INTEGER)
|
366
|
+
#
|
367
|
+
# IN: ['foo_id', sql_in([sql_type(\3, SQL_INTEGER)])]
|
368
|
+
# OUT QUERY: '`foo_id` IN (?)'
|
369
|
+
# OUT BIND: sql_type(\3, SQL_INTEGER)
|
370
|
+
#
|
371
|
+
# IN: ['created_on', sql_gt(sql_raw('DATE_SUB(NOW(), INTERVAL 1 DAY)')) ]
|
372
|
+
# OUT QUERY: '`created_on` > DATE_SUB(NOW(), INTERVAL 1 DAY)'
|
373
|
+
# OUT BIND:
|
374
|
+
|
375
|
+
=head1 SEE ALSO
|
376
|
+
|
377
|
+
L<SQL::Maker>
|
378
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'sql/query_maker'
|
2
|
+
require 'sql/maker/select_set'
|
3
|
+
|
4
|
+
module SQL::Maker::Helper
|
5
|
+
# SQL::QueryMaker Helper
|
6
|
+
(%w[and or in not_in op raw] + SQL::QueryMaker::FNOP.keys).each do |fn|
|
7
|
+
method = "sql_#{fn}" # sql_and
|
8
|
+
define_method(method) do |*args|
|
9
|
+
SQL::QueryMaker.send(method, *args)
|
10
|
+
end
|
11
|
+
module_function method
|
12
|
+
end
|
13
|
+
|
14
|
+
# SQL::Maker::SelectSet Helper
|
15
|
+
SQL::Maker::SelectSet::FNOP.each do |fn|
|
16
|
+
method = "sql_#{fn}" # sql_union
|
17
|
+
define_method(method) do |*args|
|
18
|
+
SQL::Maker::SelectSet.send(method, *args)
|
19
|
+
end
|
20
|
+
module_function method
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# reference: ActiveRecord::ConnectionAdapter::Quoting
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module SQL::Maker::Quoting
|
5
|
+
# Quotes the value rather than column name to help prevent
|
6
|
+
# {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
|
7
|
+
def self.quote(value, column = nil)
|
8
|
+
case value
|
9
|
+
when String
|
10
|
+
value = value.to_s
|
11
|
+
return "'#{quote_string(value)}'" unless column
|
12
|
+
|
13
|
+
case column.type
|
14
|
+
when :integer then value.to_i.to_s
|
15
|
+
when :float then value.to_f.to_s
|
16
|
+
else
|
17
|
+
"'#{quote_string(value)}'"
|
18
|
+
end
|
19
|
+
|
20
|
+
when true, false
|
21
|
+
if column && column.type == :integer
|
22
|
+
value ? '1' : '0'
|
23
|
+
else
|
24
|
+
value ? quoted_true : quoted_false
|
25
|
+
end
|
26
|
+
# BigDecimals need to be put in a non-normalized form and quoted.
|
27
|
+
when nil then "NULL"
|
28
|
+
when BigDecimal then value.to_s('F')
|
29
|
+
when Numeric then value.to_s
|
30
|
+
when Date, Time then "'#{quoted_date(value)}'"
|
31
|
+
when Symbol then "'#{quote_string(value.to_s)}'"
|
32
|
+
when Class then "'#{value.to_s}'"
|
33
|
+
else
|
34
|
+
"'#{quote_string(YAML.dump(value))}'"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Cast a +value+ to a type that the database understands. For example,
|
39
|
+
# SQLite does not understand dates, so this method will convert a Date
|
40
|
+
# to a String.
|
41
|
+
def self.type_cast(value, column)
|
42
|
+
return value.id if value.respond_to?(:quoted_id)
|
43
|
+
|
44
|
+
case value
|
45
|
+
when String
|
46
|
+
value = value.to_s
|
47
|
+
return value unless column
|
48
|
+
|
49
|
+
case column.type
|
50
|
+
when :integer then value.to_i
|
51
|
+
when :float then value.to_f
|
52
|
+
else
|
53
|
+
value
|
54
|
+
end
|
55
|
+
|
56
|
+
when true, false
|
57
|
+
if column && column.type == :integer
|
58
|
+
value ? 1 : 0
|
59
|
+
else
|
60
|
+
value ? 't' : 'f'
|
61
|
+
end
|
62
|
+
# BigDecimals need to be put in a non-normalized form and quoted.
|
63
|
+
when nil then nil
|
64
|
+
when BigDecimal then value.to_s('F')
|
65
|
+
when Numeric then value
|
66
|
+
when Date, Time then quoted_date(value)
|
67
|
+
when Symbol then value.to_s
|
68
|
+
else
|
69
|
+
to_type = column ? " to #{column.type}" : ""
|
70
|
+
raise TypeError, "can't cast #{value.class}#{to_type}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Quotes a string, escaping any ' (single quote) and \ (backslash)
|
75
|
+
# characters.
|
76
|
+
def self.quote_string(s)
|
77
|
+
s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)
|
78
|
+
end
|
79
|
+
|
80
|
+
# # def quote_string(s)
|
81
|
+
# def self.define_quote_string
|
82
|
+
# klass = self.singleton_class
|
83
|
+
# return if klass.method_defined?(:quote_string)
|
84
|
+
# if @driver == 'mysql' and defined?(Mysql2::Client)
|
85
|
+
# klass.send(:define_method, :quote_string) do |s|
|
86
|
+
# Mysql2::Client.escape(s) if s
|
87
|
+
# end
|
88
|
+
# elsif @driver == 'postgresql' and defined?(PG::Connection)
|
89
|
+
# klass.send(:define_method, :quote_string) do |s|
|
90
|
+
# PG::Connection.escape_string(s) if s
|
91
|
+
# end
|
92
|
+
# else
|
93
|
+
# # Escape any ' (single quote) and \ (backslash)
|
94
|
+
# # reference: ActiveRecord::ConnectionAdapter::Quoting
|
95
|
+
# klass.send(:define_method, :quote_string) do |s|
|
96
|
+
# s.gsub(/\\/, '\&\&').gsub(/'/, "''") if s # ' (for ruby-mode)
|
97
|
+
# end
|
98
|
+
# end
|
99
|
+
# end
|
100
|
+
|
101
|
+
# Quotes the column name. Defaults to no quoting.
|
102
|
+
def self.quote_column_name(column_name)
|
103
|
+
column_name
|
104
|
+
end
|
105
|
+
|
106
|
+
# Quotes the table name. Defaults to column name quoting.
|
107
|
+
def self.quote_table_name(table_name)
|
108
|
+
quote_column_name(table_name)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Override to return the quoted table name for assignment. Defaults to
|
112
|
+
# table quoting.
|
113
|
+
#
|
114
|
+
# This works for mysql and mysql2 where table.column can be used to
|
115
|
+
# resolve ambiguity.
|
116
|
+
#
|
117
|
+
# We override this in the sqlite and postgresql adapters to use only
|
118
|
+
# the column name (as per syntax requirements).
|
119
|
+
def self.quote_table_name_for_assignment(table, attr)
|
120
|
+
quote_table_name("#{table}.#{attr}")
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.quoted_true
|
124
|
+
"'t'"
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.quoted_false
|
128
|
+
"'f'"
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.quoted_date(value)
|
132
|
+
if value.is_a?(Time)
|
133
|
+
# ToDo: getutc support
|
134
|
+
value = value.getlocal
|
135
|
+
end
|
136
|
+
value.strftime("%Y-%m-%d %H:%M:%S")
|
137
|
+
end
|
138
|
+
end
|