Esempio n. 1
0
// PRIMARY INDEX hash partitioning clause
bool SqlParser::ParseTeradataPrimaryIndex(Token *unique, Token *primary, int obj_scope, Token *last_colname,
											Token *last_colend)
{
	if(primary == NULL)
		return false;

	Token *index = GetNextWordToken("INDEX", L"INDEX", 5);

	if(index == NULL)
		return false;

	Token *open = GetNextCharToken('(', L'(');

	// List of partitioning columns
	while(true)
	{
		Token *column = GetNextToken();

		if(column == NULL)
			break;

		Token *comma = GetNextCharToken(',', L',');

		if(comma == NULL)
			break;
	}

	Token *close = GetNextCharToken(open, ')', L')');

	// Add UNIQUE constraint for other databases
	if(unique != NULL && _target != SQL_TERADATA)
	{
		AppendNoFormat(last_colend, ",", L",", 1);
		Append(last_colend, "\n", L"\n", 1, last_colname);
		
		AppendCopy(last_colend, unique);
		AppendNoFormat(last_colend, " ", L" ", 1);
		AppendCopy(last_colend, open, close);

		Token::Remove(unique, false);
	}

	// PARTITION BY HASH in Oracle
	if(_target == SQL_ORACLE)
	{
		// In Oracle, temporary tables cannot be partitioned 
		if(obj_scope != SQL_SCOPE_TEMP_TABLE)
		{
			Token::Change(primary, "PARTITION", L"PARTITION", 9);
			Token::Change(index, "BY HASH", L"BY HASH", 7);
		}
		else
			Token::Remove(primary, close);
	}

	return true;
}
Esempio n. 2
0
// Append the token with the specified value
Token* SqlParser::Append(Token *token, const char *str, const wchar_t *wstr, int len, Token *format)
{
	if(token == NULL)
		return NULL;

	Token *append = new Token();

	if(format == NULL)
		*append = *token;
	else
		*append = *format;

	append->prev = NULL;
	append->next = NULL;

	append->t_str = NULL;
	append->t_wstr = NULL;
	append->t_len = 0;
	
	// If token starts with newline, add the same number of spaces before appended token
	if(str != NULL && str[0] == '\n')
	{
		// Add newline token first
		Token newline;

#ifdef WIN32
		newline.type = TOKEN_SYMBOL;
		newline.chr = '\r';
		
		AppendCopy(token, &newline);
#endif

		newline.type = TOKEN_SYMBOL;
		newline.chr = '\n';
		
		AppendCopy(token, &newline);

		Token *prev = (format != NULL) ? format->prev : token->prev;

		// Copy all blankes 
		while(prev != NULL && (prev->Compare(' ', L' ') == true || prev->Compare('\t', L'\t') == true))
		{
			AppendCopy(token, prev);
			prev = prev->prev;
		}

		// Skip new line in the token
		Token::Change(append, str + 1, wstr + 1, len - 1);
	}
	else
		Token::Change(append, str, wstr, len);

	Append(token, append);

	return append;
}
Esempio n. 3
0
// Append the range of tokens (inclusively)
Token* SqlParser::AppendCopy(Token *token, Token *first, Token *last, bool append_removed)
{
	if(token == NULL || first == NULL)
		return NULL;

	Token *cur = first;

	Token *new_first = NULL;

	while(cur != NULL)
	{
		bool removed = cur->IsRemoved();

		// Check whether we need to skip already removed tokens
		if(removed == false || (removed == true && append_removed == true))
		{
			Token *append = AppendCopy(token, cur);

			if(new_first == NULL)
				new_first = append;
		}

		// Append single token only
		if(first == last || last == NULL)
			break;

		// Tokens appended inclusively
		if(cur == last)
			break;

		cur = cur->next;
	}

	return new_first;
}
Esempio n. 4
0
void SqlParser::AppendSpaceCopy(Token *token, Token *first, Token *last, bool append_removed)
{
	if(token == NULL)
		return;

	AppendNoFormat(token, " ", L" ", 1);
	AppendCopy(token, first, last, append_removed);
}
Esempio n. 5
0
// Append a space and specified token
void SqlParser::AppendSpaceCopy(Token *token, Token *append)
{
	if(token == NULL || append == NULL)
		return;

	AppendNoFormat(token, " ", L" ", 1);
	AppendCopy(token, append);
}
Esempio n. 6
0
// Prepend the token with the specified value
Token* SqlParser::Prepend(Token *token, const char *str, const wchar_t *wstr, int len, Token *format)
{
	if(token == NULL)
		return NULL;

	Token *prepend = new Token();

	// Define how to format the token
	if(format == NULL)
		*prepend = *token;
	else
		*prepend = *format;

	prepend->prev = NULL;
	prepend->next = NULL;
	prepend->t_str = NULL;
	prepend->t_wstr = NULL;
	prepend->t_len = 0;

	prepend->flags = TOKEN_INSERTED;

	Token::Change(prepend, str, wstr, len);
	Prepend(token, prepend);

	// If token ends with newline, add the same number of spaces after prepended token
	if(str != NULL && str[len - 1] == '\n')
	{
		Token *prev = token->prev;

		// Skip previously appended tokens
		while(prev != NULL && prev->flags & TOKEN_INSERTED)
			prev = prev->prev;

		// Copy all blanks and tabs before appended token after new line
		while(prev != NULL && (prev->Compare(' ', L' ') == true || prev->Compare('\t', L'\t') == true))
		{
			AppendCopy(prepend, prev);
			prev = prev->prev;
		}
	}

	return prepend;
}
Esempio n. 7
0
// Parse MySQL CREATE TABLE storage clause
bool SqlParser::ParseMysqlStorageClause(Token *table_name, Token **id_start, Token **comment_out)
{
	bool exists = false;

	// Auto_increment start value
	Token *auto_start = NULL;

	while(true)
	{
		Token *next = GetNextToken();

		if(next == NULL)
			break;

		// ENGINE = type
		if(next->Compare("ENGINE", L"ENGINE", 6) == true)
		{
			// Equal sign = is optional in the clause
			Token *equal = GetNextCharToken('=', L'=');
			Token *type = GetNextToken();

			if(_target != SQL_MYSQL)
			{
				Token::Remove(next);
				Token::Remove(equal);
				Token::Remove(type);
			}

			exists = true;
			continue;
		}
		else
		// AUTO_INCREMENT = start table option
		if(next->Compare("AUTO_INCREMENT", L"AUTO_INCREMENT", 14) == true)
		{
			// Equal sign = is optional in the clause
			Token *equal = GetNextCharToken('=', L'=');
			auto_start = GetNextNumberToken();

			if(_target != SQL_MYSQL)
			{
				Token::Remove(next);
				Token::Remove(equal);
				Token::Remove(auto_start);
			}

			exists = true;
			continue;
		}
		else
		// DEFAULT CHARSET
		if(next->Compare("DEFAULT", L"DEFAULT", 7) == true)
		{
			Token *option = GetNextToken();

			if(option == NULL)
				break;

			// CHARSET
			if(option->Compare("CHARSET", L"CHARSET", 7) == true)
			{
				Token *equal = GetNextCharToken('=', L'=');
				Token *value = GetNextIdentToken();

				if(_target != SQL_MYSQL)
					Token::Remove(next, value);
			}
			else
			// CHARACTER SET
			if(option->Compare("CHARACTER", L"CHARACTER", 9) == true)
			{
				Token *set = GetNextWordToken("SET", L"SET", 3);
				Token *equal = GetNextCharToken('=', L'=');
				Token *value = GetNextIdentToken();

				if(_target != SQL_MYSQL)
					Token::Remove(next, value);
			}

			exists = true;
			continue;
		}
		else
		// COLLATE = value
		if(next->Compare("COLLATE", L"COLLATE", 7) == true)
		{
			Token *equal = GetNextCharToken('=', L'=');
			Token *value = GetNextIdentToken();

			if(_target != SQL_MYSQL)
				Token::Remove(next, value);

			exists = true;
			continue;
		}
		else
		// COMMENT = 'table comment'
		if(next->Compare("COMMENT", L"COMMENT", 7) == true)
		{
			// Equal sign = is optional in the clause
			Token *equal = GetNextCharToken('=', L'=');
			Token *text = GetNextToken();

			if(comment_out != NULL)
				*comment_out = text;

			// Remove from CREATE TABLE
			if(_target != SQL_MYSQL)
				Token::Remove(next, text);

			exists = true;
			continue;
		}
		else
		// PACK_KEYS = 0 | 1 | DEFAULT
		if(next->Compare("PACK_KEYS", L"PACK_KEYS", 9) == true)
		{
			// Optional = 
			Token *equal = GetNextCharToken('=', L'=');
			Token *value = GetNextToken();

			if(_target != SQL_MYSQL)
				Token::Remove(next, value);

			exists = true;
			continue;
		}
		else
		// ROW_FORMAT = type | DEFAULT
		if(next->Compare("ROW_FORMAT", L"ROW_FORMAT", 10) == true)
		{
			// Optional = 
			Token *equal = GetNextCharToken('=', L'=');
			Token *value = GetNextToken();

			if(_target != SQL_MYSQL)
				Token::Remove(next, value);

			exists = true;
			continue;
		}

		// Not a MySQL stoage clause
		PushBack(next);

		break;
	}

	if(id_start != NULL)
		*id_start = auto_start;

	// Restart sequence for PostgreSQL and Greenplum
	if(auto_start != NULL && (_target == SQL_POSTGRESQL || _target == SQL_GREENPLUM))
	{
		// Try to get ;
		Token *semi = GetNextCharToken(';', L';');

		Token *last = (semi != NULL) ? semi : GetLastToken();

		// Append ALTER SEQUENCE command
		Append(last, "\n\nALTER SEQUENCE ", L"\n\nALTER SEQUENCE ", 17);

		// Add sequence name
		Token *seq_name = AppendIdentifier(table_name, "_seq", L"_seq", 4);
		Append(last, seq_name);

		Append(last, " RESTART WITH ", L" RESTART WITH ", 14);
		AppendCopy(last, auto_start);
		Append(last, ";", L";", 1);
	}

	return exists;
}
Esempio n. 8
0
// DB2 for z/OS COMMENT ON tab (col IS 'comment' , ...)
bool SqlParser::ParseDb2Comment(Token *comment, Token *on, Token *name)
{
	if(comment == NULL)
		return false;

	Token *open = GetNextCharToken('(', L'(');

	if(open == NULL)
		return false;

	int num = 1;

	// Multipe comma separated comments can be specified
	while(true)
	{
		Token *col = GetNextIdentToken();

		if(col == NULL)
			break;

		// IS keyword must follow now
		Token *is = GetNextWordToken("IS", L"IS", 2);

		if(is == NULL)
			break;

		// Comment text
		Token *text = GetNextToken();

		if(_target == SQL_SQL_SERVER)
		{
			Append(text, ", 'table', ", L", 'table', ", 11);
			AppendCopy(text, name);
			Append(text, ", 'column', ", L", 'column', ", 12);
			AppendCopy(text, col);

			Token::Remove(col);
			Token::Remove(is);
		}
		else
		if(_target == SQL_ORACLE)
		{
			if(num == 1)
			{
				Append(on, " COLUMN", L" COLUMN", 7);
				Append(name, ".", L".", 1);
				AppendCopy(name, col);

				Token::Remove(col);
			}
		}

		Token *comma = GetNextCharToken(',', L',');

		if(comma == NULL)
			break;

		num++;
	}

	Token *close = GetNextCharToken(')', L')');

	if(_target == SQL_SQL_SERVER)
	{
		Token::Change(comment, "EXECUTE", L"EXECUTE", 7);
		AppendNoFormat(comment, " sp_addextendedproperty 'Comment',", L" sp_addextendedproperty 'Comment',", 34); 

		Token::Remove(on);
		Token::Remove(name);
		Token::Remove(open);
		Token::Remove(close);
	}
	else
	if(_target == SQL_ORACLE)
	{
		Token::Remove(open);
		Token::Remove(close);
	}

	return true;
}
Esempio n. 9
0
// DB2 z/OS USING STOGROUP clause
bool SqlParser::ParseDb2StogroupClause(Token *objname, Token *using_, Token *stogroup, int scope)
{
	if(using_ == NULL || stogroup == NULL)
		return false;

	// STOGROUP name
	Token *name = GetNextToken();

	if(name == NULL)
		return false;

	bool exists = false;

	// STOGROUP options
	while(true)
	{
		Token *next = GetNextToken();

		if(next == NULL)
			break;

		// PRIQTY num
		if(next->Compare("PRIQTY", L"PRIQTY", 6) == true)
		{
			Token *num = GetNextToken();

			if(_target != SQL_DB2)
				Token::Remove(next, num);

			exists = true;
			continue;
		}
		else
		// SECQTY num
		if(next->Compare("SECQTY", L"SECQTY", 6) == true)
		{
			Token *num = GetNextToken();

			if(_target != SQL_DB2)
				Token::Remove(next, num);

			exists = true;
			continue;
		}
		else
		// ERASE NO | YES
		if(next->Compare("ERASE", L"ERASE", 5) == true)
		{
			Token *value = GetNextToken();

			if(_target != SQL_DB2)
				Token::Remove(next, value);

			exists = true;
			continue;
		}
	
		PushBack(next);
		break;
	}

	// Set DATAFILE clause in CREATE TABLESPACE for Oracle
	if(_target == SQL_ORACLE && scope == SQL_SCOPE_TABLESPACE)
	{
		Token::Change(using_, "DATAFILE '", L"DATAFILE '", 10);

		// Use tablespace name as multiple tablespaces can be created in a single storage group
		AppendCopy(using_, objname);
		Append(using_, ".dbf'", L".dbf'", 5);

		Token::Remove(stogroup);
		Token::Remove(name);
	}
	else
	if(_target != SQL_DB2)
		Token::Remove(using_, name);

	return exists;
}
Esempio n. 10
0
// DB2 identity options in GENERATED ALWAYS or BY DEFAULT AS IDENTITY
bool SqlParser::ParseDb2GeneratedClause(Token *create, Token *table_name, Token *column, Token *generated,
									Token **id_col, Token **id_start, Token **id_inc, bool *id_default)
{
	if(generated == NULL)
		return false;

	Token *always = GetNextWordToken("ALWAYS", L"ALWAYS", 6);
	Token *by = NULL;
	Token *default_ = NULL;

	if(always == NULL)
	{
		by = GetNextWordToken("BY", L"BY", 2);
		default_ = GetNextWordToken("DEFAULT", L"DEFAULT", 7);

		if(id_default != NULL)
			*id_default = true;
	}
	else
	{
		if(id_default != NULL)
			*id_default = false;
	}
	
	Token *as = GetNextWordToken("AS", L"AS", 2);
	Token *identity = GetNextWordToken("IDENTITY", L"IDENTITY", 8);

	// Identity parameters are optional
	Token *open = GetNextCharToken('(', L'(');
	Token *close = NULL;

	Token *start_with = NULL;
	Token *increment_by = NULL;

	while(true)
	{
		bool exists = false;

		if(open == NULL)
			break;

		Token *option = GetNextToken();

		if(option == NULL)
			break;

		// START WITH
		if(option->Compare("START", L"START", 5) == true)
		{
			/*Token *with */ (void) GetNextWordToken("WITH", L"WITH", 4);
			start_with = GetNextNumberToken();

			exists = true;
			continue;
		}
		else
		// INCREMENT BY
		if(option->Compare("INCREMENT", L"INCREMENT", 9) == true)
		{
			/*Token *by */ (void) GetNextWordToken("BY", L"BY", 2);
			increment_by = GetNextNumberToken();

			exists = true;
			continue;
		}
		else
		// MINVALUE
		if(option->Compare("MINVALUE", L"MINVALUE", 8) == true)
		{
			/*Token *value */ (void) GetNextNumberToken();

			exists = true;
			continue;
		}
		else
		// MAXVALUE
		if(option->Compare("MAXVALUE", L"MAXVALUE", 8) == true)
		{
			/*Token *value */ (void) GetNextNumberToken();

			exists = true;
			continue;
		}
		else
		// NO CYCLE and NO ORDER
		if(option->Compare("NO", L"NO", 2) == true)
		{
			/*Token *attr */ (void) GetNextToken();

			exists = true;
			continue;
		}
		else
		// CACHE
		if(option->Compare("CACHE", L"CACHE", 5) == true)
		{
			/*Token *value */ (void) GetNextNumberToken();

			exists = true;
			continue;
		}
		else
		// CYCLE
		if(option->Compare("CYCLE", L"CYCLE", 5) == true)
		{
			exists = true;
			continue;
		}
		else
		// Looks like comma is optional
		if(option->Compare(',', L',') == true)
		{
			exists = true;
			continue;
		}

		PushBack(option);
		break;
	}

	if(open != NULL)
		close = GetNextCharToken(')', L')');
	
	// IDENTITY(start, inc) in SQL Server
	if(_target == SQL_SQL_SERVER)
	{
		Token::Change(generated, "IDENTITY(", L"IDENTITY(", 9);
		AppendCopy(generated, start_with);

		if(increment_by != NULL)
		{
			Append(generated, ", ", L", ", 2);
			AppendCopy(generated, increment_by);
		}

		Append(generated, ")", L")", 1);

		if(always != NULL)
			Token::Remove(always, close);
		else
			Token::Remove(by, close);
	}
	else
	// Use a sequence and DEFAULT nextval for PostgreSQL and Greenplum
	if(_target == SQL_POSTGRESQL || _target == SQL_GREENPLUM)
	{
		TokenStr seq_name(table_name);

		AppendIdentifier(seq_name, "_seq", L"_seq", 4);
		
		// Generate CREATE SEQUENCE before CREATE TABLE
		Prepend(create, "CREATE SEQUENCE ", L"CREATE SEQUENCE ", 16);
		PrependNoFormat(create, &seq_name);

		if(start_with != NULL)
		{
			Prepend(create, " START WITH ", L" START WITH ", 12);
			PrependCopy(create, start_with);
		}

		if(increment_by != NULL)
		{
			Prepend(create, " INCREMENT BY ", L" INCREMENT BY ", 14);
			PrependCopy(create, increment_by);
		}

		Prepend(create, ";\n\n", L";\n\n", 3);

		// Generate DEFAULT nextval('tablename_seq') clause
		Token::Change(generated, "DEFAULT ", L"DEFAULT ", 8);
		Append(generated, "nextval ('", L"nextval ('", 10);

		AppendNoFormat(generated, &seq_name);
		Append(generated, "')", L"')", 2);

		if(always != NULL)
			Token::Remove(always, close);
		else
			Token::Remove(by, close);
	}
	else
	// Remove for other databases
	if(_target != SQL_DB2)
	{
		Token::Remove(generated);

		Token::Remove(always);
		Token::Remove(by, default_);
		Token::Remove(as, identity);
		
		Token::Remove(open, close);
	}

	if(id_col != NULL)
		*id_col = column; 

	if(id_start != NULL)
		*id_start = start_with;
	
	if(id_inc != NULL)
		*id_inc = increment_by;

	return true;
}
Esempio n. 11
0
// Parse specific DB2 AND syntax: (c1, c2, ...) = (v1, v2, ...)
bool SqlParser::ParseDb2AndBooleanExpression(Token *open)
{
	if(open == NULL)
		return false;

	Token *next = GetNextToken();

	if(next == NULL)
		return false;

	// Comma must follow after the first token
	Token *comma = GetNextCharToken(',', L',');

	if(comma == NULL)
	{
		PushBack(next);
		return false;
	}

	ListWM first;
	first.Add(next, comma);

	// Select left columns of AND expressions
	while(true)
	{
		Token *nextn = GetNextToken();

		if(next == NULL)
			break;

		Token *comma = GetNextCharToken(',', L',');

		first.Add(nextn, comma);

		if(comma == NULL)
			break;
	}

	Token *close = GetNextCharToken(')', L')');

	// = before list of right AND expressions
	Token *equal = GetNextCharToken('=', L'=');

	if(equal == NULL)
	{
		PushBack(next);
		return false;
	}
	
	Token *open2 = GetNextCharToken('(', L'(');

	ListwmItem *i = first.GetFirst();

	// Select right columns of AND expressions
	while(true)
	{
		Token *nextn = GetNextToken();

		if(next == NULL)
			break;

		Token *comma = GetNextCharToken(',', L',');

		Token *f_col = NULL;
		Token *f_comma = NULL;
		Token *f_append = NULL;

		if(i != NULL)
		{
			f_col = (Token*)i->value;
			f_comma = (Token*)i->value2;

			f_append = Nvl(f_comma, f_col);
		}

		// Oracle does not support this syntax, use c1 = v1 AND ...
		if(_target == SQL_ORACLE)
		{
			AppendNoFormat(f_append, " = ", L" = ", 3);
			AppendCopy(f_append, nextn);

			if(comma != NULL)
				AppendNoFormat(f_append, " AND", L" AND", 4);

			Token::Remove(f_comma);
			Token::Remove(nextn);
			Token::Remove(comma);
		}

		if(comma == NULL)
			break;

		if(i != NULL)
			i = i->next;
	}

	if(_target == SQL_ORACLE)
	{
		Token::Remove(close);
		Token::Remove(equal);
		Token::Remove(open2);
	}

	// Note: function mustn't parse closing ), it processed by the caller
	return true;
}
Esempio n. 12
0
// Parse SQL Server, Sybase ASE UPDATE statememt
bool SqlParser::ParseSqlServerUpdateStatement(Token *update)
{
	Token *name = GetNextIdentToken(SQL_IDENT_OBJECT);

	if(name == NULL)
		return false;

	Token *set = TOKEN_GETNEXTW("SET");

	if(set == NULL)
	{
		PushBack(name);
		return false;
	}
	
	// Parser list of assignments: c1 = exp1, ...
	while(true)
	{
		Token *col = GetNextIdentToken();

		if(col == NULL)
			break;
		
		Token *equal = TOKEN_GETNEXT('=');

		if(equal == NULL)
			break;

		// Single value or (SELECT c1, c2, ...) can be specified
		Token *open = TOKEN_GETNEXT('(');

		Token *select = NULL;

		// Check for SELECT statement
		if(open != NULL)
		{
			select = GetNextSelectStartKeyword();

			// A subquery used to specify assignment values
			if(select != NULL)
				ParseSelectStatement(select, 0, SQL_SEL_UPDATE_SET, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);

			TOKEN_GETNEXT(')');
		}
		else
		{
			Token *exp = GetNextToken();

			if(exp == NULL)
				break;

			ParseExpression(exp);
		}
		
		Token *comma = TOKEN_GETNEXT(',');

		if(comma == NULL)
			break;
	}

	Token *from = NULL;
	Token *from_end = NULL;

	// FROM clause can include inner/outer joins
	ParseSelectFromClause(NULL, false, &from, &from_end, NULL, true, NULL);
        	
    Token *where_ = NULL;
    Token *where_end = NULL;

	// optional WHERE clause
	ParseWhereClause(SQL_STMT_UPDATE, &where_, &where_end, NULL);

	// UPDATE FROM syntax is used
	if(from != NULL)
	{
		// MySQL, MariaDB use syntax UPDATE t1, t2 SET ... WHERE
		if(Target(SQL_MYSQL, SQL_MARIADB))
		{
			Token::Remove(from);

			AppendCopy(name, from, from_end, false);

			Token::Remove(name);
			Token::Remove(from, from_end);
		}
	}

    // Implement CONTINUE handler for NOT FOUND in Oracle
	if(_target == SQL_ORACLE)
		OracleContinueHandlerForUpdate(update);

	// Add statement delimiter if not set when source is SQL Server
	SqlServerAddStmtDelimiter();

	return true;
}
Esempio n. 13
0
// Convert row-level trigger using inserted and deleted system tables
void SqlParser::SqlServerConvertRowLevelTrigger(Token *table, Token *when, Token *insert, Token *update, 
					Token *delete_, Token *end)
{
	if(table == NULL || when == NULL)
		return;

	TokenStr old_cols;
	TokenStr old_vars;

	Token *decl_end = _spl_last_declare;

	// Skip statement delimiter
	if(decl_end != NULL && Token::Compare(decl_end->next, ';', L';') == true)
		decl_end = decl_end->next;
	
	int num = 0;
	size_t old_prefix_len = 4;

	// Skip old. in column name references (old already prefixed with @ in target)
	if(_spl_old_correlation_name != NULL)
		old_prefix_len = _spl_old_correlation_name->len + 1 + 1;

	// Generate declaration for referenced OLD columns
	for(ListwItem *i = _spl_tr_old_columns.GetFirst(); i != NULL; i = i->next)
	{
		Token *var = (Token*)i->value;

		if(num == 0)
			Append(decl_end, "\n", L"\n", 1);
		else
		{
			old_cols.Append(", ", L", ", 2);
			old_vars.Append(", ", L", ", 2);
		}

		Append(decl_end, "\nDECLARE ", L"\nDECLARE ", 9, _declare_format);
		AppendCopy(decl_end, var);
		Append(decl_end, " ", L" ", 1);

		// Define the data type of the variable
		TokenStr type;
		GuessType(var, type);
		AppendNoFormat(decl_end, &type);

		Append(decl_end, ";", L";", 1);

		old_cols.Append(var, old_prefix_len, var->len - old_prefix_len + 1);
		old_vars.Append(var);

		num++;
	}
	
	// Declare deleted cursor
	if(delete_ != NULL)
	{
		Append(decl_end, "\nDECLARE ", L"\nDECLARE ", 9, _declare_format); 
		AppendNoFormat(decl_end, "deleted_cur ", L"deleted_cur ", 12); 
		Append(decl_end, "CURSOR FOR SELECT ", L"CURSOR FOR SELECT ", 18, _declare_format);
		AppendNoFormat(decl_end, &old_cols);
		Append(decl_end, " FROM ", L" FROM ", 6, _declare_format);
		AppendNoFormat(decl_end, "deleted;", L"deleted;", 8);
	}

	if(delete_ != NULL)
		Append(decl_end, "\n\nOPEN deleted_cur;", L"\n\nOPEN deleted_cur;", 19); 

	Append(decl_end, "\nWHILE 1=0 BEGIN", L"\nWHILE 1=0 BEGIN", 16); 

	if(delete_ != NULL)
	{
		Append(decl_end, "\nFETCH deleted_cur INTO ", L"\nFETCH deleted_cur INTO ", 24);
		AppendNoFormat(decl_end, &old_vars);
		Append(decl_end, ";", L";", 1);
	}

	Append(decl_end, "\nIF @@FETCH_STATUS <> 0 BREAK;", L"\nIF @@FETCH_STATUS <> 0 BREAK;", 30);

	// Terminate the cursor loop
	Token *end_loop = Prepend(end, "END\n", L"END\n", 4);

	if(delete_ != NULL)
	{
		Append(end_loop, "CLOSE deleted_cur;", L"CLOSE deleted_cur;", 18);
		Append(end_loop, "\nDEALLOCATE deleted_cur;", L"\nDEALLOCATE deleted_cur;", 24);
	}

	// Perform DELETE in INSTEAD OF trigger
	if(delete_ != NULL && insert == NULL && update == NULL)
	{
		Append(end_loop, "\nDELETE FROM ", L"\nDELETE FROM ", 13);
		AppendCopy(end_loop, table);
		Append(end_loop, " WHERE (", L" WHERE (", 8); 
		AppendNoFormat(end_loop, &old_cols);
		Append(end_loop, ") IN (SELECT ", L") IN (SELECT ", 13);
		AppendNoFormat(end_loop, &old_cols);
		Append(end_loop, " FROM deleted);\n", L" FROM deleted);\n", 16);
	}
}