dbd-mysql 0.3.3
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 +3694 -0
- data/LICENSE +25 -0
- data/README +271 -0
- data/lib/dbd/Mysql.rb +111 -0
- data/lib/dbd/mysql/database.rb +391 -0
- data/lib/dbd/mysql/driver.rb +125 -0
- data/lib/dbd/mysql/statement.rb +186 -0
- data/test/DBD_TESTS +48 -0
- data/test/dbd/general/test_database.rb +157 -0
- data/test/dbd/general/test_statement.rb +240 -0
- data/test/dbd/general/test_types.rb +253 -0
- data/test/dbd/mysql/base.rb +26 -0
- data/test/dbd/mysql/down.sql +17 -0
- data/test/dbd/mysql/test_blob.rb +18 -0
- data/test/dbd/mysql/test_patches.rb +66 -0
- data/test/dbd/mysql/up.sql +26 -0
- data/test/ts_dbd.rb +118 -0
- metadata +90 -0
@@ -0,0 +1,125 @@
|
|
1
|
+
module DBI::DBD::Mysql
|
2
|
+
#
|
3
|
+
# Models the DBI::BaseDriver API to create DBI::DriverHandle objects.
|
4
|
+
#
|
5
|
+
class Driver < DBI::BaseDriver
|
6
|
+
include Util
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super("0.4.0")
|
10
|
+
end
|
11
|
+
|
12
|
+
def default_user
|
13
|
+
['', nil]
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# Parameters in the dbname as follows:
|
18
|
+
#
|
19
|
+
# * host: host to connect to
|
20
|
+
# * port: port to connect to
|
21
|
+
# * socket: connect to a specific unix socket instead of a TCP socket.
|
22
|
+
# * flag: an OR'd collection of flags to pass to the lower-level
|
23
|
+
# connection attempt.
|
24
|
+
# * mysql_read_default_file: FIXME
|
25
|
+
# * mysql_read_default_group: FIXME
|
26
|
+
# * mysql_compression: FIXME
|
27
|
+
# * mysql_local_infile: FIXME
|
28
|
+
# * mysql_client_found_rows: FIXME boolean, modifies the 'flag'
|
29
|
+
# setting above.
|
30
|
+
def connect(dbname, user, auth, attr)
|
31
|
+
# connect to database server
|
32
|
+
hash = DBI::Utils.parse_params(dbname)
|
33
|
+
|
34
|
+
hash['host'] ||= 'localhost'
|
35
|
+
|
36
|
+
# these two connection parameters should be passed as numbers
|
37
|
+
hash['port'] = hash['port'].to_i unless hash['port'].nil?
|
38
|
+
hash['flag'] = hash['flag'].nil? ? 0 : hash['flag'] = hash['flag'].to_i
|
39
|
+
|
40
|
+
handle = ::Mysql.init
|
41
|
+
|
42
|
+
# Look for options in connect string to be handled
|
43
|
+
# through mysql_options() before connecting
|
44
|
+
!hash['mysql_read_default_file'].nil? and
|
45
|
+
handle.options(::Mysql::READ_DEFAULT_FILE,
|
46
|
+
hash['mysql_read_default_file'])
|
47
|
+
!hash['mysql_read_default_group'].nil? and
|
48
|
+
handle.options(::Mysql::READ_DEFAULT_GROUP,
|
49
|
+
hash['mysql_read_default_group'])
|
50
|
+
# The following options can be handled either using mysql_options()
|
51
|
+
# or in the flag argument to connect().
|
52
|
+
hash['mysql_compression'].to_i != 0 and
|
53
|
+
handle.options(::Mysql::OPT_COMPRESS, nil)
|
54
|
+
hash['mysql_local_infile'].to_i != 0 and
|
55
|
+
handle.options(::Mysql::OPT_LOCAL_INFILE, true)
|
56
|
+
|
57
|
+
# Look for options to be handled in the flags argument to connect()
|
58
|
+
if !hash['mysql_client_found_rows'].nil?
|
59
|
+
if hash['mysql_client_found_rows'].to_i != 0
|
60
|
+
hash['flag'] |= ::Mysql::CLIENT_FOUND_ROWS
|
61
|
+
else
|
62
|
+
hash['flag'] &= ~::Mysql::CLIENT_FOUND_ROWS
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
handle.connect(hash['host'], user, auth, hash['database'], hash['port'], hash['socket'], hash['flag'])
|
67
|
+
|
68
|
+
return Database.new(handle, attr)
|
69
|
+
rescue MyError => err
|
70
|
+
error(err)
|
71
|
+
end
|
72
|
+
|
73
|
+
def data_sources
|
74
|
+
handle = ::Mysql.new
|
75
|
+
res = handle.list_dbs.collect {|db| "dbi:Mysql:database=#{db}" }
|
76
|
+
handle.close
|
77
|
+
return res
|
78
|
+
rescue MyError => err
|
79
|
+
error(err)
|
80
|
+
end
|
81
|
+
|
82
|
+
#--
|
83
|
+
# Driver-specific functions ------------------------------------------------
|
84
|
+
#++
|
85
|
+
|
86
|
+
public
|
87
|
+
|
88
|
+
def __createdb(db, host, user, password, port=nil, sock=nil, flag=nil)
|
89
|
+
handle = ::Mysql.connect(host, user, password, nil, port, sock, flag)
|
90
|
+
begin
|
91
|
+
handle.create_db(db)
|
92
|
+
ensure
|
93
|
+
handle.close if handle
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def __dropdb(db, host, user, password, port=nil, sock=nil, flag=nil)
|
98
|
+
handle = ::Mysql.connect(host, user, password, nil, port, sock, flag)
|
99
|
+
begin
|
100
|
+
handle.drop_db(db)
|
101
|
+
ensure
|
102
|
+
handle.close if handle
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def __shutdown(host, user, password, port=nil, sock=nil, flag=nil)
|
107
|
+
handle = ::Mysql.connect(host, user, password, nil, port, sock, flag)
|
108
|
+
begin
|
109
|
+
handle.shutdown
|
110
|
+
ensure
|
111
|
+
handle.close if handle
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def __reload(host, user, password, port=nil, sock=nil, flag=nil)
|
116
|
+
handle = ::Mysql.connect(host, user, password, nil, port, sock, flag)
|
117
|
+
begin
|
118
|
+
handle.reload
|
119
|
+
ensure
|
120
|
+
handle.close if handle
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end # class Driver
|
125
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
module DBI::DBD::Mysql
|
2
|
+
#
|
3
|
+
# Models the DBI::BaseStatement API to create DBI::StatementHandle objects.
|
4
|
+
#
|
5
|
+
class Statement < DBI::BaseStatement
|
6
|
+
include Util
|
7
|
+
|
8
|
+
def initialize(parent, handle, statement, mutex)
|
9
|
+
super(nil)
|
10
|
+
|
11
|
+
@parent, @handle, @mutex = parent, handle, mutex
|
12
|
+
@params = []
|
13
|
+
|
14
|
+
@prep_stmt = DBI::SQL::PreparedStatement.new(@parent, statement)
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# See DBI::BaseStatement#bind_param. This method will also raise
|
19
|
+
# DBI::InterfaceError if +param+ is not a Fixnum, to prevent incorrect
|
20
|
+
# binding.
|
21
|
+
#
|
22
|
+
def bind_param(param, value, attribs)
|
23
|
+
raise InterfaceError, "only ? parameters supported" unless param.is_a? Fixnum
|
24
|
+
@params[param-1] = value
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# See DBI::BaseStatement#execute. If DBI thinks this is a query via DBI::SQL.query?(),
|
29
|
+
# it will force the row processed count to 0. Otherwise, it will return
|
30
|
+
# what MySQL thinks is the row processed count.
|
31
|
+
#
|
32
|
+
def execute
|
33
|
+
sql = @prep_stmt.bind(@params)
|
34
|
+
@mutex.synchronize {
|
35
|
+
@handle.query_with_result = true
|
36
|
+
@res_handle = @handle.query(sql)
|
37
|
+
@column_info = self.column_info
|
38
|
+
@current_row = 0
|
39
|
+
@rows = DBI::SQL.query?(sql) ? 0 : @handle.affected_rows
|
40
|
+
}
|
41
|
+
rescue MyError => err
|
42
|
+
error(err)
|
43
|
+
end
|
44
|
+
|
45
|
+
def finish
|
46
|
+
@res_handle.free if @res_handle
|
47
|
+
rescue MyError => err
|
48
|
+
error(err)
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Helper method to aid #fetch. Do not call directly.
|
53
|
+
#
|
54
|
+
def fill_array(rowdata)
|
55
|
+
return nil if rowdata.nil?
|
56
|
+
return rowdata.dup
|
57
|
+
end
|
58
|
+
|
59
|
+
def fetch
|
60
|
+
@current_row += 1
|
61
|
+
fill_array(@res_handle.fetch_row)
|
62
|
+
rescue MyError => err
|
63
|
+
error(err)
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# See DBI::BaseStatement#fetch_scroll. These additional constants are also supported:
|
68
|
+
#
|
69
|
+
# * DBI::SQL_FETCH_PRIOR: Fetch the row previous to the current one.
|
70
|
+
# * DBI::SQL_FETCH_FIRST: Fetch the first row.
|
71
|
+
# * DBI::SQL_FETCH_ABSOLUTE: Fetch the row at the offset provided.
|
72
|
+
# * DBI::SQL_FETCH_RELATIVE: Fetch the row at the current point + offset.
|
73
|
+
#
|
74
|
+
def fetch_scroll(direction, offset)
|
75
|
+
case direction
|
76
|
+
when DBI::SQL_FETCH_NEXT
|
77
|
+
@current_row += 1
|
78
|
+
fill_array(@res_handle.fetch_row)
|
79
|
+
when DBI::SQL_FETCH_PRIOR
|
80
|
+
@res_handle.data_seek(@current_row - 1)
|
81
|
+
fill_array(@res_handle.fetch_row)
|
82
|
+
when DBI::SQL_FETCH_FIRST
|
83
|
+
@current_row = 1
|
84
|
+
@res_handle.data_seek(@current_row - 1)
|
85
|
+
fill_array(@res_handle.fetch_row)
|
86
|
+
when DBI::SQL_FETCH_LAST
|
87
|
+
@current_row = @res_handle.num_rows
|
88
|
+
@res_handle.data_seek(@current_row - 1)
|
89
|
+
fill_array(@res_handle.fetch_row)
|
90
|
+
when DBI::SQL_FETCH_ABSOLUTE
|
91
|
+
@current_row = offset + 1
|
92
|
+
@res_handle.data_seek(@current_row - 1)
|
93
|
+
fill_array(@res_handle.fetch_row)
|
94
|
+
when DBI::SQL_FETCH_RELATIVE
|
95
|
+
@current_row += offset + 1
|
96
|
+
@res_handle.data_seek(@current_row - 1)
|
97
|
+
fill_array(@res_handle.fetch_row)
|
98
|
+
else
|
99
|
+
raise NotSupportedError
|
100
|
+
end
|
101
|
+
#end
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# See DBI::BaseStatement#column_info, and DBI::DBD::Mysql::Database#columns.
|
106
|
+
#
|
107
|
+
# This method provides all the attributes the +columns+ method
|
108
|
+
# provides, and a few others:
|
109
|
+
#
|
110
|
+
# * mysql_type: These correspond to constants in the Mysql::Types
|
111
|
+
# package, in the lower-level 'mysql' package.
|
112
|
+
# * mysql_type_name: A text representation of +mysql_type+.
|
113
|
+
# * mysql_length: The length of the column.
|
114
|
+
# * mysql_max_length: The max length of the column. FIXME DESCRIBE
|
115
|
+
# DIFFERENCE
|
116
|
+
# * mysql_flags: Internal MySQL flags on this column.
|
117
|
+
#
|
118
|
+
def column_info
|
119
|
+
retval = []
|
120
|
+
|
121
|
+
return [] if @res_handle.nil?
|
122
|
+
|
123
|
+
unique_key_flag = MysqlField.const_get(:UNIQUE_KEY_FLAG)
|
124
|
+
multiple_key_flag = MysqlField.const_get(:MULTIPLE_KEY_FLAG)
|
125
|
+
indexed = (unique_key_flag | multiple_key_flag)
|
126
|
+
|
127
|
+
# Note: Cannot get 'default' column attribute because MysqlField.def
|
128
|
+
# is set only by mysql_list_fields()
|
129
|
+
|
130
|
+
@res_handle.fetch_fields.each {|col|
|
131
|
+
mysql_type_name = Database::TYPE_MAP[col.type][0] rescue nil
|
132
|
+
xopen_info = Database::MYSQL_to_XOPEN[mysql_type_name] ||
|
133
|
+
Database::MYSQL_to_XOPEN[nil]
|
134
|
+
sql_type = xopen_info[0]
|
135
|
+
type_name = DBI::SQL_TYPE_NAMES[sql_type]
|
136
|
+
|
137
|
+
retval << {
|
138
|
+
# Standard Ruby DBI column attributes
|
139
|
+
'name' => col.name,
|
140
|
+
'sql_type' => sql_type,
|
141
|
+
'type_name' => type_name,
|
142
|
+
# XXX it seems mysql counts the literal decimal point when weighing in the "length".
|
143
|
+
'precision' => col.decimals > 0 ? col.length - col.decimals - 1 : col.length,
|
144
|
+
'scale' => col.decimals,
|
145
|
+
'nullable' => !col.is_not_null?,
|
146
|
+
'indexed' => ((col.flags & indexed) != 0) ||
|
147
|
+
col.is_pri_key?,
|
148
|
+
'primary' => col.is_pri_key?,
|
149
|
+
'unique' => ((col.flags & unique_key_flag) != 0) ||
|
150
|
+
col.is_pri_key?,
|
151
|
+
# MySQL-specific attributes (signified by leading "mysql_")
|
152
|
+
'mysql_type' => col.type,
|
153
|
+
'mysql_type_name' => mysql_type_name,
|
154
|
+
'mysql_length' => col.length,
|
155
|
+
'mysql_max_length' => col.max_length,
|
156
|
+
'mysql_flags' => col.flags
|
157
|
+
}
|
158
|
+
|
159
|
+
if retval[-1]['sql_type'] == DBI::SQL_TINYINT and retval[-1]['precision'] == 1
|
160
|
+
retval[-1]['dbi_type'] = DBI::Type::Boolean
|
161
|
+
end
|
162
|
+
}
|
163
|
+
retval
|
164
|
+
rescue MyError => err
|
165
|
+
error(err)
|
166
|
+
end
|
167
|
+
|
168
|
+
def rows
|
169
|
+
@rows
|
170
|
+
end
|
171
|
+
|
172
|
+
# def []=(attr, value)
|
173
|
+
# case attr
|
174
|
+
# when 'mysql_use_result'
|
175
|
+
# @attr['mysql_store_result'] = ! value
|
176
|
+
# @attr['mysql_use_result'] = value
|
177
|
+
# when 'mysql_store_result'
|
178
|
+
# @attr['mysql_use_result'] = ! value
|
179
|
+
# @attr['mysql_store_result'] = value
|
180
|
+
# else
|
181
|
+
# raise NotSupportedError
|
182
|
+
# end
|
183
|
+
# end
|
184
|
+
|
185
|
+
end # class Statement
|
186
|
+
end
|
data/test/DBD_TESTS
ADDED
@@ -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
|