pod4 0.6.2
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/.hgignore +18 -0
- data/.hgtags +19 -0
- data/.rspec +4 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/LICENSE.md +21 -0
- data/README.md +556 -0
- data/Rakefile +30 -0
- data/lib/pod4/alert.rb +87 -0
- data/lib/pod4/basic_model.rb +137 -0
- data/lib/pod4/errors.rb +80 -0
- data/lib/pod4/interface.rb +110 -0
- data/lib/pod4/metaxing.rb +66 -0
- data/lib/pod4/model.rb +347 -0
- data/lib/pod4/nebulous_interface.rb +408 -0
- data/lib/pod4/null_interface.rb +148 -0
- data/lib/pod4/param.rb +29 -0
- data/lib/pod4/pg_interface.rb +460 -0
- data/lib/pod4/sequel_interface.rb +303 -0
- data/lib/pod4/tds_interface.rb +394 -0
- data/lib/pod4/version.rb +3 -0
- data/lib/pod4.rb +54 -0
- data/md/fixme.md +32 -0
- data/md/roadmap.md +69 -0
- data/pod4.gemspec +49 -0
- data/spec/README.md +19 -0
- data/spec/alert_spec.rb +173 -0
- data/spec/basic_model_spec.rb +220 -0
- data/spec/doc_no_pending.rb +5 -0
- data/spec/fixtures/database.rb +13 -0
- data/spec/model_spec.rb +760 -0
- data/spec/nebulous_interface_spec.rb +286 -0
- data/spec/null_interface_spec.rb +153 -0
- data/spec/param_spec.rb +89 -0
- data/spec/pg_interface_spec.rb +452 -0
- data/spec/pod4_spec.rb +88 -0
- data/spec/sequel_interface_spec.rb +466 -0
- data/spec/shared_examples_for_interface.rb +160 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/tds_interface_spec.rb +494 -0
- data/tags +106 -0
- metadata +316 -0
@@ -0,0 +1,303 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
require 'octothorpe'
|
3
|
+
|
4
|
+
require_relative 'interface'
|
5
|
+
require_relative 'errors'
|
6
|
+
|
7
|
+
|
8
|
+
module Pod4
|
9
|
+
|
10
|
+
|
11
|
+
##
|
12
|
+
# Pod4 Interface for a Sequel table.
|
13
|
+
#
|
14
|
+
# If your DB table is one-one with your model, you shouldn't need to override
|
15
|
+
# anything.
|
16
|
+
#
|
17
|
+
# Example:
|
18
|
+
# class CustomerInterface < SwingShift::SequelInterface
|
19
|
+
# set_table :customer
|
20
|
+
# set_id_fld :id
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# Data types: Sequel itself will translate to BigDecimal, Float, Integer,
|
24
|
+
# date, and datetime as appropriate -- but it also depends on the underlying
|
25
|
+
# adapter. TinyTds maps dates to strings, for example.
|
26
|
+
#
|
27
|
+
class SequelInterface < Interface
|
28
|
+
|
29
|
+
attr_reader :id_fld
|
30
|
+
|
31
|
+
|
32
|
+
class << self
|
33
|
+
#---
|
34
|
+
# These are set in the class because it keeps the model code cleaner: the
|
35
|
+
# definition of the interface stays in the interface, and doesn't leak
|
36
|
+
# out into the model.
|
37
|
+
#+++
|
38
|
+
|
39
|
+
|
40
|
+
##
|
41
|
+
# Use this to set the schema name (optional)
|
42
|
+
#
|
43
|
+
def set_schema(schema)
|
44
|
+
define_class_method(:schema) {schema.to_s.to_sym}
|
45
|
+
end
|
46
|
+
|
47
|
+
def schema; nil; end
|
48
|
+
|
49
|
+
|
50
|
+
##
|
51
|
+
# Set the table name.
|
52
|
+
#
|
53
|
+
def set_table(table)
|
54
|
+
define_class_method(:table) {table.to_s.to_sym}
|
55
|
+
end
|
56
|
+
|
57
|
+
def table
|
58
|
+
raise Pod4Error, "You need to use set_table to set the table name"
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
##
|
63
|
+
# Set the unique id field on the table.
|
64
|
+
#
|
65
|
+
def set_id_fld(idFld)
|
66
|
+
define_class_method(:id_fld) {idFld.to_s.to_sym}
|
67
|
+
end
|
68
|
+
|
69
|
+
def id_fld
|
70
|
+
raise Pod4Error, "You need to use set_id_fld to set the ID column name"
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
##
|
75
|
+
|
76
|
+
|
77
|
+
##
|
78
|
+
# Initialise the interface by passing it the Sequel DB object.
|
79
|
+
#
|
80
|
+
def initialize(db)
|
81
|
+
raise(ArgumentError, "Bad database") unless db.kind_of? Sequel::Database
|
82
|
+
|
83
|
+
raise(Pod4Error, 'no call to set_table in the interface definition') \
|
84
|
+
if self.class.table.nil?
|
85
|
+
|
86
|
+
raise(Pod4Error, 'no call to set_id_fld in the interface definition') \
|
87
|
+
if self.class.id_fld.nil?
|
88
|
+
|
89
|
+
@db = db # referemce to the db object
|
90
|
+
@table = db[schema ? "#{schema}__#{table}".to_sym : table]
|
91
|
+
@id_fld = self.class.id_fld
|
92
|
+
|
93
|
+
rescue => e
|
94
|
+
handle_error(e)
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def schema; self.class.schema; end
|
99
|
+
def table; self.class.table; end
|
100
|
+
def id_fld; self.class.id_fld; end
|
101
|
+
|
102
|
+
def quoted_table
|
103
|
+
if schema
|
104
|
+
%Q|#{@db.quote_identifier schema}.#{@db.quote_identifier table}|
|
105
|
+
else
|
106
|
+
@db.quote_identifier(table)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
|
112
|
+
##
|
113
|
+
# Selection is whatever Sequel's `where` supports.
|
114
|
+
#
|
115
|
+
def list(selection=nil)
|
116
|
+
sel = sanitise_hash(selection)
|
117
|
+
|
118
|
+
Pod4.logger.debug(__FILE__) do
|
119
|
+
"Listing #{self.class.table}: #{sel.inspect}"
|
120
|
+
end
|
121
|
+
|
122
|
+
(sel ? @table.where(sel) : @table.all).map {|x| Octothorpe.new(x) }
|
123
|
+
rescue => e
|
124
|
+
handle_error(e)
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
##
|
129
|
+
# Record is a hash of field: value
|
130
|
+
# By a happy coincidence, insert returns the unique ID for the record,
|
131
|
+
# which is just what we want to do, too.
|
132
|
+
#
|
133
|
+
def create(record)
|
134
|
+
raise(ArgumentError, "Bad type for record parameter") \
|
135
|
+
unless record.kind_of?(Hash) || record.kind_of?(Octothorpe)
|
136
|
+
|
137
|
+
Pod4.logger.debug(__FILE__) do
|
138
|
+
"Creating #{self.class.table}: #{record.inspect}"
|
139
|
+
end
|
140
|
+
|
141
|
+
@table.insert( sanitise_hash(record.to_h) )
|
142
|
+
|
143
|
+
rescue => e
|
144
|
+
handle_error(e)
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
##
|
149
|
+
# ID corresponds to whatever you set in set_id_fld
|
150
|
+
#
|
151
|
+
def read(id)
|
152
|
+
raise(ArgumentError, "ID parameter is nil") if id.nil?
|
153
|
+
|
154
|
+
Pod4.logger.debug(__FILE__) do
|
155
|
+
"Reading #{self.class.table} where #{@id_fld}=#{id}"
|
156
|
+
end
|
157
|
+
|
158
|
+
Octothorpe.new( @table[@id_fld => id] )
|
159
|
+
|
160
|
+
rescue Sequel::DatabaseError
|
161
|
+
raise CantContinue, "Problem reading record. Is '#{id}' really an ID?"
|
162
|
+
|
163
|
+
rescue => e
|
164
|
+
handle_error(e)
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
##
|
169
|
+
# ID is whatever you set in the interface using set_id_fld
|
170
|
+
# record should be a Hash or Octothorpe.
|
171
|
+
#
|
172
|
+
def update(id, record)
|
173
|
+
read_or_die(id)
|
174
|
+
|
175
|
+
Pod4.logger.debug(__FILE__) do
|
176
|
+
"Updating #{self.class.table} where #{@id_fld}=#{id}: #{record.inspect}"
|
177
|
+
end
|
178
|
+
|
179
|
+
@table.where(@id_fld => id).update( sanitise_hash(record.to_h) )
|
180
|
+
self
|
181
|
+
rescue => e
|
182
|
+
handle_error(e)
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
##
|
187
|
+
# ID is whatever you set in the interface using set_id_fld
|
188
|
+
#
|
189
|
+
def delete(id)
|
190
|
+
read_or_die(id)
|
191
|
+
|
192
|
+
Pod4.logger.debug(__FILE__) do
|
193
|
+
"Deleting #{self.class.table} where #{@id_fld}=#{id}"
|
194
|
+
end
|
195
|
+
|
196
|
+
@table.where(@id_fld => id).delete
|
197
|
+
self
|
198
|
+
rescue => e
|
199
|
+
handle_error(e)
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
##
|
204
|
+
# Bonus method: execute arbitrary SQL. Returns nil.
|
205
|
+
#
|
206
|
+
def execute(sql)
|
207
|
+
raise(ArgumentError, "Bad sql parameter") unless sql.kind_of?(String)
|
208
|
+
|
209
|
+
Pod4.logger.debug(__FILE__) { "Execute SQL: #{sql}" }
|
210
|
+
@db.run(sql)
|
211
|
+
rescue => e
|
212
|
+
handle_error(e)
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
##
|
217
|
+
# Bonus method: execute arbitrary SQL and return the resulting dataset as a
|
218
|
+
# Hash.
|
219
|
+
#
|
220
|
+
def select(sql)
|
221
|
+
raise(ArgumentError, "Bad sql parameter") unless sql.kind_of?(String)
|
222
|
+
|
223
|
+
Pod4.logger.debug(__FILE__) { "Select SQL: #{sql}" }
|
224
|
+
@db[sql].all
|
225
|
+
rescue => e
|
226
|
+
handle_error(e)
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
protected
|
231
|
+
|
232
|
+
|
233
|
+
##
|
234
|
+
# Helper routine to handle or re-raise the right exception.
|
235
|
+
# Unless kaller is passed, we re-raise on the caller of the caller, which
|
236
|
+
# is likely the original bug
|
237
|
+
#
|
238
|
+
def handle_error(err, kaller=nil)
|
239
|
+
kaller ||= caller[1..-1]
|
240
|
+
|
241
|
+
Pod4.logger.error(__FILE__){ err.message }
|
242
|
+
|
243
|
+
case err
|
244
|
+
|
245
|
+
# Just raise the error as is
|
246
|
+
when ArgumentError,
|
247
|
+
Pod4::Pod4Error,
|
248
|
+
Pod4::CantContinue
|
249
|
+
|
250
|
+
raise err.class, err.message, kaller
|
251
|
+
|
252
|
+
# Special Case for validation
|
253
|
+
when Sequel::ValidationFailed,
|
254
|
+
Sequel::UniqueConstraintViolation,
|
255
|
+
Sequel::ForeignKeyConstraintViolation
|
256
|
+
|
257
|
+
raise Pod4::ValidationError, err.message, kaller
|
258
|
+
|
259
|
+
# This is more serious
|
260
|
+
when Sequel::DatabaseError
|
261
|
+
raise Pod4::DatabaseError, err.message, kaller
|
262
|
+
|
263
|
+
# The default is to raise a generic Pod4 error.
|
264
|
+
else
|
265
|
+
raise Pod4::Pod4Error, err.message, kaller
|
266
|
+
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|
270
|
+
|
271
|
+
|
272
|
+
##
|
273
|
+
# Sequel behaves VERY oddly if you pass a symbol as a value to the hash you
|
274
|
+
# give to a selection,etc on a dataset. (It raises an error complaining that
|
275
|
+
# the symbol does not exist as a column in the table...)
|
276
|
+
#
|
277
|
+
def sanitise_hash(sel)
|
278
|
+
|
279
|
+
case sel
|
280
|
+
when Hash
|
281
|
+
sel.each_with_object({}) do |(k,v),m|
|
282
|
+
m[k] = v.kind_of?(Symbol) ? v.to_s : v
|
283
|
+
end
|
284
|
+
|
285
|
+
else
|
286
|
+
sel
|
287
|
+
|
288
|
+
end
|
289
|
+
|
290
|
+
end
|
291
|
+
|
292
|
+
|
293
|
+
private
|
294
|
+
|
295
|
+
|
296
|
+
def read_or_die(id)
|
297
|
+
raise CantContinue, "'No record found with ID '#{id}'" if read(id).empty?
|
298
|
+
end
|
299
|
+
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
end
|
@@ -0,0 +1,394 @@
|
|
1
|
+
require 'tiny_tds'
|
2
|
+
require 'octothorpe'
|
3
|
+
require 'date'
|
4
|
+
require 'time'
|
5
|
+
require 'bigdecimal'
|
6
|
+
|
7
|
+
require_relative 'interface'
|
8
|
+
require_relative 'errors'
|
9
|
+
|
10
|
+
|
11
|
+
module Pod4
|
12
|
+
|
13
|
+
|
14
|
+
##
|
15
|
+
# Pod4 Interface for requests on a SQL table via TinyTds.
|
16
|
+
#
|
17
|
+
# If your DB table is one-one with your model, you shouldn't need to override
|
18
|
+
# anything.
|
19
|
+
#
|
20
|
+
# Example:
|
21
|
+
# class CustomerInterface < SwingShift::TdsInterface
|
22
|
+
# set_db :fred
|
23
|
+
# set_table :customer
|
24
|
+
# set_id_fld :id
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
class TdsInterface < Interface
|
28
|
+
|
29
|
+
attr_reader :id_fld
|
30
|
+
|
31
|
+
|
32
|
+
class << self
|
33
|
+
#--
|
34
|
+
# These are set in the class because it keeps the model code cleaner: the
|
35
|
+
# definition of the interface stays in the interface, and doesn't leak
|
36
|
+
# out into the model.
|
37
|
+
#++
|
38
|
+
|
39
|
+
|
40
|
+
##
|
41
|
+
# Use this to set the database name.
|
42
|
+
#
|
43
|
+
def set_db(db)
|
44
|
+
define_class_method(:db) {db.to_s.to_sym}
|
45
|
+
end
|
46
|
+
|
47
|
+
def db
|
48
|
+
raise Pod4Error, "You need to use set_db to set the database name"
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
##
|
53
|
+
# Use this to set the schema name (optional)
|
54
|
+
#
|
55
|
+
def set_schema(schema)
|
56
|
+
define_class_method(:schema) {schema.to_s.to_sym}
|
57
|
+
end
|
58
|
+
|
59
|
+
def schema; nil; end
|
60
|
+
|
61
|
+
|
62
|
+
##
|
63
|
+
# Use this to set the name of the table
|
64
|
+
#
|
65
|
+
def set_table(table)
|
66
|
+
define_class_method(:table) {table.to_s.to_sym}
|
67
|
+
end
|
68
|
+
|
69
|
+
def table
|
70
|
+
raise Pod4Error, "You need to use set_table to set the table name"
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
##
|
75
|
+
# This sets the column that holds the unique id for the table
|
76
|
+
#
|
77
|
+
def set_id_fld(idFld)
|
78
|
+
define_class_method(:id_fld) {idFld.to_s.to_sym}
|
79
|
+
end
|
80
|
+
|
81
|
+
def id_fld
|
82
|
+
raise Pod4Error, "You need to use set_table to set the table name"
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
##
|
87
|
+
|
88
|
+
|
89
|
+
##
|
90
|
+
# Initialise the interface by passing it a TinyTds connection hash.
|
91
|
+
# For testing ONLY you can also pass an object which pretends to be a
|
92
|
+
# TinyTds client, in which case the hash is pretty much ignored.
|
93
|
+
#
|
94
|
+
def initialize(connectHash, testClient=nil)
|
95
|
+
|
96
|
+
raise(Pod4Error, 'no call to set_db in the interface definition') \
|
97
|
+
if self.class.db.nil?
|
98
|
+
|
99
|
+
raise(Pod4Error, 'no call to set_table in the interface definition') \
|
100
|
+
if self.class.table.nil?
|
101
|
+
|
102
|
+
raise(Pod4Error, 'no call to set_id_fld in the interface definition') \
|
103
|
+
if self.class.id_fld.nil?
|
104
|
+
|
105
|
+
raise(ArgumentError, 'invalid connection hash') \
|
106
|
+
unless connectHash.kind_of?(Hash)
|
107
|
+
|
108
|
+
@connect_hash = connectHash.dup
|
109
|
+
@test_client = testClient
|
110
|
+
@client = nil
|
111
|
+
|
112
|
+
TinyTds::Client.default_query_options[:as] = :hash
|
113
|
+
TinyTds::Client.default_query_options[:symbolize_keys] = true
|
114
|
+
|
115
|
+
rescue => e
|
116
|
+
handle_error(e)
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
def db; self.class.db; end
|
121
|
+
def schema; self.class.schema; end
|
122
|
+
def table; self.class.table; end
|
123
|
+
def id_fld; self.class.id_fld; end
|
124
|
+
|
125
|
+
def quoted_table
|
126
|
+
schema ? %Q|[#{schema}].[#{table}]| : %Q|[#{table}]|
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
##
|
131
|
+
# Selection is a hash or something like it: keys should be field names. We
|
132
|
+
# return any records where the given fields equal the given values.
|
133
|
+
#
|
134
|
+
def list(selection=nil)
|
135
|
+
|
136
|
+
raise(Pod4::DatabaseError, 'selection parameter is not a hash') \
|
137
|
+
unless selection.nil? || selection.respond_to?(:keys)
|
138
|
+
|
139
|
+
if selection
|
140
|
+
sel = selection.map {|k,v| "[#{k}] = #{quote v}" }.join(" and ")
|
141
|
+
sql = %Q|select *
|
142
|
+
from #{quoted_table}
|
143
|
+
where #{sel};|
|
144
|
+
|
145
|
+
else
|
146
|
+
sql = %Q|select * from #{quoted_table};|
|
147
|
+
end
|
148
|
+
|
149
|
+
select(sql) {|r| Octothorpe.new(r) }
|
150
|
+
|
151
|
+
rescue => e
|
152
|
+
handle_error(e)
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
##
|
157
|
+
# Record is a hash of field: value
|
158
|
+
# By a happy coincidence, insert returns the unique ID for the record,
|
159
|
+
# which is just what we want to do, too.
|
160
|
+
#
|
161
|
+
def create(record)
|
162
|
+
raise(ArgumentError, "Bad type for record parameter") \
|
163
|
+
unless record.kind_of?(Hash) || record.kind_of?(Octothorpe)
|
164
|
+
|
165
|
+
ks = record.keys.map {|k| "[#{k}]" }
|
166
|
+
vs = record.values.map {|v| quote v }
|
167
|
+
|
168
|
+
sql = "insert into #{quoted_table}\n"
|
169
|
+
sql << " ( " << ks.join(",") << ")\n"
|
170
|
+
sql << " output inserted.[#{id_fld}]\n"
|
171
|
+
sql << " values( " << vs.join(",") << ");"
|
172
|
+
|
173
|
+
x = select(sql)
|
174
|
+
x.first[id_fld]
|
175
|
+
|
176
|
+
rescue => e
|
177
|
+
handle_error(e)
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
##
|
182
|
+
# ID corresponds to whatever you set in set_id_fld
|
183
|
+
#
|
184
|
+
def read(id)
|
185
|
+
raise(ArgumentError, "ID parameter is nil") if id.nil?
|
186
|
+
|
187
|
+
sql = %Q|select *
|
188
|
+
from #{quoted_table}
|
189
|
+
where [#{id_fld}] = #{quote id};|
|
190
|
+
|
191
|
+
Octothorpe.new( select(sql).first )
|
192
|
+
|
193
|
+
rescue => e
|
194
|
+
# select already wrapped any error in a Pod4::DatabaseError, but in this
|
195
|
+
# case we want to try to catch something. (Side note: TinyTds' error
|
196
|
+
# class structure is a bit poor...)
|
197
|
+
raise CantContinue, "Problem reading record. Is '#{id}' really an ID?" \
|
198
|
+
if e.cause.class == TinyTds::Error \
|
199
|
+
&& e.cause.message =~ /invalid column/i
|
200
|
+
|
201
|
+
handle_error(e)
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
##
|
206
|
+
# ID is whatever you set in the interface using set_id_fld
|
207
|
+
# record should be a Hash or Octothorpe.
|
208
|
+
#
|
209
|
+
def update(id, record)
|
210
|
+
raise(ArgumentError, "Bad type for record parameter") \
|
211
|
+
unless record.kind_of?(Hash) || record.kind_of?(Octothorpe)
|
212
|
+
|
213
|
+
read_or_die(id)
|
214
|
+
|
215
|
+
sets = record.map {|k,v| " [#{k}] = #{quote v}" }
|
216
|
+
|
217
|
+
sql = "update #{quoted_table} set\n"
|
218
|
+
sql << sets.join(",") << "\n"
|
219
|
+
sql << "where [#{id_fld}] = #{quote id};"
|
220
|
+
execute(sql)
|
221
|
+
|
222
|
+
self
|
223
|
+
|
224
|
+
rescue => e
|
225
|
+
handle_error(e)
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
##
|
230
|
+
# ID is whatever you set in the interface using set_id_fld
|
231
|
+
#
|
232
|
+
def delete(id)
|
233
|
+
read_or_die(id)
|
234
|
+
execute( %Q|delete #{quoted_table} where [#{id_fld}] = #{quote id};| )
|
235
|
+
|
236
|
+
self
|
237
|
+
|
238
|
+
rescue => e
|
239
|
+
handle_error(e)
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
##
|
244
|
+
# Run SQL code on the server. Return the results.
|
245
|
+
#
|
246
|
+
# Will return an array of records, or you can use it in block mode, like
|
247
|
+
# this:
|
248
|
+
#
|
249
|
+
# select("select * from customer") do |r|
|
250
|
+
# # r is a single record
|
251
|
+
# end
|
252
|
+
#
|
253
|
+
# The returned results will be an array of hashes (or if you passed a
|
254
|
+
# block, of whatever you returned from the block).
|
255
|
+
#
|
256
|
+
def select(sql)
|
257
|
+
raise(ArgumentError, "Bad sql parameter") unless sql.kind_of?(String)
|
258
|
+
|
259
|
+
open unless connected?
|
260
|
+
|
261
|
+
Pod4.logger.debug(__FILE__){ "select: #{sql}" }
|
262
|
+
query = @client.execute(sql)
|
263
|
+
|
264
|
+
rows = []
|
265
|
+
query.each do |r|
|
266
|
+
|
267
|
+
if block_given?
|
268
|
+
rows << yield(r)
|
269
|
+
else
|
270
|
+
rows << r
|
271
|
+
end
|
272
|
+
|
273
|
+
end
|
274
|
+
|
275
|
+
query.cancel
|
276
|
+
rows
|
277
|
+
|
278
|
+
rescue => e
|
279
|
+
handle_error(e)
|
280
|
+
end
|
281
|
+
|
282
|
+
|
283
|
+
##
|
284
|
+
# Run SQL code on the server; return true or false for success or failure
|
285
|
+
#
|
286
|
+
def execute(sql)
|
287
|
+
raise(ArgumentError, "Bad sql parameter") unless sql.kind_of?(String)
|
288
|
+
|
289
|
+
open unless connected?
|
290
|
+
|
291
|
+
Pod4.logger.debug(__FILE__){ "execute: #{sql}" }
|
292
|
+
r = @client.execute(sql)
|
293
|
+
|
294
|
+
r.do
|
295
|
+
r
|
296
|
+
|
297
|
+
rescue => e
|
298
|
+
handle_error(e)
|
299
|
+
end
|
300
|
+
|
301
|
+
|
302
|
+
protected
|
303
|
+
|
304
|
+
|
305
|
+
##
|
306
|
+
# Open the connection to the database.
|
307
|
+
#
|
308
|
+
# No parameters are needed: the option hash has everything we need.
|
309
|
+
#
|
310
|
+
def open
|
311
|
+
Pod4.logger.info(__FILE__){ "Connecting to DB" }
|
312
|
+
client = @test_Client || TinyTds::Client.new(@connect_hash)
|
313
|
+
raise "Bad Connection" unless client.active?
|
314
|
+
|
315
|
+
@client = client
|
316
|
+
execute("use [#{self.class.db}]")
|
317
|
+
|
318
|
+
self
|
319
|
+
|
320
|
+
rescue => e
|
321
|
+
handle_error(e)
|
322
|
+
end
|
323
|
+
|
324
|
+
|
325
|
+
##
|
326
|
+
# Close the connection to the database.
|
327
|
+
# We don't actually use this, but it's here for completeness. Maybe a
|
328
|
+
# caller will find it useful.
|
329
|
+
#
|
330
|
+
def close
|
331
|
+
Pod4.logger.info(__FILE__){ "Closing connection to DB" }
|
332
|
+
@client.close unless @client.nil?
|
333
|
+
|
334
|
+
rescue => e
|
335
|
+
handle_error(e)
|
336
|
+
end
|
337
|
+
|
338
|
+
|
339
|
+
##
|
340
|
+
# True if we are connected to a database
|
341
|
+
#
|
342
|
+
def connected?
|
343
|
+
@client && @client.active?
|
344
|
+
end
|
345
|
+
|
346
|
+
|
347
|
+
def handle_error(err, kaller=nil)
|
348
|
+
kaller ||= caller[1..-1]
|
349
|
+
|
350
|
+
Pod4.logger.error(__FILE__){ err.message }
|
351
|
+
|
352
|
+
case err
|
353
|
+
|
354
|
+
when ArgumentError, Pod4::Pod4Error, Pod4::CantContinue
|
355
|
+
raise err.class, err.message, kaller
|
356
|
+
|
357
|
+
when TinyTds::Error
|
358
|
+
raise Pod4::DatabaseError, err.message, kaller
|
359
|
+
|
360
|
+
else
|
361
|
+
raise Pod4::Pod4Error, err.message, kaller
|
362
|
+
|
363
|
+
end
|
364
|
+
|
365
|
+
end
|
366
|
+
|
367
|
+
|
368
|
+
def quote(fld)
|
369
|
+
|
370
|
+
case fld
|
371
|
+
when DateTime, Time
|
372
|
+
%Q|'#{fld.to_s[0..-7]}'|
|
373
|
+
when String, Date
|
374
|
+
%Q|'#{fld}'|
|
375
|
+
when BigDecimal
|
376
|
+
fld.to_f
|
377
|
+
when nil
|
378
|
+
'NULL'
|
379
|
+
else
|
380
|
+
fld
|
381
|
+
end
|
382
|
+
|
383
|
+
end
|
384
|
+
|
385
|
+
|
386
|
+
def read_or_die(id)
|
387
|
+
raise CantContinue, "'No record found with ID '#{id}'" if read(id).empty?
|
388
|
+
end
|
389
|
+
|
390
|
+
|
391
|
+
end
|
392
|
+
|
393
|
+
|
394
|
+
end
|
data/lib/pod4/version.rb
ADDED