aboutsummaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/coreSQLiteStudio/selectresolver.h
diff options
context:
space:
mode:
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio/selectresolver.h')
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/selectresolver.h299
1 files changed, 299 insertions, 0 deletions
diff --git a/SQLiteStudio3/coreSQLiteStudio/selectresolver.h b/SQLiteStudio3/coreSQLiteStudio/selectresolver.h
new file mode 100644
index 0000000..c634c5c
--- /dev/null
+++ b/SQLiteStudio3/coreSQLiteStudio/selectresolver.h
@@ -0,0 +1,299 @@
+#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;
+ int flags = 0;
+ SqliteSelect::Core::ResultColumn* originalColumn = nullptr;
+
+ int operator==(const Column& other);
+ Table getTable();
+ };
+
+ SelectResolver(Db* db, const QString &originalQuery);
+ SelectResolver(Db* db, const QString &originalQuery, const BiStrHash& dbNameToAttach);
+ ~SelectResolver();
+
+ 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);
+
+ 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;
+
+ /**
+ * @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