ydbi 0.5.6 → 0.5.7

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