pod4 0.7.2 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.hgignore +1 -0
- data/.hgtags +1 -0
- data/Gemfile +3 -1
- data/Rakefile +17 -5
- data/lib/pod4/pg_interface.rb +81 -57
- data/lib/pod4/sequel_interface.rb +61 -6
- data/lib/pod4/sql_helper.rb +229 -0
- data/lib/pod4/tds_interface.rb +65 -50
- data/lib/pod4/version.rb +1 -1
- data/md/roadmap.md +81 -25
- data/spec/common/basic_model_spec.rb +5 -0
- data/spec/common/model_spec.rb +49 -7
- data/spec/common/nebulous_interface_spec.rb +2 -0
- data/spec/common/sequel_interface_pg_spec.rb +511 -0
- data/spec/common/sql_helper_spec.rb +299 -0
- data/spec/jruby/sequel_interface_jdbc_ms_spec.rb +525 -0
- data/spec/jruby/sequel_interface_jdbc_pg_spec.rb +520 -0
- data/spec/mri/pg_interface_spec.rb +140 -16
- data/spec/mri/sequel_interface_spec.rb +146 -13
- data/spec/mri/tds_interface_spec.rb +82 -12
- metadata +11 -4
- data/spec/jruby/pg_interface_spec.rb +0 -469
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 14459fc92264f22a30f5fc92078d2de5343102b0
|
4
|
+
data.tar.gz: 8595eb150b4615d49ba9447972a1dc25bca60406
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0002b34674f809b1320cb0d47b05f8c1514eb91d2993639d5210c2ac4b9be08d57d08c7edc47112f657c326204be4301977037a2363772108a21e602a09fbde6
|
7
|
+
data.tar.gz: e983618952a89b09f28514bcf7bf299cf261f3e33347444b9a0a0a14444ce69fedde0e3c30c7972e254486471162285b34ebc33dfdd70de29e7a3aa9a15bf0c5
|
data/.hgignore
CHANGED
data/.hgtags
CHANGED
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1,8 +1,3 @@
|
|
1
|
-
#require "bundler/gem_tasks"
|
2
|
-
#require "rspec/core/rake_task"
|
3
|
-
|
4
|
-
#RSpec::Core::RakeTask.new(:spec)
|
5
|
-
|
6
1
|
desc "Push doc to HARS"
|
7
2
|
task :hars do
|
8
3
|
sh "rsync -aP --delete doc/ /home/hars/hars/public/pod4"
|
@@ -13,6 +8,7 @@ task :retag do
|
|
13
8
|
sh "ripper-tags -R"
|
14
9
|
end
|
15
10
|
|
11
|
+
|
16
12
|
namespace :rspec do
|
17
13
|
|
18
14
|
desc "run tests (mri)"
|
@@ -25,4 +21,20 @@ namespace :rspec do
|
|
25
21
|
sh "rspec spec/common spec/jruby"
|
26
22
|
end
|
27
23
|
|
24
|
+
desc "run one test (pass as parameter)"
|
25
|
+
task :one do |task, args|
|
26
|
+
|
27
|
+
if args.extras.count > 0 # rake rspec[path/to/file]
|
28
|
+
sh "rspec #{args.extras.first}"
|
29
|
+
|
30
|
+
elsif ARGV.count == 2 && File.exist?(ARGV[1]) # rake rspec path/to/file
|
31
|
+
sh "rspec #{ARGV[1]}"
|
32
|
+
|
33
|
+
else
|
34
|
+
raise "You need to specify a test"
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
28
39
|
end
|
40
|
+
|
data/lib/pod4/pg_interface.rb
CHANGED
@@ -5,6 +5,7 @@ require 'bigdecimal'
|
|
5
5
|
|
6
6
|
require_relative 'interface'
|
7
7
|
require_relative 'errors'
|
8
|
+
require_relative 'sql_helper'
|
8
9
|
|
9
10
|
|
10
11
|
module Pod4
|
@@ -23,6 +24,7 @@ module Pod4
|
|
23
24
|
# end
|
24
25
|
#
|
25
26
|
class PgInterface < Interface
|
27
|
+
include SQLHelper
|
26
28
|
|
27
29
|
attr_reader :id_fld
|
28
30
|
|
@@ -91,29 +93,15 @@ module Pod4
|
|
91
93
|
def table; self.class.table; end
|
92
94
|
def id_fld; self.class.id_fld; end
|
93
95
|
|
94
|
-
def quoted_table
|
95
|
-
schema ? %Q|"#{schema}"."#{table}"| : %Q|"#{table}"|
|
96
|
-
end
|
97
|
-
|
98
96
|
|
99
97
|
##
|
100
|
-
# Selection is whatever Sequel's `where` supports.
|
101
98
|
#
|
102
99
|
def list(selection=nil)
|
103
100
|
raise(ArgumentError, 'selection parameter is not a hash') \
|
104
101
|
unless selection.nil? || selection.respond_to?(:keys)
|
105
102
|
|
106
|
-
|
107
|
-
|
108
|
-
sql = %Q|select *
|
109
|
-
from #{quoted_table}
|
110
|
-
where #{sel};|
|
111
|
-
|
112
|
-
else
|
113
|
-
sql = %Q|select * from #{quoted_table};|
|
114
|
-
end
|
115
|
-
|
116
|
-
select(sql) {|r| Octothorpe.new(r) }
|
103
|
+
sql, vals = sql_select(nil, selection)
|
104
|
+
selectp(sql, *vals) {|r| Octothorpe.new(r) }
|
117
105
|
|
118
106
|
rescue => e
|
119
107
|
handle_error(e)
|
@@ -130,15 +118,8 @@ module Pod4
|
|
130
118
|
raise(ArgumentError, "Bad type for record parameter") \
|
131
119
|
unless record.kind_of?(Hash) || record.kind_of?(Octothorpe)
|
132
120
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
sql = %Q|insert into #{quoted_table}
|
137
|
-
( #{ks} )
|
138
|
-
values( #{vs} )
|
139
|
-
returning "#{id_fld}";|
|
140
|
-
|
141
|
-
x = select(sql)
|
121
|
+
sql, vals = sql_insert(record)
|
122
|
+
x = selectp(sql, *vals)
|
142
123
|
x.first[id_fld]
|
143
124
|
|
144
125
|
rescue => e
|
@@ -152,11 +133,9 @@ module Pod4
|
|
152
133
|
def read(id)
|
153
134
|
raise(ArgumentError, "ID parameter is nil") if id.nil?
|
154
135
|
|
155
|
-
sql =
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
Octothorpe.new( select(sql).first )
|
136
|
+
sql, vals = sql_select(nil, id_fld => id)
|
137
|
+
rows = selectp(sql, *vals)
|
138
|
+
Octothorpe.new(rows.first)
|
160
139
|
|
161
140
|
rescue => e
|
162
141
|
# Select has already wrapped the error in a Pod4Error, but in this case we want to catch
|
@@ -177,13 +156,9 @@ module Pod4
|
|
177
156
|
unless record.kind_of?(Hash) || record.kind_of?(Octothorpe)
|
178
157
|
|
179
158
|
read_or_die(id)
|
180
|
-
sets = record.map {|k,v| %Q| "#{k}" = #{quote v}| }.join(',')
|
181
159
|
|
182
|
-
sql =
|
183
|
-
|
184
|
-
where "#{id_fld}" = #{quote id};|
|
185
|
-
|
186
|
-
execute(sql)
|
160
|
+
sql, vals = sql_update(record, id_fld => id)
|
161
|
+
executep(sql, *vals)
|
187
162
|
|
188
163
|
self
|
189
164
|
|
@@ -197,7 +172,9 @@ module Pod4
|
|
197
172
|
#
|
198
173
|
def delete(id)
|
199
174
|
read_or_die(id)
|
200
|
-
|
175
|
+
|
176
|
+
sql, vals = sql_delete(id_fld => id)
|
177
|
+
executep(sql, *vals)
|
201
178
|
|
202
179
|
self
|
203
180
|
|
@@ -250,6 +227,44 @@ module Pod4
|
|
250
227
|
end
|
251
228
|
|
252
229
|
|
230
|
+
##
|
231
|
+
# Run SQL code on the server as per select() but with parameter insertion.
|
232
|
+
#
|
233
|
+
# Placeholders in the SQL string should all be %s as per sql_helper methods.
|
234
|
+
# Values should be as returned by sql_helper methods.
|
235
|
+
#
|
236
|
+
def selectp(sql, *vals)
|
237
|
+
raise(ArgumentError, "Bad SQL parameter") unless sql.kind_of?(String)
|
238
|
+
|
239
|
+
ensure_connection
|
240
|
+
|
241
|
+
Pod4.logger.debug(__FILE__){ "select: #{sql} #{vals.inspect}" }
|
242
|
+
|
243
|
+
rows = []
|
244
|
+
@client.exec_params( *parse_for_params(sql, vals) ) do |query|
|
245
|
+
oids = make_oid_hash(query)
|
246
|
+
|
247
|
+
query.each do |r|
|
248
|
+
row = cast_row_fudge(r, oids)
|
249
|
+
|
250
|
+
if block_given?
|
251
|
+
rows << yield(row)
|
252
|
+
else
|
253
|
+
rows << row
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
@client.cancel
|
260
|
+
|
261
|
+
rows
|
262
|
+
|
263
|
+
rescue => e
|
264
|
+
handle_error(e)
|
265
|
+
end
|
266
|
+
|
267
|
+
|
253
268
|
##
|
254
269
|
# Run SQL code on the server; return true or false for success or failure
|
255
270
|
#
|
@@ -266,6 +281,25 @@ module Pod4
|
|
266
281
|
end
|
267
282
|
|
268
283
|
|
284
|
+
##
|
285
|
+
# Run SQL code on the server as per execute() but with parameter insertion.
|
286
|
+
#
|
287
|
+
# Placeholders in the SQL string should all be %s as per sql_helper methods.
|
288
|
+
# Values should be as returned by sql_helper methods.
|
289
|
+
#
|
290
|
+
def executep(sql, *vals)
|
291
|
+
raise(ArgumentError, "Bad SQL parameter") unless sql.kind_of?(String)
|
292
|
+
|
293
|
+
ensure_connection
|
294
|
+
|
295
|
+
Pod4.logger.debug(__FILE__){ "parameterised execute: #{sql}" }
|
296
|
+
@client.exec_params( *parse_for_params(sql, vals) )
|
297
|
+
|
298
|
+
rescue => e
|
299
|
+
handle_error(e)
|
300
|
+
end
|
301
|
+
|
302
|
+
|
269
303
|
protected
|
270
304
|
|
271
305
|
|
@@ -369,26 +403,6 @@ module Pod4
|
|
369
403
|
end
|
370
404
|
|
371
405
|
|
372
|
-
def quote(fld)
|
373
|
-
|
374
|
-
case fld
|
375
|
-
when Date, Time
|
376
|
-
"'#{fld}'"
|
377
|
-
when String
|
378
|
-
"'#{fld.gsub("'", "''")}'"
|
379
|
-
when Symbol
|
380
|
-
"'#{fld.to_s.gsub("'", "''")}'"
|
381
|
-
when BigDecimal
|
382
|
-
fld.to_f
|
383
|
-
when nil
|
384
|
-
'NULL'
|
385
|
-
else
|
386
|
-
fld
|
387
|
-
end
|
388
|
-
|
389
|
-
end
|
390
|
-
|
391
|
-
|
392
406
|
private
|
393
407
|
|
394
408
|
|
@@ -404,6 +418,7 @@ module Pod4
|
|
404
418
|
end
|
405
419
|
|
406
420
|
|
421
|
+
|
407
422
|
##
|
408
423
|
# Cast a query row
|
409
424
|
#
|
@@ -449,6 +464,15 @@ module Pod4
|
|
449
464
|
raise CantContinue, "'No record found with ID '#{id}'" if read(id).empty?
|
450
465
|
end
|
451
466
|
|
467
|
+
|
468
|
+
def parse_for_params(sql, vals)
|
469
|
+
new_params = sql.scan("%s").map.with_index{|e,i| "$#{i + 1}" }
|
470
|
+
new_vals = vals.map{|v| v ? quote(v, nil).to_s : nil }
|
471
|
+
|
472
|
+
[ sql_subst(sql, *new_params), new_vals ]
|
473
|
+
end
|
474
|
+
|
475
|
+
|
452
476
|
end
|
453
477
|
|
454
478
|
|
@@ -83,6 +83,14 @@ module Pod4
|
|
83
83
|
@table = db[schema ? "#{schema}__#{table}".to_sym : table]
|
84
84
|
@id_fld = self.class.id_fld
|
85
85
|
|
86
|
+
# Work around a problem with jdbc-postgresql where it throws an exception whenever it sees
|
87
|
+
# the money type. This workaround actually allows us to return a BigDecimal, so it's better
|
88
|
+
# than using postgres_pr when under jRuby!
|
89
|
+
if @db.uri =~ /jdbc:postgresql/
|
90
|
+
@db.conversion_procs[790] = ->(s){BigDecimal.new s[1..-1] rescue nil}
|
91
|
+
Sequel::JDBC::Postgres::Dataset::PG_SPECIFIC_TYPES << Java::JavaSQL::Types::DOUBLE
|
92
|
+
end
|
93
|
+
|
86
94
|
rescue => e
|
87
95
|
handle_error(e)
|
88
96
|
end
|
@@ -118,16 +126,25 @@ module Pod4
|
|
118
126
|
##
|
119
127
|
# Record is a hash of field: value
|
120
128
|
#
|
121
|
-
# By a happy coincidence, insert returns the unique ID for the record, which is just what we
|
122
|
-
# want to do, too.
|
123
|
-
#
|
124
129
|
def create(record)
|
125
130
|
raise(ArgumentError, "Bad type for record parameter") \
|
126
131
|
unless record.kind_of?(Hash) || record.kind_of?(Octothorpe)
|
127
132
|
|
128
133
|
Pod4.logger.debug(__FILE__) { "Creating #{self.class.table}: #{record.inspect}" }
|
129
134
|
|
130
|
-
@table.insert( sanitise_hash(record.to_h) )
|
135
|
+
id = @table.insert( sanitise_hash(record.to_h) )
|
136
|
+
|
137
|
+
# Sequel doesn't return the key unless it is an autoincrement; otherwise it turns a row
|
138
|
+
# number regardless. It probably doesn' t matter, but try to catch that anyway.
|
139
|
+
# (bamf: If your non-incrementing key happens to be an integer, this won't work...)
|
140
|
+
|
141
|
+
id_val = record[id_fld] || record[id_fld.to_s]
|
142
|
+
|
143
|
+
if (id.kind_of?(Fixnum) || id.nil?) && id_val && !id_val.kind_of?(Fixnum)
|
144
|
+
id_val
|
145
|
+
else
|
146
|
+
id
|
147
|
+
end
|
131
148
|
|
132
149
|
rescue => e
|
133
150
|
handle_error(e)
|
@@ -191,27 +208,65 @@ module Pod4
|
|
191
208
|
#
|
192
209
|
def execute(sql)
|
193
210
|
raise(ArgumentError, "Bad sql parameter") unless sql.kind_of?(String)
|
194
|
-
|
195
211
|
Pod4.logger.debug(__FILE__) { "Execute SQL: #{sql}" }
|
212
|
+
|
196
213
|
@db.run(sql)
|
197
214
|
rescue => e
|
198
215
|
handle_error(e)
|
199
216
|
end
|
200
217
|
|
201
218
|
|
219
|
+
##
|
220
|
+
# Bonus method: execute SQL as per execute(), but parameterised.
|
221
|
+
#
|
222
|
+
# Use ? as a placeholder in the SQL
|
223
|
+
# mode is either :insert :update or :delete
|
224
|
+
# Please quote values for yourself, we don't.
|
225
|
+
#
|
226
|
+
# "update and delete should return the number of rows affected, and insert should return the
|
227
|
+
# autogenerated primary integer key for the row inserted (if any)"
|
228
|
+
#
|
229
|
+
def executep(sql, mode, *values)
|
230
|
+
raise(ArgumentError, "Bad sql parameter") unless sql.kind_of?(String)
|
231
|
+
raise(ArgumentError, "Bad mode parameter") unless %i|insert delete update|.include?(mode)
|
232
|
+
Pod4.logger.debug(__FILE__) { "Parameterised execute #{mode} SQL: #{sql}" }
|
233
|
+
|
234
|
+
@db[sql, *values].send(mode)
|
235
|
+
rescue => e
|
236
|
+
handle_error(e)
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
|
202
241
|
##
|
203
242
|
# Bonus method: execute arbitrary SQL and return the resulting dataset as a Hash.
|
204
243
|
#
|
205
244
|
def select(sql)
|
206
245
|
raise(ArgumentError, "Bad sql parameter") unless sql.kind_of?(String)
|
207
|
-
|
208
246
|
Pod4.logger.debug(__FILE__) { "Select SQL: #{sql}" }
|
247
|
+
|
209
248
|
@db[sql].all
|
210
249
|
rescue => e
|
211
250
|
handle_error(e)
|
212
251
|
end
|
213
252
|
|
214
253
|
|
254
|
+
##
|
255
|
+
# Bonus method: execute arbitrary SQL as per select(), but parameterised.
|
256
|
+
#
|
257
|
+
# Use ? as a placeholder in the SQL
|
258
|
+
# Please quote values for yourself, we don't.
|
259
|
+
#
|
260
|
+
def selectp(sql, *values)
|
261
|
+
raise(ArgumentError, "Bad sql parameter") unless sql.kind_of?(String)
|
262
|
+
Pod4.logger.debug(__FILE__) { "Parameterised select SQL: #{sql}" }
|
263
|
+
|
264
|
+
@db.fetch(sql, *values).all
|
265
|
+
rescue => e
|
266
|
+
handle_error(e)
|
267
|
+
end
|
268
|
+
|
269
|
+
|
215
270
|
protected
|
216
271
|
|
217
272
|
|
@@ -0,0 +1,229 @@
|
|
1
|
+
module Pod4
|
2
|
+
|
3
|
+
|
4
|
+
##
|
5
|
+
# A mixin to help interfaces that need to generate SQL
|
6
|
+
#
|
7
|
+
# Most of these methods return two things: an sql string with %s where each value should be; and
|
8
|
+
# an array of values to insert.
|
9
|
+
#
|
10
|
+
# You can override placeholder() to change %s to something else. You can override quote() to
|
11
|
+
# change how values are quoted and quote_fld() to change how column names are quoted. Of course
|
12
|
+
# the SQL here won't be suitable for all data source libraries even then, but, it gives us some
|
13
|
+
# common ground to start with.
|
14
|
+
#
|
15
|
+
# You can call sql_subst() to turn the SQL and the values array into actual SQL -- but don't do
|
16
|
+
# that; you should call the parameterised query routines for the data source library instead.
|
17
|
+
#
|
18
|
+
module SQLHelper
|
19
|
+
|
20
|
+
|
21
|
+
##
|
22
|
+
# Return the name of the table quoted as for inclusion in SQL. Might include the name of the
|
23
|
+
# schema, too, if you have set one.
|
24
|
+
#
|
25
|
+
# table() is mandatory for an Interface, and if we have a schema it will be schema().
|
26
|
+
#
|
27
|
+
def quoted_table
|
28
|
+
defined?(schema) && schema ? %Q|"#{schema}"."#{table}"| : %Q|"#{table}"|
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
|
35
|
+
##
|
36
|
+
# Given a list of fields and a selection hash, return an SQL string and an array of values
|
37
|
+
# for an SQL SELECT.
|
38
|
+
#
|
39
|
+
def sql_select(fields, selection)
|
40
|
+
flds = fields ? Array(fields).flatten.map{|f| quote_field f} : ["*"]
|
41
|
+
|
42
|
+
wsql, wvals = sql_where(selection)
|
43
|
+
|
44
|
+
sql = %Q|select #{flds.join ','}
|
45
|
+
from #{quoted_table}
|
46
|
+
#{wsql};|
|
47
|
+
|
48
|
+
[sql, wvals]
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
##
|
53
|
+
# Given a column:value hash, return an SQL string and an array of values for an SQL INSERT.
|
54
|
+
#
|
55
|
+
# Note that we get the table ID field from id_fld, which is mandatory for an Interface.
|
56
|
+
#
|
57
|
+
def sql_insert(fldsValues)
|
58
|
+
raise ArgumentError, "Needs a field:value hash" if fldsValues.nil? || fldsValues.empty?
|
59
|
+
|
60
|
+
flds, vals = parse_fldsvalues(fldsValues)
|
61
|
+
ph = Array(placeholder).flatten * flds.count
|
62
|
+
|
63
|
+
sql = %Q|insert into #{quoted_table}
|
64
|
+
( #{flds.join ','} )
|
65
|
+
values( #{ph.join ','} )
|
66
|
+
returning #{quote_field id_fld};|
|
67
|
+
|
68
|
+
[sql, vals]
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
##
|
73
|
+
# Given a column:value hash and a selection hash, return an SQL string and an array of values
|
74
|
+
# for an SQL UPDATE.
|
75
|
+
#
|
76
|
+
def sql_update(fldsValues, selection)
|
77
|
+
raise ArgumentError, "Needs a field:value hash" if fldsValues.nil? || fldsValues.empty?
|
78
|
+
|
79
|
+
flds, vals = parse_fldsvalues(fldsValues)
|
80
|
+
sets = flds.map {|f| %Q| #{f} = #{placeholder}| }
|
81
|
+
|
82
|
+
wsql, wvals = sql_where(selection)
|
83
|
+
|
84
|
+
sql = %Q|update #{quoted_table}
|
85
|
+
set #{sets.join ','}
|
86
|
+
#{wsql};|
|
87
|
+
|
88
|
+
[sql, vals + wvals]
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
##
|
93
|
+
# Given a selection hash, return an SQL string and an array of values for an SQL DELETE.
|
94
|
+
#
|
95
|
+
def sql_delete(selection)
|
96
|
+
wsql, wval = sql_where(selection)
|
97
|
+
[ %Q|delete from #{quoted_table} #{wsql};|,
|
98
|
+
wval ]
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
##
|
103
|
+
# Given a selection hash, return an SQL string and an array of values
|
104
|
+
# for an SQL where clause.
|
105
|
+
#
|
106
|
+
# This is used internally; you probably don't need it unless you are trying to override
|
107
|
+
# sql_select(), sql_update() etc.
|
108
|
+
#
|
109
|
+
def sql_where(selection)
|
110
|
+
return ["", []] if (selection.nil? || selection == {})
|
111
|
+
|
112
|
+
flds, vals = parse_fldsvalues(selection)
|
113
|
+
|
114
|
+
[ "where " + flds.map {|f| %Q|#{f} = #{placeholder}| }.join(" and "),
|
115
|
+
vals ]
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
##
|
121
|
+
# Given a string which is supposedly the name of a column, return a string with the column name
|
122
|
+
# quoted for inclusion to SQL.
|
123
|
+
#
|
124
|
+
# Defaults to SQL standard double quotes. If you want something else, pass the new quote
|
125
|
+
# character as the optional second parameter, and/or override the method.
|
126
|
+
#
|
127
|
+
def quote_field(fld, qc=%q|"|)
|
128
|
+
raise ArgumentError, "bad field name" unless fld.kind_of?(String) || fld.kind_of?(Symbol)
|
129
|
+
%Q|#{qc}#{fld}#{qc}|
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
##
|
134
|
+
# Given some value, quote it for inclusion in SQL.
|
135
|
+
#
|
136
|
+
# Tries to follow the generic SQL standard -- single quotes for strings, NULL for nil, etc.
|
137
|
+
# If you want something else, pass a different quote character as the second parameter, and/or
|
138
|
+
# override the method.
|
139
|
+
#
|
140
|
+
# Note that this also turns 'O'Claire' into 'O''Claire', as required by SQL.
|
141
|
+
#
|
142
|
+
def quote(fld, qc=%q|'|)
|
143
|
+
|
144
|
+
case fld
|
145
|
+
when Date, Time
|
146
|
+
%Q|#{qc}#{fld}#{qc}|
|
147
|
+
when String
|
148
|
+
%Q|#{qc}#{fld.gsub("#{qc}", "#{qc}#{qc}")}#{qc}|
|
149
|
+
when Symbol
|
150
|
+
%Q|#{qc}#{fld.to_s.gsub("#{qc}", "#{qc}#{qc}")}#{qc}|
|
151
|
+
when BigDecimal
|
152
|
+
fld.to_f
|
153
|
+
when nil
|
154
|
+
"NULL"
|
155
|
+
else
|
156
|
+
fld
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
##
|
163
|
+
# Return the placeholder to use in place of values when we return SQL. Defaults to the
|
164
|
+
# Ruby-friendly %s. Override it if you want everything else.
|
165
|
+
#
|
166
|
+
def placeholder
|
167
|
+
"%s"
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
##
|
172
|
+
# Given a string (SQL) with %s placeholders and one or more values -- substitute the values for
|
173
|
+
# the placeholders.
|
174
|
+
#
|
175
|
+
# sql_subst("foo %s bar %s", "$1", "$2") #-> "foo $1 bar $2"
|
176
|
+
# sql_subst("foo %s bar %s", "$$"] ) #-> "foo $$ bar $$"
|
177
|
+
#
|
178
|
+
# You can use this to configure your SQL ready for the parameterised query routine that comes
|
179
|
+
# with your data library. Note: this does not work if you redefine placeholder().
|
180
|
+
#
|
181
|
+
# You could also use it to turn a sql-with-placeholders string into valid SQL, by passing the
|
182
|
+
# (quoted) values array that you got from sql_select, etc.:
|
183
|
+
#
|
184
|
+
# sql, vals = sql_select(nil, id => 4)
|
185
|
+
# validSQL = sql_subst( sql, *vals.map{|v| quote v} )
|
186
|
+
#
|
187
|
+
# Note: Don't do this. Dreadful idea.
|
188
|
+
# If at all possible you should instead get the data source library to combine these two
|
189
|
+
# things. This will protect you against SQL injection (or if not, the library has screwed up).
|
190
|
+
#
|
191
|
+
def sql_subst(sql, *args)
|
192
|
+
raise ArgumentError, "bad SQL" unless sql.kind_of? String
|
193
|
+
raise ArgumentError, "missing SQL" if sql.empty?
|
194
|
+
|
195
|
+
vals = args.map(&:to_s)
|
196
|
+
|
197
|
+
case
|
198
|
+
when vals.empty? then sql
|
199
|
+
when vals.size == 1 then sql.gsub("%s", vals.first)
|
200
|
+
else
|
201
|
+
raise ArgumentError, "wrong number of values" unless sql.scan("%s").count == vals.count
|
202
|
+
sql % args
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
##
|
208
|
+
# Helper routine: given a hash, quote the keys as column names and keep the values as they are
|
209
|
+
# (since we don't know whether your parameterised query routine in your data source library
|
210
|
+
# does that for you).
|
211
|
+
#
|
212
|
+
# Return the hash as two arrays, to ensure the ordering is consistent.
|
213
|
+
#
|
214
|
+
def parse_fldsvalues(hash)
|
215
|
+
flds = []; vals = []
|
216
|
+
|
217
|
+
hash.each do|f, v|
|
218
|
+
flds << quote_field(f.to_s)
|
219
|
+
vals << v
|
220
|
+
end
|
221
|
+
|
222
|
+
[flds, vals]
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
end
|
229
|
+
|