ffi-mysql 0.0.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.hgtags CHANGED
@@ -1 +1,2 @@
1
1
  afd66b4b7cda50c08a79f51574d46e49c4dc083b v0.0.1
2
+ 2bfcf2ddedd5d9589ecc0dc915d0393e218aa603 v0.0.2
data/History.markdown ADDED
@@ -0,0 +1,18 @@
1
+ 1.0.0 / 2010-04-21
2
+ ------------------
3
+
4
+ * fix Fixnum size bugs
5
+ * update to mysql-ruby-2.8.2 test-suite
6
+
7
+ 0.0.2 / 2010-03-08
8
+ ------------------
9
+
10
+ * support double and float fields
11
+ * improve performance of fetch_row
12
+
13
+
14
+ 0.0.1 / 2010-03-03
15
+ ------------------
16
+
17
+ * 1 major enhancement
18
+ * mostly compatible to MySQL/Ruby
@@ -2,45 +2,50 @@ ffi-mysql
2
2
  by Frank Fischer
3
3
  http://bitbucket.org/lyro/ffi-mysql
4
4
 
5
- == DESCRIPTION:
5
+ DESCRIPTION
6
+ -----------
6
7
 
7
8
  Pure Ruby FFI interface to MySQL.
8
9
 
9
- == FEATURES/PROBLEMS:
10
+ FEATURES/PROBLEMS
11
+ -----------------
10
12
 
11
- * satisfies almost complete test-suite of MySQL/Ruby
12
- * prepared statement does not handle Double fields
13
- correctly
13
+ * satisfies complete test-suite of MySQL/Ruby 2.8.1
14
14
 
15
- == SYNOPSIS:
15
+ SYNOPSIS
16
+ --------
16
17
 
17
18
  Use
18
19
 
19
- require 'ffi-mysql'
20
+ `require 'ffi-mysql'`
20
21
 
21
22
  instead of
22
23
 
23
- require 'mysql'
24
+ `require 'mysql'`
24
25
 
25
26
  ffi-mysql should be compatible with MySQL/Ruby, so have a look at
26
27
  * http://www.tmtm.org/en/mysql/ruby and
27
28
  * http://www.kitebird.com/articles/ruby-mysql.html
28
29
  for examples.
29
30
 
30
- == REQUIREMENTS:
31
+ REQUIREMENTS
32
+ ------------
31
33
 
32
34
  * ffi >= 0.6.2
33
35
 
34
- == INSTALL:
36
+ INSTALL
37
+ -------
35
38
 
36
- gem install ffi-ruby
39
+ `gem install ffi-ruby`
37
40
 
38
- == ACKNOWLEDGEMENTS:
41
+ ACKNOWLEDGEMENTS
42
+ ----------------
39
43
 
40
44
  Tomita Masahiro for his MySQL/Ruby and Ruby/MySQL gems from which some
41
45
  of the code has been stolen.
42
46
 
43
- == LICENSE:
47
+ LICENSE
48
+ -------
44
49
 
45
50
  (The MIT License)
46
51
 
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ ensure_in_path 'lib'
9
9
  require 'ffi-mysql/version'
10
10
 
11
11
  task :default => 'test:run'
12
- task 'gem:release' => 'test:run'
12
+ #task 'gem:release' => 'test:run'
13
13
 
