#ifndef SELECTRESOLVER_H #define SELECTRESOLVER_H #include "parser/ast/sqliteselect.h" #include "common/bistrhash.h" #include "dialect.h" #include "expectedtoken.h" #include #include #include #include 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 { Table(); Table(const Table& other); /** * @brief Database name. * * Either sqlite name, like "main", or "temp", or an attach name. */ QString database; QString originalDatabase; QString table; QString tableAlias; QStringList oldTableAliases; int flags = 0; int operator==(const Table& other); void pushTableAlias(); }; /** * @brief Result column resolved by the resolver. */ struct API_EXPORT Column : public Table { enum Type { COLUMN, OTHER }; Type type; /** * @brief Column name or expression. * * If a column is of OTHER type, then column member contains detokenized column expression. */ QString column; QString alias; 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 resolveColumnsFromFirstCore(); QList > resolveColumns(); QList resolve(SqliteSelect::Core* selectCore); QList > resolve(SqliteSelect* select); QList resolveAvailableColumns(SqliteSelect::Core* selectCore); QList > resolveAvailableColumns(SqliteSelect* select); QSet resolveTables(SqliteSelect::Core* selectCore); QList > 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 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 resolveCore(SqliteSelect::Core* selectCore); QList 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 resolveJoinSource(SqliteSelect::Core::JoinSource* joinSrc); QList resolveSingleSource(SqliteSelect::Core::SingleSource* joinSrc); QList resolveSingleSourceSubSelect(SqliteSelect::Core::SingleSource* joinSrc); QList resolveOtherSource(SqliteSelect::Core::JoinSourceOther *otherSrc); QList resolveSubSelect(SqliteSelect* select); QList 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& 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 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 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 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