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.
@@ -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