Stop specifying parameter datatype oids in parse message. Unfortunately it is inflexi...
authorHiroshi Inoue <h-inoue@dream.email.ne.jp>
Sat, 4 Mar 2017 12:45:47 +0000 (21:45 +0900)
committerHiroshi Inoue <h-inoue@dream.email.ne.jp>
Wed, 8 Mar 2017 10:49:12 +0000 (19:49 +0900)
It's not necessary to cast any more the 2nd parameter of {call a_b_c_d_e(?, ?, ?, ?, ?)} in odbc-escapes-test.

bind.h
convert.c
pgtypes.c
pgtypes.h
statement.c
test/expected/odbc-escapes.out
test/src/odbc-escapes-test.c

diff --git a/bind.h b/bind.h
index 09088035d01acc5edd0b80332b4a2fa38463f232..00b60704308155b79aee32002dcd6bc80eb1b28b 100644 (file)
--- a/bind.h
+++ b/bind.h
@@ -97,6 +97,10 @@ typedef struct
    PutDataClass    *pdata;
 }  PutDataInfo;
 
+#define    PARSE_PARAM_CAST    FALSE
+#define    EXEC_PARAM_CAST     TRUE
+#define    SIMPLE_PARAM_CAST   TRUE
+
 #define CALC_BOOKMARK_ADDR(book, offset, bind_size, index) \
    (book->buffer + offset + \
    (bind_size > 0 ? bind_size : (SQL_C_VARBOOKMARK == book->returntype ? book->buflen : sizeof(UInt4))) * index)
@@ -124,7 +128,7 @@ void    PDATA_free_params(PutDataInfo *pdata, char option);
 void   SC_param_next(const StatementClass*, int *param_number, ParameterInfoClass **, ParameterImplClass **);
 
 RETCODE       prepareParameters(StatementClass *stmt, BOOL fake_params);
-RETCODE       prepareParametersNoDesc(StatementClass *stmt, BOOL fake_params);
+RETCODE       prepareParametersNoDesc(StatementClass *stmt, BOOL fake_params, BOOL param_cast);
 int    decideHowToPrepare(StatementClass *stmt, BOOL force);
 
 #endif
index c2ff663b20a7ba878ae7a464a52b3ad330aed4f4..f118e30d851e28e2b61ccc431046963d720aad1e 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -2066,6 +2066,7 @@ typedef enum
 #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;
@@ -2667,7 +2668,7 @@ buildProcessedStmt(const char *srvquery, ssize_t endp, int num_params)
  * query, in UseServerSidePrepare=0 mode.
  */
 RETCODE
-prepareParametersNoDesc(StatementClass *stmt, BOOL fake_params)
+prepareParametersNoDesc(StatementClass *stmt, BOOL fake_params, BOOL param_cast)
 {
    CSTR        func = "process_statements";
    RETCODE     retval;
@@ -2689,6 +2690,8 @@ inolog("prepareParametersNoDesc\n");
    if (QB_initialize(qb, qp->stmt_len, stmt,
                      fake_params ? RPM_FAKE_PARAMS : RPM_BUILDING_PREPARE_STATEMENT) < 0)
        return SQL_ERROR;
+   if (param_cast)
+       qb->flags |= FLGB_PARAM_CAST;
 
    for (qp->opos = 0; qp->opos < qp->stmt_len; qp->opos++)
    {
@@ -2833,7 +2836,7 @@ RETCODE   prepareParameters(StatementClass *stmt, BOOL fake_params)
 
 inolog("prepareParameters\n");
 
-   if (prepareParametersNoDesc(stmt, fake_params) == SQL_ERROR)
+   if (prepareParametersNoDesc(stmt, fake_params, PARSE_PARAM_CAST) == SQL_ERROR)
        return SQL_ERROR;
    return desc_params_and_sync(stmt);
 }
@@ -2974,6 +2977,8 @@ inolog("type=%d concur=%d\n", stmt->options.cursor_type, stmt->options.scroll_co
        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 */
@@ -3984,7 +3989,21 @@ parse_to_numeric_struct(const char *wv, SQL_NUMERIC_STRUCT *ns, BOOL *overflow)
    }
 }
 
