ext/result.cc in swift-0.6.1 vs ext/result.cc in swift-0.7.0
- old
+ new
@@ -1,30 +1,55 @@
#include "result.h"
+#include <math.h>
VALUE cBigDecimal;
-VALUE cDate;
-VALUE cDateTime;
VALUE cStringIO;
VALUE cSwiftResult;
-VALUE fNew, fNewBang;
+ID fnew, fto_date, fload;
-uint64_t epoch_ajd_n, epoch_ajd_d;
-VALUE daysecs, sg;
+void result_mark(ResultWrapper *handle) {
+ if (handle)
+ rb_gc_mark(handle->adapter);
+}
-void result_free(dbi::AbstractResultSet *result) {
- if (result) {
- result->cleanup();
- delete result;
+void result_free(ResultWrapper *handle) {
+ if (handle) {
+ if (handle->free) {
+ handle->result->cleanup();
+ delete handle->result;
+ }
+ delete handle;
}
}
VALUE result_alloc(VALUE klass) {
- dbi::AbstractResultSet *result = 0;
- return Data_Wrap_Struct(klass, 0, result_free, result);
+ 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.");
}
@@ -33,57 +58,51 @@
rb_raise(eSwiftRuntimeError, "dup is not allowed.");
}
VALUE result_each(VALUE self) {
uint64_t length;
- const char *data;
+ const char *data, *tzstring;
- dbi::AbstractResultSet *result = result_handle(self);
- VALUE scheme = rb_iv_get(self, "@scheme");
+ 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 {
- VALUE fields = rb_ary_new();
std::vector<string> result_fields = result->fields();
std::vector<int> result_types = result->types();
- for (uint32_t i = 0; i < result_fields.size(); i++) {
- rb_ary_push(fields, ID2SYM(rb_intern(result_fields[i].c_str())));
- }
+ std::vector<VALUE> 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,
- rb_ary_entry(fields, column),
- typecast_field(result_types[column], data, length)
+ fields[column],
+ typecast_field(result_types[column], data, length, tzstring)
);
}
else {
- rb_hash_aset(tuple, rb_ary_entry(fields, column), Qnil);
+ rb_hash_aset(tuple, fields[column], Qnil);
}
} // column loop
- NIL_P(scheme) ? rb_yield(tuple) : rb_yield(rb_funcall(scheme, rb_intern("load"), 1, tuple));
+ NIL_P(scheme) ? rb_yield(tuple) : rb_yield(rb_funcall(scheme, fload, 1, tuple));
} // row loop
}
CATCH_DBI_EXCEPTIONS();
return Qnil;
}
-dbi::AbstractResultSet* result_handle(VALUE self) {
- dbi::AbstractResultSet *result;
- Data_Get_Struct(self, dbi::AbstractResultSet, result);
- if (!result) rb_raise(eSwiftRuntimeError, "Invalid object, did you forget to call #super?");
-
- return result;
-}
-
static VALUE result_finish(VALUE self) {
- dbi::AbstractResultSet *result = result_handle(self);
+ dbi::AbstractResult *result = result_handle(self);
try {
result->finish();
}
CATCH_DBI_EXCEPTIONS();
}
@@ -94,35 +113,57 @@
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));
}
-// pinched from do_postgres
-static void reduce(uint64_t *numerator, uint64_t *denominator) {
- uint64_t a, b, c;
- a = *numerator;
- b = *denominator;
- while (a) {
- c = a; a = b % a; b = c;
+// 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;
}
- *numerator = *numerator / b;
- *denominator = *denominator / b;
-}
-VALUE typecast_timestamp(VALUE klass, const char *data, uint64_t len) {
- struct tm tm;
- int64_t epoch, adjust, offset;
+ // setup.
+ snprintf(buffer, 512, ":%s", zone);
+ setenv("TZ", buffer, 1);
+ tzset();
- double usec = 0;
- char tzsign = 0;
- int tzhour = 0, tzmin = 0;
+ // 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%lf%c%02d:%02d",
- &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec,
- &usec, &tzsign, &tzhour, &tzmin);
+ 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);
@@ -134,82 +175,99 @@
if (tm.tm_mday > 0) {
epoch = mktime(&tm);
adjust = client_tzoffset(epoch, tm.tm_isdst);
offset = adjust;
- if (tzsign == '+' || tzsign == '-') {
+ 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);
+ }
- // 32bit platforms are for weenies
- uint64_t ajd_n = (epoch + adjust - offset)*1000000 + usec*1000000, ajd_d = DAYMICROSECS;
- reduce(&ajd_n, &ajd_d);
- ajd_n = epoch_ajd_n*ajd_d + ajd_n*epoch_ajd_d;
- ajd_d = epoch_ajd_d*ajd_d;
- reduce(&ajd_n, &ajd_d);
-
- VALUE ajd = rb_rational_new(SIZET2NUM(ajd_n), SIZET2NUM(ajd_d));
- return rb_funcall(klass, fNewBang, 3, ajd, rb_rational_new(INT2FIX(adjust), daysecs), sg);
+ return rb_time_new(epoch+adjust-offset, usec);
}
- // TODO: throw a warning ?
+ printf("WARNING: Unable to parse timestamp value '%s'\n", data);
return rb_str_new(data, len);
}
-#define typecast_datetime(data,len) typecast_timestamp(cDateTime, data, len)
-#define typecast_date(data,len) typecast_timestamp(cDate, data, len)
+#define typecast_date(data,len,tz) rb_funcall(typecast_timestamp(data,len,tz), fto_date, 0)
-VALUE typecast_field(int type, const char *data, uint64_t length) {
+/*
+ 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_TEXT:
- return rb_enc_str_new(data, length, rb_utf8_encoding());
- case DBI_TYPE_TIME:
- return typecast_datetime(data, length);
+ 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);
+ return typecast_date(data, length, timezone);
case DBI_TYPE_NUMERIC:
- return rb_funcall(cBigDecimal, fNew, 1, rb_str_new2(data));
+ 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::AbstractResultSet *result = result_handle(self);
+ dbi::AbstractResult *result = result_handle(self);
try {
return SIZET2NUM(result->lastInsertID());
}
CATCH_DBI_EXCEPTIONS();
return Qnil;
}
VALUE result_rows(VALUE self) {
- dbi::AbstractResultSet *result = result_handle(self);
+ dbi::AbstractResult *result = result_handle(self);
try {
return SIZET2NUM(result->rows());
}
CATCH_DBI_EXCEPTIONS();
}
VALUE result_columns(VALUE self) {
- dbi::AbstractResultSet *result = result_handle(self);
+ dbi::AbstractResult *result = result_handle(self);
try {
return SIZET2NUM(result->columns());
}
CATCH_DBI_EXCEPTIONS();
}
VALUE result_fields(VALUE self) {
- dbi::AbstractResultSet *result = result_handle(self);
+ dbi::AbstractResult *result = result_handle(self);
try {
std::vector<string> 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())));
@@ -223,17 +281,16 @@
rb_require("stringio");
rb_require("date");
VALUE mSwift = rb_define_module("Swift");
cSwiftResult = rb_define_class_under(mSwift, "Result", rb_cObject);
- cDateTime = CONST_GET(rb_mKernel, "DateTime");
- cDate = CONST_GET(rb_mKernel, "Date");
cStringIO = CONST_GET(rb_mKernel, "StringIO");
cBigDecimal = CONST_GET(rb_mKernel, "BigDecimal");
- fNew = rb_intern("new");
- fNewBang = rb_intern("new!");
+ 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);
@@ -242,13 +299,7 @@
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);
-
- // typecast_datetime setup.
- epoch_ajd_d = 2;
- epoch_ajd_n = 4881175L; // 1970-01-01 00:00:00 is 2440587.5 in ajd
- daysecs = SIZET2NUM(86400L);
- sg = SIZET2NUM(2299161L); // day of calendar reform Date::ITALY
}