Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 36 additions & 20 deletions ext/tiny_tds/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@

VALUE cTinyTdsClient;
extern VALUE mTinyTds, cTinyTdsError;
static ID sym_username, sym_password, sym_dataserver, sym_database, sym_appname, sym_tds_version, sym_login_timeout, sym_timeout, sym_encoding, sym_azure, sym_contained, sym_use_utf16;
static ID sym_username, sym_password, sym_dataserver, sym_database, sym_appname, sym_tds_version, sym_login_timeout, sym_timeout, sym_encoding, sym_azure, sym_contained, sym_use_utf16, sym_message_handler;
static ID intern_source_eql, intern_severity_eql, intern_db_error_number_eql, intern_os_error_number_eql;
static ID intern_new, intern_dup, intern_transpose_iconv_encoding, intern_local_offset, intern_gsub;
static ID intern_new, intern_dup, intern_transpose_iconv_encoding, intern_local_offset, intern_gsub, intern_call;
VALUE opt_escape_regex, opt_escape_dblquote;
VALUE message_handler;


// Lib Macros
Expand Down Expand Up @@ -41,6 +42,15 @@ VALUE rb_tinytds_raise_error(DBPROCESS *dbproc, int cancel, const char *error, c
rb_funcall(e, intern_db_error_number_eql, 1, INT2FIX(dberr));
if (oserr)
rb_funcall(e, intern_os_error_number_eql, 1, INT2FIX(oserr));

if (severity <= 10) {
if (message_handler && message_handler != Qnil && rb_respond_to(message_handler, intern_call) != 0) {
rb_funcall(message_handler, intern_call, 1, e);
}

return Qnil;
}

rb_exc_raise(e);
return Qnil;
}
Expand Down Expand Up @@ -128,25 +138,27 @@ int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, c
int tinytds_msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, char *procname, int line) {
static const char *source = "message";
GET_CLIENT_USERDATA(dbproc);
if (severity > 10) {
// See tinytds_err_handler() for info about why we do this
if (userdata && userdata->nonblocking) {
if (!userdata->nonblocking_error.is_set) {
userdata->nonblocking_error.cancel = 1;
strncpy(userdata->nonblocking_error.error, msgtext, ERROR_MSG_SIZE);
strncpy(userdata->nonblocking_error.source, source, ERROR_MSG_SIZE);
userdata->nonblocking_error.severity = severity;
userdata->nonblocking_error.dberr = msgno;
userdata->nonblocking_error.oserr = msgstate;
userdata->nonblocking_error.is_set = 1;
}
if (!dbdead(dbproc) && !userdata->closed) {
dbcancel(dbproc);
userdata->dbcancel_sent = 1;
}
} else {
rb_tinytds_raise_error(dbproc, 1, msgtext, source, severity, msgno, msgstate);

int is_error = severity > 10 ? 1 : 0;

// See tinytds_err_handler() for info about why we do this
if (userdata && userdata->nonblocking) {
if (!userdata->nonblocking_error.is_set) {
userdata->nonblocking_error.cancel = is_error;
strncpy(userdata->nonblocking_error.error, msgtext, ERROR_MSG_SIZE);
strncpy(userdata->nonblocking_error.source, source, ERROR_MSG_SIZE);
userdata->nonblocking_error.severity = severity;
userdata->nonblocking_error.dberr = msgno;
userdata->nonblocking_error.oserr = msgstate;
userdata->nonblocking_error.is_set = 1;
}

if (is_error && !dbdead(dbproc) && !userdata->closed) {
dbcancel(dbproc);
userdata->dbcancel_sent = 1;
}
} else {
rb_tinytds_raise_error(dbproc, is_error, msgtext, source, severity, msgno, msgstate);
}
return 0;
}
Expand Down Expand Up @@ -308,6 +320,7 @@ static VALUE rb_tinytds_connect(VALUE self, VALUE opts) {
azure = rb_hash_aref(opts, sym_azure);
contained = rb_hash_aref(opts, sym_contained);
use_utf16 = rb_hash_aref(opts, sym_use_utf16);
message_handler = rb_hash_aref(opts, sym_message_handler);
/* Dealing with options. */
if (dbinit() == FAIL) {
rb_raise(cTinyTdsError, "failed dbinit() function");
Expand Down Expand Up @@ -352,6 +365,7 @@ static VALUE rb_tinytds_connect(VALUE self, VALUE opts) {
rb_warn("TinyTds: :use_utf16 option not supported in this version of FreeTDS.\n");
}
#endif

cwrap->client = dbopen(cwrap->login, StringValueCStr(dataserver));
if (cwrap->client) {
VALUE transposed_encoding;
Expand Down Expand Up @@ -410,6 +424,7 @@ void init_tinytds_client() {
sym_azure = ID2SYM(rb_intern("azure"));
sym_contained = ID2SYM(rb_intern("contained"));
sym_use_utf16 = ID2SYM(rb_intern("use_utf16"));
sym_message_handler = ID2SYM(rb_intern("message_handler"));
/* Intern TinyTds::Error Accessors */
intern_source_eql = rb_intern("source=");
intern_severity_eql = rb_intern("severity=");
Expand All @@ -421,6 +436,7 @@ void init_tinytds_client() {
intern_transpose_iconv_encoding = rb_intern("transpose_iconv_encoding");
intern_local_offset = rb_intern("local_offset");
intern_gsub = rb_intern("gsub");
intern_call = rb_intern("call");
/* Escape Regexp Global */
opt_escape_regex = rb_funcall(rb_cRegexp, intern_new, 1, rb_str_new2("\\\'"));
opt_escape_dblquote = rb_str_new2("''");
Expand Down
7 changes: 7 additions & 0 deletions lib/tiny_tds/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Client
}

attr_reader :query_options
attr_reader :message_handler

class << self

Expand Down Expand Up @@ -37,6 +38,12 @@ def initialize(opts = {})
if opts[:dataserver].to_s.empty? && opts[:host].to_s.empty?
raise ArgumentError, 'missing :host option if no :dataserver given'
end

@message_handler = opts[:message_handler] || (block_given? ? Proc.new : nil)
if @message_handler && !@message_handler.respond_to?(:call)
raise ArgumentError, ':message_handler must implement `call` (eg, a Proc or a Method)'
end

opts[:username] = parse_username(opts)
@query_options = self.class.default_query_options.dup
opts[:password] = opts[:password].to_s if opts[:password] && opts[:password].to_s.strip != ''
Expand Down
2 changes: 1 addition & 1 deletion test/client_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class ClientTest < TinyTds::TestCase
end

it 'must be able to use :host/:port connection' do
host = ENV['TINYTDS_UNIT_HOST_TEST'] || ENV['TINYTDS_UNIT_HOST']
host = ENV['TINYTDS_UNIT_HOST_TEST'] || ENV['TINYTDS_UNIT_HOST'] || 'localhost'
port = ENV['TINYTDS_UNIT_PORT_TEST'] || ENV['TINYTDS_UNIT_PORT'] || 1433
begin
client = new_connection dataserver: nil, host: host, port: port
Expand Down
30 changes: 30 additions & 0 deletions test/result_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,36 @@ class ResultTest < TinyTds::TestCase

if sqlserver?

describe 'using :message_handler' do
messages = []

before do
close_client
@client = new_connection message_handler: Proc.new { |m| messages << m }
end

after do
messages.clear
end

it 'calls the provided message handler when severity is 10 or less' do
(1..10).to_a.each do |severity|
messages.clear

msg = "Test #{severity} severity"
state = rand(1..255)
@client.execute("RAISERROR(N'#{msg}', #{severity}, #{state})").do

m = messages.first
assert_equal 1, messages.length, 'there should be one message after one raiserror'
assert_equal msg, m.message, 'message text'
assert_equal severity, m.severity, 'message severity' unless severity == 10 && m.severity.to_i == 0
assert_equal state, m.os_error_number, 'message state'
end
end

end

it 'must not raise an error when severity is 10 or less' do
(1..10).to_a.each do |severity|
@client.execute("RAISERROR(N'Test #{severity} severity', #{severity}, 1)").do
Expand Down
4 changes: 2 additions & 2 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ def connection_options(options={})
username = (sqlserver_azure? ? ENV['TINYTDS_UNIT_AZURE_USER'] : ENV['TINYTDS_UNIT_USER']) || 'tinytds'
password = (sqlserver_azure? ? ENV['TINYTDS_UNIT_AZURE_PASS'] : ENV['TINYTDS_UNIT_PASS']) || ''
{ :dataserver => sqlserver_azure? ? nil : ENV['TINYTDS_UNIT_DATASERVER'],
:host => ENV['TINYTDS_UNIT_HOST'],
:port => ENV['TINYTDS_UNIT_PORT'],
:host => ENV['TINYTDS_UNIT_HOST'] || 'localhost',
:port => ENV['TINYTDS_UNIT_PORT'] || '1433',
:tds_version => ENV['TINYTDS_UNIT_VERSION'],
:username => username,
:password => password,
Expand Down