Skip to content

Commit

Permalink
Merge pull request #113 from snowflakedb/date_as_ts
Browse files Browse the repository at this point in the history
Fixed DATE type to SF_TIMESTAMP struct Conversion bug
  • Loading branch information
sfc-gh-kwagner authored Nov 7, 2018
2 parents b75e4c0 + f92121a commit 63e5163
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 144 deletions.
158 changes: 15 additions & 143 deletions lib/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,6 @@ static const int64 pow10_int64[] = {
1000000000LL
};

/**
* Maximum one-directional range of offset-based timezones (24 hours)
*/
#define TIMEZONE_OFFSET_RANGE (int64)(24 * 60);

/**
* Find string size, create buffer, copy over, and return.
* @param var output buffer
Expand Down Expand Up @@ -152,136 +147,6 @@ static void log_lock_func(void *udata, int lock) {
}
}

/**
* Extracts a Snowflake internal representation of timestamp into
* seconds, nanoseconds and optionally Timezone offset.
* @param sec pointer of seconds
* @param nsec pointer of nanoseconds
* @param tzoffset pointer of Timezone offset.
* @param src source buffer including a Snowflake internal timestamp
* @param timezone Timezone for TIMESTAMP_LTZ
* @param scale Timestamp data type scale between 0 and 9
* @return SF_BOOLEAN_TRUE if success otherwise SF_BOOLEAN_FALSE
*/
static sf_bool _extract_timestamp_dynamic(
char **result_ptr, size_t *result_len_ptr, SF_DB_TYPE sftype,
const char *src, const char *timezone, int64 scale) {
sf_bool ret = SF_BOOLEAN_FALSE;
time_t nsec = 0L;
time_t sec = 0L;
int64 tzoffset = 0;
struct tm tm_obj;
struct tm *tm_ptr = NULL;
char tzname[64];
char *tzptr = (char *) timezone;
size_t len = 0;
char *value = NULL;

memset(&tm_obj, 0, sizeof(tm_obj));

/* Search for a decimal point */
char *ptr = strchr(src, (int) '.');
if (ptr == NULL) {
ret = SF_BOOLEAN_FALSE;
goto cleanup;
}
sec = strtoll(src, NULL, 10);

/* Search for a space for TIMESTAMP_TZ */
char *sptr = strchr(ptr + 1, (int) ' ');
nsec = strtoll(ptr + 1, NULL, 10);
if (sptr != NULL) {
/* TIMESTAMP_TZ */
nsec = strtoll(ptr + 1, NULL, 10);
tzoffset = strtoll(sptr + 1, NULL, 10) - TIMEZONE_OFFSET_RANGE;
}
if (sec < 0 && nsec > 0) {
nsec = pow10_int64[scale] - nsec;
sec--;
}
log_info("sec: %lld, nsec: %lld", sec, nsec);

if (sftype == SF_DB_TYPE_TIMESTAMP_TZ) {
/* make up Timezone name from the tzoffset */
ldiv_t dm = ldiv((long) tzoffset, 60L);
sprintf(tzname, "UTC%c%02ld:%02ld",
dm.quot > 0 ? '+' : '-', labs(dm.quot), labs(dm.rem));
tzptr = tzname;
}

/* replace a dot character with NULL */
if (sftype == SF_DB_TYPE_TIMESTAMP_NTZ ||
sftype == SF_DB_TYPE_TIME) {
tm_ptr = sf_gmtime(&sec, &tm_obj);
} else if (sftype == SF_DB_TYPE_TIMESTAMP_LTZ ||
sftype == SF_DB_TYPE_TIMESTAMP_TZ) {
/* set the environment variable TZ to the session timezone
* so that localtime_tz honors it.
*/
_mutex_lock(&gmlocaltime_lock);
const char *prev_tz_ptr = sf_getenv("TZ");
sf_setenv("TZ", tzptr);
sf_tzset();
sec += tzoffset * 60 * 2; /* adjust for TIMESTAMP_TZ */
tm_ptr = sf_localtime(&sec, &tm_obj);
if (prev_tz_ptr != NULL) {
sf_setenv("TZ", prev_tz_ptr); /* cannot set to NULL */
} else {
sf_unsetenv("TZ");
}
sf_tzset();
_mutex_unlock(&gmlocaltime_lock);
}
if (tm_ptr == NULL) {
len = 0;
ret = SF_BOOLEAN_FALSE;
goto cleanup;
}
const char *fmt0;
size_t max_len = 1;
if (sftype != SF_DB_TYPE_TIME) {
max_len += 21;
fmt0 = "%Y-%m-%d %H:%M:%S";
} else {
max_len += 8;
fmt0 = "%H:%M:%S";
}
/* adjust scale */
char fmt[20];
sprintf(fmt, ".%%0%lldld", scale);

// Add space for scale if scale is greater than 0
max_len += (scale > 0) ? 1 + scale : 0;
// Add space for timezone if SF_DB_TYPE_TIMESTAMP_TZ is set
max_len += (sftype == SF_DB_TYPE_TIMESTAMP_TZ) ? 7 : 0;
// Allocate string buffer to store date using our calculated max length
value = global_hooks.calloc(1, max_len);
len = strftime(value, max_len, fmt0, &tm_obj);
if (scale > 0) {
len += snprintf(
&((char *) value)[len],
max_len - len, fmt,
nsec);
}
if (sftype == SF_DB_TYPE_TIMESTAMP_TZ) {
/* Timezone info */
ldiv_t dm = ldiv((long) tzoffset, 60L);
len += snprintf(
&((char *) value)[len],
max_len - len,
" %c%02ld:%02ld",
dm.quot > 0 ? '+' : '-', labs(dm.quot), labs(dm.rem));
}

ret = SF_BOOLEAN_TRUE;

cleanup:
*result_len_ptr = len;
*result_ptr = value;
return ret;
}


