ffi-mysql 0.0.2 → 1.0.0

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