/*
 * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * 1. Redistributions of source code must retain the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * An tokenizer for SQL
 *
 * This file contains C code that splits an SQL input string up into
 * individual tokens and sends those tokens one-by-one over to the
 * parser for analysis.
 */
#include <unicode/utf8.h>
#include <unicode/uchar.h>

#include "box/session.h"
#include "box/schema.h"
#include "say.h"
#include "sqlInt.h"
#include "tarantoolInt.h"

/* Character classes for tokenizing
 *
 * In the sql_token() function, a switch() on sql_ascii_class[c]
 * is implemented using a lookup table, whereas a switch()
 * directly on c uses a binary search. The lookup table is much
 * faster. To maximize speed, and to ensure that a lookup table is
 * used, all of the classes need to be small integers and all of
 * them need to be used within the switch.
 */
#define CC_X          0		/* The letter 'x', or start of BLOB literal */
#define CC_KYWD       1		/* Alphabetics or '_'.  Usable in a keyword */
#define CC_ID         2		/* unicode characters usable in IDs */
#define CC_DIGIT      3		/* Digits */
/** Character ':'. */
#define CC_COLON      4
/** SQL variable special characters: '@', '#', and '$'. */
#define CC_VARALPHA   5
#define CC_VARNUM     6		/* '?'.  Numeric SQL variables */
#define CC_SPACE      7		/* Space characters */
#define CC_QUOTE      8		/* '\''. String literals */
#define CC_DQUOTE     9		/* '"'. Identifiers*/
#define CC_PIPE      10		/* '|'.   Bitwise OR or concatenate */
#define CC_MINUS     11		/* '-'.  Minus or SQL-style comment */
#define CC_LT        12		/* '<'.  Part of < or <= or <> */
#define CC_GT        13		/* '>'.  Part of > or >= */
#define CC_EQ        14		/* '='.  Part of = or == */
#define CC_BANG      15		/* '!'.  Part of != */
#define CC_SLASH     16		/* '/'.  / or c-style comment */
#define CC_LP        17		/* '(' */
#define CC_RP        18		/* ')' */
#define CC_SEMI      19		/* ';' */
#define CC_PLUS      20		/* '+' */
#define CC_STAR      21		/* '*' */
#define CC_PERCENT   22		/* '%' */
#define CC_COMMA     23		/* ',' */
#define CC_AND       24		/* '&' */
#define CC_TILDA     25		/* '~' */
#define CC_DOT       26		/* '.' */
#define CC_ILLEGAL   27		/* Illegal character */
#define CC_LINEFEED  28		/* '\n' */
#define CC_LB        29		/* '[' */
#define CC_RB        30		/* ']' */
/** Character '{'. */
#define CC_LCB       31
/** Character '}'. */
#define CC_RCB       32

static const char sql_ascii_class[] = {
/*       x0  x1  x2  x3  x4  x5  x6  x7  x8 x9  xa xb  xc xd xe  xf */
/* 0x */ 28, 27, 27, 27, 27, 27, 27, 27, 27, 7, 28, 7, 7, 7, 27, 27,
/* 1x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
/* 2x */ 7, 15, 9, 5, 5, 22, 24, 8, 17, 18, 21, 20, 23, 11, 26, 16,
/* 3x */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 19, 12, 14, 13, 6,
/* 4x */ 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* 5x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 29, 27, 30, 27, 1,
/* 6x */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* 7x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 31, 10, 32, 25, 27,
/* 8x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* 9x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* Ax */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* Bx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* Cx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* Dx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* Ex */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* Fx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
};

/**
 * The charMap() macro maps alphabetic characters (only) into
 * their lower-case ASCII equivalent.  On ASCII machines, this
 * is just an upper-to-lower case map.
 *
 * Used by keywordhash.h
 */
#define charMap(X) sqlUpperToLower[(unsigned char)X]

/*
 * The sqlKeywordCode 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.
 *
 * The implementation of this routine was generated by a program,
 * mkkeywordhash.c, located in the tool subdirectory of the distribution.
 * The output of the mkkeywordhash.c program is written into a file
 * named keywordhash.h and then included into this source file by
 * the #include below.
 */
#include "keywordhash.h"

#define maybe_utf8(c) ((sqlCtypeMap[c] & 0x40) != 0)

/*
 * Return the id of the next token in string (*pz). Before returning, set
 * (*pz) to point to the byte following the parsed token.
 */
