Compare commits
24 Commits
Author | SHA1 | Date |
---|---|---|
Brian Lopez | f6d7e877b2 | |
Brian Lopez | 6c4fd8a9ca | |
Brian Lopez | 4a5fcbae09 | |
Brian Lopez | ddcd1064cd | |
Brian Lopez | 7b6d210c97 | |
Brian Lopez | 39a28c4a91 | |
Brian Lopez | 39ea9c6bdb | |
Brian Lopez | 9a84f88d90 | |
Aaron Patterson | a684efd696 | |
Brian Lopez | 731b456862 | |
Brian Lopez | 8aa1e9bb65 | |
Brian Lopez | ee4fd98611 | |
Brian Lopez | d20bc8dc8b | |
Aaron Patterson | c7515a3e46 | |
Aaron Patterson | 45adf1e23b | |
Aaron Patterson | d510ff675e | |
Aaron Patterson | 9b1789fbfc | |
Aaron Patterson | 27cd68f456 | |
Aaron Patterson | 6e38bff28d | |
Aaron Patterson | a3cdd92a9c | |
Aaron Patterson | 7e95b543c9 | |
Aaron Patterson | 2c86f9d72a | |
Aaron Patterson | a5d8a087a7 | |
Aaron Patterson | 2ad51dcac2 |
|
@ -516,6 +516,20 @@ static VALUE init_connection(VALUE self) {
|
|||
return self;
|
||||
}
|
||||
|
||||
/* call-seq: client.create_statement # => Mysql2::Statement
|
||||
*
|
||||
* Create a new prepared statement.
|
||||
*/
|
||||
static VALUE create_statement(VALUE self) {
|
||||
MYSQL * client;
|
||||
MYSQL_STMT * stmt;
|
||||
|
||||
Data_Get_Struct(self, MYSQL, client);
|
||||
stmt = mysql_stmt_init(client);
|
||||
|
||||
return Data_Wrap_Struct(cMysql2Statement, 0, mysql_stmt_close, stmt);
|
||||
}
|
||||
|
||||
void init_mysql2_client() {
|
||||
cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
|
||||
|
||||
|
@ -530,6 +544,7 @@ void init_mysql2_client() {
|
|||
rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
|
||||
rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
|
||||
rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
|
||||
rb_define_method(cMysql2Client, "create_statement", create_statement, 0);
|
||||
|
||||
rb_define_private_method(cMysql2Client, "reconnect=", set_reconnect, 1);
|
||||
rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1);
|
||||
|
|
|
@ -2,6 +2,21 @@
|
|||
|
||||
VALUE mMysql2, cMysql2Error;
|
||||
|
||||
/* call-seq: client.create_statement # => Mysql2::Statement
|
||||
*
|
||||
* Create a new prepared statement.
|
||||
*/
|
||||
static VALUE create_statement(VALUE self)
|
||||
{
|
||||
MYSQL * client;
|
||||
MYSQL_STMT * stmt;
|
||||
|
||||
Data_Get_Struct(self, MYSQL, client);
|
||||
stmt = mysql_stmt_init(client);
|
||||
|
||||
return Data_Wrap_Struct(cMysql2Statement, 0, mysql_stmt_close, stmt);
|
||||
}
|
||||
|
||||
/* Ruby Extension initializer */
|
||||
void Init_mysql2() {
|
||||
mMysql2 = rb_define_module("Mysql2");
|
||||
|
@ -9,4 +24,5 @@ void Init_mysql2() {
|
|||
|
||||
init_mysql2_client();
|
||||
init_mysql2_result();
|
||||
init_mysql2_statement();
|
||||
}
|
||||
|
|
|
@ -28,5 +28,6 @@
|
|||
|
||||
#include <client.h>
|
||||
#include <result.h>
|
||||
#include <statement.h>
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
#include <mysql2_ext.h>
|
||||
|
||||
VALUE cMysql2Statement;
|
||||
extern VALUE mMysql2, cMysql2Error;
|
||||
|
||||
/* call-seq: stmt.prepare(sql)
|
||||
*
|
||||
* Prepare +sql+ for execution
|
||||
*/
|
||||
static VALUE prepare(VALUE self, VALUE sql)
|
||||
{
|
||||
MYSQL_STMT * stmt;
|
||||
Data_Get_Struct(self, MYSQL_STMT, stmt);
|
||||
|
||||
if(mysql_stmt_prepare(stmt, StringValuePtr(sql), RSTRING_LEN(sql))) {
|
||||
rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt));
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/* call-seq: stmt.param_count # => 2
|
||||
*
|
||||
* Returns the number of parameters the prepared statement expects.
|
||||
*/
|
||||
static VALUE param_count(VALUE self)
|
||||
{
|
||||
MYSQL_STMT * stmt;
|
||||
Data_Get_Struct(self, MYSQL_STMT, stmt);
|
||||
|
||||
return ULL2NUM(mysql_stmt_param_count(stmt));
|
||||
}
|
||||
|
||||
/* call-seq: stmt.field_count # => 2
|
||||
*
|
||||
* Returns the number of fields the prepared statement returns.
|
||||
*/
|
||||
static VALUE field_count(VALUE self)
|
||||
{
|
||||
MYSQL_STMT * stmt;
|
||||
Data_Get_Struct(self, MYSQL_STMT, stmt);
|
||||
|
||||
return UINT2NUM(mysql_stmt_field_count(stmt));
|
||||
}
|
||||
|
||||
/* call-seq: stmt.execute
|
||||
*
|
||||
* Executes the current prepared statement, returns +stmt+.
|
||||
*/
|
||||
static VALUE execute(VALUE self)
|
||||
{
|
||||
MYSQL_STMT * stmt;
|
||||
Data_Get_Struct(self, MYSQL_STMT, stmt);
|
||||
|
||||
if(mysql_stmt_execute(stmt))
|
||||
rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt));
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/* call-seq: stmt.fields -> array
|
||||
*
|
||||
* Returns a list of fields that will be returned by this statement.
|
||||
*/
|
||||
static VALUE fields(VALUE self)
|
||||
{
|
||||
MYSQL_STMT * stmt;
|
||||
MYSQL_FIELD * fields;
|
||||
MYSQL_RES * metadata;
|
||||
unsigned int field_count;
|
||||
unsigned int i;
|
||||
VALUE field_list;
|
||||
VALUE cMysql2Field;
|
||||
|
||||
Data_Get_Struct(self, MYSQL_STMT, stmt);
|
||||
metadata = mysql_stmt_result_metadata(stmt);
|
||||
fields = mysql_fetch_fields(metadata);
|
||||
field_count = mysql_stmt_field_count(stmt);
|
||||
field_list = rb_ary_new2((long)field_count);
|
||||
|
||||
cMysql2Field = rb_const_get(mMysql2, rb_intern("Field"));
|
||||
|
||||
for(i = 0; i < field_count; i++) {
|
||||
VALUE argv[2];
|
||||
VALUE field;
|
||||
|
||||
/* FIXME: encoding. Also, can this return null? */
|
||||
argv[0] = rb_str_new2(fields[i].name);
|
||||
argv[1] = INT2NUM(fields[i].type);
|
||||
|
||||
field = rb_class_new_instance(2, argv, cMysql2Field);
|
||||
|
||||
rb_ary_store(field_list, (long)i, field);
|
||||
}
|
||||
|
||||
return field_list;
|
||||
}
|
||||
|
||||
static VALUE each(VALUE self)
|
||||
{
|
||||
MYSQL_STMT * stmt;
|
||||
MYSQL_FIELD * fields;
|
||||
MYSQL_RES * metadata;
|
||||
|
||||
MYSQL_BIND * binds;
|
||||
my_bool * is_null;
|
||||
my_bool * error;
|
||||
unsigned long * length;
|
||||
int int_data;
|
||||
MYSQL_TIME ts;
|
||||
|
||||
unsigned int field_count;
|
||||
unsigned int i;
|
||||
VALUE block;
|
||||
|
||||
Data_Get_Struct(self, MYSQL_STMT, stmt);
|
||||
|
||||
block = rb_block_proc();
|
||||
metadata = mysql_stmt_result_metadata(stmt);
|
||||
fields = mysql_fetch_fields(metadata);
|
||||
field_count = mysql_stmt_field_count(stmt);
|
||||
|
||||
binds = xcalloc(field_count, sizeof(MYSQL_BIND));
|
||||
is_null = xcalloc(field_count, sizeof(my_bool));
|
||||
error = xcalloc(field_count, sizeof(my_bool));
|
||||
length = xcalloc(field_count, sizeof(unsigned long));
|
||||
|
||||
for(i = 0; i < field_count; i++) {
|
||||
switch(fields[i].type) {
|
||||
case MYSQL_TYPE_LONGLONG:
|
||||
binds[i].buffer_type = MYSQL_TYPE_LONG;
|
||||
binds[i].buffer = (char *)&int_data;
|
||||
break;
|
||||
case MYSQL_TYPE_DATETIME:
|
||||
binds[i].buffer_type = MYSQL_TYPE_DATETIME;
|
||||
binds[i].buffer = (char *)&ts;
|
||||
break;
|
||||
default:
|
||||
rb_raise(cMysql2Error, "unhandled mysql type: %d", fields[i].type);
|
||||
}
|
||||
|
||||
binds[i].is_null = &is_null[i];
|
||||
binds[i].length = &length[i];
|
||||
binds[i].error = &error[i];
|
||||
}
|
||||
|
||||
if(mysql_stmt_bind_result(stmt, binds)) {
|
||||
xfree(binds);
|
||||
xfree(is_null);
|
||||
xfree(error);
|
||||
xfree(length);
|
||||
rb_raise(cMysql2Error, "%s", mysql_stmt_error(stmt));
|
||||
}
|
||||
|
||||
while(!mysql_stmt_fetch(stmt)) {
|
||||
VALUE row = rb_ary_new2((long)field_count);
|
||||
|
||||
for(i = 0; i < field_count; i++) {
|
||||
VALUE column = Qnil;
|
||||
switch(binds[i].buffer_type) {
|
||||
case MYSQL_TYPE_LONG:
|
||||
column = INT2NUM(int_data);
|
||||
break;
|
||||
/* FIXME: maybe we want to return a datetime in this case? */
|
||||
case MYSQL_TYPE_DATETIME:
|
||||
column = rb_funcall(rb_cTime,
|
||||
rb_intern("mktime"), 6,
|
||||
UINT2NUM(ts.year),
|
||||
UINT2NUM(ts.month),
|
||||
UINT2NUM(ts.day),
|
||||
UINT2NUM(ts.hour),
|
||||
UINT2NUM(ts.minute),
|
||||
UINT2NUM(ts.second));
|
||||
break;
|
||||
default:
|
||||
rb_raise(cMysql2Error, "unhandled buffer type: %d",
|
||||
binds[i].buffer_type);
|
||||
break;
|
||||
}
|
||||
rb_ary_store(row, (long)i, column);
|
||||
}
|
||||
rb_yield(row);
|
||||
}
|
||||
|
||||
xfree(binds);
|
||||
xfree(is_null);
|
||||
xfree(error);
|
||||
xfree(length);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void init_mysql2_statement()
|
||||
{
|
||||
cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
|
||||
|
||||
rb_define_method(cMysql2Statement, "prepare", prepare, 1);
|
||||
rb_define_method(cMysql2Statement, "param_count", param_count, 0);
|
||||
rb_define_method(cMysql2Statement, "field_count", field_count, 0);
|
||||
rb_define_method(cMysql2Statement, "execute", execute, 0);
|
||||
rb_define_method(cMysql2Statement, "each", each, 0);
|
||||
rb_define_method(cMysql2Statement, "fields", fields, 0);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef MYSQL2_STATEMENT_H
|
||||
#define MYSQL2_STATEMENT_H
|
||||
|
||||
extern VALUE cMysql2Statement;
|
||||
|
||||
void init_mysql2_statement();
|
||||
|
||||
#endif
|
|
@ -7,6 +7,7 @@ require 'mysql2/error'
|
|||
require 'mysql2/mysql2'
|
||||
require 'mysql2/client'
|
||||
require 'mysql2/result'
|
||||
require 'mysql2/field'
|
||||
|
||||
# = Mysql2
|
||||
#
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
module Mysql2
|
||||
class Field < Struct.new(:name, :type)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,93 @@
|
|||
# encoding: UTF-8
|
||||
require 'spec_helper'
|
||||
|
||||
describe Mysql2::Statement do
|
||||
before :each do
|
||||
@client = Mysql2::Client.new :host => "localhost", :username => "root"
|
||||
end
|
||||
|
||||
it "should create a statement" do
|
||||
stmt = @client.create_statement
|
||||
stmt.should be_kind_of Mysql2::Statement
|
||||
end
|
||||
|
||||
it "prepares some sql" do
|
||||
stmt = @client.create_statement
|
||||
lambda { stmt.prepare 'SELECT 1' }.should_not raise_error
|
||||
end
|
||||
|
||||
it "return self when prepare some sql" do
|
||||
stmt = @client.create_statement
|
||||
stmt.prepare('SELECT 1').should == stmt
|
||||
end
|
||||
|
||||
it "should raise an exception when server disconnects" do
|
||||
stmt = @client.create_statement
|
||||
@client.close
|
||||
lambda { stmt.prepare 'SELECT 1' }.should raise_error(Mysql2::Error)
|
||||
end
|
||||
|
||||
it "should tell us the param count" do
|
||||
stmt = @client.create_statement
|
||||
stmt.prepare 'SELECT ?, ?'
|
||||
stmt.param_count.should == 2
|
||||
|
||||
stmt.prepare 'SELECT 1'
|
||||
stmt.param_count.should == 0
|
||||
end
|
||||
|
||||
it "should tell us the field count" do
|
||||
stmt = @client.create_statement
|
||||
stmt.prepare 'SELECT ?, ?'
|
||||
stmt.field_count.should == 2
|
||||
|
||||
stmt.prepare 'SELECT 1'
|
||||
stmt.field_count.should == 1
|
||||
end
|
||||
|
||||
it "should let us execute our statement" do
|
||||
stmt = @client.create_statement
|
||||
stmt.prepare 'SELECT 1'
|
||||
stmt.execute.should == stmt
|
||||
end
|
||||
|
||||
it "should raise an exception on error" do
|
||||
stmt = @client.create_statement
|
||||
lambda { stmt.execute }.should raise_error(Mysql2::Error)
|
||||
end
|
||||
|
||||
it "should raise an exception without a block" do
|
||||
stmt = @client.create_statement
|
||||
stmt.prepare 'SELECT 1'
|
||||
stmt.execute
|
||||
lambda { stmt.each }.should raise_error
|
||||
end
|
||||
|
||||
it "should let us iterate over results" do
|
||||
stmt = @client.create_statement
|
||||
stmt.prepare 'SELECT 1'
|
||||
stmt.execute
|
||||
rows = []
|
||||
stmt.each { |row| rows << row }
|
||||
rows.should == [[1]]
|
||||
end
|
||||
|
||||
it "should select dates" do
|
||||
stmt = @client.create_statement
|
||||
stmt.prepare 'SELECT NOW()'
|
||||
stmt.execute
|
||||
rows = []
|
||||
stmt.each { |row| rows << row }
|
||||
rows.first.first.should be_kind_of Time
|
||||
end
|
||||
|
||||
it "should tell us about the fields" do
|
||||
stmt = @client.create_statement
|
||||
stmt.prepare 'SELECT 1 as foo, 2'
|
||||
stmt.execute
|
||||
list = stmt.fields
|
||||
list.length.should == 2
|
||||
list.first.name.should == 'foo'
|
||||
list[1].name.should == '2'
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue