#ifndef SQLITESTATEMENT_H #define SQLITESTATEMENT_H #include "common/utils.h" #include "parser/token.h" #include #include #include #include #include #include // TODO start using attach() in most cases where setParent() is used class SqliteStatement; typedef QSharedPointer SqliteStatementPtr; /** * @defgroup sqlite_statement Parser result containers */ /** * @ingroup sqlite_statement * * @brief General container for any type of parsed object. * * Statements can have multiple children statements and single parent statement. * This way they create a tree of objects. Since those statements represent various language * statements (here SQL), such tree is also called an Abstract Syntax Tree (aka AST). * * The AST lets you to examine structure of the query in a logical manner. * In other words, once you parse query into AST, you have a tree of object, where each oject in * the tree represents some part of the query and provides all its parameters as member variables. * * Deleting single statement causes all it's children to be deleted automatically. * * @section output_from_parser SqliteStatement as output from Parser * * The SqliteStatement is the most generic representation of Parser processing results. * Every parsed query is represented by its specialized, derived class, but it's also * the SqliteQuery and every SqliteQuery is also the SqliteStatement. * * Apart from SqliteQuery objects, which represent complete SQLite queries, there are also * other statements, like expressions (http://sqlite.org/lang_expr.html) and others. * Those statements don't inherit from SqliteQuery, but directly from SqliteStatement. * * Every parsed statement contains list of tokens that were used to parse this statement * in SqliteStatement::tokens. * * There is also SqliteStatement::tokensMap, which is a table mapping grammar rule name * into tokens used to fulfill that rule. To learn possible keys for each SqliteStatement, * you have to look into sqlite3.y file and see definition of the statement, * that you're examining SqliteStatement::tokensMap for. * * @note SqliteStatement::tokensMap is a low level API and it's not very predictible, * unless you get to know it very well. That's why it's not recommended to use it. * * Example of working with SqliteStatement::tokensMap: you have a SqliteAttachPtr from the parser. * You can learn the "attach name" from SqliteAttach::name, like this: * @code * QString name; * if (attachPtr->name) * name = attachPtr->name->detokenize(); * @endcode * or you can use tokensMap like this: * @code * QString name; * if (attachPtr->tokensMap["expr2"]) * name = attachPtr->tokensMap["expr2"]->detokenize(); * @endcode * * Why using tokensMap, when you can read values from object member easly? Well, object members * have plain values (string, integer, etc), while tokensMap has tokens, so you can examine * at which character exactly was the value placed, where it ended, etc. * * @section query_generation SqliteStatement as utility for generating query string * * Generating query string with SqliteStatement makes sense only in case, when you have parsed * query and got SqliteStatement as a result. You can modify some parameters of the query * and detokenize it back to SQL string. This is done in 4 steps: *
    *
  • Parse SQL query string,
  • *
  • Modify values in parsed statements,
  • *
  • Re-generate tokens in all modified statements,
  • *
  • Detokenize tokens from statements.
  • *
