/* Module: convert.c * * Description: This module contains routines related to * converting parameters and columns into requested data types. * Parameters are converted from their SQL_C data types into * the appropriate postgres type. Columns are converted from * their postgres type (SQL type) into the appropriate SQL_C * data type. * * Classes: n/a * * API functions: none * * Comments: See "notice.txt" for copyright and license information. * */ #include #include #include #include #include #include #include #include "convert.h" #include "statement.h" #include "bind.h" #include "pgtypes.h" /******** A Guide for date/time/timestamp conversions ************** field_type fCType Output ---------- ------ ---------- PG_TYPE_DATE SQL_C_DEFAULT SQL_C_DATE PG_TYPE_DATE SQL_C_DATE SQL_C_DATE PG_TYPE_DATE SQL_C_TIMESTAMP SQL_C_TIMESTAMP (time = 0 (midnight)) PG_TYPE_TIME SQL_C_DEFAULT SQL_C_TIME PG_TYPE_TIME SQL_C_TIME SQL_C_TIME PG_TYPE_TIME SQL_C_TIMESTAMP SQL_C_TIMESTAMP (date = current date) PG_TYPE_ABSTIME SQL_C_DEFAULT SQL_C_TIMESTAMP PG_TYPE_ABSTIME SQL_C_DATE SQL_C_DATE (time is truncated) PG_TYPE_ABSTIME SQL_C_TIME SQL_C_TIME (date is truncated) PG_TYPE_ABSTIME SQL_C_TIMESTAMP SQL_C_TIMESTAMP ******************************************************************************/ /* This is called by SQLFetch() */ int copy_and_convert_field_bindinfo(Int4 field_type, void *value, BindInfoClass *bic) { return copy_and_convert_field(field_type, value, (Int2)bic->returntype, (PTR)bic->buffer, (SDWORD)bic->buflen, (SDWORD *)bic->used); } /* This is called by SQLGetData() */ int copy_and_convert_field(Int4 field_type, void *value, Int2 fCType, PTR rgbValue, SDWORD cbValueMax, SDWORD *pcbValue) { Int4 len = 0, nf; char day[4], mon[4], tz[4]; SIMPLE_TIME st; time_t t = time(NULL); struct tm *tim; int bool; memset(&st, 0, sizeof(SIMPLE_TIME)); /* Initialize current date */ tim = localtime(&t); st.m = tim->tm_mon + 1; st.d = tim->tm_mday; st.y = tim->tm_year + 1900; bool = 0; mylog("copy_and_convert: field_type = %d, fctype = %d, value = '%s', cbValueMax=%d\n", field_type, fCType, value, cbValueMax); if(value) { /******************************************************************** First convert any specific postgres types into more useable data. NOTE: Conversions from PG char/varchar of a date/time/timestamp value to SQL_C_DATE,SQL_C_TIME, SQL_C_TIMESTAMP not supported *********************************************************************/ switch(field_type) { /* $$$ need to add parsing for date/time/timestamp strings in PG_TYPE_CHAR,VARCHAR $$$ */ case PG_TYPE_DATE: sscanf(value, "%2d-%2d-%4d", &st.m, &st.d, &st.y); break; case PG_TYPE_TIME: sscanf(value, "%2d:%2d:%2d", &st.hh, &st.mm, &st.ss); break; case PG_TYPE_ABSTIME: case PG_TYPE_DATETIME: if (strnicmp(value, "invalid", 7) != 0) { nf = sscanf(value, "%3s %3s %2d %2d:%2d:%2d %4d %3s", &day, &mon, &st.d, &st.hh, &st.mm, &st.ss, &st.y, &tz); if (nf == 7 || nf == 8) { /* convert month name to month number */ st.m = monthToNumber(mon); } } else { /* The timestamp is invalid so set something conspicuous, like the epoch */ t = 0; tim = localtime(&t); st.m = tim->tm_mon + 1; st.d = tim->tm_mday; st.y = tim->tm_year + 1900; st.hh = tim->tm_hour; st.mm = tim->tm_min; st.ss = tim->tm_sec; } break; case PG_TYPE_BOOL: { /* change T/F to 1/0 */ char *s = (char *) value; if (s[0] == 'T' || s[0] == 't' || s[0] == '1') bool = 1; else bool = 0; } break; /* This is for internal use by SQLStatistics() */ case PG_TYPE_INT28: { // this is an array of eight integers short *short_array = (short *)rgbValue; len = 16; sscanf(value, "%hd %hd %hd %hd %hd %hd %hd %hd", &short_array[0], &short_array[1], &short_array[2], &short_array[3], &short_array[4], &short_array[5], &short_array[6], &short_array[7]); /* There is no corresponding fCType for this. */ if(pcbValue) *pcbValue = len; return COPY_OK; /* dont go any further or the data will be trashed */ } } /* Change default into something useable */ if (fCType == SQL_C_DEFAULT) { fCType = pgtype_to_ctype(field_type); mylog("copy_and_convert, SQL_C_DEFAULT: fCType = %d\n", fCType); } if(fCType == SQL_C_CHAR) { /* Special character formatting as required */ switch(field_type) { case PG_TYPE_DATE: len = 11; if (cbValueMax > len) sprintf((char *)rgbValue, "%.4d-%.2d-%.2d", st.y, st.m, st.d); break; case PG_TYPE_TIME: len = 9; if (cbValueMax > len) sprintf((char *)rgbValue, "%.2d:%.2d:%.2d", st.hh, st.mm, st.ss); break; case PG_TYPE_ABSTIME: case PG_TYPE_DATETIME: len = 19; if (cbValueMax > len) sprintf((char *) rgbValue, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d", st.y, st.m, st.d, st.hh, st.mm, st.ss); break; case PG_TYPE_BOOL: len = 1; if (cbValueMax > len) { strcpy((char *) rgbValue, bool ? "1" : "0"); mylog("PG_TYPE_BOOL: rgbValue = '%s'\n", rgbValue); } break; case PG_TYPE_BYTEA: // convert binary data to hex strings (i.e, 255 = "FF") len = convert_pgbinary_to_char(value, rgbValue, cbValueMax); break; default: /* convert linefeeds to carriage-return/linefeed */ convert_linefeeds( (char *) value, rgbValue, cbValueMax); len = strlen(rgbValue); mylog(" SQL_C_CHAR, default: len = %d, cbValueMax = %d, rgbValue = '%s'\n", len, cbValueMax, rgbValue); break; } } else { /* for SQL_C_CHAR, its probably ok to leave currency symbols in. But to convert to numeric types, it is necessary to get rid of those. */ if (field_type == PG_TYPE_MONEY) convert_money(value); switch(fCType) { case SQL_C_DATE: len = 6; if (cbValueMax >= len) { DATE_STRUCT *ds = (DATE_STRUCT *) rgbValue; ds->year = st.y; ds->month = st.m; ds->day = st.d; } break; case SQL_C_TIME: len = 6; if (cbValueMax >= len) { TIME_STRUCT *ts = (TIME_STRUCT *) rgbValue; ts->hour = st.hh; ts->minute = st.mm; ts->second = st.ss; } break; case SQL_C_TIMESTAMP: len = 16; if (cbValueMax >= len) { TIMESTAMP_STRUCT *ts = (TIMESTAMP_STRUCT *) rgbValue; ts->year = st.y; ts->month = st.m; ts->day = st.d; ts->hour = st.hh; ts->minute = st.mm; ts->second = st.ss; ts->fraction = 0; } break; case SQL_C_BIT: len = 1; if (cbValueMax >= len || field_type == PG_TYPE_BOOL) { *((UCHAR *)rgbValue) = (UCHAR) bool; mylog("SQL_C_BIT: val = %d, cb = %d, rgb=%d\n", atoi(value), cbValueMax, *((UCHAR *)rgbValue)); } break; case SQL_C_STINYINT: case SQL_C_TINYINT: len = 1; if (cbValueMax >= len) *((SCHAR *) rgbValue) = atoi(value); break; case SQL_C_UTINYINT: len = 1; if (cbValueMax >= len) *((UCHAR *) rgbValue) = atoi(value); break; case SQL_C_FLOAT: len = 4; if(cbValueMax >= len) *((SFLOAT *)rgbValue) = (float) atof(value); break; case SQL_C_DOUBLE: len = 8; if(cbValueMax >= len) *((SDOUBLE *)rgbValue) = atof(value); break; case SQL_C_SSHORT: case SQL_C_SHORT: len = 2; if(cbValueMax >= len) *((SWORD *)rgbValue) = atoi(value); break; case SQL_C_USHORT: len = 2; if(cbValueMax >= len) *((UWORD *)rgbValue) = atoi(value); break; case SQL_C_SLONG: case SQL_C_LONG: len = 4; if(cbValueMax >= len) *((SDWORD *)rgbValue) = atol(value); break; case SQL_C_ULONG: len = 4; if(cbValueMax >= len) *((UDWORD *)rgbValue) = atol(value); break; case SQL_C_BINARY: // truncate if necessary // convert octal escapes to bytes len = convert_from_pgbinary(value, rgbValue, cbValueMax); mylog("SQL_C_BINARY: len = %d\n", len); break; default: return COPY_UNSUPPORTED_TYPE; } } } else { /* handle a null just by returning SQL_NULL_DATA in pcbValue, */ /* and doing nothing to the buffer. */ if(pcbValue) { *pcbValue = SQL_NULL_DATA; } } // store the length of what was copied, if there's a place for it // unless it was a NULL (in which case it was already set above) if(pcbValue && value) *pcbValue = len; if(len > cbValueMax) { mylog("!!! COPY_RESULT_TRUNCATED !!!\n"); // Don't return truncated because an application // (like Access) will try to call GetData again // to retrieve the rest of the data. Since we // are not currently ready for this, and the result // is an endless loop, we better just silently // truncate the data. // return COPY_RESULT_TRUNCATED; *pcbValue = cbValueMax -1; return COPY_OK; } else { return COPY_OK; } } /* This function inserts parameters into an SQL statements. It will also modify a SELECT statement for use with declare/fetch cursors. This function no longer does any dynamic memory allocation! */ int copy_statement_with_parameters(StatementClass *stmt) { unsigned int opos, npos; char param_string[128], tmp[256], cbuf[TEXT_FIELD_SIZE+5]; int param_number; Int2 param_ctype, param_sqltype; char *old_statement = stmt->statement; char *new_statement = stmt->stmt_with_params; SIMPLE_TIME st; time_t t = time(NULL); struct tm *tim; SDWORD FAR *used; char *buffer, *buf; if ( ! old_statement) return SQL_ERROR; memset(&st, 0, sizeof(SIMPLE_TIME)); /* Initialize current date */ tim = localtime(&t); st.m = tim->tm_mon + 1; st.d = tim->tm_mday; st.y = tim->tm_year + 1900; // For selects, prepend a declare cursor to the statement if (stmt->statement_type == STMT_TYPE_SELECT) { sprintf(new_statement, "declare C%u cursor for ", stmt); npos = strlen(new_statement); } else { new_statement[0] = '0'; npos = 0; } param_number = -1; for (opos = 0; opos < strlen(old_statement); opos++) { // Squeeze carriage-returns/linfeed pairs to linefeed only if (old_statement[opos] == '\r' && opos+1= stmt->parameters_allocated) break; /* Assign correct buffers based on data at exec param or not */ if ( stmt->parameters[param_number].data_at_exec) { used = stmt->parameters[param_number].EXEC_used; buffer = stmt->parameters[param_number].EXEC_buffer; } else { used = stmt->parameters[param_number].used; buffer = stmt->parameters[param_number].buffer; } /* Handle NULL parameter data */ if (used && *used == SQL_NULL_DATA) { strcpy(&new_statement[npos], "NULL"); npos += 4; continue; } /* If no buffer, and its not null, then what the hell is it? Just leave it alone then. */ if ( ! buffer) { new_statement[npos++] = '?'; continue; } param_ctype = stmt->parameters[param_number].CType; param_sqltype = stmt->parameters[param_number].SQLType; mylog("copy_statement_with_params: from(fcType)=%d, to(fSqlType)=%d\n", param_ctype, param_sqltype); // replace DEFAULT with something we can use if(param_ctype == SQL_C_DEFAULT) param_ctype = sqltype_to_default_ctype(param_sqltype); buf = NULL; param_string[0] = '\0'; cbuf[0] = '\0'; /* Convert input C type to a neutral format */ switch(param_ctype) { case SQL_C_BINARY: case SQL_C_CHAR: buf = buffer; break; case SQL_C_DOUBLE: sprintf(param_string, "%f", *((SDOUBLE *) buffer)); break; case SQL_C_FLOAT: sprintf(param_string, "%f", *((SFLOAT *) buffer)); break; case SQL_C_SLONG: case SQL_C_LONG: sprintf(param_string, "%ld", *((SDWORD *) buffer)); break; case SQL_C_SSHORT: case SQL_C_SHORT: sprintf(param_string, "%d", *((SWORD *) buffer)); break; case SQL_C_STINYINT: case SQL_C_TINYINT: sprintf(param_string, "%d", *((SCHAR *) buffer)); break; case SQL_C_ULONG: sprintf(param_string, "%lu", *((UDWORD *) buffer)); break; case SQL_C_USHORT: sprintf(param_string, "%u", *((UWORD *) buffer)); break; case SQL_C_UTINYINT: sprintf(param_string, "%u", *((UCHAR *) buffer)); break; case SQL_C_BIT: { int i = *((UCHAR *) buffer); sprintf(param_string, "'%s'", i ? "t" : "f"); break; } case SQL_C_DATE: { DATE_STRUCT *ds = (DATE_STRUCT *) buffer; st.m = ds->month; st.d = ds->day; st.y = ds->year; break; } case SQL_C_TIME: { TIME_STRUCT *ts = (TIME_STRUCT *) buffer; st.hh = ts->hour; st.mm = ts->minute; st.ss = ts->second; break; } case SQL_C_TIMESTAMP: { TIMESTAMP_STRUCT *tss = (TIMESTAMP_STRUCT *) buffer; st.m = tss->month; st.d = tss->day; st.y = tss->year; st.hh = tss->hour; st.mm = tss->minute; st.ss = tss->second; mylog("m=%d,d=%d,y=%d,hh=%d,mm=%d,ss=%d\n", st.m, st.d, st.y, st.hh, st.mm, st.ss); break; } default: // error stmt->errormsg = "Unrecognized C_parameter type in copy_statement_with_parameters"; stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; new_statement[npos] = '\0'; // just in case return SQL_ERROR; } /* Now that the input data is in a neutral format, convert it to the desired output format (sqltype) */ switch(param_sqltype) { case SQL_CHAR: case SQL_VARCHAR: case SQL_LONGVARCHAR: new_statement[npos++] = '\''; /* Open Quote */ /* it was a SQL_C_CHAR */ if (buf) { convert_returns(buf, &new_statement[npos], used ? *used : SQL_NTS); npos += strlen(&new_statement[npos]); } /* it was a numeric type */ else if (param_string[0] != '\0') { strcpy(&new_statement[npos], param_string); npos += strlen(param_string); } /* it was date,time,timestamp -- use m,d,y,hh,mm,ss */ else { char *buf = convert_time(&st); strcpy(&new_statement[npos], buf); npos += strlen(buf); } new_statement[npos++] = '\''; /* Close Quote */ break; case SQL_DATE: if (buf && used) { /* copy char data to time */ my_strcpy(cbuf, sizeof(cbuf), buf, *used); parse_datetime(cbuf, &st); } sprintf(tmp, "'%.2d-%.2d-%.4d'", st.m, st.d, st.y); strcpy(&new_statement[npos], tmp); npos += strlen(tmp); break; case SQL_TIME: if (buf && used) { /* copy char data to time */ my_strcpy(cbuf, sizeof(cbuf), buf, *used); parse_datetime(cbuf, &st); } sprintf(tmp, "'%.2d:%.2d:%.2d'", st.hh, st.mm, st.ss); strcpy(&new_statement[npos], tmp); npos += strlen(tmp); break; case SQL_TIMESTAMP: { char *tbuf; if (buf && used) { my_strcpy(cbuf, sizeof(cbuf), buf, *used); parse_datetime(cbuf, &st); } tbuf = convert_time(&st); sprintf(&new_statement[npos], "'%s'", tbuf); npos += strlen(tbuf) + 2; break; } case SQL_BINARY: case SQL_VARBINARY: case SQL_LONGVARBINARY: /* non-ascii characters should be converted to octal */ new_statement[npos++] = '\''; /* Open Quote */ mylog("SQL_LONGVARBINARY: about to call convert_to_pgbinary, *used = %d\n", *used); npos += convert_to_pgbinary(buf, &new_statement[npos], *used); new_statement[npos++] = '\''; /* Close Quote */ break; default: /* a numeric type */ strcpy(&new_statement[npos], param_string); npos += strlen(param_string); break; } } /* end, for */ // make sure new_statement is always null-terminated new_statement[npos] = '\0'; return SQL_SUCCESS; } // This function returns a pointer to static memory! char * convert_escape(char *value) { char key[32], val[256]; static char escape[256]; SIMPLE_TIME st; sscanf(value, "%[^'] '%[^']'", key, val); mylog("convert_escape: key='%s', val='%s'\n", key, val); if ( ! strncmp(key, "d", 1)) { sscanf(val, "%4d-%2d-%2d", &st.y, &st.m, &st.d); sprintf(escape, "'%.2d-%.2d-%.4d'", st.m, st.d, st.y); } else if (! strncmp(key, "t", 1)) { sprintf(escape, "'%s'", val); } else if (! strncmp(key, "ts", 2)) { sscanf(val, "%4d-%2d-%2d %2d:%2d:%2d", &st.y, &st.m, &st.d, &st.hh, &st.mm, &st.ss); strcpy(escape, convert_time(&st)); } else { return NULL; } return escape; } int monthToNumber(char *mon) { int m = 0; if ( ! stricmp(mon, "Jan")) m = 1; else if ( ! stricmp(mon, "Feb")) m = 2; else if ( ! stricmp(mon, "Mar")) m = 3; else if ( ! stricmp(mon, "Apr")) m = 4; else if ( ! stricmp(mon, "May")) m = 5; else if ( ! stricmp(mon, "Jun")) m = 6; else if ( ! stricmp(mon, "Jul")) m = 7; else if ( ! stricmp(mon, "Aug")) m = 8; else if ( ! stricmp(mon, "Sep")) m = 9; else if ( ! stricmp(mon, "Oct")) m = 10; else if ( ! stricmp(mon, "Nov")) m = 11; else if ( ! stricmp(mon, "Dec")) m = 12; return m; } char * convert_money(char *s) { size_t i = 0, out = 0; for (i = 0; i < strlen(s); i++) { if (s[i] == '$' || s[i] == ',' || s[i] == ')') ; // skip these characters else if (s[i] == '(') s[out++] = '-'; else s[out++] = s[i]; } s[out] = '\0'; return s; } /* Convert a discrete time into a localized string */ char * convert_time(SIMPLE_TIME *st) { struct tm tim; static char buf[1024]; mylog("convert_time: m=%d,d=%d,y=%d,hh=%d,mm=%d,ss=%d\n", st->m, st->d, st->y, st->hh, st->mm, st->ss); memset(&tim, 0, sizeof(tim)); tim.tm_mon = st->m - 1; tim.tm_mday = st->d; tim.tm_year = st->y - 1900; tim.tm_hour = st->hh; tim.tm_min = st->mm; tim.tm_sec = st->ss; /* Dont bother trying to figure out the day of week because postgres will determine it correctly. However, the timezone should be taken into account. $$$$ */ // tim.tm_isdst = _daylight; strftime(buf, sizeof(buf), "%b %d %H:%M:%S %Y", &tim); mylog("convert_time: buf = '%s'\n", buf); return buf; } /* This function parses a character string for date/time info and fills in SIMPLE_TIME */ /* It does not zero out SIMPLE_TIME in case it is desired to initialize it with a value */ char parse_datetime(char *buf, SIMPLE_TIME *st) { int y,m,d,hh,mm,ss; int nf; y = m = d = hh = mm = ss = 0; if (buf[4] == '-') /* year first */ nf = sscanf(buf, "%4d-%2d-%2d %2d:%2d:%2d", &y,&m,&d,&hh,&mm,&ss); else nf = sscanf(buf, "%2d-%2d-%4d %2d:%2d:%2d", &m,&d,&y,&hh,&mm,&ss); if (nf == 5 || nf == 6) { st->y = y; st->m = m; st->d = d; st->hh = hh; st->mm = mm; st->ss = ss; return TRUE; } if (buf[4] == '-') /* year first */ nf = sscanf(buf, "%4d-%2d-%2d", &y, &m, &d); else nf = sscanf(buf, "%2d-%2d-%4d", &m, &d, &y); if (nf == 3) { st->y = y; st->m = m; st->d = d; return TRUE; } nf = sscanf(buf, "%2d:%2d:%2d", &hh, &mm, &ss); if (nf == 2 || nf == 3) { st->hh = hh; st->mm = mm; st->ss = ss; return TRUE; } return FALSE; } /* Change linefeed to carriage-return/linefeed */ char * convert_linefeeds(char *si, char *dst, size_t max) { size_t i = 0, out = 0; static char sout[TEXT_FIELD_SIZE+5]; char *p; if (dst) p = dst; else { p = sout; max = sizeof(sout); } p[0] = '\0'; for (i = 0; i < strlen(si) && out < max-2; i++) { if (si[i] == '\n') { p[out++] = '\r'; p[out++] = '\n'; } else p[out++] = si[i]; } p[out] = '\0'; return p; } /* Change carriage-return/linefeed to just linefeed */ char * convert_returns(char *si, char *dst, int used) { size_t i = 0, out = 0, max; static char sout[TEXT_FIELD_SIZE+5]; char *p; if (dst) p = dst; else p = sout; p[0] = '\0'; if (used == SQL_NTS) max = strlen(si); else max = used; for (i = 0; i < max; i++) { if (si[i] == '\r' && i+1 < strlen(si) && si[i+1] == '\n') continue; else p[out++] = si[i]; } p[out] = '\0'; return p; } int convert_pgbinary_to_char(char *value, char *rgbValue, int cbValueMax) { return 0; } unsigned int conv_from_octal(unsigned char *s) { int i, y=0; for (i = 1; i <= 3; i++) { y += (s[i] - 48) * (int) pow(8, 3-i); } return y; } // convert octal escapes to bytes int convert_from_pgbinary(unsigned char *value, unsigned char *rgbValue, int cbValueMax) { size_t i; int o=0; for (i = 0; i < strlen(value); ) { if (value[i] == '\\') { rgbValue[o] = conv_from_octal(&value[i]); i += 4; } else { rgbValue[o] = value[i++]; } mylog("convert_from_pgbinary: i=%d, rgbValue[%d] = %d, %c\n", i, o, rgbValue[o], rgbValue[o]); o++; } return o; } char * conv_to_octal(unsigned char val) { int i; static char x[6]; x[0] = '\\'; x[1] = '\\'; x[5] = '\0'; for (i = 4; i > 1; i--) { x[i] = (val & 7) + 48; val >>= 3; } return x; } // convert non-ascii bytes to octal escape sequences int convert_to_pgbinary(unsigned char *in, char *out, int len) { int i, o=0; for (i = 0; i < len; i++) { mylog("convert_to_pgbinary: in[%d] = %d, %c\n", i, in[i], in[i]); if (in[i] < 32 || in[i] > 126) { strcpy(&out[o], conv_to_octal(in[i])); o += 5; } else out[o++] = in[i]; } mylog("convert_to_pgbinary: returning %d, out='%.*s'\n", o, o, out); return o; }