+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;
+}
 /*
  * Resolve one parameter.
  *
@@ -4128,7 +4147,13 @@ inolog("ipara=%p paramType=%d %d proc_return=%d\n", ipara, ipara ? ipara->paramT
        char    pnum[16];
 
        qb->dollar_number++;
-       sprintf(pnum, "$%d", qb->dollar_number);
+       if (ipara &&
+           SQL_PARAM_OUTPUT != ipara->paramType &&
+           (qb->flags & FLGB_PARAM_CAST) != 0 &&
+           !parameter_is_with_cast(qp))
+           sprintf(pnum, "$%d%s", qb->dollar_number, sqltype_to_pgcast(conn, ipara->SQLType));
+       else
+           sprintf(pnum, "$%d", qb->dollar_number);
        CVT_APPEND_STR(qb, pnum);
        return SQL_SUCCESS;
    }
@@ -4882,7 +4907,7 @@ mylog("cvt_null_date_string=%d pgtype=%d buf=%p\n", conn->connInfo.cvt_null_date
 
        if (add_parens)
            CVT_APPEND_CHAR(qb, ')');
-       if (lastadd)
+       if (lastadd && (FLGB_PARAM_CAST & qb->flags) != 0)
            CVT_APPEND_STR(qb, lastadd);
    }
 
index f2756c726a3b33bf39a9291ca0f644fe38d1d210..a58925cf9a8d186219a979b4b463fa40fa305eb2 100644 (file)
--- a/pgtypes.c
+++ b/pgtypes.c
@@ -1236,86 +1236,72 @@ pgtype_attr_transfer_octet_length(const ConnectionClass *conn, OID type, int att
 
 
 /*
- * This is used when binding a query parameter, to decide which PostgreSQL
+ * This was used when binding a query parameter, to decide which PostgreSQL
  * datatype to send to the server, depending on the SQL datatype that was
  * used in the SQLBindParameter call.
  *
- * For most types, this returns 0, which means that the server should treat
- * the parameter the same as an untyped literal string, and deduce the type
- * based on the context.
- *
- * This is different from sqltype_to_pgtype(), which doesn't return 0 but
- * tries to give a closer match to the actual datatype.
+ * However it is inflexible and rather harmful.
+ * Now this function always returns 0.
+ * We use cast instead to keep regression test results
+ * in order to keep regression test results.
  */
 OID
 sqltype_to_bind_pgtype(const ConnectionClass *conn, SQLSMALLINT fSqlType)
 {
-   OID     pgType;
+   OID     pgType = 0;
+
+   return pgType;
+}
+
+/*
+ * Casting parameters e.g. ?::timestamp is much more flexible
+ * than specifying parameter datatype oids determined by
+ * sqltype_to_bind_pgtype() via parse message.
+ */
+const char *
+sqltype_to_pgcast(const ConnectionClass *conn, SQLSMALLINT fSqlType)
+{
+   const char *pgCast = NULL_STRING;
 
    switch (fSqlType)
    {
        case SQL_BINARY:
        case SQL_VARBINARY:
-           pgType = PG_TYPE_BYTEA;
+           pgCast = "::bytea";
            break;
-
-       case SQL_LONGVARBINARY:
-           /*
-            * SQL_LONGVARBINARY could mean a large object, but it could also
-            * mean bytea, in particular with ByteaAsLongVarBinary=1. With
-            * large binary parameters, there isn't much danger of confusing
-            * the datatype in practice - you typically just INSERT them
-            * into a table or UPDATE them, and don't apply any operators to
-            * them. So instead of trying to guess whether this is a 'bytea'
-            * or large object, let the server decide.
-            */
-           pgType = 0;
-           break;
-
        case SQL_TYPE_DATE:
        case SQL_DATE:
-           pgType = PG_TYPE_DATE;
+           pgCast = "::date";
            break;
-
        case SQL_DECIMAL:
        case SQL_NUMERIC:
-           pgType = PG_TYPE_NUMERIC;
+           pgCast = "::numeric";
            break;
-
        case SQL_BIGINT:
-           pgType = PG_TYPE_INT8;
+           pgCast = "::int8";
            break;
-
        case SQL_INTEGER:
-           pgType = PG_TYPE_INT4;
+           pgCast = "::int4";
            break;
-
        case SQL_REAL:
-           pgType = PG_TYPE_FLOAT4;
+           pgCast = "::float4";
            break;
-
        case SQL_SMALLINT:
        case SQL_TINYINT:
-           pgType = PG_TYPE_INT2;
+           pgCast = "::int2";
            break;
-
        case SQL_TIME:
        case SQL_TYPE_TIME:
-           pgType = PG_TYPE_TIME;
+           pgCast = "::time";
            break;
-
        case SQL_TIMESTAMP:
        case SQL_TYPE_TIMESTAMP:
-           pgType = PG_TYPE_DATETIME;
+           pgCast = "::timestamp";
            break;
-
        case SQL_GUID:
            if (PG_VERSION_GE(conn, 8.3))
-               pgType = PG_TYPE_UUID;
-           else
-               pgType = 0;
+               pgCast = "::uuid";
            break;
-
        case SQL_INTERVAL_MONTH:
        case SQL_INTERVAL_YEAR:
        case SQL_INTERVAL_YEAR_TO_MONTH:
@@ -1329,28 +1315,11 @@ sqltype_to_bind_pgtype(const ConnectionClass *conn, SQLSMALLINT fSqlType)
        case SQL_INTERVAL_HOUR_TO_MINUTE:
        case SQL_INTERVAL_HOUR_TO_SECOND:
        case SQL_INTERVAL_MINUTE_TO_SECOND:
-           pgType = PG_TYPE_INTERVAL;
-           break;
-
-       case SQL_CHAR:
-#ifdef UNICODE_SUPPORT
-       case SQL_WCHAR:
-#endif /* UNICODE_SUPPORT */
-       case SQL_LONGVARCHAR:
-#ifdef UNICODE_SUPPORT
-       case SQL_WLONGVARCHAR:
-#endif /* UNICODE_SUPPORT */
-       case SQL_VARCHAR:
-#if    UNICODE_SUPPORT
-       case SQL_WVARCHAR:
-#endif /* UNICODE_SUPPORT */
-       default:
-           /* The default is to let the server choose */
-           pgType = 0;
+           pgCast = "::interval";
            break;
    }
 
-   return pgType;
+   return pgCast;
 }
 
 OID
