//
// Copyright (c) 2017-2026, Manticore Software LTD (https://manticoresearch.com)
// Copyright (c) 2001-2016, Andrew Aksyonoff
// Copyright (c) 2008-2016, Sphinx Technologies Inc
// All rights reserved
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License. You should have
// received a copy of the GPL license along with this program; if you
// did not, you can find it at http://www.gnu.org/
//

#ifndef _searchdsql_
#define _searchdsql_

#include "sphinx.h"

/// refcounted vector
template < typename T >
class RefcountedVector_c : public CSphVector<T>, public ISphRefcountedMT
{};

struct AttrValue_t
{
	int64_t m_iValue;
	float	m_fValue;
	bool	m_bFloat;

	bool operator == ( const AttrValue_t & rhs ) const	{ return m_iValue==rhs.m_iValue && m_fValue==rhs.m_fValue && m_bFloat==rhs.m_bFloat; }
	bool operator < ( const AttrValue_t & rhs ) const	{ return m_iValue<rhs.m_iValue; }
};

using AttrValues_p = CSphRefcountedPtr < RefcountedVector_c<AttrValue_t> >;
using AttrValueVec_t = CSphVector<AttrValue_t>;


/// parser view on a generic node
/// CAUTION, nodes get copied in the parser all the time, must keep assignment slim
struct SqlNode_t final
{
	int						m_iStart = 0;	///< first byte relative to m_pBuf, inclusive
	int						m_iEnd = 0;		///< last byte relative to m_pBuf, exclusive! thus length = end - start
	int						m_iType = 0;	///< TOK_xxx type for insert values; SPHINXQL_TOK_xxx code for special idents
	float					m_fValue = 0.0;
	int 					m_iValues = -1;    ///< filter values vector (idx of vec stored in the parser)
	uint64_t				m_uValue = 0;
	int						m_iParsedOp = -1;
	bool					m_bNegative = false;	// this flag means that '-' was explicitly specified before the integer const
	bool					m_bFloat = false;

							SqlNode_t() = default;

	void					SetValueInt ( int64_t iValue ) noexcept;
	void					SetValueInt ( uint64_t uValue, bool bNegative ) noexcept;
	void					SetValueFloat ( float fValue ) noexcept;
	int64_t					GetValueInt() const noexcept;
	uint64_t				GetValueUint() const noexcept;
	float					GetValueFloat() const noexcept	{ return m_fValue; }
	void					CopyValueInt ( const SqlNode_t & tRhs ) noexcept;
};

/// types of string-list filters.
enum class StrList_e
{
	// string matching: assume attr is a whole solid string
	// attr MUST match any of variants provided, assuming collation applied
	STR_IN,

	// tags matching: assume attr is string of space-separated tags, no collation
	// any separate tag of attr MUST match any of variants provided
	STR_ANY,	/// 'hello world' OP ('hello', 'foo') true, OP ('foo', 'fee' ) false

	// every separate tag of attr MUST match any of variants provided
	STR_ALL    /// 'hello world' OP ('world', 'hello') true, OP ('a','world','hello') false
};


/// magic codes passed via SqlNode_t::m_iStart to handle certain special tokens
/// for instance, to fixup "count(*)" as "@count" easily
enum
{
	SPHINXQL_TOK_COUNT		= -1,
	SPHINXQL_TOK_GROUPBY	= -2,
	SPHINXQL_TOK_WEIGHT		= -3,
};


enum SqlStmt_e : BYTE
{
	STMT_PARSE_ERROR = 0,
	STMT_DUMMY,

