swift-db-mysql 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,3 @@
1
+ == 0.1.0 (2012-07-20)
2
+
3
+ * Initial version.
data/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # Swift MySQL adapter
2
+
3
+ MRI adapter for MySQL
4
+
5
+ ## Features
6
+
7
+ * Lightweight & fast
8
+ * Result typecasting
9
+ * Prepared statements, yeah finally!
10
+ * Asynchronous support
11
+ * Nested Transactions
12
+
13
+ ## API
14
+
15
+ ```
16
+ Swift::DB::Mysql
17
+ .new(options)
18
+ #execute(sql, *bind)
19
+ #prepare(sql)
20
+ #begin(savepoint = nil)
21
+ #commit(savepoint = nil)
22
+ #rollback(savepoint = nil)
23
+ #transaction(savepoint = nil, &block)
24
+ #close
25
+ #closed?
26
+ #escape(text)
27
+ #query(sql, *bind)
28
+ #fileno
29
+ #result
30
+ #write(table, fields = nil, io_or_string)
31
+
32
+ Swift::DB::MySql::Statement
33
+ .new(Swift::DB::Mysql, sql)
34
+ #execute(*bind)
35
+ #release
36
+
37
+ Swift::DB::Mysql::Result
38
+ #selected_rows
39
+ #affected_rows
40
+ #fields
41
+ #types
42
+ #each
43
+ #insert_id
44
+ ```
45
+
46
+ ## Example
47
+
48
+ ```ruby
49
+ require 'swift/db/mysql'
50
+
51
+ db = Swift::DB::Mysql.new(db: 'swift_test')
52
+
53
+ db.execute('drop table if exists users')
54
+ db.execute('create table users (id serial, name text, age integer, created_at datetime)')
55
+ db.execute('insert into users(name, age, created_at) values(?, ?, ?)', 'test', 30, Time.now.utc)
56
+
57
+ row = db.execute('select * from users').first
58
+ p row #=> {:id => 1, :name => 'test', :age => 30, :created_at=> #<Swift::DateTime>}
59
+ ```
60
+
61
+ ### Asynchronous
62
+
63
+ Hint: You can use `Adapter#fileno` and `EventMachine.watch` if you need to use this with EventMachine.
64
+
65
+ ```ruby
66
+ require 'swift/db/mysql'
67
+
68
+ rows = []
69
+ pool = 3.times.map {Swift::DB::Mysql.new(db: 'swift_test')}
70
+
71
+ 3.times do |n|
72
+ Thread.new do
73
+ pool[n].query("select sleep(#{(3 - n) / 10.0}), #{n + 1} as query_id") do |row|
74
+ rows << row[:query_id]
75
+ end
76
+ end
77
+ end
78
+
79
+ Thread.list.reject {|thread| Thread.current == thread}.each(&:join)
80
+ p rows #=> [3, 2, 1]
81
+ ```
82
+
83
+ ### Data I/O
84
+
85
+ The adapter supports data write via LOAD DATA LOCAL INFILE command.
86
+
87
+ ```ruby
88
+ require 'swift/db/mysql'
89
+
90
+ db = Swift::DB::Mysql.new(db: 'swift_test')
91
+ db.execute('drop table if exists users')
92
+ db.execute('create table users (id int auto_increment primary key, name text)')
93
+
94
+ db.write('users', %w{name}, "foo\nbar\nbaz\n")
95
+ db.write('users', %w{name}, StringIO.new("foo\nbar\nbaz\n"))
96
+ db.write('users', %w{name}, File.open("users.dat"))
97
+ ```
98
+
99
+ ## Performance
100
+
101
+ Don't read too much into it. Each library has its advantages and disadvantages.
102
+
103
+ ```
104
+ # insert 1000 rows and read them back 100 times
105
+
106
+ $ ruby check.rb
107
+ user system total real
108
+ do_mysql insert 0.170000 0.100000 0.270000 ( 0.629025)
109
+ do_mysql select 1.080000 0.130000 1.210000 ( 1.227585)
110
+
111
+ mysql2 insert 0.210000 0.040000 0.250000 ( 0.704030)
112
+ mysql2 select 0.940000 0.250000 1.190000 ( 1.206372)
113
+
114
+ swift insert 0.100000 0.060000 0.160000 ( 0.483229)
115
+ swift select 0.260000 0.010000 0.270000 ( 0.282307)
116
+ ```
117
+
118
+ ## License
119
+
120
+ MIT
@@ -0,0 +1,434 @@
1
+ // vim:ts=4:sts=4:sw=4:expandtab
2
+
3
+ // (c) Bharanee Rathna 2012
4
+
5
+ #include "adapter.h"
6
+ #include "typecast.h"
7
+
8
+ /* declaration */
9
+ VALUE cDMA, sUser;
10
+ VALUE db_mysql_result_each(VALUE);
11
+ VALUE db_mysql_result_allocate(VALUE);
12
+ VALUE db_mysql_result_load(VALUE, MYSQL_RES *, size_t, size_t);
13
+ VALUE db_mysql_statement_allocate(VALUE);
14
+ VALUE db_mysql_statement_initialize(VALUE, VALUE, VALUE);
15
+
16
+ /* definition */
17
+ Adapter* db_mysql_adapter_handle(VALUE self) {
18
+ Adapter *a;
19
+ Data_Get_Struct(self, Adapter, a);
20
+ if (!a)
21
+ rb_raise(eSwiftRuntimeError, "Invalid mysql adapter");
22
+ return a;
23
+ }
24
+
25
+ Adapter* db_mysql_adapter_handle_safe(VALUE self) {
26
+ Adapter *a = db_mysql_adapter_handle(self);
27
+ if (!a->connection)
28
+ rb_raise(eSwiftConnectionError, "mysql database is not open");
29
+ return a;
30
+ }
31
+
32
+ void db_mysql_adapter_mark(Adapter *a) {
33
+ if (a)
34
+ rb_gc_mark_maybe(a->io);
35
+ }
36
+
37
+ VALUE db_mysql_adapter_deallocate(Adapter *a) {
38
+ if (a && a->connection)
39
+ mysql_close(a->connection);
40
+ if (a)
41
+ free(a);
42
+ }
43
+
44
+ VALUE db_mysql_adapter_allocate(VALUE klass) {
45
+ Adapter *a = (Adapter*)malloc(sizeof(Adapter));
46
+
47
+ a->connection = 0;
48
+ a->t_nesting = 0;
49
+ a->io = Qnil;
50
+ return Data_Wrap_Struct(klass, db_mysql_adapter_mark, db_mysql_adapter_deallocate, a);
51
+ }
52
+
53
+ int db_mysql_adapter_infile_init(void **ptr, const char *filename, void *self) {
54
+ Adapter *a = db_mysql_adapter_handle_safe((VALUE)self);
55
+ *ptr = (void *)self;
56
+ return NIL_P(a->io) ? -1 : 0;
57
+ }
58
+
59
+ int db_mysql_adapter_infile_read(void *ptr, char *buffer, unsigned int size) {
60
+ VALUE data;
61
+ Adapter *a = db_mysql_adapter_handle_safe((VALUE)ptr);
62
+
63
+ data = rb_funcall(a->io, rb_intern("read"), 1, INT2NUM(size));
64
+
65
+ if (NIL_P(data)) return 0;
66
+
67
+ memcpy(buffer, RSTRING_PTR(data), RSTRING_LEN(data));
68
+ return RSTRING_LEN(data);
69
+ }
70
+
71
+ void db_mysql_adapter_infile_end(void *ptr) {
72
+ Adapter *a = db_mysql_adapter_handle_safe((VALUE)ptr);
73
+ a->io = Qnil;
74
+ }
75
+
76
+ int db_mysql_adapter_infile_error(void *ptr, char *error, unsigned int size) {
77
+ Adapter *a = db_mysql_adapter_handle_safe((VALUE)ptr);
78
+ a->io = Qnil;
79
+ snprintf(error, size, "error loading data using LOAD INFILE");
80
+ return 0;
81
+ }
82
+
83
+ char *ssl_option(VALUE ssl, char *key) {
84
+ VALUE option = rb_hash_aref(ssl, ID2SYM(rb_intern(key)));
85
+ return NIL_P(option) ? NULL : CSTRING(option);
86
+ }
87
+
88
+ VALUE db_mysql_adapter_initialize(VALUE self, VALUE options) {
89
+ char MYSQL_BOOL_TRUE = 1;
90
+ VALUE db, user, pass, host, port, ssl;
91
+ Adapter *a = db_mysql_adapter_handle(self);
92
+
93
+ if (TYPE(options) != T_HASH)
94
+ rb_raise(eSwiftArgumentError, "options needs to be a hash");
95
+
96
+ db = rb_hash_aref(options, ID2SYM(rb_intern("db")));
97
+ user = rb_hash_aref(options, ID2SYM(rb_intern("user")));
98
+ pass = rb_hash_aref(options, ID2SYM(rb_intern("pass")));
99
+ host = rb_hash_aref(options, ID2SYM(rb_intern("host")));
100
+ port = rb_hash_aref(options, ID2SYM(rb_intern("port")));
101
+ ssl = rb_hash_aref(options, ID2SYM(rb_intern("ssl")));
102
+
103
+ if (NIL_P(db))
104
+ rb_raise(eSwiftConnectionError, "Invalid db name");
105
+ if (NIL_P(host))
106
+ host = rb_str_new2("127.0.0.1");
107
+ if (NIL_P(port))
108
+ port = rb_str_new2("3306");
109
+ if (NIL_P(user))
110
+ user = sUser;
111
+
112
+ a->connection = mysql_init(0);
113
+ mysql_options(a->connection, MYSQL_OPT_RECONNECT, &MYSQL_BOOL_TRUE);
114
+ mysql_options(a->connection, MYSQL_OPT_LOCAL_INFILE, 0);
115
+
116
+ if (!NIL_P(ssl)) {
117
+ if (TYPE(ssl) != T_HASH)
118
+ rb_raise(eSwiftArgumentError, "ssl options needs to be a hash");
119
+
120
+ mysql_ssl_set(
121
+ a->connection,
122
+ ssl_option(ssl, "key"),
123
+ ssl_option(ssl, "cert"),
124
+ ssl_option(ssl, "ca"),
125
+ ssl_option(ssl, "capath"),
126
+ ssl_option(ssl, "cipher")
127
+ );
128
+ }
129
+
130
+ if (!mysql_real_connect(a->connection,
131
+ CSTRING(host), CSTRING(user), CSTRING(pass), CSTRING(db), atoi(CSTRING(port)), 0, CLIENT_FOUND_ROWS))
132
+ rb_raise(eSwiftConnectionError, "%s", mysql_error(a->connection));
133
+
134
+ mysql_set_character_set(a->connection, "utf8");
135
+ mysql_set_local_infile_handler(
136
+ a->connection,
137
+ db_mysql_adapter_infile_init, db_mysql_adapter_infile_read, db_mysql_adapter_infile_end, db_mysql_adapter_infile_error,
138
+ (void *)self
139
+ );
140
+ return self;
141
+ }
142
+
143
+ typedef struct Query {
144
+ MYSQL *connection;
145
+ VALUE sql;
146
+ } Query;
147
+
148
+ VALUE nogvl_mysql_adapter_execute(void *ptr) {
149
+ Query *q = (Query *)ptr;
150
+ return (VALUE)mysql_real_query(q->connection, RSTRING_PTR(q->sql), RSTRING_LEN(q->sql));
151
+ }
152
+
153
+ VALUE db_mysql_adapter_execute(int argc, VALUE *argv, VALUE self) {
154
+ VALUE sql, bind;
155
+ MYSQL_RES *result;
156
+ Adapter *a = db_mysql_adapter_handle_safe(self);
157
+ MYSQL *c = a->connection;
158
+
159
+ rb_scan_args(argc, argv, "10*", &sql, &bind);
160
+ sql = TO_S(sql);
161
+
162
+ if (RARRAY_LEN(bind) > 0)
163
+ sql = db_mysql_bind_sql(self, sql, bind);
164
+
165
+ Query q = {.connection = c, .sql = sql};
166
+
167
+ if ((int)rb_thread_blocking_region(nogvl_mysql_adapter_execute, &q, RUBY_UBF_IO, 0) != 0)
168
+ rb_raise(eSwiftRuntimeError, "%s", mysql_error(c));
169
+
170
+ result = mysql_store_result(c);
171
+ return db_mysql_result_load(db_mysql_result_allocate(cDMR), result, mysql_insert_id(c), mysql_affected_rows(c));
172
+ }
173
+
174
+ VALUE db_mysql_adapter_begin(int argc, VALUE *argv, VALUE self) {
175
+ char command[256];
176
+ VALUE savepoint;
177
+
178
+ Adapter *a = db_mysql_adapter_handle_safe(self);
179
+ rb_scan_args(argc, argv, "01", &savepoint);
180
+
181
+ if (a->t_nesting == 0) {
182
+ strcpy(command, "begin");
183
+ if (mysql_real_query(a->connection, command, strlen(command)) != 0)
184
+ rb_raise(eSwiftRuntimeError, "%s", mysql_error(a->connection));
185
+ a->t_nesting++;
186
+ if (NIL_P(savepoint))
187
+ return Qtrue;
188
+ }
189
+
190
+ if (NIL_P(savepoint))
191
+ savepoint = rb_uuid_string();
192
+
193
+ snprintf(command, 256, "savepoint %s", CSTRING(savepoint));
194
+ if (mysql_real_query(a->connection, command, strlen(command)) != 0)
195
+ rb_raise(eSwiftRuntimeError, "%s", mysql_error(a->connection));
196
+
197
+ a->t_nesting++;
198
+ return savepoint;
199
+ }
200
+
201
+ VALUE db_mysql_adapter_commit(int argc, VALUE *argv, VALUE self) {
202
+ VALUE savepoint;
203
+ char command[256];
204
+
205
+ Adapter *a = db_mysql_adapter_handle_safe(self);
206
+ rb_scan_args(argc, argv, "01", &savepoint);
207
+
208
+ if (a->t_nesting == 0)
209
+ return Qfalse;
210
+
211
+ if (NIL_P(savepoint)) {
212
+ strcpy(command, "commit");
213
+ if (mysql_real_query(a->connection, command, strlen(command)) != 0)
214
+ rb_raise(eSwiftRuntimeError, "%s", mysql_error(a->connection));
215
+ a->t_nesting--;
216
+ }
217
+ else {
218
+ snprintf(command, 256, "release savepoint %s", CSTRING(savepoint));
219
+ if (mysql_real_query(a->connection, command, strlen(command)) != 0)
220
+ rb_raise(eSwiftRuntimeError, "%s", mysql_error(a->connection));
221
+ a->t_nesting--;
222
+ }
223
+ return Qtrue;
224
+ }
225
+
226
+ VALUE db_mysql_adapter_rollback(int argc, VALUE *argv, VALUE self) {
227
+ VALUE savepoint;
228
+ char command[256];
229
+
230
+ Adapter *a = db_mysql_adapter_handle_safe(self);
231
+ rb_scan_args(argc, argv, "01", &savepoint);
232
+
233
+ if (a->t_nesting == 0)
234
+ return Qfalse;
235
+
236
+ if (NIL_P(savepoint)) {
237
+ strcpy(command, "rollback");
238
+ if (mysql_real_query(a->connection, command, strlen(command)) != 0)
239
+ rb_raise(eSwiftRuntimeError, "%s", mysql_error(a->connection));
240
+ a->t_nesting--;
241
+ }
242
+ else {
243
+ snprintf(command, 256, "rollback to savepoint %s", CSTRING(savepoint));
244
+ if (mysql_real_query(a->connection, command, strlen(command)) != 0)
245
+ rb_raise(eSwiftRuntimeError, "%s", mysql_error(a->connection));
246
+ a->t_nesting--;
247
+ }
248
+ return Qtrue;
249
+ }
250
+
251
+ VALUE db_mysql_adapter_transaction(int argc, VALUE *argv, VALUE self) {
252
+ int status;
253
+ VALUE savepoint, block, block_result;
254
+
255
+ Adapter *a = db_mysql_adapter_handle_safe(self);
256
+ rb_scan_args(argc, argv, "01&", &savepoint, &block);
257
+
258
+ if (NIL_P(block))
259
+ rb_raise(eSwiftRuntimeError, "mysql transaction requires a block");
260
+
261
+ if (a->t_nesting == 0) {
262
+ db_mysql_adapter_begin(1, &savepoint, self);
263
+ block_result = rb_protect(rb_yield, self, &status);
264
+ if (!status) {
265
+ db_mysql_adapter_commit(1, &savepoint, self);
266
+ if (!NIL_P(savepoint))
267
+ db_mysql_adapter_commit(0, 0, self);
268
+ }
269
+ else {
270
+ db_mysql_adapter_rollback(1, &savepoint, self);
271
+ if (!NIL_P(savepoint))
272
+ db_mysql_adapter_rollback(0, 0, self);
273
+ rb_jump_tag(status);
274
+ }
275
+ }
276
+ else {
277
+ if (NIL_P(savepoint))
278
+ savepoint = rb_uuid_string();
279
+ db_mysql_adapter_begin(1, &savepoint, self);
280
+ block_result = rb_protect(rb_yield, self, &status);
281
+ if (!status)
282
+ db_mysql_adapter_commit(1, &savepoint, self);
283
+ else {
284
+ db_mysql_adapter_rollback(1, &savepoint, self);
285
+ rb_jump_tag(status);
286
+ }
287
+ }
288
+
289
+ return block_result;
290
+ }
291
+
292
+ VALUE db_mysql_adapter_close(VALUE self) {
293
+ Adapter *a = db_mysql_adapter_handle(self);
294
+ if (a->connection) {
295
+ mysql_close(a->connection);
296
+ a->connection = 0;
297
+ return Qtrue;
298
+ }
299
+ return Qfalse;
300
+ }
301
+
302
+ VALUE db_mysql_adapter_closed_q(VALUE self) {
303
+ Adapter *a = db_mysql_adapter_handle(self);
304
+ return a->connection ? Qfalse : Qtrue;
305
+ }
306
+
307
+ VALUE db_mysql_adapter_prepare(VALUE self, VALUE sql) {
308
+ return db_mysql_statement_initialize(db_mysql_statement_allocate(cDMS), self, sql);
309
+ }
310
+
311
+ VALUE db_mysql_adapter_escape(VALUE self, VALUE fragment) {
312
+ VALUE text = TO_S(fragment);
313
+ char escaped[RSTRING_LEN(text) * 2 + 1];
314
+ Adapter *a = db_mysql_adapter_handle_safe(self);
315
+ mysql_real_escape_string(a->connection, escaped, RSTRING_PTR(text), RSTRING_LEN(text));
316
+ return rb_str_new2(escaped);
317
+ }
318
+
319
+ VALUE db_mysql_adapter_fileno(VALUE self) {
320
+ Adapter *a = db_mysql_adapter_handle_safe(self);
321
+ return INT2NUM(a->connection->net.fd);
322
+ }
323
+
324
+ VALUE db_mysql_adapter_query(int argc, VALUE *argv, VALUE self) {
325
+ VALUE sql, bind, result;
326
+ MYSQL_RES *r;
327
+ Adapter *a = db_mysql_adapter_handle_safe(self);
328
+ MYSQL *c = a->connection;
329
+
330
+ rb_scan_args(argc, argv, "10*", &sql, &bind);
331
+ sql = TO_S(sql);
332
+
333
+ if (RARRAY_LEN(bind) > 0)
334
+ sql = db_mysql_bind_sql(self, sql, bind);
335
+
336
+ mysql_send_query(c, RSTRING_PTR(sql), RSTRING_LEN(sql));
337
+
338
+ if (rb_block_given_p()) {
339
+ rb_thread_wait_fd(a->connection->net.fd);
340
+ if (mysql_read_query_result(c) != 0)
341
+ rb_raise(eSwiftRuntimeError, "%s", mysql_error(c));
342
+
343
+ r = mysql_store_result(c);
344
+ result = db_mysql_result_load(db_mysql_result_allocate(cDMR), r, mysql_insert_id(c), mysql_affected_rows(c));
345
+ return db_mysql_result_each(result);
346
+ }
347
+
348
+ return Qtrue;
349
+ }
350
+
351
+ VALUE db_mysql_adapter_result(VALUE self) {
352
+ MYSQL_RES *r;
353
+ Adapter *a = db_mysql_adapter_handle_safe(self);
354
+ MYSQL *c = a->connection;
355
+
356
+ if (mysql_read_query_result(c) != 0)
357
+ rb_raise(eSwiftRuntimeError, "%s", mysql_error(c));
358
+
359
+ r = mysql_store_result(c);
360
+ return db_mysql_result_load(db_mysql_result_allocate(cDMR), r, mysql_insert_id(c), mysql_affected_rows(c));
361
+ }
362
+
363
+ VALUE db_mysql_adapter_write(int argc, VALUE *argv, VALUE self) {
364
+ VALUE table, fields, io, data;
365
+
366
+ char *sql;
367
+ Adapter *a = db_mysql_adapter_handle_safe(self);
368
+ MYSQL *c = a->connection;
369
+
370
+ if (argc < 2 || argc > 3)
371
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 2..3)", argc);
372
+
373
+ table = fields = io = Qnil;
374
+ switch (argc) {
375
+ case 2:
376
+ table = argv[0];
377
+ io = argv[1];
378
+ break;
379
+ case 3:
380
+ table = argv[0];
381
+ fields = argv[1];
382
+ io = argv[2];
383
+ if (TYPE(fields) != T_ARRAY)
384
+ rb_raise(eSwiftArgumentError, "fields needs to be an array");
385
+ if (RARRAY_LEN(fields) < 1)
386
+ fields = Qnil;
387
+ }
388
+
389
+ if (argc > 1) {
390
+ sql = (char *)malloc(4096);
391
+ if (NIL_P(fields))
392
+ snprintf(sql, 4096, "load data local infile 'swift' replace into table %s", CSTRING(table));
393
+ else
394
+ snprintf(sql, 4096, "load data local infile 'swift' replace into table %s(%s)",
395
+ CSTRING(table), CSTRING(rb_ary_join(fields, rb_str_new2(", "))));
396
+
397
+ a->io = rb_respond_to(io, rb_intern("read")) ? io : rb_funcall(cStringIO, rb_intern("new"), 1, TO_S(io));
398
+ rb_gc_mark(a->io);
399
+ if (mysql_real_query(a->connection, sql, strlen(sql)) != 0) {
400
+ free(sql);
401
+ a->io = Qnil;
402
+ rb_raise(eSwiftRuntimeError, "%s", mysql_error(a->connection));
403
+ }
404
+
405
+ free(sql);
406
+ }
407
+
408
+ return db_mysql_result_load(db_mysql_result_allocate(cDMR), 0, mysql_insert_id(c), mysql_affected_rows(c));
409
+ }
410
+
411
+ void init_swift_db_mysql_adapter() {
412
+ rb_require("etc");
413
+ sUser = rb_funcall(CONST_GET(rb_mKernel, "Etc"), rb_intern("getlogin"), 0);
414
+ cDMA = rb_define_class_under(mDB, "Mysql", rb_cObject);
415
+
416
+ rb_define_alloc_func(cDMA, db_mysql_adapter_allocate);
417
+
418
+ rb_define_method(cDMA, "initialize", db_mysql_adapter_initialize, 1);
419
+ rb_define_method(cDMA, "execute", db_mysql_adapter_execute, -1);
420
+ rb_define_method(cDMA, "prepare", db_mysql_adapter_prepare, 1);
421
+ rb_define_method(cDMA, "begin", db_mysql_adapter_begin, -1);
422
+ rb_define_method(cDMA, "commit", db_mysql_adapter_commit, -1);
423
+ rb_define_method(cDMA, "rollback", db_mysql_adapter_rollback, -1);
424
+ rb_define_method(cDMA, "transaction", db_mysql_adapter_transaction, -1);
425
+ rb_define_method(cDMA, "close", db_mysql_adapter_close, 0);
426
+ rb_define_method(cDMA, "closed?", db_mysql_adapter_closed_q, 0);
427
+ rb_define_method(cDMA, "escape", db_mysql_adapter_escape, 1);
428
+ rb_define_method(cDMA, "fileno", db_mysql_adapter_fileno, 0);
429
+ rb_define_method(cDMA, "query", db_mysql_adapter_query, -1);
430
+ rb_define_method(cDMA, "result", db_mysql_adapter_result, 0);
431
+ rb_define_method(cDMA, "write", db_mysql_adapter_write, -1);
432
+
433
+ rb_global_variable(&sUser);
434
+ }