index 34931d49d13a178dddd7bf4309a9dbad5c8ae645..e107c4a70f9fc0bb0dc49e708f040bbfcb27f58b 100644 (file)
--- a/pgtypes.h
+++ b/pgtypes.h
@@ -84,6 +84,7 @@ extern SQLSMALLINT sqlTypes[];
 OID        pg_true_type(const ConnectionClass *, OID, OID);
 OID        sqltype_to_pgtype(const ConnectionClass *conn, SQLSMALLINT fSqlType);
 OID        sqltype_to_bind_pgtype(const ConnectionClass *conn, SQLSMALLINT fSqlType);
+const char *sqltype_to_pgcast(const ConnectionClass *conn, SQLSMALLINT fSqlType);
 
 SQLSMALLINT    pgtype_to_concise_type(const StatementClass *stmt, OID type, int col);
 SQLSMALLINT    pgtype_to_sqldesctype(const StatementClass *stmt, OID type, int col);
index 69fd7671e61c0912a004044733fefece8ddecf72..55018eccfae6ff78e87fdec6e99c1f89be2b7707 100644 (file)
@@ -1,4 +1,4 @@
-/*-------
+/*------
  * Module:         statement.c
  *
  * Description:        This module contains functions related to creating
@@ -2435,11 +2435,12 @@ libpq_bind_and_exec(StatementClass *stmt)
 
        if (!stmt->processed_statements)
        {
-           if (prepareParametersNoDesc(stmt, FALSE) == SQL_ERROR)
+           if (prepareParametersNoDesc(stmt, FALSE, EXEC_PARAM_CAST) == SQL_ERROR)
                goto cleanup;
        }
 
        pstmt = stmt->processed_statements;
+       mylog("%s execParams query=%s nParams=%d\n", __FUNCTION__, pstmt->query, nParams);
        pgres = PQexecParams(conn->pqconn,
                             pstmt->query,
                             nParams,
@@ -2460,9 +2461,10 @@ libpq_bind_and_exec(StatementClass *stmt)
        }
 
        /* prepareParameters() set plan name, so don't fetch this earlier */
-       plan_name = stmt->plan_name ? stmt->plan_name : "";
+       plan_name = stmt->plan_name ? stmt->plan_name : NULL_STRING;
 
        /* already prepared */
+       mylog("%s execPrepared plan=%s nParams=%d\n", __FUNCTION__, plan_name, nParams);
        pgres = PQexecPrepared(conn->pqconn,
                               plan_name,   /* portal name == plan name */
                               nParams,
index 0f8a4db125e6dfdf503c9127854457c2394cd239..9f8116516d8994f1e0616892592bdc988b3f6b93 100644 (file)
@@ -55,7 +55,7 @@ Query: SELECT {ts '2014-12-21 20:30:40' } + '1 day 1 hour 1 minute 1 second'::in
 Result set:
 2014-12-22 21:31:41
 
-Query: {call a_b_c_d_e(?, ?::timestamp, ?, ?, ?)}
+Query: {call a_b_c_d_e(?, ?, ?, ?, ?)}
 Param 1 is an OUT parameter
 Param 2: 2017-02-23 11:34:46
 Param 3 is an I-O parameter
@@ -120,7 +120,7 @@ Query: SELECT {ts '2014-12-21 20:30:40' } + '1 day 1 hour 1 minute 1 second'::in
 Result set:
 2014-12-22 21:31:41
 
-Query: {call a_b_c_d_e(?, ?::timestamp, ?, ?, ?)}
+Query: {call a_b_c_d_e(?, ?, ?, ?, ?)}
 Param 1 is an OUT parameter
 Param 2: 2017-02-23 11:34:46
 Param 3 is an I-O parameter
index 9409916ded7d3cd8d444b8b1b0ee31ebb14fcc7d..affc1d83b3c7c93953bc3b57c012f51c5ae97689 100644 (file)
@@ -145,7 +145,7 @@ static void escape_test(HSTMT hstmt)
    executeQuery(hstmt);
 
    /**** call procedure with out and i-o parameters ****/
-   prepareQuery(hstmt, "{call a_b_c_d_e(?, ?::timestamp, ?, ?, ?)}");
+   prepareQuery(hstmt, "{call a_b_c_d_e(?, ?, ?, ?, ?)}");
    memset(outbuf1, 0, sizeof(outbuf1));
    bindOutParamString(hstmt, 1, outbuf1, sizeof(outbuf1) - 1, 0);
    bindParamString(hstmt, 2, "2017-02-23 11:34:46");