@@ -3921,6 +3921,85 @@ SQLRETURN EsSQLColAttributeW(
39213921#undef PNUMATTR_ASSIGN
39223922}
39233923
3924+ /* very simple counter of non-quoted, not-escaped single question marks.
3925+ * no statement validation done */
3926+ static SQLRETURN count_param_markers (esodbc_stmt_st * stmt , SQLSMALLINT * p_cnt )
3927+ {
3928+ SQLSMALLINT cnt ;
3929+ wstr_st u16sql ;
3930+ SQLWCHAR * pos , * end , * sav ;
3931+ BOOL quoting , escaping ;
3932+ SQLWCHAR crr , qchar ; /* char that starting the quote (`'` or `"`) */
3933+
3934+ /* The SQL query is received originally as UTF-16 SQLWCHAR, but converted
3935+ * to UTF-8 MB right away and stored like that, for conveience. Easiest to
3936+ * parse it is in its original SQLWCHAR format, though and since that's
3937+ * only needed (assumably) rarely, we'll convert it back here, instead of
3938+ * storing also the original. */
3939+ if (& u16sql != utf8_to_wstr (& stmt -> u8sql , & u16sql )) {
3940+ ERRH (stmt , "failed to convert stmt `" LCPDL "` back to UTF-16 WC" ,
3941+ LCSTR (& stmt -> u8sql ));
3942+ RET_HDIAGS (stmt , SQL_STATE_HY000 );
3943+ }
3944+ pos = u16sql .str ;
3945+ end = pos + u16sql .cnt ;
3946+ cnt = 0 ;
3947+ quoting = FALSE;
3948+ escaping = FALSE;
3949+ crr = 0 ;
3950+ while (pos < end ) {
3951+ switch ((crr = * pos ++ )) {
3952+ case MK_WPTR (ESODBC_PARAM_MARKER ): /* ~ L'?' */
3953+ if (escaping || quoting ) {
3954+ break ;
3955+ }
3956+ /* skip groups `???` */
3957+ sav = pos - 1 ; /* position of first `?` */
3958+ while (pos < end && * pos == crr ) {
3959+ pos ++ ;
3960+ }
3961+ /* only count if single */
3962+ if (sav + 1 == pos ) {
3963+ cnt ++ ;
3964+ }
3965+ break ;
3966+ case L'"' :
3967+ case L'\'' :
3968+ if (escaping ) {
3969+ break ;
3970+ }
3971+ if (! quoting ) {
3972+ quoting = TRUE;
3973+ qchar = crr ;
3974+ } else if (qchar == crr ) {
3975+ quoting = FALSE;
3976+ } /* else: sequence is: `"..'` or `'.."` -> ignore */
3977+ break ;
3978+ case MK_WPTR (ESODBC_CHAR_ESCAPE ): /* ~ L'\\' */
3979+ if (escaping ) {
3980+ break ;
3981+ }
3982+ escaping = TRUE;
3983+ continue ;
3984+ }
3985+ escaping = FALSE;
3986+ }
3987+
3988+ if (escaping || quoting ) {
3989+ ERRH (stmt , "invalid SQL statement: [%zu] `" LWPDL "`." , u16sql .cnt ,
3990+ LWSTR (& u16sql ));
3991+ free (u16sql .str );
3992+ RET_HDIAG (stmt , SQL_STATE_HY000 , "failed to parse statement" , 0 );
3993+ }
3994+
3995+ DBGH (stmt , "counted %hd param marker(s) in SQL `" LWPDL "`." , cnt ,
3996+ LWSTR (& u16sql ));
3997+ free (u16sql .str );
3998+
3999+ * p_cnt = cnt ;
4000+ return SQL_SUCCESS ;
4001+ }
4002+
39244003/* function implementation is correct, but it can't really be used as
39254004 * intended, since the driver's "preparation" doesn't really involve sending
39264005 * it to ES or even parameter marker counting; the later would be doable now,
@@ -3938,8 +4017,16 @@ SQLRETURN EsSQLNumParams(
39384017 RET_HDIAGS (stmt , SQL_STATE_HY010 );
39394018 }
39404019
4020+ # if 0
4021+ /* The correct implementation, once a statement trully is prepared. */
39414022 return EsSQLGetDescFieldW (stmt -> ipd , NO_REC_NR ,
39424023 SQL_DESC_COUNT , ParameterCountPtr , SQL_IS_SMALLINT , NULL );
4024+ # else /* 0 */
4025+ /* Only count params on-demand */
4026+ /* some apps (like pyodbc) will verify the user-provided parameters number
4027+ * against the result of this function -> implement a crude parser. */
4028+ return count_param_markers (stmt , ParameterCountPtr );
4029+ # endif /* 0 */
39434030}
39444031
39454032SQLRETURN EsSQLRowCount (_In_ SQLHSTMT StatementHandle , _Out_ SQLLEN * RowCount )
0 commit comments