Compare commits

...

24 Commits
master ... stmt

Author SHA1 Message Date
Brian Lopez f6d7e877b2 Merge branch 'master' into stmt
* master:
  only use wait_timeout if it's a Fixnum
  update gemspec from file updates
  no need to invalidate the FD now that we're only modifying it once, in one place
  no need to check if the FD is >= 0 now that we centralized the close/free logic. Once closed, none of that code will run again
  wording fix in readme
2010-10-18 12:15:35 -07:00
Brian Lopez 6c4fd8a9ca merging in latest from master 2010-10-17 17:43:24 -07:00
Brian Lopez 4a5fcbae09 Merge branch 'master' into stmt 2010-10-15 22:38:06 -07:00
Brian Lopez ddcd1064cd Merge branch 'master' into stmt
* master:
  avoid potential race-condition with closing a connection
  add option for setting the wait_timeout in the AR adapter (this can be done in database.yml)
  add some more defaults to the connect flags
  add connect_flags to default options and add REMEMBER_OPTIONS to that list. fix NUM2INT to be NUM2ULONG as it should be for flags
  free the client after close if we can
  forgot to remove this
  get rid of double-pointer casting
  a couple of minor updates to connection management with some specs
  check for error from mysql_affected_rows call
  change connection check symantecs
2010-10-15 16:19:11 -07:00
Brian Lopez 7b6d210c97 Merge branch 'master' into stmt
* master:
  Detach before executing callbacks.
  make sure we don't hit a race condition if this EM spec is taking longer to run than normal
  was in a hurry earlier
  whoops, lost this line in a previous patch
  Dry windows configuration options
  Inject 1.8/1.9 pure-ruby entry point during xcompile
  Use MySQL 5.1.51 now from available mirror
2010-10-05 17:14:45 -07:00
Brian Lopez 39a28c4a91 Merge branch 'master' into stmt 2010-09-24 14:39:21 -07:00
Brian Lopez 39ea9c6bdb Merge branch 'master' into stmt 2010-08-20 20:24:37 -07:00
Brian Lopez 9a84f88d90 Merge branch 'master' into stmt 2010-08-20 10:07:26 -07:00
Aaron Patterson a684efd696 xfree or die hard 2010-08-20 09:16:22 -07:00
Brian Lopez 731b456862 bring over latest from master 2010-08-16 02:10:10 -07:00
Brian Lopez 8aa1e9bb65 Merge branch 'master' into stmt
* master:
  remove Sequel adapter as it's now in Sequel core :)
  move -Wextra to development flags area
  update AR adapter to reflect timezone setting update
  application_timezone is allowed to be nil
  default application_timezone to nil
  sync up with sequel adapter from my Sequel fork until it's officially merged in
  convert :timezone option into two new ones :database_timezone - the timezone (:utc or :local) Mysql2 will assume time/datetime fields are stored in the db. This modifies what initial timezone your Time objects will be in when creating them from libmysql in C and :application_timezone - the timezone (:utc or :local) you'd finally like the Time objects converted to before you get them
  can't call literal here because it'll try to join it's own thread
  Mysql2::Client uses the :username key, set it to :user if that was used instead
  heh
  fix typo in comment
  major refactor of Sequel adapter - it's now green in Sequel
  add :cast_booleans option for automatically casting tinyint(1) fields into true/false for ruby
  move most previously global symbols to static to prevent conflicts (thanks for catching this Eric)
  respect :symbolize_keys option for Mysql2::Result#fields if it's called before the first row is built
  initialize @active early on to prevent warnings later
  let's try that again - libmysql only allows one query be sent at a time per connection, bail early if that's attempted
  Revert "libmysql only allows one query be sent at a time per connection, bail early if that's attempted"
  libmysql only allows one query be sent at a time per connection, bail early if that's attempted
  no need to carry over options twice as we're already doing it up in rb_mysql_client_async_result
2010-08-06 12:47:50 -07:00
Brian Lopez ee4fd98611 Merge branch 'master' into stmt 2010-08-02 10:04:07 -07:00
Brian Lopez d20bc8dc8b bring in latest from master (specifically the split out of Mysql2::Result into it's own C file) 2010-07-30 13:58:48 -07:00
Aaron Patterson c7515a3e46 Merge branch 'master' into stmt
* master:
  check for and support field-level encodings
  on second thought, we should make sure we were given a string earlier on
  no need to Check_Type in these spots since we're using StringValuePtr as well
2010-07-12 08:45:50 -07:00
Aaron Patterson 45adf1e23b prepared statements can pull times 2010-07-09 11:11:31 -07:00
Aaron Patterson d510ff675e returning ints from prepared statements works 2010-07-09 11:11:31 -07:00
Aaron Patterson 9b1789fbfc adding Stmt#fields 2010-07-09 11:11:31 -07:00
Aaron Patterson 27cd68f456 adding a gdb rake task 2010-07-09 11:11:31 -07:00
Aaron Patterson 6e38bff28d adding a class to wrap fields 2010-07-09 11:11:31 -07:00
Aaron Patterson a3cdd92a9c execute raises an exception on error 2010-07-09 11:11:31 -07:00
Aaron Patterson 7e95b543c9 execute is defined on statmenet 2010-07-09 11:11:31 -07:00
Aaron Patterson 2c86f9d72a prepared statements will tell us the field count 2010-07-09 11:11:31 -07:00
Aaron Patterson a5d8a087a7 statements can count parameters 2010-07-09 11:11:31 -07:00
Aaron Patterson 2ad51dcac2 we can prepare statements! 2010-07-09 11:11:31 -07:00
8 changed files with 341 additions and 0 deletions

View File

@ -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);

View File

@ -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();
}

View File

@ -28,5 +28,6 @@
#include <client.h>
#include <result.h>
#include <statement.h>
#endif

203
ext/mysql2/statement.c Normal file
View File

@ -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);
}

8
ext/mysql2/statement.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef MYSQL2_STATEMENT_H
#define MYSQL2_STATEMENT_H
extern VALUE cMysql2Statement;
void init_mysql2_statement();
#endif

View File

@ -7,6 +7,7 @@ require 'mysql2/error'
require 'mysql2/mysql2'
require 'mysql2/client'
require 'mysql2/result'
require 'mysql2/field'
# = Mysql2
#

4
lib/mysql2/field.rb Normal file
View File

@ -0,0 +1,4 @@
module Mysql2
class Field < Struct.new(:name, :type)
end
end

View File

@ -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