14
14
  Bones {
15
15
  name 'ffi-mysql'
@@ -17,11 +17,12 @@ Bones {
17
17
  email 'frank.fischer@mathematik.tu-chemnitz.de'
18
18
  url 'http://bitbucket.org/lyro/ffi-mysql'
19
19
  ignore_file '.hgignore'
20
- readme_file 'README.rdoc'
21
- history_file 'History.rdoc'
20
+ readme_file 'README.markdown'
21
+ history_file 'History.markdown'
22
22
  version Mysql::VERSION
23
23
  gem.extras = { :has_rdoc => 'yard' }
24
- depend_on 'ffi', '>= 0.6.2'
24
+ yard.opts = %w(--no-private)
25
+ depend_on 'ffi', '~> 1.0'
25
26
  }
26
27
 
27
28
  # EOF
@@ -1,6 +1,6 @@
1
1
  class Mysql
2
2
  # Information about one column.
3
- class Field
3
+ class Field
4
4
  attr_reader :name
5
5
  attr_reader :org_name
6
6
  attr_reader :table
@@ -21,7 +21,7 @@ class Mysql
21
21
  attr_reader :decimals
22
22
  attr_reader :charsetnr
23
23
  attr_reader :type
24
-
24
+
25
25
  def initialize(c_field)
26
26
  @name = c_field[:name]
27
27
  @org_name = c_field[:org_name]
@@ -65,14 +65,14 @@ class Mysql
65
65
  # @return [Hash] this field as a hash
66
66
  def hash
67
67
  h = {}
68
- h['name'] = @name
69
- h['table'] = @table
70
- h['def'] = @def
71
- h['length'] = @length
72
- h['max_length'] = @max_length
73
- h['flags'] = @flags
74
- h['decimals'] = @decimals
75
- h['type'] = @type
68
+ h['name'] = @name
69
+ h['table'] = @table
70
+ h['def'] = @def
71
+ h['length'] = @length
72
+ h['max_length'] = @max_length
73
+ h['flags'] = @flags
74
+ h['decimals'] = @decimals
75
+ h['type'] = @type
76
76
  h
77
77
  end
78
78
 
@@ -3,13 +3,12 @@ require 'ffi'
3
3
  # Basic MySQL class, provides interface to a server.
4
4
  # @author Frank Fischer
5
5
  class Mysql
6
-
6
+
7
7
  # FFI interface.
8
8
  module C
9
9
  extend FFI::Library
10
- #ffi_lib ["mysqlclient", "libmysqlclient.so.15"]
11
10
  ffi_lib ["mysqlclient", "libmysqlclient.so.15", "libmysqlclient.so.16"]
12
-
11
+
13
12
  # FieldType = enum(:decimal, Mysql::Field::TYPE_DECIMAL,
14
13
  # :tiny, Mysql::Field::TYPE_TINY,
15
14
  # :short, Mysql::Field::TYPE_SHORT,
@@ -129,7 +128,7 @@ class Mysql
129
128
  # Creates a new MySQL connector and opens a connection.
130
129
  def self.new( host = nil, user = nil, passwd = nil, db = nil, port = 0, sock = nil, flag = 0)
131
130
  mysql = allocate
132
- mysql.send :initialize
131
+ mysql.send :initialize
133
132
  mysql.real_connect( host, user, passwd, db, port, sock, flag )
134
133
  mysql
135
134
  end
@@ -148,7 +147,7 @@ class Mysql
148
147
  def self.client_info
149
148
  C::mysql_get_client_info
150
149
  end
151
-
150
+
152
151
  # Escape special character in MySQL.
153
152
  # === Note
154
153
  # In Ruby 1.8, this is not safe for multibyte charset such as 'SJIS'.
@@ -164,7 +163,7 @@ class Mysql
164
163
  end
165
164
  end
166
165
  end
167
-
166
+
168
167
  class << self
169
168
  alias :real_connect :new
170
169
  alias :connect :new
@@ -173,7 +172,7 @@ class Mysql
173
172
  alias quote escape_string
174
173
  end
175
174
 
176
-
175
+
177
176
  # if true (the default), query return the first result
178
177
  attr_accessor :query_with_result
179
178
 
@@ -185,17 +184,18 @@ class Mysql
185
184
  @mysql = C::mysql_init( nil )
186
185
  @connected = false
187
186
  @query_with_result = true
187
+ @reconnect = false
188
188
  end
189
189
 
190
- # internal finalizer
190
+ # internal finalizer
191
191
  def self.finalizer(mysql, mysql_free)
192
192
  Proc.new do |*args|
193
193
  unless mysql_free[0]
194
- C::mysql_close(mysql)
194
+ C::mysql_close(mysql)
195
195
  end
196
196
  end
197
197
  end
198
-
198
+
199
199
  # Opens a new connection to a MySQL server.
200
200
  #
201
201
  # @param [String] host the MySQL server
@@ -208,11 +208,11 @@ class Mysql
208
208
  raise Error, "Already connected to a MySQL server" if @connected
209
209
 
210
210
  ObjectSpace.define_finalizer( self, Mysql.finalizer(@mysql, @mysql_free))
211
-
211
+
212
212
  if C::mysql_real_connect( @mysql, host, user, passwd, db, port, sock, flag ).null?
213
213
  raise Mysql::Error, error_msg
214
214
  end
215
-
215
+
216
216
  @connected = true
217
217
  self
218
218
  end
@@ -242,8 +242,8 @@ class Mysql
242
242
  def sqlstate
243
243
  C::mysql_sqlstate(@mysql)
244
244
  end
245
-
246
-
245
+
246
+
247
247
  # Sets extra connection options.
248
248
  #
249
249
  # @param [Integer] option the option to set
@@ -276,7 +276,21 @@ class Mysql
276
276
  self
277
277
  end
278
278
 
279
-
279
+
280
+ # Sets the auto-reconnect flag of mysql
281
+ #
282
+ # @param [true,false] The new reconnect state.
283
+ def reconnect= flag
284
+ options OPT_RECONNECT, flag
285
+ @reconnect = flag
286
+ end
287
+
288
+ # Returns the current state of the reconnect flag
289
+ # @return [true,false] true if auto-reconnection is enabled.
290
+ def reconnect
291
+ @reconnect
292
+ end
293
+
280
294
  # Execute a query statement.
281
295
  #
282
296
  # @param [String] sql the SQL statement
@@ -1,9 +1,9 @@
1
1
  class Mysql
2
-
2
+
3
3
  # Result set.
4
4
  class Result
5
5
  include Enumerable
6
-
6
+
7
7
  attr_reader :fields
8
8
 
9
9
  # Create the next result object.
@@ -89,7 +89,7 @@ class Mysql
89
89
  else
90
90
  @colnames ||= fetch_fields.map{|f| f.name}
91
91
  end
92
-
92
+
93
93
  hash = {}
94
94
  row.each_with_index do |value, i|
95
95
  hash[keys[i]] = value
@@ -106,7 +106,7 @@ class Mysql
106
106
  yield row
107
107
  end
108
108
  end
109
-
109
+
110
110
  # @return [Integer] The position of the field cursor after the last fetch_field.
111
111
  def field_tell
112
112
  raise Error, "Result has been freed" unless @result
@@ -165,5 +165,5 @@ class Mysql
165
165
  end
166
166
  end
167
167
  end
168
-
168
+
169
169
  end
@@ -12,28 +12,29 @@ class Mysql
12
12
  :neg, :uchar,
13
13
  :time_type, :int)
