sql-maker 0.0.1
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.
- 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
|