#include "result.h" #include VALUE cBigDecimal; VALUE cStringIO; VALUE cSwiftResult; ID fnew, fto_date, fload; void result_mark(ResultWrapper *handle) { if (handle) rb_gc_mark(handle->adapter); } void result_free(ResultWrapper *handle) { if (handle) { if (handle->free) { handle->result->cleanup(); delete handle->result; } delete handle; } } VALUE result_alloc(VALUE klass) { ResultWrapper *handle = 0; return Data_Wrap_Struct(klass, result_mark, result_free, handle); } VALUE result_wrap_handle(VALUE klass, VALUE adapter, dbi::AbstractResult *result, bool free) { ResultWrapper *handle = new ResultWrapper; handle->result = result; handle->adapter = adapter; handle->free = free; VALUE obj = Data_Wrap_Struct(klass, result_mark, result_free, handle); if (!NIL_P(adapter)) rb_iv_set(obj, "@timezone", rb_iv_get(adapter, "@timezone")); return obj; } dbi::AbstractResult* result_handle(VALUE self) { ResultWrapper *handle; Data_Get_Struct(self, ResultWrapper, handle); if (!handle) rb_raise(eSwiftRuntimeError, "Invalid object, did you forget to call #super?"); return handle->result; } // TODO: static VALUE result_clone(VALUE self) { rb_raise(eSwiftRuntimeError, "clone is not allowed."); } // TODO: static VALUE result_dup(VALUE self) { rb_raise(eSwiftRuntimeError, "dup is not allowed."); } VALUE result_each(VALUE self) { uint64_t length; const char *data, *tzstring; dbi::AbstractResult *result = result_handle(self); VALUE scheme = rb_iv_get(self, "@scheme"); VALUE timezone = rb_iv_get(self, "@timezone"); tzstring = NIL_P(timezone) ? 0 : CSTRING(timezone); try { std::vector result_fields = result->fields(); std::vector result_types = result->types(); std::vector fields; for (uint32_t i = 0; i < result_fields.size(); i++) fields.push_back(ID2SYM(rb_intern(result_fields[i].c_str()))); result->seek(0); for (uint32_t row = 0; row < result->rows(); row++) { VALUE tuple = rb_hash_new(); for (uint32_t column = 0; column < result->columns(); column++) { data = (const char*)result->read(row, column, &length); if (data) { rb_hash_aset( tuple, fields[column], typecast_field(result_types[column], data, length, tzstring) ); } else { rb_hash_aset(tuple, fields[column], Qnil); } } // column loop NIL_P(scheme) ? rb_yield(tuple) : rb_yield(rb_funcall(scheme, fload, 1, tuple)); } // row loop } CATCH_DBI_EXCEPTIONS(); return Qnil; } static VALUE result_finish(VALUE self) { dbi::AbstractResult *result = result_handle(self); try { result->finish(); } CATCH_DBI_EXCEPTIONS(); } // Calculates local offset at a given time, including dst. int64_t client_tzoffset(int64_t local, int isdst) { struct tm tm; gmtime_r((const time_t*)&local, &tm); // TODO: This won't work in Lord Howe Island, Australia which uses half hour shift. return (int64_t)(local + (isdst ? 3600 : 0) - mktime(&tm)); } // Calculates server offset at a given time, including dst. int64_t server_tzoffset(struct tm* tm, const char *zone) { uint64_t local; int64_t offset; char buffer[512]; char *old, saved[512]; struct tm tm_copy; // save current zone setting. if ((old = getenv("TZ"))) { strncpy(saved, old, 512); saved[511] = 0; } // setup. snprintf(buffer, 512, ":%s", zone); setenv("TZ", buffer, 1); tzset(); // pretend we're on server timezone and calculate offset. memcpy(&tm_copy, tm, sizeof(struct tm)); tm_copy.tm_isdst = -1; local = mktime(&tm_copy); offset = client_tzoffset(local, tm_copy.tm_isdst); // reset timezone to what it was before. old ? setenv("TZ", saved, 1) : unsetenv("TZ"); tzset(); return offset; } VALUE typecast_timestamp(const char *data, uint64_t len, const char *zone) { struct tm tm; int64_t epoch, adjust, offset; uint64_t usec = 0; char tzsign = 0, subsec[32]; int tzhour = 0, tzmin = 0; memset(&tm, 0, sizeof(struct tm)); // Based on github.com/jeremyevans/sequel_pg. atoll & pow seem to be tad faster than sscanf %Lf // NOTE: Reading subsec as string means malformed subsec > 32 digits could break this. if (strchr(data, '.')) { sscanf(data, "%04d-%02d-%02d %02d:%02d:%02d.%s%c%02d:%02d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, subsec, &tzsign, &tzhour, &tzmin); usec = atoll(subsec)*(uint64_t)pow(10, 6 - strlen(subsec)); } else { sscanf(data, "%04d-%02d-%02d %02d:%02d:%02d%c%02d:%02d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &tzsign, &tzhour, &tzmin); } tm.tm_year -= 1900; tm.tm_mon -= 1; tm.tm_isdst = -1; if (tm.tm_mday > 0) { epoch = mktime(&tm); adjust = client_tzoffset(epoch, tm.tm_isdst); offset = adjust; if (tzsign) { offset = tzsign == '+' ? (time_t)tzhour * 3600 + (time_t)tzmin * 60 : (time_t)tzhour * -3600 + (time_t)tzmin * -60; } else if (zone) { if (strncasecmp(zone, "UTC", 3) == 0 || strncasecmp(zone, "GMT", 3) == 0) offset = 0; else if (strcmp(zone, "+00:00") == 0 || strcmp(zone, "+0000") == 0) offset = 0; else if (sscanf(zone, "%c%02d%02d", &tzsign, &tzhour, &tzmin) == 3) offset = tzsign == '+' ? (time_t)tzhour * 3600 + (time_t)tzmin * 60 : (time_t)tzhour * -3600 + (time_t)tzmin * -60; else if (sscanf(zone, "%c%02d:%02d", &tzsign, &tzhour, &tzmin) >= 2) offset = tzsign == '+' ? (time_t)tzhour * 3600 + (time_t)tzmin * 60 : (time_t)tzhour * -3600 + (time_t)tzmin * -60; else offset = server_tzoffset(&tm, zone); } return rb_time_new(epoch+adjust-offset, usec); } printf("WARNING: Unable to parse timestamp value '%s'\n", data); return rb_str_new(data, len); } #define typecast_date(data,len,tz) rb_funcall(typecast_timestamp(data,len,tz), fto_date, 0) /* This is my wish list below for rubycore - to be built into core ruby. 1. Time class represents time - time zone invariant 2. Date class represents a date - time zone invariant 3. DateTime class represents a timestamp with full zoneinfo support. */ VALUE typecast_field(int type, const char *data, uint64_t length, const char* timezone) { switch(type) { case DBI_TYPE_BOOLEAN: return (data && (data[0] =='t' || data[0] == '1')) ? Qtrue : Qfalse; case DBI_TYPE_INT: return rb_cstr2inum(data, 10); case DBI_TYPE_BLOB: return rb_funcall(cStringIO, fnew, 1, rb_str_new(data, length)); case DBI_TYPE_TIMESTAMP: return typecast_timestamp(data, length, timezone); case DBI_TYPE_DATE: return typecast_date(data, length, timezone); case DBI_TYPE_NUMERIC: return rb_funcall(cBigDecimal, fnew, 1, rb_str_new2(data)); case DBI_TYPE_FLOAT: return rb_float_new(atof(data)); // DBI_TYPE_TIME // DBI_TYPE_TEXT default: return rb_enc_str_new(data, length, rb_utf8_encoding()); } } VALUE result_insert_id(VALUE self) { dbi::AbstractResult *result = result_handle(self); try { return SIZET2NUM(result->lastInsertID()); } CATCH_DBI_EXCEPTIONS(); return Qnil; } VALUE result_rows(VALUE self) { dbi::AbstractResult *result = result_handle(self); try { return SIZET2NUM(result->rows()); } CATCH_DBI_EXCEPTIONS(); } VALUE result_columns(VALUE self) { dbi::AbstractResult *result = result_handle(self); try { return SIZET2NUM(result->columns()); } CATCH_DBI_EXCEPTIONS(); } VALUE result_fields(VALUE self) { dbi::AbstractResult *result = result_handle(self); try { std::vector result_fields = result->fields(); VALUE fields = rb_ary_new(); for (int i = 0; i < result_fields.size(); i++) rb_ary_push(fields, ID2SYM(rb_intern(result_fields[i].c_str()))); return fields; } CATCH_DBI_EXCEPTIONS(); } void init_swift_result() { rb_require("bigdecimal"); rb_require("stringio"); rb_require("date"); VALUE mSwift = rb_define_module("Swift"); cSwiftResult = rb_define_class_under(mSwift, "Result", rb_cObject); cStringIO = CONST_GET(rb_mKernel, "StringIO"); cBigDecimal = CONST_GET(rb_mKernel, "BigDecimal"); fnew = rb_intern("new"); fto_date = rb_intern("to_date"); fload = rb_intern("load"); rb_define_alloc_func(cSwiftResult, result_alloc); rb_include_module(cSwiftResult, CONST_GET(rb_mKernel, "Enumerable")); rb_define_method(cSwiftResult, "clone", RUBY_METHOD_FUNC(result_clone), 0); rb_define_method(cSwiftResult, "dup", RUBY_METHOD_FUNC(result_dup), 0); rb_define_method(cSwiftResult, "each", RUBY_METHOD_FUNC(result_each), 0); rb_define_method(cSwiftResult, "finish", RUBY_METHOD_FUNC(result_finish), 0); rb_define_method(cSwiftResult, "insert_id", RUBY_METHOD_FUNC(result_insert_id), 0); rb_define_method(cSwiftResult, "rows", RUBY_METHOD_FUNC(result_rows), 0); rb_define_method(cSwiftResult, "columns", RUBY_METHOD_FUNC(result_columns), 0); rb_define_method(cSwiftResult, "fields", RUBY_METHOD_FUNC(result_fields), 0); }