14
14
  end
15
-
15
+
16
16
  class C::Bind < FFI::Struct
17
- layout(:length, :pointer, # output length pointer
18
- :is_null, :pointer, # Pointer to null indicator
17
+ layout(:length, :pointer, # output length pointer
18
+ :is_null, :pointer, # Pointer to null indicator
19
19
  :buffer, :pointer, # buffer to get/put data
20
- # set this if you want to track data truncations happened during fetch
20
+ # set this if you want to track data truncations happened during fetch
21
21
  :error, :pointer,
22
- :buffer_type, :int, # buffer type
23
- # output buffer length, must be set when fetching str/binary
22
+ :row_ptr, :pointer, # for the current data position
23
+ :store_param_func, :pointer,
24
+ :fetch_result, :pointer,
25
+ :skip_result, :pointer,
26
+ # output buffer length, must be set when fetching str/binary
24
27
  :buffer_length, :ulong,
25
- :row_ptr, :pointer, # for the current data position
26
- :offset, :ulong, # offset position for char/binary fetch
28
+ :offset, :ulong, # offset position for char/binary fetch
27
29
  :length_value, :ulong, # Used if length is 0
28
30
  :param_number, :uint, # For null count and error messages
29
31
  :pack_length, :uint, # Internal length for packed data
32
+ :buffer_type, :int, # buffer type
30
33
  :error_value, :char, # used if error is 0
31
34
  :is_unsigned, :char, # set if integer type is unsigned
32
35
  :long_data_used, :char, # If used with mysql_send_long_data
33
36
  :is_null_value, :char, # Used if is_null is 0
34
- :store_param_func, :pointer,
35
- :fetch_result, :pointer,
36
- :skip_result, :pointer)
37
+ :extension, :pointer)
37
38
 
38
39
  def prepare_result( field )
39
40
  self[:buffer_type] = field.type
@@ -78,19 +79,21 @@ class Mysql
78
79
  raise TypeError, "unrecognized class: #{arg.class}"
79
80
  end
80
81
  end
81
-
82
+
82
83
  def set_param( arg )
83
84
  case arg
84
85
  when nil
85
86
  self[:buffer_type] = Field::TYPE_NULL
