/*
  Copyright 2000-2008 MySQL AB

  The MySQL Connector/ODBC is licensed under the terms of the
  GPL, like most MySQL Connectors. There are special exceptions
  to the terms and conditions of the GPL as it is applied to
  this software, see the FLOSS License Exception available on
  mysql.com.

  This program is free software; you can redistribute it and/or modify
  it under the terms of version 2 of the GNU General Public License as
  published by the Free Software Foundation.

  There are special exceptions to the terms and conditions of the GPL
  as it is applied to this software. View the full text of the exception
  in file LICENSE.exceptions in the top-level directory of this software
  distribution.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/**
  @file  prepare.c
  @brief Prepared statement functions.
*/

/***************************************************************************
 * The following ODBC APIs are implemented in this file:		   *
 *									   *
 *   SQLPrepare		 (ISO 92)					   *
 *   SQLBindParameter	 (ODBC)						   *
 *   SQLDescribeParam	 (ODBC)						   *
 *   SQLParamOptions	 (ODBC, Deprecated)				   *
 *   SQLNumParams	 (ISO 92)					   *
 *   SQLSetScrollOptions (ODBC, Deprecated)				   *
 *									   *
 ****************************************************************************/

#include "driver.h"
#ifndef _UNIX_
# include <dos.h>
#endif /* !_UNIX_ */
#include <my_list.h>
#include <m_ctype.h>

/**
  Prepare a statement for later execution.

  @param[in] hStmt     Handle of the statement
  @param[in] szSqlStr  The statement to prepare
  @param[in] cbSqlStr  The length of the statement (or @c SQL_NTS if it is
                       NUL-terminated)
*/
SQLRETURN SQL_API SQLPrepare(SQLHSTMT hStmt, SQLCHAR *szSqlStr,
                             SQLINTEGER cbSqlStr)
{
  STMT *stmt= (STMT *)hStmt;
  /*
    We free orig_query here, instead of my_SQLPrepare, because
    my_SQLPrepare is used by my_pos_update() when a statement requires
    additional parameters.
  */
  if (stmt->orig_query)
  {
    x_free(stmt->orig_query);
  }

  return my_SQLPrepare(hStmt, szSqlStr, cbSqlStr);
}


/*
  @type    : myodbc3 internal
  @purpose : prepares an SQL string for execution
*/

SQLRETURN my_SQLPrepare( SQLHSTMT hstmt, SQLCHAR FAR *szSqlStr, SQLINTEGER cbSqlStr )
{
    STMT FAR *stmt= (STMT FAR*) hstmt;
    char in_string,*pos;
    uint param_count;
#ifdef USE_MB
    char *end;
#endif
    CHARSET_INFO *charset_info= stmt->dbc->mysql.charset;
    int bPerhapsEmbraced = 1;
    int bEmbraced = 0;
    char *pcLastCloseBrace = 0;

#ifdef USE_MB
    LINT_INIT(end);
#endif

    CLEAR_STMT_ERROR(stmt);
    x_free(stmt->query);

#ifdef NOT_NEEDED  
    SQLFreeStmt(hstmt,SQL_UNBIND);    /* Not needed according to VB 5.0 */
#endif

    if (!(stmt->query= dupp_str((char*) szSqlStr, cbSqlStr)))
        return set_error(stmt, MYERR_S1001, NULL, 4001);

    /* Count number of parameters and save position for each parameter */
    in_string= 0;
    param_count= 0;

#ifdef USE_MB
    if (use_mb(charset_info))
        end= strend(stmt->query);
#endif

    for (pos= stmt->query; *pos ; pos++)
    {
#ifdef USE_MB
        if (use_mb(charset_info))
        {
            int l;
            if ((l= my_ismbchar(charset_info, pos , end)))
            {
                pos+= l-1;
                continue;
            }
        }
#endif

        /* handle case where we have statement within {} - in this case we want to replace'em with ' ' */
        if ( bPerhapsEmbraced )
        {
            if ( *pos == '{' )
            {
                bPerhapsEmbraced = 0;
                bEmbraced = 1;
                *pos =  ' ';
                pos++;
                continue;
            }
            else if ( !isspace( *pos ) )
                bPerhapsEmbraced = 0;
        }
        else if ( bEmbraced && *pos == '}' )
            pcLastCloseBrace = pos;

        /* escape char? */
        if (*pos == '\\' && pos[1])     /* Next char is escaped */
        {
            pos++;
            continue;
        }

        /* in a string? */
        if (*pos == in_string)
        {
            if (pos[1] == in_string)      /* Two quotes is ok */
                pos++;
            else
                in_string= 0;
            continue;
        }

        /* parameter marker? */
        if (!in_string)
        {
            if (*pos == '\'' || *pos == '"' || *pos == '`') /* start of string? */
            {
                in_string= *pos;
                continue;
            }
            if (*pos == '?')
            {
                PARAM_BIND *param;

                if (param_count >= stmt->params.elements)
                {
                    PARAM_BIND tmp_param;
                    bzero(&tmp_param,sizeof(tmp_param));
                    if (push_dynamic(&stmt->params,(DYNAMIC_ELEMENT)&tmp_param))
                    {
                        return set_error(stmt, MYERR_S1001, NULL, 4001);
                    }
                }
                param= dynamic_element(&stmt->params,param_count,PARAM_BIND*);
                param->pos_in_query= pos;
                param_count++;
            }
        }
    }

    /* remove closing brace if we have one */
    if ( pcLastCloseBrace )
        *pcLastCloseBrace = ' ';

    stmt->param_count= param_count;
    /* Reset current_param so that SQLParamData starts fresh. */
    stmt->current_param= 0;
    stmt->query_end= pos;
    stmt->state= ST_PREPARED;
    return SQL_SUCCESS;
}