* * This is how it's usually done: * @code * // STEP 1 * Parser parser; * if (!parser.parse("SELECT column FROM test WHERE value = 5") || parser.getQueries().size() == 0) * { * // handle parsing error, or no queries parsed (which is also some kind of error) * return; * } * * SqliteQueryPtr query = parser.getQueries().first(); * SqliteSelectPtr select = query.dynamicCast(); * if (!select) * { * // we want to deal with the SELECT only * return; * } * * // STEP 2 * SqliteSelect::Core* core = select->coreSelects.first(); * * // Prepare new result column statement * SqliteSelect::Core::ResultColumn* resCol = new SqliteSelect::Core::ResultColumn(); * * SqliteExpr* expr = new SqliteExpr(); // create expression for result column * expr->initLiteral("test value"); // let the expression be a constant literal value * * resCol->attach(resCol->expr, expr); // put the defined expression into result column statement * core->attach(core->resultColumns, resCol); // add new result column to rest of columns * * // STEP 3 * select->rebuildTokens(); * * // STEP 4 * QString newQuery = select->detokenize(); * @endcode * * In the result, the newQuery will contain: SELECT column, 'test value' FROM test WHERE value = 5. * * @warning It is important to use SqliteStatement::attach() and SqliteStatement::detach() * when modifying AST layout. The tree hierarchy is used to delete objects recurrently, * so deleting the SqliteSelect will also delete all it's children. Assembling or disassembling statements * manually (without SqliteStatement::attach() and SqliteStatement::detach()) is not safe and will most likely * result in a memory leak, or application crash. * * For example of SqliteStatement::detach() usage see section below. * * @section ptr_vs_shared_ptr C++ pointer to SqliteStatement vs. SqliteStatementPtr * * SqliteStatementPtr is a shared pointer (QSharedPointer) to SqliteStatement. All derived classes * also have variations of their types as shared pointers. However only the top level objects * returned from the Parser are actually provided as shared pointers. All children objects are * regular C++ pointers. The reason behind this is to avoid memory leaks. Top level objects from Parser * are shared pointers, so you can use them casually, without worrying who and when should delete them. * On the other hand, any children of those top level objects are regular pointers, cause they will * be deleted automatically when their parent is deleted. * * Sometimes you might want to use just some child statement from parsed query. Normally you would need to * assign that child statement to some local variable and reset its parent to nullptr. Fortunately * SqliteStatement provides handful method SqliteStatement::detach(), which does all that for you. * It also provides detached statement as a new shared pointer, so it's easier to manage it. * Additionally there's a template version of detach() method which can return detached statement * as provided statement type. * * Example: * @code * Parser parser; * if (!parser.parse("SELECT column FROM test WHERE value = 5") || parser.getQueries().size() == 0) * { * // handle parsing error, or no queries parsed (which is also some kind of error) * return SqliteExprPtr(); * } * * SqliteQueryPtr query = parser.getQueries().first(); * SqliteSelectPtr select = query.dynamicCast(); * if (!select) * { * // we want to deal with the SELECT only * return SqliteExprPtr(); * } * * SqliteSelect::Core* core = select->coreSelects.first(); * * // Our point is to get the SqliteExpr which represents the result column "column". * SqliteExprPtr expr = core->resultColumns.first().detach(); * return expr; * @endcode * * After the above parser goes out of scope, so it's deleted and all its parsed * queries get deleted as well, because their shared pointers were not copied anywhere else. * The captured expr would normally also be deleted, but when we detached it, it became * an independed entity, with its own lifetime. * * For the opposite operation, use SqliteStatement::attach(). */ class API_EXPORT SqliteStatement : public QObject { Q_OBJECT public: struct FullObject { enum Type { TABLE, INDEX, TRIGGER, VIEW, DATABASE, NONE }; bool isValid() const; Type type = NONE; TokenPtr database; TokenPtr object; }; SqliteStatement(); SqliteStatement(const SqliteStatement& other); virtual ~SqliteStatement(); QString detokenize(); Range getRange(); SqliteStatement* findStatementWithToken(TokenPtr token); SqliteStatement* findStatementWithPosition(quint64 cursorPosition); SqliteStatement* parentStatement(); QList childStatements(); QStringList getContextColumns(bool checkParent = true, bool checkChilds = true); QStringList getContextTables(bool checkParent = true, bool checkChilds = true); QStringList getContextDatabases(bool checkParent = true, bool checkChilds = true); TokenList getContextColumnTokens(bool checkParent = true, bool checkChilds = true); TokenList getContextTableTokens(bool checkParent = true, bool checkChilds = true); TokenList getContextDatabaseTokens(bool checkParent = true, bool checkChilds = true); QList getContextFullObjects(bool checkParent = true, bool checkChilds = true); void rebuildTokens(); void attach(SqliteStatement*& memberForChild, SqliteStatement* childStatementToAttach); SqliteStatementPtr detach(); void processPostParsing(); virtual SqliteStatement* clone() = 0; template T* typeClone() { return dynamic_cast(clone()); } template void attach(QList& listMemberForChild, T* childStatementToAttach) { listMemberForChild << childStatementToAttach; childStatementToAttach->setParent(this); } template QSharedPointer detach() {return detach().dynamicCast();} template QList getAllTypedStatements() { QList results; T* casted = dynamic_cast(this); if (casted) results << casted; for (SqliteStatement* stmt : getContextStatements(this, false, true)) results += stmt->getAllTypedStatements(); return results; } /** * @brief Ordered list of all tokens for this statement. * An ordered list of tokens that represent current statement. Tokens include * Token::SPACE and Token::COMMENT types, so detokenizing this list results * in the equal SQL string as was passed to the parser at the begining. */ TokenList tokens; /** * @brief Map of grammar terminals and non-terminals into their tokens. * This is map of ordered token lists that represent each node of SQLite grammar definition * used to build this statement. For example grammar definition: * test ::= value1 TERMINAL_TOKEN value2 * will result in tokens map containing following entries: * value1 = {list of tokens from value1} * TERMINAL_TOKEN = {list of only one token: TERMINAL_TOKEN} * value2 = {list of tokens from value2} * * In case there are two non-terminals with same name used (for example "name name name"), * then first non-terminal is used as a key just as is, but second is renamed to "name2", * and third to "name3". If we had example: * test ::= value TERMINAL_TOKEN value * then it will result in tokens map containing following entries: * value = {list of tokens from first value} * TERMINAL_TOKEN = {list of only one token: TERMINAL_TOKEN} * value2 = {list of tokens from second value} */ QHash tokensMap; protected: QStringList getContextColumns(SqliteStatement* caller, bool checkParent, bool checkChilds); QStringList getContextTables(SqliteStatement* caller, bool checkParent, bool checkChilds); QStringList getContextDatabases(SqliteStatement* caller, bool checkParent, bool checkChilds); TokenList getContextColumnTokens(SqliteStatement* caller, bool checkParent, bool checkChilds); TokenList getContextTableTokens(SqliteStatement* caller, bool checkParent, bool checkChilds); TokenList getContextDatabaseTokens(SqliteStatement* caller, bool checkParent, bool checkChilds); QList getContextFullObjects(SqliteStatement* caller, bool checkParent, bool checkChilds); virtual QStringList getColumnsInStatement(); virtual QStringList getTablesInStatement(); virtual QStringList getDatabasesInStatement(); virtual TokenList getColumnTokensInStatement(); virtual TokenList getTableTokensInStatement(); virtual TokenList getDatabaseTokensInStatement(); virtual QList getFullObjectsInStatement(); virtual TokenList rebuildTokensFromContents(); virtual void evaluatePostParsing(); static TokenList extractPrintableTokens(const TokenList& tokens, bool skipMeaningless = true); QStringList getStrListFromValue(const QString& value); TokenList getTokenListFromNamedKey(const QString& tokensMapKey, int idx = 0); TokenPtr getDbTokenFromFullname(const QString& tokensMapKey = "fullname"); TokenPtr getObjectTokenFromFullname(const QString& tokensMapKey = "fullname"); TokenPtr getDbTokenFromNmDbnm(const QString& tokensMapKey1 = "nm", const QString& tokensMapKey2 = "dbnm"); TokenPtr getObjectTokenFromNmDbnm(const QString& tokensMapKey1 = "nm", const QString& tokensMapKey2 = "dbnm"); TokenList getDbTokenListFromFullname(const QString& tokensMapKey = "fullname"); TokenList getObjectTokenListFromFullname(const QString& tokensMapKey = "fullname"); TokenList getDbTokenListFromNmDbnm(const QString& tokensMapKey1 = "nm", const QString& tokensMapKey2 = "dbnm"); TokenList getObjectTokenListFromNmDbnm(const QString& tokensMapKey1 = "nm", const QString& tokensMapKey2 = "dbnm"); FullObject getFullObjectFromFullname(FullObject::Type type, const QString& tokensMapKey = "fullname"); FullObject getFullObjectFromNmDbnm(FullObject::Type type, const QString& tokensMapKey1 = "nm", const QString& tokensMapKey2 = "dbnm"); FullObject getFirstDbFullObject(); FullObject getDbFullObject(TokenPtr dbToken); FullObject getFullObject(SqliteStatement::FullObject::Type type, TokenPtr dbToken, TokenPtr objToken); void setContextDbForFullObject(TokenPtr dbToken); /** * @brief Token representing a database. * Keeps db context for getFullObjectsInStatement(), so for example "CREATE TABLE xyz.abc (id)" will know, * that for column "id" the database is "xyz" and table "abc". The value is spread across childrens * of this statement when getContextFullObjects() is called. * The value of this variable is defined in overwritten implementation of getFullObjectsInStatement() method. */ TokenPtr dbTokenForFullObjects; /** * @brief List of database names as seen in the side Database List. * It is resoled at top-level statement being queried for databases and then it's propagated down to all child statements. * It helps to identify whether the xyz in "xyz." is a table or a database prefix. */ QStringList validDbNames; private: QList getContextStatements(SqliteStatement* caller, bool checkParent, bool checkChilds); void prepareDbNames(); }; #endif // SQLITESTATEMENT_H