86
- when Fixnum
87
- self[:buffer_type] = Field::TYPE_LONG
88
- self[:buffer] = @buf = FFI::MemoryPointer.new( :long )
89
- @buf.write_int(arg)
90
- when Bignum
91
- self[:buffer_type] = Field::TYPE_LONGLONG
92
- self[:buffer] = @buf = FFI::MemoryPointer.new( :long_long )
93
- @buf.write_long_long(arg)
87
+ when Integer
88
+ if arg.size == 4
89
+ self[:buffer_type] = Field::TYPE_LONG
90
+ self[:buffer] = @buf = FFI::MemoryPointer.new( :long )
91
+ @buf.write_long(arg)
92
+ else
93
+ self[:buffer_type] = Field::TYPE_LONGLONG
94
+ self[:buffer] = @buf = FFI::MemoryPointer.new( :long_long )
95
+ @buf.write_long_long(arg)
96
+ end
94
97
  when Float
95
98
  self[:buffer_type] = Field::TYPE_DOUBLE
96
99
  self[:buffer] = @buf = FFI::MemoryPointer.new( :double )
@@ -98,9 +101,9 @@ class Mysql
98
101
  when String
99
102
  self[:buffer_type] = Field::TYPE_STRING
100
103
  self[:buffer] = @buf = FFI::MemoryPointer::from_string(arg)
101
- self[:buffer_length] = arg.size
104
+ self[:buffer_length] = arg.bytesize
102
105
  self[:length] = @buflen = FFI::MemoryPointer.new( :ulong )
103
- @buflen.write_long( arg.size )
106
+ @buflen.write_long( arg.bytesize )
104
107
  when ::Time, Mysql::Time
105
108
  self[:buffer_type] = Field::TYPE_DATETIME
106
109
  self[:buffer] = @buf = FFI::MemoryPointer.new( C::Time, 1, true )
@@ -162,14 +165,14 @@ class Mysql
162
165
  end
163
166
  end
164
167
  end
165
-
168
+
166
169
  def initialize( mysql )
167
170
  @mysql = mysql
168
171
  @stmt = C::mysql_stmt_init(mysql)
169
172
  @params = []
170
173
  raise Error, error_msg if @stmt.null?
171
174
  ObjectSpace.define_finalizer( self, Stmt.finalizer(@stmt) )
172
-
175
+
173
176
  _true = FFI::MemoryPointer.new(:int)
174
177
  _true.write_int(1)
175
178
  if C::mysql_stmt_attr_set(@stmt, :update_max_length, _true) != 0
@@ -179,11 +182,11 @@ class Mysql
179
182
 
180
183
  # Closes the statement.
181
184
  def close
182
- C::mysql_stmt_close(@stmt)
185
+ C::mysql_stmt_close(@stmt)
183
186
  @stmt = nil
184
187
  ObjectSpace.undefine_finalizer(self)
185
188
  end
186
-
189
+
187
190
  # Preparse the statement
188
191
  # @param [String] stmt the SQL statement
189
192
  # @return [self]
@@ -198,7 +201,7 @@ class Mysql
198
201
  @param_binds = (0...nparams).map {|i|
199
202
  C::Bind.new(FFI::Pointer.new(C::Bind, @param_binds_ary.address + i * C::Bind.size))
200
203
  }
201
-
204
+
202
205
  if @result = result_metadata
203
206
  fields = @result.fetch_fields
204
207
  @result_binds_ary = FFI::MemoryPointer.new C::Bind, fields.size, true
@@ -211,7 +214,7 @@ class Mysql
211
214
  @result_binds_ary = nil
212
215
  @result_binds = []
213
216
  end
214
-
217
+
215
218
  self
216
219
  end
217
220
 
@@ -221,9 +224,9 @@ class Mysql
221
224
  def execute(*args)
222
225
  raise Error, "Statment already closed" unless @stmt
223
226
  if args.size != @param_binds.size
224
- raise Error, "param_count(#{@param_binds.size}) != number of arguments(#{args.size})"
227
+ raise Error, "param_count(#{@param_binds.size}) != number of arguments(#{args.size})"
225
228
  end
226
-
229
+
227
230
  free_result
228
231
  unless @param_binds.empty?
229
232
  @param_binds.each_with_index do |bind, i|
@@ -233,7 +236,7 @@ class Mysql
233
236
  raise Error, error_msg
234
237
  end
235
238
  end
236
-
239
+
237
240
  if C::mysql_stmt_execute(@stmt) != 0
238
241
  raise Error, error_msg
239
242
  end
@@ -252,7 +255,7 @@ class Mysql
252
255
  raise Error, error_msg
253
256
  end
254
257
  end
255
-
258
+
256
259
  self
257
260
  end
258
261
 