	STMT_SELECT,
	STMT_INSERT,
	STMT_REPLACE,
	STMT_DELETE,
	STMT_SHOW_WARNINGS,
	STMT_SHOW_STATUS,
	STMT_SHOW_META,
	STMT_SET,
	STMT_BEGIN,
	STMT_COMMIT,
	STMT_ROLLBACK,
	STMT_CALL, // check.pl STMT_CALL_SNIPPETS STMT_CALL_KEYWORDS
	STMT_DESCRIBE,
	STMT_SHOW_TABLES,
	STMT_CREATE_TABLE,
	STMT_CREATE_TABLE_LIKE,
	STMT_DROP_TABLE,
	STMT_DROP_CACHE,
	STMT_SHOW_CREATE_TABLE,
	STMT_UPDATE,
	STMT_CREATE_FUNCTION,
	STMT_DROP_FUNCTION,
	STMT_ATTACH_INDEX,
	STMT_FLUSH_RTINDEX,
	STMT_FLUSH_RAMCHUNK,
	STMT_SHOW_VARIABLES,
	STMT_TRUNCATE_RTINDEX,
	STMT_SELECT_COLUMNS, // was STMT_SELECT_SYSVAR
	STMT_SHOW_COLLATION,
	STMT_SHOW_CHARACTER_SET,
	STMT_OPTIMIZE_INDEX,
	STMT_SHOW_AGENT_STATUS,
	STMT_SHOW_INDEX_STATUS,
	STMT_SHOW_FEDERATED_INDEX_STATUS,
	STMT_SHOW_PROFILE,
	STMT_ALTER_ADD,
	STMT_ALTER_DROP,
	STMT_ALTER_MODIFY,
	STMT_SHOW_PLAN,
//	STMT_SELECT_DUAL, // deprecated to STMT_SELECT_COLUMNS as more general
	STMT_SHOW_DATABASES,
	STMT_CREATE_PLUGIN,
	STMT_DROP_PLUGIN,
	STMT_SHOW_PLUGINS,
	STMT_SHOW_THREADS,
	STMT_FACET,
	STMT_ALTER_RECONFIGURE,
	STMT_SHOW_INDEX_SETTINGS,
	STMT_FLUSH_INDEX,
	STMT_RELOAD_PLUGINS,
	STMT_RELOAD_INDEX,
	STMT_FLUSH_HOSTNAMES,
	STMT_FLUSH_LOGS,
	STMT_RELOAD_INDEXES,
	STMT_SYSFILTERS,
	STMT_DEBUG,
	STMT_ALTER_KLIST_TARGET,
	STMT_ALTER_INDEX_SETTINGS,
	STMT_ALTER_EMBEDDINGS_API_KEY,
	STMT_ALTER_EMBEDDINGS_API_URL,
	STMT_ALTER_EMBEDDINGS_API_TIMEOUT,
	STMT_JOIN_CLUSTER,
	STMT_CLUSTER_CREATE,
	STMT_CLUSTER_DELETE,
	STMT_CLUSTER_EXIT,
	STMT_CLUSTER_ALTER_ADD,
	STMT_CLUSTER_ALTER_DROP,
	STMT_CLUSTER_ALTER_UPDATE,
	STMT_EXPLAIN,
	STMT_IMPORT_TABLE,
	STMT_FREEZE,
	STMT_UNFREEZE,
	STMT_SHOW_SETTINGS,
	STMT_ALTER_REBUILD_SI,
	STMT_KILL,
	STMT_SHOW_LOCKS,
	STMT_SHOW_SCROLL,
	STMT_SHOW_TABLE_INDEXES,
	STMT_ALTER_REBUILD_KNN,
	STMT_ALTER_REBUILD_EMBEDDINGS,
	STMT_LOCK_TABLES,
	STMT_UNLOCK_TABLES,
	STMT_RELOAD_AUTH,
	STMT_SHOW_USAGE,
	STMT_SHOW_PERMISSIONS,
	STMT_SHOW_USERS,
	STMT_SHOW_TOKEN,
	STMT_SET_PASSWORD,
	STMT_TOKEN,
	STMT_CREATE_USER,
	STMT_DROP_USER,
	STMT_GRANT,
	STMT_REVOKE,

	STMT_TOTAL
};

