aboutsummaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/coreSQLiteStudio/selectresolver.h
blob: 1edfcb9846b2ad74c683119445093e729e70e672 (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
#ifndef SELECTRESOLVER_H
#define SELECTRESOLVER_H

#include "parser/ast/sqliteselect.h"
#include "common/bistrhash.h"
#include "dialect.h"
#include "expectedtoken.h"
#include <QString>
#include <QHash>
#include <QStringList>

class Db;
class SchemaResolver;

/**
 * @brief Result column introspection tool
 * The SelectResolver provides full information about what would
 * result columns be for given SELECT statement. It investigates
 * deeply "FROM" clause and the typed result column list
 * and in produces list of column objects where each of them
 * is described by it's source table, column name in that table,
 * a database and a column alias (if any).
 *
 * The database is a name database as seen by SQLite, which is
 * for example "main", "temp", or any name used with "ATTACH".
 *
 * If the column is not related to the table, but instead is
 * an expression, then it's type is set to "OTHER".
 *
 * If column's table is named with an alias, then it is also provided.
 *
 * The displayName field describes how would the column be named
 * by SQLite itself if it was returned in query results.
 *
 * The returned column object has also a reference to original
 * SqliteSelect::Core::ResultColumn object, so that one can relate
 * which queried column produced given column object in this class.
 *
 * Result column like "table.*" will produce one or more column
 * objects from this class.
 *
 * There's one unsupported case: When the select has a subselect
 * in "FROM" clause and that subselect is actually a multi-core
 * select (with UNIONs), then columns produced from such source
 * won't be related to any table, because currently it's impossible
 * for SelectResolver to tell from which table of multi-core
 * subselect the column is read from. Therefore in this case
 * the column object has it's name, but no table or database.
 */
class API_EXPORT SelectResolver
{
    public:
        enum Flag
        {
            FROM_COMPOUND_SELECT = 0x01,
            FROM_ANONYMOUS_SELECT = 0x02,
            FROM_DISTINCT_SELECT = 0x04,
            FROM_GROUPED_SELECT = 0x08,
            FROM_CTE_SELECT = 0x10
        };

        /**
         * @brief Table resolved by the resolver.
         */
        struct API_EXPORT Table
        {
            /**
             * @brief Database name.
             *
             * Either sqlite name, like "main", or "temp", or an attach name.
             */
            QString database;
            QString originalDatabase;
            QString table;
            QString alias;
            int flags = 0;

            int operator==(const Table& other);
        };

        /**
         * @brief Result column resolved by the resolver.
         */
        struct API_EXPORT Column
        {
            enum Type
            {
                COLUMN,
                OTHER
            };

            Type type;

            /**
             * @brief Database name.
             *
             * Either sqlite name, like "main", or "temp", or an attach name.
             */
            QString database;
            QString originalDatabase;
            QString table;

            /**
             * @brief Column name or expression.
             *
             * If a column is of OTHER type, then column member contains detokenized column expression.
             */
            QString column;
            QString alias;
            QString tableAlias;
            QString displayName;
            bool aliasDefinedInSubQuery = false;
            int flags = 0;
            SqliteSelect::Core::ResultColumn* originalColumn = nullptr;

            int operator==(const Column& other);
            Table getTable() const;
        };

        SelectResolver(Db* db, const QString &originalQuery);
        SelectResolver(Db* db, const QString &originalQuery, const BiStrHash& dbNameToAttach);
        ~SelectResolver();

        QList<Column> resolveColumnsFromFirstCore();
        QList<QList<Column> > resolveColumns();

        QList<Column> resolve(SqliteSelect::Core* selectCore);
        QList<QList<Column> > resolve(SqliteSelect* select);

        QList<Column> resolveAvailableColumns(SqliteSelect::Core* selectCore);
        QList<QList<Column> > resolveAvailableColumns(SqliteSelect* select);

        QSet<Table> resolveTables(SqliteSelect::Core* selectCore);
        QList<QSet<Table> > resolveTables(SqliteSelect* select);

        /**
         * @brief Translates tokens representing column name in the SELECT into full column objects.
         * @param select Select statement containing all queried tokens.
         * @param columnTokens Column tokens to translate.
         * @return Full column objects with table and database fields filled in, unless translation failed for some column.
         * This method lets you to get full information about columns being specified anywhere in the select,
         * no matter if they were typed with database and table prefix or not. It uses smart algorighms
         * from the very same class to resolve all available columns, given all data sources of the select,
         * finds the queried column token in those available columns  and creates full column object.
         * Every column token passed to the method will result in a column objects in the results.
         * If for some reason the translation was not possible, then the respective column in the results will
         * have an OTHER type, while successfully translated columns will have a COLUMN type.
         *
         * In other words, given the column names (as tokens), this methods finds an occurance of this token in
         * the given select statement and provides information in what context was the column used (database, table).
         *
         * This method is used for example in TableModifier to find out what columns from the modified table
         * were used in the referencing CREATE VIEW statements.
         */
        QList<Column> translateToColumns(SqliteSelect* select, const TokenList& columnTokens);

        /**
         * @brief Translates token representing column name in the SELECT into full column objects.
         * @param select Select statement containing queried token.
         * @param token Column token to translate.
         * @return Full column object with table and database fields filled in.
         * This method does pretty much the same thing as #translateToColumns(SqliteSelect*,const TokenList&),
         * except it takes only one token as an argument.
         *
         * Internally this method is used by #translateToColumns(SqliteSelect*,const TokenList&) in a loop.
         */
        Column translateToColumns(SqliteSelect* select, TokenPtr token);

        /**
         * @brief Tells whether there were any errors during resolving.
         * @return true if there were any errors, or false otherwise.
         */
        bool hasErrors() const;

        /**
         * @brief Provides list of errors encountered during resolving.
         * @return List of localized error messages.
         */
        const QStringList& getErrors() const;

        /**
         * @brief resolveMultiCore
         * If true (by default), the multi-core subselects will be resolved using
         * first core from the list. If false, then the subselect will be ignored by resolver,
         * which will result in empty columns and/or tables resolved for that subselect.
         */
        bool resolveMultiCore = true;

        /**
         * @brief ignoreInvalidNames
         * If true, then problems with resolving real objects in sqlite_master mentioned in the select,
         * are ignored silently. Otherwise those accidents are reported with qDebug().
         */
        bool ignoreInvalidNames = false;

    private:
        QList<Column> resolveCore(SqliteSelect::Core* selectCore);
        QList<Column> resolveAvailableCoreColumns(SqliteSelect::Core* selectCore);
        Column translateTokenToColumn(SqliteSelect* select, TokenPtr token);
        void resolve(SqliteSelect::Core::ResultColumn* resCol);
        void resolveStar(SqliteSelect::Core::ResultColumn* resCol);
        void resolveExpr(SqliteSelect::Core::ResultColumn* resCol);
        void resolveDbAndTable(SqliteSelect::Core::ResultColumn *resCol);
        Column resolveRowIdColumn(SqliteExpr* expr);
        Column resolveExplicitColumn(const QString& columnName);
        Column resolveExplicitColumn(const QString& table, const QString& columnName);
        Column resolveExplicitColumn(const QString& database, const QString& table, const QString& columnName);

        QList<Column> resolveJoinSource(SqliteSelect::Core::JoinSource* joinSrc);
        QList<Column> resolveSingleSource(SqliteSelect::Core::SingleSource* joinSrc);
        QList<Column> resolveSingleSourceSubSelect(SqliteSelect::Core::SingleSource* joinSrc);
        QList<Column> resolveOtherSource(SqliteSelect::Core::JoinSourceOther *otherSrc);
        QList<Column> resolveSubSelect(SqliteSelect* select);
        QList<Column> resolveView(const QString& database, const QString& name, const QString &alias);
        bool isView(const QString& database, const QString& name);
        QStringList getTableColumns(const QString& database, const QString& table, const QString &alias);
        void applySubSelectAlias(QList<Column>& columns, const QString& alias);
        QString resolveDatabase(const QString& database);
        bool parseOriginalQuery();

        void markDistinctColumns();
        void markCompoundColumns();
        void markCteColumns();
        void markGroupedColumns();
        void fixColumnNames();
        void markCurrentColumnsWithFlag(Flag flag);
        bool matchTable(const Column& sourceColumn, const QString& table);
        TokenList getResColTokensWithoutAlias(SqliteSelect::Core::ResultColumn *resCol);

        Db* db = nullptr;
        QString query;
        SqliteSelectPtr originalQueryParsed;

        /**
         * @brief Database name to attach name map.
         *
         * When this map is defined, then every occurance of the database in the query will be
         * checked against being an attach name and if it is an attach name, then it will be
         * translated into the original database name used in the query using this map.
         *
         * This will result in original database names in "originalDatabase" and "displayName"
         * members returned from the resolver.
         *
         * The map should be collected when the attaching is performed. For example DbAttacher
         * does the job for you - it attaches databases and prepares the map, that you can
         * use here.
         */
        BiStrHash dbNameToAttach;

        /**
         * @brief currentCoreResults
         * List of columns that will be returned from resultColumns of the queried SELECT.
         * Columns are linked to their tables from "FROM" clause if possible,
         * otherwise they have null table name.
         * This list is built progressively when iterating through result columns.
         * Then it's returned from resolve() call.
         */
        QList<Column> currentCoreResults;

        /**
         * @brief tableColumnsCache
         * When the resolver asks database for list of its columns (by PRAGMA table_info()),
         * then it stores results in this cache, becuase it's very likely that this table
         * will be queried for columns more times.
         */
        QHash<Table,QStringList> tableColumnsCache;

        /**
         * @brief currentCoreSourceColumns
         * List of every column available from current selectCore sources.
         * There can be many columns with same database and table.
         * It can be also interpreted as list of all available tables in the "FROM" clause
         * (but only at the top level, recursive subselects or subjoins).
         * This list is created at the begining of resolve() call, before any result column
         * is being actually resolved. This is also created by resolveTables() call
         * and in that case no result columns are being resolved, just this list is being
         * converted into the list of SqliteStatement::Table and returned.
         *
         * Note, that some entries in this list will have only column name filled in
         * and no table related information - this happens when the column comes from
         * subselect, where it was not a table related result column.
         */
        QList<Column> currentCoreSourceColumns;

        /**
         * @brief schemaResolver
         * Used to get list of column names in a table.
         */
        SchemaResolver* schemaResolver = nullptr;

        /**
         * @brief List of errors encountered during resolving.
         *
         * This may contain errors like missing table alias that was used in result column list, etc.
         */
        QStringList errors;
};

API_EXPORT int operator==(const SelectResolver::Table& t1, const SelectResolver::Table& t2);
API_EXPORT uint qHash(const SelectResolver::Table& table);

API_EXPORT int operator==(const SelectResolver::Column& c1, const SelectResolver::Column& c2);
API_EXPORT uint qHash(const SelectResolver::Column& column);

#endif // SELECTRESOLVER_H