sequel 0.4.0 → 0.4.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.
- data/CHANGELOG +4 -0
- data/README +1 -6
- data/Rakefile +1 -1
- data/bin/sequel +1 -3
- data/lib/sequel.rb +18 -1
- data/lib/sequel/adapters/ado.rb +104 -0
- data/lib/sequel/adapters/db2.rb +160 -0
- data/lib/sequel/adapters/dbi.rb +130 -0
- data/lib/sequel/adapters/informix.rb +78 -0
- data/lib/sequel/adapters/mysql.rb +256 -0
- data/lib/sequel/adapters/odbc.rb +144 -0
- data/lib/sequel/adapters/oracle.rb +109 -0
- data/lib/sequel/adapters/postgres.rb +507 -0
- data/lib/sequel/adapters/sqlite.rb +186 -0
- data/lib/sequel/ado.rb +2 -104
- data/lib/{sequel-core → sequel}/array_keys.rb +0 -0
- data/lib/{sequel-core → sequel}/connection_pool.rb +0 -0
- data/lib/{sequel-core → sequel}/core_ext.rb +0 -0
- data/lib/{sequel-core → sequel}/core_sql.rb +0 -0
- data/lib/{sequel-core → sequel}/database.rb +10 -20
- data/lib/{sequel-core → sequel}/dataset.rb +0 -0
- data/lib/{sequel-core → sequel}/dataset/convenience.rb +0 -0
- data/lib/{sequel-core → sequel}/dataset/sequelizer.rb +0 -0
- data/lib/{sequel-core → sequel}/dataset/sql.rb +0 -0
- data/lib/sequel/db2.rb +2 -160
- data/lib/sequel/dbi.rb +2 -130
- data/lib/{sequel-core → sequel}/error.rb +0 -0
- data/lib/sequel/informix.rb +2 -78
- data/lib/{sequel-core → sequel}/migration.rb +0 -0
- data/lib/{sequel-core → sequel}/model.rb +0 -0
- data/lib/{sequel-core → sequel}/model/base.rb +0 -0
- data/lib/{sequel-core → sequel}/model/caching.rb +0 -0
- data/lib/{sequel-core → sequel}/model/hooks.rb +0 -0
- data/lib/{sequel-core → sequel}/model/record.rb +0 -0
- data/lib/{sequel-core → sequel}/model/relations.rb +0 -0
- data/lib/{sequel-core → sequel}/model/schema.rb +0 -0
- data/lib/sequel/mysql.rb +2 -256
- data/lib/sequel/odbc.rb +2 -144
- data/lib/sequel/oracle.rb +2 -109
- data/lib/sequel/postgres.rb +2 -507
- data/lib/{sequel-core → sequel}/pretty_table.rb +0 -0
- data/lib/{sequel-core → sequel}/schema.rb +0 -0
- data/lib/{sequel-core → sequel}/schema/schema_generator.rb +0 -0
- data/lib/{sequel-core → sequel}/schema/schema_sql.rb +0 -0
- data/lib/sequel/sqlite.rb +2 -186
- data/lib/{sequel-core → sequel}/worker.rb +0 -0
- data/spec/database_spec.rb +7 -9
- metadata +39 -29
@@ -0,0 +1,109 @@
|
|
1
|
+
if !Object.const_defined?('Sequel')
|
2
|
+
require File.join(File.dirname(__FILE__), '../../sequel')
|
3
|
+
end
|
4
|
+
|
5
|
+
require 'oci8'
|
6
|
+
|
7
|
+
module Sequel
|
8
|
+
module Oracle
|
9
|
+
class Database < Sequel::Database
|
10
|
+
set_adapter_scheme :oracle
|
11
|
+
|
12
|
+
# AUTO_INCREMENT = 'IDENTITY(1,1)'.freeze
|
13
|
+
#
|
14
|
+
# def auto_increment_sql
|
15
|
+
# AUTO_INCREMENT
|
16
|
+
# end
|
17
|
+
|
18
|
+
def connect
|
19
|
+
if @opts[:database]
|
20
|
+
dbname = @opts[:host] ? \
|
21
|
+
"//#{@opts[:host]}/#{@opts[:database]}" : @opts[:database]
|
22
|
+
else
|
23
|
+
dbname = @opts[:host]
|
24
|
+
end
|
25
|
+
conn = OCI8.new(@opts[:user], @opts[:password], dbname, @opts[:privilege])
|
26
|
+
conn.autocommit = true
|
27
|
+
conn.non_blocking = true
|
28
|
+
conn
|
29
|
+
end
|
30
|
+
|
31
|
+
def disconnect
|
32
|
+
@pool.disconnect {|c| c.logoff}
|
33
|
+
end
|
34
|
+
|
35
|
+
def dataset(opts = nil)
|
36
|
+
Oracle::Dataset.new(self, opts)
|
37
|
+
end
|
38
|
+
|
39
|
+
def execute(sql)
|
40
|
+
@logger.info(sql) if @logger
|
41
|
+
@pool.hold {|conn| conn.exec(sql)}
|
42
|
+
end
|
43
|
+
|
44
|
+
alias_method :do, :execute
|
45
|
+
end
|
46
|
+
|
47
|
+
class Dataset < Sequel::Dataset
|
48
|
+
def literal(v)
|
49
|
+
case v
|
50
|
+
when Time: literal(v.iso8601)
|
51
|
+
else
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def fetch_rows(sql, &block)
|
57
|
+
@db.synchronize do
|
58
|
+
cursor = @db.execute sql
|
59
|
+
begin
|
60
|
+
@columns = cursor.get_col_names.map {|c| c.to_sym}
|
61
|
+
while r = cursor.fetch
|
62
|
+
row = {}
|
63
|
+
r.each_with_index {|v, i| row[columns[i]] = v}
|
64
|
+
yield row
|
65
|
+
end
|
66
|
+
ensure
|
67
|
+
cursor.close
|
68
|
+
end
|
69
|
+
end
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
def hash_row(row)
|
74
|
+
@columns.inject({}) do |m, c|
|
75
|
+
m[c] = row.shift
|
76
|
+
m
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def array_tuples_fetch_rows(sql, &block)
|
81
|
+
@db.synchronize do
|
82
|
+
cursor = @db.execute sql
|
83
|
+
begin
|
84
|
+
@columns = cursor.get_col_names.map {|c| c.to_sym}
|
85
|
+
while r = cursor.fetch
|
86
|
+
r.keys = columns
|
87
|
+
yield r
|
88
|
+
end
|
89
|
+
ensure
|
90
|
+
cursor.close
|
91
|
+
end
|
92
|
+
end
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
def insert(*values)
|
97
|
+
@db.do insert_sql(*values)
|
98
|
+
end
|
99
|
+
|
100
|
+
def update(values, opts = nil)
|
101
|
+
@db.do update_sql(values, opts)
|
102
|
+
end
|
103
|
+
|
104
|
+
def delete(opts = nil)
|
105
|
+
@db.do delete_sql(opts)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,507 @@
|
|
1
|
+
if !Object.const_defined?('Sequel')
|
2
|
+
require File.join(File.dirname(__FILE__), '../../sequel')
|
3
|
+
end
|
4
|
+
|
5
|
+
require 'postgres'
|
6
|
+
|
7
|
+
class PGconn
|
8
|
+
# the pure-ruby postgres adapter does not have a quote method.
|
9
|
+
unless methods.include?('quote')
|
10
|
+
def self.quote(obj)
|
11
|
+
case obj
|
12
|
+
when true: 't'
|
13
|
+
when false: 'f'
|
14
|
+
when nil: 'NULL'
|
15
|
+
when String: "'#{obj}'"
|
16
|
+
else obj.to_s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class << self
|
22
|
+
# The postgres gem's string quoting doesn't render string literals properly, which this fixes.
|
23
|
+
#
|
24
|
+
# "a basic string" #=> 'a basic string'
|
25
|
+
# "this\or that" #=> E'this\\or that'
|
26
|
+
#
|
27
|
+
# See <http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html> for details.
|
28
|
+
def quote_with_proper_escaping(s)
|
29
|
+
value = quote_without_proper_escaping(s)
|
30
|
+
value = "E#{value}" if value =~ /\\/
|
31
|
+
return value
|
32
|
+
end
|
33
|
+
alias_method :quote_without_proper_escaping, :quote
|
34
|
+
alias_method :quote, :quote_with_proper_escaping
|
35
|
+
end
|
36
|
+
|
37
|
+
def connected?
|
38
|
+
status == PGconn::CONNECTION_OK
|
39
|
+
end
|
40
|
+
|
41
|
+
def execute(sql)
|
42
|
+
begin
|
43
|
+
async_exec(sql)
|
44
|
+
rescue PGError => e
|
45
|
+
unless connected?
|
46
|
+
reset
|
47
|
+
async_exec(sql)
|
48
|
+
else
|
49
|
+
raise e
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_accessor :transaction_in_progress
|
55
|
+
|
56
|
+
SELECT_CURRVAL = "SELECT currval('%s')".freeze
|
57
|
+
|
58
|
+
def last_insert_id(table)
|
59
|
+
@table_sequences ||= {}
|
60
|
+
if !@table_sequences.include?(table)
|
61
|
+
pkey_and_seq = pkey_and_sequence(table)
|
62
|
+
if pkey_and_seq
|
63
|
+
@table_sequences[table] = pkey_and_seq[1]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
if seq = @table_sequences[table]
|
67
|
+
r = async_query(SELECT_CURRVAL % seq)
|
68
|
+
return r[0][0].to_i unless r.nil? || r.empty?
|
69
|
+
end
|
70
|
+
nil # primary key sequence not found
|
71
|
+
end
|
72
|
+
|
73
|
+
# Shamelessly appropriated from ActiveRecord's Postgresql adapter.
|
74
|
+
|
75
|
+
SELECT_PK_AND_SERIAL_SEQUENCE = <<-end_sql
|
76
|
+
SELECT attr.attname, name.nspname, seq.relname
|
77
|
+
FROM pg_class seq, pg_attribute attr, pg_depend dep,
|
78
|
+
pg_namespace name, pg_constraint cons
|
79
|
+
WHERE seq.oid = dep.objid
|
80
|
+
AND seq.relnamespace = name.oid
|
81
|
+
AND seq.relkind = 'S'
|
82
|
+
AND attr.attrelid = dep.refobjid
|
83
|
+
AND attr.attnum = dep.refobjsubid
|
84
|
+
AND attr.attrelid = cons.conrelid
|
85
|
+
AND attr.attnum = cons.conkey[1]
|
86
|
+
AND cons.contype = 'p'
|
87
|
+
AND dep.refobjid = '%s'::regclass
|
88
|
+
end_sql
|
89
|
+
|
90
|
+
SELECT_PK_AND_CUSTOM_SEQUENCE = <<-end_sql
|
91
|
+
SELECT attr.attname, name.nspname, split_part(def.adsrc, '''', 2)
|
92
|
+
FROM pg_class t
|
93
|
+
JOIN pg_namespace name ON (t.relnamespace = name.oid)
|
94
|
+
JOIN pg_attribute attr ON (t.oid = attrelid)
|
95
|
+
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
96
|
+
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
97
|
+
WHERE t.oid = '%s'::regclass
|
98
|
+
AND cons.contype = 'p'
|
99
|
+
AND def.adsrc ~* 'nextval'
|
100
|
+
end_sql
|
101
|
+
|
102
|
+
SELECT_PK = <<-end_sql
|
103
|
+
SELECT pg_attribute.attname
|
104
|
+
FROM pg_class, pg_attribute, pg_index
|
105
|
+
WHERE pg_class.oid = pg_attribute.attrelid AND
|
106
|
+
pg_class.oid = pg_index.indrelid AND
|
107
|
+
pg_index.indkey[0] = pg_attribute.attnum AND
|
108
|
+
pg_index.indisprimary = 't' AND
|
109
|
+
pg_class.relname = '%s'
|
110
|
+
end_sql
|
111
|
+
|
112
|
+
def pkey_and_sequence(table)
|
113
|
+
r = async_query(SELECT_PK_AND_SERIAL_SEQUENCE % table)
|
114
|
+
return [r[0].first, r[0].last] unless r.nil? or r.empty?
|
115
|
+
|
116
|
+
r = async_query(SELECT_PK_AND_CUSTOM_SEQUENCE % table)
|
117
|
+
return [r[0].first, r[0].last] unless r.nil? or r.empty?
|
118
|
+
rescue
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
|
122
|
+
def primary_key(table)
|
123
|
+
r = async_query(SELECT_PK % table)
|
124
|
+
pkey = r[0].first unless r.nil? or r.empty?
|
125
|
+
return pkey.to_sym if pkey
|
126
|
+
rescue
|
127
|
+
nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class String
|
132
|
+
def postgres_to_bool
|
133
|
+
if self == 't'
|
134
|
+
true
|
135
|
+
elsif self == 'f'
|
136
|
+
false
|
137
|
+
else
|
138
|
+
nil
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
module Sequel
|
144
|
+
module Postgres
|
145
|
+
PG_TYPES = {
|
146
|
+
16 => :postgres_to_bool,
|
147
|
+
20 => :to_i,
|
148
|
+
21 => :to_i,
|
149
|
+
22 => :to_i,
|
150
|
+
23 => :to_i,
|
151
|
+
700 => :to_f,
|
152
|
+
701 => :to_f,
|
153
|
+
1114 => :to_time
|
154
|
+
}
|
155
|
+
|
156
|
+
if PGconn.respond_to?(:translate_results=)
|
157
|
+
PGconn.translate_results = true
|
158
|
+
AUTO_TRANSLATE = true
|
159
|
+
else
|
160
|
+
AUTO_TRANSLATE = false
|
161
|
+
end
|
162
|
+
|
163
|
+
class Database < Sequel::Database
|
164
|
+
set_adapter_scheme :postgres
|
165
|
+
|
166
|
+
def connect
|
167
|
+
conn = PGconn.connect(
|
168
|
+
@opts[:host] || 'localhost',
|
169
|
+
@opts[:port] || 5432,
|
170
|
+
'', '',
|
171
|
+
@opts[:database],
|
172
|
+
@opts[:user],
|
173
|
+
@opts[:password]
|
174
|
+
)
|
175
|
+
if encoding = @opts[:encoding] || @opts[:charset]
|
176
|
+
conn.set_client_encoding(encoding)
|
177
|
+
end
|
178
|
+
conn
|
179
|
+
end
|
180
|
+
|
181
|
+
def disconnect
|
182
|
+
@pool.disconnect {|c| c.close}
|
183
|
+
end
|
184
|
+
|
185
|
+
def dataset(opts = nil)
|
186
|
+
Postgres::Dataset.new(self, opts)
|
187
|
+
end
|
188
|
+
|
189
|
+
RELATION_QUERY = {:from => [:pg_class], :select => [:relname]}.freeze
|
190
|
+
RELATION_FILTER = "(relkind = 'r') AND (relname !~ '^pg|sql')".freeze
|
191
|
+
SYSTEM_TABLE_REGEXP = /^pg|sql/.freeze
|
192
|
+
|
193
|
+
def tables
|
194
|
+
dataset(RELATION_QUERY).filter(RELATION_FILTER).map {|r| r[:relname].to_sym}
|
195
|
+
end
|
196
|
+
|
197
|
+
def locks
|
198
|
+
dataset.from("pg_class, pg_locks").
|
199
|
+
select("pg_class.relname, pg_locks.*").
|
200
|
+
filter("pg_class.relfilenode=pg_locks.relation")
|
201
|
+
end
|
202
|
+
|
203
|
+
def execute(sql)
|
204
|
+
@logger.info(sql) if @logger
|
205
|
+
@pool.hold {|conn| conn.execute(sql)}
|
206
|
+
rescue => e
|
207
|
+
@logger.error(e.message) if @logger
|
208
|
+
raise e
|
209
|
+
end
|
210
|
+
|
211
|
+
def execute_and_forget(sql)
|
212
|
+
@logger.info(sql) if @logger
|
213
|
+
@pool.hold {|conn| conn.execute(sql).clear}
|
214
|
+
rescue => e
|
215
|
+
@logger.error(e.message) if @logger
|
216
|
+
raise e
|
217
|
+
end
|
218
|
+
|
219
|
+
def primary_key_for_table(conn, table)
|
220
|
+
@primary_keys ||= {}
|
221
|
+
@primary_keys[table] ||= conn.primary_key(table)
|
222
|
+
end
|
223
|
+
|
224
|
+
RE_CURRVAL_ERROR = /currval of sequence "(.*)" is not yet defined in this session/.freeze
|
225
|
+
|
226
|
+
def insert_result(conn, table, values)
|
227
|
+
begin
|
228
|
+
result = conn.last_insert_id(table)
|
229
|
+
return result if result
|
230
|
+
rescue PGError => e
|
231
|
+
# An error could occur if the inserted values include a primary key
|
232
|
+
# value, while the primary key is serial.
|
233
|
+
if e.message =~ RE_CURRVAL_ERROR
|
234
|
+
raise SequelError, "Could not return primary key value for the inserted record. Are you specifying a primary key value for a serial primary key?"
|
235
|
+
else
|
236
|
+
raise e
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
case values
|
241
|
+
when Hash:
|
242
|
+
values[primary_key_for_table(conn, table)]
|
243
|
+
when Array:
|
244
|
+
values.first
|
245
|
+
else
|
246
|
+
nil
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def execute_insert(sql, table, values)
|
251
|
+
@logger.info(sql) if @logger
|
252
|
+
@pool.hold do |conn|
|
253
|
+
conn.execute(sql).clear
|
254
|
+
insert_result(conn, table, values)
|
255
|
+
end
|
256
|
+
rescue => e
|
257
|
+
@logger.error(e.message) if @logger
|
258
|
+
raise e
|
259
|
+
end
|
260
|
+
|
261
|
+
def synchronize(&block)
|
262
|
+
@pool.hold(&block)
|
263
|
+
end
|
264
|
+
|
265
|
+
SQL_BEGIN = 'BEGIN'.freeze
|
266
|
+
SQL_COMMIT = 'COMMIT'.freeze
|
267
|
+
SQL_ROLLBACK = 'ROLLBACK'.freeze
|
268
|
+
|
269
|
+
def transaction
|
270
|
+
@pool.hold do |conn|
|
271
|
+
if conn.transaction_in_progress
|
272
|
+
yield conn
|
273
|
+
else
|
274
|
+
@logger.info(SQL_BEGIN) if @logger
|
275
|
+
conn.async_exec(SQL_BEGIN)
|
276
|
+
begin
|
277
|
+
conn.transaction_in_progress = true
|
278
|
+
result = yield
|
279
|
+
begin
|
280
|
+
@logger.info(SQL_COMMIT) if @logger
|
281
|
+
conn.async_exec(SQL_COMMIT)
|
282
|
+
rescue => e
|
283
|
+
@logger.error(e.message) if @logger
|
284
|
+
raise e
|
285
|
+
end
|
286
|
+
result
|
287
|
+
rescue => e
|
288
|
+
@logger.info(SQL_ROLLBACK) if @logger
|
289
|
+
conn.async_exec(SQL_ROLLBACK) rescue nil
|
290
|
+
raise e unless SequelRollbackError === e
|
291
|
+
ensure
|
292
|
+
conn.transaction_in_progress = nil
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
def serial_primary_key_options
|
299
|
+
{:primary_key => true, :type => :serial}
|
300
|
+
end
|
301
|
+
|
302
|
+
def drop_table_sql(name)
|
303
|
+
"DROP TABLE #{name} CASCADE"
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
class Dataset < Sequel::Dataset
|
308
|
+
def literal(v)
|
309
|
+
case v
|
310
|
+
when LiteralString: v
|
311
|
+
when String, Fixnum, Float, TrueClass, FalseClass: PGconn.quote(v)
|
312
|
+
else
|
313
|
+
super
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def match_expr(l, r)
|
318
|
+
case r
|
319
|
+
when Regexp:
|
320
|
+
r.casefold? ? \
|
321
|
+
"(#{literal(l)} ~* #{literal(r.source)})" :
|
322
|
+
"(#{literal(l)} ~ #{literal(r.source)})"
|
323
|
+
else
|
324
|
+
super
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
FOR_UPDATE = ' FOR UPDATE'.freeze
|
329
|
+
FOR_SHARE = ' FOR SHARE'.freeze
|
330
|
+
|
331
|
+
def select_sql(opts = nil)
|
332
|
+
row_lock_mode = opts ? opts[:lock] : @opts[:lock]
|
333
|
+
sql = super
|
334
|
+
case row_lock_mode
|
335
|
+
when :update : sql << FOR_UPDATE
|
336
|
+
when :share : sql << FOR_SHARE
|
337
|
+
end
|
338
|
+
sql
|
339
|
+
end
|
340
|
+
|
341
|
+
def for_update
|
342
|
+
clone_merge(:lock => :update)
|
343
|
+
end
|
344
|
+
|
345
|
+
def for_share
|
346
|
+
clone_merge(:lock => :share)
|
347
|
+
end
|
348
|
+
|
349
|
+
EXPLAIN = 'EXPLAIN '.freeze
|
350
|
+
EXPLAIN_ANALYZE = 'EXPLAIN ANALYZE '.freeze
|
351
|
+
QUERY_PLAN = 'QUERY PLAN'.to_sym
|
352
|
+
|
353
|
+
def explain(opts = nil)
|
354
|
+
analysis = []
|
355
|
+
fetch_rows(EXPLAIN + select_sql(opts)) do |r|
|
356
|
+
analysis << r[QUERY_PLAN]
|
357
|
+
end
|
358
|
+
analysis.join("\r\n")
|
359
|
+
end
|
360
|
+
|
361
|
+
def analyze(opts = nil)
|
362
|
+
analysis = []
|
363
|
+
fetch_rows(EXPLAIN_ANALYZE + select_sql(opts)) do |r|
|
364
|
+
analysis << r[QUERY_PLAN]
|
365
|
+
end
|
366
|
+
analysis.join("\r\n")
|
367
|
+
end
|
368
|
+
|
369
|
+
LOCK = 'LOCK TABLE %s IN %s MODE'.freeze
|
370
|
+
|
371
|
+
ACCESS_SHARE = 'ACCESS SHARE'.freeze
|
372
|
+
ROW_SHARE = 'ROW SHARE'.freeze
|
373
|
+
ROW_EXCLUSIVE = 'ROW EXCLUSIVE'.freeze
|
374
|
+
SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
|
375
|
+
SHARE = 'SHARE'.freeze
|
376
|
+
SHARE_ROW_EXCLUSIVE = 'SHARE ROW EXCLUSIVE'.freeze
|
377
|
+
EXCLUSIVE = 'EXCLUSIVE'.freeze
|
378
|
+
ACCESS_EXCLUSIVE = 'ACCESS EXCLUSIVE'.freeze
|
379
|
+
|
380
|
+
# Locks the table with the specified mode.
|
381
|
+
def lock(mode, &block)
|
382
|
+
sql = LOCK % [@opts[:from], mode]
|
383
|
+
@db.synchronize do
|
384
|
+
if block # perform locking inside a transaction and yield to block
|
385
|
+
@db.transaction {@db.execute_and_forget(sql); yield}
|
386
|
+
else
|
387
|
+
@db.execute_and_forget(sql) # lock without a transaction
|
388
|
+
self
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
def insert(*values)
|
394
|
+
@db.execute_insert(insert_sql(*values), @opts[:from],
|
395
|
+
values.size == 1 ? values.first : values)
|
396
|
+
end
|
397
|
+
|
398
|
+
def update(values, opts = nil)
|
399
|
+
@db.synchronize do
|
400
|
+
result = @db.execute(update_sql(values))
|
401
|
+
begin
|
402
|
+
affected = result.cmdtuples
|
403
|
+
ensure
|
404
|
+
result.clear
|
405
|
+
end
|
406
|
+
affected
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
def delete(opts = nil)
|
411
|
+
@db.synchronize do
|
412
|
+
result = @db.execute(delete_sql(opts))
|
413
|
+
begin
|
414
|
+
affected = result.cmdtuples
|
415
|
+
ensure
|
416
|
+
result.clear
|
417
|
+
end
|
418
|
+
affected
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
def fetch_rows(sql, &block)
|
423
|
+
@db.synchronize do
|
424
|
+
result = @db.execute(sql)
|
425
|
+
begin
|
426
|
+
conv = row_converter(result)
|
427
|
+
result.each {|r| yield conv[r]}
|
428
|
+
ensure
|
429
|
+
result.clear
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
@@converters_mutex = Mutex.new
|
435
|
+
@@converters = {}
|
436
|
+
|
437
|
+
def row_converter(result)
|
438
|
+
@columns = []; translators = []
|
439
|
+
result.fields.each_with_index do |f, idx|
|
440
|
+
@columns << f.to_sym
|
441
|
+
translators << PG_TYPES[result.type(idx)]
|
442
|
+
end
|
443
|
+
|
444
|
+
# create result signature and memoize the converter
|
445
|
+
sig = [@columns, translators].hash
|
446
|
+
@@converters_mutex.synchronize do
|
447
|
+
@@converters[sig] ||= compile_converter(@columns, translators)
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
def compile_converter(columns, translators)
|
452
|
+
used_columns = []
|
453
|
+
kvs = []
|
454
|
+
columns.each_with_index do |column, idx|
|
455
|
+
next if used_columns.include?(column)
|
456
|
+
used_columns << column
|
457
|
+
|
458
|
+
if !AUTO_TRANSLATE and translator = translators[idx]
|
459
|
+
kvs << ":\"#{column}\" => ((t = r[#{idx}]) ? t.#{translator} : nil)"
|
460
|
+
else
|
461
|
+
kvs << ":\"#{column}\" => r[#{idx}]"
|
462
|
+
end
|
463
|
+
end
|
464
|
+
eval("lambda {|r| {#{kvs.join(COMMA_SEPARATOR)}}}")
|
465
|
+
end
|
466
|
+
|
467
|
+
def array_tuples_fetch_rows(sql, &block)
|
468
|
+
@db.synchronize do
|
469
|
+
result = @db.execute(sql)
|
470
|
+
begin
|
471
|
+
conv = array_tuples_row_converter(result)
|
472
|
+
result.each {|r| yield conv[r]}
|
473
|
+
ensure
|
474
|
+
result.clear
|
475
|
+
end
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
@@array_tuples_converters_mutex = Mutex.new
|
480
|
+
@@array_tuples_converters = {}
|
481
|
+
|
482
|
+
def array_tuples_row_converter(result)
|
483
|
+
@columns = []; translators = []
|
484
|
+
result.fields.each_with_index do |f, idx|
|
485
|
+
@columns << f.to_sym
|
486
|
+
translators << PG_TYPES[result.type(idx)]
|
487
|
+
end
|
488
|
+
|
489
|
+
# create result signature and memoize the converter
|
490
|
+
sig = [@columns, translators].hash
|
491
|
+
@@array_tuples_converters_mutex.synchronize do
|
492
|
+
@@array_tuples_converters[sig] ||= array_tuples_compile_converter(@columns, translators)
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
def array_tuples_compile_converter(columns, translators)
|
497
|
+
tr = []
|
498
|
+
columns.each_with_index do |column, idx|
|
499
|
+
if !AUTO_TRANSLATE and t = translators[idx]
|
500
|
+
tr << "if (v = r[#{idx}]); r[#{idx}] = v.#{t}; end"
|
501
|
+
end
|
502
|
+
end
|
503
|
+
eval("lambda {|r| r.keys = columns; #{tr.join(';')}; r}")
|
504
|
+
end
|
505
|
+
end
|
506
|
+
end
|
507
|
+
end
|