/*------- * 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 "readme.txt" for copyright and license information. *------- */ /* Multibyte support Eiji Tokuya 2001-03-15 */ #include "convert.h" #include "unicode_support.h" #include "misc.h" #ifdef WIN32 #include #define HAVE_LOCALE_H #endif /* WIN32 */ #include #include #include #include "multibyte.h" #include #ifdef HAVE_LOCALE_H #include #endif #include #include #include #include "statement.h" #include "qresult.h" #include "bind.h" #include "pgtypes.h" #include "lobj.h" #include "connection.h" #include "catfunc.h" #include "pgapifunc.h" CSTR NAN_STRING = "NaN"; CSTR INFINITY_STRING = "Infinity"; CSTR MINFINITY_STRING = "-Infinity"; #if defined(WIN32) || defined(__CYGWIN__) #define TIMEZONE_GLOBAL _timezone #define TZNAME_GLOBAL _tzname #define DAYLIGHT_GLOBAL _daylight #elif defined(HAVE_INT_TIMEZONE) #define TIMEZONE_GLOBAL timezone #define TZNAME_GLOBAL tzname #define DAYLIGHT_GLOBAL daylight #endif /* * How to map ODBC scalar functions {fn func(args)} to Postgres. * This is just a simple substitution. List augmented from: * http://www.merant.com/datadirect/download/docs/odbc16/Odbcref/rappc.htm * - thomas 2000-04-03 */ static const struct { /* * There's a horrible hack in odbc_name field: if it begins with a %, * the digit after the % indicates the number of arguments. Otherwise, * the entry matches any number of args. */ char *odbc_name; char *pgsql_name; } mapFuncs[] = { /* { "ASCII", "ascii" }, built_in */ {"CHAR", "chr($*)" }, {"CONCAT", "concat($1::text, $2::text)" }, /* { "DIFFERENCE", "difference" }, how to ? */ {"INSERT", "substring($1 from 1 for $2 - 1) || $4 || substring($1 from $2 + $3)" }, {"LCASE", "lower($*)" }, /* {"LEFT", "left" }, built_in */ {"%2LOCATE", "strpos($2, $1)" }, /* 2 parameters */ {"%3LOCATE", "strpos(substring($2 from $3), $1) + $3 - 1" }, /* 3 parameters */ {"LENGTH", "char_length($*)"}, /* { "LTRIM", "ltrim" }, built_in */ /* {"RIGHT", "right" }, built_in */ {"SPACE", "repeat(' ', $1)" }, /* { "REPEAT", "repeat" }, built_in */ /* { "REPLACE", "replace" }, ??? */ /* { "RTRIM", "rtrim" }, built_in */ /* { "SOUNDEX", "soundex" }, how to ? */ {"SUBSTRING", "substr($*)" }, {"UCASE", "upper($*)" }, /* { "ABS", "abs" }, built_in */ /* { "ACOS", "acos" }, built_in */ /* { "ASIN", "asin" }, built_in */ /* { "ATAN", "atan" }, built_in */ /* { "ATAN2", "atan2" }, built_in */ {"CEILING", "ceil($*)" }, /* { "COS", "cos" }, built_in */ /* { "COT", "cot" }, built_in */ /* { "DEGREES", "degrees" }, built_in */ /* { "EXP", "exp" }, built_in */ /* { "FLOOR", "floor" }, built_in */ {"LOG", "ln($*)" }, {"LOG10", "log($*)" }, /* { "MOD", "mod" }, built_in */ /* { "PI", "pi" }, built_in */ /* {"POWER", "power" }, built_in */ /* { "RADIANS", "radians" }, built_in */ {"%0RAND", "random()" }, /* 0 parameters */ {"%1RAND", "(setseed($1) * .0 + random())" }, /* 1 parameters */ /* { "ROUND", "round" }, built_in */ /* { "SIGN", "sign" }, built_in */ /* { "SIN", "sin" }, built_in */ /* { "SQRT", "sqrt" }, built_in */ /* { "TAN", "tan" }, built_in */ {"TRUNCATE", "trunc($*)" }, {"CURRENT_DATE", "current_date" }, {"CURRENT_TIME", "current_time" }, {"CURRENT_TIMESTAMP", "current_timestamp" }, {"LOCALTIME", "localtime" }, {"LOCALTIMESTAMP", "localtimestamp" }, {"CURRENT_USER", "cast(current_user as text)" }, {"SESSION_USER", "cast(session_user as text)" }, {"CURDATE", "current_date" }, {"CURTIME", "current_time" }, {"DAYNAME", "to_char($1, 'Day')" }, {"DAYOFMONTH", "cast(extract(day from $1) as integer)" }, {"DAYOFWEEK", "(cast(extract(dow from $1) as integer) + 1)" }, {"DAYOFYEAR", "cast(extract(doy from $1) as integer)" }, {"HOUR", "cast(extract(hour from $1) as integer)" }, {"MINUTE", "cast(extract(minute from $1) as integer)" }, {"MONTH", "cast(extract(month from $1) as integer)" }, {"MONTHNAME", " to_char($1, 'Month')" }, /* { "NOW", "now" }, built_in */ {"QUARTER", "cast(extract(quarter from $1) as integer)" }, {"SECOND", "cast(extract(second from $1) as integer)" }, {"WEEK", "cast(extract(week from $1) as integer)" }, {"YEAR", "cast(extract(year from $1) as integer)" }, // TIMESTAMPADD() {"TIMESTAMPADD(SQL_TSI_YEAR", "($3+make_interval(years := $2))" }, {"TIMESTAMPADD(SQL_TSI_MONTH", "($3+make_interval(months := $2))" }, {"TIMESTAMPADD(SQL_TSI_WEEK", "($3+make_interval(weeks := $2))" }, {"TIMESTAMPADD(SQL_TSI_DAY", "($3+make_interval(days := $2))" }, {"TIMESTAMPADD(SQL_TSI_HOUR", "($3+make_interval(hours := $2))" }, {"TIMESTAMPADD(SQL_TSI_MINUTE", "($3+make_interval(mins := $2))" }, {"TIMESTAMPADD(SQL_TSI_SECOND", "($3+make_interval(secs := $2))" }, {"TIMESTAMPADD(SQL_TSI_FRAC_SECOND", "($3+make_interval(secs := $2::float / 1000000))" }, // TIMESTAMPDIFF() /* doesn't work properly? {"TIMESTAMPDIFF(SQL_TSI_YEAR", "cast(extract(year from ($3 - $2)) as integer)" }, {"TIMESTAMPDIFF(SQL_TSI_MONTH", "cast(extract(month from ($3 - $2)) as integer)" }, {"TIMESTAMPDIFF(SQL_TSI_QUARTER", "cast(extract(quarter from ($3 - $2)) as integer)" }, {"TIMESTAMPDIFF(SQL_TSI_WEEK", "cast(extract(week from ($3 - $2)) as integer)" }, */ /* {"TIMESTAMPDIFF(SQL_TSI_DAY", "cast(extract(day from ($3 - $2)) as integer)" }, {"TIMESTAMPDIFF(SQL_TSI_HOUR", "cast(extract(hour from ($3 - $2)) as integer)" }, {"TIMESTAMPDIFF(SQL_TSI_HOUR", "(extract(epoch from $3) - extract(epoch from $2)) / 3600" }, {"TIMESTAMPDIFF(SQL_TSI_MINUTE", "cast(extract(minute from ($3 - $2)) as integer)" }, {"TIMESTAMPDIFF(SQL_TSI_SECOND", "cast(extract(second from ($3 - $2)) as integer)" }, */ {"TIMESTAMPDIFF(SQL_TSI_DAY", "cast((extract(epoch from $3) - extract(epoch from $2)) / (24*60*60) as int)" }, {"TIMESTAMPDIFF(SQL_TSI_HOUR", "cast((extract(epoch from $3) - extract(epoch from $2)) / 3600 as int)" }, {"TIMESTAMPDIFF(SQL_TSI_MINUTE", "cast((extract(epoch from $3) - extract(epoch from $2)) / 60 as int)" }, {"TIMESTAMPDIFF(SQL_TSI_SECOND", "cast((extract(epoch from $3) - extract(epoch from $2)) as int)" }, {"TIMESTAMPDIFF(SQL_TSI_FRAC_SECOND", "mod(cast(extract(second from ($3 - $2)) as numeric), 1.0) * 1000000" }, /* { "EXTRACT", "extract" }, built_in */ /* { "DATABASE", "database" }, */ {"IFNULL", "coalesce($*)" }, {"USER", "cast(current_user as text)" }, {0, 0} }; typedef struct { int infinity; int m; int d; int y; int hh; int mm; int ss; int fr; } SIMPLE_TIME; static const char *mapFunction(const char *func, int param_count, const char * keyword); static BOOL convert_money(const char *s, char *sout, size_t soutmax); static char parse_datetime(const char *buf, SIMPLE_TIME *st); size_t convert_linefeeds(const char *s, char *dst, size_t max, BOOL convlf, BOOL *changed); static size_t convert_from_pgbinary(const char *value, char *rgbValue, SQLLEN cbValueMax); static int convert_lo(StatementClass *stmt, const void *value, SQLSMALLINT fCType, PTR rgbValue, SQLLEN cbValueMax, SQLLEN *pcbValue); static int conv_from_octal(const char *s); static SQLLEN pg_bin2hex(const char *src, char *dst, SQLLEN length); #ifdef UNICODE_SUPPORT static SQLLEN pg_bin2whex(const char *src, SQLWCHAR *dst, SQLLEN length); #endif /* UNICODE_SUPPORT */ /*--------- * 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 *--------- */ /* * Macros for unsigned long handling. */ #ifdef WIN32 #define ATOI32U(val) strtoul(val, NULL, 10) #elif defined(HAVE_STRTOUL) #define ATOI32U(val) strtoul(val, NULL, 10) #else /* HAVE_STRTOUL */ #define ATOI32U atol #endif /* WIN32 */ /* * Macros for BIGINT handling. */ #ifdef ODBCINT64 #ifdef WIN32 #define ATOI64(val) _strtoi64(val, NULL, 10) #define ATOI64U(val) _strtoui64(val, NULL, 10) #elif (SIZEOF_LONG == 8) #define ATOI64(val) strtol(val, NULL, 10) #define ATOI64U(val) strtoul(val, NULL, 10) #else #if defined(HAVE_STRTOLL) #define ATOI64(val) strtoll(val, NULL, 10) #define ATOI64U(val) strtoull(val, NULL, 10) #else static ODBCINT64 ATOI64(const char *val) { ODBCINT64 ll; sscanf(val, "%lld", &ll); return ll; } static unsigned ODBCINT64 ATOI64U(const char *val) { unsigned ODBCINT64 ll; sscanf(val, "%llu", &ll); return ll; } #endif /* HAVE_STRTOLL */ #endif /* WIN32 */ #endif /* ODBCINT64 */ static void ResolveNumericParam(const SQL_NUMERIC_STRUCT *ns, char *chrform); static void parse_to_numeric_struct(const char *wv, SQL_NUMERIC_STRUCT *ns, BOOL *overflow); /* * TIMESTAMP <-----> SIMPLE_TIME * precision support since 7.2. * time zone support is unavailable(the stuff is unreliable) */ static BOOL timestamp2stime(const char *str, SIMPLE_TIME *st, BOOL *bZone, int *zone) { char rest[64], bc[16], *ptr; int scnt, i; int y, m, d, hh, mm, ss; #ifdef TIMEZONE_GLOBAL long timediff; #endif BOOL withZone = *bZone; *bZone = FALSE; *zone = 0; st->fr = 0; st->infinity = 0; rest[0] = '\0'; bc[0] = '\0'; if ((scnt = sscanf(str, "%4d-%2d-%2d %2d:%2d:%2d%31s %15s", &y, &m, &d, &hh, &mm, &ss, rest, bc)) < 6) { if (scnt == 3) /* date */ { st->y = y; st->m = m; st->d = d; st->hh = 0; st->mm = 0; st->ss = 0; return TRUE; } if ((scnt = sscanf(str, "%2d:%2d:%2d%31s %15s", &hh, &mm, &ss, rest, bc)) < 3) return FALSE; else { st->hh = hh; st->mm = mm; st->ss = ss; if (scnt == 3) /* time */ return TRUE; } } else { st->y = y; st->m = m; st->d = d; st->hh = hh; st->mm = mm; st->ss = ss; if (scnt == 6) return TRUE; } switch (rest[0]) { case '+': *bZone = TRUE; *zone = atoi(&rest[1]); break; case '-': *bZone = TRUE; *zone = -atoi(&rest[1]); break; case '.': if ((ptr = strchr(rest, '+')) != NULL) { *bZone = TRUE; *zone = atoi(&ptr[1]); *ptr = '\0'; } else if ((ptr = strchr(rest, '-')) != NULL) { *bZone = TRUE; *zone = -atoi(&ptr[1]); *ptr = '\0'; } for (i = 1; i < 10; i++) { if (!isdigit((UCHAR) rest[i])) break; } for (; i < 10; i++) rest[i] = '0'; rest[i] = '\0'; st->fr = atoi(&rest[1]); break; case 'B': if (stricmp(rest, "BC") == 0) st->y *= -1; return TRUE; default: return TRUE; } if (stricmp(bc, "BC") == 0) { st->y *= -1; } if (!withZone || !*bZone || st->y < 1970) return TRUE; #ifdef TIMEZONE_GLOBAL if (!TZNAME_GLOBAL[0] || !TZNAME_GLOBAL[0][0]) { *bZone = FALSE; return TRUE; } timediff = TIMEZONE_GLOBAL + (*zone) * 3600; if (!DAYLIGHT_GLOBAL && timediff == 0) /* the same timezone */ return TRUE; else { struct tm tm, *tm2; time_t time0; *bZone = FALSE; tm.tm_year = st->y - 1900; tm.tm_mon = st->m - 1; tm.tm_mday = st->d; tm.tm_hour = st->hh; tm.tm_min = st->mm; tm.tm_sec = st->ss; tm.tm_isdst = -1; time0 = mktime(&tm); if (time0 < 0) return TRUE; if (tm.tm_isdst > 0) timediff -= 3600; if (timediff == 0) /* the same time zone */ return TRUE; time0 -= timediff; #ifdef HAVE_LOCALTIME_R if (time0 >= 0 && (tm2 = localtime_r(&time0, &tm)) != NULL) #else if (time0 >= 0 && (tm2 = localtime(&time0)) != NULL) #endif /* HAVE_LOCALTIME_R */ { st->y = tm2->tm_year + 1900; st->m = tm2->tm_mon + 1; st->d = tm2->tm_mday; st->hh = tm2->tm_hour; st->mm = tm2->tm_min; st->ss = tm2->tm_sec; *bZone = TRUE; } } #endif /* TIMEZONE_GLOBAL */ return TRUE; } static int stime2timestamp(const SIMPLE_TIME *st, char *str, size_t bufsize, BOOL bZone, int precision) { char precstr[16], zonestr[16]; int i; precstr[0] = '\0'; if (st->infinity > 0) { return snprintf(str, bufsize, "%s", INFINITY_STRING); } else if (st->infinity < 0) { return snprintf(str, bufsize, "%s", MINFINITY_STRING); } if (precision > 0 && st->fr) { SPRINTF_FIXED(precstr, ".%09d", st->fr); if (precision < 9) precstr[precision + 1] = '\0'; else if (precision > 9) precision = 9; for (i = precision; i > 0; i--) { if (precstr[i] != '0') break; precstr[i] = '\0'; } if (i == 0) precstr[i] = '\0'; } zonestr[0] = '\0'; #ifdef TIMEZONE_GLOBAL if (bZone && TZNAME_GLOBAL[0] && TZNAME_GLOBAL[0][0] && st->y >= 1970) { long zoneint; struct tm tm; time_t time0; zoneint = TIMEZONE_GLOBAL; if (DAYLIGHT_GLOBAL && st->y >= 1900) { tm.tm_year = st->y - 1900; tm.tm_mon = st->m - 1; tm.tm_mday = st->d; tm.tm_hour = st->hh; tm.tm_min = st->mm; tm.tm_sec = st->ss; tm.tm_isdst = -1; time0 = mktime(&tm); if (time0 >= 0 && tm.tm_isdst > 0) zoneint -= 3600; } if (zoneint > 0) SPRINTF_FIXED(zonestr, "-%02d", (int) zoneint / 3600); else SPRINTF_FIXED(zonestr, "+%02d", -(int) zoneint / 3600); } #endif /* TIMEZONE_GLOBAL */ if (st->y < 0) return snprintf(str, bufsize, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d%s%s BC", -st->y, st->m, st->d, st->hh, st->mm, st->ss, precstr, zonestr); else return snprintf(str, bufsize, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d%s%s", st->y, st->m, st->d, st->hh, st->mm, st->ss, precstr, zonestr); } static SQLINTERVAL interval2itype(SQLSMALLINT ctype) { SQLINTERVAL sqlitv = 0; switch (ctype) { case SQL_C_INTERVAL_YEAR: sqlitv = SQL_IS_YEAR; break; case SQL_C_INTERVAL_MONTH: sqlitv = SQL_IS_MONTH; break; case SQL_C_INTERVAL_YEAR_TO_MONTH: sqlitv = SQL_IS_YEAR_TO_MONTH; break; case SQL_C_INTERVAL_DAY: sqlitv = SQL_IS_DAY; break; case SQL_C_INTERVAL_HOUR: sqlitv = SQL_IS_HOUR; break; case SQL_C_INTERVAL_DAY_TO_HOUR: sqlitv = SQL_IS_DAY_TO_HOUR; break; case SQL_C_INTERVAL_MINUTE: sqlitv = SQL_IS_MINUTE; break; case SQL_C_INTERVAL_DAY_TO_MINUTE: sqlitv = SQL_IS_DAY_TO_MINUTE; break; case SQL_C_INTERVAL_HOUR_TO_MINUTE: sqlitv = SQL_IS_HOUR_TO_MINUTE; break; case SQL_C_INTERVAL_SECOND: sqlitv = SQL_IS_SECOND; break; case SQL_C_INTERVAL_DAY_TO_SECOND: sqlitv = SQL_IS_DAY_TO_SECOND; break; case SQL_C_INTERVAL_HOUR_TO_SECOND: sqlitv = SQL_IS_HOUR_TO_SECOND; break; case SQL_C_INTERVAL_MINUTE_TO_SECOND: sqlitv = SQL_IS_MINUTE_TO_SECOND; break; } return sqlitv; } /* * Interval data <-----> SQL_INTERVAL_STRUCT */ static int getPrecisionPart(int precision, const char * precPart) { char fraction[] = "000000000"; int fracs = sizeof(fraction) - 1; size_t cpys; if (precision < 0) precision = 6; /* default */ if (precision == 0) return 0; cpys = strlen(precPart); if (cpys > fracs) cpys = fracs; memcpy(fraction, precPart, cpys); fraction[precision] = '\0'; return atoi(fraction); } static BOOL interval2istruct(SQLSMALLINT ctype, int precision, const char *str, SQL_INTERVAL_STRUCT *st) { char lit1[64], lit2[64]; int scnt, years, mons, days, hours, minutes, seconds; BOOL sign; SQLINTERVAL itype = interval2itype(ctype); memset(st, 0, sizeof(SQL_INTERVAL_STRUCT)); if ((scnt = sscanf(str, "%d-%d", &years, &mons)) >=2) { if (SQL_IS_YEAR_TO_MONTH == itype) { sign = years < 0 ? SQL_TRUE : SQL_FALSE; st->interval_type = itype; st->interval_sign = sign; st->intval.year_month.year = sign ? (-years) : years; st->intval.year_month.month = mons; return TRUE; } return FALSE; } else if (scnt = sscanf(str, "%d %02d:%02d:%02d.%09s", &days, &hours, &minutes, &seconds, lit2), 5 == scnt || 4 == scnt) { sign = days < 0 ? SQL_TRUE : SQL_FALSE; st->interval_type = itype; st->interval_sign = sign; st->intval.day_second.day = sign ? (-days) : days; st->intval.day_second.hour = hours; st->intval.day_second.minute = minutes; st->intval.day_second.second = seconds; if (scnt > 4) st->intval.day_second.fraction = getPrecisionPart(precision, lit2); return TRUE; } else if ((scnt = sscanf(str, "%d %10s %d %10s", &years, lit1, &mons, lit2)) >=4) { if (strnicmp(lit1, "year", 4) == 0 && strnicmp(lit2, "mon", 2) == 0 && (SQL_IS_MONTH == itype || SQL_IS_YEAR_TO_MONTH == itype)) { sign = years < 0 ? SQL_TRUE : SQL_FALSE; st->interval_type = itype; st->interval_sign = sign; st->intval.year_month.year = sign ? (-years) : years; st->intval.year_month.month = sign ? (-mons) : mons; return TRUE; } return FALSE; } if ((scnt = sscanf(str, "%d %10s %d", &years, lit1, &days)) == 2) { sign = years < 0 ? SQL_TRUE : SQL_FALSE; if (SQL_IS_YEAR == itype && (stricmp(lit1, "year") == 0 || stricmp(lit1, "years") == 0)) { st->interval_type = itype; st->interval_sign = sign; st->intval.year_month.year = sign ? (-years) : years; return TRUE; } if (SQL_IS_MONTH == itype && (stricmp(lit1, "mon") == 0 || stricmp(lit1, "mons") == 0)) { st->interval_type = itype; st->interval_sign = sign; st->intval.year_month.month = sign ? (-years) : years; return TRUE; } if (SQL_IS_DAY == itype && (stricmp(lit1, "day") == 0 || stricmp(lit1, "days") == 0)) { st->interval_type = itype; st->interval_sign = sign; st->intval.day_second.day = sign ? (-years) : years; return TRUE; } return FALSE; } if (itype == SQL_IS_YEAR || itype == SQL_IS_MONTH || itype == SQL_IS_YEAR_TO_MONTH) { /* these formats should've been handled above already */ return FALSE; } scnt = sscanf(str, "%d %10s %02d:%02d:%02d.%09s", &days, lit1, &hours, &minutes, &seconds, lit2); if (scnt == 5 || scnt == 6) { if (strnicmp(lit1, "day", 3) != 0) return FALSE; sign = days < 0 ? SQL_TRUE : SQL_FALSE; st->interval_type = itype; st->interval_sign = sign; st->intval.day_second.day = sign ? (-days) : days; st->intval.day_second.hour = sign ? (-hours) : hours; st->intval.day_second.minute = minutes; st->intval.day_second.second = seconds; if (scnt > 5) st->intval.day_second.fraction = getPrecisionPart(precision, lit2); return TRUE; } scnt = sscanf(str, "%02d:%02d:%02d.%09s", &hours, &minutes, &seconds, lit2); if (scnt == 3 || scnt == 4) { sign = hours < 0 ? SQL_TRUE : SQL_FALSE; st->interval_type = itype; st->interval_sign = sign; st->intval.day_second.hour = sign ? (-hours) : hours; st->intval.day_second.minute = minutes; st->intval.day_second.second = seconds; if (scnt > 3) st->intval.day_second.fraction = getPrecisionPart(precision, lit2); return TRUE; } return FALSE; } #ifdef HAVE_LOCALE_H /* * Get the decimal point of the current locale. * * XXX: This isn't thread-safe, if another thread changes the locale with * setlocale() concurrently. There are two problems with that: * * 1. The pointer returned by localeconv(), or the lc->decimal_point string, * might be invalidated by calls in other threads. Until someone comes up * with a thread-safe version of localeconv(), there isn't much we can do * about that. (libc implementations that return a static buffer (like glibc) * happen to be safe from the lconv struct being invalidated, but the * decimal_point string might still not point to a static buffer). * * 2. The between the call to sprintf() and get_current_decimal_point(), the * decimal point might change. That would cause set_server_decimal_point() * to fail to recognize a decimal separator, and we might send a numeric * string to the server that the server won't recognize. This would cause * the query to fail in the server. * * XXX: we only take into account the first byte of the decimal separator. */ static char get_current_decimal_point(void) { struct lconv *lc = localeconv(); return lc->decimal_point[0]; } /* * Modify the string representation of a numeric/float value, converting the * decimal point from '.' to the correct decimal separator of the current * locale. */ static void set_server_decimal_point(char *num, SQLLEN len) { char current_decimal_point = get_current_decimal_point(); char *str; SQLLEN i; if ('.' == current_decimal_point) return; i = 0; for (str = num; '\0' != *str; str++) { if (*str == current_decimal_point) { *str = '.'; break; } if (len != SQL_NTS && i++ >= len) break; } } /* * Inverse of set_server_decimal_point. */ static void set_client_decimal_point(char *num) { char current_decimal_point = get_current_decimal_point(); char *str; if ('.' == current_decimal_point) return; for (str = num; '\0' != *str; str++) { if (*str == '.') { *str = current_decimal_point; break; } } } #else static void set_server_decimal_point(char *num) {} static void set_client_decimal_point(char *num, BOOL) {} #endif /* HAVE_LOCALE_H */ /* This is called by SQLFetch() */ int copy_and_convert_field_bindinfo(StatementClass *stmt, OID field_type, int atttypmod, void *value, int col) { ARDFields *opts = SC_get_ARDF(stmt); BindInfoClass *bic; SQLULEN offset = opts->row_offset_ptr ? *opts->row_offset_ptr : 0; if (opts->allocated <= col) extend_column_bindings(opts, col + 1); bic = &(opts->bindings[col]); SC_set_current_col(stmt, -1); return copy_and_convert_field(stmt, field_type, atttypmod, value, bic->returntype, bic->precision, (PTR) (bic->buffer + offset), bic->buflen, LENADDR_SHIFT(bic->used, offset), LENADDR_SHIFT(bic->indicator, offset)); } /* * Is 'str' a valid integer literal, consisting only of ASCII characters * 0-9 ? * * Also, *negative is set to TRUE if the value was negative. * * We don't check for overflow here. This is just to decide if we need to * quote the value. */ static BOOL valid_int_literal(const char *str, SQLLEN len, BOOL *negative) { SQLLEN i = 0; /* Check there is a minus sign in front */ if ((len == SQL_NTS || len > 0) && str[0] == '-') { i++; *negative = TRUE; } else *negative = FALSE; /* * Must begin with a digit. This also rejects empty strings and '-'. */ if (i == len || !(str[i] >= '0' && str[i] <= '9')) return FALSE; for (; str[i] && (len == SQL_NTS || i < len); i++) { if (!(str[i] >= '0' && str[i] <= '9')) return FALSE; } return TRUE; } static double get_double_value(const char *str) { if (stricmp(str, NAN_STRING) == 0) #ifdef NAN return (double) NAN; #else { double a = .0; return .0 / a; } #endif /* NAN */ else if (stricmp(str, INFINITY_STRING) == 0) #ifdef INFINITY return (double) INFINITY; #else return (double) (HUGE_VAL * HUGE_VAL); #endif /* INFINITY */ else if (stricmp(str, MINFINITY_STRING) == 0) #ifdef INFINITY return (double) -INFINITY; #else return (double) -(HUGE_VAL * HUGE_VAL); #endif /* INFINITY */ return atof(str); } static int char2guid(const char *str, SQLGUID *g) { /* * SQLGUID.Data1 is an "unsigned long" on some platforms, and * "unsigned int" on others. For format "%08X", it should be an * "unsigned int", so use a temporary variable for it. */ unsigned int Data1; if (sscanf(str, "%08X-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX", &Data1, &g->Data2, &g->Data3, &g->Data4[0], &g->Data4[1], &g->Data4[2], &g->Data4[3], &g->Data4[4], &g->Data4[5], &g->Data4[6], &g->Data4[7]) < 11) return COPY_GENERAL_ERROR; g->Data1 = Data1; return COPY_OK; } static int effective_fraction(int fraction, int *width) { for (*width = 9; fraction % 10 == 0; (*width)--, fraction /= 10) ; return fraction; } static int get_terminator_len(SQLSMALLINT fCType) { switch (fCType) { #ifdef UNICODE_SUPPORT case SQL_C_WCHAR: return WCLEN; #endif /* UNICODE_SUPPORT */ case SQL_C_BINARY: return 0; } /* SQL_C_CHAR or INTERNAL_ASIS_TYPE */ return 1; } static SQLLEN get_adjust_len(SQLSMALLINT fCType, SQLLEN len) { switch (fCType) { #ifdef UNICODE_SUPPORT case SQL_C_WCHAR: return (len / WCLEN) * WCLEN; #endif /* UNICODE_SUPPORT */ } return len; } #define BYTEA_PROCESS_ESCAPE 1 #define BYTEA_PROCESS_BINARY 2 static int setup_getdataclass(SQLLEN * const length_return, const char ** const ptr_return, int *needbuflen_return, GetDataClass * const pgdc, const char *neut_str, const OID field_type, const SQLSMALLINT fCType, const SQLLEN cbValueMax, const ConnectionClass * const conn) { SQLLEN len = (-2); const char *ptr = NULL; int needbuflen = 0; int result = COPY_OK; BOOL lf_conv = conn->connInfo.lf_conversion; int bytea_process_kind = 0; BOOL already_processed = FALSE; BOOL changed = FALSE; int len_for_wcs_term = 0; #ifdef UNICODE_SUPPORT char *allocbuf = NULL; int unicode_count = -1; BOOL localize_needed = FALSE; BOOL hybrid = FALSE; #endif /* UNICODE_SUPPORT */ if (PG_TYPE_BYTEA == field_type) { if (SQL_C_BINARY == fCType) bytea_process_kind = BYTEA_PROCESS_BINARY; else if (0 == strnicmp(neut_str, "\\x", 2)) /* hex format */ neut_str += 2; else bytea_process_kind = BYTEA_PROCESS_ESCAPE; } #ifdef UNICODE_SUPPORT if (0 == bytea_process_kind) { if (get_convtype() > 0) /* conversion between the current locale is available */ { BOOL wcs_debug = conn->connInfo.wcs_debug; BOOL same_encoding = (conn->ccsc == pg_CS_code(conn->locale_encoding)); BOOL is_utf8 = (UTF8 == conn->ccsc); switch (field_type) { case PG_TYPE_UNKNOWN: case PG_TYPE_BPCHAR: case PG_TYPE_VARCHAR: case PG_TYPE_TEXT: case PG_TYPE_BPCHARARRAY: case PG_TYPE_VARCHARARRAY: case PG_TYPE_TEXTARRAY: if (SQL_C_CHAR == fCType || SQL_C_BINARY == fCType) localize_needed = (!same_encoding || wcs_debug); if (SQL_C_WCHAR == fCType) hybrid = (!is_utf8 || (same_encoding && wcs_debug)); } MYLOG(0, "localize=%d hybrid=%d is_utf8=%d same_encoding=%d wcs_debug=%d\n", localize_needed, hybrid, is_utf8, same_encoding, wcs_debug); } } if (fCType == SQL_C_WCHAR) { if (BYTEA_PROCESS_ESCAPE == bytea_process_kind) unicode_count = convert_from_pgbinary(neut_str, NULL, 0) * 2; else if (hybrid) { MYLOG(0, "hybrid estimate\n"); if ((unicode_count = bindcol_hybrid_estimate(neut_str, lf_conv, &allocbuf)) < 0) { result = COPY_INVALID_STRING_CONVERSION; goto cleanup; } } else /* normally */ { unicode_count = utf8_to_ucs2_lf(neut_str, SQL_NTS, lf_conv, NULL, 0, FALSE); } len = WCLEN * unicode_count; already_processed = changed = TRUE; } else if (localize_needed) { if ((len = bindcol_localize_estimate(neut_str, lf_conv, &allocbuf)) < 0) { result = COPY_INVALID_STRING_CONVERSION; goto cleanup; } already_processed = changed = TRUE; } #endif /* UNICODE_SUPPORT */ if (already_processed) /* skip */ ; else if (0 != bytea_process_kind) { len = convert_from_pgbinary(neut_str, NULL, 0); if (BYTEA_PROCESS_BINARY != bytea_process_kind) len *= 2; changed = TRUE; } else /* convert linefeeds to carriage-return/linefeed */ len = convert_linefeeds(neut_str, NULL, 0, lf_conv, &changed); /* just returns length info */ if (cbValueMax == 0) { result = COPY_RESULT_TRUNCATED; goto cleanup; } if (!pgdc->ttlbuf) pgdc->ttlbuflen = 0; needbuflen = len + get_terminator_len(fCType); if (SQL_C_BINARY == fCType) { /* * Though Binary doesn't have NULL terminator, * bindcol_localize_exec() needs output buffer * for NULL terminator. */ len_for_wcs_term = 1; } if (changed || needbuflen > cbValueMax) { if (needbuflen > (SQLLEN) pgdc->ttlbuflen) { pgdc->ttlbuf = realloc(pgdc->ttlbuf, needbuflen + len_for_wcs_term); pgdc->ttlbuflen = needbuflen; } already_processed = FALSE; #ifdef UNICODE_SUPPORT if (fCType == SQL_C_WCHAR) { if (BYTEA_PROCESS_ESCAPE == bytea_process_kind) { len = convert_from_pgbinary(neut_str, pgdc->ttlbuf, pgdc->ttlbuflen); len = pg_bin2whex(pgdc->ttlbuf, (SQLWCHAR *) pgdc->ttlbuf, len); } else { if (!hybrid) /* normally */ utf8_to_ucs2_lf(neut_str, SQL_NTS, lf_conv, (SQLWCHAR *) pgdc->ttlbuf, unicode_count, FALSE); else /* hybrid */ { MYLOG(0, "hybrid convert\n"); if (bindcol_hybrid_exec((SQLWCHAR *) pgdc->ttlbuf, neut_str, unicode_count + 1, lf_conv, &allocbuf) < 0) { result = COPY_INVALID_STRING_CONVERSION; goto cleanup; } } } already_processed = TRUE; } else if (localize_needed) { if (bindcol_localize_exec(pgdc->ttlbuf, len + 1, lf_conv, &allocbuf) < 0) { result = COPY_INVALID_STRING_CONVERSION; goto cleanup; } already_processed = TRUE; } #endif /* UNICODE_SUPPORT */ if (already_processed) ; else if (0 != bytea_process_kind) { len = convert_from_pgbinary(neut_str, pgdc->ttlbuf, pgdc->ttlbuflen); if (BYTEA_PROCESS_ESCAPE == bytea_process_kind) len = pg_bin2hex(pgdc->ttlbuf, pgdc->ttlbuf, len); } else convert_linefeeds(neut_str, pgdc->ttlbuf, pgdc->ttlbuflen, lf_conv, &changed); ptr = pgdc->ttlbuf; pgdc->ttlbufused = len; } else { if (pgdc->ttlbuf) { free(pgdc->ttlbuf); pgdc->ttlbuf = NULL; } ptr = neut_str; } cleanup: #ifdef UNICODE_SUPPORT if (allocbuf) free(allocbuf); #endif /* UNICODE_SUPPORT */ *length_return = len; *ptr_return = ptr; *needbuflen_return = needbuflen; return result; } /* gdata SC_get_GDTI(stmt) current_col stmt->current_col */ /* * fCType treated in the following function is * * SQL_C_CHAR, SQL_C_BINARY, SQL_C_WCHAR or INTERNAL_ASIS_TYPE */ static int convert_text_field_to_sql_c(GetDataInfo * const gdata, const int current_col, const char * const neut_str, const OID field_type, const SQLSMALLINT fCType, char * const rgbValueBindRow, const SQLLEN cbValueMax, const ConnectionClass * const conn, SQLLEN * const length_return) { int result = COPY_OK; SQLLEN len = (-2); GetDataClass *pgdc; int copy_len = 0, needbuflen = 0, i; const char *ptr; MYLOG(0, "field_type=%u type=%d\n", field_type, fCType); switch (field_type) { case PG_TYPE_FLOAT4: case PG_TYPE_FLOAT8: case PG_TYPE_NUMERIC: set_client_decimal_point((char *) neut_str); break; } if (current_col < 0) { pgdc = &(gdata->fdata); pgdc->data_left = -1; } else pgdc = &gdata->gdata[current_col]; if (pgdc->data_left < 0) { if (COPY_OK != (result = setup_getdataclass(&len, &ptr, &needbuflen, pgdc, neut_str, field_type, fCType, cbValueMax, conn))) goto cleanup; } else { ptr = pgdc->ttlbuf; len = pgdc->ttlbufused; } MYLOG(0, "DEFAULT: len = " FORMAT_LEN ", ptr = '%.*s'\n", len, (int) len, ptr); if (current_col >= 0) { if (pgdc->data_left > 0) { ptr += (len - pgdc->data_left); len = pgdc->data_left; needbuflen = len + (pgdc->ttlbuflen - pgdc->ttlbufused); } else pgdc->data_left = len; } if (cbValueMax > 0) { BOOL already_copied = FALSE; int terminatorlen; terminatorlen = get_terminator_len(fCType); if (terminatorlen >= cbValueMax) copy_len = 0; else if (len + terminatorlen > cbValueMax) copy_len = get_adjust_len(fCType, cbValueMax - terminatorlen); else copy_len = len; if (!already_copied) { /* Copy the data */ if (copy_len > 0) memcpy(rgbValueBindRow, ptr, copy_len); /* Add null terminator */ for (i = 0; i < terminatorlen && copy_len + i < cbValueMax; i++) rgbValueBindRow[copy_len + i] = '\0'; } /* Adjust data_left for next time */ if (current_col >= 0) pgdc->data_left -= copy_len; } /* * Finally, check for truncation so that proper status can * be returned */ if (cbValueMax > 0 && needbuflen > cbValueMax) result = COPY_RESULT_TRUNCATED; else { if (pgdc->ttlbuf != NULL) { free(pgdc->ttlbuf); pgdc->ttlbuf = NULL; } } #ifdef UNICODE_SUPPORT if (SQL_C_WCHAR == fCType) MYLOG(0, " SQL_C_WCHAR, default: len = " FORMAT_LEN ", cbValueMax = " FORMAT_LEN ", rgbValueBindRow = '%s'\n", len, cbValueMax, rgbValueBindRow); else #endif /* UNICODE_SUPPORT */ if (SQL_C_BINARY == fCType) MYLOG(0, " SQL_C_BINARY, default: len = " FORMAT_LEN ", cbValueMax = " FORMAT_LEN ", rgbValueBindRow = '%.*s'\n", len, cbValueMax, copy_len, rgbValueBindRow); else MYLOG(0, " SQL_C_CHAR, default: len = " FORMAT_LEN ", cbValueMax = " FORMAT_LEN ", rgbValueBindRow = '%s'\n", len, cbValueMax, rgbValueBindRow); cleanup: *length_return = len; return result; } /* This is called by SQLGetData() */ int copy_and_convert_field(StatementClass *stmt, OID field_type, int atttypmod, void *valuei, SQLSMALLINT fCType, int precision, PTR rgbValue, SQLLEN cbValueMax, SQLLEN *pcbValue, SQLLEN *pIndicator) { CSTR func = "copy_and_convert_field"; const char *value = valuei; ARDFields *opts = SC_get_ARDF(stmt); GetDataInfo *gdata = SC_get_GDTI(stmt); SQLLEN len = 0; SIMPLE_TIME std_time; #ifdef HAVE_LOCALTIME_R struct tm tm; #endif /* HAVE_LOCALTIME_R */ SQLLEN pcbValueOffset, rgbValueOffset; char *rgbValueBindRow = NULL; SQLLEN *pcbValueBindRow = NULL, *pIndicatorBindRow = NULL; SQLSETPOSIROW bind_row = stmt->bind_row; int bind_size = opts->bind_size; int result = COPY_OK; const ConnectionClass *conn = SC_get_conn(stmt); BOOL text_bin_handling; const char *neut_str = value; char booltemp[3]; char midtemp[64]; GetDataClass *pgdc; if (stmt->current_col >= 0) { if (stmt->current_col >= opts->allocated) { return SQL_ERROR; } if (gdata->allocated != opts->allocated) extend_getdata_info(gdata, opts->allocated, TRUE); pgdc = &gdata->gdata[stmt->current_col]; if (pgdc->data_left == -2) pgdc->data_left = (cbValueMax > 0) ? 0 : -1; /* This seems to be * * needed by ADO ? */ if (pgdc->data_left == 0) { if (pgdc->ttlbuf != NULL) { free(pgdc->ttlbuf); pgdc->ttlbuf = NULL; pgdc->ttlbuflen = 0; } pgdc->data_left = -2; /* needed by ADO ? */ return COPY_NO_DATA_FOUND; } } /*--------- * rgbValueOffset is *ONLY* for character and binary data. * pcbValueOffset is for computing any pcbValue location *--------- */ if (bind_size > 0) pcbValueOffset = rgbValueOffset = (bind_size * bind_row); else { pcbValueOffset = bind_row * sizeof(SQLLEN); rgbValueOffset = bind_row * cbValueMax; } /* * The following is applicable in case bind_size > 0 * or the fCType is of variable length. */ if (rgbValue) rgbValueBindRow = (char *) rgbValue + rgbValueOffset; if (pcbValue) pcbValueBindRow = LENADDR_SHIFT(pcbValue, pcbValueOffset); if (pIndicator) { pIndicatorBindRow = LENADDR_SHIFT(pIndicator, pcbValueOffset); *pIndicatorBindRow = 0; } memset(&std_time, 0, sizeof(SIMPLE_TIME)); MYLOG(0, "field_type = %d, fctype = %d, value = '%s', cbValueMax=" FORMAT_LEN "\n", field_type, fCType, (value == NULL) ? "" : value, cbValueMax); if (!value) { MYLOG(0, "null_cvt_date_string=%d\n", conn->connInfo.cvt_null_date_string); /* a speicial handling for FOXPRO NULL -> NULL_STRING */ if (conn->connInfo.cvt_null_date_string > 0 && (PG_TYPE_DATE == field_type || PG_TYPE_DATETIME == field_type || PG_TYPE_TIMESTAMP_NO_TMZONE == field_type) && (SQL_C_CHAR == fCType || #ifdef UNICODE_SUPPORT SQL_C_WCHAR == fCType || #endif /* UNICODE_SUPPORT */ SQL_C_DATE == fCType || SQL_C_TYPE_DATE == fCType || SQL_C_DEFAULT == fCType)) { if (pcbValueBindRow) *pcbValueBindRow = 0; switch (fCType) { case SQL_C_CHAR: if (rgbValueBindRow && cbValueMax > 0) *rgbValueBindRow = '\0'; else result = COPY_RESULT_TRUNCATED; break; case SQL_C_DATE: case SQL_C_TYPE_DATE: case SQL_C_DEFAULT: if (rgbValueBindRow && cbValueMax >= sizeof(DATE_STRUCT)) { memset(rgbValueBindRow, 0, cbValueMax); if (pcbValueBindRow) *pcbValueBindRow = sizeof(DATE_STRUCT); } else result = COPY_RESULT_TRUNCATED; break; #ifdef UNICODE_SUPPORT case SQL_C_WCHAR: if (rgbValueBindRow && cbValueMax >= WCLEN) memset(rgbValueBindRow, 0, WCLEN); else result = COPY_RESULT_TRUNCATED; break; #endif /* UNICODE_SUPPORT */ } return result; } /* * handle a null just by returning SQL_NULL_DATA in pcbValue, and * doing nothing to the buffer. */ else if (pIndicator) { *pIndicatorBindRow = SQL_NULL_DATA; return COPY_OK; } else { SC_set_error(stmt, STMT_RETURN_NULL_WITHOUT_INDICATOR, "StrLen_or_IndPtr was a null pointer and NULL data was retrieved", func); return COPY_GENERAL_ERROR; } } if (stmt->hdbc->DataSourceToDriver != NULL) { size_t length = strlen(value); stmt->hdbc->DataSourceToDriver(stmt->hdbc->translation_option, SQL_CHAR, valuei, (SDWORD) length, valuei, (SDWORD) length, NULL, NULL, 0, NULL); } /* * 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, "%4d-%2d-%2d", &std_time.y, &std_time.m, &std_time.d); break; case PG_TYPE_TIME: { BOOL bZone = FALSE; /* time zone stuff is unreliable */ int zone; timestamp2stime(value, &std_time, &bZone, &zone); } break; case PG_TYPE_ABSTIME: case PG_TYPE_DATETIME: case PG_TYPE_TIMESTAMP_NO_TMZONE: case PG_TYPE_TIMESTAMP: std_time.fr = 0; std_time.infinity = 0; if (strnicmp(value, INFINITY_STRING, 8) == 0) { std_time.infinity = 1; std_time.m = 12; std_time.d = 31; std_time.y = 9999; std_time.hh = 23; std_time.mm = 59; std_time.ss = 59; } if (strnicmp(value, MINFINITY_STRING, 9) == 0) { std_time.infinity = -1; std_time.m = 1; std_time.d = 1; // std_time.y = -4713; std_time.y = -9999; std_time.hh = 0; std_time.mm = 0; std_time.ss = 0; } if (strnicmp(value, "invalid", 7) != 0) { BOOL bZone = field_type != PG_TYPE_TIMESTAMP_NO_TMZONE; int zone; /* * sscanf(value, "%4d-%2d-%2d %2d:%2d:%2d", &std_time.y, &std_time.m, * &std_time.d, &std_time.hh, &std_time.mm, &std_time.ss); */ bZone = FALSE; /* time zone stuff is unreliable */ timestamp2stime(value, &std_time, &bZone, &zone); MYLOG(DETAIL_LOG_LEVEL, "2stime fr=%d\n", std_time.fr); } else { /* * The timestamp is invalid so set something conspicuous, * like the epoch */ struct tm *tim; time_t t = 0; #ifdef HAVE_LOCALTIME_R tim = localtime_r(&t, &tm); #else tim = localtime(&t); #endif /* HAVE_LOCALTIME_R */ std_time.m = tim->tm_mon + 1; std_time.d = tim->tm_mday; std_time.y = tim->tm_year + 1900; std_time.hh = tim->tm_hour; std_time.mm = tim->tm_min; std_time.ss = tim->tm_sec; } break; case PG_TYPE_BOOL: { /* change T/F to 1/0 */ const ConnInfo *ci = &(conn->connInfo); switch (((char *)value)[0]) { case 'f': case 'F': case 'n': case 'N': case '0': STRCPY_FIXED(booltemp, "0"); break; default: if (ci->true_is_minus1) STRCPY_FIXED(booltemp, "-1"); else STRCPY_FIXED(booltemp, "1"); } neut_str = booltemp; } break; /* This is for internal use by SQLStatistics() */ case PG_TYPE_INT2VECTOR: if (SQL_C_DEFAULT == fCType) { int i, nval, maxc; const char *vp; /* this is an array of eight integers */ short *short_array = (short *) rgbValueBindRow, shortv; maxc = 0; if (NULL != short_array) maxc = (int) cbValueMax / sizeof(short); vp = value; nval = 0; MYLOG(0, "index=("); for (i = 0;; i++) { if (sscanf(vp, "%hi", &shortv) != 1) break; MYPRINTF(0, " %hi", shortv); nval++; if (nval < maxc) short_array[i + 1] = shortv; /* skip the current token */ while (IS_NOT_SPACE(*vp)) vp++; /* and skip the space to the next token */ while ((*vp != '\0') && (isspace(*vp))) vp++; if (*vp == '\0') break; } MYPRINTF(0, ") nval = %i\n", nval); if (maxc > 0) short_array[0] = nval; /* There is no corresponding fCType for this. */ len = (nval + 1) * sizeof(short); if (pcbValue) *pcbValueBindRow = len; if (len <= cbValueMax) return COPY_OK; /* dont go any further or the data will be * trashed */ else return COPY_RESULT_TRUNCATED; } break; /* * This is a large object OID, which is used to store * LONGVARBINARY objects. */ case PG_TYPE_LO_UNDEFINED: return convert_lo(stmt, value, fCType, rgbValueBindRow, cbValueMax, pcbValueBindRow); case 0: break; default: if (field_type == stmt->hdbc->lobj_type /* hack until permanent type available */ || (PG_TYPE_OID == field_type && SQL_C_BINARY == fCType && conn->lo_is_domain) ) return convert_lo(stmt, value, fCType, rgbValueBindRow, cbValueMax, pcbValueBindRow); } /* Change default into something useable */ if (fCType == SQL_C_DEFAULT) { fCType = pgtype_attr_to_ctype(conn, field_type, atttypmod); #ifdef UNICODE_SUPPORT if (fCType == SQL_C_WCHAR && CC_default_is_c(conn)) fCType = SQL_C_CHAR; #endif MYLOG(0, ", SQL_C_DEFAULT: fCType = %d\n", fCType); } text_bin_handling = FALSE; switch (fCType) { case INTERNAL_ASIS_TYPE: #ifdef UNICODE_SUPPORT case SQL_C_WCHAR: #endif /* UNICODE_SUPPORT */ case SQL_C_CHAR: text_bin_handling = TRUE; break; case SQL_C_BINARY: switch (field_type) { case PG_TYPE_UNKNOWN: case PG_TYPE_BPCHAR: case PG_TYPE_VARCHAR: case PG_TYPE_TEXT: case PG_TYPE_XML: case PG_TYPE_BPCHARARRAY: case PG_TYPE_VARCHARARRAY: case PG_TYPE_TEXTARRAY: case PG_TYPE_XMLARRAY: case PG_TYPE_BYTEA: text_bin_handling = TRUE; break; } break; } if (text_bin_handling) { BOOL pre_convert = TRUE; int midsize = sizeof(midtemp); int i; /* Special character formatting as required */ /* * These really should return error if cbValueMax is not big * enough. */ switch (field_type) { case PG_TYPE_DATE: len = SPRINTF_FIXED(midtemp, "%.4d-%.2d-%.2d", std_time.y, std_time.m, std_time.d); break; case PG_TYPE_TIME: len = SPRINTF_FIXED(midtemp, "%.2d:%.2d:%.2d", std_time.hh, std_time.mm, std_time.ss); if (std_time.fr > 0) { int wdt; int fr = effective_fraction(std_time.fr, &wdt); len = SPRINTFCAT_FIXED(midtemp, ".%0*d", wdt, fr); } break; case PG_TYPE_ABSTIME: case PG_TYPE_DATETIME: case PG_TYPE_TIMESTAMP_NO_TMZONE: case PG_TYPE_TIMESTAMP: len = stime2timestamp(&std_time, midtemp, midsize, FALSE, (int) (midsize - 19 - 2) ); break; case PG_TYPE_UUID: len = strlen(neut_str); for (i = 0; i < len && i < midsize - 2; i++) midtemp[i] = toupper((UCHAR) neut_str[i]); midtemp[i] = '\0'; MYLOG(0, "PG_TYPE_UUID: rgbValueBindRow = '%s'\n", rgbValueBindRow); break; /* * Currently, data is SILENTLY TRUNCATED for BYTEA and * character data types if there is not enough room in * cbValueMax because the driver can't handle multiple * calls to SQLGetData for these, yet. Most likely, the * buffer passed in will be big enough to handle the * maximum limit of postgres, anyway. * * LongVarBinary types are handled correctly above, observing * truncation and all that stuff since there is * essentially no limit on the large object used to store * those. */ case PG_TYPE_BYTEA:/* convert binary data to hex strings * (i.e, 255 = "FF") */ default: pre_convert = FALSE; } if (pre_convert) neut_str = midtemp; result = convert_text_field_to_sql_c(gdata, stmt->current_col, neut_str, field_type, fCType, rgbValueBindRow, cbValueMax, conn, &len); } else { SQLGUID g; /* * for SQL_C_CHAR, it's 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) { if (convert_money(neut_str, midtemp, sizeof(midtemp))) neut_str = midtemp; else { MYLOG(0, "couldn't convert money type to %d\n", fCType); return COPY_UNSUPPORTED_TYPE; } } switch (fCType) { case SQL_C_DATE: case SQL_C_TYPE_DATE: /* 91 */ len = 6; { DATE_STRUCT *ds; struct tm *tim; if (bind_size > 0) ds = (DATE_STRUCT *) rgbValueBindRow; else ds = (DATE_STRUCT *) rgbValue + bind_row; /* * Initialize date in case conversion destination * expects date part from this source time data. * A value may be partially set here, so do some * sanity checks on the existing values before * setting them. */ tim = SC_get_localtime(stmt); if (std_time.m == 0) std_time.m = tim->tm_mon + 1; if (std_time.d == 0) std_time.d = tim->tm_mday; if (std_time.y == 0) std_time.y = tim->tm_year + 1900; ds->year = std_time.y; ds->month = std_time.m; ds->day = std_time.d; } break; case SQL_C_TIME: case SQL_C_TYPE_TIME: /* 92 */ len = 6; { TIME_STRUCT *ts; if (bind_size > 0) ts = (TIME_STRUCT *) rgbValueBindRow; else ts = (TIME_STRUCT *) rgbValue + bind_row; ts->hour = std_time.hh; ts->minute = std_time.mm; ts->second = std_time.ss; } break; case SQL_C_TIMESTAMP: case SQL_C_TYPE_TIMESTAMP: /* 93 */ len = 16; { struct tm *tim; TIMESTAMP_STRUCT *ts; if (bind_size > 0) ts = (TIMESTAMP_STRUCT *) rgbValueBindRow; else ts = (TIMESTAMP_STRUCT *) rgbValue + bind_row; /* * Initialize date in case conversion destination * expects date part from this source time data. * A value may be partially set here, so do some * sanity checks on the existing values before * setting them. */ tim = SC_get_localtime(stmt); if (std_time.m == 0) std_time.m = tim->tm_mon + 1; if (std_time.d == 0) std_time.d = tim->tm_mday; if (std_time.y == 0) std_time.y = tim->tm_year + 1900; ts->year = std_time.y; ts->month = std_time.m; ts->day = std_time.d; ts->hour = std_time.hh; ts->minute = std_time.mm; ts->second = std_time.ss; ts->fraction = std_time.fr; } break; case SQL_C_BIT: len = 1; if (bind_size > 0) *((UCHAR *) rgbValueBindRow) = atoi(neut_str); else *((UCHAR *) rgbValue + bind_row) = atoi(neut_str); MYLOG(99, "SQL_C_BIT: bind_row = " FORMAT_POSIROW " val = %d, cb = " FORMAT_LEN ", rgb=%d\n", bind_row, atoi(neut_str), cbValueMax, *((UCHAR *)rgbValue)); break; case SQL_C_STINYINT: case SQL_C_TINYINT: len = 1; if (bind_size > 0) *((SCHAR *) rgbValueBindRow) = atoi(neut_str); else *((SCHAR *) rgbValue + bind_row) = atoi(neut_str); break; case SQL_C_UTINYINT: len = 1; if (bind_size > 0) *((UCHAR *) rgbValueBindRow) = atoi(neut_str); else *((UCHAR *) rgbValue + bind_row) = atoi(neut_str); break; case SQL_C_FLOAT: set_client_decimal_point((char *) neut_str); len = 4; if (bind_size > 0) *((SFLOAT *) rgbValueBindRow) = (float) get_double_value(neut_str); else *((SFLOAT *) rgbValue + bind_row) = (float) get_double_value(neut_str); break; case SQL_C_DOUBLE: set_client_decimal_point((char *) neut_str); len = 8; if (bind_size > 0) *((SDOUBLE *) rgbValueBindRow) = get_double_value(neut_str); else *((SDOUBLE *) rgbValue + bind_row) = get_double_value(neut_str); break; case SQL_C_NUMERIC: { SQL_NUMERIC_STRUCT *ns; BOOL overflowed; if (bind_size > 0) ns = (SQL_NUMERIC_STRUCT *) rgbValueBindRow; else ns = (SQL_NUMERIC_STRUCT *) rgbValue + bind_row; parse_to_numeric_struct(neut_str, ns, &overflowed); if (overflowed) result = COPY_RESULT_TRUNCATED; } break; case SQL_C_SSHORT: case SQL_C_SHORT: len = 2; if (bind_size > 0) *((SQLSMALLINT *) rgbValueBindRow) = atoi(neut_str); else *((SQLSMALLINT *) rgbValue + bind_row) = atoi(neut_str); break; case SQL_C_USHORT: len = 2; if (bind_size > 0) *((SQLUSMALLINT *) rgbValueBindRow) = atoi(neut_str); else *((SQLUSMALLINT *) rgbValue + bind_row) = atoi(neut_str); break; case SQL_C_SLONG: case SQL_C_LONG: len = 4; if (bind_size > 0) *((SQLINTEGER *) rgbValueBindRow) = atol(neut_str); else *((SQLINTEGER *) rgbValue + bind_row) = atol(neut_str); break; case SQL_C_ULONG: len = 4; if (bind_size > 0) *((SQLUINTEGER *) rgbValueBindRow) = ATOI32U(neut_str); else *((SQLUINTEGER *) rgbValue + bind_row) = ATOI32U(neut_str); break; #ifdef ODBCINT64 case SQL_C_SBIGINT: len = 8; if (bind_size > 0) *((SQLBIGINT *) rgbValueBindRow) = ATOI64(neut_str); else *((SQLBIGINT *) rgbValue + bind_row) = ATOI64(neut_str); break; case SQL_C_UBIGINT: len = 8; if (bind_size > 0) *((SQLUBIGINT *) rgbValueBindRow) = ATOI64U(neut_str); else *((SQLUBIGINT *) rgbValue + bind_row) = ATOI64U(neut_str); break; #endif /* ODBCINT64 */ case SQL_C_BINARY: /* The following is for SQL_C_VARBOOKMARK */ if (PG_TYPE_INT4 == field_type) { UInt4 ival = ATOI32U(neut_str); MYLOG(DETAIL_LOG_LEVEL, "SQL_C_VARBOOKMARK value=%d\n", ival); if (pcbValue) *pcbValueBindRow = sizeof(ival); if (cbValueMax >= sizeof(ival)) { memcpy(rgbValueBindRow, &ival, sizeof(ival)); return COPY_OK; } else return COPY_RESULT_TRUNCATED; } else if (PG_TYPE_UUID == field_type) { int rtn = char2guid(neut_str, &g); if (COPY_OK != rtn) return rtn; if (pcbValue) *pcbValueBindRow = sizeof(g); if (cbValueMax >= sizeof(g)) { memcpy(rgbValueBindRow, &g, sizeof(g)); return COPY_OK; } else return COPY_RESULT_TRUNCATED; } else { MYLOG(0, "couldn't convert the type %d to SQL_C_BINARY\n", field_type); return COPY_UNSUPPORTED_TYPE; } break; case SQL_C_GUID: result = char2guid(neut_str, &g); if (COPY_OK != result) { MYLOG(0, "Could not convert to SQL_C_GUID\n"); return COPY_UNSUPPORTED_TYPE; } len = sizeof(g); if (bind_size > 0) *((SQLGUID *) rgbValueBindRow) = g; else *((SQLGUID *) rgbValue + bind_row) = g; break; case SQL_C_INTERVAL_YEAR: case SQL_C_INTERVAL_MONTH: case SQL_C_INTERVAL_YEAR_TO_MONTH: case SQL_C_INTERVAL_DAY: case SQL_C_INTERVAL_HOUR: case SQL_C_INTERVAL_DAY_TO_HOUR: case SQL_C_INTERVAL_MINUTE: case SQL_C_INTERVAL_HOUR_TO_MINUTE: case SQL_C_INTERVAL_SECOND: case SQL_C_INTERVAL_DAY_TO_SECOND: case SQL_C_INTERVAL_HOUR_TO_SECOND: case SQL_C_INTERVAL_MINUTE_TO_SECOND: interval2istruct(fCType, precision, neut_str, bind_size > 0 ? (SQL_INTERVAL_STRUCT *) rgbValueBindRow : (SQL_INTERVAL_STRUCT *) rgbValue + bind_row); break; default: MYLOG(0, "conversion to the type %d isn't supported\n", fCType); return COPY_UNSUPPORTED_TYPE; } } /* store the length of what was copied, if there's a place for it */ if (pcbValue) *pcbValueBindRow = len; if (result == COPY_OK && stmt->current_col >= 0) gdata->gdata[stmt->current_col].data_left = 0; return result; } /*-------------------------------------------------------------------- * Functions/Macros to get rid of query size limit. * * I always used the following macros to convert from * old_statement to new_statement. Please improve it * if you have a better way. Hiroshi 2001/05/22 *-------------------------------------------------------------------- */ #define FLGP_USING_CURSOR (1L << 1) #define FLGP_SELECT_INTO (1L << 2) #define FLGP_SELECT_FOR_UPDATE_OR_SHARE (1L << 3) #define FLGP_MULTIPLE_STATEMENT (1L << 5) #define FLGP_SELECT_FOR_READONLY (1L << 6) typedef struct _QueryParse { const char *statement; int statement_type; size_t opos; ssize_t from_pos; ssize_t where_pos; ssize_t stmt_len; int in_status; char escape_in_literal, prev_token_end; const char *dollar_tag; ssize_t taglen; char token_curr[64]; int token_len; size_t declare_pos; UInt4 flags, comment_level; encoded_str encstr; } QueryParse; static void QP_initialize(QueryParse *q, const StatementClass *stmt) { q->statement = stmt->statement; q->statement_type = stmt->statement_type; q->opos = 0; q->from_pos = -1; q->where_pos = -1; q->stmt_len = (q->statement) ? strlen(q->statement) : -1; q->in_status = 0; q->escape_in_literal = '\0'; q->dollar_tag = NULL; q->taglen = -1; q->token_curr[0] = '\0'; q->token_len = 0; q->prev_token_end = TRUE; q->declare_pos = 0; q->flags = 0; q->comment_level = 0; make_encoded_str(&q->encstr, SC_get_conn(stmt), q->statement); } enum { QP_IN_IDENT_KEYWORD = 1L /* identifier or keyword */ , QP_IN_DQUOTE_IDENTIFIER = (1L << 1) /* "" */ , QP_IN_LITERAL = (1L << 2) /* '' */ , QP_IN_ESCAPE = (1L << 3) /* \ in literal */ , QP_IN_DOLLAR_QUOTE = (1L << 4) /* $...$ $...$ */ , QP_IN_COMMENT_BLOCK = (1L << 5) /* slash asterisk */ , QP_IN_LINE_COMMENT = (1L << 6) /* -- */ }; #define QP_in_idle_status(qp) ((qp)->in_status == 0) #define QP_is_in(qp, status) (((qp)->in_status & status) != 0) #define QP_enter(qp, status) ((qp)->in_status |= status) #define QP_exit(qp, status) ((qp)->in_status &= (~status)) /* * ResolveOneParam can be work in these four modes: * * RPM_REPLACE_PARAMS * Replace parameter markers with their values. * * RPM_FAKE_PARAMS * The query is going to be sent to the server only to be able to * Describe the result set. Parameter markers are replaced with NULL * literals if we don't know their real values yet. * * RPM_BUILDING_PREPARE_STATEMENT * Building a query suitable for PREPARE statement, or server-side Parse. * Parameter markers are replaced with $n-style parameter markers. * * RPM_BUILDING_BIND_REQUEST * Building the actual parameter values to send to the server in a Bind * request. Return an unescaped value that will be accepted by the * server's input function. */ typedef enum { RPM_REPLACE_PARAMS, RPM_FAKE_PARAMS, RPM_BUILDING_PREPARE_STATEMENT, RPM_BUILDING_BIND_REQUEST } ResolveParamMode; #define FLGB_INACCURATE_RESULT (1L << 4) #define FLGB_CREATE_KEYSET (1L << 5) #define FLGB_KEYSET_DRIVEN (1L << 6) #define FLGB_CONVERT_LF (1L << 7) #define FLGB_DISCARD_OUTPUT (1L << 8) #define FLGB_BINARY_AS_POSSIBLE (1L << 9) #define FLGB_LITERAL_EXTENSION (1L << 10) #define FLGB_HEX_BIN_FORMAT (1L << 11) #define FLGB_PARAM_CAST (1L << 12) typedef struct _QueryBuild { char *query_statement; size_t str_alsize; size_t npos; SQLLEN current_row; Int2 param_number; Int2 dollar_number; Int2 num_io_params; Int2 num_output_params; Int2 num_discard_params; Int2 proc_return; Int2 brace_level; char parenthesize_the_first; APDFields *apdopts; IPDFields *ipdopts; PutDataInfo *pdata; size_t load_stmt_len; size_t load_from_pos; ResolveParamMode param_mode; UInt4 flags; int ccsc; int errornumber; const char *errormsg; ConnectionClass *conn; /* mainly needed for LO handling */ StatementClass *stmt; /* needed to set error info in ENLARGE_.. */ } QueryBuild; #define INIT_MIN_ALLOC 4096 static ssize_t QB_initialize(QueryBuild *qb, size_t size, StatementClass *stmt, ResolveParamMode param_mode) { size_t newsize = 0; qb->param_mode = param_mode; qb->flags = 0; qb->load_stmt_len = 0; qb->load_from_pos = 0; qb->stmt = stmt; qb->apdopts = NULL; qb->ipdopts = NULL; qb->pdata = NULL; qb->proc_return = 0; qb->num_io_params = 0; qb->num_output_params = 0; qb->num_discard_params = 0; qb->brace_level = 0; qb->parenthesize_the_first = FALSE; /* Copy options from statement */ qb->apdopts = SC_get_APDF(stmt); qb->ipdopts = SC_get_IPDF(stmt); qb->pdata = SC_get_PDTI(stmt); qb->conn = SC_get_conn(stmt); if (stmt->discard_output_params) qb->flags |= FLGB_DISCARD_OUTPUT; qb->num_io_params = CountParameters(stmt, NULL, NULL, &qb->num_output_params); qb->proc_return = stmt->proc_return; if (0 != (qb->flags & FLGB_DISCARD_OUTPUT)) qb->num_discard_params = qb->num_output_params; if (qb->num_discard_params < qb->proc_return) qb->num_discard_params = qb->proc_return; /* Copy options from connection */ if (qb->conn->connInfo.lf_conversion) qb->flags |= FLGB_CONVERT_LF; qb->ccsc = qb->conn->ccsc; if (CC_get_escape(qb->conn) && PG_VERSION_GE(qb->conn, 8.1)) qb->flags |= FLGB_LITERAL_EXTENSION; if (PG_VERSION_GE(qb->conn, 9.0)) qb->flags |= FLGB_HEX_BIN_FORMAT; newsize = INIT_MIN_ALLOC; while (newsize <= size) newsize *= 2; if ((qb->query_statement = malloc(newsize)) == NULL) { qb->str_alsize = 0; return -1; } qb->query_statement[0] = '\0'; qb->str_alsize = newsize; qb->npos = 0; qb->current_row = stmt->exec_current_row < 0 ? 0 : stmt->exec_current_row; qb->param_number = -1; qb->dollar_number = 0; qb->errornumber = 0; qb->errormsg = NULL; return newsize; } static int QB_initialize_copy(QueryBuild *qb_to, const QueryBuild *qb_from, UInt4 size) { memcpy(qb_to, qb_from, sizeof(QueryBuild)); if ((qb_to->query_statement = malloc(size)) == NULL) { qb_to->str_alsize = 0; return -1; } qb_to->query_statement[0] = '\0'; qb_to->str_alsize = size; qb_to->npos = 0; return size; } static void QB_replace_SC_error(StatementClass *stmt, const QueryBuild *qb, const char *func) { int number; if (0 == qb->errornumber) return; if ((number = SC_get_errornumber(stmt)) > 0) return; if (number < 0 && qb->errornumber < 0) return; SC_set_error(stmt, qb->errornumber, qb->errormsg, func); } static void QB_Destructor(QueryBuild *qb) { if (qb->query_statement) { free(qb->query_statement); qb->query_statement = NULL; qb->str_alsize = 0; } } /* * New macros (Aceto) *-------------------- */ #define F_OldChar(qp) ((qp)->statement[(qp)->opos]) #define F_OldPtr(qp) ((qp)->statement + (qp)->opos) #define F_OldNext(qp) (++(qp)->opos) #define F_OldPrior(qp) (--(qp)->opos) #define F_OldPos(qp) (qp)->opos #define F_ExtractOldTo(qp, buf, ch, maxsize) \ do { \ size_t c = 0; \ while ((qp)->statement[qp->opos] != '\0' && (qp)->statement[qp->opos] != ch) \ { \ if (c >= maxsize) \ break; \ buf[c++] = (qp)->statement[qp->opos++]; \ } \ if ((qp)->statement[qp->opos] == '\0') \ {retval = SQL_ERROR; goto cleanup;} \ buf[c] = '\0'; \ } while (0) #define F_NewChar(qb) (qb->query_statement[(qb)->npos]) #define F_NewPtr(qb) ((qb)->query_statement + (qb)->npos) #define F_NewNext(qb) (++(qb)->npos) #define F_NewPos(qb) ((qb)->npos) static int convert_escape(QueryParse *qp, QueryBuild *qb); static int inner_process_tokens(QueryParse *qp, QueryBuild *qb); static int ResolveOneParam(QueryBuild *qb, QueryParse *qp, BOOL *isnull, BOOL *usebinary, Oid *pgType); static int processParameters(QueryParse *qp, QueryBuild *qb, size_t *output_count, SQLLEN param_pos[][2]); static size_t convert_to_pgbinary(const char *in, char *out, size_t len, QueryBuild *qb); static BOOL convert_special_chars(QueryBuild *qb, const char *si, size_t used); /* * Enlarge qb->query_statement buffer so that it is at least newsize + 1 * in size. */ static ssize_t enlarge_query_statement(QueryBuild *qb, size_t newsize) { size_t newalsize = INIT_MIN_ALLOC; CSTR func = "enlarge_statement"; while (newalsize <= newsize) newalsize *= 2; if (!(qb->query_statement = realloc(qb->query_statement, newalsize))) { qb->str_alsize = 0; if (qb->stmt) { SC_set_error(qb->stmt, STMT_EXEC_ERROR, "Query buffer allocate error in copy_statement_with_parameters", func); } else { qb->errormsg = "Query buffer allocate error in copy_statement_with_parameters"; qb->errornumber = STMT_EXEC_ERROR; } return 0; } qb->str_alsize = newalsize; return newalsize; } /*---------- * Enlarge stmt_with_params if necessary. *---------- */ #define ENLARGE_NEWSTATEMENT(qb, newpos) \ do { \ if ((newpos) >= (qb)->str_alsize) \ { \ if (enlarge_query_statement(qb, newpos) <= 0) \ { \ retval = SQL_ERROR; \ goto cleanup; \ } \ } \ } while(0) /*---------- * Terminate the stmt_with_params string with NULL. *---------- */ #define CVT_TERMINATE(qb) \ do { \ if (NULL == (qb)->query_statement) \ { \ retval = SQL_ERROR; \ goto cleanup; \ } \ (qb)->query_statement[(qb)->npos] = '\0'; \ } while (0) /*---------- * Append a data. *---------- */ #define CVT_APPEND_DATA(qb, s, len) \ do { \ size_t newpos = (qb)->npos + len; \ ENLARGE_NEWSTATEMENT((qb), newpos); \ memcpy(&(qb)->query_statement[(qb)->npos], s, len); \ (qb)->npos = newpos; \ (qb)->query_statement[newpos] = '\0'; \ } while (0) /*---------- * Append a string. *---------- */ #define CVT_APPEND_STR(qb, s) \ do { \ size_t len = strlen(s); \ CVT_APPEND_DATA(qb, s, len); \ } while (0) /*---------- * Append a char. *---------- */ #define CVT_APPEND_CHAR(qb, c) \ do { \ ENLARGE_NEWSTATEMENT(qb, (qb)->npos + 1); \ (qb)->query_statement[(qb)->npos++] = c; \ } while (0) /*---------- * Append a binary data. * Newly required size may be overestimated currently. *---------- */ #define CVT_APPEND_BINARY(qb, buf, used) \ do { \ size_t newlimit; \ \ newlimit = (qb)->npos; \ if (qb->flags & FLGB_HEX_BIN_FORMAT) \ newlimit += 2 * used + 4; \ else \ newlimit += 5 * used; \ ENLARGE_NEWSTATEMENT(qb, newlimit); \ (qb)->npos += convert_to_pgbinary(buf, \ &qb->query_statement[(qb)->npos], \ used, qb); \ } while (0) /*---------- * *---------- */ #define CVT_SPECIAL_CHARS(qb, buf, used) \ do { \ if (!convert_special_chars(qb, buf, used)) \ { \ retval = SQL_ERROR; \ goto cleanup; \ } \ } while (0) static RETCODE QB_start_brace(QueryBuild *qb) { BOOL replace_by_parenthesis = TRUE; RETCODE retval = SQL_ERROR; if (0 == qb->brace_level) { if (0 == F_NewPos(qb)) { qb->parenthesize_the_first = FALSE; replace_by_parenthesis = FALSE; } else qb->parenthesize_the_first = TRUE; } if (replace_by_parenthesis) CVT_APPEND_CHAR(qb, '('); qb->brace_level++; retval = SQL_SUCCESS; cleanup: return retval; } static RETCODE QB_end_brace(QueryBuild *qb) { BOOL replace_by_parenthesis = TRUE; RETCODE retval = SQL_ERROR; if (qb->brace_level <= 1 && !qb->parenthesize_the_first) replace_by_parenthesis = FALSE; if (replace_by_parenthesis) CVT_APPEND_CHAR(qb, ')'); qb->brace_level--; retval = SQL_SUCCESS; cleanup: return retval; } static RETCODE QB_append_space_to_separate_identifiers(QueryBuild *qb, const QueryParse *qp) { unsigned char tchar = F_OldChar(qp); encoded_str encstr; BOOL add_space = FALSE; RETCODE retval = SQL_ERROR; if (ODBC_ESCAPE_END != tchar) return SQL_SUCCESS; encoded_str_constr(&encstr, qb->ccsc, F_OldPtr(qp) + 1); tchar = encoded_nextchar(&encstr); if (MBCS_NON_ASCII(encstr)) add_space = TRUE; else { if (isalnum(tchar)) add_space = TRUE; else { switch (tchar) { case '_': case '$': add_space = TRUE; } } } if (add_space) CVT_APPEND_CHAR(qb, ' '); retval = SQL_SUCCESS; cleanup: return retval; } /*---------- * Check if the statement is * SELECT ... INTO table FROM ..... * This isn't really a strict check but ... *---------- */ static BOOL into_table_from(const char *stmt) { if (strnicmp(stmt, "into", 4)) return FALSE; stmt += 4; while (isspace((UCHAR) *stmt)) stmt++; switch (*stmt) { case '\0': case ',': case LITERAL_QUOTE: case DOLLAR_QUOTE: return FALSE; case '-': case '/': return TRUE; case IDENTIFIER_QUOTE: /* double quoted table name ? */ do { do { ++stmt; } while (*stmt != IDENTIFIER_QUOTE && *stmt); if (*stmt) stmt++; } while (*stmt == IDENTIFIER_QUOTE); break; default: while (IS_NOT_SPACE(*stmt)) stmt++; break; } if (!*stmt) return FALSE; while (isspace((UCHAR) *stmt)) stmt++; if ('/' == *stmt || '-' == *stmt) return TRUE; if (strnicmp(stmt, "from", 4)) return FALSE; return TRUE; } /*---------- * Check if the statement is * SELECT ... FOR UPDATE ..... * This isn't really a strict check but ... *---------- */ static UInt4 table_for_update_or_share(const char *stmt, size_t *endpos) { const char *wstmt = stmt; int advance; UInt4 flag = 0, zeroflag = 0; while (isspace((UCHAR) *wstmt)) wstmt++; if (!*wstmt) return 0; if (0 == strnicmp(wstmt, "update", advance = 6)) flag |= FLGP_SELECT_FOR_UPDATE_OR_SHARE; else if (0 == strnicmp(wstmt, "share", advance = 5)) flag |= FLGP_SELECT_FOR_UPDATE_OR_SHARE; else if (0 == strnicmp(wstmt, "read", advance = 4)) flag |= FLGP_SELECT_FOR_READONLY; else { flag |= FLGP_SELECT_FOR_UPDATE_OR_SHARE; /* maybe */ return flag; } zeroflag = flag; /* returns flag anyway */ wstmt += advance; if (IS_NOT_SPACE(wstmt[0])) return zeroflag; else if (0 != (flag & FLGP_SELECT_FOR_READONLY)) { if (IS_NOT_SPACE(wstmt[0])) return zeroflag; while (isspace((UCHAR) *wstmt)) wstmt++; if (!*wstmt) return zeroflag; if (0 != strnicmp(wstmt, "only", advance = 4)) return zeroflag; wstmt += advance; } if (IS_NOT_SPACE(wstmt[0])) return zeroflag; *endpos = wstmt - stmt; return flag; } /*---------- * Check if the statement has OUTER JOIN * This isn't really a strict check but ... *---------- */ static BOOL check_join(StatementClass *stmt, const char *curptr, size_t curpos) { const char *wstmt; ssize_t stapos, endpos, tokenwd; const int backstep = 4; BOOL outerj = TRUE; for (endpos = curpos, wstmt = curptr; endpos >= 0 && isspace((UCHAR) *wstmt); endpos--, wstmt--) ; if (endpos < 0) return FALSE; for (endpos -= backstep, wstmt -= backstep; endpos >= 0 && isspace((UCHAR) *wstmt); endpos--, wstmt--) ; if (endpos < 0) return FALSE; for (stapos = endpos; stapos >= 0 && IS_NOT_SPACE(*wstmt); stapos--, wstmt--) ; if (stapos < 0 || 0 == *wstmt) return FALSE; wstmt++; switch (tokenwd = endpos - stapos) { case 4: if (strnicmp(wstmt, "FULL", tokenwd) == 0 || strnicmp(wstmt, "LEFT", tokenwd) == 0) break; return FALSE; case 5: if (strnicmp(wstmt, "OUTER", tokenwd) == 0 || strnicmp(wstmt, "RIGHT", tokenwd) == 0) break; if (strnicmp(wstmt, "INNER", tokenwd) == 0 || strnicmp(wstmt, "CROSS", tokenwd) == 0) { outerj = FALSE; break; } return FALSE; default: return FALSE; } if (stmt) { if (outerj) SC_set_outer_join(stmt); else SC_set_inner_join(stmt); } return TRUE; } /*---------- * Check if the statement is * INSERT INTO ... () VALUES () * This isn't really a strict check but ... *---------- */ static BOOL insert_without_target(const char *stmt, size_t *endpos) { const char *wstmt = stmt; while (isspace((UCHAR) *wstmt)) wstmt++; if (!*wstmt) return FALSE; if (strnicmp(wstmt, "VALUES", 6)) return FALSE; wstmt += 6; if (!wstmt[0] || IS_NOT_SPACE(wstmt[0])) return FALSE; while (isspace((UCHAR) *wstmt)) wstmt++; if (*wstmt != '(' || *(++wstmt) != ')') return FALSE; wstmt++; *endpos = wstmt - stmt; return !wstmt[0] || isspace((UCHAR) wstmt[0]) || ';' == wstmt[0]; } static ProcessedStmt * buildProcessedStmt(const char *srvquery, ssize_t endp, int num_params) { ProcessedStmt *pstmt; size_t qlen; qlen = (endp == SQL_NTS) ? strlen(srvquery) : endp; pstmt = malloc(sizeof(ProcessedStmt)); if (!pstmt) return NULL; pstmt->next = NULL; pstmt->query = malloc(qlen + 1); if (!pstmt->query) { free(pstmt); return NULL; } memcpy(pstmt->query, srvquery, qlen); pstmt->query[qlen] = '\0'; pstmt->num_params = num_params; return pstmt; } /* * Process the original SQL query for execution using server-side prepared * statements. * * Split a possible multi-statement query into parts, and replace ?-style * parameter markers with $n. The resulting queries are stored in a linked * list in stmt->processed_statements. * * If 'fake_params' is true, we will replace ?-style parameter markers with * fake parameter values instead. This is used when a query's result columns * have to be described (SQLPrepare+SQLDescribeCol) before executing the * query, in UseServerSidePrepare=0 mode. */ RETCODE prepareParametersNoDesc(StatementClass *stmt, BOOL fake_params, BOOL param_cast) { CSTR func = "process_statements"; RETCODE retval; ConnectionClass *conn = SC_get_conn(stmt); char plan_name[32]; po_ind_t multi; const char *orgquery = NULL, *srvquery = NULL; ssize_t endp1, endp2; SQLSMALLINT num_pa = 0, num_p1, num_p2; ProcessedStmt *pstmt; ProcessedStmt *last_pstmt; QueryParse query_org, *qp; QueryBuild query_crt, *qb; MYLOG(DETAIL_LOG_LEVEL, "entering\n"); qp = &query_org; QP_initialize(qp, stmt); qb = &query_crt; if (QB_initialize(qb, qp->stmt_len, stmt, fake_params ? RPM_FAKE_PARAMS : RPM_BUILDING_PREPARE_STATEMENT) < 0) { SC_set_errornumber(stmt, STMT_NO_MEMORY_ERROR); return SQL_ERROR; } if (param_cast) qb->flags |= FLGB_PARAM_CAST; for (qp->opos = 0; qp->opos < qp->stmt_len; qp->opos++) { retval = inner_process_tokens(qp, qb); if (SQL_ERROR == retval) { QB_replace_SC_error(stmt, qb, func); QB_Destructor(qb); return retval; } } CVT_TERMINATE(qb); retval = SQL_ERROR; #define return DONT_CALL_RETURN_FROM_HERE??? if (NAMED_PARSE_REQUEST == SC_get_prepare_method(stmt)) SPRINTF_FIXED(plan_name, "_PLAN%p", stmt); else plan_name[0] = '\0'; stmt->current_exec_param = 0; multi = stmt->multi_statement; orgquery = stmt->statement; srvquery = qb->query_statement; SC_scanQueryAndCountParams(orgquery, conn, &endp1, &num_p1, &multi, NULL); SC_scanQueryAndCountParams(srvquery, conn, &endp2, NULL, NULL, NULL); MYLOG(0, "parsed for the first command length=" FORMAT_SSIZE_T "(" FORMAT_SSIZE_T ") num_p=%d\n", endp2, endp1, num_p1); pstmt = buildProcessedStmt(srvquery, endp2 < 0 ? SQL_NTS : endp2, fake_params ? 0 : num_p1); if (!pstmt) { SC_set_errornumber(stmt, STMT_NO_MEMORY_ERROR); goto cleanup; } stmt->processed_statements = last_pstmt = pstmt; while (multi > 0) { orgquery += (endp1 + 1); srvquery += (endp2 + 1); num_pa += num_p1; SC_scanQueryAndCountParams(orgquery, conn, &endp1, &num_p1, &multi, NULL); SC_scanQueryAndCountParams(srvquery, conn, &endp2, &num_p2, NULL, NULL); MYLOG(0, "parsed for the subsequent command length=" FORMAT_SSIZE_T "(" FORMAT_SSIZE_T ") num_p=%d\n", endp2, endp1, num_p1); pstmt = buildProcessedStmt(srvquery, endp2 < 0 ? SQL_NTS : endp2, fake_params ? 0 : num_p1); if (!pstmt) { SC_set_errornumber(stmt, STMT_NO_MEMORY_ERROR); goto cleanup; } last_pstmt->next = pstmt; last_pstmt = pstmt; } SC_set_planname(stmt, plan_name); SC_set_prepared(stmt, plan_name[0] ? PREPARING_PERMANENTLY : PREPARING_TEMPORARILY); retval = SQL_SUCCESS; cleanup: #undef return stmt->current_exec_param = -1; QB_Destructor(qb); return retval; } /* * Describe the parameters and portal for given query. */ static RETCODE desc_params_and_sync(StatementClass *stmt) { CSTR func = "desc_params_and_sync"; RETCODE retval; ConnectionClass *conn = SC_get_conn(stmt); QResultClass *res; char *plan_name; int func_cs_count = 0; SQLSMALLINT num_pa = 0; ProcessedStmt *pstmt; MYLOG(DETAIL_LOG_LEVEL, "entering\n"); retval = SQL_ERROR; #define return DONT_CALL_RETURN_FROM_HERE??? ENTER_INNER_CONN_CS(conn, func_cs_count); plan_name = stmt->plan_name ? stmt->plan_name : ""; pstmt = stmt->processed_statements; stmt->current_exec_param = 0; res = ParseAndDescribeWithLibpq(stmt, plan_name, pstmt->query, pstmt->num_params, "prepare_and_describe", NULL); if (res == NULL) goto cleanup; // SC_set_Result(stmt, res); QR_Destructor(stmt->parsed); stmt->parsed = res; if (!QR_command_maybe_successful(res)) { SC_set_error(stmt, STMT_EXEC_ERROR, "Error while preparing parameters", func); goto cleanup; } num_pa = pstmt->num_params; for (pstmt = pstmt->next; pstmt; pstmt = pstmt->next) { if (pstmt->num_params > 0) { stmt->current_exec_param = num_pa; res = ParseAndDescribeWithLibpq(stmt, plan_name, pstmt->query, pstmt->num_params, "prepare_and_describe", NULL); if (res == NULL) goto cleanup; QR_Destructor(res); num_pa += pstmt->num_params; } } retval = SQL_SUCCESS; cleanup: #undef return CLEANUP_FUNC_CONN_CS(func_cs_count, conn); stmt->current_exec_param = -1; return retval; } /* * Process the original SQL query, and and ask the server describe the * parameters. */ RETCODE prepareParameters(StatementClass *stmt, BOOL fake_params) { ConnectionClass *conn = SC_get_conn(stmt); switch (stmt->prepared) { case PREPARED_TEMPORARILY: if (conn->unnamed_prepared_stmt == stmt) return SQL_SUCCESS; else break; case NOT_YET_PREPARED: case PREPARING_PERMANENTLY: case PREPARING_TEMPORARILY: break; default: return SQL_SUCCESS; } MYLOG(DETAIL_LOG_LEVEL, "calling prepareParameters\n"); if (prepareParametersNoDesc(stmt, fake_params, PARSE_PARAM_CAST) == SQL_ERROR) return SQL_ERROR; return desc_params_and_sync(stmt); } /* * This function inserts parameters into an SQL statements. * It will also modify a SELECT statement for use with declare/fetch cursors. * This function does a dynamic memory allocation to get rid of query size limit! */ int copy_statement_with_parameters(StatementClass *stmt, BOOL buildPrepareStatement) { CSTR func = "copy_statement_with_parameters"; RETCODE retval; QueryParse query_org, *qp; QueryBuild query_crt, *qb; char *new_statement; ConnectionClass *conn = SC_get_conn(stmt); ConnInfo *ci = &(conn->connInfo); const char *bestitem = NULL; MYLOG(DETAIL_LOG_LEVEL, "entering prepared=%d\n", stmt->prepared); if (!stmt->statement) { SC_set_error(stmt, STMT_INTERNAL_ERROR, "No statement string", func); return SQL_ERROR; } qp = &query_org; QP_initialize(qp, stmt); if (stmt->statement_type != STMT_TYPE_SELECT) { stmt->options.cursor_type = SQL_CURSOR_FORWARD_ONLY; stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; } else if (stmt->options.cursor_type == SQL_CURSOR_FORWARD_ONLY) stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; else if (stmt->options.scroll_concurrency != SQL_CONCUR_READ_ONLY) { if (SQL_CURSOR_DYNAMIC == stmt->options.cursor_type && 0 == (ci->updatable_cursors & ALLOW_DYNAMIC_CURSORS)) stmt->options.cursor_type = SQL_CURSOR_KEYSET_DRIVEN; if (SQL_CURSOR_KEYSET_DRIVEN == stmt->options.cursor_type && 0 == (ci->updatable_cursors & ALLOW_KEYSET_DRIVEN_CURSORS)) stmt->options.cursor_type = SQL_CURSOR_STATIC; switch (stmt->options.cursor_type) { case SQL_CURSOR_DYNAMIC: case SQL_CURSOR_KEYSET_DRIVEN: if (SC_update_not_ready(stmt)) parse_statement(stmt, TRUE); if (SC_is_updatable(stmt) && stmt->ntab > 0) { if (bestitem = GET_NAME(stmt->ti[0]->bestitem), NULL == bestitem) stmt->options.cursor_type = SQL_CURSOR_STATIC; } break; } if (SQL_CURSOR_STATIC == stmt->options.cursor_type) { if (0 == (ci->updatable_cursors & ALLOW_STATIC_CURSORS)) stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; else if (SC_update_not_ready(stmt)) parse_statement(stmt, TRUE); } if (SC_parsed_status(stmt) == STMT_PARSE_FATAL) { stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; if (stmt->options.cursor_type == SQL_CURSOR_KEYSET_DRIVEN) stmt->options.cursor_type = SQL_CURSOR_STATIC; } else if (!stmt->updatable) { stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; stmt->options.cursor_type = SQL_CURSOR_STATIC; } else { qp->from_pos = stmt->from_pos; qp->where_pos = stmt->where_pos; } MYLOG(DETAIL_LOG_LEVEL, "type=" FORMAT_UINTEGER " concur=" FORMAT_UINTEGER "\n", stmt->options.cursor_type, stmt->options.scroll_concurrency); } SC_miscinfo_clear(stmt); /* If the application hasn't set a cursor name, then generate one */ if (!SC_cursor_is_valid(stmt)) { char curname[32]; SPRINTF_FIXED(curname, "SQL_CUR%p", stmt); STRX_TO_NAME(stmt->cursor_name, curname); } if (stmt->stmt_with_params) { free(stmt->stmt_with_params); stmt->stmt_with_params = NULL; } SC_no_fetchcursor(stmt); qb = &query_crt; qb->query_statement = NULL; if (PREPARED_PERMANENTLY == stmt->prepared) { /* already prepared */ retval = SQL_SUCCESS; goto cleanup; } /* * If it's a simple read-only cursor, we use extended query protocol to * Parse it. */ if (buildPrepareStatement && SQL_CONCUR_READ_ONLY == stmt->options.scroll_concurrency) { /* Nothing to do here. It will be prepared before execution. */ char plan_name[32]; if (NAMED_PARSE_REQUEST == SC_get_prepare_method(stmt)) SPRINTF_FIXED(plan_name, "_PLAN%p", stmt); else plan_name[0] = '\0'; SC_set_planname(stmt, plan_name); SC_set_prepared(stmt, plan_name[0] ? PREPARING_PERMANENTLY : PREPARING_TEMPORARILY); retval = SQL_SUCCESS; goto cleanup; } /* Otherwise... */ if (QB_initialize(qb, qp->stmt_len, stmt, RPM_REPLACE_PARAMS) < 0) { retval = SQL_ERROR; goto cleanup; } if (SIMPLE_PARAM_CAST) qb->flags |= FLGB_PARAM_CAST; new_statement = qb->query_statement; /* For selects, prepend a declare cursor to the statement */ if (SC_may_use_cursor(stmt) && stmt->external) { const char *opt_scroll = NULL_STRING, *opt_hold = NULL_STRING; if (ci->drivers.use_declarefetch /** && SQL_CONCUR_READ_ONLY == stmt->options.scroll_concurrency **/ ) { SC_set_fetchcursor(stmt); if (SC_is_with_hold(stmt)) opt_hold = " with hold"; if (SQL_CURSOR_FORWARD_ONLY != stmt->options.cursor_type) opt_scroll = " scroll"; } if (SC_is_fetchcursor(stmt)) { snprintfcat(new_statement, qb->str_alsize, "declare \"%s\"%s cursor%s for ", SC_cursor_name(stmt), opt_scroll, opt_hold); qb->npos = strlen(new_statement); qp->flags |= FLGP_USING_CURSOR; qp->declare_pos = qb->npos; } if (SQL_CONCUR_READ_ONLY != stmt->options.scroll_concurrency) { qb->flags |= FLGB_CREATE_KEYSET; if (SQL_CURSOR_KEYSET_DRIVEN == stmt->options.cursor_type) qb->flags |= FLGB_KEYSET_DRIVEN; } } for (qp->opos = 0; qp->opos < qp->stmt_len; qp->opos++) { retval = inner_process_tokens(qp, qb); if (SQL_ERROR == retval) { QB_replace_SC_error(stmt, qb, func); QB_Destructor(qb); return retval; } } /* make sure new_statement is always null-terminated */ CVT_TERMINATE(qb); new_statement = qb->query_statement; stmt->statement_type = qp->statement_type; if (0 == (qp->flags & FLGP_USING_CURSOR)) SC_no_fetchcursor(stmt); #ifdef NOT_USED /* this seems problematic */ else if (0 == (qp->flags & (FLGP_SELECT_FOR_UPDATE_OR_SHARE | FLGP_SELECT_FOR_READONLY)) && 0 == stmt->multi_statement && PG_VERSION_GE(conn, 8.3)) { BOOL semi_colon_found = FALSE; const UCHAR *ptr = NULL, semi_colon = ';'; int npos; if (npos = F_NewPos(qb) - 1, npos >= 0) ptr = F_NewPtr(qb) - 1; for (; npos >= 0 && isspace(*ptr); npos--, ptr--) ; if (npos >= 0 && semi_colon == *ptr) { qb->npos = npos; semi_colon_found = TRUE; } CVT_APPEND_STR(qb, " for read only"); if (semi_colon_found) CVT_APPEND_CHAR(qb, semi_colon); CVT_TERMINATE(qb); } #endif /* NOT_USED */ if (0 != (qp->flags & FLGP_SELECT_INTO) || 0 != (qp->flags & FLGP_MULTIPLE_STATEMENT)) { stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; } if (0 != (qp->flags & FLGP_SELECT_FOR_UPDATE_OR_SHARE)) { stmt->options.scroll_concurrency = SQL_CONCUR_READ_ONLY; } if (conn->DriverToDataSource != NULL) { size_t length = strlen(new_statement); conn->DriverToDataSource(conn->translation_option, SQL_CHAR, new_statement, (SDWORD) length, new_statement, (SDWORD) length, NULL, NULL, 0, NULL); } if (!stmt->load_statement && qp->from_pos >= 0) { size_t npos = qb->load_stmt_len; if (0 == npos) { npos = qb->npos; for (; npos > 0; npos--) { if (isspace((unsigned char) new_statement[npos - 1])) continue; if (';' != new_statement[npos - 1]) break; } if (0 != (qb->flags & FLGB_KEYSET_DRIVEN)) { qb->npos = npos; /* ---------- * 1st query is for field information * 2nd query is keyset gathering */ CVT_APPEND_STR(qb, " where ctid = '(0,0)';select \"ctid"); if (bestitem) { CVT_APPEND_STR(qb, "\", \""); CVT_APPEND_STR(qb, bestitem); } CVT_APPEND_STR(qb, "\" from "); CVT_APPEND_DATA(qb, qp->statement + qp->from_pos + 5, qp->stmt_len - qp->from_pos - 5); } } npos -= qp->declare_pos; stmt->load_statement = malloc(npos + 1); if (!stmt->load_statement) { retval = SQL_ERROR; goto cleanup; } memcpy(stmt->load_statement, qb->query_statement + qp->declare_pos, npos); stmt->load_from_pos = qb->load_from_pos - qp->declare_pos; stmt->load_statement[npos] = '\0'; } stmt->stmt_with_params = qb->query_statement; retval = SQL_SUCCESS; cleanup: return retval; } static void remove_declare_cursor(QueryBuild *qb, QueryParse *qp) { qp->flags &= ~FLGP_USING_CURSOR; if (qp->declare_pos <= 0) return; memmove(qb->query_statement, qb->query_statement + qp->declare_pos, qb->npos - qp->declare_pos); qb->npos -= qp->declare_pos; qp->declare_pos = 0; } /* * When 'tag' starts with dollar-quoted tag, e.g. "$foo$...", return * the length of the tag (e.g. 5, with the previous example). If there * is no end-dollar in the string, returns 0. The caller should've checked * that the string begins with a dollar. */ size_t findTag(const char *tag, int ccsc) { size_t taglen = 0; encoded_str encstr; UCHAR tchar; encoded_str_constr(&encstr, ccsc, tag + 1); for (tchar = encoded_nextchar(&encstr); tchar; tchar = encoded_nextchar(&encstr)) { if (MBCS_NON_ASCII(encstr)) continue; if (DOLLAR_QUOTE == tchar) { taglen = encstr.pos + 2; break; } if (!isalnum(tchar)) break; } return taglen; } int findIdentifier(const UCHAR *str, int ccsc, const UCHAR **next_token) { int slen = -1; encoded_str encstr; UCHAR tchar; BOOL dquote = FALSE; *next_token = NULL; encoded_str_constr(&encstr, ccsc, (const char *) str); for (tchar = encoded_nextchar(&encstr); tchar; tchar = encoded_nextchar(&encstr)) { if (MBCS_NON_ASCII(encstr)) continue; if (encstr.pos == 0) /* the first character */ { if (dquote = (IDENTIFIER_QUOTE == tchar), dquote) continue; if (!isalpha(tchar)) { slen = 0; if (IS_NOT_SPACE(tchar)) *next_token = ENCODE_PTR(encstr); break; } } if (dquote) { if (IDENTIFIER_QUOTE == tchar) { tchar = encoded_nextchar(&encstr); if (IDENTIFIER_QUOTE == tchar) continue; slen = encstr.pos; break; } } else { if (isalnum(tchar)) continue; switch (tchar) { case '_': case DOLLAR_QUOTE: continue; } slen = encstr.pos; if (IS_NOT_SPACE(tchar)) *next_token = ENCODE_PTR(encstr); break; } } if (slen < 0 && !dquote) slen = encstr.pos; if (NULL == *next_token) { for (; tchar; tchar = encoded_nextchar(&encstr)) { if (IS_NOT_SPACE((UCHAR) tchar)) { *next_token = ENCODE_PTR(encstr); break; } } } return slen; } static pgNAME lower_or_remove_dquote(pgNAME nm, const UCHAR *src, int srclen, int ccsc) { int i, outlen; char *tc; UCHAR tchar; BOOL idQuote; encoded_str encstr; if (nm.name) tc = realloc(nm.name, srclen + 1); else tc = malloc(srclen + 1); if (!tc) { NULL_THE_NAME(nm); return nm; } nm.name = tc; idQuote = (src[0] == IDENTIFIER_QUOTE); encoded_str_constr(&encstr, ccsc, (const char *) src); for (i = 0, tchar = encoded_nextchar(&encstr), outlen = 0; i < srclen; i++, tchar = encoded_nextchar(&encstr)) { if (MBCS_NON_ASCII(encstr)) { tc[outlen++] = tchar; continue; } if (idQuote) { if (IDENTIFIER_QUOTE == tchar) { if (0 == i) continue; if (i == srclen - 1) continue; i++; tchar = encoded_nextchar(&encstr); } tc[outlen++] = tchar; } else { tc[outlen++] = tolower(tchar); } } tc[outlen] = '\0'; return nm; } int eatTableIdentifiers(const UCHAR *str, int ccsc, pgNAME *table, pgNAME *schema) { int len; const UCHAR *next_token; const UCHAR *tstr = str; while (isspace(*tstr)) tstr++; if ((len = findIdentifier(tstr, ccsc, &next_token)) <= 0) return len; /* table name doesn't exist */ if (table) { if (IDENTIFIER_QUOTE == *tstr) *table = lower_or_remove_dquote(*table, tstr, len, ccsc); else STRN_TO_NAME(*table, tstr, len); } if (!next_token || '.' != *next_token || (int) (next_token - tstr) != len) return (int) (next_token - str); /* table only */ tstr = next_token + 1; if ((len = findIdentifier(tstr, ccsc, &next_token)) <= 0) return -1; if (table) { if (schema) MOVE_NAME(*schema, *table); *table = lower_or_remove_dquote(*table, tstr, len, ccsc); } if (!next_token || '.' != *next_token || (int) (next_token - tstr) != len) return (int) (next_token - str); /* schema.table */ tstr = next_token + 1; if ((len = findIdentifier(tstr, ccsc, &next_token)) <= 0) return -1; if (table) { if (schema) MOVE_NAME(*schema, *table); *table = lower_or_remove_dquote(*table, tstr, len, ccsc); } return (int) (next_token - str); /* catalog.schema.table */ } static void token_start(QueryParse *qp, char oldchar) { qp->prev_token_end = FALSE; qp->token_curr[0] = oldchar; qp->token_len = 1; } static int token_finish(QueryParse *qp, char oldchar, char *finished_token) { int ret = -1; if (!qp->prev_token_end) { if (oldchar && qp->token_len + 1 < sizeof(qp->token_curr)) qp->token_curr[qp->token_len++] = oldchar; qp->prev_token_end = TRUE; qp->token_curr[qp->token_len] = '\0'; strncpy_null(finished_token, qp->token_curr, sizeof(qp->token_curr)); MYLOG(DETAIL_LOG_LEVEL, "finished token=%s\n", finished_token); ret = qp->token_len; } return ret; } static int token_restart(QueryParse *qp, char oldchar, char *finished_token) { int ret = token_finish(qp, 0, finished_token); if (IS_NOT_SPACE(oldchar)) token_start(qp, oldchar); return ret; } static int token_continue(QueryParse *qp, char oldchar) { if (qp->prev_token_end) token_start(qp, oldchar); else if (qp->token_len + 1 < sizeof(qp->token_curr)) qp->token_curr[qp->token_len++] = oldchar; return qp->token_len; } /* * ParseToken functions */ typedef struct { QueryParse *qp; int token_len; BOOL curchar_processed; unsigned int in_status; char finished_token[sizeof(((QueryParse *) NULL)->token_curr)]; } ParseToken; static void PT_initialize(ParseToken *pt, QueryParse *qp) { pt->qp = qp; pt->token_len = -1; pt->curchar_processed = FALSE; pt->in_status = 0; pt->finished_token[0] = '\0'; } static int PT_token_finish(ParseToken *pt, char oldchar) { int token_len_tmp; if (pt->curchar_processed) return pt->token_len; if ((token_len_tmp = token_finish(pt->qp, oldchar, pt->finished_token)) > 0) { pt->token_len = token_len_tmp; pt->in_status = pt->qp->in_status; } if (oldchar) pt->curchar_processed = TRUE; return pt->token_len; } static int PT_token_restart(ParseToken *pt, char oldchar) { int token_len_tmp; unsigned int in_status_save; if (pt->curchar_processed) return pt->token_len; in_status_save = pt->qp->in_status; if ((token_len_tmp = token_restart(pt->qp, oldchar, pt->finished_token)) > 0) { pt->token_len = token_len_tmp; pt->in_status = in_status_save; } pt->curchar_processed = TRUE; return pt->token_len; } static int PT_token_continue(ParseToken *pt, char oldchar) { if (pt->curchar_processed) return pt->token_len; token_continue(pt->qp, oldchar); pt->curchar_processed = TRUE; return pt->token_len; } #define PT_TOKEN_IGNORE(pt) ((pt)->curchar_processed = TRUE) static int inner_process_tokens(QueryParse *qp, QueryBuild *qb) { CSTR func = "inner_process_tokens"; BOOL lf_conv = ((qb->flags & FLGB_CONVERT_LF) != 0); const char *bestitem = NULL; RETCODE retval; Int4 opos; char oldchar; StatementClass *stmt = qb->stmt; BOOL isnull; BOOL isbinary; Oid dummy; ParseToken pts, *pt = &pts; PT_initialize(pt, qp); if (stmt->ntab > 0) bestitem = GET_NAME(stmt->ti[0]->bestitem); opos = (Int4) qp->opos; if (opos < 0) { qb->errornumber = STMT_SEQUENCE_ERROR; qb->errormsg = "Function call for inner_process_tokens sequence error"; return SQL_ERROR; } if (qp->from_pos == opos) { if (0 == (qb->flags & FLGB_CREATE_KEYSET)) { qb->errornumber = STMT_SEQUENCE_ERROR; qb->errormsg = "Should come here only when handling updatable cursors"; return SQL_ERROR; } CVT_APPEND_STR(qb, ", \"ctid"); if (bestitem) { CVT_APPEND_STR(qb, "\", \""); CVT_APPEND_STR(qb, bestitem); } CVT_APPEND_STR(qb, "\" "); qb->load_from_pos = qb->npos; } else if (qp->where_pos == opos) { qb->load_stmt_len = qb->npos; if (0 != (qb->flags & FLGB_KEYSET_DRIVEN)) { CVT_APPEND_STR(qb, "where ctid = '(0,0)';select \"ctid"); if (bestitem) { CVT_APPEND_STR(qb, "\", \""); CVT_APPEND_STR(qb, bestitem); } CVT_APPEND_STR(qb, "\" from "); CVT_APPEND_DATA(qb, qp->statement + qp->from_pos + 5, qp->where_pos - qp->from_pos - 5); } } oldchar = encoded_byte_check(&qp->encstr, qp->opos); if (MBCS_NON_ASCII(qp->encstr)) { if (QP_in_idle_status(qp)) { PT_token_restart(pt, oldchar); /* placed before QP_enter() */ QP_enter(qp, QP_IN_IDENT_KEYWORD); /* identifier */ } else if (qp->token_len > 0) PT_token_continue(pt, oldchar); CVT_APPEND_CHAR(qb, oldchar); return SQL_SUCCESS; } /* * From here we are guaranteed to handle a 1-byte character. */ if (QP_is_in(qp, QP_IN_IDENT_KEYWORD)) /* identifier or keyword */ { if (isalnum((UCHAR)oldchar) || DOLLAR_QUOTE == oldchar || '_' == oldchar) { CVT_APPEND_CHAR(qb, oldchar); PT_token_continue(pt, oldchar); return SQL_SUCCESS; } PT_token_finish(pt, 0); /* placed before QP_exit() */ QP_exit(qp, QP_IN_IDENT_KEYWORD); } if (QP_is_in(qp, QP_IN_ESCAPE)) /* escape in literal check */ { QP_exit(qp, QP_IN_ESCAPE); CVT_APPEND_CHAR(qb, oldchar); return SQL_SUCCESS; } else if (QP_is_in(qp, QP_IN_DOLLAR_QUOTE)) /* dollar quote check */ { if (oldchar == DOLLAR_QUOTE) { if (strncmp(F_OldPtr(qp), qp->dollar_tag, qp->taglen) == 0) { CVT_APPEND_DATA(qb, F_OldPtr(qp), qp->taglen); qp->opos += (qp->taglen - 1); QP_exit(qp, QP_IN_DOLLAR_QUOTE); qp->dollar_tag = NULL; qp->taglen = -1; return SQL_SUCCESS; } } CVT_APPEND_CHAR(qb, oldchar); return SQL_SUCCESS; } else if (QP_is_in(qp, QP_IN_LITERAL)) /* quote check */ { if (oldchar == LITERAL_QUOTE) { PT_token_finish(pt, oldchar); /* placed before QP_exit() */ QP_exit(qp, QP_IN_LITERAL); } else { PT_token_continue(pt, oldchar); if (oldchar == qp->escape_in_literal) QP_enter(qp, QP_IN_ESCAPE); /* escape in literal */ } CVT_APPEND_CHAR(qb, oldchar); return SQL_SUCCESS; } else if (QP_is_in(qp, QP_IN_DQUOTE_IDENTIFIER)) /* double quote check */ { if (oldchar == IDENTIFIER_QUOTE) { PT_token_finish(pt, oldchar); /* placed before QP_exit() */ QP_exit(qp, QP_IN_DQUOTE_IDENTIFIER); } else PT_token_continue(pt, oldchar); CVT_APPEND_CHAR(qb, oldchar); return SQL_SUCCESS; } else if (QP_is_in(qp, QP_IN_COMMENT_BLOCK)) /* comment_level check */ { if ('/' == oldchar && '*' == F_OldPtr(qp)[1]) { qp->comment_level++; QP_enter(qp, QP_IN_COMMENT_BLOCK); CVT_APPEND_CHAR(qb, oldchar); F_OldNext(qp); oldchar = F_OldChar(qp); } else if ('*' == oldchar && '/' == F_OldPtr(qp)[1]) { if (--qp->comment_level <= 0) QP_exit(qp, QP_IN_COMMENT_BLOCK); CVT_APPEND_CHAR(qb, oldchar); F_OldNext(qp); oldchar = F_OldChar(qp); } CVT_APPEND_CHAR(qb, oldchar); return SQL_SUCCESS; } else if (QP_is_in(qp, QP_IN_LINE_COMMENT)) /* line comment check */ { if (PG_LINEFEED == oldchar) QP_exit(qp, QP_IN_LINE_COMMENT); CVT_APPEND_CHAR(qb, oldchar); return SQL_SUCCESS; } if (!QP_in_idle_status(qp)) { qb->errornumber = STMT_EXEC_ERROR; qb->errormsg = "logic error? not in QP_in_idle_status"; return SQL_ERROR; } /* * From here we are guaranteed to be in neither a literal_escape, * a LITREAL_QUOTE nor an IDENTIFIER_QUOTE. */ /* Squeeze carriage-return/linefeed pairs to linefeed only */ if (lf_conv && PG_CARRIAGE_RETURN == oldchar && qp->opos + 1 < qp->stmt_len && PG_LINEFEED == qp->statement[qp->opos + 1]) return SQL_SUCCESS; /* * Handle literals (date, time, timestamp) and ODBC scalar * functions */ if (oldchar == ODBC_ESCAPE_START) { PT_token_finish(pt, 0); if (SQL_ERROR == convert_escape(qp, qb)) { if (0 == qb->errornumber) { qb->errornumber = STMT_EXEC_ERROR; qb->errormsg = "ODBC escape convert error"; } MYLOG(0, "convert_escape error\n"); return SQL_ERROR; } PT_TOKEN_IGNORE(pt); return SQL_SUCCESS; } /* End of an escape sequence */ else if (oldchar == ODBC_ESCAPE_END) { PT_token_finish(pt, 0); PT_TOKEN_IGNORE(pt); return QB_end_brace(qb); } else if (oldchar == '@' && strnicmp(F_OldPtr(qp), "@@identity", 10) == 0) { ConnectionClass *conn = SC_get_conn(stmt); BOOL converted = FALSE; COL_INFO *coli; #ifdef NOT_USED /* lastval() isn't always appropriate */ if (PG_VERSION_GE(conn, 8.1)) { CVT_APPEND_STR(qb, "lastval()"); converted = TRUE; } else #endif /* NOT_USED */ if (NAME_IS_VALID(conn->tableIns)) { TABLE_INFO ti, *pti = &ti; memset(&ti, 0, sizeof(ti)); NAME_TO_NAME(ti.schema_name, conn->schemaIns); NAME_TO_NAME(ti.table_name, conn->tableIns); getCOLIfromTI(func, conn, qb->stmt, 0, &pti); coli = ti.col_info; NULL_THE_NAME(ti.schema_name); NULL_THE_NAME(ti.table_name); if (NULL != coli) { int i, num_fields = QR_NumResultCols(coli->result); const char *auto_increment; for (i = 0; i < num_fields; i++) { auto_increment = (const char *) QR_get_value_backend_text(coli->result, i, COLUMNS_AUTO_INCREMENT); if (auto_increment && auto_increment[0] == '1') { converted = TRUE; break; } } if (converted) { const char *column_def = (const char *) QR_get_value_backend_text(coli->result, i, COLUMNS_COLUMN_DEF); if (NULL != column_def && strncmp(column_def, "nextval", 7) == 0) { CVT_APPEND_STR(qb, "curr"); CVT_APPEND_STR(qb, column_def + 4); } else { char relcnv[128]; const char *column_name = (const char *) QR_get_value_backend_text(coli->result, i, COLUMNS_COLUMN_NAME); CVT_APPEND_STR(qb, "currval(pg_get_serial_sequence('"); if (NAME_IS_VALID(conn->schemaIns)) { CVT_APPEND_STR(qb, identifierEscape((const SQLCHAR *) SAFE_NAME(conn->schemaIns), SQL_NTS, conn, relcnv, sizeof(relcnv), TRUE)); CVT_APPEND_STR(qb, "."); } CVT_APPEND_STR(qb, identifierEscape((const SQLCHAR *) SAFE_NAME(conn->tableIns), SQL_NTS, conn, relcnv, sizeof(relcnv), TRUE)); CVT_APPEND_STR(qb, "','"); if (NULL != column_name) CVT_APPEND_STR(qb, identifierEscape((const SQLCHAR *) column_name, SQL_NTS, conn, relcnv, sizeof(relcnv), FALSE)); CVT_APPEND_STR(qb, "')::regclass)"); } } } TI_ClearObject(pti); } if (!converted) CVT_APPEND_STR(qb, "NULL"); qp->opos += 10; return SQL_SUCCESS; } /* * Can you have parameter markers inside of quotes? I dont think * so. All the queries I've seen expect the driver to put quotes * if needed. */ else if (oldchar != '?') { if (oldchar == DOLLAR_QUOTE) { PT_token_finish(pt, 0); qp->taglen = findTag(F_OldPtr(qp), qp->encstr.ccsc); if (qp->taglen > 0) { QP_enter(qp, QP_IN_DOLLAR_QUOTE); qp->dollar_tag = F_OldPtr(qp); CVT_APPEND_DATA(qb, F_OldPtr(qp), qp->taglen); qp->opos += (qp->taglen - 1); return SQL_SUCCESS; } } else if (oldchar == LITERAL_QUOTE) { PT_token_restart(pt, oldchar); /* placed before QP_enter() */ QP_enter(qp, QP_IN_LITERAL); qp->escape_in_literal = CC_get_escape(qb->conn); if (!qp->escape_in_literal) { if (LITERAL_EXT == F_OldPtr(qp)[-1]) qp->escape_in_literal = ESCAPE_IN_LITERAL; } } else if (oldchar == IDENTIFIER_QUOTE) { PT_token_restart(pt, oldchar); /* placed before QP_enter() */ QP_enter(qp, QP_IN_DQUOTE_IDENTIFIER); } else if ('/' == oldchar && '*' == F_OldPtr(qp)[1]) { qp->comment_level++; PT_token_finish(pt, 0); /* comments are excluded */ QP_enter(qp, QP_IN_COMMENT_BLOCK); PT_TOKEN_IGNORE(pt); } else if ('-' == oldchar && '-' == F_OldPtr(qp)[1]) { PT_token_finish(pt, 0); /* comments are excluded */ QP_enter(qp, QP_IN_LINE_COMMENT); PT_TOKEN_IGNORE(pt); } else if (oldchar == ';') { PT_token_restart(pt, 0); /* * can't parse multiple statement using protocol V3. * reset the dollar number here in case it is divided * to parse. */ qb->dollar_number = 0; if (0 != (qp->flags & FLGP_USING_CURSOR)) { const char *vp = &(qp->statement[qp->opos + 1]); while (*vp && isspace((unsigned char) *vp)) vp++; if (*vp) /* multiple statement */ { qp->flags |= FLGP_MULTIPLE_STATEMENT; qb->flags &= ~FLGB_KEYSET_DRIVEN; remove_declare_cursor(qb, qp); } } } else if (isalnum(oldchar)) { PT_token_restart(pt, oldchar); /* placed before QP_enter() */ QP_enter(qp, QP_IN_IDENT_KEYWORD); /* identifier or keyword */ } else { /* * a hack to handle boolean items in VBA * with MS Access. * VBA seems to transform the where condition * a_boolean_item=True * into * ("a_boolean_item" = 1) * which causes an ERROR:Operator does not exist boolean = integer . * So transforms it into * ("a_boolean_item"='1') * here. */ if (')' == oldchar && qb->conn->ms_jet && 1 == qp->token_len && '1' == qp->token_curr[0] && 8 <= F_OldPos(qp)) { const char *oldptr = F_OldPtr(qp); int oldpos = F_OldPos(qp); if (strncmp(oldptr - 5, "\" =", 3) == 0) { int i; for (i = 6; i < oldpos - 1; i++) { if (oldptr[-i] == '"') { if (oldptr[-(i+1)] == '(') { F_NewPtr(qb)[-4] = '='; F_NewPtr(qb)[-3] = '\''; F_NewPtr(qb)[-2] = '1'; F_NewPtr(qb)[-1] = '\''; } break; } } } } if (!isalnum((UCHAR) oldchar)) { PT_token_restart(pt, oldchar); } else PT_token_continue(pt, oldchar); } if (pt->token_len > 0) MYLOG(0, "token_len=%d status=%x token=%s\n", pt->token_len, pt->in_status, pt->finished_token); if (!pt->curchar_processed) { MYLOG(0, "Forgot to process ParseToken char=%c status=%u\n", oldchar, qp->in_status); #ifdef NOT_USED /* strict check for debugging */ qb->errornumber = STMT_EXEC_ERROR; qb->errormsg = "Forget to process ParseToken"; return SQL_ERROR; #endif /* NOT_USED */ } switch (pt->token_len) { case 4: if (0 != (qp->flags & FLGP_USING_CURSOR) && into_table_from(&qp->statement[qp->opos - pt->token_len])) { qp->flags |= FLGP_SELECT_INTO; qb->flags &= ~FLGB_KEYSET_DRIVEN; qp->statement_type = STMT_TYPE_CREATE; remove_declare_cursor(qb, qp); } else if (stricmp(pt->finished_token, "join") == 0) check_join(stmt, F_OldPtr(qp), F_OldPos(qp)); break; case 3: if (0 != (qp->flags & FLGP_USING_CURSOR) && strnicmp(pt->finished_token, "for", 3) == 0) { UInt4 flg; size_t endpos; flg = table_for_update_or_share(F_OldPtr(qp), &endpos); if (0 != (FLGP_SELECT_FOR_UPDATE_OR_SHARE & flg)) { qp->flags |= flg; remove_declare_cursor(qb, qp); } else qp->flags |= flg; } break; case 2: { size_t endpos; if (STMT_TYPE_INSERT == qp->statement_type && strnicmp(pt->finished_token, "()", 2) == 0 && insert_without_target(F_OldPtr(qp), &endpos)) { qb->npos -= 2; CVT_APPEND_STR(qb, "DEFAULT VALUES"); qp->opos += endpos; return SQL_SUCCESS; } break; } case 1: { size_t endpos; if (STMT_TYPE_INSERT == qp->statement_type && pt->finished_token[0] == '(' && oldchar == ')' && insert_without_target(F_OldPtr(qp)+1, &endpos)) { qb->npos --; CVT_APPEND_STR(qb, " DEFAULT VALUES"); qp->opos += endpos; return SQL_SUCCESS; } break; } } CVT_APPEND_CHAR(qb, oldchar); return SQL_SUCCESS; } else PT_token_restart(pt, oldchar); /* * It's a '?' parameter alright */ retval = ResolveOneParam(qb, qp, &isnull, &isbinary, &dummy); if (retval < 0) return retval; if (SQL_SUCCESS_WITH_INFO == retval) /* means discarding output parameter */ { } retval = SQL_SUCCESS; cleanup: return retval; } #define MIN_ALC_SIZE 128 /* * Build an array of parameters to pass to libpq's PQexecPrepared * function. */ BOOL build_libpq_bind_params(StatementClass *stmt, int *nParams, OID **paramTypes, char ***paramValues, int **paramLengths, int **paramFormats, int *resultFormat) { CSTR func = "build_libpq_bind_params"; QueryBuild qb; SQLSMALLINT num_p; int i, num_params; ConnectionClass *conn = SC_get_conn(stmt); BOOL ret = FALSE, discard_output; RETCODE retval; const IPDFields *ipdopts = SC_get_IPDF(stmt); *paramTypes = NULL; *paramValues = NULL; *paramLengths = NULL; *paramFormats = NULL; num_params = stmt->num_params; if (num_params < 0) { PGAPI_NumParams(stmt, &num_p); num_params = num_p; } if (ipdopts->allocated < num_params) { char tmp[100]; if (0 == ipdopts->allocated) STRCPY_FIXED(tmp, "Parameters exist but IPD isn't set. Please call SQLDescribeParam()"); else SPRINTF_FIXED(tmp, "The # of IPD parameters %d < %d the # of parameter markers", ipdopts->allocated, num_params); MYLOG(0, "%s\n", tmp); SC_set_error(stmt, STMT_COUNT_FIELD_INCORRECT, tmp, func); return FALSE; } if (QB_initialize(&qb, MIN_ALC_SIZE, stmt, RPM_BUILDING_BIND_REQUEST) < 0) return FALSE; if (num_params > 0) { *paramTypes = malloc(sizeof(OID) * num_params); if (*paramTypes == NULL) goto cleanup; *paramValues = malloc(sizeof(char *) * num_params); if (*paramValues == NULL) goto cleanup; memset(*paramValues, 0, sizeof(char *) * num_params); *paramLengths = malloc(sizeof(int) * num_params); if (*paramLengths == NULL) goto cleanup; *paramFormats = malloc(sizeof(int) * num_params); if (*paramFormats == NULL) goto cleanup; } qb.flags |= FLGB_BINARY_AS_POSSIBLE; MYLOG(DETAIL_LOG_LEVEL, "num_params=%d proc_return=%d\n", num_params, stmt->proc_return); num_p = num_params - qb.num_discard_params; MYLOG(DETAIL_LOG_LEVEL, "num_p=%d\n", num_p); discard_output = (0 != (qb.flags & FLGB_DISCARD_OUTPUT)); *nParams = 0; if (num_p > 0) { ParameterImplClass *parameters = ipdopts->parameters; int pno; BOOL isnull; BOOL isbinary; char *val_copy; OID pgType; /* * Now build the parameter values. */ for (i = 0, pno = 0; i < stmt->num_params; i++) { qb.npos = 0; retval = ResolveOneParam(&qb, NULL, &isnull, &isbinary, &pgType); if (SQL_ERROR == retval) { QB_replace_SC_error(stmt, &qb, func); ret = FALSE; goto cleanup; } MYLOG(DETAIL_LOG_LEVEL, "%dth parameter type oid is %u\n", i, PIC_dsp_pgtype(conn, parameters[i])); if (i < qb.proc_return) continue; if (SQL_PARAM_OUTPUT == parameters[i].paramType) { if (discard_output) continue; (*paramTypes)[pno] = PG_TYPE_VOID; (*paramValues)[pno] = NULL; (*paramLengths)[pno] = 0; (*paramFormats)[pno] = 0; pno++; continue; } if (!isnull) { val_copy = malloc(qb.npos + 1); if (!val_copy) goto cleanup; memcpy(val_copy, qb.query_statement, qb.npos); val_copy[qb.npos] = '\0'; (*paramTypes)[pno] = pgType; (*paramValues)[pno] = val_copy; if (qb.npos > INT_MAX) goto cleanup; (*paramLengths)[pno] = (int) qb.npos; } else { (*paramTypes)[pno] = pgType; (*paramValues)[pno] = NULL; (*paramLengths)[pno] = 0; } if (isbinary) MYLOG(0, "%dth parameter is of binary format\n", pno); (*paramFormats)[pno] = isbinary ? 1 : 0; pno++; } *nParams = pno; } /* result format is text */ *resultFormat = 0; ret = TRUE; cleanup: QB_Destructor(&qb); return ret; } /* * With SQL_MAX_NUMERIC_LEN = 16, the highest representable number is * 2^128 - 1, which fits in 39 digits. */ #define MAX_NUMERIC_DIGITS 39 /* * Convert a SQL_NUMERIC_STRUCT into string representation. */ static void ResolveNumericParam(const SQL_NUMERIC_STRUCT *ns, char *chrform) { Int4 i, vlen, len, newlen; const UCHAR *val = (const UCHAR *) ns->val; UCHAR vals[SQL_MAX_NUMERIC_LEN]; int lastnonzero; UCHAR calv[MAX_NUMERIC_DIGITS]; int precision; MYLOG(DETAIL_LOG_LEVEL, "C_NUMERIC [prec=%d scale=%d]", ns->precision, ns->scale); if (0 == ns->precision) { if (chrform) strncpy_null(chrform, "0", 2); return; } precision = ns->precision; if (precision > MAX_NUMERIC_DIGITS) precision = MAX_NUMERIC_DIGITS; /* * The representation in SQL_NUMERIC_STRUCT is 16 bytes with most * significant byte first. Make a working copy. */ memcpy(vals, val, SQL_MAX_NUMERIC_LEN); vlen = SQL_MAX_NUMERIC_LEN; len = 0; do { UInt2 d, r; /* * Divide the number by 10, and output the reminder as the next digit. * * Begin from the most-significant byte (last in the array), and at * each step, carry the remainder to the prev byte. */ r = 0; lastnonzero = -1; for (i = vlen - 1; i >= 0; i--) { UInt2 v; v = ((UInt2) vals[i]) + (r << 8); d = v / 10; r = v % 10; vals[i] = (UCHAR) d; if (d != 0 && lastnonzero == -1) lastnonzero = i; } /* output the remainder */ calv[len++] = (UCHAR) r; vlen = lastnonzero + 1; } while(lastnonzero >= 0 && len < precision); /* * calv now contains the digits in reverse order, i.e. least significant * digit is at calv[0] */ MYPRINTF(DETAIL_LOG_LEVEL, " len2=%d", len); /* build the final output string. */ newlen = 0; if (0 == ns->sign) chrform[newlen++] = '-'; i = len - 1; if (i < ns->scale) i = ns->scale; for (; i >= ns->scale; i--) { if (i >= len) chrform[newlen++] = '0'; else chrform[newlen++] = calv[i] + '0'; } if (ns->scale > 0) { chrform[newlen++] = '.'; for (; i >= 0; i--) { if (i >= len) chrform[newlen++] = '0'; else chrform[newlen++] = calv[i] + '0'; } } if (0 == len) chrform[newlen++] = '0'; chrform[newlen] = '\0'; MYLOG(DETAIL_LOG_LEVEL, " convval(2) len=%d %s\n", newlen, chrform); } /* * Convert a string representation of a numeric into SQL_NUMERIC_STRUCT. */ static void parse_to_numeric_struct(const char *wv, SQL_NUMERIC_STRUCT *ns, BOOL *overflow) { int i, nlen, dig; char calv[SQL_MAX_NUMERIC_LEN * 3]; BOOL dot_exist; *overflow = FALSE; /* skip leading space */ while (*wv && isspace((unsigned char) *wv)) wv++; /* sign */ ns->sign = 1; if (*wv == '-') { ns->sign = 0; wv++; } else if (*wv == '+') wv++; /* skip leading zeros */ while (*wv == '0') wv++; /* read the digits into calv */ ns->precision = 0; ns->scale = 0; for (nlen = 0, dot_exist = FALSE;; wv++) { if (*wv == '.') { if (dot_exist) break; dot_exist = TRUE; } else if (*wv == '\0' || !isdigit((unsigned char) *wv)) break; else { if (nlen >= sizeof(calv)) { if (dot_exist) break; else { ns->scale--; *overflow = TRUE; continue; } } if (dot_exist) ns->scale++; calv[nlen++] = *wv; } } ns->precision = nlen; /* Convert the decimal digits to binary */ memset(ns->val, 0, sizeof(ns->val)); for (dig = 0; dig < nlen; dig++) { UInt4 carry; /* multiply the current value by 10, and add the next digit */ carry = calv[dig] - '0'; for (i = 0; i < sizeof(ns->val); i++) { UInt4 t; t = ((UInt4) ns->val[i]) * 10 + carry; ns->val[i] = (unsigned char) (t & 0xFF); carry = (t >> 8); } if (carry != 0) *overflow = TRUE; } } static BOOL parameter_is_with_cast(const QueryParse *qp) { const char *str = F_OldPtr(qp); if ('?' != *str) return FALSE; while (isspace(*(++str))) ; if (strncmp(str, "::", 2) == 0) return TRUE; if (strnicmp(str, "as", 2) != 0) return FALSE; if (isspace(str[2])) return TRUE; return FALSE; } #ifdef UNICODE_SUPPORT enum { ErrorOutConversionErrors /* error out conversion errors */ , ReturnZeroLengthString /* simply returns zero length strings */ }; static int convert_err_flag = #ifdef WIN32 ReturnZeroLengthString; #else ErrorOutConversionErrors; #endif /* WIN32 */ static BOOL handle_lu_onvert_error(QueryBuild *qb, int flag, char *buffer, SQLLEN paralen) { int blen = paralen; if (!buffer) return FALSE; if (get_mylog() > 0 || ReturnZeroLengthString != flag) { const UCHAR *buf = (UCHAR *) buffer; int i; PQExpBufferData pbuf = {0}; if (SQL_NTS == blen) blen = strlen(buffer); initPQExpBuffer(&pbuf); appendPQExpBuffer(&pbuf, "Could not convert the current data '"); for (i = 0; i < blen; i++) { if (buf[i] >= 0x80) appendPQExpBuffer(&pbuf, "\\%03o", buf[i]); else if ('\\' == buf[i]) appendPQExpBuffer(&pbuf, "\\\\"); else appendPQExpBuffer(&pbuf, "%c", buf[i]); } appendPQExpBuffer(&pbuf, "' to wide chars"); MYLOG(0, "%s\n", pbuf.data); if (ReturnZeroLengthString != flag) { if (qb->stmt) SC_set_error(qb->stmt, STMT_EXEC_ERROR, pbuf.data, __FUNCTION__); else qb->errormsg = "could not convert the current data to wide chars"; } termPQExpBuffer(&pbuf); } switch (flag) { case ReturnZeroLengthString: if (qb->stmt) SC_set_error(qb->stmt, STMT_ERROR_IN_ROW, "conversion error to wide chars occurred", __FUNCTION__); return TRUE; default: qb->errornumber = STMT_EXEC_ERROR; return FALSE; } } #endif /* UNICODE_SUPPORT */ /* * Resolve one parameter. * * *isnull is set to TRUE if it was NULL. * *isbinary is set to TRUE, if the binary output format was used. (binary * output is only produced if the FLGB_BINARY_AS_POSSIBLE flag is set) * *pgType is set to the PostgreSQL type OID that should be used when binding * (or 0, to let the server decide) */ static int ResolveOneParam(QueryBuild *qb, QueryParse *qp, BOOL *isnull, BOOL *isbinary, OID *pgType) { ConnectionClass *conn = qb->conn; const APDFields *apdopts = qb->apdopts; const IPDFields *ipdopts = qb->ipdopts; PutDataInfo *pdata = qb->pdata; int param_number; char param_string[150], tmp[256]; char cbuf[PG_NUMERIC_MAX_PRECISION * 2]; /* seems big enough to handle the data in this function */ OID param_pgtype; SQLSMALLINT param_ctype, param_sqltype; SIMPLE_TIME st; struct tm *tim; SQLLEN used; const char *send_buf; char *buffer, *allocbuf = NULL, *lastadd = NULL; OID lobj_oid; int lobj_fd; SQLULEN offset = apdopts->param_offset_ptr ? *apdopts->param_offset_ptr : 0; size_t current_row = qb->current_row; BOOL handling_large_object = FALSE, req_bind; BOOL need_quotes = TRUE; BOOL add_parens = FALSE; BOOL negative; ParameterInfoClass *apara; ParameterImplClass *ipara; BOOL outputDiscard, valueOutput; SDOUBLE dbv; SFLOAT flv; SQL_INTERVAL_STRUCT *ivstruct; const char *ivsign; BOOL final_binary_convert = FALSE; RETCODE retval = SQL_ERROR; *isnull = FALSE; *isbinary = FALSE; *pgType = 0; outputDiscard = (0 != (qb->flags & FLGB_DISCARD_OUTPUT)); valueOutput = (qb->param_mode != RPM_FAKE_PARAMS && qb->param_mode != RPM_BUILDING_PREPARE_STATEMENT); req_bind = (qb->param_mode == RPM_BUILDING_BIND_REQUEST); if (qb->proc_return < 0 && qb->stmt) qb->proc_return = qb->stmt->proc_return; /* * It's a '?' parameter alright */ param_number = ++qb->param_number; MYLOG(DETAIL_LOG_LEVEL, "para:%d(%d,%d)\n", param_number, ipdopts->allocated, apdopts->allocated); apara = NULL; ipara = NULL; if (param_number < apdopts->allocated) apara = apdopts->parameters + param_number; if (param_number < ipdopts->allocated) ipara = ipdopts->parameters + param_number; if ((!apara || !ipara) && valueOutput) { MYLOG(0, "The # of (A|I)PD parameters (%d, %d) < %d the # of parameter markers\n", apdopts->allocated, ipdopts->allocated, param_number); qb->errormsg = "The # of binded parameters < the # of parameter markers"; qb->errornumber = STMT_COUNT_FIELD_INCORRECT; CVT_TERMINATE(qb); /* just in case */ return SQL_ERROR; } MYLOG(DETAIL_LOG_LEVEL, "ipara=%p paramName=%s paramType=%d %d proc_return=%d\n", ipara, ipara ? PRINT_NAME(ipara->paramName) : PRINT_NULL, ipara ? ipara->paramType : -1, PG_VERSION_LT(conn, 8.1), qb->proc_return); if (param_number < qb->proc_return) { if (ipara && SQL_PARAM_OUTPUT != ipara->paramType) { qb->errormsg = "The function return value isn't marked as output parameter"; qb->errornumber = STMT_EXEC_ERROR; CVT_TERMINATE(qb); /* just in case */ return SQL_ERROR; } return SQL_SUCCESS; } if (ipara && SQL_PARAM_OUTPUT == ipara->paramType) { if (PG_VERSION_LT(conn, 8.1)) { qb->errormsg = "Output parameter isn't available before 8.1 version"; qb->errornumber = STMT_INTERNAL_ERROR; CVT_TERMINATE(qb); /* just in case */ return SQL_ERROR; } if (outputDiscard) { ssize_t npos = 0; for (npos = qb->npos - 1; npos >= 0 && isspace((unsigned char) qb->query_statement[npos]) ; npos--) ; if (npos >= 0) { switch (qb->query_statement[npos]) { case ',': qb->npos = npos; qb->query_statement[npos] = '\0'; break; case '(': if (!qp) break; for (npos = qp->opos + 1; isspace((unsigned char) qp->statement[npos]); npos++) ; if (qp->statement[npos] == ',') qp->opos = npos; break; } } return SQL_SUCCESS_WITH_INFO; } } else { /* For procedures, use named notation if a parameter name is specified */ if (!req_bind && ipara && NAME_IS_VALID(ipara->paramName) && qp && qp->statement_type == STMT_TYPE_PROCCALL) { char named_notation[COLUMN_NAME_STORAGE_LEN + 7]; SPRINTF_FIXED(named_notation, "\"%s\" := ", GET_NAME(ipara->paramName)); CVT_APPEND_STR(qb, named_notation); } } if ((!apara || !ipara) && qb->param_mode == RPM_FAKE_PARAMS) { CVT_APPEND_STR(qb, "NULL"); qb->flags |= FLGB_INACCURATE_RESULT; return SQL_SUCCESS; } if (qb->param_mode == RPM_BUILDING_PREPARE_STATEMENT) { char pnum[16]; qb->dollar_number++; if (ipara && SQL_PARAM_OUTPUT != ipara->paramType && (qb->flags & FLGB_PARAM_CAST) != 0 && !parameter_is_with_cast(qp)) SPRINTF_FIXED(pnum, "$%d%s", qb->dollar_number, sqltype_to_pgcast(conn, ipara->SQLType)); else SPRINTF_FIXED(pnum, "$%d", qb->dollar_number); CVT_APPEND_STR(qb, pnum); return SQL_SUCCESS; } /* * After this point, we can assume apara and ipara to be set. The only * cases where we allow them to be NULL is when param_mode is * RPM_FAKE_PARAMS or RPM_BUILDING_PREPARE_STATEMENT, and we've now handled * those cases. */ /* Assign correct buffers based on data at exec param or not */ if (apara->data_at_exec) { if (pdata->allocated != apdopts->allocated) extend_putdata_info(pdata, apdopts->allocated, TRUE); used = pdata->pdata[param_number].EXEC_used ? *pdata->pdata[param_number].EXEC_used : SQL_NTS; buffer = pdata->pdata[param_number].EXEC_buffer; if (pdata->pdata[param_number].lobj_oid) handling_large_object = TRUE; } else { UInt4 bind_size = apdopts->param_bind_type; UInt4 ctypelen; BOOL bSetUsed = FALSE; buffer = apara->buffer + offset; if (current_row > 0) { if (bind_size > 0) buffer += (bind_size * current_row); else if (ctypelen = ctype_length(apara->CType), ctypelen > 0) buffer += current_row * ctypelen; else buffer += current_row * apara->buflen; } if (apara->used || apara->indicator) { SQLULEN p_offset; if (bind_size > 0) p_offset = offset + bind_size * current_row; else p_offset = offset + sizeof(SQLLEN) * current_row; if (apara->indicator) { used = *LENADDR_SHIFT(apara->indicator, p_offset); if (SQL_NULL_DATA == used) bSetUsed = TRUE; } if (!bSetUsed && apara->used) { used = *LENADDR_SHIFT(apara->used, p_offset); bSetUsed = TRUE; } } if (!bSetUsed) used = SQL_NTS; } /* Handle DEFAULT_PARAM parameter data. Should be NULL ? if (used == SQL_DEFAULT_PARAM) { return SQL_SUCCESS; } */ param_ctype = apara->CType; param_sqltype = ipara->SQLType; param_pgtype = PIC_dsp_pgtype(qb->conn, *ipara); /* XXX: should we use param_pgtype here instead? */ *pgType = sqltype_to_bind_pgtype(conn, param_sqltype); if (0 == param_sqltype) /* calling SQLSetStmtAttr(.., SQL_ATTR_APP_PARAM_DES, an ARD of another statement) may cause this */ { if (0 != param_pgtype) { param_sqltype = pgtype_attr_to_concise_type(conn, param_pgtype, PG_ATP_UNSET, PG_ADT_UNSET, PG_UNKNOWNS_UNSET); MYLOG(0, "convert from pgtype(%u) to sqltype(%d)\n", param_pgtype, param_sqltype); } } MYLOG(0, "from(fcType)=%d, to(fSqlType)=%d(%u), *pgType=%u\n", param_ctype, param_sqltype, param_pgtype, *pgType); /* Handle NULL parameter data */ if (SQL_PARAM_OUTPUT == ipara->paramType || used == SQL_NULL_DATA || used == SQL_DEFAULT_PARAM) { *isnull = TRUE; if (!req_bind) CVT_APPEND_STR(qb, "NULL"); return SQL_SUCCESS; } /* * If no buffer, and it's not null, then what the hell is it? Just * leave it alone then. */ if (!buffer) { if (qb->param_mode == RPM_FAKE_PARAMS) { CVT_APPEND_STR(qb, "NULL"); qb->flags |= FLGB_INACCURATE_RESULT; return SQL_SUCCESS; } else if (!handling_large_object) { /* shouldn't happen */ qb->errormsg = "unexpected NULL parameter value"; qb->errornumber = STMT_EXEC_ERROR; return SQL_ERROR; } } /* replace DEFAULT with something we can use */ if (param_ctype == SQL_C_DEFAULT) { param_ctype = sqltype_to_default_ctype(conn, param_sqltype); #ifdef UNICODE_SUPPORT if (param_ctype == SQL_C_WCHAR && CC_default_is_c(conn)) param_ctype =SQL_C_CHAR; #endif } allocbuf = NULL; send_buf = NULL; param_string[0] = '\0'; cbuf[0] = '\0'; memset(&st, 0, sizeof(st)); ivstruct = (SQL_INTERVAL_STRUCT *) buffer; /* Convert input C type to a neutral format */ #ifdef UNICODE_SUPPORT if (get_convtype() > 0) /* conversion between the current locale is available */ { BOOL wcs_debug = conn->connInfo.wcs_debug; BOOL is_utf8 = (UTF8 == conn->ccsc); BOOL same_encoding = (conn->ccsc == pg_CS_code(conn->locale_encoding)); switch (param_ctype) { case SQL_C_CHAR: if (!same_encoding || wcs_debug) { SQLLEN paralen = used; MYLOG(0, "locale param convert\n"); if ((used = bindpara_msg_to_utf8(buffer, &allocbuf, used)) < 0) { if (!handle_lu_onvert_error(qb, convert_err_flag, buffer, paralen)) goto cleanup; send_buf = NULL_STRING; used = 0; } else send_buf = allocbuf; } break; case SQL_C_WCHAR: if (!is_utf8 || (same_encoding && wcs_debug)) { MYLOG(0, "hybrid param convert\n"); if ((used = bindpara_wchar_to_msg((SQLWCHAR *) buffer, &allocbuf, used)) < 0) { qb->errormsg = "Could not convert from wide characters to the current locale"; qb->errornumber = STMT_EXEC_ERROR; goto cleanup; } send_buf = allocbuf; } break; } } #endif /* UNICODE_SUPPORT */ switch (param_ctype) { case SQL_C_BINARY: send_buf = buffer; break; case SQL_C_CHAR: if (NULL == send_buf) send_buf = buffer; break; #ifdef UNICODE_SUPPORT case SQL_C_WCHAR: MYLOG(0, " C_WCHAR=%d contents=%s(" FORMAT_LEN ")\n", param_ctype, buffer, used); if (NULL == send_buf) { allocbuf = ucs2_to_utf8((SQLWCHAR *) buffer, used > 0 ? used / WCLEN : used, &used, FALSE); send_buf = allocbuf; } break; #endif /* UNICODE_SUPPORT */ case SQL_C_DOUBLE: dbv = *((SDOUBLE *) buffer); #ifdef WIN32 if (_finite(dbv)) #endif /* WIN32 */ { SPRINTF_FIXED(param_string, "%.*g", PG_DOUBLE_DIGITS, dbv); set_server_decimal_point(param_string, SQL_NTS); } #ifdef WIN32 else if (_isnan(dbv)) STRCPY_FIXED(param_string, NAN_STRING); else if (dbv < .0) STRCPY_FIXED(param_string, MINFINITY_STRING); else STRCPY_FIXED(param_string, INFINITY_STRING); #endif /* WIN32 */ break; case SQL_C_FLOAT: flv = *((SFLOAT *) buffer); #ifdef WIN32 if (_finite(flv)) #endif /* WIN32 */ { SPRINTF_FIXED(param_string, "%.*g", PG_REAL_DIGITS, flv); set_server_decimal_point(param_string, SQL_NTS); } #ifdef WIN32 else if (_isnan(flv)) STRCPY_FIXED(param_string, NAN_STRING); else if (flv < .0) STRCPY_FIXED(param_string, MINFINITY_STRING); else STRCPY_FIXED(param_string, INFINITY_STRING); #endif /* WIN32 */ break; case SQL_C_SLONG: case SQL_C_LONG: SPRINTF_FIXED(param_string, FORMAT_INTEGER, *((SQLINTEGER *) buffer)); break; #ifdef ODBCINT64 case SQL_C_SBIGINT: case SQL_BIGINT: /* Is this needed ? */ SPRINTF_FIXED(param_string, FORMATI64, *((SQLBIGINT *) buffer)); break; case SQL_C_UBIGINT: SPRINTF_FIXED(param_string, FORMATI64U, *((SQLUBIGINT *) buffer)); break; #endif /* ODBCINT64 */ case SQL_C_SSHORT: case SQL_C_SHORT: ITOA_FIXED(param_string, *((SQLSMALLINT *) buffer)); break; case SQL_C_STINYINT: case SQL_C_TINYINT: ITOA_FIXED(param_string, *((SCHAR *) buffer)); break; case SQL_C_ULONG: SPRINTF_FIXED(param_string, FORMAT_UINTEGER, *((SQLUINTEGER *) buffer)); break; case SQL_C_USHORT: SPRINTF_FIXED(param_string, "%u", *((SQLUSMALLINT *) buffer)); break; case SQL_C_UTINYINT: SPRINTF_FIXED(param_string, "%u", *((UCHAR *) buffer)); break; case SQL_C_BIT: { int i = *((UCHAR *) buffer); ITOA_FIXED(param_string, i ? 1 : 0); break; } case SQL_C_DATE: case SQL_C_TYPE_DATE: /* 91 */ { DATE_STRUCT *ds = (DATE_STRUCT *) buffer; st.m = ds->month; st.d = ds->day; st.y = ds->year; break; } case SQL_C_TIME: case SQL_C_TYPE_TIME: /* 92 */ { TIME_STRUCT *ts = (TIME_STRUCT *) buffer; st.hh = ts->hour; st.mm = ts->minute; st.ss = ts->second; /* * Initialize date in case conversion destination * expects date part from this source time data. */ tim = SC_get_localtime(qb->stmt); st.m = tim->tm_mon + 1; st.d = tim->tm_mday; st.y = tim->tm_year + 1900; break; } case SQL_C_TIMESTAMP: case SQL_C_TYPE_TIMESTAMP: /* 93 */ { 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; st.fr = tss->fraction; MYLOG(0, "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; } case SQL_C_NUMERIC: { ResolveNumericParam((SQL_NUMERIC_STRUCT *) buffer, param_string); break; } case SQL_C_INTERVAL_YEAR: ivsign = ivstruct->interval_sign ? "-" : ""; SPRINTF_FIXED(param_string, "%s%u years", ivsign, (unsigned int) ivstruct->intval.year_month.year); break; case SQL_C_INTERVAL_MONTH: case SQL_C_INTERVAL_YEAR_TO_MONTH: ivsign = ivstruct->interval_sign ? "-" : ""; SPRINTF_FIXED(param_string, "%s%u years %s%u mons", ivsign, (unsigned int) ivstruct->intval.year_month.year, ivsign, (unsigned int) ivstruct->intval.year_month.month); break; case SQL_C_INTERVAL_DAY: ivsign = ivstruct->interval_sign ? "-" : ""; SPRINTF_FIXED(param_string, "%s%u days", ivsign, (unsigned int) ivstruct->intval.day_second.day); break; case SQL_C_INTERVAL_HOUR: case SQL_C_INTERVAL_DAY_TO_HOUR: ivsign = ivstruct->interval_sign ? "-" : ""; SPRINTF_FIXED(param_string, "%s%u days %s%02u:00:00", ivsign, (unsigned int) ivstruct->intval.day_second.day, ivsign, (unsigned int) ivstruct->intval.day_second.hour); break; case SQL_C_INTERVAL_MINUTE: case SQL_C_INTERVAL_HOUR_TO_MINUTE: case SQL_C_INTERVAL_DAY_TO_MINUTE: ivsign = ivstruct->interval_sign ? "-" : ""; SPRINTF_FIXED(param_string, "%s%u days %s%02u:%02u:00", ivsign, (unsigned int) ivstruct->intval.day_second.day, ivsign, (unsigned int) ivstruct->intval.day_second.hour, (unsigned int) ivstruct->intval.day_second.minute); break; case SQL_C_INTERVAL_SECOND: case SQL_C_INTERVAL_DAY_TO_SECOND: case SQL_C_INTERVAL_HOUR_TO_SECOND: case SQL_C_INTERVAL_MINUTE_TO_SECOND: ivsign = ivstruct->interval_sign ? "-" : ""; SPRINTF_FIXED(param_string, "%s%u days %s%02u:%02u:%02u", ivsign, (unsigned int) ivstruct->intval.day_second.day, ivsign, (unsigned int) ivstruct->intval.day_second.hour, (unsigned int) ivstruct->intval.day_second.minute, (unsigned int) ivstruct->intval.day_second.second); if (ivstruct->intval.day_second.fraction > 0) { int fraction = ivstruct->intval.day_second.fraction, prec = apara->precision; while (fraction % 10 == 0) { fraction /= 10; prec--; } SPRINTFCAT_FIXED(param_string, ".%0*d", prec, fraction); } break; case SQL_C_GUID: { /* * SQLGUID.Data1 is an "unsigned long" on some platforms, and * "unsigned int" on others. */ SQLGUID *g = (SQLGUID *) buffer; SPRINTF_FIXED (param_string, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", (unsigned int) g->Data1, g->Data2, g->Data3, g->Data4[0], g->Data4[1], g->Data4[2], g->Data4[3], g->Data4[4], g->Data4[5], g->Data4[6], g->Data4[7]); } break; default: /* error */ qb->errormsg = "Unrecognized C_parameter type in copy_statement_with_parameters"; qb->errornumber = STMT_NOT_IMPLEMENTED_ERROR; CVT_TERMINATE(qb); /* just in case */ goto cleanup; } /* * Now that the input data is in a neutral format, convert it to * the desired output format (sqltype) */ /* Special handling NULL string For FOXPRO */ MYLOG(0, "cvt_null_date_string=%d pgtype=%d send_buf=%p\n", conn->connInfo.cvt_null_date_string, param_pgtype, send_buf); if (conn->connInfo.cvt_null_date_string > 0 && (PG_TYPE_DATE == param_pgtype || PG_TYPE_DATETIME == param_pgtype || PG_TYPE_TIMESTAMP_NO_TMZONE == param_pgtype) && NULL != send_buf && ( (SQL_C_CHAR == param_ctype && '\0' == send_buf[0]) #ifdef UNICODE_SUPPORT || (SQL_C_WCHAR ==param_ctype && '\0' == send_buf[0] && '\0' == send_buf[1]) #endif /* UNICODE_SUPPORT */ )) { *isnull = TRUE; if (!req_bind) CVT_APPEND_STR(qb, "NULL"); retval = SQL_SUCCESS; goto cleanup; } /* * We now have the value we want to print in one of these three canonical * formats: * * 1. As a string in 'send_buf', with length indicated by 'used' (can be * SQL_NTS). * 2. As a null-terminated string in 'param_string'. * 3. Time-related fields in 'st'. */ /* * For simplicity, fold the param_string representation into 'send_buf'. */ if (!send_buf && param_string[0]) { send_buf = param_string; used = SQL_NTS; } /* * Do some further processing to create the final string we want to output. * This will use the fields in 'st' to create a string if it's a time/date * value, and do some other conversions. */ switch (param_sqltype) { case SQL_CHAR: case SQL_VARCHAR: case SQL_LONGVARCHAR: #ifdef UNICODE_SUPPORT case SQL_WCHAR: case SQL_WVARCHAR: case SQL_WLONGVARCHAR: #endif /* UNICODE_SUPPORT */ case SQL_BIT: /* Special handling for some column types */ switch (param_pgtype) { case PG_TYPE_BOOL: /* * consider True is -1 case. * * FIXME: This actually matches anything that begins * with -1, like "-1234" or "-1foobar". Is that * intentional? */ if (NULL != send_buf && '-' == send_buf[0] && '1' == send_buf[1]) { send_buf = "1"; used = 1; } break; case PG_TYPE_FLOAT4: case PG_TYPE_FLOAT8: case PG_TYPE_NUMERIC: if (NULL != send_buf) set_server_decimal_point((char *) send_buf, used); break; } if (!send_buf) { /* it was date,time,timestamp -- use m,d,y,hh,mm,ss */ SPRINTF_FIXED(tmp, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d", st.y, st.m, st.d, st.hh, st.mm, st.ss); send_buf = tmp; used = SQL_NTS; } break; case SQL_DATE: case SQL_TYPE_DATE: /* 91 */ if (send_buf) { /* copy char data to time */ my_strcpy(cbuf, sizeof(cbuf), send_buf, used); parse_datetime(cbuf, &st); } if (st.y < 0) SPRINTF_FIXED(tmp, "%.4d-%.2d-%.2d BC", -st.y, st.m, st.d); else SPRINTF_FIXED(tmp, "%.4d-%.2d-%.2d", st.y, st.m, st.d); lastadd = "::date"; send_buf = tmp; used = SQL_NTS; break; case SQL_TIME: case SQL_TYPE_TIME: /* 92 */ if (send_buf) { /* copy char data to time */ my_strcpy(cbuf, sizeof(cbuf), send_buf, used); parse_datetime(cbuf, &st); } if (st.fr > 0) { int wdt; int fr = effective_fraction(st.fr, &wdt); SPRINTF_FIXED(tmp, "%.2d:%.2d:%.2d.%0*d", st.hh, st.mm, st.ss, wdt, fr); } else SPRINTF_FIXED(tmp, "%.2d:%.2d:%.2d", st.hh, st.mm, st.ss); lastadd = "::time"; send_buf = tmp; used = SQL_NTS; break; case SQL_TIMESTAMP: case SQL_TYPE_TIMESTAMP: /* 93 */ if (send_buf) { my_strcpy(cbuf, sizeof(cbuf), send_buf, used); parse_datetime(cbuf, &st); } /* * SPRINTF_FIXED(tmp, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d", st.y, * st.m, st.d, st.hh, st.mm, st.ss); */ /* Time zone stuff is unreliable */ stime2timestamp(&st, tmp, sizeof(tmp), USE_ZONE, 6); lastadd = "::timestamp"; send_buf = tmp; used = SQL_NTS; break; case SQL_BINARY: case SQL_VARBINARY: case SQL_LONGVARBINARY: switch (param_ctype) { case SQL_C_BINARY: break; case SQL_C_CHAR: #ifdef UNICODE_SUPPORT case SQL_C_WCHAR: #endif /* UNICODE_SUPPORT */ switch (used) { case SQL_NTS: used = strlen(send_buf); break; } allocbuf = malloc(used / 2 + 1); if (allocbuf) { pg_hex2bin(send_buf, allocbuf, used); send_buf = allocbuf; used /= 2; } break; default: qb->errormsg = "Could not convert the ctype to binary type"; qb->errornumber = STMT_EXEC_ERROR; goto cleanup; } if (param_pgtype == PG_TYPE_BYTEA) { if (0 != (qb->flags & FLGB_BINARY_AS_POSSIBLE)) { MYLOG(0, "sending binary data leng=" FORMAT_LEN "\n", used); *isbinary = TRUE; } else { /* non-ascii characters should be * converted to octal */ MYLOG(0, "SQL_VARBINARY: about to call convert_to_pgbinary, used = " FORMAT_LEN "\n", used); final_binary_convert = TRUE; } break; } if (PG_TYPE_OID == param_pgtype && conn->lo_is_domain) ; else if (param_pgtype != conn->lobj_type) { qb->errormsg = "Could not convert binary other than LO type"; qb->errornumber = STMT_EXEC_ERROR; goto cleanup; } if (apara->data_at_exec) lobj_oid = pdata->pdata[param_number].lobj_oid; else { BOOL is_in_trans_at_entry = CC_is_in_trans(conn); int write_result; /* begin transaction if needed */ if (!is_in_trans_at_entry) { if (!CC_begin(conn)) { qb->errormsg = "Could not begin (in-line) a transaction"; qb->errornumber = STMT_EXEC_ERROR; goto cleanup; } } /* store the oid */ lobj_oid = odbc_lo_creat(conn, INV_READ | INV_WRITE); if (lobj_oid == 0) { qb->errornumber = STMT_EXEC_ERROR; qb->errormsg = "Couldn't create (in-line) large object."; goto cleanup; } /* store the fd */ lobj_fd = odbc_lo_open(conn, lobj_oid, INV_WRITE); if (lobj_fd < 0) { qb->errornumber = STMT_EXEC_ERROR; qb->errormsg = "Couldn't open (in-line) large object for writing."; goto cleanup; } write_result = odbc_lo_write(conn, lobj_fd, buffer, (Int4) used); if (write_result < 0) { qb->errornumber = STMT_EXEC_ERROR; qb->errormsg = "Couldn't write to (in-line) large object."; goto cleanup; } odbc_lo_close(conn, lobj_fd); /* commit transaction if needed */ if (!is_in_trans_at_entry) { if (!CC_commit(conn)) { qb->errormsg = "Could not commit (in-line) a transaction"; qb->errornumber = STMT_EXEC_ERROR; goto cleanup; } } } /* * the oid of the large object -- just put that in for the * parameter marker -- the data has already been sent to * the large object */ SPRINTF_FIXED(param_string, "%u", lobj_oid); lastadd = "::lo"; send_buf = param_string; used = SQL_NTS; break; /* * because of no conversion operator for bool and int4, * SQL_BIT */ /* must be quoted (0 or 1 is ok to use inside the quotes) */ case SQL_REAL: set_server_decimal_point((char *) send_buf, used); lastadd = "::float4"; break; case SQL_FLOAT: case SQL_DOUBLE: set_server_decimal_point((char *) send_buf, used); lastadd = "::float8"; break; case SQL_NUMERIC: break; /* * If it looks like a valid integer, we can pass it without quotes * and let the server interpret it. Arguably, it would always be * better to explicitly pass it as 'xxx'::integer or 'xxx'::smallint, * but historically we haven't done that, so let's avoid changing the * behaviour. * * If it's a negative number, we have to wrap it in parens. Otherwise * a query like "SELECT 0-?" would turn into "SELECT 0--123". */ case SQL_INTEGER: if (valid_int_literal(send_buf, used, &negative)) { need_quotes = FALSE; add_parens = negative; } else { /* * Doesn't look like a valid integer. The server will most * likely throw an error, unless it's in some format we don't * recognize but the server does. */ lastadd = "::int4"; } break; case SQL_SMALLINT: if (valid_int_literal(send_buf, used, &negative)) { need_quotes = FALSE; add_parens = negative; } else lastadd = "::smallint"; break; default: /* a numeric type or SQL_BIT */ break; } if (!send_buf) { qb->errormsg = "Could not convert parameter ctype to sqltype"; qb->errornumber = STMT_EXEC_ERROR; goto cleanup; } if (used == SQL_NTS) used = strlen(send_buf); /* * Ok, we now have the final string representation in 'send_buf', length 'used'. * We're ready to output the final string, with quotes and other * embellishments if necessary. * * In bind-mode, we don't need to do any quoting. */ if (req_bind) CVT_APPEND_DATA(qb, send_buf, used); else { if (add_parens) CVT_APPEND_CHAR(qb, '('); if (need_quotes) { if ((qb->flags & FLGB_LITERAL_EXTENSION) != 0) CVT_APPEND_CHAR(qb, LITERAL_EXT); CVT_APPEND_CHAR(qb, LITERAL_QUOTE); if (final_binary_convert) CVT_APPEND_BINARY(qb, send_buf, used); else CVT_SPECIAL_CHARS(qb, send_buf, used); CVT_APPEND_CHAR(qb, LITERAL_QUOTE); } else CVT_APPEND_DATA(qb, send_buf, used); if (add_parens) CVT_APPEND_CHAR(qb, ')'); if (lastadd && (FLGB_PARAM_CAST & qb->flags) != 0) CVT_APPEND_STR(qb, lastadd); } retval = SQL_SUCCESS; cleanup: if (allocbuf) free(allocbuf); return retval; } static const char * mapFunction(const char *func, int param_count, const char *keyword) { int i; const char *p1, *p2; for (i = 0; (p1 = mapFuncs[i].odbc_name) != NULL; i++) { if (p1[0] == '%') { if (p1[1] - '0' == param_count && !stricmp(p1 + 2, func)) return mapFuncs[i].pgsql_name; } else if (!stricmp(p1, func)) return mapFuncs[i].pgsql_name; else if (p2 = strchr(p1, (int) '('), NULL != p2) { int len = (int) (p2 - mapFuncs[i].odbc_name); if (strlen(func) == len && !strnicmp(p1, func, len) && !stricmp(p2 + 1, keyword)) return mapFuncs[i].pgsql_name; } } return NULL; } /* * processParameters() * Process function parameters and work with embedded escapes sequences. */ static int processParameters(QueryParse *qp, QueryBuild *qb, size_t *output_count, SQLLEN param_pos[][2]) { int retval, innerParenthesis, param_count; BOOL stop; /* begin with outer '(' */ innerParenthesis = 0; param_count = 0; if (NULL != output_count) *output_count = 0; stop = FALSE; for (; F_OldPos(qp) < qp->stmt_len; F_OldNext(qp)) { retval = inner_process_tokens(qp, qb); if (retval == SQL_ERROR) return retval; if (MBCS_NON_ASCII(qp->encstr)) continue; if (!QP_in_idle_status(qp)) continue; switch (F_OldChar(qp)) { case ',': if (1 == innerParenthesis) { param_pos[param_count][1] = F_NewPos(qb) - 2; param_count++; param_pos[param_count][0] = F_NewPos(qb); param_pos[param_count][1] = -1; } break; case '(': if (0 == innerParenthesis) { param_pos[param_count][0] = F_NewPos(qb); param_pos[param_count][1] = -1; } innerParenthesis++; break; case ')': innerParenthesis--; if (0 == innerParenthesis) { param_pos[param_count][1] = F_NewPos(qb) - 2; param_count++; param_pos[param_count][0] = param_pos[param_count][1] = -1; } if (output_count) *output_count = F_NewPos(qb); break; case ODBC_ESCAPE_END: stop = (0 == innerParenthesis); break; } if (stop) /* returns with the last } position */ break; } if (param_pos[param_count][0] >= 0) { MYLOG(0, "closing ) not found %d\n", innerParenthesis); qb->errornumber = STMT_EXEC_ERROR; qb->errormsg = "processParameters closing ) not found"; return SQL_ERROR; } else if (1 == param_count) /* the 1 parameter is really valid ? */ { BOOL param_exist = FALSE; SQLLEN i; for (i = param_pos[0][0]; i <= param_pos[0][1]; i++) { if (IS_NOT_SPACE(qb->query_statement[i])) { param_exist = TRUE; break; } } if (!param_exist) { param_pos[0][0] = param_pos[0][1] = -1; } } return SQL_SUCCESS; } /* * convert_escape() * This function doesn't return a pointer to static memory any longer ! */ static int convert_escape(QueryParse *qp, QueryBuild *qb) { RETCODE retval = SQL_SUCCESS; char buf[1024], buf_small[128], key[65]; UCHAR ucv; UInt4 prtlen; QueryBuild nqb; BOOL nqb_is_valid = FALSE; if (F_OldChar(qp) == ODBC_ESCAPE_START) /* skip the first { */ F_OldNext(qp); /* Separate off the key, skipping leading and trailing whitespace */ while ((ucv = F_OldChar(qp)) != '\0' && isspace(ucv)) F_OldNext(qp); /* * procedure calls */ /* '?=' to accept return values exists ? */ if (F_OldChar(qp) == '?') { qb->param_number++; qb->proc_return = 1; if (qb->stmt) qb->stmt->proc_return = 1; while (isspace((UCHAR) qp->statement[++qp->opos])); if (F_OldChar(qp) != '=') { F_OldPrior(qp); return SQL_SUCCESS; } while (isspace((UCHAR) qp->statement[++qp->opos])); } sscanf(F_OldPtr(qp), "%32s", key); while ((ucv = F_OldChar(qp)) != '\0' && (IS_NOT_SPACE(ucv))) F_OldNext(qp); while ((ucv = F_OldChar(qp)) != '\0' && isspace(ucv)) F_OldNext(qp); /* Avoid the concatenation of the function name with the previous word. Aceto */ if (stricmp(key, "call") == 0) { size_t funclen; const UCHAR *next_token; if (SQL_ERROR == QB_start_brace(qb)) { retval = SQL_ERROR; goto cleanup; } if (qb->num_io_params > 1 || (0 == qb->proc_return)) CVT_APPEND_STR(qb, "SELECT * FROM "); else CVT_APPEND_STR(qb, "SELECT "); funclen = findIdentifier((const UCHAR *) F_OldPtr(qp), qb->ccsc, &next_token); if (next_token && ODBC_ESCAPE_END == *next_token) { CVT_APPEND_DATA(qb, F_OldPtr(qp), funclen); CVT_APPEND_STR(qb, "()"); if (SQL_ERROR == QB_end_brace(qb)) { retval = SQL_ERROR; goto cleanup; } /* positioned at } */ qp->opos += ((const char *) next_token - F_OldPtr(qp)); } else { /* Continue at inner_process_tokens loop */ F_OldPrior(qp); return SQL_SUCCESS; } } else if (stricmp(key, "d") == 0) { /* Literal; return the escape part adding type cast */ F_ExtractOldTo(qp, buf_small, ODBC_ESCAPE_END, sizeof(buf_small)); prtlen = SPRINTF_FIXED(buf, "%s::date", buf_small); CVT_APPEND_DATA(qb, buf, prtlen); retval = QB_append_space_to_separate_identifiers(qb, qp); } else if (stricmp(key, "t") == 0) { /* Literal; return the escape part adding type cast */ F_ExtractOldTo(qp, buf_small, ODBC_ESCAPE_END, sizeof(buf_small)); prtlen = SPRINTF_FIXED(buf, "%s::time", buf_small); CVT_APPEND_DATA(qb, buf, prtlen); retval = QB_append_space_to_separate_identifiers(qb, qp); } else if (stricmp(key, "ts") == 0) { /* Literal; return the escape part adding type cast */ F_ExtractOldTo(qp, buf_small, ODBC_ESCAPE_END, sizeof(buf_small)); prtlen = SPRINTF_FIXED(buf, "%s::timestamp", buf_small); CVT_APPEND_DATA(qb, buf, prtlen); retval = QB_append_space_to_separate_identifiers(qb, qp); } else if (stricmp(key, "oj") == 0) /* {oj syntax support for 7.1 * servers */ { if (qb->stmt) SC_set_outer_join(qb->stmt); retval = QB_start_brace(qb); /* Continue at inner_process_tokens loop */ F_OldPrior(qp); goto cleanup; } else if (stricmp(key, "escape") == 0) /* like escape syntax support for 7.1+ servers */ { /* Literal; return the escape part adding type cast */ F_ExtractOldTo(qp, buf_small, ODBC_ESCAPE_END, sizeof(buf_small)); prtlen = SPRINTF_FIXED(buf, "%s %s", key, buf_small); CVT_APPEND_DATA(qb, buf, prtlen); retval = QB_append_space_to_separate_identifiers(qb, qp); } else if (stricmp(key, "fn") == 0) { const char *mapExpr; int i, param_count; SQLLEN from, to; size_t param_consumed; SQLLEN param_pos[16][2]; BOOL cvt_func = FALSE; /* Separate off the func name, skipping leading and trailing whitespace */ i = 0; while ((ucv = F_OldChar(qp)) != '\0' && ucv != '(' && (IS_NOT_SPACE(ucv))) { if (i < sizeof(key) - 1) key[i++] = ucv; F_OldNext(qp); } key[i] = '\0'; while ((ucv = F_OldChar(qp)) != '\0' && isspace(ucv)) F_OldNext(qp); /* * We expect left parenthesis here, else return fn body as-is * since it is one of those "function constants". */ if (F_OldChar(qp) != '(') { CVT_APPEND_STR(qb, key); goto cleanup; } /* * Process parameter list and inner escape * sequences * Aceto 2002-01-29 */ QB_initialize_copy(&nqb, qb, 1024); nqb_is_valid = TRUE; if (retval = processParameters(qp, &nqb, ¶m_consumed, param_pos), retval == SQL_ERROR) { qb->errornumber = nqb.errornumber; qb->errormsg = nqb.errormsg; goto cleanup; } for (param_count = 0;; param_count++) { if (param_pos[param_count][0] < 0) break; } if (param_count == 1 && param_pos[0][1] < param_pos[0][0]) param_count = 0; mapExpr = NULL; if (stricmp(key, "convert") == 0) cvt_func = TRUE; else { char keyword[64] = ""; if (param_count > 0) { int i, from, to; const char * p; for (i = param_pos[0][0], p = nqb.query_statement + i; i <= param_pos[0][1] && isspace(*p); i++, p++) ; from = i; for (; i <= param_pos[0][1] && IS_NOT_SPACE(*p); i++, p++) ; to = i - 1; if (to >= from) { int len = to - from + 1; if (len < sizeof(keyword)) { memcpy(keyword, nqb.query_statement + from, len); keyword[len] = '\0'; } } } mapExpr = mapFunction(key, param_count, keyword); } if (cvt_func) { if (2 == param_count) { BOOL add_cast = FALSE, add_quote = FALSE; const char *pptr; from = param_pos[0][0]; to = param_pos[0][1]; for (pptr = nqb.query_statement + from; *pptr && isspace((unsigned char) *pptr); pptr++) ; if (LITERAL_QUOTE == *pptr) ; else if ('-' == *pptr) add_quote = TRUE; else if (isdigit((unsigned char) *pptr)) add_quote = TRUE; else add_cast = TRUE; if (add_quote) CVT_APPEND_CHAR(qb, LITERAL_QUOTE); else if (add_cast) CVT_APPEND_CHAR(qb, '('); CVT_APPEND_DATA(qb, nqb.query_statement + from, to - from + 1); if (add_quote) CVT_APPEND_CHAR(qb, LITERAL_QUOTE); else if (add_cast) { const char *cast_form = NULL; char sqltype[32]; int typel; CVT_APPEND_CHAR(qb, ')'); from = param_pos[1][0]; to = param_pos[1][1]; typel = to - from + 1; if (typel < sizeof(sqltype)) { const char *type; memcpy(sqltype, nqb.query_statement + from, typel); sqltype[typel] = '\0'; MYLOG(0, FORMAT_LEN "-" FORMAT_LEN " SQLtype=%s SQL_BIT=%d\n", to, from, sqltype, SQL_BIT); for (type = sqltype; *type && isspace(*type); type++) ; if (strncmp(type, "SQL_", 4) == 0) { type += 4; if (strcmp(type, "INTEGER") == 0) cast_form = "int4"; else if (strcmp(type, "CHAR") == 0) cast_form = "varchar"; else if (strcmp(type, "VARCHAR") == 0) cast_form = "varchar"; else if (strcmp(type, "LONGVARCHAR") == 0) cast_form = "text"; else if (strcmp(type, "WCHAR") == 0) cast_form = "varchar"; else if (strcmp(type, "WVARCHAR") == 0) cast_form = "varchar"; else if (strcmp(type, "WLONGVARCHAR") == 0) cast_form = "text"; else if (strcmp(type, "NUMERIC") == 0) cast_form = "numeric"; else if (strcmp(type, "DOUBLE") == 0) cast_form = "float8"; else if (strcmp(type, "FLOAT") == 0) cast_form = "float8"; else if (strcmp(type, "REAL") == 0) cast_form = "float4"; else if (strcmp(type, "BIGINT") == 0) cast_form = "int8"; else if (strcmp(type, "DECIMAL") == 0) cast_form = "numeric"; else if (strcmp(type, "SMALLINT") == 0) cast_form = "int2"; else if (strcmp(type, "TYPE_DATE") == 0) cast_form = "date"; else if (strcmp(type, "TYPE_TIME") == 0) cast_form = "time"; else if (strcmp(type, "TYPE_TIMESTAMP") == 0) cast_form = "timestamp"; else if (strcmp(type, "BIT") == 0) cast_form = "bit"; } } if (NULL != cast_form) { CVT_APPEND_STR(qb, "::"); CVT_APPEND_STR(qb, cast_form); } } } else { qb->errornumber = STMT_EXEC_ERROR; qb->errormsg = "convert param count must be 2"; retval = SQL_ERROR; } } else if (mapExpr == NULL) { CVT_APPEND_STR(qb, key); CVT_APPEND_DATA(qb, nqb.query_statement, nqb.npos); } else { const char *mapptr; SQLLEN paramlen; int pidx; for (mapptr = mapExpr; *mapptr; mapptr++) { if (*mapptr != '$') { CVT_APPEND_CHAR(qb, *mapptr); continue; } mapptr++; if (*mapptr == '*') { from = 1; to = param_consumed - 2; } else if (isdigit((unsigned char) *mapptr)) { pidx = *mapptr - '0' - 1; if (pidx < 0 || param_pos[pidx][0] < 0) { qb->errornumber = STMT_EXEC_ERROR; qb->errormsg = "param not found"; MYLOG(0, "%dth param not found for the expression %s\n", pidx + 1, mapExpr); retval = SQL_ERROR; break; } from = param_pos[pidx][0]; to = param_pos[pidx][1]; } else { qb->errornumber = STMT_EXEC_ERROR; qb->errormsg = "internal expression error"; MYLOG(0, "internal expression error %s\n", mapExpr); retval = SQL_ERROR; break; } paramlen = to - from + 1; if (paramlen > 0) CVT_APPEND_DATA(qb, nqb.query_statement + from, paramlen); } } if (0 == qb->errornumber) { qb->errornumber = nqb.errornumber; qb->errormsg = nqb.errormsg; } if (SQL_ERROR != retval) { qb->param_number = nqb.param_number; qb->dollar_number = nqb.dollar_number; qb->flags = nqb.flags; } } else { /* Bogus key, leave untranslated */ retval = SQL_ERROR; } cleanup: if (nqb_is_valid) QB_Destructor(&nqb); return retval; } static BOOL convert_money(const char *s, char *sout, size_t soutmax) { char in, decp = 0; size_t i = 0, out = 0; int num_in = -1, period_in = -1, comma_in = -1; for (i = 0; s[i]; i++) { switch (in = s[i]) { case '.': if (period_in < 0) period_in = i; break; case ',': if (comma_in < 0) comma_in = i; break; default: if ('0' <= in && '9' >= in) num_in = i; break; } } if (period_in > comma_in) { if ( period_in >= num_in - 2) decp = '.'; } else if (comma_in >= 0 && comma_in >= num_in - 2) decp = ','; for (i = 0; s[i] && out + 1 < soutmax; i++) { switch (in = s[i]) { case '(': case '-': sout[out++] = '-'; break; default: if (in >= '0' && in <= '9') sout[out++] = in; else if (in == decp) sout[out++] = '.'; } } sout[out] = '\0'; return TRUE; } /* * 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 */ static char parse_datetime(const char *buf, SIMPLE_TIME *st) { int y, m, d, hh, mm, ss; int nf; BOOL bZone; int zone; y = m = d = hh = mm = ss = 0; st->fr = 0; st->infinity = 0; /* * Handle ODBC time/date/timestamp literals, e.g. * { d '2011-04-22' } * { t '12:34:56' } * { ts '2011-04-22 12:34:56' } */ if (buf[0] == ODBC_ESCAPE_START) { while (*(++buf) && *buf != LITERAL_QUOTE); if (!(*buf)) return FALSE; buf++; } bZone = FALSE; if (timestamp2stime(buf, st, &bZone, &zone)) return TRUE; 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 */ size_t convert_linefeeds(const char *si, char *dst, size_t max, BOOL convlf, BOOL *changed) { size_t i = 0, out = 0; if (max == 0) max = 0xffffffff; *changed = FALSE; for (i = 0; si[i] && out < max - 1; i++) { if (convlf && si[i] == '\n') { /* Only add the carriage-return if needed */ if (i > 0 && PG_CARRIAGE_RETURN == si[i - 1]) { if (dst) dst[out++] = si[i]; else out++; continue; } *changed = TRUE; if (dst) { dst[out++] = PG_CARRIAGE_RETURN; dst[out++] = '\n'; } else out += 2; } else { if (dst) dst[out++] = si[i]; else out++; } } if (dst) dst[out] = '\0'; return out; } /* * Change carriage-return/linefeed to just linefeed * Plus, escape any special characters. */ static BOOL convert_special_chars(QueryBuild *qb, const char *si, size_t used) { size_t i = 0, max; char tchar; encoded_str encstr; BOOL convlf = (0 != (qb->flags & FLGB_CONVERT_LF)); BOOL double_special = (qb->param_mode != RPM_BUILDING_BIND_REQUEST); int ccsc = qb->ccsc; char escape_in_literal = CC_get_escape(qb->conn); if (used == SQL_NTS) max = strlen(si); else max = used; /* * Make sure there's room for the null-terminator, if the input is an * empty string. XXX: I don't think the null-termination is actually * even required, but better safe than sorry. */ if (!enlarge_query_statement(qb, qb->npos + 1)) return FALSE; encoded_str_constr(&encstr, ccsc, si); for (i = 0; i < max && si[i]; i++) { tchar = encoded_nextchar(&encstr); /* * Make sure there is room for three more bytes in the buffer. We * expand quotes to two bytes, plus null-terminate the end. */ if (qb->npos + 3 >= qb->str_alsize) { if (!enlarge_query_statement(qb, qb->npos + 3)) return FALSE; } if (MBCS_NON_ASCII(encstr)) { qb->query_statement[qb->npos++] = tchar; continue; } if (convlf && /* CR/LF -> LF */ PG_CARRIAGE_RETURN == tchar && PG_LINEFEED == si[i + 1]) continue; else if (double_special && /* double special chars ? */ (tchar == LITERAL_QUOTE || tchar == escape_in_literal)) { qb->query_statement[qb->npos++] = tchar; } qb->query_statement[qb->npos++] = tchar; } qb->query_statement[qb->npos] = '\0'; return TRUE; } static int conv_from_octal(const char *s) { ssize_t i; int y = 0; for (i = 1; i <= 3; i++) y += (s[i] - '0') << (3 * (3 - i)); return y; } /* convert octal escapes to bytes */ static size_t convert_from_pgbinary(const char *value, char *rgbValue, SQLLEN cbValueMax) { size_t i, ilen = strlen(value); size_t o = 0; for (i = 0; i < ilen;) { if (value[i] == BYTEA_ESCAPE_CHAR) { if (value[i + 1] == BYTEA_ESCAPE_CHAR) { if (rgbValue) rgbValue[o] = value[i]; o++; i += 2; } else if (value[i + 1] == 'x') { i += 2; if (i < ilen) { ilen -= i; if (rgbValue) pg_hex2bin(value + i, rgbValue + o, ilen); o += ilen / 2; } break; } else { if (rgbValue) rgbValue[o] = conv_from_octal(&value[i]); o++; i += 4; } } else { if (rgbValue) rgbValue[o] = value[i]; o++; i++; } /** if (rgbValue) MYLOG(0, "i=%d, rgbValue[%d] = %d, %c\n", i, o, rgbValue[o], rgbValue[o]); ***/ } if (rgbValue) rgbValue[o] = '\0'; /* extra protection */ MYLOG(0, "in=" FORMAT_SIZE_T ", out = " FORMAT_SIZE_T "\n", ilen, o); return o; } static UInt2 conv_to_octal(UCHAR val, char *octal, char escape_ch) { int i, pos = 0, len; if (escape_ch) octal[pos++] = escape_ch; octal[pos] = BYTEA_ESCAPE_CHAR; len = 4 + pos; octal[len] = '\0'; for (i = len - 1; i > pos; i--) { octal[i] = (val & 7) + '0'; val >>= 3; } return (UInt2) len; } static char * conv_to_octal2(UCHAR val, char *octal) { int i; octal[0] = BYTEA_ESCAPE_CHAR; octal[4] = '\0'; for (i = 3; i > 0; i--) { octal[i] = (val & 7) + '0'; val >>= 3; } return octal; } /* convert non-ascii bytes to octal escape sequences */ static size_t convert_to_pgbinary(const char *in, char *out, size_t len, QueryBuild *qb) { UCHAR inc; size_t i, o = 0; char escape_in_literal = CC_get_escape(qb->conn); BOOL esc_double = (qb->param_mode != RPM_BUILDING_BIND_REQUEST && 0 != escape_in_literal); /* use hex format for 9.0 or later servers */ if (0 != (qb->flags & FLGB_HEX_BIN_FORMAT)) { if (esc_double) out[o++] = escape_in_literal; out[o++] = '\\'; out[o++] = 'x'; o += pg_bin2hex(in, out + o, len); return o; } for (i = 0; i < len; i++) { inc = in[i]; MYLOG(DETAIL_LOG_LEVEL, "in[" FORMAT_SIZE_T "] = %d, %c\n", i, inc, inc); if (inc < 128 && (isalnum(inc) || inc == ' ')) out[o++] = inc; else { if (esc_double) { o += conv_to_octal(inc, &out[o], escape_in_literal); } else { conv_to_octal2(inc, &out[o]); o += 4; } } } MYLOG(0, "leaving " FORMAT_SIZE_T ", out='%.*s'\n", o, (int) o, out); return o; } static const char *hextbl = "0123456789ABCDEF"; #define def_bin2hex(type) \ (const char *src, type *dst, SQLLEN length) \ { \ const char *src_wk; \ UCHAR chr; \ type *dst_wk; \ BOOL backwards; \ int i; \ \ backwards = FALSE; \ if ((char *) dst < src) \ { \ if ((char *) (dst + 2 * (length - 1)) > src + length - 1) \ return -1; \ } \ else if ((char *) dst < src + length) \ backwards = TRUE; \ if (backwards) \ { \ for (i = 0, src_wk = src + length - 1, dst_wk = dst + 2 * length - 1; i < length; i++, src_wk--) \ { \ chr = *src_wk; \ *dst_wk-- = hextbl[chr % 16]; \ *dst_wk-- = hextbl[chr >> 4]; \ } \ } \ else \ { \ for (i = 0, src_wk = src, dst_wk = dst; i < length; i++, src_wk++) \ { \ chr = *src_wk; \ *dst_wk++ = hextbl[chr >> 4]; \ *dst_wk++ = hextbl[chr % 16]; \ } \ } \ dst[2 * length] = '\0'; \ return 2 * length * sizeof(type); \ } #ifdef UNICODE_SUPPORT static SQLLEN pg_bin2whex def_bin2hex(SQLWCHAR) #endif /* UNICODE_SUPPORT */ static SQLLEN pg_bin2hex def_bin2hex(char) SQLLEN pg_hex2bin(const char *src, char *dst, SQLLEN length) { UCHAR chr; const char *src_wk; char *dst_wk; SQLLEN i; int val; BOOL HByte = TRUE; for (i = 0, src_wk = src, dst_wk = dst; i < length; i++, src_wk++) { chr = *src_wk; if (!chr) break; if (chr >= 'a' && chr <= 'f') val = chr - 'a' + 10; else if (chr >= 'A' && chr <= 'F') val = chr - 'A' + 10; else val = chr - '0'; if (HByte) *dst_wk = (val << 4); else { *dst_wk += val; dst_wk++; } HByte = !HByte; } *dst_wk = '\0'; return length; } /*------- * 1. get oid (from 'value') * 2. open the large object * 3. read from the large object (handle multiple GetData) * 4. close when read less than requested? -OR- * lseek/read each time * handle case where application receives truncated and * decides not to continue reading. * * CURRENTLY, ONLY LONGVARBINARY is handled, since that is the only * data type currently mapped to a PG_TYPE_LO. But, if any other types * are desired to map to a large object (PG_TYPE_LO), then that would * need to be handled here. For example, LONGVARCHAR could possibly be * mapped to PG_TYPE_LO someday, instead of PG_TYPE_TEXT as it is now. *------- */ static int convert_lo(StatementClass *stmt, const void *value, SQLSMALLINT fCType, PTR rgbValue, SQLLEN cbValueMax, SQLLEN *pcbValue) { CSTR func = "convert_lo"; OID oid; int result; Int8 retval; Int8 left64 = -1; struct GetBlobDataClass *gdata_blob = NULL; ConnectionClass *conn = SC_get_conn(stmt); ConnInfo *ci = &(conn->connInfo); GetDataInfo *gdata_info = SC_get_GDTI(stmt); int factor; oid = ATOI32U(value); if (0 == oid) { if (pcbValue) *pcbValue = SQL_NULL_DATA; return COPY_OK; } switch (fCType) { case SQL_C_CHAR: factor = 2; break; case SQL_C_BINARY: factor = 1; break; default: SC_set_error(stmt, STMT_EXEC_ERROR, "Could not convert lo to the c-type", func); return COPY_GENERAL_ERROR; } /* If using SQLGetData, then current_col will be set */ if (stmt->current_col >= 0) { gdata_blob = &(gdata_info->gdata[stmt->current_col].blob); left64 = gdata_blob->data_left64; } /* * if this is the first call for this column, open the large object * for reading */ if (!gdata_blob || gdata_blob->data_left64 == -1) { /* begin transaction if needed */ if (!CC_is_in_trans(conn)) { if (!CC_begin(conn)) { SC_set_error(stmt, STMT_EXEC_ERROR, "Could not begin (in-line) a transaction", func); return COPY_GENERAL_ERROR; } } stmt->lobj_fd = odbc_lo_open(conn, oid, INV_READ); if (stmt->lobj_fd < 0) { SC_set_error(stmt, STMT_EXEC_ERROR, "Couldnt open large object for reading.", func); return COPY_GENERAL_ERROR; } /* Get the size */ retval = odbc_lo_lseek64(conn, stmt->lobj_fd, 0L, SEEK_END); if (retval >= 0) { left64 = odbc_lo_tell64(conn, stmt->lobj_fd); if (gdata_blob) gdata_blob->data_left64 = left64; /* return to beginning */ odbc_lo_lseek64(conn, stmt->lobj_fd, 0L, SEEK_SET); } } else if (left64 == 0) return COPY_NO_DATA_FOUND; MYLOG(0, "lo data left = " FORMATI64 "\n", left64); if (stmt->lobj_fd < 0) { SC_set_error(stmt, STMT_EXEC_ERROR, "Large object FD undefined for multiple read.", func); return COPY_GENERAL_ERROR; } if (0 >= cbValueMax) retval = 0; else retval = (Int8) odbc_lo_read(conn, stmt->lobj_fd, (char *) rgbValue, (Int4) (factor > 1 ? (cbValueMax - 1) / factor : cbValueMax)); if (retval < 0) { odbc_lo_close(conn, stmt->lobj_fd); /* commit transaction if needed */ if (!ci->drivers.use_declarefetch && CC_does_autocommit(conn)) { if (!CC_commit(conn)) { SC_set_error(stmt, STMT_EXEC_ERROR, "Could not commit (in-line) a transaction", func); return COPY_GENERAL_ERROR; } } stmt->lobj_fd = -1; SC_set_error(stmt, STMT_EXEC_ERROR, "Error reading from large object.", func); return COPY_GENERAL_ERROR; } if (factor > 1) pg_bin2hex((char *) rgbValue, (char *) rgbValue, retval); if (retval < left64) result = COPY_RESULT_TRUNCATED; else result = COPY_OK; if (pcbValue) { Int8 leftbytes = left64 * factor; *pcbValue = left64 < 0 ? SQL_NO_TOTAL : (leftbytes == (SQLLEN) leftbytes ? leftbytes : /* exceeds SQLLEN limit */ SQL_NO_TOTAL); } if (gdata_blob && gdata_blob->data_left64 > 0) gdata_blob->data_left64 -= retval; if (!gdata_blob || gdata_blob->data_left64 == 0) { odbc_lo_close(conn, stmt->lobj_fd); /* commit transaction if needed */ if (!ci->drivers.use_declarefetch && CC_does_autocommit(conn)) { if (!CC_commit(conn)) { SC_set_error(stmt, STMT_EXEC_ERROR, "Could not commit (in-line) a transaction", func); return COPY_GENERAL_ERROR; } } stmt->lobj_fd = -1; /* prevent further reading */ } return result; }