aboutsummaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitestatement.h
blob: 794b6de9a6186988129cb6f5a1e9f0d56a6419e8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
#ifndef SQLITESTATEMENT_H
#define SQLITESTATEMENT_H

#include "common/utils.h"
#include "parser/token.h"
#include <QList>
#include <QHash>
#include <QObject>
#include <QPair>
#include <QStringList>
#include <QSharedPointer>

// TODO start using attach() in most cases where setParent() is used

class SqliteStatement;
typedef QSharedPointer<SqliteStatement> 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:
 * <ul>
 * <li>Parse SQL query string,</li>
 * <li>Modify values in parsed statements,</li>
 * <li>Re-generate tokens in all modified statements,</li>
 * <li>Detokenize tokens from statements.</li>
 * </ul>
 *
 * 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<SqliteSelect>();
 * 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: <tt>SELECT column, 'test value' FROM test WHERE value = 5</tt>.
 *
 * @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<SqliteSelect>();
 * 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<SqliteExpr>();
 * return expr;
 * @endcode
 *
 * After the above <tt>parser</tt> 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 <tt>expr</tt> 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<SqliteStatement*> 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<FullObject> getContextFullObjects(bool checkParent = true, bool checkChilds = true);
        void rebuildTokens();
        void attach(SqliteStatement*& memberForChild, SqliteStatement* childStatementToAttach);
        SqliteStatementPtr detach();
        void processPostParsing();
        virtual SqliteStatement* clone() = 0;

        template <class T>
        T* typeClone()
        {
            return dynamic_cast<T*>(clone());
        }

        template <class T>
        void attach(QList<T*>& listMemberForChild, T* childStatementToAttach)
        {
            listMemberForChild << childStatementToAttach;
            childStatementToAttach->setParent(this);
        }

        template <class X>
        QSharedPointer<X> detach() {return detach().dynamicCast<X>();}

        template <class T>
        QList<T*> getAllTypedStatements()
        {
            QList<T*> results;

            T* casted = dynamic_cast<T*>(this);
            if (casted)
                results << casted;

            for (SqliteStatement* stmt : getContextStatements(this, false, true))
                results += stmt->getAllTypedStatements<T>();

            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<QString,TokenList> 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<FullObject> 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<FullObject> 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<SqliteStatement*> getContextStatements(SqliteStatement* caller, bool checkParent, bool checkChilds);
        void prepareDbNames();
};

#endif // SQLITESTATEMENT_H