dbd-odbc 0.2.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.
@@ -0,0 +1,38 @@
1
+ #
2
+ # See DBI::BaseDriver
3
+ #
4
+ class DBI::DBD::ODBC::Driver < DBI::BaseDriver
5
+ def initialize
6
+ super("0.4.0")
7
+ end
8
+
9
+ def data_sources
10
+ ::ODBC.datasources.collect {|dsn| "dbi:ODBC:" + dsn.name }
11
+ rescue DBI::DBD::ODBC::ODBCErr => err
12
+ raise DBI::DatabaseError.new(err.message)
13
+ end
14
+
15
+ def connect(dbname, user, auth, attr)
16
+ driver_attrs = dbname.split(';')
17
+
18
+ if driver_attrs.size > 1
19
+ # DNS-less connection
20
+ drv = ::ODBC::Driver.new
21
+ drv.name = 'Driver1'
22
+ driver_attrs.each do |param|
23
+ pv = param.split('=')
24
+ next if pv.size < 2
25
+ drv.attrs[pv[0]] = pv[1]
26
+ end
27
+ db = ::ODBC::Database.new
28
+ handle = db.drvconnect(drv)
29
+ else
30
+ # DNS given
31
+ handle = ::ODBC.connect(dbname, user, auth)
32
+ end
33
+
34
+ return DBI::DBD::ODBC::Database.new(handle, attr)
35
+ rescue ODBCErr => err
36
+ raise DBI::DatabaseError.new(err.message)
37
+ end
38
+ end
@@ -0,0 +1,131 @@
1
+ #
2
+ # See DBI::BaseStatement.
3
+ #
4
+ class DBI::DBD::ODBC::Statement < DBI::BaseStatement
5
+ def initialize(handle, statement)
6
+ @statement = statement
7
+ @handle = handle
8
+ @params = []
9
+ @arr = []
10
+ end
11
+
12
+ #
13
+ # See DBI::BaseStatement#bind_param. This method will also raise
14
+ # DBI::InterfaceError if +param+ is not a Fixnum, to prevent incorrect
15
+ # binding.
16
+ #
17
+ def bind_param(param, value, attribs)
18
+ raise DBI::InterfaceError, "only ? parameters supported" unless param.is_a? Fixnum
19
+ @params[param-1] = value
20
+ end
21
+
22
+ def execute
23
+ @handle.execute(*@params)
24
+ rescue DBI::DBD::ODBC::ODBCErr => err
25
+ raise DBI::DatabaseError.new(err.message)
26
+ end
27
+
28
+ def finish
29
+ @handle.drop
30
+ rescue DBI::DBD::ODBC::ODBCErr => err
31
+ raise DBI::DatabaseError.new(err.message)
32
+ end
33
+
34
+ def cancel
35
+ @handle.cancel
36
+ rescue DBI::DBD::ODBC::ODBCErr => err
37
+ raise DBI::DatabaseError.new(err.message)
38
+ end
39
+
40
+ def fetch
41
+ convert_row(@handle.fetch)
42
+ rescue DBI::DBD::ODBC::ODBCErr => err
43
+ raise DBI::DatabaseError.new(err.message)
44
+ end
45
+
46
+ #
47
+ # See DBI::BaseStatement#fetch_scroll.
48
+ #
49
+ # ODBC has a native version of this method and the constnats in the ODBC
50
+ # driver themselves are supported. If you'd prefer to use DBI constants
51
+ # (recommended), you can use these which map to the ODBC functionality:
52
+ #
53
+ # * DBI::SQL_FETCH_FIRST
54
+ # * DBI::SQL_FETCH_LAST
55
+ # * DBI::SQL_FETCH_NEXT
56
+ # * DBI::SQL_FETCH_PRIOR
57
+ # * DBI::SQL_FETCH_ABSOLUTE
58
+ # * DBI::SQL_FETCH_RELATIVE
59
+ #
60
+ def fetch_scroll(direction, offset)
61
+ direction = case direction
62
+ when DBI::SQL_FETCH_FIRST then ::ODBC::SQL_FETCH_FIRST
63
+ when DBI::SQL_FETCH_LAST then ::ODBC::SQL_FETCH_LAST
64
+ when DBI::SQL_FETCH_NEXT then ::ODBC::SQL_FETCH_NEXT
65
+ when DBI::SQL_FETCH_PRIOR then ::ODBC::SQL_FETCH_PRIOR
66
+ when DBI::SQL_FETCH_ABSOLUTE then ::ODBC::SQL_FETCH_ABSOLUTE
67
+ when DBI::SQL_FETCH_RELATIVE then ::ODBC::SQL_FETCH_RELATIVE
68
+ else
69
+ direction
70
+ end
71
+
72
+ convert_row(@handle.fetch_scroll(direction, offset))
73
+ rescue DBI::DBD::ODBC::ODBCErr => err
74
+ raise DBI::DatabaseError.new(err.message)
75
+ end
76
+
77
+ #
78
+ # See DBI::BaseStatement#column_info. These additional attributes are also
79
+ # supported:
80
+ #
81
+ # * table: the table this column came from, if available.
82
+ # * nullable: boolean, true if NULL is accepted as a value in this column.
83
+ # * searchable: FIXME DOCUMENT
84
+ # * length: FIXME DOCUMENT
85
+ # * unsigned: For numeric columns, whether or not the result value is signed.
86
+ #
87
+ def column_info
88
+ info = []
89
+ @handle.columns(true).each do |col|
90
+ info << {
91
+ 'name' => col.name,
92
+ 'table' => col.table,
93
+ 'nullable' => col.nullable,
94
+ 'searchable' => col.searchable,
95
+ 'precision' => col.precision,
96
+ 'scale' => col.scale,
97
+ 'sql_type' => col.type,
98
+ 'type_name' => DBI::SQL_TYPE_NAMES[col.type],
99
+ 'length' => col.length,
100
+ 'unsigned' => col.unsigned
101
+ }
102
+ end
103
+ info
104
+ rescue DBI::DBD::ODBC::ODBCErr => err
105
+ raise DBI::DatabaseError.new(err.message)
106
+ end
107
+
108
+ #
109
+ # See DBI::BaseStatement#rows.
110
+ #
111
+ # For queries which DBI::SQL.query? returns true, will explicitly return 0.
112
+ # Otherwise, it will return the row processed count.
113
+ #
114
+ def rows
115
+ return 0 if DBI::SQL.query?(@statement)
116
+ return @handle.nrows
117
+ rescue DBI::DBD::ODBC::ODBCErr => err
118
+ raise DBI::DatabaseError.new(err.message)
119
+ end
120
+
121
+ private # -----------------------------------
122
+
123
+ # convert the ODBC datatypes to DBI datatypes
124
+ def convert_row(row)
125
+ return nil if row.nil?
126
+ row.collect do |col|
127
+ col = col.to_s unless col.nil?
128
+ col
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,48 @@
1
+ ================================================================================
2
+ Using DBD tests
3
+ ================================================================================
4
+
5
+ Create a YAML file named .ruby-dbi.test-config.yaml in your home directory.
6
+
7
+ This file is a hash of keys that determine what you want to test and how you
8
+ access the databases related to those tests.
9
+
10
+ The key 'dbtypes' is an array which determines what tests you want to run. They
11
+ *do not* correspond to the driver names, they correspond to the test
12
+ directories that were made for them.
13
+
14
+ Each 'dbtype' has a key that contains a hash of values:
15
+ - username: the username of your account
16
+ - password: the password for your account
17
+ - dbname: the name of the database to connect to
18
+
19
+ NOTE that tests expect to connect to a database on localhost currently. This
20
+ may be fixed in the future, especially when we start writing Oracle and
21
+ SQLServer tests.
22
+
23
+ Each DBD test relies on database semantics which may not match up entirely with
24
+ this configuration. For instance, the postgresql tests expect you to be able to
25
+ work with the database directly via the `psql' client. This is something which
26
+ will eventually be remedied as time and ability allows.
27
+
28
+ Here is a sample configuration to get you started with the postgresql tests:
29
+
30
+ ################################################################################
31
+
32
+ ---
33
+ dbtypes:
34
+ - postgresql
35
+ postgresql:
36
+ username: erikh
37
+ password: monkeys
38
+ dbname: rubytest
39
+
40
+ ################################################################################
41
+
42
+ NOTE the --- is part of the file and is not a separator.
43
+
44
+ ================================================================================
45
+ Writing DBD tests
46
+ ================================================================================
47
+
48
+ Coming soon.
@@ -0,0 +1,157 @@
1
+ @class = Class.new(DBDConfig.testbase(DBDConfig.current_dbtype)) do
2
+ def test_ping
3
+ assert @dbh.ping
4
+ # XXX if it isn't obvious, this should be tested better. Not sure what
5
+ # good behavior is yet.
6
+ end
7
+
8
+ def test_columns
9
+ assert_nothing_raised do
10
+ cols = @dbh.columns("precision_test")
11
+
12
+ assert(cols)
13
+ assert_kind_of(Array, cols)
14
+ assert_equal(2, cols.length)
15
+
16
+ # the first column should always be "text_field" and have the following
17
+ # properties:
18
+ assert_equal("text_field", cols[0]["name"])
19
+ assert(!cols[0]["nullable"])
20
+
21
+ assert_equal(20, cols[0]["precision"])
22
+ # scale can be either nil or 0 for character types.
23
+ case cols[0]["scale"]
24
+ when nil
25
+ assert_equal(nil, cols[0]["scale"])
26
+ when 0
27
+ assert_equal(0, cols[0]["scale"])
28
+ else
29
+ flunk "scale can be either 0 or nil for character types"
30
+ end
31
+
32
+ assert_equal(
33
+ DBI::Type::Varchar,
34
+ DBI::TypeUtil.type_name_to_module(cols[0]["type_name"])
35
+ )
36
+
37
+ # the second column should always be "integer_field" and have the following
38
+ # properties:
39
+ assert_equal("integer_field", cols[1]["name"])
40
+ assert(cols[1]["nullable"])
41
+ assert_equal(1, cols[1]["scale"])
42
+ assert_equal(2, cols[1]["precision"])
43
+ assert_equal(
44
+ DBI::Type::Decimal,
45
+ DBI::TypeUtil.type_name_to_module(cols[1]["type_name"])
46
+ )
47
+
48
+ # finally, we ensure that every column in the array is a ColumnInfo
49
+ # object
50
+ cols.each { |col| assert_kind_of(DBI::ColumnInfo, col) }
51
+ end
52
+ end
53
+
54
+ def test_prepare
55
+ @sth = @dbh.prepare('select * from names')
56
+
57
+ assert @sth
58
+ assert_kind_of DBI::StatementHandle, @sth
59
+
60
+ @sth.finish
61
+ end
62
+
63
+ def test_do
64
+ assert_equal 1, @dbh.do("insert into names (name, age) values (?, ?)", "Billy", 21)
65
+ @sth = @dbh.prepare("select * from names where name = ?")
66
+ @sth.execute("Billy")
67
+ assert_equal ["Billy", 21], @sth.fetch
68
+ @sth.finish
69
+ end
70
+
71
+ def test_tables
72
+ tables = @dbh.tables.sort
73
+
74
+ # since this is a general test, let's prune the system tables
75
+ # FIXME not so sure if this should be a general test anymore.
76
+ if dbtype == "odbc"
77
+ tables -= [
78
+ "administrable_role_authorizations",
79
+ "applicable_roles",
80
+ "attributes",
81
+ "check_constraint_routine_usage",
82
+ "check_constraints",
83
+ "column_domain_usage",
84
+ "column_privileges",
85
+ "column_udt_usage",
86
+ "columns",
87
+ "constraint_column_usage",
88
+ "constraint_table_usage",
89
+ "data_type_privileges",
90
+ "domain_constraints",
91
+ "domain_udt_usage",
92
+ "domains",
93
+ "element_types",
94
+ "enabled_roles",
95
+ "information_schema_catalog_name",
96
+ "key_column_usage",
97
+ "parameters",
98
+ "referential_constraints",
99
+ "role_column_grants",
100
+ "role_routine_grants",
101
+ "role_table_grants",
102
+ "role_usage_grants",
103
+ "routine_privileges",
104
+ "routines",
105
+ "schemata",
106
+ "sequences",
107
+ "sql_features",
108
+ "sql_implementation_info",
109
+ "sql_languages",
110
+ "sql_packages",
111
+ "sql_parts",
112
+ "sql_sizing",
113
+ "sql_sizing_profiles",
114
+ "table_constraints",
115
+ "table_privileges",
116
+ "tables",
117
+ "triggered_update_columns",
118
+ "triggers",
119
+ "usage_privileges",
120
+ "view_column_usage",
121
+ "view_routine_usage",
122
+ "view_table_usage",
123
+ "views"
124
+ ]
125
+ end
126
+
127
+ case dbtype
128
+ when "postgresql"
129
+ tables.reject! { |x| x =~ /^pg_/ }
130
+ assert_equal %w(array_test bit_test blob_test boolean_test bytea_test field_types_test names precision_test time_test timestamp_test view_names), tables
131
+ else
132
+ assert_equal %w(bit_test blob_test boolean_test field_types_test names precision_test time_test timestamp_test view_names), tables
133
+ end
134
+ end
135
+
136
+ def test_attrs
137
+ # test defaults
138
+ assert @dbh["AutoCommit"] # should be true
139
+
140
+ # test setting
141
+ assert !(@dbh["AutoCommit"] = false)
142
+ assert !@dbh["AutoCommit"]
143
+
144
+ # test committing an outstanding transaction
145
+
146
+ @sth = @dbh.prepare("insert into names (name, age) values (?, ?)")
147
+ @sth.execute("Billy", 22)
148
+ @sth.finish
149
+
150
+ assert @dbh["AutoCommit"] = true # should commit at this point
151
+
152
+ @sth = @dbh.prepare("select * from names where name = ?")
153
+ @sth.execute("Billy")
154
+ assert_equal [ "Billy", 22 ], @sth.fetch
155
+ @sth.finish
156
+ end
157
+ end
@@ -0,0 +1,240 @@
1
+ @class = Class.new(DBDConfig.testbase(DBDConfig.current_dbtype)) do
2
+ def test_quoting # FIXME breaks sqlite-ruby to a segfault - research
3
+ @sth = nil
4
+
5
+ assert_nothing_raised do
6
+ if dbtype == "postgresql"
7
+ @sth = @dbh.prepare('select E\'\\\\\'')
8
+ else
9
+ @sth = @dbh.prepare('select \'\\\\\'')
10
+ end
11
+ @sth.execute
12
+ row = @sth.fetch
13
+ assert_equal ['\\'], row
14
+ @sth.finish
15
+ end
16
+ end
17
+
18
+ def test_duplicate_columns
19
+ assert_nothing_raised do
20
+ @sth = @dbh.prepare("select name, name from names where name = ?")
21
+ @sth.execute("Bob")
22
+ assert_equal [["Bob", "Bob"]], @sth.fetch_all
23
+ @sth.finish
24
+ end
25
+ end
26
+
27
+ def test_columninfo
28
+ @sth = nil
29
+
30
+ assert_nothing_raised do
31
+ @sth = @dbh.prepare("select * from precision_test")
32
+ @sth.execute
33
+
34
+ cols = @sth.column_info
35
+
36
+ assert(cols)
37
+ assert_kind_of(Array, cols)
38
+ assert_equal(2, cols.length)
39
+
40
+ # the first column should always be "text_field" and have the following
41
+ # properties:
42
+ assert_equal("text_field", cols[0]["name"])
43
+ assert_equal(20, cols[0]["precision"])
44
+ # scale can be either nil or 0 for character types.
45
+ case cols[0]["scale"]
46
+ when nil
47
+ assert_equal(nil, cols[0]["scale"])
48
+ when 0
49
+ assert_equal(0, cols[0]["scale"])
50
+ else
51
+ flunk "scale can be either 0 or nil for character types"
52
+ end
53
+
54
+ assert_equal(
55
+ DBI::Type::Varchar,
56
+ DBI::TypeUtil.type_name_to_module(cols[0]["type_name"])
57
+ )
58
+
59
+ # the second column should always be "integer_field" and have the following
60
+ # properties:
61
+ assert_equal("integer_field", cols[1]["name"])
62
+ assert_equal(1, cols[1]["scale"])
63
+ assert_equal(2, cols[1]["precision"])
64
+ assert_equal(
65
+ DBI::Type::Decimal,
66
+ DBI::TypeUtil.type_name_to_module(cols[1]["type_name"])
67
+ )
68
+
69
+ cols.each { |col| assert_kind_of(DBI::ColumnInfo, col) }
70
+ @sth.finish
71
+ end
72
+ end
73
+
74
+ def test_duplicate_columns
75
+ assert_nothing_raised do
76
+ @sth = @dbh.prepare("select name, name from names where name = ?")
77
+ @sth.execute("Bob")
78
+ assert_equal [["Bob", "Bob"]], @sth.fetch_all
79
+ @sth.finish
80
+ end
81
+ end
82
+
83
+ def test_rows
84
+ @sth = nil
85
+
86
+ assert_nothing_raised do
87
+ @sth = @dbh.prepare("insert into names (name, age) values (?, ?)")
88
+ @sth.execute("Bill", 22);
89
+ end
90
+
91
+ assert 1, @sth.rows
92
+
93
+ @sth.finish
94
+ @sth = nil
95
+
96
+ assert_nothing_raised do
97
+ @sth = @dbh.prepare("delete from names where name = ?")
98
+ @sth.execute("Bill");
99
+ end
100
+
101
+ assert 1, @sth.rows
102
+
103
+ @sth.finish
104
+
105
+ assert_nothing_raised do
106
+ @sth = @dbh.prepare("select * from names")
107
+ @sth.execute
108
+ end
109
+
110
+ assert_equal 0, @sth.rows
111
+ assert @sth.fetchable?
112
+ assert @sth.any?
113
+ assert @sth.rows.zero?
114
+ @sth.finish
115
+ end
116
+
117
+ def test_execute
118
+ assert_nothing_raised do
119
+ @sth = @dbh.prepare("select * from names")
120
+ @sth.execute
121
+ @sth.finish
122
+ end
123
+
124
+ assert_nothing_raised do
125
+ @sth = @dbh.prepare("select * from names where name = ?")
126
+ @sth.execute("Bob")
127
+ @sth.finish
128
+ end
129
+
130
+ assert_nothing_raised do
131
+ @sth = @dbh.prepare("insert into names (name, age) values (?, ?)")
132
+ @sth.execute("Bill", 22);
133
+ @sth.finish
134
+ end
135
+ end
136
+
137
+ def test_execute_with_transactions
138
+ @dbh["AutoCommit"] = false
139
+ config = DBDConfig.get_config['sqlite3']
140
+
141
+ # rollback 1 (the right way)
142
+ @sth = nil
143
+ @sth2 = nil
144
+
145
+ assert_nothing_raised do
146
+ @sth = @dbh.prepare("insert into names (name, age) values (?, ?)")
147
+ @sth.execute("Billy", 23)
148
+ @sth2 = @dbh.prepare("select * from names where name = ?")
149
+ @sth2.execute("Billy")
150
+ end
151
+ assert_equal ["Billy", 23 ], @sth2.fetch
152
+ @sth2.finish
153
+ @sth.finish
154
+ assert_nothing_raised { @dbh.rollback }
155
+
156
+ @sth = @dbh.prepare("select * from names where name = ?")
157
+ @sth.execute("Billy")
158
+ assert_nil @sth.fetch
159
+ @sth.finish
160
+
161
+ # rollback 2 (without closing statements first)
162
+
163
+ @sth = nil
164
+ @sth2 = nil
165
+
166
+ assert_nothing_raised do
167
+ @sth = @dbh.prepare("insert into names (name, age) values (?, ?)")
168
+ @sth.execute("Billy", 23)
169
+ @sth2 = @dbh.prepare("select * from names where name = ?")
170
+ @sth2.execute("Billy")
171
+ end
172
+
173
+ assert_equal ["Billy", 23], @sth2.fetch
174
+
175
+ # FIXME some throw here, some don't. we should probably normalize this
176
+ @dbh.rollback rescue true
177
+
178
+ @sth2.finish
179
+ @sth.finish
180
+ assert_nothing_raised { @dbh.rollback }
181
+
182
+ @sth = @dbh.prepare("select * from names where name = ?")
183
+ @sth.execute("Billy")
184
+ assert_nil @sth.fetch
185
+ @sth.finish
186
+
187
+ # commit
188
+
189
+ @sth = nil
190
+ @sth2 = nil
191
+
192
+ assert_nothing_raised do
193
+ @sth = @dbh.prepare("insert into names (name, age) values (?, ?)")
194
+ @sth.execute("Billy", 23)
195
+ @sth2 = @dbh.prepare("select * from names where name = ?")
196
+ @sth2.execute("Billy")
197
+ end
198
+ assert_equal ["Billy", 23 ], @sth2.fetch
199
+ @sth2.finish
200
+ @sth.finish
201
+ assert_nothing_raised { @dbh.commit }
202
+
203
+ @sth = @dbh.prepare("select * from names where name = ?")
204
+ @sth.execute("Billy")
205
+ assert_equal ["Billy", 23 ], @sth.fetch
206
+ @sth.finish
207
+ end
208
+
209
+ def test_fetch
210
+ @sth = nil
211
+ assert_nothing_raised do
212
+ @sth = @dbh.prepare("select * from names order by age")
213
+ @sth.execute
214
+ end
215
+
216
+ # this tests that we're getting the rows in the right order,
217
+ # and that the types are being converted.
218
+ assert_equal ["Joe", 19], @sth.fetch
219
+ assert_equal ["Bob", 21], @sth.fetch
220
+ assert_equal ["Jim", 30], @sth.fetch
221
+ assert_nil @sth.fetch
222
+
223
+ @sth.finish
224
+ end
225
+
226
+ def test_transaction_block
227
+ @dbh["AutoCommit"] = false
228
+ # this transaction should not fail because it called return early
229
+ @dbh.transaction do |dbh|
230
+ dbh.do('INSERT INTO names (name, age) VALUES (?, ?)', "Cooter", 69)
231
+ return 42
232
+ end
233
+ @sth = @dbh.prepare("select * from names where name = ?")
234
+ @sth.execute("Cooter")
235
+ row = @sth.fetch
236
+ assert row
237
+ assert_equal ["Cooter", 69], row
238
+ @sth.finish
239
+ end
240
+ end