/* ** If the input string begins with "<html>" and contains "</html>" somewhere ** before it ends, then return the number of characters through the end of ** the </html>. If the <html> or the </html> is missing, return 0. */ static int is_html(const char *z){ int i; extern int sqliteStrNICmp(const char *, const char*, int); if( sqliteStrNICmp(z, "<html>", 6) ) return 0; for(i=6; z[i]; i++){ if( z[i]=='<' && sqliteStrNICmp(&z[i],"</html>",7)==0 ) return i+7; } return 0; }
/* ** This function looks up an identifier to determine if it is a ** keyword. If it is a keyword, the token code of that keyword is ** returned. If the input is not a keyword, TK_ID is returned. */ int sqliteKeywordCode(const char *z, int n){ int h, i; Keyword *p; static char needInit = 1; if( needInit ){ /* Initialize the keyword hash table */ sqliteOsEnterMutex(); if( needInit ){ int nk; nk = sizeof(aKeywordTable)/sizeof(aKeywordTable[0]); for(i=0; i<nk; i++){ aKeywordTable[i].len = strlen(aKeywordTable[i].zName); h = sqliteHashNoCase(aKeywordTable[i].zName, aKeywordTable[i].len); h %= KEY_HASH_SIZE; aKeywordTable[i].iNext = aiHashTable[h]; aiHashTable[h] = i+1; } needInit = 0; } sqliteOsLeaveMutex(); } h = sqliteHashNoCase(z, n) % KEY_HASH_SIZE; for(i=aiHashTable[h]; i; i=p->iNext){ p = &aKeywordTable[i-1]; if( p->len==n && sqliteStrNICmp(p->zName, z, n)==0 ){ return p->tokenType; } } return TK_ID; }
/* ** Do a deep comparison of two expression trees. Return TRUE (non-zero) ** if they are identical and return FALSE if they differ in any way. */ int sqliteExprCompare(Expr *pA, Expr *pB){ int i; if( pA==0 ){ return pB==0; }else if( pB==0 ){ return 0; } if( pA->op!=pB->op ) return 0; if( !sqliteExprCompare(pA->pLeft, pB->pLeft) ) return 0; if( !sqliteExprCompare(pA->pRight, pB->pRight) ) return 0; if( pA->pList ){ if( pB->pList==0 ) return 0; if( pA->pList->nExpr!=pB->pList->nExpr ) return 0; for(i=0; i<pA->pList->nExpr; i++){ if( !sqliteExprCompare(pA->pList->a[i].pExpr, pB->pList->a[i].pExpr) ){ return 0; } } }else if( pB->pList ){ return 0; } if( pA->pSelect || pB->pSelect ) return 0; if( pA->iTable!=pB->iTable || pA->iColumn!=pB->iColumn ) return 0; if( pA->token.z ){ if( pB->token.z==0 ) return 0; if( pB->token.n!=pA->token.n ) return 0; if( sqliteStrNICmp(pA->token.z, pB->token.z, pB->token.n)!=0 ) return 0; } return 1; }
/* ** Return TRUE if the line typed in is an SQL command terminator other ** than a semi-colon. The SQL Server style "go" command is understood ** as is the Oracle "/". */ static int _is_command_terminator(const char *zLine){ extern int sqliteStrNICmp(const char*,const char*,int); while( isspace(*zLine) ){ zLine++; }; if( zLine[0]=='/' && _all_whitespace(&zLine[1]) ) return 1; /* Oracle */ if( sqliteStrNICmp(zLine,"go",2)==0 && _all_whitespace(&zLine[2]) ){ return 1; /* SQL Server */ } return 0; }
/* ** This routine is called by the parser to process a DETACH statement: ** ** DETACH DATABASE dbname ** ** The pDbname argument is the name of the database in the DETACH statement. */ void sqliteDetach(Parse *pParse, Token *pDbname){ int i; sqlite *db; Vdbe *v; Db *pDb; v = sqliteGetVdbe(pParse); sqliteVdbeAddOp(v, OP_Halt, 0, 0); if( pParse->explain ) return; db = pParse->db; for(i=0; i<db->nDb; i++){ pDb = &db->aDb[i]; if( pDb->pBt==0 || pDb->zName==0 ) continue; if( strlen(pDb->zName)!=pDbname->n ) continue; if( sqliteStrNICmp(pDb->zName, pDbname->z, pDbname->n)==0 ) break; } if( i>=db->nDb ){ sqliteErrorMsg(pParse, "no such database: %T", pDbname); return; } if( i<2 ){ sqliteErrorMsg(pParse, "cannot detach database %T", pDbname); return; } #ifndef SQLITE_OMIT_AUTHORIZATION if( sqliteAuthCheck(pParse,SQLITE_DETACH,db->aDb[i].zName,0,0)!=SQLITE_OK ){ return; } #endif /* SQLITE_OMIT_AUTHORIZATION */ sqliteBtreeClose(pDb->pBt); pDb->pBt = 0; sqliteFree(pDb->zName); sqliteResetInternalSchema(db, i); if( pDb->pAux && pDb->xFreeAux ) pDb->xFreeAux(pDb->pAux); db->nDb--; if( i<db->nDb ){ db->aDb[i] = db->aDb[db->nDb]; memset(&db->aDb[db->nDb], 0, sizeof(db->aDb[0])); sqliteResetInternalSchema(db, i); } }
/* ** Return TRUE if the given SQL string ends in a semicolon. ** ** Special handling is require for CREATE TRIGGER statements. ** Whenever the CREATE TRIGGER keywords are seen, the statement ** must end with ";END;". ** ** This implementation uses a state machine with 7 states: ** ** (0) START At the beginning or end of an SQL statement. This routine ** returns 1 if it ends in the START state and 0 if it ends ** in any other state. ** ** (1) EXPLAIN The keyword EXPLAIN has been seen at the beginning of ** a statement. ** ** (2) CREATE The keyword CREATE has been seen at the beginning of a ** statement, possibly preceeded by EXPLAIN and/or followed by ** TEMP or TEMPORARY ** ** (3) NORMAL We are in the middle of statement which ends with a single ** semicolon. ** ** (4) TRIGGER We are in the middle of a trigger definition that must be ** ended by a semicolon, the keyword END, and another semicolon. ** ** (5) SEMI We've seen the first semicolon in the ";END;" that occurs at ** the end of a trigger definition. ** ** (6) END We've seen the ";END" of the ";END;" that occurs at the end ** of a trigger difinition. ** ** Transitions between states above are determined by tokens extracted ** from the input. The following tokens are significant: ** ** (0) tkEXPLAIN The "explain" keyword. ** (1) tkCREATE The "create" keyword. ** (2) tkTEMP The "temp" or "temporary" keyword. ** (3) tkTRIGGER The "trigger" keyword. ** (4) tkEND The "end" keyword. ** (5) tkSEMI A semicolon. ** (6) tkWS Whitespace ** (7) tkOTHER Any other SQL token. ** ** Whitespace never causes a state transition and is always ignored. */ int sqlite_complete(const char *zSql){ u8 state = 0; /* Current state, using numbers defined in header comment */ u8 token; /* Value of the next token */ /* The following matrix defines the transition from one state to another ** according to what token is seen. trans[state][token] returns the ** next state. */ static const u8 trans[7][8] = { /* Token: */ /* State: ** EXPLAIN CREATE TEMP TRIGGER END SEMI WS OTHER */ /* 0 START: */ { 1, 2, 3, 3, 3, 0, 0, 3, }, /* 1 EXPLAIN: */ { 3, 2, 3, 3, 3, 0, 1, 3, }, /* 2 CREATE: */ { 3, 3, 2, 4, 3, 0, 2, 3, }, /* 3 NORMAL: */ { 3, 3, 3, 3, 3, 0, 3, 3, }, /* 4 TRIGGER: */ { 4, 4, 4, 4, 4, 5, 4, 4, }, /* 5 SEMI: */ { 4, 4, 4, 4, 6, 5, 5, 4, }, /* 6 END: */ { 4, 4, 4, 4, 4, 0, 6, 4, }, }; while( *zSql ){ switch( *zSql ){ case ';': { /* A semicolon */ token = tkSEMI; break; } case ' ': case '\r': case '\t': case '\n': case '\f': { /* White space is ignored */ token = tkWS; break; } case '/': { /* C-style comments */ if( zSql[1]!='*' ){ token = tkOTHER; break; } zSql += 2; while( zSql[0] && (zSql[0]!='*' || zSql[1]!='/') ){ zSql++; } if( zSql[0]==0 ) return 0; zSql++; token = tkWS; break; } case '-': { /* SQL-style comments from "--" to end of line */ if( zSql[1]!='-' ){ token = tkOTHER; break; } while( *zSql && *zSql!='\n' ){ zSql++; } if( *zSql==0 ) return state==0; token = tkWS; break; } case '[': { /* Microsoft-style identifiers in [...] */ zSql++; while( *zSql && *zSql!=']' ){ zSql++; } if( *zSql==0 ) return 0; token = tkOTHER; break; } case '"': /* single- and double-quoted strings */ case '\'': { int c = *zSql; zSql++; while( *zSql && *zSql!=c ){ zSql++; } if( *zSql==0 ) return 0; token = tkOTHER; break; } default: { if( isIdChar[(u8)*zSql] ){ /* Keywords and unquoted identifiers */ int nId; for(nId=1; isIdChar[(u8)zSql[nId]]; nId++){} switch( *zSql ){ case 'c': case 'C': { if( nId==6 && sqliteStrNICmp(zSql, "create", 6)==0 ){ token = tkCREATE; }else{ token = tkOTHER; } break; } case 't': case 'T': { if( nId==7 && sqliteStrNICmp(zSql, "trigger", 7)==0 ){ token = tkTRIGGER; }else if( nId==4 && sqliteStrNICmp(zSql, "temp", 4)==0 ){ token = tkTEMP; }else if( nId==9 && sqliteStrNICmp(zSql, "temporary", 9)==0 ){ token = tkTEMP; }else{ token = tkOTHER; } break; } case 'e': case 'E': { if( nId==3 && sqliteStrNICmp(zSql, "end", 3)==0 ){ token = tkEND; }else if( nId==7 && sqliteStrNICmp(zSql, "explain", 7)==0 ){ token = tkEXPLAIN; }else{ token = tkOTHER; } break; } default: { token = tkOTHER; break; } } zSql += nId-1; }else{ /* Operators and special symbols */ token = tkOTHER; } break; } } state = trans[state][token]; zSql++; } return state==0; }
/* ** This is called by the parser when it sees a CREATE TRIGGER statement ** up to the point of the BEGIN before the trigger actions. A Trigger ** structure is generated based on the information available and stored ** in pParse->pNewTrigger. After the trigger actions have been parsed, the ** sqliteFinishTrigger() function is called to complete the trigger ** construction process. */ void sqliteBeginTrigger( Parse *pParse, /* The parse context of the CREATE TRIGGER statement */ Token *pName, /* The name of the trigger */ int tr_tm, /* One of TK_BEFORE, TK_AFTER, TK_INSTEAD */ int op, /* One of TK_INSERT, TK_UPDATE, TK_DELETE */ IdList *pColumns, /* column list if this is an UPDATE OF trigger */ SrcList *pTableName,/* The name of the table/view the trigger applies to */ int foreach, /* One of TK_ROW or TK_STATEMENT */ Expr *pWhen, /* WHEN clause */ int isTemp /* True if the TEMPORARY keyword is present */ ){ Trigger *nt; Table *tab; char *zName = 0; /* Name of the trigger */ sqlite *db = pParse->db; int iDb; /* When database to store the trigger in */ DbFixer sFix; /* Check that: ** 1. the trigger name does not already exist. ** 2. the table (or view) does exist in the same database as the trigger. ** 3. that we are not trying to create a trigger on the sqlite_master table ** 4. That we are not trying to create an INSTEAD OF trigger on a table. ** 5. That we are not trying to create a BEFORE or AFTER trigger on a view. */ if( sqlite_malloc_failed ) goto trigger_cleanup; assert( pTableName->nSrc==1 ); if( db->init.busy && sqliteFixInit(&sFix, pParse, db->init.iDb, "trigger", pName) && sqliteFixSrcList(&sFix, pTableName) ){ goto trigger_cleanup; } tab = sqliteSrcListLookup(pParse, pTableName); if( !tab ){ goto trigger_cleanup; } iDb = isTemp ? 1 : tab->iDb; if( iDb>=2 && !db->init.busy ){ sqliteErrorMsg(pParse, "triggers may not be added to auxiliary " "database %s", db->aDb[tab->iDb].zName); goto trigger_cleanup; } zName = sqliteStrNDup(pName->z, pName->n); sqliteDequote(zName); if( sqliteHashFind(&(db->aDb[iDb].trigHash), zName,pName->n+1) ){ sqliteErrorMsg(pParse, "trigger %T already exists", pName); goto trigger_cleanup; } if( sqliteStrNICmp(tab->zName, "sqlite_", 7)==0 ){ sqliteErrorMsg(pParse, "cannot create trigger on system table"); pParse->nErr++; goto trigger_cleanup; } if( tab->pSelect && tr_tm != TK_INSTEAD ){ sqliteErrorMsg(pParse, "cannot create %s trigger on view: %S", (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName, 0); goto trigger_cleanup; } if( !tab->pSelect && tr_tm == TK_INSTEAD ){ sqliteErrorMsg(pParse, "cannot create INSTEAD OF" " trigger on table: %S", pTableName, 0); goto trigger_cleanup; } #ifndef SQLITE_OMIT_AUTHORIZATION { int code = SQLITE_CREATE_TRIGGER; const char *zDb = db->aDb[tab->iDb].zName; const char *zDbTrig = isTemp ? db->aDb[1].zName : zDb; if( tab->iDb==1 || isTemp ) code = SQLITE_CREATE_TEMP_TRIGGER; if( sqliteAuthCheck(pParse, code, zName, tab->zName, zDbTrig) ){ goto trigger_cleanup; } if( sqliteAuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(tab->iDb), 0, zDb)){ goto trigger_cleanup; } } #endif /* INSTEAD OF triggers can only appear on views and BEGIN triggers ** cannot appear on views. So we might as well translate every ** INSTEAD OF trigger into a BEFORE trigger. It simplifies code ** elsewhere. */ if (tr_tm == TK_INSTEAD){ tr_tm = TK_BEFORE; } /* Build the Trigger object */ nt = (Trigger*)sqliteMalloc(sizeof(Trigger)); if( nt==0 ) goto trigger_cleanup; nt->name = zName; zName = 0; nt->table = sqliteStrDup(pTableName->a[0].zName); if( sqlite_malloc_failed ) goto trigger_cleanup; nt->iDb = iDb; nt->iTabDb = tab->iDb; nt->op = op; nt->tr_tm = tr_tm; nt->pWhen = sqliteExprDup(pWhen); nt->pColumns = sqliteIdListDup(pColumns); nt->foreach = foreach; sqliteTokenCopy(&nt->nameToken,pName); assert( pParse->pNewTrigger==0 ); pParse->pNewTrigger = nt; trigger_cleanup: sqliteFree(zName); sqliteSrcListDelete(pTableName); sqliteIdListDelete(pColumns); sqliteExprDelete(pWhen); }