/**
* Reset the connection parameters with the returned parameteres
* @param sf SF_CONNECT object
Expand Down Expand Up @@ -2661,20 +2526,26 @@ SF_STATUS STDCALL snowflake_timestamp_from_epoch_seconds(SF_TIMESTAMP *ts, const

/* Search for a decimal point */
char *ptr = strchr(str, (int) '.');
if (ptr == NULL) {
// No decimal point exists for date types
if (ptr == NULL && ts->ts_type != SF_DB_TYPE_DATE) {
ret = SF_STATUS_ERROR_GENERAL;
goto cleanup;
}
sec = strtoll(str, NULL, 10);

/* Search for a space for TIMESTAMP_TZ */
char *sptr = strchr(ptr + 1, (int) ' ');
nsec = strtoll(ptr + 1, NULL, 10);
if (sptr != NULL) {
/* TIMESTAMP_TZ */
if (ts->ts_type == SF_DB_TYPE_DATE) {
sec = sec * SECONDS_IN_AN_HOUR;
} else {
/* Search for a space for TIMESTAMP_TZ */
char *sptr = strchr(ptr + 1, (int) ' ');
nsec = strtoll(ptr + 1, NULL, 10);
tzoffset = strtoll(sptr + 1, NULL, 10) - TIMEZONE_OFFSET_RANGE;
if (sptr != NULL) {
/* TIMESTAMP_TZ */
nsec = strtoll(ptr + 1, NULL, 10);
tzoffset = strtoll(sptr + 1, NULL, 10) - TIMEZONE_OFFSET_RANGE;
}
}

// If timestamp is before the epoch, we have to do some
// math to make things work just right
if (sec < 0 && nsec > 0) {
Expand All @@ -2696,7 +2567,8 @@ SF_STATUS STDCALL snowflake_timestamp_from_epoch_seconds(SF_TIMESTAMP *ts, const

/* replace a dot character with NULL */
if (ts->ts_type == SF_DB_TYPE_TIMESTAMP_NTZ ||
ts->ts_type == SF_DB_TYPE_TIME) {
ts->ts_type == SF_DB_TYPE_TIME ||
ts->ts_type == SF_DB_TYPE_DATE) {
tm_ptr = sf_gmtime(&sec, &ts->tm_obj);
} else if (ts->ts_type == SF_DB_TYPE_TIMESTAMP_LTZ ||
ts->ts_type == SF_DB_TYPE_TIMESTAMP_TZ) {
Expand Down
6 changes: 6 additions & 0 deletions lib/client_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
#define REQUEST_TYPE_ISSUE "ISSUE"

#define DATE_STRING_MAX_SIZE 12
#define SECONDS_IN_AN_HOUR 86400L

/**
* Maximum one-directional range of offset-based timezones (24 hours)
*/
#define TIMEZONE_OFFSET_RANGE (int64)(24 * 60);

int uuid4_generate_non_terminated(char *dst);
int uuid4_generate(char *dst);
Expand Down
3 changes: 2 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ SET(TESTS_C
test_timezone
test_adjust_fetch_data
test_issue_76
test_column_fetch)
test_column_fetch
test_native_timestamp)

SET(TESTS_CXX
test_unit_jwt
Expand Down
93 changes: 93 additions & 0 deletions tests/test_native_timestamp.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright (c) 2018 Snowflake Computing, Inc. All rights reserved.
*/
#include <string.h>
#include "utils/test_setup.h"

void test_native_timestamp(void **unused) {
SF_STATUS status;
SF_CONNECT *sf = NULL;
SF_STMT *sfstmt = NULL;

// Setup connection, run query, and get results back
setup_and_run_query(&sf, &sfstmt, "select to_time('12:34:56'),"
"date_from_parts(2018, 09, 14), "
"timestamp_ltz_from_parts(2014, 03, 20, 15, 30, 45, 493679329), "
"timestamp_ntz_from_parts(2014, 03, 20, 15, 30, 45, 493679329), "
"timestamp_tz_from_parts(2014, 03, 20, 15, 30, 45, 493679329, 'America/Los_Angeles')");

// Stores the result from the fetch operation
SF_TIMESTAMP ts;

while ((status = snowflake_fetch(sfstmt)) == SF_STATUS_SUCCESS) {
// Converting a TIME type into a SF_TIMESTAMP
if (snowflake_column_as_timestamp(sfstmt, 1, &ts)) {
dump_error(&(sfstmt->error));
}
assert_int_equal(12, snowflake_timestamp_get_hours(&ts));
assert_int_equal(34, snowflake_timestamp_get_minutes(&ts));
assert_int_equal(56, snowflake_timestamp_get_seconds(&ts));
assert_int_equal(0, snowflake_timestamp_get_nanoseconds(&ts));

// Converting a DATE type into a SF_TIMESTAMP
if (snowflake_column_as_timestamp(sfstmt, 2, &ts)) {
dump_error(&(sfstmt->error));
}
assert_int_equal(14, snowflake_timestamp_get_mday(&ts));
assert_int_equal(9, snowflake_timestamp_get_month(&ts));
assert_int_equal(2018, snowflake_timestamp_get_year(&ts));
assert_int_equal(0, snowflake_timestamp_get_hours(&ts));
assert_int_equal(0, snowflake_timestamp_get_minutes(&ts));
assert_int_equal(0, snowflake_timestamp_get_seconds(&ts));
assert_int_equal(0, snowflake_timestamp_get_nanoseconds(&ts));

// Converting a TIMESTAMP_LTZ type into a SF_TIMESTAMP
if (snowflake_column_as_timestamp(sfstmt, 3, &ts)) {
dump_error(&(sfstmt->error));
}
assert_int_equal(20, snowflake_timestamp_get_mday(&ts));
assert_int_equal(3, snowflake_timestamp_get_month(&ts));
assert_int_equal(2014, snowflake_timestamp_get_year(&ts));
assert_int_equal(15, snowflake_timestamp_get_hours(&ts));
assert_int_equal(30, snowflake_timestamp_get_minutes(&ts));
assert_int_equal(45, snowflake_timestamp_get_seconds(&ts));
assert_int_equal(493679329, snowflake_timestamp_get_nanoseconds(&ts));

// Converting a TIMESTAMP_NTZ type into a SF_TIMESTAMP
if (snowflake_column_as_timestamp(sfstmt, 4, &ts)) {
dump_error(&(sfstmt->error));
}
assert_int_equal(20, snowflake_timestamp_get_mday(&ts));
assert_int_equal(3, snowflake_timestamp_get_month(&ts));
assert_int_equal(2014, snowflake_timestamp_get_year(&ts));
assert_int_equal(15, snowflake_timestamp_get_hours(&ts));
assert_int_equal(30, snowflake_timestamp_get_minutes(&ts));
assert_int_equal(45, snowflake_timestamp_get_seconds(&ts));
assert_int_equal(493679329, snowflake_timestamp_get_nanoseconds(&ts));

// Converting a TIMESTAMP_TZ type into a SF_TIMESTAMP
if (snowflake_column_as_timestamp(sfstmt, 5, &ts)) {
dump_error(&(sfstmt->error));
}
assert_int_equal(20, snowflake_timestamp_get_mday(&ts));
assert_int_equal(3, snowflake_timestamp_get_month(&ts));
assert_int_equal(2014, snowflake_timestamp_get_year(&ts));
assert_int_equal(15, snowflake_timestamp_get_hours(&ts));
assert_int_equal(30, snowflake_timestamp_get_minutes(&ts));
assert_int_equal(45, snowflake_timestamp_get_seconds(&ts));
assert_int_equal(493679329, snowflake_timestamp_get_nanoseconds(&ts));
}

snowflake_stmt_term(sfstmt);
snowflake_term(sf);
}

int main(void) {
initialize_test(SF_BOOLEAN_FALSE);
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_native_timestamp),
};
int ret = cmocka_run_group_tests(tests, NULL, NULL);
snowflake_global_term();
return ret;
}

0 comments on commit 63e5163

Please sign in to comment.