static int
getToken(const char **pz)
{
	const char *z = *pz;
	int t;	/* Token type to return */
	bool is_reserved;
	do {
		z += sql_token(z, &t, &is_reserved);
	} while (t == TK_SPACE || t == TK_LINEFEED);
	if (t == TK_ID ||
	    t == TK_STRING ||
	    t == TK_JOIN_KW ||
	    t == TK_WINDOW ||
	    t == TK_OVER ||
	    sqlParserFallback(t) == TK_ID) {
		t = TK_ID;
	}
	*pz = z;
	return t;
}

/*
 * The following three functions are called immediately after the tokenizer
 * reads the keywords WINDOW, OVER and FILTER, respectively, to determine
 * whether the token should be treated as a keyword or an SQL identifier.
 * This cannot be handled by the usual lemon %fallback method, due to
 * the ambiguity in some constructions. e.g.
 *
 *   SELECT sum(x) OVER ...
 *
 * In the above, "OVER" might be a keyword, or it might be an alias for the
 * sum(x) expression. If a "%fallback ID OVER" directive were added to
 * grammar, then SQLite would always treat "OVER" as an alias, making it
 * impossible to call a window-function without a FILTER clause.
 *
 * WINDOW is treated as a keyword if:
 *
 *   * the following token is an identifier, or a keyword that can fallback
 *     to being an identifier, and
 *   * the token after than one is TK_AS.
 *
 * OVER is a keyword if:
 *
 *   * the previous token was TK_RP, and
 *   * the next token is either TK_LP or an identifier.
 *
 * FILTER is a keyword if:
 *
 *   * the previous token was TK_RP, and
 *   * the next token is TK_LP.
 */
static int
analyzeWindowKeyword(const char *z)
{
	int t;
	t = getToken(&z);
	if (t != TK_ID)
		return TK_ID;
	t = getToken(&z);
	if (t != TK_AS)
		return TK_ID;
	return TK_WINDOW;
}

static int
analyzeOverKeyword(const char *z, int lastToken)
{
	if (lastToken == TK_RP) {
		int t = getToken(&z);
	if (t == TK_LP || t == TK_ID)
		return TK_OVER;
	}
	return TK_ID;
}

static int
analyzeFilterKeyword(const char *z, int lastToken)
{
	if (lastToken == TK_RP && getToken(&z) == TK_LP) {
		return TK_FILTER;
	}
	return TK_ID;
}

/**
 * Return true if current symbol is space.
 *
 * @param z Input stream.
 * @retval True if current symbol space.
 */
static inline bool
sql_is_space_char(const char *z)
{
	if (sqlIsspace(z[0]))
		return true;
	if (maybe_utf8(*(unsigned char*)z)) {
		UChar32 c;
		int unused = 0;
		U8_NEXT_UNSAFE(z, unused, c);
		if (u_isspace(c))
			return true;
	}
	return false;
}

/**
 * Calculate length of continuous sequence of
 * space symbols.
 *
 * @param z Input stream.
 * @retval Number of bytes which constitute sequence of spaces.
 *         Can be 0 if first symbol in stram is not space.
 */
static inline int
sql_skip_spaces(const char *z)
{
	int idx = 0;
	while (true) {
		if (sqlIsspace(z[idx])) {
			idx += 1;
		} else if (maybe_utf8(*(unsigned char *)(z + idx))) {
			UChar32 c;
			int new_offset = idx;
			U8_NEXT_UNSAFE(z, new_offset, c);
			if (!u_isspace(c))
				break;
			idx = new_offset;
		} else {
			break;
		}
	}
	return idx;
}

