sam / do fork watch download tarball
public
Rubygem
Description: DataObjects
Homepage: http://rubyforge.org/projects/dorb
Clone URL: git://github.com/sam/do.git
Added C Date/DateTime parsing (swiped from Bernard's Sqlite driver), 
massive speed improvement.  Added benchmark rake task
Scott Bauer (author)
Fri Feb 22 11:07:23 -0800 2008
commit  59aa1319eb253dae5cacec22c3dcf20b5118673b
tree    72534c3a861299c094b0b55db20f5cba656e9fa7
parent  a6c8360f700f097f312668f7234326f5fc22e44b
...
33
34
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
37
...
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
0
@@ -33,4 +33,36 @@ end
0
 
0
 task :install => [:package] do
0
   sh %{sudo gem install pkg/#{NAME}-#{VERSION}}
0
+end
0
+
0
+
0
+task :benchmark do
0
+ $:.push 'lib'
0
+ require 'do_mysql'
0
+ require 'benchmark'
0
+
0
+ @connection = DataObject::Mysql::Connection.new('host=127.0.0.1 user=root dbname=rbmysql_test')
0
+ @connection.open
0
+
0
+ begin
0
+ Benchmark.bm do |bm|
0
+ bm.report do
0
+ 1000.times do |i|
0
+ command = @connection.create_command('SELECT * FROM widgets')
0
+
0
+ reader = command.execute_reader
0
+
0
+ until (row = reader.next).nil?
0
+ reader.field_count.times { |i|
0
+ reader.item(i)
0
+ }
0
+ end
0
+ # reader.close
0
+ end
0
+ end
0
+ end
0
+ ensure
0
+ @connection.close
0
+ end
0
+
0
 end
0
\ No newline at end of file
...
1
 
 
2
3
4
...
6
7
8
9
10
11
12
13
14
15
 
 
 
 
 
 
 
 
 
16
17
 
18
19
20
...
86
87
88
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
93
94
...
267
268
269
 
 
 
 
 
 
 
 
270
271
272
 
273
274
275
...
1
2
3
4
5
6
...
8
9
10
 
 
 
 
 
 
 
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
...
91
92
93
 
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
 
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
...
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
0
@@ -1,4 +1,6 @@
0
 #include <ruby.h>
0
+#include <string.h>
0
+#include <math.h>
0
 #include <mysql.h>
0
 #include <errmsg.h>
0
 #include <mysqld_error.h>
0
@@ -6,15 +8,18 @@
0
 #define RUBY_CLASS(name) rb_const_get(rb_cObject, rb_intern(name))
0
 #define CHAR_TO_STRING(name) rb_str_new2(name)
0
 #define TAINTED_STRING(name) rb_tainted_str_new2(name)
0
-#define ID_TO_I rb_intern("to_i")
0
-#define ID_TO_F rb_intern("to_f")
0
-#define ID_PARSE rb_intern("parse")
0
-#define ID_TO_TIME rb_intern("to_time")
0
-#define ID_NEW rb_intern("new")
0
-#define ID_CONST_GET rb_intern("const_get")
0
-
0
+
0
+static ID ID_TO_I;
0
+static ID ID_TO_F;
0
+static ID ID_PARSE;
0
+static ID ID_TO_TIME;
0
+static ID ID_NEW;
0
+static ID ID_NEW_BANG;
0
+static ID ID_CONST_GET;
0
+
0
 static VALUE rb_cDate;
0
 static VALUE rb_cDateTime;
0
+static VALUE rb_cRational;
0
  
0
 VALUE mRbMysql;
0
 VALUE cConnection;
0
@@ -86,9 +91,61 @@ VALUE cast_mysql_value_to_ruby_value(const char* data, char* ruby_class_name) {
0
   } else if (0 == strcmp("TrueClass", ruby_class_name) || 0 == strcmp("FalseClass", ruby_class_name)) {
0
     ruby_value = (NULL == data || 0 == data || 0 == strcmp("0", data)) ? Qfalse : Qtrue;
0
   } else if (0 == strcmp("Date", ruby_class_name)) {
0
- ruby_value = rb_funcall(rb_cDate, ID_PARSE, 1, TAINTED_STRING(data));
0
+
0
+ int year, month, day;
0
+
0
+ // Used by math pulled out of Date.civil_to_jd and jd_to_ajd
0
+ int a, b, jd, ajd;
0
+ VALUE rational;
0
+
0
+ sscanf(data, "%4d-%2d-%2d", &year, &month, &day);
0
+
0
+ // Math from Date.civil_to_jd
0
+ if ( month <= 2 ) {
0
+ year -= 1;
0
+ month += 12;
0
+ }
0
+ a = year / 100;
0
+ b = 2 - a + (a / 4);
0
+ jd = floor(365.25 * (year + 4716)) + floor(30.6001 * (month + 1)) + day + b - 1524;
0
+
0
+ // Math from Date.jd_to_ajd
0
+ ajd = jd * 2 - 1;
0
+ rational = rb_funcall(rb_cRational, ID_NEW_BANG, 2, INT2NUM(ajd), INT2NUM(2));
0
+
0
+ // Original (slooooow) Date [~5.8 seconds / 1000.times]:
0
+ // ruby_value = rb_funcall(rb_cDate, ID_PARSE, 1, rb_str_new2(sqlite3_value_text(value)));
0
+ // Faster Date [~2.2 seconds / 1000.times]:
0
+ // ruby_value = rb_funcall(rb_cDate, ID_CIVIL, 3, INT2NUM(year), INT2NUM(month), INT2NUM(day));
0
+
0
+ // Super fastest Date creation! [~0.25 seconds / 1000.times]
0
+ // Yeah, that's 23 times faster than Date.parse
0
+ ruby_value = rb_funcall(rb_cDate, ID_NEW_BANG, 3, rational, INT2NUM(0), INT2NUM(2299161));
0
   } else if (0 == strcmp("DateTime", ruby_class_name)) {
0
- ruby_value = rb_funcall(rb_cDateTime, ID_PARSE, 1, TAINTED_STRING(data));
0
+ int a, b, jd;
0
+ int y, m, d, h, min, s;
0
+
0
+ sscanf(data, "%4d-%2d-%2d %2d:%2d:%2d", &y, &m, &d, &h, &min, &s);
0
+
0
+ // Original (slooooow) DateTime [~12 seconds / 1000.times]
0
+ // ruby_value = rb_funcall(rb_cDateTime, ID_PARSE, 1, rb_str_new2(sqlite3_value_text(value)));
0
+
0
+ // Faster DateTime [ ~7.3 seconds / 1000.times]
0
+ // ruby_value = rb_funcall(rb_cDateTime, ID_CIVIL, 6, INT2NUM(y), INT2NUM(m), INT2NUM(d), INT2NUM(h), INT2NUM(min), INT2NUM(s));
0
+
0
+ // Somewhat Faster [~6.3 seconds / 1000.times ]
0
+
0
+ if ( m <= 2 ) {
0
+ y -= 1;
0
+ m += 12;
0
+ }
0
+ a = y / 100;
0
+ b = 2 - a + (a / 4);
0
+ jd = floor(365.25 * (y + 4716)) + floor(30.6001 * (m + 1)) + d + b - 1524;
0
+
0
+ VALUE fraction = rb_funcall(rb_cDate, rb_intern("time_to_day_fraction"), 3, INT2NUM(h), INT2NUM(min), INT2NUM(s));
0
+ VALUE ajd = rb_funcall(rb_cDate, rb_intern("jd_to_ajd"), 2, INT2NUM(jd), fraction);
0
+ ruby_value = rb_funcall(rb_cDateTime, rb_intern("new!"), 3, ajd, INT2NUM(0), INT2NUM(2299161));
0
   } else {
0
     ruby_value = TAINTED_STRING(data);
0
   }
0
@@ -267,9 +324,18 @@ VALUE cResult_close(VALUE self) {
0
  
0
  
0
 void Init_rbmysql() {
0
+ ID_TO_I = rb_intern("to_i");
0
+ ID_TO_F = rb_intern("to_f");
0
+ ID_PARSE = rb_intern("parse");
0
+ ID_TO_TIME = rb_intern("to_time");
0
+ ID_NEW = rb_intern("new");
0
+ ID_NEW_BANG = rb_intern("new!");
0
+ ID_CONST_GET = rb_intern("const_get");
0
+
0
   // Store references to a few helpful clases that aren't in Ruby Core
0
   rb_cDate = RUBY_CLASS("Date");
0
   rb_cDateTime = RUBY_CLASS("DateTime");
0
+ rb_cRational = RUBY_CLASS("Rational");
0
  
0
   // Top Level Module that all the classes live under
0
   mRbMysql = rb_define_module("RbMysql");
...
1
 
2
3
4
5
6
7
8
9
10
11
12
13
 
14
15
16
17
18
19
...
29
30
31
32
33
34
35
36
 
 
37
38
39
40
41
42
43
44
45
46
47
 
48
49
50
...
98
99
100
101
 
102
103
104
105
106
107
108
109
110
111
112
 
 
 
 
 
113
114
115
 
116
117
118
119
 
120
121
122
123
 
 
124
125
 
126
127
128
 
 
 
 
129
130
131
132
133
134
135
136
 
137
138
139
140
141
 
142
143
144
145
146
 
147
148
149
...
157
158
159
160
 
161
162
163
164
165
 
166
167
168
...
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
210
211
...
214
215
216
217
218
219
220
 
 
 
221
222
223
...
231
232
233
234
235
236
237
238
239
240
 
 
 
 
241
242
243
...
 
1
2
3
4
5
 
 
6
7
8
9
10
 
11
12
13
 
14
15
16
...
26
27
28
 
 
 
 
 
29
30
31
32
33
34
 
 
 
 
 
 
 
35
36
37
38
...
86
87
88
 
89
90
91
92
93
94
95
 
 
 
 
 
96
97
98
99
100
101
102
 
103
104
105
 
 
106
107
 
 
 
108
109
110
 
111
112
113
114
115
116
117
118
119
 
 
 
 
 
 
 
120
121
122
123
124
 
125
126
127
128
129
 
130
131
132
133
...
141
142
143
 
144
145
146
147
148
 
149
150
151
152
...
160
161
162
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
...
198
199
200
 
 
 
 
201
202
203
204
205
206
...
214
215
216
 
 
 
 
 
 
 
217
218
219
220
221
222
223
0
@@ -1,19 +1,16 @@
0
-require 'mysql_c'
0
+require 'rbmysql'
0
 require 'data_objects'
0
 
0
 module DataObject
0
   module Mysql
0
- TYPES = Hash[*Mysql_c.constants.select {|x| x.include?("MYSQL_TYPE")}.map {|x| [Mysql_c.const_get(x), x.gsub(/^MYSQL_TYPE_/, "")]}.flatten]
0
-
0
     QUOTE_STRING = "\""
0
     QUOTE_COLUMN = "`"
0
     
0
     class Connection < DataObject::Connection
0
       
0
- attr_reader :db
0
+ attr_accessor :mysql_connection
0
       
0
       def initialize(connection_string)
0
- @state = STATE_CLOSED
0
         @connection_string = connection_string
0
         opts = connection_string.split(" ")
0
         opts.each do |opt|
0
@@ -29,22 +26,13 @@ module DataObject
0
       end
0
       
0
       def open
0
- @db = Mysql_c.mysql_init(nil)
0
- raise ConnectionFailed, "could not allocate a MySQL connection" unless @db
0
- conn = Mysql_c.mysql_real_connect(@db, @host, @user, @password, @dbname, @port || 0, @socket, @flags || 0)
0
- raise ConnectionFailed, "Unable to connect to database with provided connection string. \n#{Mysql_c.mysql_error(@db)}" unless conn
0
- @state = STATE_OPEN
0
+ @mysql_connection = RbMysql::Connection.new(@host, @user, @password || '', @dbname, @port || 0, @socket, @flags || 0)
0
+ raise ConnectionFailed, "Unable to connect to database with provided connection string. \n#{Mysql_c.mysql_error(@db)}" unless @mysql_connection
0
         true
0
       end
0
       
0
       def close
0
- if @state == STATE_OPEN
0
- Mysql_c.mysql_close(@db)
0
- @state = STATE_CLOSED
0
- true
0
- else
0
- false
0
- end
0
+ @mysql_connection.close
0
       end
0
       
0
       def create_command(text)
0
@@ -98,52 +86,48 @@ module DataObject
0
 
0
       def exec_sql(sql)
0
         @connection.logger.debug(sql)
0
- Mysql_c.mysql_query(@connection.db, sql)
0
+ @connection.mysql_reader.execute_non_reader(sql)
0
       end
0
 
0
     end
0
     
0
     class Reader < DataObject::Reader
0
       
0
- def initialize(db, reader)
0
- @reader = reader
0
- unless @reader
0
- if Mysql_c.mysql_field_count(db) == 0
0
- @records_affected = Mysql_c.mysql_affected_rows(db)
0
+ def initialize(mysql_reader)
0
+ @mysql_reader = mysql_reader
0
+ unless @mysql_reader
0
+ if mysql_reader.field_count == 0
0
+ @records_affected = mysql_reader.affected_rows
0
             close
0
           else
0
- raise UnknownError, "An unknown error has occured while trying to process a MySQL query.\n#{Mysql_c.mysql_error(db)}"
0
+ raise UnknownError, "An unknown error has occured while trying to process a MySQL query.\n#{@mysql_reader.connection.last_error}"
0
           end
0
         else
0
- @field_count = @reader.field_count
0
- @state = STATE_OPEN
0
+ @field_count = @mysql_reader.field_count
0
           
0
- @native_fields, @fields = Mysql_c.mysql_c_fetch_field_types(@reader, @field_count), Mysql_c.mysql_c_fetch_field_names(@reader, @field_count)
0
-
0
- raise UnknownError, "An unknown error has occured while trying to process a MySQL query. There were no fields in the resultset\n#{Mysql_c.mysql_error(db)}" if @native_fields.empty?
0
+ # @native_fields, @fields = Mysql_c.mysql_c_fetch_field_types(@reader, @field_count), Mysql_c.mysql_c_fetch_field_names(@reader, @field_count)
0
+ # raise UnknownError, "An unknown error has occured while trying to process a MySQL query. There were no fields in the resultset\n#{Mysql_c.mysql_error(db)}" if @native_fields.empty?
0
           
0
- @has_rows = !(@row = Mysql_c.mysql_c_fetch_row(@reader)).nil?
0
+ @has_rows = !(@row = @mysql_reader.fetch_row).nil?
0
         end
0
       end
0
       
0
+ def set_types(type_array)
0
+ @mysql_reader.set_types type_array
0
+ end
0
+
0
       def close
0
- if @state == STATE_OPEN
0
- Mysql_c.mysql_free_result(@reader)
0
- @state = STATE_CLOSED
0
- true
0
- else
0
- false
0
- end
0
+ @mysql_reader.close
0
       end
0
       
0
       def name(col)
0
         super
0
- @fields[col]
0
+ @mysql_reader.field_names[col]
0
       end
0
       
0
       def get_index(name)
0
         super
0
- @fields.index(name)
0
+ @mysql_reader.field_names.index(name)
0
       end
0
       
0
       def null?(idx)
0
@@ -157,12 +141,12 @@ module DataObject
0
       
0
       def item(idx)
0
         super
0
- typecast(@row[idx], idx)
0
+ @row[idx]
0
       end
0
       
0
       def next
0
         super
0
- @row = Mysql_c.mysql_c_fetch_row(@reader)
0
+ @row = @mysql_reader.fetch_row
0
         close if @row.nil?
0
         @row ? true : nil
0
       end
0
@@ -176,36 +160,36 @@ module DataObject
0
         end
0
       end
0
       
0
- protected
0
- def native_type(col)
0
- super
0
- TYPES[@native_fields[col].type]
0
- end
0
-
0
- def typecast(val, idx)
0
- return nil if val.nil? || val == "NULL"
0
- field = @native_fields[idx]
0
- case TYPES[field]
0
- when "NULL"
0
- nil
0
- when "TINY"
0
- val != "0"
0
- when "BIT"
0
- val.to_i(2)
0
- when "SHORT", "LONG", "INT24", "LONGLONG"
0
- val == '' ? nil : val.to_i
0
- when "DECIMAL", "NEWDECIMAL", "FLOAT", "DOUBLE", "YEAR"
0
- val.to_f
0
- when "TIMESTAMP", "DATETIME"
0
- DateTime.parse(val) rescue nil
0
- when "TIME"
0
- DateTime.parse(val).to_time rescue nil
0
- when "DATE"
0
- Date.parse(val) rescue nil
0
- else
0
- val
0
- end
0
- end
0
+ # protected
0
+ # def native_type(col)
0
+ # super
0
+ # TYPES[@native_fields[col].type]
0
+ # end
0
+
0
+ # def typecast(val, idx)
0
+ # return nil if val.nil? || val == "NULL"
0
+ # field = @native_fields[idx]
0
+ # case TYPES[field]
0
+ # when "NULL"
0
+ # nil
0
+ # when "TINY"
0
+ # val != "0"
0
+ # when "BIT"
0
+ # val.to_i(2)
0
+ # when "SHORT", "LONG", "INT24", "LONGLONG"
0
+ # val == '' ? nil : val.to_i
0
+ # when "DECIMAL", "NEWDECIMAL", "FLOAT", "DOUBLE", "YEAR"
0
+ # val.to_f
0
+ # when "TIMESTAMP", "DATETIME"
0
+ # DateTime.parse(val) rescue nil
0
+ # when "TIME"
0
+ # DateTime.parse(val).to_time rescue nil
0
+ # when "DATE"
0
+ # Date.parse(val) rescue nil
0
+ # else
0
+ # val
0
+ # end
0
+ # end
0
     end
0
     
0
     class Command < DataObject::Command
0
@@ -214,10 +198,9 @@ module DataObject
0
         super
0
         sql = escape_sql(args)
0
         @connection.logger.debug { sql }
0
- result = Mysql_c.mysql_query(@connection.db, sql)
0
- # TODO: Real Error
0
- raise QueryError, "Your query failed.\n#{Mysql_c.mysql_error(@connection.db)}\n#{@text}" unless result == 0
0
- reader = Reader.new(@connection.db, Mysql_c.mysql_use_result(@connection.db))
0
+ mysql_reader = @connection.mysql_connection.execute_reader(sql)
0
+ raise QueryError, "Your query failed.\n#{@connection.db.last_error}\n#{@text}" unless mysql_reader
0
+ reader = Reader.new(mysql_reader)
0
         if block_given?
0
           result = yield(reader)
0
           reader.close
0
@@ -231,13 +214,10 @@ module DataObject
0
         super
0
         sql = escape_sql(args)
0
         @connection.logger.debug { sql }
0
- result = Mysql_c.mysql_query(@connection.db, sql)
0
- raise QueryError, "Your query failed.\n#{Mysql_c.mysql_error(@connection.db)}\n#{@text}" unless result == 0
0
- reader = Mysql_c.mysql_store_result(@connection.db)
0
- raise QueryError, "You called execute_non_query on a query: #{@text}" if reader
0
- rows_affected = Mysql_c.mysql_affected_rows(@connection.db)
0
- Mysql_c.mysql_free_result(reader)
0
- return ResultData.new(@connection, rows_affected, Mysql_c.mysql_insert_id(@connection.db))
0
+ result = @connection.db.execute_non_reader(sql)
0
+ raise QueryError, "Your query failed.\n#{@connection.db.last_error}\n#{@text}" unless result
0
+ rows_affected = result.affected_rows
0
+ return ResultData.new(@connection, rows_affected, result.inserted_id)
0
       end
0
       
0
       def quote_time(value)

Comments

    No one has commented yet.