ydbi 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/ChangeLog +3699 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +25 -0
  6. data/Rakefile +8 -0
  7. data/TODO +44 -0
  8. data/bench/bench.rb +79 -0
  9. data/bin/dbi +518 -0
  10. data/bin/test_broken_dbi +37 -0
  11. data/build/Rakefile.dbi.rb +60 -0
  12. data/build/rake_task_lib.rb +187 -0
  13. data/doc/DBD_SPEC.rdoc +88 -0
  14. data/doc/DBI_SPEC.rdoc +157 -0
  15. data/doc/homepage/contact.html +62 -0
  16. data/doc/homepage/development.html +124 -0
  17. data/doc/homepage/index.html +83 -0
  18. data/doc/homepage/ruby-dbi.css +91 -0
  19. data/examples/test1.pl +39 -0
  20. data/examples/test1.rb +20 -0
  21. data/examples/xmltest.rb +8 -0
  22. data/lib/dbd/Mysql.rb +137 -0
  23. data/lib/dbd/ODBC.rb +89 -0
  24. data/lib/dbd/Pg.rb +188 -0
  25. data/lib/dbd/SQLite.rb +97 -0
  26. data/lib/dbd/SQLite3.rb +124 -0
  27. data/lib/dbd/mysql/database.rb +405 -0
  28. data/lib/dbd/mysql/driver.rb +125 -0
  29. data/lib/dbd/mysql/statement.rb +188 -0
  30. data/lib/dbd/odbc/database.rb +128 -0
  31. data/lib/dbd/odbc/driver.rb +38 -0
  32. data/lib/dbd/odbc/statement.rb +137 -0
  33. data/lib/dbd/pg/database.rb +516 -0
  34. data/lib/dbd/pg/exec.rb +47 -0
  35. data/lib/dbd/pg/statement.rb +160 -0
  36. data/lib/dbd/pg/tuples.rb +121 -0
  37. data/lib/dbd/pg/type.rb +209 -0
  38. data/lib/dbd/sqlite/database.rb +151 -0
  39. data/lib/dbd/sqlite/statement.rb +125 -0
  40. data/lib/dbd/sqlite3/database.rb +201 -0
  41. data/lib/dbd/sqlite3/statement.rb +78 -0
  42. data/lib/dbi.rb +336 -0
  43. data/lib/dbi/base_classes.rb +12 -0
  44. data/lib/dbi/base_classes/database.rb +135 -0
  45. data/lib/dbi/base_classes/driver.rb +47 -0
  46. data/lib/dbi/base_classes/statement.rb +171 -0
  47. data/lib/dbi/binary.rb +25 -0
  48. data/lib/dbi/columninfo.rb +107 -0
  49. data/lib/dbi/exceptions.rb +65 -0
  50. data/lib/dbi/handles.rb +49 -0
  51. data/lib/dbi/handles/database.rb +241 -0
  52. data/lib/dbi/handles/driver.rb +60 -0
  53. data/lib/dbi/handles/statement.rb +408 -0
  54. data/lib/dbi/row.rb +269 -0
  55. data/lib/dbi/sql.rb +22 -0
  56. data/lib/dbi/sql/preparedstatement.rb +115 -0
  57. data/lib/dbi/sql_type_constants.rb +75 -0
  58. data/lib/dbi/trace.rb +91 -0
  59. data/lib/dbi/types.rb +218 -0
  60. data/lib/dbi/typeutil.rb +109 -0
  61. data/lib/dbi/utils.rb +60 -0
  62. data/lib/dbi/utils/date.rb +59 -0
  63. data/lib/dbi/utils/tableformatter.rb +112 -0
  64. data/lib/dbi/utils/time.rb +52 -0
  65. data/lib/dbi/utils/timestamp.rb +96 -0
  66. data/lib/dbi/utils/xmlformatter.rb +73 -0
  67. data/lib/dbi/version.rb +3 -0
  68. data/prototypes/types2.rb +237 -0
  69. data/readme.md +274 -0
  70. data/setup.rb +1585 -0
  71. data/test/DBD_TESTS +50 -0
  72. data/test/TESTING +16 -0
  73. data/test/dbd/general/test_database.rb +206 -0
  74. data/test/dbd/general/test_statement.rb +326 -0
  75. data/test/dbd/general/test_types.rb +296 -0
  76. data/test/dbd/mysql/base.rb +26 -0
  77. data/test/dbd/mysql/down.sql +19 -0
  78. data/test/dbd/mysql/test_blob.rb +18 -0
  79. data/test/dbd/mysql/test_new_methods.rb +7 -0
  80. data/test/dbd/mysql/test_patches.rb +111 -0
  81. data/test/dbd/mysql/up.sql +28 -0
  82. data/test/dbd/odbc/base.rb +30 -0
  83. data/test/dbd/odbc/down.sql +19 -0
  84. data/test/dbd/odbc/test_new_methods.rb +12 -0
  85. data/test/dbd/odbc/test_ping.rb +10 -0
  86. data/test/dbd/odbc/test_statement.rb +44 -0
  87. data/test/dbd/odbc/test_transactions.rb +58 -0
  88. data/test/dbd/odbc/up.sql +33 -0
  89. data/test/dbd/postgresql/base.rb +31 -0
  90. data/test/dbd/postgresql/down.sql +31 -0
  91. data/test/dbd/postgresql/test_arrays.rb +179 -0
  92. data/test/dbd/postgresql/test_async.rb +121 -0
  93. data/test/dbd/postgresql/test_blob.rb +36 -0
  94. data/test/dbd/postgresql/test_bytea.rb +87 -0
  95. data/test/dbd/postgresql/test_ping.rb +10 -0
  96. data/test/dbd/postgresql/test_timestamp.rb +77 -0
  97. data/test/dbd/postgresql/test_transactions.rb +58 -0
  98. data/test/dbd/postgresql/testdbipg.rb +307 -0
  99. data/test/dbd/postgresql/up.sql +60 -0
  100. data/test/dbd/sqlite/base.rb +32 -0
  101. data/test/dbd/sqlite/test_database.rb +30 -0
  102. data/test/dbd/sqlite/test_driver.rb +68 -0
  103. data/test/dbd/sqlite/test_statement.rb +112 -0
  104. data/test/dbd/sqlite/up.sql +25 -0
  105. data/test/dbd/sqlite3/base.rb +32 -0
  106. data/test/dbd/sqlite3/test_database.rb +77 -0
  107. data/test/dbd/sqlite3/test_driver.rb +67 -0
  108. data/test/dbd/sqlite3/test_statement.rb +88 -0
  109. data/test/dbd/sqlite3/up.sql +33 -0
  110. data/test/dbi/tc_columninfo.rb +94 -0
  111. data/test/dbi/tc_date.rb +88 -0
  112. data/test/dbi/tc_dbi.rb +184 -0
  113. data/test/dbi/tc_row.rb +256 -0
  114. data/test/dbi/tc_sqlbind.rb +168 -0
  115. data/test/dbi/tc_statementhandle.rb +29 -0
  116. data/test/dbi/tc_time.rb +77 -0
  117. data/test/dbi/tc_timestamp.rb +142 -0
  118. data/test/dbi/tc_types.rb +268 -0
  119. data/test/ts_dbd.rb +131 -0
  120. data/test/ts_dbi.rb +16 -0
  121. data/ydbi.gemspec +24 -0
  122. metadata +224 -0