int
sql_token(const char *z, int *type, bool *is_reserved)
{
	*is_reserved = false;
	int i;
	char c, delim;
	/* Switch on the character-class of the first byte
	 * of the token. See the comment on the CC_ defines
	 * above.
	 */
	switch (sql_ascii_class[*(unsigned char*)z]) {
	case CC_SPACE:
		i = 1 + sql_skip_spaces(z+1);
		*type = TK_SPACE;
		return i;
	case CC_LINEFEED:
		*type = TK_LINEFEED;
		return 1;
	case CC_MINUS:
		/*
		 * Ignore single-line comment started with "--"
		 * till the end of parsing string or next line.
		 */
		if (z[1] == '-') {
			for (i = 2; true; i++) {
				if (z[i] == '\0') {
					*type = TK_SPACE;
					return i;
				} else if (z[i] == '\n') {
					*type = TK_LINEFEED;
					return ++i;
				}
			}
		}
		*type = TK_MINUS;
		return 1;
	case CC_LP:
		*type = TK_LP;
		return 1;
	case CC_RP:
		*type = TK_RP;
		return 1;
	case CC_LB:
		*type = TK_LB;
		return 1;
	case CC_RB:
		*type = TK_RB;
		return 1;
	case CC_LCB:
		*type = TK_LCB;
		return 1;
	case CC_RCB:
		*type = TK_RCB;
		return 1;
	case CC_SEMI:
		*type = TK_SEMI;
		return 1;
	case CC_PLUS:
		*type = TK_PLUS;
		return 1;
	case CC_STAR:
		*type = TK_STAR;
		return 1;
	case CC_SLASH:
		if (z[1] != '*' || z[2] == 0) {
			*type = TK_SLASH;
			return 1;
		}
		for (i = 3, c = z[2];
		     (c != '*' || z[i] != '/') && (c = z[i]) != 0;
		     i++) {
		}
		if (c)
			i++;
		*type = TK_SPACE;
		return i;
	case CC_PERCENT:
		*type = TK_REM;
		return 1;
	case CC_EQ:
		*type = TK_EQ;
		return 1 + (z[1] == '=');
	case CC_LT:
		if ((c = z[1]) == '=') {
			*type = TK_LE;
			return 2;
		} else if (c == '>') {
			*type = TK_NE;
			return 2;
		} else if (c == '<') {
			*type = TK_LSHIFT;
			return 2;
		} else {
			*type = TK_LT;
			return 1;
		}
	case CC_GT:
		if ((c = z[1]) == '=') {
			*type = TK_GE;
			return 2;
		} else if (c == '>') {
			*type = TK_RSHIFT;
			return 2;
		} else {
			*type = TK_GT;
			return 1;
		}
	case CC_BANG:
		if (z[1] != '=') {
			*type = TK_ILLEGAL;
			return 1;
		} else {
			*type = TK_NE;
			return 2;
		}
	case CC_PIPE:
		if (z[1] != '|') {
			*type = TK_BITOR;
			return 1;
		} else {
			*type = TK_CONCAT;
			return 2;
		}
	case CC_COMMA:
		*type = TK_COMMA;
		return 1;
	case CC_AND:
		*type = TK_BITAND;
		return 1;
	case CC_TILDA:
		*type = TK_BITNOT;
		return 1;
	case CC_QUOTE:
	case CC_DQUOTE:
		delim = z[0];
		for (i = 1; (c = z[i]) != 0; i++) {
			if (c == delim) {
				if (z[i + 1] == delim)
					i++;
				else
					break;
			}
		}
		if (c == '\'') {
			*type = TK_STRING;
			return i + 1;
		} else if (c != 0) {
			*type = TK_ID;
			return i + 1;
		} else {
			*type = TK_ILLEGAL;
			return i;
		}
		FALLTHROUGH;
	case CC_DOT:
		if (!sqlIsdigit(z[1])) {
			*type = TK_DOT;
			return 1;
		}
		/* If the next character is a digit, this is a
		 * floating point number that begins with ".".
		 * Fall thru into the next case.
		 */
		FALLTHROUGH;
	case CC_DIGIT:
		*type = TK_INTEGER;
		if (z[0] == '0' && (z[1] == 'x' || z[1] == 'X') &&
		    sqlIsxdigit(z[2])) {
			for (i = 3; sqlIsxdigit(z[i]); i++) {
			}
			return i;
		}
		for (i = 0; sqlIsdigit(z[i]); i++) {
		}
		if (z[i] == '.') {
			while (sqlIsdigit(z[++i])) {
			}
			*type = TK_DECIMAL;
		}
		if ((z[i] == 'e' || z[i] == 'E') &&
		    (sqlIsdigit(z[i + 1])
		     || ((z[i + 1] == '+' || z[i + 1] == '-') &&
			 sqlIsdigit(z[i + 2])))) {
			i += 2;
			while (sqlIsdigit(z[i]))
				i++;
			*type = TK_FLOAT;
		}
		if (IdChar(z[i])) {
			*type = TK_ILLEGAL;
			while (IdChar(z[++i])) {
			}
		}
		return i;
	case CC_VARNUM:
		*type = TK_VARNUM;
		return 1;
	case CC_COLON:
		*type = TK_COLON;
		return 1;
	case CC_VARALPHA:
		*type = TK_VARIABLE;
		return 1;
	case CC_KYWD:
		for (i = 1; sql_ascii_class[*(unsigned char*)(z+i)] <= CC_KYWD;
		     i++) {
		}
		if (!sql_is_space_char(z + i) && IdChar(z[i])) {
			/* This token started out using characters
			 * that can appear in keywords, but z[i] is
			 * a character not allowed within keywords,
			 * so this must be an identifier instead.
			 */
			i++;
			break;
		}
		*type = TK_ID;
		return keywordCode(z, i, type, is_reserved);
	case CC_X:
		if (z[1] == '\'') {
			*type = TK_BLOB;
			for (i = 2; sqlIsxdigit(z[i]); i++) {
			}
			if (z[i] != '\'' || i % 2) {
				*type = TK_ILLEGAL;
				while (z[i] != 0 && z[i] != '\'')
					i++;
			}
			if (z[i] != 0)
				i++;
			return i;
		}
		/* If it is not a BLOB literal, then it must be an
		 * ID, since no SQL keywords start with the letter
		 * 'x'.  Fall through.
		 */
		FALLTHROUGH;
	case CC_ID:
		i = 1;
		break;
	default:
		*type = TK_ILLEGAL;
		return 1;
	}
	int spaces_len = sql_skip_spaces(z);
	if (spaces_len > 0) {
		*type = TK_SPACE;
		return spaces_len;
	}
	while (IdChar(z[i]))
		i++;
	*type = TK_ID;
	return i;
}

