ydbi 0.5.2 → 0.5.7
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 +5 -5
- data/.github/workflows/ruby.yml +35 -0
- data/.gitignore +8 -0
- data/.travis.yml +15 -0
- data/ChangeLog +339 -314
- data/Gemfile +5 -0
- data/Rakefile +10 -0
- data/TODO +44 -0
- data/bench/bench.rb +79 -0
- data/build/rake_task_lib.rb +186 -0
- data/doc/DBD_SPEC.rdoc +88 -0
- data/doc/DBI_SPEC.rdoc +157 -0
- data/doc/homepage/contact.html +62 -0
- data/doc/homepage/development.html +124 -0
- data/doc/homepage/index.html +83 -0
- data/doc/homepage/ruby-dbi.css +91 -0
- data/lib/dbd/Mysql.rb +137 -0
- data/lib/dbd/ODBC.rb +89 -0
- data/lib/dbd/Pg.rb +188 -0
- data/lib/dbd/SQLite.rb +97 -0
- data/lib/dbd/SQLite3.rb +124 -0
- data/lib/dbd/mysql/database.rb +405 -0
- data/lib/dbd/mysql/driver.rb +125 -0
- data/lib/dbd/mysql/statement.rb +188 -0
- data/lib/dbd/odbc/database.rb +128 -0
- data/lib/dbd/odbc/driver.rb +38 -0
- data/lib/dbd/odbc/statement.rb +137 -0
- data/lib/dbd/pg/database.rb +504 -0
- data/lib/dbd/pg/exec.rb +47 -0
- data/lib/dbd/pg/statement.rb +160 -0
- data/lib/dbd/pg/tuples.rb +121 -0
- data/lib/dbd/pg/type.rb +209 -0
- data/lib/dbd/sqlite/database.rb +151 -0
- data/lib/dbd/sqlite/statement.rb +125 -0
- data/lib/dbd/sqlite3/database.rb +201 -0
- data/lib/dbd/sqlite3/statement.rb +78 -0
- data/lib/dbi.rb +14 -17
- data/lib/dbi/utils/date.rb +7 -3
- data/lib/dbi/version.rb +1 -1
- data/prototypes/types2.rb +237 -0
- data/readme.md +15 -0
- data/setup.rb +1585 -0
- data/test/DBD_TESTS +50 -0
- data/test/TESTING +16 -0
- data/test/dbd/general/test_database.rb +206 -0
- data/test/dbd/general/test_statement.rb +326 -0
- data/test/dbd/general/test_types.rb +296 -0
- data/test/dbd/mysql/base.rb +26 -0
- data/test/dbd/mysql/down.sql +19 -0
- data/test/dbd/mysql/test_blob.rb +18 -0
- data/test/dbd/mysql/test_new_methods.rb +7 -0
- data/test/dbd/mysql/test_patches.rb +111 -0
- data/test/dbd/mysql/up.sql +28 -0
- data/test/dbd/odbc/base.rb +30 -0
- data/test/dbd/odbc/down.sql +19 -0
- data/test/dbd/odbc/test_new_methods.rb +12 -0
- data/test/dbd/odbc/test_ping.rb +10 -0
- data/test/dbd/odbc/test_statement.rb +44 -0
- data/test/dbd/odbc/test_transactions.rb +58 -0
- data/test/dbd/odbc/up.sql +33 -0
- data/test/dbd/postgresql/base.rb +31 -0
- data/test/dbd/postgresql/down.sql +31 -0
- data/test/dbd/postgresql/test_arrays.rb +179 -0
- data/test/dbd/postgresql/test_async.rb +121 -0
- data/test/dbd/postgresql/test_blob.rb +36 -0
- data/test/dbd/postgresql/test_bytea.rb +87 -0
- data/test/dbd/postgresql/test_ping.rb +10 -0
- data/test/dbd/postgresql/test_timestamp.rb +77 -0
- data/test/dbd/postgresql/test_transactions.rb +58 -0
- data/test/dbd/postgresql/testdbipg.rb +307 -0
- data/test/dbd/postgresql/up.sql +60 -0
- data/test/dbd/sqlite/base.rb +32 -0
- data/test/dbd/sqlite/test_database.rb +30 -0
- data/test/dbd/sqlite/test_driver.rb +68 -0
- data/test/dbd/sqlite/test_statement.rb +112 -0
- data/test/dbd/sqlite/up.sql +25 -0
- data/test/dbd/sqlite3/base.rb +32 -0
- data/test/dbd/sqlite3/test_database.rb +77 -0
- data/test/dbd/sqlite3/test_driver.rb +67 -0
- data/test/dbd/sqlite3/test_statement.rb +88 -0
- data/test/dbd/sqlite3/up.sql +33 -0
- data/test/dbi/tc_columninfo.rb +4 -9
- data/test/dbi/tc_date.rb +2 -9
- data/test/dbi/tc_dbi.rb +3 -9
- data/test/dbi/tc_row.rb +17 -23
- data/test/dbi/tc_sqlbind.rb +6 -7
- data/test/dbi/tc_statementhandle.rb +3 -4
- data/test/dbi/tc_time.rb +2 -8
- data/test/dbi/tc_timestamp.rb +2 -16
- data/test/dbi/tc_types.rb +5 -11
- data/test/ts_dbd.rb +131 -0
- data/ydbi.gemspec +23 -0
- metadata +128 -10
@@ -0,0 +1,121 @@
|
|
1
|
+
class TestPostgresAsync < DBDConfig.testbase(:postgresql)
|
2
|
+
def get_dbh
|
3
|
+
config = DBDConfig.get_config['postgresql']
|
4
|
+
DBI.connect("dbi:Pg:#{config['dbname']}", config['username'], config['password'])
|
5
|
+
end
|
6
|
+
|
7
|
+
def get_db_info
|
8
|
+
config = DBDConfig.get_config['postgresql']
|
9
|
+
dsn = "dbi:Pg:database=#{config['dbname']}"
|
10
|
+
user = config['username']
|
11
|
+
pass = config['password']
|
12
|
+
|
13
|
+
return [dsn, user, pass]
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_async_default
|
17
|
+
dsn, user, pass = get_db_info
|
18
|
+
|
19
|
+
DBI.connect(dsn, user, pass) do |dbh|
|
20
|
+
assert_equal false, dbh['pg_async']
|
21
|
+
assert_equal false, dbh['NonBlocking']
|
22
|
+
dbh.prepare('SELECT 1') do |sth| # Statement inherits
|
23
|
+
assert_equal false, sth['pg_async']
|
24
|
+
assert_equal false, sth['NonBlocking']
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_async_dsn_enable
|
30
|
+
dsn, user, pass = get_db_info
|
31
|
+
|
32
|
+
for enable in ['true', 'TRUE', 'tRuE']
|
33
|
+
DBI.connect(dsn + ";pg_async=#{enable}", user, pass) do |dbh|
|
34
|
+
assert_equal true, dbh['pg_async']
|
35
|
+
assert_equal true, dbh['NonBlocking']
|
36
|
+
dbh.prepare('SELECT 1') do |sth| # Statement inherits
|
37
|
+
assert_equal true, sth['pg_async']
|
38
|
+
assert_equal true, sth['NonBlocking']
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_async_attr_enable
|
45
|
+
dsn, user, pass = get_db_info
|
46
|
+
|
47
|
+
for enable in ['true', 'TRUE', 'tRuE']
|
48
|
+
DBI.connect(dsn, user, pass, { 'pg_async' => enable } ) do |dbh|
|
49
|
+
assert_equal true, dbh['pg_async']
|
50
|
+
assert_equal true, dbh['NonBlocking']
|
51
|
+
dbh.prepare('SELECT 1') do |sth| # Statement inherits
|
52
|
+
assert_equal true, sth['pg_async']
|
53
|
+
assert_equal true, sth['NonBlocking']
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_async_dsn_disable
|
60
|
+
dsn, user, pass = get_db_info
|
61
|
+
|
62
|
+
for disable in ['false', 'FALSE', 'fAlSe']
|
63
|
+
DBI.connect(dsn + ";pg_async=#{disable}", user, pass) do |dbh|
|
64
|
+
assert_equal false, dbh['pg_async']
|
65
|
+
assert_equal false, dbh['NonBlocking']
|
66
|
+
dbh.prepare('SELECT 1') do |sth| # Statement inherits
|
67
|
+
assert_equal false, sth['pg_async']
|
68
|
+
assert_equal false, sth['NonBlocking']
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_async_attr_disable
|
75
|
+
dsn, user, pass = get_db_info
|
76
|
+
|
77
|
+
for disable in ['false', 'FALSE', 'fAlSe']
|
78
|
+
DBI.connect(dsn, user, pass, { 'pg_async' => disable }) do |dbh|
|
79
|
+
assert_equal false, dbh['pg_async']
|
80
|
+
assert_equal false, dbh['NonBlocking']
|
81
|
+
dbh.prepare('SELECT 1') do |sth| # Statement inherits
|
82
|
+
assert_equal false, sth['pg_async']
|
83
|
+
assert_equal false, sth['NonBlocking']
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_manual_enable
|
90
|
+
dsn, user, pass = get_db_info
|
91
|
+
|
92
|
+
DBI.connect(dsn, user, pass) do |dbh|
|
93
|
+
dbh['pg_async'] = true
|
94
|
+
assert_equal true, dbh['pg_async']
|
95
|
+
assert_equal true, dbh['NonBlocking']
|
96
|
+
dbh.prepare('SELECT 1') do |sth| # Statement inherits
|
97
|
+
assert_equal true, sth['pg_async']
|
98
|
+
assert_equal true, sth['NonBlocking']
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_async_commands
|
104
|
+
dsn, user, pass = get_db_info
|
105
|
+
|
106
|
+
DBI.connect(dsn + ";pg_async=true", user, pass) do |dbh|
|
107
|
+
assert_equal true, dbh['pg_async']
|
108
|
+
assert_equal true, dbh['NonBlocking']
|
109
|
+
ret = dbh.select_all('SELECT 1')
|
110
|
+
assert_equal [[1]], ret
|
111
|
+
|
112
|
+
ret = dbh.select_all(%q{SELECT 1 WHERE 'foo' = ?}, 'bar')
|
113
|
+
assert_equal [], ret
|
114
|
+
|
115
|
+
dbh.prepare(%q{SELECT 1 WHERE 'foo' = ?}) do |sth|
|
116
|
+
sth.execute('bar')
|
117
|
+
assert_equal [], sth.fetch_all
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class TestPostgresBlob < DBDConfig.testbase(:postgresql)
|
2
|
+
DATA = "this is my new binary object"
|
3
|
+
|
4
|
+
def test_insert
|
5
|
+
assert @dbh
|
6
|
+
assert @dbh.ping
|
7
|
+
|
8
|
+
# test with DBI::Binary
|
9
|
+
assert_equal 1, @dbh.do("INSERT INTO blob_test (name, data) VALUES (?,?)", "test", DBI::Binary.new(DATA))
|
10
|
+
|
11
|
+
# test with blob_create directly
|
12
|
+
blob = @dbh.func(:blob_create, PGconn::INV_WRITE)
|
13
|
+
assert blob
|
14
|
+
assert @dbh.func(:blob_write, blob, DATA)
|
15
|
+
assert_equal 1, @dbh.do("INSERT INTO blob_test (name, data) VALUES (?,?)", "test (2)", blob)
|
16
|
+
|
17
|
+
# test with blob_import directly
|
18
|
+
File.open('/tmp/pg_dbi_import_test', 'w') { |f| f << DATA }
|
19
|
+
blob = @dbh.func(:blob_import, '/tmp/pg_dbi_import_test')
|
20
|
+
assert blob
|
21
|
+
assert_equal 1, @dbh.do("INSERT INTO blob_test (name, data) VALUES (?,?)", "test (2)", blob)
|
22
|
+
|
23
|
+
index = 0
|
24
|
+
@dbh.select_all("SELECT name, data FROM blob_test") do |name, data|
|
25
|
+
index += 1
|
26
|
+
assert_equal DATA, @dbh.func(:blob_read, data, DATA.length)
|
27
|
+
@dbh.func(:blob_export, data, '/tmp/pg_dbi_read_test')
|
28
|
+
assert_equal DATA, File.read('/tmp/pg_dbi_read_test')
|
29
|
+
end
|
30
|
+
|
31
|
+
assert_equal 3, index
|
32
|
+
|
33
|
+
File.unlink("/tmp/pg_dbi_read_test")
|
34
|
+
File.unlink("/tmp/pg_dbi_import_test")
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'dbd/Pg'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rubygems'
|
5
|
+
gem 'pg'
|
6
|
+
rescue Exception => e
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'pg'
|
10
|
+
|
11
|
+
LEN = 50
|
12
|
+
|
13
|
+
class TestPostgresByteA < DBDConfig.testbase(:postgresql)
|
14
|
+
# FIXME the 'pg' module is broken and doesn't encode/decode properly.
|
15
|
+
# this test should prove that the 'pg' module works so we can back out our
|
16
|
+
# hacks.
|
17
|
+
def skip_underlying_driver
|
18
|
+
str = generate_random_string
|
19
|
+
|
20
|
+
encoded = PGconn.escape_bytea(str.dup)
|
21
|
+
decoded = PGconn.unescape_bytea(encoded)
|
22
|
+
|
23
|
+
assert_equal str, decoded
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_encode_decode
|
27
|
+
bytea = DBI::DBD::Pg::Type::ByteA
|
28
|
+
|
29
|
+
# some specific cases that were failing intermittenly
|
30
|
+
# poor \\ handling
|
31
|
+
str = "\236\000\257\202G<\371\035TPEO\211\005*AH'H\3136\360\004\245\261\037\340u\003s\\772X\231\002\200\n\327\202\217\353\177r\317o\341\237\341"
|
32
|
+
encoded = bytea.escape_bytea(str)
|
33
|
+
decoded = bytea.parse(encoded)
|
34
|
+
|
35
|
+
assert_equal str, decoded
|
36
|
+
|
37
|
+
# the split hack not working
|
38
|
+
str = "\343\336e\260\337\373\314\026\323#\237i\035\0302\024\346X\274\016\324\371\206\036\230\374\206#rA\n\214\272\316\330\025\374\000\2663\244M\255x\360\002\266q\336\231"
|
39
|
+
|
40
|
+
encoded = bytea.escape_bytea(str)
|
41
|
+
decoded = bytea.parse(encoded)
|
42
|
+
|
43
|
+
assert_equal str, decoded
|
44
|
+
|
45
|
+
# delimiter at the end
|
46
|
+
str = "\343\336e\260\337\373\314\026\323#\237i\035\0302\024\346X\274\016\324\371\206\036\230\374\206#rA\n\214\272\316\330\025\374\000\2663\244M\255x\360\002\266q\336\231\\\\\\\\"
|
47
|
+
|
48
|
+
encoded = bytea.escape_bytea(str)
|
49
|
+
decoded = bytea.parse(encoded)
|
50
|
+
|
51
|
+
assert_equal str, decoded
|
52
|
+
|
53
|
+
# a huge test to weed out all the stragglers
|
54
|
+
5_000.times do
|
55
|
+
str = generate_random_string
|
56
|
+
|
57
|
+
encoded = bytea.escape_bytea(str)
|
58
|
+
decoded = bytea.parse(encoded)
|
59
|
+
|
60
|
+
assert_equal str, decoded
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_insert_retrieve
|
65
|
+
assert_nothing_raised do
|
66
|
+
str = generate_random_string
|
67
|
+
sth = @dbh.prepare('insert into bytea_test (foo) values (?)')
|
68
|
+
sth.execute(DBI::DBD::Pg::Type::ByteA.new(str))
|
69
|
+
sth.finish
|
70
|
+
|
71
|
+
sth = @dbh.prepare('select * from bytea_test')
|
72
|
+
sth.execute
|
73
|
+
assert_equal([str], sth.fetch)
|
74
|
+
sth.finish
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def generate_random_string
|
79
|
+
# random string test
|
80
|
+
str = " " * LEN
|
81
|
+
for i in 0...LEN
|
82
|
+
str[i] = (rand * 256).to_i.chr
|
83
|
+
end
|
84
|
+
|
85
|
+
return str
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class TestPostgresPing < DBDConfig.testbase(:postgresql)
|
2
|
+
def test_ping
|
3
|
+
config = DBDConfig.get_config['postgresql']
|
4
|
+
dbh = DBI.connect("dbi:Pg:#{config['dbname']}", config['username'], config['password'])
|
5
|
+
assert dbh
|
6
|
+
assert dbh.ping
|
7
|
+
dbh.disconnect
|
8
|
+
assert_raises(DBI::InterfaceError) { dbh.ping }
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
class TestPostgresTimestamp < DBDConfig.testbase(:postgresql)
|
2
|
+
def get_tstamp
|
3
|
+
DateTime.parse("2008-03-08 10:39:01.012300")
|
4
|
+
end
|
5
|
+
|
6
|
+
# XXX DateTime is not allowed to modify the sec_fraction component, so this test is moot.
|
7
|
+
# perhaps we need to fix DateTime.
|
8
|
+
def skip_test_timestamp_altered_fraction
|
9
|
+
ts = nil
|
10
|
+
|
11
|
+
assert_nothing_raised do
|
12
|
+
@sth = @dbh.prepare("insert into timestamp_test (mytimestamp) values (?)")
|
13
|
+
ts = DateTime.parse(Time.now.to_s)
|
14
|
+
ts.sec_fraction = 22200000
|
15
|
+
@sth.execute(ts)
|
16
|
+
@sth.finish
|
17
|
+
end
|
18
|
+
|
19
|
+
assert_nothing_raised do
|
20
|
+
@sth = @dbh.prepare("select * from timestamp_test")
|
21
|
+
@sth.execute
|
22
|
+
row = @sth.fetch
|
23
|
+
@sth.finish
|
24
|
+
assert_equal ts.sec_fraction, row[0].sec_fraction
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_current_timestamp
|
29
|
+
assert @dbh
|
30
|
+
# syntax db-specific (e.g., "from dual", "...timestamp()", etc.)
|
31
|
+
ts = @dbh.select_one("SELECT CURRENT_TIMESTAMP")[0]
|
32
|
+
assert_kind_of DateTime, ts
|
33
|
+
assert_not_nil ts.sec_fraction
|
34
|
+
end
|
35
|
+
|
36
|
+
# Just like the 'general' test, but checking for fractional seconds
|
37
|
+
def test_timestamp_fractional
|
38
|
+
assert @dbh
|
39
|
+
@sth = nil
|
40
|
+
t = get_tstamp
|
41
|
+
assert_nothing_raised do
|
42
|
+
@sth = @dbh.prepare("insert into timestamp_test (mytimestamp) values (?)")
|
43
|
+
@sth.execute(t)
|
44
|
+
@sth.finish
|
45
|
+
end
|
46
|
+
|
47
|
+
assert_nothing_raised do
|
48
|
+
@sth = @dbh.prepare("select * from timestamp_test")
|
49
|
+
@sth.execute
|
50
|
+
row = @sth.fetch
|
51
|
+
assert_kind_of DateTime, row[0]
|
52
|
+
assert_equal t.year, row[0].year
|
53
|
+
assert_equal t.month, row[0].month
|
54
|
+
assert_equal t.day, row[0].day
|
55
|
+
assert_equal t.hour, row[0].hour
|
56
|
+
assert_equal t.min, row[0].min
|
57
|
+
assert_equal t.sec, row[0].sec
|
58
|
+
assert_not_nil row[0].sec_fraction
|
59
|
+
assert_equal t.sec_fraction, row[0].sec_fraction
|
60
|
+
@sth.finish
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Is our DBI::Timestamp equivalent to its canonical string literal
|
65
|
+
# form cast appropriately?
|
66
|
+
def test_timestamp_from_cast
|
67
|
+
assert @dbh
|
68
|
+
sql_ts = "SELECT CAST('2008-03-08 10:39:01.012300' AS TIMESTAMP)"
|
69
|
+
|
70
|
+
row = @dbh.select_one(sql_ts)
|
71
|
+
assert_not_nil row
|
72
|
+
assert_equal 1, row.size
|
73
|
+
|
74
|
+
assert_kind_of DateTime, row[0]
|
75
|
+
assert_equal row[0], get_tstamp
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class TestPostgresTransaction < DBDConfig.testbase(:postgresql)
|
2
|
+
def test_rollback
|
3
|
+
dbh = get_dbh
|
4
|
+
dbh["AutoCommit"] = false
|
5
|
+
@sth = dbh.prepare('insert into names (name, age) values (?, ?)')
|
6
|
+
@sth.execute("Foo", 51)
|
7
|
+
dbh.rollback
|
8
|
+
assert_equal 1, @sth.rows
|
9
|
+
@sth.finish
|
10
|
+
|
11
|
+
|
12
|
+
@sth = dbh.prepare('select name, age from names where name=?')
|
13
|
+
@sth.execute("Foo")
|
14
|
+
assert !@sth.fetch
|
15
|
+
@sth.finish
|
16
|
+
dbh.disconnect
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_commit
|
20
|
+
dbh = get_dbh
|
21
|
+
dbh["AutoCommit"] = false
|
22
|
+
@sth = dbh.prepare('insert into names (name, age) values (?, ?)')
|
23
|
+
@sth.execute("Foo", 51)
|
24
|
+
dbh.commit
|
25
|
+
assert_equal 1, @sth.rows
|
26
|
+
@sth.finish
|
27
|
+
|
28
|
+
@sth = dbh.prepare('select name, age from names where name=?')
|
29
|
+
@sth.execute("Foo")
|
30
|
+
row = @sth.fetch
|
31
|
+
assert row
|
32
|
+
assert_equal "Foo", row[0]
|
33
|
+
assert_equal 51, row[1]
|
34
|
+
@sth.finish
|
35
|
+
dbh.disconnect
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_select_transaction
|
39
|
+
# per bug #10466
|
40
|
+
dbh = get_dbh
|
41
|
+
dbh["AutoCommit"] = false
|
42
|
+
@sth = dbh.prepare('select * from test_insert(?, ?)');
|
43
|
+
@sth.execute("Foo", 51)
|
44
|
+
dbh.rollback
|
45
|
+
@sth.finish
|
46
|
+
|
47
|
+
@sth = dbh.prepare('select name, age from names where name=?')
|
48
|
+
@sth.execute("Foo")
|
49
|
+
assert !@sth.fetch
|
50
|
+
@sth.finish
|
51
|
+
dbh.disconnect
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_dbh
|
55
|
+
config = DBDConfig.get_config
|
56
|
+
DBI.connect("dbi:Pg:#{config['postgresql']['dbname']}", config['postgresql']['username'], config['postgresql']['password'])
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,307 @@
|
|
1
|
+
require 'dbd/Pg'
|
2
|
+
|
3
|
+
module DBI
|
4
|
+
class ForcedError < ::DBI::Error
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
######################################################################
|
9
|
+
# Test the PostgreSql DBD driver. This test exercises options
|
10
|
+
# difficult to test through the standard DBI interface.
|
11
|
+
#
|
12
|
+
class TestDbdPostgres < DBDConfig.testbase(:postgresql)
|
13
|
+
|
14
|
+
# FIXME this is a feature that should be there, but currently isn't.
|
15
|
+
# def test_connect
|
16
|
+
# dbd = get_dbd
|
17
|
+
# assert_not_nil dbd.connection
|
18
|
+
# assert_equal 'localhost', dbd.connection.host
|
19
|
+
# assert_equal 'erikh', dbd.connection.user
|
20
|
+
# assert_equal 'rubytest', dbd.connection.db
|
21
|
+
# assert_equal 5432, dbd.connection.port
|
22
|
+
# ensure
|
23
|
+
# dbd.disconnect if dbd
|
24
|
+
# end
|
25
|
+
|
26
|
+
# this monkeypatch is used for the following test... NEVER integrate this into DBI proper.
|
27
|
+
class DBI::StatementHandle < DBI::Handle
|
28
|
+
def stmt_name
|
29
|
+
@handle.instance_variable_get(:@stmt_name)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_database_name
|
34
|
+
assert_nothing_raised do
|
35
|
+
assert_equal DBDConfig.get_config[dbtype]['dbname'], @dbh.database_name
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_enum_type
|
40
|
+
assert_nothing_raised do
|
41
|
+
assert(@dbh.convert_types)
|
42
|
+
@sth = @dbh.prepare("insert into enum_type_test values (?)")
|
43
|
+
@sth.execute("one")
|
44
|
+
@sth.finish
|
45
|
+
|
46
|
+
@sth = @dbh.prepare("select foo from enum_type_test")
|
47
|
+
@sth.execute
|
48
|
+
assert_equal(@sth.fetch, ['one'])
|
49
|
+
@sth.finish
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_statement_finish_deallocates_sth
|
54
|
+
assert_nothing_raised do
|
55
|
+
@sth = @dbh.prepare("select * from names")
|
56
|
+
@sth.execute
|
57
|
+
sth_internal_name = @sth.stmt_name
|
58
|
+
assert(sth_internal_name)
|
59
|
+
assert(!sth_internal_name.empty?)
|
60
|
+
@sth.finish
|
61
|
+
|
62
|
+
# at this point, the statement name should no longer exist
|
63
|
+
#
|
64
|
+
# XXX this is a potentially horrible way of doing it since it'll
|
65
|
+
# create another prepared statement, but *at this time*, I don't
|
66
|
+
# see any drawbacks and the alternative is considerably uglier.
|
67
|
+
|
68
|
+
@sth = @dbh.prepare("select count(*) from pg_prepared_statements where name = ?")
|
69
|
+
@sth.execute(sth_internal_name)
|
70
|
+
assert_equal([0], @sth.fetch)
|
71
|
+
@sth.finish
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_binding
|
76
|
+
assert(@dbh["pg_native_binding"])
|
77
|
+
|
78
|
+
assert_raises(DBI::ProgrammingError) do
|
79
|
+
@sth = @dbh.prepare("select * from names where age IS NOT ?")
|
80
|
+
@sth.execute("NULL")
|
81
|
+
@sth.finish
|
82
|
+
end
|
83
|
+
|
84
|
+
assert_nothing_raised do
|
85
|
+
@dbh["pg_native_binding"] = false
|
86
|
+
@sth = @dbh.prepare("select * from names where age IS NOT ? order by age")
|
87
|
+
@sth.execute("NULL")
|
88
|
+
assert_equal(
|
89
|
+
[
|
90
|
+
["Joe", 19],
|
91
|
+
["Bob", 21],
|
92
|
+
["Jim", 30],
|
93
|
+
],
|
94
|
+
@sth.fetch_all
|
95
|
+
)
|
96
|
+
|
97
|
+
@sth.finish
|
98
|
+
|
99
|
+
@sth = @dbh.prepare("select * from names where age = ?")
|
100
|
+
@sth.execute(19)
|
101
|
+
assert_equal(
|
102
|
+
[
|
103
|
+
["Joe", 19]
|
104
|
+
],
|
105
|
+
@sth.fetch_all
|
106
|
+
)
|
107
|
+
|
108
|
+
@sth.finish
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_function_multiple_return_values
|
113
|
+
@sth = @dbh.prepare("SELECT age, select_subproperty(age, NULL), select_subproperty(age, 1) FROM names WHERE age = 19")
|
114
|
+
@sth.execute
|
115
|
+
assert_equal([[19, nil, 19]], @sth.fetch_all)
|
116
|
+
@sth.finish
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_columns
|
120
|
+
assert_equal(
|
121
|
+
[
|
122
|
+
{
|
123
|
+
:name =>"age",
|
124
|
+
:default =>nil,
|
125
|
+
:primary =>nil,
|
126
|
+
:scale =>nil,
|
127
|
+
:sql_type =>4,
|
128
|
+
:nullable =>true,
|
129
|
+
:indexed =>false,
|
130
|
+
:precision =>4,
|
131
|
+
:type_name =>"integer",
|
132
|
+
:unique =>nil,
|
133
|
+
:array_of_type =>nil
|
134
|
+
},
|
135
|
+
{
|
136
|
+
:name =>"name",
|
137
|
+
:default =>nil,
|
138
|
+
:primary =>nil,
|
139
|
+
:scale =>nil,
|
140
|
+
:sql_type =>12,
|
141
|
+
:nullable =>true,
|
142
|
+
:indexed =>false,
|
143
|
+
:precision =>255,
|
144
|
+
:type_name =>"character varying",
|
145
|
+
:unique =>nil,
|
146
|
+
:array_of_type =>nil
|
147
|
+
}
|
148
|
+
], @dbh.columns("names").sort_by { |x| x["name"] })
|
149
|
+
|
150
|
+
assert_equal(2, @dbh.columns("names").size) # make sure this works before the search path change
|
151
|
+
|
152
|
+
assert_equal(0, @dbh.columns("tbl").size) # tbl doesn't exist in public
|
153
|
+
|
154
|
+
@dbh.do('SET search_path TO schema1,schema2,"$user",public')
|
155
|
+
|
156
|
+
assert_equal(1, @dbh.columns('tbl').size);
|
157
|
+
assert_equal(
|
158
|
+
[
|
159
|
+
{
|
160
|
+
:name =>"foo",
|
161
|
+
:default =>nil,
|
162
|
+
:primary =>nil,
|
163
|
+
:scale =>nil,
|
164
|
+
:sql_type =>4,
|
165
|
+
:nullable =>true,
|
166
|
+
:indexed =>false,
|
167
|
+
:precision =>4,
|
168
|
+
:type_name =>"integer",
|
169
|
+
:unique =>nil,
|
170
|
+
:array_of_type =>nil
|
171
|
+
|
172
|
+
}
|
173
|
+
],
|
174
|
+
@dbh.columns('tbl')
|
175
|
+
)
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_statement_name_uniqueness
|
180
|
+
5000.times do
|
181
|
+
begin
|
182
|
+
@dbh.prepare('SELECT 1').execute()
|
183
|
+
raise DBI::ForcedError
|
184
|
+
sth.finish # never reached
|
185
|
+
rescue DBI::ProgrammingError => e
|
186
|
+
# ERROR: prepared statement "ruby-dbi:Pg:-604926268" already exists
|
187
|
+
# This should never happen
|
188
|
+
raise e
|
189
|
+
rescue DBI::ForcedError
|
190
|
+
# no-op
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_connect_errors
|
196
|
+
dbd = nil
|
197
|
+
ex = assert_raises(DBI::OperationalError) {
|
198
|
+
dbd = DBI::DBD::Pg::Database.new('rubytest:1234', 'jim', nil, {})
|
199
|
+
}
|
200
|
+
ex = assert_raises(DBI::OperationalError) {
|
201
|
+
dbd = DBI::DBD::Pg::Database.new('bad_db_name', 'jim', nil, {})
|
202
|
+
}
|
203
|
+
|
204
|
+
# this corresponds to the test_parse_url_expected_errors test in tc_dbi.rb
|
205
|
+
assert_raises(DBI::InterfaceError) do
|
206
|
+
DBI.connect("dbi:Pg").disconnect
|
207
|
+
end
|
208
|
+
|
209
|
+
ensure
|
210
|
+
dbd.disconnect if dbd
|
211
|
+
end
|
212
|
+
|
213
|
+
def skip_test_type_map
|
214
|
+
dbd = get_dbd
|
215
|
+
def dbd.type_map
|
216
|
+
@type_map
|
217
|
+
end
|
218
|
+
assert dbd.type_map
|
219
|
+
assert_equal 21, dbd.convert("21", 23)
|
220
|
+
assert_equal "21", dbd.convert("21", 1043)
|
221
|
+
assert_equal 21.5, dbd.convert("21.5", 701)
|
222
|
+
end
|
223
|
+
|
224
|
+
def test_simple_command
|
225
|
+
dbd = get_dbd
|
226
|
+
res = dbd.do("INSERT INTO names (name, age) VALUES('Dan', 16)")
|
227
|
+
assert_equal 1, res
|
228
|
+
|
229
|
+
@sth = get_dbi.prepare("SELECT name FROM names WHERE age=16")
|
230
|
+
@sth.execute
|
231
|
+
assert @sth.fetchable?
|
232
|
+
# XXX FIXME This is a bug in the DBD. #rows should equal 1 for select statements.
|
233
|
+
assert_equal 0, @sth.rows
|
234
|
+
ensure
|
235
|
+
dbd.do("DELETE FROM names WHERE age < 20")
|
236
|
+
dbd.disconnect if dbd
|
237
|
+
end
|
238
|
+
|
239
|
+
def test_bad_command
|
240
|
+
dbd = get_dbd
|
241
|
+
assert_raises(DBI::ProgrammingError) {
|
242
|
+
dbd.do("INSERT INTO bad_table (name, age) VALUES('Dave', 12)")
|
243
|
+
}
|
244
|
+
ensure
|
245
|
+
dbd.disconnect if dbd
|
246
|
+
end
|
247
|
+
|
248
|
+
def test_query_single
|
249
|
+
dbd = get_dbi
|
250
|
+
res = dbd.prepare("SELECT name, age FROM names WHERE age=21;")
|
251
|
+
assert res
|
252
|
+
res.execute
|
253
|
+
fields = res.column_info
|
254
|
+
assert_equal 2, fields.length
|
255
|
+
assert_equal 'name', fields[0]['name']
|
256
|
+
assert_equal 'varchar', fields[0]['type_name']
|
257
|
+
assert_equal 'age', fields[1]['name']
|
258
|
+
assert_equal 'int4', fields[1]['type_name']
|
259
|
+
|
260
|
+
row = res.fetch
|
261
|
+
|
262
|
+
assert_equal 'Bob', row[0]
|
263
|
+
assert_equal 21, row[1]
|
264
|
+
|
265
|
+
row = res.fetch
|
266
|
+
assert_nil row
|
267
|
+
|
268
|
+
res.finish
|
269
|
+
ensure
|
270
|
+
dbd.disconnect if dbd
|
271
|
+
end
|
272
|
+
|
273
|
+
def test_query_multi
|
274
|
+
dbd = get_dbd
|
275
|
+
res = dbd.prepare("SELECT name, age FROM names WHERE age > 20;")
|
276
|
+
|
277
|
+
expected_list = ['Jim', 'Bob', 'Charlie']
|
278
|
+
res.execute
|
279
|
+
while row=res.fetch
|
280
|
+
expected = expected_list.shift
|
281
|
+
assert_equal expected, row[0]
|
282
|
+
end
|
283
|
+
|
284
|
+
res.finish
|
285
|
+
ensure
|
286
|
+
dbd.disconnect if dbd
|
287
|
+
end
|
288
|
+
|
289
|
+
def test_tables_call
|
290
|
+
# per bug #1082, views do not show up in tables listing.
|
291
|
+
assert get_dbi.tables.include?("view_names")
|
292
|
+
end
|
293
|
+
|
294
|
+
def get_dbi
|
295
|
+
config = DBDConfig.get_config
|
296
|
+
DBI.connect("dbi:Pg:#{config['postgresql']['dbname']}", config['postgresql']['username'], config['postgresql']['password'])
|
297
|
+
end
|
298
|
+
|
299
|
+
def get_dbd
|
300
|
+
config = DBDConfig.get_config['postgresql']
|
301
|
+
result = DBI::DBD::Pg::Database.new(config['dbname'], config['username'], config['password'], {})
|
302
|
+
result['AutoCommit'] = true
|
303
|
+
result
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# --------------------------------------------------------------------
|