constexpr const char* SqlStmt2Str(SqlStmt_e eStmt)
{
	constexpr const char* dNames[STMT_TOTAL]
	{
	"parse_error", "dummy", "select", "insert", "replace", "delete", "show_warnings",
	"show_status", "show_meta", "set", "begin", "commit", "rollback", "call",
	"desc", "show_tables", "create_table", "create_table_like", "drop_table", "drop_cache", "show_create_table", "update", "create_func",
	"drop_func", "attach_index", "flush_rtindex", "flush_ramchunk", "show_variables", "truncate_rtindex",
	"select_columns", "show_collation", "show_character_set", "optimize_index", "show_agent_status",
	"show_index_status", "show_index_status", "show_profile", "alter_add", "alter_drop", "alter_modify", "show_plan",
	"show_databases", "create_plugin", "drop_plugin", "show_plugins", "show_threads",
	"facet", "alter_reconfigure", "show_index_settings", "flush_index", "reload_plugins", "reload_index",
	"flush_hostnames", "flush_logs", "reload_indexes", "sysfilters", "debug", "alter_killlist_target",
	"alter_index_settings", "alter_embeddings_api_key", "alter_embeddings_api_url", "alter_embeddings_api_timeout", "join_cluster", "cluster_create", "cluster_delete", "cluster_exit", "cluster_index_add",
	"cluster_index_delete", "cluster_update", "explain", "import_table", "freeze_indexes", "unfreeze_indexes",
	"show_settings", "alter_rebuild_si", "kill", "show_locks", "show_scroll", "show_table_indexes", "alter_rebuild_knn", "alter_rebuild_embeddings", "lock_tables", "unlock_tables",
	"reload_auth", "show_usage", "show_permissions", "show_users", "show_token", "set_password", "token", "create_user", "drop_user", "grant", "revoke"
			};
	return dNames[eStmt];
}


enum SqlSet_e
{
	SET_LOCAL,
	SET_GLOBAL_UVAR,
	SET_GLOBAL_SVAR,
	SET_INDEX_UVAR,
	SET_CLUSTER_UVAR,
	SET_EXTRA
};


/// insert value
struct SqlInsert_t
{
	// some internal tokens for bison grammar parser.
	// originaly we fetched values from parser itself, but it is more convenient to push own values instead.
	// in order to make it most seamless way, let's follow this manual:
	// To add new value XXX (if necessary) look into generated bissphinxql.h for TOK_XXX value,
	// then add the number BOTH into sphinxql.y (to fix this value forever), and into this enum (without TOK_ prefix),
	// as: for TOK_SYSVAR which is now 268 - change '%token TOK_SYSVAR' to '%token TOK_SYSVAR 268' in bison file,
	// then add SYSVAR = 268 here.
	enum Tokens_e
	{
		CONST_INT = 260,
		CONST_FLOAT = 261,
		CONST_MVA = 262,
		QUOTED_STRING = 263,
		CONST_STRINGS = 269,
		TABLE = 378,
		TOK_NULL = 473, // NULL is already reserved using TOK_NULL
	};

	int						m_iType = 0;
	float					m_fVal = 0.0;
	CSphString				m_sVal;		// OPTIMIZE? use char* and point to node?
	AttrValues_p			m_pVals;

	void					SetValueInt ( uint64_t uValue, bool bNegative );
	void					SetValueInt ( int64_t iValue );
	int64_t					GetValueInt() const;
	uint64_t				GetValueUint() const;
	bool					IsNegativeInt() const { return m_bNegative; }
	void					CopyValueInt ( const SqlNode_t & tRhs );

private:
	uint64_t				m_uValue = 0;
	bool					m_bNegative = false;
};

namespace DebugCmd { struct DebugCommand_t; }

/// parsing result
/// one day, we will start subclassing this
struct SqlStmt_t
{
	SqlStmt_e				m_eStmt = STMT_PARSE_ERROR;
	int						m_iRowsAffected = 0;
	const char *			m_sStmt = nullptr; // for error reporting

											   // SELECT specific
	CSphQuery				m_tQuery;
	std::unique_ptr<ISphTableFunc> m_pTableFunc;

	CSphString				m_sTableFunc;
	StrVec_t				m_dTableFuncArgs;

	// used by INSERT, DELETE, CALL, DESC, ATTACH, ALTER, RELOAD INDEX
	CSphString				m_sIndex;
	CSphString				m_sCluster;
	bool					m_bClusterUpdateNodes = false;

	// INSERT (and CALL) specific
	CSphVector<SqlInsert_t>	m_dInsertValues; // reused by CALL
	StrVec_t				m_dInsertSchema;
	int						m_iSchemaSz = 0;

	// SET specific
	CSphString				m_sSetName;		// reused by ATTACH
	SqlSet_e				m_eSet = SET_LOCAL;
	int64_t					m_iSetValue = 0;
	CSphString				m_sSetValue;
	CSphVector<SphAttr_t>	m_dSetValues;