/**
 * This function is called to release parsing artifacts
 * during table creation or column addition. The only objects
 * allocated using malloc are index defs.
 * Note that this functions can't be called on ordinary
 * space object. It's purpose is to clean-up parser->new_space.
 *
 * @param space Space to be deleted.
 */
static void
parser_space_delete(struct space *space)
{
	if (space == NULL)
		return;
	assert(space->def->opts.is_ephemeral);
	struct space *altered_space = space_by_name(space->def->name);
	uint32_t i = 0;
	/*
	 * Don't delete already existing defs and start from new
	 * ones.
	 */
	if (altered_space != NULL)
		i = altered_space->index_count;
	for (; i < space->index_count; ++i)
		index_def_delete(space->index[i]->def);
}

static void *
new_xmalloc(size_t n)
{
	return xmalloc(n);
}

/**
 * Run the parser on the given SQL string.
 *
 * @param pParse Parser context.
 * @param zSql SQL string.
 * @retval 0 on success.
 * @retval -1 on error.
 */
int
sqlRunParser(Parse * pParse, const char *zSql)
{
	int n = 0;		/* Length of the next token */
	void *pEngine;		/* The LEMON-generated LALR(1) parser */
	int tokenType;		/* type of the next token */
	int lastTokenParsed = -1;	/* type of the previous token */
	int mxSqlLen;			/* Max length of an SQL string */

	assert(zSql != 0);
	mxSqlLen = SQL_MAX_SQL_LENGTH;
	pParse->zTail = zSql;
	/* sqlParserTrace(stdout, "parser: "); */
	pEngine = sqlParserAlloc(new_xmalloc);
	assert(pParse->create_table_def.new_space == NULL);
	assert(pParse->parsed_ast.trigger == NULL);
	assert(pParse->nVar == 0);
	assert(pParse->pVList == 0);
	while (1) {
		n = sql_token(zSql, &tokenType,
			      &pParse->sLastToken.isReserved);
		mxSqlLen -= n;
		if (mxSqlLen < 0) {
			diag_set(ClientError, ER_SQL_PARSER_LIMIT,
				 "SQL command length", mxSqlLen,
				 SQL_MAX_SQL_LENGTH);
			pParse->is_aborted = true;
			break;
		}
		if (tokenType >= TK_WINDOW) {
			if (tokenType == TK_SPACE) {
				zSql += n;
				pParse->line_pos += n;
				continue;
			}
			if (zSql[0] == 0) {
				/* Upon reaching the end of input, call
				 * the parser two more times
				 * with tokens TK_SEMI and 0, in that order.
				 */
				if (lastTokenParsed == TK_SEMI) {
					tokenType = 0;
				} else if (lastTokenParsed == 0) {
					break;
				} else {
					tokenType = TK_SEMI;
				}
				n = 0;
			} else if (tokenType == TK_LINEFEED) {
				/* '\0' for some reason generates a TK_LINEFEED
				 * token with a length of 1 (even though the
				 * tail of the input string is empty at that
				 * point), so we must make sure to check for
				 * TK_LINEFEED only after that check for
				 * (zSql[0] == 0) above
				 */
				pParse->line_count++;
				pParse->line_pos = 1;
				zSql += n;
				continue;
			} else if (tokenType == TK_WINDOW) {
				assert(n == 6);
				tokenType = analyzeWindowKeyword(&zSql[6]);
			} else if (tokenType == TK_OVER) {
				assert(n == 4);
				tokenType = analyzeOverKeyword(
						&zSql[4], lastTokenParsed);
			} else if (tokenType == TK_FILTER) {
				assert(n == 6);
				tokenType = analyzeFilterKeyword(
						&zSql[6], lastTokenParsed);
			} else if (tokenType == TK_ILLEGAL) {
				diag_set(ClientError, ER_SQL_UNKNOWN_TOKEN,
					 pParse->line_count, pParse->line_pos,
					 n, zSql);
				pParse->is_aborted = true;
				break;
			}
		}
		pParse->sLastToken.z = zSql;
		pParse->sLastToken.n = n;
		zSql += n;
		sqlParser(pEngine, tokenType, pParse->sLastToken,
			  pParse);
		pParse->line_pos += pParse->sLastToken.n;
		lastTokenParsed = tokenType;
		if (pParse->is_aborted)
			break;
	}
	pParse->zTail = zSql;
	sqlParserFree(pEngine, free);
	if (pParse->pVdbe != NULL && pParse->is_aborted) {
		sqlVdbeDelete(pParse->pVdbe);
		pParse->pVdbe = 0;
	}
	parser_space_delete(pParse->create_column_def.space);

	if (pParse->pWithToFree)
		sqlWithDelete(pParse->pWithToFree);
	sql_xfree(pParse->pVList);
	return pParse->is_aborted ? -1 : 0;
}

