代码拉取完成,页面将自动刷新
/*-------
* 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 <float.h>
#define HAVE_LOCALE_H
#endif /* WIN32 */
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "multibyte.h"
#include <time.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include <math.h>
#include <stdlib.h>
#include <limits.h>
#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", "textcat($*)" },
/* { "DIFFERENCE", "difference" }, how to ? */
{"INSERT", "substring($1 from 1 for $2 - 1) || $4 || substring($1 from $2 + $3)" },
{"LCASE", "lower($*)" },
{"LEFT", "ltrunc($*)" },
{"%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", "rtrunc($*)" },
{"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", "pow($*)" },
/* { "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)" },
/* { "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);
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, bool binary_rawout, 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);
/*
* field_is_bytea
* 检查oid field_type在数据库中是否为类似bytea的二进制数据类型
* 返回0 表示不是二进制数据类型
* 返回1 表示是二进制数据类型,并且byteaout进行输出
* 有两种字符串输出方式 (由数据库的bytea_output选项控制,分别为hex和escape)
* a. 将数据的每个字节以十六进制输出,在最前面加上 \x (\x010203)
* b. 将数据的每个字节以八进制输出, 每个字节前加上 \ (\001\002\003),但如果是可打印字符,那么还是按字符打出
* 返回2 表示是二进制数据类型,并且rawout进行输出
* 将数据的每个字节以十六进制输出即可
*
*/
#define FIELD_BINARY_BYTEAOUT 1
#define FIELD_BINARY_RAWOUT 2
static int
filed_is_bytea(const OID field_type)
{
int res = 0;
switch (field_type) {
case PG_TYPE_BYTEA:
case PG_TYPE_MYSQL_BINARY:
res = FIELD_BINARY_BYTEAOUT;
break;
case PG_TYPE_RAW:
case PG_TYPE_LONG_RAW:
case PG_TYPE_BLOB:
res = FIELD_BINARY_RAWOUT;
break;
default:
res = 0;
}
return res;
}
/*
* 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;
bool binary_rawout = false;
#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 (FIELD_BINARY_BYTEAOUT == filed_is_bytea(field_type))
{
//BYTEA类型数据从pg内核发送过来时,会根据参数添加\x表示十六进制
//或者直接收到其二进制裸数据的八进制字符串(\001 \002 \003)
//如果以SQL_C_BINARY接收,则要将字符串转换成二进制字节流
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;
}
else if (FIELD_BINARY_RAWOUT == filed_is_bytea(field_type))
{
//对于raw调用的rawout,将收到裸数据的十六进制字符串,且前面没有\x
if (SQL_C_BINARY == fCType)
{
bytea_process_kind = BYTEA_PROCESS_BINARY;
binary_rawout = true;
}
}
#ifdef UNICODE_SUPPORT
if (0 == bytea_process_kind)
{
if (get_convtype() > 0) /* coversion 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_NVARCHAR2:
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, binary_rawout, 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, binary_rawout, 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, binary_rawout, 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, binary_rawout, 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) ? "<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 SQL_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:
case PG_TYPE_SMALLDATETIME:
case PG_TYPE_ORADATE:
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 */
/* CodeDEX with CID=11902 */
if (NULL == tim)
{
MYLOG(MIN_LOG_LEVEL, "%s: failed to get localtime.", func);
SC_set_error(stmt, STMT_STATUS_ERROR, "Failed to get local time", func);
return SQL_ERROR;
}
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_NVARCHAR2:
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;
default:
if (filed_is_bytea(field_type)) {
text_bin_handling = TRUE;
}
}
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);
#pragma GCC diagnostic push
len = SPRINTF_FIXED(midtemp, "%s.%0*d", midtemp, wdt, fr);
#pragma GCC diagnostic pop
}
break;
case PG_TYPE_ABSTIME:
case PG_TYPE_DATETIME:
case PG_TYPE_TIMESTAMP_NO_TMZONE:
case PG_TYPE_TIMESTAMP:
case PG_TYPE_ORADATE:
len = stime2timestamp(&std_time, midtemp, midsize, FALSE,
(int) (midsize - 19 - 2) );
break;
case PG_TYPE_SMALLDATETIME:
len = 16;
SPRINTF_FIXED(midtemp, "%.4d-%.2d-%.2d %.2d:%.2d", std_time.y, std_time.m, std_time.d,std_time.hh, std_time.mm);
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 follwoing 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);
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=%d concur=%d\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, npos - 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;
int ret = SQL_SUCCESS;
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;
BOOL found = FALSE;
memset(&ti, 0, sizeof(ti));
NAME_TO_NAME(ti.schema_name, conn->schemaIns);
NAME_TO_NAME(ti.table_name, conn->tableIns);
found = getCOLIfromTI(func, conn, qb->stmt, 0, &pti);
if(!found && conn->status== CONN_DOWN)
ret = SQL_ERROR;
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)");
}
}
}
}
if (!converted)
CVT_APPEND_STR(qb, "NULL");
qp->opos += 10;
return ret;
}
/*
* 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;
}
/*
* Build an array of parameters to pass to libpq's PQexecPreparedBatch
* function.
*/
BOOL
build_libpq_bind_params_batch(StatementClass *stmt,
int *nParams,
int *nBatchCount, /* [output] It's only for batch protocol. */
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;
int total_rows;
ConnectionClass *conn = SC_get_conn(stmt);
BOOL ret = FALSE, discard_output;
RETCODE retval;
const IPDFields *ipdopts = SC_get_IPDF(stmt);
const APDFields *apdopts = SC_get_APDF(stmt);
*paramTypes = NULL;
*paramValues = NULL;
*paramLengths = NULL;
*paramFormats = NULL;
*nBatchCount = 0;
total_rows = apdopts->paramset_size;
num_params = stmt->num_params;
if (num_params < 0)
{
PGAPI_NumParams(stmt, &num_p);
num_params = num_p;
}
if (ipdopts->allocated < num_params)
{
SC_set_error(stmt, STMT_COUNT_FIELD_INCORRECT, "The # of binded parameters < the # of parameter markers", func);
return FALSE;
}
if (QB_initialize(&qb, MIN_ALC_SIZE, stmt, RPM_BUILDING_BIND_REQUEST) < 0)
return FALSE;
*paramTypes = malloc(sizeof(OID) * num_params);
if (*paramTypes == NULL)
goto cleanup;
*paramValues = malloc(sizeof(char *) * num_params * total_rows);
if (*paramValues == NULL)
goto cleanup;
memset(*paramValues, 0, sizeof(char *) * num_params * total_rows);
*paramLengths = malloc(sizeof(int) * num_params * total_rows);
if (*paramLengths == NULL)
goto cleanup;
*paramFormats = malloc(sizeof(int) * num_params * total_rows);
if (*paramFormats == NULL)
goto cleanup;
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);
stmt->exec_start_row = 0;
stmt->exec_end_row = total_rows;
if (num_p > 0)
{
ParameterImplClass *parameters = ipdopts->parameters;
int row = 0;
stmt->exec_current_row = 0;
do
{
BOOL paramproceed = FALSE;
/*
* Now build the parameter values.
*/
*nParams = 0;
stmt->exec_current_row = row;
QB_Destructor(&qb);
if (QB_initialize(&qb, MIN_ALC_SIZE, stmt, RPM_BUILDING_BIND_REQUEST) < 0)
return FALSE;
discard_output = (0 != (qb.flags & FLGB_DISCARD_OUTPUT));
qb.flags |= FLGB_BINARY_AS_POSSIBLE;
for (i = 0; i < stmt->num_params; i++)
{
BOOL isnull = FALSE;
BOOL isbinary = FALSE;
char *val_copy = NULL;
OID pgType = 0;
MYLOG(DETAIL_LOG_LEVEL, "%dth parameter type oid is %u\n", i, PIC_dsp_pgtype(conn, parameters[i]));
if (discard_output && SQL_PARAM_OUTPUT == parameters[i].paramType)
continue;
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;
}
/* Batch execution, ignore the parameters user set to ignored.
* It's different from normal execution, in which case, the ignored
* parameters are treated by the caller.
*/
if (apdopts->param_operation_ptr &&
apdopts->param_operation_ptr[row] == SQL_PARAM_IGNORE)
{
(*nParams)++;
continue;
}
else
{
paramproceed = TRUE;
}
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)[i] = pgType;
(*paramValues)[(*nBatchCount) * stmt->num_params + i] = val_copy;
if (qb.npos > INT_MAX)
goto cleanup;
(*paramLengths)[(*nBatchCount) * stmt->num_params + i] = (int) qb.npos;
}
else
{
(*paramTypes)[i] = pgType;
(*paramValues)[(*nBatchCount) * stmt->num_params + i] = NULL;
(*paramLengths)[(*nBatchCount) * stmt->num_params + i] = 0;
}
if (isbinary)
mylog("%dth parameter is of binary format\n", *nParams);
(*paramFormats)[i] = isbinary ? 1 : 0;
(*nParams)++;
}
if (paramproceed)
{
*nBatchCount += 1;
}
row ++;
}while(row < total_rows);
}
else
*nParams = 0;
/* 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 occured", __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 paramType=%d %d proc_return=%d\n", ipara, 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;
}
}
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;
}
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) /* coversion 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 (filed_is_bytea(param_pgtype))
{
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)
{
int i;
for (i = 0; mapFuncs[i].odbc_name; i++)
{
if (mapFuncs[i].odbc_name[0] == '%')
{
if (mapFuncs[i].odbc_name[1] - '0' == param_count &&
!stricmp(&mapFuncs[i].odbc_name[2], func))
return mapFuncs[i].pgsql_name;
}
else if (!stricmp(mapFuncs[i].odbc_name, func))
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
mapExpr = mapFunction(key, param_count);
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;
CVT_APPEND_CHAR(qb, ')');
from = param_pos[1][0];
to = param_pos[1][1];
if (to < from + 9)
{
char num[10];
memcpy(num, nqb.query_statement + from, to - from + 1);
num[to - from + 1] = '\0';
MYLOG(0, FORMAT_LEN "-" FORMAT_LEN " num=%s SQL_BIT=%d\n", to, from, num, SQL_BIT);
switch (atoi(num))
{
case SQL_BIT:
cast_form = "boolean";
break;
case SQL_INTEGER:
cast_form = "int4";
break;
}
}
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->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, bool binary_rawout, 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 (binary_rawout)
{
//rawout, 十六进制,但最前面没有\x
if (i < ilen)
{
ilen -= i;
if (rgbValue)
pg_hex2bin(value + i, rgbValue + o, ilen);
o += ilen / 2;
}
break;
}
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;
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。