	// GRANT/REVOKE specific
	CSphString				m_sAuthAction;
	CSphString				m_sAuthTarget;
	CSphString				m_sAuthUser;
	CSphString				m_sAuthPassword;
	CSphString				m_sAuthBudget;
	int						m_iAuthAllow = 1;

	// CALL specific
	CSphString				m_sCallProc;
	StrVec_t				m_dCallOptNames;
	CSphVector<SqlInsert_t>	m_dCallOptValues;
	StrVec_t				m_dCallStrings;

	// UPDATE specific
private:
	mutable AttrUpdateSharedPtr_t	m_pUpdate { nullptr }; // made private for lazy initialization
public:
	int						m_iListStart = -1; // < the position of start and end of index's definition in original query.
	int						m_iListEnd = -1;

	// CREATE/DROP FUNCTION, INSTALL PLUGIN specific
	CSphString				m_sUdfName; // FIXME! move to arg1?
	CSphString				m_sUdfLib;
	ESphAttr				m_eUdfType = SPH_ATTR_NONE;

	// ALTER specific
	CSphString				m_sAlterAttr;
	CSphString				m_sAlterOption;
	ESphAttr				m_eAlterColType = SPH_ATTR_NONE;
	AttrEngine_e			m_eEngine = AttrEngine_e::DEFAULT;
	DWORD					m_uFieldFlags = 0;
	DWORD					m_uAttrFlags = 0;
	int						m_iBits = -1;
	knn::IndexSettings_t	m_tAlterKNN;
	knn::ModelSettings_t	m_tAlterKNNModel;
	CSphString				m_sAlterKnnFrom;
	bool					m_bAlterKnnFromSet = false;

	// CREATE TABLE specific
	CreateTableSettings_t	m_tCreateTable;

	// DROP TABLE specific
	bool					m_bIfExists = false;

	// SHOW THREADS specific
	int						m_iThreadsCols = -1;
	CSphString				m_sThreadFormat;

	// JOIN-specific
	CSphQuery				m_tJoinQueryOptions;

	// generic parameter, different meanings in different statements
	// filter pattern in DESCRIBE, SHOW TABLES / META / VARIABLES
	// target index name in ATTACH
	// token filter options in INSERT
	// plugin type in INSTALL PLUGIN
	// path in RELOAD INDEX
	CSphString				m_sStringParam;

	// generic integer parameter, used in SHOW SETTINGS, default value -1
	// for opt_scope TOK_GLOBAL = 0, TOK_SESSION = 1.
	int						m_iIntParam = -1;

	bool					m_bJson = false;
	bool					m_bShardPhysicalUpdate = false;
	CSphString				m_sEndpoint;
	CSphString				m_sRawQuery;
	CSphString				m_sFullUrl;

	CSphVector<CSphString>	m_dStringSubkeys;
	CSphVector<int64_t>		m_dIntSubkeys;
	bool					m_bForce = false;
	bool					m_bDescTopo = false;
	bool					m_bFormatOutWordsFile = false;

	std::unique_ptr<DebugCmd::DebugCommand_t> m_pDebugCmd;

	SqlStmt_t ();
	~SqlStmt_t();

	SqlStmt_t ( SqlStmt_t&& ) = default;
	SqlStmt_t& operator= ( SqlStmt_t&& ) = default;

	bool AddSchemaItem ( const char * psName );
	// check if the number of fields which would be inserted is in accordance to the given schema
	bool CheckInsertIntegrity();

	CSphAttrUpdate& AttrUpdate() const
	{
		if ( !m_pUpdate )
			m_pUpdate =	new CSphAttrUpdate;
		return *m_pUpdate;
	}

	AttrUpdateSharedPtr_t AttrUpdatePtr() const
	{
		if ( !m_pUpdate )
			m_pUpdate = new CSphAttrUpdate;
		return m_pUpdate;
	}
};

enum class Option_e : BYTE;

class SqlParserTraits_c : ISphNoncopyable
{
public:
	const char *	m_pBuf;
	CSphString *	m_pParseError;
	void *			m_pScanner = nullptr;
	const char *	m_pLastTokenStart = nullptr;
	CSphQuery *		m_pQuery = nullptr;
	SqlStmt_t *		m_pStmt = nullptr;
	CSphString		m_sErrorHeader = "PER:";