struct Expr *
sql_expr_compile(const char *expr, int expr_len)
{
	const char *outer = "FUNCTION ";
	int len = strlen(outer) + expr_len;

	struct Parse parser;
	sql_parser_create(&parser, SQL_DEFAULT_FLAGS);
	/*
	 * Since SELECT token is added to the original expression,
	 * to make error message display correct position we should
	 * account its length.
	 */
	parser.line_pos -= strlen(outer);
	parser.parse_only = true;
	parser.is_expr = true;

	struct Expr *expression = NULL;
	char *stmt = xregion_alloc(&parser.region, len + 1);
	snprintf(stmt, len + 1, "%s%.*s", outer, expr_len, expr);

	if (sqlRunParser(&parser, stmt) == 0) {
		assert(parser.parsed_ast_type == AST_TYPE_EXPR);
		expression = parser.parsed_ast.expr;
		parser.parsed_ast.expr = NULL;
	}
	sql_parser_destroy(&parser);
	return expression;
}

struct Select *
sql_view_compile(const char *view_stmt)
{
	struct Parse parser;
	sql_parser_create(&parser, SQL_DEFAULT_FLAGS);
	parser.parse_only = true;

	struct Select *select = NULL;

	if (sqlRunParser(&parser, view_stmt) != 0 ||
	    parser.parsed_ast_type != AST_TYPE_SELECT) {
		diag_set(ClientError, ER_SQL_EXECUTE, view_stmt);
	} else {
		select = parser.parsed_ast.select;
		parser.parsed_ast.select = NULL;
	}

	sql_parser_destroy(&parser);
	return select;
}

struct sql_trigger *
sql_trigger_compile(const char *sql)
{
	struct Parse parser;
	sql_parser_create(&parser, SQL_DEFAULT_FLAGS);
	parser.parse_only = true;
	struct sql_trigger *trigger = NULL;
	if (sqlRunParser(&parser, sql) == 0 &&
	    parser.parsed_ast_type == AST_TYPE_TRIGGER) {
		trigger = parser.parsed_ast.trigger;
		parser.parsed_ast.trigger = NULL;
	}

	sql_parser_destroy(&parser);
	return trigger;
}