@@ -290,7 +293,7 @@ class Mysql
290
293
  @result_binds.map{|bind| bind.value}
291
294
  end
292
295
  end
293
-
296
+
294
297
  # Iterates over all rows in this result set.
295
298
  # @yield [Array] Called once for each row in this result set
296
299
  # @see fetch_row
@@ -331,7 +334,7 @@ class Mysql
331
334
  raise Error, error_msg
332
335
  end
333
336
  end
334
-
337
+
335
338
  # @return [Integer] the number of rows of this statement
336
339
  def num_rows
337
340
  raise Error, "Statment already closed" unless @stmt
@@ -392,7 +395,7 @@ class Mysql
392
395
 
393
396
  def self.finalizer(stmt)
394
397
  Proc.new do |*args|
395
- C::mysql_stmt_close(stmt)
398
+ C::mysql_stmt_close(stmt)
396
399
  end
397
400
  end
398
401
  end
@@ -1,14 +1,14 @@
1
1
  class Mysql
2
2
  # Major version
3
- MAJOR = 0
3
+ MAJOR = 1
4
4
  # Minor version
5
5
  MINOR = 0
6
6
  # Tiny version
7
- TINY = 2
8
-
7
+ TINY = 0
8
+
9
9
  # Version string
10
10
  VERSION = [MAJOR, MINOR, TINY].join('.')
11
-
11
+
12
12
  # Returns the version string for the library.
13
13
  #
14
14
  def self.version
data/test/test_mysql.rb CHANGED
@@ -29,9 +29,9 @@ class TC_Mysql < Test::Unit::TestCase
29
29
  def teardown()
30
30
  end
31
31
 
32
- # def test_version()
33
- # assert_equal("0.1.0", Mysql::VERSION)
34
- # end
32
+ def test_version()
33
+ assert_equal("1.0.0", Mysql::VERSION)
34
+ end
35
35
 
36
36
  def test_init()
37
37
  assert_nothing_raised{@m = Mysql.init}
@@ -205,15 +205,11 @@ class TC_Mysql2 < Test::Unit::TestCase
205
205
 
206
206
  def test_sqlstate()
207
207
  if @m.server_version >= 40100 then
208
- if RUBY_PLATFORM !~ /mingw|mswin/ then
209
- assert_equal("00000", @m.sqlstate)
210
- else
211
- assert_equal("HY000", @m.sqlstate)
212
- end
208
+ assert_equal("00000", @m.sqlstate)
213
209
  assert_raises(Mysql::Error){@m.query("hogehoge")}
214
210
  assert_equal("42000", @m.sqlstate)
215
211
  end
216
- end
212
+ end if Mysql.client_version >= 40100
217
213
 
218
214
  def test_query_with_result()
219
215
  assert_equal(true, @m.query_with_result)
@@ -222,6 +218,14 @@ class TC_Mysql2 < Test::Unit::TestCase
222
218
  assert_equal(true, @m.query_with_result = true)
223
219
  assert_equal(true, @m.query_with_result)
224
220
  end
221
+
222
+ def test_reconnect()
223
+ assert_equal(false, @m.reconnect)
224
+ assert_equal(true, @m.reconnect = true)
225
+ assert_equal(true, @m.reconnect)
226
+ assert_equal(false, @m.reconnect = false)
227
+ assert_equal(false, @m.reconnect)
228
+ end
225
229
  end
226
230
 
227
231
  class TC_MysqlRes < Test::Unit::TestCase
@@ -1297,12 +1301,16 @@ class TC_MysqlStmt2 < Test::Unit::TestCase
1297
1301
 
1298
1302
  def test_insert_id()
1299
1303
  if @m.server_version >= 40100 then
1300
- @m.query("create temporary table t (i int auto_increment, unique(i))")
1301
- @s.prepare("insert into t values (0)")
1302
- @s.execute()
1304
+ @m.query("create temporary table t (i bigint auto_increment, unique(i))")
1305
+ @s.prepare("insert into t values (?)")
1306
+ @s.execute(0)
1303
1307
  assert_equal(1, @s.insert_id())
1304
- @s.execute()
1308
+ @s.execute(0)
1305
1309
  assert_equal(2, @s.insert_id())
1310
+ @s.execute(2**32)
1311
+ assert_equal(2**32, @s.insert_id())
1312
+ @s.execute(0)
1313
+ assert_equal(2**32+1, @s.insert_id())
1306
1314
  end
1307
1315
  end
1308
1316