	void			PushQuery();
	CSphString &	ToString ( CSphString & sRes, const SqlNode_t & tNode ) const noexcept;
	CSphString		GetString ( const SqlNode_t& tNode ) const noexcept;
	Str_t			GetStrt ( const SqlNode_t& tNode ) const noexcept;
	CSphString		ToStringUnescape ( const SqlNode_t & tNode ) const;
	float			ToFloat ( const SqlNode_t & tNode ) const { return (float) strtod ( m_pBuf+tNode.m_iStart, nullptr ); }
	void			ProcessParsingError ( const char* szMessage );
	bool 			IsWrongSyntaxError() const noexcept;

	bool			AddOption ( const SqlNode_t & tIdent, const SqlNode_t & tValue );
	bool			AddOption ( const SqlNode_t & tIdent, const SqlNode_t & tValue, const SqlNode_t & sArg );
	bool			AddOption ( const SqlNode_t & tIdent, CSphVector<CSphNamedVariant> & dNamed );
	void			DefaultOk ( std::initializer_list<const char*> sList = {} );
	void			SetIndex ( const SqlNode_t& tNode ) const;
	void			SetIndex ( const CSphString& sIndex ) const;
	void 			Comment ( const SqlNode_t& tNode ) const;
	int				AddMvaVec () noexcept;
	AttrValueVec_t&	GetMvaVec (int iIdx) const noexcept;
	AttrValues_p	CloneMvaVecPtr ( int iIdx ) const noexcept;

	void			SetDefaultTableForOptions();
	bool			SetTableForOptions ( const SqlNode_t & tNode );
	bool			NumIsSaturated ( uint64_t uValue, bool bNegative );

protected:
	CSphVector<SqlStmt_t> &		m_dStmt;
	CSphVector<AttrValueVec_t>	m_dMultiValues;
	CSphQuery *					m_pQueryForOptions = nullptr;

					SqlParserTraits_c ( CSphVector<SqlStmt_t> &	dStmt, const char* szQuery, CSphString* pError );

	bool			CheckInteger ( const CSphString& sOpt, const CSphString& sVal ) const;
	virtual bool	CheckOption ( Option_e eOption ) const;

private:
	bool m_bWrongParserSyntaxError = false;
};


bool	sphParseSqlQuery ( Str_t sQuery, CSphVector<SqlStmt_t> & dStmt, CSphString & sError, ESphCollation eCollation );
bool	PercolateParseFilters ( const char * sFilters, ESphCollation eCollation, const CSphSchema & tSchema, CSphVector<CSphFilterSettings> & dFilters, CSphVector<FilterTreeItem_t> & dFilterTree, CSphString & sError );
void	SqlParser_SplitClusterIndex ( CSphString & sIndex, CSphString * pCluster );
void	InitParserOption();
bool	FormatScrollSettings ( const AggrResult_t & tAggrRes, const CSphQuery & tQuery, CSphString & sSettings );

enum class AddOption_e
{
	NOT_FOUND,
	ADDED,
	FAILED
};

AddOption_e AddOption ( CSphQuery & tQuery, const CSphString & sOpt, const CSphString & sVal, const CSphString & sValOrig, const std::function<CSphString ()> & fnGetUnescaped, SqlStmt_e eStmt, CSphString & sError );
AddOption_e AddOption ( CSphQuery & tQuery, const CSphString & sOpt, const CSphString & sValue, int64_t iValue, SqlStmt_e eStmt, CSphString & sError );
AddOption_e AddOption ( CSphQuery & tQuery, const CSphString & sOpt, CSphVector<CSphNamedVariant> & dNamed, SqlStmt_e eStmt, CSphString & sError );
AddOption_e AddOptionRanker ( CSphQuery & tQuery, const CSphString & sOpt, const CSphString & sVal, const std::function<CSphString ()> & fnGetUnescaped, SqlStmt_e eStmt, CSphString & sError );

enum class ParseResult_e { PARSE_OK, PARSE_ERROR, PARSE_SYNTAX_ERROR };

struct ParsedOption_t
{
	int64_t m_iValue = 0;
	float m_fValue = 0.0;
	bool m_bValue = false;
	CSphString m_sValue;
};


#endif // _searchdsql_