@@ -0,0 +1,516 @@
1
+ #
2
+ # See DBI::BaseDatabase.
3
+ #
4
+ class DBI::DBD::Pg::Database < DBI::BaseDatabase
5
+
6
+ # type map
7
+ POSTGRESQL_to_XOPEN = {
8
+ "boolean" => [DBI::SQL_CHAR, 1, nil],
9
+ "character" => [DBI::SQL_CHAR, 1, nil],
10
+ "char" => [DBI::SQL_CHAR, 1, nil],
11
+ "real" => [DBI::SQL_REAL, 4, 6],
12
+ "double precision" => [DBI::SQL_DOUBLE, 8, 15],
13
+ "smallint" => [DBI::SQL_SMALLINT, 2],
14
+ "integer" => [DBI::SQL_INTEGER, 4],
15
+ "bigint" => [DBI::SQL_BIGINT, 8],
16
+ "numeric" => [DBI::SQL_NUMERIC, nil, nil],
17
+ "time with time zone" => [DBI::SQL_TIME, nil, nil],
18
+ "timestamp with time zone" => [DBI::SQL_TIMESTAMP, nil, nil],
19
+ "bit varying" => [DBI::SQL_BINARY, nil, nil], #huh??
20
+ "character varying" => [DBI::SQL_VARCHAR, nil, nil],
21
+ "bit" => [DBI::SQL_TINYINT, nil, nil],
22
+ "text" => [DBI::SQL_VARCHAR, nil, nil],
23
+ nil => [DBI::SQL_OTHER, nil, nil]
24
+ }
25
+
26
+ attr_reader :type_map
27
+
28
+ #
29
+ # See DBI::BaseDatabase#new. These attributes are also supported:
30
+ #
31
+ # * pg_async: boolean or strings 'true' or 'false'. Indicates if we're to
32
+ # use PostgreSQL's asyncrohonous support. 'NonBlocking' is a synonym for
33
+ # this.
34
+ # * AutoCommit: 'unchained' mode in PostgreSQL. Commits after each
35
+ # statement execution.
36
+ # * pg_client_encoding: set the encoding for the client.
37
+ # * pg_native_binding: Boolean. Indicates whether to use libpq native
38
+ # binding or DBI's inline binding. Defaults to true.
39
+ #
40
+ def initialize(dbname, user, auth, attr)
41
+ hash = DBI::Utils.parse_params(dbname)
42
+
43
+ if hash['dbname'].nil? and hash['database'].nil?
44
+ raise DBI::InterfaceError, "must specify database"
45
+ end
46
+
47
+ hash['options'] ||= nil
48
+ hash['tty'] ||= ''
49
+ hash['port'] = hash['port'].to_i unless hash['port'].nil?
50
+
51
+ @connection = PGconn.new(hash['host'], hash['port'], hash['options'], hash['tty'],
52
+ hash['dbname'] || hash['database'], user, auth)
53
+
54
+ @exec_method = :exec
55
+ @in_transaction = false
56
+
57
+ # set attribute defaults, and look for pg_* attrs in the DSN
58
+ @attr = { 'AutoCommit' => true, 'pg_async' => false }
59
+ hash.each do |key, value|
60
+ @attr[key] = value if key =~ /^pg_./
61
+ end
62
+ @attr.merge!(attr || {})
63
+ if @attr['pg_async'].is_a?(String)
64
+ case @attr['pg_async'].downcase
65
+ when 'true'
66
+ @attr['pg_async'] = true
67
+ when 'false'
68
+ @attr['pg_async'] = false
69
+ else
70
+ raise InterfaceError, %q{'pg_async' must be 'true' or 'false'}
71
+ end
72
+ end
73
+
74
+ @attr.each { |k,v| self[k] = v}
75
+ @attr["pg_native_binding"] = true unless @attr.has_key? "pg_native_binding"
76
+
77
+ load_type_map
78
+
79
+ self['AutoCommit'] = true # Postgres starts in unchained mode (AutoCommit=on) by default
80
+
81
+ rescue PGError => err
82
+ raise DBI::OperationalError.new(err.message)
83
+ end
84
+
85
+ def disconnect
86
+ if not @attr['AutoCommit'] and @in_transaction
87
+ _exec("ROLLBACK") # rollback outstanding transactions
88
+ end
89
+ @connection.close
90
+ end
91
+
92
+ def ping
93
+ answer = _exec("SELECT 1")
94
+ if answer
95
+ return answer.num_tuples == 1
96
+ else
97
+ return false
98
+ end
99
+ rescue PGError
100
+ return false
101
+ ensure
102
+ answer.clear if answer
103
+ end
104
+
105
+ def database_name
106
+ @connection.db
107
+ end
108
+
109
+ def tables
110
+ stmt = execute("SELECT c.relname FROM pg_catalog.pg_class c WHERE c.relkind IN ('r','v') and pg_catalog.pg_table_is_visible(c.oid)")
111
+ res = stmt.fetch_all.collect {|row| row[0]}
112
+ stmt.finish
113
+ res
114
+ end
115
+
116
+ #
117
+ # See DBI::BaseDatabase.
118
+ #
119
+ # These additional attributes are also supported:
120
+ #
121
+ # * nullable: true if NULL values are allowed in this column.
122
+ # * indexed: true if this column is a part of an index.
123
+ # * primary: true if this column is a part of a primary key.
124
+ # * unique: true if this column is a part of a unique key.
125
+ # * default: what will be insert if this column is left out of an insert query.
126
+ # * array_of_type: true if this is actually an array of this type.
127
+ # +dbi_type+ will be the type authority if this is the case.
128
+ #
129
+ def columns(table)
130
+ sql1 = %[
131
+ select a.attname, i.indisprimary, i.indisunique
132
+ from pg_class bc inner join pg_index i
133
+ on bc.oid = i.indrelid
134
+ inner join pg_class c
135
+ on c.oid = i.indexrelid
136
+ inner join pg_attribute a
137
+ on c.oid = a.attrelid
138
+ where bc.relname = ?
139
+ and bc.relkind in ('r', 'v')
140
+ and pg_catalog.pg_table_is_visible(bc.oid);
141
+ ]
142
+
143
+ sql2 = %[
144
+ SELECT a.attname, a.atttypid, a.attnotnull, a.attlen, format_type(a.atttypid, a.atttypmod)
145
+ FROM pg_catalog.pg_class c, pg_attribute a, pg_type t
146
+ WHERE a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid AND c.relname = ?
147
+ AND c.relkind IN ('r','v')
148
+ AND pg_catalog.pg_table_is_visible(c.oid)
149
+ ]
150
+
151
+ # by Michael Neumann (get default value)
152
+ # corrected by Joseph McDonald
153
+ sql3 = %[
154
+ SELECT pg_attrdef.adsrc, pg_attribute.attname
155
+ FROM pg_attribute, pg_attrdef, pg_catalog.pg_class
156
+ WHERE pg_catalog.pg_class.relname = ? AND
157
+ pg_attribute.attrelid = pg_catalog.pg_class.oid AND
158
+ pg_attrdef.adrelid = pg_catalog.pg_class.oid AND
159
+ pg_attrdef.adnum = pg_attribute.attnum
160
+ AND pg_catalog.pg_class.relkind IN ('r','v')
161
+ AND pg_catalog.pg_table_is_visible(pg_catalog.pg_class.oid)
162
+ ]
163
+
164
+ dbh = DBI::DatabaseHandle.new(self)
165
+ dbh.driver_name = DBI::DBD::Pg.driver_name
166
+ indices = {}
167
+ default_values = {}
168
+
169
+ dbh.select_all(sql3, table) do |default, name|
170
+ default_values[name] = default
171
+ end
172
+
173
+ dbh.select_all(sql1, table) do |name, primary, unique|
174
+ indices[name] = [primary, unique]
175
+ end
176
+
177
+ ##########
178
+
179
+ ret = []
180
+ dbh.execute(sql2, table) do |sth|
181
+ ret = sth.collect do |row|
182
+ name, pg_type, notnullable, len, ftype = row
183
+ #name = row[2]
184
+ indexed = false
185
+ primary = nil
186
+ unique = nil
187
+ if indices.has_key?(name)
188
+ indexed = true
189
+ primary, unique = indices[name]
190
+ end
191
+
192
+ typeinfo = DBI::DBD::Pg.parse_type(ftype)
193
+ typeinfo[:size] ||= len
194
+
195
+ if POSTGRESQL_to_XOPEN.has_key?(typeinfo[:type])
196
+ sql_type = POSTGRESQL_to_XOPEN[typeinfo[:type]][0]
197
+ else
198
+ sql_type = POSTGRESQL_to_XOPEN[nil][0]
199
+ end
200
+
201
+ row = {}
202
+ row['name'] = name
203
+ row['sql_type'] = sql_type
204
+ row['type_name'] = typeinfo[:type]
205
+ row['nullable'] = ! notnullable
206
+ row['indexed'] = indexed
207
+ row['primary'] = primary
208
+ row['unique'] = unique
209
+ row['precision'] = typeinfo[:size]
210
+ row['scale'] = typeinfo[:decimal]
211
+ row['default'] = default_values[name]
212
+ row['array_of_type'] = typeinfo[:array]
213
+
214
+ if typeinfo[:array]
215
+ row['dbi_type'] =
216
+ DBI::DBD::Pg::Type::Array.new(
217
+ DBI::TypeUtil.type_name_to_module(typeinfo[:type])
218
+ )
219
+ end
220
+ row
221
+ end # collect
222
+ end # execute
223
+
224
+ return ret
225
+ end
226
+
227
+ def prepare(statement)
228
+ DBI::DBD::Pg::Statement.new(self, statement)
229
+ end
230
+
231
+ def [](attr)
232
+ case attr
233
+ when 'pg_client_encoding'
234
+ @connection.client_encoding
235
+ when 'NonBlocking'
236
+ @attr['pg_async']
237
+ else
238
+ @attr[attr]
239
+ end
240
+ end
241
+
242
+ def []=(attr, value)
243
+ case attr
244
+ when 'AutoCommit'
245
+ if @attr['AutoCommit'] != value then
246
+ if value # turn AutoCommit ON
247
+ if @in_transaction
248
+ # TODO: commit outstanding transactions?
249
+ _exec("COMMIT")
250
+ @in_transaction = false
251
+ end
252
+ else # turn AutoCommit OFF
253
+ @in_transaction = false
254
+ end
255
+ end
256
+ # value is assigned below
257
+ when 'NonBlocking', 'pg_async'
258
+ # booleanize input
259
+ value = value ? true : false
260
+ @pgexec = (value ? DBI::DBD::Pg::PgExecutorAsync : DBI::DBD::Pg::PgExecutor).new(@connection)
261
+ # value is assigned to @attr below
262
+ when 'pg_client_encoding'
263
+ @connection.set_client_encoding(value)
264
+ when 'pg_native_binding'
265
+ @attr[attr] = value
266
+ else
267
+ if attr =~ /^pg_/ or attr != /_/
268
+ raise DBI::NotSupportedError, "Option '#{attr}' not supported"
269
+ else # option for some other driver - quitly ignore
270
+ return
271
+ end
272
+ end
273
+ @attr[attr] = value
274
+ end
275
+
276
+ def commit
277
+ if @in_transaction
278
+ _exec("COMMIT")
279
+ @in_transaction = false
280
+ else
281
+ # TODO: Warn?
282
+ end
283
+ end
284
+
285
+ def rollback
286
+ if @in_transaction
287
+ _exec("ROLLBACK")
288
+ @in_transaction = false
289
+ else
290
+ # TODO: Warn?
291
+ end
292
+ end
293
+
294
+ #
295
+ # Are we in an transaction?
296
+ #
297
+ def in_transaction?
298
+ @in_transaction
299
+ end
300
+
301
+ #
302
+ # Forcibly initializes a new transaction.
303
+ #
304
+ def start_transaction
305
+ _exec("BEGIN")
306
+ @in_transaction = true
307
+ end
308
+
309
+ def _exec(sql, *parameters)
310
+ @pgexec.exec(sql, parameters)
311
+ end
312
+
313
+ def _exec_prepared(stmt_name, *parameters)
314
+ @pgexec.exec_prepared(stmt_name, parameters)
315
+ end
316
+
317
+ def _prepare(stmt_name, sql)
318
+ @pgexec.prepare(stmt_name, sql)
319
+ end
320
+
321
+ private
322
+
323
+ # special quoting if value is element of an array
324
+ def quote_array_elements( value )
325
+ # XXX is this method still being used?
326
+ case value
327
+ when Array
328
+ '{'+ value.collect{|v| quote_array_elements(v) }.join(',') + '}'
329
+ when String
330
+ '"' + value.gsub(/\\/){ '\\\\' }.gsub(/"/){ '\\"' } + '"'
331
+ else
332
+ quote( value ).sub(/^'/,'').sub(/'$/,'')
333
+ end
334
+ end
335
+
336
+ def parse_type_name(type_name)
337
+ case type_name
338
+ when 'bool' then DBI::Type::Boolean
339
+ when 'int8', 'int4', 'int2' then DBI::Type::Integer
340
+ when 'varchar' then DBI::Type::Varchar
341
+ when 'float4','float8' then DBI::Type::Float
342
+ when 'time', 'timetz' then DBI::Type::Timestamp
343
+ when 'timestamp', 'timestamptz' then DBI::Type::Timestamp
344
+ when 'date' then DBI::Type::Timestamp
345
+ when 'decimal', 'numeric' then DBI::Type::Decimal
346
+ when 'bytea' then DBI::DBD::Pg::Type::ByteA
347
+ when 'enum' then DBI::Type::Varchar
348
+ end
349
+ end
350
+
351
+ #
352
+ # Gathers the types from the postgres database and attempts to
353
+ # locate matching DBI::Type objects for them.
354
+ #
355
+ def load_type_map
356
+ @type_map = Hash.new
357
+
358
+ res = _exec("SELECT oid, typname, typelem FROM pg_type WHERE typtype IN ('b', 'e')")
359
+
360
+ res.each do |row|
361
+ rowtype = parse_type_name(row["typname"])
362
+ @type_map[row["oid"].to_i] =
363
+ {
364
+ "type_name" => row["typname"],
365
+ "dbi_type" =>
366
+ if rowtype
367
+ rowtype
368
+ elsif row["typname"] =~ /^_/ and row["typelem"].to_i > 0 then
369
+ # arrays are special and have a subtype, as an
370
+ # oid held in the "typelem" field.
371
+ # Since we may not have a mapping for the
372
+ # subtype yet, defer by storing the typelem
373
+ # integer as a base type in a constructed
374
+ # Type::Array object. dirty, i know.
375
+ #
376
+ # These array objects will be reconstructed
377
+ # after all rows are processed and therefore
378
+ # the oid -> type mapping is complete.
379
+ #
380
+ DBI::DBD::Pg::Type::Array.new(row["typelem"].to_i)
381
+ else
382
+ DBI::Type::Varchar
383
+ end
384
+ }
385
+ end
386
+ # additional conversions
387
+ @type_map[705] ||= DBI::Type::Varchar # select 'hallo'
388
+ @type_map[1114] ||= DBI::Type::Timestamp # TIMESTAMP WITHOUT TIME ZONE
389
+
390
+ # remap array subtypes
391
+ @type_map.each_key do |key|
392
+ if @type_map[key]["dbi_type"].class == DBI::DBD::Pg::Type::Array
393
+ oid = @type_map[key]["dbi_type"].base_type
394
+ if @type_map[oid]
395
+ @type_map[key]["dbi_type"] = DBI::DBD::Pg::Type::Array.new(@type_map[oid]["dbi_type"])
396
+ else
397
+ # punt
398
+ @type_map[key] = DBI::DBD::Pg::Type::Array.new(DBI::Type::Varchar)
399
+ end
400
+ end
401
+ end
402
+ end
403
+
404
+ public
405
+
406
+ # return the postgresql types for this session. returns an oid -> type name mapping.
407
+ def __types(force=nil)
408
+ load_type_map if (!@type_map or force)
409
+ @type_map
410
+ end
411
+
412
+ # deprecated.
413
+ def __types_old
414
+ h = { }
415
+
416
+ _exec('select oid, typname from pg_type').each do |row|
417
+ h[row["oid"].to_i] = row["typname"]
418
+ end
419
+
420
+ return h
421
+ end
422
+
423
+ #
424
+ # Import a BLOB from a file.
425
+ #
426
+ def __blob_import(file)
427
+ start_transaction unless @in_transaction
428
+ @connection.lo_import(file)
429
+ rescue PGError => err
430
+ raise DBI::DatabaseError.new(err.message)
431
+ end
432
+
433
+ #
434
+ # Export a BLOB to a file.
435
+ #
436
+ def __blob_export(oid, file)
437
+ start_transaction unless @in_transaction
438
+ @connection.lo_export(oid.to_i, file)
439
+ rescue PGError => err
440
+ raise DBI::DatabaseError.new(err.message)
441
+ end
442
+
443
+ #
444
+ # Create a BLOB.
445
+ #
446
+ def __blob_create(mode=PGconn::INV_READ)
447
+ start_transaction unless @in_transaction
448
+ @connection.lo_creat(mode)
449
+ rescue PGError => err
450
+ raise DBI::DatabaseError.new(err.message)
451
+ end
452
+
453
+ #
454
+ # Open a BLOB.
455
+ #
456
+ def __blob_open(oid, mode=PGconn::INV_READ)
457
+ start_transaction unless @in_transaction
458
+ @connection.lo_open(oid.to_i, mode)
459
+ rescue PGError => err
460
+ raise DBI::DatabaseError.new(err.message)
461
+ end
462
+
463
+ #
464
+ # Remove a BLOB.
465
+ #
466
+ def __blob_unlink(oid)
467
+ start_transaction unless @in_transaction
468
+ @connection.lo_unlink(oid.to_i)
469
+ rescue PGError => err
470
+ raise DBI::DatabaseError.new(err.message)
471
+ end
472
+
473
+ #
474
+ # Read a BLOB and return the data.
475
+ #
476
+ def __blob_read(oid, length)
477
+ blob = @connection.lo_open(oid.to_i, PGconn::INV_READ)
478
+
479
+ if length.nil?
480
+ data = @connection.lo_read(blob)
481
+ else
482
+ data = @connection.lo_read(blob, length)
483
+ end
484
+
485
+ # FIXME it doesn't like to close here either.
486
+ # @connection.lo_close(blob)
487
+ data
488
+ rescue PGError => err
489
+ raise DBI::DatabaseError.new(err.message)
490
+ end
491
+
492
+ #
493
+ # Write the value to the BLOB.
494
+ #
495
+ def __blob_write(oid, value)
496
+ start_transaction unless @in_transaction
497
+ blob = @connection.lo_open(oid.to_i, PGconn::INV_WRITE)
498
+ res = @connection.lo_write(blob, value)
499
+ # FIXME not sure why PG doesn't like to close here -- seems to be
500
+ # working but we should make sure it's not eating file descriptors
501
+ # up before release.
502
+ # @connection.lo_close(blob)
503
+ return res
504
+ rescue PGError => err
505
+ raise DBI::DatabaseError.new(err.message)
506
+ end
507
+
508
+ #
509
+ # FIXME DOCUMENT
510
+ #
511
+ def __set_notice_processor(proc)
512
+ @connection.set_notice_processor proc
513
+ rescue PGError => err
514
+ raise DBI::DatabaseError.new(err.message)
515
+ end
516
+ end # Database