/*
  @type    : myodbc3 internal
  @purpose : binds a buffer to a parameter marker in an SQL statement.
*/

SQLRETURN SQL_API my_SQLBindParameter( SQLHSTMT     hstmt,
                                       SQLUSMALLINT ipar,
                                       SQLSMALLINT  fParamType __attribute__((unused)),
                                       SQLSMALLINT  fCType,
                                       SQLSMALLINT  fSqlType,
                                       SQLULEN      cbColDef __attribute__((unused)),
                                       SQLSMALLINT  ibScale __attribute__((unused)),
                                       SQLPOINTER   rgbValue,
                                       SQLLEN       cbValueMax,
                                       SQLLEN *     pcbValue )
{
    STMT FAR *stmt= (STMT FAR*) hstmt;
    PARAM_BIND param;

    CLEAR_STMT_ERROR(stmt);

    if (ipar-- < 1)
    {
        set_error(stmt,MYERR_S1093,NULL,0);
        return SQL_ERROR;
    }
    if (fCType == SQL_C_NUMERIC) /* We don't support this now */
    {
        set_error(stmt,MYERR_07006,
                  "Restricted data type attribute violation(SQL_C_NUMERIC)",0);
        return SQL_ERROR;
    }
    if (stmt->params.elements > ipar)
    {
        /* Change old bind parameter */
        PARAM_BIND *old= dynamic_element(&stmt->params,ipar,PARAM_BIND*);
        if (old->alloced)
        {
            old->alloced= 0;
            x_free(old->value);
        }
        memcpy(&param, old, sizeof(param));
    }
    else
        bzero(&param, sizeof(param));
    /* Simply record the values. These are used later (SQLExecute) */
    param.used= 1;
    param.SqlType= fSqlType;
    param.CType= (fCType == SQL_C_DEFAULT ? default_c_type(fSqlType) : fCType);
    param.buffer= rgbValue;
    param.ValueMax= cbValueMax;
    param.actual_len= pcbValue;
    param.real_param_done= TRUE;

    if (set_dynamic(&stmt->params, (DYNAMIC_ELEMENT)&param, ipar))
    {
        set_error(stmt,MYERR_S1001,NULL,4001);
        return SQL_ERROR;
    }
    return SQL_SUCCESS;
}


/**
  Deprecated function, for more details see SQLBindParamater. 

  @param[in] stmt           Handle to statement
  @param[in] ipar           Parameter number
  @param[in] fCType         Value type
  @param[in] fSqlType       Parameter type
  @param[in] cbColDef       Column size
  @param[in] ibScale        Decimal digits
  @param[in] rgbValue       Parameter value pointer
  @param[in] pcbValue       String length or index pointer

  @return SQL_SUCCESS or SQL_ERROR (and diag is set)

*/

