/* * tdbcTokenize.c -- * * Code for a Tcl command that will extract subsitutable parameters * from a SQL statement. * * Copyright (c) 2007 by D. Richard Hipp. * Copyright (c) 2010, 2011 by Kevin B. Kenny. * * Please refer to the file, 'license.terms' for the conditions on * redistribution of this file and for a DISCLAIMER OF ALL WARRANTIES. * * RCS: @(#) $Id$ * *----------------------------------------------------------------------------- */ #include "tdbcInt.h" #include /* *----------------------------------------------------------------------------- * * Tdbc_TokenizeSql -- * * Tokenizes a SQL statement. * * Results: * Returns a zero-reference Tcl object that gives the statement in * tokenized form, or NULL if an error occurs. * * Side effects: * If an error occurs, and 'interp' is not NULL, stores an error * message in the interpreter result. * * This is demonstration code for a TCL command that will extract * host parameters from an SQL statement. * * A "host parameter" is a variable within the SQL statement. * Different systems do host parameters in different ways. This * tokenizer recognizes three forms: * * $ident * :ident * @ident * * In other words, a host parameter is an identifier proceeded * by one of the '$', ':', or '@' characters. * * This function returns a Tcl_Obj representing a list. The * concatenation of the returned list will be equivalent to the * input string. Each element of the list will be either a * host parameter, a semicolon, or other text from the SQL * statement. * * Example: * * tokenize_sql {SELECT * FROM table1 WHERE :name='bob';} * * Resulting in: * * {SELECT * FROM table1 WHERE } {:name} {=} {'bob'} {;} * * The tokenizer knows about SQL comments and strings and will * not mistake a host parameter or semicolon embedded in a string * or comment as a real host parameter or semicolon. * *----------------------------------------------------------------------------- */ TDBCAPI Tcl_Obj* Tdbc_TokenizeSql( Tcl_Interp *interp, const char* zSql) { Tcl_Obj *resultPtr; size_t i; resultPtr = Tcl_NewObj(); for(i = 0; zSql[i]; i++){ switch( zSql[i] ){ /* Skip over quoted strings. Strings can be quoted in several ** ways: '...' "..." [....] */ case '\'': case '"': case '[': { int endChar = zSql[i]; if (endChar == '[') endChar = ']'; for(i++; zSql[i] && zSql[i]!=endChar; i++){} if (zSql[i] == 0) i--; break; } /* Skip over SQL-style comments: -- to end of line */ case '-': { if (zSql[i+1] == '-') { for(i+=2; zSql[i] && zSql[i]!='\n'; i++){} if (zSql[i] == 0) i--; } break; } /* Skip over C-style comments */ case '/': { if (zSql[i+1] == '*') { i += 3; while (zSql[i] && (zSql[i]!='/' || zSql[i-1]!='*')) { i++; } if (zSql[i] == 0) i--; } break; } /* Break up multiple SQL statements at each semicolon */ case ';': { if (i>0 ){ Tcl_ListObjAppendElement(interp, resultPtr, Tcl_NewStringObj(zSql, i)); } Tcl_ListObjAppendElement(interp, resultPtr, Tcl_NewStringObj(";",1)); zSql += i + 1; i = -1; break; } /* Any of the characters ':', '$', or '@' which is followed ** by an alphanumeric or '_' and is not preceded by the same ** is a host parameter. A name following a doubled colon '::' ** is also not a host parameter. */ case ':': { if (i > 0 && zSql[i-1] == ':') break; } /* fallthru */ case '$': case '@': { if (i>0 && (isalnum((unsigned char)(zSql[i-1])) || zSql[i-1]=='_')) break; if (!isalnum((unsigned char)(zSql[i+1])) && zSql[i+1]!='_') break; if (i>0 ){ Tcl_ListObjAppendElement(interp, resultPtr, Tcl_NewStringObj(zSql, i)); zSql += i; } i = 1; while (zSql[i] && (isalnum((unsigned char)(zSql[i])) || zSql[i]=='_')) { i++; } Tcl_ListObjAppendElement(interp, resultPtr, Tcl_NewStringObj(zSql, i)); zSql += i; i = -1; break; } } } if (i > 0) { Tcl_ListObjAppendElement(interp, resultPtr, Tcl_NewStringObj(zSql, i)); } return resultPtr; } /* *----------------------------------------------------------------------------- * * TdbcTokenizeObjCmd -- * * Tcl command to tokenize a SQL statement. * * Usage: * ::tdbc::tokenize statement * * Results: * Returns a list as from passing the given statement to * Tdbc_TokenizeSql above. * *----------------------------------------------------------------------------- */ MODULE_SCOPE int TdbcTokenizeObjCmd( TCL_UNUSED(void *), /* Unused */ Tcl_Interp* interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj *const objv[]) /* Parameter vector */ { Tcl_Obj* retval; /* Check param count */ if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "statement"); return TCL_ERROR; } /* Parse the statement */ retval = Tdbc_TokenizeSql(interp, Tcl_GetString(objv[1])); if (retval == NULL) { return TCL_ERROR; } /* Return the tokenized statement */ Tcl_SetObjResult(interp, retval); return TCL_OK; }