pod4 0.7.2 → 0.8.0
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 +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
|
+
|