SQLRETURN SQL_API SQLSetParam(SQLHSTMT        hstmt,
                              SQLUSMALLINT    ipar, 
                              SQLSMALLINT     fCType, 
                              SQLSMALLINT     fSqlType,
                              SQLULEN         cbColDef, 
                              SQLSMALLINT     ibScale,
                              SQLPOINTER      rgbValue, 
                              SQLLEN *        pcbValue)
{
  return my_SQLBindParameter(hstmt, ipar, SQL_PARAM_INPUT_OUTPUT, fCType, 
                             fSqlType, cbColDef, ibScale, rgbValue, 
                             SQL_SETPARAM_VALUE_MAX, pcbValue);
}


/*
  @type    : ODBC 2.0 API
  @purpose : binds a buffer to a parameter marker in an SQL statement.
*/

SQLRETURN SQL_API SQLBindParameter( SQLHSTMT        hstmt,
                                    SQLUSMALLINT    ipar, 
                                    SQLSMALLINT     fParamType,
                                    SQLSMALLINT     fCType, 
                                    SQLSMALLINT     fSqlType,
                                    SQLULEN         cbColDef, 
                                    SQLSMALLINT     ibScale,
                                    SQLPOINTER      rgbValue, 
                                    SQLLEN          cbValueMax,
                                    SQLLEN *        pcbValue )
{
  return my_SQLBindParameter(hstmt, ipar, fParamType, fCType, fSqlType,
                             cbColDef, ibScale, rgbValue, cbValueMax, pcbValue);
}


/*
  @type    : ODBC 1.0 API
  @purpose : returns the description of a parameter marker associated
  with a prepared SQL statement
*/

SQLRETURN SQL_API SQLDescribeParam( SQLHSTMT        hstmt,
                                    SQLUSMALLINT    ipar __attribute__((unused)),
                                    SQLSMALLINT FAR *pfSqlType,
                                    SQLULEN *       pcbColDef,
                                    SQLSMALLINT FAR *pibScale __attribute__((unused)),
                                    SQLSMALLINT FAR *pfNullable )
{
    STMT FAR *stmt= (STMT FAR*) hstmt;

    if (pfSqlType)
        *pfSqlType= SQL_VARCHAR;
    if (pcbColDef)
        *pcbColDef= (stmt->dbc->flag & FLAG_BIG_PACKETS ? 24*1024*1024L : 255);
    if (pfNullable)
        *pfNullable= SQL_NULLABLE_UNKNOWN;

    return SQL_SUCCESS;
}


/*
  @type    : ODBC 1.0 API
  @purpose : sets multiple values (arrays) for the set of parameter markers
*/

#ifdef USE_SQLPARAMOPTIONS_SQLULEN_PTR
SQLRETURN SQL_API SQLParamOptions( SQLHSTMT     hstmt, 
                                   SQLULEN      crow,
                                   SQLULEN      *pirow __attribute__((unused)) )
#else
SQLRETURN SQL_API SQLParamOptions( SQLHSTMT     hstmt, 
                                   SQLUINTEGER  crow,
                                   SQLUINTEGER *pirow __attribute__((unused)) )
#endif
{
    if (crow != 1)
    {
        /*
          Currently return warning for batch processing request,
          but need to handle in the future..
        */
        return set_error(hstmt, MYERR_01S02,
                         "Option value changed to default parameter size", 0);
    }
    return SQL_SUCCESS;
}


/*
  @type    : ODBC 1.0 API
  @purpose : returns the number of parameter markers.
*/

SQLRETURN SQL_API SQLNumParams(SQLHSTMT hstmt, SQLSMALLINT *pcpar)
{
  STMT *stmt= (STMT *)hstmt;

  if (pcpar)
    *pcpar= stmt->param_count;

  return SQL_SUCCESS;
}


/*
  @type    : ODBC 1.0 API
  @purpose : sets options that control the behavior of cursors.
*/

SQLRETURN SQL_API SQLSetScrollOptions(  SQLHSTMT        hstmt,
                                        SQLUSMALLINT    fConcurrency __attribute__((unused)),
                                        SQLLEN          crowKeyset __attribute__((unused)),
                                        SQLUSMALLINT    crowRowset )
{
    STMT FAR *stmt= (STMT FAR*) hstmt;

    stmt->stmt_options.rows_in_set= crowRowset;

    return SQL_SUCCESS;
}
