diff options
| author | 2014-12-06 17:33:25 -0500 | |
|---|---|---|
| committer | 2014-12-06 17:33:25 -0500 | |
| commit | 7167ce41b61d2ba2cdb526777a4233eb84a3b66a (patch) | |
| tree | a35c14143716e1f2c98f808c81f89426045a946f /SQLiteStudio3/coreSQLiteStudio | |
Imported Upstream version 2.99.6upstream/2.99.6
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio')
364 files changed, 73428 insertions, 0 deletions
diff --git a/SQLiteStudio3/coreSQLiteStudio/TODO.txt b/SQLiteStudio3/coreSQLiteStudio/TODO.txt new file mode 100644 index 0000000..a86a49b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/TODO.txt @@ -0,0 +1,66 @@ +* Next versions: +- commiting DataView should be async +- syntax checkers as services - per language +- code assistants as services - per language +- specialized validation of expressions for DEFAULT constraint. +- "recovery" after failed startup - detecting if previous start crashed and if yes, propose cleaning of configuration. +- tcl highlighter +- plugin to do performance testing +- plugins to generate artifacts +- qtscript syntax checker +- tcl syntax checker +- dbf import +- dbf export +- code assistant as a service with plugins, so it can be extended with other langs and injected in custom functions window, collations window, etc +- in configuration dialog option to disable each individual native SQL function +- move "integrity check" into dedicated window, add "PRAGMA foreign_key_check" as a second stage of checking and present all in one window +- tips&tricks dialog +- crash management +- SqlEditor::refreshValidObjects() doesn't add valid object names from other databases (not yet attached). It might be tricky to implement. +- need an idea for some smart "revert" or "backup", so users are protected from accidentaly deleting table, or data in table. +- expose query executor steps chain to plugins +- complete plugin for "Search in database(s)" +- complete plugin for compare tables/databases +- executing queries with bind parameters +- completer: when suggesting table in FROM clause, look at columns after SELECT to give related tables first. +- constraints tab in table window should have toolbar for adding/editing/deleting constraints + +CLI: +- plugin management commands +- export commands +- import commands +- populate commands +- formatting command + +UNIT TESTS: +- Parser::parse: more complex examples, errors detecting +- Lexer::detokenize +- utils_sql (for splitQueries() check the CREATE TRIGGER) +- utils +- SelectResolver +- SchemaResolver + +* Advanced syntax error checks: +- many idxColumns are not allowed for column FK +- autoincrement not allowed for not integer type + + + + + + + + + +Qt mingw: +Not an easy task. I got Perl, Python, Ruby and MinGw/bin in path and +use this line + +configure -opensource -confirm-license -platform win32-g++ -make libs +-qt-libjpeg -qt-libpng -no-openssl -no-icu -qt-zlib -qt-pcre +-no-iconv -nomake examples -nomake tests -qt-style-windowsxp +-qt-style-windowsvista -opengl +desktop + +Qt linux: +./configure -no-icu -nomake examples -nomake tests -no-dbus -opensource -skip webkit -skip quickcontrols -prefix /home/spakowane/qt5.3.1/output diff --git a/SQLiteStudio3/coreSQLiteStudio/committable.cpp b/SQLiteStudio3/coreSQLiteStudio/committable.cpp new file mode 100644 index 0000000..892437a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/committable.cpp @@ -0,0 +1,41 @@ +#include "committable.h" +#include <QDebug> + +Committable::ConfirmFunction Committable::confirmFunc = nullptr; +QList<Committable*> Committable::instances; + +Committable::Committable() +{ + instances << this; +} + +Committable::~Committable() +{ + instances.removeOne(this); +} + +void Committable::init(Committable::ConfirmFunction confirmFunc) +{ + Committable::confirmFunc = confirmFunc; +} + +bool Committable::canQuit() +{ + if (!confirmFunc) + { + qCritical() << "No confirm function defined for Committable!"; + return true; + } + + QList<Committable*> uncommitedInstances; + for (Committable* c : instances) + { + if (c->isUncommited()) + uncommitedInstances << c; + } + + if (uncommitedInstances.size() == 0) + return true; + + return confirmFunc(uncommitedInstances); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/committable.h b/SQLiteStudio3/coreSQLiteStudio/committable.h new file mode 100644 index 0000000..cf1d48b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/committable.h @@ -0,0 +1,27 @@ +#ifndef COMMITTABLE_H +#define COMMITTABLE_H + +#include "coreSQLiteStudio_global.h" +#include <QList> +#include <functional> + +class API_EXPORT Committable +{ + public: + typedef std::function<bool(const QList<Committable*>& instances)> ConfirmFunction; + + Committable(); + virtual ~Committable(); + + virtual bool isUncommited() const = 0; + virtual QString getQuitUncommitedConfirmMessage() const = 0; + + static void init(ConfirmFunction confirmFunc); + static bool canQuit(); + + private: + static ConfirmFunction confirmFunc; + static QList<Committable*> instances; +}; + +#endif // COMMITTABLE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/bihash.h b/SQLiteStudio3/coreSQLiteStudio/common/bihash.h new file mode 100644 index 0000000..cde93d1 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/bihash.h @@ -0,0 +1,302 @@ +#ifndef BIHASH_H
+#define BIHASH_H
+
+#include <QHash>
+
+/**
+ * @brief Bi-directional QHash
+ *
+ * Bi-directional hash treats both inserted values as keys to each other.
+ * Bi-directional hash built on the <tt>left</tt> and <tt>right</tt> values concept.
+ *
+ * It's not multi-value hash, so when you try to insert existing value to any
+ * of sides (left or right), it will replace the whole conflicting entry.
+ *
+ * It doesn't provide operator[], because returning reference to an object,
+ * which then can be changed outside would desynchronize internals of this hash
+ * (internal inverted map could not be synchronized properly according to changes
+ * to the external reference).
+ */
+template <class L, class R>
+class BiHash
+{
+ public:
+ /**
+ * @brief Creates empty hash.
+ */
+ BiHash() {}
+
+ /**
+ * @brief Creates hash initialized with given values.
+ * @param list C++11 style initializer list, like: <tt>{{x=y}, {a=b}}</tt>
+ */
+ BiHash(std::initializer_list<std::pair<L, R>> list)
+ {
+ hash = QHash<L,R>(list);
+ initInverted();
+ }
+
+ /**
+ * @brief Creates bi-hash basing on QHash.
+ * @param other QHash to copy data from.
+ *
+ * If multiple keys in the QHash refer to the same value,
+ * this is not defined which one of those values will remain,
+ * but there will definitely be copied only one of them.
+ */
+ BiHash(const QHash<L, R> & other)
+ {
+ unite(other);
+ }
+
+ /**
+ * @brief Creates copy of BiHash.
+ * @param other BiHash to copy.
+ */
+ BiHash(const BiHash<L, R> & other) : hash(other.hash), inverted(other.inverted) {}
+
+ /**
+ * @brief Inserts new values to the hash.
+ * @param left Left value to be inserted.
+ * @param right Right value to be inserted.
+ *
+ * Note that if the \p left is already in left values, then the current entry
+ * for the left will be replaced with this new one.
+ * The same applies for the \p right.
+ *
+ * If it happens, that both \p left and \p right have already entries in the
+ * hash, but those are 2 different entries, then the insert operation will
+ * remove both conflicting records and insert the new one.
+ */
+ void insert(const L& left, const R& right)
+ {
+ if (hash.contains(left))
+ removeLeft(left);
+
+ if (inverted.contains(right))
+ removeRight(right);
+
+ inverted.insert(right, left);
+ hash.insert(left, right);
+ }
+
+ /**
+ * @brief Tests if left values contain given value.
+ * @param left Value to test.
+ * @return true if left values containe the \p left value.
+ */
+ bool containsLeft(const L& left) const
+ {
+ return hash.contains(left);
+ }
+
+ /**
+ * @brief Tests if right values contain given value.
+ * @param right Value to test.
+ * @return true if right values containe the \p right value.
+ */
+ bool containsRight(const R& right) const
+ {
+ return inverted.contains(right);
+ }
+
+ /**
+ * @brief Removes entry with left value equal to given value.
+ * @param left Value to remove by.
+ * @return Number of removed entries.
+ */
+ int removeLeft(const L& left)
+ {
+ if (!hash.contains(left))
+ return 0;
+
+ inverted.remove(hash.value(left));
+ hash.remove(left);
+
+ return 1;
+ }
+
+ /**
+ * @brief Removes entry with right value equal to given value.
+ * @param right Value to remove by.
+ * @return Number of removed entries.
+ */
+ int removeRight(const R& right)
+ {
+ if (!inverted.contains(right))
+ return 0;
+
+ hash.remove(inverted.value(right));
+ inverted.remove(right);
+
+ return 1;
+ }
+
+ /**
+ * @brief Pops entry with left value equal to given value.
+ * @param left Value to pop by.
+ * @return Right value assigned to given left value.
+ */
+ R takeLeft(const L& left)
+ {
+ R right = hash.take(left);
+ inverted.remove(right);
+ return right;
+ }
+
+ /**
+ * @brief Pops entry with right value equal to given value.
+ * @param right Value to pop by.
+ * @return Left value assigned to given right value.
+ */
+ L takeRight(const R& right)
+ {
+ R left = inverted.take(right);
+ hash.remove(left);
+ return left;
+ }
+
+ /**
+ * @brief Copies all entries from other BiHash to this hash.
+ * @param other BiHash to copy from.
+ * @return Reference to this hash after values are copied.
+ *
+ * Any entries from the \p other hash will overwrite current
+ * entries in case of conflict (by either left or right value).
+ */
+ BiHash<L,R>& unite(const BiHash<L,R>& other)
+ {
+ unite(other.hash);
+ return *this;
+ }
+
+ /**
+ * @overload
+ */
+ BiHash<L,R>& unite(const QHash<L,R>& other)
+ {
+ QHashIterator<L, R> it(other);
+ while (it.hasNext())
+ insert(it.next().key(), it.value());
+
+ return *this;
+ }
+
+ /**
+ * @brief Finds right value associated with given left value.
+ * @param left Left value to match.
+ * @return Right value if found, or default constructed value of right type.
+ */
+ R valueByLeft(const L& left) const
+ {
+ return hash.value(left);
+ }
+
+ /**
+ * @brief Finds left value associated with given right value.
+ * @param right Right value to match.
+ * @return Left value if found, or default constructed value of left type.
+ */
+ L valueByRight(const R& right) const
+ {
+ return inverted.value(right);
+ }
+
+ /**
+ * @brief Provides all left values.
+ * @return List of values from left side.
+ */
+ QList<L> leftValues() const
+ {
+ return hash.keys();
+ }
+
+ /**
+ * @brief Provides all right values.
+ * @return List of values from right side.
+ */
+ QList<R> rightValues() const
+ {
+ return inverted.keys();
+ }
+
+ /**
+ * @brief Provides java-like iterator for the hash.
+ * @return Iterator object for this hash.
+ */
+ QHashIterator<L,R> iterator() const
+ {
+ return QHashIterator<L,R>(hash);
+ }
+
+ /**
+ * @brief Removes all entries from the hash.
+ */
+ void clear()
+ {
+ hash.clear();
+ inverted.clear();
+ }
+
+ /**
+ * @brief Counts all entries in the hash.
+ * @return Number of entries.
+ */
+ int count() const
+ {
+ return hash.count();
+ }
+
+ /**
+ * @brief Tests whether the hash is empty or not.
+ * @return true if the hash is empty, false otherwise.
+ */
+ bool isEmpty() const
+ {
+ return hash.isEmpty();
+ }
+
+ /**
+ * @brief Provides QHash from this BiHash.
+ * @return QHash with left values as keys.
+ */
+ const QHash<L,R>& toQHash() const
+ {
+ return hash;
+ }
+
+ /**
+ * @brief Provides QHash with inverted values (right-to-left)
+ * @return QHash with right values as keys.
+ */
+ const QHash<R,L>& toInvertedQHash() const
+ {
+ return inverted;
+ }
+
+ private:
+ /**
+ * @brief Fills inverted internal hash basing on values from hash class member.
+ */
+ void initInverted()
+ {
+ QHashIterator<L,R> it(hash);
+ while (it.hasNext())
+ {
+ it.next();
+ inverted[it.value()] = it.key();
+ }
+ }
+
+ /**
+ * @brief Hash containing left-to-right mapping.
+ */
+ QHash<L,R> hash;
+
+ /**
+ * @brief Hash containing right-to-left mapping.
+ */
+ QHash<R,L> inverted;
+};
+
+#endif // BIHASH_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/bistrhash.h b/SQLiteStudio3/coreSQLiteStudio/common/bistrhash.h new file mode 100644 index 0000000..65c907b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/bistrhash.h @@ -0,0 +1,362 @@ +#ifndef BISTRHASH_H
+#define BISTRHASH_H
+
+#include "bihash.h"
+#include <QHash>
+#include <QString>
+
+/**
+ * @brief Bi-directional string-oriented hash.
+ *
+ * This hash is very similar to BiHash, except it always uses QString
+ * for both left and right values. Given that, it also provides Qt::CaseSensitivity support
+ * for any operations accepting values in parameters.
+ *
+ * Just like BiHash, the BiStrHash doesn't provide operator[]. For more details see BiHash.
+ */
+class BiStrHash
+{
+ public:
+ /**
+ * @brief Creates empty hash.
+ */
+ BiStrHash() {}
+
+ /**
+ * @brief Creates pre-initialized hash.
+ * @param list C++11 style initializer list, like: <tt>{{"x"="y"}, {"a"="b"}}</tt>
+ */
+ BiStrHash(std::initializer_list<std::pair<QString, QString>> list)
+ {
+ hash = QHash<QString,QString>(list);
+ initInvertedAndLower();
+ }
+
+ /**
+ * @brief Creates BiStrHash basing on QHash.
+ * @param other QHash to copy values from.
+ *
+ * Any conflicting values from the \p other hash will overwrite
+ * current values in the hash.
+ */
+ BiStrHash(const QHash<QString, QString> & other)
+ {
+ unite(other);
+ }
+
+ /**
+ * @brief Copy constructor.
+ * @param other Other hash to copy.
+ */
+ BiStrHash(const BiStrHash& other) : hash(other.hash), inverted(other.inverted),
+ lowerHash(other.lowerHash), lowerInverted(other.lowerInverted) {}
+
+ /**
+ * @brief Inserts entry into the hash.
+ * @param left Left-side value to insert.
+ * @param right Right-side value to insert.
+ *
+ * Inserting to the hash is done in case-insensitive manner, hence any conflicting
+ * values matched with case insensitive method will be replaced with the new entry.
+ */
+ void insert(const QString& left, const QString& right)
+ {
+ if (lowerHash.contains(left.toLower()))
+ removeLeft(left, Qt::CaseInsensitive);
+
+ if (lowerInverted.contains(right.toLower()))
+ removeRight(right, Qt::CaseInsensitive);
+
+ inverted.insert(right, left);
+ hash.insert(left, right);
+ lowerHash.insert(left.toLower(), left);
+ lowerInverted.insert(right.toLower(), right);
+ }
+
+ /**
+ * @brief Tests if given value is in the left values of the hash.
+ * @param left Left-side value to match.
+ * @param cs Case sensitivity flag.
+ * @return true if the key was matched in left side values, or false otherwise.
+ */
+ bool containsLeft(const QString& left, Qt::CaseSensitivity cs = Qt::CaseSensitive)
+ {
+ if (cs == Qt::CaseSensitive)
+ return hash.contains(left);
+ else
+ return lowerHash.contains(left.toLower());
+ }
+
+ /**
+ * @brief Tests if given value is in the right values of the hash.
+ * @param right Right-side value to match.
+ * @param cs Case sensitivity flag.
+ * @return true if the key was matched in right side values, or false otherwise.
+ */
+ bool containsRight(const QString& right, Qt::CaseSensitivity cs = Qt::CaseSensitive)
+ {
+ if (cs == Qt::CaseSensitive)
+ return inverted.contains(right);
+ else
+ return lowerInverted.contains(right.toLower());
+ }
+
+ /**
+ * @brief Removes entry matching given value in left-side values.
+ * @param left Left-side value to match.
+ * @param cs Case sensitivity flag.
+ * @return Number of entries removed.
+ */
+ int removeLeft(const QString& left, Qt::CaseSensitivity cs = Qt::CaseSensitive)
+ {
+ if (cs == Qt::CaseSensitive)
+ {
+ if (!hash.contains(left))
+ return 0;
+
+ inverted.remove(hash.value(left));
+ hash.remove(left);
+
+ return 1;
+ }
+ else
+ {
+ QString lowerLeft = left.toLower();
+ if (!lowerHash.contains(lowerLeft))
+ return 0;
+
+ QString right = hash.value(lowerHash.value(lowerLeft));
+
+ hash.remove(inverted.value(right));
+ inverted.remove(right);
+ lowerHash.remove(lowerLeft);
+ lowerInverted.remove(right.toLower());
+
+ return 1;
+ }
+ }
+
+ /**
+ * @brief Removes entry matching given value in right-side values.
+ * @param right Right-side value to match.
+ * @param cs Case sensitivity flag.
+ * @return Number of entries removed.
+ */
+ int removeRight(const QString& right, Qt::CaseSensitivity cs = Qt::CaseSensitive)
+ {
+ if (cs == Qt::CaseSensitive)
+ {
+ if (!inverted.contains(right))
+ return 0;
+
+ hash.remove(inverted.value(right));
+ inverted.remove(right);
+
+ return 1;
+ }
+ else
+ {
+ QString lowerRight = right.toLower();
+ if (!lowerInverted.contains(lowerRight))
+ return 0;
+
+ QString left = inverted.value(lowerInverted.value(lowerRight));
+
+ inverted.remove(hash.value(left));
+ hash.remove(left);
+ lowerHash.remove(left.toLower());
+ lowerInverted.remove(lowerRight);
+
+ return 1;
+ }
+ }
+
+ /**
+ * @brief Removes entry from hash and returns it.
+ * @param left Left-side value to match.
+ * @param cs Case sensitivity flag.
+ * @return Right side value, or null string if the \p left was not matched.
+ */
+ QString takeLeft(const QString& left, Qt::CaseSensitivity cs = Qt::CaseSensitive)
+ {
+ if (cs == Qt::CaseSensitive)
+ {
+ QString right = hash.take(left);
+ inverted.remove(right);
+ return right;
+ }
+ else
+ {
+ QString right = hash.take(lowerHash.take(left.toLower()));
+ inverted.remove(lowerInverted.take(right.toLower()));
+ return right;
+ }
+ }
+
+ /**
+ * @brief Removes entry from hash and returns it.
+ * @param right Right-side value to match.
+ * @param cs Case sensitivity flag.
+ * @return Left side value, or null string if the \p left was not matched.
+ */
+ QString takeRight(const QString& right, Qt::CaseSensitivity cs = Qt::CaseSensitive)
+ {
+ if (cs == Qt::CaseSensitive)
+ {
+ QString left = inverted.take(right);
+ hash.remove(left);
+ return left;
+ }
+ else
+ {
+ QString left = inverted.take(lowerInverted.take(right.toLower()));
+ hash.remove(lowerHash.take(left.toLower()));
+ return left;
+ }
+ }
+
+ /**
+ * @brief Copies all entries from the other hash to this hash.
+ * @param other Other hash to copy values from.
+ * @return Reference to this hash, after update.
+ */
+ BiStrHash& unite(const BiStrHash& other)
+ {
+ unite(other.hash);
+ return *this;
+ }
+
+ /**
+ * @overload
+ */
+ BiStrHash& unite(const QHash<QString,QString>& other)
+ {
+ QHashIterator<QString, QString> it(other);
+ while (it.hasNext())
+ insert(it.next().key(), it.value());
+
+ return *this;
+ }
+
+ /**
+ * @brief Finds right-side value by matching the left-side value.
+ * @param left Left-side value to match.
+ * @param cs Case sensitivity flag.
+ * @return Right-side value, or null string if left-side value was not matched.
+ */
+ QString valueByLeft(const QString& left, Qt::CaseSensitivity cs = Qt::CaseSensitive) const
+ {
+ if (cs == Qt::CaseSensitive)
+ return hash.value(left);
+ else
+ return hash.value(lowerHash.value(left.toLower()));
+ }
+
+ /**
+ * @brief Finds left-side value by matching the right-side value.
+ * @param right Right-side value to match.
+ * @param cs Case sensitivity flag.
+ * @return Left-side value, or null string if right-side value was not matched.
+ */
+ QString valueByRight(const QString& right, Qt::CaseSensitivity cs = Qt::CaseSensitive) const
+ {
+ if (cs == Qt::CaseSensitive)
+ return inverted.value(right);
+ else
+ return inverted.value(lowerInverted.value(right.toLower()));
+ }
+
+ /**
+ * @brief Gives all left-side values.
+ * @return List of values.
+ */
+ QStringList leftValues() const
+ {
+ return hash.keys();
+ }
+
+ /**
+ * @brief Gives all right-side values.
+ * @return List of values.
+ */
+ QStringList rightValues() const
+ {
+ return inverted.keys();
+ }
+
+ /**
+ * @brief Provides java-style iterator for this hash.
+ * @return Iterator object.
+ */
+ QHashIterator<QString,QString> iterator() const
+ {
+ return QHashIterator<QString,QString>(hash);
+ }
+
+ /**
+ * @brief Removes all entries from the hash.
+ */
+ void clear()
+ {
+ hash.clear();
+ inverted.clear();
+ lowerHash.clear();
+ lowerInverted.clear();
+ }
+
+ /**
+ * @brief Counts all entries in the hash.
+ * @return Number of entries in the hash.
+ */
+ int count() const
+ {
+ return hash.count();
+ }
+
+ /**
+ * @brief Tests whether the hash is empty or not.
+ * @return true if the hash is empty, false otherwise.
+ */
+ bool isEmpty() const
+ {
+ return hash.isEmpty();
+ }
+
+ private:
+ /**
+ * @brief Fills inverted and lower internal hashes basing on the main hash class member.
+ */
+ void initInvertedAndLower()
+ {
+ QHashIterator<QString,QString> it(hash);
+ while (it.hasNext())
+ {
+ it.next();
+ inverted[it.value()] = it.key();
+ lowerHash[it.key().toLower()] = it.key();
+ lowerInverted[it.value().toLower()] = it.value();
+ }
+ }
+
+ /**
+ * @brief Standard mapping - left to right.
+ */
+ QHash<QString,QString> hash;
+
+ /**
+ * @brief Right to left mapping.
+ */
+ QHash<QString,QString> inverted;
+
+ /**
+ * @brief Lower left to true left key mapping.
+ */
+ QHash<QString,QString> lowerHash;
+
+ /**
+ * @brief Lower right to true right key mapping.
+ */
+ QHash<QString,QString> lowerInverted;
+};
+
+#endif // BISTRHASH_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/column.cpp b/SQLiteStudio3/coreSQLiteStudio/common/column.cpp new file mode 100644 index 0000000..cc282e6 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/column.cpp @@ -0,0 +1,38 @@ +#include "column.h"
+#include <QHash>
+
+Column::Column() : Table()
+{
+}
+
+Column::Column(const QString& database, const QString& table, const QString& column) :
+ Table(database, table)
+{
+ setColumn(column);
+}
+
+Column::Column(const Column& other) :
+ Table(other.database, other.table)
+{
+ column = other.column;
+}
+
+int Column::operator ==(const Column& other) const
+{
+ return Table::operator==(other) && column == other.column;
+}
+
+QString Column::getColumn() const
+{
+ return column;
+}
+
+void Column::setColumn(const QString& value)
+{
+ column = value;
+}
+
+int qHash(Column column)
+{
+ return qHash(column.getDatabase() + "." + column.getTable() + "." + column.getColumn());
+}
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/column.h b/SQLiteStudio3/coreSQLiteStudio/common/column.h new file mode 100644 index 0000000..18e5edd --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/column.h @@ -0,0 +1,26 @@ +#ifndef COLUMN_H
+#define COLUMN_H
+
+#include "table.h"
+#include "coreSQLiteStudio_global.h"
+#include <QString>
+
+struct API_EXPORT Column : public Table
+{
+ public:
+ Column();
+ Column(const QString& database, const QString& table, const QString& column);
+ Column(const Column& other);
+
+ int operator ==(const Column& other) const;
+
+ QString getColumn() const;
+ void setColumn(const QString& value);
+
+ private:
+ QString column;
+};
+
+int API_EXPORT qHash(Column column);
+
+#endif // COLUMN_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/global.h b/SQLiteStudio3/coreSQLiteStudio/common/global.h new file mode 100644 index 0000000..bc228ca --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/global.h @@ -0,0 +1,66 @@ +#ifndef GLOBAL_H +#define GLOBAL_H + +/** @file */ + +#define DEEP_COPY_FIELD(T, F) \ + if (other.F) \ + { \ + F = new T(*other.F); \ + F->setParent(this); \ + } + +#define DEEP_COPY_COLLECTION(T, F) \ + T* _new##T; \ + foreach (T* _element, other.F) \ + { \ + _new##T = new T(*_element); \ + _new##T->setParent(this); \ + F << _new##T; \ + } + +/** + * @brief Deletes object and sets the pointer to null. + * + * Deletes object under given pointer, but only if the pointer is not null. + * Also sets the pointer to the null after deleting is done. + */ +#define safe_delete(var) \ + if (var) \ + { \ + delete var; \ + var = nullptr; \ + } + +#define static_char static constexpr const char + +#define static_qstring(N,V) const static QString N = QStringLiteral(V) + +#define DECLARE_SINGLETON(Cls) \ + public: \ + static Cls* getInstance(); \ + static void destroy(); \ + \ + private: \ + static Cls* _instance; + +#define DEFINE_SINGLETON(Cls) \ + Cls* Cls::_instance = nullptr; \ + \ + Cls* Cls::getInstance() \ + { \ + if (!_instance) \ + _instance = new Cls(); \ + \ + return _instance; \ + } \ + \ + void Cls::destroy() \ + { \ + safe_delete(_instance); \ + } + +#define STRINGIFY(s) _STRINGIFY(s) +#define _STRINGIFY(s) #s + +#endif // GLOBAL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/memoryusage.cpp b/SQLiteStudio3/coreSQLiteStudio/common/memoryusage.cpp new file mode 100644 index 0000000..386c65c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/memoryusage.cpp @@ -0,0 +1,83 @@ +#include "memoryusage.h" +#include <QtGlobal> + +#ifdef Q_OS_LINUX +#include <QFile> +#include <QRegularExpression> +#else + +#ifdef Q_OS_WIN32 +#include "windows.h" +#include "psapi.h" +#else + +#ifdef Q_OS_MAC +#include <mach/mach.h> +#endif // Q_OS_MAC + +#endif // Q_OS_WIN32 +#endif // Q_OS_LINUX + +#ifdef Q_OS_LINUX + +int getMemoryUsage() +{ + static const QRegularExpression re("VmSize\\:\\s+(\\d+)\\s+(\\w+)"); + + QFile file("/proc/self/status"); + if (!file.open(QIODevice::ReadOnly)) + return -1; + + QString contents = file.readAll(); + QRegularExpressionMatch match = re.match(contents); + if (!match.hasMatch()) + return -1; + + bool ok; + int result = match.captured(1).toInt(&ok); + if (!ok) + return -1; + + QString unit = match.captured(2).toLower(); + if (unit == "mb") + return result * 1024 * 1024; + + if (unit == "kb") + return result * 1024; + + return result; +} + +#else +#ifdef Q_OS_WIN32 + +int getMemoryUsage() +{ + PROCESS_MEMORY_COUNTERS_EX pmc; + GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc)); + return pmc.PrivateUsage; +} + +#else +#ifdef Q_OS_MAC + +int getMemoryUsage() +{ + struct task_basic_info t_info; + mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT; + + if (KERN_SUCCESS != task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count)) + return -1; + + return t_info.virtual_size; +} + +#else +int getMemoryUsage() +{ + return -1; +} + +#endif // Q_OS_MAC +#endif // Q_OS_WIN32 +#endif // Q_OS_LINUX diff --git a/SQLiteStudio3/coreSQLiteStudio/common/memoryusage.h b/SQLiteStudio3/coreSQLiteStudio/common/memoryusage.h new file mode 100644 index 0000000..55edfdb --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/memoryusage.h @@ -0,0 +1,6 @@ +#ifndef MEMORYUSAGE_H +#define MEMORYUSAGE_H + +int getMemoryUsage(); + +#endif // MEMORYUSAGE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/nulldevice.cpp b/SQLiteStudio3/coreSQLiteStudio/common/nulldevice.cpp new file mode 100644 index 0000000..304aa9d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/nulldevice.cpp @@ -0,0 +1,19 @@ +#include "nulldevice.h" + +NullDevice::NullDevice(QObject *parent) : + QIODevice(parent) +{ +} + +qint64 NullDevice::readData(char *data, qint64 maxSize) +{ + (void)(data); // slicence unused var + (void)(maxSize); // slicence unused var + return 0; +} + +qint64 NullDevice::writeData(const char *data, qint64 maxSize) +{ + (void)(data); // slicence unused var + return maxSize; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/common/nulldevice.h b/SQLiteStudio3/coreSQLiteStudio/common/nulldevice.h new file mode 100644 index 0000000..d714114 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/nulldevice.h @@ -0,0 +1,15 @@ +#ifndef NULLDEVICE_H +#define NULLDEVICE_H + +#include <QIODevice> + +class NullDevice : public QIODevice +{ + public: + explicit NullDevice(QObject *parent = 0); + + qint64 readData(char * data, qint64 maxSize); + qint64 writeData(const char * data, qint64 maxSize); +}; + +#endif // NULLDEVICE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/objectpool.h b/SQLiteStudio3/coreSQLiteStudio/common/objectpool.h new file mode 100644 index 0000000..b9f6b9f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/objectpool.h @@ -0,0 +1,84 @@ +#ifndef OBJECTPOOL_H +#define OBJECTPOOL_H + +#include <QHash> +#include <QHashIterator> +#include <QMutex> +#include <QWaitCondition> + +template <class T> +class ObjectPool +{ + public: + ObjectPool(quint32 min, quint32 max); + + T* reserve(); + void release(T* obj); + + private: + QHash<T*, bool> pool; + QMutex mutex; + QWaitCondition waitCond; + int min; + int max; +}; + +template <class T> +ObjectPool::ObjectPool(quint32 min, quint32 max) + : min(min), max(max) +{ + Q_ASSERT(min > 0); + T* obj = nullptr; + for (int i = 0; i < min; i++) + { + obj = new T(); + pool[obj] = false; + } +} + +T* ObjectPool::reserve() +{ + mutex.lock(); + + forever + { + QHashIterator<T*, bool> i(pool); + while (i.hasNext()) + { + i.next(); + if (!i.value()) + { + pool[i.key()] = true; + T* obj = i.key(); + mutex.unlock(); + return obj; + } + } + + // Check if we can enlarge the pool + if (pool.size() < max) + { + T* obj = new T(); + pool[i.key()] = true; + mutex.unlock(); + return obj; + } + + // Wait for release + waitCond.wait(&mutex); + } + + // no need to unlock, because the loop will repeat + // until the free obj is found and then mutex is unlocked. +} + +template <class T> +void ObjectPool::release(T* obj) +{ + mutex.lock(); + pool[obj] = false; + mutex.unlock(); + waitCond.wakeOne(); +} + +#endif // OBJECTPOOL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.cpp b/SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.cpp new file mode 100644 index 0000000..0eaca75 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.cpp @@ -0,0 +1,103 @@ +#include "readwritelocker.h" +#include "parser/lexer.h" +#include <QReadWriteLock> +#include <QReadLocker> +#include <QWriteLocker> + +ReadWriteLocker::ReadWriteLocker(QReadWriteLock* lock, Mode mode) +{ + init(lock, mode); +} + +ReadWriteLocker::ReadWriteLocker(QReadWriteLock* lock, const QString& query, Dialect dialect, bool noLock) +{ + init(lock, getMode(query, dialect, noLock)); +} + +ReadWriteLocker::~ReadWriteLocker() +{ + if (readLocker) + { + delete readLocker; + readLocker = nullptr; + } + + if (writeLocker) + { + delete writeLocker; + writeLocker = nullptr; + } +} + +void ReadWriteLocker::init(QReadWriteLock* lock, ReadWriteLocker::Mode mode) +{ + switch (mode) + { + case ReadWriteLocker::READ: + readLocker = new QReadLocker(lock); + break; + case ReadWriteLocker::WRITE: + writeLocker = new QWriteLocker(lock); + break; + case ReadWriteLocker::NONE: + // Nothing to lock. + break; + } +} + +ReadWriteLocker::Mode ReadWriteLocker::getMode(const QString &query, Dialect dialect, bool noLock) +{ + static QStringList readOnlyCommands = {"ANALYZE", "EXPLAIN", "PRAGMA"}; + + if (noLock) + return ReadWriteLocker::NONE; + + TokenList tokens = Lexer::tokenize(query, dialect); + int keywordIdx = tokens.indexOf(Token::KEYWORD); + + if (keywordIdx > -1 && readOnlyCommands.contains(tokens[keywordIdx]->value.toUpper())) + return ReadWriteLocker::READ; + + if (keywordIdx > -1 && tokens[keywordIdx]->value.toUpper() == "WITH") + { + bool matched = false; + bool isSelect = false; + int depth = 0; + for (TokenPtr token : tokens) + { + switch (token->type) + { + case Token::PAR_LEFT: + depth++; + break; + case Token::PAR_RIGHT: + depth--; + break; + case Token::KEYWORD: + if (depth == 0) + { + QString val = token->value.toUpper(); + if (val == "SELECT") + { + matched = true; + isSelect = true; + } + else if (val == "DELETE" || val == "UPDATE" || val == "INSERT") + { + matched = true; + } + } + break; + default: + break; + } + + if (matched) + break; + } + if (isSelect) + return ReadWriteLocker::READ; + } + + return ReadWriteLocker::WRITE; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.h b/SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.h new file mode 100644 index 0000000..cac9368 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/readwritelocker.h @@ -0,0 +1,65 @@ +#ifndef READWRITELOCKER_H +#define READWRITELOCKER_H + +#include "coreSQLiteStudio_global.h" +#include "dialect.h" + +class QReadLocker; +class QWriteLocker; +class QReadWriteLock; + +/** + * @brief The ReadWriteLocker class + * + * This class behaves pretty much like QReadLocker or QWriteLocker + * (it actually uses those internally), except it can be either + * of those and this is to be decided at the moment of creation. + * Therefore the locker can work as read or write locker depending + * on an external condition. + * There's also a possibility to not lock anything as a third + * choice of working mode, so this also can be decided + * at construction moment. + */ +class API_EXPORT ReadWriteLocker +{ + public: + enum Mode + { + READ, + WRITE, + NONE + }; + + ReadWriteLocker(QReadWriteLock* lock, Mode mode); + ReadWriteLocker(QReadWriteLock* lock, const QString& query, Dialect dialect, bool noLock); + virtual ~ReadWriteLocker(); + + /** + * @brief Provides required locking mode for given query. + * @param query Query to be executed. + * @return Locking mode: READ or WRITE. + * + * Given the query this method analyzes what is the query and provides information if the query + * will do some changes on the database, or not. Then it returns proper locking mode that should + * be used for this query execution. + * + * Query execution methods from this class check if lock mode of the query to be executed isn't + * in conflict with the lock being currently applied on the dbOperLock (if any is applied at the moment). + * + * This method works on a very simple rule. It assumes that queries: SELECT, ANALYZE, EXPLAIN, + * and PRAGMA - are read-only, while all other queries are read-write. + * In case of PRAGMA this is not entirely true, but it's not like using PRAGMA for changing + * some setting would cause database state inconsistency. At least not from perspective of SQLiteStudio. + * + * In case of WITH statement it filters out the "WITH clause" and then checks for SELECT keyword. + */ + static ReadWriteLocker::Mode getMode(const QString& query, Dialect dialect, bool noLock); + + private: + void init(QReadWriteLock* lock, Mode mode); + + QReadLocker* readLocker = nullptr; + QWriteLocker* writeLocker = nullptr; +}; + +#endif // READWRITELOCKER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/sortedhash.h b/SQLiteStudio3/coreSQLiteStudio/common/sortedhash.h new file mode 100644 index 0000000..9ca6ade --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/sortedhash.h @@ -0,0 +1,164 @@ +#ifndef SORTEDHASH_H
+#define SORTEDHASH_H
+
+#include <QHash>
+
+/**
+ * @brief Partially implemented sorted hash.
+ *
+ * This is kind of a sorted QHash, except it doesn't act as sorted with iterators,
+ * just with keys. It also doesn't work with multiple values for single key.
+ *
+ * The complete sorted hash might be implemented later on.
+ */
+template <class Key, class T>
+class SortedHash : public QHash<Key, T>
+{
+ public:
+ SortedHash(std::initializer_list<std::pair<Key, T>> list) : QHash<Key, T>(list)
+ {
+ sortedKeys = keys();
+ }
+
+ SortedHash(const QHash<Key, T>& other) : QHash<Key, T>(other)
+ {
+ sortedKeys = keys();
+ }
+
+ SortedHash(QHash<Key, T>&& other) : QHash<Key, T>(other)
+ {
+ sortedKeys = keys();
+ }
+
+ SortedHash() : QHash<Key, T>()
+ {
+ }
+
+ typename QHash<Key, T>::iterator insert(const Key& key, const T& value)
+ {
+ if (!sortedKeys.contains(key))
+ sortedKeys << key;
+
+ return QHash<Key, T>::insert(key, value);
+ }
+
+ int remove(const Key& key)
+ {
+ sortedKeys.removeOne(key);
+ return QHash<Key, T>::remove(key);
+ }
+
+ void swap(QHash<Key, T>& other)
+ {
+ QHash<Key, T>::swap(other);
+ sortedKeys = keys();
+ }
+
+ void swap(SortedHash<Key, T>& other)
+ {
+ QHash<Key, T>::swap(other);
+ sortedKeys = other.sortedKeys;
+ }
+
+ T take(const Key& key)
+ {
+ sortedKeys.removeOne(key);
+ return QHash<Key, T>::take(key);
+ }
+
+ QList<Key> keys() const
+ {
+ return sortedKeys;
+ }
+
+ QList<Key> keys(const T& value) const
+ {
+ QList<Key> results;
+ foreach (const Key& k, sortedKeys)
+ if (value(k) == value)
+ results << k;
+
+ return results;
+ }
+
+ SortedHash<Key, T>& unite(const QHash<Key, T>& other)
+ {
+ QHash<Key, T>::unite(other);
+ sortedKeys += other.keys();
+ return *this;
+ }
+
+ SortedHash<Key, T>& unite(const SortedHash<Key, T>& other)
+ {
+ QHash<Key, T>::unite(other);
+ sortedKeys += other.sortedKeys;
+ return *this;
+ }
+
+ QList<T> values() const
+ {
+ QList<T> results;
+ foreach (const Key& k, sortedKeys)
+ results << value(k);
+
+ return results;
+ }
+
+ bool operator!=(const SortedHash<Key, T>& other) const
+ {
+ return !operator==(other);
+ }
+
+ SortedHash<Key, T>& operator=(const QHash<Key, T>& other)
+ {
+ QHash<Key, T>::operator=(other);
+ sortedKeys = other.keys();
+ return *this;
+ }
+
+ SortedHash<Key, T>& operator=(const SortedHash<Key, T>& other)
+ {
+ QHash<Key, T>::operator=(other);
+ sortedKeys = other.sortedKeys;
+ return *this;
+ }
+
+ bool operator==(const SortedHash<Key, T>& other) const
+ {
+ return QHash<Key, T>::operator==(other) && sortedKeys == other.sortedKeys;
+ }
+
+ T & operator[](const Key& key)
+ {
+ if (!sortedKeys.contains(key))
+ sortedKeys << key;
+
+ return QHash<Key, T>::operator[](key);
+ }
+
+ const T operator[](const Key& key) const
+ {
+ return QHash<Key, T>::operator[](key);
+ }
+
+ Key firstKey() const
+ {
+ if (sortedKeys.size() == 0)
+ return Key();
+
+ return sortedKeys.first();
+ }
+
+ Key lastKey() const
+ {
+ if (sortedKeys.size() == 0)
+ return Key();
+
+ return sortedKeys.last();
+ }
+
+ private:
+ QList<Key> sortedKeys;
+};
+
+#endif // SORTEDHASH_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/strhash.h b/SQLiteStudio3/coreSQLiteStudio/common/strhash.h new file mode 100644 index 0000000..4fb7bb3 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/strhash.h @@ -0,0 +1,182 @@ +#ifndef STRHASH_H
+#define STRHASH_H
+
+#include <QHash>
+#include <QString>
+#include <QStringList>
+#include <QDebug>
+
+template <class T>
+class StrHash
+{
+ public:
+ StrHash() {}
+ StrHash(std::initializer_list<std::pair<QString,T>> list) : hash(QHash<QString,T>(list))
+ {
+ initLower();
+ }
+
+ StrHash(const QHash<QString,T>& other) : hash(QHash<QString,T>(other))
+ {
+ initLower();
+ }
+
+ void insert(const QString& key, const T& value)
+ {
+ if (lowerCaseHash.contains(key.toLower()))
+ remove(key, Qt::CaseInsensitive);
+
+ hash.insert(key, value);
+ lowerCaseHash.insert(key.toLower(), key);
+ }
+
+ bool contains(const QString& key, Qt::CaseSensitivity cs = Qt::CaseSensitive) const
+ {
+ if (cs == Qt::CaseSensitive)
+ return hash.contains(key);
+
+ return lowerCaseHash.contains(key.toLower());
+ }
+
+ int remove(const QString& key, Qt::CaseSensitivity cs = Qt::CaseSensitive)
+ {
+ if (cs == Qt::CaseSensitive)
+ {
+ int res = hash.remove(key);
+ if (res > 0)
+ lowerCaseHash.remove(key.toLower());
+
+ return res;
+ }
+
+ // Case insensitive
+ QString lowerKey = key.toLower();
+ if (lowerCaseHash.contains(lowerKey))
+ {
+ int res = hash.remove(lowerCaseHash.value(lowerKey));
+ lowerCaseHash.remove(lowerKey);
+ return res;
+ }
+
+ return 0;
+ }
+
+ T take(const QString& key, Qt::CaseSensitivity cs = Qt::CaseSensitive)
+ {
+ if (cs == Qt::CaseSensitive)
+ {
+ lowerCaseHash.remove(key.toLower());
+ return hash.take(key);
+ }
+
+ // Case insensitive
+ QString lowerKey = key.toLower();
+ if (lowerCaseHash.contains(lowerKey))
+ {
+ QString theKey = lowerCaseHash.value(lowerKey);
+ lowerCaseHash.remove(lowerKey);
+ return hash.take(theKey);
+ }
+
+ return QString();
+ }
+
+ StrHash<T>& unite(const StrHash<T>& other)
+ {
+ unite(other.hash);
+ return *this;
+ }
+
+ StrHash<T>& unite(const QHash<QString,T>& other)
+ {
+ QHashIterator<QString,T> it(other);
+ while (it.hasNext())
+ {
+ it.next();
+ insert(it.key(), it.value());
+ }
+
+ return *this;
+ }
+
+ T value(const QString& key, Qt::CaseSensitivity cs = Qt::CaseSensitive) const
+ {
+ if (cs == Qt::CaseSensitive)
+ return hash.value(key);
+
+ return hash.value(lowerCaseHash.value(key.toLower()));
+ }
+
+ QList<T> values() const
+ {
+ return hash.values();
+ }
+
+ QStringList keys() const
+ {
+ return hash.keys();
+ }
+
+ QHashIterator<QString,T> iterator() const
+ {
+ return QHashIterator<QString,T>(hash);
+ }
+
+ void clear()
+ {
+ hash.clear();
+ lowerCaseHash.clear();
+ }
+
+ int count() const
+ {
+ return hash.count();
+ }
+
+ int count(const QString& key, Qt::CaseSensitivity cs = Qt::CaseSensitive) const
+ {
+ if (cs == Qt::CaseSensitive)
+ return hash.count(key);
+
+ return lowerCaseHash.count(key.toLower());
+ }
+
+ bool isEmpty() const
+ {
+ return hash.isEmpty();
+ }
+
+ T& operator[](const QString& key)
+ {
+ if (lowerCaseHash.contains(key.toLower()) && !hash.contains(key))
+ {
+ T value = hash[lowerCaseHash[key.toLower()]];
+ remove(key, Qt::CaseInsensitive);
+ hash[key] = value;
+ }
+
+ lowerCaseHash[key.toLower()] = key;
+ return hash[key];
+ }
+
+ const T operator[](const QString& key) const
+ {
+ return hash[lowerCaseHash[key.toLower()]];
+ }
+
+ private:
+ void initLower()
+ {
+ QHashIterator<QString,T> it(hash);
+ while (it.hasNext())
+ {
+ it.next();
+ lowerCaseHash[it.key().toLower()] = it.key();
+ }
+ }
+
+ QHash<QString,QString> lowerCaseHash;
+ QHash<QString,T> hash;
+};
+
+#endif // STRHASH_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/table.cpp b/SQLiteStudio3/coreSQLiteStudio/common/table.cpp new file mode 100644 index 0000000..c590995 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/table.cpp @@ -0,0 +1,52 @@ +#include "table.h"
+#include <QHash>
+
+Table::Table()
+{
+}
+
+Table::Table(const QString& database, const QString& table)
+{
+ setDatabase(database);
+ setTable(table);
+}
+
+Table::Table(const Table& other)
+{
+ database = other.database;
+ table = other.table;
+}
+
+Table::~Table()
+{
+}
+
+int Table::operator ==(const Table &other) const
+{
+ return other.database == this->database && other.table == this->table;
+}
+
+QString Table::getTable() const
+{
+ return table;
+}
+
+void Table::setTable(const QString& value)
+{
+ table = value;
+}
+
+QString Table::getDatabase() const
+{
+ return database;
+}
+
+void Table::setDatabase(const QString& value)
+{
+ database = value.isEmpty() ? "main" : value;
+}
+
+int qHash(Table table)
+{
+ return qHash(table.getDatabase() + "." + table.getTable());
+}
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/table.h b/SQLiteStudio3/coreSQLiteStudio/common/table.h new file mode 100644 index 0000000..d17a729 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/table.h @@ -0,0 +1,32 @@ +#ifndef TABLE_H
+#define TABLE_H
+
+#include "coreSQLiteStudio_global.h"
+#include <QString>
+
+class API_EXPORT Table
+{
+ public:
+ Table();
+ Table(const QString& database, const QString& table);
+ Table(const Table& other);
+ virtual ~Table();
+
+ int operator ==(const Table& other) const;
+
+ QString getDatabase() const;
+ void setDatabase(const QString& value);
+
+ QString getTable() const;
+ void setTable(const QString& value);
+
+ protected:
+ QString database;
+ QString table;
+
+};
+
+int API_EXPORT qHash(Table table);
+
+
+#endif // TABLE_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/common/unused.h b/SQLiteStudio3/coreSQLiteStudio/common/unused.h new file mode 100644 index 0000000..090a8a2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/unused.h @@ -0,0 +1,6 @@ +#ifndef UNUSED_H +#define UNUSED_H + +#define UNUSED(X) (void)(X) + +#endif // UNUSED_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/utils.cpp b/SQLiteStudio3/coreSQLiteStudio/common/utils.cpp new file mode 100644 index 0000000..d56d838 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/utils.cpp @@ -0,0 +1,855 @@ +#include "common/utils.h" +#include "common/global.h" +#include "dbobjecttype.h" +#include "rsa/RSA.h" +#include <QTextCodec> +#include <QString> +#include <QSet> +#include <QVariant> +#include <QDateTime> +#include <QSysInfo> +#include <QDebug> +#include <QRegularExpression> +#include <QDir> + +#ifdef Q_OS_LINUX +#include <sys/utsname.h> + +#include <QFileInfo> +#endif + +void initUtils() +{ + qRegisterMetaType<QList<int>>("QList<int>"); + qRegisterMetaType<DbObjectType>("DbObjectType"); +} + +bool isXDigit(const QChar& c) +{ + return c.isDigit() || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} + +QChar charAt(const QString& str, int pos) +{ + if (pos < 0 || pos >= str.size()) + return QChar(0); + + return str[pos]; +} + +int rand(int min, int max) +{ + return qrand() % (max-min) + min; +} + +QString randStr(int length, bool numChars, bool whiteSpaces) +{ + static const char* alphaNumChars = " abcdefghijklmnopqrstuvwxyz1234567890"; + int start = 1; + int range = start + (numChars ? 36 : 26); + + if (whiteSpaces) + { + start--; + range++; + } + + QString output = ""; + for (int i = 0; i < length; i++) + output += alphaNumChars[rand(start, range)]; + + return output; +} + +QString randStr(int length, const QString& charCollection) +{ + int range = charCollection.size(); + QString output = ""; + for (int i = 0; i < length; i++) + output += charCollection[rand(0, range)]; + + return output; +} + +QString randBinStr(int length) +{ + char* output = new char[length]; + for (int i =0; i < length; i++) + output[i] = rand(0, 256); + + return QString::fromLatin1(output, length); +} + +QString randStrNotIn(int length, const QSet<QString> set, bool numChars, bool whiteSpaces) +{ + if (length == 0) + return ""; + + QString outStr; + do + { + outStr = randStr(length, numChars, whiteSpaces); + } + while (set.contains(outStr)); + + return outStr; +} + + +Range::Range() : + from(0), to(0) +{ +} + +Range::Range(qint64 from, qint64 to) + :from(from), to(to) +{ + fromValid = true; + toValid = true; +} + +void Range::setFrom(qint64 from) +{ + this->from = from; + fromValid = true; +} + +void Range::setTo(qint64 to) +{ + this->to = to; + toValid = true; +} + +qint64 Range::getFrom() const +{ + return from; +} + +qint64 Range::getTo() const +{ + return to; +} + +bool Range::isValid() const +{ + return fromValid && toValid && from <= to; +} + +bool Range::contains(qint64 position) const +{ + return position >= from && position <= to; +} + +bool Range::overlaps(const Range& other) const +{ + return overlaps(other.from, other.to); +} + +bool Range::overlaps(qint64 from, qint64 to) const +{ + return (this->from >= from && this->from <= to) || (this->to >= from && this->to <= to); +} + +Range Range::common(const Range& other) const +{ + return common(other.from, other.to); +} + +Range Range::common(qint64 from, qint64 to) const +{ + if (!isValid() || from > to) + return Range(); + + if (this->from >= from) + { + if (this->from > to) + return Range(); + + if (this->to < to) + return Range(this->from, this->to); + else + return Range(this->from, to); + } + else + { + if (from > this->to) + return Range(); + + if (to < this->to) + return Range(from, to); + else + return Range(from, this->to); + } +} + +qint64 Range::length() const +{ + return to - from + 1; +} + +QString generateUniqueName(const QString &baseName, const QStringList &existingNames) +{ + QString name = baseName; + int i = 0; + while (existingNames.contains(name)) + name = baseName+QString::number(i++); + + return name; +} + +bool isNumeric(const QVariant& value) +{ + bool ok; + value.toLongLong(&ok); + if (ok) + return true; + + value.toDouble(&ok); + return ok; +} + +QString rStrip(const QString& str) +{ + if (str.isNull()) + return str; + + for (int n = str.size() - 1; n >= 0; n--) + { + if (!str.at(n).isSpace()) + return str.left(n + 1); + } + return ""; +} + +QStringList tokenizeArgs(const QString& str) +{ + QStringList results; + QString token; + bool quote = false; + bool escape = false; + QChar c; + for (int i = 0; i < str.length(); i++) + { + c = str[i]; + if (escape) + { + token += c; + } + else if (c == '\\') + { + escape = true; + } + else if (quote) + { + if (c == '"') + { + results << token; + token.clear(); + quote = false; + } + else + { + token += c; + } + } + else if (c == '"') + { + if (token.length() > 0) + token += c; + else + quote = true; + } + else if (c.isSpace()) + { + if (token.length() > 0) + { + results << token; + token.clear(); + } + } + else + { + token += c; + } + } + + if (token.length() > 0) + results << token; + + return results; +} + +QStringList prefixEach(const QString& prefix, const QStringList& list) +{ + QStringList result; + foreach (const QString& item, list) + result << (prefix + item); + + return result; +} + +QByteArray hashToBytes(const QHash<QString,QVariant>& hash) +{ + QByteArray bytes; + QDataStream stream(&bytes, QIODevice::WriteOnly); + stream << QVariant(hash); + return bytes; +} + +QHash<QString,QVariant> bytesToHash(const QByteArray& bytes) +{ + if (bytes.isNull()) + return QHash<QString,QVariant>(); + + QVariant deserializedValue; + QDataStream stream(bytes); + stream >> deserializedValue; + return deserializedValue.toHash(); +} + +int indexOf(const QStringList& list, const QString& value, Qt::CaseSensitivity cs) +{ + return indexOf(list, value, 0, cs); +} + +int indexOf(const QStringList& list, const QString& value, int from, Qt::CaseSensitivity cs) +{ + if (cs == Qt::CaseSensitive) + return list.indexOf(value, from); + + int cnt = list.size(); + for (int i = from; i < cnt; i++) + if (list[i].compare(value, cs) == 0) + return i; + + return -1; +} + +QString pad(const QString& str, int length, const QChar& fillChar) +{ + if (str.length() >= abs(length)) + return str; + + QString result = str; + QString fill = QString(fillChar).repeated(abs(length) - str.length()); + if (length >= 0) + return result.append(fill); + else + return result.prepend(fill); +} + +QString center(const QString& str, int length, const QChar& fillChar) +{ + if (str.length() >= length) + return str; + + QString result = str; + QString fillLeft = QString(fillChar).repeated((length - str.length()) / 2); + QString fillRight = fillLeft; + if ((fillLeft.length() + fillRight.length() + str.length()) < length) + fillLeft += fillChar; + + return result.prepend(fillLeft).append(fillRight); +} + +QString longest(const QStringList& strList) +{ + int max = 0; + QString result; + foreach (const QString str, strList) + { + if (str.size() > max) + { + result = str; + max = str.size(); + } + } + return result; +} + +QString shortest(const QStringList& strList) +{ + int max = INT_MAX; + QString result; + foreach (const QString str, strList) + { + if (str.size() < max) + { + result = str; + max = str.size(); + } + } + return result; +} + +QString longestCommonPart(const QStringList& strList) +{ + if (strList.size() == 0) + return QString::null; + + QString common; + QString first = strList.first(); + for (int i = 0; i < first.length(); i++) + { + common += first[i]; + foreach (const QString& str, strList) + { + if (!str.startsWith(common)) + return common.left(i); + } + } + return common; +} + +QStringList applyMargin(const QString& str, int margin) +{ + QStringList lines; + QString line; + foreach (QString word, str.split(" ")) + { + if (((line + word).length() + 1) > margin) + { + if (!line.isEmpty()) + { + lines << line; + line.clear(); + } + + while ((line + word).length() > margin) + { + line += word.left(margin); + lines << line; + word = word.mid(margin); + } + } + + if (!line.isEmpty()) + line += " "; + + line += word; + + if (line.endsWith("\n")) + { + lines << line.trimmed(); + line.clear(); + } + } + + if (!line.isEmpty()) + lines << line; + + if (lines.size() == 0) + lines << QString(); + + return lines; +} + +QDateTime toGregorian(double julianDateTime) +{ + int Z = (int)julianDateTime; + double F = julianDateTime - Z; + + int A; + if (Z < 2299161) + { + A = Z; + } + else + { + int alpha = (int)((Z - 1867216.25)/36524.25); + A = Z + 1 + alpha - (int)(alpha / 4); + } + + int B = A + 1524; + int C = (int)((B - 122.1) / 365.25); + int D = (int)(365.25 * C); + int E = (int)((B-D) / 30.6001); + int DD = B - D - (int)(30.6001 * E) + F; + int MM = (E <= 13) ? E - 1 : E - 13; + int YYYY = (MM <= 2) ? C - 4715 : C - 4716; + + int mmmBase = qRound(F * 86400000.0); + int mmm = mmmBase % 1000; + int ssBase = mmmBase / 1000; + int ss = ssBase % 60; + int mmBase = ssBase / 60; + int mm = mmBase % 60; + int hh = (mmBase / 60) + 12; + if (hh >= 24) + { + hh -= 24; + DD++; + } + + QDateTime dateTime; + dateTime.setDate(QDate(YYYY, MM, DD)); + dateTime.setTime(QTime(hh, mm, ss, mmm)); + return dateTime; +} + +double toJulian(const QDateTime& gregDateTime) +{ + QDate date = gregDateTime.date(); + QTime time = gregDateTime.time(); + return toJulian(date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second(), time.msec()); +} + +double toJulian(int year, int month, int day, int hour, int minute, int second, int msec) +{ + int a = (14 - month) / 12; + int y = year + 4800 + a; + int m = month + 12 * a - 3; + + // Julian Day + int jnd = day + (153 * m + 2) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 32045; + + // Julian Day + Julian Time + double jndt = jnd + (hour - 12.0) / 24.0 + minute / 1440.0 + second / 86400.0 + msec / 86400000.0; + + return jndt; +} + +QString formatFileSize(quint64 size) +{ + quint64 bytes = size; + quint64 kb = 0; + quint64 mb = 0; + quint64 gb = 0; + + QStringList words; + if (bytes > (1024*1024*1024)) + { + gb = bytes / (1024*1024*1024); + bytes %= (1024*1024*1024); + words << QString("%1GB").arg(gb); + } + + if (bytes > (1024*1024)) + { + mb = bytes / (1024*1024); + bytes %= (1024*1024); + words << QString("%1MB").arg(mb); + } + + if (bytes > 1024) + { + kb = bytes / 1024; + bytes %= 1024; + words << QString("%1KB").arg(kb); + } + + if (bytes > 0) + words << QString("%1B").arg(bytes); + + return words.join(" "); +} + +QString formatTimePeriod(int msecs) +{ + int hours = 0; + int minutes = 0; + int seconds = 0; + + QStringList words; + if (msecs > (1000*60*60)) + { + hours = msecs / (1000*60*60); + msecs %= (1000*60*60); + words << QString("%1h").arg(hours); + } + + if (msecs > (1000*60)) + { + minutes = msecs / (1000*60); + msecs %= (1000*60); + words << QString("%1m").arg(minutes); + } + + if (msecs > (1000)) + { + seconds = msecs / 1000; + msecs %= 1000; + words << QString("%1s").arg(seconds); + } + + if (msecs > 0) + words << QString("%1ms").arg(msecs); + + return words.join(" "); +} + +QStringList common(const QStringList& list1, const QStringList& list2, Qt::CaseSensitivity cs) +{ + QStringList newList; + for (const QString& str : list1) + { + if (list2.contains(str, cs)) + newList << str; + } + return newList; +} + +QStringList textCodecNames() +{ + QList<QByteArray> codecs = QTextCodec::availableCodecs(); + QStringList names; + QSet<QString> nameSet; + for (const QByteArray& codec : codecs) + nameSet << QString::fromLatin1(codec.constData()); + + names = nameSet.toList(); + qSort(names); + return names; +} + +QTextCodec* codecForName(const QString& name) +{ + return QTextCodec::codecForName(name.toLatin1()); +} + +QTextCodec* defaultCodec() +{ + return QTextCodec::codecForLocale(); +} + +QString defaultCodecName() +{ + return QString::fromLatin1(QTextCodec::codecForLocale()->name()); +} + +QStringList splitByLines(const QString& str) +{ + return str.split(QRegExp("\r?\n")); +} + +QString joinLines(const QStringList& lines) +{ +#ifdef Q_OS_WIN + static_char* newLine = "\r\n"; +#else + static_char* newLine = "\n"; +#endif + return lines.join(newLine); +} + +int sum(const QList<int>& integers) +{ + int res = 0; + for (int i : integers) + res += i; + + return res; +} + +QString getOsString() +{ +#if defined(Q_OS_WIN) + QString os = "Windows"; + switch (QSysInfo::WindowsVersion) + { + case QSysInfo::WV_XP: + os += " XP"; + break; + case QSysInfo::WV_2003: + os += " 2003"; + break; + case QSysInfo::WV_VISTA: + os += " Vista"; + break; + case QSysInfo::WV_WINDOWS7: + os += " 7"; + break; + case QSysInfo::WV_WINDOWS8: + os += " 8"; + break; + case QSysInfo::WV_WINDOWS8_1: + os += " 8.1"; + break; + case QSysInfo::WV_32s: + case QSysInfo::WV_95: + case QSysInfo::WV_98: + case QSysInfo::WV_Me: + case QSysInfo::WV_DOS_based: + case QSysInfo::WV_NT: + case QSysInfo::WV_2000: + case QSysInfo::WV_NT_based: + case QSysInfo::WV_CE: + case QSysInfo::WV_CENET: + case QSysInfo::WV_CE_5: + case QSysInfo::WV_CE_6: + case QSysInfo::WV_CE_based: + break; + } +#elif defined(Q_OS_LINUX) + QString os = "Linux"; + utsname uts; + if (uname(&uts) != 0) + { + qWarning() << "Error while calling uname() for OS version. Error code: " << errno; + } + else + { + os += " " + QString::fromLatin1(uts.release); + } +#elif defined(Q_OS_OSX) + QString os = "MacOS X"; + switch (QSysInfo::MacintoshVersion) + { + case QSysInfo::MV_10_4: + os += " 10.4 Tiger"; + break; + case QSysInfo::MV_10_5: + os += " 10.5 Leopard"; + break; + case QSysInfo::MV_10_6: + os += " 10.6 Snow Leopard"; + break; + case QSysInfo::MV_10_7: + os += " 10.7 Lion"; + break; + case QSysInfo::MV_10_8: + os += " 10.8 Mountain Lion"; + break; + case QSysInfo::MV_10_9: + os += " 10.9 Mavericks"; + break; + case QSysInfo::MV_9: + case QSysInfo::MV_10_0: + case QSysInfo::MV_10_1: + case QSysInfo::MV_10_2: + case QSysInfo::MV_10_3: + case QSysInfo::MV_IOS: + case QSysInfo::MV_IOS_4_3: + case QSysInfo::MV_IOS_5_0: + case QSysInfo::MV_IOS_5_1: + case QSysInfo::MV_IOS_6_0: + case QSysInfo::MV_IOS_6_1: + case QSysInfo::MV_IOS_7_0: + case QSysInfo::MV_IOS_7_1: + case QSysInfo::MV_Unknown: + break; + } +#elif defined(Q_OS_UNIX) + QString os = "Unix"; +#else + QString os = "Unknown"; +#endif + + os += ", " + QString::number(QSysInfo::WordSize) + "bit"; + return os; +} + +DistributionType getDistributionType() +{ +#if defined(Q_OS_OSX) + return DistributionType::OSX_BOUNDLE; +#elif defined(PORTABLE_CONFIG) + return DistributionType::PORTABLE; +#else + return DistributionType::OS_MANAGED; +#endif +} + +bool validateEmail(const QString& email) +{ + static const QRegularExpression re("^[a-zA-Z0-9_\\.-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-\\.]+$"); + return re.match(email).hasMatch(); +} + +bool isHex(const QString& str) +{ + bool ok; + str.toLongLong(&ok, 16); + return ok; +} + +QString formatVersion(int version) +{ + int majorVer = version / 10000; + int minorVer = version % 10000 / 100; + int patchVer = version % 100; + return QString::number(majorVer) + "." + QString::number(minorVer) + "." + QString::number(patchVer); +} + +bool copyRecursively(const QString& src, const QString& dst) +{ + // Code taken from QtCreator: + // https://qt.gitorious.org/qt-creator/qt-creator/source/1a37da73abb60ad06b7e33983ca51b266be5910e:src/app/main.cpp#L13-189 + QFileInfo srcFileInfo(src); + if (srcFileInfo.isDir()) + { + QDir targetDir(dst); + targetDir.cdUp(); + if (!targetDir.mkdir(QFileInfo(dst).fileName())) + return false; + + QDir sourceDir(src); + QStringList fileNames = sourceDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); + for (const QString &fileName : fileNames) + { + const QString newSrcFilePath = src + QLatin1Char('/') + fileName; + const QString newTgtFilePath = dst + QLatin1Char('/') + fileName; + if (!copyRecursively(newSrcFilePath, newTgtFilePath)) + return false; + } + } + else if (srcFileInfo.isSymLink()) + { + QString trg = QFile(src).symLinkTarget(); + QFile::link(trg, dst); + } + else + { + if (!QFile::copy(src, dst)) + return false; + } + return true; +} + +bool renameBetweenPartitions(const QString& src, const QString& dst) +{ + if (QDir(dst).exists()) + return false; + + int res = copyRecursively(src, dst); + if (res) + QDir(src).removeRecursively(); + else + QDir(dst).removeRecursively(); + + return res; +} + +bool isWritableRecursively(const QString& dir) +{ + QFileInfo fi(dir); + if (!fi.isWritable()) + return false; + + if (fi.isDir()) + { + QStringList fileNames = QDir(dir).entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); + for (const QString &fileName : fileNames) + { + if (!isWritableRecursively(dir + QLatin1Char('/') + fileName)) + return false; + } + } + return true; +} + +QString encryptRsa(const QString& input, const QString& modulus, const QString& exponent) +{ + std::string inputStdStr = input.toStdString(); + Key key = Key(BigInt(modulus.toStdString()), BigInt(exponent.toStdString())); + std::string result = RSA::Encrypt(inputStdStr, key); + return QString::fromStdString(result); +} + +QString decryptRsa(const QString& input, const QString& modulus, const QString& exponent) +{ + std::string inputStdStr = input.toStdString(); + Key key = Key(BigInt(modulus.toStdString()), BigInt(exponent.toStdString())); + std::string result = RSA::Decrypt(inputStdStr, key); + return QString::fromStdString(result); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/common/utils.h b/SQLiteStudio3/coreSQLiteStudio/common/utils.h new file mode 100644 index 0000000..4ded52b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/utils.h @@ -0,0 +1,244 @@ +#ifndef UTILS_H +#define UTILS_H + +#include "coreSQLiteStudio_global.h" +#include <QList> +#include <QMutableListIterator> +#include <QSet> +#include <QChar> +#include <QStringList> +#include <QFileInfo> + +class QTextCodec; + +API_EXPORT void initUtils(); + +class API_EXPORT Range +{ + public: + Range(); + Range(qint64 from, qint64 to); + + void setFrom(qint64 from); + void setTo(qint64 to); + qint64 getFrom() const; + qint64 getTo() const; + bool isValid() const; + bool contains(qint64 position) const; + bool overlaps(const Range& other) const; + bool overlaps(qint64 from, qint64 to) const; + Range common(const Range& other) const; + Range common(qint64 from, qint64 to) const; + qint64 length() const; + + private: + qint64 from; + qint64 to; + bool fromValid = false; + bool toValid = false; +}; + +API_EXPORT bool isXDigit(const QChar& c); + +/** + * @brief Get character from string. + * @param str String to get char from. + * @param pos Character position. + * @return Requested character or null character. + * + * This is safe getter for a character of the string, + * thish returns null if pos index is out of range. + */ +QChar API_EXPORT charAt(const QString& str, int pos); + +API_EXPORT int rand(int min = 0, int max = RAND_MAX); +API_EXPORT QString randStr(int length, bool numChars = true, bool whiteSpaces = false); +API_EXPORT QString randStr(int length, const QString& charCollection); +API_EXPORT QString randBinStr(int length); +API_EXPORT QString randStrNotIn(int length, const QSet<QString> set, bool numChars = true, bool whiteSpaces = false); +API_EXPORT QString generateUniqueName(const QString& prefix, const QStringList& existingNames); +API_EXPORT bool isNumeric(const QVariant& value); +API_EXPORT QString rStrip(const QString& str); +API_EXPORT QStringList tokenizeArgs(const QString& str); +API_EXPORT QStringList prefixEach(const QString& prefix, const QStringList& list); +API_EXPORT QByteArray hashToBytes(const QHash<QString,QVariant>& hash); +API_EXPORT QHash<QString,QVariant> bytesToHash(const QByteArray& bytes); +/** + * @brief indexOf Extension to QStringList::indexOf(). + * + * This method does pretty much the same as QStringList::indexOf(), except it supports + * case sensitivity flag, unlike the original method. + */ +API_EXPORT int indexOf(const QStringList& list, const QString& value, int from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive); +API_EXPORT int indexOf(const QStringList& list, const QString& value, Qt::CaseSensitivity cs = Qt::CaseSensitive); + +/** + * @brief Returns only those elements from the list, which passed the filter. + * @tparam T type for which the filter will be applied for. It should match the type in the list and in the function argument. + * @param list List to filter elements from. + * @param filterFunction Function that accepts elements from the list and returns true for elements that should be returned by the filter. + * @return List of elements that passed custom function validation. + */ +template <class T> +QList<T> filter(const QList<T>& list, std::function<bool(const T& value)> filterFunction) +{ + QList<T> results; + for (const T& value : list) + { + if (filterFunction(value)) + results << value; + } + return results; +} + +template <class T> +bool contains(const QList<T>& list, std::function<bool(const T& value)> testFunction) +{ + for (const T& value : list) + { + if (testFunction(value)) + return true; + } + return false; +} + +/** + * @brief Appends or prepends characters to the string to make it of specified length. + * @param str Input string to work with. + * @param length Desired length of output string. + * @param fillChar Character to use to fill missing part of string. + * @param String which is at least \p length characters long, using \p str as an initial value. + * + * It appends or prepends as many \p fillChar characters to the \p str, so the \p str becomes \p length characters long. + * In case the \p str is already \p length characters long, or even longer, then the original string is returned. + * + * If \p length is positive value, characters are appended to string. It it's negative, then values are prepended to the string, + * using an absolute value of the \p length for calculating output length. + */ +API_EXPORT QString pad(const QString& str, int length, const QChar& fillChar); + +API_EXPORT QString center(const QString& str, int length, const QChar& fillChar); + +/** + * @brief Picks the longest string from the list. + * @param strList List to pick from. + * @return Longest value from the list, or empty string if the \p strList was empty as well. + * + * If there are many values with the same, longest length, then the first one is picked. + */ +API_EXPORT QString longest(const QStringList& strList); + +/** + * @brief Picks the shortest string from the list. + * @param strList List to pick from. + * @return Shortest value from the list, or empty string if the \p strList was empty as well. + * + * If there are many values with the same, shortest length, then the first one is picked. + */ +API_EXPORT QString shortest(const QStringList& strList); + +/** + * @brief Finds the longest common part of all strings. + * @param strList List to compare strings from. + * @return Longest common string (looking from the begining of each string) from the list. + */ +API_EXPORT QString longestCommonPart(const QStringList& strList); + +/** + * @brief Applies margin of given number of characters to the string, splitting it into lines. + * @param str String to apply the margin to. + * @param margin Number of characters allows in single line. + * @return List of lines produced by applying the margin. + * + * If \p str is longer than \p margin number of characters, this method splits \p str into several lines + * in order to respect the \p margin. White spaces are taken as points of splitting, but if there is + * a single word longer than \p margin, then this word gets splitted. + */ +API_EXPORT QStringList applyMargin(const QString& str, int margin); + +/** + * @brief toGregorian Converts Julian Date to Gregorian Date. + * @param julianDateTime Floating point representing Julian Day and Julian time. + * @return Gregorian date. + * + * Converts Julian Calendar date into Gregorian Calendar date. + * See Wikipedia for details. + */ +API_EXPORT QDateTime toGregorian(double julianDateTime); + +/** + * @brief toJulian Converts Gregorian Date to Julian Date. + * @param gregDateTime Gregorian calendar date and time. + * @return Julian calendar date and time. + * + * Converts the usually used Gregorian date and time into Julian Date format, + * which is floating point, where integral part is the Julian Day and fraction part is time of the day. + */ +API_EXPORT double toJulian(const QDateTime& gregDateTime); + +/** + * @brief toJulian Converts Gregorian Date to Julian Date. + * @overload + */ +API_EXPORT double toJulian(int year, int month, int day, int hour, int minute, int second, int msec); + +API_EXPORT QString formatFileSize(quint64 size); + +API_EXPORT QString formatTimePeriod(int msecs); + +API_EXPORT QStringList common(const QStringList& list1, const QStringList& list2, Qt::CaseSensitivity cs = Qt::CaseSensitive); + +API_EXPORT QStringList textCodecNames(); +API_EXPORT QString defaultCodecName(); +API_EXPORT QTextCodec* defaultCodec(); +API_EXPORT QTextCodec* codecForName(const QString& name); +API_EXPORT QStringList splitByLines(const QString& str); +API_EXPORT QString joinLines(const QStringList& lines); +API_EXPORT int sum(const QList<int>& integers); +API_EXPORT QString getOsString(); +API_EXPORT bool validateEmail(const QString& email); +API_EXPORT bool isHex(const QString& str); +API_EXPORT QString formatVersion(int version); +API_EXPORT bool copyRecursively(const QString& src, const QString& dst); +API_EXPORT bool renameBetweenPartitions(const QString& src, const QString& dst); +API_EXPORT bool isWritableRecursively(const QString& dir); +API_EXPORT QString encryptRsa(const QString& input, const QString& modulus, const QString& exponent); +API_EXPORT QString decryptRsa(const QString& input, const QString& modulus, const QString& exponent); + +enum class DistributionType +{ + PORTABLE, + OSX_BOUNDLE, + OS_MANAGED +}; + +API_EXPORT DistributionType getDistributionType(); + +template <class T> +QList<T> reverse(const QList<T>& list) +{ + QList<T> result; + for (const T& el : list) + result.prepend(el); + + return result; +} + +template <class T> +void removeDuplicates(QList<T>& list) +{ + QSet<T> set; + QMutableListIterator<T> i(list); + while (i.hasNext()) + { + i.next(); + if (set.contains(i.value())) + i.remove(); + else + set << i.value(); + } +} + +Q_DECLARE_METATYPE(QList<int>) + +#endif // UTILS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.cpp b/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.cpp new file mode 100644 index 0000000..fcf49af --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.cpp @@ -0,0 +1,552 @@ +#include "common/utils_sql.h" +#include "common/utils.h" +#include "db/sqlquery.h" +#include "parser/token.h" +#include "parser/lexer.h" +#include "parser/keywords.h" +#include <QHash> +#include <QPair> +#include <QString> +#include <QDebug> +#include <QMetaType> + +QString invalidIdCharacters = "[]()$\"'@*.,+-=/%&|:; \t\n<>"; +QHash<NameWrapper,QPair<QChar,QChar> > wrapperChars; +QList<NameWrapper> sqlite3Wrappers; +QList<NameWrapper> sqlite2Wrappers; + +void initUtilsSql() +{ + wrapperChars[NameWrapper::BRACKET] = QPair<QChar,QChar>('[', ']'); + wrapperChars[NameWrapper::QUOTE] = QPair<QChar,QChar>('\'', '\''); + wrapperChars[NameWrapper::BACK_QUOTE] = QPair<QChar,QChar>('`', '`'); + wrapperChars[NameWrapper::DOUBLE_QUOTE] = QPair<QChar,QChar>('"', '"'); + + sqlite3Wrappers << NameWrapper::DOUBLE_QUOTE + << NameWrapper::BRACKET + << NameWrapper::QUOTE + << NameWrapper::BACK_QUOTE; + sqlite2Wrappers << NameWrapper::DOUBLE_QUOTE + << NameWrapper::BRACKET + << NameWrapper::QUOTE; + + qRegisterMetaType<SqlQueryPtr>("SqlQueryPtr"); +} + +bool doesObjectNeedWrapping(const QString& str, Dialect dialect) +{ + if (str.isEmpty()) + return true; + + if (isObjWrapped(str, dialect)) + return false; + + if (isKeyword(str, dialect)) + return true; + + for (int i = 0; i < str.size(); i++) + if (doesObjectNeedWrapping(str[i])) + return true; + + if (str[0].isDigit()) + return true; + + return false; +} + +bool doesObjectNeedWrapping(const QChar& c) +{ + return invalidIdCharacters.indexOf(c) >= 0; +} + +bool isObjectWrapped(const QChar& c, Dialect dialect) +{ + return !doesObjectNeedWrapping(c, dialect); +} + +bool isObjectWrapped(const QChar& c) +{ + return !doesObjectNeedWrapping(c); +} + +QString wrapObjIfNeeded(const QString& obj, Dialect dialect, NameWrapper favWrapper) +{ + if (doesObjectNeedWrapping(obj, dialect)) + return wrapObjName(obj, dialect, favWrapper); + + return obj; +} + +QString wrapObjName(const QString& obj, Dialect dialect, NameWrapper favWrapper) +{ + QString result = obj; + if (result.isNull()) + result = ""; + + QPair<QChar,QChar> wrapChars = getQuoteCharacter(result, dialect, favWrapper); + + if (wrapChars.first.isNull() || wrapChars.second.isNull()) + { + qDebug() << "No quote character possible for object name: " << result; + return result; + } + result.prepend(wrapChars.first); + result.append(wrapChars.second); + return result; +} + +QString wrapObjName(const QString& obj, NameWrapper wrapper) +{ + QString result = obj; + if (wrapper == NameWrapper::null) + return result; + + result.prepend(wrapperChars[wrapper].first); + result.append(wrapperChars[wrapper].second); + return result; +} + +QPair<QChar,QChar> getQuoteCharacter(QString& obj, Dialect dialect, NameWrapper favWrapper) +{ + QList<NameWrapper> wrappers = (dialect == Dialect::Sqlite3) ? sqlite3Wrappers : sqlite2Wrappers; + + // Move favourite wrapper to front of list + if (wrappers.contains(favWrapper)) + { + wrappers.removeOne(favWrapper); + wrappers.insert(0, favWrapper); + } + + QPair<QChar,QChar> wrapChars; + foreach (NameWrapper wrapper, wrappers) + { + wrapChars = wrapperChars[wrapper]; + if (obj.indexOf(wrapChars.first) > -1) + continue; + + if (obj.indexOf(wrapChars.second) > -1) + continue; + + return wrapChars; + } + + return QPair<QChar,QChar>(); +} + +QList<QString> wrapObjNames(const QList<QString>& objList, Dialect dialect, NameWrapper favWrapper) +{ + QList<QString> results; + for (int i = 0; i < objList.size(); i++) + results << wrapObjName(objList[i], dialect, favWrapper); + + return results; +} + +QList<QString> wrapObjNamesIfNeeded(const QList<QString>& objList, Dialect dialect, NameWrapper favWrapper) +{ + QList<QString> results; + for (int i = 0; i < objList.size(); i++) + results << wrapObjIfNeeded(objList[i], dialect, favWrapper); + + return results; +} + +QList<NameWrapper> getAllNameWrappers(Dialect dialect) +{ + if (dialect == Dialect::Sqlite3) + return {NameWrapper::DOUBLE_QUOTE, NameWrapper::BRACKET, NameWrapper::BACK_QUOTE, NameWrapper::QUOTE}; + else + return {NameWrapper::DOUBLE_QUOTE, NameWrapper::BRACKET, NameWrapper::QUOTE}; +} + +QString wrapString(const QString& str) +{ + QString result = str; + result.prepend("'"); + result.append("'"); + return result; +} + +bool doesStringNeedWrapping(const QString& str) +{ + return str[0] == '\'' && str[str.length()-1] == '\''; +} + +bool isStringWrapped(const QString& str) +{ + return !doesStringNeedWrapping(str); +} + +QString wrapStringIfNeeded(const QString& str) +{ + if (isStringWrapped(str)) + return wrapString(str); + + return str; +} + +QString escapeString(QString& str) +{ + return str.replace('\'', "''"); +} + +QString escapeString(const QString& str) +{ + QString newStr = str; + return newStr.replace('\'', "''"); +} + +QString stripString(QString& str) +{ + if (str.length() <= 1) + return str; + + if (str[0] == '\'' && str[str.length()-1] == '\'') + return str.mid(1, str.length()-2); + + return str; +} + +QString stripString(const QString& str) +{ + QString newStr = str; + return stripString(newStr); +} + +QString stripEndingSemicolon(const QString& str) +{ + QString newStr = rStrip(str); + if (newStr.size() == 0) + return str; + + if (newStr[newStr.size()-1] == ';') + { + newStr.chop(1); + return newStr; + } + else + return str; +} + +QString stripObjName(const QString &str, Dialect dialect) +{ + QString newStr = str; + return stripObjName(newStr, dialect); +} + +QString stripObjName(QString &str, Dialect dialect) +{ + if (str.isNull()) + return str; + + if (str.length() <= 1) + return str; + + if (!isObjWrapped(str, dialect)) + return str; + + return str.mid(1, str.length()-2); +} + +bool isObjWrapped(const QString& str, Dialect dialect) +{ + return getObjWrapper(str, dialect) != NameWrapper::null; +} + +NameWrapper getObjWrapper(const QString& str, Dialect dialect) +{ + if (str.isEmpty()) + return NameWrapper::null; + + QList<NameWrapper> wrappers; + + if (dialect == Dialect::Sqlite2) + wrappers = sqlite2Wrappers; + else + wrappers = sqlite3Wrappers; + + foreach (NameWrapper wrapper, wrappers) + { + QPair<QChar,QChar> chars = wrapperChars[wrapper]; + if (str[0] == chars.first && str[str.length()-1] == chars.second) + return wrapper; + } + return NameWrapper::null; +} + +bool isWrapperChar(const QChar& c, Dialect dialect) +{ + QList<NameWrapper> wrappers; + if (dialect == Dialect::Sqlite2) + wrappers = sqlite2Wrappers; + else + wrappers = sqlite3Wrappers; + + foreach (NameWrapper wrapper, wrappers) + { + QPair<QChar,QChar> chars = wrapperChars[wrapper]; + if (c == chars.first || c == chars.second) + return true; + } + return false; +} + +int qHash(NameWrapper wrapper) +{ + return (uint)wrapper; +} + +QString getPrefixDb(const QString& origDbName, Dialect dialect) +{ + if (origDbName.isEmpty()) + return "main"; + else + return wrapObjIfNeeded(origDbName, dialect); +} + +bool isSystemTable(const QString &name) +{ + return name.startsWith("sqlite_"); +} + +bool isSystemIndex(const QString &name, Dialect dialect) +{ + switch (dialect) + { + case Dialect::Sqlite3: + return name.startsWith("sqlite_autoindex_"); + case Dialect::Sqlite2: + { + QRegExp re("*(*autoindex*)*"); + re.setPatternSyntax(QRegExp::Wildcard); + return re.exactMatch(name); + } + } + return false; +} + + +TokenPtr stripObjName(TokenPtr token, Dialect dialect) +{ + if (!token) + return token; + + token->value = stripObjName(token->value, dialect); + return token; +} + +QString removeComments(const QString& value) +{ + Lexer lexer(Dialect::Sqlite3); + TokenList tokens = lexer.tokenize(value); + while (tokens.remove(Token::COMMENT)) + continue; + + return tokens.detokenize(); +} + +QList<TokenList> splitQueries(const TokenList& tokenizedQuery, bool* complete) +{ + QList<TokenList> queries; + TokenList currentQueryTokens; + QString value; + int createTriggerMeter = 0; + bool insideTrigger = false; + bool completeQuery = false; + foreach (const TokenPtr& token, tokenizedQuery) + { + value = token->value.toUpper(); + if (!token->isWhitespace()) + completeQuery = false; + + if (insideTrigger) + { + if (token->type == Token::KEYWORD && value == "END") + { + insideTrigger = false; + completeQuery = true; + } + + currentQueryTokens << token; + continue; + } + + if (token->type == Token::KEYWORD) + { + if (value == "CREATE" || value == "TRIGGER" || value == "BEGIN") + createTriggerMeter++; + + if (createTriggerMeter == 3) + insideTrigger = true; + + currentQueryTokens << token; + } + else if (token->type == Token::OPERATOR && value == ";") + { + createTriggerMeter = 0; + currentQueryTokens << token; + queries << currentQueryTokens; + currentQueryTokens.clear(); + completeQuery = true; + } + else + { + currentQueryTokens << token; + } + } + + if (currentQueryTokens.size() > 0) + queries << currentQueryTokens; + + if (complete) + *complete = completeQuery; + + return queries; +} + +QStringList splitQueries(const QString& sql, Dialect dialect, bool keepEmptyQueries, bool* complete) +{ + TokenList tokens = Lexer::tokenize(sql, dialect); + QList<TokenList> tokenizedQueries = splitQueries(tokens, complete); + + QString query; + QStringList queries; + foreach (const TokenList& queryTokens, tokenizedQueries) + { + query = queryTokens.detokenize(); + if (keepEmptyQueries || !query.trimmed().isEmpty()) + queries << query; + } + + return queries; +} + +QString getQueryWithPosition(const QStringList& queries, int position, int* startPos) +{ + int currentPos = 0; + int length = 0; + + if (startPos) + *startPos = 0; + + foreach (const QString& query, queries) + { + length = query.length(); + if (position >= currentPos && position < currentPos+length) + return query; + + currentPos += length; + + if (startPos) + *startPos += length; + } + + // If we passed all queries and it happens that the cursor is just after last query - this is the query we want. + if (position == currentPos && queries.size() > 0) + { + if (startPos) + *startPos -= length; + + return queries.last(); + } + + if (startPos) + *startPos = -1; + + return QString::null; +} + +QString getQueryWithPosition(const QString& queries, int position, Dialect dialect, int* startPos) +{ + QStringList queryList = splitQueries(queries, dialect); + return getQueryWithPosition(queryList, position, startPos); +} + +QString trimBindParamPrefix(const QString& param) +{ + if (param == "?") + return param; + + if (param.startsWith("$") || param.startsWith("@") || param.startsWith(":") || param.startsWith("?")) + return param.mid(1); + + return param; +} + +QList<QueryWithParamNames> getQueriesWithParamNames(const QString& query, Dialect dialect) +{ + QList<QueryWithParamNames> results; + + TokenList allTokens = Lexer::tokenize(query, dialect); + QList<TokenList> queries = splitQueries(allTokens); + + QString queryStr; + QStringList paramNames; + foreach (const TokenList& tokens, queries) + { + paramNames.clear(); + foreach (const TokenPtr& token, tokens.filter(Token::BIND_PARAM)) + paramNames << token->value; + + queryStr = tokens.detokenize().trimmed(); + if (!queryStr.isEmpty()) + results << QueryWithParamNames(queryStr, paramNames); + } + return results; +} + +QList<QueryWithParamCount> getQueriesWithParamCount(const QString& query, Dialect dialect) +{ + QList<QueryWithParamCount> results; + + TokenList allTokens = Lexer::tokenize(query, dialect); + QList<TokenList> queries = splitQueries(allTokens); + + QString queryStr; + foreach (const TokenList& tokens, queries) + { + queryStr = tokens.detokenize().trimmed(); + if (!queryStr.isEmpty()) + results << QueryWithParamCount(queryStr, tokens.filter(Token::BIND_PARAM).size()); + } + + return results; +} + +QueryWithParamNames getQueryWithParamNames(const QString& query, Dialect dialect) +{ + TokenList allTokens = Lexer::tokenize(query, dialect); + + QStringList paramNames; + foreach (const TokenPtr& token, allTokens.filter(Token::BIND_PARAM)) + paramNames << token->value; + + return QueryWithParamNames(query, paramNames); +} + +QueryWithParamCount getQueryWithParamCount(const QString& query, Dialect dialect) +{ + TokenList allTokens = Lexer::tokenize(query, dialect); + return QueryWithParamCount(query, allTokens.filter(Token::BIND_PARAM).size()); +} + +QString commentAllSqlLines(const QString& sql) +{ + QStringList lines = splitByLines(sql); + QMutableStringListIterator it(lines); + while (it.hasNext()) + it.next().prepend("-- "); + + return joinLines(lines); +} + +QString getBindTokenName(const TokenPtr& token) +{ + if (token->type != Token::BIND_PARAM) + return QString(); + + if (token->value == "?") + return token->value; + + return token->value.mid(1); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.h b/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.h new file mode 100644 index 0000000..8e1f3ff --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/common/utils_sql.h @@ -0,0 +1,71 @@ +#ifndef UTILS_SQL_H +#define UTILS_SQL_H + +#include "dialect.h" +#include "parser/token.h" +#include "coreSQLiteStudio_global.h" +#include <QString> +#include <QChar> +#include <QPair> + +// TODO: unit tests for most of methods from this module + +enum class NameWrapper +{ + DOUBLE_QUOTE, + QUOTE, + BACK_QUOTE, + BRACKET, + null +}; + +typedef QPair<QString,QStringList> QueryWithParamNames; +typedef QPair<QString,int> QueryWithParamCount; + +API_EXPORT void initUtilsSql(); +API_EXPORT bool doesObjectNeedWrapping(const QString& str, Dialect dialect); +API_EXPORT bool doesObjectNeedWrapping(const QChar& c); +API_EXPORT bool isObjectWrapped(const QChar& c, Dialect dialect); +API_EXPORT bool isObjectWrapped(const QChar& c); +API_EXPORT bool doesStringNeedWrapping(const QString& str); +API_EXPORT bool isStringWrapped(const QString& str); +API_EXPORT QString wrapObjIfNeeded(const QString& obj, Dialect dialect, NameWrapper favWrapper = NameWrapper::null); +API_EXPORT QString wrapObjName(const QString& obj, Dialect dialect, NameWrapper favWrapper = NameWrapper::null); +API_EXPORT QString wrapObjName(const QString& obj, NameWrapper wrapper); +API_EXPORT TokenPtr stripObjName(TokenPtr token, Dialect dialect); +API_EXPORT QString stripObjName(const QString &str, Dialect dialect); +API_EXPORT QString stripObjName(QString& str, Dialect dialect); +API_EXPORT bool isObjWrapped(const QString& str, Dialect dialect); +API_EXPORT NameWrapper getObjWrapper(const QString& str, Dialect dialect); +API_EXPORT bool isWrapperChar(const QChar& c, Dialect dialect); +API_EXPORT QString wrapString(const QString& str); +API_EXPORT QString wrapStringIfNeeded(const QString& str); +API_EXPORT QString escapeString(QString &str); +API_EXPORT QString escapeString(const QString& str); +API_EXPORT QString stripString(QString& str); +API_EXPORT QString stripString(const QString& str); +API_EXPORT QString stripEndingSemicolon(const QString& str); +API_EXPORT QPair<QChar,QChar> getQuoteCharacter(QString& obj, Dialect dialect, + NameWrapper favWrapper = NameWrapper::null); +API_EXPORT QList<QString> wrapObjNames(const QList<QString>& objList, Dialect dialect = Dialect::Sqlite3, NameWrapper favWrapper = NameWrapper::null); +API_EXPORT QList<QString> wrapObjNamesIfNeeded(const QList<QString>& objList, Dialect dialect, NameWrapper favWrapper = NameWrapper::null); +API_EXPORT QList<NameWrapper> getAllNameWrappers(Dialect dialect = Dialect::Sqlite3); +API_EXPORT int qHash(NameWrapper wrapper); +API_EXPORT QString getPrefixDb(const QString& origDbName, Dialect dialect); +API_EXPORT bool isSystemTable(const QString& name); +API_EXPORT bool isSystemIndex(const QString& name, Dialect dialect); +API_EXPORT QString removeComments(const QString& value); +API_EXPORT QList<TokenList> splitQueries(const TokenList& tokenizedQueries, bool* complete = nullptr); +API_EXPORT QStringList splitQueries(const QString& sql, Dialect dialect, bool keepEmptyQueries = true, bool* complete = nullptr); +API_EXPORT QString getQueryWithPosition(const QStringList& queries, int position, int* startPos = nullptr); +API_EXPORT QString getQueryWithPosition(const QString& queries, int position, Dialect dialect, int* startPos = nullptr); +API_EXPORT QList<QueryWithParamNames> getQueriesWithParamNames(const QString& query, Dialect dialect); +API_EXPORT QList<QueryWithParamCount> getQueriesWithParamCount(const QString& query, Dialect dialect); +API_EXPORT QueryWithParamNames getQueryWithParamNames(const QString& query, Dialect dialect); +API_EXPORT QueryWithParamCount getQueryWithParamCount(const QString& query, Dialect dialect); +API_EXPORT QString trimBindParamPrefix(const QString& param); +API_EXPORT QString commentAllSqlLines(const QString& sql); +API_EXPORT QString getBindTokenName(const TokenPtr& token); + + +#endif // UTILS_SQL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/completioncomparer.cpp b/SQLiteStudio3/coreSQLiteStudio/completioncomparer.cpp new file mode 100644 index 0000000..978395f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/completioncomparer.cpp @@ -0,0 +1,439 @@ +#include "completioncomparer.h" +#include "completionhelper.h" +#include "parser/ast/sqliteselect.h" +#include "db/db.h" +#include "parser/token.h" +#include <QDebug> + +CompletionComparer::CompletionComparer(CompletionHelper *helper) + : helper(helper) +{ + dialect = helper->db->getDialect(); + init(); +} + +bool CompletionComparer::operator ()(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2) +{ + if ((token1->priority > 0 || token2->priority > 0) && token1->priority != token2->priority) + return token1->priority > token2->priority; + + if (token1->type != token2->type) + return token1->type < token2->type; + + switch (token1->type) + { + case ExpectedToken::COLUMN: + return compareColumns(token1, token2); + case ExpectedToken::TABLE: + return compareTables(token1, token2); + case ExpectedToken::INDEX: + return compareIndexes(token1, token2); + case ExpectedToken::TRIGGER: + return compareTriggers(token1, token2); + case ExpectedToken::VIEW: + return compareViews(token1, token2); + case ExpectedToken::DATABASE: + return compareDatabases(token1, token2); + case ExpectedToken::KEYWORD: + case ExpectedToken::FUNCTION: + case ExpectedToken::OPERATOR: + case ExpectedToken::PRAGMA: + return compareValues(token1, token2); + case ExpectedToken::COLLATION: + { + if (dialect == Dialect::Sqlite3) + return compareValues(token1, token2); + else + return false; + } + case ExpectedToken::OTHER: + case ExpectedToken::STRING: + case ExpectedToken::NUMBER: + case ExpectedToken::BLOB: + case ExpectedToken::NO_VALUE: + return false; + } + + return false; +} + +void CompletionComparer::init() +{ + if (helper->originalParsedQuery) + { + bool contextObjectsInitialized = false; + if (helper->originalParsedQuery->queryType == SqliteQueryType::Select) + contextObjectsInitialized = initSelect(); + + if (!contextObjectsInitialized) + { + contextColumns = helper->originalParsedQuery->getContextColumns(); + contextTables = helper->originalParsedQuery->getContextTables(); + contextDatabases = helper->originalParsedQuery->getContextDatabases(false); + } + + foreach (SelectResolver::Table table, helper->selectAvailableTables + helper->parentSelectAvailableTables) + availableTableNames += table.table; + } +} + +bool CompletionComparer::initSelect() +{ + if (!helper->originalCurrentSelectCore) + return false; + + // This is similar to what is done in init() itself, except here it's limited + // to the current select core, excluding parent statement. + contextColumns = helper->originalCurrentSelectCore->getContextColumns(false); + contextTables = helper->originalCurrentSelectCore->getContextTables(false); + contextDatabases = helper->originalCurrentSelectCore->getContextDatabases(false); + + foreach (SqliteSelect::Core* core, helper->parentSelectCores) + { + parentContextColumns += core->getContextColumns(false); + parentContextTables += core->getContextTables(false); + parentContextDatabases += core->getContextDatabases(false); + } + + if (helper->context == CompletionHelper::Context::SELECT_RESULT_COLUMN) + { + // Getting list of result columns already being selected in the query + resultColumns = helper->selectResolver->resolve(helper->currentSelectCore); + } + + return true; +} + +bool CompletionComparer::compareColumns(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2) +{ + if (!helper->parsedQuery) + return compareValues(token1, token2); + + bool ok = false; + bool result = true; + switch (helper->context) + { + case CompletionHelper::Context::SELECT_WHERE: + case CompletionHelper::Context::SELECT_GROUP_BY: + case CompletionHelper::Context::SELECT_HAVING: + case CompletionHelper::Context::SELECT_RESULT_COLUMN: + case CompletionHelper::Context::SELECT_ORDER_BY: + result = compareColumnsForSelectResCol(token1, token2, &ok); + break; + case CompletionHelper::Context::UPDATE_COLUMN: + case CompletionHelper::Context::UPDATE_WHERE: + result = compareColumnsForUpdateCol(token1, token2, &ok); + break; + case CompletionHelper::Context::DELETE_WHERE: + result = compareColumnsForDeleteCol(token1, token2, &ok); + break; + case CompletionHelper::Context::CREATE_TABLE: + result = compareColumnsForCreateTable(token1, token2, &ok); + break; + default: + return compareValues(token1, token2); + } + + if (ok) + return result; + + result = compareByContext(token1->value, token2->value, {contextColumns, parentContextColumns}, true, &ok); + if (ok) + return result; + + // Context info of the column has a strong meaning when sorting (system tables are pushed to the end) + bool firstIsSystem = token1->contextInfo.toLower().startsWith("sqlite_"); + bool secondIsSystem = token2->contextInfo.toLower().startsWith("sqlite_"); + if (firstIsSystem && !secondIsSystem) + return false; + + if (!firstIsSystem && secondIsSystem) + return true; + + return compareValues(token1->value, token2->value, true); +} + +bool CompletionComparer::compareColumnsForSelectResCol(const ExpectedTokenPtr &token1, const ExpectedTokenPtr &token2, bool *result) +{ + *result = true; + + // Checking if columns are on list of columns available in FROM clause + bool token1available = isTokenOnAvailableList(token1); + bool token2available = isTokenOnAvailableList(token2); + if (token1available && !token2available) + return true; + + if (!token1available && token2available) + return false; + + // Checking if columns are on list of columns available in FROM clause of any parent SELECT core + bool token1parentAvailable = isTokenOnParentAvailableList(token1); + bool token2parentAvailable = isTokenOnParentAvailableList(token2); + if (token1parentAvailable && !token2parentAvailable) + return true; + + if (!token1parentAvailable && token2parentAvailable) + return false; + + // Checking if columns were already mentioned in results list. + // It it was, it should be pushed back. + bool token1onResCols = isTokenOnResultColumns(token1); + bool token2onResCols = isTokenOnResultColumns(token2); + if (token1onResCols && !token2onResCols) + return false; + + if (!token1onResCols && token2onResCols) + return true; + + *result = false; + return false; +} + +bool CompletionComparer::compareColumnsForUpdateCol(const ExpectedTokenPtr &token1, const ExpectedTokenPtr &token2, bool *result) +{ + *result = true; + if (token1->contextInfo == token2->contextInfo) + return compareValues(token1->value, token2->value); + + return compareByContext(token1->contextInfo, token2->contextInfo, contextTables); +} + +bool CompletionComparer::compareColumnsForDeleteCol(const ExpectedTokenPtr &token1, const ExpectedTokenPtr &token2, bool *result) +{ + *result = true; + if (token1->contextInfo == token2->contextInfo) + return compareValues(token1->value, token2->value); + + return compareByContext(token1->contextInfo, token2->contextInfo, contextTables); +} + +bool CompletionComparer::compareColumnsForCreateTable(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2, bool* result) +{ + *result = true; + + bool token1OnAvailableList = helper->favoredColumnNames.contains(token1->value) && contextTables.contains(token1->contextInfo); + bool token2OnAvailableList = helper->favoredColumnNames.contains(token2->value) && contextTables.contains(token2->contextInfo); + if (token1OnAvailableList && !token2OnAvailableList) + return true; + + if (!token1OnAvailableList && token2OnAvailableList) + return false; + + *result = false; + return false; +} + +bool CompletionComparer::compareTables(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2) +{ + if (!helper->parsedQuery || helper->parsedQuery->queryType != SqliteQueryType::Select) + return compareValues(token1, token2); + + if (helper->context == CompletionHelper::Context::SELECT_FROM) + { + // In case the table was already mentioned in any FROM clause, we push it back. + bool token1OnAvailableList = availableTableNames.contains(token1->value); + bool token2OnAvailableList = availableTableNames.contains(token2->value); + if (token1OnAvailableList && !token2OnAvailableList) + return false; + + if (!token1OnAvailableList && token2OnAvailableList) + return true; + } + + bool ok; + bool result = compareByContext(token1->value, token2->value, contextTables, &ok); + if (ok) + return result; + + result = compareByContext(token1->contextInfo, token2->contextInfo, contextDatabases, &ok); + if (ok) + return result; + + result = compareByContext(token1->value, token2->value, parentContextTables, &ok); + if (ok) + return result; + + result = compareByContext(token1->contextInfo, token2->contextInfo, parentContextDatabases, &ok); + if (ok) + return result; + + return compareValues(token1->value, token2->value, true); +} + +bool CompletionComparer::compareIndexes(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2) +{ + return compareValues(token1, token2, true); +} + +bool CompletionComparer::compareTriggers(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2) +{ + return compareValues(token1, token2); +} + +bool CompletionComparer::compareViews(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2) +{ + return compareValues(token1, token2); +} + +bool CompletionComparer::compareDatabases(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2) +{ + if (!helper->parsedQuery || helper->parsedQuery->queryType != SqliteQueryType::Select) + return compareValues(token1, token2); + + return compareByContext(token1->value, token2->value, {contextDatabases, parentContextDatabases}); +} + +bool CompletionComparer::compareValues(const ExpectedTokenPtr &token1, const ExpectedTokenPtr &token2, bool handleSystemNames) +{ + return compareValues(token1->value, token2->value, handleSystemNames); +} + +bool CompletionComparer::compareValues(const QString &token1, const QString &token2, bool handleSystemNames) +{ + //qDebug() << "comparing" << token1 << "and" << token2 << "=" << token1.compare(token2, Qt::CaseInsensitive); + if (handleSystemNames) + { + bool firstIsSystem = token1.toLower().startsWith("sqlite_"); + bool secondIsSystem = token2.toLower().startsWith("sqlite_"); + if (firstIsSystem && !secondIsSystem) + return false; + + if (!firstIsSystem && secondIsSystem) + return true; + } + + return token1.compare(token2, Qt::CaseInsensitive) < 0; +} + +bool CompletionComparer::compareByContext(const QString &token1, const QString &token2, const QStringList &contextValues, bool *ok) +{ + return compareByContext(token1, token2, contextValues, false, ok); +} + +bool CompletionComparer::compareByContext(const QString &token1, const QString &token2, const QList<QStringList> &contextValues, bool *ok) +{ + return compareByContext(token1, token2, contextValues, false, ok); +} + +bool CompletionComparer::compareByContext(const QString &token1, const QString &token2, const QStringList &contextValues, bool handleSystemNames, bool* ok) +{ + if (ok) + *ok = true; + + bool localOk = false; + bool result = compareByContextOnly(token1, token2, contextValues, handleSystemNames, &localOk); + + if (localOk) + return result; + + // Otherwise we compare by value. + if (ok) + *ok = false; + + return compareValues(token1, token2, handleSystemNames); +} + +bool CompletionComparer::compareByContext(const QString &token1, const QString &token2, const QList<QStringList>& contextValues, bool handleSystemNames, bool* ok) +{ + if (ok) + *ok = true; + + bool localOk = false; + bool result; + + for (const QStringList& ctxValues : contextValues) + { + result = compareByContextOnly(token1, token2, ctxValues, handleSystemNames, &localOk); + if (localOk) + return result; + } + + // Otherwise we compare by value. + if (ok) + *ok = false; + + return compareValues(token1, token2, handleSystemNames); +} + +bool CompletionComparer::compareByContextOnly(const QString &token1, const QString &token2, const QStringList &contextValues, bool handleSystemNames, bool *ok) +{ + *ok = true; + + bool token1InContext = contextValues.contains(token1); + bool token2InContext = contextValues.contains(token2); + + // token1 < token2 is true only if token1 is in context and token2 is not. + // This means that token1 will be on the list before token2. + if (token1InContext && !token2InContext) + return true; + + // If token2 is in context, but token1 is not, then it's definite false. + if (!token1InContext && token2InContext) + return false; + + if (handleSystemNames) + { + bool firstIsSystem = token1.toLower().startsWith("sqlite_"); + bool secondIsSystem = token2.toLower().startsWith("sqlite_"); + if (firstIsSystem && !secondIsSystem) + return false; + + if (!firstIsSystem && secondIsSystem) + return true; + } + + *ok = false; + return false; +} + +bool CompletionComparer::isTokenOnAvailableList(const ExpectedTokenPtr &token) +{ + return isTokenOnColumnList(token, helper->selectAvailableColumns); +} + +bool CompletionComparer::isTokenOnParentAvailableList(const ExpectedTokenPtr &token) +{ + return isTokenOnColumnList(token, helper->parentSelectAvailableColumns); +} + +bool CompletionComparer::isTokenOnResultColumns(const ExpectedTokenPtr &token) +{ + return isTokenOnColumnList(token, resultColumns); +} + +bool CompletionComparer::isTokenOnColumnList(const ExpectedTokenPtr &token, const QList<SelectResolver::Column> &columnList) +{ + foreach (SelectResolver::Column column, columnList) + { + // If column name doesn't match, then it's not this column + if (token->value.compare(column.column, Qt::CaseInsensitive) != 0) + continue; + + // At this point, column name is matched + if (token->prefix.isNull() && token->contextInfo.isNull()) + { + // No prefix, nor context info, just column name. + return true; + } + + // Table alias or just table name? + QString toCompareWithPrefix; + if (!column.tableAlias.isNull()) + toCompareWithPrefix = column.tableAlias; + else + toCompareWithPrefix = column.table; + + // Do we have actual prefix, or just context information and transparent (null) prefix? + QString prefix; +// if (!token->prefix.isNull()) +// prefix = token->prefix; +// else + prefix = token->contextInfo; + + // Does the table/alias match prefix? + if (prefix.compare(column.table, Qt::CaseInsensitive) == 0) + return true; + } + return false; +} + diff --git a/SQLiteStudio3/coreSQLiteStudio/completioncomparer.h b/SQLiteStudio3/coreSQLiteStudio/completioncomparer.h new file mode 100644 index 0000000..d482f3a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/completioncomparer.h @@ -0,0 +1,74 @@ +#ifndef COMPLETIONCOMPARER_H +#define COMPLETIONCOMPARER_H + +#include "expectedtoken.h" +#include "dialect.h" +#include "selectresolver.h" + +class CompletionHelper; + +class CompletionComparer +{ + public: + explicit CompletionComparer(CompletionHelper* helper); + + bool operator()(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2); + + private: + CompletionHelper* helper = nullptr; + Dialect dialect; + /** + * @brief contextDatabases + * Context objects are any names mentioned anywhere in the query at the same level as completion takes place. + * The level means the sub-query level, for example in case of sub selects. + */ + QStringList contextDatabases; + QStringList contextTables; + QStringList contextColumns; + + /** + * @brief parentContextDatabases + * Parent context objects are any names mentioned anywhere in the the query at all upper levels. + */ + QStringList parentContextDatabases; + QStringList parentContextTables; + QStringList parentContextColumns; + QList<SelectResolver::Column> resultColumns; + + /** + * @brief availableTableNames + * Names of all tables mentioned in FROM clause in the current and all parent select cores. + */ + QStringList availableTableNames; + + void init(); + bool initSelect(); + bool compareColumns(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2); + bool compareColumnsForSelectResCol(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2, bool* result); + bool compareColumnsForUpdateCol(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2, bool* result); + bool compareColumnsForDeleteCol(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2, bool* result); + bool compareColumnsForCreateTable(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2, bool* result); + bool compareTables(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2); + bool compareIndexes(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2); + bool compareTriggers(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2); + bool compareViews(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2); + bool compareDatabases(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2); + bool compareValues(const ExpectedTokenPtr& token1, const ExpectedTokenPtr& token2, bool handleSystemNames = false); + bool compareValues(const QString& token1, const QString& token2, bool handleSystemNames = false); + bool compareByContext(const QString &token1, const QString &token2, + const QStringList& contextValues, bool* ok = nullptr); + bool compareByContext(const QString &token1, const QString &token2, + const QList<QStringList>& contextValues, bool* ok = nullptr); + bool compareByContext(const QString &token1, const QString &token2, + const QStringList& contextValues, bool handleSystemNames, bool* ok = nullptr); + bool compareByContext(const QString &token1, const QString &token2, + const QList<QStringList>& contextValues, bool handleSystemNames, bool* ok = nullptr); + bool compareByContextOnly(const QString &token1, const QString &token2, + const QStringList& contextValues, bool handleSystemNames, bool* ok); + bool isTokenOnAvailableList(const ExpectedTokenPtr& token); + bool isTokenOnParentAvailableList(const ExpectedTokenPtr& token); + bool isTokenOnResultColumns(const ExpectedTokenPtr& token); + static bool isTokenOnColumnList(const ExpectedTokenPtr& token, const QList<SelectResolver::Column>& columnList); +}; + +#endif // COMPLETIONCOMPARER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/completionhelper.cpp b/SQLiteStudio3/coreSQLiteStudio/completionhelper.cpp new file mode 100644 index 0000000..99eb334 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/completionhelper.cpp @@ -0,0 +1,1425 @@ +#include "completionhelper.h" +#include "completioncomparer.h" +#include "db/db.h" +#include "parser/keywords.h" +#include "parser/parser.h" +#include "parser/lexer.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "dbattacher.h" +#include "common/utils.h" +#include "common/utils_sql.h" +#include "services/dbmanager.h" +#include <QStringList> +#include <QDebug> + +QStringList sqlite3Pragmas; +QStringList sqlite2Pragmas; +QStringList sqlite3Functions; +QStringList sqlite2Functions; + +bool CompletionHelper::enableLemonDebug = false; + +CompletionHelper::CompletionHelper(const QString &sql, Db* db) + : CompletionHelper(sql, sql.length(), db) +{ +} + +CompletionHelper::CompletionHelper(const QString &sql, quint32 cursorPos, Db* db) + : db(db), cursorPosition(cursorPos), fullSql(sql) +{ + schemaResolver = new SchemaResolver(db); + selectResolver = new SelectResolver(db, fullSql); + selectResolver->ignoreInvalidNames = true; + dbAttacher = SQLITESTUDIO->createDbAttacher(db); +} + +void CompletionHelper::init() +{ + sqlite3Pragmas << "auto_vacuum" << "automatic_index" << "busy_timeout" << "cache_size" + << "case_sensitive_like" << "checkpoint_fullfsync" << "collation_list" + << "compile_options" << "count_changes" << "data_store_directory" + << "database_list" << "default_cache_size" << "empty_result_callbacks" + << "encoding" << "foreign_key_check" << "foreign_key_list" << "foreign_keys" + << "freelist_count" << "full_column_names" << "fullfsync" + << "ignore_check_constraints" << "incremental_vacuum" << "index_info" + << "index_list" << "integrity_check" << "journal_mode" << "journal_size_limit" + << "legacy_file_format" << "locking_mode" << "max_page_count" << "page_count" + << "page_size" << "quick_check" << "read_uncommitted" << "recursive_triggers" + << "reverse_unordered_selects" << "schema_version" << "secure_delete" + << "short_column_names" << "shrink_memory" << "synchronous" << "table_info" + << "temp_store" << "temp_store_directory" << "user_version" + << "wal_autocheckpoint" << "wal_checkpoint" << "writable_schema"; + + sqlite2Pragmas << "cache_size" << "count_changes" << "database_list" << "default_cache_size" + << "default_synchronous" << "default_temp_store" << "empty_result_callbacks" + << "foreign_key_list" << "full_column_names" << "index_info" << "index_list" + << "integrity_check" << "parser_trace" << "show_datatypes" << "synchronous" + << "table_info" << "temp_store"; + + sqlite3Functions << "avg(X)" << "count(X)" << "count(*)" << "group_concat(X)" + << "group_concat(X,Y)" << "max(X)" << "min(X)" << "sum(X)" << "total(X)" + << "abs(X)" << "changes()" << "char(X1,X2,...,XN)" << "coalesce(X,Y,...)" + << "glob(X,Y)" << "ifnull(X,Y)" << "instr(X,Y)" << "hex(X)" + << "last_insert_rowid()" << "length(X)" << "like(X,Y)" << "like(X,Y,Z)" + << "load_extension(X,Y)" << "lower(X)" << "ltrim(X)" << "ltrim(X,Y)" + << "max(X,Y,...)" << "min(X,Y,...)" << "nullif(X,Y)" << "quote(X)" + << "random()" << "randomblob(N)" << "hex(randomblob(16))" + << "lower(hex(randomblob(16)))" << "replace(X,Y,Z)" << "round(X)" + << "round(X,Y)" << "rtrim(X)" << "rtrim(X,Y)" << "soundex(X)" + << "sqlite_compileoption_get(N)" << "sqlite_compileoption_used(X)" + << "sqlite_source_id()" << "sqlite_version()" << "substr(X,Y,Z)" + << "substr(X,Y)" << "total_changes()" << "trim(X)" << "trim(X,Y)" + << "typeof(X)" << "unicode(X)" << "upper(X)" << "zeroblob(N)" + << "date(timestr,mod,mod,...)" << "time(timestr,mod,mod,...)" + << "datetime(timestr,mod,mod,...)" << "julianday(timestr,mod,mod,...)" + << "strftime(format,timestr,mod,mod,...)" << "likelihood(X,Y)" + << "likely(X)" << "unlikely(X)"; + + sqlite2Functions << "abs(X)" << "coalesce(X,Y,...)" << "glob(X,Y)" << "ifnull(X,Y)" + << "last_insert_rowid()" << "length(X)" << "like(X,Y)" << "lower(X)" + << "max(X,Y,...)" << "min(X,Y,...)" << "nullif(X,Y)" << "random(*)" + << "round(X,)" << "round(X,Y)" << "soundex(X)" << "sqlite_version(*)" + << "substr(X,Y,Z)" << "typeof(X)" << "upper(X)" << "avg(X)" << "count(X)" + << "count(*)" << "max(X)" << "min(X)" << "sum(X)"; + + sqlite2Pragmas.sort(); + sqlite3Pragmas.sort(); + sqlite2Functions.sort(); + sqlite3Functions.sort(); +} + +CompletionHelper::~CompletionHelper() +{ + if (schemaResolver) + { + delete schemaResolver; + schemaResolver = nullptr; + } + + if (selectResolver) + { + delete selectResolver; + selectResolver = nullptr; + } + + if (dbAttacher) + { + delete dbAttacher; + dbAttacher = nullptr; + } +} + +CompletionHelper::Results CompletionHelper::getExpectedTokens() +{ + if (!db || !db->isValid()) + return Results(); + + // Get SQL up to the current cursor position. + QString adjustedSql = fullSql.mid(0, cursorPosition); + + // If asked for completion when being in the middle of keyword or ID, + // then remove that unfinished keyword/ID from sql and put it into + // the final filter - to be used at the end of this method. + QString finalFilter = QString::null; + bool wrappedFilter = false; + adjustedSql = removeStartedToken(adjustedSql, finalFilter, wrappedFilter); + + // Parse SQL up to cursor position, get accepted tokens and tokens that were parsed. + Parser parser(db->getDialect()); + TokenList tokens = parser.getNextTokenCandidates(adjustedSql); + TokenList parsedTokens = parser.getParsedTokens(); + + // Parse the full sql in regular mode to extract query statement + // for the results comparer and table-alias mapping. + parseFullSql(); + + // Collect used db names in original query (before using attach names) + collectOtherDatabases(); + + // Handle transparent db attaching + attachDatabases(); + + // Get previous ID tokens (db and table) if any + extractPreviousIdTokens(parsedTokens); + + // Now, that we have parsed query, we can extract some useful information + // depending on the type of query we have. + extractQueryAdditionalInfo(); + + // Convert accepted tokens to expected tokens + QList<ExpectedTokenPtr> results; + foreach (TokenPtr token, tokens) + results += getExpectedTokens(token); + + // Filter redundant tokens from results + filterContextKeywords(results, tokens); + filterOtherId(results, tokens); + filterDuplicates(results); + + // ...and sort the output. + sort(results); + + // Detach any databases attached for the completer needs + detachDatabases(); + + Results complexResult; + complexResult.expectedTokens = results; + complexResult.partialToken = finalFilter; + complexResult.wrappedToken = wrappedFilter; + return complexResult; +} + +QList<ExpectedTokenPtr> CompletionHelper::getExpectedTokens(TokenPtr token) +{ + QList<ExpectedTokenPtr> results; + + // Initial conditions + if (previousId) + { + if (!token->isDbObjectType()) + return results; + + if (twoIdsBack && token->type != Token::CTX_COLUMN) + return results; + } + + // Main routines + switch (token->type) + { + case Token::CTX_ROWID_KW: + results += getExpectedToken(ExpectedToken::KEYWORD, token->value); + break; + case Token::CTX_NEW_KW: + { + if (context == Context::CREATE_TRIGGER) + results += getExpectedToken(ExpectedToken::TABLE, "new", QString::null, tr("New row reference"), 1); + + break; + } + case Token::CTX_OLD_KW: + { + if (context == Context::CREATE_TRIGGER) + results += getExpectedToken(ExpectedToken::TABLE, "old", QString::null, tr("Old row reference"), 1); + + break; + } + case Token::CTX_TABLE_NEW: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("New table name")); + break; + case Token::CTX_INDEX_NEW: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("New index name")); + break; + case Token::CTX_VIEW_NEW: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("New view name")); + break; + case Token::CTX_TRIGGER_NEW: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("New trigger name")); + break; + case Token::CTX_ALIAS: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("Table or column alias")); + break; + case Token::CTX_TRANSACTION: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("transaction name")); + break; + case Token::CTX_COLUMN_NEW: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("New column name")); + break; + case Token::CTX_COLUMN_TYPE: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("Column data type")); + break; + case Token::CTX_CONSTRAINT: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("Constraint name")); + break; + case Token::CTX_FK_MATCH: + { + foreach (QString kw, getFkMatchKeywords()) + results += getExpectedToken(ExpectedToken::KEYWORD, kw); + + break; + } + case Token::CTX_PRAGMA: + results += getPragmas(db->getDialect()); + break; + case Token::CTX_ERROR_MESSAGE: + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("Error message")); + break; + case Token::CTX_COLUMN: + { + results += getColumns(); + break; + } + case Token::CTX_TABLE: + { + results += getTables(); + break; + } + case Token::CTX_INDEX: + { + results += getIndexes(); + break; + } + case Token::CTX_TRIGGER: + { + results += getTriggers(); + break; + } + case Token::CTX_VIEW: + { + results += getViews(); + break; + } + case Token::CTX_DATABASE: + { + results += getDatabases(); + break; + } + case Token::CTX_FUNCTION: + { + results += getFunctions(db); + break; + } + case Token::CTX_COLLATION: + { + if (db->getDialect() == Dialect::Sqlite2) + { + // SQLite 2 doesn't really support collation. It has collations + // in grammar, but doesn't make use of them. There's no list + // of collations to be suggested. + results += getExpectedToken(ExpectedToken::NO_VALUE, QString::null, QString::null, tr("Collation name")); + } + else + { + results += getCollations(); + } + break; + } + case Token::CTX_JOIN_OPTS: + { + foreach (QString joinKw, getJoinKeywords()) + results += getExpectedToken(ExpectedToken::KEYWORD, joinKw); + break; + } + case Token::OTHER: + results += getExpectedToken(ExpectedToken::OTHER, QString::null, QString::null, tr("Any word")); + break; + case Token::STRING: + results += getExpectedToken(ExpectedToken::STRING); + break; + case Token::FLOAT: + results += getExpectedToken(ExpectedToken::NUMBER); + break; + case Token::INTEGER: + results += getExpectedToken(ExpectedToken::NUMBER); + break; + case Token::OPERATOR: + results += getExpectedToken(ExpectedToken::OPERATOR, token->value); + break; + case Token::PAR_LEFT: + results += getExpectedToken(ExpectedToken::OPERATOR, "("); + break; + case Token::PAR_RIGHT: + results += getExpectedToken(ExpectedToken::OPERATOR, ")"); + break; + case Token::BLOB: + results += getExpectedToken(ExpectedToken::BLOB); + break; + case Token::KEYWORD: + results += getExpectedToken(ExpectedToken::KEYWORD, token->value); + break; + case Token::INVALID: + // No-op + break; + case Token::BIND_PARAM: + // No-op + break; + case Token::SPACE: + // No-op + break; + case Token::COMMENT: + // No-op + break; + } + + return results; +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type) +{ + ExpectedToken* token = new ExpectedToken(); + token->type = type; + return ExpectedTokenPtr(token); +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type, const QString &value) +{ + ExpectedTokenPtr token = getExpectedToken(type); + token->value = value; + return token; +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type, const QString &value, + int priority) +{ + ExpectedTokenPtr token = getExpectedToken(type, value); + token->priority = priority; + return token; +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString& contextInfo) +{ + ExpectedTokenPtr token = getExpectedToken(type, value); + token->contextInfo = contextInfo; + token->label = contextInfo; + return token; +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString& contextInfo, int priority) +{ + ExpectedTokenPtr token = getExpectedToken(type, value, contextInfo); + token->priority = priority; + return token; +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString& contextInfo, + const QString& label) +{ + ExpectedTokenPtr token = getExpectedToken(type, value, contextInfo); + token->label = label; + return token; +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString& contextInfo, + const QString& label, int priority) +{ + ExpectedTokenPtr token = getExpectedToken(type, value, contextInfo, label); + token->priority = priority; + return token; +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString& contextInfo, + const QString& label, + const QString& prefix) +{ + ExpectedTokenPtr token = getExpectedToken(type, value, contextInfo, label); + token->prefix = prefix; + return token; +} + +ExpectedTokenPtr CompletionHelper::getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString& contextInfo, + const QString& label, + const QString& prefix, + int priority) +{ + ExpectedTokenPtr token = getExpectedToken(type, value, contextInfo, label, prefix); + token->priority = priority; + return token; +} + +bool CompletionHelper::validatePreviousIdForGetObjects(QString* dbName) +{ + QString localDbName; + if (previousId) + { + localDbName = previousId->value; + QStringList databases = schemaResolver->getDatabases().toList(); + databases += DBLIST->getDbNames(); + if (!databases.contains(localDbName, Qt::CaseInsensitive)) + return false; // if db is not on the set, then getObjects() would return empty list anyway; + + if (dbName) + *dbName = localDbName; + } + return true; +} + +QList<ExpectedTokenPtr> CompletionHelper::getTables() +{ + QString dbName; + if (!validatePreviousIdForGetObjects(&dbName)) + return QList<ExpectedTokenPtr>(); + + QList<ExpectedTokenPtr> tables = getObjects(ExpectedToken::TABLE); + for (const QString& otherDb : otherDatabasesToLookupFor) + tables += getObjects(ExpectedToken::TABLE, otherDb); + + tables += getExpectedToken(ExpectedToken::TABLE, "sqlite_master", dbName); + tables += getExpectedToken(ExpectedToken::TABLE, "sqlite_temp_master", dbName); + return tables; +} + +QList<ExpectedTokenPtr> CompletionHelper::getIndexes() +{ + if (!validatePreviousIdForGetObjects()) + return QList<ExpectedTokenPtr>(); + + return getObjects(ExpectedToken::INDEX); +} + +QList<ExpectedTokenPtr> CompletionHelper::getTriggers() +{ + if (!validatePreviousIdForGetObjects()) + return QList<ExpectedTokenPtr>(); + + return getObjects(ExpectedToken::TRIGGER); +} + +QList<ExpectedTokenPtr> CompletionHelper::getViews() +{ + if (!validatePreviousIdForGetObjects()) + return QList<ExpectedTokenPtr>(); + + return getObjects(ExpectedToken::VIEW); +} + +QList<ExpectedTokenPtr> CompletionHelper::getDatabases() +{ + QList<ExpectedTokenPtr> results; + + results += getExpectedToken(ExpectedToken::DATABASE, "main", "main", tr("Default database")); + results += getExpectedToken(ExpectedToken::DATABASE, "temp", "temp", tr("Temporary objects database")); + + QSet<QString> databases = schemaResolver->getDatabases(); + foreach (QString dbName, databases) + { + if (dbAttacher->getDbNameToAttach().containsRight(dbName, Qt::CaseInsensitive)) + continue; + + results += getExpectedToken(ExpectedToken::DATABASE, dbName); + } + + Dialect dialect = db->getDialect(); + + foreach (Db* otherDb, DBLIST->getValidDbList()) + { + if (otherDb->getDialect() != dialect) + continue; + + results += getExpectedToken(ExpectedToken::DATABASE, otherDb->getName()); + } + + return results; +} + +QList<ExpectedTokenPtr> CompletionHelper::getObjects(ExpectedToken::Type type) +{ + if (previousId) + return getObjects(type, previousId->value); + else + return getObjects(type, QString()); +} + +QList<ExpectedTokenPtr> CompletionHelper::getObjects(ExpectedToken::Type type, const QString& database) +{ + QString dbName; + QString originalDbName; + if (!database.isNull()) + { + dbName = translateDatabase(database); + originalDbName = database; + } + + QString typeStr; + switch (type) + { + case ExpectedToken::TABLE: + typeStr = "table"; + break; + case ExpectedToken::INDEX: + typeStr = "index"; + break; + case ExpectedToken::TRIGGER: + typeStr = "trigger"; + break; + case ExpectedToken::VIEW: + typeStr = "view"; + break; + default: + qWarning() << "Invalid type passed to CompletionHelper::getObject()."; + return QList<ExpectedTokenPtr>(); + } + + QList<ExpectedTokenPtr> results; + foreach (QString object, schemaResolver->getObjects(dbName, typeStr)) + results << getExpectedToken(type, object, originalDbName); + + return results; +} + +QList<ExpectedTokenPtr> CompletionHelper::getColumns() +{ + QList<ExpectedTokenPtr> results; + if (previousId) + { + if (twoIdsBack) + results += getColumns(twoIdsBack->value, previousId->value); + else + results += getColumns(previousId->value); + } + else + { + results += getColumnsNoPrefix(); + } + + if (favoredColumnNames.size() > 0) + results += getFavoredColumns(results); + + return results; +} + +QList<ExpectedTokenPtr> CompletionHelper::getColumnsNoPrefix() +{ + QList<ExpectedTokenPtr> results; + + // Use available columns for other databases (transparently attached) + QString ctx; + for (SelectResolver::Column& column : selectAvailableColumns) + { + if (column.database.isNull()) + continue; + + if (column.tableAlias.isNull()) + ctx = translateDatabaseBack(column.database)+"."+column.table; + else + ctx = column.tableAlias+" = "+translateDatabaseBack(column.database)+"."+column.table; + + results << getExpectedToken(ExpectedToken::COLUMN, column.column, ctx); + } + + // No db or table provided. For each column its table is remembered, + // so in case some column repeats in more than one table, then we need + // to add prefix for the completion proposal. + QHash<QString,QStringList> columnList; + + // Getting all tables for main db. If any column repeats in many tables, + // then tables are stored as a list for the same column. + foreach (QString table, schemaResolver->getTables(QString::null)) + foreach (QString column, schemaResolver->getTableColumns(table)) + columnList[column] += table; + + // Now, for each column the expected token is created. + // If a column occured in more tables, then multiple expected tokens + // are created to reflect all possible tables. + QHashIterator<QString,QStringList> it(columnList); + while (it.hasNext()) + { + it.next(); + results << getColumnsNoPrefix(it.key(), it.value()); + } + + return results; +} + +QList<ExpectedTokenPtr> CompletionHelper::getColumnsNoPrefix(const QString& column, const QStringList& tables) +{ + QList<ExpectedTokenPtr> results; + + QStringList availableTableNames; + foreach (SelectResolver::Table resolverTable, selectAvailableTables + parentSelectAvailableTables) + { + // This method is called only when collecting columns of tables in "main" database. + // If here we have resolved table from other database, we don't compare it. + if (!resolverTable.database.isNull() && resolverTable.database.toLower() != "main") + continue; + + availableTableNames += resolverTable.table; + } + + int availableTableCount = 0; + foreach (QString availTable, availableTableNames) + if (tables.contains(availTable)) + availableTableCount++; + + foreach (QString table, tables) + { + // Table prefix is used only if there is more than one table in FROM clause + // that has this column, or table alias was used. + if (!currentSelectCore || (availableTableCount <= 1 && !tableToAlias.contains(table))) + results << getExpectedToken(ExpectedToken::COLUMN, column, table); + else + { + // The prefix table might need translation to an alias. + QString prefix = table; + QString label = table; + if (tableToAlias.contains(prefix)) + { + foreach (prefix, tableToAlias[prefix]) + { + label = prefix+" = "+table; + results << getExpectedToken(ExpectedToken::COLUMN, column, table, label, prefix); + } + } + else + results << getExpectedToken(ExpectedToken::COLUMN, column, table, label, prefix); + } + } + + return results; +} + +QList<ExpectedTokenPtr> CompletionHelper::getColumns(const QString &prefixTable) +{ + QList<ExpectedTokenPtr> results; + + QString label = prefixTable; + QString table = prefixTable; + QString dbName; + if (aliasToTable.contains(prefixTable)) + { + Table tableAndDb = aliasToTable.value(prefixTable); + table = tableAndDb.getTable(); + dbName = tableAndDb.getDatabase(); + label = prefixTable+" = "+table; + } + + // CREATE TRIGGER has a special "old" and "new" keywords as aliases for deleted/inserted/updated rows. + // They should refer to a table that the trigger is created for. + QString tableLower = table; + if (context == Context::CREATE_TRIGGER && (tableLower == "old" || tableLower == "new")) + { + if (!createTriggerTable.isNull()) + { + table = createTriggerTable; + label = createTriggerTable; + } + else + { + SqliteCreateTriggerPtr createTrigger = parsedQuery.dynamicCast<SqliteCreateTrigger>(); + if (createTrigger && !createTrigger->table.isNull()) + { + table = createTrigger->table; + label = table; + } + } + } + + // Get columns for given table in main db. + foreach (const QString& column, schemaResolver->getTableColumns(dbName, table)) + results << getExpectedToken(ExpectedToken::COLUMN, column, table, label); + + return results; +} + +QList<ExpectedTokenPtr> CompletionHelper::getColumns(const QString &prefixDb, const QString &prefixTable) +{ + QList<ExpectedTokenPtr> results; + + // Get columns for given table in given db. + QString context = prefixDb+"."+prefixTable; + foreach (const QString& column, schemaResolver->getTableColumns(translateDatabase(prefixDb), prefixTable)) + results << getExpectedToken(ExpectedToken::COLUMN, column, context); + + return results; +} + +QList<ExpectedTokenPtr> CompletionHelper::getFavoredColumns(const QList<ExpectedTokenPtr>& resultsSoFar) +{ + // Prepare list that doesn't create duplicates with the results we already have. + // Since results so far have more chance to provide context into, we will keep the original ones + // from results so far and avoid adding then from favored list. + QStringList columnsToAdd = favoredColumnNames; + foreach (const ExpectedTokenPtr& token, resultsSoFar) + { + if (token->prefix.isNull() && columnsToAdd.contains(token->value)) + columnsToAdd.removeOne(token->value); + } + + QString ctxInfo; + if (context == Context::CREATE_TABLE && parsedQuery) + ctxInfo = parsedQuery.dynamicCast<SqliteCreateTable>()->table; + + QList<ExpectedTokenPtr> results; + foreach (const QString& column, columnsToAdd) + results << getExpectedToken(ExpectedToken::COLUMN, column, ctxInfo); + + return results; +} + +QList<ExpectedTokenPtr> CompletionHelper::getFunctions(Db* db) +{ + // TODO to do later - make function completion more verbose, + // like what are arguments of the function, etc. + Dialect dialect = db->getDialect(); + + QStringList functions; + if (dialect == Dialect::Sqlite2) + functions = sqlite2Functions; + else + functions = sqlite3Functions; + + for (FunctionManager::ScriptFunction* fn : FUNCTIONS->getScriptFunctionsForDatabase(db->getName())) + functions << fn->toString(); + + for (FunctionManager::NativeFunction* fn : FUNCTIONS->getAllNativeFunctions()) + functions << fn->toString(); + + QList<ExpectedTokenPtr> expectedTokens; + foreach (QString function, functions) + expectedTokens += getExpectedToken(ExpectedToken::FUNCTION, function); + + return expectedTokens; +} + +QList<ExpectedTokenPtr> CompletionHelper::getPragmas(Dialect dialect) +{ + QStringList pragmas; + if (dialect == Dialect::Sqlite2) + pragmas = sqlite2Pragmas; + else + pragmas = sqlite3Pragmas; + + QList<ExpectedTokenPtr> expectedTokens; + foreach (QString pragma, pragmas) + expectedTokens += getExpectedToken(ExpectedToken::PRAGMA, pragma); + + return expectedTokens; +} + +QList<ExpectedTokenPtr> CompletionHelper::getCollations() +{ + SqlQueryPtr results = db->exec("PRAGMA collation_list;"); + if (results->isError()) + { + qWarning() << "Got error when trying to get collation_list: " + << results->getErrorText(); + } + QList<ExpectedTokenPtr> expectedTokens; + foreach (SqlResultsRowPtr row, results->getAll()) + expectedTokens += getExpectedToken(ExpectedToken::COLLATION, row->value("name").toString()); + + return expectedTokens; +} + +TokenPtr CompletionHelper::getPreviousDbOrTable(const TokenList &parsedTokens) +{ + // First check if we even get to deal with db.table or table.column. + // In order to do that we iterate backwards starting from the end. + TokenPtr token; + QListIterator<TokenPtr> it(parsedTokens); + it.toBack(); + + if (!it.hasPrevious()) + return TokenPtr(); // No tokens at all. Shouldn't really happen. + + token = it.previous(); + + // Skip spaces and comments + while ((token->type == Token::SPACE || token->type == Token::COMMENT) && it.hasPrevious()) + token = it.previous(); + + // Check if first non-space and non-comment token is our dot. + if (token->type != Token::OPERATOR || token->value != ".") + return TokenPtr(); // Previous token is not a dot. + + // We have a dot, now let's look for another token before. + if (!it.hasPrevious()) + return TokenPtr(); // No more tokens left in front. + + token = it.previous(); + + // Skip spaces and comments + while ((token->type == Token::SPACE || token->type == Token::COMMENT) && it.hasPrevious()) + token = it.previous(); + + // Check if this token is an ID. + if (token->type != Token::OTHER) + return TokenPtr(); // One more token before is not an ID. + + // Okay, so now we now we have "some_id." + // Since this method is called in known context (looking for either db or table), + // we don't need to find out what the "some_id" is. We simple return its value. + return token; +} + +void CompletionHelper::attachDatabases() +{ + if (!parsedQuery) + return; + + if (!dbAttacher->attachDatabases(parsedQuery)) + return; + + QString query = parsedQuery->detokenize(); + + Parser parser(db->getDialect()); + if (parser.parse(query, true) && !parser.getQueries().isEmpty()) + parsedQuery = parser.getQueries().first(); +} + +void CompletionHelper::detachDatabases() +{ + dbAttacher->detachDatabases(); +} + +QString CompletionHelper::translateDatabase(const QString& dbName) +{ + if (!dbAttacher->getDbNameToAttach().containsLeft(dbName, Qt::CaseInsensitive)) + return dbName; + + return dbAttacher->getDbNameToAttach().valueByLeft(dbName, Qt::CaseInsensitive); +} + +QString CompletionHelper::translateDatabaseBack(const QString& dbName) +{ + if (!dbAttacher->getDbNameToAttach().containsRight(dbName, Qt::CaseInsensitive)) + return dbName; + + return dbAttacher->getDbNameToAttach().valueByRight(dbName, Qt::CaseInsensitive); +} + +void CompletionHelper::collectOtherDatabases() +{ + otherDatabasesToLookupFor.clear(); + if (!parsedQuery) + return; + + otherDatabasesToLookupFor = parsedQuery->getContextDatabases(); +} + +QString CompletionHelper::removeStartedToken(const QString& adjustedSql, QString& finalFilter, bool& wrappedFilter) +{ + QString result = adjustedSql; + + Lexer lexer(db->getDialect()); + TokenList tokens = lexer.tokenize(adjustedSql); + if (tokens.size() == 0) + return result; + + TokenPtr lastToken = tokens.last(); + + if (isFilterType(lastToken->type)) + { + result = Lexer::detokenize(tokens.mid(0, tokens.size()-1)); + finalFilter = lastToken->value; + + if (finalFilter.length() > 0 && isWrapperChar(finalFilter[0], db->getDialect())) + { + finalFilter = finalFilter.mid(1); + wrappedFilter = true; + } + } + return result; +} + +void CompletionHelper::filterContextKeywords(QList<ExpectedTokenPtr> &resultsSoFar, const TokenList &tokens) +{ + bool wasJoinKw = false; + bool wasFkMatchKw = false; + foreach (TokenPtr token, tokens) + { + if (token->type == Token::CTX_JOIN_OPTS) + wasJoinKw = true; + + if (token->type == Token::CTX_FK_MATCH) + wasFkMatchKw = true; + } + + if (wasJoinKw && wasFkMatchKw) + return; + + QMutableListIterator<ExpectedTokenPtr> it(resultsSoFar); + while (it.hasNext()) + { + ExpectedTokenPtr token = it.next(); + if (token->type != ExpectedToken::KEYWORD) + continue; + + if ( + (!wasJoinKw && isJoinKeyword(token->value)) || + (!wasFkMatchKw && isFkMatchKeyword(token->value)) + ) + it.remove(); + } +} + +void CompletionHelper::filterOtherId(QList<ExpectedTokenPtr> &resultsSoFar, const TokenList &tokens) +{ + bool wasCtx = false; + foreach (TokenPtr token, tokens) + { + switch (token->type) + { + case Token::CTX_COLUMN: + case Token::CTX_TABLE: + case Token::CTX_DATABASE: + case Token::CTX_FUNCTION: + case Token::CTX_COLLATION: + case Token::CTX_INDEX: + case Token::CTX_TRIGGER: + case Token::CTX_VIEW: + case Token::CTX_JOIN_OPTS: + case Token::CTX_TABLE_NEW: + case Token::CTX_INDEX_NEW: + case Token::CTX_VIEW_NEW: + case Token::CTX_TRIGGER_NEW: + case Token::CTX_ALIAS: + case Token::CTX_TRANSACTION: + case Token::CTX_COLUMN_NEW: + case Token::CTX_COLUMN_TYPE: + case Token::CTX_CONSTRAINT: + case Token::CTX_FK_MATCH: + case Token::CTX_PRAGMA: + case Token::CTX_ROWID_KW: + case Token::CTX_NEW_KW: + case Token::CTX_OLD_KW: + case Token::CTX_ERROR_MESSAGE: + wasCtx = true; + break; + default: + break; + } + if (wasCtx) + break; + } + + if (!wasCtx) + return; + + QMutableListIterator<ExpectedTokenPtr> it(resultsSoFar); + while (it.hasNext()) + { + ExpectedTokenPtr token = it.next(); + if (token->type == ExpectedToken::OTHER) + it.remove(); + } +} + +void CompletionHelper::filterDuplicates(QList<ExpectedTokenPtr>& resultsSoFar) +{ + QSet<ExpectedTokenPtr> set = resultsSoFar.toSet(); + resultsSoFar = set.toList(); +} + +void CompletionHelper::applyFilter(QList<ExpectedTokenPtr>& resultsSoFar, const QString& filter) +{ + if (filter.isEmpty()) + return; + + QMutableListIterator<ExpectedTokenPtr> it(resultsSoFar); + while (it.hasNext()) + { + ExpectedTokenPtr token = it.next(); + if (!token->value.startsWith(filter, Qt::CaseInsensitive)) + it.remove(); + } +} + +bool CompletionHelper::isFilterType(Token::Type type) +{ + switch (type) + { + case Token::COMMENT: + case Token::SPACE: + case Token::PAR_LEFT: + case Token::PAR_RIGHT: + case Token::OPERATOR: + return false; + default: + return true; + } +} + +void CompletionHelper::parseFullSql() +{ + Dialect dialect = db->getDialect(); + + Parser parser(dialect); + parser.setLemonDebug(enableLemonDebug); + + QString sql = fullSql; + + // Selecting query at cursor position + QString query = getQueryWithPosition(sql, cursorPosition, dialect); + + // Token list of the query. Also useful, not only parsed query. + queryTokens = Lexer::tokenize(query, dialect); + queryTokens.trim(); + + // Completing query + if (!query.trimmed().endsWith(";")) + query += ";"; + + // Parsing query + if (parser.parse(query, true) && !parser.getQueries().isEmpty()) + { + parsedQuery = parser.getQueries().first(); + originalParsedQuery = SqliteQueryPtr(dynamic_cast<SqliteQuery*>(parsedQuery->clone())); + } +} + +void CompletionHelper::sort(QList<ExpectedTokenPtr> &resultsSoFar) +{ + CompletionComparer comparer(this); + qSort(resultsSoFar.begin(), resultsSoFar.end(), comparer); +} + +void CompletionHelper::extractPreviousIdTokens(const TokenList &parsedTokens) +{ + Dialect dialect = db->getDialect(); + + // The previous ID token (if any) is being used in + // getExpectedToken() and it's always the same token, + // so here we find it just once and reuse it. + previousId = stripObjName(getPreviousDbOrTable(parsedTokens), dialect); + + // In case of column context we need to know if there was + // up to two ID tokens before. If we had one above, + // then we check for one more here. + twoIdsBack.clear(); + if (previousId) + { + int idx = parsedTokens.indexOf(previousId); + TokenList parsedTokensSubSet = parsedTokens.mid(0, idx); + twoIdsBack = stripObjName(getPreviousDbOrTable(parsedTokensSubSet), dialect); + } +} + +void CompletionHelper::extractQueryAdditionalInfo() +{ + if (extractSelectCore()) + { + extractSelectAvailableColumnsAndTables(); + extractTableAliasMap(); + removeDuplicates(parentSelectAvailableColumns); + detectSelectContext(); + } + else if (isInUpdateColumn()) + { + context = Context::UPDATE_COLUMN; + } + else if (isInUpdateWhere()) + { + context = Context::UPDATE_WHERE; + } + else if (isInDeleteWhere()) + { + context = Context::DELETE_WHERE; + } + else if (isInCreateTable()) + { + context = Context::CREATE_TABLE; + extractCreateTableColumns(); + } + else if (isInCreateTrigger()) + { + context = Context::CREATE_TRIGGER; + } + else if (isInExpr()) + { + context = Context::EXPR; + } +} + +void CompletionHelper::detectSelectContext() +{ + QStringList mapNames = {"SELECT", "distinct", "selcollist", "from", "where_opt", "groupby_opt", "having_opt", "orderby_opt", "limit_opt"}; + QList<Context> contexts = {Context::SELECT_RESULT_COLUMN, Context::SELECT_FROM, Context::SELECT_WHERE, Context::SELECT_GROUP_BY, + Context::SELECT_HAVING, Context::SELECT_ORDER_BY, Context::SELECT_LIMIT}; + + // Assert that we have exactly 2 more map names, than defined contexts, cause we will start with 3rd map name and 1st context. + Q_ASSERT((mapNames.size() - 2) == contexts.size()); + + for (int i = 2; i < mapNames.size(); i++) + { + if (cursorAfterTokenMaps(currentSelectCore, mapNames.mid(0, i)) && cursorBeforeTokenMaps(currentSelectCore, mapNames.mid(i+1))) + { + context = contexts[i-2]; + break; + } + } +} + +bool CompletionHelper::isInUpdateColumn() +{ + // We will never get here if the subquery SELECT was used anywhere in the query, + // (and the cursor position is in that subselect), because in that case the extractSelectCore() + // will take over the flow before it reaches here. + return isIn(SqliteQueryType::Update, "setlist", "SET"); +} + +bool CompletionHelper::isInUpdateWhere() +{ + return isIn(SqliteQueryType::Update, "where_opt", "WHERE"); +} + +bool CompletionHelper::isInDeleteWhere() +{ + return isIn(SqliteQueryType::Delete, "where_opt", "WHERE"); +} + +bool CompletionHelper::isInCreateTable() +{ + if (!parsedQuery) + { + if (testQueryToken(0, Token::KEYWORD, "CREATE") && + (testQueryToken(1, Token::KEYWORD, "TABLE") || + testQueryToken(2, Token::KEYWORD, "TABLE"))) + { + return true; + } + + return false; + } + + if (parsedQuery->queryType != SqliteQueryType::CreateTable) + return false; + + return true; +} + +bool CompletionHelper::isInCreateTrigger() +{ + if (!parsedQuery) + { + if (testQueryToken(0, Token::KEYWORD, "CREATE") && + (testQueryToken(1, Token::KEYWORD, "TRIGGER") || + testQueryToken(2, Token::KEYWORD, "TRIGGER"))) + { + return true; + } + + return false; + } + + if (parsedQuery->queryType != SqliteQueryType::CreateTrigger) + return false; + + return true; +} + +bool CompletionHelper::isIn(SqliteQueryType queryType, const QString &tokenMapKey, const QString &prefixKeyword) +{ + if (!parsedQuery) + return false; + + if (parsedQuery->queryType != queryType) + return false; + + // We're looking for curPos - 1, because tokens indexing ends in the same, with -1 index. + TokenPtr token = parsedQuery->tokens.atCursorPosition(cursorPosition - 1); + if (!token) + return false; + + if (parsedQuery->tokensMap[tokenMapKey].contains(token)) + return true; + + // In case cursor is just before the requested token map entry, but it is after a whitespace, then we can + // assume, that what's coming next is our token map entry. + if (token->isWhitespace()) + { + int idx = parsedQuery->tokens.indexOf(token); + if (idx < 0) + return false; + + TokenList tokens = parsedQuery->tokens.mid(0, idx + 1); + tokens.trim(); + if (tokens.size() > 0 && tokens.last()->type == Token::KEYWORD && tokens.last()->value.compare(prefixKeyword, Qt::CaseInsensitive) == 0) + return true; + } + + return false; +} + +bool CompletionHelper::isInExpr() +{ + if (!parsedQuery) + return false; + + // Finding in which statement the cursor is positioned + // We're looking for curPos - 1, because tokens indexing ends in the same, with -1 index. + SqliteStatement* stmt = parsedQuery->findStatementWithPosition(cursorPosition - 1); + + // Now going up in statements tree in order to find first expr + while (stmt && !dynamic_cast<SqliteExpr*>(stmt)) + stmt = stmt->parentStatement(); + + return (stmt && dynamic_cast<SqliteExpr*>(stmt)); +} + +bool CompletionHelper::testQueryToken(int tokenPosition, Token::Type type, const QString& value, Qt::CaseSensitivity cs) +{ + if (tokenPosition >= queryTokens.size()) + return false; + + if (tokenPosition < 0) + return 0; + + TokenPtr token = queryTokens[tokenPosition]; + return (token->type == type && token->value.compare(value, cs) == 0); +} + +bool CompletionHelper::cursorAfterTokenMaps(SqliteStatement* stmt, const QStringList& mapNames) +{ + TokenList tokens; + foreach (const QString& name, mapNames) + { + if (!stmt->tokensMap.contains(name) || stmt->tokensMap[name].size() == 0) + continue; + + tokens = stmt->tokensMap[name]; + tokens.trimRight(); + if (tokens.size() == 0) + continue; + + if (tokens.last()->end >= cursorPosition) + return false; + } + return true; +} + +bool CompletionHelper::cursorBeforeTokenMaps(SqliteStatement* stmt, const QStringList& mapNames) +{ + TokenList tokens; + foreach (const QString& name, mapNames) + { + if (!stmt->tokensMap.contains(name) || stmt->tokensMap[name].size() == 0) + continue; + + tokens = stmt->tokensMap[name]; + tokens.trimLeft(); + if (tokens.size() == 0) + continue; + + if (tokens.first()->start < cursorPosition) + return false; + } + return true; +} +QString CompletionHelper::getCreateTriggerTable() const +{ + return createTriggerTable; +} + +void CompletionHelper::setCreateTriggerTable(const QString& value) +{ + createTriggerTable = value; +} + +DbAttacher* CompletionHelper::getDbAttacher() const +{ + return dbAttacher; +} + +void CompletionHelper::setDbAttacher(DbAttacher* value) +{ + if (dbAttacher) + delete dbAttacher; + + dbAttacher = value; +} + + +bool CompletionHelper::extractSelectCore() +{ + currentSelectCore = extractSelectCore(parsedQuery); + originalCurrentSelectCore = extractSelectCore(originalParsedQuery); + return (currentSelectCore != nullptr); +} + +SqliteSelect::Core* CompletionHelper::extractSelectCore(SqliteQueryPtr query) +{ + if (!query) + return nullptr; + + // Finding in which statement the cursor is positioned + // We're looking for curPos - 1, because tokens indexing ends in the same, with -1 index. + SqliteStatement* stmt = query->findStatementWithPosition(cursorPosition - 1); + + // Now going up in statements tree in order to find first select core + while (stmt && !dynamic_cast<SqliteSelect::Core*>(stmt)) + stmt = stmt->parentStatement(); + + if (stmt && dynamic_cast<SqliteSelect::Core*>(stmt)) + { + // We found our select core + return dynamic_cast<SqliteSelect::Core*>(stmt); + } + + return nullptr; +} + +void CompletionHelper::extractSelectAvailableColumnsAndTables() +{ + selectAvailableColumns = selectResolver->resolveAvailableColumns(currentSelectCore); + selectAvailableTables = selectResolver->resolveTables(currentSelectCore); + + // Now checking for any parent select cores. + SqliteStatement* stmt = currentSelectCore->parentStatement(); + SqliteSelect::Core* parentCore = nullptr; + while (stmt) + { + while (stmt && !dynamic_cast<SqliteSelect::Core*>(stmt)) + stmt = stmt->parentStatement(); + + if (!stmt || !dynamic_cast<SqliteSelect::Core*>(stmt)) + return; + + // We got another select core at higher level + parentCore = dynamic_cast<SqliteSelect::Core*>(stmt); + parentSelectCores += parentCore; + + // Collecting columns and tables + parentSelectAvailableColumns += selectResolver->resolveAvailableColumns(parentCore); + parentSelectAvailableTables += selectResolver->resolveTables(parentCore); + + // Moving on, until we're on top of the syntax tree. + stmt = stmt->parentStatement(); + } +} + +void CompletionHelper::extractTableAliasMap() +{ + foreach (SelectResolver::Column column, selectAvailableColumns) + { + if (column.type != SelectResolver::Column::COLUMN) + continue; + + if (!column.tableAlias.isNull() && !tableToAlias[column.table].contains(column.tableAlias)) + { + tableToAlias[column.table] += column.tableAlias; + aliasToTable[column.tableAlias] = Table(column.database, column.table); + } + } + + // We have sorted list of available columns from parent selects. + // Given the above, we can extract table aliases in an order from deepest + // to shallowest, skipping any duplicates, becase the deeper alias is mentioned, + // the higher is its priority. + foreach (SelectResolver::Column column, parentSelectAvailableColumns) + { + if (column.type != SelectResolver::Column::COLUMN) + continue; + + if (tableToAlias.contains(column.table)) + continue; + + if (!column.tableAlias.isNull() && !tableToAlias[column.table].contains(column.tableAlias)) + { + tableToAlias[column.table] += column.tableAlias; + aliasToTable[column.tableAlias] = Table(column.database, column.table); + } + } +} + +void CompletionHelper::extractCreateTableColumns() +{ + if (!parsedQuery) + return; + + SqliteCreateTablePtr createTable = parsedQuery.dynamicCast<SqliteCreateTable>(); + foreach (SqliteCreateTable::Column* col, createTable->columns) + favoredColumnNames << col->name; +} + +QList<ExpectedTokenPtr> CompletionHelper::Results::filtered() +{ + QList<ExpectedTokenPtr> tokens = expectedTokens; + applyFilter(tokens, partialToken); + return tokens; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/completionhelper.h b/SQLiteStudio3/coreSQLiteStudio/completionhelper.h new file mode 100644 index 0000000..63b7225 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/completionhelper.h @@ -0,0 +1,264 @@ +#ifndef COMPLETIONHELPER_H +#define COMPLETIONHELPER_H + +#include "expectedtoken.h" +#include "schemaresolver.h" +#include "selectresolver.h" +#include "dialect.h" +#include "completioncomparer.h" +#include "parser/ast/sqliteselect.h" +#include "parser/token.h" +#include "db/db.h" +#include "common/strhash.h" +#include "common/table.h" +#include <QObject> +#include <QSet> + +class DbAttacher; + +class API_EXPORT CompletionHelper : public QObject +{ + friend class CompletionComparer; + + public: + struct API_EXPORT Results + { + QList<ExpectedTokenPtr> filtered(); + + QList<ExpectedTokenPtr> expectedTokens; + QString partialToken; + bool wrappedToken = false; + }; + + CompletionHelper(const QString& sql, Db* db); + CompletionHelper(const QString& sql, quint32 cursorPos, Db* db); + ~CompletionHelper(); + + static void applyFilter(QList<ExpectedTokenPtr> &results, const QString &filter); + static void init(); + + Results getExpectedTokens(); + + DbAttacher* getDbAttacher() const; + void setDbAttacher(DbAttacher* value); + + static bool enableLemonDebug; + + QString getCreateTriggerTable() const; + void setCreateTriggerTable(const QString& value); + + private: + enum class Context + { + NONE, + SELECT_RESULT_COLUMN, + SELECT_FROM, + SELECT_WHERE, + SELECT_GROUP_BY, + SELECT_HAVING, + SELECT_ORDER_BY, + SELECT_LIMIT, + UPDATE_COLUMN, + UPDATE_WHERE, + DELETE_WHERE, + CREATE_TABLE, + CREATE_TRIGGER, + EXPR + }; + + QList<ExpectedTokenPtr> getExpectedTokens(TokenPtr token); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type, const QString& value); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type, const QString& value, + int priority); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type, const QString& value, + const QString& contextInfo); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type, const QString& value, + const QString& contextInfo, int priority); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString &contextInfo, const QString &label); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString &contextInfo, const QString &label, + int priority); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString &contextInfo, const QString &label, + const QString &prefix); + ExpectedTokenPtr getExpectedToken(ExpectedToken::Type type, const QString &value, + const QString &contextInfo, const QString &label, + const QString &prefix, int priority); + bool validatePreviousIdForGetObjects(QString* dbName = nullptr); + QList<ExpectedTokenPtr> getTables(); + QList<ExpectedTokenPtr> getIndexes(); + QList<ExpectedTokenPtr> getTriggers(); + QList<ExpectedTokenPtr> getViews(); + QList<ExpectedTokenPtr> getDatabases(); + QList<ExpectedTokenPtr> getObjects(ExpectedToken::Type type); + QList<ExpectedTokenPtr> getObjects(ExpectedToken::Type type, const QString& database); + QList<ExpectedTokenPtr> getColumns(); + QList<ExpectedTokenPtr> getColumnsNoPrefix(); + QList<ExpectedTokenPtr> getColumnsNoPrefix(const QString &column, const QStringList &tables); + QList<ExpectedTokenPtr> getColumns(const QString& prefixTable); + QList<ExpectedTokenPtr> getColumns(const QString& prefixDb, const QString& prefixTable); + QList<ExpectedTokenPtr> getFavoredColumns(const QList<ExpectedTokenPtr>& resultsSoFar); + + QList<ExpectedTokenPtr> getPragmas(Dialect dialect); + QList<ExpectedTokenPtr> getFunctions(Db* db); + QList<ExpectedTokenPtr> getCollations(); + TokenPtr getPreviousDbOrTable(const TokenList& parsedTokens); + void attachDatabases(); + void detachDatabases(); + QString translateDatabase(const QString& dbName); + QString translateDatabaseBack(const QString& dbName); + void collectOtherDatabases(); + QString removeStartedToken(const QString& adjustedSql, QString &finalFilter, bool& wrappedFilter); + void filterContextKeywords(QList<ExpectedTokenPtr> &results, const TokenList& tokens); + void filterOtherId(QList<ExpectedTokenPtr> &results, const TokenList& tokens); + void filterDuplicates(QList<ExpectedTokenPtr> &results); + bool isFilterType(Token::Type type); + void parseFullSql(); + void sort(QList<ExpectedTokenPtr> &results); + void extractPreviousIdTokens(const TokenList& parsedTokens); + void extractQueryAdditionalInfo(); + void extractSelectAvailableColumnsAndTables(); + bool extractSelectCore(); + SqliteSelect::Core* extractSelectCore(SqliteQueryPtr query); + void extractTableAliasMap(); + void extractCreateTableColumns(); + void detectSelectContext(); + bool isInUpdateColumn(); + bool isInUpdateWhere(); + bool isInDeleteWhere(); + bool isInCreateTable(); + bool isInCreateTrigger(); + bool isIn(SqliteQueryType queryType, const QString& tokenMapKey, const QString &prefixKeyword); + bool isInExpr(); + bool testQueryToken(int tokenPosition, Token::Type type, const QString& value, Qt::CaseSensitivity cs = Qt::CaseInsensitive); + bool cursorAfterTokenMaps(SqliteStatement* stmt, const QStringList& mapNames); + bool cursorBeforeTokenMaps(SqliteStatement* stmt, const QStringList& mapNames); + + template <class T> + bool cursorFitsInCollection(const QList<T*>& collection) + { + if (collection.size() == 0) + return false; + + T* firstStmt = collection.first(); + T* lastStmt = collection.last(); + + int startIdx = -1; + int endIdx = -1; + if (firstStmt && firstStmt->tokens.size() > 0) + startIdx = firstStmt->tokens.first()->start; + + if (lastStmt && lastStmt->tokens.size() > 0) + endIdx = lastStmt->tokens.last()->end; + + if (startIdx < 0 || endIdx < 0) + return false; + + return (cursorPosition >= startIdx && cursorPosition <= endIdx); + } + + template <class T> + bool cursorFitsInStatement(T* stmt) + { + if (!stmt || stmt->tokens.size() == 0) + return false; + + int startIdx = stmt->tokens.first()->start; + int endIdx = stmt->tokens.last()->end; + + return (cursorPosition >= startIdx && cursorPosition <= endIdx); + } + + + Context context = Context::NONE; + Db* db = nullptr; + qint64 cursorPosition; + QString fullSql; + TokenPtr previousId; + TokenPtr twoIdsBack; + TokenList queryTokens; + SqliteQueryPtr parsedQuery; + SqliteQueryPtr originalParsedQuery; + SchemaResolver* schemaResolver = nullptr; + SelectResolver* selectResolver = nullptr; + DbAttacher* dbAttacher = nullptr; + QString createTriggerTable; + + /** + * @brief tableToAlias + * This map maps real table name to its alias. Every table can be typed multiple times + * with different aliases, therefore this map has a list on the right side. + */ + QHash<QString,QStringList> tableToAlias; + + /** + * @brief aliasToTable + * Maps table alias to table's real name. + */ + //QHash<QString,QString> aliasToTable; + StrHash<Table> aliasToTable; + + /** + * @brief currentSelectCore + * The SqliteSelect::Core object that contains current cursor position. + */ + SqliteSelect::Core* currentSelectCore = nullptr; + + /** + * @brief originalCurrentSelectCore + * + * The same as currentSelectCore, but relates to originalParsedQuery, not just parsedQuery. + */ + SqliteSelect::Core* originalCurrentSelectCore = nullptr; + + /** + * @brief selectAvailableColumns + * Available columns are columns that can be selected basing on what tables are mentioned in FROM clause. + */ + QList<SelectResolver::Column> selectAvailableColumns; + + /** + * @brief selectAvailableTables + * Availble tables are tables mentioned in FROM clause. + */ + QSet<SelectResolver::Table> selectAvailableTables; + + /** + * @brief parentSelectCores + * List of all parent select core objects in order: from direct parent, to the oldest parent. + */ + QList<SqliteSelect::Core*> parentSelectCores; + + /** + * @brief parentSelectAvailableColumns + * List of all columns available in all tables mentioned in all parent select cores. + */ + QList<SelectResolver::Column> parentSelectAvailableColumns; + + /** + * @brief parentSelectAvailableTables + * Same as parentSelectAvailableColumns, but for tables in FROM clauses. + */ + QSet<SelectResolver::Table> parentSelectAvailableTables; + + /** + * @brief favoredColumnNames + * For some contexts there are favored column names, like for example CREATE TABLE + * will favour column names specified so far in its definition. + * Such columns will be added to completion results even they weren't result + * of regular computation. + */ + QStringList favoredColumnNames; + + /** + * @brief Context databases to look for objects in. + * + * If the query uses some other databases (not the currentone), then user might be interested + * also in objects from those databases. This list contains those databases. + */ + QStringList otherDatabasesToLookupFor; +}; + +#endif // COMPLETIONHELPER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder.h b/SQLiteStudio3/coreSQLiteStudio/config_builder.h new file mode 100644 index 0000000..f4e030e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder.h @@ -0,0 +1,76 @@ +#ifndef CFGINTERNALS_H
+#define CFGINTERNALS_H
+
+#include "config_builder/cfgmain.h"
+#include "config_builder/cfgcategory.h"
+#include "config_builder/cfgentry.h"
+#include "config_builder/cfglazyinitializer.h"
+
+#define CFG_CATEGORIES(Type,Body) _CFG_CATEGORIES_WITH_METANAME_AND_TITLE(Type,Body,"",QString())
+
+#define CFG_CATEGORY(Name,Body) \
+ _CFG_CATEGORY_WITH_TITLE(Name,Body,QString())
+
+#define CFG_ENTRY(Type, Name, ...) CfgTypedEntry<Type> Name = CfgTypedEntry<Type>(#Name, ##__VA_ARGS__);
+
+#define CFG_DEFINE(Type) _CFG_DEFINE(Type, true)
+#define CFG_DEFINE_RUNTIME(Type) _CFG_DEFINE(Type, false)
+#define CFG_LOCAL(Type, Name) Cfg::Type Name = Cfg::Type(false);
+#define CFG_LOCAL_PERSISTABLE(Type, Name) Cfg::Type Name = Cfg::Type(true);
+#define CFG_DEFINE_LAZY(Type) \
+ namespace Cfg\
+ {\
+ Type* cfgMainInstance##Type = nullptr;\
+ void init##Type##Instance()\
+ {\
+ cfgMainInstance##Type = new Type(true);\
+ }\
+ Type* get##Type##Instance()\
+ {\
+ return cfgMainInstance##Type;\
+ }\
+ CfgLazyInitializer* cfgMainInstance##Type##LazyInit = new CfgLazyInitializer(init##Type##Instance, #Type);\
+ }
+
+#define CFG_INSTANCE(Type) (*Cfg::get##Type##Instance())
+
+// Macros below are kind of private. You should not need to use them explicitly.
+// They are called from macros above.
+
+#define _CFG_CATEGORIES_WITH_METANAME(Type,Body,MetaName) \
+ _CFG_CATEGORIES_WITH_METANAME_AND_TITLE(Type,Body,MetaName,QString())
+
+#define _CFG_CATEGORIES_WITH_TITLE(Type,Body,Title) \
+ _CFG_CATEGORIES_WITH_METANAME_AND_TITLE(Type,Body,"",Title)
+
+#define _CFG_CATEGORIES_WITH_METANAME_AND_TITLE(Type,Body,MetaName,Title) \
+ namespace Cfg\
+ {\
+ struct API_EXPORT Type : public CfgMain\
+ {\
+ Type(bool persistable) : CfgMain(#Type, persistable, MetaName, Title) {}\
+ Body\
+ };\
+ API_EXPORT Type* get##Type##Instance();\
+ }
+
+#define _CFG_DEFINE(Type, Persistant) \
+ namespace Cfg\
+ {\
+ Type* cfgMainInstance##Type = new Type(Persistant);\
+ Type* get##Type##Instance()\
+ {\
+ return cfgMainInstance##Type;\
+ }\
+ }
+
+#define _CFG_CATEGORY_WITH_TITLE(Name,Body,Title) \
+ struct API_EXPORT _##Name##Type : public CfgCategory\
+ {\
+ _##Name##Type() : CfgCategory(#Name, Title) {}\
+ Body\
+ };\
+ _##Name##Type Name;
+
+
+#endif // CFGINTERNALS_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgcategory.cpp b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgcategory.cpp new file mode 100644 index 0000000..a79e08a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgcategory.cpp @@ -0,0 +1,99 @@ +#include "cfgcategory.h" +#include "config_builder/cfgmain.h" +#include "config_builder/cfgentry.h" + +CfgCategory* lastCreatedCfgCategory = nullptr; +extern CfgMain* lastCreatedCfgMain; + +CfgCategory::CfgCategory(const CfgCategory &other) : + QObject(), name(other.name), title(other.title), persistable(other.persistable), childs(other.childs) +{ + lastCreatedCfgCategory = this; + lastCreatedCfgMain->childs[name] = this; + cfgParent = lastCreatedCfgMain; + for (CfgEntry* entry : childs) + entry->parent = this; +} + +CfgCategory::CfgCategory(const QString &name, const QString &title) : + name(name), title(title) +{ + this->persistable = lastCreatedCfgMain->persistable; + lastCreatedCfgCategory = this; + cfgParent = lastCreatedCfgMain; + lastCreatedCfgMain->childs[name] = this; +} + +QString CfgCategory::toString() const +{ + return name; +} + +QHash<QString, CfgEntry *> &CfgCategory::getEntries() +{ + return childs; +} + +void CfgCategory::reset() +{ + for (CfgEntry* entry : childs) + entry->reset(); +} + +void CfgCategory::savepoint(bool transaction) +{ + for (CfgEntry* entry : childs) + entry->savepoint(transaction); +} + +void CfgCategory::restore() +{ + for (CfgEntry* entry : childs) + entry->restore(); +} + +void CfgCategory::release() +{ + for (CfgEntry* entry : childs) + entry->release(); +} + +void CfgCategory::commit() +{ + release(); +} + +void CfgCategory::rollback() +{ + rollback(); +} + +void CfgCategory::begin() +{ + savepoint(true); +} + +QString CfgCategory::getTitle() const +{ + return title; +} + +CfgMain*CfgCategory::getMain() const +{ + return cfgParent; +} + +CfgCategory::operator CfgCategory *() +{ + return this; +} + +void CfgCategory::handleEntryChanged() +{ + emit changed(dynamic_cast<CfgEntry*>(sender())); +} + +CfgCategory::operator QString() const +{ + return name; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgcategory.h b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgcategory.h new file mode 100644 index 0000000..2a6ccaf --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgcategory.h @@ -0,0 +1,53 @@ +#ifndef CFGCATEGORY_H +#define CFGCATEGORY_H + +#include "coreSQLiteStudio_global.h" +#include <QVariant> +#include <QHash> +#include <QString> +#include <QObject> + +class CfgEntry; +class CfgMain; + +class API_EXPORT CfgCategory : public QObject +{ + Q_OBJECT + + friend class CfgEntry; + + public: + CfgCategory(const CfgCategory& other); + CfgCategory(const QString& name, const QString& title); + + QString toString() const; + operator QString() const; + QHash<QString,CfgEntry*>& getEntries(); + void reset(); + void savepoint(bool transaction = false); + void restore(); + void release(); + void commit(); + void rollback(); + void begin(); + QString getTitle() const; + CfgMain* getMain() const; + operator CfgCategory*(); + + private: + QString name; + QString title; + CfgMain* cfgParent = nullptr; + bool persistable = true; + QHash<QString,CfgEntry*> childs; + + private slots: + void handleEntryChanged(); + + signals: + void changed(CfgEntry* entry); +}; + +Q_DECLARE_METATYPE(CfgCategory*) + +#endif // CFGCATEGORY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgentry.cpp b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgentry.cpp new file mode 100644 index 0000000..6a5f6a4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgentry.cpp @@ -0,0 +1,189 @@ +#include "cfgentry.h" +#include "config_builder/cfgmain.h" +#include "config_builder/cfgcategory.h" +#include "services/config.h" +#include <QDebug> + +extern CfgCategory* lastCreatedCfgCategory; + +CfgEntry::CfgEntry(const CfgEntry& other) : + QObject(), persistable(other.persistable), parent(other.parent), name(other.name), defValue(other.defValue), + title(other.title), defValueFunc(other.defValueFunc) +{ + connect(this, SIGNAL(changed(QVariant)), parent, SLOT(handleEntryChanged())); +} + +CfgEntry::CfgEntry(const QString &name, const QVariant &defValue, const QString &title) : + QObject(), name(name), defValue(defValue), title(title) +{ + if (lastCreatedCfgCategory == nullptr) + { + qCritical() << "No last created category while creating CfgEntry!"; + return; + } + + parent = lastCreatedCfgCategory; + persistable = parent->persistable; + parent->childs[name] = this; + connect(this, SIGNAL(changed(QVariant)), parent, SLOT(handleEntryChanged())); +} + +CfgEntry::~CfgEntry() +{ +} + +QVariant CfgEntry::get() const +{ + if (cached) + return cachedValue; + + QVariant cfgVal; + if (persistable) + cfgVal = CFG->get(parent->toString(), name); + + cachedValue = cfgVal; + cached = true; + if (!persistable || !cfgVal.isValid()) + { + if (defValueFunc) + cachedValue = (*defValueFunc)(); + else + cachedValue = defValue; + + return cachedValue; + } + + return cfgVal; +} + +QVariant CfgEntry::getDefultValue() const +{ + if (defValueFunc) + return (*defValueFunc)(); + else + return defValue; +} + +void CfgEntry::set(const QVariant &value) +{ + bool doPersist = persistable && !transaction; + bool wasChanged = (value != cachedValue); + + if (doPersist && wasChanged) + CFG->set(parent->toString(), name, value); + + if (wasChanged) + cachedValue = value; + + cached = true; + + if (wasChanged) + emit changed(value); + + if (doPersist) + emit persisted(value); +} + +void CfgEntry::defineDefaultValueFunction(CfgEntry::DefaultValueProviderFunc func) +{ + defValueFunc = func; +} + +QString CfgEntry::getFullKey() const +{ + return parent->toString()+"."+name; +} + +QString CfgEntry::getTitle() const +{ + return title; +} + +void CfgEntry::reset() +{ + set(getDefultValue()); +} + +bool CfgEntry::isPersistable() const +{ + return persistable; +} + +bool CfgEntry::isPersisted() const +{ + if (persistable) + return !CFG->get(parent->toString(), name).isNull(); + + return false; +} + +void CfgEntry::savepoint(bool transaction) +{ + backup = get(); + this->transaction = transaction; +} + +void CfgEntry::begin() +{ + savepoint(true); +} + +void CfgEntry::restore() +{ + cachedValue = backup; + cached = true; + transaction = false; +} + +void CfgEntry::release() +{ + backup.clear(); + if (transaction) + { + transaction = false; + if (cached) + { + QVariant valueToSet = cachedValue; + cachedValue = QVariant(); + cached = false; + set(valueToSet); + } + } + +} + +void CfgEntry::commit() +{ + if (!transaction) + return; + + release(); +} + +void CfgEntry::rollback() +{ + if (!transaction) + return; + + restore(); +} + +CfgCategory* CfgEntry::getCategory() const +{ + return parent; +} + +CfgMain* CfgEntry::getMain() const +{ + return parent->getMain(); +} + +CfgEntry::operator CfgEntry*() +{ + return this; +} + +CfgEntry::operator QString() const +{ + return name; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgentry.h b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgentry.h new file mode 100644 index 0000000..92b2a5f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgentry.h @@ -0,0 +1,123 @@ +#ifndef CFGENTRY_H +#define CFGENTRY_H + +#include "coreSQLiteStudio_global.h" +#include <QString> +#include <QVariant> +#include <QObject> + +class CfgCategory; +class CfgMain; + +class API_EXPORT CfgEntry : public QObject +{ + Q_OBJECT + + friend class CfgCategory; + + public: + typedef QVariant (*DefaultValueProviderFunc)(); + + explicit CfgEntry(const CfgEntry& other); + CfgEntry(const QString& name, const QVariant& defValue, const QString& title); + virtual ~CfgEntry(); + + QVariant get() const; + QVariant getDefultValue() const; + void set(const QVariant& value); + operator QString() const; + void defineDefaultValueFunction(DefaultValueProviderFunc func); + QString getFullKey() const; + QString getTitle() const; + void reset(); + bool isPersistable() const; + bool isPersisted() const; + void savepoint(bool transaction = false); + void begin(); + void restore(); + void release(); + void commit(); + void rollback(); + CfgCategory* getCategory() const; + CfgMain* getMain() const; + + /** + * @brief operator CfgEntry * + * + * Allows implict casting from value object into pointer. It simply returns "this". + * It's useful to use config objects directly in QObject::connect() arguments, + * cause it accepts pointers, not values, but CfgEntry is usually accessed by value. + */ + operator CfgEntry*(); + + protected: + bool persistable = true; + CfgCategory* parent = nullptr; + QString name; + QVariant defValue; + QString title; + QVariant backup; + bool transaction = false; + mutable bool cached = false; + mutable QVariant cachedValue; + DefaultValueProviderFunc defValueFunc = nullptr; + + signals: + void changed(const QVariant& newValue); + void persisted(const QVariant& newValue); +}; + +template <class T> +class CfgTypedEntry : public CfgEntry +{ + public: + CfgTypedEntry(const QString& name, DefaultValueProviderFunc func, const QString& title) : + CfgEntry(name, QVariant(), title) + { + defineDefaultValueFunction(func); + } + + CfgTypedEntry(const QString& name, const T& defValue, const QString& title) : + CfgEntry(name, QVariant::fromValue(defValue), title) {} + + CfgTypedEntry(const QString& name, DefaultValueProviderFunc func) : + CfgTypedEntry(name, func, QString()) {} + + CfgTypedEntry(const QString& name, const T& defValue, bool persistable) : + CfgTypedEntry(name, defValue, QString()) + { + this->persistable = persistable; + } + + CfgTypedEntry(const QString& name, DefaultValueProviderFunc func, bool persistable) : + CfgTypedEntry(name, func, QString()) + { + this->persistable = persistable; + } + + CfgTypedEntry(const QString& name, const T& defValue) : + CfgTypedEntry(name, defValue, QString()) {} + + CfgTypedEntry(const QString& name) : + CfgEntry(name, QVariant(), QString()) {} + + CfgTypedEntry(const CfgTypedEntry& other) : + CfgEntry(other) {} + + T get() + { + QVariant v = CfgEntry::get(); + return v.value<T>(); + } + + void set(const T& value) + { + CfgEntry::set(QVariant::fromValue<T>(value)); + } +}; + +typedef CfgTypedEntry<QString> CfgStringEntry; + +Q_DECLARE_METATYPE(CfgEntry*) + +#endif // CFGENTRY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder/cfglazyinitializer.cpp b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfglazyinitializer.cpp new file mode 100644 index 0000000..7e554a8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfglazyinitializer.cpp @@ -0,0 +1,28 @@ +#include "cfglazyinitializer.h" +#include "common/unused.h" + +QList<CfgLazyInitializer*>* CfgLazyInitializer::instances = nullptr; + +CfgLazyInitializer::CfgLazyInitializer(std::function<void ()> initFunc, const char *name) : + initFunc(initFunc) +{ + UNUSED(name); + if (!instances) + instances = new QList<CfgLazyInitializer*>(); + + *instances << this; +} + +void CfgLazyInitializer::init() +{ + if (!instances) + instances = new QList<CfgLazyInitializer*>(); + + for (CfgLazyInitializer* initializer : *instances) + initializer->doInitialize(); +} + +void CfgLazyInitializer::doInitialize() +{ + initFunc(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder/cfglazyinitializer.h b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfglazyinitializer.h new file mode 100644 index 0000000..d97edc2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfglazyinitializer.h @@ -0,0 +1,23 @@ +#ifndef CFGLAZYINITIALIZER_H +#define CFGLAZYINITIALIZER_H + +#include "coreSQLiteStudio_global.h" +#include <QList> +#include <functional> + +class API_EXPORT CfgLazyInitializer +{ + public: + CfgLazyInitializer(std::function<void(void)> initFunc, const char* name); + + static void init(); + + private: + void doInitialize(); + + std::function<void(void)> initFunc; + + static QList<CfgLazyInitializer*>* instances; +}; + +#endif // CFGLAZYINITIALIZER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgmain.cpp b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgmain.cpp new file mode 100644 index 0000000..72fc0d0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgmain.cpp @@ -0,0 +1,120 @@ +#include "cfgmain.h" +#include "config_builder/cfgcategory.h" +#include "config_builder/cfgentry.h" + +CfgMain* lastCreatedCfgMain = nullptr; +QList<CfgMain*>* CfgMain::instances = nullptr; + +CfgMain::CfgMain(const QString& name, bool persistable, const char *metaName, const QString &title) : + name(name), metaName(metaName), title(title), persistable(persistable) +{ + lastCreatedCfgMain = this; + + if (!instances) + instances = new QList<CfgMain*>(); + + *instances << this; +} + +CfgMain::~CfgMain() +{ + if (!instances) + instances = new QList<CfgMain*>(); + + instances->removeOne(this); +} + +void CfgMain::staticInit() +{ + qRegisterMetaType<CfgMain*>("CfgMain*"); + qRegisterMetaType<CfgCategory*>("CfgCategory*"); + qRegisterMetaType<CfgEntry*>("CfgEntry*"); +} + +QList<CfgMain*> CfgMain::getInstances() +{ + if (!instances) + instances = new QList<CfgMain*>(); + + return *instances; +} + +QList<CfgMain*> CfgMain::getPersistableInstances() +{ + QList<CfgMain*> list; + for (CfgMain* main : getInstances()) + { + if (main->isPersistable()) + list << main; + } + return list; +} + +QHash<QString, CfgCategory *> &CfgMain::getCategories() +{ + return childs; +} + +void CfgMain::reset() +{ + for (CfgCategory* ctg : childs) + ctg->reset(); +} + +void CfgMain::savepoint(bool transaction) +{ + for (CfgCategory* ctg : childs) + ctg->savepoint(transaction); +} + +void CfgMain::restore() +{ + for (CfgCategory* ctg : childs) + ctg->restore(); +} + +void CfgMain::release() +{ + for (CfgCategory* ctg : childs) + ctg->release(); +} + +void CfgMain::begin() +{ + savepoint(true); +} + +void CfgMain::commit() +{ + release(); +} + +void CfgMain::rollback() +{ + restore(); +} + +bool CfgMain::isPersistable() const +{ + return persistable; +} + +QString CfgMain::getName() const +{ + return name; +} + +const char *CfgMain::getMetaName() const +{ + return metaName; +} + +QString CfgMain::getTitle() const +{ + return title; +} + +CfgMain::operator CfgMain*() +{ + return this; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgmain.h b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgmain.h new file mode 100644 index 0000000..bc9490d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/config_builder/cfgmain.h @@ -0,0 +1,51 @@ +#ifndef CFGMAIN_H +#define CFGMAIN_H + +#include "coreSQLiteStudio_global.h" +#include <QVariant> +#include <QList> +#include <QHash> +#include <QString> + +class CfgCategory; + +class API_EXPORT CfgMain +{ + friend class CfgCategory; + + public: + CfgMain(const QString& name, bool persistable, const char* metaName, const QString& title); + ~CfgMain(); + + static void staticInit(); + static QList<CfgMain*> getInstances(); + static QList<CfgMain*> getPersistableInstances(); + + QHash<QString,CfgCategory*>& getCategories(); + void reset(); + void savepoint(bool transaction = false); + void restore(); + void release(); + void begin(); + void commit(); + void rollback(); + + bool isPersistable() const; + QString getName() const; + const char* getMetaName() const; + QString getTitle() const; + operator CfgMain*(); + + private: + QString name; + const char* metaName; + QString title; + bool persistable = true; + QHash<QString,CfgCategory*> childs; + + static QList<CfgMain*>* instances; +}; + +Q_DECLARE_METATYPE(CfgMain*) + +#endif // CFGMAIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio.pro b/SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio.pro new file mode 100644 index 0000000..6aa14e2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio.pro @@ -0,0 +1,421 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2013-02-28T23:00:28 +# +#------------------------------------------------- + +include($$PWD/../dirs.pri) +include($$PWD/../utils.pri) + +OBJECTS_DIR = $$OBJECTS_DIR/coreSQLiteStudio +MOC_DIR = $$MOC_DIR/coreSQLiteStudio +UI_DIR = $$UI_DIR/coreSQLiteStudio + +QT -= gui +QT += script network + +TARGET = coreSQLiteStudio +TEMPLATE = lib + +win32 { + LIBS += -lpsapi $$PWD/../../../lib/libquazip.a +} + +linux: { + DEFINES += SYS_PLUGINS_DIR=/usr/lib/sqlitestudio + portable: { + DESTDIR = $$DESTDIR/lib + } +} + +macx: { + out_file = $$DESTDIR/lib $$TARGET .dylib + QMAKE_POST_LINK += install_name_tool -change libsqlite3.dylib @loader_path/../Frameworks/libsqlite3.dylib $$join(out_file) +} + +LIBS += -lsqlite3 + +DEFINES += CORESQLITESTUDIO_LIBRARY + +portable { + DEFINES += PORTABLE_CONFIG +} + +CONFIG += c++11 +QMAKE_CXXFLAGS += -pedantic + +SOURCES += sqlitestudio.cpp \ + returncode.cpp \ + services/config.cpp \ + common/nulldevice.cpp \ + parser/lexer_low_lev.cpp \ + common/utils.cpp \ + parser/keywords.cpp \ + common/utils_sql.cpp \ + parser/token.cpp \ + parser/lexer.cpp \ + parser/sqlite3_parse.cpp \ + parser/parsercontext.cpp \ + parser/parser.cpp \ + parser/sqlite2_parse.cpp \ + parser/ast/sqlitestatement.cpp \ + parser/ast/sqlitequery.cpp \ + parser/ast/sqlitealtertable.cpp \ + parser/ast/sqliteanalyze.cpp \ + parser/ast/sqlitebegintrans.cpp \ + parser/ast/sqlitecommittrans.cpp \ + parser/ast/sqlitecreateindex.cpp \ + parser/ast/sqlitecreatetable.cpp \ + parser/ast/sqlitecreatetrigger.cpp \ + parser/ast/sqlitecreateview.cpp \ + parser/ast/sqlitecreatevirtualtable.cpp \ + parser/ast/sqlitedelete.cpp \ + parser/ast/sqlitedetach.cpp \ + parser/ast/sqlitedroptable.cpp \ + parser/ast/sqlitedroptrigger.cpp \ + parser/ast/sqlitedropindex.cpp \ + parser/ast/sqlitedropview.cpp \ + parser/ast/sqliteinsert.cpp \ + parser/ast/sqlitepragma.cpp \ + parser/ast/sqlitereindex.cpp \ + parser/ast/sqlitesavepoint.cpp \ + parser/ast/sqliterelease.cpp \ + parser/ast/sqliterollback.cpp \ + parser/ast/sqliteselect.cpp \ + parser/ast/sqliteupdate.cpp \ + parser/ast/sqlitevacuum.cpp \ + parser/ast/sqlitecopy.cpp \ + parser/ast/sqliteemptyquery.cpp \ + parser/parser_helper_stubs.cpp \ + parser/ast/sqliteexpr.cpp \ + parser/ast/sqliteforeignkey.cpp \ + parser/ast/sqliteindexedcolumn.cpp \ + parser/ast/sqlitecolumntype.cpp \ + parser/ast/sqliteconflictalgo.cpp \ + parser/ast/sqlitesortorder.cpp \ + parser/ast/sqliteraise.cpp \ + parser/ast/sqliteorderby.cpp \ + parser/ast/sqlitelimit.cpp \ + parser/ast/sqliteattach.cpp \ + parser/parsererror.cpp \ + selectresolver.cpp \ + schemaresolver.cpp \ + parser/ast/sqlitequerytype.cpp \ + db/db.cpp \ + services/dbmanager.cpp \ + db/sqlresultsrow.cpp \ + db/asyncqueryrunner.cpp \ + completionhelper.cpp \ + completioncomparer.cpp \ + db/queryexecutor.cpp \ + qio.cpp \ + plugins/pluginsymbolresolver.cpp \ + db/sqlerrorresults.cpp \ + db/queryexecutorsteps/queryexecutorstep.cpp \ + db/queryexecutorsteps/queryexecutorcountresults.cpp \ + db/queryexecutorsteps/queryexecutorparsequery.cpp \ + db/queryexecutorsteps/queryexecutorexecute.cpp \ + db/queryexecutorsteps/queryexecutorattaches.cpp \ + db/queryexecutorsteps/queryexecutoraddrowids.cpp \ + db/queryexecutorsteps/queryexecutorlimit.cpp \ + db/queryexecutorsteps/queryexecutorcolumns.cpp \ + db/queryexecutorsteps/queryexecutorcellsize.cpp \ + db/queryexecutorsteps/queryexecutororder.cpp \ + db/sqlerrorcodes.cpp \ + common/readwritelocker.cpp \ + db/queryexecutorsteps/queryexecutorwrapdistinctresults.cpp \ + csvformat.cpp \ + csvserializer.cpp \ + db/queryexecutorsteps/queryexecutordatasources.cpp \ + expectedtoken.cpp \ + sqlhistorymodel.cpp \ + db/queryexecutorsteps/queryexecutorexplainmode.cpp \ + services/notifymanager.cpp \ + parser/statementtokenbuilder.cpp \ + parser/ast/sqlitedeferrable.cpp \ + tablemodifier.cpp \ + db/chainexecutor.cpp \ + db/queryexecutorsteps/queryexecutorreplaceviews.cpp \ + services/codeformatter.cpp \ + viewmodifier.cpp \ + log.cpp \ + plugins/plugintype.cpp \ + plugins/genericplugin.cpp \ + common/memoryusage.cpp \ + ddlhistorymodel.cpp \ + datatype.cpp \ + common/table.cpp \ + common/column.cpp \ + dbattacher.cpp \ + services/functionmanager.cpp \ + plugins/scriptingqt.cpp \ + services/impl/configimpl.cpp \ + services/impl/dbmanagerimpl.cpp \ + db/abstractdb.cpp \ + services/impl/functionmanagerimpl.cpp \ + services/impl/pluginmanagerimpl.cpp \ + impl/dbattacherimpl.cpp \ + db/dbsqlite3.cpp \ + plugins/dbpluginsqlite3.cpp \ + parser/ast/sqlitewith.cpp \ + services/impl/collationmanagerimpl.cpp \ + services/exportmanager.cpp \ + exportworker.cpp \ + plugins/scriptingsql.cpp \ + db/queryexecutorsteps/queryexecutordetectschemaalter.cpp \ + querymodel.cpp \ + plugins/genericexportplugin.cpp \ + dbobjectorganizer.cpp \ + db/attachguard.cpp \ + db/invaliddb.cpp \ + dbversionconverter.cpp \ + diff/diff_match_patch.cpp \ + db/sqlquery.cpp \ + db/queryexecutorsteps/queryexecutorvaluesmode.cpp \ + services/importmanager.cpp \ + importworker.cpp \ + services/populatemanager.cpp \ + pluginservicebase.cpp \ + populateworker.cpp \ + plugins/populatesequence.cpp \ + plugins/populaterandom.cpp \ + plugins/populaterandomtext.cpp \ + plugins/populateconstant.cpp \ + plugins/populatedictionary.cpp \ + plugins/populatescript.cpp \ + plugins/builtinplugin.cpp \ + plugins/scriptingqtdbproxy.cpp \ + plugins/sqlformatterplugin.cpp \ + services/bugreporter.cpp \ + services/updatemanager.cpp \ + config_builder/cfgmain.cpp \ + config_builder/cfgcategory.cpp \ + config_builder/cfgentry.cpp \ + config_builder/cfglazyinitializer.cpp \ + committable.cpp \ + services/extralicensemanager.cpp \ + tsvserializer.cpp \ + rsa/BigInt.cpp \ + rsa/Key.cpp \ + rsa/KeyPair.cpp \ + rsa/PrimeGenerator.cpp \ + rsa/RSA.cpp + +HEADERS += sqlitestudio.h\ + coreSQLiteStudio_global.h \ + returncode.h \ + services/config.h \ + common/nulldevice.h \ + parser/lexer_low_lev.h \ + common/utils.h \ + parser/keywords.h \ + parser/token.h \ + common/utils_sql.h \ + parser/lexer.h \ + parser/sqlite3_parse.h \ + parser/parsercontext.h \ + parser/parser.h \ + parser/sqlite2_parse.h \ + parser/ast/sqlitestatement.h \ + parser/ast/sqlitequery.h \ + parser/ast/sqlitealtertable.h \ + parser/ast/sqliteanalyze.h \ + parser/ast/sqlitebegintrans.h \ + parser/ast/sqlitecommittrans.h \ + parser/ast/sqlitecreateindex.h \ + parser/ast/sqlitecreatetable.h \ + parser/ast/sqlitecreatetrigger.h \ + parser/ast/sqlitecreateview.h \ + parser/ast/sqlitecreatevirtualtable.h \ + parser/ast/sqlitedelete.h \ + parser/ast/sqlitedetach.h \ + parser/ast/sqlitedroptable.h \ + parser/ast/sqlitedroptrigger.h \ + parser/ast/sqlitedropindex.h \ + parser/ast/sqlitedropview.h \ + parser/ast/sqliteinsert.h \ + parser/ast/sqlitepragma.h \ + parser/ast/sqlitereindex.h \ + parser/ast/sqlitesavepoint.h \ + parser/ast/sqliterelease.h \ + parser/ast/sqliterollback.h \ + parser/ast/sqliteselect.h \ + parser/ast/sqliteupdate.h \ + parser/ast/sqlitevacuum.h \ + parser/ast/sqlitecopy.h \ + parser/ast/sqlitequerytype.h \ + parser/ast/sqliteemptyquery.h \ + parser/parser_helper_stubs.h \ + parser/ast/sqliteconflictalgo.h \ + parser/ast/sqliteexpr.h \ + parser/ast/sqliteforeignkey.h \ + parser/ast/sqliteindexedcolumn.h \ + parser/ast/sqlitecolumntype.h \ + parser/ast/sqlitesortorder.h \ + parser/ast/sqlitedeferrable.h \ + parser/ast/sqliteraise.h \ + parser/ast/sqliteorderby.h \ + parser/ast/sqlitelimit.h \ + parser/ast/sqliteattach.h \ + parser/parsererror.h \ + common/objectpool.h \ + selectresolver.h \ + schemaresolver.h \ + dialect.h \ + db/db.h \ + services/dbmanager.h \ + db/sqlresultsrow.h \ + db/asyncqueryrunner.h \ + completionhelper.h \ + expectedtoken.h \ + completioncomparer.h \ + plugins/dbplugin.h \ + services/pluginmanager.h \ + db/queryexecutor.h \ + qio.h \ + db/dbpluginoption.h \ + common/global.h \ + parser/ast/sqlitetablerelatedddl.h \ + plugins/pluginsymbolresolver.h \ + db/sqlerrorresults.h \ + db/sqlerrorcodes.h \ + db/queryexecutorsteps/queryexecutorstep.h \ + db/queryexecutorsteps/queryexecutorcountresults.h \ + db/queryexecutorsteps/queryexecutorparsequery.h \ + db/queryexecutorsteps/queryexecutorexecute.h \ + db/queryexecutorsteps/queryexecutorattaches.h \ + db/queryexecutorsteps/queryexecutoraddrowids.h \ + db/queryexecutorsteps/queryexecutorlimit.h \ + db/queryexecutorsteps/queryexecutorcolumns.h \ + db/queryexecutorsteps/queryexecutorcellsize.h \ + common/unused.h \ + db/queryexecutorsteps/queryexecutororder.h \ + common/readwritelocker.h \ + db/queryexecutorsteps/queryexecutorwrapdistinctresults.h \ + csvformat.h \ + csvserializer.h \ + db/queryexecutorsteps/queryexecutordatasources.h \ + sqlhistorymodel.h \ + db/queryexecutorsteps/queryexecutorexplainmode.h \ + services/notifymanager.h \ + parser/statementtokenbuilder.h \ + tablemodifier.h \ + db/chainexecutor.h \ + db/queryexecutorsteps/queryexecutorreplaceviews.h \ + plugins/sqlformatterplugin.h \ + services/codeformatter.h \ + viewmodifier.h \ + log.h \ + plugins/plugintype.h \ + plugins/plugin.h \ + plugins/genericplugin.h \ + common/memoryusage.h \ + ddlhistorymodel.h \ + datatype.h \ + plugins/generalpurposeplugin.h \ + common/table.h \ + common/column.h \ + common/bihash.h \ + common/strhash.h \ + dbattacher.h \ + common/bistrhash.h \ + services/functionmanager.h \ + common/sortedhash.h \ + plugins/scriptingplugin.h \ + plugins/scriptingqt.h \ + services/impl/configimpl.h \ + services/impl/dbmanagerimpl.h \ + db/abstractdb.h \ + services/impl/functionmanagerimpl.h \ + services/impl/pluginmanagerimpl.h \ + impl/dbattacherimpl.h \ + db/abstractdb3.h \ + db/dbsqlite3.h \ + plugins/dbpluginsqlite3.h \ + db/abstractdb2.h \ + parser/ast/sqlitewith.h \ + services/collationmanager.h \ + services/impl/collationmanagerimpl.h \ + plugins/exportplugin.h \ + config_builder.h \ + services/exportmanager.h \ + exportworker.h \ + plugins/scriptingsql.h \ + db/queryexecutorsteps/queryexecutordetectschemaalter.h \ + querymodel.h \ + plugins/genericexportplugin.h \ + dbobjectorganizer.h \ + db/attachguard.h \ + interruptable.h \ + db/invaliddb.h \ + dbversionconverter.h \ + diff/diff_match_patch.h \ + db/sqlquery.h \ + dbobjecttype.h \ + db/queryexecutorsteps/queryexecutorvaluesmode.h \ + plugins/importplugin.h \ + services/importmanager.h \ + importworker.h \ + plugins/populateplugin.h \ + services/populatemanager.h \ + pluginservicebase.h \ + populateworker.h \ + plugins/populatesequence.h \ + plugins/populaterandom.h \ + plugins/populaterandomtext.h \ + plugins/populateconstant.h \ + plugins/populatedictionary.h \ + plugins/populatescript.h \ + plugins/builtinplugin.h \ + plugins/scriptingqtdbproxy.h \ + plugins/codeformatterplugin.h \ + services/bugreporter.h \ + services/updatemanager.h \ + config_builder/cfgmain.h \ + config_builder/cfgcategory.h \ + config_builder/cfgentry.h \ + config_builder/cfglazyinitializer.h \ + plugins/confignotifiableplugin.h \ + committable.h \ + plugins/uiconfiguredplugin.h \ + services/extralicensemanager.h \ + db/stdsqlite3driver.h \ + tsvserializer.h \ + rsa/BigInt.h \ + rsa/Key.h \ + rsa/KeyPair.h \ + rsa/PrimeGenerator.h \ + rsa/RSA.h + +unix:!symbian { + maemo5 { + target.path = /opt/usr/lib + } else { + target.path = /usr/lib + } + INSTALLS += target +} + +OTHER_FILES += \ + parser/lempar.c \ + parser/sqlite3_parse.y \ + parser/sqlite2_parse.y \ + parser/run_lemon.sh \ + TODO.txt \ + licenses/fugue_icons.txt \ + licenses/qhexedit.txt \ + licenses/sqlitestudio_license.txt \ + licenses/lgpl.txt \ + licenses/diff_match.txt \ + licenses/gpl.txt + +FORMS += \ + plugins/populatesequence.ui \ + plugins/populaterandom.ui \ + plugins/populaterandomtext.ui \ + plugins/populateconstant.ui \ + plugins/populatedictionary.ui \ + plugins/populatescript.ui + +RESOURCES += \ + coresqlitestudio.qrc diff --git a/SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio_global.h b/SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio_global.h new file mode 100644 index 0000000..402246f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/coreSQLiteStudio_global.h @@ -0,0 +1,19 @@ +#ifndef CORESQLITESTUDIO_GLOBAL_H +#define CORESQLITESTUDIO_GLOBAL_H + +#include <QtCore/qglobal.h> + +#if defined(CORESQLITESTUDIO_LIBRARY) +# define API_EXPORT Q_DECL_EXPORT +#else +# define API_EXPORT +//# define API_EXPORT Q_DECL_IMPORT +#endif + +#ifdef Q_OS_WIN +# define PATH_LIST_SEPARATOR ";" +#else +# define PATH_LIST_SEPARATOR ":" +#endif + +#endif // CORESQLITESTUDIO_GLOBAL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/coresqlitestudio.qrc b/SQLiteStudio3/coreSQLiteStudio/coresqlitestudio.qrc new file mode 100644 index 0000000..2b58106 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/coresqlitestudio.qrc @@ -0,0 +1,21 @@ +<RCC> + <qresource prefix="/forms"> + <file>plugins/populatesequence.ui</file> + <file>plugins/populaterandomtext.ui</file> + <file>plugins/populatedictionary.ui</file> + <file>plugins/populateconstant.ui</file> + <file>plugins/populaterandom.ui</file> + <file>plugins/populatescript.ui</file> + </qresource> + <qresource prefix="/images"> + <file>plugins/scriptingsql.png</file> + <file>plugins/scriptingqt.png</file> + </qresource> + <qresource prefix="/docs"> + <file>licenses/fugue_icons.txt</file> + <file>licenses/sqlitestudio_license.txt</file> + <file>licenses/lgpl.txt</file> + <file>licenses/diff_match.txt</file> + <file>licenses/gpl.txt</file> + </qresource> +</RCC> diff --git a/SQLiteStudio3/coreSQLiteStudio/csvformat.cpp b/SQLiteStudio3/coreSQLiteStudio/csvformat.cpp new file mode 100644 index 0000000..2876b88 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/csvformat.cpp @@ -0,0 +1,13 @@ +#include "csvformat.h" +#include <QtGlobal> + +const CsvFormat CsvFormat::DEFAULT = {",", "\n"}; + +CsvFormat::CsvFormat() +{ +} + +CsvFormat::CsvFormat(const QString& columnSeparator, const QString& rowSeparator) : + columnSeparator(columnSeparator), rowSeparator(rowSeparator) +{ +} diff --git a/SQLiteStudio3/coreSQLiteStudio/csvformat.h b/SQLiteStudio3/coreSQLiteStudio/csvformat.h new file mode 100644 index 0000000..c569147 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/csvformat.h @@ -0,0 +1,18 @@ +#ifndef CSVFORMAT_H +#define CSVFORMAT_H + +#include "coreSQLiteStudio_global.h" +#include <QString> + +struct API_EXPORT CsvFormat +{ + CsvFormat(); + CsvFormat(const QString& columnSeparator, const QString& rowSeparator); + + QString columnSeparator; + QString rowSeparator; + + static const CsvFormat DEFAULT; +}; + +#endif // CSVFORMAT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/csvserializer.cpp b/SQLiteStudio3/coreSQLiteStudio/csvserializer.cpp new file mode 100644 index 0000000..97cc739 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/csvserializer.cpp @@ -0,0 +1,94 @@ +#include "csvserializer.h" +#include <QStringList> + +// TODO write unit tests for CsvSerializer + +QString CsvSerializer::serialize(const QList<QStringList>& data, const CsvFormat& format) +{ + QStringList outputRows; + + foreach (const QStringList& dataRow, data) + outputRows << serialize(dataRow, format); + + return outputRows.join(format.rowSeparator); +} + +QString CsvSerializer::serialize(const QStringList& data, const CsvFormat& format) +{ + QString value; + bool hasQuote; + QStringList outputCells; + foreach (const QString& rowValue, data) + { + value = rowValue; + + hasQuote = value.contains("\""); + if (hasQuote) + value.replace("\"", "\"\""); + + if (hasQuote || value.contains(format.columnSeparator) || value.contains(format.rowSeparator)) + value = "\""+value+"\""; + + outputCells << value; + } + + return outputCells.join(format.columnSeparator); +} + +QList<QStringList> CsvSerializer::deserialize(const QString& data, const CsvFormat& format) +{ + QList<QStringList> rows; + QStringList cells; + + int pos = 0; + int lgt = data.length(); + bool quotes = false; + QString field = ""; + QChar c; + + while (pos < lgt) + { + c = data[pos]; + if (!quotes && c == '"' ) + { + quotes = true; + } + else if (quotes && c == '"' ) + { + if (pos + 1 < data.length() && data[pos+1] == '"' ) + { + field += c; + pos++; + } + else + { + quotes = false; + } + } + else if (!quotes && format.columnSeparator.contains(c)) + { + cells << field; + field.clear(); + } + else if (!quotes && format.rowSeparator.contains(c)) + { + cells << field; + rows << cells; + cells.clear(); + field.clear(); + } + else + { + field += c; + } + pos++; + } + + if (field.size() > 0) + cells << field; + + if (cells.size() > 0) + rows << cells; + + return rows; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/csvserializer.h b/SQLiteStudio3/coreSQLiteStudio/csvserializer.h new file mode 100644 index 0000000..3217203 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/csvserializer.h @@ -0,0 +1,15 @@ +#ifndef CSVSERIALIZER_H +#define CSVSERIALIZER_H + +#include "coreSQLiteStudio_global.h" +#include "csvformat.h" + +class API_EXPORT CsvSerializer +{ + public: + static QString serialize(const QList<QStringList>& data, const CsvFormat& format); + static QString serialize(const QStringList& data, const CsvFormat& format); + static QList<QStringList> deserialize(const QString& data, const CsvFormat& format); +}; + +#endif // CSVSERIALIZER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/datatype.cpp b/SQLiteStudio3/coreSQLiteStudio/datatype.cpp new file mode 100644 index 0000000..613e6dc --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/datatype.cpp @@ -0,0 +1,231 @@ +#include "datatype.h" +#include <QMetaEnum> +#include <QRegularExpression> + +QList<DataType::Enum> DataType::values = [=]() -> QList<DataType::Enum> +{ + QList<DataType::Enum> list; + QMetaEnum metaEnum = DataType::staticMetaObject.enumerator(0); + DataType::Enum value; + for (int i = 0; i < metaEnum.keyCount(); i++) + { + value = static_cast<DataType::Enum>(metaEnum.value(i)); + if (value == DataType::unknown) + continue; + + list << value; + } + + return list; +}(); + +const QStringList DataType::names = [=]() -> QStringList +{ + QStringList list; + QMetaEnum metaEnum = DataType::staticMetaObject.enumerator(0); + DataType::Enum value; + for (int i = 0; i < metaEnum.keyCount(); i++) + { + value = static_cast<DataType::Enum>(metaEnum.value(i)); + if (value == DataType::unknown) + continue; + + list << DataType::toString(value); + } + + return list; +}(); + +DataType::DataType() +{ + setEmpty(); +} + +DataType::DataType(const QString& fullTypeString) +{ + static const QRegularExpression + re(R"(" + "^(?<type>[^\)]*)\s*(\((?<scale>[\d\.]+)\s*(,\s*(?<precision>[\d\.])+\s*)?\))?$" + ")"); + + QRegularExpressionMatch match = re.match(fullTypeString); + if (!match.hasMatch()) + { + setEmpty(); + return; + } + + typeStr = match.captured("type"); + type = fromString(typeStr, Qt::CaseInsensitive); + precision = match.captured("precision"); + scale = match.captured("scale"); +} + +DataType::DataType(const QString& type, const QVariant& scale, const QVariant& precision) +{ + this->type = fromString(type, Qt::CaseInsensitive); + this->typeStr = type; + this->precision = precision; + this->scale = scale; +} + +DataType::DataType(const DataType& other) : + QObject() +{ + operator=(other); +} + +void DataType::setEmpty() +{ + type = ::DataType::unknown; + typeStr = ""; + precision = QVariant(); + scale = QVariant(); +} + +DataType::Enum DataType::getType() const +{ + return type; +} + +void DataType::setType(DataType::Enum value) +{ + type = value; + typeStr = toString(type); +} + +QVariant DataType::getPrecision() const +{ + return precision; +} + +void DataType::setPrecision(const QVariant& value) +{ + precision = value; +} + +QVariant DataType::getScale() const +{ + return scale; +} + +void DataType::setScale(const QVariant& value) +{ + scale = value; +} + +QString DataType::toString() const +{ + return typeStr; +} + +QString DataType::toFullTypeString() const +{ + QString str = typeStr; + if (!precision.isNull()) + { + if (!scale.isNull()) + str += " ("+scale.toString()+", "+precision.toString()+")"; + else + str += " ("+scale.toString()+")"; + } + return str; +} + +bool DataType::isNumeric() +{ + return isNumeric(type); +} + +bool DataType::isBinary() +{ + return isBinary(typeStr); +} + +bool DataType::isNull() +{ + return type == ::DataType::unknown; +} + +bool DataType::isEmpty() +{ + return typeStr.isEmpty(); +} + +DataType& DataType::operator=(const DataType& other) +{ + this->type = other.type; + this->typeStr = other.typeStr; + this->precision = other.precision; + this->scale = scale; + return *this; +} + +QString DataType::toString(DataType::Enum e) +{ + QMetaEnum metaEnum = staticMetaObject.enumerator(0); + const char* key = metaEnum.valueToKey(e); + if (!key) + return QString::null; + + return key; +} + +DataType::Enum DataType::fromString(QString key, Qt::CaseSensitivity cs) +{ + QMetaEnum metaEnum = staticMetaObject.enumerator(0); + + if (cs == Qt::CaseInsensitive) + key = key.toUpper(); + + bool ok; + Enum value = static_cast<Enum>(metaEnum.keyToValue(key.toLatin1().data(), &ok)); + if (!ok) + return unknown; + + return value; +} + +bool DataType::isNumeric(DataType::Enum e) +{ + switch (e) + { + case BIGINT: + case DECIMAL: + case DOUBLE: + case INTEGER: + case INT: + case NUMERIC: + case REAL: + return true; + case BLOB: + case BOOLEAN: + case CHAR: + case DATE: + case DATETIME: + case NONE: + case STRING: + case TEXT: + case TIME: + case VARCHAR: + case unknown: + break; + } + return false; +} + +bool DataType::isBinary(const QString& type) +{ + static const QStringList binaryTypes = {"BLOB", "CLOB", "LOB"}; + return binaryTypes.contains(type.toUpper()); +} + +QList<DataType::Enum> DataType::getAllTypes() +{ + return values; +} + +QStringList DataType::getAllNames() +{ + return names; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/datatype.h b/SQLiteStudio3/coreSQLiteStudio/datatype.h new file mode 100644 index 0000000..9d8ca4e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/datatype.h @@ -0,0 +1,72 @@ +#ifndef DATATYPE_H +#define DATATYPE_H + +#include "coreSQLiteStudio_global.h" +#include <QObject> +#include <QVariant> + +class API_EXPORT DataType : public QObject +{ + Q_OBJECT + Q_ENUMS(Enum) + + public: + enum Enum + { + BIGINT, + BLOB, + BOOLEAN, + CHAR, + DATE, + DATETIME, + DECIMAL, + DOUBLE, + INTEGER, + INT, + NONE, + NUMERIC, + REAL, + STRING, + TEXT, + TIME, + VARCHAR, + unknown + }; + + DataType(); + DataType(const QString& fullTypeString); + DataType(const QString& type, const QVariant& scale, const QVariant& precision); + DataType(const DataType& other); + Enum getType() const; + void setType(Enum value); + QVariant getPrecision() const; + void setPrecision(const QVariant& value); + QVariant getScale() const; + void setScale(const QVariant& value); + QString toString() const; + QString toFullTypeString() const; + void setEmpty(); + bool isNumeric(); + bool isBinary(); + bool isNull(); + bool isEmpty(); + DataType& operator=(const DataType& other); + + static QString toString(Enum e); + static Enum fromString(QString key, Qt::CaseSensitivity cs = Qt::CaseSensitive); + static bool isNumeric(Enum e); + static bool isBinary(const QString& type); + static QList<Enum> getAllTypes(); + static QStringList getAllNames(); + + private: + Enum type = unknown; + QVariant precision; + QVariant scale; + QString typeStr; + + static QList<Enum> values; + static const QStringList names; +}; + +#endif // DATATYPE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp new file mode 100644 index 0000000..56275aa --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.cpp @@ -0,0 +1,879 @@ +#include "abstractdb.h" +#include "services/dbmanager.h" +#include "common/utils.h" +#include "asyncqueryrunner.h" +#include "sqlresultsrow.h" +#include "common/utils_sql.h" +#include "services/config.h" +#include "sqlerrorresults.h" +#include "sqlerrorcodes.h" +#include "services/notifymanager.h" +#include "log.h" +#include "parser/lexer.h" +#include <QDebug> +#include <QTime> +#include <QWriteLocker> +#include <QReadLocker> +#include <QThreadPool> +#include <QMetaEnum> +#include <QtConcurrent/QtConcurrentRun> + +quint32 AbstractDb::asyncId = 1; + +AbstractDb::AbstractDb(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions) : + name(name), path(path), connOptions(connOptions) +{ +} + +AbstractDb::~AbstractDb() +{ +} + +bool AbstractDb::open() +{ + bool res = isOpen() || openQuiet(); + if (res) + emit connected(); + + return res; +} + +bool AbstractDb::close() +{ + bool deny = false; + emit aboutToDisconnect(deny); + if (deny) + return false; + + bool res = !isOpen() || closeQuiet(); + if (res) + emit disconnected(); + + return res; +} + +bool AbstractDb::openQuiet() +{ + QWriteLocker locker(&dbOperLock); + QWriteLocker connectionLocker(&connectionStateLock); + return openAndSetup(); +} + +bool AbstractDb::closeQuiet() +{ + QWriteLocker locker(&dbOperLock); + QWriteLocker connectionLocker(&connectionStateLock); + interruptExecution(); + bool res = closeInternal(); + clearAttaches(); + registeredFunctions.clear(); + registeredCollations.clear(); + if (FUNCTIONS) // FUNCTIONS is already null when closing db while closing entire app + disconnect(FUNCTIONS, SIGNAL(functionListChanged()), this, SLOT(registerAllFunctions())); + + return res; +} + +bool AbstractDb::openForProbing() +{ + QWriteLocker locker(&dbOperLock); + QWriteLocker connectionLocker(&connectionStateLock); + bool res = openInternal(); + if (!res) + return res; + + // Implementation specific initialization + initAfterOpen(); + return res; +} + +void AbstractDb::registerAllFunctions() +{ + for (const RegisteredFunction& regFn : registeredFunctions) + { + if (!deregisterFunction(regFn.name, regFn.argCount)) + qWarning() << "Failed to deregister custom SQL function:" << regFn.name; + } + + registeredFunctions.clear(); + + RegisteredFunction regFn; + for (FunctionManager::ScriptFunction* fnPtr : FUNCTIONS->getScriptFunctionsForDatabase(getName())) + { + regFn.argCount = fnPtr->undefinedArgs ? -1 : fnPtr->arguments.count(); + regFn.name = fnPtr->name; + regFn.type = fnPtr->type; + registerFunction(regFn); + } + + for (FunctionManager::NativeFunction* fnPtr : FUNCTIONS->getAllNativeFunctions()) + { + regFn.argCount = fnPtr->undefinedArgs ? -1 : fnPtr->arguments.count(); + regFn.name = fnPtr->name; + regFn.type = fnPtr->type; + registerFunction(regFn); + } + + disconnect(FUNCTIONS, SIGNAL(functionListChanged()), this, SLOT(registerAllFunctions())); + connect(FUNCTIONS, SIGNAL(functionListChanged()), this, SLOT(registerAllFunctions())); +} + +void AbstractDb::registerAllCollations() +{ + foreach (const QString& name, registeredCollations) + { + if (!deregisterCollation(name)) + qWarning() << "Failed to deregister custom collation:" << name; + } + + registeredCollations.clear(); + + foreach (const CollationManager::CollationPtr& collPtr, COLLATIONS->getCollationsForDatabase(getName())) + registerCollation(collPtr->name); + + disconnect(COLLATIONS, SIGNAL(collationListChanged()), this, SLOT(registerAllCollations())); + connect(COLLATIONS, SIGNAL(collationListChanged()), this, SLOT(registerAllCollations())); +} + +bool AbstractDb::isOpen() +{ + // We use separate mutex for connection state to avoid situations, when some query is being executed, + // and we cannot check if database is open, which is not invasive method call. + QReadLocker connectionLocker(&connectionStateLock); + return isOpenInternal(); +} + +QString AbstractDb::generateUniqueDbName(bool lock) +{ + if (lock) + { + QReadLocker locker(&dbOperLock); + return generateUniqueDbNameNoLock(); + } + else + { + return generateUniqueDbNameNoLock(); + } +} + +QString AbstractDb::generateUniqueDbNameNoLock() +{ + SqlQueryPtr results = exec("PRAGMA database_list;", Db::Flag::NO_LOCK); + if (results->isError()) + { + qWarning() << "Could not get PRAGMA database_list. Falling back to internal db list. Error was:" << results->getErrorText(); + return generateUniqueName("attached", attachedDbMap.leftValues()); + } + + QStringList existingDatabases; + foreach (SqlResultsRowPtr row, results->getAll()) + existingDatabases << row->value("name").toString(); + + return generateUniqueName("attached", existingDatabases); +} + +ReadWriteLocker::Mode AbstractDb::getLockingMode(const QString &query, Flags flags) +{ + return ReadWriteLocker::getMode(query, getDialect(), flags.testFlag(Flag::NO_LOCK)); +} + +QString AbstractDb::getName() +{ + return name; +} + +QString AbstractDb::getPath() +{ + return path; +} + +quint8 AbstractDb::getVersion() +{ + return version; +} + +Dialect AbstractDb::getDialect() +{ + if (version == 2) + return Dialect::Sqlite2; + else + return Dialect::Sqlite3; +} + +QString AbstractDb::getEncoding() +{ + bool doClose = false; + if (!isOpen()) + { + if (!openQuiet()) + return QString::null; + + doClose = true; + } + QString encoding = exec("PRAGMA encoding;")->getSingleCell().toString(); + if (doClose) + closeQuiet(); + + return encoding; +} + +QHash<QString, QVariant>& AbstractDb::getConnectionOptions() +{ + return connOptions; +} + +void AbstractDb::setName(const QString& value) +{ + if (isOpen()) + { + qWarning() << "Tried to change database's name while the database was open."; + return; + } + name = value; +} + +void AbstractDb::setPath(const QString& value) +{ + if (isOpen()) + { + qWarning() << "Tried to change database's file path while the database was open."; + return; + } + path = value; +} + +void AbstractDb::setConnectionOptions(const QHash<QString, QVariant>& value) +{ + if (isOpen()) + { + qWarning() << "Tried to change database's connection options while the database was open."; + return; + } + connOptions = value; +} + +SqlQueryPtr AbstractDb::exec(const QString& query, AbstractDb::Flags flags) +{ + return exec(query, QList<QVariant>(), flags); +} + +SqlQueryPtr AbstractDb::exec(const QString& query, const QVariant& arg) +{ + return exec(query, {arg}); +} + +SqlQueryPtr AbstractDb::exec(const QString& query, std::initializer_list<QVariant> argList) +{ + return exec(query, QList<QVariant>(argList)); +} + +SqlQueryPtr AbstractDb::exec(const QString &query, std::initializer_list<std::pair<QString, QVariant> > argMap) +{ + return exec(query, QHash<QString,QVariant>(argMap)); +} + +void AbstractDb::asyncExec(const QString &query, const QList<QVariant> &args, AbstractDb::QueryResultsHandler resultsHandler, AbstractDb::Flags flags) +{ + quint32 asyncId = asyncExec(query, args, flags); + resultHandlers[asyncId] = resultsHandler; +} + +void AbstractDb::asyncExec(const QString &query, const QHash<QString, QVariant> &args, AbstractDb::QueryResultsHandler resultsHandler, AbstractDb::Flags flags) +{ + quint32 asyncId = asyncExec(query, args, flags); + resultHandlers[asyncId] = resultsHandler; +} + +void AbstractDb::asyncExec(const QString &query, AbstractDb::QueryResultsHandler resultsHandler, AbstractDb::Flags flags) +{ + quint32 asyncId = asyncExec(query, flags); + resultHandlers[asyncId] = resultsHandler; +} + +SqlQueryPtr AbstractDb::exec(const QString &query, const QList<QVariant>& args, Flags flags) +{ + return execListArg(query, args, flags); +} + +SqlQueryPtr AbstractDb::exec(const QString& query, const QHash<QString, QVariant>& args, AbstractDb::Flags flags) +{ + return execHashArg(query, args, flags); +} + +SqlQueryPtr AbstractDb::execHashArg(const QString& query, const QHash<QString,QVariant>& args, Flags flags) +{ + if (!isOpenInternal()) + return SqlQueryPtr(new SqlErrorResults(SqlErrorCode::DB_NOT_OPEN, tr("Cannot execute query on closed database."))); + + logSql(this, query, args, flags); + QString newQuery = query; + SqlQueryPtr queryStmt = prepare(newQuery); + queryStmt->setArgs(args); + queryStmt->setFlags(flags); + queryStmt->execute(); + + if (flags.testFlag(Flag::PRELOAD)) + queryStmt->preload(); + + return queryStmt; +} + +SqlQueryPtr AbstractDb::execListArg(const QString& query, const QList<QVariant>& args, Flags flags) +{ + if (!isOpenInternal()) + return SqlQueryPtr(new SqlErrorResults(SqlErrorCode::DB_NOT_OPEN, tr("Cannot execute query on closed database."))); + + logSql(this, query, args, flags); + QString newQuery = query; + SqlQueryPtr queryStmt = prepare(newQuery); + queryStmt->setArgs(args); + queryStmt->setFlags(flags); + queryStmt->execute(); + + if (flags.testFlag(Flag::PRELOAD)) + queryStmt->preload(); + + return queryStmt; +} + +bool AbstractDb::openAndSetup() +{ + bool result = openInternal(); + if (!result) + return result; + + // When this is an internal configuration database + if (connOptions.contains(DB_PURE_INIT)) + return true; + + // Implementation specific initialization + initAfterOpen(); + + // Custom SQL functions + registerAllFunctions(); + + // Custom collations + registerAllCollations(); + + return result; +} + +void AbstractDb::initAfterOpen() +{ +} + +void AbstractDb::checkForDroppedObject(const QString& query) +{ + TokenList tokens = Lexer::tokenize(query, getDialect()); + tokens.trim(Token::OPERATOR, ";"); + if (tokens.size() == 0) + return; + + if (tokens[0]->type != Token::KEYWORD || tokens.first()->value.toUpper() != "DROP") + return; + + tokens.removeFirst(); // remove "DROP" from front + tokens.trimLeft(); // remove whitespaces and comments from front + if (tokens.size() == 0) + { + qWarning() << "Successful execution of DROP, but after removing DROP from front of the query, nothing has left. Original query:" << query; + return; + } + + QString type = tokens.first()->value.toUpper(); + + // Now go to the first ID in the tokens + while (tokens.size() > 0 && tokens.first()->type != Token::OTHER) + tokens.removeFirst(); + + if (tokens.size() == 0) + { + qWarning() << "Successful execution of DROP, but after removing DROP and non-ID tokens from front of the query, nothing has left. Original query:" << query; + return; + } + + QString database = "main"; + QString object; + + if (tokens.size() > 1) + { + database = tokens.first()->value; + object = tokens.last()->value; + } + else + object = tokens.first()->value; + + object = stripObjName(object, getDialect()); + + if (type == "TABLE") + emit dbObjectDeleted(database, object, DbObjectType::TABLE); + else if (type == "INDEX") + emit dbObjectDeleted(database, object, DbObjectType::INDEX); + else if (type == "TRIGGER") + emit dbObjectDeleted(database, object, DbObjectType::TRIGGER); + else if (type == "VIEW") + emit dbObjectDeleted(database, object, DbObjectType::VIEW); + else + qWarning() << "Unknown object type dropped:" << type; +} + +bool AbstractDb::registerCollation(const QString& name) +{ + if (registeredCollations.contains(name)) + { + qCritical() << "Collation" << name << "is already registered!" + << "It should already be deregistered while call to register is being made."; + return false; + } + + if (registerCollationInternal(name)) + { + registeredCollations << name; + return true; + } + + qCritical() << "Could not register collation:" << name; + return false; +} + +bool AbstractDb::deregisterCollation(const QString& name) +{ + if (!registeredCollations.contains(name)) + { + qCritical() << "Collation" << name << "not registered!" + << "It should already registered while call to deregister is being made."; + return false; + } + + if (deregisterCollationInternal(name)) + { + registeredCollations.removeOne(name); + return true; + } + qWarning() << "Could not deregister collation:" << name; + return false; +} + +bool AbstractDb::isCollationRegistered(const QString& name) +{ + return registeredCollations.contains(name); +} + +QHash<QString, QVariant> AbstractDb::getAggregateContext(void* memPtr) +{ + if (!memPtr) + { + qCritical() << "Could not allocate aggregate context."; + return QHash<QString, QVariant>(); + } + + QHash<QString,QVariant>** aggCtxPtr = reinterpret_cast<QHash<QString,QVariant>**>(memPtr); + if (!*aggCtxPtr) + *aggCtxPtr = new QHash<QString,QVariant>(); + + return **aggCtxPtr; +} + +void AbstractDb::setAggregateContext(void* memPtr, const QHash<QString, QVariant>& aggregateContext) +{ + if (!memPtr) + { + qCritical() << "Could not extract aggregate context."; + return; + } + + QHash<QString,QVariant>** aggCtxPtr = reinterpret_cast<QHash<QString,QVariant>**>(memPtr); + **aggCtxPtr = aggregateContext; +} + +void AbstractDb::releaseAggregateContext(void* memPtr) +{ + if (!memPtr) + { + qCritical() << "Could not release aggregate context."; + return; + } + + QHash<QString,QVariant>** aggCtxPtr = reinterpret_cast<QHash<QString,QVariant>**>(memPtr); + delete *aggCtxPtr; +} + +QVariant AbstractDb::evaluateScalar(void* dataPtr, const QList<QVariant>& argList, bool& ok) +{ + if (!dataPtr) + return QVariant(); + + FunctionUserData* userData = reinterpret_cast<FunctionUserData*>(dataPtr); + + return FUNCTIONS->evaluateScalar(userData->name, userData->argCount, argList, userData->db, ok); +} + +void AbstractDb::evaluateAggregateStep(void* dataPtr, QHash<QString, QVariant>& aggregateContext, QList<QVariant> argList) +{ + if (!dataPtr) + return; + + FunctionUserData* userData = reinterpret_cast<FunctionUserData*>(dataPtr); + + QHash<QString,QVariant> storage = aggregateContext["storage"].toHash(); + if (!aggregateContext.contains("initExecuted")) + { + FUNCTIONS->evaluateAggregateInitial(userData->name, userData->argCount, userData->db, storage); + aggregateContext["initExecuted"] = true; + } + + FUNCTIONS->evaluateAggregateStep(userData->name, userData->argCount, argList, userData->db, storage); + aggregateContext["storage"] = storage; +} + +QVariant AbstractDb::evaluateAggregateFinal(void* dataPtr, QHash<QString, QVariant>& aggregateContext, bool& ok) +{ + if (!dataPtr) + return QVariant(); + + FunctionUserData* userData = reinterpret_cast<FunctionUserData*>(dataPtr); + QHash<QString,QVariant> storage = aggregateContext["storage"].toHash(); + + return FUNCTIONS->evaluateAggregateFinal(userData->name, userData->argCount, userData->db, ok, storage); +} + +quint32 AbstractDb::asyncExec(const QString &query, Flags flags) +{ + AsyncQueryRunner* runner = new AsyncQueryRunner(query, QList<QVariant>(), flags); + return asyncExec(runner); +} + +quint32 AbstractDb::asyncExec(const QString& query, const QHash<QString, QVariant>& args, AbstractDb::Flags flags) +{ + AsyncQueryRunner* runner = new AsyncQueryRunner(query, args, flags); + return asyncExec(runner); +} + +quint32 AbstractDb::asyncExec(const QString& query, const QList<QVariant>& args, AbstractDb::Flags flags) +{ + AsyncQueryRunner* runner = new AsyncQueryRunner(query, args, flags); + return asyncExec(runner); +} + +quint32 AbstractDb::asyncExec(AsyncQueryRunner *runner) +{ + quint32 asyncId = generateAsyncId(); + runner->setDb(this); + runner->setAsyncId(asyncId); + + connect(runner, SIGNAL(finished(AsyncQueryRunner*)), + this, SLOT(asyncQueryFinished(AsyncQueryRunner*))); + + QThreadPool::globalInstance()->start(runner); + + return asyncId; +} + +void AbstractDb::asyncQueryFinished(AsyncQueryRunner *runner) +{ + // Extract everything from the runner + SqlQueryPtr results = runner->getResults(); + quint32 asyncId = runner->getAsyncId(); + delete runner; + + if (handleResultInternally(asyncId, results)) + return; + + emit asyncExecFinished(asyncId, results); + + if (isReadable() && isWritable()) + emit idle(); +} + +QString AbstractDb::attach(Db* otherDb, bool silent) +{ + QWriteLocker locker(&dbOperLock); + if (!isOpenInternal()) + return QString::null; + + if (attachedDbMap.containsRight(otherDb)) + { + attachCounter[otherDb]++; + return attachedDbMap.valueByRight(otherDb); + } + + QString attName = generateUniqueDbName(false); + SqlQueryPtr results = exec(getAttachSql(otherDb, attName), Flag::NO_LOCK); + if (results->isError()) + { + if (!silent) + notifyError(tr("Error attaching database %1: %2").arg(otherDb->getName()).arg(results->getErrorText())); + else + qDebug() << QString("Error attaching database %1: %2").arg(otherDb->getName()).arg(results->getErrorText()); + + return QString::null; + } + + attachedDbMap.insert(attName, otherDb); + + emit attached(otherDb); + return attName; +} + +void AbstractDb::detach(Db* otherDb) +{ + QWriteLocker locker(&dbOperLock); + + if (!isOpenInternal()) + return; + + detachInternal(otherDb); +} + +void AbstractDb::detachInternal(Db* otherDb) +{ + if (!attachedDbMap.containsRight(otherDb)) + return; + + if (attachCounter.contains(otherDb)) + { + attachCounter[otherDb]--; + return; + } + + exec(QString("DETACH %1;").arg(attachedDbMap.valueByRight(otherDb)), Flag::NO_LOCK); + attachedDbMap.removeRight(otherDb); + emit detached(otherDb); +} + +void AbstractDb::clearAttaches() +{ + attachedDbMap.clear(); + attachCounter.clear(); +} + +void AbstractDb::detachAll() +{ + QWriteLocker locker(&dbOperLock); + + if (!isOpenInternal()) + return; + + foreach (Db* db, attachedDbMap.rightValues()) + detachInternal(db); +} + +const QHash<Db *, QString> &AbstractDb::getAttachedDatabases() +{ + QReadLocker locker(&dbOperLock); + return attachedDbMap.toInvertedQHash(); +} + +QSet<QString> AbstractDb::getAllAttaches() +{ + QReadLocker locker(&dbOperLock); + QSet<QString> attaches = attachedDbMap.leftValues().toSet(); + // TODO query database for attached databases and unite them here + return attaches; +} + +QString AbstractDb::getUniqueNewObjectName(const QString &attachedDbName) +{ + QString dbName = getPrefixDb(attachedDbName, getDialect()); + + QSet<QString> existingNames; + SqlQueryPtr results = exec(QString("SELECT name FROM %1.sqlite_master").arg(dbName)); + + foreach (SqlResultsRowPtr row, results->getAll()) + existingNames << row->value(0).toString(); + + return randStrNotIn(16, existingNames, false); +} + +QString AbstractDb::getErrorText() +{ + QReadLocker locker(&dbOperLock); + return getErrorTextInternal(); +} + +int AbstractDb::getErrorCode() +{ + QReadLocker locker(&dbOperLock); + return getErrorCodeInternal(); +} + +bool AbstractDb::initAfterCreated() +{ + bool isOpenBefore = isOpen(); + if (!isOpenBefore) + { + if (!openForProbing()) + { + qWarning() << "Could not open database for initAfterCreated(). Database:" << name; + return false; + } + } + + // SQLite version + QVariant value = exec("SELECT sqlite_version()")->getSingleCell(); + version = value.toString().mid(0, 1).toUInt(); + + if (!isOpenBefore) + closeQuiet(); + + return true; +} + +void AbstractDb::setTimeout(int secs) +{ + timeout = secs; +} + +int AbstractDb::getTimeout() const +{ + return timeout; +} + +bool AbstractDb::isValid() const +{ + return true; +} + +QString AbstractDb::getAttachSql(Db* otherDb, const QString& generatedAttachName) +{ + return QString("ATTACH '%1' AS %2;").arg(otherDb->getPath(), generatedAttachName); +} + +quint32 AbstractDb::generateAsyncId() +{ + if (asyncId > 4000000000) + asyncId = 1; + + return asyncId++; +} + +bool AbstractDb::begin() +{ + QWriteLocker locker(&dbOperLock); + + if (!isOpenInternal()) + return false; + + SqlQueryPtr results = exec("BEGIN;", Flag::NO_LOCK); + if (results->isError()) + { + qCritical() << "Error while starting a transaction: " << results->getErrorCode() << results->getErrorText(); + return false; + } + + return true; +} + +bool AbstractDb::commit() +{ + QWriteLocker locker(&dbOperLock); + + if (!isOpenInternal()) + return false; + + SqlQueryPtr results = exec("COMMIT;", Flag::NO_LOCK); + if (results->isError()) + { + qCritical() << "Error while commiting a transaction: " << results->getErrorCode() << results->getErrorText(); + return false; + } + + return true; +} + +bool AbstractDb::rollback() +{ + QWriteLocker locker(&dbOperLock); + + if (!isOpenInternal()) + return false; + + SqlQueryPtr results = exec("ROLLBACK;", Flag::NO_LOCK); + if (results->isError()) + { + qCritical() << "Error while rolling back a transaction: " << results->getErrorCode() << results->getErrorText(); + return false; + } + + return true; +} + +void AbstractDb::interrupt() +{ + // Lock connection state to forbid closing db before interrupt() returns. + // This is required by SQLite. + QWriteLocker locker(&connectionStateLock); + interruptExecution(); +} + +void AbstractDb::asyncInterrupt() +{ + QtConcurrent::run(this, &AbstractDb::interrupt); +} + +bool AbstractDb::isReadable() +{ + bool res = dbOperLock.tryLockForRead(); + if (res) + dbOperLock.unlock(); + + return res; +} + +bool AbstractDb::isWritable() +{ + bool res = dbOperLock.tryLockForWrite(); + if (res) + dbOperLock.unlock(); + + return res; +} + +AttachGuard AbstractDb::guardedAttach(Db* otherDb, bool silent) +{ + QString attachName = attach(otherDb, silent); + return AttachGuard::create(this, otherDb, attachName); +} + +bool AbstractDb::handleResultInternally(quint32 asyncId, SqlQueryPtr results) +{ + if (!resultHandlers.contains(asyncId)) + return false; + + resultHandlers[asyncId](results); + resultHandlers.remove(asyncId); + + return true; +} + +void AbstractDb::registerFunction(const AbstractDb::RegisteredFunction& function) +{ + if (registeredFunctions.contains(function)) + return; // native function was overwritten by script function + + bool successful = false; + switch (function.type) + { + case FunctionManager::ScriptFunction::SCALAR: + successful = registerScalarFunction(function.name, function.argCount); + break; + case FunctionManager::ScriptFunction::AGGREGATE: + successful = registerAggregateFunction(function.name, function.argCount); + break; + } + + if (successful) + registeredFunctions << function; + else + qCritical() << "Could not register SQL function:" << function.name << function.argCount << function.type; +} + +int qHash(const AbstractDb::RegisteredFunction& fn) +{ + return qHash(fn.name) ^ fn.argCount ^ fn.type; +} + +bool operator==(const AbstractDb::RegisteredFunction& fn1, const AbstractDb::RegisteredFunction& fn2) +{ + return fn1.name == fn2.name && fn1.argCount == fn2.argCount && fn1.type == fn2.type; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h new file mode 100644 index 0000000..89edf03 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb.h @@ -0,0 +1,485 @@ +#ifndef ABSTRACTDB_H +#define ABSTRACTDB_H + +#include "returncode.h" +#include "sqlquery.h" +#include "dialect.h" +#include "db/db.h" +#include "common/bihash.h" +#include "services/functionmanager.h" +#include "common/readwritelocker.h" +#include "coreSQLiteStudio_global.h" +#include <QObject> +#include <QVariant> +#include <QList> +#include <QHash> +#include <QSet> +#include <QReadWriteLock> +#include <QRunnable> +#include <QStringList> + +class AsyncQueryRunner; + +/** + * @brief Base database logic implementation. + * + * This class implements common base logic for all database implementations. It's still abstract class + * and needs further implementation to be usable. + */ +class API_EXPORT AbstractDb : public Db +{ + Q_OBJECT + + public: + /** + * @brief Initializes database object. + * @param name Name for the database. + * @param path File path of the database. + * @param connOptions Connection options. See below for details. + * + * Connection options are handled individually by the derived database implementation class. + * It can be password for encrypted databases, read-only access flag, etc. + */ + AbstractDb(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions); + + virtual ~AbstractDb(); + + bool isOpen(); + QString getName(); + QString getPath(); + quint8 getVersion(); + Dialect getDialect(); + QString getEncoding(); + QHash<QString,QVariant>& getConnectionOptions(); + void setName(const QString& value); + void setPath(const QString& value); + void setConnectionOptions(const QHash<QString,QVariant>& value); + SqlQueryPtr exec(const QString& query, const QList<QVariant> &args, Flags flags = Flag::NONE); + SqlQueryPtr exec(const QString& query, const QHash<QString, QVariant>& args, Flags flags = Flag::NONE); + SqlQueryPtr exec(const QString &query, Db::Flags flags = Flag::NONE); + SqlQueryPtr exec(const QString &query, const QVariant &arg); + SqlQueryPtr exec(const QString &query, std::initializer_list<QVariant> argList); + SqlQueryPtr exec(const QString &query, std::initializer_list<std::pair<QString,QVariant>> argMap); + void asyncExec(const QString& query, const QList<QVariant>& args, QueryResultsHandler resultsHandler, Flags flags = Flag::NONE); + void asyncExec(const QString& query, const QHash<QString, QVariant>& args, QueryResultsHandler resultsHandler, Flags flags = Flag::NONE); + void asyncExec(const QString& query, QueryResultsHandler resultsHandler, Flags flags = Flag::NONE); + quint32 asyncExec(const QString& query, const QList<QVariant>& args, Flags flags = Flag::NONE); + quint32 asyncExec(const QString& query, const QHash<QString, QVariant>& args, Flags flags = Flag::NONE); + quint32 asyncExec(const QString& query, Flags flags = Flag::NONE); + bool begin(); + bool commit(); + bool rollback(); + void interrupt(); + void asyncInterrupt(); + bool isReadable(); + bool isWritable(); + AttachGuard guardedAttach(Db* otherDb, bool silent = false); + QString attach(Db* otherDb, bool silent = false); + void detach(Db* otherDb); + void detachAll(); + const QHash<Db*,QString>& getAttachedDatabases(); + QSet<QString> getAllAttaches(); + QString getUniqueNewObjectName(const QString& attachedDbName = QString()); + QString getErrorText(); + int getErrorCode(); + bool initAfterCreated(); + void setTimeout(int secs); + int getTimeout() const; + bool isValid() const; + + protected: + struct FunctionUserData + { + QString name; + int argCount = 0; + Db* db = nullptr; + }; + + virtual QString getAttachSql(Db* otherDb, const QString& generatedAttachName); + + /** + * @brief Generates unique database name for ATTACH. + * @param lock Defines if the lock on dbOperLock mutex. + * @return Unique database name. + * + * Database name here is the name to be used for ATTACH statement. + * For example it will never be "main" or "temp", as those names are already used. + * It also will never be any name that is currently used for any ATTACH'ed database. + * It respects both manual ATTACH'es (called by user), as well as by attach() calls. + * + * Operations on database are normally locked during name generation, because it involves + * queries to the database about what are currently existing objects. + * The lock can be ommited if the calling method already locked dbOperLock. + */ + QString generateUniqueDbName(bool lock = true); + + /** + * @brief Detaches given database object from this database. + * @param otherDb Other registered database. + * + * This is called from detach() and detachAll(). + */ + void detachInternal(Db* otherDb); + + /** + * @brief Clears attached databases list. + * + * Called by closeQuiet(). Only clears maps and lists regarding attached databases. + * It doesn't call detach(), because closing the database will already detach all databases. + */ + void clearAttaches(); + + /** + * @brief Generated unique ID for asynchronous query execution. + * @return Unique ID. + */ + static quint32 generateAsyncId(); + + /** + * @brief Executes query asynchronously. + * @param runner Prepared object for asynchronous execution. + * @return Asynchronous execution unique ID. + * + * This is called by asyncExec(). Runs prepared runner object (which has all information about the query) + * on separate thread. + */ + quint32 asyncExec(AsyncQueryRunner* runner); + + /** + * @brief Opens the database and calls initial setup. + * @return true on success, false on failure. + * + * Calls openInternal() and if it succeeded, calls initialDbSetup(). + * It's called from openQuiet(). + */ + bool openAndSetup(); + + /** + * @brief Checks if the database connection is open. + * @return true if the connection is open, or false otherwise. + * + * This is called from isOpen(). Implementation should test and return information if the database + * connection is open. A lock on connectionStateLock is already set by the isOpen() method. + */ + virtual bool isOpenInternal() = 0; + + /** + * @brief Interrupts execution of any queries. + * + * Implementation of this method should interrupt any query executions that are currently in progress. + * Typical implementation for SQLite databases will call sqlite_interupt() / sqlite3_interupt(). + */ + virtual void interruptExecution() = 0; + + /** + * @brief Returns error message. + * @return Error string. + * + * This can be either error from last query execution, but also from connection opening problems, etc. + */ + virtual QString getErrorTextInternal() = 0; + + /** + * @brief Returns error code. + * @return Error code. + * + * This can be either error from last query execution, but also from connection opening problems, etc. + */ + virtual int getErrorCodeInternal() = 0; + + /** + * @brief Opens database connection. + * @return true on success, false on failure. + * + * Opens database. Called by open() and openAndSetup(). + */ + virtual bool openInternal() = 0; + + /** + * @brief Closes database connection. + * + * Closes database. Called by open() and openQuiet(). + */ + virtual bool closeInternal() = 0; + + virtual void initAfterOpen(); + + void checkForDroppedObject(const QString& query); + bool registerCollation(const QString& name); + bool deregisterCollation(const QString& name); + bool isCollationRegistered(const QString& name); + + /** + * @brief Registers a collation sequence implementation in the database. + * @param name Name of the collation. + * @return true on success, false on failure. + * + * This should be low-level implementation depended on SQLite driver. + * The general implementation of registerCollation() in this class just keeps track on collations + * registered. + */ + virtual bool registerCollationInternal(const QString& name) = 0; + + /** + * @brief Deregisters previously registered collation from this database. + * @param name Collation name. + * @return true on success, false on failure. + * + * This should be low-level implementation depended on SQLite driver. + * The general implementation of registerCollation() in this class just keeps track on collations + * registered. + */ + virtual bool deregisterCollationInternal(const QString& name) = 0; + + static QHash<QString,QVariant> getAggregateContext(void* memPtr); + static void setAggregateContext(void* memPtr, const QHash<QString,QVariant>& aggregateContext); + static void releaseAggregateContext(void* memPtr); + + /** + * @brief Evaluates requested function using defined implementation code and provides result. + * @param dataPtr SQL function user data (defined when registering function). Must be of FunctionUserData* type, or descendant. + * @param argList List of arguments passed to the function. + * @param[out] ok true (default) to indicate successful execution, or false to report an error. + * @return Result returned from the plugin handling function implementation. + * + * This method is aware of the implementation language and the code defined for it, + * so it delegates the execution to the proper plugin handling that language. + * + * This method is called for scalar functions. + */ + static QVariant evaluateScalar(void* dataPtr, const QList<QVariant>& argList, bool& ok); + static void evaluateAggregateStep(void* dataPtr, QHash<QString, QVariant>& aggregateContext, QList<QVariant> argList); + static QVariant evaluateAggregateFinal(void* dataPtr, QHash<QString, QVariant>& aggregateContext, bool& ok); + + /** + * @brief Database name. + * + * It must be unique across all Db instances. Use generateUniqueDbName() to get the unique name + * for new database. It's used as a key for DbManager. + * + * Databases are also presented to the user with this name on UI. + */ + QString name; + + /** + * @brief Path to the database file. + */ + QString path; + + /** + * @brief Connection options. + * + * There are no standard options. Custom DbPlugin implementations may support some options. + */ + QHash<QString,QVariant> connOptions; + + /** + * @brief SQLite version of this database. + * + * This is only a major version number (2 or 3). + */ + quint8 version = 0; + + /** + * @brief Map of databases attached to this database. + * + * It's mapping from ATTACH name to the database object. It contains only attaches + * that were made with attach() calls. + */ + BiHash<QString,Db*> attachedDbMap; + + /** + * @brief Counter of attaching requrests for each database. + * + * When calling attach() on other Db, it gets its own entry in this mapping. + * If the mapping already exists, its value is incremented. + * Then, when calling detach(), counter is decremented and when it reaches 0, + * the database is actualy detached. + */ + QHash<Db*,int> attachCounter; + + /** + * @brief Result handler functions for asynchronous executions. + * + * For each asyncExec() with function pointer in argument there's an entry in this map + * pointing to the function. Keys are asynchronous IDs. + */ + QHash<int,QueryResultsHandler> resultHandlers; + + /** + * @brief Database operation lock. + * + * This lock is set whenever any operation on the actual database is performed (i.e. call to + * exec(), interrupt(), open(), close(), generateUniqueDbName(true), attach(), detach(), and others... + * generally anything that does operations on database that must be synchronous). + * + * In case of exec() it can be locked for READ or WRITE (depending on query type), + * because there can be multiple SELECTs and there's nothing wrong with it, + * while for other methods is always lock for WRITE. + */ + QReadWriteLock dbOperLock; + + private: + /** + * @brief Represents single function that is registered in the database. + * + * Registered custom SQL functions are diversed by SQLite by their name, arguments count and their type, + * so this structure has exactly those parameters. + */ + struct RegisteredFunction + { + /** + * @brief Function name. + */ + QString name; + + /** + * @brief Arguments count (-1 for undefined count). + */ + int argCount; + + /** + * @brief Function type. + */ + FunctionManager::ScriptFunction::Type type; + }; + + friend int qHash(const AbstractDb::RegisteredFunction& fn); + friend bool operator==(const AbstractDb::RegisteredFunction& fn1, const AbstractDb::RegisteredFunction& fn2); + + /** + * @brief Applies execution flags and executes query. + * @param query Query to be executed. + * @param args Query parameters. + * @param flags Query execution flags. + * @return Execution results - either successful or failed. + * + * This is called from both exec() and execNoLock() and is a final step before calling execInternal() + * (the plugin-provided execution). This is where \p flags are interpreted and applied. + */ + SqlQueryPtr execHashArg(const QString& query, const QHash<QString, QVariant>& args, Flags flags); + + /** + * @overload + */ + SqlQueryPtr execListArg(const QString& query, const QList<QVariant>& args, Flags flags); + + /** + * @brief Generates unique database name. + * @return Unique database name. + * + * This is a lock-less variant of generateUniqueDbName(). It is called from that method. + * See generateUniqueDbName() for details. + */ + QString generateUniqueDbNameNoLock(); + + /** + * @brief Provides required locking mode for given query. + * @param query Query to be executed. + * @return Locking mode: READ or WRITE. + * + * Given the query this method analyzes what is the query and provides information if the query + * will do some changes on the database, or not. Then it returns proper locking mode that should + * be used for this query execution. + * + * Query execution methods from this class check if lock mode of the query to be executed isn't + * in conflict with the lock being currently applied on the dbOperLock (if any is applied at the moment). + * + * This method works on a very simple rule. It assumes that queries: SELECT, ANALYZE, EXPLAIN, + * and PRAGMA - are read-only, while all other queries are read-write. + * In case of PRAGMA this is not entirely true, but it's not like using PRAGMA for changing + * some setting would cause database state inconsistency. At least not from perspective of SQLiteStudio. + * + * In case of WITH statement it filters out the "WITH clause" and then checks for SELECT keyword. + */ + ReadWriteLocker::Mode getLockingMode(const QString& query, Db::Flags flags); + + /** + * @brief Handles asynchronous query results with results handler function. + * @param asyncId Asynchronous ID. + * @param results Results from execution. + * @return true if the results were handled, or false if they were not. + * + * This method checks if there is a handler function for given asynchronous ID (in resultHandlers) + * and if there is, then evaluates it and returns true. Otherwise does nothing and returns false. + */ + bool handleResultInternally(quint32 asyncId, SqlQueryPtr results); + + /** + * @brief Registers single custom SQL function. + * @param function Function to register. + * + * If function got registered successfully, it's added to registeredFunctions. + * If there was a function with the same name, argument count and type already registered, + * it will be overwritten (both in SQLite and in registeredFunctions). + */ + void registerFunction(const RegisteredFunction& function); + + /** + * @brief Connection state lock. + * + * It's locked whenever the connection state is changed or tested. + * For open() and close() it's a WRITE lock, for isOpen() it's READ lock. + */ + QReadWriteLock connectionStateLock; + + /** + * @brief Sequence container for generating unique asynchronous IDs. + */ + static quint32 asyncId; + + /** + * @brief Current timeout (in seconds) for waiting for the database to be released from the lock. + * + * See Db::setTimeout() for details. + */ + int timeout = 60; + + /** + * @brief List of all functions currently registered in this database. + */ + QSet<RegisteredFunction> registeredFunctions; + + /** + * @brief List of all collations currently registered in this database. + */ + QStringList registeredCollations; + + private slots: + /** + * @brief Handles asynchronous execution results. + * @param runner Container with input and output data of the query. + * + * This is called from the other thread when it finished asynchronous execution. + * It checks if there is any handler function to evaluate it with results + * and if there's not, emits asyncExecFinished() signal. + */ + void asyncQueryFinished(AsyncQueryRunner* runner); + + public slots: + bool open(); + bool close(); + bool openQuiet(); + bool closeQuiet(); + bool openForProbing(); + void registerAllFunctions(); + void registerAllCollations(); +}; + +/** + * @brief Standard function required by QHash. + * @param fn Function to calculate hash for. + * @return Hash value calculated from all members of DbBase::RegisteredFunction. + */ +int qHash(const AbstractDb::RegisteredFunction& fn); + +/** + * @brief Simple comparator operator, compares all members. + * @param other Other function to compare. + * @return true if \p other is equal, false otherwise. + * + * This function had to be declared/defined outside of the DbBase::RegisteredFunction, because QSet/QHash requires this. + */ +bool operator==(const AbstractDb::RegisteredFunction& fn1, const AbstractDb::RegisteredFunction& fn2); + +#endif // ABSTRACTDB_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h new file mode 100644 index 0000000..e35e038 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb2.h @@ -0,0 +1,882 @@ +#ifndef ABSTRACTDB2_H +#define ABSTRACTDB2_H + +#include "db/abstractdb.h" +#include "parser/lexer.h" +#include "common/utils_sql.h" +#include "common/unused.h" +#include "db/sqlerrorcodes.h" +#include "db/sqlerrorresults.h" +#include <sqlite.h> +#include <QThread> +#include <QPointer> +#include <QDebug> + +/** + * @brief Complete implementation of SQLite 2 driver for SQLiteStudio. + * + * Inherit this when implementing Db for SQLite 2. In most cases you will only need + * to create one public constructor, which forwards parameters to the AbstractDb constructor. + * This be sufficient to implement SQLite 2 database plugin. + * Just link it with proper SQLite library. + * + * The template parameter is currently not used for anything specific, so pass any unique type name. + * The best would be to define empty class/structure just for this purpose. + * The parameter is there, so this class becomes a template class. + * We need a template class so we can provide common code base for all SQLite 2 plugins, while the + * code doesn't introduce dependency to SQLite 2 library, until it's used, which is in SQLite 2 plugins. + * + * @see DbQt + */ +template <class T> +class AbstractDb2 : public AbstractDb +{ + public: + /** + * @brief Creates SQLite database object. + * @param name Name for the database. + * @param path File path of the database. + * @param connOptions Connection options. See AbstractDb for details. + * + * All values from this constructor are just passed to AbstractDb constructor. + */ + AbstractDb2(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions); + + ~AbstractDb2(); + + protected: + bool isOpenInternal(); + void interruptExecution(); + QString getErrorTextInternal(); + int getErrorCodeInternal(); + bool openInternal(); + bool closeInternal(); + void initAfterOpen(); + SqlQueryPtr prepare(const QString& query); + QString getTypeLabel(); + bool deregisterFunction(const QString& name, int argCount); + bool registerScalarFunction(const QString& name, int argCount); + bool registerAggregateFunction(const QString& name, int argCount); + bool registerCollationInternal(const QString& name); + bool deregisterCollationInternal(const QString& name); + + private: + class Query : public SqlQuery, public Sqlite2ColumnDataTypeHelper + { + public: + class Row : public SqlResultsRow + { + public: + void init(const QStringList& columns, const QList<QVariant>& resultValues); + }; + + Query(AbstractDb2<T>* db, const QString& query); + ~Query(); + + QString getErrorText(); + int getErrorCode(); + QStringList getColumnNames(); + int columnCount(); + qint64 rowsAffected(); + QString finalize(); + + protected: + SqlResultsRowPtr nextInternal(); + bool hasNextInternal(); + bool execInternal(const QList<QVariant>& args); + bool execInternal(const QHash<QString, QVariant>& args); + + private: + int prepareStmt(const QString& processedQuery); + int resetStmt(); + int bindParam(int paramIdx, const QVariant& value); + int fetchNext(); + int fetchFirst(); + void init(int columnsCount, const char** columns); + bool checkDbState(); + void copyErrorFromDb(); + void copyErrorToDb(); + void setError(int code, const QString& msg); + + static QString replaceNamedParams(const QString& query); + + QPointer<AbstractDb2<T>> db; + sqlite_vm* stmt = nullptr; + int errorCode = SQLITE_OK; + QString errorMessage; + int colCount = -1; + QStringList colNames; + QList<QVariant> nextRowValues; + int affected = 0; + bool rowAvailable = false; + }; + + void cleanUp(); + void resetError(); + QString freeStatement(sqlite_vm* stmt); + + static void storeResult(sqlite_func* func, const QVariant& result, bool ok); + static QList<QVariant> getArgs(int argCount, const char** args); + static void evaluateScalar(sqlite_func* func, int argCount, const char** args); + static void evaluateAggregateStep(sqlite_func* func, int argCount, const char** args); + static void evaluateAggregateFinal(sqlite_func* func); + static void* getContextMemPtr(sqlite_func* func); + static QHash<QString,QVariant> getAggregateContext(sqlite_func* func); + static void setAggregateContext(sqlite_func* func, const QHash<QString,QVariant>& aggregateContext); + static void releaseAggregateContext(sqlite_func* func); + + sqlite* dbHandle = nullptr; + QString dbErrorMessage; + int dbErrorCode = SQLITE_OK; + QList<FunctionUserData*> userDataList; + QList<Query*> queries; +}; + +//------------------------------------------------------------------------------------ +// AbstractDb2 +//------------------------------------------------------------------------------------ + +template <class T> +AbstractDb2<T>::AbstractDb2(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions) : + AbstractDb(name, path, connOptions) +{ +} + +template <class T> +AbstractDb2<T>::~AbstractDb2() +{ + if (isOpenInternal()) + closeInternal(); +} + +template <class T> +bool AbstractDb2<T>::isOpenInternal() +{ + return dbHandle != nullptr; +} + +template <class T> +SqlQueryPtr AbstractDb2<T>::prepare(const QString& query) +{ + return SqlQueryPtr(new Query(this, query)); +} + +template <class T> +void AbstractDb2<T>::interruptExecution() +{ + if (!isOpenInternal()) + return; + + sqlite_interrupt(dbHandle); +} + +template <class T> +QString AbstractDb2<T>::getErrorTextInternal() +{ + return dbErrorMessage; +} + +template <class T> +int AbstractDb2<T>::getErrorCodeInternal() +{ + return dbErrorCode; +} + +template <class T> +bool AbstractDb2<T>::openInternal() +{ + resetError(); + sqlite* handle = nullptr; + char* errMsg = nullptr; + handle = sqlite_open(path.toUtf8().constData(), 0, &errMsg); + if (!handle) + { + dbErrorCode = SQLITE_ERROR; + + if (errMsg) + { + dbErrorMessage = tr("Could not open database: %1").arg(QString::fromUtf8(errMsg)); + sqlite_freemem(errMsg); + } + return false; + } + dbHandle = handle; + return true; +} + +template <class T> +bool AbstractDb2<T>::closeInternal() +{ + resetError(); + if (!dbHandle) + return false; + + cleanUp(); + + sqlite_close(dbHandle); + dbHandle = nullptr; + return true; +} + +template <class T> +void AbstractDb2<T>::initAfterOpen() +{ +} + +template <class T> +QString AbstractDb2<T>::getTypeLabel() +{ + return T::label; +} + +template <class T> +bool AbstractDb2<T>::deregisterFunction(const QString& name, int argCount) +{ + if (!dbHandle) + return false; + + sqlite_create_function(dbHandle, name.toLatin1().data(), argCount, nullptr, nullptr); + sqlite_create_aggregate(dbHandle, name.toLatin1().data(), argCount, nullptr, nullptr, nullptr); + + FunctionUserData* userData = nullptr; + QMutableListIterator<FunctionUserData*> it(userDataList); + while (it.hasNext()) + { + userData = it.next(); + if (userData->name == name && userData->argCount == argCount) + { + it.remove(); + delete userData; + } + } + + return true; +} + +template <class T> +bool AbstractDb2<T>::registerScalarFunction(const QString& name, int argCount) +{ + if (!dbHandle) + return false; + + FunctionUserData* userData = new FunctionUserData; + userData->db = this; + userData->name = name; + userData->argCount = argCount; + userDataList << userData; + + int res = sqlite_create_function(dbHandle, name.toUtf8().constData(), argCount, + &AbstractDb2<T>::evaluateScalar, userData); + + return res == SQLITE_OK; +} + +template <class T> +bool AbstractDb2<T>::registerAggregateFunction(const QString& name, int argCount) +{ + if (!dbHandle) + return false; + + FunctionUserData* userData = new FunctionUserData; + userData->db = this; + userData->name = name; + userData->argCount = argCount; + userDataList << userData; + + int res = sqlite_create_aggregate(dbHandle, name.toUtf8().constData(), argCount, + &AbstractDb2<T>::evaluateAggregateStep, + &AbstractDb2<T>::evaluateAggregateFinal, + userData); + + return res == SQLITE_OK; +} + +template <class T> +bool AbstractDb2<T>::registerCollationInternal(const QString& name) +{ + // Not supported in SQLite 2 + UNUSED(name); + return false; +} + +template <class T> +bool AbstractDb2<T>::deregisterCollationInternal(const QString& name) +{ + // Not supported in SQLite 2 + UNUSED(name); + return false; +} + +template <class T> +void AbstractDb2<T>::cleanUp() +{ + for (Query* q : queries) + q->finalize(); +} + +template <class T> +void AbstractDb2<T>::resetError() +{ + dbErrorCode = 0; + dbErrorMessage = QString::null; +} + +template <class T> +void AbstractDb2<T>::storeResult(sqlite_func* func, const QVariant& result, bool ok) +{ + if (!ok) + { + QByteArray ba = result.toString().toUtf8(); + sqlite_set_result_error(func, ba.constData(), ba.size()); + return; + } + + // Code below is a modified code from Qt (its SQLite plugin). + if (result.isNull()) + { + sqlite_set_result_string(func, nullptr, -1); + return; + } + + switch (result.type()) + { + case QVariant::ByteArray: + { + QByteArray ba = result.toByteArray(); + sqlite_set_result_string(func, ba.constData(), ba.size()); + break; + } + case QVariant::Int: + case QVariant::Bool: + case QVariant::UInt: + case QVariant::LongLong: + { + sqlite_set_result_int(func, result.toInt()); + break; + } + case QVariant::Double: + { + sqlite_set_result_double(func, result.toDouble()); + break; + } + case QVariant::List: + { + QList<QVariant> list = result.toList(); + QStringList strList; + for (const QVariant& v : list) + strList << v.toString(); + + QByteArray ba = strList.join(" ").toUtf8(); + sqlite_set_result_string(func, ba.constData(), ba.size()); + break; + } + case QVariant::StringList: + { + QByteArray ba = result.toStringList().join(" ").toUtf8(); + sqlite_set_result_string(func, ba.constData(), ba.size()); + break; + } + default: + { + // SQLITE_TRANSIENT makes sure that sqlite buffers the data + QByteArray ba = result.toString().toUtf8(); + sqlite_set_result_string(func, ba.constData(), ba.size()); + break; + } + } +} + +template <class T> +QList<QVariant> AbstractDb2<T>::getArgs(int argCount, const char** args) +{ + QList<QVariant> results; + + for (int i = 0; i < argCount; i++) + { + if (!args[i]) + { + results << QVariant(); + continue; + } + + results << QString::fromUtf8(args[i]); + } + return results; +} + +template <class T> +void AbstractDb2<T>::evaluateScalar(sqlite_func* func, int argCount, const char** args) +{ + QList<QVariant> argList = getArgs(argCount, args); + bool ok = true; + QVariant result = AbstractDb::evaluateScalar(sqlite_user_data(func), argList, ok); + storeResult(func, result, ok); +} + +template <class T> +void AbstractDb2<T>::evaluateAggregateStep(sqlite_func* func, int argCount, const char** args) +{ + void* dataPtr = sqlite_user_data(func); + QList<QVariant> argList = getArgs(argCount, args); + QHash<QString,QVariant> aggregateContext = getAggregateContext(func); + + AbstractDb::evaluateAggregateStep(dataPtr, aggregateContext, argList); + + setAggregateContext(func, aggregateContext); +} + +template <class T> +void AbstractDb2<T>::evaluateAggregateFinal(sqlite_func* func) +{ + void* dataPtr = sqlite_user_data(func); + QHash<QString,QVariant> aggregateContext = getAggregateContext(func); + + bool ok = true; + QVariant result = AbstractDb::evaluateAggregateFinal(dataPtr, aggregateContext, ok); + + storeResult(func, result, ok); + releaseAggregateContext(func); +} + +template <class T> +void*AbstractDb2<T>::getContextMemPtr(sqlite_func* func) +{ + return sqlite_aggregate_context(func, sizeof(QHash<QString,QVariant>**)); +} + +template <class T> +QHash<QString, QVariant> AbstractDb2<T>::getAggregateContext(sqlite_func* func) +{ + return AbstractDb::getAggregateContext(getContextMemPtr(func)); +} + +template <class T> +void AbstractDb2<T>::setAggregateContext(sqlite_func* func, const QHash<QString, QVariant>& aggregateContext) +{ + AbstractDb::setAggregateContext(getContextMemPtr(func), aggregateContext); +} + +template <class T> +void AbstractDb2<T>::releaseAggregateContext(sqlite_func* func) +{ + AbstractDb::releaseAggregateContext(getContextMemPtr(func)); +} + +//------------------------------------------------------------------------------------ +// Query +//------------------------------------------------------------------------------------ + +template <class T> +AbstractDb2<T>::Query::Query(AbstractDb2<T>* db, const QString& query) : + db(db) +{ + this->query = query; + db->queries << this; +} + +template <class T> +AbstractDb2<T>::Query::~Query() +{ + if (db.isNull()) + return; + + finalize(); + db->queries.removeOne(this); +} + +template <class T> +void AbstractDb2<T>::Query::copyErrorFromDb() +{ + if (db->dbErrorCode != 0) + { + errorCode = db->dbErrorCode; + errorMessage = db->dbErrorMessage; + return; + } +} + +template <class T> +void AbstractDb2<T>::Query::copyErrorToDb() +{ + db->dbErrorCode = errorCode; + db->dbErrorMessage = errorMessage; +} + +template <class T> +void AbstractDb2<T>::Query::setError(int code, const QString& msg) +{ + if (errorCode != SQLITE_OK) + return; // don't overwrite first error + + errorCode = code; + errorMessage = msg; + copyErrorToDb(); +} + +template <class T> +int AbstractDb2<T>::Query::prepareStmt(const QString& processedQuery) +{ + char* errMsg = nullptr; + const char* tail; + QByteArray queryBytes = processedQuery.toUtf8(); + int res = sqlite_compile(db->dbHandle, queryBytes.constData(), &tail, &stmt, &errMsg); + if (res != SQLITE_OK) + { + finalize(); + if (errMsg) + { + setError(res, QString::fromUtf8((errMsg))); + sqlite_freemem(errMsg); + } + return res; + } + + if (tail && !QString::fromUtf8(tail).trimmed().isEmpty()) + qWarning() << "Executed query left with tailing contents:" << tail << ", while executing query:" << query; + + return SQLITE_OK; +} + +template <class T> +int AbstractDb2<T>::Query::resetStmt() +{ + errorCode = 0; + errorMessage = QString::null; + affected = 0; + colCount = -1; + rowAvailable = false; + nextRowValues.clear(); + + char* errMsg = nullptr; + int res = sqlite_reset(stmt, &errMsg); + if (res != SQLITE_OK) + { + stmt = nullptr; + if (errMsg) + { + setError(res, QString::fromUtf8((errMsg))); + sqlite_freemem(errMsg); + } + return res; + } + return SQLITE_OK; +} + +template <class T> +bool AbstractDb2<T>::Query::execInternal(const QList<QVariant>& args) +{ + if (!checkDbState()) + return false; + + ReadWriteLocker locker(&(db->dbOperLock), query, Dialect::Sqlite2, flags.testFlag(Db::Flag::NO_LOCK)); + + QueryWithParamCount queryWithParams = getQueryWithParamCount(query, Dialect::Sqlite2); + QString singleStr = replaceNamedParams(queryWithParams.first); + + int res; + if (stmt) + res = resetStmt(); + else + res = prepareStmt(singleStr); + + if (res != SQLITE_OK) + return false; + + for (int paramIdx = 1; paramIdx <= queryWithParams.second; paramIdx++) + { + res = bindParam(paramIdx, args[paramIdx-1]); + if (res != SQLITE_OK) + return false; + } + + bool ok = (fetchFirst() == SQLITE_OK); + if (ok) + db->checkForDroppedObject(query); + + return ok; +} + +template <class T> +bool AbstractDb2<T>::Query::execInternal(const QHash<QString, QVariant>& args) +{ + if (!checkDbState()) + return false; + + ReadWriteLocker locker(&(db->dbOperLock), query, Dialect::Sqlite2, flags.testFlag(Db::Flag::NO_LOCK)); + + QueryWithParamNames queryWithParams = getQueryWithParamNames(query, Dialect::Sqlite2); + QString singleStr = replaceNamedParams(queryWithParams.first); + + int res; + if (stmt) + res = resetStmt(); + else + res = prepareStmt(singleStr); + + if (res != SQLITE_OK) + return false; + + int paramIdx = 1; + foreach (const QString& paramName, queryWithParams.second) + { + if (!args.contains(paramName)) + { + setError(SqlErrorCode::OTHER_EXECUTION_ERROR, "Error while preparing statement: could not bind parameter " + paramName); + return false; + } + + res = bindParam(paramIdx++, args[paramName]); + if (res != SQLITE_OK) + return false; + } + + bool ok = (fetchFirst() == SQLITE_OK); + if (ok) + db->checkForDroppedObject(query); + + return ok; +} + +template <class T> +QString AbstractDb2<T>::Query::replaceNamedParams(const QString& query) +{ + TokenList tokens = Lexer::tokenize(query, Dialect::Sqlite2); + for (TokenPtr token : tokens) + { + if (token->type == Token::BIND_PARAM) + token->value = "?"; + } + return tokens.detokenize(); +} + +template <class T> +int AbstractDb2<T>::Query::bindParam(int paramIdx, const QVariant& value) +{ + if (value.isNull()) + { + return sqlite_bind(stmt, paramIdx, nullptr, 0, 0); + } + + switch (value.type()) + { + case QVariant::ByteArray: + { + // NOTE: SQLite 2 has a bug that makes it impossible to write BLOB with nulls inside. First occurrance of the null + // makes the whole value to be saved as truncated to that position. Nothing I can do about it. + QByteArray ba = value.toByteArray(); + return sqlite_bind(stmt, paramIdx, ba.constData(), ba.size(), true); + } + default: + { + QByteArray ba = value.toString().toUtf8(); + ba.append('\0'); + return sqlite_bind(stmt, paramIdx, ba.constData(), ba.size(), true); + } + } + + return SQLITE_MISUSE; // not going to happen +} +template <class T> +QString AbstractDb2<T>::Query::getErrorText() +{ + return errorMessage; +} + +template <class T> +int AbstractDb2<T>::Query::getErrorCode() +{ + return errorCode; +} + +template <class T> +QStringList AbstractDb2<T>::Query::getColumnNames() +{ + return colNames; +} + +template <class T> +int AbstractDb2<T>::Query::columnCount() +{ + return colCount; +} + +template <class T> +qint64 AbstractDb2<T>::Query::rowsAffected() +{ + return affected; +} + +template <class T> +SqlResultsRowPtr AbstractDb2<T>::Query::nextInternal() +{ + if (!rowAvailable || db.isNull()) + return SqlResultsRowPtr(); + + Row* row = new Row; + row->init(colNames, nextRowValues); + + int res = fetchNext(); + if (res != SQLITE_OK) + { + delete row; + return SqlResultsRowPtr(); + } + return SqlResultsRowPtr(row); +} + +template <class T> +bool AbstractDb2<T>::Query::hasNextInternal() +{ + return rowAvailable && stmt; +} + +template <class T> +int AbstractDb2<T>::Query::fetchFirst() +{ + rowAvailable = true; + int res = fetchNext(); + if (res == SQLITE_OK) + { + if (colCount == 0) + { + affected = 0; + } + else + { + affected = sqlite_changes(db->dbHandle); + insertRowId["ROWID"] = sqlite_last_insert_rowid(db->dbHandle); + } + } + return res; +} + +template <class T> +bool AbstractDb2<T>::Query::checkDbState() +{ + if (db.isNull() || !db->dbHandle) + { + setError(SqlErrorCode::DB_NOT_DEFINED, "SqlQuery is no longer valid."); + return false; + } + + return true; +} + +template <class T> +QString AbstractDb2<T>::Query::finalize() +{ + QString msg; + if (stmt) + { + char* errMsg = nullptr; + sqlite_finalize(stmt, &errMsg); + stmt = nullptr; + if (errMsg) + { + msg = QString::fromUtf8(errMsg); + sqlite_freemem(errMsg); + } + } + return msg; +} + +template <class T> +int AbstractDb2<T>::Query::fetchNext() +{ + if (!checkDbState()) + rowAvailable = false; + + if (!rowAvailable || !stmt) + { + setError(SQLITE_MISUSE, tr("Result set expired or no row available.")); + return SQLITE_MISUSE; + } + + rowAvailable = false; + + const char** values; + const char** columns; + int columnsCount; + + int res; + int secondsSpent = 0; + while ((res = sqlite_step(stmt, &columnsCount, &values, &columns)) == SQLITE_BUSY && secondsSpent < db->getTimeout()) + { + QThread::sleep(1); + if (db->getTimeout() >= 0) + secondsSpent++; + } + + switch (res) + { + case SQLITE_ROW: + rowAvailable = true; + break; + case SQLITE_DONE: + // Empty pointer as no more results are available. + break; + default: + setError(res, finalize()); + return SQLITE_ERROR; + } + + // First row, initialize members + if (colCount == -1) + init(columnsCount, columns); + + // Then read the next row data + nextRowValues.clear(); + if (rowAvailable) + { + for (int i = 0; i < colCount; i++) + { + if (isBinaryColumn(i)) + nextRowValues << QByteArray(values[i]); + else + nextRowValues << QString::fromUtf8(values[i]); + } + } + + return SQLITE_OK; +} + +template <class T> +void AbstractDb2<T>::Query::init(int columnsCount, const char** columns) +{ + colCount = columnsCount; + + TokenList columnDescription; + for (int i = 0; i < colCount; i++) + { + columnDescription = Lexer::tokenize(QString::fromUtf8(columns[i]), Dialect::Sqlite2).filterWhiteSpaces(); + if (columnDescription.size() > 0) + { + // If the column is prefixed with dbname and table name, then we remove them. + for (int j = 0; j < 2 &&columnDescription.size() > 1 && columnDescription[1]->type == Token::OPERATOR && columnDescription[1]->value == "."; j++) + { + columnDescription.removeFirst(); + columnDescription.removeFirst(); + } + + colNames << stripObjName(columnDescription.first()->value, Dialect::Sqlite2); + } + else + colNames << ""; + } +} + +//------------------------------------------------------------------------------------ +// Row +//------------------------------------------------------------------------------------ + +template <class T> +void AbstractDb2<T>::Query::Row::init(const QStringList& columns, const QList<QVariant>& resultValues) +{ + for (int i = 0; i < columns.size(); i++) + { + values << resultValues[i]; + valuesMap[columns[i]] = resultValues[i]; + } +} + +#endif // ABSTRACTDB2_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h new file mode 100644 index 0000000..d8e54b4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/abstractdb3.h @@ -0,0 +1,1157 @@ +#ifndef ABSTRACTDB3_H +#define ABSTRACTDB3_H + +#include "db/abstractdb.h" +#include "parser/lexer.h" +#include "common/utils_sql.h" +#include "common/unused.h" +#include "services/collationmanager.h" +#include "sqlitestudio.h" +#include "db/sqlerrorcodes.h" +#include <QThread> +#include <QPointer> +#include <QDebug> + +/** + * @brief Complete implementation of SQLite 3 driver for SQLiteStudio. + * + * Inherit this when implementing Db for SQLite 3. In most cases you will only need + * to create one public constructor, which forwards parameters to the AbstractDb constructor. + * This be sufficient to implement SQLite 3 database plugin. + * Just link it with proper SQLite library. + * + * The template parameter should provide all necessary SQLite symbols used by this implementation. + * This way every Db plugin can provide it's own symbols to work on SQLite and so it allows + * for loading multiple SQLite libraries into the same application, while symbols in each library + * can be different (and should be different, to avoid name conflicts and symbol overlapping). + * See how it's done in dbsqlite3.h. + * + * @see DbQt + */ +template <class T> +class AbstractDb3 : public AbstractDb +{ + public: + /** + * @brief Creates SQLite database object. + * @param name Name for the database. + * @param path File path of the database. + * @param connOptions Connection options. See AbstractDb for details. + * + * All values from this constructor are just passed to AbstractDb constructor. + */ + AbstractDb3(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions); + ~AbstractDb3(); + + protected: + bool isOpenInternal(); + void interruptExecution(); + QString getErrorTextInternal(); + int getErrorCodeInternal(); + bool openInternal(); + bool closeInternal(); + void initAfterOpen(); + SqlQueryPtr prepare(const QString& query); + QString getTypeLabel(); + bool deregisterFunction(const QString& name, int argCount); + bool registerScalarFunction(const QString& name, int argCount); + bool registerAggregateFunction(const QString& name, int argCount); + bool registerCollationInternal(const QString& name); + bool deregisterCollationInternal(const QString& name); + + private: + class Query : public SqlQuery + { + public: + class Row : public SqlResultsRow + { + public: + int init(const QStringList& columns, typename T::stmt* stmt); + + private: + int getValue(typename T::stmt* stmt, int col, QVariant& value); + }; + + Query(AbstractDb3<T>* db, const QString& query); + ~Query(); + + QString getErrorText(); + int getErrorCode(); + QStringList getColumnNames(); + int columnCount(); + qint64 rowsAffected(); + void finalize(); + + protected: + SqlResultsRowPtr nextInternal(); + bool hasNextInternal(); + bool execInternal(const QList<QVariant>& args); + bool execInternal(const QHash<QString, QVariant>& args); + + private: + int prepareStmt(); + int resetStmt(); + int bindParam(int paramIdx, const QVariant& value); + int fetchFirst(); + int fetchNext(); + bool checkDbState(); + void copyErrorFromDb(); + void copyErrorToDb(); + void setError(int code, const QString& msg); + + QPointer<AbstractDb3<T>> db; + typename T::stmt* stmt = nullptr; + int errorCode = T::OK; + QString errorMessage; + int colCount = 0; + QStringList colNames; + int affected = 0; + bool rowAvailable = false; + }; + + struct CollationUserData + { + QString name; + AbstractDb3<T>* db = nullptr; + }; + + QString extractLastError(); + void cleanUp(); + void resetError(); + + /** + * @brief Registers function to call when unknown collation was encountered by the SQLite. + * + * For unknown collations SQLite calls function registered by this method and expects it to register + * proper function handling that collation, otherwise the query will result with error. + * + * The default collation handler does a simple QString::compare(), case insensitive. + */ + void registerDefaultCollationRequestHandler(); + + /** + * @brief Stores given result in function's context. + * @param context Custom SQL function call context. + * @param result Value returned from function execution. + * @param ok true if the result is from a successful execution, or false if the result contains error message (QString). + * + * This method is called after custom implementation of the function was evaluated and it returned the result. + * It stores the result in function's context, so it becomes the result of the function call. + */ + static void storeResult(typename T::context* context, const QVariant& result, bool ok); + + /** + * @brief Converts SQLite arguments into the list of argument values. + * @param argCount Number of arguments. + * @param args SQLite argument values. + * @return Convenient Qt list with argument values as QVariant. + * + * This function does necessary conversions reflecting internal SQLite datatype, so if the type + * was for example BLOB, then the QVariant will be a QByteArray, etc. + */ + static QList<QVariant> getArgs(int argCount, typename T::value** args); + + /** + * @brief Evaluates requested function using defined implementation code and provides result. + * @param context SQL function call context. + * @param argCount Number of arguments passed to the function. + * @param args Arguments passed to the function. + * + * This method is aware of the implementation language and the code defined for it, + * so it delegates the execution to the proper plugin handling that language. Then it stores + * result returned from the plugin in function's context, so it becomes function's result. + * + * This method is called for scalar functions. + * + * @see DbQt::evaluateScalar() + */ + static void evaluateScalar(typename T::context* context, int argCount, typename T::value** args); + + /** + * @brief Evaluates requested function using defined implementation code and provides result. + * @param context SQL function call context. + * @param argCount Number of arguments passed to the function. + * @param args Arguments passed to the function. + * + * This method is called for aggregate functions. + * + * If this is the first call to this function using this context, then it will execute + * both "initial" and then "per step" code for this function implementation. + * + * @see DbQt3::evaluateScalar() + * @see DbQt::evaluateAggregateStep() + */ + static void evaluateAggregateStep(typename T::context* context, int argCount, typename T::value** args); + + /** + * @brief Evaluates "final" code for aggregate function. + * @param context SQL function call context. + * + * This method is called for aggregate functions. + * + * It's called once, at the end of aggregate function evaluation. + * It executes "final" code of the function implementation. + */ + static void evaluateAggregateFinal(typename T::context* context); + + /** + * @brief Evaluates code of the collation. + * @param userData Collation user data (name of the collation inside). + * @param length1 Number of characters in value1 (excluding \0). + * @param value1 First value to compare. + * @param length2 Number of characters in value2 (excluding \0). + * @param value2 Second value to compare. + * @return -1, 0, or 1, as SQLite's collation specification demands it. + */ + static int evaluateCollation(void* userData, int length1, const void* value1, int length2, const void* value2); + + /** + * @brief Cleans up collation user data when collation is deregistered. + * @param userData User data to delete. + */ + static void deleteCollationUserData(void* userData); + + /** + * @brief Destructor for function user data object. + * @param dataPtr Pointer to the user data object. + * + * This is called by SQLite when the function is deregistered. + */ + static void deleteUserData(void* dataPtr); + + /** + * @brief Allocates and/or returns shared memory for the aggregate SQL function call. + * @param context SQL function call context. + * @return Pointer to the memory. + * + * It allocates exactly the number of bytes required to store pointer to a QHash. + * The memory is released after the aggregate function is finished. + */ + static void* getContextMemPtr(typename T::context* context); + + /** + * @brief Allocates and/or returns QHash shared across all aggregate function steps. + * @param context SQL function call context. + * @return Shared hash table. + * + * The hash table is created before initial aggregate function step is made. + * Then it's shared across all further steps (using this method to get it) + * and then releases the memory after the last (final) step of the function call. + */ + static QHash<QString,QVariant> getAggregateContext(typename T::context* context); + + /** + * @brief Sets new value of the aggregate function shared hash table. + * @param context SQL function call context. + * @param aggregateContext New shared hash table value to store. + * + * This should be called after each time the context was requested with getAggregateContext() and then modified. + */ + static void setAggregateContext(typename T::context* context, const QHash<QString,QVariant>& aggregateContext); + + /** + * @brief Releases aggregate function shared hash table. + * @param context SQL function call context. + * + * This should be called from final aggregate function step to release the shared context (delete QHash). + * The memory used to store pointer to the shared context will be released by the SQLite itself. + */ + static void releaseAggregateContext(typename T::context* context); + + /** + * @brief Registers default collation for requested collation. + * @param fnUserData User data passed when registering collation request handling function. + * @param fnDbHandle Database handle for which this call is being made. + * @param eTextRep Text encoding (for now always T::UTF8). + * @param collationName Name of requested collation. + * + * This function is called by SQLite to order registering collation with given name. We register default collation, + * cause all known collations should already be registered. + * + * Default collation is implemented by evaluateDefaultCollation(). + */ + static void registerDefaultCollation(void* fnUserData, typename T::handle* fnDbHandle, int eTextRep, const char* collationName); + + /** + * @brief Called as a default collation implementation. + * @param userData Collation user data, not used. + * @param length1 Number of characters in value1 (excluding \0). + * @param value1 First value to compare. + * @param length2 Number of characters in value2 (excluding \0). + * @param value2 Second value to compare. + * @return -1, 0, or 1, as SQLite's collation specification demands it. + */ + static int evaluateDefaultCollation(void* userData, int length1, const void* value1, int length2, const void* value2); + + typename T::handle* dbHandle = nullptr; + QString dbErrorMessage; + int dbErrorCode = T::OK; + QList<Query*> queries; + + /** + * @brief User data for default collation request handling function. + * + * That function doesn't have destructor function pointer, so we need to keep track of that user data + * and delete it when database is closed. + */ + CollationUserData* defaultCollationUserData = nullptr; +}; + +//------------------------------------------------------------------------------------ +// AbstractDb3 +//------------------------------------------------------------------------------------ + +template <class T> +AbstractDb3<T>::AbstractDb3(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions) : + AbstractDb(name, path, connOptions) +{ +} + +template <class T> +AbstractDb3<T>::~AbstractDb3() +{ + if (isOpenInternal()) + closeInternal(); +} + +template <class T> +bool AbstractDb3<T>::isOpenInternal() +{ + return dbHandle != nullptr; +} + +template <class T> +void AbstractDb3<T>::interruptExecution() +{ + if (!isOpenInternal()) + return; + + T::interrupt(dbHandle); +} + +template <class T> +QString AbstractDb3<T>::getErrorTextInternal() +{ + return dbErrorMessage; +} + +template <class T> +int AbstractDb3<T>::getErrorCodeInternal() +{ + return dbErrorCode; +} + +template <class T> +bool AbstractDb3<T>::openInternal() +{ + resetError(); + typename T::handle* handle = nullptr; + int res = T::open_v2(path.toUtf8().constData(), &handle, T::OPEN_READWRITE|T::OPEN_CREATE, nullptr); + if (res != T::OK) + { + if (handle) + T::close(handle); + + dbErrorMessage = tr("Could not open database: %1").arg(extractLastError()); + dbErrorCode = res; + return false; + } + dbHandle = handle; + return true; +} + +template <class T> +bool AbstractDb3<T>::closeInternal() +{ + resetError(); + if (!dbHandle) + return false; + + cleanUp(); + + int res = T::close(dbHandle); + if (res != T::OK) + { + dbErrorMessage = tr("Could not close database: %1").arg(extractLastError()); + dbErrorCode = res; + qWarning() << "Error closing database. That's weird:" << dbErrorMessage; + return false; + } + dbHandle = nullptr; + return true; +} + +template <class T> +void AbstractDb3<T>::initAfterOpen() +{ + T::enable_load_extension(dbHandle, true); + registerDefaultCollationRequestHandler();; + exec("PRAGMA foreign_keys = 1;", Flag::NO_LOCK); + exec("PRAGMA recursive_triggers = 1;", Flag::NO_LOCK); +} + +template <class T> +SqlQueryPtr AbstractDb3<T>::prepare(const QString& query) +{ + return SqlQueryPtr(new Query(this, query)); +} + +template <class T> +QString AbstractDb3<T>::getTypeLabel() +{ + return T::label; +} + +template <class T> +bool AbstractDb3<T>::deregisterFunction(const QString& name, int argCount) +{ + if (!dbHandle) + return false; + + T::create_function(dbHandle, name.toUtf8().constData(), argCount, T::UTF8, 0, nullptr, nullptr, nullptr); + return true; +} + +template <class T> +bool AbstractDb3<T>::registerScalarFunction(const QString& name, int argCount) +{ + if (!dbHandle) + return false; + + FunctionUserData* userData = new FunctionUserData; + userData->db = this; + userData->name = name; + userData->argCount = argCount; + + int res = T::create_function_v2(dbHandle, name.toUtf8().constData(), argCount, T::UTF8, userData, + &AbstractDb3<T>::evaluateScalar, + nullptr, + nullptr, + &AbstractDb3<T>::deleteUserData); + + return res == T::OK; +} + +template <class T> +bool AbstractDb3<T>::registerAggregateFunction(const QString& name, int argCount) +{ + if (!dbHandle) + return false; + + FunctionUserData* userData = new FunctionUserData; + userData->db = this; + userData->name = name; + userData->argCount = argCount; + + int res = T::create_function_v2(dbHandle, name.toUtf8().constData(), argCount, T::UTF8, userData, + nullptr, + &AbstractDb3<T>::evaluateAggregateStep, + &AbstractDb3<T>::evaluateAggregateFinal, + &AbstractDb3<T>::deleteUserData); + + return res == T::OK; +} + +template <class T> +bool AbstractDb3<T>::registerCollationInternal(const QString& name) +{ + if (!dbHandle) + return false; + + CollationUserData* userData = new CollationUserData; + userData->name = name; + + int res = T::create_collation_v2(dbHandle, name.toUtf8().constData(), T::UTF8, userData, + &AbstractDb3<T>::evaluateCollation, + &AbstractDb3<T>::deleteCollationUserData); + return res == T::OK; +} + +template <class T> +bool AbstractDb3<T>::deregisterCollationInternal(const QString& name) +{ + if (!dbHandle) + return false; + + T::create_collation_v2(dbHandle, name.toUtf8().constData(), T::UTF8, nullptr, nullptr, nullptr); + return true; +} + +template <class T> +QString AbstractDb3<T>::extractLastError() +{ + dbErrorCode = T::extended_errcode(dbHandle); + dbErrorMessage = QString::fromUtf8(T::errmsg(dbHandle)); + return dbErrorMessage; +} + +template <class T> +void AbstractDb3<T>::cleanUp() +{ + for (Query* q : queries) + q->finalize(); + + safe_delete(defaultCollationUserData); +} + +template <class T> +void AbstractDb3<T>::resetError() +{ + dbErrorCode = 0; + dbErrorMessage = QString::null; +} + +template <class T> +void AbstractDb3<T>::storeResult(typename T::context* context, const QVariant& result, bool ok) +{ + if (!ok) + { + QString str = result.toString(); + T::result_error16(context, str.utf16(), str.size() * sizeof(QChar)); + return; + } + + // Code below is a modified code from Qt (its SQLite plugin). + if (result.isNull()) + { + T::result_null(context); + return; + } + + switch (result.type()) + { + case QVariant::ByteArray: + { + QByteArray ba = result.toByteArray(); + T::result_blob(context, ba.constData(), ba.size(), T::TRANSIENT()); + break; + } + case QVariant::Int: + case QVariant::Bool: + { + T::result_int(context, result.toInt()); + break; + } + case QVariant::Double: + { + T::result_double(context, result.toDouble()); + break; + } + case QVariant::UInt: + case QVariant::LongLong: + { + T::result_int64(context, result.toLongLong()); + break; + } + case QVariant::List: + { + QList<QVariant> list = result.toList(); + QStringList strList; + for (const QVariant& v : list) + strList << v.toString(); + + QString str = strList.join(" "); + T::result_text16(context, str.utf16(), str.size() * sizeof(QChar), T::TRANSIENT()); + break; + } + case QVariant::StringList: + { + QString str = result.toStringList().join(" "); + T::result_text16(context, str.utf16(), str.size() * sizeof(QChar), T::TRANSIENT()); + break; + } + default: + { + // T::TRANSIENT makes sure that sqlite buffers the data + QString str = result.toString(); + T::result_text16(context, str.utf16(), str.size() * sizeof(QChar), T::TRANSIENT()); + break; + } + } +} + +template <class T> +QList<QVariant> AbstractDb3<T>::getArgs(int argCount, typename T::value** args) +{ + int dataType; + QList<QVariant> results; + QVariant value; + + // The loop below uses slightly modified code from Qt (its SQLite plugin) to extract values. + for (int i = 0; i < argCount; i++) + { + dataType = T::value_type(args[i]); + switch (dataType) + { + case T::INTEGER: + value = T::value_int64(args[i]); + break; + case T::BLOB: + value = QByteArray( + static_cast<const char*>(T::value_blob(args[i])), + T::value_bytes(args[i]) + ); + break; + case T::FLOAT: + value = T::value_double(args[i]); + break; + case T::NULL_TYPE: + value = QVariant(QVariant::String); + break; + default: + value = QString( + reinterpret_cast<const QChar*>(T::value_text16(args[i])), + T::value_bytes16(args[i]) / sizeof(QChar) + ); + break; + } + results << value; + } + return results; +} + +template <class T> +void AbstractDb3<T>::evaluateScalar(typename T::context* context, int argCount, typename T::value** args) +{ + QList<QVariant> argList = getArgs(argCount, args); + bool ok = true; + QVariant result = AbstractDb::evaluateScalar(T::user_data(context), argList, ok); + storeResult(context, result, ok); +} + +template <class T> +void AbstractDb3<T>::evaluateAggregateStep(typename T::context* context, int argCount, typename T::value** args) +{ + void* dataPtr = T::user_data(context); + QList<QVariant> argList = getArgs(argCount, args); + QHash<QString,QVariant> aggregateContext = getAggregateContext(context); + + AbstractDb::evaluateAggregateStep(dataPtr, aggregateContext, argList); + + setAggregateContext(context, aggregateContext); +} + +template <class T> +void AbstractDb3<T>::evaluateAggregateFinal(typename T::context* context) +{ + void* dataPtr = T::user_data(context); + QHash<QString,QVariant> aggregateContext = getAggregateContext(context); + + bool ok = true; + QVariant result = AbstractDb::evaluateAggregateFinal(dataPtr, aggregateContext, ok); + + storeResult(context, result, ok); + releaseAggregateContext(context); +} + +template <class T> +int AbstractDb3<T>::evaluateCollation(void* userData, int length1, const void* value1, int length2, const void* value2) +{ + UNUSED(length1); + UNUSED(length2); + CollationUserData* collUserData = reinterpret_cast<CollationUserData*>(userData); + return COLLATIONS->evaluate(collUserData->name, QString::fromUtf8((const char*)value1), QString::fromUtf8((const char*)value2)); +} + +template <class T> +void AbstractDb3<T>::deleteCollationUserData(void* userData) +{ + if (!userData) + return; + + CollationUserData* collUserData = reinterpret_cast<CollationUserData*>(userData); + delete collUserData; +} + +template <class T> +void AbstractDb3<T>::deleteUserData(void* dataPtr) +{ + if (!dataPtr) + return; + + FunctionUserData* userData = reinterpret_cast<FunctionUserData*>(dataPtr); + delete userData; +} + +template <class T> +void* AbstractDb3<T>::getContextMemPtr(typename T::context* context) +{ + return T::aggregate_context(context, sizeof(QHash<QString,QVariant>**)); +} + +template <class T> +QHash<QString, QVariant> AbstractDb3<T>::getAggregateContext(typename T::context* context) +{ + return AbstractDb::getAggregateContext(getContextMemPtr(context)); +} + +template <class T> +void AbstractDb3<T>::setAggregateContext(typename T::context* context, const QHash<QString, QVariant>& aggregateContext) +{ + AbstractDb::setAggregateContext(getContextMemPtr(context), aggregateContext); +} + +template <class T> +void AbstractDb3<T>::releaseAggregateContext(typename T::context* context) +{ + AbstractDb::releaseAggregateContext(getContextMemPtr(context)); +} + +template <class T> +void AbstractDb3<T>::registerDefaultCollation(void* fnUserData, typename T::handle* fnDbHandle, int eTextRep, const char* collationName) +{ + CollationUserData* defUserData = reinterpret_cast<CollationUserData*>(fnUserData); + if (!defUserData) + { + qWarning() << "Null userData in AbstractDb3<T>::registerDefaultCollation()."; + return; + } + + AbstractDb3<T>* db = defUserData->db; + if (!db) + { + qWarning() << "No database defined in userData of AbstractDb3<T>::registerDefaultCollation()."; + return; + } + + // If SQLite seeks for collation implementation with different encoding, we force it to use existing one. + if (db->isCollationRegistered(QString::fromUtf8(collationName))) + return; + + // Check if dbHandle matches - just in case + if (db->dbHandle != fnDbHandle) + { + qWarning() << "Mismatch of dbHandle in AbstractDb3<T>::registerDefaultCollation()."; + return; + } + + int res = T::create_collation_v2(fnDbHandle, collationName, eTextRep, nullptr, + &AbstractDb3<T>::evaluateDefaultCollation, nullptr); + + if (res != T::OK) + qWarning() << "Could not register default collation in AbstractDb3<T>::registerDefaultCollation()."; +} + +template <class T> +int AbstractDb3<T>::evaluateDefaultCollation(void* userData, int length1, const void* value1, int length2, const void* value2) +{ + UNUSED(userData); + UNUSED(length1); + UNUSED(length2); + return COLLATIONS->evaluateDefault(QString::fromUtf8((const char*)value1), QString::fromUtf8((const char*)value2)); +} + +template <class T> +void AbstractDb3<T>::registerDefaultCollationRequestHandler() +{ + if (!dbHandle) + return; + + defaultCollationUserData = new CollationUserData; + defaultCollationUserData->db = this; + + int res = T::collation_needed(dbHandle, defaultCollationUserData, &AbstractDb3<T>::registerDefaultCollation); + if (res != T::OK) + qWarning() << "Could not register default collation request handler. Unknown collations will cause errors."; +} + +//------------------------------------------------------------------------------------ +// Results +//------------------------------------------------------------------------------------ + +template <class T> +AbstractDb3<T>::Query::Query(AbstractDb3<T>* db, const QString& query) : + db(db) +{ + this->query = query; + db->queries << this; +} + +template <class T> +AbstractDb3<T>::Query::~Query() +{ + if (db.isNull()) + return; + + finalize(); + db->queries.removeOne(this); +} + +template <class T> +void AbstractDb3<T>::Query::copyErrorFromDb() +{ + if (db->dbErrorCode != 0) + { + errorCode = db->dbErrorCode; + errorMessage = db->dbErrorMessage; + return; + } +} + +template <class T> +void AbstractDb3<T>::Query::copyErrorToDb() +{ + db->dbErrorCode = errorCode; + db->dbErrorMessage = errorMessage; +} + +template <class T> +void AbstractDb3<T>::Query::setError(int code, const QString& msg) +{ + if (errorCode != T::OK) + return; // don't overwrite first error + + errorCode = code; + errorMessage = msg; + copyErrorToDb(); +} + +template <class T> +int AbstractDb3<T>::Query::prepareStmt() +{ + const char* tail; + QByteArray queryBytes = query.toUtf8(); + int res = T::prepare_v2(db->dbHandle, queryBytes.constData(), queryBytes.size(), &stmt, &tail); + if (res != T::OK) + { + stmt = nullptr; + db->extractLastError(); + copyErrorFromDb(); + return res; + } + + if (tail && !QString::fromUtf8(tail).trimmed().isEmpty()) + qWarning() << "Executed query left with tailing contents:" << tail << ", while executing query:" << query; + + return T::OK; +} + +template <class T> +int AbstractDb3<T>::Query::resetStmt() +{ + errorCode = 0; + errorMessage = QString::null; + affected = 0; + colCount = -1; + rowAvailable = false; + + int res = T::reset(stmt); + if (res != T::OK) + { + stmt = nullptr; + setError(res, QString::fromUtf8(T::errmsg(db->dbHandle))); + return res; + } + return T::OK; +} + +template <class T> +bool AbstractDb3<T>::Query::execInternal(const QList<QVariant>& args) +{ + if (!checkDbState()) + return false; + + ReadWriteLocker locker(&(db->dbOperLock), query, Dialect::Sqlite3, flags.testFlag(Db::Flag::NO_LOCK)); + QueryWithParamCount queryWithParams = getQueryWithParamCount(query, Dialect::Sqlite3); + + int res; + if (stmt) + res = resetStmt(); + else + res = prepareStmt(); + + if (res != T::OK) + return false; + + for (int paramIdx = 1; paramIdx <= queryWithParams.second; paramIdx++) + { + res = bindParam(paramIdx, args[paramIdx-1]); + if (res != T::OK) + { + db->extractLastError(); + copyErrorFromDb(); + return false; + } + } + + bool ok = (fetchFirst() == T::OK); + if (ok) + db->checkForDroppedObject(query); + + return ok; +} + +template <class T> +bool AbstractDb3<T>::Query::execInternal(const QHash<QString, QVariant>& args) +{ + if (!checkDbState()) + return false; + + ReadWriteLocker locker(&(db->dbOperLock), query, Dialect::Sqlite3, flags.testFlag(Db::Flag::NO_LOCK)); + + QueryWithParamNames queryWithParams = getQueryWithParamNames(query, Dialect::Sqlite3); + + int res; + if (stmt) + res = resetStmt(); + else + res = prepareStmt(); + + if (res != T::OK) + return false; + + int paramIdx = 1; + foreach (const QString& paramName, queryWithParams.second) + { + if (!args.contains(paramName)) + { + setError(SqlErrorCode::OTHER_EXECUTION_ERROR, "Error while preparing statement: could not bind parameter " + paramName); + return false; + } + + res = bindParam(paramIdx++, args[paramName]); + if (res != T::OK) + { + db->extractLastError(); + copyErrorFromDb(); + return false; + } + } + + bool ok = (fetchFirst() == T::OK); + if (ok) + db->checkForDroppedObject(query); + + return ok; +} + +template <class T> +int AbstractDb3<T>::Query::bindParam(int paramIdx, const QVariant& value) +{ + if (value.isNull()) + { + return T::bind_null(stmt, paramIdx); + } + + switch (value.type()) + { + case QVariant::ByteArray: + { + QByteArray ba = value.toByteArray(); + return T::bind_blob(stmt, paramIdx, ba.constData(), ba.size(), T::TRANSIENT()); + } + case QVariant::Int: + case QVariant::Bool: + { + return T::bind_int(stmt, paramIdx, value.toInt()); + } + case QVariant::Double: + { + return T::bind_double(stmt, paramIdx, value.toDouble()); + } + case QVariant::UInt: + case QVariant::LongLong: + { + return T::bind_int64(stmt, paramIdx, value.toLongLong()); + } + default: + { + // T::TRANSIENT makes sure that sqlite buffers the data + QString str = value.toString(); + return T::bind_text16(stmt, paramIdx, str.utf16(), str.size() * sizeof(QChar), T::TRANSIENT()); + } + } + + return T::MISUSE; // not going to happen +} + +template <class T> +bool AbstractDb3<T>::Query::checkDbState() +{ + if (db.isNull() || !db->dbHandle) + { + setError(SqlErrorCode::DB_NOT_DEFINED, "SqlQuery is no longer valid."); + return false; + } + + return true; +} + +template <class T> +void AbstractDb3<T>::Query::finalize() +{ + if (stmt) + { + T::finalize(stmt); + stmt = nullptr; + } +} + +template <class T> +QString AbstractDb3<T>::Query::getErrorText() +{ + return errorMessage; +} + +template <class T> +int AbstractDb3<T>::Query::getErrorCode() +{ + return errorCode; +} + +template <class T> +QStringList AbstractDb3<T>::Query::getColumnNames() +{ + return colNames; +} + +template <class T> +int AbstractDb3<T>::Query::columnCount() +{ + return colCount; +} + +template <class T> +qint64 AbstractDb3<T>::Query::rowsAffected() +{ + return affected; +} + +template <class T> +SqlResultsRowPtr AbstractDb3<T>::Query::nextInternal() +{ + Row* row = new Row; + int res = row->init(colNames, stmt); + if (res != T::OK) + { + delete row; + setError(res, QString::fromUtf8(T::errmsg(db->dbHandle))); + return SqlResultsRowPtr(); + } + + res = fetchNext(); + if (res != T::OK) + { + delete row; + return SqlResultsRowPtr(); + } + + return SqlResultsRowPtr(row); +} + +template <class T> +bool AbstractDb3<T>::Query::hasNextInternal() +{ + return rowAvailable && stmt && checkDbState(); +} + +template <class T> +int AbstractDb3<T>::Query::fetchFirst() +{ + colCount = T::column_count(stmt); + for (int i = 0; i < colCount; i++) + colNames << QString::fromUtf8(T::column_name(stmt, i)); + + rowAvailable = true; + int res = fetchNext(); + + affected = 0; + if (res == T::OK) + { + affected = T::changes(db->dbHandle); + insertRowId["ROWID"] = T::last_insert_rowid(db->dbHandle); + } + + return res; +} + +template <class T> +int AbstractDb3<T>::Query::fetchNext() +{ + if (!checkDbState()) + rowAvailable = false; + + if (!rowAvailable || !stmt) + { + setError(T::MISUSE, tr("Result set expired or no row available.")); + return T::MISUSE; + } + + rowAvailable = false; + int res; + int secondsSpent = 0; + while ((res = T::step(stmt)) == T::BUSY && secondsSpent < db->getTimeout()) + { + QThread::sleep(1); + if (db->getTimeout() >= 0) + secondsSpent++; + } + + switch (res) + { + case T::ROW: + rowAvailable = true; + break; + case T::DONE: + // Empty pointer as no more results are available. + break; + default: + setError(res, QString::fromUtf8(T::errmsg(db->dbHandle))); + return T::ERROR; + } + return T::OK; +} + +//------------------------------------------------------------------------------------ +// Row +//------------------------------------------------------------------------------------ + +template <class T> +int AbstractDb3<T>::Query::Row::init(const QStringList& columns, typename T::stmt* stmt) +{ + int res = T::OK; + QVariant value; + for (int i = 0; i < columns.size(); i++) + { + res = getValue(stmt, i, value); + if (res != T::OK) + return res; + + values << value; + valuesMap[columns[i]] = value; + } + return res; +} + +template <class T> +int AbstractDb3<T>::Query::Row::getValue(typename T::stmt* stmt, int col, QVariant& value) +{ + int dataType = T::column_type(stmt, col); + switch (dataType) + { + case T::INTEGER: + value = T::column_int64(stmt, col); + break; + case T::BLOB: + value = QByteArray( + static_cast<const char*>(T::column_blob(stmt, col)), + T::column_bytes(stmt, col) + ); + break; + case T::FLOAT: + value = T::column_double(stmt, col); + break; + case T::NULL_TYPE: + value = QVariant(QVariant::String); + break; + default: + value = QString( + reinterpret_cast<const QChar*>(T::column_text16(stmt, col)), + T::column_bytes16(stmt, col) / sizeof(QChar) + ); + break; + } + return T::OK; +} + +#endif // ABSTRACTDB3_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/asyncqueryrunner.cpp b/SQLiteStudio3/coreSQLiteStudio/db/asyncqueryrunner.cpp new file mode 100644 index 0000000..ff7e51a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/asyncqueryrunner.cpp @@ -0,0 +1,62 @@ +#include "db/asyncqueryrunner.h" +#include "db/sqlquery.h" +#include "db/db.h" +#include <QDebug> + +AsyncQueryRunner::AsyncQueryRunner(const QString &query, const QVariant& args, Db::Flags flags) + : query(query), args(args), flags(flags) +{ + init(); +} + +void AsyncQueryRunner::init() +{ + setAutoDelete(false); +} + +void AsyncQueryRunner::run() +{ + if (!db || !db->isValid()) + { + qCritical() << "No Db or invalid Db defined in AsyncQueryRunner!"; + emit finished(this); + } + + SqlQueryPtr res; + if (args.userType() == QVariant::List) + { + res = db->exec(query, args.toList(), flags); + } + else if (args.userType() == QVariant::Hash) + { + res = db->exec(query, args.toHash(), flags); + } + else + { + qCritical() << "Invalid argument type in AsyncQueryRunner::run():" << args.userType(); + } + + results = SqlQueryPtr(res); + emit finished(this); +} + +SqlQueryPtr AsyncQueryRunner::getResults() +{ + return results; +} + + +void AsyncQueryRunner::setDb(Db *db) +{ + this->db = db; +} + +void AsyncQueryRunner::setAsyncId(quint32 id) +{ + asyncId = id; +} + +quint32 AsyncQueryRunner::getAsyncId() +{ + return asyncId; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/asyncqueryrunner.h b/SQLiteStudio3/coreSQLiteStudio/db/asyncqueryrunner.h new file mode 100644 index 0000000..5f0e41c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/asyncqueryrunner.h @@ -0,0 +1,126 @@ +#ifndef ASYNCQUERYRUNNER_H +#define ASYNCQUERYRUNNER_H + +#include "db.h" + +#include <QVariant> +#include <QHash> +#include <QRunnable> +#include <QString> +#include <QPointer> +#include <QByteArray> + +/** + * @brief Direct query executor to be run in a thread. + * + * It's an implementation of QRunnable (so it can be run simply within QThread), + * that takes query string and arguments for the query and executes the query + * in separate thread (the one that is owning the runner). + * + * The runner is not deleted automatically. Instead the slot for finished() signal + * has to delete it. It's done like that because the slot will also be interested + * in execution results and asyncId, before the runner gets deleted. + * + * What it does is simply execute Db::exec() from another thread. + * + * It's a kind of helper class that is used to implement Db::asyncExec(). + */ +class AsyncQueryRunner : public QObject, public QRunnable +{ + Q_OBJECT + + public: + /** + * @brief Creates runner and defines basic parameters. + * @param query Query string to be executed. + * @param args Parameters to the query (can be either QHash or QList). + * @param flags Execution flags, that will be later passed to Db::exec(). + * + * It's not enough to just create runner. You also need to define db with setDb() + * and asyncId with setAsyncId(). + */ + AsyncQueryRunner(const QString& query, const QVariant& args, Db::Flags flags); + + /** + * @brief Executes query. + * + * This is the major method inherited from QRunnable. It's called from another thread + * and it executes the query. + */ + void run(); + + /** + * @brief Provides result from execution. + * @return Execution results. + */ + SqlQueryPtr getResults(); + + /** + * @brief Defines database for execution. + * @param db Database object. + */ + void setDb(Db* db); + + /** + * @brief Defines asynchronous ID for this execution. + * @param id Unique ID. + */ + void setAsyncId(quint32 id); + + /** + * @brief Provides previously defined asynchronous ID. + * @return Unique asynchronous ID. + */ + quint32 getAsyncId(); + + private: + /** + * @brief Initializes default values. + */ + void init(); + + /** + * @brief Database to execute the query on. + */ + Db* db = nullptr; + + /** + * @brief Query to execute. + */ + QString query; + + /** + * @brief Results from execution. + */ + SqlQueryPtr results; + + /** + * @brief Parameters for execution. + * + * It's either QList<QVariant> or QHash<QString,QVariant>. If it's anything else, + * then no execution will be performed and critical error will be logged. + */ + QVariant args; + + /** + * @brief The unique asynchronous ID for this query execution. + */ + quint32 asyncId; + + /** + * @brief Execution flags passed to Db::exec(). + */ + Db::Flags flags; + + signals: + /** + * @brief Emitted after the runner has finished its job. + * + * Slot connected to this signal should at least delete the runner, + * but it can also extract execution results. + */ + void finished(AsyncQueryRunner*); +}; + + +#endif // ASYNCQUERYRUNNER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/attachguard.cpp b/SQLiteStudio3/coreSQLiteStudio/db/attachguard.cpp new file mode 100644 index 0000000..bcd4890 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/attachguard.cpp @@ -0,0 +1,20 @@ +#include "attachguard.h" +#include "db/db.h" + +GuardedAttach::GuardedAttach(Db* db, Db* attachedDb, const QString& attachName) : + db(db), attachedDb(attachedDb), name(attachName) +{ +} + +GuardedAttach::~GuardedAttach() +{ + if (name.isNull()) + return; + + db->detach(attachedDb); +} + +QString GuardedAttach::getName() const +{ + return name; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/attachguard.h b/SQLiteStudio3/coreSQLiteStudio/db/attachguard.h new file mode 100644 index 0000000..be708b8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/attachguard.h @@ -0,0 +1,24 @@ +#ifndef ATTACHGUARD_H +#define ATTACHGUARD_H + +#include <QSharedPointer> + +class Db; + +class GuardedAttach +{ + public: + GuardedAttach(Db* db, Db* attachedDb, const QString& attachName); + virtual ~GuardedAttach(); + + QString getName() const; + + private: + Db* db = nullptr; + Db* attachedDb = nullptr; + QString name; +}; + +typedef QSharedPointer<GuardedAttach> AttachGuard; + +#endif // ATTACHGUARD_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.cpp b/SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.cpp new file mode 100644 index 0000000..f36a3bd --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.cpp @@ -0,0 +1,200 @@ +#include "chainexecutor.h" +#include "sqlerrorcodes.h" +#include "db/sqlquery.h" +#include <QDebug> + +ChainExecutor::ChainExecutor(QObject *parent) : + QObject(parent) +{ +} + +bool ChainExecutor::getTransaction() const +{ + return transaction; +} + +void ChainExecutor::setTransaction(bool value) +{ + transaction = value; +} +QStringList ChainExecutor::getQueries() const +{ + return sqls; +} + +void ChainExecutor::setQueries(const QStringList& value) +{ + sqls = value; + queryParams.clear(); +} + +void ChainExecutor::exec() +{ + if (!db) + { + emit failure(SqlErrorCode::DB_NOT_DEFINED, tr("The database for executing queries was not defined.", "chain executor")); + return; + } + + if (!db->isOpen()) + { + emit failure(SqlErrorCode::DB_NOT_OPEN, tr("The database for executing queries was not open.", "chain executor")); + return; + } + + if (transaction && !db->begin()) + { + emit failure(db->getErrorCode(), tr("Could not start a database transaction. Details: %1", "chain executor").arg(db->getErrorText())); + return; + } + + currentSqlIndex = 0; + if (async) + executeCurrentSql(); + else + executeSync(); +} + +void ChainExecutor::interrupt() +{ + interrupted = true; + db->interrupt(); +} + +void ChainExecutor::executeCurrentSql() +{ + if (currentSqlIndex >= sqls.size()) + { + executionSuccessful(); + return; + } + + if (interrupted) + { + executionFailure(SqlErrorCode::INTERRUPTED, tr("Interrupted", "chain executor")); + return; + } + + asyncId = db->asyncExec(sqls[currentSqlIndex], queryParams); +} + +QList<bool> ChainExecutor::getMandatoryQueries() const +{ + return mandatoryQueries; +} + +void ChainExecutor::setMandatoryQueries(const QList<bool>& value) +{ + mandatoryQueries = value; +} + +Db* ChainExecutor::getDb() const +{ + return db; +} + +void ChainExecutor::setDb(Db* value) +{ + if (db) + disconnect(db, SIGNAL(asyncExecFinished(quint32,SqlQueryPtr)), this, SLOT(handleAsyncResults(quint32,SqlQueryPtr))); + + db = value; + + if (db) + connect(db, SIGNAL(asyncExecFinished(quint32,SqlQueryPtr)), this, SLOT(handleAsyncResults(quint32,SqlQueryPtr))); +} + + +void ChainExecutor::handleAsyncResults(quint32 asyncId, SqlQueryPtr results) +{ + if (asyncId != this->asyncId) + return; + + if (!handleResults(results)) + return; + + currentSqlIndex++; + executeCurrentSql(); +} + +void ChainExecutor::executionFailure(int errorCode, const QString& errorText) +{ + if (transaction) + db->rollback(); + + successfulExecution = false; + executionErrors << ExecutionError(errorCode, errorText); + emit failure(errorCode, errorText); +} + +void ChainExecutor::executionSuccessful() +{ + if (transaction && !db->commit()) + { + executionFailure(db->getErrorCode(), tr("Could not commit a database transaction. Details: %1", "chain executor").arg(db->getErrorText())); + return; + } + + successfulExecution = true; + emit success(); +} + +void ChainExecutor::executeSync() +{ + SqlQueryPtr results; + foreach (const QString& sql, sqls) + { + results = db->exec(sql, queryParams); + if (!handleResults(results)) + return; + + currentSqlIndex++; + } + executionSuccessful(); +} + +bool ChainExecutor::handleResults(SqlQueryPtr results) +{ + if (results->isError()) + { + if (interrupted || currentSqlIndex >= mandatoryQueries.size() || mandatoryQueries[currentSqlIndex]) + { + executionFailure(results->getErrorCode(), results->getErrorText()); + return false; + } + } + return true; +} +bool ChainExecutor::getSuccessfulExecution() const +{ + return successfulExecution; +} + +void ChainExecutor::setParam(const QString ¶mName, const QVariant &value) +{ + queryParams[paramName] = value; +} + +bool ChainExecutor::getAsync() const +{ + return async; +} + +void ChainExecutor::setAsync(bool value) +{ + async = value; +} + +QStringList ChainExecutor::getErrorsMessages() const +{ + QStringList msgs; + for (const ExecutionError& e : executionErrors) + msgs << e.second; + + return msgs; +} + +const QList<ChainExecutor::ExecutionError>& ChainExecutor::getErrors() const +{ + return executionErrors; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.h b/SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.h new file mode 100644 index 0000000..2d3e3d3 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/chainexecutor.h @@ -0,0 +1,359 @@ +#ifndef CHAINEXECUTOR_H +#define CHAINEXECUTOR_H + +#include "db/db.h" +#include <QObject> + +// TODO add parameters support for ChainExecutor. +// it requires clever api, cause there can be multiple queries and each can use differend parameters, +// while we cannot apply same parameter set for each query, cause they are bind by an available list/hash. + +/** + * @brief Simple query executor, which executes queries one by one. + * + * A query executor which lets you execute query (or many queries) + * using asynchronous execution and it gets back to the called only when + * all queries succeeded, or it failed at some query. + * + * This is very useful if there is a sequence of queries to be executed + * and you're interested only in the result of the last query. + * + * It also lets to configure if queries should be executed within transaction, + * or not. + */ +class API_EXPORT ChainExecutor : public QObject +{ + Q_OBJECT + + public: + typedef QPair<int,QString> ExecutionError; + + /** + * @brief Creates executor. + * @param parent Parent object for QObject. + */ + explicit ChainExecutor(QObject *parent = 0); + + /** + * @brief Tells if transactional execution is enabled. + * @return Transactional execution status. + */ + bool getTransaction() const; + + /** + * @brief Enabled or disables transactional execution. + * @param value True to enable, false to disable. + * + * Transactional execution is enabled by default. It means that all defined SQL queries + * will be executed in single SQL transaction. + */ + void setTransaction(bool value); + + /** + * @brief Provides list of SQL queries configured for this executor. + * @return List of queries. + */ + QStringList getQueries() const; + + /** + * @brief Defines list of queries to be executed. + * @param value List of query strings. + * + * This is the main mathod you're interested in when using ChainExecutor. + * This is how you define what SQL queries will be executed. + * + * Calling this method will clear any parameters defined previously with setParam(). + */ + void setQueries(const QStringList& value); + + /** + * @brief Provides currently configured database. + * @return Database that the queries are executed on in this executor. + */ + Db* getDb() const; + + /** + * @brief Defines database for executing queries. + * @param value The database object. + * + * It is necessary to define the database before executing queries, + * otherwise the start() will emit failure() signal and do nothing else. + */ + void setDb(Db* value); + + /** + * @brief Provides list of configured query mandatory flags. + * @return List of flags. + * + * See setMandatoryQueries() for details on mandatory flags. + */ + QList<bool> getMandatoryQueries() const; + + /** + * @brief Defines list of mandatory flags for queries. + * @param value List of flags - a boolean per each defined query. + * + * Setting mandatory flags lets you define which queries (defined with setSqls()) + * are mandatory for the successful execution and which are not. + * Queries are mandatory by default (when flags are not defined), + * which means that every defined query execution must be successfull, + * otherwise executor breaks the execution chain and reports error. + * + * By defining mandatory flags to false for some queries, you're telling + * to ChainExecutor, that it's okay if those queries fail and it should + * move along. + * + * For example: + * @code + * ChainExecutor executor; + * executor.setSqls({ + * "DELETE FROM table1 WHERE value = 5", + * "DELETE FROM possibly_not_existing_table WHERE column > 3", + * "INSERT INTO table1 VALUES (4, 6)" + * }); + * executor.setMandatoryQueries({true, false, true}); + * @endcode + * We defined second query to be optional, therefore if the table + * "possibly_not_existing_table" doesn't exist, that's fine. + * It will be ignored and the third query will be executed. + * If flags were not defined, then execution of second query would fail, + * executor would stop there, report error (with failure() signal) + * and the third query would not be executed. + * + * It also affects transactions. If executor was defined to execute + * in a transaction (with setTransaction()), then failed query + * that was not mandatory will also not rollback the transaction. + * + * In other words, queries marked as not mandatory are silently ignored + * when failed. + */ + void setMandatoryQueries(const QList<bool>& value); + + /** + * @brief Provides list of execution error messages. + * @return List of messages. + * + * Execution error messages usually have zero or one message, + * but if you defined some queries to be not mandatory, + * then each failed optional query will be silently ignored, + * but its error message will be stored and returned by this method. + * In that case, the result of this method can provide more than + * one message. + */ + QStringList getErrorsMessages() const; + + /** + * @brief Provides list of execution errors. + * @return List of errors. + * + * These are the same errors as returned by getErrorsMessages(), except this list contains + * both error code (as returned from SQLite) and error message. + */ + const QList<ExecutionError>& getErrors() const; + + /** + * @brief Tells if the executor is configured for asynchronous execution. + * @return Asynchronous flag value. + */ + bool getAsync() const; + + /** + * @brief Defines asynchronous execution mode. + * @param value true to enable asynchronous execution, false to disable it. + * + * Asynchronous execution causes start() to return immediately. + * + * When asynchronous mode is enabled, results of execution + * have to be handled by connecting to failed() and success() signals. + * + * If the asynchronous mode is disabled, result can be queried + * by getSuccessfulExecution() call. + */ + void setAsync(bool value); + + /** + * @brief Tells if the most recent execution was successful. + * @return true if execution was successful, or false if it failed. + * + * Successful execution means that all mandatory queries + * (see setMandatoryQueries()) executed successfully. + * Optional (not mandatory) queries do not affect result of this method. + * + * If this method returns true, it also means that success() signal + * was emitted. + * If this method returns false, it also means that failure() signal + * was emitted. + */ + bool getSuccessfulExecution() const; + + /** + * @brief Defines named parameter to bind in queries. + * @param paramName Parameter name (must include the preceding ':'). + * @param value Value for the parameter. + * + * Any parameter defined with this method will be applied to each query + * executed by the executor. If some query doesn't include parameter + * placeholder with defined name, then the parameter will simply + * not be applied to that query. + */ + void setParam(const QString& paramName, const QVariant& value); + + private: + /** + * @brief Executes query defines as the current one. + * + * Checks is there is a current query defined (pointed by currentSqlIndex). + * If there is, then executes it. If not, goes to executionSuccessful(). + * + * This is called for each next query in asynchronous mode. + */ + void executeCurrentSql(); + + /** + * @brief Handles failed execution. + * @param errorCode Error code. + * @param errorText Error message. + * + * Rolls back transaction (in case of transactional execution) and emits failure(). + */ + void executionFailure(int errorCode, const QString& errorText); + + /** + * @brief Handles successful execution. + * + * Commits transaction (in case of transactional execution) and emits success(). + */ + void executionSuccessful(); + + /** + * @brief Executes all queries synchronously. + * + * If the asynchronous mode is disabled, then this method executes all queries. + */ + void executeSync(); + + /** + * @brief Handles single query execution results. + * @param results Results from the query. + * @return true if the execution was successful, or false otherwise. + * + * If there was an error while execution, then executionFailure() is also called. + */ + bool handleResults(SqlQueryPtr results); + + /** + * @brief Database for execution. + */ + Db* db = nullptr; + + /** + * @brief Transactional execution mode. + */ + bool transaction = true; + + /** + * @brief Asynchronous execution mode. + */ + bool async = true; + + /** + * @brief Queries to be executed. + */ + QStringList sqls; + + /** + * @brief List of flags for mandatory queries. + * + * See setMandatoryQueries() for details. + */ + QList<bool> mandatoryQueries; + + /** + * @brief Index pointing to the current query in sqls list. + * + * When executing query in asynchronous mode, this index points to the next + * query that should be executed. + */ + int currentSqlIndex = -1; + + /** + * @brief Asynchronous ID of current query execution. + * + * The ID is provided by Db::asyncExec(). + */ + quint32 asyncId = -1; + + /** + * @brief Execution interrupted flag. + * + * Once the interrup() was called, this flag is set to true, + * so the executor knows that it should not execute any further queries. + */ + bool interrupted = false; + + /** + * @brief Errors raised during queries execution. + * + * In case of major failure, the error message is appended to this list, + * but when mandatory flags allow some failures, than this list may + * contain more error messages. + */ + QList<ExecutionError> executionErrors; + + /** + * @brief Successful execution indicator. + * + * This is set after execution is finished. + */ + bool successfulExecution = false; + + /** + * @brief Parameters to bind to queries. + * + * This is filled with setParam() calls and used later to bind + * parameters to executed queries. + */ + QHash<QString,QVariant> queryParams; + + public slots: + /** + * @brief Interrupts query execution. + */ + void interrupt(); + + /** + * @brief Starts execution of all defined queries, one by one. + */ + void exec(); + + private slots: + /** + * @brief Handles asynchronous execution results from Db::asyncExec(). + * @param asyncId Asynchronous ID of the execution for the results. + * @param results Results returned from execution. + * + * Checks if given asynchronous ID matches the internally stored asyncId + * and if yes, then handles results and executes next query in the queue. + */ + void handleAsyncResults(quint32 asyncId, SqlQueryPtr results); + + signals: + /** + * @brief Emitted when all mandatory queries were successfully executed. + * + * See setMandatoryQueries() for details on mandatory queries. + */ + void success(); + + /** + * @brief Emitted when major error occurred while executing a query. + * @param errorCode Error code. + * @param errorText Error message. + * + * It's emitted only when mandatory query has failed execution. + * See setMandatoryQueries() for details on mandatory queries. + */ + void failure(int errorCode, const QString& errorText); +}; + +#endif // CHAINEXECUTOR_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/db.cpp b/SQLiteStudio3/coreSQLiteStudio/db/db.cpp new file mode 100644 index 0000000..c89349c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/db.cpp @@ -0,0 +1,57 @@ +#include "db.h" +#include <QMetaEnum> + +Db::Db() +{ +} + +Db::~Db() +{ +} + +void Db::metaInit() +{ + qRegisterMetaType<Db*>("Db*"); + qRegisterMetaTypeStreamOperators<Db*>("Db*"); +} + +QString Db::flagsToString(Db::Flags flags) +{ + int idx = staticMetaObject.indexOfEnumerator("Flag"); + if (idx == -1) + return QString::null; + + QMetaEnum en = staticMetaObject.enumerator(idx); + return en.valueToKeys(static_cast<int>(flags)); +} + +QDataStream &operator <<(QDataStream &out, const Db* myObj) +{ + out << reinterpret_cast<quint64>(myObj); + return out; +} + + +QDataStream &operator >>(QDataStream &in, Db*& myObj) +{ + quint64 ptr; + in >> ptr; + myObj = reinterpret_cast<Db*>(ptr); + return in; +} + + +void Sqlite2ColumnDataTypeHelper::setBinaryType(int columnIndex) +{ + binaryColumns << columnIndex; +} + +bool Sqlite2ColumnDataTypeHelper::isBinaryColumn(int columnIndex) const +{ + return binaryColumns.contains(columnIndex); +} + +void Sqlite2ColumnDataTypeHelper::clearBinaryTypes() +{ + binaryColumns.clear(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/db.h b/SQLiteStudio3/coreSQLiteStudio/db/db.h new file mode 100644 index 0000000..7d10a05 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/db.h @@ -0,0 +1,831 @@ +#ifndef DB_H +#define DB_H + +#include "returncode.h" +#include "dialect.h" +#include "services/functionmanager.h" +#include "common/readwritelocker.h" +#include "coreSQLiteStudio_global.h" +#include "db/attachguard.h" +#include "interruptable.h" +#include "dbobjecttype.h" +#include <QObject> +#include <QVariant> +#include <QList> +#include <QHash> +#include <QReadWriteLock> +#include <QRunnable> +#include <QStringList> +#include <QSet> + +/** @file */ + +class AsyncQueryRunner; +class Db; +class DbManager; +class SqlQuery; + +typedef QSharedPointer<SqlQuery> SqlQueryPtr; + +/** + * @brief Option to make new Db instance not install any functions or collations in the database. + * + * This connection option should be used (with boolean value = true) when creating Db instance + * to be used internally (not exposed to the user) and you don't want any special features + * (like custom SQL functions, custom collations) to be registered in that database. + */ +static_char* DB_PURE_INIT = "sqlitestudio_pure_db_initalization"; + +/** + * @brief Option name for plugin handling the database. + * + * This is a constant naming the connection option, which tells SQLiteStudio which plugin was dedicated to handle + * particular database. + */ +static_char* DB_PLUGIN = "plugin"; + +/** + * @brief Database managed by application. + * + * Everything you might want to do with SQLite databases goes through this interface in the application. + * It's has a common interface for common database operations, such as connecting and disconnecting, + * checking current status, executing queries and reading results. + * It keeps information about the database version, dialect (SQLite 2 vs SQLite 3), encoding (UTF-8, UTF-16, etc.), + * symbolic name of the database and path to the file. + * + * Regular routine with the database object would be to open it (if not open yet), execute some query + * and collect results. It can be done in several ways, but here's simple one: + * @code + * QList<int> queryDb(const QString& dbName, const QString& colValue1, int colValue2) + * { + * // Getting database object by its name and opening it if necessary + * Db* db = DBLIST->getDb(dbName); + * if (!db) + * return; // no such database + * + * if (!db->isOpen()) + * db->open(); + * + * // Executing query and getting results + * SqlQueryPtr results = db->exec("SELECT intCol FROM table WHERE col1 = ?, col2 = ?", colValue1, colValue2) + * + * QList<int> resultList; + * SqlResultsRowPtr row; + * while (row = results->next()) + * { + * resultList << row->value("intCol").toInt(); + * } + * return resultList; + * } + * @endcode + * + * The example above is very generic way to do things. You can use many methods which simplifies tasks in case + * you work with smaller data sets. For example: + * @code + * int getRowId(Db* db, int colVal) + * { + * // We assume that db is already open + * return db->exec("SELECT rowid FROM table WHERE column = ?", colVal).getSingleCell().toInt(); + * } + * @endcode + * + * To write some data into database you can write as this: + * @code + * void insert(Db* db, const QString& val1, int val2) + * { + * // We assume that db is already open + * db->exec("INSERT INTO table (col1, col2) VALUES (?, ?)", val1, val2); + * } + * @endcode + * + * You can use named parameters: + * @code + * void insert(Db* db, const QString& val1, int val2) + * { + * QHash<QString,QVariant> params; + * params["c1"] = val1; + * params["c2"] = val2; + * db->exec("INSERT INTO table (col1, col2) VALUES (:c1, :c2)", params); + * } + * @endcode + * + * To check if the execution was successful, test results: + * @code + * void insert(Db* db, const QString& val1, int val2) + * { + * SqlQueryPtr results = db->exec("INSERT INTO table (col1, col2) VALUES (?, ?)", val1, val2); + * if (results->isError()) + * { + * qWarning() << "Error while inserting:" << results->getErrorCode() << results->getErrorText(); + * } + * } + * @endcode + * + * @see DbBase + * @see DbQt + * @see DbQt2 + * @see DbQt3 + */ +class API_EXPORT Db : public QObject, public Interruptable +{ + Q_OBJECT + + public: + /** + * @brief Flags for query execution. + * + * Those flags are used by exec() and asyncExec(). They can be used with bit-wise operators. + */ + enum class Flag + { + NONE = 0x0, /**< No flags. This is default. */ + PRELOAD = 0x1, /**< Preloads all execution results into the results object. Useful for asynchronous execution. */ + NO_LOCK = 0x2 /**< + * Prevents SQLiteStudio from setting the lock for execution on this base (not the SQLite lock, + * just a Db internal lock for multi-threading access to the Db::exec()). This should be used + * only in justified circumstances. That is when the Db call has to be done from within the part + * of code, where the lock on Db was already set. Never (!) use this to ommit lock from different + * threads. Justified situation is when you implement Db::initialDbSetup() in the derived class, + * or when you implement SqlFunctionPlugin. Don't use it for the usual cases. + */ + }; + Q_DECLARE_FLAGS(Flags, Flag) + + /** + * @brief Function to handle SQL query results. + * + * The function has to accept single results object and return nothing. + * After results are processed, they will be deleted automatically, no need to handle that. + */ + typedef std::function<void(SqlQueryPtr)> QueryResultsHandler; + + /** + * @brief Default, empty constructor. + */ + Db(); + + /** + * @brief Releases resources. + * + * Detaches any attached databases and closes the database if open. + */ + virtual ~Db(); + + /** + * @brief Registers Db in Qt meta subsystem. + * + * It's called at the application startup. Makes Db* supported by Qt meta subsystem. + */ + static void metaInit(); + + /** + * @brief Converts flags into string representation. + * @param flags Flags to convert. Can be multiple flags OR'ed. + * @return Flags as string representation, for example: STRING_REPLACE_ARGS. + */ + static QString flagsToString(Flags flags); + + /** + * @brief Checks if database is open (connected). + * @return true if the database is connected, or false otherwise. + */ + virtual bool isOpen() = 0; + + /** + * @brief Gets database symbolic name. + * @return Database symbolic name (as it was defined in call to DbManager#addDb() or DbManager#updateDb()). + */ + virtual QString getName() = 0; + + /** + * @brief Gets database file path. + * @return Database file path (as it was defined in call to DbManager#addDb() or DbManager#updateDb()). + */ + virtual QString getPath() = 0; + + /** + * @brief Gets SQLite version major number for this database. + * @return Major version number, that is 3 for SQLite 3.x.x and 2 for SQLite 2.x.x. + * + * You don't have to open the database. This information is always available. + */ + virtual quint8 getVersion() = 0; + + /** + * @brief Gets database dialect. + * @return Database dialect, which is either Sqlite2 or Sqlite3. + * + * You don't have to open the database. This information is always available. + */ + virtual Dialect getDialect() = 0; + + /** + * @brief Gets database encoding. + * @return Database encoding as returned from SQLite query: <tt>PRAGMA encoding;</tt> + * + * If the database is not open, then this methods quickly opens it, queries the encoding and closes the database. + * The opening and closing of the database is not visible outside, it's just an internal operation. + */ + virtual QString getEncoding() = 0; + + /** + * @brief Gets connection options. + * @return Connection options, the same as were passed to DbManager#addDb() or DbManager#updateDb(). + */ + virtual QHash<QString,QVariant>& getConnectionOptions() = 0; + + /** + * @brief Sets new name for the database. + * @param value New name. + * + * This method works only on closed databases. If the database is open, then warning is logged + * and function does nothing more. + */ + virtual void setName(const QString& value) = 0; + + /** + * @brief Sets new file path for the database. + * @param value New file path. + * + * This method works only on closed databases. If the database is open, then warning is logged + * and function does nothing more. + */ + virtual void setPath(const QString& value) = 0; + + /** + * @brief Sets connection options for the database. + * @param value Connection options. See DbManager::addDb() for details. + * + * This method works only on closed databases. If the database is open, then warning is logged + * and function does nothing more. + */ + virtual void setConnectionOptions(const QHash<QString,QVariant>& value) = 0; + + /** + * @brief Sets the timeout for waiting for the database to be unlocked. + * @param secs Number of seconds. + * + * When the database is locked by another application, then the SQLiteStudio will wait given number + * of seconds for the database to be released, before the execution error is reported. + * + * Set it to negative value to set infinite timeout. + * + * This doesn't involve locking done by SQLiteStudio internally (see Db::Flag::NO_LOCK), which doesn't time out. + */ + virtual void setTimeout(int secs) = 0; + + /** + * @brief Gets the current database lock waiting timeout value. + * @return Number of seconds to wait for the database to be released. + * + * See setTimeout() for details. + */ + virtual int getTimeout() const = 0; + + /** + * @brief Executes SQL query. + * @param query Query to be executed. Parameter placeholders can be either of: ?, :param, \@param, just don't mix different types in single query. + * @param args List of values to bind to parameter placeholders. As those are unnamed parameters, the order is important. + * @param flags Execution flags. + * @return Execution results. + * + * Executes SQL query and returns results. If there was an error, the results will tell you when you call SqlResults::isError(). + * + * Queries like SELECT, INSERT, UPDATE, and DELETE accept positional parameters, but only for column values. If you would like to pass table name + * for SELECT, you would have to use Flags::STRING_REPLACE_ARGS and parameter placeholders in format %1, %2, %3, and so on. You cannot mix + * string parameters (as for Flags::STRING_REPLACE_ARGS) and regular SQLite parameters in single query. If you really need to, then you should + * build query string first (using QString::arg() for string parameters) and then pass it to exec(), which will accept SQLite parameters binding. + * + * If the query doesn't return any interesting results (for example it's INSERT) and you don't care about errors, you can safely ignore results object. + * The result object is shared pointer, therefore it will delete itself if not used. + * + * Given C++11 you can initialize list with braces, like this: + * @code + * SqlQueryPtr results = db->exec("SELECT * FROM table WHERE c1 = ? AND c2 = ? AND c3 = ? AND c4 = ?", + * {45, 76, "test", 3.56}); + * @endcode + */ + virtual SqlQueryPtr exec(const QString& query, const QList<QVariant> &args, Flags flags = Flag::NONE) = 0; + + /** + * @brief Executes SQL query using named parameters. + * @param query Query to be executed. Parameter placeholders can be either of: :param, \@param, just don't mix different types in single query. + * @param args Map of parameter name and the value assigned to it. + * @param flags Execution flags. See exec() for setails. + * @return Execution results. + * + * Given C++11 you can initialize hash map with braces, like this: + * @code + * SqlQueryPtr results = db->exec("SELECT * FROM table WHERE id = :userId AND name = :firstName", + * { + * {":userId", 45}, + * {":firstName", "John"} + * }); + * @endcode + * + * @overload + */ + virtual SqlQueryPtr exec(const QString& query, const QHash<QString, QVariant>& args, Flags flags = Flag::NONE) = 0; + + /** + * @brief Executes SQL query. + * @overload + */ + virtual SqlQueryPtr exec(const QString &query, Db::Flags flags = Flag::NONE) = 0; + + /** + * @brief Executes SQL query. + * @overload + */ + virtual SqlQueryPtr exec(const QString &query, const QVariant &arg) = 0; + + /** + * @brief Executes SQL query. + * @overload + */ + virtual SqlQueryPtr exec(const QString &query, std::initializer_list<QVariant> argList) = 0; + + /** + * @brief Executes SQL query. + * @overload + */ + virtual SqlQueryPtr exec(const QString &query, std::initializer_list<std::pair<QString,QVariant>> argMap) = 0; + + /** + * @brief Executes SQL query asynchronously using list of parameters. + * @param query Query to be executed. Parameter placeholders can be either of: ?, :param, \@param, just don't mix different types in single query. + * @param args List of parameter values to bind. + * @param resultsHandler Function (can be lambda) to handle results. The function has to accept single SqlQueryPtr object and return nothing. + * @param flags Execution flags. See exec() for setails. + * + * Asynchronous execution takes place in another thread. Once the execution is finished, the results handler function is called. + * + * Example: + * @code + * db->asyncExec("SELECT * FROM table WHERE col = ?", {5}, [=](SqlQueryPtr results) + * { + * qDebug() << "Received" << results->rowCount() << "rows in results."; + * }); + * @endcode + */ + virtual void asyncExec(const QString& query, const QList<QVariant>& args, QueryResultsHandler resultsHandler, Flags flags = Flag::NONE) = 0; + + /** + * @brief Executes SQL query asynchronously using named parameters. + * @param query Query to be executed. Parameter placeholders can be either of: :param, \@param, just don't mix different types in single query. + * @param args Map of parameter name and the value assigned to it. + * @param resultsHandler Function (can be lambda) to handle results. The function has to accept single SqlQueryPtr object and return nothing. + * @param flags Execution flags. See exec() for details. + * @return Asynchronous execution ID. + * @overload + */ + virtual void asyncExec(const QString& query, const QHash<QString, QVariant>& args, QueryResultsHandler resultsHandler, Flags flags = Flag::NONE) = 0; + + /** + * @brief Executes SQL query asynchronously. + * @param query Query to be executed. See exec() for details. + * @param resultsHandler Function (can be lambda) to handle results. The function has to accept single SqlQueryPtr object and return nothing. + * @param flags Execution flags. See exec() for details. + * @return Asynchronous execution ID. + * @overload + */ + virtual void asyncExec(const QString& query, QueryResultsHandler resultsHandler, Flags flags = Flag::NONE) = 0; + + /** + * @brief Executes SQL query asynchronously using list of parameters. + * @param query Query to be executed. Parameter placeholders can be either of: ?, :param, \@param, just don't mix different types in single query. + * @param args List of parameter values to bind. + * @param flags Execution flags. See exec() for setails. + * @return Asynchronous execution ID. + * + * Asynchronous execution takes place in another thread. Once the execution is finished, the results is provided + * with asyncExecFinished() signal. You should get the ID from results of this method and compare it with ID + * from the signal, so when it matches, it means that the results object from signal is the answer to this execution. + * + * It's recommended to use method version which takes function pointer for results handing, as it's more resiliant to errors in the code. + * + * Given C++11 you can initialize list with braces, like this: + * @code + * int asyncId = db->asyncExec("SELECT * FROM table WHERE c1 = ? AND c2 = ? AND c3 = ? AND c4 = ?", + * {45, 76, "test", 3.56}); + * @endcode + */ + virtual quint32 asyncExec(const QString& query, const QList<QVariant>& args, Flags flags = Flag::NONE) = 0; + + /** + * @brief Executes SQL query asynchronously using named parameters. + * @param query Query to be executed. Parameter placeholders can be either of: :param, \@param, just don't mix different types in single query. + * @param args Map of parameter name and the value assigned to it. + * @param flags Execution flags. See exec() for details. + * @return Asynchronous execution ID. + * @overload + * + * It's recommended to use method version which takes function pointer for results handing, as it's more resiliant to errors in the code. + */ + virtual quint32 asyncExec(const QString& query, const QHash<QString, QVariant>& args, Flags flags = Flag::NONE) = 0; + + /** + * @brief Executes SQL query asynchronously. + * @param query Query to be executed. See exec() for details. + * @param flags Execution flags. See exec() for details. + * @return Asynchronous execution ID. + * @overload + * + * It's recommended to use method version which takes function pointer for results handing, as it's more resiliant to errors in the code. + */ + virtual quint32 asyncExec(const QString& query, Flags flags = Flag::NONE) = 0; + + virtual SqlQueryPtr prepare(const QString& query) = 0; + + /** + * @brief Begins SQL transaction. + * @return true on success, or false on failure. + * + * This method uses basic "BEGIN" statement to begin transaction, therefore recurrent transactions are not supported. + * This is because SQLite2 doesn't support "SAVEPOINT" and this is the common interface for all SQLite versions. + */ + virtual bool begin() = 0; + + /** + * @brief Commits SQL transaction. + * @return true on success, or false otherwise. + */ + virtual bool commit() = 0; + + /** + * @brief Rolls back the transaction. + * @return true on success, or false otherwise (i.e. there was no transaction open, there was a connection problem, etc). + */ + virtual bool rollback() = 0; + + /** + * @brief Interrupts current execution asynchronously. + * + * It's almost the same as interrupt(), except it returns immediately, instead of waiting for the interruption to finish. + * In case of some heavy queries the interruption process might take a little while. + */ + virtual void asyncInterrupt() = 0; + + /** + * @brief Checks if the database is readable at the moment. + * @return true if the database is readable, or false otherwise. + * + * The database can be in 3 states: not locked, locked for reading or locked for writing. + * If it's locked for writing, than it's not readable and this method will return false. + * If it's locked for reading or not locked at all, then this method will return true. + * Database can be locked by other threads executing their queries on the database. + */ + virtual bool isReadable() = 0; + + /** + * @brief Checks if the database is writable at the moment. + * @return true if the database is writable, or false otherwise. + * + * The database can be in 3 states: not locked, locked for reading or locked for writing. + * If it's locked for writing (by other thread) or reading, than it's not writable and this method will return false. + * If it's not locked at all, then this method will return true. + * Database can be locked by other threads executing their queries on the database. + */ + virtual bool isWritable() = 0; + + /** + * @brief Tells if the database is valid for operating on it. + * @return true if the databse is valid, false otherwise. + * + * A valid database is the one that has valid path and driver plugin support loaded. + * Invalid database is the one that application failed to load. Those are marked with the exclamation icon on the UI. + */ + virtual bool isValid() const = 0; + + /** + * @brief Attaches given database to this database. + * @param otherDb Other registered database object. + * @param silent If true, no errors or warnings will be reported to the NotifyManager (they will still appear in logs). + * @return Name of the attached database (it's not the symbolic name of the other database, it's a name you would use in <tt>ATTACH 'name'</tt> statement). + * + * This is convinent method to attach other registered databases to this database. It generates attached database name, so it doesn't conflict + * with other - already attached - database names, attaches the database with that name and returns that name to you, so you can refer to it in queries. + */ + virtual QString attach(Db* otherDb, bool silent = false) = 0; + + /** + * @brief Attaches given database to this database using guarded attach. + * @param otherDb Other registered database object. + * @param silent If true, no errors or warnings will be reported to the NotifyManager (they will still appear in logs). + * @return Guarded attach instance with the name of the attached database inside. + * + * The guarded attach automatically detaches attached database when the attach guard is destroyed (goes out of scope). + * The AttachGuard is in fact a QSharedPointer, so you can pass it by value to other functions prolong attchment. + */ + virtual AttachGuard guardedAttach(Db* otherDb, bool silent = false) = 0; + + /** + * @brief Detaches given database from this database. + * @param otherDb Other registered database object. + * + * If the otherDb is not attached, this method does nothing. Otherwise it calls <tt>DETACH</tt> statement using the attach name generated before by attach(). + * You don't have to provide the attach name, as Db class remembers those names internally. + */ + virtual void detach(Db* otherDb) = 0; + + /** + * @brief Detaches all attached databases. + * + * Detaches all attached databases. This includes only databases attached with attach(). Databases attached with manual <tt>ATTACH</tt> query execution + * will not be detached. + */ + virtual void detachAll() = 0; + + /** + * @brief Gets attached databases. + * @return Table of attached databases and the attach names used to attach them. + * + * This method returns only databases attached with attach() method. + */ + virtual const QHash<Db*,QString>& getAttachedDatabases() = 0; + + /** + * @brief Gets all attached databases. + * @return Set of attach names. + * + * This method returns all attached database names (the attach names), including both those from attach() and manual <tt>ATTACH</tt> query execution. + */ + virtual QSet<QString> getAllAttaches() = 0; + + /** + * @brief Generates unique name for object to be created in the database. + * @param attachedDbName Optional attach name, so the name will be in context of that database. + * @return Unique object name. + * + * Queries database for all existing objects and then generates name that is not on that list. + * The generated name is a random string of length 16. + */ + virtual QString getUniqueNewObjectName(const QString& attachedDbName = QString()) = 0; + + /** + * @brief Gets last error string from database driver. + * @return Last encountered error. + * + * Result of this method is determinated by DbPlugin. + */ + virtual QString getErrorText() = 0; + + /** + * @brief Gets last error code from database driver. + * @return Code of last encountered error. + * + * Result of this method is determinated by DbPlugin. + */ + virtual int getErrorCode() = 0; + + /** + * @brief Gets database type label. + * @return Database type label. + * + * The database type label is used on UI to tell user what database it is (SQLite 3, SQLite 2, Encrypted SQLite 3, etc). + * This is defined by DbPlugin. + */ + virtual QString getTypeLabel() = 0; + + /** + * @brief Initializes resources once the all derived Db classes are constructed. + * @return true on success, false on failure. + * + * It's called just after this object was created. Implementation of this method can call virtual methods, which was a bad idea + * to do in constructor (because of how it's works in C++, if you didn't know). + * + * It usually queries database for it's version, etc. + */ + virtual bool initAfterCreated() = 0; + + /** + * @brief Deregisters custom SQL function from this database. + * @param name Name of the function. + * @param argCount Number of arguments accepted by the function (-1 for undefined). + * @return true if deregistering was successful, or false otherwise. + * + * @see FunctionManager + */ + virtual bool deregisterFunction(const QString& name, int argCount) = 0; + + /** + * @brief Registers scalar custom SQL function. + * @param name Name of the function. + * @param argCount Number of arguments accepted by the function (-1 for undefined). + * @return true on success, false on failure. + * + * Scalar functions are evaluated for each row and their result is used in place of function invokation. + * Example of SQLite built-in scalar function is abs(), or length(). + * + * This method is used only to let the database know, that the given function exists in FunctionManager and we want it to be visible + * in this database's context. When the function is called from SQL query, then the function execution is delegated to the FunctionManager. + * + * For details about usage of custom SQL functions see http://wiki.sqlitestudio.pl/index.php/User_Manual#Custom_SQL_functions + * + * @see FunctionManager + */ + virtual bool registerScalarFunction(const QString& name, int argCount) = 0; + + /** + * @brief Registers aggregate custom SQL function. + * @param name Name of the function. + * @param argCount Number of arguments accepted by the function (-1 for undefined). + * @return true on success, false on failure. + * + * Aggregate functions are used to aggregate many rows into single row. They are common in queries with GROUP BY statements. + * The aggregate function in SQLite is actually implemented by 2 functions - one for executing per each row (and which doesn't return any result yet, + * just collects the data) and then the second function, executed at the end. The latter one must return the result, which becomes the result + * of aggregate function. + * + * Aggregate functions in SQLiteStudio are almost the same as in SQLite itself, except SQLiteStudio has also a third function, which is called + * at the very begining, before the first "per step" function is called. It's used to initialize anything that the step function might need. + * + * This method is used only to let the database know, that the given function exists in FunctionManager and we want it to be visible + * in this database's context. When the function is called from SQL query, then the function execution is delegated to the FunctionManager. + * + * For details about usage of custom SQL functions see http://wiki.sqlitestudio.pl/index.php/User_Manual#Custom_SQL_functions + * + * @see FunctionManager + */ + virtual bool registerAggregateFunction(const QString& name, int argCount) = 0; + + /** + * @brief Registers a collation sequence implementation in the database. + * @param name Name of the collation. + * @return true on success, false on failure. + * + * Collations are not supported by SQLite 2, so this method will always fail for those databases. + * + * Collations are handled by CollationManager. Each collation managed by the manager has a code implemented to return -1, 0 or 1 + * when comparing 2 values in the database in order to sort query results. The name passed to this method is a name of the collation + * as it is used in SQL queries and also the same name must be used when defining collation in Collations editor window. + * + * For details about usage of custom collations see http://wiki.sqlitestudio.pl/index.php/User_Manual#Custom_collations + * + * @see CollationManager + */ + virtual bool registerCollation(const QString& name) = 0; + + /** + * @brief Deregisters previously registered collation from this database. + * @param name Collation name. + * @return true on success, false on failure. + * + * See registerCollation() for details on custom collations. + */ + virtual bool deregisterCollation(const QString& name) = 0; + + signals: + /** + * @brief Emitted when the connection to the database was established. + */ + void connected(); + + /** + * @brief Emitted after connection to the database was closed. + */ + void disconnected(); + + /** + * @brief Emitted when other database was attached to this datbase. + * @param db Other database that was attached. + * + * This is emitted only when the database was attached with attach() call. + * Manual "ATTACH" query execution doesn't cause this signal to be emitted. + */ + void attached(Db* db); + + /** + * @brief Emitted when other database was detached from this datbase. + * @param db Other database that was detached. + * + * This is emitted only when the database was detached with detach() call. + * Manual "DETACH" query execution doesn't cause this signal to be emitted. + */ + void detached(Db* db); + + //void attached(QString db); // TODO emit when called by user's sql + //void detached(QString db); // TODO emit when called by user's sql + + /** + * @brief Emitted when the asynchronous execution was finished. + * @param asyncId Asynchronous ID. + * @param results Results from query execution. + * + * This signal is emitted only when no handler function was passed to asyncExec(). + * It's emitted, so the results can be handled. + * Always test \p asyncId if it's equal to ID returned from asyncExec(). + */ + void asyncExecFinished(quint32 asyncId, SqlQueryPtr results); + + /** + * @brief idle Database became idle and awaits for instructions. + * + * This signal is emited after async execution has finished. + * It is important to re-check isWritable() or isReadable() + * in any slot connected to this signal, because some other slot + * called before currently processed slot could already order + * another async execution. + */ + void idle(); + + /** + * @brief Emitted when any database object (table, index, trigger, or view) was just deleted from this database. + * @param database Database (attach) name from which the object was deleted. Usually the "main". + * @param name Name of the object deleted. + * @param type Type of the object deleted. + * + * This signal covers only deletions made by this database of course. Deletions made by any other application + * are not announced by this signal (as this is impossible to detect it just like that). + */ + void dbObjectDeleted(const QString& database, const QString& name, DbObjectType type); + + /** + * @brief Emitted just before disconnecting and user can deny it. + * @param disconnectingDenied If set to true by anybody, then disconnecting is aborted. + */ + void aboutToDisconnect(bool& disconnectingDenied); + + public slots: + /** + * @brief Opens connection to the database. + * @return true on success, false on error. + * + * Emits connected() only on success. + */ + virtual bool open() = 0; + + /** + * @brief Closes connection to the database. + * @return true on success, false on error. + * + * Emits disconnected() only on success (i.e. db was open before). + */ + virtual bool close() = 0; + + /** + * @brief Opens connection to the database quietly. + * @return true on success, false on error. + * + * Opens database, doesn't emit any signal. + */ + virtual bool openQuiet() = 0; + + /** + * @brief Opens connection to the database quietly, without applying any specific settings. + * @return true on success, false on error. + * + * Opens database, doesn't emit any signal. It also doesn't apply any pragmas, neither registers + * functions or collations. It should be used when you want to do some basic query on the database, + * like when you probe the database for being the correct database for this implementation (driver, etc). + * Actually, that's what DbPluginSqlite3 plugin (among others) use. + * + * To close database open with this method use closeQuiet(). + */ + virtual bool openForProbing() = 0; + + /** + * @brief Closes connection to the database quietly. + * @return true on success, false on error. + * + * Closes database, doesn't emit any signal. + */ + virtual bool closeQuiet() = 0; + + /** + * @brief Deregisters all funtions registered in the database and registers new (possibly the same) functions. + * + * This slot is called from openAndSetup() and then every time user modifies custom SQL functions and commits changes to them. + * It deregisters all functions registered before in this database and registers new functions, currently defined for + * this database. + * + * @see FunctionManager + */ + virtual void registerAllFunctions() = 0; + + /** + * @brief Deregisters all collations registered in the database and registers new (possibly the same) collations. + * + * This slot is called from openAndsetup() and then every time user modifies custom collations and commits changes to them. + */ + virtual void registerAllCollations() = 0; +}; + +QDataStream &operator<<(QDataStream &out, const Db* myObj); +QDataStream &operator>>(QDataStream &in, Db*& myObj); + +Q_DECLARE_METATYPE(Db*) +Q_DECLARE_OPERATORS_FOR_FLAGS(Db::Flags) + +class API_EXPORT Sqlite2ColumnDataTypeHelper +{ + public: + void setBinaryType(int columnIndex); + bool isBinaryColumn(int columnIndex) const; + void clearBinaryTypes(); + + private: + QSet<int> binaryColumns; +}; + +#endif // DB_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/dbpluginoption.h b/SQLiteStudio3/coreSQLiteStudio/db/dbpluginoption.h new file mode 100644 index 0000000..7b594ef --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/dbpluginoption.h @@ -0,0 +1,97 @@ +#ifndef DBPLUGINOPTION_H +#define DBPLUGINOPTION_H + +#include <QString> +#include <QVariant> + +/** + * @brief Database plugin connection options. + * + * It is used to identify connection options that the DbPlugin implementation needs + * for the plugin to be configured by the user in the DbDialog. + * + * Single DbPluginOption represents in DbDialog: + * <ul> + * <li>single QLabel with text set to DbPluginOption::label,</li> + * <li>an input widget, that depends on DbPluginOption::type.</li> + * </ul> + * + * The input widget is different for different data type expected for the option. + * See DbPluginOption::Type for details. + * + * After user entered his values for options in DbDialog, they are passed + * to the DbPlugin::getInstance() and later to the Db::init(). Options are passed + * as key-value pairs, given the DbPluginOption::key and value specified by the user + * in DbDialog. + */ +struct DbPluginOption +{ + /** + * @brief Option data type + * + * Determinates what kind of input widget will be added to DbDialog. + */ + enum Type + { + STRING = 0, /**< QLineEdit will be added. */ + INT = 1, /**< QSpinBox will be added */ + BOOL = 2, /**< QCheckBox will be added */ + DOUBLE = 3, /**< QDoubleSpinBox will be added */ + FILE = 4, /**< QLineEdit will be added */ + PASSWORD = 5, /**< QLineEdit with value masking will be added */ + CHOICE = 6 /**< QComboBox will be added */ + }; + + /** + * @brief Name for the key in QHash collected from options in DbDialog and + * later passed to DbPlugin::getInstance(). + */ + QString key; + + /** + * @brief Label text to be used in DbDialog to inform user what is this option. + */ + QString label; + + /** + * @brief Optional tooltip to show for added widget. + */ + QString toolTip; + + /** + * @brief Optional placeholder text for QLineEdit widget. + */ + QString placeholderText; + + /** + * @brief List of values for QComboBox. + */ + QStringList choiceValues; + + /** + * @brief Default value to be set in the editor widget. + */ + QVariant defaultValue; + + /** + * @brief Indicates if the combobox should be read only or writable. + */ + bool choiceReadOnly = true; + + /** + * @brief Minimum value for numeric editors (double or int). + */ + QVariant minValue; + + /** + * @brief Maximum value for numeric editors (double or int). + */ + QVariant maxValue; + + /** + * @brief Expected data type for the option. + */ + Type type; +}; + +#endif // DBPLUGINOPTION_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite.h.autosave b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite.h.autosave new file mode 100644 index 0000000..9d11dac --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite.h.autosave @@ -0,0 +1,72 @@ +#ifndef DBSQLITE_H +#define DBSQLITE_H + +#include "db.h" +#include "../returncode.h" +#include "sqlerror.h" +#include "sqlresults.h" +#include "../dialect.h" + +#include <QObject> +#include <QSqlDatabase> +#include <QVariant> +#include <QList> +#include <QMap> +#include <QHash> +#include <QMutex> +#include <QRunnable> + +class AsyncQueryRunner; + +class DbSqlite : public Db +{ + Q_OBJECT + + public: + virtual ~DbSqlite(); + + static DbPtr getInstance(const QString &name, const QString& path, + const QString &options = QString::null); + + QString getName(); + QString getPath(); + quint8 getVersion(); + virtual QString driver() = 0; + Dialect getDialect(); + + quint32 asyncExec(const QString& query, const QVariant& arg1 = QVariant(), + const QVariant& arg2 = QVariant(), const QVariant& arg3 = QVariant()); + quint32 asyncExecStr(const QString& query, const QVariant& arg1 = QVariant(), + const QVariant& arg2 = QVariant(), const QVariant& arg3 = QVariant()); + quint32 asyncExecArgs(const QString& query, const QList<QVariant>& args); + quint32 asyncExecArgs(const QString& query, const QMap<QString,QVariant>& args); + + void begin(); + bool commit(); + void rollback(); + QString getType(); + SqlError lastError(); + + protected: + Db(); + + void cleanUp(); + QString generateUniqueDbName(); + bool isOpenNoLock(); + quint32 asyncExec(AsyncQueryRunner* runner); + SqlResultsPtr execInternal(const QString& query, const QList<QVariant>& args, + bool singleCell); + SqlResultsPtr execInternal(const QString& query, const QMap<QString,QVariant>& args, + bool singleCell); + bool init(); + + QSqlDatabase db; + quint8 version = 0; + + public slots: + bool openQuiet(); + bool closeQuiet(); + +}; + +#endif // DBSQLITE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp new file mode 100644 index 0000000..a4a8b73 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.cpp @@ -0,0 +1,11 @@ +#include "dbsqlite3.h" + +DbSqlite3::DbSqlite3(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions) : + AbstractDb3(name, path, connOptions) +{ +} + +DbSqlite3::DbSqlite3(const QString& name, const QString& path) : + DbSqlite3(name, path, QHash<QString,QVariant>()) +{ +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h new file mode 100644 index 0000000..29db5a8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/dbsqlite3.h @@ -0,0 +1,33 @@ +#ifndef DBSQLITE3_H +#define DBSQLITE3_H + +#include "abstractdb3.h" +#include "common/global.h" +#include "stdsqlite3driver.h" +#include <sqlite3.h> + +STD_SQLITE3_DRIVER(Sqlite3, "SQLite 3",,) + +class API_EXPORT DbSqlite3 : public AbstractDb3<Sqlite3> +{ + public: + /** + * @brief Creates SQLite database object. + * @param name Name for the database. + * @param path File path of the database. + * @param connOptions Connection options. See AbstractDb for details. + * + * All values from this constructor are just passed to AbstractDb3 constructor. + */ + DbSqlite3(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions); + + /** + * @brief Creates SQLite database object. + * @param name Name for the database. + * @param path File path of the database. + * @overload + */ + DbSqlite3(const QString& name, const QString& path); +}; + +#endif // DBSQLITE3_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp new file mode 100644 index 0000000..e4810a1 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.cpp @@ -0,0 +1,336 @@ +#include "invaliddb.h" +#include "common/unused.h" +#include <QSet> + +InvalidDb::InvalidDb(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions) : + name(name), path(path), connOptions(connOptions) +{ +} + +bool InvalidDb::isOpen() +{ + return false; +} + +QString InvalidDb::getName() +{ + return name; +} + +QString InvalidDb::getPath() +{ + return path; +} + +quint8 InvalidDb::getVersion() +{ + return 0; +} + +Dialect InvalidDb::getDialect() +{ + return Dialect::Sqlite3; +} + +QString InvalidDb::getEncoding() +{ + return QString::null; +} + +QHash<QString, QVariant>& InvalidDb::getConnectionOptions() +{ + return connOptions; +} + +void InvalidDb::setName(const QString& value) +{ + name = value; +} + +void InvalidDb::setPath(const QString& value) +{ + path = value; +} + +void InvalidDb::setConnectionOptions(const QHash<QString, QVariant>& value) +{ + connOptions = value; +} + +void InvalidDb::setTimeout(int secs) +{ + timeout = secs; +} + +int InvalidDb::getTimeout() const +{ + return timeout; +} + +SqlQueryPtr InvalidDb::exec(const QString& query, const QList<QVariant>& args, Db::Flags flags) +{ + UNUSED(query); + UNUSED(args); + UNUSED(flags); + return SqlQueryPtr(); +} + +SqlQueryPtr InvalidDb::exec(const QString& query, const QHash<QString, QVariant>& args, Db::Flags flags) +{ + UNUSED(query); + UNUSED(args); + UNUSED(flags); + return SqlQueryPtr(); +} + +SqlQueryPtr InvalidDb::exec(const QString& query, Db::Flags flags) +{ + UNUSED(query); + UNUSED(flags); + return SqlQueryPtr(); +} + +SqlQueryPtr InvalidDb::exec(const QString& query, const QVariant& arg) +{ + UNUSED(query); + UNUSED(arg); + return SqlQueryPtr(); +} + +SqlQueryPtr InvalidDb::exec(const QString& query, std::initializer_list<QVariant> argList) +{ + UNUSED(query); + UNUSED(argList); + return SqlQueryPtr(); +} + +SqlQueryPtr InvalidDb::exec(const QString& query, std::initializer_list<std::pair<QString, QVariant> > argMap) +{ + UNUSED(query); + UNUSED(argMap); + return SqlQueryPtr(); +} + +void InvalidDb::asyncExec(const QString& query, const QList<QVariant>& args, Db::QueryResultsHandler resultsHandler, Db::Flags flags) +{ + UNUSED(query); + UNUSED(args); + UNUSED(resultsHandler); + UNUSED(flags); +} + +void InvalidDb::asyncExec(const QString& query, const QHash<QString, QVariant>& args, Db::QueryResultsHandler resultsHandler, Db::Flags flags) +{ + UNUSED(query); + UNUSED(args); + UNUSED(resultsHandler); + UNUSED(flags); +} + +void InvalidDb::asyncExec(const QString& query, Db::QueryResultsHandler resultsHandler, Db::Flags flags) +{ + UNUSED(query); + UNUSED(resultsHandler); + UNUSED(flags); +} + +quint32 InvalidDb::asyncExec(const QString& query, const QList<QVariant>& args, Db::Flags flags) +{ + UNUSED(query); + UNUSED(args); + UNUSED(flags); + return 0; +} + +quint32 InvalidDb::asyncExec(const QString& query, const QHash<QString, QVariant>& args, Db::Flags flags) +{ + UNUSED(query); + UNUSED(args); + UNUSED(flags); + return 0; +} + +quint32 InvalidDb::asyncExec(const QString& query, Db::Flags flags) +{ + UNUSED(query); + UNUSED(flags); + return 0; +} + +SqlQueryPtr InvalidDb::prepare(const QString& query) +{ + UNUSED(query); + return SqlQueryPtr(); +} + +bool InvalidDb::begin() +{ + return false; +} + +bool InvalidDb::commit() +{ + return false; +} + +bool InvalidDb::rollback() +{ + return false; +} + +void InvalidDb::asyncInterrupt() +{ +} + +bool InvalidDb::isReadable() +{ + return false; +} + +bool InvalidDb::isWritable() +{ + return false; +} + +QString InvalidDb::attach(Db* otherDb, bool silent) +{ + UNUSED(otherDb); + UNUSED(silent); + return QString::null; +} + +AttachGuard InvalidDb::guardedAttach(Db* otherDb, bool silent) +{ + UNUSED(silent); + return AttachGuard::create(this, otherDb, QString::null); +} + +void InvalidDb::detach(Db* otherDb) +{ + UNUSED(otherDb); +} + +void InvalidDb::detachAll() +{ +} + +const QHash<Db*, QString>& InvalidDb::getAttachedDatabases() +{ + return attachedDbs; +} + +QSet<QString> InvalidDb::getAllAttaches() +{ + return QSet<QString>(); +} + +QString InvalidDb::getUniqueNewObjectName(const QString& attachedDbName) +{ + UNUSED(attachedDbName); + return QString::null; +} + +QString InvalidDb::getErrorText() +{ + return QString::null; +} + +int InvalidDb::getErrorCode() +{ + return 0; +} + +QString InvalidDb::getTypeLabel() +{ + return QStringLiteral("INVALID"); +} + +bool InvalidDb::initAfterCreated() +{ + return false; +} + +bool InvalidDb::deregisterFunction(const QString& name, int argCount) +{ + UNUSED(name); + UNUSED(argCount); + return false; +} + +bool InvalidDb::registerScalarFunction(const QString& name, int argCount) +{ + UNUSED(name); + UNUSED(argCount); + return false; +} + +bool InvalidDb::registerAggregateFunction(const QString& name, int argCount) +{ + UNUSED(name); + UNUSED(argCount); + return false; +} + +bool InvalidDb::registerCollation(const QString& name) +{ + UNUSED(name); + return false; +} + +bool InvalidDb::deregisterCollation(const QString& name) +{ + UNUSED(name); + return false; +} + +bool InvalidDb::open() +{ + return false; +} + +bool InvalidDb::close() +{ + return false; +} + +bool InvalidDb::openQuiet() +{ + return false; +} + +bool InvalidDb::openForProbing() +{ + return false; +} + +bool InvalidDb::closeQuiet() +{ + return false; +} + +void InvalidDb::registerAllFunctions() +{ +} + +void InvalidDb::registerAllCollations() +{ +} +QString InvalidDb::getError() const +{ + return error; +} + +void InvalidDb::setError(const QString& value) +{ + error = value; +} + + +void InvalidDb::interrupt() +{ +} + +bool InvalidDb::isValid() const +{ + return false; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h new file mode 100644 index 0000000..759aa4c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/invaliddb.h @@ -0,0 +1,81 @@ +#ifndef INVALIDDB_H +#define INVALIDDB_H + +#include "db/db.h" + +class API_EXPORT InvalidDb : public Db +{ + public: + InvalidDb(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions); + + bool isOpen(); + QString getName(); + QString getPath(); + quint8 getVersion(); + Dialect getDialect(); + QString getEncoding(); + QHash<QString, QVariant>& getConnectionOptions(); + void setName(const QString& value); + void setPath(const QString& value); + void setConnectionOptions(const QHash<QString, QVariant>& value); + void setTimeout(int secs); + int getTimeout() const; + SqlQueryPtr exec(const QString& query, const QList<QVariant>& args, Flags flags); + SqlQueryPtr exec(const QString& query, const QHash<QString, QVariant>& args, Flags flags); + SqlQueryPtr exec(const QString& query, Db::Flags flags); + SqlQueryPtr exec(const QString& query, const QVariant& arg); + SqlQueryPtr exec(const QString& query, std::initializer_list<QVariant> argList); + SqlQueryPtr exec(const QString& query, std::initializer_list<std::pair<QString, QVariant> > argMap); + void asyncExec(const QString& query, const QList<QVariant>& args, QueryResultsHandler resultsHandler, Flags flags); + void asyncExec(const QString& query, const QHash<QString, QVariant>& args, QueryResultsHandler resultsHandler, Flags flags); + void asyncExec(const QString& query, QueryResultsHandler resultsHandler, Flags flags); + quint32 asyncExec(const QString& query, const QList<QVariant>& args, Flags flags); + quint32 asyncExec(const QString& query, const QHash<QString, QVariant>& args, Flags flags); + quint32 asyncExec(const QString& query, Flags flags); + SqlQueryPtr prepare(const QString& query); + bool begin(); + bool commit(); + bool rollback(); + void asyncInterrupt(); + bool isReadable(); + bool isWritable(); + QString attach(Db* otherDb, bool silent); + AttachGuard guardedAttach(Db* otherDb, bool silent); + void detach(Db* otherDb); + void detachAll(); + const QHash<Db*, QString>& getAttachedDatabases(); + QSet<QString> getAllAttaches(); + QString getUniqueNewObjectName(const QString& attachedDbName); + QString getErrorText(); + int getErrorCode(); + QString getTypeLabel(); + bool initAfterCreated(); + bool deregisterFunction(const QString& name, int argCount); + bool registerScalarFunction(const QString& name, int argCount); + bool registerAggregateFunction(const QString& name, int argCount); + bool registerCollation(const QString& name); + bool deregisterCollation(const QString& name); + void interrupt(); + bool isValid() const; + QString getError() const; + void setError(const QString& value); + + public slots: + bool open(); + bool close(); + bool openQuiet(); + bool openForProbing(); + bool closeQuiet(); + void registerAllFunctions(); + void registerAllCollations(); + + private: + QString name; + QString path; + QHash<QString, QVariant> connOptions; + int timeout = 0; + QHash<Db*, QString> attachedDbs; + QString error; +}; + +#endif // INVALIDDB_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.cpp new file mode 100644 index 0000000..c840947 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.cpp @@ -0,0 +1,784 @@ +#include "queryexecutor.h" +#include "sqlerrorresults.h" +#include "sqlerrorcodes.h" +#include "services/dbmanager.h" +#include "db/sqlerrorcodes.h" +#include "services/notifymanager.h" +#include "queryexecutorsteps/queryexecutoraddrowids.h" +#include "queryexecutorsteps/queryexecutorcolumns.h" +#include "queryexecutorsteps/queryexecutorparsequery.h" +#include "queryexecutorsteps/queryexecutorattaches.h" +#include "queryexecutorsteps/queryexecutorcountresults.h" +#include "queryexecutorsteps/queryexecutorexecute.h" +#include "queryexecutorsteps/queryexecutorcellsize.h" +#include "queryexecutorsteps/queryexecutorlimit.h" +#include "queryexecutorsteps/queryexecutororder.h" +#include "queryexecutorsteps/queryexecutorwrapdistinctresults.h" +#include "queryexecutorsteps/queryexecutordatasources.h" +#include "queryexecutorsteps/queryexecutorexplainmode.h" +#include "queryexecutorsteps/queryexecutorreplaceviews.h" +#include "queryexecutorsteps/queryexecutordetectschemaalter.h" +#include "queryexecutorsteps/queryexecutorvaluesmode.h" +#include "common/unused.h" +#include <QMutexLocker> +#include <QDateTime> +#include <QThreadPool> +#include <QDebug> +#include <schemaresolver.h> +#include <parser/lexer.h> +#include <common/table.h> +#include <QtMath> + +// TODO modify all executor steps to use rebuildTokensFromContents() method, instead of replacing tokens manually. + +QueryExecutor::QueryExecutor(Db* db, const QString& query, QObject *parent) : + QObject(parent) +{ + context = new Context(); + originalQuery = query; + setDb(db); + setAutoDelete(false); + + connect(this, SIGNAL(executionFinished(SqlQueryPtr)), this, SLOT(cleanupAfterExecFinished(SqlQueryPtr))); + connect(this, SIGNAL(executionFailed(int,QString)), this, SLOT(cleanupAfterExecFailed(int,QString))); + connect(DBLIST, SIGNAL(dbAboutToBeUnloaded(Db*, DbPlugin*)), this, SLOT(cleanupBeforeDbDestroy(Db*, DbPlugin*))); +} + +QueryExecutor::~QueryExecutor() +{ + delete context; + context = nullptr; +} + +void QueryExecutor::setupExecutionChain() +{ + executionChain << new QueryExecutorParseQuery("initial") + << new QueryExecutorDetectSchemaAlter() + << new QueryExecutorExplainMode() + << new QueryExecutorValuesMode() + << new QueryExecutorAttaches() // needs to be at the begining, because columns needs to know real databases + << new QueryExecutorParseQuery("after Attaches") + << new QueryExecutorDataSources() + << new QueryExecutorReplaceViews() + << new QueryExecutorParseQuery("after ReplaceViews") + << new QueryExecutorAddRowIds() + << new QueryExecutorParseQuery("after AddRowIds") + << new QueryExecutorColumns() + << new QueryExecutorParseQuery("after Columns") + //<< new QueryExecutorColumnAliases() + << new QueryExecutorOrder() + << new QueryExecutorWrapDistinctResults() + << new QueryExecutorParseQuery("after WrapDistinctResults") + << new QueryExecutorCellSize() + << new QueryExecutorCountResults() + << new QueryExecutorParseQuery("after Order") + << new QueryExecutorLimit() + << new QueryExecutorParseQuery("after Limit") + << new QueryExecutorExecute(); + + foreach (QueryExecutorStep* step, executionChain) + step->init(this, context); +} + +void QueryExecutor::clearChain() +{ + foreach (QueryExecutorStep* step, executionChain) + delete step; + + executionChain.clear(); +} + +void QueryExecutor::executeChain() +{ + // Go through all remaining steps + bool result; + foreach (QueryExecutorStep* currentStep, executionChain) + { + if (interrupted) + { + stepFailed(currentStep); + return; + } + + result = currentStep->exec(); + if (!result) + { + stepFailed(currentStep); + return; + } + } + + // We're done. + clearChain(); + + executionMutex.lock(); + executionInProgress = false; + executionMutex.unlock(); + + emit executionFinished(context->executionResults); +} + +void QueryExecutor::stepFailed(QueryExecutorStep* currentStep) +{ + qDebug() << "Smart execution failed at step" << currentStep->metaObject()->className() << currentStep->objectName() + << "\nUsing simple execution method."; + + clearChain(); + + if (interrupted) + { + executionInProgress = false; + emit executionFailed(SqlErrorCode::INTERRUPTED, tr("Execution interrupted.")); + return; + } + + // Clear anything meaningful set up for smart execution - it's not valid anymore and misleads results for simple method + context->rowIdColumns.clear(); + + executeSimpleMethod(); +} + +void QueryExecutor::cleanupAfterExecFinished(SqlQueryPtr results) +{ + UNUSED(results); + cleanup(); +} + +void QueryExecutor::cleanupAfterExecFailed(int code, QString errorMessage) +{ + UNUSED(code); + UNUSED(errorMessage); + cleanup(); +} + +void QueryExecutor::cleanupBeforeDbDestroy(Db* dbToBeUnloaded, DbPlugin* plugin) +{ + UNUSED(plugin); + if (!dbToBeUnloaded || dbToBeUnloaded != db) + return; + + setDb(nullptr); + context->executionResults.clear(); +} + +void QueryExecutor::setQuery(const QString& query) +{ + originalQuery = query; +} + +void QueryExecutor::exec(Db::QueryResultsHandler resultsHandler) +{ + if (!db) + { + qWarning() << "Database is not set in QueryExecutor::exec()."; + return; + } + + if (!db->isOpen()) + { + error(SqlErrorCode::DB_NOT_OPEN, tr("Database is not open.")); + return; + } + + // Get exclusive flow for execution on this query executor + executionMutex.lock(); + if (executionInProgress) + { + error(SqlErrorCode::QUERY_EXECUTOR_ERROR, tr("Only one query can be executed simultaneously.")); + executionMutex.unlock(); + return; + } + executionInProgress = true; + executionMutex.unlock(); + + this->resultsHandler = resultsHandler; + + if (asyncMode) + QThreadPool::globalInstance()->start(this); + else + run(); +} + +void QueryExecutor::run() +{ + execInternal(); +} + +void QueryExecutor::execInternal() +{ + simpleExecution = false; + interrupted = false; + + if (resultsCountingAsyncId != 0) + { + resultsCountingAsyncId = 0; + db->interrupt(); + } + + // Reset context + delete context; + context = new Context(); + context->processedQuery = originalQuery; + context->explainMode = explainMode; + context->skipRowCounting = skipRowCounting; + context->noMetaColumns = noMetaColumns; + context->resultsHandler = resultsHandler; + context->preloadResults = preloadResults; + + // Start the execution + setupExecutionChain(); + executeChain(); +} + +void QueryExecutor::interrupt() +{ + if (!db) + { + qWarning() << "Called interrupt() on empty db in QueryExecutor."; + return; + } + + interrupted = true; + db->asyncInterrupt(); +} + +void QueryExecutor::countResults() +{ + if (context->skipRowCounting) + return; + + if (context->countingQuery.isEmpty()) // simple method doesn't provide that + return; + + if (asyncMode) + { + // Start asynchronous results counting query + resultsCountingAsyncId = db->asyncExec(context->countingQuery, context->queryParameters); + } + else + { + SqlQueryPtr results = db->exec(context->countingQuery, context->queryParameters); + context->totalRowsReturned = results->getSingleCell().toLongLong(); + context->totalPages = (int)qCeil(((double)(context->totalRowsReturned)) / ((double)getResultsPerPage())); + + emit resultsCountingFinished(context->rowsAffected, context->totalRowsReturned, context->totalPages); + + if (results->isError()) + { + notifyError(tr("An error occured while executing the count(*) query, thus data paging will be disabled. Error details from the database: %1") + .arg(results->getErrorText())); + } + } +} + +qint64 QueryExecutor::getLastExecutionTime() const +{ + return context->executionTime; +} + +qint64 QueryExecutor::getRowsAffected() const +{ + return context->rowsAffected; +} + +qint64 QueryExecutor::getTotalRowsReturned() const +{ + return context->totalRowsReturned; +} + +SqliteQueryType QueryExecutor::getExecutedQueryType(int index) +{ + if (context->parsedQueries.size() == 0) + return SqliteQueryType::UNDEFINED; + + if (index < 0) + return context->parsedQueries.last()->queryType; + + if (index < context->parsedQueries.size()) + return context->parsedQueries[index]->queryType; + + return SqliteQueryType::UNDEFINED; +} + +QSet<QueryExecutor::SourceTablePtr> QueryExecutor::getSourceTables() const +{ + return context->sourceTables; +} + +int QueryExecutor::getTotalPages() const +{ + return context->totalPages; +} + +QList<QueryExecutor::ResultColumnPtr> QueryExecutor::getResultColumns() const +{ + return context->resultColumns; +} + +QList<QueryExecutor::ResultRowIdColumnPtr> QueryExecutor::getRowIdResultColumns() const +{ + return context->rowIdColumns; +} + +int QueryExecutor::getMetaColumnCount() const +{ + int count = 0; + for (ResultRowIdColumnPtr rowIdCol : context->rowIdColumns) + count += rowIdCol->queryExecutorAliasToColumn.size(); + + return count; +} + +QSet<QueryExecutor::EditionForbiddenReason> QueryExecutor::getEditionForbiddenGlobalReasons() const +{ + return context->editionForbiddenReasons; +} + +void QueryExecutor::setParam(const QString& name, const QVariant& value) +{ + context->queryParameters[name] = value; +} + +void QueryExecutor::arg(const QVariant& value) +{ + QVariant::Type type = value.type(); + switch (type) + { + case QVariant::Bool: + case QVariant::Int: + originalQuery = originalQuery.arg(value.toInt()); + break; + case QVariant::LongLong: + originalQuery = originalQuery.arg(value.toLongLong()); + break; + case QVariant::UInt: + originalQuery = originalQuery.arg(value.toUInt()); + break; + case QVariant::ULongLong: + originalQuery = originalQuery.arg(value.toULongLong()); + break; + case QVariant::Double: + originalQuery = originalQuery.arg(value.toDouble()); + break; + case QVariant::String: + { + if (value.canConvert(QVariant::LongLong)) + originalQuery = originalQuery.arg(value.toLongLong()); + else if (value.canConvert(QVariant::Double)) + originalQuery = originalQuery.arg(value.toDouble()); + else + originalQuery = originalQuery.arg("'"+value.toString().replace("'", "''")+"'"); + + break; + } + default: + return; + } +} + +void QueryExecutor::exec(const QString& query) +{ + setQuery(query); + exec(); +} + +void QueryExecutor::dbAsyncExecFinished(quint32 asyncId, SqlQueryPtr results) +{ + if (handleRowCountingResults(asyncId, results)) + return; + + if (!simpleExecution) + return; + + if (this->asyncId == 0) + return; + + if (this->asyncId != asyncId) + return; + + this->asyncId = 0; + + simpleExecutionFinished(results); +} + +void QueryExecutor::executeSimpleMethod() +{ + simpleExecution = true; + context->editionForbiddenReasons << EditionForbiddenReason::SMART_EXECUTION_FAILED; + simpleExecutionStartTime = QDateTime::currentMSecsSinceEpoch(); + asyncId = db->asyncExec(originalQuery, context->queryParameters, Db::Flag::PRELOAD); +} + +void QueryExecutor::simpleExecutionFinished(SqlQueryPtr results) +{ + if (results->isError()) + { + executionMutex.lock(); + executionInProgress = false; + executionMutex.unlock(); + error(results->getErrorCode(), results->getErrorText()); + return; + } + + if (simpleExecIsSelect()) + context->countingQuery = "SELECT count(*) AS cnt FROM ("+originalQuery+");"; + else + context->rowsCountingRequired = true; + + ResultColumnPtr resCol; + context->resultColumns.clear(); + foreach (const QString& colName, results->getColumnNames()) + { + resCol = ResultColumnPtr::create(); + resCol->displayName = colName; + context->resultColumns << resCol; + } + + context->executionTime = QDateTime::currentMSecsSinceEpoch() - simpleExecutionStartTime; + context->rowsAffected = results->rowsAffected(); + context->totalRowsReturned = 0; + + executionMutex.lock(); + executionInProgress = false; + executionMutex.unlock(); + if (context->resultsHandler) + { + context->resultsHandler(results); + context->resultsHandler = nullptr; + } + + notifyWarn(tr("SQLiteStudio was unable to extract metadata from the query. Results won't be editable.")); + + emit executionFinished(results); +} + +bool QueryExecutor::simpleExecIsSelect() +{ + TokenList tokens = Lexer::tokenize(originalQuery, db->getDialect()); + tokens.trim(); + + // First check if it's explicit "SELECT" or "VALUES" (the latter one added in SQLite 3.8.4). + TokenPtr token = tokens.first(); + QString upper = token->value.toUpper(); + if (token->type == Token::KEYWORD && (upper == "SELECT" || upper == "VALUES")) + return true; + + // Now it's only possible to be a SELECT if it starts with "WITH" statement. + if (token->type != Token::KEYWORD || upper != "WITH") + return false; + + // Go through all tokens and find which one appears first (exclude contents indise parenthesis, + // cause there will always be a SELECT for Common Table Expression). + int depth = 0; + foreach (token, tokens) + { + switch (token->type) + { + case Token::PAR_LEFT: + depth--; + break; + case Token::PAR_RIGHT: + depth++; + break; + case Token::KEYWORD: + { + if (depth > 0) + break; + + upper = token->value.toUpper(); + if (upper == "SELECT") + return true; + + if (upper == "UPDATE" || upper == "DELETE" || upper == "INSERT") + return false; + + break; + } + default: + break; + } + } + return false; +} + +void QueryExecutor::cleanup() +{ + Db* attDb = nullptr; + foreach (const QString& attDbName, context->dbNameToAttach.leftValues()) + { + attDb = DBLIST->getByName(attDbName, Qt::CaseInsensitive); + if (attDbName.isNull()) + { + qWarning() << "Could not find db by name for cleanup after execution in QueryExecutor. Searched for db named:" << attDbName; + continue; + } + db->detach(attDb); + } +} + +bool QueryExecutor::handleRowCountingResults(quint32 asyncId, SqlQueryPtr results) +{ + if (resultsCountingAsyncId == 0) + return false; + + if (resultsCountingAsyncId != asyncId) + return false; + + if (isExecutionInProgress()) // shouldn't be true, but just in case + return false; + + resultsCountingAsyncId = 0; + + context->totalRowsReturned = results->getSingleCell().toLongLong(); + context->totalPages = (int)qCeil(((double)(context->totalRowsReturned)) / ((double)getResultsPerPage())); + + emit resultsCountingFinished(context->rowsAffected, context->totalRowsReturned, context->totalPages); + + if (results->isError()) + { + notifyError(tr("An error occured while executing the count(*) query, thus data paging will be disabled. Error details from the database: %1") + .arg(results->getErrorText())); + } + + return true; +} +bool QueryExecutor::getNoMetaColumns() const +{ + return noMetaColumns; +} + +void QueryExecutor::setNoMetaColumns(bool value) +{ + noMetaColumns = value; +} + +SqlQueryPtr QueryExecutor::getResults() const +{ + return context->executionResults; +} + +bool QueryExecutor::wasSchemaModified() const +{ + return context->schemaModified; +} + +QList<DataType> QueryExecutor::resolveColumnTypes(Db* db, QList<QueryExecutor::ResultColumnPtr>& columns, bool noDbLocking) +{ + QSet<Table> tables; + for (ResultColumnPtr col : columns) + tables << Table(col->database, col->table); + + SchemaResolver resolver(db); + resolver.setNoDbLocking(noDbLocking); + + QHash<Table,SqliteCreateTablePtr> parsedTables; + SqliteCreateTablePtr createTable; + for (const Table& t : tables) + { + createTable = resolver.getParsedObject(t.getDatabase(), t.getTable(), SchemaResolver::TABLE).dynamicCast<SqliteCreateTable>(); + if (!createTable) + { + qWarning() << "Could not resolve columns of table" << t.getTable() << "while quering datatypes for queryexecutor columns."; + continue; + } + parsedTables[t] = createTable; + } + + QList<DataType> datatypeList; + Table t; + SqliteCreateTable::Column* parsedCol = nullptr; + for (ResultColumnPtr col : columns) + { + t = Table(col->database, col->table); + if (!parsedTables.contains(t)) + { + datatypeList << DataType(); + continue; + } + + parsedCol = parsedTables[t]->getColumn(col->column); + if (!parsedCol || !parsedCol->type) + { + datatypeList << DataType(); + continue; + } + + datatypeList << parsedCol->type->toDataType(); + } + return datatypeList; +} + +bool QueryExecutor::getAsyncMode() const +{ + return asyncMode; +} + +void QueryExecutor::setAsyncMode(bool value) +{ + asyncMode = value; +} + +void QueryExecutor::setPreloadResults(bool value) +{ + preloadResults = value; +} + +bool QueryExecutor::getExplainMode() const +{ + return explainMode; +} + +void QueryExecutor::setExplainMode(bool value) +{ + explainMode = value; +} + + +void QueryExecutor::error(int code, const QString& text) +{ + emit executionFailed(code, text); +} + +Db* QueryExecutor::getDb() const +{ + return db; +} + +void QueryExecutor::setDb(Db* value) +{ + if (db) + disconnect(db, SIGNAL(asyncExecFinished(quint32,SqlQueryPtr)), this, SLOT(dbAsyncExecFinished(quint32,SqlQueryPtr))); + + db = value; + + if (db) + connect(db, SIGNAL(asyncExecFinished(quint32,SqlQueryPtr)), this, SLOT(dbAsyncExecFinished(quint32,SqlQueryPtr))); +} + +bool QueryExecutor::getSkipRowCounting() const +{ + return skipRowCounting; +} + +void QueryExecutor::setSkipRowCounting(bool value) +{ + skipRowCounting = value; +} + +QString QueryExecutor::getOriginalQuery() const +{ + return originalQuery; +} + +int qHash(QueryExecutor::EditionForbiddenReason reason) +{ + return static_cast<int>(reason); +} + +int qHash(QueryExecutor::ColumnEditionForbiddenReason reason) +{ + return static_cast<int>(reason); +} + +int QueryExecutor::getDataLengthLimit() const +{ + return dataLengthLimit; +} + +void QueryExecutor::setDataLengthLimit(int value) +{ + dataLengthLimit = value; +} + +bool QueryExecutor::isRowCountingRequired() const +{ + return context->rowsCountingRequired; +} + +QString QueryExecutor::getCountingQuery() const +{ + return context->countingQuery; +} + +int QueryExecutor::getResultsPerPage() const +{ + return resultsPerPage; +} + +void QueryExecutor::setResultsPerPage(int value) +{ + resultsPerPage = value; +} + +int QueryExecutor::getPage() const +{ + return page; +} + +void QueryExecutor::setPage(int value) +{ + page = value; +} + +bool QueryExecutor::isExecutionInProgress() +{ + QMutexLocker executionLock(&executionMutex); + return executionInProgress; +} + +QueryExecutor::Sort::Sort() +{ +} + +QueryExecutor::Sort::Sort(QueryExecutor::Sort::Order order, int column) + : order(order), column(column) +{ +} + +QueryExecutor::Sort::Sort(Qt::SortOrder order, int column) + : column(column) +{ + switch (order) + { + case Qt::AscendingOrder: + this->order = ASC; + break; + case Qt::DescendingOrder: + this->order = DESC; + break; + default: + this->order = NONE; + qWarning() << "Invalid sort order passed to QueryExecutor::setSortOrder():" << order; + break; + } +} + +Qt::SortOrder QueryExecutor::Sort::getQtOrder() const +{ + // The column should be checked first for being > -1. + if (order == QueryExecutor::Sort::DESC) + return Qt::DescendingOrder; + + return Qt::AscendingOrder; +} + +QueryExecutor::SortList QueryExecutor::getSortOrder() const +{ + return sortOrder; +} + +void QueryExecutor::setSortOrder(const SortList& value) +{ + sortOrder = value; +} + +int operator==(const QueryExecutor::SourceTable& t1, const QueryExecutor::SourceTable& t2) +{ + return t1.database == t2.database && t1.table == t2.table && t1.alias == t2.alias; +} + +int qHash(QueryExecutor::SourceTable sourceTable) +{ + return qHash(sourceTable.database + "." + sourceTable.table + "/" + sourceTable.alias); +} + diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h new file mode 100644 index 0000000..c4a3e4d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutor.h @@ -0,0 +1,1369 @@ +#ifndef QUERYEXECUTOR_H +#define QUERYEXECUTOR_H + +#include "db/db.h" +#include "parser/token.h" +#include "selectresolver.h" +#include "coreSQLiteStudio_global.h" +#include "common/bistrhash.h" +#include "datatype.h" +#include <QObject> +#include <QHash> +#include <QMutex> +#include <QRunnable> + +/** @file */ + +class Parser; +class SqliteQuery; +class QueryExecutorStep; +class DbPlugin; + +/** + * @brief Advanced SQL query execution handler. + * + * QueryExecutor is an advanced SQL query execution handler, which lets you execute any query (with subqueries, joins, etc) + * and is capable of providing meta information about returned data, such as ROWID for all rows and columns, + * data sources (database, table and column) for every column, rows affected, total rows number for query, etc. + * All of this available for both SQLite versions: 2 and 3. + * + * Queries are executed asynchronously. To handle result a lambda function can be used (or any function pointer), + * or manual tracking of asynchronous execution ID and signals from this class. Function pointers and lambdas + * are recommended way to handle results. + * + * It also allows you to: + * <ul> + * <li>programatically define sorting on desired column.</li> + * <li>define result rows paging (page size and queried page)</li> + * <li>refer other databases by their symbolic name and they will be attached and detached on the fly</li> + * <li>define maximum cell data size (in bytes), so you won't read too much data at once</li> + * </ul> + * + * Total number of result rows is counted by a separate call to the database (using <tt>SELECT count(*) ...</tt>) + * and its result is provided later, which is signalized by signal resultsCountingFinished(). Row counting can + * be disabled with setSkipRowCounting(). See "Counting query" section below for details. + * + * The simplest use case would be: + * @code + * Db* db = getDb(); + * QueryExecutor *executor = new QueryExecutor(db, "SELECT * FROM table"); + * executor->exec([=](SqlQueryPtr results) + * { + * if (results->isError()) + * { + * qCritical() << "Error " << results->getErrorCode() << ": " << results->getErrorText() << "\n"; + * return; + * } + * qDebug() << results->valueList(); + * } + * @endcode + * + * Unless you want some of QueryExecutor's special features, it's recommended to use + * Db::exec() and Db::asyncExec(), because while QueryExecutor is powerful, it also does lots of thing underneeth + * you may not need at all. + * + * \note This class is used in SQL editor window (SqlQueryModel) to execute queries entered by the user. + * + * \section smart_simple_sec "smart mode" vs "simple mode" + * + * Documentation of this class references many times to "smart mode" and "simple mode" expressions. + * The "smart mode" means that the QueryExecutor was able to parse the input query, modify it for its needs + * (add some meta-information columns, etc) and executed modified query successfully. + * When the "smart mode" fails (which should be rare), the "simple mode" steps in as a fallback strategy. + * The "simple mode" doesn't modify input query, just directly executes in on the database + * and then QueryExecutor tries to extract as much meta-information from "simple mode" as it can (which is not much). + * + * The "simple mode" also doesn't apply any paging (see QueryExecutor::setPage()), nor data size limits + * (see QueryExecutor::setDataLengthLimit()). + * + * The meta-information is all the data from the query that is not the essential data requested in the input query. + * That is full description on all requested columns (their source tables, databases, data types), + * ROWID value for all returned data rows, and more... + * + * \section counting_query Counting query + * + * QueryExecutor can split results into pages. In such cases, results are not all read, instead they are limited + * at SQL level with LIMIT and OFFSET keywords. Because of that it is impossible to tell how many rows + * would actualy be returned if there were no limit keywords. + * + * To deal with it the QueryExecutor makes extra query execution, which happens asynchronously to the main query + * execution. This extra execution starts just after the main query execution has finished (with success). + * This extra query (aka "Counting query") is made of original query wrapped with: + * @code + * SELECT count(*) FROM (original_query) + * @endcode + * This way QueryExecutor know the true number of rows to be retuend by the query. + * + * Since this extra query execution takes some extra time, this is performed asynchronously and only after + * successful execution of the main query. If you need to work with QueryExecutor::getTotalRowsReturned(), + * wait for the QueryExecutor::resultsCountingFinished() signal first. + * + * Row counting query execution can be disabled with QueryExecutor::setSkipRowCounting(), + */ +class API_EXPORT QueryExecutor : public QObject, public QRunnable +{ + Q_OBJECT + + public: + /** + * @brief General reasons for which results data cannot be edited. + */ + enum class EditionForbiddenReason + { + NOT_A_SELECT, /**< Executed query was not a SELECT. Only SELECT results can be edited. */ + SMART_EXECUTION_FAILED /**< + * QueryExecutor could not perform "smart" execution, + * which means that it was unable to gather meta information + * about returned data and therefore it cannot tell what are ROWIDs + * or data sources for each column. Still it was able to perform + * simple (direct, without query modifications) execution + * and it returned results, so they can be presented to the user, + * but not edited. + * + * This happens usually when there's a but in SQLiteStudio, + * which caused - for example - error during query parsing by Parser, + * or other query syntax issues, that wasn't handled correctly + * by SQLiteStudio. + */ + }; + + /** + * @brief Per-column reasons for which the data in the column cannot be edited. + */ + enum class ColumnEditionForbiddenReason + { + COMPOUND_SELECT, /**< + * The data cell comes from compound SELECT (UNION, EXCEPT, INTERSECT), + * which makes it problematic to SQLiteStudio to find out to which table + * does the particular row belong to. + * + * It might be resolved in future SQLiteStudio versions and this enum value + * would disappear then. + */ + GROUPED_RESULTS, /**< + * The data cell comes from SELECT with aggregated results, therefore it's + * hard to hard what were ROWIDs of each row in the results. + * + * It might be resolved in future SQLiteStudio versions and this enum value + * would disappear then. + */ + DISTINCT_RESULTS, /**< + * The data cell comes from SELECT DISTINCT clause, therefore extracting + * ROWIDs from the results is impossible, becasuse querying ROWID would + * make every row unique, therefore DISTINCT would not remove any rows, + * even the rest of the data (which matters to the user) would not be + * unique and should have been removed by the DISTINCT keyword. + * + * Because of that, SQLiteStudio doesn't extract ROWIDs for DISTINCT + * queries, so the results are accurate, but in consequence, + * the data cannot be edited. + */ + EXPRESSION, /**< + * The data cell is a result of a formula, function or other expression, + * which is not a direct data source, therefore it's impossible to change + * it's value. + */ + SYSTEM_TABLE, /**< + * The data cell comes from system table (sqlite_*) and those tables cannot + * be edited. + */ + COMM_TAB_EXPR, /**< + * The data cell comes from system "WITH common-table-expression" SELECT + * statement and those tables cannot be edited for the same reasons as + * in COMPOUND_SELECT case. To learn about common table expression statement, + * see http://sqlite.org/lang_with.html + */ + }; + + /** + * @brief Sort order definition. + * + * QueryExecutor supports programmatic sort order definition. + * It supports smooth transition from/to Qt sorting direction enum + * and defines sorting column by its index (0-based). + */ + struct API_EXPORT Sort + { + /** + * @brief Sorting order. + */ + enum Order + { + ASC, /**< Ascending order */ + DESC, /**< Descending order */ + NONE /**< No sorting at all */ + }; + + /** + * @brief Default constructor with no sorting defined. + * + * Constructed object uses NONE as sorting order. + */ + Sort(); + + /** + * @brief Creates sort order with given order on given column. + * @param order Order to sort with. + * @param column 0-based column number. + */ + Sort(Order order, int column); + + /** + * @brief Creates sort order with given order on given column. + * @param order Qt typed sort order (Qt::AscendingOrder, Qt::DescendingOrder). + * @param column 0-based column number. + */ + Sort(Qt::SortOrder order, int column); + + /** + * @brief Gets Qt typed sort order. + * @return Sort order. + */ + Qt::SortOrder getQtOrder() const; + + /** + * @brief Sorting order. + */ + Order order = NONE; + + /** + * @brief 0-based column number to sort by. + */ + int column = -1; + }; + + typedef QList<Sort> SortList; + + /** + * @brief ResultColumn as represented by QueryExecutor. + * + * QueryExecutor has its own result column representation, because it provides more + * meta information on the column. + */ + struct API_EXPORT ResultColumn + { + /** + * @brief Database name that the result column comes from. + * + * It's an SQLite internal name of the database, which means it's either "main", or "temp", + * or symbolic name of registered database (as represented in the databases tree), + * or the name of any attached databases. + * + * Symbolic database name is provided when user used it in his query and SQLiteStudio attached + * it transparently. In that case the temporary name used for "ATTACH" statement would make no sense, + * because that database was detached automatically after the query execution finished. + * + * In case of databases attached manually by user, it's exactly the same string as used when executing + * "ATTACH" statement. + */ + QString database; + + /** + * @brief Table name that the result column comes from. + */ + QString table; + + /** + * @brief Table column name that the result column comes from. + */ + QString column; + + /** + * @brief Alias defined for the result column in the query. + */ + QString alias; + + /** + * @brief Table alias defined in the query. + * + * This is an alias defined in the query for the table that the result column comes from. + */ + QString tableAlias; + + /** + * @brief Name of the column as presented to user. + * + * This is the name of a column as SQLite would present it to the user. + * If the query requested just a column from table, it will be that column name. + * If the query resuested two columns with the same name, then the second column will get + * suffix ":1", next one would get suffix ":2", and so on. + * For expressions the display name is direct copy of the SQL code used to define the expression. + * + * If the alias was defined in query, than it's used for the display name instead of anything else. + */ + QString displayName; + + /** + * @brief QueryExecutor's internal alias for the column. + * + * This value has no sense outside of QueryExecutor. It's used by QueryExecutor to + * keep track of columns from subselects, etc. + */ + QString queryExecutorAlias; + + /** + * @brief Set of reasons for which column editing is denied. + * + * If the set is empty, it means that the column can be edited. + */ + QSet<ColumnEditionForbiddenReason> editionForbiddenReasons; + + /** + * @brief Flag indicating that the column is actually an expression. + * + * Column representing an expression is not just a column and it should not be ever wrapped with + * quoting wrapper ([], "", ``). Such a column is for example call to the SQL function. + * + * For regular columns this will be false. + */ + bool expression = false; + }; + + /** + * @brief Shared pointer to ResultColumn. + */ + typedef QSharedPointer<ResultColumn> ResultColumnPtr; + + /** + * @brief Combined row ID columns for tables in the query. + * + * Since version 3.8.2 SQLite introduced the "WITHOUT ROWID" clause. It allows tables to have no + * ROWID built-in. Such tables must have PRIMARY KEY defined, which does the job of the unique key + * for the table. + * + * This structure describes the unique key for the table, regardless if it's a regular ROWID, + * or if it's a PRIMARY KEY on a column, or if it's a multi-column PRIMARY KEY. + * + * You should always understand it as a set of PRIMARY KEY columns for given table. + * Referencing to that table using given columns will guarantee uniqueness of the row. + * + * In case of regular table (with no "WITHOUT ROWID" clause), there will be only one column + * defined in ResultRowIdColumn::columns and it will be named "ROWID". + */ + struct API_EXPORT ResultRowIdColumn + { + /** + * @brief Database name that the table with this row ID is in. + */ + QString database; + + /** + * @brief Table name that the row ID is for. + */ + QString table; + + /** + * @brief Table alias defined in the query. + * @see ResultColumn::tableAlias + */ + QString tableAlias; + + /** + * @brief Mapping from alias to real column. + * + * This is mapping from QueryExecutor's internal aliases for columns + * into primary key column names of the table that the result column comes from. + * + * If you want to get list of column names used for RowId, use values() on this member. + * If you want to get list of query executor aliases, use keys() on this member. + */ + QHash<QString,QString> queryExecutorAliasToColumn; + }; + + /** + * @brief Shared pointer to ResultRowIdColumn. + */ + typedef QSharedPointer<ResultRowIdColumn> ResultRowIdColumnPtr; + + /** + * @brief Table that was a data source for at least one column in the query. + */ + struct API_EXPORT SourceTable + { + /** + * @brief Table's database. + * + * Same rules apply as for ResultColumn::database. + */ + QString database; + + /** + * @brief Table name. + */ + QString table; + + /** + * @brief Table alias defined in query. + */ + QString alias; + }; + + /** + * @brief Shared pointer to SourceTable. + */ + typedef QSharedPointer<SourceTable> SourceTablePtr; + + /** + * @brief Query execution context. + * + * This class is used to share data across all executor steps. + * It also provides initial configuration for executor steps. + */ + struct Context + { + /** + * @brief Query string after last query step processing. + * + * Before any step was executed, this is the same as originalQuery. + * + * The processed query is the one that will be executed in the end, + * so any steps should apply their changes to this query. + * + * This string should be modified and updated from QueryExecutorStep implementations. + * + * You won't usually modify this string directly. Instead you will + * want to use one of 2 methods: + * <ul> + * <li>Modify tokens</li> - modify tokens of top level objects in parsedQueries + * and call QueryExecutorStep::updateQueries(). + * <li>Modify parsed objects</li> - modify logical structure and values of + * objects in parsedQueries, then call on those objects SqliteStatement::rebuildTokens() + * and finally call QueryExecutorStep::updateQueries. + * </ul> + * + * The parsedQueries are refreshed every time when QueryExecutor executes + * QueryExecutorParse step. + */ + QString processedQuery; + + /** + * @brief Number of milliseconds that query execution took. + * + * This is measured and set by QueryExecutorStepExecute step. + */ + qint64 executionTime = 0; + + /** + * @brief Number of rows affected by the query. + */ + qint64 rowsAffected = 0; + + /** + * @brief Total number of rows returned from query. + * + * It provides correct number for all queries, no matter if it's SELECT, PRAGMA, or other. + */ + qint64 totalRowsReturned = 0; + + /** + * @brief Total number of pages. + * + * If there's a lot of result rows, they are split to pages. + * There's always at least one page of results. + */ + int totalPages = 1; + + /** + * @brief Defines if row counting will be performed. + * + * In case of EXPLAIN or PRAGMA queries the number of result rows is not provided from + * SQLite (at least not from Qt's drivers for them). Instead we need to manually count + * number of rows. This is when this flag is set (it's done by QueryExecutor, + * no need to care about it). + */ + bool rowsCountingRequired = false; + + /** + * @brief Executing query in EXPLAIN mode. + * + * This is configuration parameter passed from QueryExecutor just before executing + * the query. It can be defined by QueryExecutor::setExplainMode(). + */ + bool explainMode = false; + + /** + * @brief Defines if row counting should be skipped. + * + * This is a configuration flag predefined by QueryExecutor just before executing starts. + * You can set it with QueryExecutor::setSkipRowCounting(). + * + * Row counting is done asynchronously, just after normal query execution is finished. + * It's done by executing yet another query, which is more or less an orginal query + * wrapped with "SELECT count(*) FROM (...)". + * + * Separate counting has to be done, because QueryExecutor adds LIMIT and OFFSET + * to SELECT queries for results paging. + * + * When counting is done, the resultsCountingFinished() signal is emitted. + */ + bool skipRowCounting = false; + + /** + * @brief Parameters for query execution. + * + * It's defined by setParam(). + */ + QHash<QString,QVariant> queryParameters; + + /** + * @brief Results handler function pointer. + * + * This serves the same purpose as in Db class. It's used for execution + * with results handled by provided function. See Db::QueryResultsHandler for details. + * + * It's defined by exec(). + */ + Db::QueryResultsHandler resultsHandler = nullptr; + + /** + * @brief List of queries parsed from input query string. + * + * List of parsed queries is updated each time the QueryExecutorParseQuery step + * is executed. When it's called is defined by QueryExecutor::executionChain. + */ + QList<SqliteQueryPtr> parsedQueries; + + /** + * @brief Results of executed query. + * + * This is results object defined by the final query execution. It means that the + * query executed passed all preprocessing steps and was executed in its final form. + * + * This member is defined by QueryExecutorExecute step. + */ + SqlQueryPtr executionResults; + + /** + * @brief Currently attached databases. + * + * This is a cross-context information about currently attached databases. + * As QueryExecutorAttaches step does attaching, other steps may need information + * about attached databases. It's a map of orginal_db_name_used to attached_name. + */ + BiStrHash dbNameToAttach; + + /** + * @brief Sequence used by executor steps to generate column names. + */ + int colNameSeq = 0; + + /** + * @brief List of reasons that editing results is forbidden for. + * + * Executor steps may decide that the results of query cannot be edited. + * In that case they add proper enum to this set. + */ + QSet<EditionForbiddenReason> editionForbiddenReasons; + + /** + * @brief Result columns that provide ROWID. + * + * QueryExecutorAddRowIds step adds those columns. There is one or more columns + * per data source table mentioned in the query. It depends on "WITHOUT ROWID" clause + * in CREATE TABLE of the source table. + */ + QList<ResultRowIdColumnPtr> rowIdColumns; + + /** + * @brief Result columns from the query. + * + * List of result columns, just like they would be returned from regular execution + * of the query. Column in this list are not just a names of those columns, + * they provide full meta information about every single column. + */ + QList<ResultColumnPtr> resultColumns; + + /** + * @brief Data source tables mentioned in the query. + * + * List of tables used as data source in the query. + */ + QSet<SourceTablePtr> sourceTables; + + /** + * @brief Query used for counting results. + * + * Filled with SQL to be used for results counting (even if counting is disabled). + * @see QueryExecutor::getCountingQuery() + */ + QString countingQuery; + + /** + * @brief Flag indicating results preloading. + * + * Causes flag Db::Flag::PRELOAD to be added to the query execution. + */ + bool preloadResults = false; + + /** + * @brief Tells if executed queries did modify database schema. + * + * This is defined by QueryExecutorDetectSchemaAlter step + * and can be accessed by QueryExecutor::wasSchemaModified(). + */ + bool schemaModified = false; + + /** + * @brief Forbids QueryExecutor to return meta columns. + * + * See QueryExecutor::noMetaColumns for details. + */ + bool noMetaColumns = false; + }; + + /** + * @brief Creates query executor, initializes internal context object. + * @param db Optional database. If not provided, it has to be defined later with setDb(). + * @param query Optional query to execute. If not provided, it has to be defined later with setQuery(). + * @param parent Parent QObject. + */ + QueryExecutor(Db* db = nullptr, const QString& query = QString::null, QObject *parent = 0); + + /** + * @brief Releases internal resources. + */ + ~QueryExecutor(); + + /** + * @brief Defined query to be executed. + * @param query SQL query string. + * + * The query string can actually be multiple queries separated with a semicolon, just like you would + * write multiple queries in the SQL Editor window. Query executor will handle that. + * + * The query can contain parameter placeholders (such as :param, \@param). To bind values to params + * use setParam(). + */ + void setQuery(const QString& query); + + /** + * @brief Executes the query. + * @param resultsHandler Optional handler function pointer, can be lambda function. See Db::QueryResultsHandler for details. + * + * While execution is asynchronous, the executor notifies about results by signals. + * In case of success emits executionFinished(), in case of error emits executionFailed(). + */ + void exec(Db::QueryResultsHandler resultsHandler = nullptr); + + /** + * @brief Interrupts current execution. + * + * Calls Db::asyncInterrupt() internally. + */ + void interrupt(); + + /** + * @brief Executes counting query. + * + * Executes (asynchronously) counting query for currently defined query. After execution is done, the resultsCountingFinished() + * signal is emitted. + * + * Counting query is made of original query wrapped with "SELECT count(*) FROM (original_query)". + * + * It is executed after the main query execution has finished. + */ + void countResults(); + + /** + * @brief Gets time of how long it took to execute query. + * @return Execution time in milliseconds. + * + * The execution time is number of milliseconds from begining of the query execution, till receiving of the results. + */ + qint64 getLastExecutionTime() const; + + /** + * @brief Gets number of rows affected by the query. + * @return Affected rows number. + * + * Rows affected are defined by DbPlugin implementation and are usually a number of rows modified by UPDATE statement, + * or deleted by DELETE statement, or inserted by INSERT statement. + */ + qint64 getRowsAffected() const; + + /** + * @brief Gets number of rows returned by the query. + * @return Number of rows. + * + * If QueryExecutor limits result rows number (if defined by setResultsPerPage()), the actual number of rows + * to be returned from query can be larger. This methods returns this true number of rows, + * that would be returned from the query. + * + * Calling this method makes sense only after resultsCountingFinished() was emitted, otherwise the value + * returned will not be accurate. + */ + qint64 getTotalRowsReturned() const; + + /** + * @brief Gets type of the SQL statement in the defined query. + * @param index Index of the SQL statement in the query (statements are separated by semicolon character), or -1 to get the last one. + * @return Type of the query. If there were no parsed queries in the context, or if passed index is out of range, + * then SqliteQueryType::UNDEFINED is returned. + */ + SqliteQueryType getExecutedQueryType(int index = -1); + + /** + * @brief Provides set of data source tables used in query. + * @return Set of tables. + */ + QSet<QueryExecutor::SourceTablePtr> getSourceTables() const; + + /** + * @brief Gets number of pages available. + * @return Number of pages. + * + * Since QueryExecutor organizes results of the query into pages, this method gives number of pages that is necessary + * to display all the data. In other words: "results of this method" - 1 = "last page index". + * + * Single page size is defined by setResultsPerPage(). + */ + int getTotalPages() const; + + /** + * @brief Gets ordered list of result columns. + * @return Result columns. + * + * See Context::resultColumns for details. + */ + QList<QueryExecutor::ResultColumnPtr> getResultColumns() const; + + /** + * @brief Gets list of ROWID columns. + * @return ROWID columns. + * + * Note, that this returns list of ROWID columns as entities. This means that for ROWID a single ROWID column + * can be actually couple of columns in the results. To count the ROWID columns offset for extracting + * data columns use getMetaColumnCount(). + * + * See Context::rowIdColumns for details. + */ + QList<QueryExecutor::ResultRowIdColumnPtr> getRowIdResultColumns() const; + + /** + * @brief Gives number of meta columns in the executed query. + * @return Number of the actual meta columns (such as ROWID columns) added to the executed query. + * + * This method should be used to find out the number of meta columns that were added to the begining + * of the result columns in the executed query. This way you can learn which column index use as a start + * for reading the actual data from the query. + * + * Meta columns are used by QueryExecutor to find more information about the query being executed + * (like ROWID of each row for example). + */ + int getMetaColumnCount() const; + + /** + * @brief Gets reasons for which editing results is forbidden. + * @return Set of reasons. + * + * See Context::editionForbiddenReasons for details. + */ + QSet<EditionForbiddenReason> getEditionForbiddenGlobalReasons() const; + + /** + * @brief Defines named bind parameter for the query. + * @param name Name of the parameter (without the : or @ prefix). + * @param value Value of the parameter. + * + * Positional (index oriented) parameters are not supported by the QueryExecutor. + * Always use named parameters with QueryExecutor. + */ + void setParam(const QString& name, const QVariant& value); + + /** + * @brief Replaces placeholder in the query. + * @param value Value to replace placeholder with. + * + * This works almost the same as QString::arg(), but it's specialized + * for SQL domain. It means that it will work only with numeric + * or string values passed in the parameter. If the value is numeric, + * then it just replaces a placeholder. If the value is a string, + * then it's wrapped with a quote character ('), if necessary, then + * it replaces a placeholder. + * + * Placeholders are the same as for QString::arg(): %1, %2, %3... + */ + void arg(const QVariant& value); + + /** + * @brief Gets currently defined database. + * @return Database object, or null pointer if not yet defined. + */ + Db* getDb() const; + + /** + * @brief Defines new database for query execution. + * @param value Database object. It should be open before calling exec(). + */ + void setDb(Db* value); + + /** + * @brief Gets original, not modified query. + * @return SQL query string. + */ + QString getOriginalQuery() const; + + /** + * @brief Gets data size limit. + * @return Number of bytes, or UTF-8/UTF-16 characters. + * + * See setDataLengthLimit() for details. + */ + int getDataLengthLimit() const; + + /** + * @brief Defines data size limit for results. + * @param value Number of bytes, or UTF-8/UTF-16 characters. + * + * Limit is not defined by default and in that case it's not applied + * to the query. To enable limit, set it to any positive number. + * To disable limit, set it to any negative number. + * + * When QueryExecutor prepares query for execution, it applies SUBSTR() + * to all result columns, so if the database has a huge value in some column, + * SQLiteStudio won't load 1000 rows with huge values - that would kill performance + * of the application. Instead it loads small chunk of every value. + * + * SqlQueryModel loads limited chunks of data and loads on-the-fly full cell values + * when user requests it (edits the cell, or views it in form editor). + * + * Parameter defined by this method is passed to SQLite's SUBSTR() function. + * As SQLite's documentation stand, numbers passed to that function are treated + * as number of bytes for non-textual data and for textual data they are number + * of characters (for UTF-8 and UTF-16 they can be made of more than 1 byte). + */ + void setDataLengthLimit(int value); + + // TODO manual row counting -> should be done by query executor already and returned in total rows + /** + * @brief Tests if manual row counting is required. + * @return True if manual counting is required. + * + * In case of some queries the getTotalRowsReturned() won't provide proper value. + * Then you will need to count result rows from the results object. + * + * It's okay, because this applies only for EXPLAIN and PRAGMA queries, + * which will never return any huge row counts. + */ + bool isRowCountingRequired() const; + + /** + * @brief Gets SQL query used for counting results. + * @return SQL query. + * + * This is the query used by countResults(). + */ + QString getCountingQuery() const; + + /** + * @brief Gets number of rows per page. + * @return Number of rows. + * + * By default results are not split to pages and this method will return -1. + */ + int getResultsPerPage() const; + + /** + * @brief Defines number of rows per page. + * @param value Number of rows. + * + * By default results are not split to pages. + * See setPage() for details on enabling and disabling paging. + */ + void setResultsPerPage(int value); + + /** + * @brief Gets current results page. + * @return Page index. + * + * Results page is 0-based index. It's always value between 0 and (getTotalPages() - 1). + * If results paging is disabled (see setResultsPerPage()), then this method + * will always return 0, as this is the first page (and in that case - the only one). + */ + int getPage() const; + + /** + * @brief Defines results page for next execution. + * @param value 0-based page index. + * + * If page value is negative, then paging is disabled. + * Any positive value or 0 enables paging and sets requested page of results to given page. + * + * If requested page value is greater than "getTotalPages() - 1", then no results will be returned. + * It's an invalid page value. + * If requested page value is lower then 0, then paging is disabled. + * + * Once the page is defined, the exec() must be called to get results + * from new defined page. + */ + void setPage(int value); + + /** + * @brief Tests if there's any execution in progress at the moment. + * @return true if the execution is in progress, or false otherwise. + */ + bool isExecutionInProgress(); + + /** + * @brief Gets sorting defined for executor. + * @return Sorting definition. + * + * See Sort for details. + */ + QueryExecutor::SortList getSortOrder() const; + + /** + * @brief Defines sorting for next query execution. + * @param value Sorting definition. + * + * Once the sorting definition is changed, the exec() must be called + * to receive results in new order. + */ + void setSortOrder(const QueryExecutor::SortList& value); + + /** + * @brief Tests if row counting is disabled. + * @return true if row counting will be skipped, or false otherwise. + * + * See Context::skipRowCounting for details. + */ + bool getSkipRowCounting() const; + + /** + * @brief Defines if executor should skip row counting. + * @param value New value for this parameter. + * + * See Context::skipRowCounting for details. + */ + void setSkipRowCounting(bool value); + + /** + * @brief Asynchronous executor processing in thread. + * + * This is an implementation of QRunnable::run(), so the QueryExecutor + * does it's own asynchronous work on object members. + */ + void run(); + + /** + * @brief Tests if query execution should be performed in EXPLAIN mode. + * @return true if the mode is enabled, or false otherwise. + */ + bool getExplainMode() const; + + /** + * @brief Defines EXPLAIN mode for next query execution. + * @param value true to enable EXPLAIN mode, or false to disable it. + * + * EXPLAIN mode means simply that the EXPLAIN keyword will be prepended + * to the query, except when the query already started with the EXPLAIN keyword. + * + * Once the mode is changed, the exec() must be called + * to receive "explain" results. + */ + void setExplainMode(bool value); + + /** + * @brief Defines results preloading. + * @param value true to preload results. + * + * Results preloading is disabled by default. See Db::Flag::PRELOAD for details. + */ + void setPreloadResults(bool value); + + bool getAsyncMode() const; + void setAsyncMode(bool value); + + SqlQueryPtr getResults() const; + bool wasSchemaModified() const; + + static QList<DataType> resolveColumnTypes(Db* db, QList<ResultColumnPtr>& columns, bool noDbLocking = false); + + bool getNoMetaColumns() const; + void setNoMetaColumns(bool value); + + private: + /** + * @brief Executes query. + * + * It's called from run(). This is the execution of query but called from different + * thread than exec() was called from. + */ + void execInternal(); + + /** + * @brief Raises execution error. + * @param code Error code. Can be either from SQLite error codes, or from SqlErrorCode. + * @param text Error message. + * + * This method is called when some of executor's preconditions has failed, or when SQLite + * execution raised an error. + */ + void error(int code, const QString& text); + + /** + * @brief Build chain of executor steps. + * + * Defines executionChain by adding new QueryExecutorStep descendants. + * Each step has its own purpose described in its class documentation. + * See inheritance hierarchy of QueryExecutorStep. + */ + void setupExecutionChain(); + + /** + * @brief Deletes executor step objects. + * + * Deletes all QueryExecutorStep objects from executionChain clears the list. + */ + void clearChain(); + + /** + * @brief Executes all steps from executor chain. + * + * The steps chain is defined by setupExecutionChain(). + * On execution error, the stepFailed() is called and the method returns. + */ + void executeChain(); + + /** + * @brief Executes the original, unmodified query. + * + * When smart execution (using steps chain) failed, then this method + * is a fallback. It executes original query passed to the executor. + * Given, that query was not modified, it cannot provide meta information, + * therefore results of that execution won't editable. + */ + void executeSimpleMethod(); + + /** + * @brief Handles results of simple execution. + * @param results Results object returned from Db. + * + * Checks results for errors and extracts basic meta information, + * such as rows affected, total result rows and time of execution. + * + * In case of success emits executionFinished(), in case of error emits executionFailed(). + */ + void simpleExecutionFinished(SqlQueryPtr results); + + /** + * @brief Tests whether the original query is a SELECT statement. + * @return true if the query is SELECT, or false otherwise. + * + * This method assumes that there was a problem with parsing the query with the Parser + * (and that's why we're using simple execution method) and so it tries to figure out + * a query type using other algorithms. + */ + bool simpleExecIsSelect(); + + /** + * @brief Releases resources acquired during query execution. + * + * Currently it just detaches databases attached for query execution needs (transparent + * database attaching feature). + */ + void cleanup(); + + /** + * @brief Extracts counting query results. + * @param asyncId Asynchronous ID of the counting query execution. + * @param results Results from the counting query execution. + * @return true if passed asyncId is the one for currently running counting query, or false otherwise. + * + * It's called from database asynchronous execution thread. The database might have executed + * some other acynchronous queries too, so this method checks if the asyncId is the expected one. + * + * Basicly this method is called a result of countResults() call. Extracts counted number of rows + * and stores it in query executor's context. + */ + bool handleRowCountingResults(quint32 asyncId, SqlQueryPtr results); + + /** + * @brief Query executor context object. + * + * Context object is shared across all execution steps. It's (re)initialized for every + * call to exec(). Initialization involves copying configuration parameters (such as sortOrder, + * explainMode, etc) from local members to the context. + * + * During steps execution the context is used to share information between steps. + * For example if one step modifies query in anyway, it should store updated query + * in Context::processedQuery. See QueryExecutorStep for details on possible methods + * for updating Context::processedQuery (you don't always have to build the whole processed + * query string by yourself). + * + * Finally, the context serves as a results container from all steps. QueryExecutor reads + * result columns metadata, total rows number, affected rows and other information from the context. + */ + Context* context = nullptr; + + /** + * @brief Database that all queries will be executed on. + * + * It can be passed in constructor or defined later with setDb(), but it cannot be null + * when calling exec(). The exec() will simply return with no execution performed + * and will log a warning. + */ + Db* db = nullptr; + + /** + * @brief Synchronization mutex for "execution in progress" state of executor. + * + * The state of "execution in progress" is the only value synchronized between threads. + * It makes sure that single QueryExecutor will execute only one query at the time. + */ + QMutex executionMutex; + + /** + * @brief Query to execute as defined by the user. + * + * This is a copy of original query provided by user to the executor. + */ + QString originalQuery; + + /** + * @brief Predefined number of results per page. + * + * See setResultsPerPage() for details. + */ + int resultsPerPage = -1; + + /** + * @brief Predefined results page index. + * + * See setPage() for details. + */ + int page = -1; + + /** + * @brief Predefined sorting order. + * + * There's no sorting predefined by default. If you want it, you have to apply it with setSortOrder(). + */ + SortList sortOrder; + + /** + * @brief Flag indicating that the execution is currently in progress. + * + * This variable is synchronized across threads and therefore you can always ask QueryExecutor + * if it's currently busy (with isExecutionInProgress()). + */ + bool executionInProgress = false; + + /** + * @brief Flag indicating that the most recent execution was made in "simple mode". + * + * This flag is set by executeSimpleMethod() method. See its documentation for details. + * The exec() resets this flag to false each time, but each time the smart execution fails, + * the executeSimpleMethod() is called and the flag is set to true. + */ + bool simpleExecution = false; + + /** + * @brief Flag indicating that the most recent execution was interrupted. + * + * This flag is set only if execution was interrupted by call to interrupt() on this class. + * If the execution was interrupted by another thread (which called sqlite_interrupt() + * or Db::interrupt()), then this flag is not set. + * + * This variable is tested at several stages of query execution in order to abort + * execution if the interruption was already requested. + */ + bool interrupted = false; + + /** + * @brief Flag indicating that the execution is performed in EXPLAIN mode. + * + * See setExplainMode() for details. + */ + bool explainMode = false; + + /** + * @brief Flag indicating that the row counting was disabled. + * + * See Context::skipRowCounting for details. + */ + bool skipRowCounting = false; + + /** + * @brief Defines results data size limit. + * + * See setDataLengthLimit() for details. + */ + int dataLengthLimit = -1; + + /** + * @brief Exact moment when query execution started. + * + * Expressed in number of milliseconds since 1970-01-01 00:00:00. + */ + qint64 simpleExecutionStartTime; + + /** + * @brief Asynchronous ID of query execution. + * + * Asynchronous ID returned from Db::asyncExec() for the query execution. + */ + quint32 asyncId = 0; + + /** + * @brief Asynchronous ID of counting query execution. + * + * Asynchronous ID returned from Db::asyncExec() for the counting query execution. + * See countResults() for details on counting query. + */ + quint32 resultsCountingAsyncId = 0; + + /** + * @brief Flag indicating results preloading. + * + * See Context::preloadResults. + */ + bool preloadResults = false; + + /** + * @brief Determinates if asynchronous mode is used. + * + * By default QueryExecutor runs in asynchronous mode (in another thread). + * You can set this to false to make exec() work synchronously, on calling thread. + */ + bool asyncMode = true; + + /** + * @brief Defines if the QueryExecutor will provide meta columns in the results. + * + * Set to true to forbid providing meta columns, or leave as false to let QueryExecutor + * provide meta columns. + * + * Meta columns are additional columns that are not part of the query that was passed to the executor. + * Those are for example ROWID columns (currently those are the only meta columns). + * + * You can always find out number of ROWID columns from getRowIdResultColumns(). + * + * Meta columns are placed always at the begining. + */ + bool noMetaColumns = false; + + /** + * @brief Chain of executor steps. + * + * Executor step list is set up by setupExecutionChain() and cleaned up after + * execution is finished. Steps are executed in order they appear in this list. + * + * Steps are executed one by one and if any of them raises the error, + * execution stops and error from QueryExecutor is raised (with executionFailed() signal). + */ + QList<QueryExecutorStep*> executionChain; + + /** + * @brief Execution results handler. + * + * This member keeps address of function that was defined for handling results. + * It is defined only if exec() method was called with the handler function argument. + * + * Results handler function is evaluated once the query execution has finished + * with success. It's not called on failure. + */ + Db::QueryResultsHandler resultsHandler = nullptr; + + signals: + /** + * @brief Emitted on successful query execution. + * @param results Results from query execution. + * + * It's emitted at the very end of the whole query execution process + * and only on successful execution. It doesn't matter if the execution was + * performed in "smart mode" or "simple mode". + */ + void executionFinished(SqlQueryPtr results); + + /** + * @brief Emitted on failed query execution. + * @param code Error code. + * @param errorMessage Error message. + * + * It doesn't matter if the execution was performed in "smart mode" or "simple mode". + */ + void executionFailed(int code, QString errorMessage); + + /** + * @brief Emitted on successful counting query execution. + * @param rowsAffected Rows affected by the original query. + * @param rowsReturned Rows returned by the original query. + * @param totalPages Number of pages needed to represent all rows given the value defined with setResultsPerPage(). + * + * This signal is emitted only when setSkipRowCounting() was set to false (it is by default) + * and the counting query execution was successful. + * + * The counting query actually counts only \p rowsReturned, while \p rowsAffected and \p totalPages + * are extracted from original query execution. + */ + void resultsCountingFinished(quint64 rowsAffected, quint64 rowsReturned, int totalPages); + + public slots: + /** + * @brief Executes given query. + * @param originalQuery to be executed. + * + * This is a shorthand for: + * @code + * queryExecutor->setQuery(query); + * queryExecutor->exec(); + * @endcode + * + * This exec() version is a SLOT, while the other exec() method is not. + */ + void exec(const QString& originalQuery); + + private slots: + /** + * @brief Handles asynchronous database execution results. + * @param asyncId Asynchronous ID of the execution. + * @param results Results from the execution. + * + * QueryExecutor checks whether the \p asyncId belongs to the counting query execution, + * or the simple execution. + * Dispatches query results to a proper handler method. + */ + void dbAsyncExecFinished(quint32 asyncId, SqlQueryPtr results); + + /** + * @brief Calledn when an executor step has failed with its job. + * + * An executor step reported an error. "Smart execution" failed and now the executor will try + * to execute query with a "simple method". + */ + void stepFailed(QueryExecutorStep *currentStep); + + /** + * @brief Cleanup routines after successful query execution. + * @param results Query results. + * + * Releases resources that are no longer used. Currently simply calls cleanup(). + */ + void cleanupAfterExecFinished(SqlQueryPtr results); + + /** + * @brief Cleanup routines after failed query execution. + * @param code Error code. + * @param errorMessage Error message. + * + * Releases resources that are no longer used. Currently simply calls cleanup(). + */ + void cleanupAfterExecFailed(int code, QString errorMessage); + + /** + * @brief Called when the currently set db is about to be destroyed. + * + * Deletes results from the Context if there were any, so they are not referencing anything + * from deleted Db. Keeping results is dangerous, becuase the Db driver (plugin) is most likely to + * be unloaded soon and we won't be able to call results destructor. + */ + void cleanupBeforeDbDestroy(Db* dbToBeUnloaded, DbPlugin* plugin); +}; + +int qHash(QueryExecutor::EditionForbiddenReason reason); +int qHash(QueryExecutor::ColumnEditionForbiddenReason reason); +int qHash(QueryExecutor::SourceTable sourceTable); +int operator==(const QueryExecutor::SourceTable& t1, const QueryExecutor::SourceTable& t2); + +#endif // QUERYEXECUTOR_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp new file mode 100644 index 0000000..55203e4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.cpp @@ -0,0 +1,216 @@ +#include "queryexecutoraddrowids.h" +#include "parser/ast/sqliteselect.h" +#include "selectresolver.h" +#include "common/utils_sql.h" +#include "parser/ast/sqlitecreatetable.h" +#include "schemaresolver.h" +#include <QDebug> + +bool QueryExecutorAddRowIds::exec() +{ + if (context->noMetaColumns) + return true; + + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + return true; + + if (select->coreSelects.size() > 1) + return true; + + if (select->coreSelects.first()->distinctKw || select->coreSelects.first()->valuesMode) + return true; + + bool ok = true; + addRowIdForTables(select.data(), ok); + + if (!ok) + { + qCritical() << "Error in QueryExecutorAddRowIds step."; + return false; + } + + // ...and putting it into parsed query, then update processed query + select->rebuildTokens(); + updateQueries(); + + return true; +} + +QHash<SelectResolver::Table,QHash<QString,QString>> QueryExecutorAddRowIds::addRowIdForTables(SqliteSelect* select, bool& ok, bool isTopSelect) +{ + QHash<SelectResolver::Table,QHash<QString,QString>> rowIdColsMap; + if (select->coreSelects.size() > 1) + return rowIdColsMap; + + SqliteSelect::Core* core = select->coreSelects.first(); + + if (core->groupBy.size() > 0) + return rowIdColsMap; + + if (core->distinctKw) + return rowIdColsMap; + + // Go trough subselects to add ROWID result columns there and collect rowId mapping to use here. + foreach (SqliteSelect* subSelect, getSubSelects(core)) + { + rowIdColsMap.unite(addRowIdForTables(subSelect, ok, false)); + if (!ok) + return rowIdColsMap; + } + + // Getting all tables we need to get ROWID for + SelectResolver resolver(db, select->tokens.detokenize()); + resolver.resolveMultiCore = false; // multicore subselects result in not editable columns, skip them + + QSet<SelectResolver::Table> tables = resolver.resolveTables(core); + foreach (const SelectResolver::Table& table, tables) + { + if (table.flags & (SelectResolver::FROM_COMPOUND_SELECT | SelectResolver::FROM_DISTINCT_SELECT | SelectResolver::FROM_GROUPED_SELECT)) + continue; // we don't get ROWID from compound, distinct or aggregated subselects + + if (!addResultColumns(core, table, rowIdColsMap, isTopSelect)) + { + ok = false; + return rowIdColsMap; + } + } + return rowIdColsMap; +} + +QList<SqliteSelect*> QueryExecutorAddRowIds::getSubSelects(SqliteSelect::Core* core) +{ + QList<SqliteSelect*> selects; + if (!core->from) + return selects; + + if (core->from->singleSource && core->from->singleSource->select) + selects << core->from->singleSource->select; + + foreach (SqliteSelect::Core::JoinSourceOther* otherSource, core->from->otherSources) + { + if (!otherSource->singleSource->select) + continue; + + selects << otherSource->singleSource->select; + } + + return selects; +} + +QHash<QString,QString> QueryExecutorAddRowIds::getNextColNames(const SelectResolver::Table& table) +{ + QHash<QString,QString> colNames; + + SchemaResolver resolver(db); + SqliteQueryPtr query = resolver.getParsedObject(table.database, table.table, SchemaResolver::TABLE); + SqliteCreateTablePtr createTable = query.dynamicCast<SqliteCreateTable>(); + if (!createTable) + { + qCritical() << "No CREATE TABLE object after parsing and casting in QueryExecutorAddRowIds::getNextColNames(). Cannot provide ROWID columns."; + return colNames; + } + + if (createTable->withOutRowId.isNull()) + { + // It's a regular ROWID table + colNames[getNextColName()] = "ROWID"; + return colNames; + } + + SqliteStatement* primaryKey = createTable->getPrimaryKey(); + if (!primaryKey) + { + qCritical() << "WITHOUT ROWID table, but could not find // Co PRIMARY KEY in QueryExecutorAddRowIds::getNextColNames()."; + return colNames; + } + + SqliteCreateTable::Column::Constraint* columnConstr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(primaryKey); + if (columnConstr) + { + colNames[getNextColName()] = dynamic_cast<SqliteCreateTable::Column*>(columnConstr->parentStatement())->name; + return colNames; + } + + SqliteCreateTable::Constraint* tableConstr = dynamic_cast<SqliteCreateTable::Constraint*>(primaryKey); + if (tableConstr) + { + foreach (SqliteIndexedColumn* idxCol, tableConstr->indexedColumns) + colNames[getNextColName()] = idxCol->name; + + return colNames; + } + + qCritical() << "PRIMARY KEY that is neither table or column constraint. Should never happen (QueryExecutorAddRowIds::getNextColNames())."; + return colNames; +} + +bool QueryExecutorAddRowIds::addResultColumns(SqliteSelect::Core* core, const SelectResolver::Table& table, + QHash<SelectResolver::Table,QHash<QString,QString>>& rowIdColsMap, bool isTopSelect) +{ + QHash<QString, QString> executorToRealColumns; + if (rowIdColsMap.contains(table)) + { + executorToRealColumns = rowIdColsMap[table]; // we already have resCol names from subselect + } + else + { + executorToRealColumns = getNextColNames(table); + rowIdColsMap[table] = executorToRealColumns; + } + + if (executorToRealColumns.size() == 0) + { + qCritical() << "No result column defined for a table in QueryExecutorAddRowIds::addResCols()."; + return false; + } + + QHashIterator<QString,QString> it(executorToRealColumns); + while (it.hasNext()) + { + it.next(); + if (!addResultColumns(core, table, it.key(), it.value())) + return false; + } + + if (isTopSelect) + { + // Query executor result column description + QueryExecutor::ResultRowIdColumnPtr queryExecutorResCol = QueryExecutor::ResultRowIdColumnPtr::create(); + queryExecutorResCol->database = table.database; + queryExecutorResCol->table = table.table; + queryExecutorResCol->tableAlias = table.alias; + queryExecutorResCol->queryExecutorAliasToColumn = executorToRealColumns; + context->rowIdColumns << queryExecutorResCol; + } + + return true; +} + +bool QueryExecutorAddRowIds::addResultColumns(SqliteSelect::Core* core, const SelectResolver::Table& table, const QString& queryExecutorColumn, + const QString& realColumn) +{ + SqliteSelect::Core::ResultColumn* resCol = new SqliteSelect::Core::ResultColumn(); + resCol->setParent(core); + + resCol->expr = new SqliteExpr(); + resCol->expr->setParent(resCol); + + resCol->expr->initId(realColumn); + if (!table.alias.isNull()) + { + resCol->expr->table = table.alias; + } + else + { + if (!table.database.isNull()) + resCol->expr->database = table.database; + + resCol->expr->table = table.table; + } + resCol->asKw = true; + resCol->alias = queryExecutorColumn; + + core->resultColumns.insert(0, resCol); + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.h new file mode 100644 index 0000000..a5431fa --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutoraddrowids.h @@ -0,0 +1,81 @@ +#ifndef QUERYEXECUTORADDROWIDS_H +#define QUERYEXECUTORADDROWIDS_H + +#include "queryexecutorstep.h" +#include "parser/token.h" + +/** + * @brief Adds ROWID to result columns. + * + * This step adds ROWID to result column list for each table mentioned in result columns. + * For WITHOUT ROWID tables there might be several columns per table. + * + * It also provides list of added columns in QueryExecutor::Context::rowIdColumns. + */ +class QueryExecutorAddRowIds : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); + + private: + /** + * @brief Adds ROWID columns to the result columns and the context. + * @param core SELECT's core that keeps result columns. + * @param table Table we want to add ROWID columns for. + * @param rowIdColsMap Map of ROWID columns from inner selects (built with addRowIdForTables()). + * @param isTopSelect True only for top-most select to store rowid columns in context only for the final list of columns. + * @return true on success, false on any failure. + * + * Finds columns representing ROWID for the \p table and adds them to result columns and to the context. + */ + bool addResultColumns(SqliteSelect::Core* core, const SelectResolver::Table& table, + QHash<SelectResolver::Table, QHash<QString, QString> >& rowIdColsMap, bool isTopSelect); + + /** + * @brief Adds the column to result columns list. + * @param core SELECT's core that keeps the result columns. + * @param table Table that the column is for. + * @param queryExecutorColumn Alias name for the column that will be used by the query executor. + * @param realColumn Actual column name in the database. + * @return true on success, false on any failure. + * + * Adds given column to the result column list in the SELECT statement. + */ + bool addResultColumns(SqliteSelect::Core* core, const SelectResolver::Table& table, const QString& queryExecutorColumn, + const QString& realColumn); + + /** + * @brief Adds all necessary ROWID columns to result columns. + * @param select SELECT that keeps result columns. + * @param ok[out] Reference to a flag for telling if the method was executed successly (true), or not (false). + * @param isTopSelect True only for top-most select call of this method, so the list of rowid columns is stored + * only basing on this select (and rowid mappind for it), not all subqueries. This is to avoid redundant rowid columns in context + * in case of subselects. + * @return Mapping for every table mentioned in the SELECT with map of ROWID columns for the table. + * The column map is a query_executor_alias to real_database_column_name. + * + * Adds ROWID columns for all tables mentioned in result columns of the \p select. + */ + QHash<SelectResolver::Table,QHash<QString,QString>> addRowIdForTables(SqliteSelect* select, bool& ok, bool isTopSelect = true); + + /** + * @brief Extracts all subselects used in the SELECT. + * @param core SELECT's core to extract subselects from. + * @return List of subselects. + * + * Extracts only subselects of given select core, but not recurrently. + * As it works on the SELECT's core, it means that it's not applicable for compound selects. + */ + QList<SqliteSelect*> getSubSelects(SqliteSelect::Core* core); + + /** + * @brief Provides list of columns representing ROWID for the table. + * @param table Table to get ROWID columns for. + * @return Map of query executor alias to real database column name. + */ + QHash<QString, QString> getNextColNames(const SelectResolver::Table& table); +}; + +#endif // QUERYEXECUTORADDROWIDS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorattaches.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorattaches.cpp new file mode 100644 index 0000000..0df0445 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorattaches.cpp @@ -0,0 +1,16 @@ +#include "queryexecutorattaches.h" +#include "dbattacher.h" +#include "sqlitestudio.h" +#include <QScopedPointer> + +bool QueryExecutorAttaches::exec() +{ + QScopedPointer<DbAttacher> attacher(SQLITESTUDIO->createDbAttacher(db)); + if (!attacher->attachDatabases(context->parsedQueries)) + return false; + + context->dbNameToAttach = attacher->getDbNameToAttach(); + updateQueries(); + + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorattaches.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorattaches.h new file mode 100644 index 0000000..b6346fb --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorattaches.h @@ -0,0 +1,28 @@ +#ifndef QUERYEXECUTORATTACHES_H +#define QUERYEXECUTORATTACHES_H + +#include "queryexecutorstep.h" +#include "parser/token.h" +#include <QObject> + +/** + * @brief Checks for any databases required to attach and attaches them. + * + * If the query contains any name that is identified to be name of database registered in DbManager, + * then that database gets attached to the current database (the one that we execute query on) + * and its attach name is stored in the query executor context, so all attached databases + * can be later detached. + * + * This step accomplishes a transparent database attaching feature of SQLiteStudio. + * + * @see DbAttacher + */ +class QueryExecutorAttaches : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); +}; + +#endif // QUERYEXECUTORATTACHES_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.cpp new file mode 100644 index 0000000..fceea3f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.cpp @@ -0,0 +1,99 @@ +#include "queryexecutorcellsize.h" +#include <QDebug> + +bool QueryExecutorCellSize::exec() +{ + if (queryExecutor->getDataLengthLimit() < 0) + return true; + + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + return true; + + foreach (SqliteSelect::Core* core, select->coreSelects) + { + if (!applyDataLimit(select.data(), core)) + return false; + } + + updateQueries(); + return true; +} + +bool QueryExecutorCellSize::applyDataLimit(SqliteSelect* select, SqliteSelect::Core* core) +{ + if (core->tokensMap["selcollist"].size() == 0) + { + qCritical() << "No 'selcollist' in Select::Core. Cannot apply cell size limits."; + return false; + } + + bool first = true; + TokenList tokens; + + foreach (const QueryExecutor::ResultRowIdColumnPtr& col, context->rowIdColumns) + { + if (!first) + tokens += getSeparatorTokens(); + + tokens += getNoLimitTokens(col); + first = false; + } + + foreach (const QueryExecutor::ResultColumnPtr& col, context->resultColumns) + { + if (!first) + tokens += getSeparatorTokens(); + + tokens += getLimitTokens(col); + first = false; + } + + // Wrapping original select with new select with limited columns + select->tokens = wrapSelect(select->tokens, tokens); + + return true; +} + +TokenList QueryExecutorCellSize::getLimitTokens(const QueryExecutor::ResultColumnPtr& resCol) +{ + TokenList newTokens; + newTokens << TokenPtr::create(Token::OTHER, "substr") + << TokenPtr::create(Token::PAR_LEFT, "(") + << TokenPtr::create(Token::OTHER, resCol->queryExecutorAlias) + << TokenPtr::create(Token::OPERATOR, ",") + << TokenPtr::create(Token::SPACE, " ") + << TokenPtr::create(Token::INTEGER, "1") + << TokenPtr::create(Token::OPERATOR, ",") + << TokenPtr::create(Token::SPACE, " ") + << TokenPtr::create(Token::INTEGER, QString::number(queryExecutor->getDataLengthLimit())) + << TokenPtr::create(Token::PAR_RIGHT, ")") + << TokenPtr::create(Token::SPACE, " ") + << TokenPtr::create(Token::KEYWORD, "AS") + << TokenPtr::create(Token::SPACE, " ") + << TokenPtr::create(Token::OTHER, resCol->queryExecutorAlias); + return newTokens; +} + +TokenList QueryExecutorCellSize::getNoLimitTokens(const QueryExecutor::ResultRowIdColumnPtr& resCol) +{ + TokenList newTokens; + bool first = true; + foreach (const QString& col, resCol->queryExecutorAliasToColumn.keys()) + { + if (!first) + newTokens += getSeparatorTokens(); + + newTokens << TokenPtr::create(Token::OTHER, col); + first = false; + } + return newTokens; +} + +TokenList QueryExecutorCellSize::getSeparatorTokens() +{ + TokenList newTokens; + newTokens << TokenPtr::create(Token::OPERATOR, ","); + newTokens << TokenPtr::create(Token::SPACE, " "); + return newTokens; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.h new file mode 100644 index 0000000..c174c69 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcellsize.h @@ -0,0 +1,62 @@ +#ifndef QUERYEXECUTORCELLSIZE_H +#define QUERYEXECUTORCELLSIZE_H + +#include "queryexecutorstep.h" + +/** + * @brief Applies per-cell byte size limit to the query. + * + * Size of data extracted for each cell is limited in order to avoid huge memory use + * when the database contains column with like 500MB values per row and the query + * returns for example 100 rows. + * + * This is accomplished by wrapping all result columns (except ROWID columns) with substr() SQL function. + * + * SQLiteStudio limits each column to SqlQueryModel::cellDataLengthLimit when displaying + * data in SqlQueryView. + * + * This feature is disabled by default in QueryExecutor and has to be enabled by defining + * QueryExecutor::setDataLengthLimit(). + */ +class QueryExecutorCellSize : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); + + private: + /** + * @brief Applies limit function to all result columns in given SELECT. + * @param select Select that we want to limit. + * @param core Select's core that we want to limit. + * @return true on success, false on failure. + * + * This method is called for each core in the \p select. + */ + bool applyDataLimit(SqliteSelect* select, SqliteSelect::Core* core); + + /** + * @brief Generates tokens that will return limited value of the result column. + * @param resCol Result column to wrap. + * @return List of tokens. + */ + TokenList getLimitTokens(const QueryExecutor::ResultColumnPtr& resCol); + + /** + * @brief Generates tokens that will return unlimited value of the ROWID result column. + * @param resCol ROWID result column. + * @return List of tokens. + */ + TokenList getNoLimitTokens(const QueryExecutor::ResultRowIdColumnPtr& resCol); + + /** + * @brief Generates tokens representing result columns separator. + * @return List of tokens. + * + * Result columns separator tokens are just a period and a space. + */ + TokenList getSeparatorTokens(); +}; + +#endif // QUERYEXECUTORCELLSIZE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp new file mode 100644 index 0000000..02f90b2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.cpp @@ -0,0 +1,256 @@ +#include "queryexecutorcolumns.h" +#include "common/utils_sql.h" +#include "parser/parser.h" +#include "parser/parsererror.h" +#include <QDebug> + +// TODO need to test if attach name resolving works here + +bool QueryExecutorColumns::exec() +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + { + context->editionForbiddenReasons << QueryExecutor::EditionForbiddenReason::NOT_A_SELECT; + return true; + } + + // Resolving result columns of the select + SelectResolver resolver(db, queryExecutor->getOriginalQuery(), context->dbNameToAttach); + resolver.resolveMultiCore = true; + QList<SelectResolver::Column> columns = resolver.resolve(select.data()).first(); + + if (resolver.hasErrors()) + { + qWarning() << "SelectResolver could not resolve the SELECT properly:" << resolver.getErrors().join("\n"); + return false; + } + + if (columns.size() == 0) + { + qWarning() << "SelectResolver could not resolve any column. Probably wrong table name entered by user, or something like that."; + return false; + } + + // Deleting old result columns and defining new ones + SqliteSelect::Core* core = select->coreSelects.first(); + for (SqliteSelect::Core::ResultColumn* resCol : core->resultColumns) + delete resCol; + + core->resultColumns.clear(); + + // Count total rowId columns + int rowIdColCount = 0; + for (const QueryExecutor::ResultRowIdColumnPtr& rowIdCol : context->rowIdColumns) + rowIdColCount += rowIdCol->queryExecutorAliasToColumn.size(); + + // Defining result columns + QueryExecutor::ResultColumnPtr resultColumn; + SqliteSelect::Core::ResultColumn* resultColumnForSelect = nullptr; + bool isRowIdColumn = false; + int i = 0; + for (const SelectResolver::Column& col : columns) + { + // Convert column to QueryExecutor result column + resultColumn = getResultColumn(col); + + // Adding new result column to the query + isRowIdColumn = (i < rowIdColCount); + resultColumnForSelect = getResultColumnForSelect(resultColumn, col, isRowIdColumn); + if (!resultColumnForSelect) + return false; + + resultColumnForSelect->setParent(core); + core->resultColumns << resultColumnForSelect; + + if (!isRowIdColumn) + context->resultColumns << resultColumn; // store it in context for later usage by any step + + i++; + } + + // Update query + select->rebuildTokens(); + wrapWithAliasedColumns(select.data()); + updateQueries(); + +// qDebug() << context->processedQuery; + + return true; +} + +QueryExecutor::ResultColumnPtr QueryExecutorColumns::getResultColumn(const SelectResolver::Column &resolvedColumn) +{ + QueryExecutor::ResultColumnPtr resultColumn = QueryExecutor::ResultColumnPtr::create(); + if (resolvedColumn.type == SelectResolver::Column::OTHER) + { + resultColumn->editionForbiddenReasons << QueryExecutor::ColumnEditionForbiddenReason::EXPRESSION; + resultColumn->displayName = resolvedColumn.displayName; + resultColumn->column = resolvedColumn.column; + resultColumn->alias = resolvedColumn.alias; + resultColumn->expression = true; + resultColumn->queryExecutorAlias = getNextColName(); + } + else + { + if (isSystemTable(resolvedColumn.table)) + resultColumn->editionForbiddenReasons << QueryExecutor::ColumnEditionForbiddenReason::SYSTEM_TABLE; + + if (resolvedColumn.flags & SelectResolver::FROM_COMPOUND_SELECT) + resultColumn->editionForbiddenReasons << QueryExecutor::ColumnEditionForbiddenReason::COMPOUND_SELECT; + + if (resolvedColumn.flags & SelectResolver::FROM_GROUPED_SELECT) + resultColumn->editionForbiddenReasons << QueryExecutor::ColumnEditionForbiddenReason::GROUPED_RESULTS; + + if (resolvedColumn.flags & SelectResolver::FROM_DISTINCT_SELECT) + resultColumn->editionForbiddenReasons << QueryExecutor::ColumnEditionForbiddenReason::DISTINCT_RESULTS; + + resultColumn->database = resolvedColumn.originalDatabase; + resultColumn->table = resolvedColumn.table; + resultColumn->column = resolvedColumn.column; + resultColumn->tableAlias = resolvedColumn.tableAlias; + resultColumn->alias = resolvedColumn.alias; + resultColumn->displayName = resolvedColumn.displayName; + + if (isRowIdColumnAlias(resultColumn->alias)) + { + resultColumn->queryExecutorAlias = resultColumn->alias; + } + else + { + resultColumn->queryExecutorAlias = getNextColName(); + } + } + return resultColumn; +} + +SqliteSelect::Core::ResultColumn* QueryExecutorColumns::getResultColumnForSelect(const QueryExecutor::ResultColumnPtr& resultColumn, const SelectResolver::Column& col, bool rowIdColumn) +{ + SqliteSelect::Core::ResultColumn* selectResultColumn = new SqliteSelect::Core::ResultColumn(); + + QString colString = resultColumn->column; + if (!resultColumn->expression) + colString = wrapObjIfNeeded(colString, dialect); + + Parser parser(dialect); + SqliteExpr* expr = parser.parseExpr(colString); + if (!expr) + { + qWarning() << "Could not parse result column expr:" << colString; + if (parser.getErrors().size() > 0) + qWarning() << "The error was:" << parser.getErrors().first()->getFrom() << ":" << parser.getErrors().first()->getMessage(); + + return nullptr; + } + + expr->setParent(selectResultColumn); + selectResultColumn->expr = expr; + + if (!(col.flags & SelectResolver::FROM_ANONYMOUS_SELECT)) // anonymous subselect will result in no prefixes for result column + { + if (!resultColumn->tableAlias.isNull()) + { + selectResultColumn->expr->table = resultColumn->tableAlias; + } + else if (!resultColumn->table.isNull()) + { + if (!resultColumn->database.isNull()) + { + if (context->dbNameToAttach.containsLeft(resultColumn->database, Qt::CaseInsensitive)) + selectResultColumn->expr->database = context->dbNameToAttach.valueByLeft(resultColumn->database, Qt::CaseInsensitive); + else + selectResultColumn->expr->database = resultColumn->database; + } + + selectResultColumn->expr->table = resultColumn->table; + } + } + + if (!col.alias.isNull()) + { + selectResultColumn->asKw = true; + selectResultColumn->alias = col.alias; + } + else if (rowIdColumn || resultColumn->expression) + { + selectResultColumn->asKw = true; + selectResultColumn->alias = resultColumn->queryExecutorAlias; + } + + return selectResultColumn; +} + +QString QueryExecutorColumns::resolveAttachedDatabases(const QString &dbName) +{ + if (context->dbNameToAttach.containsRight(dbName, Qt::CaseInsensitive)) + return context->dbNameToAttach.valueByRight(dbName, Qt::CaseInsensitive); + + return dbName; +} + +bool QueryExecutorColumns::isRowIdColumnAlias(const QString& alias) +{ + foreach (QueryExecutor::ResultRowIdColumnPtr rowIdColumn, context->rowIdColumns) + { + if (rowIdColumn->queryExecutorAliasToColumn.keys().contains(alias)) + return true; + } + return false; +} + +void QueryExecutorColumns::wrapWithAliasedColumns(SqliteSelect* select) +{ + // Wrap everything in a surrounding SELECT and given query executor alias to all columns this time + TokenList sepTokens; + sepTokens << TokenPtr::create(Token::OPERATOR, ",") << TokenPtr::create(Token::SPACE, " "); + + bool first = true; + TokenList outerColumns; + for (const QueryExecutor::ResultRowIdColumnPtr& rowIdColumn : context->rowIdColumns) + { + for (const QString& alias : rowIdColumn->queryExecutorAliasToColumn.keys()) + { + if (!first) + outerColumns += sepTokens; + + outerColumns << TokenPtr::create(Token::OTHER, alias); + first = false; + } + } + + QStringList columnNamesUsed; + QString baseColName; + QString colName; + static_qstring(colNameTpl, "%1:%2"); + for (const QueryExecutor::ResultColumnPtr& resCol : context->resultColumns) + { + if (!first) + outerColumns += sepTokens; + + // If alias was given, we use it. If it was anything but expression, we also use its display name, + // because it's explicit column (no matter if from table, or table alias). + baseColName = QString(); + if (!resCol->alias.isNull()) + baseColName = resCol->alias; + else if (!resCol->expression) + baseColName = resCol->column; + + if (!baseColName.isNull()) + { + colName = baseColName; + for (int i = 1; columnNamesUsed.contains(colName, Qt::CaseInsensitive); i++) + colName = colNameTpl.arg(resCol->column, QString::number(i)); + + columnNamesUsed << colName; + outerColumns << TokenPtr::create(Token::OTHER, wrapObjIfNeeded(colName, dialect)); + outerColumns << TokenPtr::create(Token::SPACE, " "); + outerColumns << TokenPtr::create(Token::KEYWORD, "AS"); + outerColumns << TokenPtr::create(Token::SPACE, " "); + } + outerColumns << TokenPtr::create(Token::OTHER, resCol->queryExecutorAlias); + first = false; + } + + //QString t = outerColumns.detokenize(); // keeping it for debug purposes + select->tokens = wrapSelect(select->tokens, outerColumns); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.h new file mode 100644 index 0000000..3f90311 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcolumns.h @@ -0,0 +1,72 @@ +#ifndef QUERYEXECUTORCOLUMNS_H +#define QUERYEXECUTORCOLUMNS_H + +#include "queryexecutorstep.h" +#include "selectresolver.h" + +/** + * @brief Assigns unique alias names for all result columns. + * + * This step replaces result columns of the SELECT query. + * It's performed only if last query is the SELECT, otherwise it does nothing. + * + * It works on subselects first, then goes towards outer SELECTs. + * + * Star operator ("all columns") is replaced by list of columns and each column gets alias. + * + * If result column comes from subselect and the subselect was already covered by this step, + * then the column does not get new alias, instead the existing one is used. + * + * While generating alias names, this step also finds out details about columns: source database, source table + * column contraints, etc. Those informations are stored using generated alias name as a key. + * + * Some columns can be defined as not editable, because of various reasons: QueryExecutor::ColumnEditionForbiddenReason. + * Those reasons are defined in this step. + */ +class QueryExecutorColumns : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); + + private: + + /** + * @brief Transforms SelectResolver's columns into QueryExecutor's columns. + * @param resolvedColumn Result columns resolved by SelectResolver. + * @return Converted column. + * + * QueryExecutor understands different model of result columns than SelectResolver. + * Converted columns are later used by other steps and it's also returned from QueryExecutor as an information + * about result columns of the query. + */ + QueryExecutor::ResultColumnPtr getResultColumn(const SelectResolver::Column& resolvedColumn); + + /** + * @brief Generates result column object with proper alias name. + * @param resultColumn Original result column from the query. + * @param col Original result column as resolved by SelectResolver. + * @param rowIdColumn Indicates if this is a call for ROWID column added by QueryExecutorRowId step. + * @return Result column object ready for rebuilding tokens and detokenizing. + */ + SqliteSelect::Core::ResultColumn* getResultColumnForSelect(const QueryExecutor::ResultColumnPtr& resultColumn, const SelectResolver::Column& col, bool rowIdColumn); + + /** + * @brief Translates attach name into database name. + * @param dbName Attach name. + * @return Database name as registered in DbManager, or \p dbName if given name was not resolved to any registered database. + */ + QString resolveAttachedDatabases(const QString& dbName); + + /** + * @brief Checks if given alias name belongs to ROWID result column. + * @param alias Alias name to check. + * @return true if the alias belongs to ROWID column, or false otherwise. + */ + bool isRowIdColumnAlias(const QString& alias); + + void wrapWithAliasedColumns(SqliteSelect* select); +}; + +#endif // QUERYEXECUTORCOLUMNS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcountresults.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcountresults.cpp new file mode 100644 index 0000000..f51bf34 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcountresults.cpp @@ -0,0 +1,22 @@ +#include "queryexecutorcountresults.h" +#include "parser/ast/sqlitequery.h" +#include "db/queryexecutor.h" +#include <math.h> +#include <QDebug> + +bool QueryExecutorCountResults::exec() +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + { + // No return rows, but we're good to go. + // Pragma and Explain statements use "rows affected" for number of rows. + return true; + } + + QString countSql = "SELECT count(*) AS cnt FROM ("+select->detokenize()+");"; + context->countingQuery = countSql; + + // qDebug() << "count sql:" << countSql; + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcountresults.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcountresults.h new file mode 100644 index 0000000..654433a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorcountresults.h @@ -0,0 +1,19 @@ +#ifndef QUERYEXECUTORCOUNTRESULTS_H +#define QUERYEXECUTORCOUNTRESULTS_H + +#include "queryexecutorstep.h" + +/** + * @brief Defines counting query string. + * + * @see QueryExecutor::countResults() + */ +class QueryExecutorCountResults : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); +}; + +#endif // QUERYEXECUTORCOUNTRESULTS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.cpp new file mode 100644 index 0000000..9a5c1c2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.cpp @@ -0,0 +1,32 @@ +#include "queryexecutordatasources.h" +#include "parser/ast/sqliteselect.h" +#include "selectresolver.h" + +bool QueryExecutorDataSources::exec() +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + return true; + + if (select->coreSelects.size() > 1) // compound selects might have different collection of tables + return true; + + if (select->coreSelects.first()->valuesMode) + return true; + + SelectResolver resolver(db, select->tokens.detokenize()); + resolver.resolveMultiCore = false; // multicore subselects result in not editable columns, skip them + + SqliteSelect::Core* core = select->coreSelects.first(); + QSet<SelectResolver::Table> tables = resolver.resolveTables(core); + foreach (SelectResolver::Table resolvedTable, tables) + { + QueryExecutor::SourceTablePtr table = QueryExecutor::SourceTablePtr::create(); + table->database = resolvedTable.database; + table->table = resolvedTable.table; + table->alias = resolvedTable.alias; + context->sourceTables << table; + } + + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.h new file mode 100644 index 0000000..26650aa --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordatasources.h @@ -0,0 +1,24 @@ +#ifndef QUERYEXECUTORDATASOURCES_H +#define QUERYEXECUTORDATASOURCES_H + +#include "queryexecutorstep.h" + +/** + * @brief Finds all source tables. + * + * Finds all source tables for the SELECT query (if it's the last query in the query string) + * and stores them in QueryExecutor::Context::sourceTables. They are later provided by QueryExecutor::getSourceTables() + * as a meta information about data sources. + * + * Source tables are tables that result columns come from. If there's multiple columns selected + * from single table, only single table is resolved. + */ +class QueryExecutorDataSources : public QueryExecutorStep +{ + Q_OBJECT + public: + bool exec(); + +}; + +#endif // QUERYEXECUTORDATASOURCES_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordetectschemaalter.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordetectschemaalter.cpp new file mode 100644 index 0000000..c3c8a5c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordetectschemaalter.cpp @@ -0,0 +1,26 @@ +#include "queryexecutordetectschemaalter.h" + +bool QueryExecutorDetectSchemaAlter::exec() +{ + for (SqliteQueryPtr query : context->parsedQueries) + { + switch (query->queryType) + { + case SqliteQueryType::AlterTable: + case SqliteQueryType::CreateIndex: + case SqliteQueryType::CreateTable: + case SqliteQueryType::CreateTrigger: + case SqliteQueryType::CreateView: + case SqliteQueryType::DropIndex: + case SqliteQueryType::DropTable: + case SqliteQueryType::DropTrigger: + case SqliteQueryType::DropView: + case SqliteQueryType::CreateVirtualTable: + context->schemaModified = true; + break; + default: + break; + } + } + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordetectschemaalter.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordetectschemaalter.h new file mode 100644 index 0000000..760513e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutordetectschemaalter.h @@ -0,0 +1,14 @@ +#include "queryexecutorstep.h" + +#ifndef QUERYEXECUTORDETECTSCHEMAALTER_H +#define QUERYEXECUTORDETECTSCHEMAALTER_H + +class QueryExecutorDetectSchemaAlter : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); +}; + +#endif // QUERYEXECUTORDETECTSCHEMAALTER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp new file mode 100644 index 0000000..7e0abe5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.cpp @@ -0,0 +1,149 @@ +#include "queryexecutorexecute.h" +#include "db/sqlerrorcodes.h" +#include "db/queryexecutor.h" +#include "parser/ast/sqlitequery.h" +#include "parser/lexer.h" +#include "parser/ast/sqlitecreatetable.h" +#include "datatype.h" +#include <QDateTime> +#include <QDebug> +#include <schemaresolver.h> + +bool QueryExecutorExecute::exec() +{ + qDebug() << "q:" << context->processedQuery; + + startTime = QDateTime::currentMSecsSinceEpoch(); + return executeQueries(); +} + +void QueryExecutorExecute::provideResultColumns(SqlQueryPtr results) +{ + QueryExecutor::ResultColumnPtr resCol; + foreach (const QString& colName, results->getColumnNames()) + { + resCol = QueryExecutor::ResultColumnPtr::create(); + resCol->displayName = colName; + context->resultColumns << resCol; + } +} + +bool QueryExecutorExecute::executeQueries() +{ + QHash<QString, QVariant> bindParamsForQuery; + SqlQueryPtr results; + + Db::Flags flags; + if (context->preloadResults) + flags |= Db::Flag::PRELOAD; + + int queryCount = context->parsedQueries.size(); + for (const SqliteQueryPtr& query : context->parsedQueries) + { + bindParamsForQuery = getBindParamsForQuery(query); + results = db->prepare(query->detokenize()); + results->setArgs(bindParamsForQuery); + results->setFlags(flags); + + queryCount--; + if (queryCount == 0) // last query? + setupSqlite2ColumnDataTypes(results); + + results->execute(); + + if (results->isError()) + { + handleFailResult(results); + return false; + } + } + handleSuccessfulResult(results); + return true; +} + +void QueryExecutorExecute::handleSuccessfulResult(SqlQueryPtr results) +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->coreSelects.size() > 1 || select->explain) + { + // In this case, the "Columns" step didn't provide result columns. + // We need to do it here, basing on actual results. + provideResultColumns(results); + } + + context->executionTime = QDateTime::currentMSecsSinceEpoch() - startTime; + context->rowsAffected = results->rowsAffected(); + + // For PRAGMA and EXPLAIN we simply count results for rows returned + SqliteQueryPtr lastQuery = context->parsedQueries.last(); + if (lastQuery->queryType != SqliteQueryType::Select || lastQuery->explain) + context->rowsCountingRequired = true; + + if (context->resultsHandler) + { + context->resultsHandler(results); + context->resultsHandler = nullptr; + } + + context->executionResults = results; +} + +void QueryExecutorExecute::handleFailResult(SqlQueryPtr results) +{ + if (!results->isInterrupted()) + { + qWarning() << "Could not execute query with smart method:" << queryExecutor->getOriginalQuery() + << "\nError message:" << results->getErrorText() + << "\nSkipping smart execution."; + } +} + +QHash<QString, QVariant> QueryExecutorExecute::getBindParamsForQuery(SqliteQueryPtr query) +{ + QHash<QString, QVariant> queryParams; + QStringList bindParams = query->tokens.filter(Token::BIND_PARAM).toStringList(); + foreach (const QString& bindParam, bindParams) + { + if (context->queryParameters.contains(bindParam)) + queryParams.insert(bindParam, context->queryParameters[bindParam]); + } + return queryParams; +} + +void QueryExecutorExecute::setupSqlite2ColumnDataTypes(SqlQueryPtr results) +{ + Sqlite2ColumnDataTypeHelper* sqlite2Helper = dynamic_cast<Sqlite2ColumnDataTypeHelper*>(results.data()); + if (!sqlite2Helper) + return; + + QPair<QString,QString> key; + SqliteCreateTablePtr createTable; + + SchemaResolver resolver(db); + QHash<QPair<QString,QString>,SqliteCreateTablePtr> tables; + for (QueryExecutor::SourceTablePtr tab : context->sourceTables) + { + if (tab->table.isNull()) + continue; + + key = QPair<QString,QString>(tab->database, tab->table); + createTable = resolver.getParsedObject(tab->database, tab->table, SchemaResolver::TABLE).dynamicCast<SqliteCreateTable>(); + tables[key] = createTable; + } + + sqlite2Helper->clearBinaryTypes(); + + SqliteCreateTable::Column* column = nullptr; + int idx = -1 + context->rowIdColumns.size(); + for (QueryExecutor::ResultColumnPtr resCol : context->resultColumns) + { + idx++; + key = QPair<QString,QString>(resCol->database, resCol->table); + if (!tables.contains(key)) + continue; + + column = tables[key]->getColumn(resCol->column); + if (column->type && DataType::isBinary(column->type->name)) + sqlite2Helper->setBinaryType(idx); + } +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.h new file mode 100644 index 0000000..a88bf56 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexecute.h @@ -0,0 +1,85 @@ +#ifndef QUERYEXECUTOREXECUTE_H +#define QUERYEXECUTOREXECUTE_H + +#include "queryexecutorstep.h" +#include <QHash> + +/** + * @brief Executes query in current form. + * + * Executes query synchronously (since entire query executor works in another thread anyway). + * After execution is finished it provides information about how long it took, whether there was + * an error, and how many rows were affected/returned. + * + * The query string may contain many queries separated by semicolon and this step will split + * them correctly, then execute one-by-one. Results are loaded only from last query execution. + * + * If the last query was not processed by QueryExecutorColumns step, then this step + * will provide list result column names basing on what names returned SQLite. + * + * For PRAGMA and EXPLAIN statements rows returned are not accurate + * and QueryExecutor::Context::rowsCountingRequired is set to true. + */ +class QueryExecutorExecute : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); + + private: + /** + * @brief Gives list of column names as SQLite returned them. + * @param results Execution results. + */ + void provideResultColumns(SqlQueryPtr results); + + /** + * @brief Executes the query. + * @return true on success, false on failure. + * + * Stops on first error and in that case rolls back transaction. + * + * If QueryExecutor::Context::preloadResults is true, then also Db::Flag::PRELOAD + * is appended to execution flags. + */ + bool executeQueries(); + + /** + * @brief Extracts meta information from results. + * @param results Execution results. + * + * Meta information includes rows affected, execution time, etc. + */ + void handleSuccessfulResult(SqlQueryPtr results); + + /** + * @brief Handles failed execution. + * @param results Execution results. + * + * Currently this method doesn't do much. It just checks whether execution + * error was caused by call to Db::interrupt(), or not and if not, + * then the warning is logged about it and executor falls back to simple + * execution method. + */ + void handleFailResult(SqlQueryPtr results); + + /** + * @brief Prepares parameters for query execution. + * @param query Query to be executed. + * @return Map of parameters for the query. + * + * It generates parameters basing on what are parameter placeholders in the query + * and what are parameter values available in QueryExecutor::Context::queryParameters. + */ + QHash<QString, QVariant> getBindParamsForQuery(SqliteQueryPtr query); + + /** + * @brief Number of milliseconds since 1970 at execution start moment. + */ + qint64 startTime; + + void setupSqlite2ColumnDataTypes(SqlQueryPtr results); +}; + +#endif // QUERYEXECUTOREXECUTE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexplainmode.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexplainmode.cpp new file mode 100644 index 0000000..117022e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexplainmode.cpp @@ -0,0 +1,28 @@ +#include "queryexecutorexplainmode.h" + +bool QueryExecutorExplainMode::exec() +{ + if (!context->explainMode) + return true; // explain mode disabled + + SqliteQueryPtr lastQuery = context->parsedQueries.last(); + + if (!lastQuery) + return true; + + // If last query wasn't in explain mode, switch it on + if (!lastQuery->explain) + { + lastQuery->explain = true; + lastQuery->tokens.prepend(TokenPtr::create(Token::SPACE, " ")); + lastQuery->tokens.prepend(TokenPtr::create(Token::KEYWORD, "EXPLAIN")); + } + + // Limit queries to only last one + context->parsedQueries.clear(); + context->parsedQueries << lastQuery; + + updateQueries(); + + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexplainmode.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexplainmode.h new file mode 100644 index 0000000..bd806c7 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorexplainmode.h @@ -0,0 +1,18 @@ +#ifndef QUERYEXECUTOREXPLAINMODE_H +#define QUERYEXECUTOREXPLAINMODE_H + +#include "queryexecutorstep.h" + +/** + * @brief Applies QueryExecutor::Context::explainMode to the query. + * + * If explain mode is enabled, it just prepends "EXPLAIN" before the query. + */ +class QueryExecutorExplainMode : public QueryExecutorStep +{ + Q_OBJECT + public: + bool exec(); +}; + +#endif // QUERYEXECUTOREXPLAINMODE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.cpp new file mode 100644 index 0000000..af1d7a6 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.cpp @@ -0,0 +1,30 @@ +#include "queryexecutorlimit.h" +#include "parser/ast/sqlitelimit.h" +#include <QDebug> + +bool QueryExecutorLimit::exec() +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + return true; + + int page = queryExecutor->getPage(); + if (page < 0) + return true; // no paging requested + + if (select->tokens.size() < 1) + return true; // shouldn't happen, but if happens, quit gracefully + + quint64 limit = queryExecutor->getResultsPerPage(); + quint64 offset = limit * page; + + // The original query is last, so if it contained any %N strings, + // they won't be replaced. + static_qstring(selectTpl, "SELECT * FROM (%1) LIMIT %2 OFFSET %3"); + QString newSelect = selectTpl.arg(select->detokenize(), QString::number(limit), QString::number(offset)); + + int begin = select->tokens.first()->start; + int length = select->tokens.last()->end - select->tokens.first()->start + 1; + context->processedQuery = context->processedQuery.replace(begin, length, newSelect); + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.h new file mode 100644 index 0000000..56714b4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorlimit.h @@ -0,0 +1,22 @@ +#ifndef QUERYEXECUTORLIMIT_H +#define QUERYEXECUTORLIMIT_H + +#include "queryexecutorstep.h" + +/** + * @brief Applies row count limit to the query. + * + * If row count limit is enabled (QueryExecutor::Context::setPage() + * and QueryExecutor::Context::setResultsPerPage), then the SELECT query + * is wrapped with another SELECT which defines it's own LIMIT and OFFSET + * basing on the page and the results per page parameters. + */ +class QueryExecutorLimit : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); +}; + +#endif // QUERYEXECUTORLIMIT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutororder.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutororder.cpp new file mode 100644 index 0000000..09e35ed --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutororder.cpp @@ -0,0 +1,79 @@ +#include "queryexecutororder.h" +#include "common/utils_sql.h" +#include "parser/parser.h" +#include <QDebug> + +bool QueryExecutorOrder::exec() +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + return true; + + QueryExecutor::SortList sortOrder = queryExecutor->getSortOrder(); + if (sortOrder.size() == 0) + return true; // no sorting requested + + if (select->tokens.size() < 1) + return true; // shouldn't happen, but if happens, leave gracefully + + TokenList tokens = getOrderTokens(sortOrder); + if (tokens.size() == 0) + return false; + + static_qstring(selectTpl, "SELECT * FROM (%1) ORDER BY %2"); + QString newSelect = selectTpl.arg(select->detokenize(), tokens.detokenize()); + + Parser parser(dialect); + if (!parser.parse(newSelect) || parser.getQueries().size() == 0) + { + qWarning() << "Could not parse SELECt after applying order. Tried to parse query:\n" << newSelect; + return false; + } + + context->parsedQueries.removeLast(); + context->parsedQueries << parser.getQueries().first(); + + updateQueries(); + return true; +} + +TokenList QueryExecutorOrder::getOrderTokens(const QueryExecutor::SortList& sortOrder) +{ + TokenList tokens; + QueryExecutor::ResultColumnPtr resCol; + bool next = false; + for (const QueryExecutor::Sort& sort : sortOrder) + { + if (sort.column >= context->resultColumns.size()) + { + qCritical() << "There is less result columns in query executor context than index of requested sort column"; + return TokenList(); + } + + if (next) + { + tokens << TokenPtr::create(Token::OPERATOR, ","); + tokens << TokenPtr::create(Token::SPACE, " "); + } + else + next = true; + + resCol = context->resultColumns[sort.column]; + + tokens << TokenPtr::create(Token::OTHER, resCol->queryExecutorAlias); + tokens << TokenPtr::create(Token::SPACE, " "); + + if (sort.order == QueryExecutor::Sort::DESC) + { + tokens << TokenPtr::create(Token::KEYWORD, "DESC"); + tokens << TokenPtr::create(Token::SPACE, " "); + } + else + { + tokens << TokenPtr::create(Token::KEYWORD, "ASC"); + tokens << TokenPtr::create(Token::SPACE, " "); + } + } + + return tokens; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutororder.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutororder.h new file mode 100644 index 0000000..d321f9b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutororder.h @@ -0,0 +1,30 @@ +#ifndef QUERYEXECUTORORDER_H +#define QUERYEXECUTORORDER_H + +#include "queryexecutorstep.h" + +/** + * @brief Applies sorting to the query. + * + * Sorting is done by enclosing SELECT query with another SELECT + * query, but the outer one uses order defined in this step. + * + * The order is defined by QueryExecutor::setSortOrder(). + */ +class QueryExecutorOrder : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); + + private: + /** + * @brief Generates tokens to sort by given column and order. + * @param sortOrder Definition of order to use. + * @return Tokens to append to the SELECT. + */ + TokenList getOrderTokens(const QueryExecutor::SortList& sortOrder); +}; + +#endif // QUERYEXECUTORORDER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorparsequery.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorparsequery.cpp new file mode 100644 index 0000000..cd91734 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorparsequery.cpp @@ -0,0 +1,48 @@ +#include "queryexecutorparsequery.h" +#include "db/queryexecutor.h" +#include "parser/parser.h" +#include <QDebug> + +QueryExecutorParseQuery::QueryExecutorParseQuery(const QString& name) + : QueryExecutorStep() +{ + setObjectName(name); +} + +QueryExecutorParseQuery::~QueryExecutorParseQuery() +{ + if (parser) + delete parser; +} + +bool QueryExecutorParseQuery::exec() +{ + // Prepare parser + if (parser) + delete parser; + + parser = new Parser(dialect); + + // Do parsing + context->parsedQueries.clear(); + parser->parse(context->processedQuery); + if (parser->getErrors().size() > 0) + { + qWarning() << "QueryExecutorParseQuery:" << parser->getErrorString() << "\n" + << "Query parsed:" << context->processedQuery; + return false; + } + + if (parser->getQueries().size() == 0) + { + qWarning() << "No queries parsed in QueryExecutorParseQuery step."; + return false; + } + + context->parsedQueries = parser->getQueries(); + + // We never want the semicolon in last query, because the query could be wrapped with a SELECT + context->parsedQueries.last()->tokens.trimRight(Token::OPERATOR, ";"); + + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorparsequery.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorparsequery.h new file mode 100644 index 0000000..6dc8ab5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorparsequery.h @@ -0,0 +1,29 @@ +#ifndef QUERYEXECUTORPARSEQUERY_H +#define QUERYEXECUTORPARSEQUERY_H + +#include "queryexecutorstep.h" + +/** + * @brief Parses current query and stores results in the context. + * + * Parses QueryExecutor::Context::processedQuery and stores results + * in QueryExecutor::Context::parsedQueries. + * + * This is used after some changes were made to the query and next steps will + * require parsed representation of queries to be updated. + */ +class QueryExecutorParseQuery : public QueryExecutorStep +{ + Q_OBJECT + + public: + explicit QueryExecutorParseQuery(const QString& name); + ~QueryExecutorParseQuery(); + + bool exec(); + + private: + Parser* parser = nullptr; +}; + +#endif // QUERYEXECUTORPARSEQUERY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.cpp new file mode 100644 index 0000000..94300a0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.cpp @@ -0,0 +1,113 @@ +#include "queryexecutorreplaceviews.h" +#include "parser/ast/sqlitecreateview.h" +#include "schemaresolver.h" +#include <QDebug> + +QueryExecutorReplaceViews::~QueryExecutorReplaceViews() +{ + if (schemaResolver) + { + delete schemaResolver; + schemaResolver = nullptr; + } +} + +bool QueryExecutorReplaceViews::exec() +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + return true; + + if (select->coreSelects.size() > 1) + return true; + + if (select->coreSelects.first()->distinctKw) + return true; + + replaceViews(select.data()); + updateQueries(); + + return true; +} + +void QueryExecutorReplaceViews::init() +{ + if (!schemaResolver) + schemaResolver = new SchemaResolver(db); +} + +QStringList QueryExecutorReplaceViews::getViews(const QString& database) +{ + QString dbName = database.isNull() ? "main" : database.toLower(); + if (views.contains(dbName)) + return views[dbName]; + + views[dbName] = schemaResolver->getViews(database); + return views[dbName]; +} + +SqliteCreateViewPtr QueryExecutorReplaceViews::getView(const QString& database, const QString& viewName) +{ + View view(database, viewName); + if (viewStatements.contains(view)) + return viewStatements[view]; + + SqliteQueryPtr query = schemaResolver->getParsedObject(database, viewName, SchemaResolver::VIEW); + if (!query) + return SqliteCreateViewPtr(); + + SqliteCreateViewPtr viewPtr = query.dynamicCast<SqliteCreateView>(); + if (!viewPtr) + return SqliteCreateViewPtr(); + + viewStatements[view] = viewPtr; + return viewPtr; +} + +void QueryExecutorReplaceViews::replaceViews(SqliteSelect* select) +{ + SqliteSelect::Core* core = select->coreSelects.first(); + + QStringList viewsInDatabase; + SqliteCreateViewPtr view; + + QList<SqliteSelect::Core::SingleSource*> sources = core->getAllTypedStatements<SqliteSelect::Core::SingleSource>(); + foreach (SqliteSelect::Core::SingleSource* src, sources) + { + if (src->table.isNull()) + continue; + + viewsInDatabase = getViews(src->database); + if (!viewsInDatabase.contains(src->table, Qt::CaseInsensitive)) + continue; + + view = getView(src->database, src->table); + if (!view) + { + qWarning() << "Object" << src->database << "." << src->table + << "was identified to be a view, but could not get it's parsed representation."; + continue; + } + + src->select = view->select; + src->database = QString::null; + src->table = QString::null; + } + + select->rebuildTokens(); +} + +uint qHash(const QueryExecutorReplaceViews::View& view) +{ + return qHash(view.database + "." + view.view); +} + +QueryExecutorReplaceViews::View::View(const QString& database, const QString& view) : + database(database), view(view) +{ +} + +int QueryExecutorReplaceViews::View::operator==(const QueryExecutorReplaceViews::View& other) const +{ + return database == other.database && view == other.view; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.h new file mode 100644 index 0000000..633b0bc --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorreplaceviews.h @@ -0,0 +1,110 @@ +#ifndef QUERYEXECUTORREPLACEVIEWS_H +#define QUERYEXECUTORREPLACEVIEWS_H + +#include "queryexecutorstep.h" +#include "parser/ast/sqlitecreateview.h" + +/** + * @brief Replaces all references to views in query with SELECTs from those views. + * + * Replacing views with their SELECTs (as subselects) simplifies later tasks + * with the query. + */ +class QueryExecutorReplaceViews : public QueryExecutorStep +{ + Q_OBJECT + + public: + ~QueryExecutorReplaceViews(); + + bool exec(); + + protected: + void init(); + + private: + /** + * @brief View representation in context of QueryExecutorReplaceViews step. + */ + struct View + { + /** + * @brief Creates view. + * @param database Database of the view. + * @param view View name. + */ + View(const QString& database, const QString& view); + + /** + * @brief Database of the view. + */ + QString database; + + /** + * @brief View name. + */ + QString view; + + /** + * @brief Checks if it's the same view as the \p other. + * @param other Other view to compare. + * @return 1 if other view is the same one, or 0 otherwise. + * + * Views are equal if they have equal name and database. + */ + int operator==(const View& other) const; + }; + + friend uint qHash(const View& view); + + /** + * @brief Provides all views existing in the database. + * @param database Database name as typed in the query. + * @return List of view names. + * + * Uses internal cache (using views). + */ + QStringList getViews(const QString& database); + + /** + * @brief Reads view's DDL, parses it and returns results. + * @param database Database of the view. + * @param viewName View name. + * @return Parsed view or null pointer if view doesn't exist or could not be parsed. + * + * It uses internal cache (using viewStatements). + */ + SqliteCreateViewPtr getView(const QString& database, const QString& viewName); + + /** + * @brief Replaces views in the query with SELECT statements. + * @param select SELECT statement to replace views in. + * + * It explores the \p select looking for view names and replaces them with + * apropriate subselect queries, using getView() calls. + */ + void replaceViews(SqliteSelect* select); + + /** + * @brief Used for caching view list per database. + */ + QHash<QString,QStringList> views; + + /** + * @brief Resolver used several time in this step. + * + * It's stored as member of this class, cause otherwise it would be created + * and deleted many times. Instead it's shared across all calls to resolve something + * from schema. + */ + SchemaResolver* schemaResolver = nullptr; + + /** + * @brief Used for caching parsed view statement. + */ + QHash<View,SqliteCreateViewPtr> viewStatements; +}; + +uint qHash(const QueryExecutorReplaceViews::View& view); + +#endif // QUERYEXECUTORREPLACEVIEWS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.cpp new file mode 100644 index 0000000..d64ea2e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.cpp @@ -0,0 +1,63 @@ +#include "queryexecutorstep.h" +#include "db/queryexecutor.h" +#include "common/unused.h" + +QueryExecutorStep::~QueryExecutorStep() +{ +} + +void QueryExecutorStep::init(QueryExecutor *queryExecutor, QueryExecutor::Context *context) +{ + this->queryExecutor = queryExecutor; + this->context = context; + db = queryExecutor->getDb(); + dialect = db->getDialect(); + init(); +} + +void QueryExecutorStep::updateQueries() +{ + QString newQuery; + foreach (SqliteQueryPtr query, context->parsedQueries) + { + newQuery += query->detokenize(); + newQuery += "\n"; + } + context->processedQuery = newQuery; +} + +QString QueryExecutorStep::getNextColName() +{ + return "ResCol_" + QString::number(context->colNameSeq++); +} + +SqliteSelectPtr QueryExecutorStep::getSelect() +{ + SqliteQueryPtr lastQuery = context->parsedQueries.last(); + if (lastQuery->queryType != SqliteQueryType::Select) + return SqliteSelectPtr(); + + return lastQuery.dynamicCast<SqliteSelect>(); +} + +void QueryExecutorStep::init() +{ +} + +TokenList QueryExecutorStep::wrapSelect(const TokenList& selectTokens, const TokenList& resultColumnsTokens) +{ + TokenList oldSelectTokens = selectTokens; + oldSelectTokens.trimRight(Token::OPERATOR, ";"); + + TokenList newTokens; + newTokens << TokenPtr::create(Token::KEYWORD, "SELECT") + << TokenPtr::create(Token::SPACE, " "); + newTokens += resultColumnsTokens; + newTokens << TokenPtr::create(Token::SPACE, " ") + << TokenPtr::create(Token::KEYWORD, "FROM") + << TokenPtr::create(Token::SPACE, " ") + << TokenPtr::create(Token::PAR_LEFT, "("); + newTokens += oldSelectTokens; + newTokens << TokenPtr::create(Token::PAR_RIGHT, ")"); + return newTokens; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.h new file mode 100644 index 0000000..a15ad9c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorstep.h @@ -0,0 +1,158 @@ +#ifndef QUERYEXECUTORSTEP_H +#define QUERYEXECUTORSTEP_H + +#include "db/sqlquery.h" +#include "parser/ast/sqliteselect.h" +#include "db/queryexecutor.h" +#include <QObject> + +/** + * @brief Base class for all query executor steps. + * + * Query Executor step is a single step that query executor performs at a time + * during the "smart execution" (see QueryExecutor for details). + * + * Steps are created and queued in QueryExecutor::setupExecutionChain(). + * Order of steps execution is very important and should not be distrurbed, + * unless you know what you're doing. + * + * A step must implement one method: exec(). + * The implementation can access QueryExecutor instance that is running the step + * and also it can access common context for executor and all the steps. + * + * Steps usually introduce some modifications to the query, so the final execution + * can provide more meta-information, or works on limited set of data, etc. + * + * Steps can access common context to get parsed object of the current query. + * The current query is the query processed by previous steps and re-parsed after + * those modifications. Current query is also available in a string representation + * in the context. The original query string (before any modifications) is also + * available in the context. See QueryExecutor::Context for more. + * + * QueryExecutorStep provides several methods to help dealing with common routines, + * such as updating current query with modified query definition (updateQueries()), + * or extracting parsed SELECT (if the last query defined was the SELECT) object + * (getSelect()). When the step needs to add new result column, it can use + * getNextColName() to generate unique name. + * + * To access database object, that the query is executed on, use QueryExecutor::getDb(). + */ +class QueryExecutorStep : public QObject +{ + Q_OBJECT + + public: + /** + * @brief Cleans up resources. + */ + virtual ~QueryExecutorStep(); + + /** + * @brief Initializes step with a pointer to calling executor and a common context. + * @param queryExecutor Calling query executor. + * @param context Common context, shared among query executor and all steps. + * + * This method also calls virtual method init(), which can be used to one-time setup + * in derived step. + */ + void init(QueryExecutor* queryExecutor, QueryExecutor::Context* context); + + /** + * @brief Executes step routines. + * @return result of execution - successful or failed. + * + * The exec() method should execute all necessary routines required by this step. + * If it needs to execute anything on the database + * + * Once the false is returned by exec(), there should be no signal emitted by the step. + */ + virtual bool exec() = 0; + + protected: + /** + * @brief Updates QueryExecutor::Context::processedQuery string. + * + * Goes through all queries in QueryExecutor::Context::parsedQueries and detokenizes + * their tokens, concatenatins all results into QueryExecutor::Context::processedQuery. + * + * This should be called every time tokens of any parsed query were modified + * and you want those changes to be reflected in the processed query. + * + * See QueryExecutor::Context::processedQuery for more details; + */ + void updateQueries(); + + /** + * @brief Generates unique name for result column alias. + * @return Unique name. + * + * When a step needs to add another column to results, it can use this method + * to make sure that the name (alias) of this column will be unique across + * other result columns. + * + * QueryExecutorColumn step makes sure that every result column processed + * by query executor gets its own unique alias name. + * + * The getNextColName() whould be used only once per result column. When forwarding + * the result column from inner select to outer select, use the same alias name + * ad in the inner select. + */ + QString getNextColName(); + + /** + * @brief Extracts SELECT object from parsed queries. + * @return Parsed SELECT, or null pointer. + * + * This is a helper method. Since most of the logic in steps is required in regards + * of the last SELECT statement in all queries, this method comes handy. + * + * If the last statement in executed queries is the SELECT, then this method + * returns parsed object of that statement, otherwise it returns null pointer. + */ + SqliteSelectPtr getSelect(); + + /** + * @brief Custom initialization of the step. + * + * If a step needs some initial code to be executed, or some members to be initialized, + * this is the place to put it into. + */ + virtual void init(); + + /** + * @brief Puts the SELECT as a subselect. + * @param selectTokens All tokens of the original SELECT. + * @param resultColumnsTokens Result columns tokens for the new SELECT. + * @return New SELECT tokens. + * + * Original SELECT tokens are put into subselect of the new SELECT statement. New SELECT statement + * is built using given \p resultColumnTokens. + */ + TokenList wrapSelect(const TokenList& selectTokens, const TokenList& resultColumnsTokens); + + /** + * @brief Pointer to the calling executor. + */ + QueryExecutor* queryExecutor = nullptr; + + /** + * @brief Pointer to a shared context for all steps. + */ + QueryExecutor::Context* context = nullptr; + + /** + * @brief Database that all queries will be executed on. + * + * Defined by init(). + */ + Db* db = nullptr; + + /** + * @brief SQLite dialect of the database. + * + * Defined by init(). + */ + Dialect dialect; +}; + +#endif // QUERYEXECUTORSTEP_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorvaluesmode.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorvaluesmode.cpp new file mode 100644 index 0000000..8807178 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorvaluesmode.cpp @@ -0,0 +1,26 @@ +#include "queryexecutorvaluesmode.h" + +bool QueryExecutorValuesMode::exec() +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + return true; + + bool modified = false; + for (SqliteSelect::Core* core : select->coreSelects) + { + if (core->valuesMode) + { + core->valuesMode = false; + modified = true; + } + } + + if (modified) + { + select->rebuildTokens(); + updateQueries(); + } + + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorvaluesmode.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorvaluesmode.h new file mode 100644 index 0000000..4037a5b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorvaluesmode.h @@ -0,0 +1,14 @@ +#ifndef QUERYEXECUTORVALUESMODE_H +#define QUERYEXECUTORVALUESMODE_H + +#include "queryexecutorstep.h" + +class QueryExecutorValuesMode : public QueryExecutorStep +{ + Q_OBJECT + + public: + bool exec(); +}; + +#endif // QUERYEXECUTORVALUESMODE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorwrapdistinctresults.cpp b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorwrapdistinctresults.cpp new file mode 100644 index 0000000..2565c2c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorwrapdistinctresults.cpp @@ -0,0 +1,42 @@ +#include "queryexecutorwrapdistinctresults.h" + +bool QueryExecutorWrapDistinctResults::exec() +{ + SqliteSelectPtr select = getSelect(); + if (!select || select->explain) + return true; + + SqliteSelect::Core* core = select->coreSelects.first(); + + if (core->distinctKw || core->groupBy.size() > 0) + wrapSelect(select.data()); + + return true; +} + +void QueryExecutorWrapDistinctResults::wrapSelect(SqliteSelect* select) +{ + // Trim tokens from right side + TokenList origTokens = select->tokens; + origTokens.trimRight(); + + // Remove ; operator if present at the end + while (origTokens.last()->type == Token::OPERATOR && origTokens.last()->value == ";") + origTokens.removeLast(); + + // Create new list + TokenList tokens; + tokens << TokenPtr::create(Token::KEYWORD, "SELECT"); + tokens << TokenPtr::create(Token::SPACE, " "); + tokens << TokenPtr::create(Token::OPERATOR, "*"); + tokens << TokenPtr::create(Token::SPACE, " "); + tokens << TokenPtr::create(Token::KEYWORD, "FROM"); + tokens << TokenPtr::create(Token::SPACE, " "); + tokens << TokenPtr::create(Token::OPERATOR, "("); + tokens += origTokens; + tokens << TokenPtr::create(Token::OPERATOR, ")"); + tokens << TokenPtr::create(Token::OPERATOR, ";"); + + select->tokens = tokens; + updateQueries(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorwrapdistinctresults.h b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorwrapdistinctresults.h new file mode 100644 index 0000000..eeb94ef --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/queryexecutorsteps/queryexecutorwrapdistinctresults.h @@ -0,0 +1,33 @@ +#ifndef QUERYEXECUTORWRAPDISTINCTRESULTS_H +#define QUERYEXECUTORWRAPDISTINCTRESULTS_H + +#include "queryexecutorstep.h" + +/** + * @brief The QueryExecutorWrapDistinctResults class + * + * This step is necessary for DISTINCT and GROUP BY selects, + * because result columns are always limited by substr() function, + * which makes INT and TEXT types of data to be the same, which is false. + * For those cases, we need to put DISTINCT/GROUP BY into subselect, + * so it works on original result columns, then get the substr() from them. + * + * Processed query is stored in QueryExecutor::Context::processedQuery. + */ +class QueryExecutorWrapDistinctResults : public QueryExecutorStep +{ + Q_OBJECT + public: + bool exec(); + + private: + /** + * @brief Wraps single SELECT statement. + * @param select SELECT to wrap. + * + * Puts given \p select as subselect of a new SELECT. + */ + void wrapSelect(SqliteSelect* select); +}; + +#endif // QUERYEXECUTORWRAPDISTINCTRESULTS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorcodes.cpp b/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorcodes.cpp new file mode 100644 index 0000000..f18a785 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorcodes.cpp @@ -0,0 +1,13 @@ +#include "db/sqlerrorcodes.h" + +// This is defined in sqlite3.h, but we don't want to make a dependency +// from coreSQLiteStudio to sqlite3.h just for this one constraint, +// while the coreSQLiteStudio doesn't depend on sqlite lib at all. +// This should not change anyway. It's a documented value. +#define SQLITE_INTERRUPT 9 + +bool SqlErrorCode::isInterrupted(int errorCode) +{ + // Luckily SQLITE_INTERRUPT has the same value defined in both SQLite versions. + return (errorCode == SqlErrorCode::INTERRUPTED || errorCode == SQLITE_INTERRUPT); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorcodes.h b/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorcodes.h new file mode 100644 index 0000000..12849cf --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorcodes.h @@ -0,0 +1,39 @@ +#ifndef SQLERRORCODES_H +#define SQLERRORCODES_H + +#include "coreSQLiteStudio_global.h" + +/** + * @brief Custom SQL error codes. + * + * Those are custom error codes that can be returned by SqlResults::getErrorCode(). + * Usually error codes come from SQLite itself, but some errors can be generated + * by SQLiteStudio and for those cases this enum lists possible codes. + * + * Codes in this enum are not conflicting with error codes returned from SQLite. + */ +struct API_EXPORT SqlErrorCode +{ + enum + { + DB_NOT_OPEN = -1000, /**< Database was not open */ + QUERY_EXECUTOR_ERROR = -1001, /**< QueryExecutor error (its sophisticated logic encountered some problem) */ + PARSER_ERROR = -1002, /**< Parser class could not parse the query, because it was either invalid SQL, or bug in the Parser */ + INTERRUPTED = -1003, /**< Query execution was interrupted */ + INVALID_ARGUMENT = -1004, /**< Passed query argument was invalid (out of range, invalid format, etc.) */ + DB_NOT_DEFINED = -1005, /**< Database was not defined */ + OTHER_EXECUTION_ERROR = -1006 /**< Identifies other execution errors, see error message for details */ + }; + + /** + * @brief Tests if given error code means that execution was interrupted. + * @param errorCode Error code to test. + * @return true if the code represents interruption, or false otherwise. + * + * This method checks both SqlErrorCode::INTERRUPTED and SQLITE_INTERRUPT values, + * so if the code is either of them, it returns true. + */ + static bool isInterrupted(int errorCode); +}; + +#endif // SQLERRORCODES_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorresults.cpp b/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorresults.cpp new file mode 100644 index 0000000..e7f6acd --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorresults.cpp @@ -0,0 +1,55 @@ +#include "sqlerrorresults.h" +#include "common/unused.h" + +SqlErrorResults::SqlErrorResults(int code, const QString& text) +{ + errText = text; + errCode = code; +} + +QString SqlErrorResults::getErrorText() +{ + return errText; +} + +int SqlErrorResults::getErrorCode() +{ + return errCode; +} + +QStringList SqlErrorResults::getColumnNames() +{ + return QStringList(); +} + +int SqlErrorResults::columnCount() +{ + return 0; +} + +qint64 SqlErrorResults::rowsAffected() +{ + return 0; +} + +SqlResultsRowPtr SqlErrorResults::nextInternal() +{ + return SqlResultsRowPtr(); +} + +bool SqlErrorResults::hasNextInternal() +{ + return false; +} + +bool SqlErrorResults::execInternal(const QList<QVariant>& args) +{ + UNUSED(args); + return false; +} + +bool SqlErrorResults::execInternal(const QHash<QString, QVariant>& args) +{ + UNUSED(args); + return false; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorresults.h b/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorresults.h new file mode 100644 index 0000000..f837245 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlerrorresults.h @@ -0,0 +1,48 @@ +#ifndef SQLERRORRESULTS_H +#define SQLERRORRESULTS_H + +#include "sqlquery.h" +#include <QStringList> + +/** + * @brief SqlResults implementation for returning errors. + * + * It's very simple implementation of SqlResults, which has hardcoded number of columns and rows (0). + * It has single constructor which accepts error code and error message, which are later + * returned from getErrorCode() and getErrorText(). + */ +class SqlErrorResults : public SqlQuery +{ + public: + /** + * @brief Creates error results with given code and message. + * @param code Error code. + * @param text Error message. + */ + SqlErrorResults(int code, const QString &text); + + QString getErrorText(); + int getErrorCode(); + QStringList getColumnNames(); + int columnCount(); + qint64 rowsAffected(); + + protected: + SqlResultsRowPtr nextInternal(); + bool hasNextInternal(); + bool execInternal(const QList<QVariant>& args); + bool execInternal(const QHash<QString, QVariant>& args); + + private: + /** + * @brief Error message passed in constructor. + */ + QString errText; + + /** + * @brief errCode Error code passed in constructor. + */ + int errCode; +}; + +#endif // SQLERRORRESULTS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp new file mode 100644 index 0000000..4217711 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.cpp @@ -0,0 +1,142 @@ +#include "sqlquery.h" +#include "db/sqlerrorcodes.h" + +SqlQuery::~SqlQuery() +{ +} + +bool SqlQuery::execute() +{ + if (queryArgs.type() == QVariant::Hash) + return execInternal(queryArgs.toHash()); + else + return execInternal(queryArgs.toList()); +} + +SqlResultsRowPtr SqlQuery::next() +{ + if (preloaded) + { + if (preloadedRowIdx >= preloadedData.size()) + return SqlResultsRowPtr(); + + return preloadedData[preloadedRowIdx++]; + } + return nextInternal(); +} + +bool SqlQuery::hasNext() +{ + if (preloaded) + return (preloadedRowIdx < preloadedData.size()); + + return hasNextInternal(); +} + +qint64 SqlQuery::rowsAffected() +{ + return affected; +} + +QList<SqlResultsRowPtr> SqlQuery::getAll() +{ + if (!preloaded) + preload(); + + return preloadedData; +} + +void SqlQuery::preload() +{ + if (preloaded) + return; + + QList<SqlResultsRowPtr> allRows; + while (hasNextInternal()) + allRows << nextInternal(); + + preloadedData = allRows; + preloaded = true; + preloadedRowIdx = 0; +} + +QVariant SqlQuery::getSingleCell() +{ + SqlResultsRowPtr row = next(); + if (row.isNull()) + return QVariant(); + + return row->value(0); +} + +bool SqlQuery::isError() +{ + return getErrorCode() != 0; +} + +bool SqlQuery::isInterrupted() +{ + return SqlErrorCode::isInterrupted(getErrorCode()); +} + +RowId SqlQuery::getInsertRowId() +{ + return insertRowId; +} + +qint64 SqlQuery::getRegularInsertRowId() +{ + return insertRowId["ROWID"].toLongLong(); +} + +QString SqlQuery::getQuery() const +{ + return query; +} + +void SqlQuery::setFlags(Db::Flags flags) +{ + this->flags = flags; +} + +void SqlQuery::clearArgs() +{ + queryArgs = QVariant(); +} + +void SqlQuery::setArgs(const QList<QVariant>& args) +{ + queryArgs = args; +} + +void SqlQuery::setArgs(const QHash<QString, QVariant>& args) +{ + queryArgs = args; +} + + +void RowIdConditionBuilder::setRowId(const RowId& rowId) +{ + static const QString argTempalate = QStringLiteral(":rowIdArg%1"); + + QString arg; + QHashIterator<QString,QVariant> it(rowId); + int i = 0; + while (it.hasNext()) + { + it.next(); + arg = argTempalate.arg(i++); + queryArgs[arg] = it.value(); + conditions << it.key() + " = " + arg; + } +} + +const QHash<QString, QVariant>& RowIdConditionBuilder::getQueryArgs() +{ + return queryArgs; +} + +QString RowIdConditionBuilder::build() +{ + return conditions.join(" AND "); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h new file mode 100644 index 0000000..a7610fa --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlquery.h @@ -0,0 +1,323 @@ +#ifndef SQLQUERY_H +#define SQLQUERY_H + +#include "coreSQLiteStudio_global.h" +#include "db/db.h" +#include "db/sqlresultsrow.h" +#include <QList> +#include <QSharedPointer> + +/** @file */ + +/** + * @brief Row ID of the row in any table. + * + * It's a advanced ROWID container that can hold either simple integer ROWID, + * or a set of column values reflecting one or more PRIMARY KEY columns. + * + * This way it RowId can be applied to both regular tables, as well as "WITHOUT ROWID" tables. + * + * Each entry in the RowId has a key and a value (after all it's a QHash). Keys are names + * of the column and values are values in that columns. For regular tables the RowId + * will contain exactly one entry: <tt>ROWID -> some_integer</tt> + */ +typedef QHash<QString,QVariant> RowId; + +/** + * @brief SQL query to execute and get results from + * + * This object is created by and returned from Db::exec() (and familiar methods) + * or Db::prepare() calls. + * It uses incremental reading for accessing data, so it only reads as much data + * as you ask it to. It can tell you how many rows and how many columns are available + * in the results. It also provides information about errors that occured during query execution. + * + * Typical workflow looks like this: + * @code + * SqlQueryPtr results = db->exec("SELECT * FROM table"); + * SqlResultsRowPtr row; + * while (row = results->next()) + * { + * qDebug() << row->valueList(); + * } + * @endcode + * + * To re-use compiled query, use it like this: + * @code + * SqlQueryPtr query = db->prepare("SELECT * FROM table WHERE id BETWEEN ? AND ?"); + * SqlResultsRowPtr row; + * + * query->setArgs({5, 10}); + * query->execute(); + * while (row = query->next()) + * { + * qDebug() << row->valueList(); + * } + * + * query->setArgs({1, 3}); + * query->execute(); + * while (row = query->next()) + * { + * qDebug() << row->valueList(); + * } + * @endcode + */ +class API_EXPORT SqlQuery +{ + public: + /** + * @brief Releases result resources. + */ + virtual ~SqlQuery(); + + /** + * @brief Executes or re-executes prepared query. + * @return true on success, false on failure. + * + * You can ignore result of this method and check for error later with isError(). + */ + virtual bool execute(); + + /** + * @brief Reads next row of results + * @return Next results row, or null pointer if no more rows are available. + */ + SqlResultsRowPtr next(); + + /** + * @brief Tells if there is next row available. + * @return true if there's next row, of false if there's not. + * + * If you just want to iterate through rows, you don't need to call this method. + * The next() method will return null pointer if there is no next row available, + * so you can tell when to stop iterating. Furthermore, you should avoid + * calling this method just for iterating through rows, because next() method + * already does that internally (in most implementations). + * + * In other cases this method might be useful. For example when you read single cell: + * @code + * SqlQueryPtr results = db->("SELECT value FROM table WHERE rowid = ?", {rowId}); + * if (results->isError() || !results->hasNext()) + * return "some default value"; + * + * return results->getSingleCell().toString(); + * @endcode + */ + bool hasNext(); + + /** + * @brief Gets error test of the most recent error. + * @return Error text. + */ + virtual QString getErrorText() = 0; + + /** + * @brief Gets error code of the most recent error. + * @return Error code as returned from DbPlugin. + */ + virtual int getErrorCode() = 0; + + /** + * @brief Gets list of column names in the results. + * @return List of column names. + */ + virtual QStringList getColumnNames() = 0; + + /** + * @brief Gets number of columns in the results. + * @return Columns count. + */ + virtual int columnCount() = 0; + + /** + * @brief Gets number of rows that were affected by the query. + * @return Number of rows affected. + * + * For SELECT statements this is number of returned rows. + * For UPDATE this is number of rows updated. + * For DELETE this is number of rows deleted. + * FOR INSERT this is number of rows inserted (starting with SQLite 3.7.11 you can insert multiple rows with single INSERT statement). + */ + virtual qint64 rowsAffected(); + + /** + * @brief Reads all rows immediately and returns them. + * @return All data rows as a list. + * + * Don't use this method against huge data results. + */ + virtual QList<SqlResultsRowPtr> getAll(); + + /** + * @brief Loads all data immediately into memory. + * + * This method makes sense only if you plan to use getAll() later on. + * If you won't use getAll(), then calling this method is just a waste of memory. + * + * It is useful if you execute query asynchronously and you will be using all results. + * In that case the asynchronous execution takes care of loading data from the database and the final code + * just operates on in-memory data. + */ + virtual void preload(); + + /** + * @brief Reads first column of first row and returns its value. + * @return Value read. + * + * This method is useful when dealing with for example PRAGMA statement results, + * or for SELECT queries with expected single row and single column. + */ + virtual QVariant getSingleCell(); + + /** + * @brief Tells if there was an error while query execution. + * @return true if there was an error, false otherwise. + */ + virtual bool isError(); + + /** + * @brief Tells if the query execution was interrupted. + * @return true if query was interrupted, or false otherwise. + * + * Interruption of execution is interpreted as an execution error, + * so if this method returns true, then isError() will return true as well. + */ + virtual bool isInterrupted(); + + /** + * @brief Retrieves ROWID of the INSERT'ed row. + * @return ROWID as 64-bit signed integer or set of multiple columns. If empty, then there was no row inserted. + * @see RowId + * @see getRegularInsertRowId() + */ + virtual RowId getInsertRowId(); + + /** + * @brief Retrieves ROWID of the INSERT'ed row. + * @return ROWID as 64-bit signed integer. + * + * This is different from getInsertRowId(), because it assumes that the insert was made to a regular table, + * while getInsertRowId() supports also inserts to WITHOUT ROWID tables. + * + * If you know that the insert was made to a regular table, you can use this method to simply get the ROWID. + */ + virtual qint64 getRegularInsertRowId(); + + /** + * @brief columnAsList + * @tparam T Data type to use for the result list. + * @param name name of the column to get values from. + * @return List of all values from given column. + */ + template <class T> + QList<T> columnAsList(const QString& name) + { + QList<T> list; + SqlResultsRowPtr row; + while (hasNext()) + { + row = next(); + list << row->value(name).value<T>(); + } + return list; + } + + /** + * @brief columnAsList + * @tparam T Data type to use for the result list. + * @param index Index of the column to get values from (must be between 0 and columnCount()-1). + * @return List of all values from given column. + */ + template <class T> + QList<T> columnAsList(int index) + { + QList<T> list; + if (index < 0 || index >= columnCount()) + return list; + + SqlResultsRowPtr row; + while (hasNext()) + { + row = next(); + list << row->value(index).value<T>(); + } + return list; + } + + QString getQuery() const; + void setFlags(Db::Flags flags); + void clearArgs(); + void setArgs(const QList<QVariant>& args); + void setArgs(const QHash<QString,QVariant>& args); + + protected: + /** + * @brief Reads next row of results + * @return Next results row, or null pointer if no more rows are available. + * + * This is pretty much the same as next(), except next() handles preloaded data, + * while this method should work natively on the derived implementation of results object. + */ + virtual SqlResultsRowPtr nextInternal() = 0; + + /** + * @brief Tells if there is next row available. + * @return true if there's next row, of false if there's not. + * + * This is pretty much the same as hasNext(), except hasNext() handles preloaded data, + * while this method should work natively on the derived implementation of results object. + */ + virtual bool hasNextInternal() = 0; + + virtual bool execInternal(const QList<QVariant>& args) = 0; + virtual bool execInternal(const QHash<QString, QVariant>& args) = 0; + + /** + * @brief Row ID of the most recently inserted row. + */ + RowId insertRowId; + + /** + * @brief Flag indicating if the data was preloaded with preload(). + */ + bool preloaded = false; + + /** + * @brief Index of the next row to be returned. + * + * If the data was preloaded (see preload()), then iterating with next() whould use this index to find out + * which preloaded row should be returned next. + */ + int preloadedRowIdx = -1; + + /** + * @brief Data preloaded with preload(). + */ + QList<SqlResultsRowPtr> preloadedData; + + int affected = 0; + + QString query; + QVariant queryArgs; + Db::Flags flags; +}; + +class API_EXPORT RowIdConditionBuilder +{ + public: + void setRowId(const RowId& rowId); + const QHash<QString,QVariant>& getQueryArgs(); + QString build(); + + protected: + QStringList conditions; + QHash<QString,QVariant> queryArgs; +}; + +/** + * @brief Shared pointer to query object. + * Results are usually passed as shared pointer, so it's used as needed and deleted when no longer required. + */ +typedef QSharedPointer<SqlQuery> SqlQueryPtr; + +#endif // SQLQUERY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlresultsrow.cpp b/SQLiteStudio3/coreSQLiteStudio/db/sqlresultsrow.cpp new file mode 100644 index 0000000..bb5c6e2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlresultsrow.cpp @@ -0,0 +1,42 @@ +#include "sqlresultsrow.h" + +SqlResultsRow::SqlResultsRow() +{ +} + +SqlResultsRow::~SqlResultsRow() +{ +} + +const QVariant SqlResultsRow::value(const QString &key) const +{ + return valuesMap[key]; +} + +const QHash<QString, QVariant> &SqlResultsRow::valueMap() const +{ + return valuesMap; +} + +const QList<QVariant>& SqlResultsRow::valueList() const +{ + return values; +} + +const QVariant SqlResultsRow::value(int idx) const +{ + if (idx < 0 || idx >= values.size()) + return QVariant(); + + return values[idx]; +} + +bool SqlResultsRow::contains(const QString &key) const +{ + return valuesMap.contains(key); +} + +bool SqlResultsRow::contains(int idx) const +{ + return idx >= 0 && idx < values.size(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/db/sqlresultsrow.h b/SQLiteStudio3/coreSQLiteStudio/db/sqlresultsrow.h new file mode 100644 index 0000000..2a5e17c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/sqlresultsrow.h @@ -0,0 +1,102 @@ +#ifndef SQLRESULTSROW_H +#define SQLRESULTSROW_H + +#include "coreSQLiteStudio_global.h" +#include <QVariant> +#include <QList> +#include <QHash> +#include <QSharedPointer> + +/** @file */ + +/** + * @brief SQL query results row. + * + * Single row of data from SQL query results. It already has all columns stored in memory, + * so it doesn't matter if you read only one column, or all columns available in the row. + * + * You will never encounter object of exactly this class, as it has protected constructor + * and has no methods to populate internal data members. Instead of creating objects of this class, + * other class inherits it and handles populating internal data members, then this class + * is just an interface to read data from it. + * + * In other words, it's kind of an abstract class. + */ +class API_EXPORT SqlResultsRow +{ + public: + /** + * @brief Releases resources. + */ + virtual ~SqlResultsRow(); + + /** + * @brief Gets value for given column. + * @param key Column name. + * @return Value from requested column. If column name is invalid, the invalid QVariant is returned. + */ + const QVariant value(const QString& key) const; + + /** + * @brief Gets value for given column. + * @param idx 0-based index of column. + * @return Value from requested column. If index was invalid, the invalid QVariant is returned. + */ + const QVariant value(int idx) const; + + /** + * @brief Gets table of column->value entries. + * @return Hash table with column names as keys and QVariants as their values. + * + * Note, that QHash doesn't guarantee order of entries. If you want to iterate through columns + * in order they were returned from the database, use valueList(), or iterate through SqlResults::getColumnNames() + * and use it to call value(). + */ + const QHash<QString, QVariant>& valueMap() const; + + /** + * @brief Gets list of values in this row. + * @return Ordered list of values in the row. + * + * Note, that this method returns values in order they were returned from database. + */ + const QList<QVariant>& valueList() const; + + /** + * @brief Tests if the row contains given column name. + * @param key Column name. Case sensitive. + * @return true if column exists in the row, or false otherwise. + */ + bool contains(const QString& key) const; + + /** + * @brief Tests if the row has column indexed with given number. + * @param idx 0-based index to test. + * @return true if index is in range of existing columns, or false if it's greater than "number of columns - 1", or if it's less than 0. + */ + bool contains(int idx) const; + + protected: + SqlResultsRow(); + + /** + * @brief Columns and their values in the row. + */ + QHash<QString,QVariant> valuesMap; + /** + * @brief Ordered list of values in the row. + * + * Technical note: + * We keep list of values next to valuesMap, so we have it in the same order as column names when asked by valueList(). + * This looks like having redundant data storage, but Qt container classes (such as QVariant) + * use smart pointers to keep their data internally, so here we actually keep only reference objects. + */ + QList<QVariant> values; +}; + +/** + * @brief Shared pointer to SQL query results row. + */ +typedef QSharedPointer<SqlResultsRow> SqlResultsRowPtr; + +#endif // SQLRESULTSROW_H diff --git a/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h b/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h new file mode 100644 index 0000000..eff3621 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/db/stdsqlite3driver.h @@ -0,0 +1,83 @@ +#ifndef STDSQLITE3DRIVER_H +#define STDSQLITE3DRIVER_H + +#define STD_SQLITE3_DRIVER(Name, Label, Prefix, UppercasePrefix) \ + struct Name \ + { \ + static_char* label = Label; \ + \ + static const int OK = UppercasePrefix##SQLITE_OK; \ + static const int ERROR = UppercasePrefix##SQLITE_ERROR; \ + static const int OPEN_READWRITE = UppercasePrefix##SQLITE_OPEN_READWRITE; \ + static const int OPEN_CREATE = UppercasePrefix##SQLITE_OPEN_CREATE; \ + static const int UTF8 = UppercasePrefix##SQLITE_UTF8; \ + static const int INTEGER = UppercasePrefix##SQLITE_INTEGER; \ + static const int FLOAT = UppercasePrefix##SQLITE_FLOAT; \ + static const int NULL_TYPE = UppercasePrefix##SQLITE_NULL; \ + static const int BLOB = UppercasePrefix##SQLITE_BLOB; \ + static const int MISUSE = UppercasePrefix##SQLITE_MISUSE; \ + static const int BUSY = UppercasePrefix##SQLITE_BUSY; \ + static const int ROW = UppercasePrefix##SQLITE_ROW; \ + static const int DONE = UppercasePrefix##SQLITE_DONE; \ + \ + typedef Prefix##sqlite3 handle; \ + typedef Prefix##sqlite3_stmt stmt; \ + typedef Prefix##sqlite3_context context; \ + typedef Prefix##sqlite3_value value; \ + typedef Prefix##sqlite3_int64 int64; \ + typedef Prefix##sqlite3_destructor_type destructor_type; \ + \ + static destructor_type TRANSIENT() {return UppercasePrefix##SQLITE_TRANSIENT;} \ + static void interrupt(handle* arg) {Prefix##sqlite3_interrupt(arg);} \ + static const void *value_blob(value* arg) {return Prefix##sqlite3_value_blob(arg);} \ + static double value_double(value* arg) {return Prefix##sqlite3_value_double(arg);} \ + static int64 value_int64(value* arg) {return Prefix##sqlite3_value_int64(arg);} \ + static const void *value_text16(value* arg) {return Prefix##sqlite3_value_text16(arg);} \ + static int value_bytes(value* arg) {return Prefix##sqlite3_value_bytes(arg);} \ + static int value_bytes16(value* arg) {return Prefix##sqlite3_value_bytes16(arg);} \ + static int value_type(value* arg) {return Prefix##sqlite3_value_type(arg);} \ + static int bind_blob(stmt* a1, int a2, const void* a3, int a4, void(*a5)(void*)) {return Prefix##sqlite3_bind_blob(a1, a2, a3, a4, a5);} \ + static int bind_double(stmt* a1, int a2, double a3) {return Prefix##sqlite3_bind_double(a1, a2, a3);} \ + static int bind_int(stmt* a1, int a2, int a3) {return Prefix##sqlite3_bind_int(a1, a2, a3);} \ + static int bind_int64(stmt* a1, int a2, int64 a3) {return Prefix##sqlite3_bind_int64(a1, a2, a3);} \ + static int bind_null(stmt* a1, int a2) {return Prefix##sqlite3_bind_null(a1, a2);} \ + static int bind_text16(stmt* a1, int a2, const void* a3, int a4, void(*a5)(void*)) {return Prefix##sqlite3_bind_text16(a1, a2, a3, a4, a5);} \ + static void result_blob(context* a1, const void* a2, int a3, void(*a4)(void*)) {Prefix##sqlite3_result_blob(a1, a2, a3, a4);} \ + static void result_double(context* a1, double a2) {Prefix##sqlite3_result_double(a1, a2);} \ + static void result_error16(context* a1, const void* a2, int a3) {Prefix##sqlite3_result_error16(a1, a2, a3);} \ + static void result_int(context* a1, int a2) {Prefix##sqlite3_result_int(a1, a2);} \ + static void result_int64(context* a1, int64 a2) {Prefix##sqlite3_result_int64(a1, a2);} \ + static void result_null(context* a1) {Prefix##sqlite3_result_null(a1);} \ + static void result_text16(context* a1, const void* a2, int a3, void(*a4)(void*)) {Prefix##sqlite3_result_text16(a1, a2, a3, a4);} \ + static int open_v2(const char *a1, handle **a2, int a3, const char *a4) {return Prefix##sqlite3_open_v2(a1, a2, a3, a4);} \ + static int finalize(stmt *arg) {return Prefix##sqlite3_finalize(arg);} \ + static const char *errmsg(handle* arg) {return Prefix##sqlite3_errmsg(arg);} \ + static int extended_errcode(handle* arg) {return Prefix##sqlite3_extended_errcode(arg);} \ + static const void *column_blob(stmt* arg1, int arg2) {return Prefix##sqlite3_column_blob(arg1, arg2);} \ + static int column_bytes(stmt* arg1, int arg2) {return Prefix##sqlite3_column_bytes(arg1, arg2);} \ + static int column_bytes16(stmt* arg1, int arg2) {return Prefix##sqlite3_column_bytes16(arg1, arg2);} \ + static double column_double(stmt* arg1, int arg2) {return Prefix##sqlite3_column_double(arg1, arg2);} \ + static int64 column_int64(stmt* arg1, int arg2) {return Prefix##sqlite3_column_int64(arg1, arg2);} \ + static const void *column_text16(stmt* arg1, int arg2) {return Prefix##sqlite3_column_text16(arg1, arg2);} \ + static const char *column_name(stmt* arg1, int arg2) {return Prefix##sqlite3_column_name(arg1, arg2);} \ + static int column_type(stmt* arg1, int arg2) {return Prefix##sqlite3_column_type(arg1, arg2);} \ + static int column_count(stmt* arg1) {return Prefix##sqlite3_column_count(arg1);} \ + static int changes(handle* arg) {return Prefix##sqlite3_changes(arg);} \ + static int last_insert_rowid(handle* arg) {return Prefix##sqlite3_last_insert_rowid(arg);} \ + static int step(stmt* arg) {return Prefix##sqlite3_step(arg);} \ + static int reset(stmt* arg) {return Prefix##sqlite3_reset(arg);} \ + static int close(handle* arg) {return Prefix##sqlite3_close(arg);} \ + static int enable_load_extension(handle* arg1, int arg2) {return Prefix##sqlite3_enable_load_extension(arg1, arg2);} \ + static void* user_data(context* arg) {return Prefix##sqlite3_user_data(arg);} \ + static void* aggregate_context(context* arg1, int arg2) {return Prefix##sqlite3_aggregate_context(arg1, arg2);} \ + static int collation_needed(handle* a1, void* a2, void(*a3)(void*,handle*,int eTextRep,const char*)) {return Prefix##sqlite3_collation_needed(a1, a2, a3);} \ + static int prepare_v2(handle *a1, const char *a2, int a3, stmt **a4, const char **a5) {return Prefix##sqlite3_prepare_v2(a1, a2, a3, a4, a5);} \ + static int create_function(handle *a1, const char *a2, int a3, int a4, void *a5, void (*a6)(context*,int,value**), void (*a7)(context*,int,value**), void (*a8)(context*)) \ + {return Prefix##sqlite3_create_function(a1, a2, a3, a4, a5, a6, a7, a8);} \ + static int create_function_v2(handle *a1, const char *a2, int a3, int a4, void *a5, void (*a6)(context*,int,value**), void (*a7)(context*,int,value**), void (*a8)(context*), void(*a9)(void*)) \ + {return Prefix##sqlite3_create_function_v2(a1, a2, a3, a4, a5, a6, a7, a8, a9);} \ + static int create_collation_v2(handle* a1, const char *a2, int a3, void *a4, int(*a5)(void*,int,const void*,int,const void*), void(*a6)(void*)) \ + {return Prefix##sqlite3_create_collation_v2(a1, a2, a3, a4, a5, a6);} \ + }; + +#endif // STDSQLITE3DRIVER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/dbattacher.cpp b/SQLiteStudio3/coreSQLiteStudio/dbattacher.cpp new file mode 100644 index 0000000..382f515 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/dbattacher.cpp @@ -0,0 +1,11 @@ +#include "dbattacher.h"
+#include "impl/dbattacherimpl.h"
+
+DbAttacher::~DbAttacher()
+{
+}
+
+DbAttacherFactory::~DbAttacherFactory()
+{
+}
+
diff --git a/SQLiteStudio3/coreSQLiteStudio/dbattacher.h b/SQLiteStudio3/coreSQLiteStudio/dbattacher.h new file mode 100644 index 0000000..5b94832 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/dbattacher.h @@ -0,0 +1,99 @@ +#ifndef DBATTACHER_H
+#define DBATTACHER_H
+
+#include "parser/ast/sqlitequery.h"
+#include "common/bistrhash.h"
+#include "common/strhash.h"
+#include <QList>
+
+class Db;
+
+/**
+ * @brief Transparent attaching of databases used in the query.
+ *
+ * Scans query (or queries) for names of databases registered in DbManager.
+ * If a database is recognized, it's automatically attached and the mapping
+ * of its database name to its attach name is created for later information.
+ *
+ * The attacher looks for database names only at those spots in the query,
+ * where the database name is valid by SQLite syntax (this is accomplished
+ * with SqliteStatement::getContextDatabaseTokens()).
+ */
+class API_EXPORT DbAttacher
+{
+ public:
+ /**
+ * @brief Default destructor.
+ */
+ virtual ~DbAttacher();
+
+ /**
+ * @brief Scans for databases in given query and attaches them.
+ * @param query Query string to be executed.
+ * @return true on success, or false on failure.
+ *
+ * The method can fail if any of databases used in the query could
+ * not be attached (the ATTACH statement caused error).
+ *
+ * To get query with database names replaced with attach names use getQuery().
+ */
+ virtual bool attachDatabases(const QString& query) = 0;
+
+ /**
+ * Be aware that database names in queries are replaced with attach names in SqliteStatement::tokens,
+ * thus modified query can be achived with SqliteStatement::detokenize(). This also means
+ * that the input queries will contain modified token list.
+ *
+ * @overload
+ */
+ virtual bool attachDatabases(const QList<SqliteQueryPtr>& queries) = 0;
+
+ /**
+ * @overload
+ */
+ virtual bool attachDatabases(SqliteQueryPtr query) = 0;
+
+ /**
+ * @brief Detaches all databases attached by the attacher.
+ */
+ virtual void detachDatabases() = 0;
+
+ /**
+ * @brief Provides mapping of database names to their attach names.
+ * @return Database name to attach name mapping.
+ *
+ * The returned map is bi-directional, so you can easly translate database name to attach name
+ * and vice versa. Left values of the map are database names (as registered in DbManager)
+ * and right values are attach names assigned to them.
+ */
+ virtual BiStrHash getDbNameToAttach() const = 0;
+
+ /**
+ * @brief Provides query string updated with attach names.
+ * @return Query string.
+ */
+ virtual QString getQuery() const = 0;
+};
+
+/**
+ * @brief Abstract factory for DbAttacher objects.
+ *
+ * The abstract factory is accessed from SQLiteStudio class in order to produce DbAttacher instances.
+ * The default DbAttacherFactory implementation (DbAttacherFactoryImpl) produces default DbAttacher instances
+ * (DbAttacherImpl), but it can be replaced with other factory to produce other attachers, just like unit tests
+ * in this project do.
+ */
+class API_EXPORT DbAttacherFactory
+{
+ public:
+ virtual ~DbAttacherFactory();
+
+ /**
+ * @brief Produces single attacher instance.
+ * @param db Database to produce attacher for.
+ * @return Attacher instance. Factory doesn't own it, you have to delete it when you're done.
+ */
+ virtual DbAttacher* create(Db* db) = 0;
+};
+
+#endif // DBATTACHER_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/dbobjectorganizer.cpp b/SQLiteStudio3/coreSQLiteStudio/dbobjectorganizer.cpp new file mode 100644 index 0000000..61f5cb2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/dbobjectorganizer.cpp @@ -0,0 +1,777 @@ +#include "dbobjectorganizer.h" +#include "db/db.h" +#include "common/utils_sql.h" +#include "datatype.h" +#include "services/notifymanager.h" +#include "db/attachguard.h" +#include "dbversionconverter.h" +#include <QDebug> +#include <QThreadPool> + +DbObjectOrganizer::DbObjectOrganizer() +{ + // Default organizaer denies any referenced objects + confirmFunction = [](const QStringList&) -> bool {return false;}; + nameConflictResolveFunction = [](QString&) -> bool {return false;}; + conversionConfimFunction = [](const QList<QPair<QString,QString>>&) -> bool {return false;}; + conversionErrorsConfimFunction = [](const QHash<QString,QSet<QString>>&) -> bool {return false;}; + init(); +} + +DbObjectOrganizer::DbObjectOrganizer(DbObjectOrganizer::ReferencedTablesConfimFunction confirmFunction, NameConflictResolveFunction nameConflictResolveFunction, + ConversionConfimFunction conversionConfimFunction, ConversionErrorsConfimFunction conversionErrorsConfimFunction) : + confirmFunction(confirmFunction), nameConflictResolveFunction(nameConflictResolveFunction), conversionConfimFunction(conversionConfimFunction), + conversionErrorsConfimFunction(conversionErrorsConfimFunction) +{ + init(); +} + +DbObjectOrganizer::~DbObjectOrganizer() +{ + safe_delete(srcResolver); + safe_delete(dstResolver); + safe_delete(versionConverter); +} + +void DbObjectOrganizer::init() +{ + versionConverter = new DbVersionConverter(); + connect(this, SIGNAL(preparetionFinished()), this, SLOT(processPreparationFinished())); +} + +void DbObjectOrganizer::copyObjectsToDb(Db* srcDb, const QStringList& objNames, Db* dstDb, bool includeData, bool includeIndexes, bool includeTriggers) +{ + copyOrMoveObjectsToDb(srcDb, objNames.toSet(), dstDb, includeData, includeIndexes, includeTriggers, false); +} + +void DbObjectOrganizer::moveObjectsToDb(Db* srcDb, const QStringList& objNames, Db* dstDb, bool includeData, bool includeIndexes, bool includeTriggers) +{ + copyOrMoveObjectsToDb(srcDb, objNames.toSet(), dstDb, includeData, includeIndexes, includeTriggers, true); +} + +void DbObjectOrganizer::interrupt() +{ + QMutexLocker locker(&interruptMutex); + interrupted = true; + srcDb->interrupt(); + dstDb->interrupt(); +} + +bool DbObjectOrganizer::isExecuting() +{ + QMutexLocker lock(&executingMutex); + return executing; +} + +void DbObjectOrganizer::run() +{ + switch (mode) + { + case Mode::PREPARE_TO_COPY_OBJECTS: + case Mode::PREPARE_TO_MOVE_OBJECTS: + processPreparation(); + break; + case Mode::COPY_OBJECTS: + case Mode::MOVE_OBJECTS: + emitFinished(processAll()); + break; + case Mode::unknown: + qCritical() << "DbObjectOrganizer::run() called with unknown mode."; + emitFinished(false); + return; + } +} + +void DbObjectOrganizer::reset() +{ + attachName = QString(); + mode = Mode::COPY_OBJECTS; + srcDb = nullptr; + dstDb = nullptr; + srcNames.clear(); + srcTables.clear(); + srcIndexes.clear(); + srcTriggers.clear(); + srcViews.clear(); + renamed.clear(); + srcTable = QString::null; + includeData = false; + includeIndexes = false; + includeTriggers = false; + deleteSourceObjects = false; + referencedTables.clear(); + diffListToConfirm.clear(); + errorsToConfirm.clear(); + binaryColumns.clear(); + safe_delete(srcResolver); + safe_delete(dstResolver); + interrupted = false; + setExecuting(false); +} + +void DbObjectOrganizer::copyOrMoveObjectsToDb(Db* srcDb, const QSet<QString>& objNames, Db* dstDb, bool includeData, bool includeIndexes, bool includeTriggers, bool move) +{ + if (isExecuting()) + { + notifyError("Schema modification is currently in progress. Please try again in a moment."); + qWarning() << "Tried to call DbObjectOrganizer::copyOrMoveObjectsToDb() while other execution was in progress."; + return; + } + + reset(); + setExecuting(true); + if (move) + { + mode = Mode::PREPARE_TO_MOVE_OBJECTS; + deleteSourceObjects = true; + } + else + { + mode = Mode::PREPARE_TO_COPY_OBJECTS; + } + + this->srcNames = objNames; + this->includeData = includeData; + this->includeIndexes = includeIndexes; + this->includeTriggers = includeTriggers; + setSrcAndDstDb(srcDb, dstDb); + + QThreadPool::globalInstance()->start(this); +} + +void DbObjectOrganizer::processPreparation() +{ + StrHash<SqliteQueryPtr> allParsedObjects = srcResolver->getAllParsedObjects(); + StrHash<SchemaResolver::ObjectDetails> details = srcResolver->getAllObjectDetails(); + for (const QString& srcName : srcNames) + { + if (!details.contains(srcName)) + { + qDebug() << "Object" << srcName << "not found in source database, skipping."; + continue; + } + + switch (details[srcName].type) + { + case SchemaResolver::TABLE: + srcTables << srcName; + findBinaryColumns(srcName, allParsedObjects); + collectReferencedTables(srcName, allParsedObjects); + collectReferencedIndexes(srcName); + collectReferencedTriggersForTable(srcName); + break; + case SchemaResolver::INDEX: + break; + case SchemaResolver::TRIGGER: + break; + case SchemaResolver::VIEW: + srcViews << srcName; + collectReferencedTriggersForView(srcName); + break; + case SchemaResolver::ANY: + qCritical() << "Unhandled type in DbObjectOrganizer::processPreparation():" << SchemaResolver::objectTypeToString(details[srcName].type); + break; + } + } + + if (referencedTables.size() > 0 && !confirmFunction(referencedTables.toList())) + referencedTables.clear(); + + for (const QString& srcTable : referencedTables) + { + collectReferencedIndexes(srcTable); + collectReferencedTriggersForTable(srcTable); + } + + collectDiffs(details); + + emit preparetionFinished(); +} + +bool DbObjectOrganizer::processAll() +{ + if (!srcDb->isOpen()) + { + //notifyError(tr("Cannot copy or move objects from closed database. Open it first.")); // TODO this is in another thread - handle it + return false; + } + + if (!dstDb->isOpen()) + { + //notifyError(tr("Cannot copy or move objects to closed database. Open it first.")); // TODO this is in another thread - handle it + return false; + } + + // Attaching target db if needed + AttachGuard attach; + if (!(referencedTables + srcTables).isEmpty()) + { + attach = srcDb->guardedAttach(dstDb, true); + attachName = attach->getName(); + } + + if (!srcDb->begin()) + { + // TODO message + return false; + } + + if (!dstDb->begin()) + { + // TODO message + srcDb->rollback(); + return false; + } + + if (!setFkEnabled(false)) + { + srcDb->rollback(); + dstDb->rollback(); + return false; + } + + bool res = false; + switch (mode) + { + case Mode::COPY_OBJECTS: + case Mode::MOVE_OBJECTS: + { + res = processDbObjects(); + break; + } + case Mode::PREPARE_TO_COPY_OBJECTS: + case Mode::PREPARE_TO_MOVE_OBJECTS: + { + qCritical() << "DbObjectOrganizer::processAll() called with PREAPRE mode."; + return false; // this method should not be called with this mode + } + case Mode::unknown: + { + qWarning() << "Unhandled unknown mode in DbObjectOrganizer."; + return false; + } + } + + if (!res) + { + srcDb->rollback(); + dstDb->rollback(); + setFkEnabled(true); + return false; + } + + if (!setFkEnabled(true)) + { + srcDb->rollback(); + dstDb->rollback(); + return false; + } + + if (!dstDb->commit()) + { + // notifyError(tr("Could not commit transaction in database '%1'.").arg(dstDb->getName())); // TODO this is in another thread, cannot use notifyError + dstDb->rollback(); + srcDb->rollback(); + return false; + } + + if (!srcDb->commit()) + { + // TODO message - this can happen also for attached db operations, so also for creating objects in dstDb, so this affects not only srcDb, but also dstDb + srcDb->rollback(); + return false; + } + + return true; +} + +bool DbObjectOrganizer::processDbObjects() +{ + for (const QString& table : (referencedTables + srcTables)) + { + if (!copyTableToDb(table) || isInterrupted()) + return false; + } + + for (const QString& view : srcViews) + { + if (!copyViewToDb(view) || isInterrupted()) + return false; + } + + if (includeIndexes) + { + for (const QString& idx : srcIndexes) + { + if (!copyIndexToDb(idx) || isInterrupted()) + return false; + } + } + + if (includeTriggers) + { + for (const QString& trig : srcTriggers) + { + if (!copyTriggerToDb(trig) || isInterrupted()) + return false; + } + } + + if (deleteSourceObjects) + { + for (const QString& table : (referencedTables + srcTables)) + dropTable(table); + + for (const QString& view : srcViews) + dropView(view); + } + + return true; +} + +bool DbObjectOrganizer::resolveNameConflicts() +{ + QSet<QString> names; + QStringList namesInDst; + switch (mode) + { + case Mode::PREPARE_TO_COPY_OBJECTS: + case Mode::PREPARE_TO_MOVE_OBJECTS: + case Mode::COPY_OBJECTS: + case Mode::MOVE_OBJECTS: + { + names = referencedTables + srcTables + srcViews + srcIndexes + srcTriggers; + namesInDst = dstResolver->getAllObjects(); + break; + } + case Mode::unknown: + { + qWarning() << "Unhandled unknown mode in DbObjectOrganizer::resolveNameConflicts()."; + return false; + } + } + + QString finalName; + for (const QString& srcName : names) + { + finalName = srcName; + while (namesInDst.contains(finalName, Qt::CaseInsensitive)) + { + if (!nameConflictResolveFunction(finalName)) + return false; + } + if (finalName != srcName) + renamed[srcName] = finalName; + } + return true; +} + +bool DbObjectOrganizer::copyTableToDb(const QString& table) +{ + QString ddl; + QString targetTable = table; +// AttachGuard attach = srcDb->guardedAttach(dstDb, true); +// QString attachName = attach->getName(); + if (renamed.contains(table) || !attachName.isNull()) + { + SqliteQueryPtr parsedObject = srcResolver->getParsedObject(table, SchemaResolver::TABLE); + SqliteCreateTablePtr createTable = parsedObject.dynamicCast<SqliteCreateTable>(); + if (!createTable) + { + qCritical() << "Could not parse table while copying:" << table << ", ddl:" << srcResolver->getObjectDdl(table, SchemaResolver::TABLE); + notifyError(tr("Error while creating table in target database: %1").arg(tr("Could not parse table."))); + return false; + } + + if (renamed.contains(table)) + targetTable = renamed[table]; + + createTable->table = targetTable; + if (!attachName.isNull()) + createTable->database = attachName; + + createTable->rebuildTokens(); + ddl = createTable->detokenize(); + } + else + { + ddl = srcResolver->getObjectDdl(table, SchemaResolver::TABLE); + } + + ddl = convertDdlToDstVersion(ddl); + if (ddl.trimmed() == ";") // empty query, result of ignored errors in UI + return true; + + SqlQueryPtr result; + + if (attachName.isNull()) + result = dstDb->exec(ddl); + else + result = srcDb->exec(ddl); // uses attachName to create object in attached db + + if (result->isError()) + { + notifyError(tr("Error while creating table in target database: %1").arg(result->getErrorText())); + return false; + } + + if (!includeData) + return true; + + if (isInterrupted()) + return false; + + srcTable = table; + bool res; + if (attachName.isNull()) + { + notifyInfo(tr("Database %1 could not be attached to database %2, so the data of table %3 will be copied " + "with SQLiteStudio as a mediator. This method can be slow for huge tables, so please be patient.") + .arg(dstDb->getName(), srcDb->getName(), srcTable)); + + res = copyDataAsMiddleware(targetTable); + } + else + { + res = copyDataUsingAttach(targetTable); + } + return res; +} + +bool DbObjectOrganizer::copyDataAsMiddleware(const QString& table) +{ + QStringList srcColumns = srcResolver->getTableColumns(srcTable); + QString wrappedSrcTable = wrapObjIfNeeded(srcTable, srcDb->getDialect()); + SqlQueryPtr results = srcDb->prepare("SELECT * FROM " + wrappedSrcTable); + setupSqlite2Helper(results, table, srcColumns); + if (!results->execute()) + { + notifyError(tr("Error while copying data for table %1: %2").arg(table).arg(results->getErrorText())); + return false; + } + + QStringList argPlaceholderList; + for (int i = 0, total = srcColumns.size(); i < total; ++i) + argPlaceholderList << "?"; + + QString wrappedDstTable = wrapObjIfNeeded(table, dstDb->getDialect()); + QString sql = "INSERT INTO " + wrappedDstTable + " VALUES (" + argPlaceholderList.join(", ") + ")"; + SqlQueryPtr insertQuery = dstDb->prepare(sql); + + SqlResultsRowPtr row; + int i = 0; + while (results->hasNext()) + { + row = results->next(); + if (!row) + { + notifyError(tr("Error while copying data to table %1: %2").arg(table).arg(results->getErrorText())); + return false; + } + + insertQuery->setArgs(row->valueList()); + if (!insertQuery->execute()) + { + notifyError(tr("Error while copying data to table %1: %2").arg(table).arg(insertQuery->getErrorText())); + return false; + } + + if ((i % 1000) == 0 && isInterrupted()) + return false; + + i++; + } + + if (isInterrupted()) + return false; + + return true; +} + +bool DbObjectOrganizer::copyDataUsingAttach(const QString& table) +{ + QString wrappedSrcTable = wrapObjIfNeeded(srcTable, srcDb->getDialect()); + QString wrappedDstTable = wrapObjIfNeeded(table, srcDb->getDialect()); + SqlQueryPtr results = srcDb->exec("INSERT INTO " + attachName + "." + wrappedDstTable + " SELECT * FROM " + wrappedSrcTable); + if (results->isError()) + { + notifyError(tr("Error while copying data to table %1: %2").arg(table).arg(results->getErrorText())); + return false; + } + return true; +} + +void DbObjectOrganizer::setupSqlite2Helper(SqlQueryPtr query, const QString& table, const QStringList& colNames) +{ + Sqlite2ColumnDataTypeHelper* sqlite2Helper = dynamic_cast<Sqlite2ColumnDataTypeHelper*>(query.data()); + if (sqlite2Helper && binaryColumns.contains(table)) + { + int i = 0; + QStringList binCols = binaryColumns[table]; + for (const QString& colName : colNames) + { + if (binCols.contains(colName)) + sqlite2Helper->setBinaryType(i); + + i++; + } + } +} + +void DbObjectOrganizer::dropTable(const QString& table) +{ + dropObject(table, "TABLE"); +} + +void DbObjectOrganizer::dropView(const QString& view) +{ + dropObject(view, "VIEW"); +} + +void DbObjectOrganizer::dropObject(const QString& name, const QString& type) +{ + QString wrappedSrcObj = wrapObjIfNeeded(name, srcDb->getDialect()); + SqlQueryPtr results = srcDb->exec("DROP " + type + " " + wrappedSrcObj); + if (results->isError()) + { + notifyWarn(tr("Error while dropping source view %1: %2\nTables, indexes, triggers and views copied to database %3 will remain.") + .arg(name).arg(results->getErrorText()).arg(dstDb->getName())); + } +} + +bool DbObjectOrganizer::copyViewToDb(const QString& view) +{ + return copySimpleObjectToDb(view, tr("Error while creating view in target database: %1")); +} + +bool DbObjectOrganizer::copyIndexToDb(const QString& index) +{ + return copySimpleObjectToDb(index, tr("Error while creating index in target database: %1")); +} + +bool DbObjectOrganizer::copyTriggerToDb(const QString& trigger) +{ + return copySimpleObjectToDb(trigger, tr("Error while creating trigger in target database: %1")); +} + +bool DbObjectOrganizer::copySimpleObjectToDb(const QString& name, const QString& errorMessage) +{ + QString ddl = srcResolver->getObjectDdl(name, SchemaResolver::ANY); + QString convertedDdl = convertDdlToDstVersion(ddl); + if (convertedDdl.trimmed() == ";") // empty query, result of ignored errors in UI + return true; + + SqlQueryPtr result = dstDb->exec(convertedDdl); + if (result->isError()) + { + notifyError(errorMessage.arg(result->getErrorText())); + qDebug() << "DDL that caused error in DbObjectOrganizer::copySimpleObjectToDb():" << ddl << "\nAfter converting:" << convertedDdl; + return false; + } + + return true; +} + +QSet<QString> DbObjectOrganizer::resolveReferencedTables(const QString& table, const QList<SqliteCreateTablePtr>& parsedTables) +{ + QSet<QString> tables = SchemaResolver::getFkReferencingTables(table, parsedTables).toSet(); + for (const QString& fkTable : tables) + tables += SchemaResolver::getFkReferencingTables(fkTable, parsedTables).toSet(); + + tables.remove(table); // if it appeared somewhere in the references - we still don't need it here, it's the table we asked by in the first place + return tables; +} + +void DbObjectOrganizer::collectDiffs(const StrHash<SchemaResolver::ObjectDetails>& details) +{ + if (srcDb->getVersion() == dstDb->getVersion()) + return; + + + int dstVersion = dstDb->getVersion(); + QSet<QString> names = srcTables + srcViews + referencedTables + srcIndexes + srcTriggers; + for (const QString& name : names) + { + if (!details.contains(name)) + { + qCritical() << "Object named" << name << "not found in details when trying to prepare Diff for copying or moving object."; + continue; + } + + versionConverter->reset(); + if (dstVersion == 3) + versionConverter->convert2To3(details[name].ddl); + else + versionConverter->convert3To2(details[name].ddl); + + diffListToConfirm += versionConverter->getDiffList(); + if (!versionConverter->getErrors().isEmpty()) + errorsToConfirm[name] = versionConverter->getErrors(); + } +} + +QString DbObjectOrganizer::convertDdlToDstVersion(const QString& ddl) +{ + if (srcDb->getVersion() == dstDb->getVersion()) + return ddl; + + if (dstDb->getVersion() == 3) + return versionConverter->convert2To3(ddl); + else + return versionConverter->convert3To2(ddl); +} + +void DbObjectOrganizer::collectReferencedTables(const QString& table, const StrHash<SqliteQueryPtr>& allParsedObjects) +{ + QList<SqliteCreateTablePtr> parsedTables; + SqliteCreateTablePtr parsedTable; + for (SqliteQueryPtr query : allParsedObjects.values()) + { + parsedTable = query.dynamicCast<SqliteCreateTable>(); + if (parsedTable) + parsedTables << parsedTable; + } + + QSet<QString> tables = resolveReferencedTables(table, parsedTables); + for (const QString& refTable : tables) + { + if (!referencedTables.contains(refTable) && !srcTables.contains(refTable)) + referencedTables << refTable; + } +} + +void DbObjectOrganizer::collectReferencedIndexes(const QString& table) +{ + srcIndexes += srcResolver->getIndexesForTable(table).toSet(); +} + +void DbObjectOrganizer::collectReferencedTriggersForTable(const QString& table) +{ + srcTriggers += srcResolver->getTriggersForTable(table).toSet(); +} + +void DbObjectOrganizer::collectReferencedTriggersForView(const QString& view) +{ + srcTriggers += srcResolver->getTriggersForView(view).toSet(); +} + +void DbObjectOrganizer::findBinaryColumns(const QString& table, const StrHash<SqliteQueryPtr>& allParsedObjects) +{ + if (!allParsedObjects.contains(table)) + { + qWarning() << "Parsed objects don't have table" << table << "in DbObjectOrganizer::findBinaryColumns()"; + return; + } + + SqliteQueryPtr query = allParsedObjects[table]; + SqliteCreateTablePtr createTable = query.dynamicCast<SqliteCreateTable>(); + if (!createTable) + { + qWarning() << "Not CreateTable in DbObjectOrganizer::findBinaryColumns()"; + return; + } + + for (SqliteCreateTable::Column* column : createTable->columns) + { + if (!column->type) + continue; + + if (DataType::isBinary(column->type->name)) + binaryColumns[table] << column->name; + } +} + +bool DbObjectOrganizer::setFkEnabled(bool enabled) +{ + if (dstDb->getVersion() == 2) + return true; + + SqlQueryPtr result = dstDb->exec(QString("PRAGMA foreign_keys = %1").arg(enabled ? "on" : "off")); + if (result->isError()) + { + // notifyError(tr("Error while executing PRAGMA on target database: %1").arg(result->getErrorText())); // TODO this is in another thread, cannot use notifyError + return false; + } + return true; +} + +bool DbObjectOrganizer::isInterrupted() +{ + QMutexLocker locker(&interruptMutex); + return interrupted; +} + +void DbObjectOrganizer::setExecuting(bool executing) +{ + QMutexLocker lock(&executingMutex); + this->executing = executing; +} + +void DbObjectOrganizer::setSrcAndDstDb(Db* srcDb, Db* dstDb) +{ + safe_delete(srcResolver); + safe_delete(dstResolver); + this->srcDb = srcDb; + this->dstDb = dstDb; + srcResolver = new SchemaResolver(srcDb); + dstResolver = new SchemaResolver(dstDb); + srcResolver->setIgnoreSystemObjects(true); + dstResolver->setIgnoreSystemObjects(true); +} + +void DbObjectOrganizer::emitFinished(bool success) +{ + switch (mode) + { + case Mode::COPY_OBJECTS: + case Mode::PREPARE_TO_COPY_OBJECTS: + emit finishedDbObjectsCopy(success, srcDb, dstDb); + break; + case Mode::PREPARE_TO_MOVE_OBJECTS: + case Mode::MOVE_OBJECTS: + emit finishedDbObjectsMove(success, srcDb, dstDb); + break; + case Mode::unknown: + break; + } + setExecuting(false); +} + +void DbObjectOrganizer::processPreparationFinished() +{ + if (errorsToConfirm.size() > 0 && !conversionErrorsConfimFunction(errorsToConfirm)) + { + emitFinished(false); + return; + } + + if (diffListToConfirm.size() > 0 && !conversionConfimFunction(diffListToConfirm)) + { + emitFinished(false); + return; + } + + if (!resolveNameConflicts()) + { + emitFinished(false); + return; + } + + switch (mode) + { + case Mode::PREPARE_TO_COPY_OBJECTS: + mode = Mode::COPY_OBJECTS; + break; + case Mode::PREPARE_TO_MOVE_OBJECTS: + mode = Mode::MOVE_OBJECTS; + break; + case Mode::COPY_OBJECTS: + case Mode::MOVE_OBJECTS: + case Mode::unknown: + qCritical() << "DbObjectOrganizer::processPreparationFinished() called with a not PREPARE mode."; + emitFinished(false); + return; + } + + QThreadPool::globalInstance()->start(this); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/dbobjectorganizer.h b/SQLiteStudio3/coreSQLiteStudio/dbobjectorganizer.h new file mode 100644 index 0000000..70b748f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/dbobjectorganizer.h @@ -0,0 +1,152 @@ +#ifndef DBOBJECTORGANIZER_H +#define DBOBJECTORGANIZER_H + +#include "coreSQLiteStudio_global.h" +#include "interruptable.h" +#include "schemaresolver.h" +#include <QString> +#include <QObject> +#include <QRunnable> +#include <QMutex> +#include <QStringList> +#include <QHash> + +class Db; +class DbVersionConverter; + +class API_EXPORT DbObjectOrganizer : public QObject, public QRunnable, public Interruptable +{ + Q_OBJECT + + public: + typedef std::function<bool(const QStringList& tables)> ReferencedTablesConfimFunction; + typedef std::function<bool(QString& nameInConflict)> NameConflictResolveFunction; + typedef std::function<bool(const QList<QPair<QString,QString>>& diffs)> ConversionConfimFunction; + typedef std::function<bool(const QHash<QString,QSet<QString>>& errors)> ConversionErrorsConfimFunction; + + /** + * @brief Creates organizer with default handler functions. + * + * The default handler functions are not very usable - they always return false. + * It's better to use the other constructor and pass the custom implementation of + * handler functions. + */ + DbObjectOrganizer(); + + /** + * @brief Creates organizer with handler functions defined specially for it. + * @param confirmFunction Implemented function should ask user if he wants to include referenced tables + * in the copy/move action. If it returns false, then referenced tables will be excluded. + * @param nameConflictResolveFunction Implemented function should ask user for a new name for the object + * with the name passed in the argument. The new name should be returned in the very same reference argument. + * If the function returns false, then the conflict is unresolved and copy/move action will be aborted. + * When the function returns true, it means that the user entered a new name and he wants to proceed. + * @param conversionConfimFunction This function should display changes that will be made to the SQL statements + * while executing them on the target database (because of different database versions). User can cancel the process + * (in which case, the function should return false). + * @param conversionErrorsConfimFunction This function should display critical errors that occurred when tried to + * convert SQL statements for target database (when it's of different version than source database). User + * can choose to proceed (with skipping problemating database objects), or cancel the process (in which case the function + * should return false). + * + * Use this constructor always if possible. + */ + DbObjectOrganizer(ReferencedTablesConfimFunction confirmFunction, + NameConflictResolveFunction nameConflictResolveFunction, + ConversionConfimFunction conversionConfimFunction, + ConversionErrorsConfimFunction conversionErrorsConfimFunction); + ~DbObjectOrganizer(); + + void copyObjectsToDb(Db* srcDb, const QStringList& objNames, Db* dstDb, bool includeData, bool includeIndexes, bool includeTriggers); + void moveObjectsToDb(Db* srcDb, const QStringList& objNames, Db* dstDb, bool includeData, bool includeIndexes, bool includeTriggers); + void interrupt(); + bool isExecuting(); + void run(); + + private: + enum class Mode + { + PREPARE_TO_COPY_OBJECTS, + PREPARE_TO_MOVE_OBJECTS, + COPY_OBJECTS, + MOVE_OBJECTS, + unknown + }; + + void init(); + void reset(); + void copyOrMoveObjectsToDb(Db* srcDb, const QSet<QString>& objNames, Db* dstDb, bool includeData, bool includeIndexes, bool includeTriggers, bool move); + void processPreparation(); + bool processAll(); + bool processDbObjects(); + bool processColumns(); + bool resolveNameConflicts(); + bool copyTableToDb(const QString& table); + bool copyViewToDb(const QString& view); + bool copyIndexToDb(const QString& index); + bool copyTriggerToDb(const QString& trigger); + bool copySimpleObjectToDb(const QString& name, const QString& errorMessage); + QSet<QString> resolveReferencedTables(const QString& table, const QList<SqliteCreateTablePtr>& parsedTables); + void collectDiffs(const StrHash<SchemaResolver::ObjectDetails>& details); + QString convertDdlToDstVersion(const QString& ddl); + void collectReferencedTables(const QString& table, const StrHash<SqliteQueryPtr>& allParsedObjects); + void collectReferencedIndexes(const QString& table); + void collectReferencedTriggersForTable(const QString& table); + void collectReferencedTriggersForView(const QString& view); + void findBinaryColumns(const QString& table, const StrHash<SqliteQueryPtr>& allParsedObjects); + bool copyDataAsMiddleware(const QString& table); + bool copyDataUsingAttach(const QString& table); + void setupSqlite2Helper(SqlQueryPtr query, const QString& table, const QStringList& colNames); + void dropTable(const QString& table); + void dropView(const QString& view); + void dropObject(const QString& name, const QString& type); + bool setFkEnabled(bool enabled); + bool isInterrupted(); + void setExecuting(bool executing); + void setSrcAndDstDb(Db* srcDb, Db* dstDb); + bool begin(); + bool commit(); + bool rollback(); + void emitFinished(bool success); + + ReferencedTablesConfimFunction confirmFunction; + NameConflictResolveFunction nameConflictResolveFunction; + ConversionConfimFunction conversionConfimFunction; + ConversionErrorsConfimFunction conversionErrorsConfimFunction; + Mode mode = Mode::COPY_OBJECTS; + Db* srcDb = nullptr; + Db* dstDb = nullptr; + QSet<QString> srcNames; + QSet<QString> srcTables; + QSet<QString> srcViews; + QSet<QString> srcIndexes; + QSet<QString> srcTriggers; + QHash<QString,QString> renamed; + QString srcTable; + QHash<QString,QStringList> binaryColumns; // hints for SQLite 2 databases + bool includeData = false; + bool includeIndexes = false; + bool includeTriggers = false; + bool deleteSourceObjects = false; + QSet<QString> referencedTables; + QHash<QString,QSet<QString>> errorsToConfirm; + QList<QPair<QString, QString>> diffListToConfirm; + SchemaResolver* srcResolver = nullptr; + SchemaResolver* dstResolver = nullptr; + bool interrupted = false; + bool executing = false; + DbVersionConverter* versionConverter = nullptr; + QMutex interruptMutex; + QMutex executingMutex; + QString attachName; + + private slots: + void processPreparationFinished(); + + signals: + void finishedDbObjectsMove(bool success, Db* srcDb, Db* dstDb); + void finishedDbObjectsCopy(bool success, Db* srcDb, Db* dstDb); + void preparetionFinished(); +}; + +#endif // DBOBJECTORGANIZER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/dbobjecttype.h b/SQLiteStudio3/coreSQLiteStudio/dbobjecttype.h new file mode 100644 index 0000000..981753e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/dbobjecttype.h @@ -0,0 +1,12 @@ +#ifndef DBOBJECTTYPE_H +#define DBOBJECTTYPE_H + +enum class DbObjectType +{ + TABLE, + INDEX, + TRIGGER, + VIEW +}; + +#endif // DBOBJECTTYPE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/dbversionconverter.cpp b/SQLiteStudio3/coreSQLiteStudio/dbversionconverter.cpp new file mode 100644 index 0000000..bf13eac --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/dbversionconverter.cpp @@ -0,0 +1,1253 @@ +#include "dbversionconverter.h" +#include "schemaresolver.h" +#include "common/global.h" +#include "parser/ast/sqlitealtertable.h" +#include "parser/ast/sqliteanalyze.h" +#include "parser/ast/sqliteattach.h" +#include "parser/ast/sqlitebegintrans.h" +#include "parser/ast/sqlitecommittrans.h" +#include "parser/ast/sqlitecopy.h" +#include "parser/ast/sqlitecreatevirtualtable.h" +#include "parser/ast/sqlitedelete.h" +#include "parser/ast/sqlitedetach.h" +#include "parser/ast/sqlitedropindex.h" +#include "parser/ast/sqlitedroptable.h" +#include "parser/ast/sqlitedroptrigger.h" +#include "parser/ast/sqlitedropview.h" +#include "parser/ast/sqliteemptyquery.h" +#include "parser/ast/sqliteinsert.h" +#include "parser/ast/sqlitepragma.h" +#include "parser/ast/sqlitereindex.h" +#include "parser/ast/sqliterelease.h" +#include "parser/ast/sqliterollback.h" +#include "parser/ast/sqlitesavepoint.h" +#include "parser/ast/sqliteupdate.h" +#include "parser/ast/sqlitevacuum.h" +#include "services/pluginmanager.h" +#include "plugins/dbplugin.h" +#include "services/dbmanager.h" +#include <QDebug> +#include <QDir> +#include <QFile> +#include <QtConcurrent/QtConcurrentRun> + +DbVersionConverter::DbVersionConverter() +{ + connect(this, SIGNAL(askUserForConfirmation()), this, SLOT(confirmConversion())); + connect(this, SIGNAL(conversionSuccessful()), this, SLOT(registerDbAfterSuccessfulConversion())); +} + +DbVersionConverter::~DbVersionConverter() +{ + safe_delete(fullConversionConfig); + safe_delete(resolver); +} + +void DbVersionConverter::convert(Dialect from, Dialect to, Db* srcDb, const QString& targetFile, const QString& targetName, ConversionConfimFunction confirmFunc, + ConversionErrorsConfimFunction errorsConfirmFunc) +{ + safe_delete(fullConversionConfig); + fullConversionConfig = new FullConversionConfig; + fullConversionConfig->from = from; + fullConversionConfig->to = to; + fullConversionConfig->srcDb = srcDb; + fullConversionConfig->confirmFunc = confirmFunc; + fullConversionConfig->errorsConfirmFunc = errorsConfirmFunc; + fullConversionConfig->targetFile = targetFile; + fullConversionConfig->targetName = targetName; + QtConcurrent::run(this, &DbVersionConverter::fullConvertStep1); +} + +void DbVersionConverter::convert(Dialect from, Dialect to, Db* db) +{ + if (from == Dialect::Sqlite2 && to == Dialect::Sqlite3) + convert2To3(db); + else if (from == Dialect::Sqlite3 && to == Dialect::Sqlite2) + convert3To2(db); + else + qCritical() << "Unsupported db conversion combination."; +} + +void DbVersionConverter::convert3To2(Db* db) +{ + reset(); + this->db = db; + targetDialect = Dialect::Sqlite2; + convertDb(); +} + +void DbVersionConverter::convert2To3(Db* db) +{ + reset(); + this->db = db; + targetDialect = Dialect::Sqlite3; + convertDb(); +} + +QString DbVersionConverter::convert(Dialect from, Dialect to, const QString& sql) +{ + if (from == Dialect::Sqlite2 && to == Dialect::Sqlite3) + return convert2To3(sql); + else if (from == Dialect::Sqlite3 && to == Dialect::Sqlite2) + return convert3To2(sql); + else + { + qCritical() << "Unsupported db conversion combination."; + return QString(); + } +} + +QString DbVersionConverter::convert3To2(const QString& sql) +{ + QStringList result; + for (SqliteQueryPtr query : parse(sql, Dialect::Sqlite3)) + result << convert3To2(query)->detokenize(); + + return result.join("\n"); +} + +QString DbVersionConverter::convert2To3(const QString& sql) +{ + QStringList result; + for (SqliteQueryPtr query : parse(sql, Dialect::Sqlite2)) + result << convert2To3(query)->detokenize(); + + return result.join("\n"); +} + +SqliteQueryPtr DbVersionConverter::convert(Dialect from, Dialect to, SqliteQueryPtr query) +{ + if (from == Dialect::Sqlite2 && to == Dialect::Sqlite3) + return convert2To3(query); + else if (from == Dialect::Sqlite3 && to == Dialect::Sqlite2) + return convert3To2(query); + else + { + qCritical() << "Unsupported db conversion combination."; + return SqliteQueryPtr(); + } +} + +SqliteQueryPtr DbVersionConverter::convert3To2(SqliteQueryPtr query) +{ + SqliteQueryPtr newQuery; + switch (query->queryType) + { + case SqliteQueryType::AlterTable: + errors << QObject::tr("SQLite 2 does not support 'ALTER TABLE' statement."); + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + break; + case SqliteQueryType::Analyze: + errors << QObject::tr("SQLite 2 does not support 'ANAYLZE' statement."); + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + break; + case SqliteQueryType::Attach: + newQuery = copyQuery<SqliteAttach>(query); + break; + case SqliteQueryType::BeginTrans: + newQuery = copyQuery<SqliteBeginTrans>(query); + break; + case SqliteQueryType::CommitTrans: + newQuery = copyQuery<SqliteCommitTrans>(query); + break; + case SqliteQueryType::Copy: + qWarning() << "COPY query passed to DbVersionConverter::convertToVersion2(). SQLite3 query should not have COPY statement."; + newQuery = copyQuery<SqliteCopy>(query); + break; + case SqliteQueryType::CreateIndex: + newQuery = copyQuery<SqliteCreateIndex>(query); + if (!modifyCreateIndexForVersion2(newQuery.dynamicCast<SqliteCreateIndex>().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + case SqliteQueryType::CreateTable: + newQuery = copyQuery<SqliteCreateTable>(query); + if (!modifyCreateTableForVersion2(newQuery.dynamicCast<SqliteCreateTable>().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + case SqliteQueryType::CreateTrigger: + newQuery = copyQuery<SqliteCreateTrigger>(query); + if (!modifyCreateTriggerForVersion2(newQuery.dynamicCast<SqliteCreateTrigger>().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + case SqliteQueryType::CreateView: + newQuery = copyQuery<SqliteCreateView>(query); + if (!modifyCreateViewForVersion2(newQuery.dynamicCast<SqliteCreateView>().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + case SqliteQueryType::CreateVirtualTable: + newQuery = copyQuery<SqliteCreateVirtualTable>(query); + if (!modifyVirtualTableForVesion2(newQuery, newQuery.dynamicCast<SqliteCreateVirtualTable>().data())) + { + errors << QObject::tr("SQLite 2 does not support 'CREATE VIRTUAL TABLE' statement."); + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + else + { + errors << QObject::tr("SQLite 2 does not support 'CREATE VIRTUAL TABLE' statement. The regular table can be created instead if you proceed."); + } + break; + case SqliteQueryType::Delete: + newQuery = copyQuery<SqliteDelete>(query); + if (!modifyDeleteForVersion2(newQuery.dynamicCast<SqliteDelete>().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + case SqliteQueryType::Detach: + newQuery = copyQuery<SqliteDetach>(query); + break; + case SqliteQueryType::DropIndex: + newQuery = copyQuery<SqliteDropIndex>(query); + break; + case SqliteQueryType::DropTable: + newQuery = copyQuery<SqliteDropTable>(query); + break; + case SqliteQueryType::DropTrigger: + newQuery = copyQuery<SqliteDropTrigger>(query); + break; + case SqliteQueryType::DropView: + newQuery = copyQuery<SqliteDropView>(query); + break; + case SqliteQueryType::Insert: + newQuery = copyQuery<SqliteInsert>(query); + if (!modifyInsertForVersion2(newQuery.dynamicCast<SqliteInsert>().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + case SqliteQueryType::Pragma: + newQuery = copyQuery<SqlitePragma>(query); + break; + case SqliteQueryType::Reindex: + errors << QObject::tr("SQLite 2 does not support 'REINDEX' statement."); + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + break; + case SqliteQueryType::Release: + errors << QObject::tr("SQLite 2 does not support 'RELEASE' statement."); + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + break; + case SqliteQueryType::Rollback: + newQuery = copyQuery<SqliteRollback>(query); + break; + case SqliteQueryType::Savepoint: + errors << QObject::tr("SQLite 2 does not support 'SAVEPOINT' statement."); + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + break; + case SqliteQueryType::Select: + { + newQuery = copyQuery<SqliteSelect>(query); + if (!modifySelectForVersion2(newQuery.dynamicCast<SqliteSelect>().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + } + case SqliteQueryType::Update: + newQuery = copyQuery<SqliteUpdate>(query); + if (!modifyUpdateForVersion2(newQuery.dynamicCast<SqliteUpdate>().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + case SqliteQueryType::Vacuum: + newQuery = copyQuery<SqliteVacuum>(query); + break; + case SqliteQueryType::UNDEFINED: + qWarning() << "UNDEFINED query type passed to DbVersionConverter::convertToVersion2()."; + newQuery = SqliteEmptyQueryPtr::create(); + break; + case SqliteQueryType::EMPTY: + newQuery = copyQuery<SqliteEmptyQuery>(query); + break; + } + + if (!newQuery) + { + qCritical() << "Query type not matched in DbVersionConverter::convertToVersion2():" << static_cast<int>(query->queryType); + return SqliteQueryPtr(); + } + + if (newQuery->queryType != SqliteQueryType::EMPTY) + { + newQuery->setSqliteDialect(Dialect::Sqlite2); + newQueries << newQuery; + } + + newQuery->rebuildTokens(); + return newQuery; +} + +SqliteQueryPtr DbVersionConverter::convert2To3(SqliteQueryPtr query) +{ + SqliteQueryPtr newQuery; + switch (query->queryType) + { + case SqliteQueryType::AlterTable: + newQuery = copyQuery<SqliteAlterTable>(query); + qWarning() << "ALTER TABLE query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have ALTER TABLE statement."; + break; + case SqliteQueryType::Analyze: + newQuery = copyQuery<SqliteAnalyze>(query); + qWarning() << "ANALYZE query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have ANALYZE statement."; + break; + case SqliteQueryType::Attach: + newQuery = copyQuery<SqliteAttach>(query); + break; + case SqliteQueryType::BeginTrans: + newQuery = copyQuery<SqliteBeginTrans>(query); + if (!modifyBeginTransForVersion3(newQuery.dynamicCast<SqliteBeginTrans>().data())) + { + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + } + break; + case SqliteQueryType::CommitTrans: + newQuery = copyQuery<SqliteCommitTrans>(query); + break; + case SqliteQueryType::Copy: + errors << QObject::tr("SQLite 3 does not support 'COPY' statement."); + newQuery = SqliteEmptyQueryPtr::create(); + storeErrorDiff(query.data()); + break; + case SqliteQueryType::CreateIndex: + newQuery = copyQuery<SqliteCreateIndex>(query); + break; + case SqliteQueryType::CreateTable: + newQuery = copyQuery<SqliteCreateTable>(query); + break; + case SqliteQueryType::CreateTrigger: + newQuery = copyQuery<SqliteCreateTrigger>(query); + break; + case SqliteQueryType::CreateView: + newQuery = copyQuery<SqliteCreateView>(query); + break; + case SqliteQueryType::CreateVirtualTable: + newQuery = copyQuery<SqliteCreateVirtualTable>(query); + break; + case SqliteQueryType::Delete: + newQuery = copyQuery<SqliteDelete>(query); + break; + case SqliteQueryType::Detach: + newQuery = copyQuery<SqliteDetach>(query); + break; + case SqliteQueryType::DropIndex: + newQuery = copyQuery<SqliteDropIndex>(query); + break; + case SqliteQueryType::DropTable: + newQuery = copyQuery<SqliteDropTable>(query); + break; + case SqliteQueryType::DropTrigger: + newQuery = copyQuery<SqliteDropTrigger>(query); + break; + case SqliteQueryType::DropView: + newQuery = copyQuery<SqliteDropView>(query); + break; + case SqliteQueryType::Insert: + newQuery = copyQuery<SqliteInsert>(query); + break; + case SqliteQueryType::Pragma: + newQuery = copyQuery<SqlitePragma>(query); + break; + case SqliteQueryType::Reindex: + newQuery = copyQuery<SqliteReindex>(query); + qWarning() << "REINDEX query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have REINDEX statement."; + break; + case SqliteQueryType::Release: + newQuery = copyQuery<SqliteRelease>(query); + qWarning() << "RELEASE query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have RELEASE statement."; + break; + case SqliteQueryType::Rollback: + newQuery = copyQuery<SqliteRollback>(query); + break; + case SqliteQueryType::Savepoint: + newQuery = copyQuery<SqliteSavepoint>(query); + qWarning() << "SAVEPOINT query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have SAVEPOINT statement."; + break; + case SqliteQueryType::Select: + newQuery = copyQuery<SqliteSelect>(query); + break; + case SqliteQueryType::Update: + newQuery = copyQuery<SqliteUpdate>(query); + break; + case SqliteQueryType::Vacuum: + newQuery = copyQuery<SqliteVacuum>(query); + break; + case SqliteQueryType::UNDEFINED: + qWarning() << "UNDEFINED query type passed to DbVersionConverter::convertToVersion3()."; + case SqliteQueryType::EMPTY: + newQuery = copyQuery<SqliteEmptyQuery>(query); + break; + } + + if (!newQuery) + { + qCritical() << "Query type not matched in DbVersionConverter::convertToVersion3():" << static_cast<int>(query->queryType); + return SqliteQueryPtr(); + } + + if (newQuery->queryType != SqliteQueryType::EMPTY) + { + newQuery->setSqliteDialect(Dialect::Sqlite2); + newQueries << newQuery; + } + return newQuery; +} + +QList<SqliteQueryPtr> DbVersionConverter::parse(const QString& sql, Dialect dialect) +{ + Parser parser(dialect); + if (!parser.parse(sql)) + { + errors << QObject::tr("Could not parse statement: %1\nError details: %2").arg(sql, parser.getErrorString()); + return QList<SqliteQueryPtr>(); + } + + return parser.getQueries(); +} + +bool DbVersionConverter::modifySelectForVersion2(SqliteSelect* select) +{ + if (select->with) + { + errors << QObject::tr("SQLite 2 does not support the 'WITH' clause. Cannot convert '%1' statement with that clause.").arg("SELECT"); + return false; + } + + QString sql1 = getSqlForDiff(select); + + for (SqliteSelect::Core* core : select->coreSelects) + { + if (core->valuesMode) + core->valuesMode = false; + } + + if (!modifyAllIndexedColumnsForVersion2(select)) + return false; + + if (!modifyAllExprsForVersion2(select)) + return false; + + storeDiff(sql1, select); + return true; +} + +bool DbVersionConverter::modifyDeleteForVersion2(SqliteDelete* del) +{ + if (del->with) + { + errors << QObject::tr("SQLite 2 does not support the 'WITH' clause. Cannot convert '%1' statement with that clause.").arg("DELETE"); + return false; + } + + QString sql1 = getSqlForDiff(del); + + del->indexedBy = QString::null; + del->indexedByKw = false; + del->notIndexedKw = false; + + if (!modifyAllExprsForVersion2(del)) + return false; + + storeDiff(sql1, del); + return true; +} + +bool DbVersionConverter::modifyInsertForVersion2(SqliteInsert* insert) +{ + if (insert->with) + { + errors << QObject::tr("SQLite 2 does not support the 'WITH' clause. Cannot convert '%1' statement with that clause.").arg("INSERT"); + return false; + } + + if (insert->defaultValuesKw) + { + errors << QObject::tr("SQLite 2 does not support the 'DEFAULT VALUES' clause in the 'INSERT' clause."); + return false; + } + + if (!insert->select) + { + qCritical() << "No SELECT substatement in INSERT when converting from SQLite 3 to 2."; + return false; + } + + QString sql1 = getSqlForDiff(insert); + + // Modifying SELECT deals with "VALUES" completely. + if (!modifySelectForVersion2(insert->select)) + return false; + + if (!modifyAllExprsForVersion2(insert)) + return false; + + storeDiff(sql1, insert); + return true; +} + +bool DbVersionConverter::modifyUpdateForVersion2(SqliteUpdate* update) +{ + if (update->with) + { + errors << QObject::tr("SQLite 2 does not support the 'WITH' clause. Cannot convert '%1' statement with that clause.").arg("UPDATE"); + return false; + } + + QString sql1 = getSqlForDiff(update); + + if (!modifyAllExprsForVersion2(update)) + return false; + + update->indexedBy = QString::null; + update->indexedByKw = false; + update->notIndexedKw = false; + + storeDiff(sql1, update); + return true; +} + +bool DbVersionConverter::modifyCreateTableForVersion2(SqliteCreateTable* createTable) +{ + QString sql1 = getSqlForDiff(createTable); + + if (!createTable->database.isNull()) + createTable->database = QString::null; + + // Subselect + if (createTable->select) + { + if (!modifySelectForVersion2(createTable->select)) + return false; + } + + // Table constraints + QMutableListIterator<SqliteCreateTable::Constraint*> tableConstrIt(createTable->constraints); + while (tableConstrIt.hasNext()) + { + tableConstrIt.next(); + if (tableConstrIt.value()->type == SqliteCreateTable::Constraint::PRIMARY_KEY) + tableConstrIt.value()->autoincrKw = false; + } + + // Column constraints + QMutableListIterator<SqliteCreateTable::Column*> tableColIt(createTable->columns); + while (tableColIt.hasNext()) + { + tableColIt.next(); + QMutableListIterator<SqliteCreateTable::Column::Constraint*> tableColConstrIt(tableColIt.value()->constraints); + while (tableColConstrIt.hasNext()) + { + tableColConstrIt.next(); + switch (tableColConstrIt.value()->type) + { + case SqliteCreateTable::Column::Constraint::COLLATE: // theoretically supported by SQLite2, but it raises error from SQLite2 when used + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + { + delete tableColConstrIt.value(); + tableColConstrIt.remove(); + break; + } + case SqliteCreateTable::Column::Constraint::DEFAULT: + { + if (!tableColConstrIt.value()->ctime.isNull()) + { + delete tableColConstrIt.value(); + tableColConstrIt.remove(); + } + else + tableColConstrIt.value()->name = QString::null; + + break; + } + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + { + tableColConstrIt.value()->autoincrKw = false; + break; + } + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + { + QMutableListIterator<SqliteForeignKey::Condition*> condIt(tableColConstrIt.value()->foreignKey->conditions); + while (condIt.hasNext()) + { + condIt.next(); + if (condIt.value()->reaction == SqliteForeignKey::Condition::NO_ACTION + && condIt.value()->action != SqliteForeignKey::Condition::MATCH) // SQLite 2 has no "NO ACTION" + { + condIt.remove(); + } + } + break; + } + case SqliteCreateTable::Column::Constraint::NOT_NULL: + case SqliteCreateTable::Column::Constraint::UNIQUE: + case SqliteCreateTable::Column::Constraint::CHECK: + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + } + } + + if (!modifyAllIndexedColumnsForVersion2(createTable)) + return false; + + // WITHOUT ROWID + if (!createTable->withOutRowId.isNull()) + createTable->withOutRowId = QString::null; + + storeDiff(sql1, createTable); + return true; +} + +bool DbVersionConverter::modifyCreateTriggerForVersion2(SqliteCreateTrigger* createTrigger) +{ + QString sql = getSqlForDiff(createTrigger); + + if (!createTrigger->database.isNull()) + createTrigger->database = QString::null; + + for (SqliteQuery* query : createTrigger->queries) + { + switch (query->queryType) + { + case SqliteQueryType::Delete: + { + if (!modifyDeleteForVersion2(dynamic_cast<SqliteDelete*>(query))) + return false; + + break; + } + case SqliteQueryType::Update: + { + if (!modifyUpdateForVersion2(dynamic_cast<SqliteUpdate*>(query))) + return false; + + break; + } + case SqliteQueryType::Insert: + { + if (!modifyInsertForVersion2(dynamic_cast<SqliteInsert*>(query))) + return false; + + break; + } + case SqliteQueryType::Select: + { + if (!modifySelectForVersion2(dynamic_cast<SqliteSelect*>(query))) + return false; + + break; + } + default: + qWarning() << "Unexpected query type in trigger:" << sqliteQueryTypeToString(query->queryType); + break; + } + } + + storeDiff(sql, createTrigger); + return true; +} + +bool DbVersionConverter::modifyCreateIndexForVersion2(SqliteCreateIndex* createIndex) +{ + QString sql1 = getSqlForDiff(createIndex); + + if (!createIndex->database.isNull()) + createIndex->database = QString::null; + + if (createIndex->where) + { + delete createIndex->where; + createIndex->where = nullptr; + } + + if (!modifyAllIndexedColumnsForVersion2(createIndex->indexedColumns)) + return false; + + storeDiff(sql1, createIndex); + return true; +} + +bool DbVersionConverter::modifyCreateViewForVersion2(SqliteCreateView* createView) +{ + QString sql1 = getSqlForDiff(createView); + + if (!createView->database.isNull()) + createView->database = QString::null; + + if (!modifySelectForVersion2(createView->select)) + return false; + + storeDiff(sql1, createView); + return true; +} + +bool DbVersionConverter::modifyVirtualTableForVesion2(SqliteQueryPtr& query, SqliteCreateVirtualTable* createVirtualTable) +{ + if (!resolver) + return false; + + SqliteCreateTablePtr createTable = resolver->resolveVirtualTableAsRegularTable(createVirtualTable->database, createVirtualTable->table); + if (!createTable) + return false; + + QString sql = getSqlForDiff(createVirtualTable); + storeDiff(sql, createTable.data()); + + query = createTable.dynamicCast<SqliteQuery>(); + return true; +} + +bool DbVersionConverter::modifyAllExprsForVersion2(SqliteStatement* stmt) +{ + QList<SqliteExpr*> allExprs = stmt->getAllTypedStatements<SqliteExpr>(); + for (SqliteExpr* expr : allExprs) + { + if (!modifySingleExprForVersion2(expr)) + return false; + } + return true; +} + +bool DbVersionConverter::modifySingleExprForVersion2(SqliteExpr* expr) +{ + switch (expr->mode) + { + case SqliteExpr::Mode::null: + case SqliteExpr::Mode::LITERAL_VALUE: + case SqliteExpr::Mode::BIND_PARAM: + case SqliteExpr::Mode::ID: + case SqliteExpr::Mode::UNARY_OP: + case SqliteExpr::Mode::BINARY_OP: + case SqliteExpr::Mode::FUNCTION: + case SqliteExpr::Mode::SUB_EXPR: + case SqliteExpr::Mode::LIKE: + case SqliteExpr::Mode::NULL_: + case SqliteExpr::Mode::NOTNULL: + case SqliteExpr::Mode::IS: + case SqliteExpr::Mode::BETWEEN: + case SqliteExpr::Mode::CASE: + case SqliteExpr::Mode::RAISE: + break; + case SqliteExpr::Mode::CTIME: + errors << QObject::tr("SQLite 2 does not support current date or time clauses in expressions."); + return false; + case SqliteExpr::Mode::IN: + case SqliteExpr::Mode::SUB_SELECT: + { + if (!modifySelectForVersion2(expr->select)) + return false; + + break; + } + case SqliteExpr::Mode::CAST: + errors << QObject::tr("SQLite 2 does not support 'CAST' clause in expressions."); + return false; + case SqliteExpr::Mode::EXISTS: + errors << QObject::tr("SQLite 2 does not support 'EXISTS' clause in expressions."); + return false; + case SqliteExpr::Mode::COLLATE: + { + if (dynamic_cast<SqliteOrderBy*>(expr->parentStatement())) + { + // This is the only case when SQLite2 parser puts this mode into expression, that is for sortorder + break; + } + else + { + errors << QObject::tr("SQLite 2 does not support 'COLLATE' clause in expressions."); + return false; + } + } + } + return true; +} + +bool DbVersionConverter::modifyAllIndexedColumnsForVersion2(SqliteStatement* stmt) +{ + QList<SqliteIndexedColumn*> columns = stmt->getAllTypedStatements<SqliteIndexedColumn>(); + return modifyAllIndexedColumnsForVersion2(columns); + +} + +bool DbVersionConverter::modifyAllIndexedColumnsForVersion2(const QList<SqliteIndexedColumn*> columns) +{ + for (SqliteIndexedColumn* idxCol : columns) + { + if (!modifySingleIndexedColumnForVersion2(idxCol)) + return false; + } + return true; +} + +bool DbVersionConverter::modifySingleIndexedColumnForVersion2(SqliteIndexedColumn* idxCol) +{ + if (!idxCol->collate.isNull()) + idxCol->collate = QString::null; + + return true; +} + +bool DbVersionConverter::modifyBeginTransForVersion3(SqliteBeginTrans* begin) +{ + QString sql1 = getSqlForDiff(begin); + begin->onConflict = SqliteConflictAlgo::null; + storeDiff(sql1, begin); + return true; +} + +bool DbVersionConverter::modifyCreateTableForVersion3(SqliteCreateTable* createTable) +{ + QString sql1 = getSqlForDiff(createTable); + + // Column constraints + QMutableListIterator<SqliteCreateTable::Column*> tableColIt(createTable->columns); + while (tableColIt.hasNext()) + { + tableColIt.next(); + QMutableListIterator<SqliteCreateTable::Column::Constraint*> tableColConstrIt(tableColIt.value()->constraints); + while (tableColConstrIt.hasNext()) + { + tableColConstrIt.next(); + switch (tableColConstrIt.value()->type) + { + case SqliteCreateTable::Column::Constraint::CHECK: + { + tableColConstrIt.value()->onConflict = SqliteConflictAlgo::null; + break; + } + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + case SqliteCreateTable::Column::Constraint::DEFAULT: + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + case SqliteCreateTable::Column::Constraint::NOT_NULL: + case SqliteCreateTable::Column::Constraint::UNIQUE: + case SqliteCreateTable::Column::Constraint::COLLATE: + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + } + } + + storeDiff(sql1, createTable); + return true; +} + +QString DbVersionConverter::getSqlForDiff(SqliteStatement* stmt) +{ + stmt->rebuildTokens(); + return stmt->detokenize(); +} + +void DbVersionConverter::storeDiff(const QString& sql1, SqliteStatement* stmt) +{ + stmt->rebuildTokens(); + QString sql2 = stmt->detokenize(); + if (sql1 != sql2) + diffList << QPair<QString,QString>(sql1, sql2); +} + +void DbVersionConverter::storeErrorDiff(SqliteStatement* stmt) +{ + stmt->rebuildTokens(); + diffList << QPair<QString,QString>(stmt->detokenize(), ""); +} + +void DbVersionConverter::reset() +{ + db = nullptr; + targetDialect = Dialect::Sqlite3; + diffList.clear(); + newQueries.clear(); + errors.clear(); + safe_delete(resolver); +} + +QList<Dialect> DbVersionConverter::getSupportedVersions() const +{ + QList<Dialect> versions; + for (Db* db : getAllPossibleDbInstances()) + { + versions << db->getDialect(); + delete db; + } + return versions; +} + +QStringList DbVersionConverter::getSupportedVersionNames() const +{ + QStringList versions; + for (Db* db : getAllPossibleDbInstances()) + { + versions << db->getTypeLabel(); + delete db; + } + return versions; +} + +void DbVersionConverter::fullConvertStep1() +{ + convert(fullConversionConfig->from, fullConversionConfig->to, fullConversionConfig->srcDb); + emit askUserForConfirmation(); +} + +void DbVersionConverter::fullConvertStep2() +{ + QFile outputFile(fullConversionConfig->targetFile); + if (outputFile.exists() && !outputFile.remove()) + { + emit conversionFailed(tr("Target file exists, but could not be overwritten.")); + return; + } + + Db* db = nullptr; + Db* tmpDb = nullptr; + for (DbPlugin* plugin : PLUGINS->getLoadedPlugins<DbPlugin>()) + { + tmpDb = plugin->getInstance("", ":memory:", QHash<QString,QVariant>()); + if (tmpDb->initAfterCreated() && tmpDb->getDialect() == fullConversionConfig->to) + db = plugin->getInstance(fullConversionConfig->targetName, fullConversionConfig->targetFile, QHash<QString,QVariant>()); + + delete tmpDb; + if (db) + break; + } + + if (!db) + { + emit conversionFailed(tr("Could not find proper database plugin to create target database.")); + return; + } + + if (checkForInterrupted(db, false)) + return; + + if (!db->open()) + { + emit conversionFailed(db->getErrorText()); + return; + } + + if (!db->begin()) + { + conversionError(db, db->getErrorText()); + return; + } + + QStringList tables; + if (!fullConvertCreateObjectsStep1(db, tables)) + return; // error handled inside + + if (checkForInterrupted(db, true)) + return; + + if (!fullConvertCopyData(db, tables)) + return; // error handled inside + + if (checkForInterrupted(db, true)) + return; + + if (!fullConvertCreateObjectsStep2(db)) + return; // error handled inside + + if (checkForInterrupted(db, true)) + return; + + if (!db->commit()) + { + conversionError(db, db->getErrorText()); + return; + } + emit conversionSuccessful(); +} + +bool DbVersionConverter::fullConvertCreateObjectsStep1(Db* db, QStringList& tables) +{ + SqlQueryPtr result; + SqliteCreateTablePtr createTable; + for (const SqliteQueryPtr& query : getConverted()) + { + // Triggers and indexes are created in step2, after data was copied, to avoid invoking triggers + // and speed up copying data. + if (query->queryType == SqliteQueryType::CreateTrigger || query->queryType == SqliteQueryType::CreateIndex) + continue; + + createTable = query.dynamicCast<SqliteCreateTable>(); + if (!createTable.isNull()) + tables << createTable->table; + + result = db->exec(query->detokenize()); + if (result->isError()) + { + conversionError(db, result->getErrorText()); + return false; + } + } + return true; +} + +bool DbVersionConverter::fullConvertCreateObjectsStep2(Db* db) +{ + SqlQueryPtr result; + for (const SqliteQueryPtr& query : getConverted()) + { + // Creating only triggers and indexes + if (query->queryType != SqliteQueryType::CreateTrigger && query->queryType != SqliteQueryType::CreateIndex) + continue; + + result = db->exec(query->detokenize()); + if (result->isError()) + { + conversionError(db, result->getErrorText()); + return false; + } + + if (checkForInterrupted(db, true)) + return false; + } + return true; +} + +bool DbVersionConverter::fullConvertCopyData(Db* db, const QStringList& tables) +{ + static const QString selectSql = QStringLiteral("SELECT * FROM %1;"); + static const QString insertSql = QStringLiteral("INSERT INTO %1 VALUES (%2);"); + + Dialect srcDialect = fullConversionConfig->srcDb->getDialect(); + Dialect trgDialect = db->getDialect(); + QString srcTable; + QString trgTable; + SqlQueryPtr result; + SqlQueryPtr dataResult; + SqlResultsRowPtr resultsRow; + int i = 1; + for (const QString& table : tables) + { + // Select data + srcTable = wrapObjIfNeeded(table, srcDialect); + trgTable = wrapObjIfNeeded(table, trgDialect); + dataResult = fullConversionConfig->srcDb->exec(selectSql.arg(srcTable)); + if (dataResult->isError()) + { + conversionError(db, dataResult->getErrorText()); + return false; + } + + if (checkForInterrupted(db, true)) + return false; + + // Copy row by row + i = 1; + while (dataResult->hasNext()) + { + // Get row + resultsRow = dataResult->next(); + if (dataResult->isError()) + { + conversionError(db, dataResult->getErrorText()); + return false; + } + + // Insert row + result = db->exec(insertSql.arg(trgTable, generateQueryPlaceholders(resultsRow->valueList().size())), resultsRow->valueList()); + if (result->isError()) + { + conversionError(db, result->getErrorText()); + return false; + } + + if (i++ % 100 == 0 && checkForInterrupted(db, true)) + return false; + } + } + + return true; +} + +bool DbVersionConverter::checkForInterrupted(Db* db, bool rollback) +{ + if (isInterrupted()) + { + conversionInterrupted(db, rollback); + return true; + } + return false; +} + +QList<Db*> DbVersionConverter::getAllPossibleDbInstances() const +{ + QList<Db*> dbList; + Db* db = nullptr; + for (DbPlugin* plugin : PLUGINS->getLoadedPlugins<DbPlugin>()) + { + db = plugin->getInstance("", ":memory:", QHash<QString,QVariant>()); + if (!db->initAfterCreated()) + continue; + + dbList << db; + } + return dbList; +} + +QString DbVersionConverter::generateQueryPlaceholders(int argCount) +{ + QStringList args; + for (int i = 0; i < argCount; i++) + args << "?"; + + return args.join(", "); +} + +void DbVersionConverter::sortConverted() +{ + qSort(newQueries.begin(), newQueries.end(), [](const SqliteQueryPtr& q1, const SqliteQueryPtr& q2) -> bool + { + if (!q1 || !q2) + { + if (!q1) + return false; + else + return true; + } + if (q1->queryType == q2->queryType) + return false; + + if (q1->queryType == SqliteQueryType::CreateTable) + return true; + + if (q1->queryType == SqliteQueryType::CreateView && q2->queryType != SqliteQueryType::CreateTable) + return true; + + return false; + }); +} + +void DbVersionConverter::setInterrupted(bool value) +{ + QMutexLocker locker(&interruptMutex); + interrupted = value; +} + +bool DbVersionConverter::isInterrupted() +{ + QMutexLocker locker(&interruptMutex); + return interrupted; +} + +void DbVersionConverter::conversionInterrupted(Db* db, bool rollback) +{ + emit conversionAborted(); + if (rollback) + db->rollback(); + + db->close(); + + QFile file(fullConversionConfig->targetFile); + if (file.exists()) + file.remove(); +} + +void DbVersionConverter::conversionError(Db* db, const QString& errMsg) +{ + emit conversionFailed(tr("Error while converting database: %1").arg(errMsg)); + db->rollback(); + db->close(); + + QFile file(fullConversionConfig->targetFile); + if (file.exists()) + file.remove(); +} + +void DbVersionConverter::confirmConversion() +{ + if (!errors.isEmpty() && !fullConversionConfig->errorsConfirmFunc(errors)) + { + emit conversionAborted(); + return; + } + + if (!diffList.isEmpty() && !fullConversionConfig->confirmFunc(diffList)) + { + emit conversionAborted(); + return; + } + + QtConcurrent::run(this, &DbVersionConverter::fullConvertStep2); +} + +void DbVersionConverter::registerDbAfterSuccessfulConversion() +{ + DBLIST->addDb(fullConversionConfig->targetName, fullConversionConfig->targetFile); +} + +void DbVersionConverter::interrupt() +{ + setInterrupted(true); +} + +const QList<QPair<QString, QString> >& DbVersionConverter::getDiffList() const +{ + return diffList; +} + +const QSet<QString>& DbVersionConverter::getErrors() const +{ + return errors; +} + +const QList<SqliteQueryPtr>&DbVersionConverter::getConverted() const +{ + return newQueries; +} + +QStringList DbVersionConverter::getConvertedSqls() const +{ + QStringList sqls; + for (SqliteQueryPtr query : newQueries) + sqls << query->detokenize(); + + return sqls; +} + +void DbVersionConverter::convertDb() +{ + resolver = new SchemaResolver(db); + resolver->setIgnoreSystemObjects(true); + StrHash<SqliteQueryPtr> parsedObjects = resolver->getAllParsedObjects(); + for (SqliteQueryPtr query : parsedObjects.values()) + { + switch (targetDialect) + { + case Dialect::Sqlite2: + convert3To2(query); + break; + case Dialect::Sqlite3: + convert2To3(query); + break; + } + } + sortConverted(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/dbversionconverter.h b/SQLiteStudio3/coreSQLiteStudio/dbversionconverter.h new file mode 100644 index 0000000..8343c2f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/dbversionconverter.h @@ -0,0 +1,133 @@ +#ifndef DBVERSIONCONVERTER_H +#define DBVERSIONCONVERTER_H + +#include "parser/ast/sqlitequery.h" +#include <QList> +#include <QStringList> +#include <QPair> +#include <QMutex> + +class Db; +class SchemaResolver; +class SqliteCreateTable; +class SqliteCreateTrigger; +class SqliteCreateIndex; +class SqliteCreateView; +class SqliteCreateVirtualTable; +class SqliteIndexedColumn; +class SqliteSelect; +class SqliteDelete; +class SqliteUpdate; +class SqliteInsert; +class SqliteExpr; +class SqliteBeginTrans; + +class API_EXPORT DbVersionConverter : public QObject +{ + Q_OBJECT + + public: + typedef std::function<bool(const QList<QPair<QString,QString>>& diffs)> ConversionConfimFunction; + typedef std::function<bool(const QSet<QString>& errors)> ConversionErrorsConfimFunction; + + DbVersionConverter(); + virtual ~DbVersionConverter(); + + void convert(Dialect from, Dialect to, Db* srcDb, const QString& targetFile, const QString& targetName, ConversionConfimFunction confirmFunc, + ConversionErrorsConfimFunction errorsConfirmFunc); + void convert(Dialect from, Dialect to, Db* db); + void convert3To2(Db* db); + void convert2To3(Db* db); + QString convert(Dialect from, Dialect to, const QString& sql); + QString convert3To2(const QString& sql); + QString convert2To3(const QString& sql); + SqliteQueryPtr convert(Dialect from, Dialect to, SqliteQueryPtr query); + SqliteQueryPtr convert3To2(SqliteQueryPtr query); + SqliteQueryPtr convert2To3(SqliteQueryPtr query); + + const QList<QPair<QString, QString> >& getDiffList() const; + const QSet<QString>& getErrors() const; + const QList<SqliteQueryPtr>& getConverted() const; + QStringList getConvertedSqls() const; + void reset(); + QList<Dialect> getSupportedVersions() const; + QStringList getSupportedVersionNames() const; + + private: + struct FullConversionConfig + { + Dialect from; + Dialect to; + Db* srcDb = nullptr; + QString targetFile; + QString targetName; + ConversionConfimFunction confirmFunc = nullptr; + ConversionErrorsConfimFunction errorsConfirmFunc = nullptr; + }; + + void fullConvertStep1(); + void fullConvertStep2(); + bool fullConvertCreateObjectsStep1(Db* db, QStringList& tables); + bool fullConvertCreateObjectsStep2(Db* db); + bool fullConvertCopyData(Db* db, const QStringList& tables); + bool checkForInterrupted(Db* db, bool rollback); + void convertDb(); + QList<SqliteQueryPtr> parse(const QString& sql, Dialect dialect); + bool modifySelectForVersion2(SqliteSelect* select); + bool modifyDeleteForVersion2(SqliteDelete* del); + bool modifyInsertForVersion2(SqliteInsert* insert); + bool modifyUpdateForVersion2(SqliteUpdate* update); + bool modifyCreateTableForVersion2(SqliteCreateTable* createTable); + bool modifyCreateTriggerForVersion2(SqliteCreateTrigger* createTrigger); + bool modifyCreateIndexForVersion2(SqliteCreateIndex* createIndex); + bool modifyCreateViewForVersion2(SqliteCreateView* createView); + bool modifyVirtualTableForVesion2(SqliteQueryPtr& query, SqliteCreateVirtualTable* createVirtualTable); + bool modifyAllExprsForVersion2(SqliteStatement* stmt); + bool modifySingleExprForVersion2(SqliteExpr* expr); + bool modifyAllIndexedColumnsForVersion2(SqliteStatement* stmt); + bool modifyAllIndexedColumnsForVersion2(const QList<SqliteIndexedColumn*> columns); + bool modifySingleIndexedColumnForVersion2(SqliteIndexedColumn* idxCol); + bool modifyBeginTransForVersion3(SqliteBeginTrans* begin); + bool modifyCreateTableForVersion3(SqliteCreateTable* createTable); + QString getSqlForDiff(SqliteStatement* stmt); + void storeDiff(const QString& sql1, SqliteStatement* stmt); + void storeErrorDiff(SqliteStatement* stmt); + QList<Db*> getAllPossibleDbInstances() const; + QString generateQueryPlaceholders(int argCount); + void sortConverted(); + void setInterrupted(bool value); + bool isInterrupted(); + void conversionInterrupted(Db* db, bool rollback); + + template <class T> + QSharedPointer<T> copyQuery(SqliteQueryPtr query) + { + return QSharedPointer<T>::create(*(query.dynamicCast<T>().data())); + } + + Db* db = nullptr; + Dialect targetDialect = Dialect::Sqlite3; + SchemaResolver* resolver = nullptr; + QList<QPair<QString,QString>> diffList; + QSet<QString> errors; + QList<SqliteQueryPtr> newQueries; + FullConversionConfig* fullConversionConfig = nullptr; + bool interrupted = false; + QMutex interruptMutex; + + private slots: + void conversionError(Db* db, const QString& errMsg); + void confirmConversion(); + void registerDbAfterSuccessfulConversion(); + + public slots: + void interrupt(); + + signals: + void askUserForConfirmation(); + void conversionSuccessful(); + void conversionAborted(); + void conversionFailed(const QString& errorMsg); +}; + +#endif // DBVERSIONCONVERTER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.cpp b/SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.cpp new file mode 100644 index 0000000..9df3f81 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.cpp @@ -0,0 +1,76 @@ +#include "ddlhistorymodel.h" +#include "querymodel.h" +#include <QSet> +#include <QDebug> + +DdlHistoryModel::DdlHistoryModel(Db* db, QObject *parent) : + QSortFilterProxyModel(parent) +{ + static const QString query = + "SELECT dbname," + " file," + " date(timestamp, 'unixepoch') AS date," + " count(*)" + " FROM ddl_history" + " GROUP BY dbname, file, date" + " ORDER BY date DESC"; + + internalModel = new QueryModel(db, this); + setSourceModel(internalModel); + connect(internalModel, SIGNAL(refreshed()), this, SIGNAL(refreshed())); + + setFilterKeyColumn(0); + setDynamicSortFilter(true); + + internalModel->setQuery(query); +} + +QVariant DdlHistoryModel::data(const QModelIndex& index, int role) const +{ + if (role == Qt::TextAlignmentRole && (index.column() == 2 || index.column() == 3)) + return (int)(Qt::AlignRight|Qt::AlignVCenter); + + return QSortFilterProxyModel::data(index, role); +} + +void DdlHistoryModel::refresh() +{ + internalModel->refresh(); + +} + +void DdlHistoryModel::setDbNameForFilter(const QString& value) +{ + setFilterWildcard("*"+value+"*"); +} + +QStringList DdlHistoryModel::getDbNames() const +{ + QSet<QString> dbNames; + for (int row = 0; row < rowCount(); row++) + dbNames << data(index(row, 0)).toString(); + + QStringList nameList = dbNames.toList(); + qSort(nameList); + return nameList; +} + +QVariant DdlHistoryModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) + { + switch (section) + { + case 0: + return tr("Database name", "ddl history header"); + case 1: + return tr("Database file", "ddl history header"); + case 2: + return tr("Date of execution", "ddl history header"); + case 3: + return tr("Changes", "ddl history header"); + } + return QVariant(); + } + return QSortFilterProxyModel::headerData(section, orientation, role); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.h b/SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.h new file mode 100644 index 0000000..8a82fb4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/ddlhistorymodel.h @@ -0,0 +1,31 @@ +#ifndef DDLHISTORYMODEL_H +#define DDLHISTORYMODEL_H + +#include "coreSQLiteStudio_global.h" +#include <QSortFilterProxyModel> + +class QueryModel; +class Db; + +class API_EXPORT DdlHistoryModel : public QSortFilterProxyModel +{ + Q_OBJECT + + public: + DdlHistoryModel(Db* db, QObject *parent = nullptr); + + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + void refresh(); + QString getDbNameForFilter() const; + void setDbNameForFilter(const QString& value); + QStringList getDbNames() const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + + private: + QueryModel* internalModel = nullptr; + + signals: + void refreshed(); +}; + +#endif // DDLHISTORYMODEL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/dialect.h b/SQLiteStudio3/coreSQLiteStudio/dialect.h new file mode 100644 index 0000000..f78b16a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/dialect.h @@ -0,0 +1,10 @@ +#ifndef DIALECT_H +#define DIALECT_H + +enum class Dialect : int +{ + Sqlite3 = 0, + Sqlite2 = 1 +}; + +#endif // DIALECT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/diff/diff_match_patch.cpp b/SQLiteStudio3/coreSQLiteStudio/diff/diff_match_patch.cpp new file mode 100644 index 0000000..4f0022e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/diff/diff_match_patch.cpp @@ -0,0 +1,2112 @@ +/* + * Copyright 2008 Google Inc. All Rights Reserved. + * Author: fraser@google.com (Neil Fraser) + * Author: mikeslemmer@gmail.com (Mike Slemmer) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Diff Match and Patch + * http://code.google.com/p/google-diff-match-patch/ + */ + +/* + * Version slightly modified to compile with Qt 5.2 (2 minor fixes: toAscii() -> toLatin()). + */ + +#include <algorithm> +#include <limits> +// Code known to compile and run with Qt 4.3 through Qt 4.7. +#include <QtCore> +#include <time.h> +#include "diff_match_patch.h" + + +////////////////////////// +// +// Diff Class +// +////////////////////////// + + +/** + * Constructor. Initializes the diff with the provided values. + * @param operation One of INSERT, DELETE or EQUAL + * @param text The text being applied + */ +Diff::Diff(Operation _operation, const QString &_text) : + operation(_operation), text(_text) { + // Construct a diff with the specified operation and text. +} + +Diff::Diff() { +} + + +QString Diff::strOperation(Operation op) { + switch (op) { + case INSERT: + return "INSERT"; + case DELETE: + return "DELETE"; + case EQUAL: + return "EQUAL"; + } + throw "Invalid operation."; +} + +/** + * Display a human-readable version of this Diff. + * @return text version + */ +QString Diff::toString() const { + QString prettyText = text; + // Replace linebreaks with Pilcrow signs. + prettyText.replace('\n', L'\u00b6'); + return QString("Diff(") + strOperation(operation) + QString(",\"") + + prettyText + QString("\")"); +} + +/** + * Is this Diff equivalent to another Diff? + * @param d Another Diff to compare against + * @return true or false + */ +bool Diff::operator==(const Diff &d) const { + return (d.operation == this->operation) && (d.text == this->text); +} + +bool Diff::operator!=(const Diff &d) const { + return !(operator == (d)); +} + + +///////////////////////////////////////////// +// +// Patch Class +// +///////////////////////////////////////////// + + +/** + * Constructor. Initializes with an empty list of diffs. + */ +Patch::Patch() : + start1(0), start2(0), + length1(0), length2(0) { +} + +bool Patch::isNull() const { + if (start1 == 0 && start2 == 0 && length1 == 0 && length2 == 0 + && diffs.size() == 0) { + return true; + } + return false; +} + + +/** + * Emmulate GNU diff's format. + * Header: @@ -382,8 +481,9 @@ + * Indicies are printed as 1-based, not 0-based. + * @return The GNU diff string + */ +QString Patch::toString() { + QString coords1, coords2; + if (length1 == 0) { + coords1 = QString::number(start1) + QString(",0"); + } else if (length1 == 1) { + coords1 = QString::number(start1 + 1); + } else { + coords1 = QString::number(start1 + 1) + QString(",") + + QString::number(length1); + } + if (length2 == 0) { + coords2 = QString::number(start2) + QString(",0"); + } else if (length2 == 1) { + coords2 = QString::number(start2 + 1); + } else { + coords2 = QString::number(start2 + 1) + QString(",") + + QString::number(length2); + } + QString text; + text = QString("@@ -") + coords1 + QString(" +") + coords2 + + QString(" @@\n"); + // Escape the body of the patch with %xx notation. + foreach (Diff aDiff, diffs) { + switch (aDiff.operation) { + case INSERT: + text += QString('+'); + break; + case DELETE: + text += QString('-'); + break; + case EQUAL: + text += QString(' '); + break; + } + text += QString(QUrl::toPercentEncoding(aDiff.text, " !~*'();/?:@&=+$,#")) + + QString("\n"); + } + + return text; +} + + +///////////////////////////////////////////// +// +// diff_match_patch Class +// +///////////////////////////////////////////// + +diff_match_patch::diff_match_patch() : + Diff_Timeout(1.0f), + Diff_EditCost(4), + Match_Threshold(0.5f), + Match_Distance(1000), + Patch_DeleteThreshold(0.5f), + Patch_Margin(4), + Match_MaxBits(32) { +} + + +QList<Diff> diff_match_patch::diff_main(const QString &text1, + const QString &text2) { + return diff_main(text1, text2, true); +} + +QList<Diff> diff_match_patch::diff_main(const QString &text1, + const QString &text2, bool checklines) { + // Set a deadline by which time the diff must be complete. + clock_t deadline; + if (Diff_Timeout <= 0) { + deadline = std::numeric_limits<clock_t>::max(); + } else { + deadline = clock() + (clock_t)(Diff_Timeout * CLOCKS_PER_SEC); + } + return diff_main(text1, text2, checklines, deadline); +} + +QList<Diff> diff_match_patch::diff_main(const QString &text1, + const QString &text2, bool checklines, clock_t deadline) { + // Check for null inputs. + if (text1.isNull() || text2.isNull()) { + throw "Null inputs. (diff_main)"; + } + + // Check for equality (speedup). + QList<Diff> diffs; + if (text1 == text2) { + if (!text1.isEmpty()) { + diffs.append(Diff(EQUAL, text1)); + } + return diffs; + } + + // Trim off common prefix (speedup). + int commonlength = diff_commonPrefix(text1, text2); + const QString &commonprefix = text1.left(commonlength); + QString textChopped1 = text1.mid(commonlength); + QString textChopped2 = text2.mid(commonlength); + + // Trim off common suffix (speedup). + commonlength = diff_commonSuffix(textChopped1, textChopped2); + const QString &commonsuffix = textChopped1.right(commonlength); + textChopped1 = textChopped1.left(textChopped1.length() - commonlength); + textChopped2 = textChopped2.left(textChopped2.length() - commonlength); + + // Compute the diff on the middle block. + diffs = diff_compute(textChopped1, textChopped2, checklines, deadline); + + // Restore the prefix and suffix. + if (!commonprefix.isEmpty()) { + diffs.prepend(Diff(EQUAL, commonprefix)); + } + if (!commonsuffix.isEmpty()) { + diffs.append(Diff(EQUAL, commonsuffix)); + } + + diff_cleanupMerge(diffs); + + return diffs; +} + + +QList<Diff> diff_match_patch::diff_compute(QString text1, QString text2, + bool checklines, clock_t deadline) { + QList<Diff> diffs; + + if (text1.isEmpty()) { + // Just add some text (speedup). + diffs.append(Diff(INSERT, text2)); + return diffs; + } + + if (text2.isEmpty()) { + // Just delete some text (speedup). + diffs.append(Diff(DELETE, text1)); + return diffs; + } + + { + const QString longtext = text1.length() > text2.length() ? text1 : text2; + const QString shorttext = text1.length() > text2.length() ? text2 : text1; + const int i = longtext.indexOf(shorttext); + if (i != -1) { + // Shorter text is inside the longer text (speedup). + const Operation op = (text1.length() > text2.length()) ? DELETE : INSERT; + diffs.append(Diff(op, longtext.left(i))); + diffs.append(Diff(EQUAL, shorttext)); + diffs.append(Diff(op, safeMid(longtext, i + shorttext.length()))); + return diffs; + } + + if (shorttext.length() == 1) { + // Single character string. + // After the previous speedup, the character can't be an equality. + diffs.append(Diff(DELETE, text1)); + diffs.append(Diff(INSERT, text2)); + return diffs; + } + // Garbage collect longtext and shorttext by scoping out. + } + + // Check to see if the problem can be split in two. + const QStringList hm = diff_halfMatch(text1, text2); + if (hm.count() > 0) { + // A half-match was found, sort out the return data. + const QString text1_a = hm[0]; + const QString text1_b = hm[1]; + const QString text2_a = hm[2]; + const QString text2_b = hm[3]; + const QString mid_common = hm[4]; + // Send both pairs off for separate processing. + const QList<Diff> diffs_a = diff_main(text1_a, text2_a, + checklines, deadline); + const QList<Diff> diffs_b = diff_main(text1_b, text2_b, + checklines, deadline); + // Merge the results. + diffs = diffs_a; + diffs.append(Diff(EQUAL, mid_common)); + diffs += diffs_b; + return diffs; + } + + // Perform a real diff. + if (checklines && text1.length() > 100 && text2.length() > 100) { + return diff_lineMode(text1, text2, deadline); + } + + return diff_bisect(text1, text2, deadline); +} + + +QList<Diff> diff_match_patch::diff_lineMode(QString text1, QString text2, + clock_t deadline) { + // Scan the text on a line-by-line basis first. + const QList<QVariant> b = diff_linesToChars(text1, text2); + text1 = b[0].toString(); + text2 = b[1].toString(); + QStringList linearray = b[2].toStringList(); + + QList<Diff> diffs = diff_main(text1, text2, false, deadline); + + // Convert the diff back to original text. + diff_charsToLines(diffs, linearray); + // Eliminate freak matches (e.g. blank lines) + diff_cleanupSemantic(diffs); + + // Rediff any replacement blocks, this time character-by-character. + // Add a dummy entry at the end. + diffs.append(Diff(EQUAL, "")); + int count_delete = 0; + int count_insert = 0; + QString text_delete = ""; + QString text_insert = ""; + + QMutableListIterator<Diff> pointer(diffs); + Diff *thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + while (thisDiff != NULL) { + switch (thisDiff->operation) { + case INSERT: + count_insert++; + text_insert += thisDiff->text; + break; + case DELETE: + count_delete++; + text_delete += thisDiff->text; + break; + case EQUAL: + // Upon reaching an equality, check for prior redundancies. + if (count_delete >= 1 && count_insert >= 1) { + // Delete the offending records and add the merged ones. + pointer.previous(); + for (int j = 0; j < count_delete + count_insert; j++) { + pointer.previous(); + pointer.remove(); + } + foreach(Diff newDiff, + diff_main(text_delete, text_insert, false, deadline)) { + pointer.insert(newDiff); + } + } + count_insert = 0; + count_delete = 0; + text_delete = ""; + text_insert = ""; + break; + } + thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + } + diffs.removeLast(); // Remove the dummy entry at the end. + + return diffs; +} + + +QList<Diff> diff_match_patch::diff_bisect(const QString &text1, + const QString &text2, clock_t deadline) { + // Cache the text lengths to prevent multiple calls. + const int text1_length = text1.length(); + const int text2_length = text2.length(); + const int max_d = (text1_length + text2_length + 1) / 2; + const int v_offset = max_d; + const int v_length = 2 * max_d; + int *v1 = new int[v_length]; + int *v2 = new int[v_length]; + for (int x = 0; x < v_length; x++) { + v1[x] = -1; + v2[x] = -1; + } + v1[v_offset + 1] = 0; + v2[v_offset + 1] = 0; + const int delta = text1_length - text2_length; + // If the total number of characters is odd, then the front path will + // collide with the reverse path. + const bool front = (delta % 2 != 0); + // Offsets for start and end of k loop. + // Prevents mapping of space beyond the grid. + int k1start = 0; + int k1end = 0; + int k2start = 0; + int k2end = 0; + for (int d = 0; d < max_d; d++) { + // Bail out if deadline is reached. + if (clock() > deadline) { + break; + } + + // Walk the front path one step. + for (int k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { + const int k1_offset = v_offset + k1; + int x1; + if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) { + x1 = v1[k1_offset + 1]; + } else { + x1 = v1[k1_offset - 1] + 1; + } + int y1 = x1 - k1; + while (x1 < text1_length && y1 < text2_length + && text1[x1] == text2[y1]) { + x1++; + y1++; + } + v1[k1_offset] = x1; + if (x1 > text1_length) { + // Ran off the right of the graph. + k1end += 2; + } else if (y1 > text2_length) { + // Ran off the bottom of the graph. + k1start += 2; + } else if (front) { + int k2_offset = v_offset + delta - k1; + if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) { + // Mirror x2 onto top-left coordinate system. + int x2 = text1_length - v2[k2_offset]; + if (x1 >= x2) { + // Overlap detected. + delete [] v1; + delete [] v2; + return diff_bisectSplit(text1, text2, x1, y1, deadline); + } + } + } + } + + // Walk the reverse path one step. + for (int k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { + const int k2_offset = v_offset + k2; + int x2; + if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) { + x2 = v2[k2_offset + 1]; + } else { + x2 = v2[k2_offset - 1] + 1; + } + int y2 = x2 - k2; + while (x2 < text1_length && y2 < text2_length + && text1[text1_length - x2 - 1] == text2[text2_length - y2 - 1]) { + x2++; + y2++; + } + v2[k2_offset] = x2; + if (x2 > text1_length) { + // Ran off the left of the graph. + k2end += 2; + } else if (y2 > text2_length) { + // Ran off the top of the graph. + k2start += 2; + } else if (!front) { + int k1_offset = v_offset + delta - k2; + if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) { + int x1 = v1[k1_offset]; + int y1 = v_offset + x1 - k1_offset; + // Mirror x2 onto top-left coordinate system. + x2 = text1_length - x2; + if (x1 >= x2) { + // Overlap detected. + delete [] v1; + delete [] v2; + return diff_bisectSplit(text1, text2, x1, y1, deadline); + } + } + } + } + } + delete [] v1; + delete [] v2; + // Diff took too long and hit the deadline or + // number of diffs equals number of characters, no commonality at all. + QList<Diff> diffs; + diffs.append(Diff(DELETE, text1)); + diffs.append(Diff(INSERT, text2)); + return diffs; +} + +QList<Diff> diff_match_patch::diff_bisectSplit(const QString &text1, + const QString &text2, int x, int y, clock_t deadline) { + QString text1a = text1.left(x); + QString text2a = text2.left(y); + QString text1b = safeMid(text1, x); + QString text2b = safeMid(text2, y); + + // Compute both diffs serially. + QList<Diff> diffs = diff_main(text1a, text2a, false, deadline); + QList<Diff> diffsb = diff_main(text1b, text2b, false, deadline); + + return diffs + diffsb; +} + +QList<QVariant> diff_match_patch::diff_linesToChars(const QString &text1, + const QString &text2) { + QStringList lineArray; + QMap<QString, int> lineHash; + // e.g. linearray[4] == "Hello\n" + // e.g. linehash.get("Hello\n") == 4 + + // "\x00" is a valid character, but various debuggers don't like it. + // So we'll insert a junk entry to avoid generating a null character. + lineArray.append(""); + + const QString chars1 = diff_linesToCharsMunge(text1, lineArray, lineHash); + const QString chars2 = diff_linesToCharsMunge(text2, lineArray, lineHash); + + QList<QVariant> listRet; + listRet.append(QVariant::fromValue(chars1)); + listRet.append(QVariant::fromValue(chars2)); + listRet.append(QVariant::fromValue(lineArray)); + return listRet; +} + + +QString diff_match_patch::diff_linesToCharsMunge(const QString &text, + QStringList &lineArray, + QMap<QString, int> &lineHash) { + int lineStart = 0; + int lineEnd = -1; + QString line; + QString chars; + // Walk the text, pulling out a substring for each line. + // text.split('\n') would would temporarily double our memory footprint. + // Modifying text would create many large strings to garbage collect. + while (lineEnd < text.length() - 1) { + lineEnd = text.indexOf('\n', lineStart); + if (lineEnd == -1) { + lineEnd = text.length() - 1; + } + line = safeMid(text, lineStart, lineEnd + 1 - lineStart); + lineStart = lineEnd + 1; + + if (lineHash.contains(line)) { + chars += QChar(static_cast<ushort>(lineHash.value(line))); + } else { + lineArray.append(line); + lineHash.insert(line, lineArray.size() - 1); + chars += QChar(static_cast<ushort>(lineArray.size() - 1)); + } + } + return chars; +} + + + +void diff_match_patch::diff_charsToLines(QList<Diff> &diffs, + const QStringList &lineArray) { + // Qt has no mutable foreach construct. + QMutableListIterator<Diff> i(diffs); + while (i.hasNext()) { + Diff &diff = i.next(); + QString text; + for (int y = 0; y < diff.text.length(); y++) { + text += lineArray.value(static_cast<ushort>(diff.text[y].unicode())); + } + diff.text = text; + } +} + + +int diff_match_patch::diff_commonPrefix(const QString &text1, + const QString &text2) { + // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + const int n = std::min(text1.length(), text2.length()); + for (int i = 0; i < n; i++) { + if (text1[i] != text2[i]) { + return i; + } + } + return n; +} + + +int diff_match_patch::diff_commonSuffix(const QString &text1, + const QString &text2) { + // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + const int text1_length = text1.length(); + const int text2_length = text2.length(); + const int n = std::min(text1_length, text2_length); + for (int i = 1; i <= n; i++) { + if (text1[text1_length - i] != text2[text2_length - i]) { + return i - 1; + } + } + return n; +} + +int diff_match_patch::diff_commonOverlap(const QString &text1, + const QString &text2) { + // Cache the text lengths to prevent multiple calls. + const int text1_length = text1.length(); + const int text2_length = text2.length(); + // Eliminate the null case. + if (text1_length == 0 || text2_length == 0) { + return 0; + } + // Truncate the longer string. + QString text1_trunc = text1; + QString text2_trunc = text2; + if (text1_length > text2_length) { + text1_trunc = text1.right(text2_length); + } else if (text1_length < text2_length) { + text2_trunc = text2.left(text1_length); + } + const int text_length = std::min(text1_length, text2_length); + // Quick check for the worst case. + if (text1_trunc == text2_trunc) { + return text_length; + } + + // Start by looking for a single character match + // and increase length until no match is found. + // Performance analysis: http://neil.fraser.name/news/2010/11/04/ + int best = 0; + int length = 1; + while (true) { + QString pattern = text1_trunc.right(length); + int found = text2_trunc.indexOf(pattern); + if (found == -1) { + return best; + } + length += found; + if (found == 0 || text1_trunc.right(length) == text2_trunc.left(length)) { + best = length; + length++; + } + } +} + +QStringList diff_match_patch::diff_halfMatch(const QString &text1, + const QString &text2) { + if (Diff_Timeout <= 0) { + // Don't risk returning a non-optimal diff if we have unlimited time. + return QStringList(); + } + const QString longtext = text1.length() > text2.length() ? text1 : text2; + const QString shorttext = text1.length() > text2.length() ? text2 : text1; + if (longtext.length() < 4 || shorttext.length() * 2 < longtext.length()) { + return QStringList(); // Pointless. + } + + // First check if the second quarter is the seed for a half-match. + const QStringList hm1 = diff_halfMatchI(longtext, shorttext, + (longtext.length() + 3) / 4); + // Check again based on the third quarter. + const QStringList hm2 = diff_halfMatchI(longtext, shorttext, + (longtext.length() + 1) / 2); + QStringList hm; + if (hm1.isEmpty() && hm2.isEmpty()) { + return QStringList(); + } else if (hm2.isEmpty()) { + hm = hm1; + } else if (hm1.isEmpty()) { + hm = hm2; + } else { + // Both matched. Select the longest. + hm = hm1[4].length() > hm2[4].length() ? hm1 : hm2; + } + + // A half-match was found, sort out the return data. + if (text1.length() > text2.length()) { + return hm; + } else { + QStringList listRet; + listRet << hm[2] << hm[3] << hm[0] << hm[1] << hm[4]; + return listRet; + } +} + + +QStringList diff_match_patch::diff_halfMatchI(const QString &longtext, + const QString &shorttext, + int i) { + // Start with a 1/4 length substring at position i as a seed. + const QString seed = safeMid(longtext, i, longtext.length() / 4); + int j = -1; + QString best_common; + QString best_longtext_a, best_longtext_b; + QString best_shorttext_a, best_shorttext_b; + while ((j = shorttext.indexOf(seed, j + 1)) != -1) { + const int prefixLength = diff_commonPrefix(safeMid(longtext, i), + safeMid(shorttext, j)); + const int suffixLength = diff_commonSuffix(longtext.left(i), + shorttext.left(j)); + if (best_common.length() < suffixLength + prefixLength) { + best_common = safeMid(shorttext, j - suffixLength, suffixLength) + + safeMid(shorttext, j, prefixLength); + best_longtext_a = longtext.left(i - suffixLength); + best_longtext_b = safeMid(longtext, i + prefixLength); + best_shorttext_a = shorttext.left(j - suffixLength); + best_shorttext_b = safeMid(shorttext, j + prefixLength); + } + } + if (best_common.length() * 2 >= longtext.length()) { + QStringList listRet; + listRet << best_longtext_a << best_longtext_b << best_shorttext_a + << best_shorttext_b << best_common; + return listRet; + } else { + return QStringList(); + } +} + + +void diff_match_patch::diff_cleanupSemantic(QList<Diff> &diffs) { + if (diffs.isEmpty()) { + return; + } + bool changes = false; + QStack<Diff> equalities; // Stack of equalities. + QString lastequality; // Always equal to equalities.lastElement().text + QMutableListIterator<Diff> pointer(diffs); + // Number of characters that changed prior to the equality. + int length_insertions1 = 0; + int length_deletions1 = 0; + // Number of characters that changed after the equality. + int length_insertions2 = 0; + int length_deletions2 = 0; + Diff *thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + while (thisDiff != NULL) { + if (thisDiff->operation == EQUAL) { + // Equality found. + equalities.push(*thisDiff); + length_insertions1 = length_insertions2; + length_deletions1 = length_deletions2; + length_insertions2 = 0; + length_deletions2 = 0; + lastequality = thisDiff->text; + } else { + // An insertion or deletion. + if (thisDiff->operation == INSERT) { + length_insertions2 += thisDiff->text.length(); + } else { + length_deletions2 += thisDiff->text.length(); + } + // Eliminate an equality that is smaller or equal to the edits on both + // sides of it. + if (!lastequality.isNull() + && (lastequality.length() + <= std::max(length_insertions1, length_deletions1)) + && (lastequality.length() + <= std::max(length_insertions2, length_deletions2))) { + // printf("Splitting: '%s'\n", qPrintable(lastequality)); + // Walk back to offending equality. + while (*thisDiff != equalities.top()) { + thisDiff = &pointer.previous(); + } + pointer.next(); + + // Replace equality with a delete. + pointer.setValue(Diff(DELETE, lastequality)); + // Insert a corresponding an insert. + pointer.insert(Diff(INSERT, lastequality)); + + equalities.pop(); // Throw away the equality we just deleted. + if (!equalities.isEmpty()) { + // Throw away the previous equality (it needs to be reevaluated). + equalities.pop(); + } + if (equalities.isEmpty()) { + // There are no previous equalities, walk back to the start. + while (pointer.hasPrevious()) { + pointer.previous(); + } + } else { + // There is a safe equality we can fall back to. + thisDiff = &equalities.top(); + while (*thisDiff != pointer.previous()) { + // Intentionally empty loop. + } + } + + length_insertions1 = 0; // Reset the counters. + length_deletions1 = 0; + length_insertions2 = 0; + length_deletions2 = 0; + lastequality = QString(); + changes = true; + } + } + thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + } + + // Normalize the diff. + if (changes) { + diff_cleanupMerge(diffs); + } + diff_cleanupSemanticLossless(diffs); + + // Find any overlaps between deletions and insertions. + // e.g: <del>abcxxx</del><ins>xxxdef</ins> + // -> <del>abc</del>xxx<ins>def</ins> + // e.g: <del>xxxabc</del><ins>defxxx</ins> + // -> <ins>def</ins>xxx<del>abc</del> + // Only extract an overlap if it is as big as the edit ahead or behind it. + pointer.toFront(); + Diff *prevDiff = NULL; + thisDiff = NULL; + if (pointer.hasNext()) { + prevDiff = &pointer.next(); + if (pointer.hasNext()) { + thisDiff = &pointer.next(); + } + } + while (thisDiff != NULL) { + if (prevDiff->operation == DELETE && + thisDiff->operation == INSERT) { + QString deletion = prevDiff->text; + QString insertion = thisDiff->text; + int overlap_length1 = diff_commonOverlap(deletion, insertion); + int overlap_length2 = diff_commonOverlap(insertion, deletion); + if (overlap_length1 >= overlap_length2) { + if (overlap_length1 >= deletion.length() / 2.0 || + overlap_length1 >= insertion.length() / 2.0) { + // Overlap found. Insert an equality and trim the surrounding edits. + pointer.previous(); + pointer.insert(Diff(EQUAL, insertion.left(overlap_length1))); + prevDiff->text = + deletion.left(deletion.length() - overlap_length1); + thisDiff->text = safeMid(insertion, overlap_length1); + // pointer.insert inserts the element before the cursor, so there is + // no need to step past the new element. + } + } else { + if (overlap_length2 >= deletion.length() / 2.0 || + overlap_length2 >= insertion.length() / 2.0) { + // Reverse overlap found. + // Insert an equality and swap and trim the surrounding edits. + pointer.previous(); + pointer.insert(Diff(EQUAL, deletion.left(overlap_length2))); + prevDiff->operation = INSERT; + prevDiff->text = + insertion.left(insertion.length() - overlap_length2); + thisDiff->operation = DELETE; + thisDiff->text = safeMid(deletion, overlap_length2); + // pointer.insert inserts the element before the cursor, so there is + // no need to step past the new element. + } + } + thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + } + prevDiff = thisDiff; + thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + } +} + + +void diff_match_patch::diff_cleanupSemanticLossless(QList<Diff> &diffs) { + QString equality1, edit, equality2; + QString commonString; + int commonOffset; + int score, bestScore; + QString bestEquality1, bestEdit, bestEquality2; + // Create a new iterator at the start. + QMutableListIterator<Diff> pointer(diffs); + Diff *prevDiff = pointer.hasNext() ? &pointer.next() : NULL; + Diff *thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + Diff *nextDiff = pointer.hasNext() ? &pointer.next() : NULL; + + // Intentionally ignore the first and last element (don't need checking). + while (nextDiff != NULL) { + if (prevDiff->operation == EQUAL && + nextDiff->operation == EQUAL) { + // This is a single edit surrounded by equalities. + equality1 = prevDiff->text; + edit = thisDiff->text; + equality2 = nextDiff->text; + + // First, shift the edit as far left as possible. + commonOffset = diff_commonSuffix(equality1, edit); + if (commonOffset != 0) { + commonString = safeMid(edit, edit.length() - commonOffset); + equality1 = equality1.left(equality1.length() - commonOffset); + edit = commonString + edit.left(edit.length() - commonOffset); + equality2 = commonString + equality2; + } + + // Second, step character by character right, looking for the best fit. + bestEquality1 = equality1; + bestEdit = edit; + bestEquality2 = equality2; + bestScore = diff_cleanupSemanticScore(equality1, edit) + + diff_cleanupSemanticScore(edit, equality2); + while (!edit.isEmpty() && !equality2.isEmpty() + && edit[0] == equality2[0]) { + equality1 += edit[0]; + edit = safeMid(edit, 1) + equality2[0]; + equality2 = safeMid(equality2, 1); + score = diff_cleanupSemanticScore(equality1, edit) + + diff_cleanupSemanticScore(edit, equality2); + // The >= encourages trailing rather than leading whitespace on edits. + if (score >= bestScore) { + bestScore = score; + bestEquality1 = equality1; + bestEdit = edit; + bestEquality2 = equality2; + } + } + + if (prevDiff->text != bestEquality1) { + // We have an improvement, save it back to the diff. + if (!bestEquality1.isEmpty()) { + prevDiff->text = bestEquality1; + } else { + pointer.previous(); // Walk past nextDiff. + pointer.previous(); // Walk past thisDiff. + pointer.previous(); // Walk past prevDiff. + pointer.remove(); // Delete prevDiff. + pointer.next(); // Walk past thisDiff. + pointer.next(); // Walk past nextDiff. + } + thisDiff->text = bestEdit; + if (!bestEquality2.isEmpty()) { + nextDiff->text = bestEquality2; + } else { + pointer.remove(); // Delete nextDiff. + nextDiff = thisDiff; + thisDiff = prevDiff; + } + } + } + prevDiff = thisDiff; + thisDiff = nextDiff; + nextDiff = pointer.hasNext() ? &pointer.next() : NULL; + } +} + + +int diff_match_patch::diff_cleanupSemanticScore(const QString &one, + const QString &two) { + if (one.isEmpty() || two.isEmpty()) { + // Edges are the best. + return 6; + } + + // Each port of this function behaves slightly differently due to + // subtle differences in each language's definition of things like + // 'whitespace'. Since this function's purpose is largely cosmetic, + // the choice has been made to use each language's native features + // rather than force total conformity. + QChar char1 = one[one.length() - 1]; + QChar char2 = two[0]; + bool nonAlphaNumeric1 = !char1.isLetterOrNumber(); + bool nonAlphaNumeric2 = !char2.isLetterOrNumber(); + bool whitespace1 = nonAlphaNumeric1 && char1.isSpace(); + bool whitespace2 = nonAlphaNumeric2 && char2.isSpace(); + bool lineBreak1 = whitespace1 && char1.category() == QChar::Other_Control; + bool lineBreak2 = whitespace2 && char2.category() == QChar::Other_Control; + bool blankLine1 = lineBreak1 && BLANKLINEEND.indexIn(one) != -1; + bool blankLine2 = lineBreak2 && BLANKLINESTART.indexIn(two) != -1; + + if (blankLine1 || blankLine2) { + // Five points for blank lines. + return 5; + } else if (lineBreak1 || lineBreak2) { + // Four points for line breaks. + return 4; + } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) { + // Three points for end of sentences. + return 3; + } else if (whitespace1 || whitespace2) { + // Two points for whitespace. + return 2; + } else if (nonAlphaNumeric1 || nonAlphaNumeric2) { + // One point for non-alphanumeric. + return 1; + } + return 0; +} + + +// Define some regex patterns for matching boundaries. +QRegExp diff_match_patch::BLANKLINEEND = QRegExp("\\n\\r?\\n$"); +QRegExp diff_match_patch::BLANKLINESTART = QRegExp("^\\r?\\n\\r?\\n"); + + +void diff_match_patch::diff_cleanupEfficiency(QList<Diff> &diffs) { + if (diffs.isEmpty()) { + return; + } + bool changes = false; + QStack<Diff> equalities; // Stack of equalities. + QString lastequality; // Always equal to equalities.lastElement().text + QMutableListIterator<Diff> pointer(diffs); + // Is there an insertion operation before the last equality. + bool pre_ins = false; + // Is there a deletion operation before the last equality. + bool pre_del = false; + // Is there an insertion operation after the last equality. + bool post_ins = false; + // Is there a deletion operation after the last equality. + bool post_del = false; + + Diff *thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + Diff *safeDiff = thisDiff; + + while (thisDiff != NULL) { + if (thisDiff->operation == EQUAL) { + // Equality found. + if (thisDiff->text.length() < Diff_EditCost && (post_ins || post_del)) { + // Candidate found. + equalities.push(*thisDiff); + pre_ins = post_ins; + pre_del = post_del; + lastequality = thisDiff->text; + } else { + // Not a candidate, and can never become one. + equalities.clear(); + lastequality = QString(); + safeDiff = thisDiff; + } + post_ins = post_del = false; + } else { + // An insertion or deletion. + if (thisDiff->operation == DELETE) { + post_del = true; + } else { + post_ins = true; + } + /* + * Five types to be split: + * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del> + * <ins>A</ins>X<ins>C</ins><del>D</del> + * <ins>A</ins><del>B</del>X<ins>C</ins> + * <ins>A</del>X<ins>C</ins><del>D</del> + * <ins>A</ins><del>B</del>X<del>C</del> + */ + if (!lastequality.isNull() + && ((pre_ins && pre_del && post_ins && post_del) + || ((lastequality.length() < Diff_EditCost / 2) + && ((pre_ins ? 1 : 0) + (pre_del ? 1 : 0) + + (post_ins ? 1 : 0) + (post_del ? 1 : 0)) == 3))) { + // printf("Splitting: '%s'\n", qPrintable(lastequality)); + // Walk back to offending equality. + while (*thisDiff != equalities.top()) { + thisDiff = &pointer.previous(); + } + pointer.next(); + + // Replace equality with a delete. + pointer.setValue(Diff(DELETE, lastequality)); + // Insert a corresponding an insert. + pointer.insert(Diff(INSERT, lastequality)); + thisDiff = &pointer.previous(); + pointer.next(); + + equalities.pop(); // Throw away the equality we just deleted. + lastequality = QString(); + if (pre_ins && pre_del) { + // No changes made which could affect previous entry, keep going. + post_ins = post_del = true; + equalities.clear(); + safeDiff = thisDiff; + } else { + if (!equalities.isEmpty()) { + // Throw away the previous equality (it needs to be reevaluated). + equalities.pop(); + } + if (equalities.isEmpty()) { + // There are no previous questionable equalities, + // walk back to the last known safe diff. + thisDiff = safeDiff; + } else { + // There is an equality we can fall back to. + thisDiff = &equalities.top(); + } + while (*thisDiff != pointer.previous()) { + // Intentionally empty loop. + } + post_ins = post_del = false; + } + + changes = true; + } + } + thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + } + + if (changes) { + diff_cleanupMerge(diffs); + } +} + + +void diff_match_patch::diff_cleanupMerge(QList<Diff> &diffs) { + diffs.append(Diff(EQUAL, "")); // Add a dummy entry at the end. + QMutableListIterator<Diff> pointer(diffs); + int count_delete = 0; + int count_insert = 0; + QString text_delete = ""; + QString text_insert = ""; + Diff *thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + Diff *prevEqual = NULL; + int commonlength; + while (thisDiff != NULL) { + switch (thisDiff->operation) { + case INSERT: + count_insert++; + text_insert += thisDiff->text; + prevEqual = NULL; + break; + case DELETE: + count_delete++; + text_delete += thisDiff->text; + prevEqual = NULL; + break; + case EQUAL: + if (count_delete + count_insert > 1) { + bool both_types = count_delete != 0 && count_insert != 0; + // Delete the offending records. + pointer.previous(); // Reverse direction. + while (count_delete-- > 0) { + pointer.previous(); + pointer.remove(); + } + while (count_insert-- > 0) { + pointer.previous(); + pointer.remove(); + } + if (both_types) { + // Factor out any common prefixies. + commonlength = diff_commonPrefix(text_insert, text_delete); + if (commonlength != 0) { + if (pointer.hasPrevious()) { + thisDiff = &pointer.previous(); + if (thisDiff->operation != EQUAL) { + throw "Previous diff should have been an equality."; + } + thisDiff->text += text_insert.left(commonlength); + pointer.next(); + } else { + pointer.insert(Diff(EQUAL, text_insert.left(commonlength))); + } + text_insert = safeMid(text_insert, commonlength); + text_delete = safeMid(text_delete, commonlength); + } + // Factor out any common suffixies. + commonlength = diff_commonSuffix(text_insert, text_delete); + if (commonlength != 0) { + thisDiff = &pointer.next(); + thisDiff->text = safeMid(text_insert, text_insert.length() + - commonlength) + thisDiff->text; + text_insert = text_insert.left(text_insert.length() + - commonlength); + text_delete = text_delete.left(text_delete.length() + - commonlength); + pointer.previous(); + } + } + // Insert the merged records. + if (!text_delete.isEmpty()) { + pointer.insert(Diff(DELETE, text_delete)); + } + if (!text_insert.isEmpty()) { + pointer.insert(Diff(INSERT, text_insert)); + } + // Step forward to the equality. + thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + + } else if (prevEqual != NULL) { + // Merge this equality with the previous one. + prevEqual->text += thisDiff->text; + pointer.remove(); + thisDiff = &pointer.previous(); + pointer.next(); // Forward direction + } + count_insert = 0; + count_delete = 0; + text_delete = ""; + text_insert = ""; + prevEqual = thisDiff; + break; + } + thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + } + if (diffs.back().text.isEmpty()) { + diffs.removeLast(); // Remove the dummy entry at the end. + } + + /* + * Second pass: look for single edits surrounded on both sides by equalities + * which can be shifted sideways to eliminate an equality. + * e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC + */ + bool changes = false; + // Create a new iterator at the start. + // (As opposed to walking the current one back.) + pointer.toFront(); + Diff *prevDiff = pointer.hasNext() ? &pointer.next() : NULL; + thisDiff = pointer.hasNext() ? &pointer.next() : NULL; + Diff *nextDiff = pointer.hasNext() ? &pointer.next() : NULL; + + // Intentionally ignore the first and last element (don't need checking). + while (nextDiff != NULL) { + if (prevDiff->operation == EQUAL && + nextDiff->operation == EQUAL) { + // This is a single edit surrounded by equalities. + if (thisDiff->text.endsWith(prevDiff->text)) { + // Shift the edit over the previous equality. + thisDiff->text = prevDiff->text + + thisDiff->text.left(thisDiff->text.length() + - prevDiff->text.length()); + nextDiff->text = prevDiff->text + nextDiff->text; + pointer.previous(); // Walk past nextDiff. + pointer.previous(); // Walk past thisDiff. + pointer.previous(); // Walk past prevDiff. + pointer.remove(); // Delete prevDiff. + pointer.next(); // Walk past thisDiff. + thisDiff = &pointer.next(); // Walk past nextDiff. + nextDiff = pointer.hasNext() ? &pointer.next() : NULL; + changes = true; + } else if (thisDiff->text.startsWith(nextDiff->text)) { + // Shift the edit over the next equality. + prevDiff->text += nextDiff->text; + thisDiff->text = safeMid(thisDiff->text, nextDiff->text.length()) + + nextDiff->text; + pointer.remove(); // Delete nextDiff. + nextDiff = pointer.hasNext() ? &pointer.next() : NULL; + changes = true; + } + } + prevDiff = thisDiff; + thisDiff = nextDiff; + nextDiff = pointer.hasNext() ? &pointer.next() : NULL; + } + // If shifts were made, the diff needs reordering and another shift sweep. + if (changes) { + diff_cleanupMerge(diffs); + } +} + + +int diff_match_patch::diff_xIndex(const QList<Diff> &diffs, int loc) { + int chars1 = 0; + int chars2 = 0; + int last_chars1 = 0; + int last_chars2 = 0; + Diff lastDiff; + foreach(Diff aDiff, diffs) { + if (aDiff.operation != INSERT) { + // Equality or deletion. + chars1 += aDiff.text.length(); + } + if (aDiff.operation != DELETE) { + // Equality or insertion. + chars2 += aDiff.text.length(); + } + if (chars1 > loc) { + // Overshot the location. + lastDiff = aDiff; + break; + } + last_chars1 = chars1; + last_chars2 = chars2; + } + if (lastDiff.operation == DELETE) { + // The location was deleted. + return last_chars2; + } + // Add the remaining character length. + return last_chars2 + (loc - last_chars1); +} + + +QString diff_match_patch::diff_prettyHtml(const QList<Diff> &diffs) { + QString html; + QString text; + foreach(Diff aDiff, diffs) { + text = aDiff.text; + text.replace("&", "&").replace("<", "<") + .replace(">", ">").replace("\n", "¶<br>"); + switch (aDiff.operation) { + case INSERT: + html += QString("<ins style=\"background:#e6ffe6;\">") + text + + QString("</ins>"); + break; + case DELETE: + html += QString("<del style=\"background:#ffe6e6;\">") + text + + QString("</del>"); + break; + case EQUAL: + html += QString("<span>") + text + QString("</span>"); + break; + } + } + return html; +} + + +QString diff_match_patch::diff_text1(const QList<Diff> &diffs) { + QString text; + foreach(Diff aDiff, diffs) { + if (aDiff.operation != INSERT) { + text += aDiff.text; + } + } + return text; +} + + +QString diff_match_patch::diff_text2(const QList<Diff> &diffs) { + QString text; + foreach(Diff aDiff, diffs) { + if (aDiff.operation != DELETE) { + text += aDiff.text; + } + } + return text; +} + + +int diff_match_patch::diff_levenshtein(const QList<Diff> &diffs) { + int levenshtein = 0; + int insertions = 0; + int deletions = 0; + foreach(Diff aDiff, diffs) { + switch (aDiff.operation) { + case INSERT: + insertions += aDiff.text.length(); + break; + case DELETE: + deletions += aDiff.text.length(); + break; + case EQUAL: + // A deletion and an insertion is one substitution. + levenshtein += std::max(insertions, deletions); + insertions = 0; + deletions = 0; + break; + } + } + levenshtein += std::max(insertions, deletions); + return levenshtein; +} + + +QString diff_match_patch::diff_toDelta(const QList<Diff> &diffs) { + QString text; + foreach(Diff aDiff, diffs) { + switch (aDiff.operation) { + case INSERT: { + QString encoded = QString(QUrl::toPercentEncoding(aDiff.text, + " !~*'();/?:@&=+$,#")); + text += QString("+") + encoded + QString("\t"); + break; + } + case DELETE: + text += QString("-") + QString::number(aDiff.text.length()) + + QString("\t"); + break; + case EQUAL: + text += QString("=") + QString::number(aDiff.text.length()) + + QString("\t"); + break; + } + } + if (!text.isEmpty()) { + // Strip off trailing tab character. + text = text.left(text.length() - 1); + } + return text; +} + + +QList<Diff> diff_match_patch::diff_fromDelta(const QString &text1, + const QString &delta) { + QList<Diff> diffs; + int pointer = 0; // Cursor in text1 + QStringList tokens = delta.split("\t"); + foreach(QString token, tokens) { + if (token.isEmpty()) { + // Blank tokens are ok (from a trailing \t). + continue; + } + // Each token begins with a one character parameter which specifies the + // operation of this token (delete, insert, equality). + QString param = safeMid(token, 1); + switch (token[0].toLatin1()) { + case '+': + param = QUrl::fromPercentEncoding(qPrintable(param)); + diffs.append(Diff(INSERT, param)); + break; + case '-': + // Fall through. + case '=': { + int n; + n = param.toInt(); + if (n < 0) { + throw QString("Negative number in diff_fromDelta: %1").arg(param); + } + QString text; + text = safeMid(text1, pointer, n); + pointer += n; + if (token[0] == QChar('=')) { + diffs.append(Diff(EQUAL, text)); + } else { + diffs.append(Diff(DELETE, text)); + } + break; + } + default: + throw QString("Invalid diff operation in diff_fromDelta: %1") + .arg(token[0]); + } + } + if (pointer != text1.length()) { + throw QString("Delta length (%1) smaller than source text length (%2)") + .arg(pointer).arg(text1.length()); + } + return diffs; +} + + + // MATCH FUNCTIONS + + +int diff_match_patch::match_main(const QString &text, const QString &pattern, + int loc) { + // Check for null inputs. + if (text.isNull() || pattern.isNull()) { + throw "Null inputs. (match_main)"; + } + + loc = std::max(0, std::min(loc, text.length())); + if (text == pattern) { + // Shortcut (potentially not guaranteed by the algorithm) + return 0; + } else if (text.isEmpty()) { + // Nothing to match. + return -1; + } else if (loc + pattern.length() <= text.length() + && safeMid(text, loc, pattern.length()) == pattern) { + // Perfect match at the perfect spot! (Includes case of null pattern) + return loc; + } else { + // Do a fuzzy compare. + return match_bitap(text, pattern, loc); + } +} + + +int diff_match_patch::match_bitap(const QString &text, const QString &pattern, + int loc) { + if (!(Match_MaxBits == 0 || pattern.length() <= Match_MaxBits)) { + throw "Pattern too long for this application."; + } + + // Initialise the alphabet. + QMap<QChar, int> s = match_alphabet(pattern); + + // Highest score beyond which we give up. + double score_threshold = Match_Threshold; + // Is there a nearby exact match? (speedup) + int best_loc = text.indexOf(pattern, loc); + if (best_loc != -1) { + score_threshold = std::min(match_bitapScore(0, best_loc, loc, pattern), + score_threshold); + // What about in the other direction? (speedup) + best_loc = text.lastIndexOf(pattern, loc + pattern.length()); + if (best_loc != -1) { + score_threshold = std::min(match_bitapScore(0, best_loc, loc, pattern), + score_threshold); + } + } + + // Initialise the bit arrays. + int matchmask = 1 << (pattern.length() - 1); + best_loc = -1; + + int bin_min, bin_mid; + int bin_max = pattern.length() + text.length(); + int *rd; + int *last_rd = NULL; + for (int d = 0; d < pattern.length(); d++) { + // Scan for the best match; each iteration allows for one more error. + // Run a binary search to determine how far from 'loc' we can stray at + // this error level. + bin_min = 0; + bin_mid = bin_max; + while (bin_min < bin_mid) { + if (match_bitapScore(d, loc + bin_mid, loc, pattern) + <= score_threshold) { + bin_min = bin_mid; + } else { + bin_max = bin_mid; + } + bin_mid = (bin_max - bin_min) / 2 + bin_min; + } + // Use the result from this iteration as the maximum for the next. + bin_max = bin_mid; + int start = std::max(1, loc - bin_mid + 1); + int finish = std::min(loc + bin_mid, text.length()) + pattern.length(); + + rd = new int[finish + 2]; + rd[finish + 1] = (1 << d) - 1; + for (int j = finish; j >= start; j--) { + int charMatch; + if (text.length() <= j - 1) { + // Out of range. + charMatch = 0; + } else { + charMatch = s.value(text[j - 1], 0); + } + if (d == 0) { + // First pass: exact match. + rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; + } else { + // Subsequent passes: fuzzy match. + rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) + | (((last_rd[j + 1] | last_rd[j]) << 1) | 1) + | last_rd[j + 1]; + } + if ((rd[j] & matchmask) != 0) { + double score = match_bitapScore(d, j - 1, loc, pattern); + // This match will almost certainly be better than any existing + // match. But check anyway. + if (score <= score_threshold) { + // Told you so. + score_threshold = score; + best_loc = j - 1; + if (best_loc > loc) { + // When passing loc, don't exceed our current distance from loc. + start = std::max(1, 2 * loc - best_loc); + } else { + // Already passed loc, downhill from here on in. + break; + } + } + } + } + if (match_bitapScore(d + 1, loc, loc, pattern) > score_threshold) { + // No hope for a (better) match at greater error levels. + break; + } + delete [] last_rd; + last_rd = rd; + } + delete [] last_rd; + delete [] rd; + return best_loc; +} + + +double diff_match_patch::match_bitapScore(int e, int x, int loc, + const QString &pattern) { + const float accuracy = static_cast<float> (e) / pattern.length(); + const int proximity = qAbs(loc - x); + if (Match_Distance == 0) { + // Dodge divide by zero error. + return proximity == 0 ? accuracy : 1.0; + } + return accuracy + (proximity / static_cast<float> (Match_Distance)); +} + + +QMap<QChar, int> diff_match_patch::match_alphabet(const QString &pattern) { + QMap<QChar, int> s; + int i; + for (i = 0; i < pattern.length(); i++) { + QChar c = pattern[i]; + s.insert(c, 0); + } + for (i = 0; i < pattern.length(); i++) { + QChar c = pattern[i]; + s.insert(c, s.value(c) | (1 << (pattern.length() - i - 1))); + } + return s; +} + + +// PATCH FUNCTIONS + + +void diff_match_patch::patch_addContext(Patch &patch, const QString &text) { + if (text.isEmpty()) { + return; + } + QString pattern = safeMid(text, patch.start2, patch.length1); + int padding = 0; + + // Look for the first and last matches of pattern in text. If two different + // matches are found, increase the pattern length. + while (text.indexOf(pattern) != text.lastIndexOf(pattern) + && pattern.length() < Match_MaxBits - Patch_Margin - Patch_Margin) { + padding += Patch_Margin; + pattern = safeMid(text, std::max(0, patch.start2 - padding), + std::min(text.length(), patch.start2 + patch.length1 + padding) + - std::max(0, patch.start2 - padding)); + } + // Add one chunk for good luck. + padding += Patch_Margin; + + // Add the prefix. + QString prefix = safeMid(text, std::max(0, patch.start2 - padding), + patch.start2 - std::max(0, patch.start2 - padding)); + if (!prefix.isEmpty()) { + patch.diffs.prepend(Diff(EQUAL, prefix)); + } + // Add the suffix. + QString suffix = safeMid(text, patch.start2 + patch.length1, + std::min(text.length(), patch.start2 + patch.length1 + padding) + - (patch.start2 + patch.length1)); + if (!suffix.isEmpty()) { + patch.diffs.append(Diff(EQUAL, suffix)); + } + + // Roll back the start points. + patch.start1 -= prefix.length(); + patch.start2 -= prefix.length(); + // Extend the lengths. + patch.length1 += prefix.length() + suffix.length(); + patch.length2 += prefix.length() + suffix.length(); +} + + +QList<Patch> diff_match_patch::patch_make(const QString &text1, + const QString &text2) { + // Check for null inputs. + if (text1.isNull() || text2.isNull()) { + throw "Null inputs. (patch_make)"; + } + + // No diffs provided, compute our own. + QList<Diff> diffs = diff_main(text1, text2, true); + if (diffs.size() > 2) { + diff_cleanupSemantic(diffs); + diff_cleanupEfficiency(diffs); + } + + return patch_make(text1, diffs); +} + + +QList<Patch> diff_match_patch::patch_make(const QList<Diff> &diffs) { + // No origin string provided, compute our own. + const QString text1 = diff_text1(diffs); + return patch_make(text1, diffs); +} + + +QList<Patch> diff_match_patch::patch_make(const QString &text1, + const QString &text2, + const QList<Diff> &diffs) { + // text2 is entirely unused. + return patch_make(text1, diffs); + + Q_UNUSED(text2) +} + + +QList<Patch> diff_match_patch::patch_make(const QString &text1, + const QList<Diff> &diffs) { + // Check for null inputs. + if (text1.isNull()) { + throw "Null inputs. (patch_make)"; + } + + QList<Patch> patches; + if (diffs.isEmpty()) { + return patches; // Get rid of the null case. + } + Patch patch; + int char_count1 = 0; // Number of characters into the text1 string. + int char_count2 = 0; // Number of characters into the text2 string. + // Start with text1 (prepatch_text) and apply the diffs until we arrive at + // text2 (postpatch_text). We recreate the patches one by one to determine + // context info. + QString prepatch_text = text1; + QString postpatch_text = text1; + foreach(Diff aDiff, diffs) { + if (patch.diffs.isEmpty() && aDiff.operation != EQUAL) { + // A new patch starts here. + patch.start1 = char_count1; + patch.start2 = char_count2; + } + + switch (aDiff.operation) { + case INSERT: + patch.diffs.append(aDiff); + patch.length2 += aDiff.text.length(); + postpatch_text = postpatch_text.left(char_count2) + + aDiff.text + safeMid(postpatch_text, char_count2); + break; + case DELETE: + patch.length1 += aDiff.text.length(); + patch.diffs.append(aDiff); + postpatch_text = postpatch_text.left(char_count2) + + safeMid(postpatch_text, char_count2 + aDiff.text.length()); + break; + case EQUAL: + if (aDiff.text.length() <= 2 * Patch_Margin + && !patch.diffs.isEmpty() && !(aDiff == diffs.back())) { + // Small equality inside a patch. + patch.diffs.append(aDiff); + patch.length1 += aDiff.text.length(); + patch.length2 += aDiff.text.length(); + } + + if (aDiff.text.length() >= 2 * Patch_Margin) { + // Time for a new patch. + if (!patch.diffs.isEmpty()) { + patch_addContext(patch, prepatch_text); + patches.append(patch); + patch = Patch(); + // Unlike Unidiff, our patch lists have a rolling context. + // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff + // Update prepatch text & pos to reflect the application of the + // just completed patch. + prepatch_text = postpatch_text; + char_count1 = char_count2; + } + } + break; + } + + // Update the current character count. + if (aDiff.operation != INSERT) { + char_count1 += aDiff.text.length(); + } + if (aDiff.operation != DELETE) { + char_count2 += aDiff.text.length(); + } + } + // Pick up the leftover patch if not empty. + if (!patch.diffs.isEmpty()) { + patch_addContext(patch, prepatch_text); + patches.append(patch); + } + + return patches; +} + + +QList<Patch> diff_match_patch::patch_deepCopy(QList<Patch> &patches) { + QList<Patch> patchesCopy; + foreach(Patch aPatch, patches) { + Patch patchCopy = Patch(); + foreach(Diff aDiff, aPatch.diffs) { + Diff diffCopy = Diff(aDiff.operation, aDiff.text); + patchCopy.diffs.append(diffCopy); + } + patchCopy.start1 = aPatch.start1; + patchCopy.start2 = aPatch.start2; + patchCopy.length1 = aPatch.length1; + patchCopy.length2 = aPatch.length2; + patchesCopy.append(patchCopy); + } + return patchesCopy; +} + + +QPair<QString, QVector<bool> > diff_match_patch::patch_apply( + QList<Patch> &patches, const QString &sourceText) { + QString text = sourceText; // Copy to preserve original. + if (patches.isEmpty()) { + return QPair<QString,QVector<bool> >(text, QVector<bool>(0)); + } + + // Deep copy the patches so that no changes are made to originals. + QList<Patch> patchesCopy = patch_deepCopy(patches); + + QString nullPadding = patch_addPadding(patchesCopy); + text = nullPadding + text + nullPadding; + patch_splitMax(patchesCopy); + + int x = 0; + // delta keeps track of the offset between the expected and actual location + // of the previous patch. If there are patches expected at positions 10 and + // 20, but the first patch was found at 12, delta is 2 and the second patch + // has an effective expected position of 22. + int delta = 0; + QVector<bool> results(patchesCopy.size()); + foreach(Patch aPatch, patchesCopy) { + int expected_loc = aPatch.start2 + delta; + QString text1 = diff_text1(aPatch.diffs); + int start_loc; + int end_loc = -1; + if (text1.length() > Match_MaxBits) { + // patch_splitMax will only provide an oversized pattern in the case of + // a monster delete. + start_loc = match_main(text, text1.left(Match_MaxBits), expected_loc); + if (start_loc != -1) { + end_loc = match_main(text, text1.right(Match_MaxBits), + expected_loc + text1.length() - Match_MaxBits); + if (end_loc == -1 || start_loc >= end_loc) { + // Can't find valid trailing context. Drop this patch. + start_loc = -1; + } + } + } else { + start_loc = match_main(text, text1, expected_loc); + } + if (start_loc == -1) { + // No match found. :( + results[x] = false; + // Subtract the delta for this failed patch from subsequent patches. + delta -= aPatch.length2 - aPatch.length1; + } else { + // Found a match. :) + results[x] = true; + delta = start_loc - expected_loc; + QString text2; + if (end_loc == -1) { + text2 = safeMid(text, start_loc, text1.length()); + } else { + text2 = safeMid(text, start_loc, end_loc + Match_MaxBits - start_loc); + } + if (text1 == text2) { + // Perfect match, just shove the replacement text in. + text = text.left(start_loc) + diff_text2(aPatch.diffs) + + safeMid(text, start_loc + text1.length()); + } else { + // Imperfect match. Run a diff to get a framework of equivalent + // indices. + QList<Diff> diffs = diff_main(text1, text2, false); + if (text1.length() > Match_MaxBits + && diff_levenshtein(diffs) / static_cast<float> (text1.length()) + > Patch_DeleteThreshold) { + // The end points match, but the content is unacceptably bad. + results[x] = false; + } else { + diff_cleanupSemanticLossless(diffs); + int index1 = 0; + foreach(Diff aDiff, aPatch.diffs) { + if (aDiff.operation != EQUAL) { + int index2 = diff_xIndex(diffs, index1); + if (aDiff.operation == INSERT) { + // Insertion + text = text.left(start_loc + index2) + aDiff.text + + safeMid(text, start_loc + index2); + } else if (aDiff.operation == DELETE) { + // Deletion + text = text.left(start_loc + index2) + + safeMid(text, start_loc + diff_xIndex(diffs, + index1 + aDiff.text.length())); + } + } + if (aDiff.operation != DELETE) { + index1 += aDiff.text.length(); + } + } + } + } + } + x++; + } + // Strip the padding off. + text = safeMid(text, nullPadding.length(), text.length() + - 2 * nullPadding.length()); + return QPair<QString, QVector<bool> >(text, results); +} + + +QString diff_match_patch::patch_addPadding(QList<Patch> &patches) { + short paddingLength = Patch_Margin; + QString nullPadding = ""; + for (short x = 1; x <= paddingLength; x++) { + nullPadding += QChar((ushort)x); + } + + // Bump all the patches forward. + QMutableListIterator<Patch> pointer(patches); + while (pointer.hasNext()) { + Patch &aPatch = pointer.next(); + aPatch.start1 += paddingLength; + aPatch.start2 += paddingLength; + } + + // Add some padding on start of first diff. + Patch &firstPatch = patches.first(); + QList<Diff> &firstPatchDiffs = firstPatch.diffs; + if (firstPatchDiffs.empty() || firstPatchDiffs.first().operation != EQUAL) { + // Add nullPadding equality. + firstPatchDiffs.prepend(Diff(EQUAL, nullPadding)); + firstPatch.start1 -= paddingLength; // Should be 0. + firstPatch.start2 -= paddingLength; // Should be 0. + firstPatch.length1 += paddingLength; + firstPatch.length2 += paddingLength; + } else if (paddingLength > firstPatchDiffs.first().text.length()) { + // Grow first equality. + Diff &firstDiff = firstPatchDiffs.first(); + int extraLength = paddingLength - firstDiff.text.length(); + firstDiff.text = safeMid(nullPadding, firstDiff.text.length(), + paddingLength - firstDiff.text.length()) + firstDiff.text; + firstPatch.start1 -= extraLength; + firstPatch.start2 -= extraLength; + firstPatch.length1 += extraLength; + firstPatch.length2 += extraLength; + } + + // Add some padding on end of last diff. + Patch &lastPatch = patches.first(); + QList<Diff> &lastPatchDiffs = lastPatch.diffs; + if (lastPatchDiffs.empty() || lastPatchDiffs.last().operation != EQUAL) { + // Add nullPadding equality. + lastPatchDiffs.append(Diff(EQUAL, nullPadding)); + lastPatch.length1 += paddingLength; + lastPatch.length2 += paddingLength; + } else if (paddingLength > lastPatchDiffs.last().text.length()) { + // Grow last equality. + Diff &lastDiff = lastPatchDiffs.last(); + int extraLength = paddingLength - lastDiff.text.length(); + lastDiff.text += nullPadding.left(extraLength); + lastPatch.length1 += extraLength; + lastPatch.length2 += extraLength; + } + + return nullPadding; +} + + +void diff_match_patch::patch_splitMax(QList<Patch> &patches) { + short patch_size = Match_MaxBits; + QString precontext, postcontext; + Patch patch; + int start1, start2; + bool empty; + Operation diff_type; + QString diff_text; + QMutableListIterator<Patch> pointer(patches); + Patch bigpatch; + + if (pointer.hasNext()) { + bigpatch = pointer.next(); + } + + while (!bigpatch.isNull()) { + if (bigpatch.length1 <= patch_size) { + bigpatch = pointer.hasNext() ? pointer.next() : Patch(); + continue; + } + // Remove the big old patch. + pointer.remove(); + start1 = bigpatch.start1; + start2 = bigpatch.start2; + precontext = ""; + while (!bigpatch.diffs.isEmpty()) { + // Create one of several smaller patches. + patch = Patch(); + empty = true; + patch.start1 = start1 - precontext.length(); + patch.start2 = start2 - precontext.length(); + if (!precontext.isEmpty()) { + patch.length1 = patch.length2 = precontext.length(); + patch.diffs.append(Diff(EQUAL, precontext)); + } + while (!bigpatch.diffs.isEmpty() + && patch.length1 < patch_size - Patch_Margin) { + diff_type = bigpatch.diffs.front().operation; + diff_text = bigpatch.diffs.front().text; + if (diff_type == INSERT) { + // Insertions are harmless. + patch.length2 += diff_text.length(); + start2 += diff_text.length(); + patch.diffs.append(bigpatch.diffs.front()); + bigpatch.diffs.removeFirst(); + empty = false; + } else if (diff_type == DELETE && patch.diffs.size() == 1 + && patch.diffs.front().operation == EQUAL + && diff_text.length() > 2 * patch_size) { + // This is a large deletion. Let it pass in one chunk. + patch.length1 += diff_text.length(); + start1 += diff_text.length(); + empty = false; + patch.diffs.append(Diff(diff_type, diff_text)); + bigpatch.diffs.removeFirst(); + } else { + // Deletion or equality. Only take as much as we can stomach. + diff_text = diff_text.left(std::min(diff_text.length(), + patch_size - patch.length1 - Patch_Margin)); + patch.length1 += diff_text.length(); + start1 += diff_text.length(); + if (diff_type == EQUAL) { + patch.length2 += diff_text.length(); + start2 += diff_text.length(); + } else { + empty = false; + } + patch.diffs.append(Diff(diff_type, diff_text)); + if (diff_text == bigpatch.diffs.front().text) { + bigpatch.diffs.removeFirst(); + } else { + bigpatch.diffs.front().text = safeMid(bigpatch.diffs.front().text, + diff_text.length()); + } + } + } + // Compute the head context for the next patch. + precontext = diff_text2(patch.diffs); + precontext = safeMid(precontext, precontext.length() - Patch_Margin); + // Append the end context for this patch. + if (diff_text1(bigpatch.diffs).length() > Patch_Margin) { + postcontext = diff_text1(bigpatch.diffs).left(Patch_Margin); + } else { + postcontext = diff_text1(bigpatch.diffs); + } + if (!postcontext.isEmpty()) { + patch.length1 += postcontext.length(); + patch.length2 += postcontext.length(); + if (!patch.diffs.isEmpty() + && patch.diffs.back().operation == EQUAL) { + patch.diffs.back().text += postcontext; + } else { + patch.diffs.append(Diff(EQUAL, postcontext)); + } + } + if (!empty) { + pointer.insert(patch); + } + } + bigpatch = pointer.hasNext() ? pointer.next() : Patch(); + } +} + + +QString diff_match_patch::patch_toText(const QList<Patch> &patches) { + QString text; + foreach(Patch aPatch, patches) { + text.append(aPatch.toString()); + } + return text; +} + + +QList<Patch> diff_match_patch::patch_fromText(const QString &textline) { + QList<Patch> patches; + if (textline.isEmpty()) { + return patches; + } + QStringList text = textline.split("\n", QString::SkipEmptyParts); + Patch patch; + QRegExp patchHeader("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$"); + char sign; + QString line; + while (!text.isEmpty()) { + if (!patchHeader.exactMatch(text.front())) { + throw QString("Invalid patch string: %1").arg(text.front()); + } + + patch = Patch(); + patch.start1 = patchHeader.cap(1).toInt(); + if (patchHeader.cap(2).isEmpty()) { + patch.start1--; + patch.length1 = 1; + } else if (patchHeader.cap(2) == "0") { + patch.length1 = 0; + } else { + patch.start1--; + patch.length1 = patchHeader.cap(2).toInt(); + } + + patch.start2 = patchHeader.cap(3).toInt(); + if (patchHeader.cap(4).isEmpty()) { + patch.start2--; + patch.length2 = 1; + } else if (patchHeader.cap(4) == "0") { + patch.length2 = 0; + } else { + patch.start2--; + patch.length2 = patchHeader.cap(4).toInt(); + } + text.removeFirst(); + + while (!text.isEmpty()) { + if (text.front().isEmpty()) { + text.removeFirst(); + continue; + } + sign = text.front()[0].toLatin1(); + line = safeMid(text.front(), 1); + line = line.replace("+", "%2B"); // decode would change all "+" to " " + line = QUrl::fromPercentEncoding(qPrintable(line)); + if (sign == '-') { + // Deletion. + patch.diffs.append(Diff(DELETE, line)); + } else if (sign == '+') { + // Insertion. + patch.diffs.append(Diff(INSERT, line)); + } else if (sign == ' ') { + // Minor equality. + patch.diffs.append(Diff(EQUAL, line)); + } else if (sign == '@') { + // Start of next patch. + break; + } else { + // WTF? + throw QString("Invalid patch mode '%1' in: %2").arg(sign).arg(line); + return QList<Patch>(); + } + text.removeFirst(); + } + + patches.append(patch); + + } + return patches; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/diff/diff_match_patch.h b/SQLiteStudio3/coreSQLiteStudio/diff/diff_match_patch.h new file mode 100644 index 0000000..d2eb0e8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/diff/diff_match_patch.h @@ -0,0 +1,631 @@ +/* + * Copyright 2008 Google Inc. All Rights Reserved. + * Author: fraser@google.com (Neil Fraser) + * Author: mikeslemmer@gmail.com (Mike Slemmer) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Diff Match and Patch + * http://code.google.com/p/google-diff-match-patch/ + */ + +#ifndef DIFF_MATCH_PATCH_H +#define DIFF_MATCH_PATCH_H + +#include <time.h> +#include "coreSQLiteStudio_global.h" + +/* + * Functions for diff, match and patch. + * Computes the difference between two texts to create a patch. + * Applies the patch onto another text, allowing for errors. + * + * @author fraser@google.com (Neil Fraser) + * + * Qt/C++ port by mikeslemmer@gmail.com (Mike Slemmer): + * + * Code known to compile and run with Qt 4.3 through Qt 4.7. + * + * Here is a trivial sample program which works properly when linked with this + * library: + * + + #include <QtCore> + #include <QString> + #include <QList> + #include <QMap> + #include <QVariant> + #include "diff_match_patch.h" + int main(int argc, char **argv) { + diff_match_patch dmp; + QString str1 = QString("First string in diff"); + QString str2 = QString("Second string in diff"); + + QString strPatch = dmp.patch_toText(dmp.patch_make(str1, str2)); + QPair<QString, QVector<bool> > out + = dmp.patch_apply(dmp.patch_fromText(strPatch), str1); + QString strResult = out.first; + + // here, strResult will equal str2 above. + return 0; + } + + */ + + +/**- +* The data structure representing a diff is a Linked list of Diff objects: +* {Diff(Operation.DELETE, "Hello"), Diff(Operation.INSERT, "Goodbye"), +* Diff(Operation.EQUAL, " world.")} +* which means: delete "Hello", add "Goodbye" and keep " world." +*/ +enum Operation { + DELETE, INSERT, EQUAL +}; + + +/** +* Class representing one diff operation. +*/ +class API_EXPORT Diff { + public: + Operation operation; + // One of: INSERT, DELETE or EQUAL. + QString text; + // The text associated with this diff operation. + + /** + * Constructor. Initializes the diff with the provided values. + * @param operation One of INSERT, DELETE or EQUAL. + * @param text The text being applied. + */ + Diff(Operation _operation, const QString &_text); + Diff(); + inline bool isNull() const; + QString toString() const; + bool operator==(const Diff &d) const; + bool operator!=(const Diff &d) const; + + static QString strOperation(Operation op); +}; + + +/** +* Class representing one patch operation. +*/ +class API_EXPORT Patch { + public: + QList<Diff> diffs; + int start1; + int start2; + int length1; + int length2; + + /** + * Constructor. Initializes with an empty list of diffs. + */ + Patch(); + bool isNull() const; + QString toString(); +}; + + +/** + * Class containing the diff, match and patch methods. + * Also contains the behaviour settings. + */ +class API_EXPORT diff_match_patch { + + friend class diff_match_patch_test; + + public: + // Defaults. + // Set these on your diff_match_patch instance to override the defaults. + + // Number of seconds to map a diff before giving up (0 for infinity). + float Diff_Timeout; + // Cost of an empty edit operation in terms of edit characters. + short Diff_EditCost; + // At what point is no match declared (0.0 = perfection, 1.0 = very loose). + float Match_Threshold; + // How far to search for a match (0 = exact location, 1000+ = broad match). + // A match this many characters away from the expected location will add + // 1.0 to the score (0.0 is a perfect match). + int Match_Distance; + // When deleting a large block of text (over ~64 characters), how close does + // the contents have to match the expected contents. (0.0 = perfection, + // 1.0 = very loose). Note that Match_Threshold controls how closely the + // end points of a delete need to match. + float Patch_DeleteThreshold; + // Chunk size for context length. + short Patch_Margin; + + // The number of bits in an int. + short Match_MaxBits; + + private: + // Define some regex patterns for matching boundaries. + static QRegExp BLANKLINEEND; + static QRegExp BLANKLINESTART; + + + public: + + diff_match_patch(); + + // DIFF FUNCTIONS + + + /** + * Find the differences between two texts. + * Run a faster slightly less optimal diff. + * This method allows the 'checklines' of diff_main() to be optional. + * Most of the time checklines is wanted, so default to true. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @return Linked List of Diff objects. + */ + QList<Diff> diff_main(const QString &text1, const QString &text2); + + /** + * Find the differences between two texts. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster slightly less optimal diff. + * @return Linked List of Diff objects. + */ + QList<Diff> diff_main(const QString &text1, const QString &text2, bool checklines); + + /** + * Find the differences between two texts. Simplifies the problem by + * stripping any common prefix or suffix off the texts before diffing. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster slightly less optimal diff. + * @param deadline Time when the diff should be complete by. Used + * internally for recursive calls. Users should set DiffTimeout instead. + * @return Linked List of Diff objects. + */ + private: + QList<Diff> diff_main(const QString &text1, const QString &text2, bool checklines, clock_t deadline); + + /** + * Find the differences between two texts. Assumes that the texts do not + * have any common prefix or suffix. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster slightly less optimal diff. + * @param deadline Time when the diff should be complete by. + * @return Linked List of Diff objects. + */ + private: + QList<Diff> diff_compute(QString text1, QString text2, bool checklines, clock_t deadline); + + /** + * Do a quick line-level diff on both strings, then rediff the parts for + * greater accuracy. + * This speedup can produce non-minimal diffs. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param deadline Time when the diff should be complete by. + * @return Linked List of Diff objects. + */ + private: + QList<Diff> diff_lineMode(QString text1, QString text2, clock_t deadline); + + /** + * Find the 'middle snake' of a diff, split the problem in two + * and return the recursively constructed diff. + * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @return Linked List of Diff objects. + */ + protected: + QList<Diff> diff_bisect(const QString &text1, const QString &text2, clock_t deadline); + + /** + * Given the location of the 'middle snake', split the diff in two parts + * and recurse. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param x Index of split point in text1. + * @param y Index of split point in text2. + * @param deadline Time at which to bail if not yet complete. + * @return LinkedList of Diff objects. + */ + private: + QList<Diff> diff_bisectSplit(const QString &text1, const QString &text2, int x, int y, clock_t deadline); + + /** + * Split two texts into a list of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param text1 First string. + * @param text2 Second string. + * @return Three element Object array, containing the encoded text1, the + * encoded text2 and the List of unique strings. The zeroth element + * of the List of unique strings is intentionally blank. + */ + protected: + QList<QVariant> diff_linesToChars(const QString &text1, const QString &text2); // return elems 0 and 1 are QString, elem 2 is QStringList + + /** + * Split a text into a list of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param text String to encode. + * @param lineArray List of unique strings. + * @param lineHash Map of strings to indices. + * @return Encoded string. + */ + private: + QString diff_linesToCharsMunge(const QString &text, QStringList &lineArray, + QMap<QString, int> &lineHash); + + /** + * Rehydrate the text in a diff from a string of line hashes to real lines of + * text. + * @param diffs LinkedList of Diff objects. + * @param lineArray List of unique strings. + */ + private: + void diff_charsToLines(QList<Diff> &diffs, const QStringList &lineArray); + + /** + * Determine the common prefix of two strings. + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the start of each string. + */ + public: + int diff_commonPrefix(const QString &text1, const QString &text2); + + /** + * Determine the common suffix of two strings. + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the end of each string. + */ + public: + int diff_commonSuffix(const QString &text1, const QString &text2); + + /** + * Determine if the suffix of one string is the prefix of another. + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the end of the first + * string and the start of the second string. + */ + protected: + int diff_commonOverlap(const QString &text1, const QString &text2); + + /** + * Do the two texts share a substring which is at least half the length of + * the longer text? + * This speedup can produce non-minimal diffs. + * @param text1 First string. + * @param text2 Second string. + * @return Five element String array, containing the prefix of text1, the + * suffix of text1, the prefix of text2, the suffix of text2 and the + * common middle. Or null if there was no match. + */ + protected: + QStringList diff_halfMatch(const QString &text1, const QString &text2); + + /** + * Does a substring of shorttext exist within longtext such that the + * substring is at least half the length of longtext? + * @param longtext Longer string. + * @param shorttext Shorter string. + * @param i Start index of quarter length substring within longtext. + * @return Five element String array, containing the prefix of longtext, the + * suffix of longtext, the prefix of shorttext, the suffix of shorttext + * and the common middle. Or null if there was no match. + */ + private: + QStringList diff_halfMatchI(const QString &longtext, const QString &shorttext, int i); + + /** + * Reduce the number of edits by eliminating semantically trivial equalities. + * @param diffs LinkedList of Diff objects. + */ + public: + void diff_cleanupSemantic(QList<Diff> &diffs); + + /** + * Look for single edits surrounded on both sides by equalities + * which can be shifted sideways to align the edit to a word boundary. + * e.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came. + * @param diffs LinkedList of Diff objects. + */ + public: + void diff_cleanupSemanticLossless(QList<Diff> &diffs); + + /** + * Given two strings, compute a score representing whether the internal + * boundary falls on logical boundaries. + * Scores range from 6 (best) to 0 (worst). + * @param one First string. + * @param two Second string. + * @return The score. + */ + private: + int diff_cleanupSemanticScore(const QString &one, const QString &two); + + /** + * Reduce the number of edits by eliminating operationally trivial equalities. + * @param diffs LinkedList of Diff objects. + */ + public: + void diff_cleanupEfficiency(QList<Diff> &diffs); + + /** + * Reorder and merge like edit sections. Merge equalities. + * Any edit section can move as long as it doesn't cross an equality. + * @param diffs LinkedList of Diff objects. + */ + public: + void diff_cleanupMerge(QList<Diff> &diffs); + + /** + * loc is a location in text1, compute and return the equivalent location in + * text2. + * e.g. "The cat" vs "The big cat", 1->1, 5->8 + * @param diffs LinkedList of Diff objects. + * @param loc Location within text1. + * @return Location within text2. + */ + public: + int diff_xIndex(const QList<Diff> &diffs, int loc); + + /** + * Convert a Diff list into a pretty HTML report. + * @param diffs LinkedList of Diff objects. + * @return HTML representation. + */ + public: + QString diff_prettyHtml(const QList<Diff> &diffs); + + /** + * Compute and return the source text (all equalities and deletions). + * @param diffs LinkedList of Diff objects. + * @return Source text. + */ + public: + QString diff_text1(const QList<Diff> &diffs); + + /** + * Compute and return the destination text (all equalities and insertions). + * @param diffs LinkedList of Diff objects. + * @return Destination text. + */ + public: + QString diff_text2(const QList<Diff> &diffs); + + /** + * Compute the Levenshtein distance; the number of inserted, deleted or + * substituted characters. + * @param diffs LinkedList of Diff objects. + * @return Number of changes. + */ + public: + int diff_levenshtein(const QList<Diff> &diffs); + + /** + * Crush the diff into an encoded string which describes the operations + * required to transform text1 into text2. + * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'. + * Operations are tab-separated. Inserted text is escaped using %xx notation. + * @param diffs Array of diff tuples. + * @return Delta text. + */ + public: + QString diff_toDelta(const QList<Diff> &diffs); + + /** + * Given the original text1, and an encoded string which describes the + * operations required to transform text1 into text2, compute the full diff. + * @param text1 Source string for the diff. + * @param delta Delta text. + * @return Array of diff tuples or null if invalid. + * @throws QString If invalid input. + */ + public: + QList<Diff> diff_fromDelta(const QString &text1, const QString &delta); + + + // MATCH FUNCTIONS + + + /** + * Locate the best instance of 'pattern' in 'text' near 'loc'. + * Returns -1 if no match found. + * @param text The text to search. + * @param pattern The pattern to search for. + * @param loc The location to search around. + * @return Best match index or -1. + */ + public: + int match_main(const QString &text, const QString &pattern, int loc); + + /** + * Locate the best instance of 'pattern' in 'text' near 'loc' using the + * Bitap algorithm. Returns -1 if no match found. + * @param text The text to search. + * @param pattern The pattern to search for. + * @param loc The location to search around. + * @return Best match index or -1. + */ + protected: + int match_bitap(const QString &text, const QString &pattern, int loc); + + /** + * Compute and return the score for a match with e errors and x location. + * @param e Number of errors in match. + * @param x Location of match. + * @param loc Expected location of match. + * @param pattern Pattern being sought. + * @return Overall score for match (0.0 = good, 1.0 = bad). + */ + private: + double match_bitapScore(int e, int x, int loc, const QString &pattern); + + /** + * Initialise the alphabet for the Bitap algorithm. + * @param pattern The text to encode. + * @return Hash of character locations. + */ + protected: + QMap<QChar, int> match_alphabet(const QString &pattern); + + + // PATCH FUNCTIONS + + + /** + * Increase the context until it is unique, + * but don't let the pattern expand beyond Match_MaxBits. + * @param patch The patch to grow. + * @param text Source text. + */ + protected: + void patch_addContext(Patch &patch, const QString &text); + + /** + * Compute a list of patches to turn text1 into text2. + * A set of diffs will be computed. + * @param text1 Old text. + * @param text2 New text. + * @return LinkedList of Patch objects. + */ + public: + QList<Patch> patch_make(const QString &text1, const QString &text2); + + /** + * Compute a list of patches to turn text1 into text2. + * text1 will be derived from the provided diffs. + * @param diffs Array of diff tuples for text1 to text2. + * @return LinkedList of Patch objects. + */ + public: + QList<Patch> patch_make(const QList<Diff> &diffs); + + /** + * Compute a list of patches to turn text1 into text2. + * text2 is ignored, diffs are the delta between text1 and text2. + * @param text1 Old text. + * @param text2 Ignored. + * @param diffs Array of diff tuples for text1 to text2. + * @return LinkedList of Patch objects. + * @deprecated Prefer patch_make(const QString &text1, const QList<Diff> &diffs). + */ + public: + QList<Patch> patch_make(const QString &text1, const QString &text2, const QList<Diff> &diffs); + + /** + * Compute a list of patches to turn text1 into text2. + * text2 is not provided, diffs are the delta between text1 and text2. + * @param text1 Old text. + * @param diffs Array of diff tuples for text1 to text2. + * @return LinkedList of Patch objects. + */ + public: + QList<Patch> patch_make(const QString &text1, const QList<Diff> &diffs); + + /** + * Given an array of patches, return another array that is identical. + * @param patches Array of patch objects. + * @return Array of patch objects. + */ + public: + QList<Patch> patch_deepCopy(QList<Patch> &patches); + + /** + * Merge a set of patches onto the text. Return a patched text, as well + * as an array of true/false values indicating which patches were applied. + * @param patches Array of patch objects. + * @param text Old text. + * @return Two element Object array, containing the new text and an array of + * boolean values. + */ + public: + QPair<QString,QVector<bool> > patch_apply(QList<Patch> &patches, const QString &text); + + /** + * Add some padding on text start and end so that edges can match something. + * Intended to be called only from within patch_apply. + * @param patches Array of patch objects. + * @return The padding string added to each side. + */ + public: + QString patch_addPadding(QList<Patch> &patches); + + /** + * Look through the patches and break up any which are longer than the + * maximum limit of the match algorithm. + * Intended to be called only from within patch_apply. + * @param patches LinkedList of Patch objects. + */ + public: + void patch_splitMax(QList<Patch> &patches); + + /** + * Take a list of patches and return a textual representation. + * @param patches List of Patch objects. + * @return Text representation of patches. + */ + public: + QString patch_toText(const QList<Patch> &patches); + + /** + * Parse a textual representation of patches and return a List of Patch + * objects. + * @param textline Text representation of patches. + * @return List of Patch objects. + * @throws QString If invalid input. + */ + public: + QList<Patch> patch_fromText(const QString &textline); + + /** + * A safer version of QString.mid(pos). This one returns "" instead of + * null when the postion equals the string length. + * @param str String to take a substring from. + * @param pos Position to start the substring from. + * @return Substring. + */ + private: + static inline QString safeMid(const QString &str, int pos) { + return (pos == str.length()) ? QString("") : str.mid(pos); + } + + /** + * A safer version of QString.mid(pos, len). This one returns "" instead of + * null when the postion equals the string length. + * @param str String to take a substring from. + * @param pos Position to start the substring from. + * @param len Length of substring. + * @return Substring. + */ + private: + static inline QString safeMid(const QString &str, int pos, int len) { + return (pos == str.length()) ? QString("") : str.mid(pos, len); + } +}; + +#endif // DIFF_MATCH_PATCH_H diff --git a/SQLiteStudio3/coreSQLiteStudio/expectedtoken.cpp b/SQLiteStudio3/coreSQLiteStudio/expectedtoken.cpp new file mode 100644 index 0000000..a02f8c9 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/expectedtoken.cpp @@ -0,0 +1,73 @@ +#include "expectedtoken.h" + +bool ExpectedToken::needsWrapping() const +{ + switch (type) + { + case ExpectedToken::COLUMN: + case ExpectedToken::TABLE: + case ExpectedToken::INDEX: + case ExpectedToken::TRIGGER: + case ExpectedToken::VIEW: + case ExpectedToken::DATABASE: + case ExpectedToken::OTHER: + case ExpectedToken::COLLATION: + return true; + case ExpectedToken::KEYWORD: + case ExpectedToken::FUNCTION: + case ExpectedToken::OPERATOR: + case ExpectedToken::STRING: + case ExpectedToken::NUMBER: + case ExpectedToken::BLOB: + case ExpectedToken::PRAGMA: + case ExpectedToken::NO_VALUE: + return false; + } + return false; +} + +int ExpectedToken::operator==(const ExpectedToken& other) +{ + return type == other.type && value == other.value && contextInfo == other.contextInfo && + label == other.label && prefix == other.prefix; +} + +QString ExpectedToken::toString() const +{ + return QString("%4. %1 : %2 (ctx: %3) [label: %5]").arg(value).arg(type).arg(contextInfo).arg(prefix).arg(label); +} + +ExpectedTokenPtr::ExpectedTokenPtr() : + QSharedPointer<ExpectedToken>() +{ +} + +ExpectedTokenPtr::ExpectedTokenPtr(ExpectedToken* ptr) : + QSharedPointer<ExpectedToken>(ptr) +{ +} + +ExpectedTokenPtr::ExpectedTokenPtr(const QSharedPointer<ExpectedToken>& other) : + QSharedPointer<ExpectedToken>(other) +{ +} + +ExpectedTokenPtr::ExpectedTokenPtr(const QWeakPointer<ExpectedToken>& other) : + QSharedPointer<ExpectedToken>(other) +{ +} + +int operator==(const ExpectedTokenPtr& ptr1, const ExpectedTokenPtr& ptr2) +{ + return *ptr1.data() == *ptr2.data(); +} + +int qHash(const ExpectedToken& token) +{ + return token.type ^ qHash(token.value + "/" + token.value + "/" + token.contextInfo + "/" + token.label + "/" + token.prefix); +} + +int qHash(const ExpectedTokenPtr& ptr) +{ + return qHash(*ptr.data()); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/expectedtoken.h b/SQLiteStudio3/coreSQLiteStudio/expectedtoken.h new file mode 100644 index 0000000..9242dd8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/expectedtoken.h @@ -0,0 +1,107 @@ +#ifndef EXPECTEDTOKEN_H +#define EXPECTEDTOKEN_H + +#include "coreSQLiteStudio_global.h" +#include <QString> +#include <QSharedPointer> + +struct API_EXPORT ExpectedToken +{ + /** + * @brief The expected token type + * Order of this enum matters, because it defines the order + * of sorting a list of expected tokens by CompletionComparer. + * The NO_VALUE value means that there is no prepared value, + * but some context information and/or label is available. + */ + enum Type + { + COLUMN, + TABLE, + INDEX, + TRIGGER, + VIEW, + DATABASE, + NO_VALUE, + KEYWORD, + FUNCTION, + OPERATOR, + COLLATION, + PRAGMA, + STRING, + NUMBER, + BLOB, + OTHER + }; + + /** + * @brief type Token type. + */ + Type type; + + /** + * @brief value Token value. + * The actual value to be inserted in expected position. + */ + QString value; + + /** + * @brief contextInfo Context information. + * Context of proposed token. Context is for example table, that proposed column belongs to, + * or it's database, that proposed table belongs to. This is used when completer sorts results. + */ + QString contextInfo; + + /** + * @brief label Token explanation label. + * This is a context info to be displayed in completer. It may provide info about actual object under the alias, + * or human readable info about the proposed token (like "New column name"). + */ + QString label; + + /** + * @brief prefix Token prefix. + * Necessary prefix for token to be valid. It should be concatenated with the value using a dot character. + */ + QString prefix; + + /** + * @brief Token priority on completion list. + * + * Some tokens require manual control on where they appear in the completion list. If this value is any positive + * number, than it's considered as a priority. The higher value, the higher priority. 0 and negative numbers are ignored. + * The priority has a precedence before any other sorting rules. + */ + int priority = 0; + + /** + * @brief needsWrapping Tells if the token requires wrapping. + * @return true if the token is of type that might require wrapping (in case the value contains forbidden characters or keyword as a name). + */ + bool needsWrapping() const; + + int operator==(const ExpectedToken& other); + QString toString() const; +}; + +/** + * @brief The ExpectedTokenPtr class + * This class would normally be just a typedef, but we need qHash() functionality + * being applied for this type, so simple typedef wouldn't overwrite the type matching for qHash() arguments. + * The qHash() is necessary for QSet and we need that for getting rid of duplicates. + */ +class API_EXPORT ExpectedTokenPtr : public QSharedPointer<ExpectedToken> +{ + public: + ExpectedTokenPtr(); + explicit ExpectedTokenPtr(ExpectedToken* ptr); + explicit ExpectedTokenPtr(const QSharedPointer<ExpectedToken> & other); + explicit ExpectedTokenPtr(const QWeakPointer<ExpectedToken> & other); +}; + +int operator==(const ExpectedTokenPtr& ptr1, const ExpectedTokenPtr& ptr2); + +int qHash(const ExpectedToken& token); +int qHash(const ExpectedTokenPtr &ptr); + +#endif // EXPECTEDTOKEN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/exportworker.cpp b/SQLiteStudio3/coreSQLiteStudio/exportworker.cpp new file mode 100644 index 0000000..9d227de --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/exportworker.cpp @@ -0,0 +1,516 @@ +#include "exportworker.h" +#include "plugins/exportplugin.h" +#include "schemaresolver.h" +#include "services/notifymanager.h" +#include "common/utils_sql.h" +#include "common/utils.h" +#include <QMutexLocker> +#include <QDebug> + +ExportWorker::ExportWorker(ExportPlugin* plugin, ExportManager::StandardExportConfig* config, QIODevice* output, QObject *parent) : + QObject(parent), plugin(plugin), config(config), output(output) +{ + executor = new QueryExecutor(); + executor->setAsyncMode(false); // worker runs in its own thread, we don't need (and we don't want) async executor + executor->setNoMetaColumns(true); // we don't want rowid columns, we want only columns requested by the original query +} + +ExportWorker::~ExportWorker() +{ + safe_delete(executor); + safe_delete(parser); +} + +void ExportWorker::run() +{ + bool res = false; + switch (exportMode) + { + case ExportManager::QUERY_RESULTS: + { + res = exportQueryResults(); + break; + } + case ExportManager::DATABASE: + { + res = exportDatabase(); + break; + } + case ExportManager::TABLE: + { + res = exportTable(); + break; + } + case ExportManager::UNDEFINED: + qCritical() << "Started ExportWorker with UNDEFINED mode."; + res = false; + break; + case ExportManager::FILE: + case ExportManager::CLIPBOARD: + break; + } + + plugin->cleanupAfterExport(); + + emit finished(res, output); +} + +void ExportWorker::prepareExportQueryResults(Db* db, const QString& query) +{ + this->db = db; + this->query = query; + exportMode = ExportManager::QUERY_RESULTS; + prepareParser(); +} + +void ExportWorker::prepareExportDatabase(Db* db, const QStringList& objectListToExport) +{ + this->db = db; + this->objectListToExport = objectListToExport; + exportMode = ExportManager::DATABASE; + prepareParser(); +} + +void ExportWorker::prepareExportTable(Db* db, const QString& database, const QString& table) +{ + this->db = db; + this->database = database; + this->table = table; + exportMode = ExportManager::TABLE; + prepareParser(); +} + +void ExportWorker::prepareParser() +{ + safe_delete(parser); + parser = new Parser(db->getDialect()); +} + +void ExportWorker::interrupt() +{ + QMutexLocker locker(&interruptMutex); + interrupted = true; + if (executor->isExecutionInProgress()) + executor->interrupt(); +} + +bool ExportWorker::exportQueryResults() +{ + executor->setDb(db); + executor->exec(query); + SqlQueryPtr results = executor->getResults(); + if (!results) + { + qCritical() << "Null results from executor in ExportWorker."; + return false; + } + + QList<QueryExecutor::ResultColumnPtr> resultColumns = executor->getResultColumns(); + QHash<ExportManager::ExportProviderFlag,QVariant> providerData = getProviderDataForQueryResults(); + + if (results->isInterrupted()) + return false; + + if (results->isError()) + { + notifyError(tr("Error while exporting query results: %1").arg(results->getErrorText())); + return false; + } + + if (!plugin->initBeforeExport(db, output, *config)) + return false; + + if (!plugin->beforeExportQueryResults(query, resultColumns, providerData)) + return false; + + if (isInterrupted()) + return false; + + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + if (!plugin->exportQueryResultsRow(row)) + return false; + + if (isInterrupted()) + return false; + } + + if (!plugin->afterExportQueryResults()) + return false; + + if (!plugin->afterExport()) + return false; + + return true; +} + +QHash<ExportManager::ExportProviderFlag, QVariant> ExportWorker::getProviderDataForQueryResults() +{ + static const QString colLengthSql = QStringLiteral("SELECT %1 FROM (%2)"); + static const QString colLengthTpl = QStringLiteral("max(length(%1))"); + QHash<ExportManager::ExportProviderFlag, QVariant> providerData; + + if (plugin->getProviderFlags().testFlag(ExportManager::ROW_COUNT)) + { + executor->countResults(); + providerData[ExportManager::ROW_COUNT] = executor->getTotalRowsReturned(); + } + + if (plugin->getProviderFlags().testFlag(ExportManager::DATA_LENGTHS)) + { + QStringList wrappedCols; + for (const QueryExecutor::ResultColumnPtr& col : executor->getResultColumns()) + wrappedCols << colLengthTpl.arg(wrapObjIfNeeded(col->displayName, db->getDialect())); + + executor->exec(colLengthSql.arg(wrappedCols.join(", "), query)); + SqlQueryPtr results = executor->getResults(); + if (!results) + { + qCritical() << "Null results from executor in ExportWorker."; + } + else if (results->isError()) + { + notifyError(tr("Error while counting data column width to export from query results: %2").arg(results->getErrorText())); + } + else + { + QList<int> colWidths; + for (const QVariant& value : results->next()->valueList()) + colWidths << value.toInt(); + + providerData[ExportManager::DATA_LENGTHS] = QVariant::fromValue(colWidths); + } + } + return providerData; +} + +bool ExportWorker::exportDatabase() +{ + QString err; + QList<ExportManager::ExportObjectPtr> dbObjects = collectDbObjects(&err); + if (!err.isNull()) + { + notifyError(err); + return false; + } + + if (!plugin->initBeforeExport(db, output, *config)) + return false; + + if (!plugin->beforeExportDatabase(db->getName())) + return false; + + if (isInterrupted()) + return false; + + QList<ExportManager::ExportObject::Type> order = { + ExportManager::ExportObject::TABLE, ExportManager::ExportObject::INDEX, ExportManager::ExportObject::TRIGGER, ExportManager::ExportObject::VIEW + }; + + qSort(dbObjects.begin(), dbObjects.end(), [=](const ExportManager::ExportObjectPtr& dbObj1, const ExportManager::ExportObjectPtr& dbObj2) -> bool + { + return order.indexOf(dbObj1->type) < order.indexOf(dbObj2->type); + }); + + if (!plugin->beforeExportTables()) + return false; + + if (!exportDatabaseObjects(dbObjects, ExportManager::ExportObject::TABLE)) + return false; + + if (!plugin->afterExportTables()) + return false; + + if (!plugin->beforeExportIndexes()) + return false; + + if (!exportDatabaseObjects(dbObjects, ExportManager::ExportObject::INDEX)) + return false; + + if (!plugin->afterExportIndexes()) + return false; + + if (!plugin->beforeExportTriggers()) + return false; + + if (!exportDatabaseObjects(dbObjects, ExportManager::ExportObject::TRIGGER)) + return false; + + if (!plugin->afterExportTriggers()) + return false; + + if (!plugin->beforeExportViews()) + return false; + + if (!exportDatabaseObjects(dbObjects, ExportManager::ExportObject::VIEW)) + return false; + + if (!plugin->afterExportViews()) + return false; + + if (!plugin->afterExportDatabase()) + return false; + + if (!plugin->afterExport()) + return false; + + return true; +} + +bool ExportWorker::exportDatabaseObjects(const QList<ExportManager::ExportObjectPtr>& dbObjects, ExportManager::ExportObject::Type type) +{ + SqliteQueryPtr parsedQuery; + bool res = true; + for (const ExportManager::ExportObjectPtr& obj : dbObjects) + { + if (obj->type != type) + continue; + + res = parser->parse(obj->ddl); + if (!res || parser->getQueries().size() < 1) + { + qCritical() << "Could not parse" << obj->name << ", the DDL was:" << obj->ddl << ", error is:" << parser->getErrorString(); + notifyWarn(tr("Could not parse %1 in order to export it. It will be excluded from the export output.").arg(obj->name)); + continue; + } + parsedQuery = parser->getQueries().first(); + + switch (obj->type) + { + case ExportManager::ExportObject::TABLE: + res = exportTableInternal(obj->database, obj->name, obj->ddl, parsedQuery, obj->data, obj->providerData); + break; + case ExportManager::ExportObject::INDEX: + res = plugin->exportIndex(obj->database, obj->name, obj->ddl, parsedQuery.dynamicCast<SqliteCreateIndex>()); + break; + case ExportManager::ExportObject::TRIGGER: + res = plugin->exportTrigger(obj->database, obj->name, obj->ddl, parsedQuery.dynamicCast<SqliteCreateTrigger>()); + break; + case ExportManager::ExportObject::VIEW: + res = plugin->exportView(obj->database, obj->name, obj->ddl, parsedQuery.dynamicCast<SqliteCreateView>()); + break; + default: + qDebug() << "Unhandled ExportObject type:" << obj->type; + break; + } + + if (!res) + return false; + + if (isInterrupted()) + return false; + } + return true; +} + +bool ExportWorker::exportTable() +{ + SqlQueryPtr results; + QString errorMessage; + QHash<ExportManager::ExportProviderFlag,QVariant> providerData; + queryTableDataToExport(db, table, results, providerData, &errorMessage); + if (!errorMessage.isNull()) + { + notifyError(errorMessage); + return false; + } + + SchemaResolver resolver(db); + QString ddl = resolver.getObjectDdl(database, table, SchemaResolver::TABLE); + + if (!parser->parse(ddl) || parser->getQueries().size() < 1) + { + qCritical() << "Could not parse" << table << ", the DDL was:" << ddl << ", error is:" << parser->getErrorString(); + notifyWarn(tr("Could not parse %1 in order to export it. It will be excluded from the export output.").arg(table)); + return false; + } + + SqliteQueryPtr createTable = parser->getQueries().first(); + if (!plugin->initBeforeExport(db, output, *config)) + return false; + + if (!exportTableInternal(database, table, ddl, createTable, results, providerData)) + return false; + + if (config->exportTableIndexes) + { + if (!plugin->beforeExportIndexes()) + return false; + + QList<SqliteCreateIndexPtr> parsedIndexesForTable = resolver.getParsedIndexesForTable(database, table); + for (const SqliteCreateIndexPtr& idx : parsedIndexesForTable) + { + if (!plugin->exportIndex(database, idx->index, idx->detokenize(), idx)) + return false; + } + + if (!plugin->afterExportIndexes()) + return false; + } + + if (config->exportTableTriggers) + { + if (!plugin->beforeExportTriggers()) + return false; + + QList<SqliteCreateTriggerPtr> parsedTriggersForTable = resolver.getParsedTriggersForTable(database, table); + for (const SqliteCreateTriggerPtr& trig : parsedTriggersForTable) + { + if (!plugin->exportTrigger(database, trig->trigger, trig->detokenize(), trig)) + return false; + } + + if (!plugin->afterExportTriggers()) + return false; + } + + if (!plugin->afterExport()) + return false; + + return true; +} + +bool ExportWorker::exportTableInternal(const QString& database, const QString& table, const QString& ddl, SqliteQueryPtr parsedDdl, SqlQueryPtr results, + const QHash<ExportManager::ExportProviderFlag,QVariant>& providerData) +{ + SqliteCreateTablePtr createTable = parsedDdl.dynamicCast<SqliteCreateTable>(); + SqliteCreateVirtualTablePtr createVirtualTable = parsedDdl.dynamicCast<SqliteCreateVirtualTable>(); + + if (createTable) + { + if (!plugin->exportTable(database, table, results->getColumnNames(), ddl, createTable, providerData)) + return false; + } + else + { + if (!plugin->exportVirtualTable(database, table, results->getColumnNames(), ddl, createVirtualTable, providerData)) + return false; + } + + if (isInterrupted()) + return false; + + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + if (!plugin->exportTableRow(row)) + return false; + + if (isInterrupted()) + return false; + } + + if (!plugin->afterExportTable()) + return false; + + return true; +} + +QList<ExportManager::ExportObjectPtr> ExportWorker::collectDbObjects(QString* errorMessage) +{ + SchemaResolver resolver(db); + StrHash<SchemaResolver::ObjectDetails> allDetails = resolver.getAllObjectDetails(); + + QList<ExportManager::ExportObjectPtr> objectsToExport; + ExportManager::ExportObjectPtr exportObj; + SchemaResolver::ObjectDetails details; + for (const QString& objName : objectListToExport) + { + if (!allDetails.contains(objName)) + { + qWarning() << "Object requested for export, but not on list of db object details:" << objName; + continue; + } + details = allDetails[objName]; + + exportObj = ExportManager::ExportObjectPtr::create(); + if (details.type == SchemaResolver::TABLE) + { + exportObj->type = ExportManager::ExportObject::TABLE; + queryTableDataToExport(db, objName, exportObj->data, exportObj->providerData, errorMessage); + if (!errorMessage->isNull()) + return objectsToExport; + } + else if (details.type == SchemaResolver::INDEX) + exportObj->type = ExportManager::ExportObject::INDEX; + else if (details.type == SchemaResolver::TRIGGER) + exportObj->type = ExportManager::ExportObject::TRIGGER; + else if (details.type == SchemaResolver::VIEW) + exportObj->type = ExportManager::ExportObject::VIEW; + else + continue; // warning about this case is already in SchemaResolver + + exportObj->name = objName; + exportObj->ddl = details.ddl; + objectsToExport << exportObj; + } + + qSort(objectsToExport.begin(), objectsToExport.end(), [](ExportManager::ExportObjectPtr obj1, ExportManager::ExportObjectPtr obj2) -> bool + { + return (obj1->type < obj2->type); + }); + + return objectsToExport; +} + +void ExportWorker::queryTableDataToExport(Db* db, const QString& table, SqlQueryPtr& dataPtr, QHash<ExportManager::ExportProviderFlag,QVariant>& providerData, + QString* errorMessage) const +{ + static const QString sql = QStringLiteral("SELECT * FROM %1"); + static const QString countSql = QStringLiteral("SELECT count(*) FROM %1"); + static const QString colLengthSql = QStringLiteral("SELECT %1 FROM %2"); + static const QString colLengthTpl = QStringLiteral("max(length(%1))"); + + if (config->exportData) + { + QString wrappedTable = wrapObjIfNeeded(table, db->getDialect()); + + dataPtr = db->exec(sql.arg(wrappedTable)); + if (dataPtr->isError()) + *errorMessage = tr("Error while reading data to export from table %1: %2").arg(table, dataPtr->getErrorText()); + + if (plugin->getProviderFlags().testFlag(ExportManager::ROW_COUNT)) + { + SqlQueryPtr countQuery = db->exec(countSql.arg(wrappedTable)); + if (countQuery->isError()) + { + if (errorMessage->isNull()) + *errorMessage = tr("Error while counting data to export from table %1: %2").arg(table, dataPtr->getErrorText()); + } + else + providerData[ExportManager::ROW_COUNT] = countQuery->getSingleCell().toInt(); + } + + if (plugin->getProviderFlags().testFlag(ExportManager::DATA_LENGTHS)) + { + QStringList wrappedCols; + for (const QString& col : dataPtr->getColumnNames()) + wrappedCols << colLengthTpl.arg(wrapObjIfNeeded(col, db->getDialect())); + + SqlQueryPtr colLengthQuery = db->exec(colLengthSql.arg(wrappedCols.join(", "), wrappedTable)); + if (colLengthQuery->isError()) + { + if (errorMessage->isNull()) + *errorMessage = tr("Error while counting data column width to export from table %1: %2").arg(table, dataPtr->getErrorText()); + } + else + { + QList<int> colWidths; + for (const QVariant& value : colLengthQuery->next()->valueList()) + colWidths << value.toInt(); + + providerData[ExportManager::DATA_LENGTHS] = QVariant::fromValue(colWidths); + } + } + } +} + +bool ExportWorker::isInterrupted() +{ + QMutexLocker locker(&interruptMutex); + return interrupted; +} + diff --git a/SQLiteStudio3/coreSQLiteStudio/exportworker.h b/SQLiteStudio3/coreSQLiteStudio/exportworker.h new file mode 100644 index 0000000..27620a4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/exportworker.h @@ -0,0 +1,60 @@ +#ifndef EXPORTWORKER_H +#define EXPORTWORKER_H + +#include "services/exportmanager.h" +#include "db/queryexecutor.h" +#include "parser/ast/sqlitecreatetable.h" +#include <QObject> +#include <QRunnable> +#include <QMutex> + +class Db; + +class API_EXPORT ExportWorker : public QObject, public QRunnable +{ + Q_OBJECT + public: + ExportWorker(ExportPlugin* plugin, ExportManager::StandardExportConfig* config, QIODevice* output, QObject *parent = 0); + ~ExportWorker(); + + void run(); + void prepareExportQueryResults(Db* db, const QString& query); + void prepareExportDatabase(Db* db, const QStringList& objectListToExport); + void prepareExportTable(Db* db, const QString& database, const QString& table); + + private: + void prepareParser(); + bool exportQueryResults(); + QHash<ExportManager::ExportProviderFlag, QVariant> getProviderDataForQueryResults(); + bool exportDatabase(); + bool exportDatabaseObjects(const QList<ExportManager::ExportObjectPtr>& dbObjects, ExportManager::ExportObject::Type type); + bool exportTable(); + bool exportTableInternal(const QString& database, const QString& table, const QString& ddl, SqliteQueryPtr parsedDdl, SqlQueryPtr results, + const QHash<ExportManager::ExportProviderFlag, QVariant>& providerData); + QList<ExportManager::ExportObjectPtr> collectDbObjects(QString* errorMessage); + void queryTableDataToExport(Db* db, const QString& table, SqlQueryPtr& dataPtr, QHash<ExportManager::ExportProviderFlag, QVariant>& providerData, + QString* errorMessage) const; + bool isInterrupted(); + + ExportPlugin* plugin = nullptr; + ExportManager::StandardExportConfig* config = nullptr; + QIODevice* output = nullptr; + ExportManager::ExportMode exportMode = ExportManager::UNDEFINED; + Db* db = nullptr; + QString query; + QString database; + QString table; + QStringList objectListToExport; + QueryExecutor* executor = nullptr; + bool interrupted = false; + QMutex interruptMutex; + Parser* parser = nullptr; + + public slots: + void interrupt(); + + signals: + void finished(bool result, QIODevice* output); +}; + +#endif // EXPORTWORKER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/impl/dbattacherimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/impl/dbattacherimpl.cpp new file mode 100644 index 0000000..75d94eb --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/impl/dbattacherimpl.cpp @@ -0,0 +1,178 @@ +#include "dbattacherimpl.h" +#include "db/db.h" +#include "services/dbmanager.h" +#include "parser/parser.h" +#include "services/notifymanager.h" +#include "common/utils_sql.h" +#include <QDebug> + +DbAttacherImpl::DbAttacherImpl(Db* db) +{ + this->db = db; + dialect = db->getDialect(); +} + +bool DbAttacherImpl::attachDatabases(const QString& query) +{ + Parser parser(dialect); + if (!parser.parse(query)) + return false; + + queries = parser.getQueries(); + return attachDatabases(); +} + +bool DbAttacherImpl::attachDatabases(const QList<SqliteQueryPtr>& queries) +{ + this->queries = queries; + return attachDatabases(); +} + +bool DbAttacherImpl::attachDatabases(SqliteQueryPtr query) +{ + queries.clear(); + queries << query; + return attachDatabases(); +} + +void DbAttacherImpl::detachDatabases() +{ + detachAttached(); +} + +bool DbAttacherImpl::attachDatabases() +{ + dbNameToAttach.clear(); + + prepareNameToDbMap(); + + TokenList dbTokens = getDbTokens(); + QHash<QString,TokenList> groupedDbTokens = groupDbTokens(dbTokens); + + if (!attachAllDbs(groupedDbTokens)) + return false; + + QHash<TokenPtr,TokenPtr> tokenMapping = getTokenMapping(dbTokens); + replaceTokensInQueries(tokenMapping); + + return true; +} + +TokenList DbAttacherImpl::getDbTokens() +{ + TokenList dbTokens; + foreach (SqliteQueryPtr query, queries) + dbTokens += query->getContextDatabaseTokens(); + + return dbTokens; +} + +void DbAttacherImpl::detachAttached() +{ + foreach (const QString& dbName, dbNameToAttach.leftValues()) + db->detach(nameToDbMap[dbName]); + + dbNameToAttach.clear(); + nameToDbMap.clear(); +} + +void DbAttacherImpl::prepareNameToDbMap() +{ + foreach (Db* db, DBLIST->getValidDbList()) + nameToDbMap[db->getName()] = db; +} + +QHash<QString, TokenList> DbAttacherImpl::groupDbTokens(const TokenList& dbTokens) +{ + // Filter out tokens of unknown databases and group results by name + QHash<QString,TokenList> groupedDbTokens; + QString strippedName; + foreach (TokenPtr token, dbTokens) + { + strippedName = stripObjName(token->value, dialect); + if (!nameToDbMap.contains(strippedName, Qt::CaseInsensitive)) + continue; + + groupedDbTokens[strippedName] += token; + } + return groupedDbTokens; +} + +bool DbAttacherImpl::attachAllDbs(const QHash<QString, TokenList>& groupedDbTokens) +{ + QString attachName; + foreach (const QString& dbName, groupedDbTokens.keys()) + { + attachName = db->attach(nameToDbMap[dbName]); + if (attachName.isNull()) + { + notifyError(QObject::tr("Could not attach database %1: %2").arg(dbName).arg(db->getErrorText())); + detachAttached(); + return false; + } + + dbNameToAttach.insert(dbName, attachName); + } + return true; +} + +QHash<TokenPtr, TokenPtr> DbAttacherImpl::getTokenMapping(const TokenList& dbTokens) +{ + QHash<TokenPtr, TokenPtr> tokenMapping; + QString strippedName; + TokenPtr dstToken; + foreach (TokenPtr srcToken, dbTokens) + { + strippedName = stripObjName(srcToken->value, dialect); + if (strippedName.compare("main", Qt::CaseInsensitive) == 0 || + strippedName.compare("temp", Qt::CaseInsensitive) == 0) + continue; + + if (!dbNameToAttach.containsLeft(strippedName, Qt::CaseInsensitive)) + { + qCritical() << "DB token to be replaced, but it's not on nameToAlias map! Token value:" << strippedName + << "and nameToAlias map has keys:" << dbNameToAttach.leftValues(); + continue; + } + dstToken = TokenPtr::create(dbNameToAttach.valueByLeft(strippedName, Qt::CaseInsensitive)); + tokenMapping[srcToken] = dstToken; + } + return tokenMapping; +} + +void DbAttacherImpl::replaceTokensInQueries(const QHash<TokenPtr, TokenPtr>& tokenMapping) +{ + int idx; + QHashIterator<TokenPtr,TokenPtr> it(tokenMapping); + while (it.hasNext()) + { + it.next(); + foreach (SqliteQueryPtr query, queries) + { + idx = query->tokens.indexOf(it.key()); + if (idx < 0) + continue; // token not in this query, most likely in other query + + query->tokens.replace(idx, it.value()); + } + } +} + +BiStrHash DbAttacherImpl::getDbNameToAttach() const +{ + return dbNameToAttach; +} + +QString DbAttacherImpl::getQuery() const +{ + QStringList queryStrings; + foreach (SqliteQueryPtr query, queries) + queryStrings << query->detokenize(); + + return queryStrings.join(";"); +} + +DbAttacher* DbAttacherDefaultFactory::create(Db* db) +{ + return new DbAttacherImpl(db); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/impl/dbattacherimpl.h b/SQLiteStudio3/coreSQLiteStudio/impl/dbattacherimpl.h new file mode 100644 index 0000000..49307c6 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/impl/dbattacherimpl.h @@ -0,0 +1,94 @@ +#ifndef DBATTACHERIMPL_H +#define DBATTACHERIMPL_H + +#include "dbattacher.h" + +class DbAttacherImpl : public DbAttacher +{ + public: + /** + * @brief Creates attacher with the given database as the main. + * @param db Database that the query will be executed on. + */ + DbAttacherImpl(Db* db); + + bool attachDatabases(const QString& query); + bool attachDatabases(const QList<SqliteQueryPtr>& queries); + bool attachDatabases(SqliteQueryPtr query); + void detachDatabases(); + BiStrHash getDbNameToAttach() const; + QString getQuery() const; + + private: + /** + * @brief Does the actual job, after having all input queries as parsed objects. + * @return true on success, false on failure. + */ + bool attachDatabases(); + + /** + * @brief Finds tokens representing databases in the query. + * @return List of tokens. Some tokens have non-printable value (spaces, etc), others are database names. + */ + TokenList getDbTokens(); + + /** + * @brief Detaches all databases currently attached by the attacher. + * + * Also clears names mappings. + */ + void detachAttached(); + + /** + * @brief Generates mapping of database name to its Db object for all registered databases. + */ + void prepareNameToDbMap(); + + /** + * @brief Groups tokens by the name of database they refer to. + * @param dbTokens Tokens representing databases in the query. + * + * This method is used to learn if some database is used more than once in the query, + * so we attach it only once, then replace all tokens referring to it by the attach name. + */ + QHash<QString,TokenList> groupDbTokens(const TokenList& dbTokens); + + /** + * @brief Tries to attach all required databases. + * @param groupedDbTokens Database tokens grouped by database name, as returned from groupDbTokens(). + * @return true on success, false on any problem. + * + * Major problem that can happen is when "<tt>ATTACH 'path to file'</tt>" fails for any reason. In that case + * detachAttached() is called and false is returned. + */ + bool attachAllDbs(const QHash<QString,TokenList>& groupedDbTokens); + + /** + * @brief Creates token-to-token replace map to update the query. + * @param dbTokens Tokens representing databases in the query. + * @return Mapping to be used when replacing tokens in the query. + */ + QHash<TokenPtr, TokenPtr> getTokenMapping(const TokenList& dbTokens); + + /** + * @brief Replaces tokens in the query. + * @param tokenMapping Map of tokens to replace. + * + * Replacing takes place in token lists of each query in the queries member. + */ + void replaceTokensInQueries(const QHash<TokenPtr, TokenPtr>& tokenMapping); + + QList<SqliteQueryPtr> queries; + Db* db = nullptr; + Dialect dialect; + BiStrHash dbNameToAttach; + StrHash<Db*> nameToDbMap; +}; + +class DbAttacherDefaultFactory : public DbAttacherFactory +{ + public: + DbAttacher* create(Db* db); +}; + +#endif // DBATTACHERIMPL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/importworker.cpp b/SQLiteStudio3/coreSQLiteStudio/importworker.cpp new file mode 100644 index 0000000..af4a66d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/importworker.cpp @@ -0,0 +1,171 @@ +#include "importworker.h" +#include "schemaresolver.h" +#include "services/notifymanager.h" +#include "db/db.h" +#include "plugins/importplugin.h" +#include "common/utils.h" + +ImportWorker::ImportWorker(ImportPlugin* plugin, ImportManager::StandardImportConfig* config, Db* db, const QString& table, QObject *parent) : + QObject(parent), plugin(plugin), config(config), db(db), table(table) +{ +} + +void ImportWorker::run() +{ + if (!plugin->beforeImport(*config)) + { + emit finished(false); + return; + } + + readPluginColumns(); + if (columnsFromPlugin.size() == 0) + { + error(tr("No columns provided by the import plugin.")); + return; + } + + if (!db->begin()) + { + error(tr("Could not start transaction in order to import a data: %1").arg(db->getErrorText())); + return; + } + + if (!prepareTable()) + { + db->rollback(); + return; + } + + if (!importData()) + { + db->rollback(); + return; + } + + if (!db->commit()) + { + error(tr("Could not commit transaction for imported data: %1").arg(db->getErrorText())); + db->rollback(); + return; + } + + if (tableCreated) + emit createdTable(db, table); + + emit finished(true); +} + +void ImportWorker::interrupt() +{ + QMutexLocker locker(&interruptMutex); + interrupted = true; +} + +void ImportWorker::readPluginColumns() +{ + QList<ImportPlugin::ColumnDefinition> pluginColumnDefinitions = plugin->getColumns(); + for (const ImportPlugin::ColumnDefinition& colDef : pluginColumnDefinitions) + { + columnsFromPlugin << colDef.first; + columnTypesFromPlugin << colDef.second; + } +} + +void ImportWorker::error(const QString& err) +{ + notifyError(err); + plugin->afterImport(); + emit finished(false); +} + +bool ImportWorker::prepareTable() +{ + QStringList finalColumns; + Dialect dialect = db->getDialect(); + + SchemaResolver resolver(db); + tableColumns = resolver.getTableColumns(table); + if (tableColumns.size() > 0) + { + if (tableColumns.size() < columnsFromPlugin.size()) + { + notifyWarn(tr("Table '%1' has less columns than there are columns in the data to be imported. Excessive data columns will be ignored.").arg(table)); + finalColumns = tableColumns; + } + else if (tableColumns.size() > columnsFromPlugin.size()) + { + notifyInfo(tr("Table '%1' has more columns than there are columns in the data to be imported. Some columns in the table will be left empty.").arg(table)); + finalColumns = tableColumns.mid(0, columnsFromPlugin.size()); + } + else + { + finalColumns = tableColumns; + } + } + else + { + QStringList colDefs; + for (int i = 0; i < columnsFromPlugin.size(); i++) + colDefs << (columnsFromPlugin[i] + " " + columnTypesFromPlugin[i]).trimmed(); + + static const QString ddl = QStringLiteral("CREATE TABLE %1 (%2)"); + SqlQueryPtr result = db->exec(ddl.arg(wrapObjIfNeeded(table, dialect), colDefs.join(", "))); + if (result->isError()) + { + error(tr("Could not create table to import to: %1").arg(result->getErrorText())); + return false; + } + finalColumns = columnsFromPlugin; + tableCreated = true; + } + + if (isInterrupted()) + { + error(tr("Error while importing data: %1").arg(tr("Interrupted.", "import process status update"))); + return false; + } + + targetColumns = wrapObjNamesIfNeeded(finalColumns, dialect); + return true; +} + +bool ImportWorker::importData() +{ + static const QString insertTemplate = QStringLiteral("INSERT INTO %1 VALUES (%2)"); + + int colCount = targetColumns.size(); + QStringList valList; + for (int i = 0; i < colCount; i++) + valList << "?"; + + QString theInsert = insertTemplate.arg(wrapObjIfNeeded(table, db->getDialect()), valList.join(", ")); + SqlQueryPtr query = db->prepare(theInsert); + + int rowCnt = 0; + QList<QVariant> row; + while ((row = plugin->next()).size() > 0) + { + query->setArgs(row.mid(0, colCount)); + if (!query->execute()) + { + error(tr("Error while importing data: %1").arg(query->getErrorText())); + return false; + } + + if ((rowCnt % 100) == 0 && isInterrupted()) + { + error(tr("Error while importing data: %1").arg(tr("Interrupted.", "import process status update"))); + return false; + } + rowCnt++; + } + + return true; +} + +bool ImportWorker::isInterrupted() +{ + QMutexLocker locker(&interruptMutex); + return interrupted; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/importworker.h b/SQLiteStudio3/coreSQLiteStudio/importworker.h new file mode 100644 index 0000000..562ceea --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/importworker.h @@ -0,0 +1,44 @@ +#ifndef IMPORTWORKER_H +#define IMPORTWORKER_H + +#include "services/importmanager.h" +#include <QObject> +#include <QRunnable> +#include <QMutex> + +class ImportWorker : public QObject, public QRunnable +{ + Q_OBJECT + public: + ImportWorker(ImportPlugin* plugin, ImportManager::StandardImportConfig* config, Db* db, const QString& table, QObject *parent = 0); + + void run(); + + private: + void readPluginColumns(); + void error(const QString& err); + bool prepareTable(); + bool importData(); + bool isInterrupted(); + + ImportPlugin* plugin = nullptr; + ImportManager::StandardImportConfig* config = nullptr; + Db* db = nullptr; + QString table; + QStringList columnsFromPlugin; + QStringList columnTypesFromPlugin; + QStringList tableColumns; + QStringList targetColumns; + bool interrupted = false; + QMutex interruptMutex; + bool tableCreated = false; + + public slots: + void interrupt(); + + signals: + void createdTable(Db* db, const QString& table); + void finished(bool result); +}; + +#endif // IMPORTWORKER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/interruptable.h b/SQLiteStudio3/coreSQLiteStudio/interruptable.h new file mode 100644 index 0000000..6a64855 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/interruptable.h @@ -0,0 +1,23 @@ +#ifndef INTERRUPTABLE_H +#define INTERRUPTABLE_H + +/** + * @brief The interruptable interface + * + * Represents anything that does some work, that can be interrupted. + */ +class Interruptable +{ + public: + + /** + * @brief Interrupts current execution. + * + * This method makes sense only when execution takes place in thread other, than the one calling this method. + * It interrupts execution - in most cases instantly. + * This method doesn't return until the interrupting is done. + */ + virtual void interrupt() = 0; +}; + +#endif // INTERRUPTABLE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/licenses/diff_match.txt b/SQLiteStudio3/coreSQLiteStudio/licenses/diff_match.txt new file mode 100644 index 0000000..030341a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/licenses/diff_match.txt @@ -0,0 +1,18 @@ +Copyright 2008 Google Inc. All Rights Reserved. +Author: fraser@google.com (Neil Fraser) +Author: mikeslemmer@gmail.com (Mike Slemmer) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Diff Match and Patch +http://code.google.com/p/google-diff-match-patch/ diff --git a/SQLiteStudio3/coreSQLiteStudio/licenses/fugue_icons.txt b/SQLiteStudio3/coreSQLiteStudio/licenses/fugue_icons.txt new file mode 100644 index 0000000..4fc7b75 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/licenses/fugue_icons.txt @@ -0,0 +1,80 @@ +Fugue Icons
+
+(C) 2013 Yusuke Kamiyamane. All rights reserved.
+
+These icons are licensed under a Creative Commons
+Attribution 3.0 License.
+<http://creativecommons.org/licenses/by/3.0/>
+
+If you can't or don't want to provide attribution, please
+purchase a royalty-free license.
+<http://p.yusukekamiyamane.com/>
+
+I'm unavailable for custom icon design work. But your
+suggestions are always welcome!
+<mailto:p@yusukekamiyamane.com>
+
+------------------------------------------------------------
+
+All logos and trademarks in some icons are property of their
+respective owners.
+
+------------------------------------------------------------
+
+- geotag
+
+ (C) Geotag Icon Project. All rights reserved.
+ <http://www.geotagicons.com/>
+
+ Geotag icon is licensed under a Creative Commons
+ Attribution-Share Alike 3.0 License or LGPL.
+ <http://creativecommons.org/licenses/by-sa/3.0/>
+ <http://opensource.org/licenses/lgpl-license.php>
+
+- language
+
+ (C) Language Icon Project. All rights reserved.
+ <http://www.languageicon.org/>
+
+ Language icon is licensed under a Creative Commons
+ Attribution-Share Alike 3.0 License.
+ <http://creativecommons.org/licenses/by-sa/3.0/>
+
+- open-share
+
+ (C) Open Share Icon Project. All rights reserved.
+ <http://www.openshareicons.com/>
+
+ Open Share icon is licensed under a Creative Commons
+ Attribution-Share Alike 3.0 License.
+ <http://creativecommons.org/licenses/by-sa/3.0/>
+
+- opml
+
+ (C) OPML Icon Project. All rights reserved.
+ <http://opmlicons.com/>
+
+ OPML icon is licensed under a Creative Commons
+ Attribution-Share Alike 2.5 License.
+ <http://creativecommons.org/licenses/by-sa/2.5/>
+
+- share
+
+ (C) Share Icon Project. All rights reserved.
+ <http://shareicons.com/>
+
+ Share icon is licensed under a GPL or LGPL or BSD or
+ Creative Commons Attribution 2.5 License.
+ <http://opensource.org/licenses/gpl-license.php>
+ <http://opensource.org/licenses/lgpl-license.php>
+ <http://opensource.org/licenses/bsd-license.php>
+ <http://creativecommons.org/licenses/by/2.5/>
+
+- xfn
+
+ (C) Wolfgang Bartelme. All rights reserved.
+ <http://www.bartelme.at/>
+
+ XFN icon is licensed under a Creative Commons
+ Attribution-Share Alike 2.5 License.
+ <http://creativecommons.org/licenses/by-sa/2.5/>
\ No newline at end of file diff --git a/SQLiteStudio3/coreSQLiteStudio/licenses/gpl.txt b/SQLiteStudio3/coreSQLiteStudio/licenses/gpl.txt new file mode 100644 index 0000000..10926e8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/licenses/gpl.txt @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. + diff --git a/SQLiteStudio3/coreSQLiteStudio/licenses/lgpl.txt b/SQLiteStudio3/coreSQLiteStudio/licenses/lgpl.txt new file mode 100644 index 0000000..a0a60f5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/licenses/lgpl.txt @@ -0,0 +1,502 @@ +GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +0the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/SQLiteStudio3/coreSQLiteStudio/licenses/sqlitestudio_license.txt b/SQLiteStudio3/coreSQLiteStudio/licenses/sqlitestudio_license.txt new file mode 100644 index 0000000..cc41c68 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/licenses/sqlitestudio_license.txt @@ -0,0 +1,15 @@ +SQLiteStudio, a cross-platform SQLite manager (http://sqlitestudio.pl). +Copyright (C) 2014 SalSoft (http://salsoft.com.pl) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. diff --git a/SQLiteStudio3/coreSQLiteStudio/log.cpp b/SQLiteStudio3/coreSQLiteStudio/log.cpp new file mode 100644 index 0000000..d54abdb --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/log.cpp @@ -0,0 +1,47 @@ +#include "log.h" +#include <QTime> +#include <QDebug> + +bool SQL_DEBUG = false; +QString SQL_DEBUG_FILTER = ""; + +void setSqlLoggingEnabled(bool enabled) +{ + SQL_DEBUG = enabled; +} + +void setSqlLoggingFilter(const QString& filter) +{ + SQL_DEBUG_FILTER = filter; +} + +void logSql(Db* db, const QString& str, const QHash<QString,QVariant>& args, Db::Flags flags) +{ + if (!SQL_DEBUG) + return; + + if (!SQL_DEBUG_FILTER.isEmpty() && SQL_DEBUG_FILTER != db->getName()) + return; + + qDebug() << QString("SQL %1> %2").arg(db->getName()).arg(str) << "(flags:" << Db::flagsToString(flags) << ")"; + QHashIterator<QString,QVariant> it(args); + while (it.hasNext()) + { + it.next(); + qDebug() << " SQL arg>" << it.key() << "=" << it.value(); + } +} + +void logSql(Db* db, const QString& str, const QList<QVariant>& args, Db::Flags flags) +{ + if (!SQL_DEBUG) + return; + + if (!SQL_DEBUG_FILTER.isEmpty() && SQL_DEBUG_FILTER != db->getName()) + return; + + qDebug() << QString("SQL %1> %2").arg(db->getName()).arg(str) << "(flags:" << Db::flagsToString(flags) << ")"; + int i = 0; + foreach (const QVariant& arg, args) + qDebug() << " SQL arg>" << i++ << "=" << arg; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/log.h b/SQLiteStudio3/coreSQLiteStudio/log.h new file mode 100644 index 0000000..f866c93 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/log.h @@ -0,0 +1,16 @@ +#ifndef LOG_H +#define LOG_H + +#include "db/db.h" +#include "guiSQLiteStudio_global.h" +#include <QString> +#include <QHash> +#include <QList> +#include <QVariant> + +API_EXPORT void logSql(Db* db, const QString& str, const QHash<QString,QVariant>& args, Db::Flags flags); +API_EXPORT void logSql(Db* db, const QString& str, const QList<QVariant>& args, Db::Flags flags); +API_EXPORT void setSqlLoggingEnabled(bool enabled); +API_EXPORT void setSqlLoggingFilter(const QString& filter); + +#endif // LOG_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitealtertable.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitealtertable.cpp new file mode 100644 index 0000000..6c840e7 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitealtertable.cpp @@ -0,0 +1,128 @@ +#include "sqlitealtertable.h" +#include "sqlitequerytype.h" +#include "common/global.h" + +SqliteAlterTable::SqliteAlterTable() +{ + queryType = SqliteQueryType::AlterTable; +} + +SqliteAlterTable::SqliteAlterTable(const SqliteAlterTable& other) + : SqliteQuery(other), command(other.command), newName(other.newName), database(other.database), table(other.table), columnKw(other.columnKw) +{ + DEEP_COPY_FIELD(SqliteCreateTable::Column, newColumn); +} + +SqliteAlterTable::SqliteAlterTable(const QString &name1, const QString &name2, const QString &newName) + : SqliteAlterTable() +{ + command = Command::RENAME; + initName(name1, name2); + this->newName = newName; +} + +SqliteAlterTable::SqliteAlterTable(const QString& name1, const QString& name2, bool columnKw, SqliteCreateTable::Column *column) + : SqliteAlterTable() +{ + command = Command::ADD_COLUMN; + initName(name1, name2); + this->columnKw = columnKw; + this->newColumn = column; + if (column) + column->setParent(this); +} + +SqliteAlterTable::~SqliteAlterTable() +{ +// if (newColumn) + // delete newColumn; +} + +SqliteStatement* SqliteAlterTable::clone() +{ + return new SqliteAlterTable(*this); +} + +QStringList SqliteAlterTable::getTablesInStatement() +{ + QStringList list; + if (!table.isNull()) + list << table; + + if (!newName.isNull()) + list << newName; + + return list; +} + +QStringList SqliteAlterTable::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteAlterTable::getTableTokensInStatement() +{ + return getObjectTokenListFromFullname(); +} + +TokenList SqliteAlterTable::getDatabaseTokensInStatement() +{ + return getDbTokenListFromFullname(); +} + +QList<SqliteStatement::FullObject> SqliteAlterTable::getFullObjectsInStatement() +{ + QList<FullObject> result; + + FullObject fullObj = getFullObjectFromFullname(FullObject::TABLE); + if (fullObj.isValid()) + result << fullObj; + + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + return result; +} + +void SqliteAlterTable::initName(const QString &name1, const QString &name2) +{ + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; +} + +TokenList SqliteAlterTable::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("ALTER").withSpace().withKeyword("TABLE").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table).withSpace(); + + if (newColumn) + { + builder.withKeyword("ADD").withSpace(); + if (columnKw) + builder.withKeyword("COLUMN").withSpace(); + + builder.withStatement(newColumn); + } + else if (!newName.isNull()) + { + builder.withKeyword("RENAME").withSpace().withKeyword("TO").withSpace().withOther(newName, dialect); + } + + builder.withOperator(";"); + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitealtertable.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitealtertable.h new file mode 100644 index 0000000..360db45 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitealtertable.h @@ -0,0 +1,46 @@ +#ifndef SQLITEALTERTABLE_H +#define SQLITEALTERTABLE_H + +#include "sqlitequery.h" +#include "sqlitecreatetable.h" + +class API_EXPORT SqliteAlterTable : public SqliteQuery +{ + public: + enum class Command + { + RENAME, + ADD_COLUMN, + null + }; + + SqliteAlterTable(); + SqliteAlterTable(const SqliteAlterTable& other); + SqliteAlterTable(const QString& name1, const QString& name2, const QString& newName); + SqliteAlterTable(const QString& name1, const QString& name2, bool columnKw, SqliteCreateTable::Column* column); + ~SqliteAlterTable(); + SqliteStatement* clone(); + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + + private: + void initName(const QString& name1, const QString& name2); + + public: + Command command = Command::null; + QString newName = QString::null; + QString database = QString::null; + QString table = QString::null; + bool columnKw = false; + SqliteCreateTable::Column* newColumn = nullptr; +}; + +typedef QSharedPointer<SqliteAlterTable> SqliteAlterTablePtr; + +#endif // SQLITEALTERTABLE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteanalyze.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteanalyze.cpp new file mode 100644 index 0000000..d4e5778 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteanalyze.cpp @@ -0,0 +1,80 @@ +#include "sqliteanalyze.h" +#include "sqlitequerytype.h" + +#include <parser/statementtokenbuilder.h> + +SqliteAnalyze::SqliteAnalyze() +{ + queryType = SqliteQueryType::Analyze; +} + +SqliteAnalyze::SqliteAnalyze(const SqliteAnalyze& other) : + SqliteQuery(other), database(other.database), table(other.table) +{ +} + +SqliteAnalyze::SqliteAnalyze(const QString &name1, const QString &name2) + : SqliteAnalyze() +{ + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; +} + +SqliteStatement* SqliteAnalyze::clone() +{ + return new SqliteAnalyze(*this); +} + +QStringList SqliteAnalyze::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteAnalyze::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteAnalyze::getTableTokensInStatement() +{ + return getObjectTokenListFromNmDbnm(); +} + +TokenList SqliteAnalyze::getDatabaseTokensInStatement() +{ + return getDbTokenListFromNmDbnm(); +} + +QList<SqliteStatement::FullObject> SqliteAnalyze::getFullObjectsInStatement() +{ + QList<FullObject> result; + + FullObject fullObj = getFullObjectFromNmDbnm(FullObject::TABLE); + if (fullObj.isValid()) + result << fullObj; + + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + result << fullObj; + + return result; +} + + +TokenList SqliteAnalyze::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("ANALYZE").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table).withOperator(";"); + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteanalyze.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteanalyze.h new file mode 100644 index 0000000..194e4c9 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteanalyze.h @@ -0,0 +1,29 @@ +#ifndef SQLITEANALYZE_H +#define SQLITEANALYZE_H + +#include "sqlitequery.h" +#include <QString> + +class API_EXPORT SqliteAnalyze : public SqliteQuery +{ + public: + SqliteAnalyze(); + SqliteAnalyze(const SqliteAnalyze& other); + SqliteAnalyze(const QString& name1, const QString& name2); + SqliteStatement* clone(); + + QString database = QString::null; + QString table = QString::null; + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteAnalyze> SqliteAnalyzePtr; + +#endif // SQLITEANALYZE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteattach.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteattach.cpp new file mode 100644 index 0000000..7d0b8a5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteattach.cpp @@ -0,0 +1,63 @@ +#include "sqliteattach.h" +#include "sqlitequerytype.h" +#include "sqliteexpr.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" + +SqliteAttach::SqliteAttach() +{ + queryType = SqliteQueryType::Attach; +} + +SqliteAttach::SqliteAttach(bool dbKw, SqliteExpr *url, SqliteExpr *name, SqliteExpr *key) + : SqliteAttach() +{ + databaseKw = dbKw; + databaseUrl = url; + this->name = name; + this->key = key; + + if (databaseUrl) + databaseUrl->setParent(this); + + if (name) + name->setParent(this); + + if (key) + key->setParent(this); +} + +SqliteAttach::SqliteAttach(const SqliteAttach& other) : + SqliteQuery(other), databaseKw(other.databaseKw) +{ + DEEP_COPY_FIELD(SqliteExpr, databaseUrl); + DEEP_COPY_FIELD(SqliteExpr, name); + DEEP_COPY_FIELD(SqliteExpr, key); +} + +SqliteAttach::~SqliteAttach() +{ +} + +SqliteStatement* SqliteAttach::clone() +{ + return new SqliteAttach(*this); +} + +TokenList SqliteAttach::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("ATTACH").withSpace(); + + if (databaseKw) + builder.withKeyword("DATABASE").withSpace(); + + builder.withStatement(databaseUrl).withSpace().withKeyword("AS").withSpace().withStatement(name); + if (key) + builder.withSpace().withKeyword("KEY").withSpace().withStatement(key); + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteattach.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteattach.h new file mode 100644 index 0000000..55151a5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteattach.h @@ -0,0 +1,28 @@ +#ifndef SQLITEATTACHDATABASE_H +#define SQLITEATTACHDATABASE_H + +#include "sqlitequery.h" + +class SqliteExpr; + +class API_EXPORT SqliteAttach : public SqliteQuery +{ + public: + SqliteAttach(); + SqliteAttach(const SqliteAttach& other); + SqliteAttach(bool dbKw, SqliteExpr* url, SqliteExpr* name, SqliteExpr* key); + ~SqliteAttach(); + SqliteStatement* clone(); + + bool databaseKw = false; + SqliteExpr* databaseUrl = nullptr; + SqliteExpr* name = nullptr; + SqliteExpr* key = nullptr; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteAttach> SqliteAttachPtr; + +#endif // SQLITEATTACHDATABASE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitebegintrans.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitebegintrans.cpp new file mode 100644 index 0000000..dcd9740 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitebegintrans.cpp @@ -0,0 +1,71 @@ +#include "sqlitebegintrans.h" +#include "sqlitequerytype.h" + +#include <parser/statementtokenbuilder.h> + +SqliteBeginTrans::SqliteBeginTrans() +{ + queryType = SqliteQueryType::BeginTrans; +} + +SqliteBeginTrans::SqliteBeginTrans(const SqliteBeginTrans& other) : + SqliteQuery(other), onConflict(other.onConflict), name(other.name), transactionKw(other.transactionKw), type(other.type) +{ +} + +SqliteBeginTrans::SqliteBeginTrans(SqliteBeginTrans::Type type, bool transactionKw, const QString& name) + : SqliteBeginTrans() +{ + this->type = type; + this->transactionKw = transactionKw; + this->name = name; +} + +SqliteBeginTrans::SqliteBeginTrans(bool transactionKw, const QString &name, SqliteConflictAlgo onConflict) +{ + this->onConflict = onConflict; + this->transactionKw = transactionKw; + this->name = name; +} + +SqliteStatement*SqliteBeginTrans::clone() +{ + return new SqliteBeginTrans(*this); +} + +QString SqliteBeginTrans::typeToString(SqliteBeginTrans::Type type) +{ + switch (type) + { + case Type::null: + return QString(); + case Type::DEFERRED: + return "DEFERRED"; + case Type::IMMEDIATE: + return "IMMEDIATE"; + case Type::EXCLUSIVE: + return "EXCLUSIVE"; + } + return QString(); +} + +TokenList SqliteBeginTrans::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("BEGIN"); + + if (type != Type::null) + builder.withSpace().withKeyword(typeToString(type)); + + if (transactionKw) + { + builder.withSpace().withKeyword("TRANSACTION"); + if (!name.isNull()) + builder.withSpace().withOther(name, dialect); + } + + builder.withConflict(onConflict).withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitebegintrans.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitebegintrans.h new file mode 100644 index 0000000..48f5b37 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitebegintrans.h @@ -0,0 +1,38 @@ +#ifndef SQLITEBEGINTRANS_H +#define SQLITEBEGINTRANS_H + +#include "sqlitequery.h" +#include "sqliteconflictalgo.h" +#include <QString> + +class API_EXPORT SqliteBeginTrans : public SqliteQuery +{ + public: + enum class Type + { + null, + DEFERRED, + IMMEDIATE, + EXCLUSIVE + }; + + SqliteBeginTrans(); + SqliteBeginTrans(const SqliteBeginTrans& other); + SqliteBeginTrans(Type type, bool transactionKw, const QString& name); + SqliteBeginTrans(bool transactionKw, const QString& name, SqliteConflictAlgo onConflict); + SqliteStatement* clone(); + + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; // sqlite2 only + QString name; // in docs sqlite2 only, but in gramma it's also sqlite3 + bool transactionKw = false; + Type type = Type::null; // sqlite3 only + + static QString typeToString(Type type); + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteBeginTrans> SqliteBeginTransPtr; + +#endif // SQLITEBEGINTRANS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecolumntype.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecolumntype.cpp new file mode 100644 index 0000000..cc773bb --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecolumntype.cpp @@ -0,0 +1,88 @@ +#include "sqlitecolumntype.h" +#include "parser/statementtokenbuilder.h" +#include "common/utils_sql.h" + +SqliteColumnType::SqliteColumnType() +{ +} + +SqliteColumnType::SqliteColumnType(const SqliteColumnType& other) : + SqliteStatement(other), name(other.name), scale(other.scale), precision(other.precision) +{ +} + +SqliteColumnType::SqliteColumnType(const QString &name) +{ + this->name = name; +} + +SqliteColumnType::SqliteColumnType(const QString &name, const QVariant& scale) +{ + this->name = name; + this->scale = scale; +} + +SqliteColumnType::SqliteColumnType(const QString &name, const QVariant& scale, const QVariant& precision) +{ + this->name = name; + this->precision = precision; + this->scale = scale; +} + +SqliteStatement* SqliteColumnType::clone() +{ + return new SqliteColumnType(*this); +} + +bool SqliteColumnType::isPrecisionDouble() +{ + return !precision.isNull() && precision.toString().indexOf(".") > -1; +} + +bool SqliteColumnType::isScaleDouble() +{ + return !scale.isNull() && scale.toString().indexOf(".") > -1; +} + +TokenList SqliteColumnType::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + if (name.isEmpty()) + return TokenList(); + + builder.withOther(name); + + if (!scale.isNull()) + { + builder.withSpace().withParLeft(); + if (scale.userType() == QVariant::Int) + builder.withInteger(scale.toInt()); + else if (scale.userType() == QVariant::LongLong) + builder.withInteger(scale.toLongLong()); + else if (scale.userType() == QVariant::Double) + builder.withFloat(scale.toDouble()); + else + builder.withOther(scale.toString()); + + if (!precision.isNull()) + { + builder.withOperator(",").withSpace(); + if (precision.userType() == QVariant::Int) + builder.withInteger(precision.toInt()); + else if (precision.userType() == QVariant::LongLong) + builder.withInteger(precision.toLongLong()); + else if (precision.userType() == QVariant::Double) + builder.withFloat(precision.toDouble()); + else + builder.withOther(precision.toString()); + } + builder.withParRight(); + } + + return builder.build(); +} + +DataType SqliteColumnType::toDataType() const +{ + return DataType(name, scale, precision); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecolumntype.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecolumntype.h new file mode 100644 index 0000000..fc87b6b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecolumntype.h @@ -0,0 +1,30 @@ +#ifndef SQLITECOLUMNTYPE_H +#define SQLITECOLUMNTYPE_H + +#include "sqlitestatement.h" +#include "datatype.h" +#include <QVariant> + +class API_EXPORT SqliteColumnType : public SqliteStatement +{ + public: + SqliteColumnType(); + SqliteColumnType(const SqliteColumnType& other); + explicit SqliteColumnType(const QString& name); + SqliteColumnType(const QString& name, const QVariant &scale); + SqliteColumnType(const QString& name, const QVariant &scale, const QVariant &precision); + SqliteStatement* clone(); + + bool isPrecisionDouble(); + bool isScaleDouble(); + TokenList rebuildTokensFromContents(); + DataType toDataType() const; + + QString name = QString::null; + QVariant scale = QVariant(); // first size number + QVariant precision = QVariant(); // second size number +}; + +typedef QSharedPointer<SqliteColumnType> SqliteColumnTypePtr; + +#endif // SQLITECOLUMNTYPE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecommittrans.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecommittrans.cpp new file mode 100644 index 0000000..97be8a9 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecommittrans.cpp @@ -0,0 +1,48 @@ +#include "sqlitecommittrans.h" +#include "sqlitequerytype.h" + +#include <parser/statementtokenbuilder.h> + +SqliteCommitTrans::SqliteCommitTrans() +{ + queryType = SqliteQueryType::CommitTrans; +} + +SqliteCommitTrans::SqliteCommitTrans(bool transactionKw, const QString& name, bool endKw) + : SqliteCommitTrans() +{ + this->endKw = endKw; + this->transactionKw = transactionKw; + this->name = name; +} + +SqliteCommitTrans::SqliteCommitTrans(const SqliteCommitTrans& other) : + SqliteQuery(other), endKw(other.endKw), name(other.name), transactionKw(other.transactionKw) +{ +} + +SqliteStatement* SqliteCommitTrans::clone() +{ + return new SqliteCommitTrans(*this); +} + +TokenList SqliteCommitTrans::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + if (endKw) + builder.withKeyword("END"); + else + builder.withKeyword("COMMIT"); + + if (transactionKw) + { + builder.withSpace().withKeyword("TRANSACTION"); + if (!name.isNull()) + builder.withSpace().withOther(name, dialect); + } + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecommittrans.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecommittrans.h new file mode 100644 index 0000000..ec418a6 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecommittrans.h @@ -0,0 +1,25 @@ +#ifndef SQLITECOMMITTRANS_H +#define SQLITECOMMITTRANS_H + +#include "sqlitequery.h" +#include <QString> + +class API_EXPORT SqliteCommitTrans : public SqliteQuery +{ + public: + SqliteCommitTrans(); + SqliteCommitTrans(bool transactionKw, const QString &name, bool endKw); + SqliteCommitTrans(const SqliteCommitTrans& other); + SqliteStatement* clone(); + + bool endKw = false; + QString name = QString::null; + bool transactionKw = false; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteCommitTrans> SqliteCommitTransPtr; + +#endif // SQLITECOMMITTRANS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteconflictalgo.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteconflictalgo.cpp new file mode 100644 index 0000000..56fb42d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteconflictalgo.cpp @@ -0,0 +1,37 @@ +#include "sqliteconflictalgo.h" + +SqliteConflictAlgo sqliteConflictAlgo(const QString& value) +{ + QString upper = value.toUpper(); + if (upper == "ROLLBACK") + return SqliteConflictAlgo::ROLLBACK; + else if (upper == "ABORT") + return SqliteConflictAlgo::ABORT; + else if (upper == "FAIL") + return SqliteConflictAlgo::FAIL; + else if (upper == "IGNORE") + return SqliteConflictAlgo::IGNORE; + else if (upper == "REPLACE") + return SqliteConflictAlgo::REPLACE; + else + return SqliteConflictAlgo::null; +} + +QString sqliteConflictAlgo(SqliteConflictAlgo value) +{ + switch (value) + { + case SqliteConflictAlgo::ROLLBACK: + return "ROLLBACK"; + case SqliteConflictAlgo::ABORT: + return "ABORT"; + case SqliteConflictAlgo::FAIL: + return "FAIL"; + case SqliteConflictAlgo::IGNORE: + return "IGNORE"; + case SqliteConflictAlgo::REPLACE: + return "REPLACE"; + default: + return QString::null; + } +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteconflictalgo.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteconflictalgo.h new file mode 100644 index 0000000..754672d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteconflictalgo.h @@ -0,0 +1,20 @@ +#ifndef SQLITECONFLICTALGO_H +#define SQLITECONFLICTALGO_H + +#include "coreSQLiteStudio_global.h" +#include <QString> + +enum class SqliteConflictAlgo +{ + ROLLBACK, + ABORT, + FAIL, + IGNORE, + REPLACE, + null +}; + +API_EXPORT SqliteConflictAlgo sqliteConflictAlgo(const QString& value); +API_EXPORT QString sqliteConflictAlgo(SqliteConflictAlgo value); + +#endif // SQLITECONFLICTALGO_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecopy.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecopy.cpp new file mode 100644 index 0000000..009f836 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecopy.cpp @@ -0,0 +1,92 @@ +#include "sqlitecopy.h" +#include "sqlitequerytype.h" + +#include <parser/statementtokenbuilder.h> + +SqliteCopy::SqliteCopy() +{ + queryType = SqliteQueryType::Copy; +} + +SqliteCopy::SqliteCopy(const SqliteCopy& other) : + SqliteQuery(other), onConflict(other.onConflict), database(other.database), table(other.table), file(other.file), delimiter(other.delimiter) +{ +} + +SqliteCopy::SqliteCopy(SqliteConflictAlgo onConflict, const QString &name1, const QString &name2, const QString &name3, const QString &delim) + : SqliteCopy() +{ + this->onConflict = onConflict; + + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; + + file = name3; + delimiter = delim; +} + +SqliteStatement* SqliteCopy::clone() +{ + return new SqliteCopy(*this); +} + +QStringList SqliteCopy::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteCopy::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteCopy::getTableTokensInStatement() +{ + return getObjectTokenListFromNmDbnm(); +} + +TokenList SqliteCopy::getDatabaseTokensInStatement() +{ + return getDbTokenListFromNmDbnm(); +} + +QList<SqliteStatement::FullObject> SqliteCopy::getFullObjectsInStatement() +{ + QList<FullObject> result; + + FullObject fullObj = getFullObjectFromNmDbnm(FullObject::TABLE); + if (fullObj.isValid()) + result << fullObj; + + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + result << fullObj; + + return result; +} + +TokenList SqliteCopy::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("COPY").withSpace(); + if (onConflict != SqliteConflictAlgo::null) + builder.withKeyword("OR").withSpace().withKeyword(sqliteConflictAlgo(onConflict)).withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withSpace(); + + builder.withOther(table, dialect).withSpace().withKeyword("FROM").withSpace().withString(file); + + if (!delimiter.isNull()) + builder.withSpace().withKeyword("USING").withSpace().withKeyword("DELIMITERS").withSpace().withString(delimiter); + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecopy.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecopy.h new file mode 100644 index 0000000..ff586df --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecopy.h @@ -0,0 +1,32 @@ +#ifndef SQLITECOPY_H +#define SQLITECOPY_H + +#include "sqlitequery.h" +#include "sqliteconflictalgo.h" + +class API_EXPORT SqliteCopy : public SqliteQuery +{ + public: + SqliteCopy(); + SqliteCopy(const SqliteCopy& other); + SqliteCopy(SqliteConflictAlgo onConflict, const QString& name1, const QString& name2, const QString& name3, const QString& delim = QString::null); + SqliteStatement* clone(); + + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; + QString database = QString::null; + QString table = QString::null; + QString file = QString::null; + QString delimiter = QString::null; + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteCopy> SqliteCopyPtr; + +#endif // SQLITECOPY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateindex.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateindex.cpp new file mode 100644 index 0000000..36f7aa9 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateindex.cpp @@ -0,0 +1,190 @@ +#include "sqlitecreateindex.h" +#include "sqlitequerytype.h" +#include "sqliteindexedcolumn.h" +#include "parser/statementtokenbuilder.h" +#include "parser/ast/sqliteexpr.h" +#include "common/global.h" + +SqliteCreateIndex::SqliteCreateIndex() +{ + queryType = SqliteQueryType::CreateIndex; +} + +SqliteCreateIndex::SqliteCreateIndex(const SqliteCreateIndex& other) : + SqliteQuery(other), uniqueKw(other.uniqueKw), ifNotExistsKw(other.ifNotExistsKw), database(other.database), index(other.index), + table(other.table) +{ + DEEP_COPY_COLLECTION(SqliteIndexedColumn, indexedColumns); +} + +SqliteCreateIndex::SqliteCreateIndex(bool unique, bool ifNotExists, const QString &name1, const QString &name2, const QString &name3, + const QList<SqliteIndexedColumn *> &columns, SqliteConflictAlgo onConflict) + : SqliteCreateIndex() +{ + // Constructor for SQLite 2 + uniqueKw = unique; + ifNotExistsKw = ifNotExists; + + index = name1; + + if (!name3.isNull()) + { + database = name2; + table = name3; + } + else + table = name2; + + this->onConflict = onConflict; + this->indexedColumns = columns; + + foreach (SqliteIndexedColumn* idxCol, columns) + idxCol->setParent(this); +} + +SqliteCreateIndex::SqliteCreateIndex(bool unique, bool ifNotExists, const QString& name1, const QString& name2, const QString& name3, + const QList<SqliteIndexedColumn*>& columns, SqliteExpr* where) + : SqliteCreateIndex() +{ + // Constructor for SQLite 3 + uniqueKw = unique; + ifNotExistsKw = ifNotExists; + + if (!name2.isNull()) + { + database = name1; + index = name2; + } + else + index = name1; + + table = name3; + this->indexedColumns = columns; + + foreach (SqliteIndexedColumn* idxCol, columns) + idxCol->setParent(this); + + this->where = where; +} + +SqliteCreateIndex::~SqliteCreateIndex() +{ +} + +SqliteStatement*SqliteCreateIndex::clone() +{ + return new SqliteCreateIndex(*this); +} + +QString SqliteCreateIndex::getTargetTable() const +{ + return table; +} + +QStringList SqliteCreateIndex::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteCreateIndex::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteCreateIndex::getTableTokensInStatement() +{ + if (dialect == Dialect::Sqlite2) + return getObjectTokenListFromNmDbnm("nm2", "dbnm"); + else + return getTokenListFromNamedKey("nm2"); +} + +TokenList SqliteCreateIndex::getDatabaseTokensInStatement() +{ + if (dialect == Dialect::Sqlite2) + return getDbTokenListFromNmDbnm("nm2", "dbnm"); + else + return getDbTokenListFromNmDbnm(); +} + +QList<SqliteStatement::FullObject> SqliteCreateIndex::getFullObjectsInStatement() +{ + QList<FullObject> result; + + // Table object + FullObject fullObj; + if (dialect == Dialect::Sqlite2) + fullObj = getFullObjectFromNmDbnm(FullObject::TABLE, "nm2", "dbnm"); + else + { + TokenList tableTokens = getTokenListFromNamedKey("nm2"); + if (tableTokens.size() > 0) + fullObj = getFullObject(FullObject::TABLE, TokenPtr(), tableTokens[0]); + } + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + // Index object + if (dialect == Dialect::Sqlite2) + { + TokenList tableTokens = getTokenListFromNamedKey("nm"); + if (tableTokens.size() > 0) + fullObj = getFullObject(FullObject::INDEX, TokenPtr(), tableTokens[0]); + } + else + fullObj = getFullObjectFromNmDbnm(FullObject::INDEX, "nm", "dbnm"); + + return result; +} + +TokenList SqliteCreateIndex::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withKeyword("CREATE").withSpace(); + if (uniqueKw) + builder.withKeyword("UNIQUE").withSpace(); + + builder.withKeyword("INDEX").withSpace(); + + if (ifNotExistsKw) + builder.withKeyword("IF").withSpace().withKeyword("NOT").withSpace().withKeyword("EXISTS").withSpace(); + + if (dialect == Dialect::Sqlite2) + { + builder.withOther(index, dialect).withSpace().withKeyword("ON").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table, dialect).withSpace(); + builder.withParLeft().withStatementList(indexedColumns).withParRight(); + + + if (onConflict != SqliteConflictAlgo::null) + builder.withSpace().withKeyword(sqliteConflictAlgo(onConflict)); + } + else + { + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(index, dialect).withSpace().withKeyword("ON").withSpace().withOther(table, dialect).withSpace().withParLeft() + .withStatementList(indexedColumns).withParRight(); + } + + if (where) + builder.withSpace().withKeyword("WHERE").withStatement(where); + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateindex.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateindex.h new file mode 100644 index 0000000..9251b18 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateindex.h @@ -0,0 +1,50 @@ +#ifndef SQLITECREATEINDEX_H +#define SQLITECREATEINDEX_H + +#include "sqlitequery.h" +#include "sqlitetablerelatedddl.h" +#include "sqliteconflictalgo.h" +#include "sqliteexpr.h" +#include <QString> +#include <QList> + +class SqliteIndexedColumn; + +class API_EXPORT SqliteCreateIndex : public SqliteQuery, public SqliteTableRelatedDdl +{ + public: + SqliteCreateIndex(); + SqliteCreateIndex(const SqliteCreateIndex& other); + SqliteCreateIndex(bool unique, bool ifNotExists, const QString& name1, const QString& name2, + const QString& name3, const QList<SqliteIndexedColumn*>& columns, + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null); + SqliteCreateIndex(bool unique, bool ifNotExists, const QString& name1, const QString& name2, + const QString& name3, const QList<SqliteIndexedColumn*>& columns, + SqliteExpr* where); + ~SqliteCreateIndex(); + SqliteStatement* clone(); + + QString getTargetTable() const; + + bool uniqueKw = false; + bool ifNotExistsKw = false; + QList<SqliteIndexedColumn*> indexedColumns; + // The database refers to index name in Sqlite3, but in Sqlite2 it refers to the table. + QString database = QString::null; + QString index = QString::null; + QString table = QString::null; + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; + SqliteExpr* where = nullptr; + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteCreateIndex> SqliteCreateIndexPtr; + +#endif // SQLITECREATEINDEX_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.cpp new file mode 100644 index 0000000..e31e512 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.cpp @@ -0,0 +1,771 @@ +#include "sqlitecreatetable.h" +#include "parser/statementtokenbuilder.h" +#include "common/utils_sql.h" +#include "common/global.h" + +SqliteCreateTable::SqliteCreateTable() +{ + queryType = SqliteQueryType::CreateTable; +} + +SqliteCreateTable::SqliteCreateTable(const SqliteCreateTable& other) : + SqliteQuery(other), ifNotExistsKw(other.ifNotExistsKw), tempKw(other.tempKw), temporaryKw(other.temporaryKw), + database(other.database), table(other.table), withOutRowId(other.withOutRowId) +{ + DEEP_COPY_COLLECTION(Column, columns); + DEEP_COPY_COLLECTION(Constraint, constraints); + DEEP_COPY_FIELD(SqliteSelect, select); +} + +SqliteCreateTable::SqliteCreateTable(bool ifNotExistsKw, int temp, const QString &name1, const QString &name2, const QList<Column *> &columns, const QList<Constraint*>& constraints) + : SqliteCreateTable() +{ + init(ifNotExistsKw, temp, name1, name2); + this->columns = columns; + foreach (Column* column, columns) + column->setParent(this); + + SqliteCreateTable::Constraint* constr = nullptr; + foreach (constr, constraints) + { + if (this->constraints.size() > 0 && + this->constraints.last()->type == SqliteCreateTable::Constraint::NAME_ONLY) + { + constr->name = this->constraints.last()->name; + delete this->constraints.takeLast(); + } + this->constraints << constr; + constr->setParent(this); + } +} + +SqliteCreateTable::SqliteCreateTable(bool ifNotExistsKw, int temp, const QString& name1, const QString& name2, const QList<SqliteCreateTable::Column*>& columns, const QList<SqliteCreateTable::Constraint*>& constraints, const QString& withOutRowId) : + SqliteCreateTable(ifNotExistsKw, temp, name1, name2, columns, constraints) +{ + this->withOutRowId = withOutRowId; +} + +SqliteCreateTable::SqliteCreateTable(bool ifNotExistsKw, int temp, const QString &name1, const QString &name2, SqliteSelect *select) + : SqliteCreateTable() +{ + init(ifNotExistsKw, temp, name1, name2); + this->select = select; + if (select) + select->setParent(this); +} + +SqliteCreateTable::~SqliteCreateTable() +{ +} + +SqliteStatement*SqliteCreateTable::clone() +{ + return new SqliteCreateTable(*this); +} + +QList<SqliteCreateTable::Constraint*> SqliteCreateTable::getConstraints(SqliteCreateTable::Constraint::Type type) const +{ + QList<SqliteCreateTable::Constraint*> results; + foreach (Constraint* constr, constraints) + if (constr->type == type) + results << constr; + + return results; +} + +SqliteStatement* SqliteCreateTable::getPrimaryKey() const +{ + foreach (Constraint* constr, getConstraints(Constraint::PRIMARY_KEY)) + return constr; + + Column::Constraint* colConstr = nullptr; + foreach (Column* col, columns) + { + colConstr = col->getConstraint(Column::Constraint::PRIMARY_KEY); + if (colConstr) + return colConstr; + } + + return nullptr; +} + +QStringList SqliteCreateTable::getPrimaryKeyColumns() const +{ + QStringList colNames; + SqliteStatement* primaryKey = getPrimaryKey(); + if (!primaryKey) + return colNames; + + SqliteCreateTable::Column::Constraint* columnConstr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(primaryKey); + if (columnConstr) + { + colNames << dynamic_cast<SqliteCreateTable::Column*>(columnConstr->parentStatement())->name; + return colNames; + } + + SqliteCreateTable::Constraint* tableConstr = dynamic_cast<SqliteCreateTable::Constraint*>(primaryKey); + if (tableConstr) + { + foreach (SqliteIndexedColumn* idxCol, tableConstr->indexedColumns) + colNames << idxCol->name; + } + return colNames; +} + +SqliteCreateTable::Column* SqliteCreateTable::getColumn(const QString& colName) +{ + foreach (Column* col, columns) + { + if (col->name.compare(colName, Qt::CaseInsensitive) == 0) + return col; + } + return nullptr; +} + +QList<SqliteCreateTable::Constraint*> SqliteCreateTable::getForeignKeysByTable(const QString& foreignTable) const +{ + QList<Constraint*> results; + foreach (Constraint* constr, constraints) + if (constr->type == Constraint::FOREIGN_KEY && constr->foreignKey->foreignTable.compare(foreignTable, Qt::CaseInsensitive) == 0) + results << constr; + + return results; +} + +QList<SqliteCreateTable::Column::Constraint*> SqliteCreateTable::getColumnForeignKeysByTable(const QString& foreignTable) const +{ + QList<Column::Constraint*> results; + foreach (Column* col, columns) + results += col->getForeignKeysByTable(foreignTable); + + return results; +} + +QStringList SqliteCreateTable::getColumnNames() const +{ + QStringList names; + foreach (Column* col, columns) + names << col->name; + + return names; +} + +QHash<QString, QString> SqliteCreateTable::getModifiedColumnsMap(bool lowercaseKeys, Qt::CaseSensitivity cs) const +{ + QHash<QString, QString> colMap; + QString key; + foreach (Column* col, columns) + { + key = lowercaseKeys ? col->originalName.toLower() : col->originalName; + if (col->name.compare(col->originalName, cs) != 0) + colMap[key] = col->name; + } + + return colMap; +} + +QStringList SqliteCreateTable::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteCreateTable::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteCreateTable::getTableTokensInStatement() +{ + return getObjectTokenListFromFullname(); +} + +TokenList SqliteCreateTable::getDatabaseTokensInStatement() +{ + return getDbTokenListFromFullname(); +} + +QList<SqliteStatement::FullObject> SqliteCreateTable::getFullObjectsInStatement() +{ + QList<FullObject> result; + + // Table object + FullObject fullObj = getFullObjectFromFullname(FullObject::TABLE); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + return result; +} + +TokenList SqliteCreateTable::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("CREATE"); + if (tempKw) + builder.withSpace().withKeyword("TEMP"); + else if (temporaryKw) + builder.withSpace().withKeyword("TEMPORARY"); + + builder.withSpace().withKeyword("TABLE"); + if (ifNotExistsKw) + builder.withSpace().withKeyword("IF").withSpace().withKeyword("NOT").withSpace().withKeyword("EXISTS"); + + builder.withSpace(); + if (dialect == Dialect::Sqlite3 && !database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table, dialect); + + if (select) + builder.withSpace().withKeyword("AS").withSpace().withStatement(select); + else + { + builder.withSpace().withParLeft().withStatementList(columns); + if (constraints.size() > 0) + builder.withOperator(",").withStatementList(constraints); + + builder.withParRight(); + + if (!withOutRowId.isNull()) + builder.withSpace().withKeyword("WITHOUT").withSpace().withOther("ROWID"); + } + + builder.withOperator(";"); + + return builder.build(); +} + +void SqliteCreateTable::init(bool ifNotExistsKw, int temp, const QString &name1, const QString &name2) +{ + this->ifNotExistsKw = ifNotExistsKw; + if (temp == 2) + temporaryKw = true; + else if (temp == 1) + tempKw = true; + + if (name2.isNull()) + table = name1; + else + { + database = name1; + table = name2; + } +} + + +SqliteCreateTable::Column::Constraint::Constraint() +{ +} + +SqliteCreateTable::Column::Constraint::Constraint(const SqliteCreateTable::Column::Constraint& other) : + SqliteStatement(other), type(other.type), name(other.name), sortOrder(other.sortOrder), onConflict(other.onConflict), + autoincrKw(other.autoincrKw), literalValue(other.literalValue), literalNull(other.literalNull), ctime(other.ctime), id(other.id), + collationName(other.collationName), deferrable(other.deferrable), initially(other.initially) +{ + DEEP_COPY_FIELD(SqliteExpr, expr); + DEEP_COPY_FIELD(SqliteForeignKey, foreignKey); +} + +SqliteCreateTable::Column::Constraint::~Constraint() +{ +} + +SqliteStatement* SqliteCreateTable::Column::Constraint::clone() +{ + return new SqliteCreateTable::Column::Constraint(*this); +} + +void SqliteCreateTable::Column::Constraint::initDefNameOnly(const QString &name) +{ + this->type = SqliteCreateTable::Column::Constraint::NAME_ONLY; + this->name = name; +} + +void SqliteCreateTable::Column::Constraint::initDefId(const QString &id) +{ + this->type = SqliteCreateTable::Column::Constraint::DEFAULT; + this->id = id; +} + +void SqliteCreateTable::Column::Constraint::initDefTerm(const QVariant &value, bool minus) +{ + this->type = SqliteCreateTable::Column::Constraint::DEFAULT; + if (minus) + { + if (value.type() == QVariant::Double) + literalValue = -(value.toDouble()); + else if (value.type() == QVariant::LongLong) + literalValue = -(value.toLongLong()); + } + else if (value.isNull()) + { + literalValue = value; + literalNull = true; + } + else + literalValue = value; +} + +void SqliteCreateTable::Column::Constraint::initDefCTime(const QString &name) +{ + this->type = SqliteCreateTable::Column::Constraint::DEFAULT; + ctime = name; +} + +void SqliteCreateTable::Column::Constraint::initDefExpr(SqliteExpr *expr) +{ + this->type = SqliteCreateTable::Column::Constraint::DEFAULT; + this->expr = expr; + if (expr) + expr->setParent(this); +} + +void SqliteCreateTable::Column::Constraint::initNull(SqliteConflictAlgo algo) +{ + this->type = SqliteCreateTable::Column::Constraint::NULL_; + onConflict = algo; +} + +void SqliteCreateTable::Column::Constraint::initNotNull(SqliteConflictAlgo algo) +{ + this->type = SqliteCreateTable::Column::Constraint::NOT_NULL; + onConflict = algo; +} + +void SqliteCreateTable::Column::Constraint::initPk(SqliteSortOrder order, SqliteConflictAlgo algo, bool autoincr) +{ + this->type = SqliteCreateTable::Column::Constraint::PRIMARY_KEY; + sortOrder = order; + onConflict = algo; + autoincrKw = autoincr; +} + +void SqliteCreateTable::Column::Constraint::initUnique(SqliteConflictAlgo algo) +{ + this->type = SqliteCreateTable::Column::Constraint::UNIQUE; + onConflict = algo; +} + +void SqliteCreateTable::Column::Constraint::initCheck() +{ + this->type = SqliteCreateTable::Column::Constraint::CHECK; +} + +void SqliteCreateTable::Column::Constraint::initCheck(SqliteExpr *expr) +{ + this->type = SqliteCreateTable::Column::Constraint::CHECK; + this->expr = expr; + if (expr) + expr->setParent(this); +} + +void SqliteCreateTable::Column::Constraint::initCheck(SqliteExpr *expr, SqliteConflictAlgo algo) +{ + initCheck(expr); + this->onConflict = algo; +} + +void SqliteCreateTable::Column::Constraint::initFk(const QString& table, const QList<SqliteIndexedColumn*>& indexedColumns, const QList<SqliteForeignKey::Condition*>& conditions) +{ + this->type = SqliteCreateTable::Column::Constraint::FOREIGN_KEY; + + SqliteForeignKey* fk = new SqliteForeignKey(); + fk->foreignTable = table; + fk->indexedColumns = indexedColumns; + fk->conditions = conditions; + foreignKey = fk; + fk->setParent(this); + + foreach (SqliteIndexedColumn* idxCol, indexedColumns) + idxCol->setParent(fk); + + foreach (SqliteForeignKey::Condition* cond, conditions) + cond->setParent(fk); +} + +void SqliteCreateTable::Column::Constraint::initDefer(SqliteInitially initially, SqliteDeferrable deferrable) +{ + this->type = SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY; + this->deferrable = deferrable; + this->initially = initially; +} + +void SqliteCreateTable::Column::Constraint::initColl(const QString &name) +{ + this->type = SqliteCreateTable::Column::Constraint::COLLATE; + this->collationName = name; +} + +QString SqliteCreateTable::Column::Constraint::typeString() const +{ + switch (type) + { + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + return "PRIMARY KEY"; + case SqliteCreateTable::Column::Constraint::NOT_NULL: + return "NOT NULL"; + case SqliteCreateTable::Column::Constraint::UNIQUE: + return "UNIQUE"; + case SqliteCreateTable::Column::Constraint::CHECK: + return "CHECK"; + case SqliteCreateTable::Column::Constraint::DEFAULT: + return "DEFAULT"; + case SqliteCreateTable::Column::Constraint::COLLATE: + return "COLLATE"; + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + return "FOREIGN KEY"; + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + return QString::null; +} + +SqliteCreateTable::Constraint::Constraint() +{ +} + +SqliteCreateTable::Constraint::Constraint(const SqliteCreateTable::Constraint& other) : + SqliteStatement(other), type(other.type), name(other.name), autoincrKw(other.autoincrKw), onConflict(other.onConflict), + afterComma(other.afterComma) +{ + DEEP_COPY_FIELD(SqliteForeignKey, foreignKey); + DEEP_COPY_FIELD(SqliteExpr, expr); + DEEP_COPY_COLLECTION(SqliteIndexedColumn, indexedColumns); +} + +SqliteCreateTable::Constraint::~Constraint() +{ +} + +SqliteStatement*SqliteCreateTable::Constraint::clone() +{ + return new SqliteCreateTable::Constraint(*this); +} + +void SqliteCreateTable::Constraint::initNameOnly(const QString &name) +{ + this->type = SqliteCreateTable::Constraint::NAME_ONLY; + this->name = name; +} + +void SqliteCreateTable::Constraint::initPk(const QList<SqliteIndexedColumn *> &indexedColumns, bool autoincr, SqliteConflictAlgo algo) +{ + this->type = SqliteCreateTable::Constraint::PRIMARY_KEY; + this->indexedColumns = indexedColumns; + autoincrKw = autoincr; + onConflict = algo; + + foreach (SqliteIndexedColumn* idxCol, indexedColumns) + idxCol->setParent(this); +} + +void SqliteCreateTable::Constraint::initUnique(const QList<SqliteIndexedColumn *> &indexedColumns, SqliteConflictAlgo algo) +{ + this->type = SqliteCreateTable::Constraint::UNIQUE; + this->indexedColumns = indexedColumns; + onConflict = algo; + + foreach (SqliteIndexedColumn* idxCol, indexedColumns) + idxCol->setParent(this); +} + +void SqliteCreateTable::Constraint::initCheck(SqliteExpr *expr, SqliteConflictAlgo algo) +{ + this->type = SqliteCreateTable::Constraint::CHECK; + this->expr = expr; + onConflict = algo; + if (expr) + expr->setParent(this); +} + +void SqliteCreateTable::Constraint::initCheck() +{ + this->type = SqliteCreateTable::Constraint::CHECK; +} + +void SqliteCreateTable::Constraint::initFk(const QList<SqliteIndexedColumn *> &indexedColumns, const QString& table, const QList<SqliteIndexedColumn *> &fkColumns, const QList<SqliteForeignKey::Condition *> &conditions, SqliteInitially initially, SqliteDeferrable deferrable) +{ + this->type = SqliteCreateTable::Constraint::FOREIGN_KEY; + this->indexedColumns = indexedColumns; + + foreach (SqliteIndexedColumn* idxCol, indexedColumns) + idxCol->setParent(this); + + SqliteForeignKey* fk = new SqliteForeignKey(); + fk->foreignTable = table; + fk->indexedColumns = fkColumns; + fk->conditions = conditions; + fk->deferrable = deferrable; + fk->initially = initially; + + fk->setParent(this); + + foreach (SqliteIndexedColumn* idxCol, fkColumns) + idxCol->setParent(fk); + + foreach (SqliteForeignKey::Condition* cond, conditions) + cond->setParent(fk); + + this->foreignKey = fk; +} + +bool SqliteCreateTable::Constraint::doesAffectColumn(const QString& columnName) +{ + return getAffectedColumnIdx(columnName) > -1; +} + +int SqliteCreateTable::Constraint::getAffectedColumnIdx(const QString& columnName) +{ + int i = 0; + foreach (SqliteIndexedColumn* idxCol, indexedColumns) + { + if (idxCol->name.compare(columnName, Qt::CaseInsensitive) == 0) + return i; + + i++; + } + + return -1; +} + +QString SqliteCreateTable::Constraint::typeString() const +{ + switch (type) + { + case SqliteCreateTable::Constraint::PRIMARY_KEY: + return "PRIMARY KEY"; + case SqliteCreateTable::Constraint::UNIQUE: + return "UNIQUE"; + case SqliteCreateTable::Constraint::CHECK: + return "CHECK"; + case SqliteCreateTable::Constraint::FOREIGN_KEY: + return "FOREIGN KEY"; + case SqliteCreateTable::Constraint::NAME_ONLY: + return QString::null; + } + return QString::null; +} + +TokenList SqliteCreateTable::Constraint::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + if (!name.isNull()) + builder.withKeyword("CONSTRAINT").withSpace().withOther(name, dialect).withSpace(); + + switch (type) + { + case SqliteCreateTable::Constraint::PRIMARY_KEY: + { + builder.withKeyword("PRIMARY").withSpace().withKeyword("KEY").withSpace().withParLeft().withStatementList(indexedColumns).withParRight(); + + if (autoincrKw) + builder.withSpace().withKeyword("AUTOINCREMENT"); + + builder.withConflict(onConflict); + break; + } + case SqliteCreateTable::Constraint::UNIQUE: + { + builder.withKeyword("UNIQUE").withSpace().withParLeft().withStatementList(indexedColumns).withParRight().withConflict(onConflict); + break; + } + case SqliteCreateTable::Constraint::CHECK: + { + builder.withKeyword("CHECK").withSpace().withParLeft().withStatement(expr).withParRight().withConflict(onConflict); + break; + } + case SqliteCreateTable::Constraint::FOREIGN_KEY: + { + builder.withKeyword("FOREIGN").withSpace().withKeyword("KEY").withSpace().withParLeft().withStatementList(indexedColumns) + .withParRight().withStatement(foreignKey); + break; + } + case SqliteCreateTable::Constraint::NAME_ONLY: + break; + } + + return builder.build(); +} + +SqliteCreateTable::Column::Column() +{ +} + +SqliteCreateTable::Column::Column(const SqliteCreateTable::Column& other) : + SqliteStatement(other), name(other.name), originalName(other.originalName) +{ + DEEP_COPY_FIELD(SqliteColumnType, type); + DEEP_COPY_COLLECTION(Constraint, constraints); +} + +SqliteCreateTable::Column::Column(const QString &name, SqliteColumnType *type, const QList<Constraint *> &constraints) +{ + this->name = name; + this->originalName = name; + this->type = type; + + if (type) + type->setParent(this); + + SqliteCreateTable::Column::Constraint* constr = nullptr; + foreach (constr, constraints) + { + // If last constraint on list is NAME_ONLY we apply the name + // to current constraint and remove NAME_ONLY. + // Exception is DEFERRABLE_ONLY. + if (this->constraints.size() > 0 && + this->constraints.last()->type == SqliteCreateTable::Column::Constraint::NAME_ONLY && + constr->type != SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY) + { + constr->name = this->constraints.last()->name; + delete this->constraints.takeLast(); + } + + // And the opposite of above. Now we apply DEFERRABLE_ONLY, + // but only if last item in the list is not NAME_ONLY. + if (constr->type == SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY && + this->constraints.size() > 0 && + this->constraints.last()->type != SqliteCreateTable::Column::Constraint::NAME_ONLY) + { + SqliteCreateTable::Column::Constraint* last = this->constraints.last(); + last->deferrable = constr->deferrable; + last->initially = constr->initially; + delete constr; + + // We don't want deleted constr to be added to list. We finish this now. + continue; + } + + this->constraints << constr; + constr->setParent(this); + } +} + +SqliteCreateTable::Column::~Column() +{ +} + +SqliteStatement*SqliteCreateTable::Column::clone() +{ + return new SqliteCreateTable::Column(*this); +} + +bool SqliteCreateTable::Column::hasConstraint(SqliteCreateTable::Column::Constraint::Type type) const +{ + return getConstraint(type) != nullptr; +} + +SqliteCreateTable::Column::Constraint* SqliteCreateTable::Column::getConstraint(SqliteCreateTable::Column::Constraint::Type type) const +{ + foreach (Constraint* constr, constraints) + if (constr->type == type) + return constr; + + return nullptr; +} + +QList<SqliteCreateTable::Column::Constraint*> SqliteCreateTable::Column::getForeignKeysByTable(const QString& foreignTable) const +{ + QList<Constraint*> results; + foreach (Constraint* constr, constraints) + if (constr->type == Constraint::FOREIGN_KEY && constr->foreignKey->foreignTable.compare(foreignTable, Qt::CaseInsensitive) == 0) + results << constr; + + return results; +} + +QStringList SqliteCreateTable::Column::getColumnsInStatement() +{ + return getStrListFromValue(name); +} + +TokenList SqliteCreateTable::Column::getColumnTokensInStatement() +{ + return getTokenListFromNamedKey("columnid"); +} + +TokenList SqliteCreateTable::Column::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withOther(name, dialect).withStatement(type).withStatementList(constraints, ""); + return builder.build(); +} + +TokenList SqliteCreateTable::Column::Constraint::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + if (!name.isNull()) + builder.withKeyword("CONSTRAINT").withSpace().withOther(name, dialect).withSpace(); + + switch (type) + { + case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: + { + builder.withKeyword("PRIMARY").withSpace().withKeyword("KEY").withSortOrder(sortOrder).withConflict(onConflict); + if (autoincrKw) + builder.withSpace().withKeyword("AUTOINCREMENT"); + + break; + } + case SqliteCreateTable::Column::Constraint::NOT_NULL: + { + builder.withKeyword("NOT").withSpace().withKeyword("NULL").withConflict(onConflict); + break; + } + case SqliteCreateTable::Column::Constraint::UNIQUE: + { + builder.withKeyword("UNIQUE").withConflict(onConflict); + break; + } + case SqliteCreateTable::Column::Constraint::CHECK: + { + builder.withKeyword("CHECK").withSpace().withParLeft().withStatement(expr).withParRight().withConflict(onConflict); + break; + } + case SqliteCreateTable::Column::Constraint::DEFAULT: + { + builder.withKeyword("DEFAULT").withSpace(); + if (!id.isNull()) + builder.withOther(id); + else if (!ctime.isNull()) + builder.withKeyword(ctime.toUpper()); + else if (expr) + builder.withParLeft().withStatement(expr).withParRight(); + else if (literalNull) + builder.withKeyword("NULL"); + else + builder.withLiteralValue(literalValue); + + break; + } + case SqliteCreateTable::Column::Constraint::COLLATE: + { + builder.withKeyword("COLLATE").withSpace().withOther(collationName, dialect); + break; + } + case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: + { + builder.withStatement(foreignKey); + break; + } + case SqliteCreateTable::Column::Constraint::NULL_: + case SqliteCreateTable::Column::Constraint::NAME_ONLY: + case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: + break; + } + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.h new file mode 100644 index 0000000..f3be244 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetable.h @@ -0,0 +1,205 @@ +#ifndef SQLITECREATETABLE_H +#define SQLITECREATETABLE_H + +#include "sqlitequery.h" +#include "sqliteconflictalgo.h" +#include "sqliteexpr.h" +#include "sqliteforeignkey.h" +#include "sqliteindexedcolumn.h" +#include "sqliteselect.h" +#include "sqlitecolumntype.h" +#include "sqlitesortorder.h" +#include "sqlitedeferrable.h" +#include <QVariant> +#include <QList> + +class API_EXPORT SqliteCreateTable : public SqliteQuery +{ + public: + class API_EXPORT Column : public SqliteStatement + { + public: + class API_EXPORT Constraint : public SqliteStatement + { + public: + enum Type + { + PRIMARY_KEY, + NOT_NULL, + UNIQUE, + CHECK, + DEFAULT, + COLLATE, + FOREIGN_KEY, + NULL_, // not officially supported + NAME_ONLY, // unofficial, because of bizarre sqlite grammar + DEFERRABLE_ONLY // unofficial, because of bizarre sqlite grammar + }; + + Constraint(); + Constraint(const Constraint& other); + ~Constraint(); + SqliteStatement* clone(); + + void initDefNameOnly(const QString& name); + void initDefId(const QString& id); + void initDefTerm(const QVariant& value, bool minus = false); + void initDefCTime(const QString& name); + void initDefExpr(SqliteExpr* expr); + void initNull(SqliteConflictAlgo algo); + void initNotNull(SqliteConflictAlgo algo); + void initPk(SqliteSortOrder order, SqliteConflictAlgo algo, bool autoincr); + void initUnique(SqliteConflictAlgo algo); + void initCheck(); + void initCheck(SqliteExpr* expr); + void initCheck(SqliteExpr* expr, SqliteConflictAlgo algo); + void initFk(const QString& table, const QList<SqliteIndexedColumn*>& indexedColumns, const QList<SqliteForeignKey::Condition*>& conditions); + void initDefer(SqliteInitially initially, SqliteDeferrable deferrable); + void initColl(const QString& name); + QString typeString() const; + + Type type; + QString name = QString::null; + SqliteSortOrder sortOrder = SqliteSortOrder::null; + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; + bool autoincrKw = false; + SqliteExpr* expr = nullptr; + QVariant literalValue; + bool literalNull = false; + QString ctime; + QString id; + QString collationName = QString::null; + SqliteForeignKey* foreignKey = nullptr; + SqliteDeferrable deferrable = SqliteDeferrable::null; + SqliteInitially initially = SqliteInitially::null; + + protected: + TokenList rebuildTokensFromContents(); + }; + + typedef QSharedPointer<Constraint> ConstraintPtr; + + Column(); + Column(const Column& other); + Column(const QString& name, SqliteColumnType* type, + const QList<Constraint*>& constraints); + ~Column(); + SqliteStatement* clone(); + + bool hasConstraint(Constraint::Type type) const; + Constraint* getConstraint(Constraint::Type type) const; + QList<Constraint*> getForeignKeysByTable(const QString& foreignTable) const; + + QString name = QString::null; + SqliteColumnType* type = nullptr; + QList<Constraint*> constraints; + + /** + * @brief originalName + * Used to remember original name when column was edited and the name was changed. + * It's defined in the constructor to the same value as the name member. + */ + QString originalName = QString::null; + + protected: + QStringList getColumnsInStatement(); + TokenList getColumnTokensInStatement(); + TokenList rebuildTokensFromContents(); + }; + + typedef QSharedPointer<Column> ColumnPtr; + + class API_EXPORT Constraint : public SqliteStatement + { + public: + enum Type + { + PRIMARY_KEY, + UNIQUE, + CHECK, + FOREIGN_KEY, + NAME_ONLY // unofficial, because of bizarre sqlite grammar + }; + + Constraint(); + Constraint(const Constraint& other); + ~Constraint(); + SqliteStatement* clone(); + + void initNameOnly(const QString& name); + void initPk(const QList<SqliteIndexedColumn*>& indexedColumns, + bool autoincr, SqliteConflictAlgo algo); + void initUnique(const QList<SqliteIndexedColumn*>& indexedColumns, + SqliteConflictAlgo algo); + void initCheck(SqliteExpr* expr, SqliteConflictAlgo algo); + void initCheck(); + void initFk(const QList<SqliteIndexedColumn*>& indexedColumns, const QString& table, + const QList<SqliteIndexedColumn*>& fkColumns, const QList<SqliteForeignKey::Condition*>& conditions, + SqliteInitially initially, SqliteDeferrable deferrable); + + bool doesAffectColumn(const QString& columnName); + int getAffectedColumnIdx(const QString& columnName); + QString typeString() const; + + Type type; + QString name = QString::null; + bool autoincrKw = false; // not in docs, but needs to be supported + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; + SqliteForeignKey* foreignKey = nullptr; + SqliteExpr* expr = nullptr; + QList<SqliteIndexedColumn*> indexedColumns; + bool afterComma = false; + + protected: + TokenList rebuildTokensFromContents(); + }; + + typedef QSharedPointer<Constraint> ConstraintPtr; + + SqliteCreateTable(); + SqliteCreateTable(const SqliteCreateTable& other); + SqliteCreateTable(bool ifNotExistsKw, int temp, const QString& name1, const QString& name2, + const QList<Column*>& columns, const QList<Constraint*>& constraints); + SqliteCreateTable(bool ifNotExistsKw, int temp, const QString& name1, const QString& name2, + const QList<Column*>& columns, const QList<Constraint*>& constraints, + const QString& withOutRowId); + SqliteCreateTable(bool ifNotExistsKw, int temp, const QString& name1, const QString& name2, + SqliteSelect* select); + ~SqliteCreateTable(); + SqliteStatement* clone(); + + QList<Constraint*> getConstraints(Constraint::Type type) const; + SqliteStatement* getPrimaryKey() const; + QStringList getPrimaryKeyColumns() const; + Column* getColumn(const QString& colName); + QList<Constraint*> getForeignKeysByTable(const QString& foreignTable) const; + QList<Column::Constraint*> getColumnForeignKeysByTable(const QString& foreignTable) const; + QStringList getColumnNames() const; + QHash<QString,QString> getModifiedColumnsMap(bool lowercaseKeys = false, Qt::CaseSensitivity cs = Qt::CaseInsensitive) const; + + bool ifNotExistsKw = false; + bool tempKw = false; + bool temporaryKw = false; + QString database = QString::null; + QString table = QString::null; + QList<Column*> columns; + QList<Constraint*> constraints; + SqliteSelect* select = nullptr; + QString withOutRowId = QString::null; + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + + private: + void init(bool ifNotExistsKw, int temp, const QString& name1, const QString& name2); + +}; + +typedef QSharedPointer<SqliteCreateTable> SqliteCreateTablePtr; + +#endif // SQLITECREATETABLE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetrigger.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetrigger.cpp new file mode 100644 index 0000000..3b7b0ea --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetrigger.cpp @@ -0,0 +1,381 @@ +#include "sqlitecreatetrigger.h" +#include "sqlitequerytype.h" +#include "sqliteexpr.h" +#include "sqlitedelete.h" +#include "sqliteinsert.h" +#include "sqliteupdate.h" +#include "sqliteselect.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" + +SqliteCreateTrigger::SqliteCreateTrigger() +{ + queryType = SqliteQueryType::CreateTrigger; +} + +SqliteCreateTrigger::SqliteCreateTrigger(const SqliteCreateTrigger& other) : + SqliteQuery(other), tempKw(other.tempKw), temporaryKw(other.temporaryKw), ifNotExistsKw(other.ifNotExistsKw), database(other.database), + trigger(other.trigger), table(other.table), eventTime(other.eventTime), scope(other.scope) +{ + DEEP_COPY_FIELD(Event, event); + DEEP_COPY_FIELD(SqliteExpr, precondition); + + // Special case of deep collection copy + SqliteQuery* newQuery = nullptr; + foreach (SqliteQuery* query, other.queries) + { + switch (query->queryType) + { + case SqliteQueryType::Delete: + newQuery = new SqliteDelete(*dynamic_cast<SqliteDelete*>(query)); + break; + case SqliteQueryType::Insert: + newQuery = new SqliteInsert(*dynamic_cast<SqliteInsert*>(query)); + break; + case SqliteQueryType::Update: + newQuery = new SqliteUpdate(*dynamic_cast<SqliteUpdate*>(query)); + break; + case SqliteQueryType::Select: + newQuery = new SqliteSelect(*dynamic_cast<SqliteSelect*>(query)); + break; + default: + newQuery = nullptr; + break; + } + + if (!newQuery) + continue; + + newQuery->setParent(this); + queries << newQuery; + } +} + +SqliteCreateTrigger::SqliteCreateTrigger(int temp, bool ifNotExists, const QString &name1, const QString &name2, const QString &name3, Time time, SqliteCreateTrigger::Event *event, Scope foreachType, SqliteExpr *when, const QList<SqliteQuery *> &queries, int sqliteVersion) : + SqliteCreateTrigger() +{ + this->ifNotExistsKw = ifNotExists; + this->scope = foreachType; + if (temp == 2) + temporaryKw = true; + else if (temp == 1) + tempKw = true; + + if (sqliteVersion == 3) + { + if (name2.isNull()) + trigger = name1; + else + { + database = name1; + trigger = name2; + } + table = name3; + } + else + { + trigger = name1; + if (name3.isNull()) + table = name2; + else + { + database = name2; + table = name3; + } + } + + this->event = event; + eventTime = time; + this->precondition = when; + this->queries = queries; + + if (event) + event->setParent(this); + + if (when) + when->setParent(this); + + foreach (SqliteQuery* q, queries) + q->setParent(this); +} + +SqliteCreateTrigger::~SqliteCreateTrigger() +{ +} + +SqliteStatement*SqliteCreateTrigger::clone() +{ + return new SqliteCreateTrigger(*this); +} + +QString SqliteCreateTrigger::getTargetTable() const +{ + return table; +} + +QString SqliteCreateTrigger::time(SqliteCreateTrigger::Time eventTime) +{ + switch (eventTime) + { + case SqliteCreateTrigger::Time::BEFORE: + return "BEFORE"; + case SqliteCreateTrigger::Time::AFTER: + return "AFTER"; + case SqliteCreateTrigger::Time::INSTEAD_OF: + return "INSTEAD OF"; + case SqliteCreateTrigger::Time::null: + break; + } + return QString::null; +} + +SqliteCreateTrigger::Time SqliteCreateTrigger::time(const QString& eventTime) +{ + if (eventTime == "BEFORE") + return Time::BEFORE; + + if (eventTime == "AFTER") + return Time::AFTER; + + if (eventTime == "INSTEAD OF") + return Time::INSTEAD_OF; + + return Time::null; +} + +QString SqliteCreateTrigger::scopeToString(SqliteCreateTrigger::Scope scope) +{ + switch (scope) + { + case SqliteCreateTrigger::Scope::FOR_EACH_ROW: + return "FOR EACH ROW"; + case SqliteCreateTrigger::Scope::FOR_EACH_STATEMENT: + return "FOR EACH STATEMENT"; + case SqliteCreateTrigger::Scope::null: + break; + } + return QString::null; +} + +SqliteCreateTrigger::Scope SqliteCreateTrigger::stringToScope(const QString& scope) +{ + if (scope == "FOR EACH ROW") + return Scope::FOR_EACH_ROW; + + if (scope == "FOR EACH STATEMENT") + return Scope::FOR_EACH_STATEMENT; + + return Scope::null; +} + +QStringList SqliteCreateTrigger::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteCreateTrigger::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteCreateTrigger::getTableTokensInStatement() +{ + if (dialect == Dialect::Sqlite2) + return getObjectTokenListFromNmDbnm("nm2", "dbnm"); + else + return getTokenListFromNamedKey("nm2"); +} + +TokenList SqliteCreateTrigger::getDatabaseTokensInStatement() +{ + if (dialect == Dialect::Sqlite2) + return getDbTokenListFromNmDbnm("nm2", "dbnm"); + else + return getDbTokenListFromNmDbnm(); +} + +QList<SqliteStatement::FullObject> SqliteCreateTrigger::getFullObjectsInStatement() +{ + QList<FullObject> result; + + // Table object + FullObject fullObj; + if (dialect == Dialect::Sqlite2) + fullObj = getFullObjectFromNmDbnm(FullObject::TABLE, "nm2", "dbnm"); + else + { + TokenList tableTokens = getTokenListFromNamedKey("nm2"); + if (tableTokens.size() > 0) + fullObj = getFullObject(FullObject::TABLE, TokenPtr(), tableTokens[0]); + } + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + // Trigger object + if (dialect == Dialect::Sqlite2) + { + TokenList tableTokens = getTokenListFromNamedKey("nm"); + if (tableTokens.size() > 0) + fullObj = getFullObject(FullObject::TRIGGER, TokenPtr(), tableTokens[0]); + } + else + fullObj = getFullObjectFromNmDbnm(FullObject::TRIGGER, "nm", "dbnm"); + + return result; +} + +SqliteCreateTrigger::Event::Event() +{ + this->type = Event::null; +} + +SqliteCreateTrigger::Event::Event(SqliteCreateTrigger::Event::Type type) +{ + this->type = type; +} + +SqliteCreateTrigger::Event::Event(const SqliteCreateTrigger::Event& other) : + SqliteStatement(other), type(other.type), columnNames(other.columnNames) +{ +} + +SqliteCreateTrigger::Event::Event(const QList<QString> &columns) +{ + this->type = UPDATE_OF; + columnNames = columns; +} + +SqliteStatement*SqliteCreateTrigger::Event::clone() +{ + return new SqliteCreateTrigger::Event(*this); +} + +TokenList SqliteCreateTrigger::Event::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + switch (type) + { + case SqliteCreateTrigger::Event::INSERT: + builder.withKeyword("INSERT"); + break; + case SqliteCreateTrigger::Event::UPDATE: + builder.withKeyword("UPDATE"); + break; + case SqliteCreateTrigger::Event::DELETE: + builder.withKeyword("DELETE"); + break; + case SqliteCreateTrigger::Event::UPDATE_OF: + builder.withKeyword("UPDATE").withSpace().withKeyword("OF").withSpace().withOtherList(columnNames); + break; + case SqliteCreateTrigger::Event::null: + break; + } + + return builder.build(); +} + +QString SqliteCreateTrigger::Event::typeToString(SqliteCreateTrigger::Event::Type type) +{ + switch (type) + { + case SqliteCreateTrigger::Event::INSERT: + return "INSERT"; + case SqliteCreateTrigger::Event::UPDATE: + return "UPDATE"; + case SqliteCreateTrigger::Event::DELETE: + return "DELETE"; + case SqliteCreateTrigger::Event::UPDATE_OF: + return "UPDATE OF"; + case SqliteCreateTrigger::Event::null: + break; + } + return QString::null; +} + +SqliteCreateTrigger::Event::Type SqliteCreateTrigger::Event::stringToType(const QString& type) +{ + if (type == "INSERT") + return INSERT; + + if (type == "UPDATE") + return UPDATE; + + if (type == "DELETE") + return DELETE; + + if (type == "UPDATE OF") + return UPDATE_OF; + + return Event::null; +} + +TokenList SqliteCreateTrigger::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("CREATE").withSpace(); + if (tempKw) + builder.withKeyword("TEMP").withSpace(); + else if (temporaryKw) + builder.withKeyword("TEMPORARY").withSpace(); + + builder.withKeyword("TRIGGER").withSpace(); + if (ifNotExistsKw) + builder.withKeyword("IF").withSpace().withKeyword("NOT").withSpace().withKeyword("EXISTS").withSpace(); + + if (dialect == Dialect::Sqlite3 && !database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(trigger, dialect).withSpace(); + switch (eventTime) + { + case Time::BEFORE: + builder.withKeyword("BEFORE").withSpace(); + break; + case Time::AFTER: + builder.withKeyword("AFTER").withSpace(); + break; + case Time::INSTEAD_OF: + builder.withKeyword("INSTEAD").withSpace().withKeyword("OF").withSpace(); + break; + case Time::null: + break; + } + + builder.withStatement(event).withSpace().withKeyword("ON").withSpace(); + if (dialect == Dialect::Sqlite2 && !database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table, dialect).withSpace(); + + switch (scope) + { + case SqliteCreateTrigger::Scope::FOR_EACH_ROW: + builder.withKeyword("FOR").withSpace().withKeyword("EACH").withSpace().withKeyword("ROW").withSpace(); + break; + case SqliteCreateTrigger::Scope::FOR_EACH_STATEMENT: + builder.withKeyword("FOR").withSpace().withKeyword("EACH").withSpace().withKeyword("STATEMENT").withSpace(); + break; + case SqliteCreateTrigger::Scope::null: + break; + } + + if (precondition) + builder.withKeyword("WHEN").withStatement(precondition).withSpace(); + + builder.withKeyword("BEGIN").withSpace().withStatementList(queries, ";").withOperator(";").withSpace().withKeyword("END"); + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetrigger.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetrigger.h new file mode 100644 index 0000000..2fb8ae4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatetrigger.h @@ -0,0 +1,96 @@ +#ifndef SQLITECREATETRIGGER_H +#define SQLITECREATETRIGGER_H + +#include "sqlitequery.h" +#include "sqlitetablerelatedddl.h" + +#include <QString> +#include <QList> + +class SqliteExpr; + +class API_EXPORT SqliteCreateTrigger : public SqliteQuery, public SqliteTableRelatedDdl +{ + public: + enum class Time + { + BEFORE, + AFTER, + INSTEAD_OF, + null + }; + + class API_EXPORT Event : public SqliteStatement + { + public: + enum Type + { + INSERT, + UPDATE, + DELETE, + UPDATE_OF, + null + }; + + Event(); + explicit Event(Type type); + Event(const Event& other); + explicit Event(const QList<QString>& columns); + SqliteStatement* clone(); + + TokenList rebuildTokensFromContents(); + + static QString typeToString(Type type); + static Type stringToType(const QString& type); + + Type type; + QStringList columnNames; + }; + + enum class Scope + { + FOR_EACH_ROW, + FOR_EACH_STATEMENT, // Sqlite2 only + null + }; + + SqliteCreateTrigger(); + SqliteCreateTrigger(const SqliteCreateTrigger& other); + SqliteCreateTrigger(int temp, bool ifNotExists, const QString& name1, const QString& name2, + const QString& name3, Time time, Event* event, Scope scope, + SqliteExpr* precondition, const QList<SqliteQuery*>& queries, int sqliteVersion); + ~SqliteCreateTrigger(); + SqliteStatement* clone(); + + QString getTargetTable() const; + + bool tempKw = false; + bool temporaryKw = false; + bool ifNotExistsKw = false; + // The database refers to the trigger name in Sqlite3, but in Sqlite2 it refers to the table. + QString database = QString::null; + QString trigger = QString::null; + QString table = QString::null; // can also be a view name + Event* event = nullptr; + Time eventTime = Time::null; + Scope scope = Scope::null; + SqliteExpr* precondition = nullptr; + QList<SqliteQuery*> queries; + + static QString time(Time eventTime); + static Time time(const QString& eventTime); + static QString scopeToString(Scope scope); + static Scope stringToScope(const QString& scope); + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteCreateTrigger> SqliteCreateTriggerPtr; + +#endif // SQLITECREATETRIGGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateview.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateview.cpp new file mode 100644 index 0000000..6b11c3c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateview.cpp @@ -0,0 +1,120 @@ +#include "sqlitecreateview.h" +#include "sqliteselect.h" +#include "sqlitequerytype.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" + +SqliteCreateView::SqliteCreateView() +{ + queryType = SqliteQueryType::CreateView; +} + +SqliteCreateView::SqliteCreateView(const SqliteCreateView& other) : + SqliteQuery(other), tempKw(other.tempKw), temporaryKw(other.temporaryKw), ifNotExists(other.ifNotExists), + database(other.database), view(other.view) +{ + DEEP_COPY_FIELD(SqliteSelect, select); + +} + +SqliteCreateView::SqliteCreateView(int temp, bool ifNotExists, const QString &name1, const QString &name2, SqliteSelect *select) : + SqliteCreateView() +{ + this->ifNotExists = ifNotExists; + + if (name2.isNull()) + view = name1; + else + { + database = name1; + view = name2; + } + + if (temp == 2) + temporaryKw = true; + else if (temp == 1) + tempKw = true; + + this->select = select; + + if (select) + select->setParent(this); +} + +SqliteCreateView::~SqliteCreateView() +{ +} + +SqliteStatement*SqliteCreateView::clone() +{ + return new SqliteCreateView(*this); +} + +QStringList SqliteCreateView::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteCreateView::getDatabaseTokensInStatement() +{ + if (dialect == Dialect::Sqlite3) + return getDbTokenListFromFullname(); + else + return TokenList(); +} + +QList<SqliteStatement::FullObject> SqliteCreateView::getFullObjectsInStatement() +{ + QList<FullObject> result; + + // View object + FullObject fullObj; + if (dialect == Dialect::Sqlite3) + fullObj = getFullObjectFromFullname(FullObject::VIEW); + else + { + TokenList tokens = getTokenListFromNamedKey("nm"); + if (tokens.size() > 0) + fullObj = getFullObject(FullObject::VIEW, TokenPtr(), tokens[0]); + } + + if (fullObj.isValid()) + result << fullObj; + + // Db object + if (dialect == Dialect::Sqlite3) + { + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + } + + return result; +} + +TokenList SqliteCreateView::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("CREATE").withSpace(); + if (tempKw) + builder.withKeyword("TEMP").withSpace(); + else if (temporaryKw) + builder.withKeyword("TEMPORARY").withSpace(); + + builder.withKeyword("VIEW").withSpace(); + if (ifNotExists) + builder.withKeyword("IF").withSpace().withKeyword("NOT").withSpace().withKeyword("EXISTS").withSpace(); + + if (dialect == Dialect::Sqlite3 && !database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(view, dialect).withSpace().withKeyword("AS").withStatement(select); + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateview.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateview.h new file mode 100644 index 0000000..4858227 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreateview.h @@ -0,0 +1,35 @@ +#ifndef SQLITECREATEVIEW_H +#define SQLITECREATEVIEW_H + +#include "sqlitequery.h" +#include <QString> + +class SqliteSelect; + +class API_EXPORT SqliteCreateView : public SqliteQuery +{ + public: + SqliteCreateView(); + SqliteCreateView(const SqliteCreateView& other); + SqliteCreateView(int temp, bool ifNotExists, const QString& name1, const QString& name2, SqliteSelect* select); + ~SqliteCreateView(); + + SqliteStatement* clone(); + + bool tempKw = false; + bool temporaryKw = false; + bool ifNotExists = false; + QString database = QString::null; + QString view = QString::null; + SqliteSelect* select = nullptr; + + protected: + QStringList getDatabasesInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteCreateView> SqliteCreateViewPtr; + +#endif // SQLITECREATEVIEW_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatevirtualtable.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatevirtualtable.cpp new file mode 100644 index 0000000..bc38dc9 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatevirtualtable.cpp @@ -0,0 +1,120 @@ +#include "sqlitecreatevirtualtable.h" +#include "sqlitequerytype.h" + +#include <parser/lexer.h> +#include <parser/statementtokenbuilder.h> + +SqliteCreateVirtualTable::SqliteCreateVirtualTable() +{ + queryType = SqliteQueryType::CreateVirtualTable; +} + +SqliteCreateVirtualTable::SqliteCreateVirtualTable(const SqliteCreateVirtualTable& other) : + SqliteQuery(other), ifNotExistsKw(other.ifNotExistsKw), database(other.database), table(other.table), module(other.module), args(other.args) +{ +} + +SqliteCreateVirtualTable::SqliteCreateVirtualTable(bool ifNotExists, const QString &name1, const QString &name2, const QString &name3) : + SqliteCreateVirtualTable() +{ + initName(name1, name2); + this->ifNotExistsKw = ifNotExists; + module = name3; +} + +SqliteCreateVirtualTable::SqliteCreateVirtualTable(bool ifNotExists, const QString &name1, const QString &name2, const QString &name3, const QList<QString> &args) : + SqliteCreateVirtualTable() +{ + initName(name1, name2); + this->ifNotExistsKw = ifNotExists; + module = name3; + this->args = args; +} + +SqliteStatement*SqliteCreateVirtualTable::clone() +{ + return new SqliteCreateVirtualTable(*this); +} + +QStringList SqliteCreateVirtualTable::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteCreateVirtualTable::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteCreateVirtualTable::getTableTokensInStatement() +{ + return getObjectTokenListFromFullname(); +} + +TokenList SqliteCreateVirtualTable::getDatabaseTokensInStatement() +{ + return getDbTokenListFromFullname(); +} + +QList<SqliteStatement::FullObject> SqliteCreateVirtualTable::getFullObjectsInStatement() +{ + QList<FullObject> result; + + // Table object + FullObject fullObj = getFullObjectFromFullname(FullObject::TABLE); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + return result; +} + +void SqliteCreateVirtualTable::initName(const QString &name1, const QString &name2) +{ + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; +} + +TokenList SqliteCreateVirtualTable::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("CREATE").withSpace().withKeyword("VIRTUAL").withSpace().withKeyword("TABLE"); + if (ifNotExistsKw) + builder.withKeyword("IF").withSpace().withKeyword("NOT").withSpace().withKeyword("EXISTS").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withKeyword("USING").withSpace().withOther(module, dialect); + if (!args.isEmpty()) + { + builder.withSpace(); + int i = 0; + for (const QString& arg : args) + { + if (i > 0) + builder.withOperator(",").withSpace(); + + builder.withTokens(Lexer::tokenize(arg, Dialect::Sqlite3)); + i++; + } + } + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatevirtualtable.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatevirtualtable.h new file mode 100644 index 0000000..cb2d231 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitecreatevirtualtable.h @@ -0,0 +1,42 @@ +#ifndef SQLITECREATEVIRTUALTABLE_H +#define SQLITECREATEVIRTUALTABLE_H + +#include "sqlitequery.h" + +#include <QString> +#include <QList> + +class API_EXPORT SqliteCreateVirtualTable : public SqliteQuery +{ + public: + SqliteCreateVirtualTable(); + SqliteCreateVirtualTable(const SqliteCreateVirtualTable& other); + SqliteCreateVirtualTable(bool ifNotExists, const QString& name1, const QString& name2, + const QString& name3); + SqliteCreateVirtualTable(bool ifNotExists, const QString& name1, const QString& name2, + const QString& name3, const QList<QString>& args); + + SqliteStatement* clone(); + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + + private: + void initName(const QString& name1, const QString& name2); + + public: + bool ifNotExistsKw = false; + QString database = QString::null; + QString table = QString::null; + QString module = QString::null; + QList<QString> args; +}; + +typedef QSharedPointer<SqliteCreateVirtualTable> SqliteCreateVirtualTablePtr; + +#endif // SQLITECREATEVIRTUALTABLE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedeferrable.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedeferrable.cpp new file mode 100644 index 0000000..4ef4d51 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedeferrable.cpp @@ -0,0 +1,54 @@ +#include "sqlitedeferrable.h" + +QString sqliteDeferrable(SqliteDeferrable deferrable) +{ + switch (deferrable) + { + case SqliteDeferrable::NOT_DEFERRABLE: + return "NOT DEFERRABLE"; + case SqliteDeferrable::DEFERRABLE: + return "DEFERRABLE"; + case SqliteDeferrable::null: + break; + } + return QString::null; +} + +SqliteDeferrable sqliteDeferrable(const QString& deferrable) +{ + QString upper = deferrable.toUpper(); + if (upper == "NOT DEFERRABLE") + return SqliteDeferrable::NOT_DEFERRABLE; + + if (upper == "DEFERRABLE") + return SqliteDeferrable::DEFERRABLE; + + return SqliteDeferrable::null; +} + + +QString sqliteInitially(SqliteInitially initially) +{ + switch (initially) + { + case SqliteInitially::DEFERRED: + return "DEFERRED"; + case SqliteInitially::IMMEDIATE: + return "IMMEDIATE"; + case SqliteInitially::null: + break; + } + return QString::null; +} + +SqliteInitially sqliteInitially(const QString& initially) +{ + QString upper = initially.toUpper(); + if (upper == "DEFERRED") + return SqliteInitially::DEFERRED; + + if (upper == "IMMEDIATE") + return SqliteInitially::IMMEDIATE; + + return SqliteInitially::null; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedeferrable.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedeferrable.h new file mode 100644 index 0000000..8fdf06a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedeferrable.h @@ -0,0 +1,27 @@ +#ifndef SQLITEDEFERRABLE_H +#define SQLITEDEFERRABLE_H + +#include "coreSQLiteStudio_global.h" +#include <QString> + +enum class SqliteDeferrable +{ + null, + NOT_DEFERRABLE, + DEFERRABLE +}; + +enum class SqliteInitially +{ + null, + DEFERRED, + IMMEDIATE +}; + +API_EXPORT QString sqliteDeferrable(SqliteDeferrable deferrable); +API_EXPORT SqliteDeferrable sqliteDeferrable(const QString& deferrable); + +API_EXPORT QString sqliteInitially(SqliteInitially initially); +API_EXPORT SqliteInitially sqliteInitially(const QString& initially); + +#endif // SQLITEDEFERRABLE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedelete.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedelete.cpp new file mode 100644 index 0000000..60e5878 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedelete.cpp @@ -0,0 +1,137 @@ +#include "sqlitedelete.h" +#include "sqlitequerytype.h" +#include "sqliteexpr.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" +#include "sqlitewith.h" + +SqliteDelete::SqliteDelete() +{ + queryType = SqliteQueryType::Delete; +} + +SqliteDelete::SqliteDelete(const SqliteDelete& other) : + SqliteQuery(other), database(other.database), table(other.table), indexedByKw(other.indexedByKw), notIndexedKw(other.notIndexedKw), + indexedBy(other.indexedBy) +{ + DEEP_COPY_FIELD(SqliteExpr, where); + DEEP_COPY_FIELD(SqliteWith, with); +} + +SqliteDelete::SqliteDelete(const QString &name1, const QString &name2, const QString& indexedByName, SqliteExpr *where, SqliteWith* with) + : SqliteDelete() +{ + init(name1, name2, where, with); + this->indexedBy = indexedByName; + this->indexedByKw = true; +} + +SqliteDelete::SqliteDelete(const QString &name1, const QString &name2, bool notIndexedKw, SqliteExpr *where, SqliteWith* with) + : SqliteDelete() +{ + init(name1, name2, where, with); + this->notIndexedKw = notIndexedKw; +} + +SqliteDelete::~SqliteDelete() +{ +} + +SqliteStatement*SqliteDelete::clone() +{ + return new SqliteDelete(*this); +} + +QStringList SqliteDelete::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteDelete::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteDelete::getTableTokensInStatement() +{ + return getObjectTokenListFromFullname(); +} + +TokenList SqliteDelete::getDatabaseTokensInStatement() +{ + if (tokensMap.contains("fullname")) + return getDbTokenListFromFullname(); + + if (tokensMap.contains("nm")) + return extractPrintableTokens(tokensMap["nm"]); + + return TokenList(); +} + +QList<SqliteStatement::FullObject> SqliteDelete::getFullObjectsInStatement() +{ + QList<FullObject> result; + if (!tokensMap.contains("fullname")) + return result; + + // Table object + FullObject fullObj = getFullObjectFromFullname(FullObject::TABLE); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + return result; +} + +void SqliteDelete::init(const QString &name1, const QString &name2, SqliteExpr *where, SqliteWith* with) +{ + this->where = where; + if (where) + where->setParent(this); + + this->with = with; + if (with) + with->setParent(this); + + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; +} + +TokenList SqliteDelete::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + if (with) + builder.withStatement(with); + + builder.withKeyword("DELETE").withSpace().withKeyword("FROM").withSpace(); + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table, dialect); + + if (indexedByKw) + builder.withSpace().withKeyword("INDEXED").withSpace().withKeyword("BY").withSpace().withOther(indexedBy, dialect); + else if (notIndexedKw) + builder.withSpace().withKeyword("NOT").withSpace().withKeyword("INDEXED"); + + if (where) + builder.withSpace().withKeyword("WHERE").withStatement(where); + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedelete.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedelete.h new file mode 100644 index 0000000..90e1385 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedelete.h @@ -0,0 +1,45 @@ +#ifndef SQLITEDELETE_H +#define SQLITEDELETE_H + +#include "sqlitequery.h" + +#include <QString> + +class SqliteExpr; +class SqliteWith; + +class API_EXPORT SqliteDelete : public SqliteQuery +{ + public: + SqliteDelete(); + SqliteDelete(const SqliteDelete& other); + SqliteDelete(const QString& name1, const QString& name2, const QString& indexedByName, SqliteExpr* where, SqliteWith* with); + SqliteDelete(const QString& name1, const QString& name2, bool notIndexedKw, SqliteExpr* where, SqliteWith* with); + ~SqliteDelete(); + + SqliteStatement* clone(); + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + + private: + void init(const QString& name1, const QString& name2, SqliteExpr* where, SqliteWith* with); + + public: + QString database = QString::null; + QString table = QString::null; + bool indexedByKw = false; + bool notIndexedKw = false; + QString indexedBy = QString::null; + SqliteExpr* where = nullptr; + SqliteWith* with = nullptr; +}; + +typedef QSharedPointer<SqliteDelete> SqliteDeletePtr; + +#endif // SQLITEDELETE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedetach.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedetach.cpp new file mode 100644 index 0000000..1f874d3 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedetach.cpp @@ -0,0 +1,48 @@ +#include "sqlitedetach.h" +#include "sqlitequerytype.h" +#include "sqliteexpr.h" +#include "common/global.h" +#include "parser/statementtokenbuilder.h" + +SqliteDetach::SqliteDetach() +{ + queryType = SqliteQueryType::Detach; +} + +SqliteDetach::SqliteDetach(const SqliteDetach& other) : + SqliteQuery(other), databaseKw(other.databaseKw) +{ + DEEP_COPY_FIELD(SqliteExpr, name); +} + +SqliteDetach::SqliteDetach(bool databaseKw, SqliteExpr *name) + :SqliteDetach() +{ + this->databaseKw = databaseKw; + this->name = name; + if (name) + name->setParent(this); +} + +SqliteDetach::~SqliteDetach() +{ +} + +SqliteStatement*SqliteDetach::clone() +{ + return new SqliteDetach(*this); +} + +TokenList SqliteDetach::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("DETACH").withSpace(); + + if (databaseKw) + builder.withKeyword("DATABASE").withSpace(); + + builder.withStatement(name).withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedetach.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedetach.h new file mode 100644 index 0000000..725cd47 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedetach.h @@ -0,0 +1,27 @@ +#ifndef SQLITEDETACH_H +#define SQLITEDETACH_H + +#include "sqlitequery.h" + +class SqliteExpr; + +class API_EXPORT SqliteDetach : public SqliteQuery +{ + public: + SqliteDetach(); + SqliteDetach(const SqliteDetach& other); + SqliteDetach(bool databaseKw, SqliteExpr* name); + ~SqliteDetach(); + + SqliteStatement* clone(); + + bool databaseKw = false; + SqliteExpr* name = nullptr; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteDetach> SqliteDetachPtr; + +#endif // SQLITEDETACH_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropindex.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropindex.cpp new file mode 100644 index 0000000..df924fe --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropindex.cpp @@ -0,0 +1,78 @@ +#include "sqlitedropindex.h" +#include "sqlitequerytype.h" + +#include <parser/statementtokenbuilder.h> + +SqliteDropIndex::SqliteDropIndex() +{ + queryType = SqliteQueryType::DropIndex; +} + +SqliteDropIndex::SqliteDropIndex(const SqliteDropIndex& other) : + SqliteQuery(other), ifExistsKw(other.ifExistsKw), database(other.database), index(other.index) +{ +} + +SqliteDropIndex::SqliteDropIndex(bool ifExists, const QString &name1, const QString &name2) + : SqliteDropIndex() +{ + this->ifExistsKw = ifExists; + if (!name2.isNull()) + { + database = name1; + index = name2; + } + else + index = name1; +} + +SqliteStatement*SqliteDropIndex::clone() +{ + return new SqliteDropIndex(*this); +} + +QStringList SqliteDropIndex::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteDropIndex::getDatabaseTokensInStatement() +{ + return getDbTokenListFromFullname(); +} + +QList<SqliteStatement::FullObject> SqliteDropIndex::getFullObjectsInStatement() +{ + QList<FullObject> result; + + // Index object + FullObject fullObj = getFullObjectFromFullname(FullObject::INDEX); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + result << fullObj; + + return result; +} + + +TokenList SqliteDropIndex::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("DROP").withSpace().withKeyword("INDEX").withSpace(); + + if (ifExistsKw) + builder.withKeyword("IF").withSpace().withKeyword("EXISTS").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(index).withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropindex.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropindex.h new file mode 100644 index 0000000..9ce7065 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropindex.h @@ -0,0 +1,29 @@ +#ifndef SQLITEDROPINDEX_H +#define SQLITEDROPINDEX_H + +#include "sqlitequery.h" +#include <QString> + +class API_EXPORT SqliteDropIndex : public SqliteQuery +{ + public: + SqliteDropIndex(); + SqliteDropIndex(const SqliteDropIndex& other); + SqliteDropIndex(bool ifExistsKw, const QString& name1, const QString& name2); + + SqliteStatement* clone(); + + bool ifExistsKw = false; + QString database = QString::null; + QString index = QString::null; + + protected: + QStringList getDatabasesInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteDropIndex> SqliteDropIndexPtr; + +#endif // SQLITEDROPINDEX_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptable.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptable.cpp new file mode 100644 index 0000000..9c4aa37 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptable.cpp @@ -0,0 +1,86 @@ +#include "sqlitedroptable.h" +#include "sqlitequerytype.h" +#include <parser/statementtokenbuilder.h> + +SqliteDropTable::SqliteDropTable() +{ + queryType = SqliteQueryType::DropTable; +} + +SqliteDropTable::SqliteDropTable(const SqliteDropTable& other) : + SqliteQuery(other), ifExistsKw(other.ifExistsKw), database(other.database), table(other.table) +{ +} + +SqliteDropTable::SqliteDropTable(bool ifExists, const QString& name1, const QString& name2) + : SqliteDropTable() +{ + this->ifExistsKw = ifExists; + if (name2.isNull()) + this->table = name1; + else + { + this->database = name1; + this->table = name2; + } +} + +SqliteStatement* SqliteDropTable::clone() +{ + return new SqliteDropTable(*this); +} + +QStringList SqliteDropTable::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteDropTable::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteDropTable::getTableTokensInStatement() +{ + return getObjectTokenListFromFullname(); +} + +TokenList SqliteDropTable::getDatabaseTokensInStatement() +{ + return getDbTokenListFromFullname(); +} + +QList<SqliteStatement::FullObject> SqliteDropTable::getFullObjectsInStatement() +{ + QList<FullObject> result; + + // Table object + FullObject fullObj = getFullObjectFromFullname(FullObject::TABLE); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + result << fullObj; + + return result; +} + +TokenList SqliteDropTable::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("DROP").withSpace().withKeyword("TABLE").withSpace(); + + if (ifExistsKw) + builder.withKeyword("IF").withSpace().withKeyword("EXISTS").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table).withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptable.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptable.h new file mode 100644 index 0000000..edc4da4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptable.h @@ -0,0 +1,31 @@ +#ifndef SQLITEDROPTABLE_H +#define SQLITEDROPTABLE_H + +#include "sqlitequery.h" +#include <QString> + +class API_EXPORT SqliteDropTable : public SqliteQuery +{ + public: + SqliteDropTable(); + SqliteDropTable(const SqliteDropTable& other); + SqliteDropTable(bool ifExistsKw, const QString& name1, const QString& name2); + + SqliteStatement* clone(); + + bool ifExistsKw = false; + QString database = QString::null; + QString table = QString::null; + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteDropTable> SqliteDropTablePtr; + +#endif // SQLITEDROPTABLE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptrigger.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptrigger.cpp new file mode 100644 index 0000000..471d558 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptrigger.cpp @@ -0,0 +1,79 @@ +#include "sqlitedroptrigger.h" +#include "sqlitequerytype.h" + +#include <parser/statementtokenbuilder.h> + +SqliteDropTrigger::SqliteDropTrigger() +{ + queryType = SqliteQueryType::DropTrigger; +} + +SqliteDropTrigger::SqliteDropTrigger(const SqliteDropTrigger& other) : + SqliteQuery(other), ifExistsKw(other.ifExistsKw), database(other.database), trigger(other.trigger) +{ +} + +SqliteDropTrigger::SqliteDropTrigger(bool ifExists, const QString &name1, const QString &name2) + : SqliteDropTrigger() +{ + this->ifExistsKw = ifExists; + + if (name2.isNull()) + trigger = name1; + else + { + database = name1; + trigger = name2; + } +} + +SqliteStatement*SqliteDropTrigger::clone() +{ + return new SqliteDropTrigger(*this); +} + +QStringList SqliteDropTrigger::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteDropTrigger::getDatabaseTokensInStatement() +{ + return getDbTokenListFromFullname(); +} + +QList<SqliteStatement::FullObject> SqliteDropTrigger::getFullObjectsInStatement() +{ + QList<FullObject> result; + + // Table object + FullObject fullObj = getFullObjectFromFullname(FullObject::TRIGGER); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + result << fullObj; + + return result; +} + + +TokenList SqliteDropTrigger::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("DROP").withSpace().withKeyword("TRIGGER").withSpace(); + + if (ifExistsKw) + builder.withKeyword("IF").withSpace().withKeyword("EXISTS").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(trigger).withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptrigger.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptrigger.h new file mode 100644 index 0000000..5221959 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedroptrigger.h @@ -0,0 +1,29 @@ +#ifndef SQLITEDROPTRIGGER_H +#define SQLITEDROPTRIGGER_H + +#include "sqlitequery.h" +#include <QString> + +class API_EXPORT SqliteDropTrigger : public SqliteQuery +{ + public: + SqliteDropTrigger(); + SqliteDropTrigger(const SqliteDropTrigger& other); + SqliteDropTrigger(bool ifExistsKw, const QString& name1, const QString& name2); + + SqliteStatement* clone(); + + bool ifExistsKw = false; + QString database = QString::null; + QString trigger = QString::null; + + protected: + QStringList getDatabasesInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteDropTrigger> SqliteDropTriggerPtr; + +#endif // SQLITEDROPTRIGGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropview.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropview.cpp new file mode 100644 index 0000000..06d70db --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropview.cpp @@ -0,0 +1,85 @@ +#include "sqlitedropview.h" +#include "sqlitequerytype.h" + +#include <parser/statementtokenbuilder.h> + +SqliteDropView::SqliteDropView() +{ + queryType = SqliteQueryType::DropView; +} + +SqliteDropView::SqliteDropView(const SqliteDropView& other) : + SqliteQuery(other), ifExistsKw(other.ifExistsKw), database(other.database), view(other.view) +{ +} + +SqliteDropView::SqliteDropView(bool ifExists, const QString &name1, const QString &name2) + : SqliteDropView() +{ + this->ifExistsKw = ifExists; + + if (name2.isNull()) + view = name1; + else + { + database = name1; + view = name2; + } +} + +SqliteStatement*SqliteDropView::clone() +{ + return new SqliteDropView(*this); +} + +QStringList SqliteDropView::getDatabasesInStatement() +{ + if (dialect == Dialect::Sqlite2) + return QStringList(); + + return getStrListFromValue(database); +} + +TokenList SqliteDropView::getDatabaseTokensInStatement() +{ + if (dialect == Dialect::Sqlite2) + return TokenList(); + + return getDbTokenListFromFullname(); +} + +QList<SqliteStatement::FullObject> SqliteDropView::getFullObjectsInStatement() +{ + QList<FullObject> result; + + // Table object + FullObject fullObj = getFullObjectFromFullname(FullObject::VIEW); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + result << fullObj; + + return result; +} + + +TokenList SqliteDropView::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("DROP").withSpace().withKeyword("VIEW").withSpace(); + + if (ifExistsKw) + builder.withKeyword("IF").withSpace().withKeyword("EXISTS").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(view).withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropview.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropview.h new file mode 100644 index 0000000..853ccef --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitedropview.h @@ -0,0 +1,29 @@ +#ifndef SQLITEDROPVIEW_H +#define SQLITEDROPVIEW_H + +#include "sqlitequery.h" +#include <QString> + +class API_EXPORT SqliteDropView : public SqliteQuery +{ + public: + SqliteDropView(); + SqliteDropView(const SqliteDropView& other); + SqliteDropView(bool ifExistsKw, const QString& name1, const QString& name2); + + SqliteStatement* clone(); + + bool ifExistsKw = false; + QString database = QString::null; + QString view = QString::null; + + protected: + QStringList getDatabasesInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteDropView> SqliteDropViewPtr; + +#endif // SQLITEDROPVIEW_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteemptyquery.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteemptyquery.cpp new file mode 100644 index 0000000..628aea5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteemptyquery.cpp @@ -0,0 +1,27 @@ +#include "sqliteemptyquery.h" +#include "sqlitequerytype.h" +#include "parser/statementtokenbuilder.h" +#include "common/unused.h" + +SqliteEmptyQuery::SqliteEmptyQuery() +{ + queryType = SqliteQueryType::EMPTY; +} + +SqliteEmptyQuery::SqliteEmptyQuery(const SqliteEmptyQuery& other) : + SqliteEmptyQuery() +{ + UNUSED(other); +} + +SqliteStatement*SqliteEmptyQuery::clone() +{ + return new SqliteEmptyQuery(*this); +} + +TokenList SqliteEmptyQuery::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withOperator(";"); + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteemptyquery.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteemptyquery.h new file mode 100644 index 0000000..3380532 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteemptyquery.h @@ -0,0 +1,19 @@ +#ifndef SQLITEEMPTYQUERY_H +#define SQLITEEMPTYQUERY_H + +#include "sqlitequery.h" + +class API_EXPORT SqliteEmptyQuery : public SqliteQuery +{ + public: + SqliteEmptyQuery(); + SqliteEmptyQuery(const SqliteEmptyQuery& other); + + SqliteStatement* clone(); + + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteEmptyQuery> SqliteEmptyQueryPtr; + +#endif // SQLITEEMPTYQUERY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.cpp new file mode 100644 index 0000000..969f029 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.cpp @@ -0,0 +1,644 @@ +#include "sqliteexpr.h" +#include "sqliteraise.h" +#include "sqliteselect.h" +#include "sqlitecolumntype.h" +#include "parser/statementtokenbuilder.h" +#include "common/utils_sql.h" +#include "common/global.h" +#include <QDebug> + +SqliteExpr::SqliteExpr() +{ +} + +SqliteExpr::SqliteExpr(const SqliteExpr& other) : + SqliteStatement(other), + mode(other.mode), literalValue(other.literalValue), literalNull(other.literalNull), bindParam(other.bindParam), database(other.database), table(other.table), + column(other.column), unaryOp(other.unaryOp), binaryOp(other.binaryOp), function(other.function), collation(other.collation), + ctime(other.ctime), distinctKw(other.distinctKw), allKw(other.allKw), star(other.star), notKw(other.notKw), like(other.like), + notNull(other.notNull), possibleDoubleQuotedString(other.possibleDoubleQuotedString) +{ + DEEP_COPY_FIELD(SqliteColumnType, columnType); + DEEP_COPY_FIELD(SqliteExpr, expr1); + DEEP_COPY_FIELD(SqliteExpr, expr2); + DEEP_COPY_FIELD(SqliteExpr, expr3); + DEEP_COPY_COLLECTION(SqliteExpr, exprList); + DEEP_COPY_FIELD(SqliteSelect, select); + DEEP_COPY_FIELD(SqliteRaise, raiseFunction); +} + +SqliteExpr::~SqliteExpr() +{ +} + +SqliteExpr::LikeOp SqliteExpr::likeOp(const QString &value) +{ + QString upper = value.toUpper(); + if (upper == "LIKE") + return SqliteExpr::LikeOp::LIKE; + else if (upper == "GLOB") + return SqliteExpr::LikeOp::GLOB; + else if (upper == "REGEXP") + return SqliteExpr::LikeOp::REGEXP; + else if (upper == "MATCH") + return SqliteExpr::LikeOp::MATCH; + else + return SqliteExpr::LikeOp::null; +} + +QString SqliteExpr::likeOp(SqliteExpr::LikeOp value) +{ + switch (value) + { + case SqliteExpr::LikeOp::LIKE: + return "LIKE"; + case SqliteExpr::LikeOp::GLOB: + return "GLOB"; + case SqliteExpr::LikeOp::REGEXP: + return "REGEXP"; + case SqliteExpr::LikeOp::MATCH: + return "MATCH"; + default: + return QString::null; + } +} + +SqliteExpr::NotNull SqliteExpr::notNullOp(const QString &value) +{ + if (value == "ISNULL") + return SqliteExpr::NotNull::ISNULL; + else if (value == "NOTNULL") + return SqliteExpr::NotNull::NOTNULL; + else if (value == "NOT NULL") + return SqliteExpr::NotNull::NOT_NULL; + else + return SqliteExpr::NotNull::null; +} + +QString SqliteExpr::notNullOp(SqliteExpr::NotNull value) +{ + switch (value) + { + case SqliteExpr::NotNull::ISNULL: + return "ISNULL"; + case SqliteExpr::NotNull::NOT_NULL: + return "NOT NULL"; + case SqliteExpr::NotNull::NOTNULL: + return "NOTNULL"; + default: + return QString::null; + } +} + +SqliteStatement*SqliteExpr::clone() +{ + return new SqliteExpr(*this); +} + +void SqliteExpr::initLiteral(const QVariant &value) +{ + mode = SqliteExpr::Mode::LITERAL_VALUE; + if (value.isNull()) + initNull(); + + literalValue = value; +} + +void SqliteExpr::initNull() +{ + literalNull = true; +} + +void SqliteExpr::initCTime(const QString &name) +{ + mode = SqliteExpr::Mode::CTIME; + ctime = name; +} + +void SqliteExpr::initSubExpr(SqliteExpr *expr) +{ + mode = SqliteExpr::Mode::SUB_EXPR; + expr1 = expr; + if (expr) + expr->setParent(this); +} + +void SqliteExpr::initBindParam(const QString& value) +{ + mode = SqliteExpr::Mode::BIND_PARAM; + bindParam = value; +} + +void SqliteExpr::initCollate(SqliteExpr *expr, const QString& value) +{ + mode = SqliteExpr::Mode::COLLATE; + expr1 = expr; + collation = value; + if (expr) + expr->setParent(this); +} + +void SqliteExpr::initCast(SqliteExpr *expr, SqliteColumnType *type) +{ + mode = SqliteExpr::Mode::CAST; + expr1 = expr; + columnType = type; + if (expr) + expr->setParent(this); +} + +void SqliteExpr::initFunction(const QString& fnName, int distinct, const QList<SqliteExpr*>& exprList) +{ + mode = SqliteExpr::Mode::FUNCTION; + function = fnName; + this->exprList = exprList; + if (distinct == 1) + distinctKw = true; + else if (distinct == 2) + allKw = true; + + foreach (SqliteExpr* expr, exprList) + expr->setParent(this); +} + +void SqliteExpr::initFunction(const QString& fnName, bool star) +{ + mode = SqliteExpr::Mode::FUNCTION; + function = fnName; + this->star = star; +} + +void SqliteExpr::initBinOp(SqliteExpr *expr1, const QString& op, SqliteExpr *expr2) +{ + mode = SqliteExpr::Mode::BINARY_OP; + this->expr1 = expr1; + this->expr2 = expr2; + binaryOp = op; + if (expr1) + expr1->setParent(this); + + if (expr2) + expr2->setParent(this); +} + +void SqliteExpr::initUnaryOp(SqliteExpr *expr, const QString& op) +{ + mode = SqliteExpr::Mode::UNARY_OP; + expr1 = expr; + unaryOp = op; + if (expr) + expr->setParent(this); +} + +void SqliteExpr::initLike(SqliteExpr *expr1, bool notKw, LikeOp likeOp, SqliteExpr *expr2, SqliteExpr *expr3) +{ + mode = SqliteExpr::Mode::LIKE; + this->expr1 = expr1; + this->expr2 = expr2; + this->expr3 = expr3; + this->notKw = notKw; + this->like = likeOp; + if (expr1) + expr1->setParent(this); + + if (expr2) + expr2->setParent(this); + + if (expr3) + expr3->setParent(this); +} + +void SqliteExpr::initNull(SqliteExpr *expr, const QString& value) +{ + mode = SqliteExpr::Mode::NOTNULL; + expr1 = expr; + notNull = notNullOp(value); + if (expr) + expr->setParent(this); +} + +void SqliteExpr::initIs(SqliteExpr *expr1, bool notKw, SqliteExpr *expr2) +{ + mode = SqliteExpr::Mode::IS; + this->expr1 = expr1; + this->notKw = notKw; + this->expr2 = expr2; + if (expr1) + expr1->setParent(this); + + if (expr2) + expr2->setParent(this); +} + +void SqliteExpr::initBetween(SqliteExpr *expr1, bool notKw, SqliteExpr *expr2, SqliteExpr *expr3) +{ + mode = SqliteExpr::Mode::BETWEEN; + this->expr1 = expr1; + this->expr2 = expr2; + this->expr3 = expr3; + this->notKw = notKw; + if (expr1) + expr1->setParent(this); + + if (expr2) + expr2->setParent(this); + + if (expr3) + expr3->setParent(this); +} + +void SqliteExpr::initIn(SqliteExpr *expr, bool notKw, const QList<SqliteExpr*>& exprList) +{ + mode = SqliteExpr::Mode::IN; + expr1 = expr; + this->notKw = notKw; + this->exprList = exprList; + foreach (SqliteExpr* expr, exprList) + expr->setParent(this); +} + +void SqliteExpr::initIn(SqliteExpr *expr, bool notKw, SqliteSelect *select) +{ + mode = SqliteExpr::Mode::IN; + expr1 = expr; + this->notKw = notKw; + this->select = select; + if (expr) + expr->setParent(this); + + if (select) + select->setParent(this); +} + +void SqliteExpr::initIn(SqliteExpr *expr, bool notKw, const QString& name1, const QString& name2) +{ + mode = SqliteExpr::Mode::IN; + expr1 = expr; + this->notKw = notKw; + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; + + if (expr) + expr->setParent(this); +} + +void SqliteExpr::initExists(SqliteSelect *select) +{ + mode = SqliteExpr::Mode::EXISTS; + this->select = select; + if (select) + select->setParent(this); +} + +void SqliteExpr::initSubSelect(SqliteSelect *select) +{ + mode = SqliteExpr::Mode::SUB_SELECT; + this->select = select; + if (select) + select->setParent(this); +} + +void SqliteExpr::initCase(SqliteExpr *expr1, const QList<SqliteExpr*>& exprList, SqliteExpr *expr2) +{ + mode = SqliteExpr::Mode::CASE; + this->expr1 = expr1; + this->expr2 = expr2; + this->exprList = exprList; + if (expr1) + expr1->setParent(this); + + if (expr2) + expr2->setParent(this); + + foreach (SqliteExpr* expr, exprList) + expr->setParent(this); +} + +void SqliteExpr::initRaise(const QString& type, const QString& text) +{ + mode = SqliteExpr::Mode::RAISE; + raiseFunction = new SqliteRaise(type, text); +} + +QStringList SqliteExpr::getColumnsInStatement() +{ + return getStrListFromValue(column); +} + +QStringList SqliteExpr::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteExpr::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteExpr::getColumnTokensInStatement() +{ + TokenList list; + if (!column.isNull()) + { + if (!table.isNull()) + { + if (!database.isNull()) + list << tokens[4]; + else + list << tokens[2]; + } + else + list << tokens[0]; + } + return list; +} + +TokenList SqliteExpr::getTableTokensInStatement() +{ + TokenList list; + if (!table.isNull()) + { + if (!database.isNull()) + list << tokens[2]; + else + list << tokens[0]; + } + + return list; +} + +TokenList SqliteExpr::getDatabaseTokensInStatement() +{ + TokenList list; + if (!database.isNull()) + list << tokens[0]; + + return list; +} + +QList<SqliteStatement::FullObject> SqliteExpr::getFullObjectsInStatement() +{ + QList<FullObject> result; + if (mode != Mode::ID) + return result; + + if (!table.isNull()) + { + if (!database.isNull()) + { + FullObject dbFullObject = getDbFullObject(tokens[0]); + result << dbFullObject; + dbTokenForFullObjects = dbFullObject.database; + + result << getFullObject(FullObject::TABLE, dbTokenForFullObjects, tokens[2]); + } + else + result << getFullObject(FullObject::TABLE, dbTokenForFullObjects, tokens[0]); + } + + return result; +} + +void SqliteExpr::initId(const QString &db, const QString &table, const QString &column) +{ + mode = SqliteExpr::Mode::ID; + database = db; + this->table = table; + this->column = column; +} + +void SqliteExpr::initId(const QString& table, const QString& column) +{ + mode = SqliteExpr::Mode::ID; + this->table = table; + this->column = column; +} + +void SqliteExpr::initId(const QString& column) +{ + mode = SqliteExpr::Mode::ID; + this->column = column; +} + +TokenList SqliteExpr::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + switch (mode) + { + case SqliteExpr::Mode::null: + break; + case SqliteExpr::Mode::LITERAL_VALUE: + { + if (literalNull) + builder.withKeyword("NULL"); + else + builder.withLiteralValue(literalValue); + break; + } + case SqliteExpr::Mode::CTIME: + builder.withKeyword(ctime.toUpper()); + break; + case SqliteExpr::Mode::BIND_PARAM: + builder.withBindParam(bindParam); + break; + case SqliteExpr::Mode::ID: + builder.withTokens(rebuildId()); + break; + case SqliteExpr::Mode::UNARY_OP: + builder.withOperator(unaryOp).withSpace().withStatement(expr1); + break; + case SqliteExpr::Mode::BINARY_OP: + builder.withStatement(expr1).withSpace().withOperator(binaryOp).withSpace().withStatement(expr2); + break; + case SqliteExpr::Mode::FUNCTION: + builder.withOther(function).withParLeft().withStatementList(exprList).withParRight(); + break; + case SqliteExpr::Mode::SUB_EXPR: + builder.withParLeft().withStatement(expr1).withParRight(); + break; + case SqliteExpr::Mode::CAST: + builder.withKeyword("CAST").withSpace().withParLeft().withStatement(expr1).withSpace().withKeyword("AS") + .withStatement(columnType).withParRight(); + break; + case SqliteExpr::Mode::COLLATE: + builder.withStatement(expr1).withSpace().withKeyword("COLLATE").withSpace().withOther(collation, dialect); + break; + case SqliteExpr::Mode::LIKE: + builder.withTokens(rebuildLike()); + break; + case SqliteExpr::Mode::NULL_: + builder.withKeyword("NULL"); + break; + case SqliteExpr::Mode::NOTNULL: + builder.withTokens(rebuildNotNull()); + break; + case SqliteExpr::Mode::IS: + builder.withTokens(rebuildIs()); + break; + case SqliteExpr::Mode::BETWEEN: + builder.withTokens(rebuildBetween()); + break; + case SqliteExpr::Mode::IN: + builder.withTokens(rebuildIn()); + break; + case SqliteExpr::Mode::EXISTS: + builder.withKeyword("EXISTS").withParLeft().withStatement(select).withParRight(); + break; + case SqliteExpr::Mode::CASE: + builder.withTokens(rebuildCase()); + break; + case SqliteExpr::Mode::SUB_SELECT: + builder.withParLeft().withStatement(select).withParRight(); + break; + case SqliteExpr::Mode::RAISE: + builder.withStatement(raiseFunction); + break; + } + + return builder.build(); +} + +void SqliteExpr::evaluatePostParsing() +{ + if (tokens.size() > 0) + { + QString val = tokens.first()->value; + if (val[0] == '"' && val[0] == val[val.length() - 1]) + possibleDoubleQuotedString = true; + } +} + +TokenList SqliteExpr::rebuildId() +{ + StatementTokenBuilder builder; + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + if (!table.isNull()) + builder.withOther(table, dialect).withOperator("."); + + if (possibleDoubleQuotedString) + builder.withStringPossiblyOther(column, dialect); + else + builder.withOther(column, dialect); + + return builder.build(); +} + +TokenList SqliteExpr::rebuildLike() +{ + StatementTokenBuilder builder; + builder.withStatement(expr1).withSpace(); + if (notKw) + builder.withKeyword("NOT").withSpace(); + + builder.withKeyword(likeOp(like)).withSpace().withStatement(expr2); + if (expr3) + builder.withSpace().withKeyword("ESCAPE").withStatement(expr3); + + return builder.build(); +} + +TokenList SqliteExpr::rebuildNotNull() +{ + StatementTokenBuilder builder; + switch (notNull) + { + case SqliteExpr::NotNull::ISNULL: + builder.withKeyword("ISNULL"); + break; + case SqliteExpr::NotNull::NOT_NULL: + builder.withKeyword("NOT").withSpace().withKeyword("NULL"); + break; + case SqliteExpr::NotNull::NOTNULL: + builder.withKeyword("NOTNULL"); + break; + case SqliteExpr::NotNull::null: + break; + } + return builder.build(); +} + +TokenList SqliteExpr::rebuildIs() +{ + StatementTokenBuilder builder; + builder.withStatement(expr1).withSpace().withKeyword("IS"); + if (notKw) + builder.withSpace().withKeyword("NOT"); + + builder.withStatement(expr2); + return builder.build(); +} + +TokenList SqliteExpr::rebuildBetween() +{ + StatementTokenBuilder builder; + builder.withStatement(expr1); + + if (notKw) + builder.withSpace().withKeyword("NOT"); + + builder.withSpace().withKeyword("BETWEEN").withStatement(expr2).withSpace().withKeyword("AND").withStatement(expr3); + return builder.build(); +} + +TokenList SqliteExpr::rebuildIn() +{ + StatementTokenBuilder builder; + builder.withStatement(expr1); + + if (notKw) + builder.withSpace().withKeyword("NOT"); + + builder.withSpace().withKeyword("IN").withSpace(); + if (select) + { + builder.withParLeft().withStatement(select).withParRight(); + } + else if (exprList.size() > 0) + { + builder.withParLeft().withStatementList(exprList).withParRight(); + } + else + { + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table, dialect); + } + return builder.build(); +} + +TokenList SqliteExpr::rebuildCase() +{ + StatementTokenBuilder builder; + builder.withKeyword("CASE"); + if (expr1) + builder.withStatement(expr1); + + builder.withSpace(); + + bool then = false; + foreach (SqliteExpr* expr, exprList) + { + if (then) + builder.withKeyword("THEN"); + else + builder.withKeyword("WHEN"); + + builder.withStatement(expr).withSpace(); + then = !then; + } + + if (expr2) + builder.withKeyword("ELSE").withStatement(expr2).withSpace(); + + builder.withKeyword("END"); + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.h new file mode 100644 index 0000000..f57004f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteexpr.h @@ -0,0 +1,144 @@ +#ifndef SQLITEEXPR_H +#define SQLITEEXPR_H + +#include "sqlitestatement.h" +#include <QString> +#include <QVariant> +#include <QList> + +class SqliteSelect; +class SqliteColumnType; +class SqliteRaise; + +class API_EXPORT SqliteExpr : public SqliteStatement +{ + public: + enum class Mode + { + null, + LITERAL_VALUE, + CTIME, + BIND_PARAM, + ID, + UNARY_OP, + BINARY_OP, + FUNCTION, + SUB_EXPR, + CAST, + COLLATE, // in Sqlite2 exists only in expr of sortlist + LIKE, + NULL_, + NOTNULL, + IS, + BETWEEN, + IN, + EXISTS, + CASE, + SUB_SELECT, + RAISE + }; + + enum class NotNull + { + ISNULL, + NOT_NULL, + NOTNULL, + null + }; + + enum class LikeOp + { + LIKE, + GLOB, + REGEXP, + MATCH, + null + }; + + SqliteExpr(); + SqliteExpr(const SqliteExpr& other); + ~SqliteExpr(); + + static LikeOp likeOp(const QString& value); + static QString likeOp(LikeOp value); + static NotNull notNullOp(const QString& value); + static QString notNullOp(NotNull value); + + SqliteStatement* clone(); + void initLiteral(const QVariant& value); + void initNull(); + void initCTime(const QString& name); + void initSubExpr(SqliteExpr* expr); + void initId(const QString& db, const QString& table, const QString& column); + void initId(const QString& table, const QString& column); + void initId(const QString& column); + void initBindParam(const QString& value); + void initCollate(SqliteExpr* expr, const QString& value); + void initCast(SqliteExpr* expr, SqliteColumnType* type); + void initFunction(const QString& fnName, int distinct, const QList<SqliteExpr*>& exprList); + void initFunction(const QString& fnName, bool star = false); + void initBinOp(SqliteExpr* expr1, const QString& op, SqliteExpr* expr2); + void initUnaryOp(SqliteExpr* expr, const QString& op); + void initLike(SqliteExpr* expr1, bool notKw, SqliteExpr::LikeOp likeOp, SqliteExpr* expr2, SqliteExpr* expr3 = nullptr); + void initNull(SqliteExpr* expr, const QString& value); + void initIs(SqliteExpr* expr1, bool notKw, SqliteExpr* expr2); + void initBetween(SqliteExpr* expr1, bool notKw, SqliteExpr* expr2, SqliteExpr* expr3); + void initIn(SqliteExpr* expr, bool notKw, const QList<SqliteExpr*>& exprList); + void initIn(SqliteExpr* expr, bool notKw, SqliteSelect* select); + void initIn(SqliteExpr* expr, bool notKw, const QString& name1, const QString& name2); + void initExists(SqliteSelect* select); + void initSubSelect(SqliteSelect* select); + void initCase(SqliteExpr* expr1, const QList<SqliteExpr*>& exprList, SqliteExpr* expr2); + void initRaise(const QString& type, const QString& text = QString::null); + + Mode mode = Mode::null; + QVariant literalValue = QVariant(); + bool literalNull = false; + QString bindParam = QString::null; + QString database = QString::null; + QString table = QString::null; + QString column = QString::null; + QString unaryOp = QString::null; + QString binaryOp = QString::null; + QString function = QString::null; + QString collation = QString::null; + QString ctime = QString::null; + SqliteColumnType* columnType = nullptr; + SqliteExpr* expr1 = nullptr; + SqliteExpr* expr2 = nullptr; + SqliteExpr* expr3 = nullptr; + QList<SqliteExpr*> exprList; + SqliteSelect* select = nullptr; + bool distinctKw = false; + bool allKw = false; // alias for DISTINCT as for sqlite3 grammar + bool star = false; + bool notKw = false; + LikeOp like = LikeOp::null; + NotNull notNull = NotNull::null; + SqliteRaise* raiseFunction = nullptr; + bool possibleDoubleQuotedString = false; + + protected: + QStringList getColumnsInStatement(); + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getColumnTokensInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + void evaluatePostParsing(); + + private: + TokenList rebuildId(); + TokenList rebuildLike(); + TokenList rebuildNotNull(); + TokenList rebuildIs(); + TokenList rebuildBetween(); + TokenList rebuildIn(); + TokenList rebuildCase(); +}; + +typedef QSharedPointer<SqliteExpr> SqliteExprPtr; + +#endif // SQLITEEXPR_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteforeignkey.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteforeignkey.cpp new file mode 100644 index 0000000..9a29db2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteforeignkey.cpp @@ -0,0 +1,187 @@ +#include "sqliteforeignkey.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" +#include <QDebug> + +SqliteForeignKey::Condition::Condition(SqliteForeignKey::Condition::Action action, SqliteForeignKey::Condition::Reaction reaction) +{ + this->action = action; + this->reaction = reaction; +} + +SqliteForeignKey::Condition::Condition(const QString &name) +{ + this->action = SqliteForeignKey::Condition::MATCH; + this->name = name; +} + +SqliteForeignKey::Condition::Condition(const SqliteForeignKey::Condition& other) : + SqliteStatement(other), action(other.action), name(other.name), reaction(other.reaction) +{ +} + +QString SqliteForeignKey::Condition::toString(SqliteForeignKey::Condition::Reaction reaction) +{ + switch (reaction) + { + case SqliteForeignKey::Condition::SET_NULL: + return "SET NULL"; + case SqliteForeignKey::Condition::SET_DEFAULT: + return "SET DEFAULT"; + case SqliteForeignKey::Condition::CASCADE: + return "CASCADE"; + case SqliteForeignKey::Condition::RESTRICT: + return "RESTRICT"; + case SqliteForeignKey::Condition::NO_ACTION: + return "NO ACTION"; + } + return QString::null; +} + +SqliteForeignKey::Condition::Reaction SqliteForeignKey::Condition::toEnum(const QString& reaction) +{ + QString upper = reaction.toUpper(); + if (upper == "SET NULL") + return SET_NULL; + + if (upper == "SET DEFAULT") + return SET_DEFAULT; + + if (upper == "CASCADE") + return CASCADE; + + if (upper == "RESTRICT") + return RESTRICT; + + if (upper == "NO ACTION") + return NO_ACTION; + + qCritical() << "Unknown Reaction value. Cannot convert to Condition::Reaction. Returning default, the SET_NULL."; + return SET_NULL; +} + +SqliteStatement*SqliteForeignKey::Condition::clone() +{ + return new SqliteForeignKey::Condition(*this); +} + +SqliteForeignKey::SqliteForeignKey() +{ +} + +SqliteForeignKey::SqliteForeignKey(const SqliteForeignKey& other) : + SqliteStatement(other), foreignTable(other.foreignTable), deferrable(other.deferrable), initially(other.initially) +{ + DEEP_COPY_COLLECTION(SqliteIndexedColumn, indexedColumns); + DEEP_COPY_COLLECTION(Condition, conditions); +} + +SqliteForeignKey::~SqliteForeignKey() +{ +} + +SqliteStatement*SqliteForeignKey::clone() +{ + return new SqliteForeignKey(*this); +} + +QStringList SqliteForeignKey::getTablesInStatement() +{ + return getStrListFromValue(foreignTable); +} + +TokenList SqliteForeignKey::getTableTokensInStatement() +{ + return parentStatement()->getContextTableTokens(false, false); +} + +QList<SqliteStatement::FullObject> SqliteForeignKey::getFullObjectsInStatement() +{ + QList<FullObject> result; + + // Table object + FullObject fullObj; + TokenList tokens = getTableTokensInStatement(); + if (tokens.size() > 0) + fullObj = getFullObject(FullObject::TABLE, dbTokenForFullObjects, tokens[0]); + + if (fullObj.isValid()) + result << fullObj; + + return result; +} + +TokenList SqliteForeignKey::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("REFERENCES").withSpace().withOther(foreignTable, dialect); + + if (indexedColumns.size() > 0) + builder.withSpace().withParLeft().withStatementList(indexedColumns).withParRight(); + + if (conditions.size() > 0) + builder.withSpace().withStatementList(conditions, ""); + + if (deferrable != SqliteDeferrable::null) + { + if (deferrable == SqliteDeferrable::NOT_DEFERRABLE) + builder.withSpace().withKeyword("NOT").withSpace().withKeyword("DEFERRABLE"); + else if (deferrable == SqliteDeferrable::DEFERRABLE) + builder.withSpace().withKeyword("DEFERRABLE"); + + if (initially != SqliteInitially::null) + builder.withSpace().withKeyword("INITIALLY").withSpace().withKeyword(sqliteInitially(initially)); + } + + return builder.build(); +} + + +TokenList SqliteForeignKey::Condition::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + switch (action) + { + case SqliteForeignKey::Condition::UPDATE: + builder.withKeyword("ON").withSpace().withKeyword("UPDATE").withSpace(); + applyReactionToBuilder(builder); + break; + case SqliteForeignKey::Condition::INSERT: + builder.withKeyword("ON").withSpace().withKeyword("INSERT").withSpace(); + applyReactionToBuilder(builder); + break; + case SqliteForeignKey::Condition::DELETE: + builder.withKeyword("ON").withSpace().withKeyword("DELETE").withSpace(); + applyReactionToBuilder(builder); + break; + case SqliteForeignKey::Condition::MATCH: + builder.withKeyword("MATCH").withSpace().withOther(name); + break; + } + + return builder.build(); +} + +void SqliteForeignKey::Condition::applyReactionToBuilder(StatementTokenBuilder& builder) +{ + switch (reaction) + { + case SqliteForeignKey::Condition::SET_NULL: + builder.withKeyword("SET").withSpace().withKeyword("NULL"); + break; + case SqliteForeignKey::Condition::SET_DEFAULT: + builder.withKeyword("SET").withSpace().withKeyword("DEFAULT"); + break; + case SqliteForeignKey::Condition::CASCADE: + builder.withKeyword("CASCADE"); + break; + case SqliteForeignKey::Condition::RESTRICT: + builder.withKeyword("RESTRICT"); + break; + case SqliteForeignKey::Condition::NO_ACTION: + builder.withKeyword("NO").withSpace().withKeyword("ACTION"); + break; + } +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteforeignkey.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteforeignkey.h new file mode 100644 index 0000000..18e0bcb --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteforeignkey.h @@ -0,0 +1,75 @@ +#ifndef SQLITEFOREIGNKEY_H +#define SQLITEFOREIGNKEY_H + +#include "sqlitestatement.h" +#include "sqliteindexedcolumn.h" +#include "sqlitedeferrable.h" +#include "parser/statementtokenbuilder.h" +#include <QList> +#include <QString> + +class API_EXPORT SqliteForeignKey : public SqliteStatement +{ + public: + class API_EXPORT Condition : public SqliteStatement + { + public: + enum Action + { + UPDATE, + INSERT, + DELETE, + MATCH + }; + + enum Reaction + { + SET_NULL, + SET_DEFAULT, + CASCADE, + RESTRICT, + NO_ACTION + }; + + Condition(Action action, Reaction reaction); + explicit Condition(const QString& name); + Condition(const Condition& other); + + static QString toString(Reaction reaction); + static Reaction toEnum(const QString& reaction); + + SqliteStatement* clone(); + + Action action; + QString name = QString::null; + Reaction reaction = NO_ACTION; + + protected: + TokenList rebuildTokensFromContents(); + + private: + void applyReactionToBuilder(StatementTokenBuilder& builder); + }; + + SqliteForeignKey(); + SqliteForeignKey(const SqliteForeignKey& other); + ~SqliteForeignKey(); + + SqliteStatement* clone(); + + QString foreignTable = QString::null; + QList<SqliteIndexedColumn*> indexedColumns; + QList<Condition*> conditions; + SqliteDeferrable deferrable = SqliteDeferrable::null; // Those two are for table constraint only, + SqliteInitially initially = SqliteInitially::null; // because column has its own fields for that. + + protected: + QStringList getTablesInStatement(); + TokenList getTableTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteForeignKey> SqliteForeignKeyPtr; + +#endif // SQLITEFOREIGNKEY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteindexedcolumn.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteindexedcolumn.cpp new file mode 100644 index 0000000..5e65eab --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteindexedcolumn.cpp @@ -0,0 +1,52 @@ +#include "sqliteindexedcolumn.h" +#include "parser/statementtokenbuilder.h" + +SqliteIndexedColumn::SqliteIndexedColumn() +{ +} + +SqliteIndexedColumn::SqliteIndexedColumn(const SqliteIndexedColumn& other) : + SqliteStatement(other), name(other.name), sortOrder(other.sortOrder), collate(other.collate) +{ +} + +SqliteIndexedColumn::SqliteIndexedColumn(const QString &name, const QString &collate, SqliteSortOrder sortOrder) + : SqliteIndexedColumn() +{ + this->name = name; + this->sortOrder = sortOrder; + this->collate = collate; +} + +SqliteIndexedColumn::SqliteIndexedColumn(const QString& name) + : SqliteIndexedColumn() +{ + this->name = name; +} + +SqliteStatement*SqliteIndexedColumn::clone() +{ + return new SqliteIndexedColumn(*this); +} + +QStringList SqliteIndexedColumn::getColumnsInStatement() +{ + return getStrListFromValue(name); +} + +TokenList SqliteIndexedColumn::getColumnTokensInStatement() +{ + return getTokenListFromNamedKey("nm"); +} + + +TokenList SqliteIndexedColumn::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withOther(name, dialect); + if (!collate.isNull()) + builder.withSpace().withKeyword("COLLATE").withSpace().withOther(collate, dialect); + + builder.withSortOrder(sortOrder); + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteindexedcolumn.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteindexedcolumn.h new file mode 100644 index 0000000..c013d17 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteindexedcolumn.h @@ -0,0 +1,30 @@ +#ifndef SQLITEINDEXEDCOLUMN_H +#define SQLITEINDEXEDCOLUMN_H + +#include "sqlitestatement.h" +#include "sqlitesortorder.h" +#include <QString> + +class API_EXPORT SqliteIndexedColumn : public SqliteStatement +{ + public: + SqliteIndexedColumn(); + SqliteIndexedColumn(const SqliteIndexedColumn& other); + SqliteIndexedColumn(const QString& name, const QString& collate, SqliteSortOrder sortOrder); + explicit SqliteIndexedColumn(const QString& name); + + SqliteStatement* clone(); + + QString name = QString::null; + SqliteSortOrder sortOrder = SqliteSortOrder::null; + QString collate = QString::null; + + protected: + QStringList getColumnsInStatement(); + TokenList getColumnTokensInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteIndexedColumn> SqliteIndexedColumnPtr; + +#endif // SQLITEINDEXEDCOLUMN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteinsert.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteinsert.cpp new file mode 100644 index 0000000..6c26e8d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteinsert.cpp @@ -0,0 +1,214 @@ +#include "sqliteinsert.h" +#include "sqlitequerytype.h" +#include "sqliteexpr.h" +#include "sqliteselect.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" +#include "sqlitewith.h" + +SqliteInsert::SqliteInsert() +{ + queryType = SqliteQueryType::Insert; +} + +SqliteInsert::SqliteInsert(const SqliteInsert& other) : + SqliteQuery(other), replaceKw(other.replaceKw), defaultValuesKw(other.defaultValuesKw), onConflict(other.onConflict), database(other.database), + table(other.table), columnNames(other.columnNames) +{ + DEEP_COPY_COLLECTION(SqliteExpr, values); + DEEP_COPY_FIELD(SqliteSelect, select); + DEEP_COPY_FIELD(SqliteWith, with); +} + +SqliteInsert::SqliteInsert(bool replace, SqliteConflictAlgo onConflict, const QString &name1, const QString &name2, const QList<QString> &columns, + const QList<SqliteExpr *> &row, SqliteWith* with) : + SqliteInsert() +{ + initName(name1, name2); + initMode(replace, onConflict); + columnNames = columns; + values = row; + + this->with = with; + if (with) + with->setParent(this); + + foreach (SqliteExpr* expr, row) + expr->setParent(this); +} + +SqliteInsert::SqliteInsert(bool replace, SqliteConflictAlgo onConflict, const QString &name1, const QString &name2, const QList<QString> &columns, + SqliteSelect *select, SqliteWith* with) : + SqliteInsert() +{ + initName(name1, name2); + initMode(replace, onConflict); + + this->with = with; + if (with) + with->setParent(this); + + columnNames = columns; + this->select = select; + if (select) + select->setParent(this); +} + +SqliteInsert::SqliteInsert(bool replace, SqliteConflictAlgo onConflict, const QString &name1, const QString &name2, const QList<QString> &columns, + SqliteWith* with) : + SqliteInsert() +{ + initName(name1, name2); + initMode(replace, onConflict); + + this->with = with; + if (with) + with->setParent(this); + + columnNames = columns; + defaultValuesKw = true; +} + +SqliteInsert::~SqliteInsert() +{ +} + +SqliteStatement*SqliteInsert::clone() +{ + return new SqliteInsert(*this); +} + +QStringList SqliteInsert::getColumnsInStatement() +{ + QStringList columns; + columns += columnNames; + return columns; +} + +QStringList SqliteInsert::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteInsert::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteInsert::getColumnTokensInStatement() +{ + TokenList list; + foreach (TokenPtr token, getTokenListFromNamedKey("inscollist_opt", -1)) + { + if (token->type != Token::OTHER && token->type != Token::KEYWORD) + continue; + + list << token; + } + return list; +} + +TokenList SqliteInsert::getTableTokensInStatement() +{ + return getObjectTokenListFromFullname(); +} + +TokenList SqliteInsert::getDatabaseTokensInStatement() +{ + if (tokensMap.contains("fullname")) + return getDbTokenListFromFullname(); + + if (tokensMap.contains("nm")) + return extractPrintableTokens(tokensMap["nm"]); + + return TokenList(); +} + +QList<SqliteStatement::FullObject> SqliteInsert::getFullObjectsInStatement() +{ + QList<FullObject> result; + if (!tokensMap.contains("fullname")) + return result; + + // Table object + FullObject fullObj = getFullObjectFromFullname(FullObject::TABLE); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + return result; +} + +void SqliteInsert::initName(const QString& name1, const QString& name2) +{ + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; +} + +void SqliteInsert::initMode(bool replace, SqliteConflictAlgo onConflict) +{ + replaceKw = replace; + this->onConflict = onConflict; +} + +TokenList SqliteInsert::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + if (with) + builder.withStatement(with); + + if (replaceKw) + { + builder.withKeyword("REPLACE").withSpace(); + } + else + { + builder.withKeyword("INSERT").withSpace(); + if (onConflict != SqliteConflictAlgo::null) + builder.withKeyword("OR").withSpace().withKeyword(sqliteConflictAlgo(onConflict)).withSpace(); + } + + builder.withKeyword("INTO").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table, dialect).withSpace(); + + if (defaultValuesKw) + { + builder.withKeyword("DEFAULT").withSpace().withKeyword("VALUES"); + } + else + { + if (columnNames.size() > 0) + builder.withParLeft().withOtherList(columnNames, dialect).withParRight().withSpace(); + + if (select) + { + builder.withStatement(select); + } + else if (dialect == Dialect::Sqlite2) // Sqlite2 uses classic single row values + { + builder.withKeyword("VALUES").withSpace().withParLeft().withStatementList(values).withParRight(); + } + } + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteinsert.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteinsert.h new file mode 100644 index 0000000..4287680 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteinsert.h @@ -0,0 +1,57 @@ +#ifndef SQLITEINSERT_H +#define SQLITEINSERT_H + +#include "sqlitequery.h" +#include "sqliteconflictalgo.h" +#include <QString> +#include <QList> + +class SqliteSelect; +class SqliteExpr; +class SqliteWith; + +class API_EXPORT SqliteInsert : public SqliteQuery +{ + public: + SqliteInsert(); + SqliteInsert(const SqliteInsert& other); + SqliteInsert(bool replace, SqliteConflictAlgo onConflict, const QString& name1, + const QString& name2, const QList<QString>& columns, + const QList<SqliteExpr*>& row, SqliteWith* with); + SqliteInsert(bool replace, SqliteConflictAlgo onConflict, const QString& name1, + const QString& name2, const QList<QString>& columns, SqliteSelect* select, SqliteWith* with); + SqliteInsert(bool replace, SqliteConflictAlgo onConflict, const QString& name1, + const QString& name2, const QList<QString>& columns, SqliteWith* with); + ~SqliteInsert(); + + SqliteStatement* clone(); + + protected: + QStringList getColumnsInStatement(); + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getColumnTokensInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + + private: + void initName(const QString& name1, const QString& name2); + void initMode(bool replace, SqliteConflictAlgo onConflict); + + public: + bool replaceKw = false; + bool defaultValuesKw = false; + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; + QString database = QString::null; + QString table = QString::null; + QStringList columnNames; + QList<SqliteExpr*> values; + SqliteSelect* select = nullptr; + SqliteWith* with = nullptr; +}; + +typedef QSharedPointer<SqliteInsert> SqliteInsertPtr; + +#endif // SQLITEINSERT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitelimit.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitelimit.cpp new file mode 100644 index 0000000..c44228a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitelimit.cpp @@ -0,0 +1,79 @@ +#include "sqlitelimit.h" +#include "sqliteexpr.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" + +SqliteLimit::SqliteLimit() +{ +} + +SqliteLimit::SqliteLimit(const SqliteLimit& other) : + SqliteStatement(other) +{ + DEEP_COPY_FIELD(SqliteExpr, limit); + DEEP_COPY_FIELD(SqliteExpr, offset); +} + +SqliteLimit::SqliteLimit(SqliteExpr *expr) +{ + limit = expr; + if (expr) + expr->setParent(this); +} + +SqliteLimit::SqliteLimit(SqliteExpr *expr1, SqliteExpr *expr2, bool offsetKeyword) +{ + limit = expr1; + offset = expr2; + offsetKw = offsetKeyword; + if (expr1) + expr1->setParent(this); + + if (expr2) + expr2->setParent(this); +} + +SqliteLimit::SqliteLimit(const QVariant &positiveInt) +{ + limit = new SqliteExpr(); + limit->initLiteral(positiveInt); + limit->setParent(this); +} + +SqliteLimit::SqliteLimit(const QVariant &positiveInt1, const QVariant &positiveInt2) +{ + limit = new SqliteExpr(); + limit->initLiteral(positiveInt1); + limit->setParent(this); + + offset = new SqliteExpr(); + offset->initLiteral(positiveInt2); + offset->setParent(this); +} + +SqliteLimit::~SqliteLimit() +{ +} + +SqliteStatement*SqliteLimit::clone() +{ + return new SqliteLimit(*this); +} + + +TokenList SqliteLimit::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withKeyword("LIMIT").withStatement(limit); + if (offset) + { + if (offsetKw) + builder.withSpace().withKeyword("OFFSET"); + else + builder.withOperator(","); + + builder.withStatement(offset); + } + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitelimit.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitelimit.h new file mode 100644 index 0000000..284cf49 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitelimit.h @@ -0,0 +1,31 @@ +#ifndef SQLITELIMIT_H +#define SQLITELIMIT_H + +#include "sqlitestatement.h" + +class SqliteExpr; + +class API_EXPORT SqliteLimit : public SqliteStatement +{ + public: + SqliteLimit(); + SqliteLimit(const SqliteLimit& other); + explicit SqliteLimit(SqliteExpr* expr); + SqliteLimit(SqliteExpr* expr1, SqliteExpr* expr2, bool offsetKeyword); + explicit SqliteLimit(const QVariant& positiveInt); + SqliteLimit(const QVariant& positiveInt1, const QVariant& positiveInt2); + ~SqliteLimit(); + + SqliteStatement* clone(); + + SqliteExpr* limit = nullptr; + SqliteExpr* offset = nullptr; + bool offsetKw = false; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteLimit> SqliteLimitPtr; + +#endif // SQLITELIMIT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteorderby.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteorderby.cpp new file mode 100644 index 0000000..3bb1b44 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteorderby.cpp @@ -0,0 +1,41 @@ +#include "sqliteorderby.h" +#include "sqliteexpr.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" + +SqliteOrderBy::SqliteOrderBy() +{ +} + +SqliteOrderBy::SqliteOrderBy(const SqliteOrderBy& other) : + SqliteStatement(other), order(other.order) +{ + DEEP_COPY_FIELD(SqliteExpr, expr); +} + +SqliteOrderBy::SqliteOrderBy(SqliteExpr *expr, SqliteSortOrder order) +{ + this->expr = expr; + this->order = order; + if (expr) + expr->setParent(this); +} + +SqliteOrderBy::~SqliteOrderBy() +{ +} + +SqliteStatement*SqliteOrderBy::clone() +{ + return new SqliteOrderBy(*this); +} + +TokenList SqliteOrderBy::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withStatement(expr); + if (order != SqliteSortOrder::null) + builder.withSpace().withKeyword(sqliteSortOrder(order)); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteorderby.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteorderby.h new file mode 100644 index 0000000..598423d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteorderby.h @@ -0,0 +1,28 @@ +#ifndef SQLITEORDERBY_H +#define SQLITEORDERBY_H + +#include "sqlitestatement.h" +#include "sqlitesortorder.h" + +class SqliteExpr; + +class API_EXPORT SqliteOrderBy : public SqliteStatement +{ + public: + SqliteOrderBy(); + SqliteOrderBy(const SqliteOrderBy& other); + SqliteOrderBy(SqliteExpr* expr, SqliteSortOrder order); + ~SqliteOrderBy(); + + SqliteStatement* clone(); + + SqliteExpr* expr = nullptr; + SqliteSortOrder order; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteOrderBy> SqliteOrderByPtr; + +#endif // SQLITEORDERBY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitepragma.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitepragma.cpp new file mode 100644 index 0000000..0e4f056 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitepragma.cpp @@ -0,0 +1,109 @@ +#include "sqlitepragma.h" +#include "sqlitequerytype.h" + +#include <parser/statementtokenbuilder.h> + +SqlitePragma::SqlitePragma() +{ + queryType = SqliteQueryType::Pragma; +} + +SqlitePragma::SqlitePragma(const SqlitePragma& other) : + SqliteQuery(other), database(other.database), pragmaName(other.pragmaName), value(other.value), equalsOp(other.equalsOp), parenthesis(other.parenthesis) +{ +} + +SqlitePragma::SqlitePragma(const QString &name1, const QString &name2) + : SqlitePragma() +{ + initName(name1, name2); +} + +SqlitePragma::SqlitePragma(const QString &name1, const QString &name2, const QVariant& value, bool equals) + : SqlitePragma() +{ + initName(name1, name2); + this->value = value; + if (equals) + equalsOp = true; + else + parenthesis = true; +} + +SqlitePragma::SqlitePragma(const QString &name1, const QString &name2, const QString &value, bool equals) + : SqlitePragma() +{ + initName(name1, name2); + this->value = value; + if (equals) + equalsOp = true; + else + parenthesis = true; +} + +SqliteStatement*SqlitePragma::clone() +{ + return new SqlitePragma(*this); +} + +QStringList SqlitePragma::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqlitePragma::getDatabaseTokensInStatement() +{ + if (dialect == Dialect::Sqlite2 || database.isNull()) + return TokenList(); + + return getTokenListFromNamedKey("nm"); +} + +QList<SqliteStatement::FullObject> SqlitePragma::getFullObjectsInStatement() +{ + QList<FullObject> result; + if (dialect == Dialect::Sqlite2 || database.isNull()) + return result; + + // Db object + FullObject fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + return result; +} + +void SqlitePragma::initName(const QString &name1, const QString &name2) +{ + if (!name2.isNull()) + { + database = name1; + pragmaName = name2; + } + else + pragmaName = name1; +} + +TokenList SqlitePragma::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("PRAGMA").withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(pragmaName, dialect); + + if (equalsOp) + builder.withSpace().withOperator("=").withSpace().withLiteralValue(value); + else if (parenthesis) + builder.withParLeft().withLiteralValue(value).withParRight(); + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitepragma.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitepragma.h new file mode 100644 index 0000000..364a16f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitepragma.h @@ -0,0 +1,41 @@ +#ifndef SQLITEPRAGMA_H +#define SQLITEPRAGMA_H + +#include "sqlitequery.h" + +#include <QString> +#include <QVariant> + +class API_EXPORT SqlitePragma : public SqliteQuery +{ + public: + SqlitePragma(); + SqlitePragma(const SqlitePragma& other); + SqlitePragma(const QString& name1, const QString& name2); + SqlitePragma(const QString& name1, const QString& name2, const QVariant& value, + bool equals); + SqlitePragma(const QString& name1, const QString& name2, const QString& value, + bool equals); + + SqliteStatement* clone(); + + protected: + QStringList getDatabasesInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + + private: + void initName(const QString& name1, const QString& name2); + + public: + QString database = QString::null; + QString pragmaName = QString::null; + QVariant value = QVariant(); + bool equalsOp = false; + bool parenthesis = false; +}; + +typedef QSharedPointer<SqlitePragma> SqlitePragmaPtr; + +#endif // SQLITEPRAGMA_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequery.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequery.cpp new file mode 100644 index 0000000..19c3c40 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequery.cpp @@ -0,0 +1,51 @@ +#include "sqlitequery.h" + +SqliteQuery::SqliteQuery() +{ +} + +SqliteQuery::SqliteQuery(const SqliteQuery& other) : + SqliteStatement(other), queryType(other.queryType), explain(other.explain), queryPlan(other.queryPlan) +{ +} + +bool SqliteQuery::isReadOnly() +{ + bool readOnly = true; + switch (queryType) + { + case SqliteQueryType::EMPTY: + case SqliteQueryType::Analyze: + case SqliteQueryType::Pragma: + case SqliteQueryType::Select: + readOnly = true; + break; + case SqliteQueryType::UNDEFINED: + case SqliteQueryType::AlterTable: + case SqliteQueryType::Attach: + case SqliteQueryType::BeginTrans: + case SqliteQueryType::CommitTrans: + case SqliteQueryType::Copy: + case SqliteQueryType::CreateIndex: + case SqliteQueryType::CreateTable: + case SqliteQueryType::CreateTrigger: + case SqliteQueryType::CreateView: + case SqliteQueryType::CreateVirtualTable: + case SqliteQueryType::Delete: + case SqliteQueryType::Detach: + case SqliteQueryType::DropIndex: + case SqliteQueryType::DropTable: + case SqliteQueryType::DropTrigger: + case SqliteQueryType::DropView: + case SqliteQueryType::Insert: + case SqliteQueryType::Reindex: + case SqliteQueryType::Release: + case SqliteQueryType::Rollback: + case SqliteQueryType::Savepoint: + case SqliteQueryType::Update: + case SqliteQueryType::Vacuum: + readOnly = false; + break; + } + return readOnly; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequery.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequery.h new file mode 100644 index 0000000..1667dc9 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequery.h @@ -0,0 +1,30 @@ +#ifndef SQLITEQUERY_H +#define SQLITEQUERY_H + +#include "sqlitestatement.h" +#include "sqlitequerytype.h" + +/** + * @addtogroup sqlite_statement + * @brief The SqliteQuery class + */ +class API_EXPORT SqliteQuery : public SqliteStatement +{ + public: + SqliteQuery(); + SqliteQuery(const SqliteQuery& other); + + bool isReadOnly(); + + SqliteQueryType queryType = SqliteQueryType::UNDEFINED; + + bool explain = false; + bool queryPlan = false; +}; + +/** + * Shared pointer to SqliteQuery. + */ +typedef QSharedPointer<SqliteQuery> SqliteQueryPtr; + +#endif // SQLITEQUERY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequerytype.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequerytype.cpp new file mode 100644 index 0000000..c369e0e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequerytype.cpp @@ -0,0 +1,66 @@ +#include "sqlitequerytype.h" + +QString sqliteQueryTypeToString(const SqliteQueryType& type) +{ + switch (type) + { + case SqliteQueryType::UNDEFINED: + return "UNDEFINED"; + case SqliteQueryType::EMPTY: + return "EMPTY"; + case SqliteQueryType::AlterTable: + return "AlterTable"; + case SqliteQueryType::Analyze: + return "Analyze"; + case SqliteQueryType::Attach: + return "Attach"; + case SqliteQueryType::BeginTrans: + return "BeginTrans"; + case SqliteQueryType::CommitTrans: + return "CommitTrans"; + case SqliteQueryType::Copy: + return "Copy"; + case SqliteQueryType::CreateIndex: + return "CreateIndex"; + case SqliteQueryType::CreateTable: + return "CreateTable"; + case SqliteQueryType::CreateTrigger: + return "CreateTrigger"; + case SqliteQueryType::CreateView: + return "CreateView"; + case SqliteQueryType::CreateVirtualTable: + return "CreateVirtualTable"; + case SqliteQueryType::Delete: + return "Delete"; + case SqliteQueryType::Detach: + return "Detach"; + case SqliteQueryType::DropIndex: + return "DropIndex"; + case SqliteQueryType::DropTable: + return "DropTable"; + case SqliteQueryType::DropTrigger: + return "DropTrigger"; + case SqliteQueryType::DropView: + return "DropView"; + case SqliteQueryType::Insert: + return "Insert"; + case SqliteQueryType::Pragma: + return "Pragma"; + case SqliteQueryType::Reindex: + return "Reindex"; + case SqliteQueryType::Release: + return "Release"; + case SqliteQueryType::Rollback: + return "Rollback"; + case SqliteQueryType::Savepoint: + return "Savepoint"; + case SqliteQueryType::Select: + return "Select"; + case SqliteQueryType::Update: + return "Update"; + case SqliteQueryType::Vacuum: + return "Vacuum"; + default: + return QString::null; + } +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequerytype.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequerytype.h new file mode 100644 index 0000000..763fcfa --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitequerytype.h @@ -0,0 +1,41 @@ +#ifndef SQLITEQUERYTYPE_H +#define SQLITEQUERYTYPE_H + +#include "coreSQLiteStudio_global.h" +#include <QString> + +enum class SqliteQueryType +{ + UNDEFINED, + EMPTY, // still can hold comments + AlterTable, + Analyze, + Attach, + BeginTrans, + CommitTrans, + Copy, + CreateIndex, + CreateTable, + CreateTrigger, + CreateView, + CreateVirtualTable, + Delete, + Detach, + DropIndex, + DropTable, + DropTrigger, + DropView, + Insert, + Pragma, + Reindex, + Release, + Rollback, + Savepoint, + Select, + Update, + Vacuum +}; + +QString API_EXPORT sqliteQueryTypeToString(const SqliteQueryType& type); + +#endif // SQLITEQUERYTYPE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteraise.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteraise.cpp new file mode 100644 index 0000000..b606baa --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteraise.cpp @@ -0,0 +1,70 @@ +#include "sqliteraise.h" +#include "parser/statementtokenbuilder.h" + +SqliteRaise::SqliteRaise() +{ +} + +SqliteRaise::SqliteRaise(const SqliteRaise& other) : + SqliteStatement(other), type(other.type), message(other.message) +{ +} + +SqliteRaise::SqliteRaise(const QString &type) +{ + this->type = raiseType(type); +} + +SqliteRaise::SqliteRaise(const QString &type, const QString &text) +{ + this->type = raiseType(type); + message = text; +} + +SqliteStatement*SqliteRaise::clone() +{ + return new SqliteRaise(*this); +} + +SqliteRaise::Type SqliteRaise::raiseType(const QString &value) +{ + QString upper = value.toUpper(); + if (upper == "IGNORE") + return SqliteRaise::Type::IGNORE; + else if (upper == "ROLLBACK") + return SqliteRaise::Type::ROLLBACK; + else if (upper == "ABORT") + return SqliteRaise::Type::ABORT; + else if (upper == "FAIL") + return SqliteRaise::Type::FAIL; + else + return SqliteRaise::Type::null; +} + +QString SqliteRaise::raiseType(SqliteRaise::Type value) +{ + switch (value) + { + case SqliteRaise::Type::IGNORE: + return "IGNORE"; + case SqliteRaise::Type::ROLLBACK: + return "ROLLBACK"; + case SqliteRaise::Type::ABORT: + return "ABORT"; + case SqliteRaise::Type::FAIL: + return "FAIL"; + default: + return QString::null; + } +} + +TokenList SqliteRaise::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withKeyword("RAISE").withSpace().withParLeft().withKeyword(raiseType(type)); + if (type != Type::IGNORE) + builder.withOperator(",").withSpace().withString(message); + + builder.withParRight(); + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteraise.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteraise.h new file mode 100644 index 0000000..1b844f8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteraise.h @@ -0,0 +1,38 @@ +#ifndef SQLITERAISE_H +#define SQLITERAISE_H + +#include "sqlitestatement.h" +#include <QString> + +class API_EXPORT SqliteRaise : public SqliteStatement +{ + public: + enum class Type + { + IGNORE, + ROLLBACK, + ABORT, + FAIL, + null + }; + + SqliteRaise(); + SqliteRaise(const SqliteRaise& other); + explicit SqliteRaise(const QString& type); + SqliteRaise(const QString& type, const QString& text); + + SqliteStatement* clone(); + + static Type raiseType(const QString& value); + static QString raiseType(Type value); + + Type type = Type::null; + QString message = QString::null; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteRaise> SqliteRaisePtr; + +#endif // SQLITERAISE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitereindex.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitereindex.cpp new file mode 100644 index 0000000..7584a37 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitereindex.cpp @@ -0,0 +1,82 @@ +#include "sqlitereindex.h" +#include "sqlitequerytype.h" + +#include <parser/statementtokenbuilder.h> + +SqliteReindex::SqliteReindex() +{ + queryType = SqliteQueryType::Reindex; +} + +SqliteReindex::SqliteReindex(const SqliteReindex& other) : + SqliteQuery(other), database(other.database), table(other.table) +{ +} + +SqliteReindex::SqliteReindex(const QString& name1, const QString& name2) + : SqliteReindex() +{ + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; +} + +SqliteStatement*SqliteReindex::clone() +{ + return new SqliteReindex(*this); +} + +QStringList SqliteReindex::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteReindex::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteReindex::getTableTokensInStatement() +{ + return getObjectTokenListFromNmDbnm(); +} + +TokenList SqliteReindex::getDatabaseTokensInStatement() +{ + return getDbTokenListFromNmDbnm(); +} + +QList<SqliteStatement::FullObject> SqliteReindex::getFullObjectsInStatement() +{ + QList<FullObject> result; + + // Table object + FullObject fullObj = getFullObjectFromNmDbnm(FullObject::TABLE); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + result << fullObj; + + return result; +} + +TokenList SqliteReindex::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("REINDEX"); + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table).withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitereindex.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitereindex.h new file mode 100644 index 0000000..642b5bf --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitereindex.h @@ -0,0 +1,31 @@ +#ifndef SQLITEREINDEX_H +#define SQLITEREINDEX_H + +#include "sqlitequery.h" + +#include <QString> + +class API_EXPORT SqliteReindex : public SqliteQuery +{ + public: + SqliteReindex(); + SqliteReindex(const SqliteReindex& other); + SqliteReindex(const QString& name1, const QString& name2); + + SqliteStatement* clone(); + + QString database = QString::null; + QString table = QString::null; + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<SqliteStatement::FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteReindex> SqliteReindexPtr; + +#endif // SQLITEREINDEX_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterelease.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterelease.cpp new file mode 100644 index 0000000..8510524 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterelease.cpp @@ -0,0 +1,39 @@ +#include "sqliterelease.h" +#include "sqlitequerytype.h" + +#include <parser/statementtokenbuilder.h> + +SqliteRelease::SqliteRelease() +{ + queryType = SqliteQueryType::Release; +} + +SqliteRelease::SqliteRelease(const SqliteRelease& other) : + SqliteQuery(other), name(other.name), savepointKw(other.savepointKw) +{ +} + +SqliteRelease::SqliteRelease(bool savepointKw, const QString& name) + : SqliteRelease() +{ + this->name = name; + this->savepointKw = savepointKw; +} + +SqliteStatement*SqliteRelease::clone() +{ + return new SqliteRelease(*this); +} + +TokenList SqliteRelease::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("RELEASE").withSpace(); + if (savepointKw) + builder.withKeyword("SAVEPOINT").withSpace(); + + builder.withOther(name, dialect).withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterelease.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterelease.h new file mode 100644 index 0000000..d115669 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterelease.h @@ -0,0 +1,26 @@ +#ifndef SQLITERELEASE_H +#define SQLITERELEASE_H + +#include "sqlitequery.h" + +#include <QString> + +class API_EXPORT SqliteRelease : public SqliteQuery +{ + public: + SqliteRelease(); + SqliteRelease(const SqliteRelease& other); + SqliteRelease(bool savepointKw, const QString &name); + + SqliteStatement* clone(); + + QString name = QString::null; + bool savepointKw = false; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteRelease> SqliteReleasePtr; + +#endif // SQLITERELEASE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterollback.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterollback.cpp new file mode 100644 index 0000000..a13fd4c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterollback.cpp @@ -0,0 +1,57 @@ +#include "sqliterollback.h" +#include "sqlitequerytype.h" + +#include <parser/statementtokenbuilder.h> + +SqliteRollback::SqliteRollback() +{ + queryType = SqliteQueryType::Rollback; +} + +SqliteRollback::SqliteRollback(const SqliteRollback& other) : + SqliteQuery(other), transactionKw(other.transactionKw), toKw(other.toKw), savepointKw(other.savepointKw), name(other.name) +{ +} + +SqliteRollback::SqliteRollback(bool transactionKw, const QString& name) + : SqliteRollback() +{ + this->name = name; + this->transactionKw = transactionKw; +} + +SqliteRollback::SqliteRollback(bool transactionKw, bool savePoint, const QString& name) +{ + // we ignore name from trans_opt, + // it's not officialy supported in sqlite3 + this->name = name; + this->transactionKw = transactionKw; + toKw = true; + savepointKw = savePoint; +} + +SqliteStatement*SqliteRollback::clone() +{ + return new SqliteRollback(*this); +} + +TokenList SqliteRollback::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("ROLLBACK").withSpace(); + if (transactionKw) + builder.withKeyword("TRANSACTION").withSpace(); + + if (!name.isNull()) + { + builder.withKeyword("TO").withSpace(); + if (savepointKw) + builder.withKeyword("SAVEPOINT").withSpace(); + + builder.withOther(name, dialect); + } + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterollback.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterollback.h new file mode 100644 index 0000000..1dc1574 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliterollback.h @@ -0,0 +1,28 @@ +#ifndef SQLITEROLLBACK_H +#define SQLITEROLLBACK_H + +#include "sqlitequery.h" +#include <QString> + +class API_EXPORT SqliteRollback : public SqliteQuery +{ + public: + SqliteRollback(); + SqliteRollback(const SqliteRollback& other); + SqliteRollback(bool transactionKw, const QString& name); + SqliteRollback(bool transactionKw, bool savePoint, const QString& name); + + SqliteStatement* clone(); + + bool transactionKw = false; + bool toKw = false; + bool savepointKw = false; + QString name = QString::null; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteRollback> SqliteRollPtr; + +#endif // SQLITEROLLBACK_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesavepoint.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesavepoint.cpp new file mode 100644 index 0000000..9003086 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesavepoint.cpp @@ -0,0 +1,32 @@ +#include "sqlitesavepoint.h" +#include "sqlitequerytype.h" + +#include <parser/statementtokenbuilder.h> + +SqliteSavepoint::SqliteSavepoint() +{ + queryType = SqliteQueryType::Savepoint; +} + +SqliteSavepoint::SqliteSavepoint(const SqliteSavepoint& other) : + SqliteQuery(other), name(other.name) +{ +} + +SqliteSavepoint::SqliteSavepoint(const QString &name) + : SqliteSavepoint() +{ + this->name = name; +} + +SqliteStatement*SqliteSavepoint::clone() +{ + return new SqliteSavepoint(*this); +} + +TokenList SqliteSavepoint::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withKeyword("SAVEPOINT").withSpace().withOther(name, dialect).withOperator(";"); + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesavepoint.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesavepoint.h new file mode 100644 index 0000000..bd75c76 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesavepoint.h @@ -0,0 +1,25 @@ +#ifndef SQLITESAVEPOINT_H +#define SQLITESAVEPOINT_H + +#include "sqlitequery.h" + +#include <QString> + +class API_EXPORT SqliteSavepoint : public SqliteQuery +{ + public: + SqliteSavepoint(); + SqliteSavepoint(const SqliteSavepoint& other); + explicit SqliteSavepoint(const QString& name); + + SqliteStatement* clone(); + + QString name = QString::null; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteSavepoint> SqliteSavepointPtr; + +#endif // SQLITESAVEPOINT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteselect.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteselect.cpp new file mode 100644 index 0000000..5452795 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteselect.cpp @@ -0,0 +1,783 @@ +#include "sqliteselect.h" +#include "sqlitequerytype.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" +#include "sqlitewith.h" +#include <QSet> + +SqliteSelect::SqliteSelect() +{ + queryType = SqliteQueryType::Select; +} + +SqliteSelect::SqliteSelect(const SqliteSelect& other) : + SqliteQuery(other) +{ + DEEP_COPY_COLLECTION(Core, coreSelects); + DEEP_COPY_FIELD(SqliteWith, with); +} + +SqliteSelect* SqliteSelect::append(Core* core) +{ + SqliteSelect* select = new SqliteSelect(); + select->coreSelects << core; + core->setParent(select); + return select; +} + +SqliteSelect* SqliteSelect::append(SqliteSelect* select, SqliteSelect::CompoundOperator op, Core* core) +{ + if (!select) + select = new SqliteSelect(); + + core->compoundOp = op; + select->coreSelects << core; + core->setParent(select); + return select; +} + +SqliteSelect* SqliteSelect::append(const QList<QList<SqliteExpr*>>& values) +{ + return append(nullptr, CompoundOperator::null, values); +} + +SqliteSelect* SqliteSelect::append(SqliteSelect* select, SqliteSelect::CompoundOperator op, const QList<QList<SqliteExpr*>>& values) +{ + if (!select) + select = new SqliteSelect(); + + bool first = true; + + Core::ResultColumn* resCol = nullptr; + QList<Core::ResultColumn*> resColList; + foreach (const QList<SqliteExpr*>& singleValues, values) + { + Core* core = new Core(); + core->setParent(select); + core->compoundOp = op; + core->valuesMode = true; + if (first) + { + op = CompoundOperator::UNION_ALL; + first = false; + } + select->coreSelects << core; + + resColList.clear(); + foreach (SqliteExpr* value, singleValues) + { + resCol = new Core::ResultColumn(value, false, QString::null); + resCol->rebuildTokens(); + resCol->setParent(core); + core->resultColumns << resCol; + } + } + + return select; +} + +SqliteStatement*SqliteSelect::clone() +{ + return new SqliteSelect(*this); +} + +QString SqliteSelect::compoundOperator(SqliteSelect::CompoundOperator op) +{ + switch (op) + { + case SqliteSelect::CompoundOperator::UNION: + return "UNION"; + case SqliteSelect::CompoundOperator::UNION_ALL: + return "UNION ALL"; + case SqliteSelect::CompoundOperator::INTERSECT: + return "INTERSECT"; + case SqliteSelect::CompoundOperator::EXCEPT: + return "EXCEPT"; + case SqliteSelect::CompoundOperator::null: + break; + } + return QString::null; +} + +SqliteSelect::CompoundOperator SqliteSelect::compoundOperator(const QString& op) +{ + QString upStr = op.toUpper(); + if (upStr == "UNION") + return CompoundOperator::UNION; + else if (upStr == "UNION ALL") + return CompoundOperator::UNION_ALL; + else if (upStr == "EXCEPT") + return CompoundOperator::EXCEPT; + else if (upStr == "INTERSECT") + return CompoundOperator::INTERSECT; + else + return CompoundOperator::null; +} + +void SqliteSelect::reset() +{ + foreach (Core* core, coreSelects) + delete core; + + coreSelects.clear(); +} + +void SqliteSelect::setWith(SqliteWith* with) +{ + this->with = with; + if (with) + with->setParent(this); +} + +SqliteSelect::Core::Core() +{ +} + +SqliteSelect::Core::Core(const SqliteSelect::Core& other) : + SqliteStatement(other), compoundOp(other.compoundOp), distinctKw(other.distinctKw), allKw(other.allKw) +{ + DEEP_COPY_COLLECTION(ResultColumn, resultColumns); + DEEP_COPY_FIELD(JoinSource, from); + DEEP_COPY_FIELD(SqliteExpr, where); + DEEP_COPY_FIELD(SqliteExpr, having); + DEEP_COPY_COLLECTION(SqliteExpr, groupBy); + DEEP_COPY_COLLECTION(SqliteOrderBy, orderBy); + DEEP_COPY_FIELD(SqliteLimit, limit); +} + +SqliteSelect::Core::Core(int distinct, const QList<ResultColumn *> &resCols, SqliteSelect::Core::JoinSource *src, SqliteExpr *where, const QList<SqliteExpr *> &groupBy, SqliteExpr *having, const QList<SqliteOrderBy*>& orderBy, SqliteLimit* limit) +{ + if (distinct == 1) + distinctKw = true; + else if (distinct == 2) + allKw = true; + + from = src; + this->where = where; + this->having = having; + this->groupBy = groupBy; + resultColumns = resCols; + this->limit = limit; + this->orderBy = orderBy; + + if (from) + from->setParent(this); + + if (where) + where->setParent(this); + + if (having) + having->setParent(this); + + if (limit) + limit->setParent(this); + + foreach (SqliteOrderBy* order, orderBy) + order->setParent(this); + + foreach (SqliteExpr* expr, groupBy) + expr->setParent(this); + + foreach (SqliteSelect::Core::ResultColumn* resCol, resCols) + resCol->setParent(this); +} + +SqliteStatement*SqliteSelect::Core::clone() +{ + return new SqliteSelect::Core(*this); +} + +SqliteSelect::Core::ResultColumn::ResultColumn() +{ +} + +SqliteSelect::Core::ResultColumn::ResultColumn(const SqliteSelect::Core::ResultColumn& other) : + SqliteStatement(other), star(other.star), asKw(other.asKw), alias(other.alias), table(other.table) +{ + DEEP_COPY_FIELD(SqliteExpr, expr); +} + +SqliteSelect::Core::ResultColumn::ResultColumn(SqliteExpr *expr, bool asKw, const QString &alias) +{ + this->expr = expr; + this->asKw = asKw; + this->alias = alias; + if (expr) + expr->setParent(this); +} + +SqliteSelect::Core::ResultColumn::ResultColumn(bool star, const QString &table) +{ + this->star = star; + this->table = table; +} + +SqliteSelect::Core::ResultColumn::ResultColumn(bool star) +{ + this->star = star; +} + +bool SqliteSelect::Core::ResultColumn::isRowId() +{ + if (!expr) + return false; + + if (expr->column.isEmpty()) + return false; + + return expr->column.compare("rowid", Qt::CaseInsensitive) == 0; +} + +SqliteStatement*SqliteSelect::Core::ResultColumn::clone() +{ + return new SqliteSelect::Core::ResultColumn(*this); +} + +QStringList SqliteSelect::Core::ResultColumn::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +TokenList SqliteSelect::Core::ResultColumn::getTableTokensInStatement() +{ + // To avoid warnings about missing "nm" key + if (table.isNull()) + return TokenList(); + + // Now, we know table was specified + return getTokenListFromNamedKey("nm"); +} + +QList<SqliteStatement::FullObject> SqliteSelect::Core::ResultColumn::getFullObjectsInStatement() +{ + QList<FullObject> result; + + // Table object + TokenList tableTokens = getTableTokensInStatement(); + FullObject fullObj; + if (tableTokens.size() > 0) + fullObj = getFullObject(FullObject::TABLE, dbTokenForFullObjects, tableTokens[0]); + + if (fullObj.isValid()) + result << fullObj; + + return result; +} + +SqliteSelect::Core::SingleSource::SingleSource() +{ +} + +SqliteSelect::Core::SingleSource::SingleSource(const SqliteSelect::Core::SingleSource& other) : + SqliteStatement(other), database(other.database), table(other.table), alias(other.alias), asKw(other.asKw), indexedByKw(other.indexedByKw), + notIndexedKw(other.notIndexedKw), indexedBy(other.indexedBy) +{ + DEEP_COPY_FIELD(SqliteSelect, select); + DEEP_COPY_FIELD(JoinSource, joinSource); +} + +SqliteSelect::Core::SingleSource::SingleSource(const QString& name1, const QString& name2, bool asKw, const QString& alias, bool notIndexedKw, const QString& indexedBy) +{ + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; + + this->asKw = asKw; + this->alias = alias; + this->indexedBy = indexedBy; + this->indexedByKw = !(indexedBy.isNull()); + this->notIndexedKw = notIndexedKw; +} + +SqliteSelect::Core::SingleSource::SingleSource(SqliteSelect *select, bool asKw, const QString &alias) +{ + this->select = select; + this->asKw = asKw; + this-> alias = alias; + if (select) + select->setParent(this); +} + +SqliteSelect::Core::SingleSource::SingleSource(JoinSource *src, bool asKw, const QString &alias) +{ + this->joinSource = src; + this->asKw = asKw; + this-> alias = alias; + if (src) + src->setParent(this); +} + +SqliteStatement*SqliteSelect::Core::SingleSource::clone() +{ + return new SqliteSelect::Core::SingleSource(*this); +} + +QStringList SqliteSelect::Core::SingleSource::getTablesInStatement() +{ + // This method returns tables only! No aliases. + // Otherwise the completion sorter won't work correctly. + // To return tables with aliases use/create other method. + return getStrListFromValue(table); +} + +QStringList SqliteSelect::Core::SingleSource::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteSelect::Core::SingleSource::getTableTokensInStatement() +{ + if (table.isNull()) + return TokenList(); + + return getObjectTokenListFromNmDbnm(); +} + +TokenList SqliteSelect::Core::SingleSource::getDatabaseTokensInStatement() +{ + if (database.isNull()) + return TokenList(); + + return getDbTokenListFromNmDbnm(); +} + +QList<SqliteStatement::FullObject> SqliteSelect::Core::SingleSource::getFullObjectsInStatement() +{ + QList<FullObject> result; + + // Table object + if (!table.isNull()) + { + FullObject fullObj = getFullObjectFromNmDbnm(FullObject::TABLE); + + if (fullObj.isValid()) + result << fullObj; + } + + // Db object + if (!database.isNull()) + { + FullObject fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + } + + return result; +} + +SqliteSelect::Core::JoinConstraint::JoinConstraint() +{ +} + +SqliteSelect::Core::JoinConstraint::JoinConstraint(const SqliteSelect::Core::JoinConstraint& other) : + SqliteStatement(other), columnNames(other.columnNames) +{ + DEEP_COPY_FIELD(SqliteExpr, expr); +} + +SqliteSelect::Core::JoinConstraint::JoinConstraint(SqliteExpr *expr) +{ + this->expr = expr; + if (expr) + expr->setParent(this); +} + +SqliteSelect::Core::JoinConstraint::JoinConstraint(const QList<QString> &strList) +{ + columnNames = strList; +} + +SqliteStatement*SqliteSelect::Core::JoinConstraint::clone() +{ + return new SqliteSelect::Core::JoinConstraint(*this); +} + +QStringList SqliteSelect::Core::JoinConstraint::getColumnsInStatement() +{ + QStringList list; + list += columnNames; + return list; +} + +TokenList SqliteSelect::Core::JoinConstraint::getColumnTokensInStatement() +{ + TokenList list; + foreach (TokenPtr token, getTokenListFromNamedKey("inscollist", -1)) + { + if (token->type == Token::OPERATOR) // a COMMA + continue; + + list << token; + } + return list; +} + +SqliteSelect::Core::JoinOp::JoinOp() +{ +} + +SqliteSelect::Core::JoinOp::JoinOp(const SqliteSelect::Core::JoinOp& other) : + SqliteStatement(other), comma(other.comma), joinKw(other.joinKw), naturalKw(other.naturalKw), leftKw(other.leftKw), outerKw(other.outerKw), + innerKw(other.innerKw), crossKw(other.crossKw), rightKw(other.rightKw), fullKw(other.fullKw), customKw1(other.customKw1), + customKw2(other.customKw2), customKw3(other.customKw3) +{ +} + +SqliteSelect::Core::JoinOp::JoinOp(bool comma) +{ + this->comma = comma; + this->joinKw = !comma; +} + +SqliteSelect::Core::JoinOp::JoinOp(const QString &joinToken) +{ + this->joinKw = true; + init(joinToken); +} + +SqliteSelect::Core::JoinOp::JoinOp(const QString &joinToken, const QString &word1) +{ + this->joinKw = true; + init(joinToken); + init(word1); +} + +SqliteSelect::Core::JoinOp::JoinOp(const QString &joinToken, const QString &word1, const QString &word2) +{ + this->joinKw = true; + init(joinToken); + init(word1); + init(word2); +} + +SqliteStatement*SqliteSelect::Core::JoinOp::clone() +{ + return new SqliteSelect::Core::JoinOp(*this); +} + +void SqliteSelect::Core::JoinOp::init(const QString &str) +{ + QString upStr = str.toUpper(); + if (upStr == "NATURAL") + naturalKw = true; + else if (upStr == "LEFT") + leftKw = true; + else if (upStr == "RIGHT") + rightKw = true; + else if (upStr == "FULL") + fullKw = true; + else if (upStr == "OUTER") + outerKw = true; + else if (upStr == "INNER") + innerKw = true; + else if (upStr == "CROSS") + crossKw = true; + else if (customKw1.isNull()) + customKw1 = str; + else if (customKw2.isNull()) + customKw2 = str; + else + customKw3 = str; +} + + +SqliteSelect::Core::JoinSourceOther::JoinSourceOther() +{ +} + +SqliteSelect::Core::JoinSourceOther::JoinSourceOther(const SqliteSelect::Core::JoinSourceOther& other) : + SqliteStatement(other) +{ + DEEP_COPY_FIELD(JoinOp, joinOp); + DEEP_COPY_FIELD(SingleSource, singleSource); + DEEP_COPY_FIELD(JoinConstraint, joinConstraint); +} + +SqliteSelect::Core::JoinSourceOther::JoinSourceOther(SqliteSelect::Core::JoinOp *op, SingleSource *src, JoinConstraint *constr) +{ + this->joinConstraint = constr; + this->joinOp = op; + this->singleSource = src; + if (constr) + constr->setParent(this); + + if (op) + op->setParent(this); + + if (src) + src->setParent(this); +} + +SqliteStatement*SqliteSelect::Core::JoinSourceOther::clone() +{ + return new SqliteSelect::Core::JoinSourceOther(*this); +} + + +SqliteSelect::Core::JoinSource::JoinSource() +{ +} + +SqliteSelect::Core::JoinSource::JoinSource(const JoinSource& other) : + SqliteStatement(other) +{ + DEEP_COPY_FIELD(SingleSource, singleSource); + DEEP_COPY_COLLECTION(JoinSourceOther, otherSources); +} + +SqliteSelect::Core::JoinSource::JoinSource(SqliteSelect::Core::SingleSource *singleSource, const QList<SqliteSelect::Core::JoinSourceOther *> &list) +{ + this->singleSource = singleSource; + this->otherSources = list; + if (singleSource) + singleSource->setParent(this); + + foreach (JoinSourceOther* other, otherSources) + other->setParent(this); +} + +SqliteStatement*SqliteSelect::Core::JoinSource::clone() +{ + return new SqliteSelect::Core::JoinSource(*this); +} + + +TokenList SqliteSelect::Core::ResultColumn::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + if (star) + { + if (!table.isNull()) + builder.withOther(table, dialect).withOperator("."); + + builder.withOperator("*"); + } + else + { + builder.withStatement(expr); + if (!alias.isNull()) + { + if (asKw) + builder.withSpace().withKeyword("AS"); + + builder.withSpace().withOther(alias, dialect); + } + } + + return builder.build(); +} + +TokenList SqliteSelect::Core::SingleSource::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + if (!table.isNull()) + { + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table, dialect); + + if (!alias.isNull()) + { + if (asKw) + builder.withSpace().withKeyword("AS"); + + builder.withSpace().withOther(alias, dialect); + + if (indexedByKw) + builder.withSpace().withKeyword("INDEXED").withSpace().withKeyword("BY").withSpace().withOther(indexedBy, dialect); + else if (notIndexedKw) + builder.withSpace().withKeyword("NOT").withSpace().withKeyword("INDEXED"); + } + } + else if (select) + { + builder.withParLeft().withStatement(select).withParRight(); + if (!alias.isNull()) + { + if (asKw) + builder.withSpace().withKeyword("AS"); + + builder.withSpace().withOther(alias, dialect); + } + } + else + { + builder.withParLeft().withStatement(joinSource).withParRight(); + } + + return builder.build(); +} + +TokenList SqliteSelect::Core::JoinOp::rebuildTokensFromContents() +{ + switch (dialect) + { + case Dialect::Sqlite3: + return rebuildTokensForSqlite2(); + case Dialect::Sqlite2: + return rebuildTokensForSqlite3(); + } + return TokenList(); +} + +TokenList SqliteSelect::Core::JoinOp::rebuildTokensForSqlite2() +{ + StatementTokenBuilder builder; + if (comma) + { + builder.withOperator(","); + } + else + { + if (naturalKw) + builder.withKeyword("NATURAL").withSpace(); + + if (leftKw) + builder.withKeyword("LEFT").withSpace(); + else if (rightKw) + builder.withKeyword("RIGHT").withSpace(); + else if (fullKw) + builder.withKeyword("FULL").withSpace(); + + if (innerKw) + builder.withKeyword("INNER").withSpace(); + else if (crossKw) + builder.withKeyword("CROSS").withSpace(); + else if (outerKw) + builder.withKeyword("OUTER").withSpace(); + + builder.withKeyword("JOIN"); + } + + return builder.build(); +} + +TokenList SqliteSelect::Core::JoinOp::rebuildTokensForSqlite3() +{ + StatementTokenBuilder builder; + if (comma) + { + builder.withOperator(","); + } + else + { + if (naturalKw) + builder.withKeyword("NATURAL").withSpace(); + + if (leftKw) + { + builder.withKeyword("LEFT").withSpace(); + if (outerKw) + builder.withKeyword("OUTER").withSpace(); + } + else if (innerKw) + builder.withKeyword("INNER").withSpace(); + else if (crossKw) + builder.withKeyword("CROSS").withSpace(); + + builder.withKeyword("JOIN"); + } + + return builder.build(); +} + + +TokenList SqliteSelect::Core::JoinConstraint::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + if (expr) + builder.withKeyword("ON").withStatement(expr); + else + builder.withKeyword("USING").withSpace().withParLeft().withOtherList(columnNames, dialect).withParRight(); + + return builder.build(); +} + + +TokenList SqliteSelect::Core::JoinSourceOther::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withStatement(joinOp).withStatement(singleSource).withStatement(joinConstraint); + return builder.build(); +} + + +TokenList SqliteSelect::Core::JoinSource::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withStatement(singleSource).withStatementList(otherSources, ""); + return builder.build(); +} + + +TokenList SqliteSelect::Core::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + if (valuesMode) + { + builder.withKeyword("VALUES").withSpace().withParLeft().withStatementList(resultColumns).withParRight(); + return builder.build(); + } + + builder.withKeyword("SELECT"); + if (distinctKw) + builder.withSpace().withKeyword("DISTINCT"); + else if (allKw) + builder.withSpace().withKeyword("ALL"); + + builder.withStatementList(resultColumns); + if (from) + builder.withSpace().withKeyword("FROM").withStatement(from); + + if (where) + builder.withSpace().withKeyword("WHERE").withStatement(where); + + if (groupBy.size() > 0) + { + builder.withSpace().withKeyword("GROUP").withSpace().withKeyword("BY").withStatementList(groupBy); + if (having) + builder.withSpace().withKeyword("HAVING").withStatement(having); + } + + if (orderBy.size() > 0) + builder.withSpace().withKeyword("ORDER").withSpace().withKeyword("BY").withStatementList(orderBy); + + if (limit) + builder.withStatement(limit); + + return builder.build(); +} + +TokenList SqliteSelect::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + if (with) + builder.withStatement(with); + + foreach (SqliteSelect::Core* core, coreSelects) + { + if (core->compoundOp == CompoundOperator::UNION_ALL) + { + if (core->valuesMode) + builder.withSpace().withOperator(","); + else + builder.withSpace().withKeyword("UNION").withSpace().withKeyword("ALL"); + } + else if (core->compoundOp != CompoundOperator::null) + builder.withSpace().withKeyword(compoundOperator(core->compoundOp)); + + builder.withStatement(core); + } + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteselect.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteselect.h new file mode 100644 index 0000000..22d1921 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteselect.h @@ -0,0 +1,228 @@ +#ifndef SQLITESELECT_H +#define SQLITESELECT_H + +#include "sqlitequery.h" +#include "sqliteexpr.h" +#include "sqlitelimit.h" +#include "sqliteorderby.h" + +#include <QList> + +class SqliteWith; + +/** + * @addtogroup sqlite_statement + * @brief The SqliteSelect class + */ +class API_EXPORT SqliteSelect : public SqliteQuery +{ + public: + enum class CompoundOperator + { + UNION, + UNION_ALL, + INTERSECT, + EXCEPT, + null + }; + + class API_EXPORT Core : public SqliteStatement + { + public: + class API_EXPORT ResultColumn : public SqliteStatement + { + public: + ResultColumn(); + ResultColumn(const ResultColumn& other); + ResultColumn(SqliteExpr* expr, bool asKw, const QString& alias); + ResultColumn(bool star, const QString& table); + explicit ResultColumn(bool star); + + bool isRowId(); + SqliteStatement* clone(); + + SqliteExpr* expr = nullptr; + bool star = false; + bool asKw = false; + QString alias = QString::null; + QString table = QString::null; + + protected: + QStringList getTablesInStatement(); + TokenList getTableTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + }; + + class JoinSource; // forward declaration + + class API_EXPORT SingleSource : public SqliteStatement + { + public: + SingleSource(); + SingleSource(const SingleSource& other); + SingleSource(const QString& name1, const QString& name2, + bool asKw, const QString& alias, bool notIndexedKw, const QString& indexedBy); + SingleSource(SqliteSelect* select, bool asKw, const QString& alias); + SingleSource(JoinSource* src, bool asKw, const QString& alias); + + SqliteStatement* clone(); + + QString database = QString::null; + QString table = QString::null; + QString alias = QString::null; + bool asKw = false; + bool indexedByKw = false; + bool notIndexedKw = false; + QString indexedBy = QString::null; + SqliteSelect* select = nullptr; + JoinSource* joinSource = nullptr; + + protected: + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); + }; + + class API_EXPORT JoinOp : public SqliteStatement + { + public: + JoinOp(); + JoinOp(const JoinOp& other); + explicit JoinOp(bool comma); + explicit JoinOp(const QString& joinToken); + JoinOp(const QString& joinToken, const QString& word1); + JoinOp(const QString& joinToken, const QString& word1, const QString& word2); + + SqliteStatement* clone(); + + private: + void init(const QString& str); + + public: + bool comma = false; + bool joinKw = false; + bool naturalKw = false; + bool leftKw = false; + bool outerKw = false; + bool innerKw = false; + bool crossKw = false; + bool rightKw = false; + bool fullKw = false; + QString customKw1 = QString::null; + QString customKw2 = QString::null; + QString customKw3 = QString::null; + + protected: + TokenList rebuildTokensFromContents(); + + private: + TokenList rebuildTokensForSqlite2(); + TokenList rebuildTokensForSqlite3(); + }; + + class API_EXPORT JoinConstraint : public SqliteStatement + { + public: + JoinConstraint(); + JoinConstraint(const JoinConstraint& other); + explicit JoinConstraint(SqliteExpr* expr); + explicit JoinConstraint(const QList<QString>& strList); + + SqliteStatement* clone(); + + SqliteExpr* expr = nullptr; + QList<QString> columnNames; + + protected: + QStringList getColumnsInStatement(); + TokenList getColumnTokensInStatement(); + TokenList rebuildTokensFromContents(); + }; + + class API_EXPORT JoinSourceOther : public SqliteStatement + { + public: + JoinSourceOther(); + JoinSourceOther(const JoinSourceOther& other); + JoinSourceOther(SqliteSelect::Core::JoinOp *op, + SqliteSelect::Core::SingleSource* src, + SqliteSelect::Core::JoinConstraint* constr); + + SqliteStatement* clone(); + + JoinOp* joinOp = nullptr; + SingleSource* singleSource = nullptr; + JoinConstraint* joinConstraint = nullptr; + + protected: + TokenList rebuildTokensFromContents(); + }; + + class API_EXPORT JoinSource : public SqliteStatement + { + public: + JoinSource(); + JoinSource(const JoinSource& other); + JoinSource(SingleSource* singleSource, const QList<JoinSourceOther*>& list); + + SqliteStatement* clone(); + + SingleSource* singleSource = nullptr; + QList<JoinSourceOther*> otherSources; + + protected: + TokenList rebuildTokensFromContents(); + }; + + Core(); + Core(const Core& other); + Core(int distinct, const QList<ResultColumn*>& resCols, JoinSource* src, SqliteExpr* where, + const QList<SqliteExpr*>& groupBy, SqliteExpr* having, const QList<SqliteOrderBy*>& orderBy, + SqliteLimit* limit); + + SqliteStatement* clone(); + + CompoundOperator compoundOp = CompoundOperator::null; + QList<ResultColumn*> resultColumns; + JoinSource* from = nullptr; + bool distinctKw = false; + bool allKw = false; + SqliteExpr* where = nullptr; + SqliteExpr* having = nullptr; + QList<SqliteExpr*> groupBy; + QList<SqliteOrderBy*> orderBy; + SqliteLimit* limit = nullptr; + bool valuesMode = false; + + protected: + TokenList rebuildTokensFromContents(); + }; + + SqliteSelect(); + SqliteSelect(const SqliteSelect& other); + + static SqliteSelect* append(SqliteSelect::Core* core); + static SqliteSelect* append(SqliteSelect* select, CompoundOperator op, SqliteSelect::Core* core); + static SqliteSelect* append(const QList<QList<SqliteExpr*>>& values); + static SqliteSelect* append(SqliteSelect* select, SqliteSelect::CompoundOperator op, const QList<QList<SqliteExpr*>>& values); + + SqliteStatement* clone(); + QString compoundOperator(CompoundOperator op); + CompoundOperator compoundOperator(const QString& op); + void reset(); + void setWith(SqliteWith* with); + + QList<Core*> coreSelects; + SqliteWith* with = nullptr; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteSelect> SqliteSelectPtr; + +#endif // SQLITESELECT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesortorder.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesortorder.cpp new file mode 100644 index 0000000..13f3951 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesortorder.cpp @@ -0,0 +1,25 @@ +#include "sqlitesortorder.h" + +SqliteSortOrder sqliteSortOrder(const QString& value) +{ + if (value == "ASC") + return SqliteSortOrder::ASC; + else if (value == "DESC") + return SqliteSortOrder::DESC; + else + return SqliteSortOrder::null; +} + +QString sqliteSortOrder(SqliteSortOrder value) +{ + switch (value) + { + case SqliteSortOrder::ASC: + return "ASC"; + case SqliteSortOrder::DESC: + return "DESC"; + default: + return QString::null; + + } +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesortorder.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesortorder.h new file mode 100644 index 0000000..51c7052 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitesortorder.h @@ -0,0 +1,17 @@ +#ifndef SQLITESORTORDER_H +#define SQLITESORTORDER_H + +#include "coreSQLiteStudio_global.h" +#include <QString> + +enum class SqliteSortOrder +{ + ASC, + DESC, + null +}; + +API_EXPORT SqliteSortOrder sqliteSortOrder(const QString& value); +API_EXPORT QString sqliteSortOrder(SqliteSortOrder value); + +#endif // SQLITESORTORDER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitestatement.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitestatement.cpp new file mode 100644 index 0000000..482baf8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitestatement.cpp @@ -0,0 +1,553 @@ +#include "sqlitestatement.h" +#include "../token.h" +#include "../lexer.h" +#include "common/unused.h" +#include <QDebug> + +SqliteStatement::SqliteStatement() +{ +} + +SqliteStatement::SqliteStatement(const SqliteStatement& other) : + QObject(), tokens(other.tokens), tokensMap(other.tokensMap), dialect(other.dialect) +{ + +} + +SqliteStatement::~SqliteStatement() +{ +} + +QString SqliteStatement::detokenize() +{ + return tokens.detokenize(); +} + +QStringList SqliteStatement::getContextColumns(bool checkParent, bool checkChilds) +{ + return getContextColumns(this, checkParent, checkChilds); +} + +QStringList SqliteStatement::getContextTables(bool checkParent, bool checkChilds) +{ + return getContextTables(this, checkParent, checkChilds); +} + +QStringList SqliteStatement::getContextDatabases(bool checkParent, bool checkChilds) +{ + return getContextDatabases(this, checkParent, checkChilds); +} + +TokenList SqliteStatement::getContextColumnTokens(bool checkParent, bool checkChilds) +{ + return getContextColumnTokens(this, checkParent, checkChilds); +} + +TokenList SqliteStatement::getContextTableTokens(bool checkParent, bool checkChilds) +{ + return getContextTableTokens(this, checkParent, checkChilds); +} + +TokenList SqliteStatement::getContextDatabaseTokens(bool checkParent, bool checkChilds) +{ + return getContextDatabaseTokens(this, checkParent, checkChilds); +} + +QList<SqliteStatement::FullObject> SqliteStatement::getContextFullObjects(bool checkParent, bool checkChilds) +{ + QList<FullObject> fullObjects = getContextFullObjects(this, checkParent, checkChilds); + + FullObject fullObj; + QMutableListIterator<FullObject> it(fullObjects); + while (it.hasNext()) + { + fullObj = it.next(); + if (fullObj.type == SqliteStatement::FullObject::NONE) + { + qWarning() << "FullObject of type NONE!"; + it.remove(); + continue; + } + + if (fullObj.type != SqliteStatement::FullObject::DATABASE && !fullObj.object) + { + qWarning() << "No 'object' member in FullObject that is not of DATABASE type!"; + it.remove(); + continue; + } + + if (fullObj.type == SqliteStatement::FullObject::DATABASE && !fullObj.database) + { + qWarning() << "No 'database' member in FullObject that is of DATABASE type!"; + it.remove(); + continue; + } + } + + return fullObjects; +} + +void SqliteStatement::setSqliteDialect(Dialect dialect) +{ + this->dialect = dialect; + foreach (SqliteStatement* stmt, childStatements()) + stmt->setSqliteDialect(dialect); +} + +SqliteStatementPtr SqliteStatement::detach() +{ + if (!parent()) + qWarning() << "Detaching " << this << ", but there's no parent!"; + + setParent(nullptr); + return SqliteStatementPtr(this); +} + +void SqliteStatement::processPostParsing() +{ + evaluatePostParsing(); + foreach (SqliteStatement* stmt, childStatements()) + stmt->processPostParsing(); +} + +QStringList SqliteStatement::getContextColumns(SqliteStatement *caller, bool checkParent, bool checkChilds) +{ + QStringList results = getColumnsInStatement(); + foreach (SqliteStatement* stmt, getContextStatements(caller, checkParent, checkChilds)) + results += stmt->getContextColumns(this, checkParent, checkChilds); + + return results; +} + +QStringList SqliteStatement::getContextTables(SqliteStatement *caller, bool checkParent, bool checkChilds) +{ + QStringList results = getTablesInStatement(); + foreach (SqliteStatement* stmt, getContextStatements(caller, checkParent, checkChilds)) + results += stmt->getContextTables(this, checkParent, checkChilds); + + return results; +} + +QStringList SqliteStatement::getContextDatabases(SqliteStatement *caller, bool checkParent, bool checkChilds) +{ + QStringList results = getDatabasesInStatement(); + foreach (SqliteStatement* stmt, getContextStatements(caller, checkParent, checkChilds)) + results += stmt->getContextDatabases(this, checkParent, checkChilds); + + return results; +} + +TokenList SqliteStatement::getContextColumnTokens(SqliteStatement *caller, bool checkParent, bool checkChilds) +{ + TokenList results = getColumnTokensInStatement(); + foreach (SqliteStatement* stmt, getContextStatements(caller, checkParent, checkChilds)) + results += stmt->getContextColumnTokens(this, checkParent, checkChilds); + + return results; +} + +TokenList SqliteStatement::getContextTableTokens(SqliteStatement *caller, bool checkParent, bool checkChilds) +{ + TokenList results = getTableTokensInStatement(); + foreach (SqliteStatement* stmt, getContextStatements(caller, checkParent, checkChilds)) + results += stmt->getContextTableTokens(this, checkParent, checkChilds); + + return results; +} + +TokenList SqliteStatement::getContextDatabaseTokens(SqliteStatement *caller, bool checkParent, bool checkChilds) +{ + TokenList results = getDatabaseTokensInStatement(); + foreach (SqliteStatement* stmt, getContextStatements(caller, checkParent, checkChilds)) + results += stmt->getContextDatabaseTokens(this, checkParent, checkChilds); + + return results; +} + +QList<SqliteStatement::FullObject> SqliteStatement::getContextFullObjects(SqliteStatement* caller, bool checkParent, bool checkChilds) +{ + QList<SqliteStatement::FullObject> results = getFullObjectsInStatement(); + foreach (SqliteStatement* stmt, getContextStatements(caller, checkParent, checkChilds)) + { + stmt->setContextDbForFullObject(dbTokenForFullObjects); + results += stmt->getContextFullObjects(this, checkParent, checkChilds); + } + + return results; +} + +QStringList SqliteStatement::getColumnsInStatement() +{ + return QStringList(); +} + +QStringList SqliteStatement::getTablesInStatement() +{ + return QStringList(); +} + +QStringList SqliteStatement::getDatabasesInStatement() +{ + return QStringList(); +} + +TokenList SqliteStatement::getColumnTokensInStatement() +{ + return TokenList(); +} + +TokenList SqliteStatement::getTableTokensInStatement() +{ + return TokenList(); +} + +TokenList SqliteStatement::getDatabaseTokensInStatement() +{ + return TokenList(); +} + +QList<SqliteStatement::FullObject> SqliteStatement::getFullObjectsInStatement() +{ + return QList<SqliteStatement::FullObject>(); +} + +TokenList SqliteStatement::rebuildTokensFromContents() +{ + qCritical() << "called rebuildTokensFromContents() for SqliteStatement that has no implementation for it."; + return TokenList(); +} + +void SqliteStatement::evaluatePostParsing() +{ +} + +QList<SqliteStatement *> SqliteStatement::getContextStatements(SqliteStatement *caller, bool checkParent, bool checkChilds) +{ + QList<SqliteStatement *> results; + + SqliteStatement* stmt = parentStatement(); + if (checkParent && stmt && stmt != caller) + results += stmt; + + if (checkChilds) + { + foreach (stmt, childStatements()) + { + if (stmt == caller) + continue; + + results += stmt; + } + } + + return results; +} + +TokenList SqliteStatement::extractPrintableTokens(const TokenList &tokens, bool skipMeaningless) +{ + TokenList list; + foreach (TokenPtr token, tokens) + { + switch (token->type) + { + case Token::OTHER: + case Token::STRING: + case Token::FLOAT: + case Token::INTEGER: + case Token::BIND_PARAM: + case Token::OPERATOR: + case Token::PAR_LEFT: + case Token::PAR_RIGHT: + case Token::BLOB: + case Token::KEYWORD: + list << token; + break; + case Token::COMMENT: + case Token::SPACE: + if (!skipMeaningless) + list << token; + break; + default: + break; + } + } + return list; +} + +QStringList SqliteStatement::getStrListFromValue(const QString &value) +{ + QStringList list; + if (!value.isNull()) + list << value; + + return list; +} + +TokenList SqliteStatement::getTokenListFromNamedKey(const QString &tokensMapKey, int idx) +{ + TokenList list; + if (tokensMap.contains(tokensMapKey)) + { + if (idx < 0) + list += extractPrintableTokens(tokensMap[tokensMapKey]); + else if (tokensMap[tokensMapKey].size() > idx) + list << extractPrintableTokens(tokensMap[tokensMapKey])[idx]; + } + else + qCritical() << "No '" << tokensMapKey << "' in tokens map when asked for it in getTokenListFromNamedKey()."; + + return list; +} + +TokenPtr SqliteStatement::getDbTokenFromFullname(const QString &tokensMapKey) +{ + if (!tokensMap.contains(tokensMapKey)) + { + qCritical() << "No '" << tokensMapKey << "' in tokens map when asked for it getDbTokenFromFullname()."; + return TokenPtr(); + } + + TokenList tokens = extractPrintableTokens(tokensMap[tokensMapKey]); + + if (tokens.size() == 3) + return tokens[0]; + else if (tokens.size() == 1) + return TokenPtr(); + else + qCritical() << "Expected 1 or 3 tokens in '" << tokensMapKey << "' in tokens map, but got" << tokens.size(); + + return TokenPtr(); +} + +TokenPtr SqliteStatement::getObjectTokenFromFullname(const QString &tokensMapKey) +{ + if (!tokensMap.contains(tokensMapKey)) + { + qCritical() << "No '" << tokensMapKey << "' in tokens map when asked for it."; + return TokenPtr(); + } + + TokenList tokens = extractPrintableTokens(tokensMap[tokensMapKey]); + if (tokens.size() == 3) + return tokens[2]; + else if (tokens.size() == 1) + return tokens[0]; + else + qCritical() << "Expected 1 or 3 tokens in '" << tokensMapKey << "' in tokens map, but got" << tokens.size(); + + return TokenPtr(); +} + +TokenPtr SqliteStatement::getDbTokenFromNmDbnm(const QString &tokensMapKey1, const QString &tokensMapKey2) +{ + if (!tokensMap.contains(tokensMapKey1)) + { + qCritical() << "No '" << tokensMapKey1 << "' in tokens map when asked for it in getDbTokenFromNmDbnm()."; + return TokenPtr(); + } + + // It seems like checking tokensMapKey2 has no added value to the logic, while it prevents from reporting + // database token in case of: SELECT * FROM dbName. <- the valid query with "minor" error + UNUSED(tokensMapKey2); +// if (!tokensMap.contains(tokensMapKey2)) +// { +// qCritical() << "No '" << tokensMapKey2 << "' in tokens map when asked for it in getDbTokenFromNmDbnm()."; +// return TokenPtr(); +// } + +// if (tokensMap[tokensMapKey2].size() == 0) +// return TokenPtr(); + + if (!tokensMap.contains("DOT") && tokensMap[tokensMapKey2].size() == 0) + { + // In this case the query is "SELECT * FROM test" and there is no database, + // but if there was a dot after the "test", then the "test" is a database name, + // so this block won't be executed. Instead the name of the database will be returned below. + return TokenPtr(); + } + + return extractPrintableTokens(tokensMap[tokensMapKey1])[0]; +} + +TokenPtr SqliteStatement::getObjectTokenFromNmDbnm(const QString &tokensMapKey1, const QString &tokensMapKey2) +{ + if (!tokensMap.contains(tokensMapKey1)) + { + qCritical() << "No '" << tokensMapKey1 << "' in tokens map when asked for it in getObjectTokenFromNmDbnm()."; + return TokenPtr(); + } + + if (!tokensMap.contains(tokensMapKey2)) + { + qCritical() << "No '" << tokensMapKey2 << "' in tokens map when asked for it in getObjectTokenFromNmDbnm()."; + return TokenPtr(); + } + + if (tokensMap[tokensMapKey2].size() == 0) + return extractPrintableTokens(tokensMap[tokensMapKey1])[0]; + + return extractPrintableTokens(tokensMap[tokensMapKey2])[1]; +} + +TokenList SqliteStatement::getDbTokenListFromFullname(const QString &tokensMapKey) +{ + TokenList list; + TokenPtr token = getDbTokenFromFullname(tokensMapKey); + if (token) + list << token; + + return list; +} + +TokenList SqliteStatement::getObjectTokenListFromFullname(const QString &tokensMapKey) +{ + TokenList list; + TokenPtr token = getObjectTokenFromFullname(tokensMapKey); + if (token) + list << token; + + return list; +} + +TokenList SqliteStatement::getDbTokenListFromNmDbnm(const QString &tokensMapKey1, const QString &tokensMapKey2) +{ + TokenList list; + TokenPtr token = getDbTokenFromNmDbnm(tokensMapKey1, tokensMapKey2); + if (token) + list << token; + + return list; +} + +TokenList SqliteStatement::getObjectTokenListFromNmDbnm(const QString &tokensMapKey1, const QString &tokensMapKey2) +{ + TokenList list; + TokenPtr token = getObjectTokenFromNmDbnm(tokensMapKey1, tokensMapKey2); + if (token) + list << token; + + return list; +} + +SqliteStatement::FullObject SqliteStatement::getFullObjectFromFullname(SqliteStatement::FullObject::Type type, const QString& tokensMapKey) +{ + return getFullObject(type, getDbTokenFromFullname(tokensMapKey), getObjectTokenFromFullname(tokensMapKey)); +} + +SqliteStatement::FullObject SqliteStatement::getFullObjectFromNmDbnm(SqliteStatement::FullObject::Type type, const QString& tokensMapKey1, const QString& tokensMapKey2) +{ + return getFullObject(type, getDbTokenFromNmDbnm(tokensMapKey1, tokensMapKey2), getObjectTokenFromNmDbnm(tokensMapKey1, tokensMapKey2)); +} + +SqliteStatement::FullObject SqliteStatement::getFullObject(SqliteStatement::FullObject::Type type, TokenPtr dbToken, TokenPtr objToken) +{ + FullObject fullObj; + if (!objToken) + return fullObj; + + fullObj.database = dbToken; + fullObj.object = objToken; + fullObj.type = type; + return fullObj; +} + +void SqliteStatement::setContextDbForFullObject(TokenPtr dbToken) +{ + dbTokenForFullObjects = dbToken; +} + +SqliteStatement::FullObject SqliteStatement::getFirstDbFullObject() +{ + TokenList dbTokens = getDatabaseTokensInStatement(); + return getDbFullObject(dbTokens.size() > 0 ? dbTokens[0] : TokenPtr()); +} + +SqliteStatement::FullObject SqliteStatement::getDbFullObject(TokenPtr dbToken) +{ + FullObject fullObj; + if (!dbToken) + return fullObj; + + fullObj.database = dbToken; + fullObj.type = FullObject::DATABASE; + return fullObj; +} + +Range SqliteStatement::getRange() +{ + if (tokens.size() == 0) + return Range(0, 0); + + return Range(tokens.first()->start, tokens.last()->end); +} + +SqliteStatement *SqliteStatement::findStatementWithToken(TokenPtr token) +{ + SqliteStatement* stmtWithToken = nullptr; + foreach (SqliteStatement* stmt, childStatements()) + { + stmtWithToken = stmt->findStatementWithToken(token); + if (stmtWithToken) + return stmtWithToken; + } + + if (tokens.contains(token)) + return this; + + return nullptr; +} + +SqliteStatement *SqliteStatement::findStatementWithPosition(quint64 cursorPosition) +{ + TokenPtr token = tokens.atCursorPosition(cursorPosition); + if (!token) + return nullptr; + + return findStatementWithToken(token); +} + +SqliteStatement *SqliteStatement::parentStatement() +{ + if (!parent()) + return nullptr; + + return dynamic_cast<SqliteStatement*>(parent()); +} + +QList<SqliteStatement *> SqliteStatement::childStatements() +{ + QList<SqliteStatement*> results; + foreach (QObject* obj, children()) + results += dynamic_cast<SqliteStatement*>(obj); + + return results; +} + +void SqliteStatement::rebuildTokens() +{ + tokens.clear(); + tokensMap.clear(); + tokens = rebuildTokensFromContents(); + // TODO rebuild tokensMap as well + // It shouldn't be hard to write unit tests that parse a query, remembers it tokensMap, then rebuilds tokens from contents + // and then compare new tokens map with previous one. This way we should be able to get all maps correctly. +} + +void SqliteStatement::setParent(QObject* parent) +{ + QObject::setParent(parent); + SqliteStatement* stmt = qobject_cast<SqliteStatement*>(parent); + if (stmt) + dialect = stmt->dialect; +} + +void SqliteStatement::attach(SqliteStatement*& memberForChild, SqliteStatement* childStatementToAttach) +{ + memberForChild = childStatementToAttach; + childStatementToAttach->setParent(this); +} + +bool SqliteStatement::FullObject::isValid() const +{ + return (object != nullptr || (type == DATABASE && database != nullptr)); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitestatement.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitestatement.h new file mode 100644 index 0000000..bacb2d7 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitestatement.h @@ -0,0 +1,339 @@ +#ifndef SQLITESTATEMENT_H +#define SQLITESTATEMENT_H + +#include "common/utils.h" +#include "parser/token.h" +#include "dialect.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 sqlite2.y and sqlite3.y files 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(db->getDialect()); + * 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(db->getDialect()); + * 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 setSqliteDialect(Dialect dialect); + void rebuildTokens(); + void setParent(QObject* parent); + void attach(SqliteStatement*& memberForChild, SqliteStatement* childStatementToAttach); + SqliteStatementPtr detach(); + void processPostParsing(); + virtual SqliteStatement* clone() = 0; + + 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; + + foreach (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; + + Dialect dialect = Dialect::Sqlite3; + + 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; + + private: + QList<SqliteStatement*> getContextStatements(SqliteStatement* caller, bool checkParent, bool checkChilds); +}; + +#endif // SQLITESTATEMENT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitetablerelatedddl.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitetablerelatedddl.h new file mode 100644 index 0000000..599849a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitetablerelatedddl.h @@ -0,0 +1,14 @@ +#ifndef SQLITETABLERELATEDDDL_H +#define SQLITETABLERELATEDDDL_H + +#include "coreSQLiteStudio_global.h" + +class API_EXPORT SqliteTableRelatedDdl +{ + public: + virtual QString getTargetTable() const = 0; +}; + +typedef QSharedPointer<SqliteTableRelatedDdl> SqliteTableRelatedDdlPtr; + +#endif // SQLITETABLERELATEDDDL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteupdate.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteupdate.cpp new file mode 100644 index 0000000..88ac28b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteupdate.cpp @@ -0,0 +1,207 @@ +#include "sqliteupdate.h" +#include "sqlitequerytype.h" +#include "sqliteexpr.h" +#include "parser/statementtokenbuilder.h" +#include "common/global.h" +#include "sqlitewith.h" +#include <QDebug> + +SqliteUpdate::SqliteUpdate() +{ + queryType = SqliteQueryType::Update; +} + +SqliteUpdate::SqliteUpdate(const SqliteUpdate& other) : + SqliteQuery(other), onConflict(other.onConflict), database(other.database), table(other.table), indexedByKw(other.indexedByKw), + notIndexedKw(other.notIndexedKw), indexedBy(other.indexedBy) +{ + // Special case of deep collection copy + SqliteExpr* newExpr = nullptr; + foreach (const ColumnAndValue& keyValue, other.keyValueMap) + { + newExpr = new SqliteExpr(*keyValue.second); + newExpr->setParent(this); + keyValueMap << ColumnAndValue(keyValue.first, newExpr); + } + + DEEP_COPY_FIELD(SqliteExpr, where); + DEEP_COPY_FIELD(SqliteWith, with); +} + +SqliteUpdate::~SqliteUpdate() +{ +} + +SqliteUpdate::SqliteUpdate(SqliteConflictAlgo onConflict, const QString &name1, const QString &name2, bool notIndexedKw, const QString &indexedBy, + const QList<QPair<QString,SqliteExpr*> > values, SqliteExpr *where, SqliteWith* with) + : SqliteUpdate() +{ + this->onConflict = onConflict; + + if (!name2.isNull()) + { + database = name1; + table = name2; + } + else + table = name1; + + this->indexedBy = indexedBy; + this->indexedByKw = !(indexedBy.isNull()); + this->notIndexedKw = notIndexedKw; + keyValueMap = values; + + this->where = where; + if (where) + where->setParent(this); + + this->with = with; + if (with) + with->setParent(this); + + foreach (const ColumnAndValue& keyValue, keyValueMap) + keyValue.second->setParent(this); +} + +SqliteStatement*SqliteUpdate::clone() +{ + return new SqliteUpdate(*this); +} + +SqliteExpr* SqliteUpdate::getValueForColumnSet(const QString& column) +{ + foreach (const ColumnAndValue& keyValue, keyValueMap) + { + if (keyValue.first == column) + return keyValue.second; + } + return nullptr; +} + +QStringList SqliteUpdate::getColumnsInStatement() +{ + QStringList columns; + foreach (const ColumnAndValue& keyValue, keyValueMap) + columns += keyValue.first; + + return columns; +} + +QStringList SqliteUpdate::getTablesInStatement() +{ + return getStrListFromValue(table); +} + +QStringList SqliteUpdate::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteUpdate::getColumnTokensInStatement() +{ + // This case is not simple. We only have "setlist" in tokensMap + // and it contains entire: col = expr, col = expr. + // In order to extract 'col' token, we go through all 'expr', + // for each 'expr' we get its first token, then locate it + // in entire "setlist", get back 2 tokens to get what's before "=". + TokenList list; + TokenList setListTokens = getTokenListFromNamedKey("setlist"); + int setListTokensSize = setListTokens.size(); + int colNameTokenIdx; + SqliteExpr* expr = nullptr; + foreach (const ColumnAndValue& keyValue, keyValueMap) + { + expr = keyValue.second; + colNameTokenIdx = setListTokens.indexOf(expr->tokens[0]) - 2; + if (colNameTokenIdx < 0 || colNameTokenIdx > setListTokensSize) + { + qCritical() << "Went out of bounds while looking for column tokens in SqliteUpdate::getColumnTokensInStatement()."; + continue; + } + list << setListTokens[colNameTokenIdx]; + } + return list; +} + +TokenList SqliteUpdate::getTableTokensInStatement() +{ + if (tokensMap.contains("fullname")) + return getObjectTokenListFromFullname(); + + return TokenList(); +} + +TokenList SqliteUpdate::getDatabaseTokensInStatement() +{ + if (tokensMap.contains("fullname")) + return getDbTokenListFromFullname(); + + if (tokensMap.contains("nm")) + return extractPrintableTokens(tokensMap["nm"]); + + return TokenList(); +} + +QList<SqliteStatement::FullObject> SqliteUpdate::getFullObjectsInStatement() +{ + QList<FullObject> result; + if (!tokensMap.contains("fullname")) + return result; + + // Table object + FullObject fullObj = getFullObjectFromFullname(FullObject::TABLE); + + if (fullObj.isValid()) + result << fullObj; + + // Db object + fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + { + result << fullObj; + dbTokenForFullObjects = fullObj.database; + } + + return result; +} + +TokenList SqliteUpdate::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + if (with) + builder.withStatement(with); + + builder.withKeyword("UPDATE").withSpace(); + if (onConflict != SqliteConflictAlgo::null) + builder.withKeyword("OR").withSpace().withKeyword(sqliteConflictAlgo(onConflict)).withSpace(); + + if (!database.isNull()) + builder.withOther(database, dialect).withOperator("."); + + builder.withOther(table, dialect).withSpace(); + + if (indexedByKw) + builder.withKeyword("INDEXED").withSpace().withKeyword("BY").withSpace().withOther(indexedBy, dialect).withSpace(); + else if (notIndexedKw) + builder.withKeyword("NOT").withSpace().withKeyword("INDEXED").withSpace(); + + builder.withKeyword("SET").withSpace(); + + bool first = true; + foreach (const ColumnAndValue& keyVal, keyValueMap) + { + if (!first) + builder.withOperator(",").withSpace(); + + builder.withOther(keyVal.first, dialect).withSpace().withOperator("=").withStatement(keyVal.second); + first = false; + } + + if (where) + builder.withSpace().withKeyword("WHERE").withStatement(where); + + builder.withOperator(";"); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteupdate.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteupdate.h new file mode 100644 index 0000000..7d6e0c1 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqliteupdate.h @@ -0,0 +1,51 @@ +#ifndef SQLITEUPDATE_H +#define SQLITEUPDATE_H + +#include "sqlitequery.h" +#include "sqliteconflictalgo.h" + +#include <QStringList> +#include <QMap> + +class SqliteExpr; +class SqliteWith; + +class API_EXPORT SqliteUpdate : public SqliteQuery +{ + public: + typedef QPair<QString,SqliteExpr*> ColumnAndValue; + + SqliteUpdate(); + SqliteUpdate(const SqliteUpdate& other); + ~SqliteUpdate(); + SqliteUpdate(SqliteConflictAlgo onConflict, const QString& name1, const QString& name2, + bool notIndexedKw, const QString& indexedBy, const QList<QPair<QString,SqliteExpr*> > values, + SqliteExpr* where, SqliteWith* with); + + SqliteStatement* clone(); + SqliteExpr* getValueForColumnSet(const QString& column); + + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; + QString database = QString::null; + QString table = QString::null; + bool indexedByKw = false; + bool notIndexedKw = false; + QString indexedBy = QString::null; + QList<ColumnAndValue> keyValueMap; + SqliteExpr* where = nullptr; + SqliteWith* with = nullptr; + + protected: + QStringList getColumnsInStatement(); + QStringList getTablesInStatement(); + QStringList getDatabasesInStatement(); + TokenList getColumnTokensInStatement(); + TokenList getTableTokensInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteUpdate> SqliteUpdatePtr; + +#endif // SQLITEUPDATE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitevacuum.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitevacuum.cpp new file mode 100644 index 0000000..ab7d00e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitevacuum.cpp @@ -0,0 +1,56 @@ +#include "sqlitevacuum.h" +#include "sqlitequerytype.h" + +#include <parser/statementtokenbuilder.h> + +SqliteVacuum::SqliteVacuum() +{ + queryType = SqliteQueryType::Vacuum; +} + +SqliteVacuum::SqliteVacuum(const SqliteVacuum& other) : + SqliteQuery(other), database(other.database) +{ +} + +SqliteVacuum::SqliteVacuum(const QString& name) + : SqliteVacuum() +{ + if (!name.isNull()) + database = name; +} + +SqliteStatement*SqliteVacuum::clone() +{ + return new SqliteVacuum(*this); +} + +QStringList SqliteVacuum::getDatabasesInStatement() +{ + return getStrListFromValue(database); +} + +TokenList SqliteVacuum::getDatabaseTokensInStatement() +{ + return getTokenListFromNamedKey("nm"); +} + +QList<SqliteStatement::FullObject> SqliteVacuum::getFullObjectsInStatement() +{ + QList<FullObject> result; + + // Db object + FullObject fullObj = getFirstDbFullObject(); + if (fullObj.isValid()) + result << fullObj; + + return result; +} + + +TokenList SqliteVacuum::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withKeyword("VACUUM").withOperator(";"); + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitevacuum.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitevacuum.h new file mode 100644 index 0000000..871b8f4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitevacuum.h @@ -0,0 +1,28 @@ +#ifndef SQLITEVACUUM_H +#define SQLITEVACUUM_H + +#include "sqlitequery.h" + +#include <QString> + +class API_EXPORT SqliteVacuum : public SqliteQuery +{ + public: + SqliteVacuum(); + SqliteVacuum(const SqliteVacuum& other); + explicit SqliteVacuum(const QString &name); + + SqliteStatement* clone(); + + QString database; + + protected: + QStringList getDatabasesInStatement(); + TokenList getDatabaseTokensInStatement(); + QList<FullObject> getFullObjectsInStatement(); + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteVacuum> SqliteVacuumPtr; + +#endif // SQLITEVACUUM_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitewith.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitewith.cpp new file mode 100644 index 0000000..2b9c99f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitewith.cpp @@ -0,0 +1,87 @@ +#include "sqlitewith.h" +#include "parser/statementtokenbuilder.h" +#include "sqliteselect.h" +#include "common/global.h" + +SqliteWith::SqliteWith() +{ +} + +SqliteWith::SqliteWith(const SqliteWith& other) : + SqliteStatement(other), recursive(other.recursive) +{ + DEEP_COPY_COLLECTION(CommonTableExpression, cteList); +} + +SqliteWith* SqliteWith::append(const QString& tableName, const QList<SqliteIndexedColumn*>& indexedColumns, SqliteSelect* select) +{ + SqliteWith* with = new SqliteWith(); + CommonTableExpression* cte = new CommonTableExpression(tableName, indexedColumns, select); + cte->setParent(with); + with->cteList << cte; + return with; +} + +SqliteWith* SqliteWith::append(SqliteWith* with, const QString& tableName, const QList<SqliteIndexedColumn*>& indexedColumns, SqliteSelect* select) +{ + if (!with) + with = new SqliteWith(); + + CommonTableExpression* cte = new CommonTableExpression(tableName, indexedColumns, select); + cte->setParent(with); + with->cteList << cte; + return with; +} + +SqliteStatement*SqliteWith::clone() +{ + return new SqliteWith(*this); +} + +TokenList SqliteWith::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + + builder.withKeyword("WITH").withSpace(); + if (recursive) + builder.withKeyword("RECURSIVE").withSpace(); + + builder.withStatementList(cteList); + + return builder.build(); +} + +SqliteWith::CommonTableExpression::CommonTableExpression() +{ +} + +SqliteWith::CommonTableExpression::CommonTableExpression(const SqliteWith::CommonTableExpression& other) : + SqliteStatement(other), table(other.table) +{ + DEEP_COPY_COLLECTION(SqliteIndexedColumn, indexedColumns); + DEEP_COPY_FIELD(SqliteSelect, select); +} + +SqliteWith::CommonTableExpression::CommonTableExpression(const QString& tableName, const QList<SqliteIndexedColumn*>& indexedColumns, SqliteSelect* select) : + table(tableName), indexedColumns(indexedColumns), select(select) +{ + select->setParent(this); +} + +SqliteStatement*SqliteWith::CommonTableExpression::clone() +{ + return new SqliteWith::CommonTableExpression(*this); +} + +TokenList SqliteWith::CommonTableExpression::rebuildTokensFromContents() +{ + StatementTokenBuilder builder; + builder.withOther(table, dialect); + + if (indexedColumns.size() > 0) + builder.withSpace().withParLeft().withStatementList(indexedColumns).withParRight(); + + builder.withSpace().withKeyword("AS").withSpace().withParLeft().withStatement(select).withParRight(); + + return builder.build(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitewith.h b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitewith.h new file mode 100644 index 0000000..fe64c9c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/ast/sqlitewith.h @@ -0,0 +1,45 @@ +#ifndef SQLITEWITH_H +#define SQLITEWITH_H + +#include "sqlitestatement.h" +#include "sqliteindexedcolumn.h" + +class SqliteSelect; + +class SqliteWith : public SqliteStatement +{ + public: + class CommonTableExpression : public SqliteStatement + { + public: + CommonTableExpression(); + CommonTableExpression(const CommonTableExpression& other); + CommonTableExpression(const QString& tableName, const QList<SqliteIndexedColumn*>& indexedColumns, SqliteSelect* select); + + SqliteStatement* clone(); + + QString table; + QList<SqliteIndexedColumn*> indexedColumns; + SqliteSelect* select = nullptr; + + protected: + TokenList rebuildTokensFromContents(); + }; + + SqliteWith(); + SqliteWith(const SqliteWith& other); + static SqliteWith* append(const QString& tableName, const QList<SqliteIndexedColumn*>& indexedColumns, SqliteSelect* select); + static SqliteWith* append(SqliteWith* with, const QString& tableName, const QList<SqliteIndexedColumn*>& indexedColumns, SqliteSelect* select); + + SqliteStatement* clone(); + + QList<CommonTableExpression*> cteList; + bool recursive = false; + + protected: + TokenList rebuildTokensFromContents(); +}; + +typedef QSharedPointer<SqliteWith> SqliteWithPtr; + +#endif // SQLITEWITH_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/keywords.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/keywords.cpp new file mode 100644 index 0000000..2088ff2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/keywords.cpp @@ -0,0 +1,324 @@ +#include "keywords.h" +#include "sqlite3_parse.h" +#include "sqlite2_parse.h" +#include <QDebug> +#include <QList> + +QHash<QString,int> keywords2; +QHash<QString,int> keywords3; +QSet<QString> rowIdKeywords; +QStringList joinKeywords; +QStringList fkMatchKeywords; +QStringList conflictAlgoKeywords; + +int getKeywordId2(const QString& str) +{ + QString upStr = str.toUpper(); + if (keywords2.contains(upStr)) + return keywords2[upStr]; + else + return TK2_ID; +} + +int getKeywordId3(const QString& str) +{ + QString upStr = str.toUpper(); + if (keywords3.contains(upStr)) + return keywords3[upStr]; + else + return TK3_ID; +} + +bool isRowIdKeyword(const QString& str) +{ + return rowIdKeywords.contains(str.toUpper()); +} + +const QHash<QString,int>& getKeywords2() +{ + return keywords2; +} + +const QHash<QString,int>& getKeywords3() +{ + return keywords3; +} + +void initKeywords() +{ + // SQLite 3 + keywords3["REINDEX"] = TK3_REINDEX; + keywords3["INDEXED"] = TK3_INDEXED; + keywords3["INDEX"] = TK3_INDEX; + keywords3["DESC"] = TK3_DESC; + keywords3["ESCAPE"] = TK3_ESCAPE; + keywords3["EACH"] = TK3_EACH; + keywords3["CHECK"] = TK3_CHECK; + keywords3["KEY"] = TK3_KEY; + keywords3["BEFORE"] = TK3_BEFORE; + keywords3["FOREIGN"] = TK3_FOREIGN; + keywords3["FOR"] = TK3_FOR; + keywords3["IGNORE"] = TK3_IGNORE; + keywords3["REGEXP"] = TK3_LIKE_KW; + keywords3["EXPLAIN"] = TK3_EXPLAIN; + keywords3["INSTEAD"] = TK3_INSTEAD; + keywords3["ADD"] = TK3_ADD; + keywords3["DATABASE"] = TK3_DATABASE; + keywords3["AS"] = TK3_AS; + keywords3["SELECT"] = TK3_SELECT; + keywords3["TABLE"] = TK3_TABLE; + keywords3["LEFT"] = TK3_JOIN_KW; + keywords3["THEN"] = TK3_THEN; + keywords3["END"] = TK3_END; + keywords3["DEFERRABLE"] = TK3_DEFERRABLE; + keywords3["ELSE"] = TK3_ELSE; + keywords3["EXCEPT"] = TK3_EXCEPT; + keywords3["TRANSACTION"] = TK3_TRANSACTION; + keywords3["ACTION"] = TK3_ACTION; + keywords3["ON"] = TK3_ON; + keywords3["NATURAL"] = TK3_JOIN_KW; + keywords3["ALTER"] = TK3_ALTER; + keywords3["RAISE"] = TK3_RAISE; + keywords3["EXCLUSIVE"] = TK3_EXCLUSIVE; + keywords3["EXISTS"] = TK3_EXISTS; + keywords3["SAVEPOINT"] = TK3_SAVEPOINT; + keywords3["INTERSECT"] = TK3_INTERSECT; + keywords3["TRIGGER"] = TK3_TRIGGER; + keywords3["REFERENCES"] = TK3_REFERENCES; + keywords3["CONSTRAINT"] = TK3_CONSTRAINT; + keywords3["INTO"] = TK3_INTO; + keywords3["OFFSET"] = TK3_OFFSET; + keywords3["OF"] = TK3_OF; + keywords3["SET"] = TK3_SET; + keywords3["TEMP"] = TK3_TEMP; + keywords3["TEMPORARY"] = TK3_TEMP; + keywords3["OR"] = TK3_OR; + keywords3["UNIQUE"] = TK3_UNIQUE; + keywords3["QUERY"] = TK3_QUERY; + keywords3["ATTACH"] = TK3_ATTACH; + keywords3["HAVING"] = TK3_HAVING; + keywords3["GROUP"] = TK3_GROUP; + keywords3["UPDATE"] = TK3_UPDATE; + keywords3["BEGIN"] = TK3_BEGIN; + keywords3["INNER"] = TK3_JOIN_KW; + keywords3["RELEASE"] = TK3_RELEASE; + keywords3["BETWEEN"] = TK3_BETWEEN; + keywords3["NOTNULL"] = TK3_NOTNULL; + keywords3["NOT"] = TK3_NOT; + keywords3["NO"] = TK3_NO; + keywords3["NULL"] = TK3_NULL; + keywords3["LIKE"] = TK3_LIKE_KW; + keywords3["CASCADE"] = TK3_CASCADE; + keywords3["ASC"] = TK3_ASC; + keywords3["DELETE"] = TK3_DELETE; + keywords3["CASE"] = TK3_CASE; + keywords3["COLLATE"] = TK3_COLLATE; + keywords3["CREATE"] = TK3_CREATE; + keywords3["CURRENT_DATE"] = TK3_CTIME_KW; + keywords3["DETACH"] = TK3_DETACH; + keywords3["IMMEDIATE"] = TK3_IMMEDIATE; + keywords3["JOIN"] = TK3_JOIN; + keywords3["INSERT"] = TK3_INSERT; + keywords3["MATCH"] = TK3_MATCH; + keywords3["PLAN"] = TK3_PLAN; + keywords3["ANALYZE"] = TK3_ANALYZE; + keywords3["PRAGMA"] = TK3_PRAGMA; + keywords3["ABORT"] = TK3_ABORT; + keywords3["VALUES"] = TK3_VALUES; + keywords3["VIRTUAL"] = TK3_VIRTUAL; + keywords3["LIMIT"] = TK3_LIMIT; + keywords3["WHEN"] = TK3_WHEN; + keywords3["WHERE"] = TK3_WHERE; + keywords3["RENAME"] = TK3_RENAME; + keywords3["AFTER"] = TK3_AFTER; + keywords3["REPLACE"] = TK3_REPLACE; + keywords3["AND"] = TK3_AND; + keywords3["DEFAULT"] = TK3_DEFAULT; + keywords3["AUTOINCREMENT"] = TK3_AUTOINCR; + keywords3["TO"] = TK3_TO; + keywords3["IN"] = TK3_IN; + keywords3["CAST"] = TK3_CAST; + keywords3["COLUMN"] = TK3_COLUMNKW; + keywords3["COMMIT"] = TK3_COMMIT; + keywords3["CONFLICT"] = TK3_CONFLICT; + keywords3["CROSS"] = TK3_JOIN_KW; + keywords3["CURRENT_TIMESTAMP"] = TK3_CTIME_KW; + keywords3["CURRENT_TIME"] = TK3_CTIME_KW; + keywords3["PRIMARY"] = TK3_PRIMARY; + keywords3["DEFERRED"] = TK3_DEFERRED; + keywords3["DISTINCT"] = TK3_DISTINCT; + keywords3["IS"] = TK3_IS; + keywords3["DROP"] = TK3_DROP; + keywords3["FAIL"] = TK3_FAIL; + keywords3["FROM"] = TK3_FROM; + keywords3["FULL"] = TK3_JOIN_KW; + keywords3["GLOB"] = TK3_LIKE_KW; + keywords3["BY"] = TK3_BY; + keywords3["IF"] = TK3_IF; + keywords3["ISNULL"] = TK3_ISNULL; + keywords3["ORDER"] = TK3_ORDER; + keywords3["RESTRICT"] = TK3_RESTRICT; + keywords3["OUTER"] = TK3_JOIN_KW; + keywords3["RIGHT"] = TK3_JOIN_KW; + keywords3["ROLLBACK"] = TK3_ROLLBACK; + keywords3["ROW"] = TK3_ROW; + keywords3["UNION"] = TK3_UNION; + keywords3["USING"] = TK3_USING; + keywords3["VACUUM"] = TK3_VACUUM; + keywords3["VIEW"] = TK3_VIEW; + keywords3["INITIALLY"] = TK3_INITIALLY; + keywords3["WITHOUT"] = TK3_WITHOUT; + keywords3["ALL"] = TK3_ALL; + keywords3["WITH"] = TK3_WITH; + keywords3["RECURSIVE"] = TK3_RECURSIVE; + + // SQLite 2 + keywords2["ABORT"] = TK2_ABORT; + keywords2["AFTER"] = TK2_AFTER; + keywords2["ALL"] = TK2_ALL; + keywords2["AND"] = TK2_AND; + keywords2["AS"] = TK2_AS; + keywords2["ASC"] = TK2_ASC; + keywords2["ATTACH"] = TK2_ATTACH; + keywords2["BEFORE"] = TK2_BEFORE; + keywords2["BEGIN"] = TK2_BEGIN; + keywords2["BETWEEN"] = TK2_BETWEEN; + keywords2["BY"] = TK2_BY; + keywords2["CASCADE"] = TK2_CASCADE; + keywords2["CASE"] = TK2_CASE; + keywords2["CHECK"] = TK2_CHECK; + keywords2["CLUSTER"] = TK2_CLUSTER; + keywords2["COLLATE"] = TK2_COLLATE; + keywords2["COMMIT"] = TK2_COMMIT; + keywords2["CONFLICT"] = TK2_CONFLICT; + keywords2["CONSTRAINT"] = TK2_CONSTRAINT; + keywords2["COPY"] = TK2_COPY; + keywords2["CREATE"] = TK2_CREATE; + keywords2["CROSS"] = TK2_JOIN_KW; + keywords2["DATABASE"] = TK2_DATABASE; + keywords2["DEFAULT"] = TK2_DEFAULT; + keywords2["DEFERRED"] = TK2_DEFERRED; + keywords2["DEFERRABLE"] = TK2_DEFERRABLE; + keywords2["DELETE"] = TK2_DELETE; + keywords2["DELIMITERS"] = TK2_DELIMITERS; + keywords2["DESC"] = TK2_DESC; + keywords2["DETACH"] = TK2_DETACH; + keywords2["DISTINCT"] = TK2_DISTINCT; + keywords2["DROP"] = TK2_DROP; + keywords2["END"] = TK2_END; + keywords2["EACH"] = TK2_EACH; + keywords2["ELSE"] = TK2_ELSE; + keywords2["EXCEPT"] = TK2_EXCEPT; + keywords2["EXPLAIN"] = TK2_EXPLAIN; + keywords2["FAIL"] = TK2_FAIL; + keywords2["FOR"] = TK2_FOR; + keywords2["FOREIGN"] = TK2_FOREIGN; + keywords2["FROM"] = TK2_FROM; + keywords2["FULL"] = TK2_JOIN_KW; + keywords2["GLOB"] = TK2_GLOB; + keywords2["GROUP"] = TK2_GROUP; + keywords2["HAVING"] = TK2_HAVING; + keywords2["IGNORE"] = TK2_IGNORE; + keywords2["IMMEDIATE"] = TK2_IMMEDIATE; + keywords2["IN"] = TK2_IN; + keywords2["INDEX"] = TK2_INDEX; + keywords2["INITIALLY"] = TK2_INITIALLY; + keywords2["INNER"] = TK2_JOIN_KW; + keywords2["INSERT"] = TK2_INSERT; + keywords2["INSTEAD"] = TK2_INSTEAD; + keywords2["INTERSECT"] = TK2_INTERSECT; + keywords2["INTO"] = TK2_INTO; + keywords2["IS"] = TK2_IS; + keywords2["ISNULL"] = TK2_ISNULL; + keywords2["JOIN"] = TK2_JOIN; + keywords2["KEY"] = TK2_KEY; + keywords2["LEFT"] = TK2_JOIN_KW; + keywords2["LIKE"] = TK2_LIKE; + keywords2["LIMIT"] = TK2_LIMIT; + keywords2["MATCH"] = TK2_MATCH; + keywords2["NATURAL"] = TK2_JOIN_KW; + keywords2["NOT"] = TK2_NOT; + keywords2["NOTNULL"] = TK2_NOTNULL; + keywords2["NULL"] = TK2_NULL; + keywords2["OF"] = TK2_OF; + keywords2["OFFSET"] = TK2_OFFSET; + keywords2["ON"] = TK2_ON; + keywords2["OR"] = TK2_OR; + keywords2["ORDER"] = TK2_ORDER; + keywords2["OUTER"] = TK2_JOIN_KW; + keywords2["PRAGMA"] = TK2_PRAGMA; + keywords2["PRIMARY"] = TK2_PRIMARY; + keywords2["RAISE"] = TK2_RAISE; + keywords2["REFERENCES"] = TK2_REFERENCES; + keywords2["REPLACE"] = TK2_REPLACE; + keywords2["RESTRICT"] = TK2_RESTRICT; + keywords2["RIGHT"] = TK2_JOIN_KW; + keywords2["ROLLBACK"] = TK2_ROLLBACK; + keywords2["ROW"] = TK2_ROW; + keywords2["SELECT"] = TK2_SELECT; + keywords2["SET"] = TK2_SET; + keywords2["STATEMENT"] = TK2_STATEMENT; + keywords2["TABLE"] = TK2_TABLE; + keywords2["TEMP"] = TK2_TEMP; + keywords2["TEMPORARY"] = TK2_TEMP; + keywords2["THEN"] = TK2_THEN; + keywords2["TRANSACTION"] = TK2_TRANSACTION; + keywords2["TRIGGER"] = TK2_TRIGGER; + keywords2["UNION"] = TK2_UNION; + keywords2["UNIQUE"] = TK2_UNIQUE; + keywords2["UPDATE"] = TK2_UPDATE; + keywords2["USING"] = TK2_USING; + keywords2["VACUUM"] = TK2_VACUUM; + keywords2["VALUES"] = TK2_VALUES; + keywords2["VIEW"] = TK2_VIEW; + keywords2["WHEN"] = TK2_WHEN; + keywords2["WHERE"] = TK2_WHERE; + + rowIdKeywords << "_ROWID_" + << "ROWID" + << "OID"; + + + joinKeywords << "NATURAL" << "LEFT" << "RIGHT" << "OUTER" << "INNER" << "CROSS"; + fkMatchKeywords << "SIMPLE" << "FULL" << "PARTIAL"; + conflictAlgoKeywords << "ROLLBACK" << "ABORT" << "FAIL" << "IGNORE" << "REPLACE"; +} + + +bool isJoinKeyword(const QString &str) +{ + return joinKeywords.contains(str, Qt::CaseInsensitive); +} + +QStringList getJoinKeywords() +{ + return joinKeywords; +} + +QStringList getFkMatchKeywords() +{ + return fkMatchKeywords; +} + +bool isFkMatchKeyword(const QString &str) +{ + return fkMatchKeywords.contains(str); +} + + +bool isKeyword(const QString& str, Dialect dialect) +{ + switch (dialect) + { + case Dialect::Sqlite3: + return keywords3.contains(str.toUpper()); + case Dialect::Sqlite2: + return keywords2.contains(str.toUpper()); + } + return false; +} + +QStringList getConflictAlgorithms() +{ + return conflictAlgoKeywords; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/keywords.h b/SQLiteStudio3/coreSQLiteStudio/parser/keywords.h new file mode 100644 index 0000000..a6e1d3d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/keywords.h @@ -0,0 +1,123 @@ +#ifndef KEYWORDS_H +#define KEYWORDS_H + +#include "dialect.h" +#include "coreSQLiteStudio_global.h" +#include <QString> +#include <QStringList> +#include <QHash> + +/** @file */ + +/** + * @brief Translates keyword into it's Lemon token ID for SQLite 2 dialect. + * @param str The keyword. + * @return Lemon generated token ID, or TK2_ID value when the \p str parameter was not recognized as a valid SQLite 2 keyword. + * + * This method is used internally by the Lexer. + * Comparision is done in case insensitive manner. + */ +API_EXPORT int getKeywordId2(const QString& str); + +/** + * @brief Translates keyword into it's Lemon token ID for SQLite 3 dialect. + * @param str The keyword. + * @return Lemon generated token ID, or TK3_ID value when the \p str parameter was not recognized as a valid SQLite 3 keyword. + * + * This method is used internally by the Lexer. + * Comparision is done in case insensitive manner. + */ +API_EXPORT int getKeywordId3(const QString& str); + +/** + * @brief Tests whether given string represents a keyword in given SQLite dialect. + * @param str String to test. + * @param dialect SQLite dialect. + * @return true if the string represents a keyword, or false otherwise. + * + * Comparision is done in case insensitive manner. + */ +API_EXPORT bool isKeyword(const QString& str, Dialect dialect); + +/** + * @brief Tests whether given string representing any variation of ROWID. + * @param str String to test. + * @return true if the value represents ROWID keyword, or false otherwise. + * + * ROWID keywords understood by SQLite are: <tt>ROWID</tt>, <tt>_ROWID_</tt> and <tt>OID</tt>. + * Comparision is done in case insensitive manner. + */ +API_EXPORT bool isRowIdKeyword(const QString& str); + +/** + * @brief Provides map of SQLite 2 keywords and their Lemon token IDs. + * @return Keyword-to-Lemon-ID hash map, keywords are uppercase. + */ +API_EXPORT const QHash<QString,int>& getKeywords2(); + +/** + * @brief Provides map of SQLite 3 keywords and their Lemon token IDs. + * @return Keyword-to-Lemon-ID hash map, keywords are uppercase. + */ +API_EXPORT const QHash<QString,int>& getKeywords3(); + +/** + * @brief Provides list of keywords representing types of SQL joins. + * @return Join type keywords. + * + * Join type keywords are: <tt>NATURAL</tt>, <tt>LEFT</tt>, <tt>RIGHT</tt>, <tt>OUTER</tt>, <tt>INNER</tt>, <tt>CROSS</tt>. + * + * Join type keywords are distinguished from other keywords, because SQLite grammar definitions + * allow those keywords to be used in contexts other than just join type definition. + */ +API_EXPORT QStringList getJoinKeywords(); + +/** + * @brief Tests whether the keyword is one of join type keywords. + * @param str String to test. + * @return true if the value represents join type keyword. + * + * This method simply tests if given string is on the list returned from getJoinKeywords(). + * + * Comparision is done in case insensitive manner. + */ +API_EXPORT bool isJoinKeyword(const QString& str); + +/** + * @brief Returns foreign key "match type" keywords. + * @return Match type keywords. + * + * Match type keywords are: <tt>SIMPLE</tt>, <tt>FULL</tt> and <tt>PARTIAL</tt>. + * + * Foreign key match type keywords are distinguished from other keywords, because SQLite grammar + * definitions allow those keywords to be used in contexts other than just foreign key match type definition. + */ +API_EXPORT QStringList getFkMatchKeywords(); + +/** + * @brief Tests whether the given value is one of match type keywords. + * @param str String to test. + * @return true if the value represents match type keywords, false otherwise. + * + * Comparision is done in case insensitive manner. + */ +API_EXPORT bool isFkMatchKeyword(const QString& str); + +/** + * @brief Initializes internal tables of keywords. + * + * This has to be (and it is) done at application startup. It defines all internal hash tables + * and lists with keywords. + */ +API_EXPORT void initKeywords(); + +/** + * @brief Provides list of SQLite conflict algorithm keywords. + * + * Conflict algorithms keywords are: <tt>ROLLBACK</tt>, <tt>ABORT</tt>, <tt>FAIL</tt>, <tt>IGNORE</tt>, <tt>REPLACE</tt>. + * + * Those keywords are used for example on GUI, when user has an "On conflict" algorithm to pick from drop-down list. + */ +API_EXPORT QStringList getConflictAlgorithms(); + +#endif // KEYWORDS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/lempar.c b/SQLiteStudio3/coreSQLiteStudio/parser/lempar.c new file mode 100644 index 0000000..4f9cd5c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/lempar.c @@ -0,0 +1,1021 @@ +/* Driver template for the LEMON parser generator. +** The author disclaims copyright to this source code. +** +** This version of "lempar.c" is modified, slightly, for use by SQLite. +** The only modifications are the addition of a couple of NEVER() +** macros to disable tests that are needed in the case of a general +** LALR(1) grammar but which are always false in the +** specific grammar used by SQLite. +*/ +/* First off, code is included that follows the "include" declaration +** in the input grammar file. */ +#include <stdio.h> +%% +/* Next is all token values, in a form suitable for use by makeheaders. +** This section will be null unless lemon is run with the -m switch. +*/ +/* +** These constants (all generated automatically by the parser generator) +** specify the various kinds of tokens (terminals) that the parser +** understands. +** +** Each symbol here is a terminal symbol in the grammar. +*/ +%% +/* Make sure the INTERFACE macro is defined. +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/* The next thing included is series of defines which control +** various aspects of the generated parser. +** YYCODETYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 terminals +** and nonterminals. "int" is used otherwise. +** YYNOCODE is a number of type YYCODETYPE which corresponds +** to no legal terminal or nonterminal number. This +** number is used to fill in empty slots of the hash +** table. +** YYFALLBACK If defined, this indicates that one or more tokens +** have fall-back values which should be used if the +** original value of the token will not parse. +** YYACTIONTYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 rules and +** states combined. "int" is used otherwise. +** ParseTOKENTYPE is the data type used for minor tokens given +** directly to the parser from the tokenizer. +** YYMINORTYPE is the data type used for all minor tokens. +** This is typically a union of many types, one of +** which is ParseTOKENTYPE. The entry in the union +** for base tokens is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. If +** zero the stack is dynamically sized using realloc() +** ParseARG_SDECL A static variable declaration for the %extra_argument +** ParseARG_PDECL A parameter declaration for the %extra_argument +** ParseARG_STORE Code to store %extra_argument into yypParser +** ParseARG_FETCH Code to extract %extra_argument from yypParser +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +*/ +%% +#define YY_NO_ACTION (YYNSTATE+YYNRULE+2) +#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1) +#define YY_ERROR_ACTION (YYNSTATE+YYNRULE) + +#define GET_CONTEXT yyParser* yypParser = pParser; ParseARG_FETCH + +/* The yyzerominor constant is used to initialize instances of +** YYMINORTYPE objects to zero. */ +static const YYMINORTYPE yyzerominor = { 0 }; + +/* Define the yytestcase() macro to be a no-op if is not already defined +** otherwise. +** +** Applications can choose to define yytestcase() in the %include section +** to a macro that can assist in verifying code coverage. For production +** code the yytestcase() macro should be turned off. But it is useful +** for testing. +*/ +#ifndef yytestcase +# define yytestcase(X) +#endif + + +/* Next are the tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N < YYNSTATE Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE. +** +** N == YYNSTATE+YYNRULE A syntax error has occurred. +** +** N == YYNSTATE+YYNRULE+1 The parser accepts its input. +** +** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused +** slots in the yy_action[] table. +** +** The action table is constructed as a single large table named yy_action[]. +** Given state S and lookahead X, the action is computed as +** +** yy_action[ yy_shift_ofst[S] + X ] +** +** If the index value yy_shift_ofst[S]+X is out of range or if the value +** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S] +** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table +** and that yy_default[S] should be used instead. +** +** The formula above is for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the yy_reduce_ofst[] array is used in place of +** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of +** YY_SHIFT_USE_DFLT. +** +** The following are the tables generated in this section: +** +** yy_action[] A single table containing all actions. +** yy_lookahead[] A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** yy_shift_ofst[] For each state, the offset into yy_action for +** shifting terminals. +** yy_reduce_ofst[] For each state, the offset into yy_action for +** shifting non-terminals after a reduce. +** yy_default[] Default action for each state. +*/ +%% + +/* The next table maps tokens into fallback tokens. If a construct +** like the following: +** +** %fallback ID X Y Z. +** +** appears in the grammar, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. +*/ +#ifdef YYFALLBACK +static const YYCODETYPE yyFallback[] = { +%% +}; +#endif /* YYFALLBACK */ + +/* The following structure represents a single element of the +** parser's stack. Information stored includes: +** +** + The state number for the parser at this level of the stack. +** +** + The value of the token stored at this level of the stack. +** (In other words, the "major" token.) +** +** + The semantic value stored at this level of the stack. This is +** the information used by the action routines in the grammar. +** It is sometimes called the "minor" token. +*/ +struct yyStackEntry { + YYACTIONTYPE stateno; /* The state-number */ + YYCODETYPE major; /* The major token value. This is the code + ** number for the token at this stack level */ + YYMINORTYPE minor; /* The user-supplied minor token value. This + ** is the value of the token */ + QList<Token*>* tokens = nullptr; +}; +typedef struct yyStackEntry yyStackEntry; + +/* The state of the parser is completely contained in an instance of +** the following structure */ +struct yyParser { + int yyidx; /* Index of top element in stack */ +#ifdef YYTRACKMAXSTACKDEPTH + int yyidxMax; /* Maximum value of yyidx */ +#endif + int yyerrcnt; /* Shifts left before out of the error */ + ParseARG_SDECL /* A place to hold %extra_argument */ +#if YYSTACKDEPTH<=0 + int yystksz; /* Current side of the stack */ + yyStackEntry *yystack; /* The parser's stack */ +#else + yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ +#endif +}; +typedef struct yyParser yyParser; + +#ifndef NDEBUG +#include <stdio.h> +static FILE *yyTraceFILE = 0; +static char *yyTracePrompt = 0; +#endif /* NDEBUG */ + +void *ParseCopyParserState(void* other) +{ + yyParser *pParser; + yyParser *otherParser = (yyParser*)other; + + // Copy parser + pParser = (yyParser*)malloc((size_t)sizeof(yyParser)); + memcpy(pParser, other, (size_t)sizeof(yyParser)); + +#if YYSTACKDEPTH<=0 + // Copy stack + int stackSize = sizeof(yyStackEntry) * pParser->yystksz; + pParser->yystack = malloc((size_t)stackSize); + memcpy(pParser->yystack, ((yyParser*)other)->yystack, (size_t)stackSize); +#endif + + for (int i = 0; i <= pParser->yyidx; i++) + { + pParser->yystack[i].tokens = new QList<Token*>(); + *(pParser->yystack[i].tokens) = *(otherParser->yystack[i].tokens); + } + + return pParser; +} + +void ParseAddToken(void* other, Token* token) +{ + yyParser *otherParser = (yyParser*)other; + if (otherParser->yyidx < 0) + return; // Nothing on stack yet. Might happen when parsing just whitespaces, nothing else. + + otherParser->yystack[otherParser->yyidx].tokens->append(token); +} + +void ParseRestoreParserState(void* saved, void* target) +{ + yyParser *pParser = (yyParser*)target; + yyParser *savedParser = (yyParser*)saved; + + for (int i = 0; i <= pParser->yyidx; i++) + delete pParser->yystack[i].tokens; + + memcpy(pParser, saved, (size_t)sizeof(yyParser)); + + for (int i = 0; i <= savedParser->yyidx; i++) + { + pParser->yystack[i].tokens = new QList<Token*>(); + *(pParser->yystack[i].tokens) = *(savedParser->yystack[i].tokens); + } + +#if YYSTACKDEPTH<=0 + // Copy stack + int stackSize = sizeof(yyStackEntry) * pParser->yystksz; + pParser->yystack = relloc(pParser->yystack, (size_t)stackSize); + memcpy(pParser->yystack, ((yyParser*)saved)->yystack, (size_t)stackSize); +#endif +} + +void ParseFreeSavedState(void* other) +{ + yyParser *pParser = (yyParser*)other; + for (int i = 0; i <= pParser->yyidx; i++) + delete pParser->yystack[i].tokens; + +#if YYSTACKDEPTH<=0 + free(pParser->yystack); +#endif + free(other); +} + +#ifndef NDEBUG +/* +** Turn parser tracing on by giving a stream to which to write the trace +** and a prompt to preface each trace message. Tracing is turned off +** by making either argument NULL +** +** Inputs: +** <ul> +** <li> A FILE* to which trace output should be written. +** If NULL, then tracing is turned off. +** <li> A prefix string written at the beginning of every +** line of trace output. If NULL, then tracing is +** turned off. +** </ul> +** +** Outputs: +** None. +*/ +void ParseTrace(FILE *TraceFILE, char *zTracePrompt){ + yyTraceFILE = TraceFILE; + yyTracePrompt = zTracePrompt; + if( yyTraceFILE==0 ) yyTracePrompt = 0; + else if( yyTracePrompt==0 ) yyTraceFILE = 0; +} +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *const yyTokenName[] = { +%% +}; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *const yyRuleName[] = { +%% +}; +#endif /* NDEBUG */ + + +#if YYSTACKDEPTH<=0 +/* +** Try to increase the size of the parser stack. +*/ +static void yyGrowStack(yyParser *p){ + int newSize; + yyStackEntry *pNew; + + newSize = p->yystksz*2 + 100; + pNew = realloc(p->yystack, newSize*sizeof(pNew[0])); + if( pNew ){ + p->yystack = pNew; + p->yystksz = newSize; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack grows to %d entries!\n", + yyTracePrompt, p->yystksz); + } +#endif + } +} +#endif + +/* +** This function allocates a new parser. +** The only argument is a pointer to a function which works like +** malloc. +** +** Inputs: +** A pointer to the function used to allocate memory. +** +** Outputs: +** A pointer to a parser. This pointer is used in subsequent calls +** to Parse and ParseFree. +*/ +void *ParseAlloc(void *(*mallocProc)(size_t)){ + yyParser *pParser; + pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) ); + if( pParser ){ + pParser->yyidx = -1; +#ifdef YYTRACKMAXSTACKDEPTH + pParser->yyidxMax = 0; +#endif +#if YYSTACKDEPTH<=0 + pParser->yystack = NULL; + pParser->yystksz = 0; + yyGrowStack(pParser); +#endif + } + return pParser; +} + +/* The following function deletes the value associated with a +** symbol. The symbol can be either a terminal or nonterminal. +** "yymajor" is the symbol code, and "yypminor" is a pointer to +** the value. +*/ +static void yy_destructor( + yyParser *yypParser, /* The parser */ + YYCODETYPE yymajor, /* Type code for object to destroy */ + YYMINORTYPE *yypminor /* The object to be destroyed */ +){ + ParseARG_FETCH; + if (parserContext->executeRules) + { + switch( yymajor ){ + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are not used + ** inside the C code. + */ +%% + default: break; /* If no destructor action specified: do nothing */ + } + } +} + +/* +** Pop the parser's stack once. +** +** If there is a destructor routine associated with the token which +** is popped from the stack, then call it. +** +** Return the major token number for the symbol popped. +*/ +static int yy_pop_parser_stack(yyParser *pParser){ + YYCODETYPE yymajor; + yyStackEntry *yytos = &pParser->yystack[pParser->yyidx]; + + /* There is no mechanism by which the parser stack can be popped below + ** empty in SQLite. */ + if( pParser->yyidx<0 ) return 0; +#ifndef NDEBUG + if( yyTraceFILE && pParser->yyidx>=0 ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + yymajor = yytos->major; + yy_destructor(pParser, yymajor, &yytos->minor); + delete yytos->tokens; + yytos->tokens = nullptr; + pParser->yyidx--; + return yymajor; +} + +/* +** Deallocate and destroy a parser. Destructors are all called for +** all stack elements before shutting the parser down. +** +** Inputs: +** <ul> +** <li> A pointer to the parser. This should be a pointer +** obtained from ParseAlloc. +** <li> A pointer to a function used to reclaim memory obtained +** from malloc. +** </ul> +*/ +void ParseFree( + void *p, /* The parser to be deleted */ + void (*freeProc)(void*) /* Function used to reclaim memory */ +){ + yyParser *pParser = (yyParser*)p; + /* In SQLite, we never try to destroy a parser that was not successfully + ** created in the first place. */ + if( pParser==0 ) return; + while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser); +#if YYSTACKDEPTH<=0 + free(pParser->yystack); +#endif + (*freeProc)((void*)pParser); +} + +/* +** Return the peak depth of the stack for a parser. +*/ +#ifdef YYTRACKMAXSTACKDEPTH +int ParseStackPeak(void *p){ + yyParser *pParser = (yyParser*)p; + return pParser->yyidxMax; +} +#endif + +/* +** Find the appropriate action for a parser given the terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_shift_action( + yyParser *pParser, /* The parser */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; + int stateno = pParser->yystack[pParser->yyidx].stateno; + GET_CONTEXT; + + if( stateno>YY_SHIFT_COUNT + || (i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT ){ + return yy_default[stateno]; + } + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + if( iLookAhead>0 ){ +#ifdef YYFALLBACK + YYCODETYPE iFallback; /* Fallback token */ + if( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0]) + && (iFallback = yyFallback[iLookAhead])!=0 + && parserContext->doFallbacks ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } +#endif + return yy_find_shift_action(pParser, iFallback); + } +#endif +#ifdef YYWILDCARD + { + int j = i - iLookAhead + YYWILDCARD; + if( +#if YY_SHIFT_MIN+YYWILDCARD<0 + j>=0 && +#endif +#if YY_SHIFT_MAX+YYWILDCARD>=YY_ACTTAB_COUNT + j<YY_ACTTAB_COUNT && +#endif + yy_lookahead[j]==YYWILDCARD + ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[YYWILDCARD]); + } +#endif /* NDEBUG */ + return yy_action[j]; + } + } +#endif /* YYWILDCARD */ + } + return yy_default[stateno]; + }else{ + return yy_action[i]; + } +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_reduce_action( + int stateno, /* Current state number */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; +#ifdef YYERRORSYMBOL + if( stateno>YY_REDUCE_COUNT ){ + return yy_default[stateno]; + } +#else + assert( stateno<=YY_REDUCE_COUNT ); +#endif + i = yy_reduce_ofst[stateno]; + assert( i!=YY_REDUCE_USE_DFLT ); + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; +#ifdef YYERRORSYMBOL + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + return yy_default[stateno]; + } +#else + assert( i>=0 && i<YY_ACTTAB_COUNT ); + assert( yy_lookahead[i]==iLookAhead ); +#endif + return yy_action[i]; +} + +/* +** The following routine is called if the stack overflows. +*/ +static void yyStackOverflow(yyParser *yypParser, YYMINORTYPE *yypMinor){ + ParseARG_FETCH; + yypParser->yyidx--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument var */ +} + +/* +** Perform a shift action. +*/ +static void yy_shift( + yyParser *yypParser, /* The parser to be shifted */ + int yyNewState, /* The new state to shift in */ + int yyMajor, /* The major token to shift in */ + YYMINORTYPE *yypMinor /* Pointer to the minor token to shift in */ +){ + yyStackEntry *yytos; + yypParser->yyidx++; +#ifdef YYTRACKMAXSTACKDEPTH + if( yypParser->yyidx>yypParser->yyidxMax ){ + yypParser->yyidxMax = yypParser->yyidx; + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yyidx>=YYSTACKDEPTH ){ + yyStackOverflow(yypParser, yypMinor); + return; + } +#else + if( yypParser->yyidx>=yypParser->yystksz ){ + yyGrowStack(yypParser); + if( yypParser->yyidx>=yypParser->yystksz ){ + yyStackOverflow(yypParser, yypMinor); + return; + } + } +#endif + yytos = &yypParser->yystack[yypParser->yyidx]; + yytos->stateno = (YYACTIONTYPE)yyNewState; + yytos->major = (YYCODETYPE)yyMajor; + yytos->minor = *yypMinor; + yytos->tokens = new QList<Token*>(); +#ifndef NDEBUG + if( yyTraceFILE && yypParser->yyidx>0 ){ + int i; + fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState); + fprintf(yyTraceFILE,"%sStack:",yyTracePrompt); + for(i=1; i<=yypParser->yyidx; i++) + fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]); + fprintf(yyTraceFILE,"\n"); + } +#endif +} + +/* The following table contains information about every rule that +** is used during the reduce. +*/ +static const struct { + YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ + unsigned char nrhs; /* Number of right-hand side symbols in the rule */ +} yyRuleInfo[] = { +%% +}; + +static void yy_accept(yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +*/ +static void yy_reduce( + yyParser *yypParser, /* The parser */ + int yyruleno /* Number of the rule by which to reduce */ +){ + int yygoto; /* The next state */ + int yyact; /* The next action */ + YYMINORTYPE yygotominor; /* The LHS of the rule reduced */ + yyStackEntry *yymsp; /* The top of the parser's stack */ + int yysize; /* Amount to pop the stack */ + ParseARG_FETCH; + SqliteStatement* objectForTokens = 0; + QStringList noTokenInheritanceFields; + yymsp = &yypParser->yystack[yypParser->yyidx]; +#ifndef NDEBUG + if( yyTraceFILE && yyruleno>=0 + && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){ + fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt, + yyRuleName[yyruleno]); + } +#endif /* NDEBUG */ + + /* Silence complaints from purify about yygotominor being uninitialized + ** in some cases when it is copied into the stack after the following + ** switch. yygotominor is uninitialized when a rule reduces that does + ** not set the value of its left-hand side nonterminal. Leaving the + ** value of the nonterminal uninitialized is utterly harmless as long + ** as the value is never used. So really the only thing this code + ** accomplishes is to quieten purify. + ** + ** 2007-01-16: The wireshark project (www.wireshark.org) reports that + ** without this code, their parser segfaults. I'm not sure what there + ** parser is doing to make this happen. This is the second bug report + ** from wireshark this week. Clearly they are stressing Lemon in ways + ** that it has not been previously stressed... (SQLite ticket #2172) + */ + /*memset(&yygotominor, 0, sizeof(yygotominor));*/ + yygotominor = yyzerominor; + + + if (parserContext->executeRules) + { + switch( yyruleno ){ + /* Beginning here are the reduction cases. A typical example + ** follows: + ** case 0: + ** #line <lineno> <grammarfile> + ** { ... } // User supplied code + ** #line <lineno> <thisfile> + ** break; + */ +%% + }; + } + assert( yyruleno>=0 && yyruleno<(int)(sizeof(yyRuleInfo)/sizeof(yyRuleInfo[0])) ); + yygoto = yyRuleInfo[yyruleno].lhs; + yysize = yyRuleInfo[yyruleno].nrhs; + + // Store tokens for the rule in parser context + QList<Token*> allTokens; + QList<Token*> allTokensWithAllInherited; + QString keyForTokensMap; + int tokensMapKeyCnt; + if (parserContext->setupTokens) + { + if (objectForTokens) + { + // In case this is a list with recurrent references we need + // to clear tokens before adding the new and extended list. + objectForTokens->tokens.clear(); + } + + QList<Token*> tokens; + for (int i = yypParser->yyidx - yysize + 1; i <= yypParser->yyidx; i++) + { + tokens.clear(); + const char* fieldName = yyTokenName[yypParser->yystack[i].major]; + if (parserContext->isManagedToken(yypParser->yystack[i].minor.yy0)) + tokens += yypParser->yystack[i].minor.yy0; + + tokens += *(yypParser->yystack[i].tokens); + + if (!noTokenInheritanceFields.contains(fieldName)) + { + if (objectForTokens) + { + keyForTokensMap = fieldName; + tokensMapKeyCnt = 2; + while (objectForTokens->tokensMap.contains(keyForTokensMap)) + keyForTokensMap = fieldName + QString::number(tokensMapKeyCnt++); + + objectForTokens->tokensMap[keyForTokensMap] = parserContext->getTokenPtrList(tokens); + } + + allTokens += tokens; + } + else + { + // If field is mentioned only once, then only one occurance of it will be ignored. + // Second one should be inherited. See "anylist" definition for explanation why. + noTokenInheritanceFields.removeOne(fieldName); + } + allTokensWithAllInherited += tokens; + } + if (objectForTokens) + { + objectForTokens->tokens += parserContext->getTokenPtrList(allTokens); + } + } + + // Clear token lists + for (int i = yypParser->yyidx - yysize + 1; i <= yypParser->yyidx; i++) + { + delete yypParser->yystack[i].tokens; + yypParser->yystack[i].tokens = nullptr; + } + + yypParser->yyidx -= yysize; + yyact = yy_find_reduce_action(yymsp[-yysize].stateno,(YYCODETYPE)yygoto); + if( yyact < YYNSTATE ){ +#ifdef NDEBUG + /* If we are not debugging and the reduce action popped at least + ** one element off the stack, then we can push the new element back + ** onto the stack here, and skip the stack overflow test in yy_shift(). + ** That gives a significant speed improvement. */ + if( yysize ){ + yypParser->yyidx++; + yymsp -= yysize-1; + yymsp->stateno = (YYACTIONTYPE)yyact; + yymsp->major = (YYCODETYPE)yygoto; + yymsp->minor = yygotominor; + if (parserContext->setupTokens) + *(yypParser->yystack[yypParser->yyidx].tokens) = allTokens; + }else +#endif + { + yy_shift(yypParser,yyact,yygoto,&yygotominor); + if (parserContext->setupTokens) + { + QList<Token*>* tokensPtr = yypParser->yystack[yypParser->yyidx].tokens; + *tokensPtr = allTokensWithAllInherited + *tokensPtr; + } + } + }else{ + assert( yyact == YYNSTATE + YYNRULE + 1 ); + yy_accept(yypParser); + } +} + +/* +** The following code executes when the parse fails +*/ +#ifndef YYNOERRORRECOVERY +static void yy_parse_failed( + yyParser *yypParser /* The parser */ +){ + ParseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} +#endif /* YYNOERRORRECOVERY */ + +/* +** The following code executes when a syntax error first occurs. +*/ +static void yy_syntax_error( + yyParser *yypParser, /* The parser */ + int yymajor, /* The major type of the error token */ + YYMINORTYPE yyminor /* The minor type of the error token */ +){ + ParseARG_FETCH; +#define TOKEN (yyminor.yy0) +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + ParseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser accepts */ +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "ParseAlloc" which describes the current state of the parser. +** The second argument is the major token number. The third is +** the minor token. The fourth optional argument is whatever the +** user wants (and specified in the grammar) and is available for +** use by the action routines. +** +** Inputs: +** <ul> +** <li> A pointer to the parser (an opaque structure.) +** <li> The major token number. +** <li> The minor token number. +** <li> An option argument of a grammar-specified type. +** </ul> +** +** Outputs: +** None. +*/ +void Parse( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + ParseTOKENTYPE yyminor /* The value for the token */ + ParseARG_PDECL /* Optional %extra_argument parameter */ +){ + YYMINORTYPE yyminorunion; + int yyact; /* The parser action. */ +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + int yyendofinput; /* True if we are at the end of input */ +#endif +#ifdef YYERRORSYMBOL + int yyerrorhit = 0; /* True if yymajor has invoked an error */ +#endif + yyParser *yypParser; /* The parser */ + + /* (re)initialize the parser, if necessary */ + yypParser = (yyParser*)yyp; + if( yypParser->yyidx<0 ){ +#if YYSTACKDEPTH<=0 + if( yypParser->yystksz <=0 ){ + /*memset(&yyminorunion, 0, sizeof(yyminorunion));*/ + yyminorunion = yyzerominor; + yyStackOverflow(yypParser, &yyminorunion); + return; + } +#endif + yypParser->yyidx = 0; + yypParser->yyerrcnt = -1; + yypParser->yystack[0].stateno = 0; + yypParser->yystack[0].major = 0; + yypParser->yystack[0].tokens = new QList<Token*>(); + } + yyminorunion.yy0 = yyminor; +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + yyendofinput = (yymajor==0); +#endif + ParseARG_STORE; + +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sInput %s [%s] (lemon type: %s)\n", + yyTracePrompt, + yyminor->value.toLatin1().data(), + yyminor->typeString().toLatin1().data(), + yyTokenName[yymajor]); } +#endif + + do{ + yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor); + if( yyact<YYNSTATE ){ + yy_shift(yypParser,yyact,yymajor,&yyminorunion); + yypParser->yyerrcnt--; + yymajor = YYNOCODE; + }else if( yyact < YYNSTATE + YYNRULE ){ + yy_reduce(yypParser,yyact-YYNSTATE); + }else{ + assert( yyact == YY_ERROR_ACTION ); +#ifdef YYERRORSYMBOL + int yymx; +#endif +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); + } +#endif +#ifdef YYERRORSYMBOL + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if( yypParser->yyerrcnt<0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yymx = yypParser->yystack[yypParser->yyidx].major; + if( yymx==YYERRORSYMBOL || yyerrorhit ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sDiscard input token %s\n", + yyTracePrompt,yyTokenName[yymajor]); + } +#endif + yy_destructor(yypParser, (YYCODETYPE)yymajor,&yyminorunion); + yymajor = YYNOCODE; + }else{ + while( + yypParser->yyidx >= 0 && + yymx != YYERRORSYMBOL && + (yyact = yy_find_reduce_action( + yypParser->yystack[yypParser->yyidx].stateno, + YYERRORSYMBOL)) >= YYNSTATE + ){ + yy_pop_parser_stack(yypParser); + } + if( yypParser->yyidx < 0 || yymajor==0 ){ + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yy_parse_failed(yypParser); + yymajor = YYNOCODE; + }else if( yymx!=YYERRORSYMBOL ){ + YYMINORTYPE u2; + u2.YYERRSYMDT = 0; + yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2); + } + } + yypParser->yyerrcnt = 1; // not 3 valid tokens, but 1 + yyerrorhit = 1; +#elif defined(YYNOERRORRECOVERY) + /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to + ** do any kind of error recovery. Instead, simply invoke the syntax + ** error routine and continue going as if nothing had happened. + ** + ** Applications can set this macro (for example inside %include) if + ** they intend to abandon the parse upon the first syntax error seen. + */ + yy_syntax_error(yypParser,yymajor,yyminorunion); + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yymajor = YYNOCODE; + +#else /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if( yypParser->yyerrcnt<=0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yypParser->yyerrcnt = 1; // not 3 valid tokens, but 1 + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + if( yyendofinput ){ + yy_parse_failed(yypParser); + } + yymajor = YYNOCODE; +#endif + } + }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 ); + return; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/lexer.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/lexer.cpp new file mode 100644 index 0000000..c85b3ba --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/lexer.cpp @@ -0,0 +1,314 @@ +#include "lexer.h" +#include "keywords.h" +#include "lexer_low_lev.h" +#include "sqlite2_parse.h" +#include "sqlite3_parse.h" +#include <QString> +#include <QMultiHash> +#include <QDebug> + +QHash<Token::Type,QSet<TokenPtr> > Lexer::everyTokenType2; +QHash<Token::Type,QSet<TokenPtr> > Lexer::everyTokenType3; +QHash<Token*,TokenPtr> Lexer::everyTokenTypePtrMap; +TokenPtr Lexer::semicolonTokenSqlite2; +TokenPtr Lexer::semicolonTokenSqlite3; + +Lexer::Lexer(Dialect dialect) + : dialect(dialect), sqlToTokenize(QString::null) +{ +} + +Lexer::~Lexer() +{ + cleanUp(); +} + +TokenList Lexer::tokenize(const QString &sql) +{ + TokenList resultList; + int lgt; + TokenPtr token; + QString str = sql; + + quint64 pos = 0; + for (;str.size() > 0;) + { + if (tolerant) + token = TolerantTokenPtr::create(); + else + token = TokenPtr::create(); + + lgt = lexerGetToken(str, token, dialect == Dialect::Sqlite2 ? 2 : 3, tolerant); + if (lgt == 0) + break; + + token->value = str.mid(0, lgt); + token->start = pos; + token->end = pos + lgt - 1; + + resultList << token; + str = str.mid(lgt); + pos += lgt; + } + + return resultList; +} + +void Lexer::prepare(const QString &sql) +{ + sqlToTokenize = sql; + tokenPosition = 0; +} + +TokenPtr Lexer::getToken() +{ + if (sqlToTokenize.isEmpty()) + return TokenPtr(); + + TokenPtr token; + if (tolerant) + token = TolerantTokenPtr::create(); + else + token = TokenPtr::create(); + + int lgt = lexerGetToken(sqlToTokenize, token, dialect == Dialect::Sqlite2 ? 2 : 3, tolerant); + if (lgt == 0) + return TokenPtr(); + + token->value = sqlToTokenize.mid(0, lgt); + token->start = tokenPosition; + token->end = tokenPosition + lgt - 1; + + sqlToTokenize = sqlToTokenize.mid(lgt); + tokenPosition += lgt; + + return token; +} + +void Lexer::cleanUp() +{ + sqlToTokenize.clear(); + tokenPosition = 0; +} + +void Lexer::setTolerantMode(bool enabled) +{ + tolerant = enabled; +} + +QSet<TokenPtr> Lexer::getEveryTokenType() +{ + return getEveryTokenType({Token::BIND_PARAM, Token::BLOB, Token::COMMENT, Token::FLOAT, + Token::INTEGER, Token::KEYWORD, Token::OPERATOR, Token::OTHER, + Token::PAR_LEFT, Token::PAR_RIGHT, Token::SPACE, Token::STRING, + Token::INVALID}); +} + +QSet<TokenPtr> Lexer::getEveryTokenType(QSet<Token::Type> types) +{ + // Process set of types + QSet<TokenPtr> results; + QHashIterator<Token::Type,QSet<TokenPtr> > i( + dialect == Dialect::Sqlite2 ? everyTokenType2 : everyTokenType3 + ); + while (i.hasNext()) + { + i.next(); + if (types.contains(i.key())) + { + QSet<TokenPtr> tk = i.value(); + results.unite(tk); + } + } + return results; +} + +bool Lexer::isEnd() const +{ + return sqlToTokenize.isEmpty(); +} + +TokenPtr Lexer::getSemicolonToken(Dialect dialect) +{ + return (dialect == Dialect::Sqlite3) ? semicolonTokenSqlite3 : semicolonTokenSqlite2; +} + +void Lexer::staticInit() +{ + createTokenType(Dialect::Sqlite3, TK3_SPACE, Token::SPACE, " "); + createTokenType(Dialect::Sqlite3, TK3_COMMENT, Token::COMMENT, "--"); + createTokenType(Dialect::Sqlite3, TK3_MINUS, Token::OPERATOR, "-"); + createTokenType(Dialect::Sqlite3, TK3_SPACE, Token::SPACE, " "); + createTokenType(Dialect::Sqlite3, TK3_LP, Token::PAR_LEFT, "("); + createTokenType(Dialect::Sqlite3, TK3_RP, Token::PAR_RIGHT, ")"); + semicolonTokenSqlite3 = + createTokenType(Dialect::Sqlite3, TK3_SEMI, Token::OPERATOR, ";"); + createTokenType(Dialect::Sqlite3, TK3_PLUS, Token::OPERATOR, "+"); + createTokenType(Dialect::Sqlite3, TK3_STAR, Token::OPERATOR, "*"); + createTokenType(Dialect::Sqlite3, TK3_SLASH, Token::OPERATOR, "/"); + createTokenType(Dialect::Sqlite3, TK3_COMMENT, Token::COMMENT, "/* */"); + createTokenType(Dialect::Sqlite3, TK3_EQ, Token::OPERATOR, "="); + createTokenType(Dialect::Sqlite3, TK3_EQ, Token::OPERATOR, "=="); + createTokenType(Dialect::Sqlite3, TK3_LE, Token::OPERATOR, "<="); + createTokenType(Dialect::Sqlite3, TK3_NE, Token::OPERATOR, "<>"); + createTokenType(Dialect::Sqlite3, TK3_NE, Token::OPERATOR, "!="); + createTokenType(Dialect::Sqlite3, TK3_LSHIFT, Token::OPERATOR, "<<"); + createTokenType(Dialect::Sqlite3, TK3_LT, Token::OPERATOR, "<"); + createTokenType(Dialect::Sqlite3, TK3_GE, Token::OPERATOR, ">="); + createTokenType(Dialect::Sqlite3, TK3_RSHIFT, Token::OPERATOR, ">>"); + createTokenType(Dialect::Sqlite3, TK3_GT, Token::OPERATOR, ">"); + createTokenType(Dialect::Sqlite3, TK3_BITOR, Token::OPERATOR, "|"); + createTokenType(Dialect::Sqlite3, TK3_CONCAT, Token::OPERATOR, "||"); + createTokenType(Dialect::Sqlite3, TK3_COMMA, Token::OPERATOR, ","); + createTokenType(Dialect::Sqlite3, TK3_BITAND, Token::OPERATOR, "&"); + createTokenType(Dialect::Sqlite3, TK3_BITNOT, Token::OPERATOR, "~"); + createTokenType(Dialect::Sqlite3, TK3_STRING, Token::STRING, "' '"); + createTokenType(Dialect::Sqlite3, TK3_ID, Token::OTHER, "id"); + createTokenType(Dialect::Sqlite3, TK3_DOT, Token::OPERATOR, "."); + createTokenType(Dialect::Sqlite3, TK3_INTEGER, Token::INTEGER, "1"); + createTokenType(Dialect::Sqlite3, TK3_FLOAT, Token::FLOAT, "1.0"); + createTokenType(Dialect::Sqlite3, TK3_VARIABLE, Token::BIND_PARAM, "?"); + createTokenType(Dialect::Sqlite3, TK3_BLOB, Token::BLOB, "X'53'"); + + // Contextual ID tokens + createTokenType(Dialect::Sqlite3, TK3_ID_DB, Token::CTX_DATABASE, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_TAB, Token::CTX_TABLE, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_TAB_NEW, Token::CTX_TABLE_NEW, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_COL, Token::CTX_COLUMN, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_COL_NEW, Token::CTX_COLUMN_NEW, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_COL_TYPE, Token::CTX_COLUMN_TYPE, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_COLLATE, Token::CTX_COLLATION, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_FN, Token::CTX_FUNCTION, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_ERR_MSG, Token::CTX_ERROR_MESSAGE, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_IDX, Token::CTX_INDEX, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_IDX_NEW, Token::CTX_INDEX_NEW, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_VIEW, Token::CTX_VIEW, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_VIEW_NEW, Token::CTX_VIEW_NEW, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_JOIN_OPTS, Token::CTX_JOIN_OPTS, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_CONSTR, Token::CTX_CONSTRAINT, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_FK_MATCH, Token::CTX_FK_MATCH, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_TRANS, Token::CTX_TRANSACTION, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_ALIAS, Token::CTX_ALIAS, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_PRAGMA, Token::CTX_PRAGMA, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_TRIG, Token::CTX_TRIGGER, ""); + createTokenType(Dialect::Sqlite3, TK3_ID_TRIG_NEW, Token::CTX_TRIGGER_NEW, ""); + createTokenType(Dialect::Sqlite3, TK3_CTX_ROWID_KW, Token::CTX_ROWID_KW, "ROWID"); + createTokenType(Dialect::Sqlite3, TK3_ID, Token::CTX_OLD_KW, "OLD"); + createTokenType(Dialect::Sqlite3, TK3_ID, Token::CTX_NEW_KW, "NEW"); + + QHashIterator<QString,int> i3(getKeywords3()); + while (i3.hasNext()) + { + i3.next(); + createTokenType(Dialect::Sqlite3, i3.value(), Token::KEYWORD, i3.key()); + } + + // + // SQLite 2 + // + + createTokenType(Dialect::Sqlite2, TK2_SPACE, Token::SPACE, " "); + createTokenType(Dialect::Sqlite2, TK2_COMMENT, Token::COMMENT, "--"); + createTokenType(Dialect::Sqlite2, TK2_MINUS, Token::OPERATOR, "-"); + createTokenType(Dialect::Sqlite2, TK2_SPACE, Token::SPACE, " "); + createTokenType(Dialect::Sqlite2, TK2_LP, Token::PAR_LEFT, "("); + createTokenType(Dialect::Sqlite2, TK2_RP, Token::PAR_RIGHT, ")"); + semicolonTokenSqlite2 = + createTokenType(Dialect::Sqlite2, TK2_SEMI, Token::OPERATOR, ";"); + createTokenType(Dialect::Sqlite2, TK2_PLUS, Token::OPERATOR, "+"); + createTokenType(Dialect::Sqlite2, TK2_STAR, Token::OPERATOR, "*"); + createTokenType(Dialect::Sqlite2, TK2_SLASH, Token::OPERATOR, "/"); + createTokenType(Dialect::Sqlite2, TK2_COMMENT, Token::COMMENT, "/* */"); + createTokenType(Dialect::Sqlite2, TK2_EQ, Token::OPERATOR, "="); + createTokenType(Dialect::Sqlite2, TK2_EQ, Token::OPERATOR, "=="); + createTokenType(Dialect::Sqlite2, TK2_LE, Token::OPERATOR, "<="); + createTokenType(Dialect::Sqlite2, TK2_NE, Token::OPERATOR, "<>"); + createTokenType(Dialect::Sqlite2, TK2_NE, Token::OPERATOR, "!="); + createTokenType(Dialect::Sqlite2, TK2_LSHIFT, Token::OPERATOR, "<<"); + createTokenType(Dialect::Sqlite2, TK2_LT, Token::OPERATOR, "<"); + createTokenType(Dialect::Sqlite2, TK2_GE, Token::OPERATOR, ">="); + createTokenType(Dialect::Sqlite2, TK2_RSHIFT, Token::OPERATOR, ">>"); + createTokenType(Dialect::Sqlite2, TK2_GT, Token::OPERATOR, ">"); + createTokenType(Dialect::Sqlite2, TK2_BITOR, Token::OPERATOR, "|"); + createTokenType(Dialect::Sqlite2, TK2_CONCAT, Token::OPERATOR, "||"); + createTokenType(Dialect::Sqlite2, TK2_COMMA, Token::OPERATOR, ","); + createTokenType(Dialect::Sqlite2, TK2_BITAND, Token::OPERATOR, "&"); + createTokenType(Dialect::Sqlite2, TK2_BITNOT, Token::OPERATOR, "~"); + createTokenType(Dialect::Sqlite2, TK2_STRING, Token::STRING, "' '"); + createTokenType(Dialect::Sqlite2, TK2_ID, Token::OTHER, "id"); + createTokenType(Dialect::Sqlite2, TK2_DOT, Token::OPERATOR, "."); + createTokenType(Dialect::Sqlite2, TK2_INTEGER, Token::INTEGER, "1"); + createTokenType(Dialect::Sqlite2, TK2_FLOAT, Token::FLOAT, "1.0"); + createTokenType(Dialect::Sqlite2, TK2_VARIABLE, Token::BIND_PARAM, "?"); + + // Contextual ID tokens + createTokenType(Dialect::Sqlite2, TK2_ID_DB, Token::CTX_DATABASE, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_TAB, Token::CTX_TABLE, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_TAB_NEW, Token::CTX_TABLE_NEW, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_COL, Token::CTX_COLUMN, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_COL_NEW, Token::CTX_COLUMN_NEW, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_COL_TYPE, Token::CTX_COLUMN_TYPE, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_FN, Token::CTX_FUNCTION, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_ERR_MSG, Token::CTX_ERROR_MESSAGE, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_IDX, Token::CTX_INDEX, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_IDX_NEW, Token::CTX_INDEX_NEW, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_VIEW, Token::CTX_VIEW, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_VIEW_NEW, Token::CTX_VIEW_NEW, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_JOIN_OPTS, Token::CTX_JOIN_OPTS, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_CONSTR, Token::CTX_CONSTRAINT, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_FK_MATCH, Token::CTX_FK_MATCH, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_TRANS, Token::CTX_TRANSACTION, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_ALIAS, Token::CTX_ALIAS, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_PRAGMA, Token::CTX_PRAGMA, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_TRIG, Token::CTX_TRIGGER, ""); + createTokenType(Dialect::Sqlite2, TK2_ID_TRIG_NEW, Token::CTX_TRIGGER_NEW, ""); + createTokenType(Dialect::Sqlite2, TK2_ID, Token::CTX_ROWID_KW, "ROWID"); + createTokenType(Dialect::Sqlite2, TK2_ID, Token::CTX_OLD_KW, "OLD"); + createTokenType(Dialect::Sqlite2, TK2_ID, Token::CTX_NEW_KW, "NEW"); + + QHashIterator<QString,int> i2(getKeywords2()); + while (i2.hasNext()) + { + i2.next(); + createTokenType(Dialect::Sqlite2, i2.value(), Token::KEYWORD, i2.key()); + } +} + +QString Lexer::detokenize(const TokenList& tokens) +{ + if (tokens.size() == 0) + return ""; + + QString str; + foreach (TokenPtr token, tokens) + str += token->value; + + return str; +} + +TokenList Lexer::tokenize(const QString& sql, Dialect dialect) +{ + Lexer lexer(dialect); + return lexer.tokenize(sql); +} + +TokenPtr Lexer::getEveryTokenTypePtr(Token *token) +{ + if (everyTokenTypePtrMap.contains(token)) + return everyTokenTypePtrMap[token]; + + qDebug() << "Queried token not in Lexer::everyTokenTypePtrMap:" << token->toString(); + return TokenPtr(); +} + +TokenPtr Lexer::createTokenType(Dialect dialect, int lemonType, Token::Type type, const QString &value) +{ + TokenPtr tokenPtr = TokenPtr::create(lemonType, type, value, -100, -100); + if (dialect == Dialect::Sqlite2) + everyTokenType2[type] << tokenPtr; + else + everyTokenType3[type] << tokenPtr; + + everyTokenTypePtrMap[tokenPtr.data()] = tokenPtr; + return tokenPtr; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/lexer.h b/SQLiteStudio3/coreSQLiteStudio/parser/lexer.h new file mode 100644 index 0000000..b21639e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/lexer.h @@ -0,0 +1,254 @@ +#ifndef LEXER_H +#define LEXER_H + +#include "token.h" +#include "dialect.h" + +#include <QList> +#include <QString> +#include <QSet> + +/** + * @brief Lexer for SQLite gramma. + * + * Lexer (aka tokenizer) splits SQL string into tokens. + * Tokens can be then used to syntax analysis, or for other purposes. + * + * It is useful if you have to modify some entities in the query, + * such as string, or object name, but you don't want to deal with + * all escape characters in the name, or other special characters. + * Lexer packs such entiries into separate tokens and gives them + * type, so you know what is the token representing. + */ +class API_EXPORT Lexer +{ + public: + /** + * @brief Creates lexer for given dialect. + * @param dialect SQLite dialect. + */ + Lexer(Dialect dialect); + + /** + * @brief Releases resources. + */ + virtual ~Lexer(); + + /** + * @brief Tokenizes (splits into tokens) given SQL query. + * @param sql SQL query to tokenize. + * @return List of tokens produced from tokenizing query. + */ + TokenList tokenize(const QString& sql); + + /** + * @brief Stores given SQL query internally for further processing by the lexer. + * @param sql Query to remember. + * + * This method should be followed by calls to getToken(). + */ + void prepare(const QString& sql); + + /** + * @brief Gets next token from query defined with prepare(). + * @return Token read from the query, or null token if no more tokens are available. + * + * Each call to this method generates token for next part of the query, not tokenized yet. + * Usual flow for this method looks like this: + * @code + * QString query = "..."; + * TokenPtr token; + * lexer.prepare(query); + * while (token = lexer.getToken()) + * { + * // do stuff with the token + * } + * @endcode + */ + TokenPtr getToken(); + + /** + * @brief Clears query stored with prepare(). + */ + void cleanUp(); + + /** + * @brief Enables or disabled tolerant mode. + * @param enabled If true, then all multi-line and unfinished tokens (strings, comments) will be reported + * with invalid=true in TolerantToken, but the token itself will have type like it was finished. + */ + void setTolerantMode(bool enabled); + + /** + * @brief Provides static sample tokens of all possible types. + * @return All possible token types. + * This method uses static set of tokens, so there's no need + * to delete them outside. + * + * It's used by Parser to try every token type as a possible candidate for a next valid token. + * You should not need to use this method. + */ + QSet<TokenPtr> getEveryTokenType(); + + /** + * @brief Gets static sample tokens of given types. + * @param types List of token types to get tokens for. Last element in the list must be Token::INVALID. + * + * It's used by Parser to try every token type as a possible candidate for a next valid token. + * You should not need to use this method. + * + * @overload + */ + QSet<TokenPtr> getEveryTokenType(QSet<Token::Type> types); + + /** + * @brief Tests whether lexer finished reading all tokens from the query. + * @return true if there is no more tokens to be read, or false otherwise. + * + * This method simply checks whether there's any characters in the query to be tokenized. + * The query is the one defined with prepare(). Query shrinks with very call to getToken() + * and once there's no more characters to consume by getToken(), this method will return false. + * + * If you call getToken() after isEnd() returned false, the getToken() will return Token::INVALID token. + */ + bool isEnd() const; + + /** + * @brief Initializes internal set of static tokens. + * Initializes internal set of tokens used by getEveryTokenType(). + */ + static void staticInit(); + + /** + * @brief Restores string from token list. + * @param tokens List of tokens. + * @return String that was represented by tokens. + * + * It simply joins values of all tokens from the list using empty string separator (that is no separator at all). + */ + static QString detokenize(const TokenList& tokens); + + /** + * @brief Tokenizes given SQL query with given dialect. + * @param sql SQL query to tokenize. + * @param dialect SQLite dialect to use when tokenizing. + * @return List of tokens from tokenizing. + * + * This method is a shortcut for: + * @code + * Lexer lexer(dialect); + * lexer.tokenize(sql); + * @endcode + */ + static TokenList tokenize(const QString& sql, Dialect dialect); + + /** + * @brief Translates token pointer into common token shared pointer. + * @param token Token pointer to translate. + * @return Shared pointer if found, or null pointer if not found. + * + * This method should be used against token pointers extracted from getEveryTokenType() results. + * Then pointer from any TokenPtr (returned from getEveryTokenType()) is extracted using the + * QSharedPointer::data(), then this method can be used to return back to the QSharedPointer. + * + * As Lexer keeps static internal list of tokens representing token types, + * it can translate token pointer into shared pointer by comparing them. + * + * This method and getEveryTokenType() methods are used strictly by Parser and you should not + * need to use them. + */ + static TokenPtr getEveryTokenTypePtr(Token* token); + + /** + * @brief Provides token representing semicolon in given SQLite dialect. + * @param dialect Dialect to use. + * @return Token representing semicolon. + * + * This is used by Parser to complete the parsed query in case the input query did not end with semicolon. + * Given the \p dialect it provides proper token for that dialect (they are different by Lemon token ID). + */ + static TokenPtr getSemicolonToken(Dialect dialect); + + private: + /** + * @brief Creates token for every token type internal tables. + * @param dialect SQLite dialect to create token for. + * @param lemonType Lemon token ID for this token type. + * @param type SQLiteStudio token type. + * @param value Sample value for the token. + * @return Created token. + * + * Every token type internal tables are populated using this method. + * + * @see getEveryTokenType() + */ + static TokenPtr createTokenType(Dialect dialect, int lemonType, Token::Type type, const QString& value); + + /** + * @brief Current "tolerant mode" flag. + * + * @see setTolerantMode() + */ + bool tolerant = false; + + /** + * @brief Lexer's SQLite dialect. + */ + Dialect dialect; + + /** + * @brief SQL query to be tokenized with getToken(). + * + * It's defined with prepare(). + */ + QString sqlToTokenize; + + /** + * @brief Current tokenizer position in the sqlToTokenize. + * + * This position index is used to track which SQL characters should be tokenized + * on next call to getToken(). + * + * It's reset to 0 by prepare() and cleanUp(). + */ + quint64 tokenPosition; + + /** + * @brief Internal table of every token type for SQLite 2. + * + * @see semicolonTokenSqlite3 + */ + static TokenPtr semicolonTokenSqlite2; + + /** + * @brief Internal table of every token type for SQLite 3. + * + * Internal token type table contains single token per token type, so it can be used to probe the Parser + * for next valid token candidates. + */ + static TokenPtr semicolonTokenSqlite3; + + /** + * @brief Internal table of every token type for SQLite 2. + * + * @see everyTokenType3 + */ + static QHash<Token::Type,QSet<TokenPtr> > everyTokenType2; + + /** + * @brief Internal table of every token type for SQLite 3. + * + * Set of tokens representing all token types, including diversification by values for keywords and operators. + * It's used by the Parser to probe candidates for next valid token. + */ + static QHash<Token::Type,QSet<TokenPtr> > everyTokenType3; + + /** + * @brief Map of every token type pointer to its QSharedPointer from internal tables. + * + * This is used by getEveryTokenTypePtr(). + */ + static QHash<Token*,TokenPtr> everyTokenTypePtrMap; +}; + +#endif // LEXER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/lexer_low_lev.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/lexer_low_lev.cpp new file mode 100644 index 0000000..894afd1 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/lexer_low_lev.cpp @@ -0,0 +1,470 @@ +#include "lexer_low_lev.h" +#include "token.h" +#include "keywords.h" +#include "sqlite3_parse.h" +#include "sqlite2_parse.h" +#include "common/utils.h" +#include "common/utils_sql.h" + +#include <QByteArray> +#include <QChar> +#include <QDebug> + +// +// Low-level lexer routines based on tokenizer from SQLite 3.7.15.2 +// + +bool isIdChar(const QChar& c) +{ + return c.isPrint() && !c.isSpace() && !doesObjectNeedWrapping(c); +} + +int lexerGetToken(const QString& z, TokenPtr token, int sqliteVersion, bool tolerant) +{ + if (sqliteVersion < 2 || sqliteVersion > 3) + { + qCritical() << "lexerGetToken() called with invalid sqliteVersion:" << sqliteVersion; + return 0; + } + + if (tolerant && !token.dynamicCast<TolerantToken>()) + { + qCritical() << "lexerGetToken() called with tolerant=true, but not a TolerantToken entity!"; + return 0; + } + + bool v3 = sqliteVersion == 3; + int i; + QChar c; + QChar z0 = charAt(z, 0); + + for (;;) + { + if (z0.isSpace()) + { + for(i=1; charAt(z, i).isSpace(); i++) {} + token->lemonType = v3 ? TK3_SPACE : TK2_SPACE; + token->type = Token::SPACE; + return i; + } + if (z0 == '-') + { + if (charAt(z, 1) == '-') + { + for (i=2; (c = charAt(z, i)) != 0 && c != '\n'; i++) {} + token->lemonType = v3 ? TK3_COMMENT : TK2_COMMENT; + token->type = Token::COMMENT; + return i; + } + token->lemonType = v3 ? TK3_MINUS : TK2_MINUS; + token->type = Token::OPERATOR; + return 1; + } + if (z0 == '(') + { + token->lemonType = v3 ? TK3_LP : TK2_LP; + token->type = Token::PAR_LEFT; + return 1; + } + if (z0 == ')') + { + token->lemonType = v3 ? TK3_RP : TK2_RP; + token->type = Token::PAR_RIGHT; + return 1; + } + if (z0 == ';') + { + token->lemonType = v3 ? TK3_SEMI : TK2_SEMI; + token->type = Token::OPERATOR; + return 1; + } + if (z0 == '+') + { + token->lemonType = v3 ? TK3_PLUS : TK2_PLUS; + token->type = Token::OPERATOR; + return 1; + } + if (z0 == '*') + { + token->lemonType = v3 ? TK3_STAR : TK2_STAR; + token->type = Token::OPERATOR; + return 1; + } + if (z0 == '/') + { + if ( charAt(z, 1) != '*' ) + { + token->lemonType = v3 ? TK3_SLASH : TK2_SLASH; + token->type = Token::OPERATOR; + return 1; + } + + if ( charAt(z, 2) == 0 ) + { + token->lemonType = v3 ? TK3_COMMENT : TK2_COMMENT; + token->type = Token::COMMENT; + if (tolerant) + token.dynamicCast<TolerantToken>()->invalid = true; + + return 2; + } + for (i = 3, c = charAt(z, 2); (c != '*' || charAt(z, i) != '/') && (c = charAt(z, i)) != 0; i++) {} + + if (tolerant && (c != '*' || charAt(z, i) != '/')) + token.dynamicCast<TolerantToken>()->invalid = true; + + if ( c > 0 ) + i++; + token->lemonType = v3 ? TK3_COMMENT : TK2_COMMENT; + token->type = Token::COMMENT; + return i; + } + if (z0 == '%') + { + token->lemonType = v3 ? TK3_REM : TK2_REM; + token->type = Token::OPERATOR; + return 1; + } + if (z0 == '=') + { + token->lemonType = v3 ? TK3_EQ : TK2_EQ; + token->type = Token::OPERATOR; + return 1 + (charAt(z, 1) == '='); + } + if (z0 == '<') + { + if ( (c = charAt(z, 1)) == '=' ) + { + token->lemonType = v3 ? TK3_LE : TK2_LE; + token->type = Token::OPERATOR; + return 2; + } + else if ( c == '>' ) + { + token->lemonType = v3 ? TK3_NE : TK2_NE; + token->type = Token::OPERATOR; + return 2; + } + else if( c == '<' ) + { + token->lemonType = v3 ? TK3_LSHIFT : TK2_LSHIFT; + token->type = Token::OPERATOR; + return 2; + } + else + { + token->lemonType = v3 ? TK3_LT : TK2_LT; + token->type = Token::OPERATOR; + return 1; + } + } + if (z0 == '>') + { + if ( (c = charAt(z, 1)) == '=' ) + { + token->lemonType = v3 ? TK3_GE : TK2_GE; + token->type = Token::OPERATOR; + return 2; + } + else if ( c == '>' ) + { + token->lemonType = v3 ? TK3_RSHIFT : TK2_RSHIFT; + token->type = Token::OPERATOR; + return 2; + } + else + { + token->lemonType = v3 ? TK3_GT : TK2_GT; + token->type = Token::OPERATOR; + return 1; + } + } + if (z0 == '!') + { + if ( charAt(z, 1) != '=' ) + { + token->lemonType = v3 ? TK3_ILLEGAL : TK2_ILLEGAL; + token->type = Token::INVALID; + return 2; + } + else + { + token->lemonType = v3 ? TK3_NE : TK2_NE; + token->type = Token::OPERATOR; + return 2; + } + } + if (z0 == '|') + { + if( charAt(z, 1) != '|' ) + { + token->lemonType = v3 ? TK3_BITOR : TK2_BITOR; + token->type = Token::OPERATOR; + return 1; + } + else + { + token->lemonType = v3 ? TK3_CONCAT : TK2_CONCAT; + token->type = Token::OPERATOR; + return 2; + } + } + if (z0 == ',') + { + token->lemonType = v3 ? TK3_COMMA : TK2_COMMA; + token->type = Token::OPERATOR; + return 1; + } + if (z0 == '&') + { + token->lemonType = v3? TK3_BITAND : TK2_BITAND; + token->type = Token::OPERATOR; + return 1; + } + if (z0 == '~') + { + token->lemonType = v3? TK3_BITNOT : TK2_BITAND; + token->type = Token::OPERATOR; + return 1; + } + if (z0 == '`' || + z0 == '\'' || + z0 == '"') + { + QChar delim = z0; + for (i = 1; (c = charAt(z, i)) != 0; i++) + { + if ( c == delim ) + { + if( charAt(z, i+1) == delim ) + i++; + else + break; + } + } + if ( c == '\'' ) + { + token->lemonType = v3? TK3_STRING : TK2_STRING; + token->type = Token::STRING; + return i+1; + } + else if ( c != 0 ) + { + token->lemonType = v3 ? TK3_ID : TK2_ID; + token->type = Token::OTHER; + return i+1; + } + else if (tolerant) + { + if (z0 == '\'') + { + token->lemonType = v3 ? TK3_STRING : TK2_STRING; + token->type = Token::STRING; + } + else + { + token->lemonType = v3 ? TK3_ID : TK2_ID; + token->type = Token::OTHER; + } + token.dynamicCast<TolerantToken>()->invalid = true; + return i; + } + else + { + token->lemonType = v3 ? TK3_ILLEGAL : TK2_ILLEGAL; + token->type = Token::INVALID; + return i; + } + } + if (z0 == '.') + { + if( !charAt(z, 1).isDigit() ) + { + token->lemonType = v3 ? TK3_DOT : TK2_DOT; + token->type = Token::OPERATOR; + return 1; + } + /* + * If the next character is a digit, this is a floating point + * number that begins with ".". Fall thru into the next case + */ + } + if (z0.isDigit()) + { + token->lemonType = v3 ? TK3_INTEGER : TK2_INTEGER; + token->type = Token::INTEGER; + if (v3 && charAt(z, 0) == '0' && (charAt(z, 1) == 'x' || charAt(z, 1) == 'X') && isHex(charAt(z, 2))) + { + for (i=3; isHex(charAt(z, i)); i++) {} + return i; + } + for (i=0; charAt(z, i).isDigit(); i++) {} + if ( charAt(z, i) == '.' ) + { + i++; + while ( charAt(z, i).isDigit() ) + i++; + + token->lemonType = v3 ? TK3_FLOAT : TK2_FLOAT; + token->type = Token::FLOAT; + } + if ( (charAt(z, i) == 'e' || charAt(z, i) == 'E') && + ( charAt(z, i+1).isDigit() + || ((charAt(z, i+1) == '+' || charAt(z, i+1) == '-') && charAt(z, i+2).isDigit()) + ) + ) + { + i += 2; + while ( charAt(z, i+2).isDigit() ) + i++; + + token->lemonType = v3 ? TK3_FLOAT : TK2_FLOAT; + token->type = Token::FLOAT; + } + while ( isIdChar(charAt(z, i)) ) + { + token->lemonType = v3 ? TK3_ILLEGAL : TK2_ILLEGAL; + token->type = Token::INVALID; + i++; + } + return i; + } + if (z0 == '[') + { + for (i = 1, c = z0; c!=']' && (c = charAt(z, i)) != 0; i++) {} + if (c == ']') + { + token->lemonType = v3 ? TK3_ID : TK2_ID; + token->type = Token::OTHER; + } + else if (tolerant) + { + token->lemonType = v3 ? TK3_ID : TK2_ID; + token->type = Token::OTHER; + token.dynamicCast<TolerantToken>()->invalid = true; + } + else + { + token->lemonType = v3 ? TK3_ILLEGAL : TK2_ILLEGAL; + token->type = Token::INVALID; + } + return i; + } + if (z0 == '?') + { + token->lemonType = v3 ? TK3_VARIABLE : TK2_VARIABLE; + token->type = Token::BIND_PARAM; + for (i=1; charAt(z, i+2).isDigit(); i++) {} + return i; + } + if (z0 == '$' || + z0 == '@' || /* For compatibility with MS SQL Server */ + z0 == ':') + { + int n = 0; + token->lemonType = v3 ? TK3_VARIABLE : TK2_VARIABLE; + token->type = Token::BIND_PARAM; + for (i = 1; (c = charAt(z, i)) != 0; i++) + { + if ( isIdChar(c) ) + { + n++; + } + else if ( c == '(' && n > 0 ) + { + do + { + i++; + } + while ( (c = charAt(z, i)) != 0 && !c.isSpace() && c != ')' ); + + if ( c==')' ) + { + i++; + } + else + { + token->lemonType = v3 ? TK3_ILLEGAL : TK2_ILLEGAL; + token->type = Token::INVALID; + } + break; + } + else if ( c == ':' && charAt(z, i+1) == ':' ) + { + i++; + } + else + { + break; + } + } + if( n == 0 ) + { + token->lemonType = v3 ? TK3_ILLEGAL : TK2_ILLEGAL; + token->type = Token::INVALID; + } + + return i; + } + if ((z0 == 'x' || + z0 == 'X') && + v3) + { + if ( charAt(z, 1) == '\'' ) + { + token->lemonType = TK3_BLOB; + token->type = Token::BLOB; + for (i = 2; isXDigit(charAt(z, i)); i++) {} + if (charAt(z, i) != '\'' || i%2) + { + if (tolerant) + { + token->lemonType = TK3_BLOB; + token->type = Token::BLOB; + token.dynamicCast<TolerantToken>()->invalid = true; + } + else + { + token->lemonType = TK3_ILLEGAL; + token->type = Token::INVALID; + } + while (charAt(z, i) > 0 && charAt(z, i) != '\'') + i++; + } + if ( charAt(z, i) > 0 ) + i++; + + return i; + } + /* Otherwise fall through to the next case */ + } + //default: + { + if (!isIdChar(z0)) + break; + + for (i = 1; isIdChar(charAt(z, i)); i++) {} + + if (v3) + token->lemonType = getKeywordId3(z.mid(0, i)); + else + token->lemonType = getKeywordId2(z.mid(0, i)); + + if (token->lemonType == TK3_ID || token->lemonType == TK2_ID) + token->type = Token::OTHER; + else + token->type = Token::KEYWORD; + + return i; + } + } + + if (v3) + token->lemonType = TK3_ILLEGAL; + else + token->lemonType = TK2_ILLEGAL; + + token->type = Token::INVALID; + return 1; +} + diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/lexer_low_lev.h b/SQLiteStudio3/coreSQLiteStudio/parser/lexer_low_lev.h new file mode 100644 index 0000000..87ba7e5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/lexer_low_lev.h @@ -0,0 +1,27 @@ +#ifndef LEXER_LOW_LEV_H +#define LEXER_LOW_LEV_H + +#include "parser/token.h" +#include <QString> + +/** @file */ + +/** + * @brief Low level tokenizer function used by the Lexer. + * @param z Query to tokenize. + * @param[out] token Token container to fill with values. Can be also a TolerantToken. + * @param sqliteVersion SQLite version, for which the tokenizer should work (2 or 3). + * Version affects the list of recognized keywords, a BLOB expression and an object name wrapper with the grave accent character (`). + * @param tolerant If true, then all multi-line and unfinished tokens (strings, comments) + * will be reported with invalid=true in TolerantToken, but the token itself will have type like it was finished. + * If this is true, then \p token must be of type TolerantToken, otherwise the the method will return 0 and log a critical error. + * @return Lemon token ID (see sqlite2_parse.h and sqlite3_parse.h for possible token IDs). + * + * You shouldn't normally need to use this method. Instead of that, use Lexer class, as it provides higher level API. + * + * Most of the method code was taken from SQLite tokenizer code. It is modified to support both SQLite 2 and 3 grammas + * and other SQLiteStudio specific features. + */ +int lexerGetToken(const QString& z, TokenPtr token, int sqliteVersion, bool tolerant = false); + +#endif // LEXER_LOW_LEV_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/parser.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/parser.cpp new file mode 100644 index 0000000..23a4b55 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/parser.cpp @@ -0,0 +1,323 @@ +#include "parser.h" +#include "parsercontext.h" +#include "parsererror.h" +#include "lexer.h" +#include "../db/db.h" +#include "ast/sqliteselect.h" +#include <QStringList> +#include <QDebug> + +// Generated in sqlite*_parse.c by lemon, +// but not exported in any header +void* sqlite3_parseAlloc(void *(*mallocProc)(size_t)); +void sqlite3_parseFree(void *p, void (*freeProc)(void*)); +void sqlite3_parse(void *yyp, int yymajor, Token* yyminor, ParserContext* parserContext); +void sqlite3_parseTrace(FILE *stream, char *zPrefix); +void* sqlite3_parseCopyParserState(void* other); +void sqlite3_parseRestoreParserState(void* saved, void* target); +void sqlite3_parseFreeSavedState(void* other); +void sqlite3_parseAddToken(void* other, Token* token); + +void* sqlite2_parseAlloc(void *(*mallocProc)(size_t)); +void sqlite2_parseFree(void *p, void (*freeProc)(void*)); +void sqlite2_parse(void *yyp, int yymajor, Token* yyminor, ParserContext* parserContext); +void sqlite2_parseTrace(FILE *stream, char *zPrefix); +void* sqlite2_parseCopyParserState(void* other); +void sqlite2_parseRestoreParserState(void* saved, void* target); +void sqlite2_parseFreeSavedState(void* other); +void sqlite2_parseAddToken(void* other, Token* token); + +Parser::Parser(Dialect dialect) +{ + this->dialect = dialect; + init(); +} + +Parser::~Parser() +{ + cleanUp(); +} + +void Parser::cleanUp() +{ + if (lexer) + { + delete lexer; + lexer = nullptr; + } + + if (context) + { + delete context; + context = nullptr; + } +} + +void Parser::fillSqliteDialect() +{ + foreach (SqliteQueryPtr query, context->parsedQueries) + query->setSqliteDialect(dialect); +} + +void *Parser::parseAlloc(void *(*mallocProc)(size_t)) +{ + if (dialect == Dialect::Sqlite2) + return sqlite2_parseAlloc(mallocProc); + else + return sqlite3_parseAlloc(mallocProc); +} + +void Parser::parseFree(void *p, void (*freeProc)(void *)) +{ + if (dialect == Dialect::Sqlite2) + sqlite2_parseFree(p, freeProc); + else + sqlite3_parseFree(p, freeProc); +} + +void Parser::parse(void *yyp, int yymajor, TokenPtr yyminor, ParserContext *parserContext) +{ + if (dialect == Dialect::Sqlite2) + sqlite2_parse(yyp, yymajor, yyminor.data(), parserContext); + else + sqlite3_parse(yyp, yymajor, yyminor.data(), parserContext); +} + +void Parser::parseTrace(FILE *stream, char *zPrefix) +{ + if (dialect == Dialect::Sqlite2) + sqlite2_parseTrace(stream, zPrefix); + else + sqlite3_parseTrace(stream, zPrefix); +} + +void *Parser::parseCopyParserState(void *other) +{ + if (dialect == Dialect::Sqlite2) + return sqlite2_parseCopyParserState(other); + else + return sqlite3_parseCopyParserState(other); +} + +void Parser::parseRestoreParserState(void *saved, void *target) +{ + if (dialect == Dialect::Sqlite2) + sqlite2_parseRestoreParserState(saved, target); + else + sqlite3_parseRestoreParserState(saved, target); +} + +void Parser::parseFreeSavedState(void *other) +{ + if (dialect == Dialect::Sqlite2) + sqlite2_parseFreeSavedState(other); + else + sqlite3_parseFreeSavedState(other); +} + +void Parser::parseAddToken(void *other, TokenPtr token) +{ + if (dialect == Dialect::Sqlite2) + sqlite2_parseAddToken(other, token.data()); + else + sqlite3_parseAddToken(other, token.data()); +} + +bool Parser::parse(const QString &sql, bool ignoreMinorErrors) +{ + context->ignoreMinorErrors = ignoreMinorErrors; + return parseInternal(sql, false); +} + +bool Parser::parseInternal(const QString &sql, bool lookForExpectedToken) +{ + void* pParser = parseAlloc( malloc ); + if (debugLemon) + { + char* label = nullptr; + if (dialect == Dialect::Sqlite2) + label = const_cast<char*>("[LEMON2]: "); + else + label = const_cast<char*>("[LEMON3]: "); + + parseTrace(stderr, label); + } + + reset(); + lexer->prepare(sql); + context->setupTokens = !lookForExpectedToken; + context->executeRules = !lookForExpectedToken; + context->doFallbacks = !lookForExpectedToken; + context->dialect = dialect; + + TokenPtr token = lexer->getToken(); + if (!token.isNull()) + context->addManagedToken(token); + + bool endsWithSemicolon = false; + + while (token) + { + if (token->type == Token::SPACE || + token->type == Token::COMMENT || + token->type == Token::INVALID) + { + parseAddToken(pParser, token); + token = lexer->getToken(); + if (token) + context->addManagedToken(token); + + continue; + } + + endsWithSemicolon = (token->type == Token::OPERATOR && token->value == ";"); + + parse(pParser, token->lemonType, token, context); + token = lexer->getToken(); + if (!token.isNull()) + context->addManagedToken(token); + } + + if (lookForExpectedToken) + { + expectedTokenLookup(pParser); + } + else + { + if (!endsWithSemicolon) + { + token = Lexer::getSemicolonToken(dialect); + parse(pParser, token->lemonType, token, context); + } + + qint64 endIdx = sql.length(); + TokenPtr endToken = TokenPtr::create(0, Token::INVALID, QString::null, endIdx, endIdx); + parse(pParser, 0, endToken, context); + } + + fillSqliteDialect(); + + // Free all non-termials having destructors + parseFree(pParser, free); + + context->flushErrors(); + + if (context->isSuccessful()) + { + for (SqliteQueryPtr query : context->parsedQueries) + query->processPostParsing(); + } + + return context->isSuccessful(); +} + +TokenList Parser::getNextTokenCandidates(const QString &sql) +{ + context->ignoreMinorErrors = true; + parseInternal(sql, true); + TokenList results = acceptedTokens; + acceptedTokens.clear(); + return results; +} + +bool Parser::isSuccessful() const +{ + return context->isSuccessful(); +} + +void Parser::reset() +{ + acceptedTokens.clear(); + lexer->cleanUp(); + context->cleanUp(); +} + +SqliteExpr *Parser::parseExpr(const QString &sql) +{ + SqliteSelectPtr select = parse<SqliteSelect>("SELECT "+sql+";"); + if (!select || select->coreSelects.size() == 0 || select->coreSelects.first()->resultColumns.size() == 0) + return nullptr; + + SqliteExpr* expr = select->coreSelects.first()->resultColumns.first()->expr; + expr->setParent(nullptr); + return expr; +} + +void Parser::expectedTokenLookup(void* pParser) +{ + void* savedParser = parseCopyParserState(pParser); + + ParserContext tempContext; + tempContext.executeRules = false; + tempContext.executeRules = false; + tempContext.doFallbacks = false; + QSet<TokenPtr> tokenSet = + lexer->getEveryTokenType({ + Token::KEYWORD, Token::OTHER, Token::PAR_LEFT, Token::PAR_RIGHT, Token::OPERATOR, + Token::CTX_COLLATION, Token::CTX_COLUMN, Token::CTX_DATABASE, Token::CTX_FUNCTION, + Token::CTX_INDEX, Token::CTX_JOIN_OPTS, Token::CTX_TABLE, Token::CTX_TRIGGER, + Token::CTX_VIEW, Token::CTX_FK_MATCH, Token::CTX_ERROR_MESSAGE, Token::CTX_PRAGMA, + Token::CTX_ALIAS, Token::CTX_TABLE_NEW, Token::CTX_INDEX_NEW, Token::CTX_TRIGGER_NEW, + Token::CTX_VIEW_NEW, Token::CTX_COLUMN_NEW, Token::CTX_TRANSACTION, + Token::CTX_CONSTRAINT, Token::CTX_COLUMN_TYPE, Token::CTX_OLD_KW, Token::CTX_NEW_KW, + Token::CTX_ROWID_KW, Token::INVALID + }); + + foreach (TokenPtr token, tokenSet) + { + parse(pParser, token->lemonType, token, &tempContext); + + if (tempContext.isSuccessful()) + acceptedTokens += token; + + tempContext.cleanUp(); + parseRestoreParserState(savedParser, pParser); + } + parseFreeSavedState(savedParser); +} + +void Parser::init() +{ + lexer = new Lexer(dialect); + context = new ParserContext(); +} + +const QList<ParserError *> &Parser::getErrors() +{ + return context->getErrors(); +} + +QString Parser::getErrorString() +{ + QStringList msgs; + foreach (ParserError* error, getErrors()) + { + msgs += error->getMessage(); + } + return msgs.join(",\n"); +} + +TokenList Parser::getParsedTokens() +{ + return context->getManagedTokens(); +} + +void Parser::setLemonDebug(bool enabled) +{ + debugLemon = enabled; +} + +void Parser::setDialect(Dialect dialect) +{ + if (this->dialect == dialect) + return; + + this->dialect = dialect; + delete lexer; + lexer = new Lexer(dialect); +} + +const QList<SqliteQueryPtr>& Parser::getQueries() +{ + return context->getQueries(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/parser.h b/SQLiteStudio3/coreSQLiteStudio/parser/parser.h new file mode 100644 index 0000000..aaf3962 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/parser.h @@ -0,0 +1,360 @@ +#ifndef PARSER_H +#define PARSER_H + +#include "token.h" +#include "../dialect.h" +#include "ast/sqlitequery.h" +#include "ast/sqliteexpr.h" + +class Lexer; +class ParserContext; +class ParserError; + +/** + * @brief SQL parser. + * + * The Parser analyzes given query and produces an Abstract Syntax Tree (AST). + * The AST is a tree of objects describing parsed query. + * + * Typical use case would be: + * @code + * Parser parser(db->getDialect()); + * if (parser.parse(queryString)) + * { + * QList<SqliteQueryPtr> queries = parser.getQueries(); + * qDebug() << "number of queries parsed:" << queries.size(); + * foreach (SqliteQueryPtr query, queries) + * { + * // do stuff with parsed queries + * // ... + * if (query.dynamicCast<SqliteSelect>()) + * { + * qDebug() << "it's a select!"; + * } + * } + * } + * else + * { + * qDebug() << "Error while parsing:" << parser.getErrorString(); + * } + * @endcode + * + * There's also a convenient parse<T>() method with template argument. + * + * There is a getNextTokenCandidates() to ask for all valid (according to syntax + * rules) token types to be used after given query string, + * + * Finally, there is a parseExpr() to parse just a SQLite expression + * (http://sqlite.org/lang_expr.html). + * + * Parser works basing on SQLite grammar defined in sqlite2.y and sqlite3.y files. + * Since there are 2 completly separate grammar definitions, there are 2 dialects + * that the parser works with. + * + * This is a high-level API to the Lemon Parser, the original SQLite parser. + */ +class API_EXPORT Parser +{ + public: + /** + * @brief Creates parser for given SQLite dialect. + * @param dialect SQLite dialect to use. Can be changed later with setDialect(). + */ + Parser(Dialect dialect); + + /** + * @brief Releases internal resources. + */ + virtual ~Parser(); + + /** + * @brief Enables or disables low-level debug messages for this parser. + * @param enabled true to enable, false to disable debug messages. + * + * Enabling this causes detailed debug messages from the Lemon parser + * to be printed. It is useful if you cannot understand why the parser + * thinks that the query is incorrect, etc. + */ + void setLemonDebug(bool enabled); + + /** + * @brief Changes dialect used by parser. + * @param dialect Dialect to use. + */ + void setDialect(Dialect dialect); + + /** + * @brief Parses given query string. + * @param sql SQL query string to parse. Can be multiple queries separated with semicolon. + * @param ignoreMinorErrors If true, then parser will ignore minor errors. Detailed descritpion below. + * @return true if the query was successfully parsed, or false if not. + * + * When the parser encounters syntax error, it stops and returns false. The AST objects (parsed queries) + * are partially filled with data - as much as it was possible till the error. Errors can be examined + * with getErrors() or getErrorString(). + * + * The \p ignoreMinorErrors allows to ignore minor syntax errors. The minor error is the error + * when for example there's a SELECT query, but no result column was typed yet. Normally this is incorrect + * query, cause SELECT statement requires at least 1 result column, but we can tell parser to ignore it. + * + * The usual case for minor error is when there's a SQLite expression missing at position, where it's expected, + * or when the expression is incomplete, like <tt>database.table.</tt> (no column name as the last part). + */ + bool parse(const QString& sql, bool ignoreMinorErrors = false); + + /** + * @brief Parses SQLite expression. + * @param sql SQLite expression. + * @return Parsed object, or null on failure. Parser doesn't own parsed object, you have to take care of deleting it. + * + * SQLite expression is any expression that you could type after <tt>"SELECT * FROM WHERE"</tt>, etc. + * It's syntax is described at: http://sqlite.org/lang_expr.html + */ + SqliteExpr* parseExpr(const QString& sql); + + /** + * @brief Parses given query and returns it AST specialized object. + * @tparam T Type of AST object to parse into. + * @param query SQL query string to parse. + * @return Shared pointer to the parsed AST object, or null pointer if the query could not be parsed, + * or the parsed object was not of the requested type. + * + * This is a convenient method to parse string query, pick first parsed query from getQueries() + * and case it into desired AST object type. If this process fails at any point, the result returned will be + * a null pointer. + * + * Example: + * @code + * Parser parser(db->getDialect()); + * SqliteSelectPtr select = parser.parse<SelectPtr>(queryString); + * if (!select) + * { + * qCritical() << "Could not parse" << queryString << "to a SELECT statement, details:" << parser.getErrorString(); + * return; + * } + * // do stuff with the 'select' object + * // ... + * @endcode + */ + template <class T> + QSharedPointer<T> parse(const QString& query) + { + if (!parse(query) || getQueries().size() == 0) + return QSharedPointer<T>(); + + return getQueries().first().dynamicCast<T>(); + } + + /** + * @brief Tests what are possible valid candidates for the next token. + * @param sql Part of the SQL query to check for the next token. + * @return List of token candidates. + * + * This method gets list of all token types from Lexer::getEveryTokenType() and tests which of them does the parser + * accept for the next token after the given query. + * + * You should treat the results of this method as a list of token <b>types</b>, rather than explicit tokens. + * Each token in the results represents a logical grammar entity. You should look at the Token::type and Token::value, + * while the Token::value is meaningful only for Token::KEYWORD, or Token::OPERATOR. For other token types, the value + * is just an example value (like for Token::INTEGER all numbers are valid candidates, not just one returned + * from this method). + */ + TokenList getNextTokenCandidates(const QString& sql); + + /** + * @brief Provides list of queries parsed recently by the parser. + * @return List of queries. + * + * On successful execution this list should contain at least 1 query, unless parsed query + * was a blank string - in that case this method will return list with no elements. + * + * In case of parsing error it's undefined how many elements will be in the list + * and also how much of the information will be filled in the queries - it depends on where the error appeared. + */ + const QList<SqliteQueryPtr>& getQueries(); + + /** + * @brief Provides list of errors that occurred during parsing. + * @return List of errors. + * + * Usually there's just one error, but there are cases when there might be more error on the list. + * That would be for example if you type "!" somewhere in the query where it should not be. + * Parser can deal with such errors and proceed. Such errors are later reported as failed parsing after all, + * but parser can continue and provide more data for AST objects (even they will be result of failed parsing process) + * and find other errors. In such cases, there can be 2, or even more errors on the list. + */ + const QList<ParserError*>& getErrors(); + + /** + * @brief Provides error message from recent failed parsing process. + * @return Error message. + * + * This is convenient method to get first error getom getErrors() and return message from it. + */ + QString getErrorString(); + + /** + * @brief Provides list of tokens procudes during parsing process. + * @return List of tokens. + * + * Parser tokenizes query in order to parse it. It stores those tokens, so you can use them and you don't + * need to put query through the Lexer again (after Parser did it). + */ + TokenList getParsedTokens(); + + /** + * @brief Tells whether most recent parsing was successful. + * @return true if parsing was successful, or false otherwise. + * + * This method tells result for: parse(), parse<T>(), getNextTokenCandidates() and parseExpr(). + */ + bool isSuccessful() const; + + /** + * @brief Clears parser state. + * + * Clears any parsed queries, stored tokens, errors, etc. + */ + void reset(); + + private: + + /** + * @brief Does the actual parsing job. + * @param sql Query to be parsed. + * @param lookForExpectedToken true if the parsing should be in "look for valid token candidates" mode, + * or false for regular mode. + * @return true on success, or false on failure. + * + * Both parse() and getNextTokenCandidates() call this method. + */ + bool parseInternal(const QString &sql, bool lookForExpectedToken); + + /** + * @brief Probes token types against the current parser state. + * @param pParser Pointer to Lemon parser. + * + * Probes all token types against current state of the parser. After each probe, the result is stored + * and the parser state is restored to as what it was before the probe. + * + * After all tokens were probed, we have the full information on what tokens are welcome + * at this parser state. This information is stored in the acceptedTokens member. + */ + void expectedTokenLookup(void *pParser); + + /** + * @brief Initializes Parser's internals. + * + * Creates internal Lexer and ParserContext. + */ + void init(); + + /** + * @brief Cleans up Parser's resources. + * + * Deletes internal Lexer and ParserContext. + */ + void cleanUp(); + + /** + * @brief Propagates dialect to all AST objects. + * + * This is called after successful parsing to set the adequate SQLite dialect + * in all AST objects. + */ + void fillSqliteDialect(); + + /** + * @brief Creates Lemon parser. + * @return Pointer to Lemon parser. + */ + void* parseAlloc(void *(*mallocProc)(size_t)); + + /** + * @brief Releases memory of the Lemon parser. + * @param p Pointer to Lemon parser. + */ + void parseFree(void *p, void (*freeProc)(void*)); + + /** + * @brief Invokes next step of Lemon parsing process. + * @param yyp Pointer to the Lemon parser. + * @param yymajor Lemon token ID (Token::lemonType) of the next token to be parsed. + * @param yyminor Next Token object to be parsed. + * @param parserContext Common context object for the parsing process. + * + * This method feeds Lemon parser with next token. This is the major input method + * for parsing the query. It's a bridge between the high-level Parser API + * and the low-level Lemon parser. + */ + void parse(void *yyp, int yymajor, TokenPtr yyminor, ParserContext* parserContext); + + /** + * @brief Enables low-level parser debug messages. + * @param stream Stream to write messages to. + * @param zPrefix Prefix for all messages. + */ + void parseTrace(FILE *stream, char *zPrefix); + + /** + * @brief Copies Lemon parser state. + * @param other Input parser state. + * @return Copied parser state. + */ + void* parseCopyParserState(void* other); + + /** + * @brief Restores Lemon parser state from saved copy. + * @param saved Saved copy of Lemon parser state. + * @param target Parser state to restore from saved copy. + */ + void parseRestoreParserState(void* saved, void* target); + + /** + * @brief Releases memory used for the Lemon parser state copy. + * @param other Lemon parser state to be freed. + */ + void parseFreeSavedState(void* other); + + /** + * @brief Adds meaningless token into Lemon's parser stack. + * @param other Lemon parser. + * @param token Token to be added. + * + * This method is used to add spaces and comments to the Lemon's stack. + */ + void parseAddToken(void* other, TokenPtr token); + + /** + * @brief Parser's dialect. + */ + Dialect dialect; + + /** + * @brief Flag indicating if the Lemon low-level debug messages are enabled. + */ + bool debugLemon = false; + + /** + * @brief Parser's internal Lexer. + */ + Lexer* lexer = nullptr; + + /** + * @brief Parser's internal context shared for the all Lemon parsing steps. + * + * Context is used as an output from Lemon parser. Lemon parser stores error details, token maps, + * and others in it. + * + * On the other side, Parser class puts configuration into the Context, so Lemon + * can use it. + */ + ParserContext* context = nullptr; + + /** + * @brief List of valid tokens collected by expectedTokenLookup(). + */ + TokenList acceptedTokens; +}; + +#endif // PARSER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/parser_helper_stubs.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/parser_helper_stubs.cpp new file mode 100644 index 0000000..039c9a5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/parser_helper_stubs.cpp @@ -0,0 +1,45 @@ +#include "parser_helper_stubs.h" +#include "ast/sqlitecreatetable.h" + +ParserStubAlias::ParserStubAlias(const QString &name, bool asKw) +{ + this->name = name; + this->asKw = asKw; +} + +ParserIndexedBy::ParserIndexedBy(const QString &name) +{ + indexedBy = name; +} + +ParserIndexedBy::ParserIndexedBy(bool notIndexed) +{ + this->notIndexedKw = notIndexed; +} + + +ParserStubInsertOrReplace::ParserStubInsertOrReplace(bool replace) +{ + this->replace = replace; +} + +ParserStubInsertOrReplace::ParserStubInsertOrReplace(bool replace, SqliteConflictAlgo orConflict) +{ + this->replace = replace; + this->orConflict = orConflict; +} + + +ParserStubExplain::ParserStubExplain(bool explain, bool queryPlan) +{ + this->explain = explain; + this->queryPlan = queryPlan; +} + + +ParserDeferSubClause::ParserDeferSubClause(SqliteDeferrable deferrable, SqliteInitially initially) +{ + this->initially = initially; + this->deferrable = deferrable; +} + diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/parser_helper_stubs.h b/SQLiteStudio3/coreSQLiteStudio/parser/parser_helper_stubs.h new file mode 100644 index 0000000..97a6393 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/parser_helper_stubs.h @@ -0,0 +1,118 @@ +#ifndef PARSER_HELPER_STUBS_H +#define PARSER_HELPER_STUBS_H + +#include "parsercontext.h" +#include "ast/sqlitebegintrans.h" +#include "ast/sqlitecreatetable.h" +#include "ast/sqliteconflictalgo.h" +#include "ast/sqliteselect.h" +#include "ast/sqliteindexedcolumn.h" +#include "ast/sqliteforeignkey.h" +#include "ast/sqliteorderby.h" + +#include <QString> +#include <QList> + +/** @file + * + * This file contains only structures and functions + * that are helpful in parsers generated by lemon, + * because lemon uses C unions, therefore only primitive + * types can be used as data type. + * (see %type declarations in *.y files). + */ + +/** + * @brief Stores 'dbnm' grammar rule. + */ +struct ParserFullName +{ + QString name1 = QString::null; + QString name2 = QString::null; +}; + +/** + * @brief Stores <tt>EXPLAIN</tt> and <tt>QUERY PLAN</tt> grammar rules. + */ +struct ParserStubExplain +{ + ParserStubExplain(bool explain, bool queryPlan); + + bool explain; + bool queryPlan; +}; + +/** + * @brief Stores "<tt>OR</tt> conflict" grammar rules. + */ +struct ParserStubInsertOrReplace +{ + explicit ParserStubInsertOrReplace(bool replace); + ParserStubInsertOrReplace(bool replace, SqliteConflictAlgo orConflict); + + bool replace; + SqliteConflictAlgo orConflict; +}; + +/** + * @brief Stores grammar rules for <tt>BEGIN/END/COMMIT/ROLLBACK</tt> additional parameters. + */ +struct ParserStubTransDetails +{ + QString name = QString::null; + SqliteBeginTrans::Type type = SqliteBeginTrans::Type::null; + bool transactionKw = false; + bool toKw = false; + SqliteConflictAlgo onConflict = SqliteConflictAlgo::null; +}; + +typedef QList<SqliteCreateTable::Column*> ParserCreateTableColumnList; +typedef QList<SqliteCreateTable::Constraint*> ParserCreateTableConstraintList; +typedef QList<SqliteCreateTable::Column::Constraint*> ParserCreateTableColumnConstraintList; +typedef QList<SqliteForeignKey::Condition*> ParserFkConditionList; +typedef QList<SqliteExpr*> ParserExprList; +typedef QList<SqliteSelect::Core::ResultColumn*> ParserResultColumnList; +typedef QList<SqliteSelect::Core::JoinSourceOther*> ParserOtherSourceList; +typedef QList<QString> ParserStringList; +typedef QList<SqliteOrderBy*> ParserOrderByList; +typedef QList<SqliteQuery*> ParserQueryList; +typedef QPair<QString,SqliteExpr*> ParserSetValue; +typedef QList<ParserSetValue> ParserSetValueList; +typedef QList<SqliteIndexedColumn*> ParserIndexedColumnList; +typedef QList<ParserExprList> ParserExprNestedList; + +/** + * @brief Stores parameters for defferable foreign keys. + */ +struct ParserDeferSubClause +{ + ParserDeferSubClause(SqliteDeferrable deferrable, SqliteInitially initially); + + SqliteInitially initially; + SqliteDeferrable deferrable; +}; + +/** + * @brief Stores "<tt>AS</tt> aliasName" grammar rule. + */ +struct ParserStubAlias +{ + ParserStubAlias(const QString& name, bool asKw); + + QString name = QString::null; + bool asKw = false; +}; + +/** + * @brief Stores <tt>NOT INDEXED/INDEXED BY</tt> grammar rules. + */ +struct ParserIndexedBy +{ + explicit ParserIndexedBy(const QString& name); + explicit ParserIndexedBy(bool indexedBy); + + bool notIndexedKw = false; + QString indexedBy = QString::null; +}; + +#endif // PARSER_HELPER_STUBS_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/parsercontext.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/parsercontext.cpp new file mode 100644 index 0000000..7394e75 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/parsercontext.cpp @@ -0,0 +1,184 @@ +#include "parsercontext.h" +#include "parsererror.h" +#include "lexer.h" +#include <QDebug> + +ParserContext::~ParserContext() +{ + cleanUp(); +} + +void ParserContext::addQuery(SqliteQuery *query) +{ + parsedQueries << SqliteQueryPtr(query); +} + +void ParserContext::error(TokenPtr token, const QString &text) +{ + if (token->start > -1 && token->end > -1) + errors << new ParserError(token, text); + else if (managedTokens.size() > 0) + errors << new ParserError(managedTokens.last()->start, managedTokens.last()->end + 1, text); + else + errors << new ParserError(text); + + successful = false; +} + +void ParserContext::error(Token* token, const QString& text) +{ + error(getTokenPtr(token), text); +} + +void ParserContext::error(const QString &text) +{ + errors << new ParserError(text); + successful = false; +} + +void ParserContext::minorErrorAfterLastToken(const QString &text) +{ + if (ignoreMinorErrors) + return; + + if (managedTokens.isEmpty()) + { + qCritical() << "Tried to report minor error after last token, but there's no tokens!"; + return; + } + + error(managedTokens.last(), text); +} + +void ParserContext::minorErrorBeforeNextToken(const QString &text) +{ + if (ignoreMinorErrors) + return; + + raiseErrorBeforeNextToken = true; + nextTokenError = text; +} + +void ParserContext::errorAfterLastToken(const QString& text) +{ + if (managedTokens.isEmpty()) + { + qCritical() << "Tried to report error after last token, but there's no tokens!"; + return; + } + + error(managedTokens.last(), text); +} + +void ParserContext::errorBeforeNextToken(const QString& text) +{ + raiseErrorBeforeNextToken = true; + nextTokenError = text; +} + +void ParserContext::errorAtToken(const QString& text, int pos) +{ + if (managedTokens.isEmpty()) + { + qCritical() << "Tried to report error at token" << pos << ", but there's no tokens!"; + return; + } + + int idx = managedTokens.size() - 1 + pos; + if (idx < 0 && idx >= managedTokens.size()) + { + qCritical() << "Tried to report error at token" << pos << ", calculated idx was out of range:" << idx + << "(manages tokens size:" << managedTokens.size() << ")."; + return; + } + + error(managedTokens[idx], text); +} + +void ParserContext::flushErrors() +{ + if (raiseErrorBeforeNextToken && !ignoreMinorErrors) + { + if (managedTokens.size() > 0) + error(managedTokens.last(), QObject::tr("Incomplete query.")); + else + error(QObject::tr("Incomplete query.")); + + nextTokenError = QString::null; + raiseErrorBeforeNextToken = false; + } +} + +TokenPtr ParserContext::getTokenPtr(Token* token) +{ + if (tokenPtrMap.contains(token)) + return tokenPtrMap[token]; + + TokenPtr tokenPtr = Lexer::getEveryTokenTypePtr(token); + if (!tokenPtr.isNull()) + return tokenPtr; + + qWarning() << "No TokenPtr for Token*. Token asked:" << token->toString(); + return TokenPtr(); +} + +TokenList ParserContext::getTokenPtrList(const QList<Token*>& tokens) +{ + TokenList resList; + foreach (Token* token, tokens) + resList << getTokenPtr(token); + + return resList; +} + +void ParserContext::addManagedToken(TokenPtr token) +{ + managedTokens << token; + tokenPtrMap[token.data()] = token; + + if (raiseErrorBeforeNextToken) + { + error(token, nextTokenError); + nextTokenError = QString::null; + raiseErrorBeforeNextToken = false; + } +} + +bool ParserContext::isSuccessful() const +{ + return successful; +} + +const QList<SqliteQueryPtr>& ParserContext::getQueries() +{ + return parsedQueries; +} + +const QList<ParserError *> &ParserContext::getErrors() +{ + return errors; +} + +void ParserContext::cleanUp() +{ + foreach (ParserError* err, errors) + delete err; + + parsedQueries.clear(); + errors.clear(); + managedTokens.clear(); + nextTokenError.clear(); + tokenPtrMap.clear(); + raiseErrorBeforeNextToken = false; + successful = true; +} + +bool ParserContext::isManagedToken(Token* token) +{ + return tokenPtrMap.contains(token); +} + +TokenList ParserContext::getManagedTokens() +{ + return managedTokens; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/parsercontext.h b/SQLiteStudio3/coreSQLiteStudio/parser/parsercontext.h new file mode 100644 index 0000000..d52c021 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/parsercontext.h @@ -0,0 +1,289 @@ +#ifndef PARSERCONTEXT_H +#define PARSERCONTEXT_H + +#include "ast/sqlitequery.h" +#include "parser.h" +#include "../dialect.h" + +#include <QHash> +#include <QList> +#include <QSet> + +class ParserError; + +/** + * @brief Parser context for SQL parsing process + * This class should not be used outside of @class Parser. + */ +class ParserContext +{ + friend class Parser; + + public: + /** + * @brief Releases all internal resources. + */ + virtual ~ParserContext(); + + /** + * @brief Adds parsed query to collection of parsed queries. + * @param query Parsed query (AST object). + * + * This is called by Lemon parser. + */ + void addQuery(SqliteQuery* query); + + /** + * @brief Stores error at given token with given message. + * @param token Token at which the error occurred. + * @param text Error message. + * + * This is called by Lemon parser. + */ + void error(TokenPtr token, const QString& text); + + /** + * @overload + */ + void error(Token* token, const QString& text); + + /** + * @brief Stores error with given message. + * @param text Error message. + * + * This method is used to report an error not related to specific token, + * like when Lemon's stack would get exceeded (which is very unlikely). + * + * This is called by Lemon parser. + * + * @overload + */ + void error(const QString& text); + + /** + * @brief Stores error with most recently parsed token and given message. + * @param text Error message. + * + * Lemon parser calls it when it found out that the error started at the token before. + * + * This is just a minor error, so it will be ognored if ignoreMinorErrors is set. + */ + void minorErrorAfterLastToken(const QString& text); + + /** + * @brief Marks next token to be parsed with given error message. + * @param text Error message. + * + * Lemon parser calls it when it knows that any next token will be an error. + * + * This is just a minor error, so it will be ognored if ignoreMinorErrors is set. + */ + void minorErrorBeforeNextToken(const QString& text); + + /** + * @brief Stores error message for most recently parsed token. + * @param text Error message. + * + * Lemon parser calls it when critical error occurred at the most recently parsed token. + */ + void errorAfterLastToken(const QString& text); + + /** + * @brief Stores error message for the next token to be parsed. + * @param text Error message. + * + * Lemon parser calls it when critical error is about to happen at any next token. + */ + void errorBeforeNextToken(const QString& text); + + /** + * @brief Reports parsing error at given token position. + * @param text Error message. + * @param pos Position relative to after the last token. -1 means the last token, -2 the token before it and so on. -1 is default. + * + * This method is only useful when we know exactly which token was problematic. If error relates to some already wrapped + * syntax rule, it may have many tokens and it's hard to tell which token should we blame, but sometimes it can be calculated. + * Anyway, the token with error is reported by the pos argument. If you don't pass it, it means the error is at last token. + * + * Lemon parser uses it for example when there's a statement <tt>"CREATE TABLE ... (...) WITHOUT ROWID"</tt>. The SQLite grammar + * rule says, that the <tt>"ROWID"</tt> at the end is not necessarily the <tt>ROWID</tt> keyword, but it can be any word, + * but for now SQLite doesn't understand any other words at that position anyway and returns errors. + */ + void errorAtToken(const QString& text, int pos = -1); + + /** + * @brief Flushes pending errors. + * + * In case the errorBeforeNextToken() was called and no more tokens were feed to the context, then this method flushes + * pending error as the error for the last token consumed, but only if minor errors are not ignored. + * This happens for example for "SELECT " statement, where it's not correct, but it's a minor error, cause user + * might enter more contents afterwards. + */ + void flushErrors(); + + /** + * @brief Translates token pointer to it's shared pointer instance. + * @param token Token pointer to translate. + * @return QSharedPointer for the token, or null shared pointer in case of failure. + * + * This method works basing on internal collection of managed tokens. At each step of parsing, the internal lexer + * provides token (in form of shared pointer) and that token is then passed to the Lemon parser (as a pure C++ pointer, + * extracted from shared pointer). The very same token is stored in the internal collection of managed tokens (as a shared pointer). + * This method allows to get back to the shared pointer. + * + * This method is necessary to use shared pointers together with Lemon parser, which works on unions and won't be able to use + * shared pointers. + */ + TokenPtr getTokenPtr(Token* token); + + /** + * @brief Translates list of token pointers to their shared pointer instances. + * @param tokens Token pointers to translate. + * @return List of QSharedPointers. + * + * This method is just a convenience way to call getTokenPtr() for a list of pointers. + */ + TokenList getTokenPtrList(const QList<Token*>& tokens); + + /** + * @brief Adds token to managed list. + * @param token Token to be added to managed tokens. + * Tokens managed by context are shared to the Parser, so the API allows to see all parsed tokens. + * Some tokens might be created outside of Lexer, so this is the central repository for all tokens to be shared. + */ + void addManagedToken(TokenPtr token); + + /** + * @brief Tests whether the token is in the collection of tokens managed by this context. + * @param token Token to test. + * @return true if the token is managed by this context, or false if not. + */ + bool isManagedToken(Token* token); + + /** + * @brief Provides complete list of tokens managed by this context. + * @return List of tokens. + */ + TokenList getManagedTokens(); + + /** + * @brief Tests whether there were any critical errors so far during parsing. + * @return true if there were no critical errors, or false otherwise. + */ + bool isSuccessful() const; + + /** + * @brief Provides access to list of queries parsed so far. + * @return List of parsed AST objects. + * + * If there was an error, then queries from the list might be incomplete, which means their data members + * may still be initialized with their default values. It depends on where the error appeared in the parsed query string. + */ + const QList<SqliteQueryPtr>& getQueries(); + + /** + * @brief Provides access to all errors occurred so far. + * @return List of errors. + */ + const QList<ParserError*>& getErrors(); + + /** + * @brief Flag indicating if the Lemon parser should setup token collections. + * + * This setting allows to define whether the Lemon parser should setup token collections for parsed AST objects. + * In other words, it tells whether the SqliteStatement::tokens and SqliteStatement::tokensMap should be filled. + * + * Sometimes it might be worth to disable it to speed up parsig process, but by default it's enabled. + */ + bool setupTokens = true; + + /** + * @brief Flag inficating if the Lemon parser should exectute code for the grammar rules. + * + * This setting allows to define whether the Lemon parser should execute the code associated with rules. + * Disabling it will cause no AST objects to be produced, but it can be used to find out syntax errors. + * If you don't need AST objects (output from parsing), then you can turn this off to speed up Lemon parser. + * + * The Parser class for example turns it of when it probes for next valid token candidates. In that case + * no AST output objects are used, just information whether the next candidate is valid or not. + */ + bool executeRules = true; + + /** + * @brief Flag indicating if the Lemon parser should perform "fallback" logic. + * + * The "fallback" login in the Lemon parser is used when the input token is one of the keywords and it failed + * at that step. Then the "fallback" steps in and converts keyword token into the "ID" token, which represents + * a name of any object in the database (not necessarily existing one). Then the Lemon parser retries with + * that ID token and if that fails to fulfill the syntax rules too, then the error is reported. + * + * This is enabled by default, cause SQLite usually uses that too. It is for example disabled when looking + * for the next valid token candidate in Parser::getNextTokenCandidates(), cause for that case we need + * very strict token matching against the syntax. + */ + bool doFallbacks = true; + + /** + * @brief Flag indicating if minor errors should be ignored by the Lemon parser. + * + * See description of Parser::parse() for details. + */ + bool ignoreMinorErrors = false; + + /** + * @brief Dialect used for the parsing. + * + * This is used by the Lemon parser in various situations, like for example when it strips the object name + * from it's wrapping characters ([], "", ``) - that depends on the dialect. + */ + Dialect dialect; + + private: + /** + * @brief Clears all internal containers and deletes error objects. + */ + void cleanUp(); + + /** + * @brief List of parsed AST objects. + */ + QList<SqliteQueryPtr> parsedQueries; + + /** + * @brief Tokens managed by this context. + */ + TokenList managedTokens; + + /** + * @brief Mapping from token pointer to it's shared pointer instance. + */ + QHash<Token*, TokenPtr> tokenPtrMap; + + /** + * @brief Flag indicating successful or failure parsing. + * + * Changed to false when the error was reported. + */ + bool successful = true; + + /** + * @brief List of errors reported by Lemon. + */ + QList<ParserError*> errors; + + /** + * @brief Flag indicating that the next token should raise an error. + * + * This is set by errorBeforeNextToken() and minorErrorBeforeNextToken(). + */ + bool raiseErrorBeforeNextToken = false; + + /** + * @brief Error to be used for the error at next token. + * + * Defined by errorBeforeNextToken() and minorErrorBeforeNextToken(). + */ + QString nextTokenError; +}; + +#endif // PARSERCONTEXT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/parsererror.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/parsererror.cpp new file mode 100644 index 0000000..22fc531 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/parsererror.cpp @@ -0,0 +1,44 @@ +#include "parsererror.h" +#include "token.h" + +ParserError::ParserError(TokenPtr token, const QString &text) +{ + if (token) + { + start = token->start; + end = token->end; + } + message = text; +} + +ParserError::ParserError(qint64 start, qint64 end, const QString& text) : + message(text), + start(start), + end(end) +{ +} + +ParserError::ParserError(const QString &text) +{ + message = text; +} + +QString &ParserError::getMessage() +{ + return message; +} + +qint64 ParserError::getFrom() +{ + return start; +} + +qint64 ParserError::getTo() +{ + return end; +} + +QString ParserError::toString() +{ + return QString("%1: %2").arg(start).arg(message); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/parsererror.h b/SQLiteStudio3/coreSQLiteStudio/parser/parsererror.h new file mode 100644 index 0000000..673f030 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/parsererror.h @@ -0,0 +1,80 @@ +#ifndef PARSERERROR_H +#define PARSERERROR_H + +#include "coreSQLiteStudio_global.h" +#include "parser/token.h" +#include <QString> + +/** + * @brief Class representing error during SQL parsing. + * + * It provides error message and position at which the error occurred. + */ +class API_EXPORT ParserError +{ + public: + /** + * @brief Creates error for given token and message. + * @param token Token that the error occurred at. + * @param text Error message. + */ + ParserError(TokenPtr token, const QString& text); + + /** + * @brief Creates error with given range and message. + * @param start Position where the error starts. + * @param end Position where the error ends. + * @param text Error message. + */ + ParserError(qint64 start, qint64 end, const QString& text); + + /** + * @brief Creates global error with given message. + * @param text Error message. + * + * Global errors are not related to any token or position. + */ + explicit ParserError(const QString& text); + + /** + * @brief Provides error message. + * @return Error message. + */ + QString& getMessage(); + + /** + * @brief Provides start position of the error. + * @return Character position, or -1 if the error is not related to any position (global error). + */ + qint64 getFrom(); + + /** + * @brief Provides end position of the error. + * @return Character position, or -1 if the error is not related to any position (global error). + */ + qint64 getTo(); + + /** + * @brief Serializes error to readable string. + * @return Start position and error message in form: <tt>"position: message"</tt>. + */ + QString toString(); + + private: + /** + * @brief Error message. + */ + QString message = QString::null; + + /** + * @brief Error start position. + */ + qint64 start = -1; + + /** + * @brief Error end position. + */ + qint64 end = -1; +}; + +#endif // PARSERERROR_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/run_lemon.sh b/SQLiteStudio3/coreSQLiteStudio/parser/run_lemon.sh new file mode 100755 index 0000000..3df47e1 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/run_lemon.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +#lemon -l -q -s sqlite3_parse.y +lemon -l -q sqlite3_parse.y +mv sqlite3_parse.c sqlite3_parse.cpp + +lemon -l -q sqlite2_parse.y +mv sqlite2_parse.c sqlite2_parse.cpp diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.cpp new file mode 100644 index 0000000..7fc9edc --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.cpp @@ -0,0 +1,4650 @@ +/* Driver template for the LEMON parser generator. +** The author disclaims copyright to this source code. +** +** This version of "lempar.c" is modified, slightly, for use by SQLite. +** The only modifications are the addition of a couple of NEVER() +** macros to disable tests that are needed in the case of a general +** LALR(1) grammar but which are always false in the +** specific grammar used by SQLite. +*/ +/* First off, code is included that follows the "include" declaration +** in the input grammar file. */ +#include <stdio.h> + +#include "token.h" +#include "parsercontext.h" +#include "parser/ast/sqlitealtertable.h" +#include "parser/ast/sqliteanalyze.h" +#include "parser/ast/sqliteattach.h" +#include "parser/ast/sqlitebegintrans.h" +#include "parser/ast/sqlitecommittrans.h" +#include "parser/ast/sqlitecopy.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqlitecreatevirtualtable.h" +#include "parser/ast/sqlitedelete.h" +#include "parser/ast/sqlitedetach.h" +#include "parser/ast/sqlitedropindex.h" +#include "parser/ast/sqlitedroptable.h" +#include "parser/ast/sqlitedroptrigger.h" +#include "parser/ast/sqlitedropview.h" +#include "parser/ast/sqliteemptyquery.h" +#include "parser/ast/sqliteinsert.h" +#include "parser/ast/sqlitepragma.h" +#include "parser/ast/sqlitereindex.h" +#include "parser/ast/sqliterelease.h" +#include "parser/ast/sqliterollback.h" +#include "parser/ast/sqlitesavepoint.h" +#include "parser/ast/sqliteselect.h" +#include "parser/ast/sqliteupdate.h" +#include "parser/ast/sqlitevacuum.h" +#include "parser/ast/sqliteexpr.h" +#include "parser/ast/sqlitecolumntype.h" +#include "parser/ast/sqliteconflictalgo.h" +#include "parser/ast/sqlitesortorder.h" +#include "parser/ast/sqliteindexedcolumn.h" +#include "parser/ast/sqliteforeignkey.h" +#include "parser_helper_stubs.h" +#include "common/utils_sql.h" +#include <QObject> +#include <QDebug> + +#define assert(X) Q_ASSERT(X) +#define UNUSED_PARAMETER(X) (void)(X) +#define DONT_INHERIT_TOKENS(X) noTokenInheritanceFields << X +/* Next is all token values, in a form suitable for use by makeheaders. +** This section will be null unless lemon is run with the -m switch. +*/ +/* +** These constants (all generated automatically by the parser generator) +** specify the various kinds of tokens (terminals) that the parser +** understands. +** +** Each symbol here is a terminal symbol in the grammar. +*/ +/* Make sure the INTERFACE macro is defined. +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/* The next thing included is series of defines which control +** various aspects of the generated parser. +** YYCODETYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 terminals +** and nonterminals. "int" is used otherwise. +** YYNOCODE is a number of type YYCODETYPE which corresponds +** to no legal terminal or nonterminal number. This +** number is used to fill in empty slots of the hash +** table. +** YYFALLBACK If defined, this indicates that one or more tokens +** have fall-back values which should be used if the +** original value of the token will not parse. +** YYACTIONTYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 rules and +** states combined. "int" is used otherwise. +** sqlite2_parseTOKENTYPE is the data type used for minor tokens given +** directly to the parser from the tokenizer. +** YYMINORTYPE is the data type used for all minor tokens. +** This is typically a union of many types, one of +** which is sqlite2_parseTOKENTYPE. The entry in the union +** for base tokens is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. If +** zero the stack is dynamically sized using realloc() +** sqlite2_parseARG_SDECL A static variable declaration for the %extra_argument +** sqlite2_parseARG_PDECL A parameter declaration for the %extra_argument +** sqlite2_parseARG_STORE Code to store %extra_argument into yypParser +** sqlite2_parseARG_FETCH Code to extract %extra_argument from yypParser +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +*/ +#define YYCODETYPE unsigned char +#define YYNOCODE 241 +#define YYACTIONTYPE unsigned short int +#define sqlite2_parseTOKENTYPE Token* +typedef union { + int yyinit; + sqlite2_parseTOKENTYPE yy0; + ParserCreateTableConstraintList* yy13; + SqliteSelect::Core::JoinSource* yy31; + ParserStubAlias* yy40; + SqliteExpr::LikeOp* yy41; + ParserCreateTableColumnList* yy42; + SqliteColumnType* yy57; + ParserIndexedColumnList* yy63; + QVariant* yy69; + SqliteCreateTrigger::Scope* yy83; + ParserStubExplain* yy91; + ParserFullName* yy120; + SqliteSelect::Core::SingleSource* yy121; + ParserOtherSourceList* yy131; + SqliteCreateTable::Column* yy147; + SqliteSelect::Core* yy150; + SqliteCreateTrigger::Event* yy151; + SqliteSelect* yy153; + SqliteForeignKey::Condition* yy187; + SqliteExpr* yy192; + ParserSetValueList* yy201; + SqliteQuery* yy203; + ParserStringList* yy207; + ParserResultColumnList* yy213; + SqliteSelect::Core::JoinOp* yy221; + int* yy226; + ParserExprList* yy231; + ParserOrderByList* yy243; + ParserFkConditionList* yy264; + ParserQueryList* yy270; + bool* yy291; + SqliteCreateTable::Column::Constraint* yy304; + SqliteInitially* yy312; + QString* yy319; + SqliteLimit* yy324; + ParserDeferSubClause* yy329; + ParserStubInsertOrReplace* yy344; + ParserCreateTableColumnConstraintList* yy371; + SqliteCreateTrigger::Time* yy372; + SqliteSelect::CompoundOperator* yy382; + SqliteSortOrder* yy389; + ParserStubTransDetails* yy404; + SqliteCreateTable::Constraint* yy406; + SqliteConflictAlgo* yy418; + SqliteForeignKey::Condition::Reaction* yy424; + SqliteIndexedColumn* yy428; + SqliteSelect::Core::JoinConstraint* yy455; +} YYMINORTYPE; +#ifndef YYSTACKDEPTH +#define YYSTACKDEPTH 100 +#endif +#define sqlite2_parseARG_SDECL ParserContext* parserContext; +#define sqlite2_parseARG_PDECL ,ParserContext* parserContext +#define sqlite2_parseARG_FETCH ParserContext* parserContext = yypParser->parserContext +#define sqlite2_parseARG_STORE yypParser->parserContext = parserContext +#define YYNSTATE 584 +#define YYNRULE 352 +#define YYFALLBACK 1 +#define YY_NO_ACTION (YYNSTATE+YYNRULE+2) +#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1) +#define YY_ERROR_ACTION (YYNSTATE+YYNRULE) + +#define GET_CONTEXT yyParser* yypParser = pParser; sqlite2_parseARG_FETCH + +/* The yyzerominor constant is used to initialize instances of +** YYMINORTYPE objects to zero. */ +static const YYMINORTYPE yyzerominor = { 0 }; + +/* Define the yytestcase() macro to be a no-op if is not already defined +** otherwise. +** +** Applications can choose to define yytestcase() in the %include section +** to a macro that can assist in verifying code coverage. For production +** code the yytestcase() macro should be turned off. But it is useful +** for testing. +*/ +#ifndef yytestcase +# define yytestcase(X) +#endif + + +/* Next are the tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N < YYNSTATE Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE. +** +** N == YYNSTATE+YYNRULE A syntax error has occurred. +** +** N == YYNSTATE+YYNRULE+1 The parser accepts its input. +** +** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused +** slots in the yy_action[] table. +** +** The action table is constructed as a single large table named yy_action[]. +** Given state S and lookahead X, the action is computed as +** +** yy_action[ yy_shift_ofst[S] + X ] +** +** If the index value yy_shift_ofst[S]+X is out of range or if the value +** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S] +** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table +** and that yy_default[S] should be used instead. +** +** The formula above is for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the yy_reduce_ofst[] array is used in place of +** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of +** YY_SHIFT_USE_DFLT. +** +** The following are the tables generated in this section: +** +** yy_action[] A single table containing all actions. +** yy_lookahead[] A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** yy_shift_ofst[] For each state, the offset into yy_action for +** shifting terminals. +** yy_reduce_ofst[] For each state, the offset into yy_action for +** shifting non-terminals after a reduce. +** yy_default[] Default action for each state. +*/ +#define YY_ACTTAB_COUNT (1697) +static const YYACTIONTYPE yy_action[] = { + /* 0 */ 338, 191, 186, 242, 476, 511, 576, 193, 332, 16, + /* 10 */ 511, 384, 189, 322, 239, 519, 518, 570, 337, 450, + /* 20 */ 15, 851, 125, 58, 575, 179, 851, 574, 63, 142, + /* 30 */ 401, 581, 328, 27, 84, 569, 114, 322, 573, 519, + /* 40 */ 518, 851, 851, 36, 851, 851, 851, 851, 851, 851, + /* 50 */ 851, 851, 851, 851, 851, 851, 851, 851, 851, 33, + /* 60 */ 34, 851, 851, 851, 851, 320, 379, 35, 240, 238, + /* 70 */ 121, 556, 291, 251, 57, 7, 217, 577, 265, 264, + /* 80 */ 523, 179, 555, 336, 335, 338, 569, 549, 548, 550, + /* 90 */ 271, 569, 10, 724, 199, 297, 203, 489, 459, 332, + /* 100 */ 568, 567, 451, 253, 158, 523, 449, 444, 443, 337, + /* 110 */ 187, 870, 266, 187, 580, 517, 168, 328, 505, 343, + /* 120 */ 142, 235, 490, 108, 101, 489, 523, 164, 36, 531, + /* 130 */ 187, 13, 523, 252, 234, 547, 236, 119, 340, 232, + /* 140 */ 339, 120, 216, 155, 33, 34, 481, 547, 322, 215, + /* 150 */ 519, 518, 35, 714, 456, 477, 320, 367, 547, 478, + /* 160 */ 7, 714, 400, 321, 251, 523, 714, 563, 336, 335, + /* 170 */ 500, 553, 549, 548, 550, 188, 465, 500, 401, 466, + /* 180 */ 366, 365, 552, 364, 293, 435, 40, 40, 40, 39, + /* 190 */ 523, 562, 60, 255, 714, 569, 714, 714, 852, 570, + /* 200 */ 271, 714, 502, 852, 500, 714, 570, 714, 714, 714, + /* 210 */ 714, 523, 569, 178, 531, 422, 13, 523, 45, 46, + /* 220 */ 330, 43, 43, 530, 530, 223, 852, 852, 44, 44, + /* 230 */ 44, 44, 42, 42, 42, 42, 41, 41, 40, 40, + /* 240 */ 40, 39, 199, 297, 203, 55, 236, 92, 340, 232, + /* 250 */ 339, 120, 216, 500, 106, 570, 268, 19, 187, 215, + /* 260 */ 500, 105, 553, 42, 42, 42, 42, 41, 41, 40, + /* 270 */ 40, 40, 39, 552, 41, 41, 40, 40, 40, 39, + /* 280 */ 852, 3, 568, 567, 187, 852, 512, 500, 219, 568, + /* 290 */ 567, 17, 496, 156, 322, 345, 519, 518, 569, 292, + /* 300 */ 45, 46, 330, 43, 43, 530, 530, 223, 852, 852, + /* 310 */ 44, 44, 44, 44, 42, 42, 42, 42, 41, 41, + /* 320 */ 40, 40, 40, 39, 547, 533, 852, 100, 308, 306, + /* 330 */ 305, 852, 448, 447, 418, 418, 316, 245, 568, 567, + /* 340 */ 304, 937, 122, 344, 1, 582, 45, 46, 330, 43, + /* 350 */ 43, 530, 530, 223, 852, 852, 44, 44, 44, 44, + /* 360 */ 42, 42, 42, 42, 41, 41, 40, 40, 40, 39, + /* 370 */ 338, 442, 45, 46, 330, 43, 43, 530, 530, 223, + /* 380 */ 524, 569, 44, 44, 44, 44, 42, 42, 42, 42, + /* 390 */ 41, 41, 40, 40, 40, 39, 5, 9, 524, 781, + /* 400 */ 220, 324, 328, 167, 45, 46, 330, 43, 43, 530, + /* 410 */ 530, 223, 781, 36, 44, 44, 44, 44, 42, 42, + /* 420 */ 42, 42, 41, 41, 40, 40, 40, 39, 8, 33, + /* 430 */ 34, 274, 387, 435, 547, 388, 237, 35, 774, 421, + /* 440 */ 60, 14, 219, 569, 250, 7, 774, 437, 441, 169, + /* 450 */ 523, 524, 569, 336, 335, 285, 781, 549, 548, 550, + /* 460 */ 44, 44, 44, 44, 42, 42, 42, 42, 41, 41, + /* 470 */ 40, 40, 40, 39, 332, 523, 332, 425, 470, 774, + /* 480 */ 560, 774, 774, 850, 337, 426, 337, 455, 850, 613, + /* 490 */ 774, 181, 774, 774, 774, 142, 523, 142, 31, 531, + /* 500 */ 614, 13, 523, 850, 850, 850, 850, 850, 850, 850, + /* 510 */ 850, 850, 850, 850, 850, 850, 850, 850, 850, 850, + /* 520 */ 850, 850, 850, 850, 850, 850, 850, 852, 500, 460, + /* 530 */ 357, 320, 852, 318, 75, 570, 401, 570, 311, 251, + /* 540 */ 569, 251, 446, 445, 570, 358, 359, 45, 46, 330, + /* 550 */ 43, 43, 530, 530, 223, 852, 852, 44, 44, 44, + /* 560 */ 44, 42, 42, 42, 42, 41, 41, 40, 40, 40, + /* 570 */ 39, 338, 45, 46, 330, 43, 43, 530, 530, 223, + /* 580 */ 615, 570, 44, 44, 44, 44, 42, 42, 42, 42, + /* 590 */ 41, 41, 40, 40, 40, 39, 185, 184, 497, 782, + /* 600 */ 199, 297, 203, 328, 360, 538, 96, 488, 97, 570, + /* 610 */ 103, 500, 782, 436, 36, 570, 187, 225, 568, 567, + /* 620 */ 568, 567, 332, 354, 540, 539, 294, 568, 567, 570, + /* 630 */ 33, 34, 337, 356, 482, 355, 569, 85, 35, 759, + /* 640 */ 532, 111, 70, 78, 487, 483, 7, 759, 90, 494, + /* 650 */ 508, 523, 493, 160, 336, 335, 782, 528, 549, 548, + /* 660 */ 550, 532, 440, 434, 568, 567, 118, 54, 332, 919, + /* 670 */ 214, 100, 308, 306, 305, 75, 523, 333, 337, 571, + /* 680 */ 759, 529, 759, 759, 304, 48, 177, 522, 32, 142, + /* 690 */ 30, 759, 568, 567, 759, 759, 159, 523, 568, 567, + /* 700 */ 531, 464, 13, 523, 45, 46, 330, 43, 43, 530, + /* 710 */ 530, 223, 568, 567, 44, 44, 44, 44, 42, 42, + /* 720 */ 42, 42, 41, 41, 40, 40, 40, 39, 463, 570, + /* 730 */ 498, 919, 570, 323, 514, 570, 222, 45, 46, 330, + /* 740 */ 43, 43, 530, 530, 223, 393, 392, 44, 44, 44, + /* 750 */ 44, 42, 42, 42, 42, 41, 41, 40, 40, 40, + /* 760 */ 39, 45, 46, 330, 43, 43, 530, 530, 223, 540, + /* 770 */ 539, 44, 44, 44, 44, 42, 42, 42, 42, 41, + /* 780 */ 41, 40, 40, 40, 39, 583, 1, 45, 46, 330, + /* 790 */ 43, 43, 530, 530, 223, 267, 475, 44, 44, 44, + /* 800 */ 44, 42, 42, 42, 42, 41, 41, 40, 40, 40, + /* 810 */ 39, 434, 568, 567, 420, 568, 567, 163, 568, 567, + /* 820 */ 570, 110, 218, 45, 46, 330, 43, 43, 530, 530, + /* 830 */ 223, 28, 468, 44, 44, 44, 44, 42, 42, 42, + /* 840 */ 42, 41, 41, 40, 40, 40, 39, 570, 547, 45, + /* 850 */ 46, 330, 43, 43, 530, 530, 223, 570, 212, 44, + /* 860 */ 44, 44, 44, 42, 42, 42, 42, 41, 41, 40, + /* 870 */ 40, 40, 39, 46, 330, 43, 43, 530, 530, 223, + /* 880 */ 527, 526, 44, 44, 44, 44, 42, 42, 42, 42, + /* 890 */ 41, 41, 40, 40, 40, 39, 338, 330, 43, 43, + /* 900 */ 530, 530, 223, 568, 567, 44, 44, 44, 44, 42, + /* 910 */ 42, 42, 42, 41, 41, 40, 40, 40, 39, 570, + /* 920 */ 525, 570, 64, 859, 88, 424, 198, 391, 328, 520, + /* 930 */ 568, 567, 357, 570, 516, 164, 569, 380, 12, 36, + /* 940 */ 568, 567, 569, 164, 502, 25, 570, 358, 275, 172, + /* 950 */ 171, 170, 391, 256, 569, 33, 34, 310, 66, 389, + /* 960 */ 390, 287, 76, 35, 104, 395, 547, 484, 561, 79, + /* 970 */ 452, 7, 862, 395, 547, 455, 523, 338, 80, 336, + /* 980 */ 335, 213, 480, 549, 548, 550, 75, 312, 396, 398, + /* 990 */ 570, 397, 293, 479, 570, 380, 396, 398, 259, 397, + /* 1000 */ 293, 523, 568, 567, 568, 567, 276, 538, 10, 328, + /* 1010 */ 4, 289, 474, 490, 570, 473, 568, 567, 23, 290, + /* 1020 */ 36, 65, 523, 431, 284, 531, 532, 13, 523, 568, + /* 1030 */ 567, 497, 570, 584, 439, 332, 33, 34, 490, 317, + /* 1040 */ 162, 489, 535, 570, 35, 337, 570, 532, 570, 179, + /* 1050 */ 307, 53, 7, 471, 52, 345, 139, 523, 338, 569, + /* 1060 */ 336, 335, 179, 302, 549, 548, 550, 495, 75, 303, + /* 1070 */ 413, 547, 569, 568, 567, 570, 254, 568, 567, 325, + /* 1080 */ 262, 209, 523, 272, 570, 18, 569, 361, 160, 570, + /* 1090 */ 328, 569, 515, 261, 407, 413, 51, 568, 567, 570, + /* 1100 */ 522, 36, 569, 523, 192, 582, 531, 10, 13, 523, + /* 1110 */ 99, 338, 489, 730, 569, 568, 567, 33, 34, 407, + /* 1120 */ 503, 500, 456, 403, 570, 35, 568, 567, 376, 568, + /* 1130 */ 567, 568, 567, 7, 211, 456, 570, 490, 523, 570, + /* 1140 */ 489, 336, 335, 328, 570, 549, 548, 550, 403, 497, + /* 1150 */ 730, 730, 404, 376, 36, 371, 500, 108, 568, 567, + /* 1160 */ 124, 101, 234, 523, 486, 279, 82, 568, 567, 2, + /* 1170 */ 33, 34, 568, 567, 497, 569, 197, 301, 35, 368, + /* 1180 */ 69, 75, 568, 567, 523, 492, 7, 531, 666, 13, + /* 1190 */ 523, 523, 338, 423, 336, 335, 417, 73, 549, 548, + /* 1200 */ 550, 350, 485, 570, 568, 567, 570, 568, 567, 22, + /* 1210 */ 491, 570, 21, 67, 371, 179, 523, 6, 258, 568, + /* 1220 */ 567, 219, 568, 567, 328, 569, 405, 568, 567, 570, + /* 1230 */ 255, 569, 399, 106, 394, 36, 215, 523, 71, 500, + /* 1240 */ 531, 368, 13, 523, 416, 860, 260, 386, 570, 415, + /* 1250 */ 338, 33, 34, 269, 544, 543, 205, 570, 204, 35, + /* 1260 */ 108, 350, 409, 81, 570, 385, 569, 7, 502, 507, + /* 1270 */ 245, 496, 523, 182, 50, 336, 335, 570, 569, 549, + /* 1280 */ 548, 550, 328, 160, 338, 281, 568, 567, 456, 568, + /* 1290 */ 567, 570, 381, 36, 568, 567, 115, 523, 278, 277, + /* 1300 */ 570, 351, 570, 363, 353, 187, 352, 49, 411, 33, + /* 1310 */ 34, 406, 568, 567, 538, 160, 328, 35, 523, 174, + /* 1320 */ 157, 531, 286, 13, 523, 7, 862, 36, 185, 184, + /* 1330 */ 523, 568, 567, 336, 335, 179, 362, 549, 548, 550, + /* 1340 */ 568, 567, 180, 33, 34, 569, 569, 568, 567, 383, + /* 1350 */ 74, 35, 202, 377, 296, 523, 540, 539, 241, 7, + /* 1360 */ 568, 567, 569, 579, 523, 374, 257, 336, 335, 342, + /* 1370 */ 112, 549, 548, 550, 568, 567, 523, 346, 201, 531, + /* 1380 */ 200, 13, 523, 568, 567, 568, 567, 569, 569, 523, + /* 1390 */ 542, 544, 543, 536, 544, 543, 566, 332, 263, 544, + /* 1400 */ 543, 332, 565, 373, 309, 544, 543, 337, 456, 564, + /* 1410 */ 523, 337, 341, 531, 332, 13, 523, 501, 77, 370, + /* 1420 */ 11, 559, 146, 196, 337, 378, 457, 332, 408, 558, + /* 1430 */ 557, 270, 332, 569, 332, 144, 554, 337, 187, 332, + /* 1440 */ 551, 546, 337, 17, 337, 47, 332, 332, 151, 337, + /* 1450 */ 227, 538, 228, 150, 538, 152, 337, 337, 229, 538, + /* 1460 */ 161, 24, 522, 226, 319, 538, 522, 224, 249, 334, + /* 1470 */ 59, 545, 220, 332, 332, 332, 39, 521, 37, 522, + /* 1480 */ 332, 332, 183, 337, 337, 337, 29, 109, 332, 510, + /* 1490 */ 337, 337, 522, 572, 143, 149, 145, 522, 337, 522, + /* 1500 */ 332, 248, 247, 569, 522, 332, 332, 570, 332, 246, + /* 1510 */ 337, 522, 522, 332, 332, 337, 337, 513, 337, 107, + /* 1520 */ 83, 141, 332, 337, 337, 472, 133, 132, 454, 140, + /* 1530 */ 56, 332, 337, 244, 131, 148, 453, 430, 522, 522, + /* 1540 */ 522, 337, 429, 147, 428, 522, 522, 332, 332, 534, + /* 1550 */ 326, 332, 130, 522, 332, 243, 332, 337, 337, 569, + /* 1560 */ 569, 337, 427, 419, 337, 522, 337, 98, 129, 127, + /* 1570 */ 522, 522, 135, 522, 332, 134, 332, 136, 522, 522, + /* 1580 */ 195, 332, 372, 314, 337, 207, 337, 522, 95, 288, + /* 1590 */ 569, 337, 94, 337, 206, 138, 522, 137, 298, 194, + /* 1600 */ 20, 369, 128, 414, 61, 504, 68, 153, 499, 569, + /* 1610 */ 102, 93, 522, 522, 315, 569, 522, 469, 569, 522, + /* 1620 */ 569, 522, 432, 210, 569, 300, 91, 569, 123, 72, + /* 1630 */ 166, 402, 569, 569, 117, 569, 89, 295, 283, 522, + /* 1640 */ 569, 522, 375, 382, 280, 331, 522, 569, 522, 221, + /* 1650 */ 208, 176, 569, 87, 86, 569, 347, 175, 116, 569, + /* 1660 */ 569, 569, 349, 173, 113, 126, 233, 541, 230, 154, + /* 1670 */ 537, 329, 509, 506, 458, 410, 273, 299, 470, 282, + /* 1680 */ 190, 348, 467, 462, 461, 438, 38, 165, 938, 62, + /* 1690 */ 938, 327, 231, 578, 433, 938, 412, +}; +static const YYCODETYPE yy_lookahead[] = { + /* 0 */ 4, 8, 195, 10, 80, 25, 5, 14, 156, 202, + /* 10 */ 30, 41, 19, 113, 21, 115, 116, 4, 166, 4, + /* 20 */ 79, 25, 81, 34, 23, 156, 30, 26, 35, 177, + /* 30 */ 37, 72, 36, 53, 54, 166, 43, 113, 37, 115, + /* 40 */ 116, 45, 46, 47, 48, 49, 50, 51, 52, 53, + /* 50 */ 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + /* 60 */ 64, 65, 66, 67, 68, 213, 96, 71, 75, 76, + /* 70 */ 77, 16, 220, 221, 85, 79, 156, 76, 63, 64, + /* 80 */ 84, 156, 27, 87, 88, 4, 166, 91, 92, 93, + /* 90 */ 47, 166, 79, 80, 101, 102, 103, 84, 229, 156, + /* 100 */ 87, 88, 87, 110, 161, 109, 91, 92, 93, 166, + /* 110 */ 117, 141, 187, 117, 148, 80, 161, 36, 80, 153, + /* 120 */ 177, 93, 109, 85, 31, 112, 130, 161, 47, 133, + /* 130 */ 117, 135, 136, 90, 106, 192, 93, 94, 95, 96, + /* 140 */ 97, 98, 99, 13, 63, 64, 65, 192, 113, 106, + /* 150 */ 115, 116, 71, 72, 229, 114, 213, 191, 192, 118, + /* 160 */ 79, 80, 21, 220, 221, 84, 85, 80, 87, 88, + /* 170 */ 4, 7, 91, 92, 93, 32, 90, 4, 37, 93, + /* 180 */ 214, 215, 18, 217, 218, 156, 65, 66, 67, 68, + /* 190 */ 109, 162, 163, 100, 113, 166, 115, 116, 25, 4, + /* 200 */ 47, 120, 156, 30, 4, 124, 4, 126, 127, 128, + /* 210 */ 129, 130, 166, 184, 133, 186, 135, 136, 45, 46, + /* 220 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 230 */ 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, + /* 240 */ 67, 68, 101, 102, 103, 79, 93, 85, 95, 96, + /* 250 */ 97, 98, 99, 87, 81, 4, 210, 79, 117, 106, + /* 260 */ 87, 80, 7, 59, 60, 61, 62, 63, 64, 65, + /* 270 */ 66, 67, 68, 18, 63, 64, 65, 66, 67, 68, + /* 280 */ 25, 79, 87, 88, 117, 30, 84, 87, 156, 87, + /* 290 */ 88, 129, 119, 161, 113, 22, 115, 116, 166, 132, + /* 300 */ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, + /* 310 */ 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + /* 320 */ 65, 66, 67, 68, 192, 130, 25, 94, 95, 96, + /* 330 */ 97, 30, 91, 92, 83, 84, 204, 205, 87, 88, + /* 340 */ 107, 149, 150, 151, 152, 72, 45, 46, 47, 48, + /* 350 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + /* 360 */ 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, + /* 370 */ 4, 156, 45, 46, 47, 48, 49, 50, 51, 52, + /* 380 */ 5, 166, 55, 56, 57, 58, 59, 60, 61, 62, + /* 390 */ 63, 64, 65, 66, 67, 68, 79, 48, 23, 72, + /* 400 */ 99, 26, 36, 161, 45, 46, 47, 48, 49, 50, + /* 410 */ 51, 52, 85, 47, 55, 56, 57, 58, 59, 60, + /* 420 */ 61, 62, 63, 64, 65, 66, 67, 68, 79, 63, + /* 430 */ 64, 82, 6, 156, 192, 9, 159, 71, 72, 162, + /* 440 */ 163, 125, 156, 166, 164, 79, 80, 167, 168, 169, + /* 450 */ 84, 76, 166, 87, 88, 29, 129, 91, 92, 93, + /* 460 */ 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + /* 470 */ 65, 66, 67, 68, 156, 109, 156, 180, 181, 113, + /* 480 */ 173, 115, 116, 25, 166, 188, 166, 180, 30, 82, + /* 490 */ 124, 205, 126, 127, 128, 177, 130, 177, 139, 133, + /* 500 */ 82, 135, 136, 45, 46, 47, 48, 49, 50, 51, + /* 510 */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + /* 520 */ 62, 63, 64, 65, 66, 67, 68, 25, 4, 80, + /* 530 */ 156, 213, 30, 213, 85, 4, 37, 4, 220, 221, + /* 540 */ 166, 221, 91, 92, 4, 171, 172, 45, 46, 47, + /* 550 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + /* 560 */ 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, + /* 570 */ 68, 4, 45, 46, 47, 48, 49, 50, 51, 52, + /* 580 */ 82, 4, 55, 56, 57, 58, 59, 60, 61, 62, + /* 590 */ 63, 64, 65, 66, 67, 68, 63, 64, 167, 72, + /* 600 */ 101, 102, 103, 36, 230, 231, 42, 85, 44, 4, + /* 610 */ 88, 87, 85, 89, 47, 4, 117, 85, 87, 88, + /* 620 */ 87, 88, 156, 90, 91, 92, 156, 87, 88, 4, + /* 630 */ 63, 64, 166, 100, 203, 102, 166, 82, 71, 72, + /* 640 */ 109, 82, 78, 177, 122, 123, 79, 80, 82, 109, + /* 650 */ 85, 84, 112, 222, 87, 88, 129, 21, 91, 92, + /* 660 */ 93, 130, 80, 86, 87, 88, 100, 85, 156, 10, + /* 670 */ 80, 94, 95, 96, 97, 85, 109, 211, 166, 74, + /* 680 */ 113, 93, 115, 116, 107, 120, 216, 221, 138, 177, + /* 690 */ 140, 124, 87, 88, 127, 128, 79, 130, 87, 88, + /* 700 */ 133, 11, 135, 136, 45, 46, 47, 48, 49, 50, + /* 710 */ 51, 52, 87, 88, 55, 56, 57, 58, 59, 60, + /* 720 */ 61, 62, 63, 64, 65, 66, 67, 68, 38, 4, + /* 730 */ 119, 72, 4, 221, 109, 4, 224, 45, 46, 47, + /* 740 */ 48, 49, 50, 51, 52, 39, 40, 55, 56, 57, + /* 750 */ 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, + /* 760 */ 68, 45, 46, 47, 48, 49, 50, 51, 52, 91, + /* 770 */ 92, 55, 56, 57, 58, 59, 60, 61, 62, 63, + /* 780 */ 64, 65, 66, 67, 68, 151, 152, 45, 46, 47, + /* 790 */ 48, 49, 50, 51, 52, 105, 80, 55, 56, 57, + /* 800 */ 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, + /* 810 */ 68, 86, 87, 88, 83, 87, 88, 161, 87, 88, + /* 820 */ 4, 85, 80, 45, 46, 47, 48, 49, 50, 51, + /* 830 */ 52, 139, 104, 55, 56, 57, 58, 59, 60, 61, + /* 840 */ 62, 63, 64, 65, 66, 67, 68, 4, 192, 45, + /* 850 */ 46, 47, 48, 49, 50, 51, 52, 4, 80, 55, + /* 860 */ 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + /* 870 */ 66, 67, 68, 46, 47, 48, 49, 50, 51, 52, + /* 880 */ 80, 80, 55, 56, 57, 58, 59, 60, 61, 62, + /* 890 */ 63, 64, 65, 66, 67, 68, 4, 47, 48, 49, + /* 900 */ 50, 51, 52, 87, 88, 55, 56, 57, 58, 59, + /* 910 */ 60, 61, 62, 63, 64, 65, 66, 67, 68, 4, + /* 920 */ 80, 4, 42, 138, 44, 109, 156, 84, 36, 114, + /* 930 */ 87, 88, 156, 4, 80, 161, 166, 84, 125, 47, + /* 940 */ 87, 88, 166, 161, 156, 85, 4, 171, 172, 101, + /* 950 */ 102, 103, 109, 157, 166, 63, 64, 65, 78, 101, + /* 960 */ 102, 103, 79, 71, 80, 191, 192, 122, 173, 174, + /* 970 */ 175, 79, 80, 191, 192, 180, 84, 4, 82, 87, + /* 980 */ 88, 80, 65, 91, 92, 93, 85, 82, 214, 215, + /* 990 */ 4, 217, 218, 65, 4, 142, 214, 215, 210, 217, + /* 1000 */ 218, 109, 87, 88, 87, 88, 230, 231, 79, 36, + /* 1010 */ 236, 237, 80, 84, 4, 80, 87, 88, 100, 237, + /* 1020 */ 47, 141, 130, 108, 228, 133, 109, 135, 136, 87, + /* 1030 */ 88, 167, 4, 0, 80, 156, 63, 64, 109, 121, + /* 1040 */ 161, 112, 176, 4, 71, 166, 4, 130, 4, 156, + /* 1050 */ 32, 79, 79, 80, 79, 22, 177, 84, 4, 166, + /* 1060 */ 87, 88, 156, 80, 91, 92, 93, 203, 85, 32, + /* 1070 */ 84, 192, 166, 87, 88, 4, 156, 87, 88, 137, + /* 1080 */ 187, 156, 109, 158, 4, 79, 166, 167, 222, 4, + /* 1090 */ 36, 166, 156, 187, 84, 109, 79, 87, 88, 4, + /* 1100 */ 221, 47, 166, 130, 156, 72, 133, 79, 135, 136, + /* 1110 */ 98, 4, 84, 85, 166, 87, 88, 63, 64, 109, + /* 1120 */ 130, 4, 229, 84, 4, 71, 87, 88, 84, 87, + /* 1130 */ 88, 87, 88, 79, 80, 229, 4, 109, 84, 4, + /* 1140 */ 112, 87, 88, 36, 4, 91, 92, 93, 109, 167, + /* 1150 */ 122, 123, 80, 109, 47, 84, 4, 85, 87, 88, + /* 1160 */ 81, 31, 106, 109, 122, 156, 100, 87, 88, 10, + /* 1170 */ 63, 64, 87, 88, 167, 166, 80, 47, 71, 84, + /* 1180 */ 120, 85, 87, 88, 130, 203, 79, 133, 80, 135, + /* 1190 */ 136, 84, 4, 85, 87, 88, 111, 105, 91, 92, + /* 1200 */ 93, 84, 122, 4, 87, 88, 4, 87, 88, 48, + /* 1210 */ 203, 4, 48, 131, 143, 156, 109, 79, 20, 87, + /* 1220 */ 88, 156, 87, 88, 36, 166, 80, 87, 88, 4, + /* 1230 */ 100, 166, 72, 81, 72, 47, 106, 130, 33, 87, + /* 1240 */ 133, 146, 135, 136, 109, 138, 187, 33, 4, 109, + /* 1250 */ 4, 63, 64, 170, 171, 172, 156, 4, 158, 71, + /* 1260 */ 85, 144, 130, 141, 4, 145, 166, 79, 156, 204, + /* 1270 */ 205, 119, 84, 212, 79, 87, 88, 4, 166, 91, + /* 1280 */ 92, 93, 36, 222, 4, 100, 87, 88, 229, 87, + /* 1290 */ 88, 4, 109, 47, 87, 88, 120, 109, 121, 17, + /* 1300 */ 4, 144, 4, 87, 80, 117, 80, 165, 109, 63, + /* 1310 */ 64, 109, 87, 88, 231, 222, 36, 71, 130, 81, + /* 1320 */ 206, 133, 210, 135, 136, 79, 80, 47, 63, 64, + /* 1330 */ 84, 87, 88, 87, 88, 156, 156, 91, 92, 93, + /* 1340 */ 87, 88, 179, 63, 64, 166, 166, 87, 88, 142, + /* 1350 */ 179, 71, 156, 109, 158, 109, 91, 92, 154, 79, + /* 1360 */ 87, 88, 166, 155, 84, 112, 187, 87, 88, 100, + /* 1370 */ 73, 91, 92, 93, 87, 88, 130, 156, 156, 133, + /* 1380 */ 158, 135, 136, 87, 88, 87, 88, 166, 166, 109, + /* 1390 */ 170, 171, 172, 170, 171, 172, 154, 156, 170, 171, + /* 1400 */ 172, 156, 154, 143, 170, 171, 172, 166, 229, 154, + /* 1410 */ 130, 166, 160, 133, 156, 135, 136, 130, 177, 146, + /* 1420 */ 85, 155, 177, 156, 166, 158, 130, 156, 130, 155, + /* 1430 */ 183, 28, 156, 166, 156, 177, 155, 166, 117, 156, + /* 1440 */ 155, 192, 166, 129, 166, 212, 156, 156, 177, 166, + /* 1450 */ 199, 231, 198, 177, 231, 177, 166, 166, 197, 231, + /* 1460 */ 177, 127, 221, 200, 126, 231, 221, 177, 177, 124, + /* 1470 */ 128, 201, 99, 156, 156, 156, 68, 47, 222, 221, + /* 1480 */ 156, 156, 225, 166, 166, 166, 138, 82, 156, 166, + /* 1490 */ 166, 166, 221, 156, 177, 177, 177, 221, 166, 221, + /* 1500 */ 156, 177, 177, 166, 221, 156, 156, 4, 156, 177, + /* 1510 */ 166, 221, 221, 156, 156, 166, 166, 209, 166, 209, + /* 1520 */ 82, 177, 156, 166, 166, 155, 177, 177, 166, 177, + /* 1530 */ 79, 156, 166, 178, 177, 177, 183, 155, 221, 221, + /* 1540 */ 221, 166, 155, 177, 155, 221, 221, 156, 156, 156, + /* 1550 */ 156, 156, 177, 221, 156, 178, 156, 166, 166, 166, + /* 1560 */ 166, 166, 155, 209, 166, 221, 166, 82, 177, 177, + /* 1570 */ 221, 221, 177, 221, 156, 177, 156, 177, 221, 221, + /* 1580 */ 156, 156, 158, 156, 166, 209, 166, 221, 82, 24, + /* 1590 */ 166, 166, 82, 166, 234, 177, 221, 177, 235, 156, + /* 1600 */ 138, 158, 177, 197, 177, 156, 190, 45, 156, 166, + /* 1610 */ 156, 82, 221, 221, 156, 166, 221, 156, 166, 221, + /* 1620 */ 166, 221, 156, 156, 166, 156, 82, 166, 219, 79, + /* 1630 */ 156, 190, 166, 166, 82, 166, 82, 156, 209, 221, + /* 1640 */ 166, 221, 156, 155, 209, 156, 221, 166, 221, 156, + /* 1650 */ 156, 156, 166, 82, 82, 166, 15, 238, 190, 166, + /* 1660 */ 166, 166, 239, 121, 238, 194, 176, 231, 196, 185, + /* 1670 */ 231, 226, 176, 208, 176, 197, 167, 233, 181, 209, + /* 1680 */ 167, 167, 182, 182, 182, 167, 223, 232, 240, 207, + /* 1690 */ 240, 227, 193, 189, 186, 240, 189, +}; +#define YY_SHIFT_USE_DFLT (-101) +#define YY_SHIFT_COUNT (343) +#define YY_SHIFT_MIN (-100) +#define YY_SHIFT_MAX (1641) +static const short yy_shift_ofst[] = { + /* 0 */ 273, -7, 499, -4, 141, 892, 1246, 1188, 533, 533, + /* 10 */ 13, 577, 567, 1107, 1280, 725, 81, 366, 1054, 973, + /* 20 */ 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, + /* 30 */ 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, + /* 40 */ 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1280, 1028, 43, + /* 50 */ 1296, 1296, 1296, 1296, 1265, 1265, 1296, 1265, 1265, 1265, + /* 60 */ 524, 173, 929, 1117, 1095, 1071, 1044, 1039, 1010, 986, + /* 70 */ 251, 1287, 1287, 1298, 1130, 1296, 1287, 255, 301, 153, + /* 80 */ 917, 853, 843, 540, 202, 531, 1273, 1260, 1253, 1244, + /* 90 */ 1207, 1202, 1132, 1199, 1140, 1135, 1120, 1085, 731, 816, + /* 100 */ 915, 728, 1080, 1042, 1152, 1152, 611, 1152, 990, 625, + /* 110 */ 942, 195, 605, 1225, 1225, 1225, 1225, 1225, 1225, 1225, + /* 120 */ 1225, -30, 1033, 167, 1321, 1321, -101, 502, 502, 502, + /* 130 */ 502, 502, 502, 502, 527, 327, 659, 778, 742, 716, + /* 140 */ 692, 359, 804, 804, 827, 850, 850, 405, 405, 405, + /* 150 */ 405, 204, 211, 1, 233, 1, 181, 522, 35, 375, + /* 160 */ -20, 121, -76, -100, -100, 858, 426, -100, -100, 166, + /* 170 */ 690, 690, 690, 200, 200, 200, 566, 162, 1108, 164, + /* 180 */ 93, 918, 164, 550, 678, 678, 565, 41, 164, 1641, + /* 190 */ 1542, 1641, 1552, 1562, 1572, 1571, 1554, 1269, 1552, 1562, + /* 200 */ 1550, 1544, 1529, 1562, 1314, 1510, 1462, 1565, 1506, 1485, + /* 210 */ 1451, 1269, 1269, 1269, 1269, 1403, 1503, 1451, 1269, 1438, + /* 220 */ 1503, 1405, 1348, 1430, 1408, 1373, 1342, 1345, 1334, 1338, + /* 230 */ 1314, 1321, 1269, 1269, 1403, 1269, 1269, 1335, 1297, 1297, + /* 240 */ 1297, 1269, 1297, -101, -101, -101, -101, -101, -101, -101, + /* 250 */ -101, 458, 15, 880, 349, 848, 564, 1096, 706, 1072, + /* 260 */ 983, 901, 590, 582, 451, 241, 449, 86, 38, -11, + /* 270 */ 55, 28, -59, 1238, 1157, 1226, 1224, 1216, 1282, 1177, + /* 280 */ 1176, 1183, 1185, 1195, 1122, 1214, 1175, 1205, 1198, 1162, + /* 290 */ 1160, 1146, 1138, 1082, 1164, 1161, 1092, 1060, 1159, 1066, + /* 300 */ 1079, 1056, 1012, 1017, 1037, 1006, 975, 972, 1018, 954, + /* 310 */ 935, 932, 928, 905, 896, 845, 884, 883, 860, 813, + /* 320 */ 860, 854, 815, 785, 840, 801, 800, 736, 617, 636, + /* 330 */ 588, 559, 555, 532, 316, 498, 418, 407, 317, 178, + /* 340 */ 143, 87, 130, -41, +}; +#define YY_REDUCE_USE_DFLT (-194) +#define YY_REDUCE_COUNT (250) +#define YY_REDUCE_MIN (-193) +#define YY_REDUCE_MAX (1518) +static const short yy_reduce_ofst[] = { + /* 0 */ 192, -34, 774, -57, 782, 318, -148, 879, 776, 374, + /* 10 */ 132, 29, 320, 512, 466, 277, 1427, 1425, 1420, 1418, + /* 20 */ 1400, 1398, 1395, 1392, 1391, 1375, 1366, 1358, 1357, 1352, + /* 30 */ 1350, 1349, 1344, 1332, 1325, 1324, 1319, 1318, 1317, 1291, + /* 40 */ 1290, 1283, 1278, 1276, 1271, 1258, 1245, 1241, 1065, 795, + /* 50 */ 1179, 1059, 906, 893, 1234, 1228, -75, 1223, 1220, 1083, + /* 60 */ 280, 431, 286, 920, 1443, 1424, 1267, 1222, 1196, 1100, + /* 70 */ 925, 1112, 788, 470, 297, -131, 46, 866, 1061, 307, + /* 80 */ 1489, 1495, 1494, 936, 1493, 1489, 936, 936, 1486, 936, + /* 90 */ 936, 936, 1481, 936, 936, 936, 1474, 1469, 936, 1467, + /* 100 */ 1466, 1461, 1458, 1454, 1007, 982, 1452, 864, 1449, 936, + /* 110 */ 1394, 1393, 1337, 1221, 1180, 1009, 948, 936, 770, 215, + /* 120 */ -80, 796, 634, 656, 242, -45, -193, 1093, 1093, 1093, + /* 130 */ 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, + /* 140 */ 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1093, + /* 150 */ 1093, 1093, 1093, 1507, 1508, 1504, 1499, 1482, 1499, 1464, + /* 160 */ 1463, 1093, 1499, 1499, 1499, 1444, 1455, 1499, 1499, 1518, + /* 170 */ 1502, 1501, 1500, 1514, 1513, 1509, 1470, 1478, 1484, 1498, + /* 180 */ 1497, 1465, 1496, 1445, 1439, 1436, 1472, 1471, 1490, 1426, + /* 190 */ 1423, 1419, 1435, 1468, 1354, 1354, 1354, 1488, 1429, 1441, + /* 200 */ 1409, 1354, 1354, 1416, 1406, 1354, 1363, 1360, 1376, 1354, + /* 210 */ 1377, 1407, 1389, 1387, 1382, 1353, 1362, 1355, 1370, 1310, + /* 220 */ 1323, 1308, 1257, 1256, 1093, 1233, 1270, 1263, 1251, 1254, + /* 230 */ 1261, 1249, 1285, 1281, 1247, 1274, 1266, 1252, 1255, 1248, + /* 240 */ 1242, 1208, 1204, 1171, 1163, 1114, 1093, 1093, 1093, 1093, + /* 250 */ 1142, +}; +static const YYACTIONTYPE yy_default[] = { + /* 0 */ 589, 936, 936, 862, 903, 851, 851, 851, 936, 936, + /* 10 */ 730, 936, 851, 851, 851, 936, 851, 851, 851, 851, + /* 20 */ 851, 851, 851, 851, 851, 851, 851, 851, 851, 851, + /* 30 */ 851, 851, 851, 851, 851, 851, 851, 851, 851, 851, + /* 40 */ 851, 851, 851, 851, 851, 851, 851, 851, 724, 608, + /* 50 */ 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, + /* 60 */ 616, 720, 730, 936, 936, 936, 936, 790, 777, 768, + /* 70 */ 936, 800, 800, 783, 679, 936, 800, 756, 752, 936, + /* 80 */ 839, 936, 936, 731, 936, 839, 936, 936, 936, 936, + /* 90 */ 936, 791, 784, 778, 769, 936, 936, 936, 936, 936, + /* 100 */ 936, 936, 936, 936, 720, 720, 936, 720, 936, 936, + /* 110 */ 936, 840, 594, 936, 882, 936, 936, 936, 936, 936, + /* 120 */ 936, 605, 589, 936, 936, 936, 710, 736, 773, 761, + /* 130 */ 863, 856, 857, 855, 852, 852, 852, 852, 852, 852, + /* 140 */ 852, 852, 852, 823, 816, 827, 815, 831, 841, 826, + /* 150 */ 818, 817, 819, 936, 936, 936, 936, 723, 936, 936, + /* 160 */ 936, 820, 936, 789, 698, 936, 910, 693, 601, 618, + /* 170 */ 936, 936, 936, 936, 936, 936, 936, 772, 670, 756, + /* 180 */ 645, 738, 756, 858, 936, 936, 721, 708, 756, 934, + /* 190 */ 931, 934, 739, 683, 739, 739, 739, 681, 739, 683, + /* 200 */ 796, 739, 739, 683, 772, 739, 918, 915, 739, 739, + /* 210 */ 871, 681, 681, 681, 681, 662, 936, 871, 681, 739, + /* 220 */ 936, 739, 936, 852, 821, 752, 762, 748, 760, 757, + /* 230 */ 772, 936, 681, 681, 662, 681, 681, 665, 593, 593, + /* 240 */ 593, 681, 593, 649, 649, 726, 830, 829, 828, 822, + /* 250 */ 629, 864, 936, 936, 936, 936, 936, 936, 936, 936, + /* 260 */ 936, 936, 936, 936, 936, 936, 936, 936, 936, 763, + /* 270 */ 936, 936, 936, 936, 936, 936, 936, 936, 936, 881, + /* 280 */ 936, 936, 936, 936, 936, 936, 914, 913, 936, 936, + /* 290 */ 936, 936, 936, 936, 936, 936, 936, 936, 902, 936, + /* 300 */ 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, + /* 310 */ 936, 936, 936, 936, 936, 936, 936, 936, 758, 936, + /* 320 */ 861, 843, 701, 850, 936, 936, 936, 936, 936, 842, + /* 330 */ 853, 810, 936, 749, 936, 809, 806, 808, 611, 936, + /* 340 */ 936, 936, 936, 936, 586, 590, 935, 933, 932, 930, + /* 350 */ 890, 889, 888, 886, 895, 894, 893, 892, 891, 887, + /* 360 */ 885, 884, 883, 880, 787, 775, 766, 697, 929, 927, + /* 370 */ 928, 879, 877, 878, 696, 695, 692, 691, 690, 869, + /* 380 */ 868, 866, 865, 867, 604, 906, 909, 908, 907, 912, + /* 390 */ 911, 904, 917, 916, 921, 925, 924, 923, 922, 920, + /* 400 */ 901, 795, 794, 792, 797, 788, 793, 780, 786, 785, + /* 410 */ 776, 779, 684, 771, 767, 770, 905, 694, 603, 741, + /* 420 */ 602, 607, 668, 669, 677, 680, 675, 678, 674, 673, + /* 430 */ 672, 676, 671, 667, 610, 609, 623, 621, 622, 620, + /* 440 */ 619, 617, 639, 638, 635, 637, 634, 636, 633, 632, + /* 450 */ 631, 630, 628, 661, 647, 646, 874, 876, 875, 873, + /* 460 */ 872, 654, 653, 659, 658, 657, 656, 652, 655, 651, + /* 470 */ 650, 648, 644, 814, 813, 807, 835, 707, 706, 715, + /* 480 */ 713, 712, 711, 747, 746, 745, 744, 743, 742, 735, + /* 490 */ 733, 729, 728, 734, 732, 727, 719, 717, 718, 716, + /* 500 */ 612, 802, 799, 801, 798, 737, 725, 722, 709, 751, + /* 510 */ 753, 854, 844, 834, 845, 740, 832, 833, 704, 703, + /* 520 */ 702, 853, 850, 846, 926, 838, 849, 837, 836, 825, + /* 530 */ 824, 812, 847, 848, 811, 750, 765, 898, 897, 900, + /* 540 */ 899, 896, 764, 625, 624, 705, 700, 699, 805, 804, + /* 550 */ 803, 643, 755, 754, 642, 664, 663, 660, 641, 640, + /* 560 */ 627, 626, 606, 600, 599, 598, 597, 615, 614, 613, + /* 570 */ 611, 596, 595, 689, 688, 687, 686, 685, 682, 592, + /* 580 */ 591, 588, 587, 585, +}; + +/* The next table maps tokens into fallback tokens. If a construct +** like the following: +** +** %fallback ID X Y Z. +** +** appears in the grammar, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. +*/ +#ifdef YYFALLBACK +static const YYCODETYPE yyFallback[] = { + 0, /* $ => nothing */ + 0, /* ILLEGAL => nothing */ + 0, /* COMMENT => nothing */ + 0, /* SPACE => nothing */ + 0, /* ID => nothing */ + 4, /* ABORT => ID */ + 4, /* AFTER => ID */ + 4, /* ASC => ID */ + 4, /* ATTACH => ID */ + 4, /* BEFORE => ID */ + 4, /* BEGIN => ID */ + 4, /* CASCADE => ID */ + 4, /* CLUSTER => ID */ + 4, /* CONFLICT => ID */ + 4, /* COPY => ID */ + 4, /* DATABASE => ID */ + 4, /* DEFERRED => ID */ + 4, /* DELIMITERS => ID */ + 4, /* DESC => ID */ + 4, /* DETACH => ID */ + 4, /* EACH => ID */ + 4, /* END => ID */ + 4, /* EXPLAIN => ID */ + 4, /* FAIL => ID */ + 4, /* FOR => ID */ + 4, /* GLOB => ID */ + 4, /* IGNORE => ID */ + 4, /* IMMEDIATE => ID */ + 4, /* INITIALLY => ID */ + 4, /* INSTEAD => ID */ + 4, /* LIKE => ID */ + 4, /* MATCH => ID */ + 4, /* KEY => ID */ + 4, /* OF => ID */ + 4, /* OFFSET => ID */ + 4, /* PRAGMA => ID */ + 4, /* RAISE => ID */ + 4, /* REPLACE => ID */ + 4, /* RESTRICT => ID */ + 4, /* ROW => ID */ + 4, /* STATEMENT => ID */ + 4, /* TEMP => ID */ + 4, /* TRIGGER => ID */ + 4, /* VACUUM => ID */ + 4, /* VIEW => ID */ +}; +#endif /* YYFALLBACK */ + +/* The following structure represents a single element of the +** parser's stack. Information stored includes: +** +** + The state number for the parser at this level of the stack. +** +** + The value of the token stored at this level of the stack. +** (In other words, the "major" token.) +** +** + The semantic value stored at this level of the stack. This is +** the information used by the action routines in the grammar. +** It is sometimes called the "minor" token. +*/ +struct yyStackEntry { + YYACTIONTYPE stateno; /* The state-number */ + YYCODETYPE major; /* The major token value. This is the code + ** number for the token at this stack level */ + YYMINORTYPE minor; /* The user-supplied minor token value. This + ** is the value of the token */ + QList<Token*>* tokens = nullptr; +}; +typedef struct yyStackEntry yyStackEntry; + +/* The state of the parser is completely contained in an instance of +** the following structure */ +struct yyParser { + int yyidx; /* Index of top element in stack */ +#ifdef YYTRACKMAXSTACKDEPTH + int yyidxMax; /* Maximum value of yyidx */ +#endif + int yyerrcnt; /* Shifts left before out of the error */ + sqlite2_parseARG_SDECL /* A place to hold %extra_argument */ +#if YYSTACKDEPTH<=0 + int yystksz; /* Current side of the stack */ + yyStackEntry *yystack; /* The parser's stack */ +#else + yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ +#endif +}; +typedef struct yyParser yyParser; + +#ifndef NDEBUG +#include <stdio.h> +static FILE *yyTraceFILE = 0; +static char *yyTracePrompt = 0; +#endif /* NDEBUG */ + +void *sqlite2_parseCopyParserState(void* other) +{ + yyParser *pParser; + yyParser *otherParser = (yyParser*)other; + + // Copy parser + pParser = (yyParser*)malloc((size_t)sizeof(yyParser)); + memcpy(pParser, other, (size_t)sizeof(yyParser)); + +#if YYSTACKDEPTH<=0 + // Copy stack + int stackSize = sizeof(yyStackEntry) * pParser->yystksz; + pParser->yystack = malloc((size_t)stackSize); + memcpy(pParser->yystack, ((yyParser*)other)->yystack, (size_t)stackSize); +#endif + + for (int i = 0; i <= pParser->yyidx; i++) + { + pParser->yystack[i].tokens = new QList<Token*>(); + *(pParser->yystack[i].tokens) = *(otherParser->yystack[i].tokens); + } + + return pParser; +} + +void sqlite2_parseAddToken(void* other, Token* token) +{ + yyParser *otherParser = (yyParser*)other; + if (otherParser->yyidx < 0) + return; // Nothing on stack yet. Might happen when parsing just whitespaces, nothing else. + + otherParser->yystack[otherParser->yyidx].tokens->append(token); +} + +void sqlite2_parseRestoreParserState(void* saved, void* target) +{ + yyParser *pParser = (yyParser*)target; + yyParser *savedParser = (yyParser*)saved; + + for (int i = 0; i <= pParser->yyidx; i++) + delete pParser->yystack[i].tokens; + + memcpy(pParser, saved, (size_t)sizeof(yyParser)); + + for (int i = 0; i <= savedParser->yyidx; i++) + { + pParser->yystack[i].tokens = new QList<Token*>(); + *(pParser->yystack[i].tokens) = *(savedParser->yystack[i].tokens); + } + +#if YYSTACKDEPTH<=0 + // Copy stack + int stackSize = sizeof(yyStackEntry) * pParser->yystksz; + pParser->yystack = relloc(pParser->yystack, (size_t)stackSize); + memcpy(pParser->yystack, ((yyParser*)saved)->yystack, (size_t)stackSize); +#endif +} + +void sqlite2_parseFreeSavedState(void* other) +{ + yyParser *pParser = (yyParser*)other; + for (int i = 0; i <= pParser->yyidx; i++) + delete pParser->yystack[i].tokens; + +#if YYSTACKDEPTH<=0 + free(pParser->yystack); +#endif + free(other); +} + +#ifndef NDEBUG +/* +** Turn parser tracing on by giving a stream to which to write the trace +** and a prompt to preface each trace message. Tracing is turned off +** by making either argument NULL +** +** Inputs: +** <ul> +** <li> A FILE* to which trace output should be written. +** If NULL, then tracing is turned off. +** <li> A prefix string written at the beginning of every +** line of trace output. If NULL, then tracing is +** turned off. +** </ul> +** +** Outputs: +** None. +*/ +void sqlite2_parseTrace(FILE *TraceFILE, char *zTracePrompt){ + yyTraceFILE = TraceFILE; + yyTracePrompt = zTracePrompt; + if( yyTraceFILE==0 ) yyTracePrompt = 0; + else if( yyTracePrompt==0 ) yyTraceFILE = 0; +} +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *const yyTokenName[] = { + "$", "ILLEGAL", "COMMENT", "SPACE", + "ID", "ABORT", "AFTER", "ASC", + "ATTACH", "BEFORE", "BEGIN", "CASCADE", + "CLUSTER", "CONFLICT", "COPY", "DATABASE", + "DEFERRED", "DELIMITERS", "DESC", "DETACH", + "EACH", "END", "EXPLAIN", "FAIL", + "FOR", "GLOB", "IGNORE", "IMMEDIATE", + "INITIALLY", "INSTEAD", "LIKE", "MATCH", + "KEY", "OF", "OFFSET", "PRAGMA", + "RAISE", "REPLACE", "RESTRICT", "ROW", + "STATEMENT", "TEMP", "TRIGGER", "VACUUM", + "VIEW", "OR", "AND", "NOT", + "EQ", "NE", "ISNULL", "NOTNULL", + "IS", "BETWEEN", "IN", "GT", + "GE", "LT", "LE", "BITAND", + "BITOR", "LSHIFT", "RSHIFT", "PLUS", + "MINUS", "STAR", "SLASH", "REM", + "CONCAT", "UMINUS", "UPLUS", "BITNOT", + "SEMI", "TRANSACTION", "ID_TRANS", "COMMIT", + "ROLLBACK", "CREATE", "TABLE", "LP", + "RP", "AS", "DOT", "ID_TAB_NEW", + "ID_DB", "COMMA", "ID_COL_NEW", "STRING", + "JOIN_KW", "ID_COL_TYPE", "DEFAULT", "INTEGER", + "FLOAT", "NULL", "CONSTRAINT", "PRIMARY", + "UNIQUE", "CHECK", "REFERENCES", "COLLATE", + "ON", "INSERT", "DELETE", "UPDATE", + "ID_FK_MATCH", "SET", "DEFERRABLE", "FOREIGN", + "ID_CONSTR", "ID_TAB", "DROP", "ID_VIEW_NEW", + "ID_VIEW", "UNION", "ALL", "EXCEPT", + "INTERSECT", "SELECT", "DISTINCT", "ID_ALIAS", + "FROM", "USING", "JOIN", "ID_JOIN_OPTS", + "ORDER", "BY", "GROUP", "HAVING", + "LIMIT", "WHERE", "ID_COL", "INTO", + "VALUES", "VARIABLE", "LIKE_KW", "CASE", + "ID_FN", "ID_ERR_MSG", "WHEN", "THEN", + "ELSE", "INDEX", "ID_IDX_NEW", "ID_IDX", + "ID_PRAGMA", "ID_TRIG_NEW", "ID_TRIG", "error", + "cmd", "input", "cmdlist", "ecmd", + "explain", "cmdx", "trans_opt", "onconf", + "nm", "temp", "fullname", "columnlist", + "conslist_opt", "select", "column", "columnid", + "type", "carglist", "id", "ids", + "typetoken", "typename", "signed", "plus_num", + "minus_num", "ccons", "ccons_nm", "carg", + "sortorder", "expr", "idxlist_opt", "refargs", + "defer_subclause", "refarg", "refact", "init_deferred_pred_opt", + "conslist", "tconscomma", "tcons", "idxlist", + "defer_subclause_opt", "resolvetype", "orconf", "select_stmt", + "oneselect", "multiselect_op", "distinct", "selcollist", + "from", "where_opt", "groupby_opt", "having_opt", + "orderby_opt", "limit_opt", "sclp", "as", + "joinsrc", "singlesrc", "seltablist", "joinop", + "joinconstr_opt", "dbnm", "inscollist", "sortlist", + "collate", "nexprlist", "delete_stmt", "update_stmt", + "setlist", "insert_stmt", "insert_cmd", "inscollist_opt", + "exprlist", "exprx", "not_opt", "likeop", + "case_operand", "case_exprlist", "case_else", "raisetype", + "uniqueflag", "idxlist_single", "nmnum", "number", + "trigger_time", "trigger_event", "foreach_clause", "when_clause", + "trigger_cmd_list", "trigger_cmd", "database_kw_opt", "key_opt", +}; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *const yyRuleName[] = { + /* 0 */ "input ::= cmdlist", + /* 1 */ "cmdlist ::= cmdlist ecmd", + /* 2 */ "cmdlist ::= ecmd", + /* 3 */ "ecmd ::= SEMI", + /* 4 */ "ecmd ::= explain cmdx SEMI", + /* 5 */ "explain ::=", + /* 6 */ "explain ::= EXPLAIN", + /* 7 */ "cmdx ::= cmd", + /* 8 */ "cmd ::= BEGIN trans_opt onconf", + /* 9 */ "trans_opt ::=", + /* 10 */ "trans_opt ::= TRANSACTION", + /* 11 */ "trans_opt ::= TRANSACTION nm", + /* 12 */ "trans_opt ::= TRANSACTION ID_TRANS", + /* 13 */ "cmd ::= COMMIT trans_opt", + /* 14 */ "cmd ::= END trans_opt", + /* 15 */ "cmd ::= ROLLBACK trans_opt", + /* 16 */ "cmd ::= CREATE temp TABLE fullname LP columnlist conslist_opt RP", + /* 17 */ "cmd ::= CREATE temp TABLE fullname AS select", + /* 18 */ "cmd ::= CREATE temp TABLE nm DOT ID_TAB_NEW", + /* 19 */ "cmd ::= CREATE temp TABLE ID_DB|ID_TAB_NEW", + /* 20 */ "temp ::= TEMP", + /* 21 */ "temp ::=", + /* 22 */ "columnlist ::= columnlist COMMA column", + /* 23 */ "columnlist ::= column", + /* 24 */ "column ::= columnid type carglist", + /* 25 */ "columnid ::= nm", + /* 26 */ "columnid ::= ID_COL_NEW", + /* 27 */ "id ::= ID", + /* 28 */ "ids ::= ID|STRING", + /* 29 */ "nm ::= id", + /* 30 */ "nm ::= STRING", + /* 31 */ "nm ::= JOIN_KW", + /* 32 */ "type ::=", + /* 33 */ "type ::= typetoken", + /* 34 */ "typetoken ::= typename", + /* 35 */ "typetoken ::= typename LP signed RP", + /* 36 */ "typetoken ::= typename LP signed COMMA signed RP", + /* 37 */ "typename ::= ids", + /* 38 */ "typename ::= typename ids", + /* 39 */ "typename ::= ID_COL_TYPE", + /* 40 */ "signed ::= plus_num", + /* 41 */ "signed ::= minus_num", + /* 42 */ "carglist ::= carglist ccons", + /* 43 */ "carglist ::= carglist ccons_nm ccons", + /* 44 */ "carglist ::= carglist carg", + /* 45 */ "carglist ::=", + /* 46 */ "carg ::= DEFAULT STRING", + /* 47 */ "carg ::= DEFAULT ID", + /* 48 */ "carg ::= DEFAULT INTEGER", + /* 49 */ "carg ::= DEFAULT PLUS INTEGER", + /* 50 */ "carg ::= DEFAULT MINUS INTEGER", + /* 51 */ "carg ::= DEFAULT FLOAT", + /* 52 */ "carg ::= DEFAULT PLUS FLOAT", + /* 53 */ "carg ::= DEFAULT MINUS FLOAT", + /* 54 */ "carg ::= DEFAULT NULL", + /* 55 */ "ccons_nm ::= CONSTRAINT nm", + /* 56 */ "ccons ::= NULL onconf", + /* 57 */ "ccons ::= NOT NULL onconf", + /* 58 */ "ccons ::= PRIMARY KEY sortorder onconf", + /* 59 */ "ccons ::= UNIQUE onconf", + /* 60 */ "ccons ::= CHECK LP expr RP onconf", + /* 61 */ "ccons ::= REFERENCES nm idxlist_opt refargs", + /* 62 */ "ccons ::= defer_subclause", + /* 63 */ "ccons ::= COLLATE id", + /* 64 */ "ccons ::= CHECK LP RP", + /* 65 */ "refargs ::=", + /* 66 */ "refargs ::= refargs refarg", + /* 67 */ "refarg ::= MATCH nm", + /* 68 */ "refarg ::= ON INSERT refact", + /* 69 */ "refarg ::= ON DELETE refact", + /* 70 */ "refarg ::= ON UPDATE refact", + /* 71 */ "refarg ::= MATCH ID_FK_MATCH", + /* 72 */ "refact ::= SET NULL", + /* 73 */ "refact ::= SET DEFAULT", + /* 74 */ "refact ::= CASCADE", + /* 75 */ "refact ::= RESTRICT", + /* 76 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt", + /* 77 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt", + /* 78 */ "init_deferred_pred_opt ::=", + /* 79 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED", + /* 80 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE", + /* 81 */ "conslist_opt ::=", + /* 82 */ "conslist_opt ::= COMMA conslist", + /* 83 */ "conslist ::= conslist tconscomma tcons", + /* 84 */ "conslist ::= tcons", + /* 85 */ "tconscomma ::= COMMA", + /* 86 */ "tconscomma ::=", + /* 87 */ "tcons ::= CONSTRAINT nm", + /* 88 */ "tcons ::= PRIMARY KEY LP idxlist RP onconf", + /* 89 */ "tcons ::= UNIQUE LP idxlist RP onconf", + /* 90 */ "tcons ::= CHECK LP expr RP onconf", + /* 91 */ "tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt", + /* 92 */ "tcons ::= CONSTRAINT ID_CONSTR", + /* 93 */ "tcons ::= FOREIGN KEY LP idxlist RP REFERENCES ID_TAB", + /* 94 */ "tcons ::= CHECK LP RP onconf", + /* 95 */ "defer_subclause_opt ::=", + /* 96 */ "defer_subclause_opt ::= defer_subclause", + /* 97 */ "onconf ::=", + /* 98 */ "onconf ::= ON CONFLICT resolvetype", + /* 99 */ "orconf ::=", + /* 100 */ "orconf ::= OR resolvetype", + /* 101 */ "resolvetype ::= ROLLBACK", + /* 102 */ "resolvetype ::= ABORT", + /* 103 */ "resolvetype ::= FAIL", + /* 104 */ "resolvetype ::= IGNORE", + /* 105 */ "resolvetype ::= REPLACE", + /* 106 */ "cmd ::= DROP TABLE fullname", + /* 107 */ "cmd ::= DROP TABLE nm DOT ID_TAB", + /* 108 */ "cmd ::= DROP TABLE ID_DB|ID_TAB", + /* 109 */ "cmd ::= CREATE temp VIEW nm AS select", + /* 110 */ "cmd ::= CREATE temp VIEW ID_VIEW_NEW", + /* 111 */ "cmd ::= DROP VIEW nm", + /* 112 */ "cmd ::= DROP VIEW ID_VIEW", + /* 113 */ "cmd ::= select_stmt", + /* 114 */ "select_stmt ::= select", + /* 115 */ "select ::= oneselect", + /* 116 */ "select ::= select multiselect_op oneselect", + /* 117 */ "multiselect_op ::= UNION", + /* 118 */ "multiselect_op ::= UNION ALL", + /* 119 */ "multiselect_op ::= EXCEPT", + /* 120 */ "multiselect_op ::= INTERSECT", + /* 121 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt", + /* 122 */ "distinct ::= DISTINCT", + /* 123 */ "distinct ::= ALL", + /* 124 */ "distinct ::=", + /* 125 */ "sclp ::= selcollist COMMA", + /* 126 */ "sclp ::=", + /* 127 */ "selcollist ::= sclp expr as", + /* 128 */ "selcollist ::= sclp STAR", + /* 129 */ "selcollist ::= sclp nm DOT STAR", + /* 130 */ "selcollist ::= sclp", + /* 131 */ "selcollist ::= sclp ID_TAB DOT STAR", + /* 132 */ "as ::= AS nm", + /* 133 */ "as ::= ids", + /* 134 */ "as ::= AS ID_ALIAS", + /* 135 */ "as ::= ID_ALIAS", + /* 136 */ "as ::=", + /* 137 */ "from ::=", + /* 138 */ "from ::= FROM joinsrc", + /* 139 */ "joinsrc ::= singlesrc seltablist", + /* 140 */ "joinsrc ::=", + /* 141 */ "seltablist ::= seltablist joinop singlesrc joinconstr_opt", + /* 142 */ "seltablist ::=", + /* 143 */ "singlesrc ::= nm dbnm as", + /* 144 */ "singlesrc ::= LP select RP as", + /* 145 */ "singlesrc ::= LP joinsrc RP as", + /* 146 */ "singlesrc ::=", + /* 147 */ "singlesrc ::= nm DOT", + /* 148 */ "singlesrc ::= nm DOT ID_TAB", + /* 149 */ "singlesrc ::= ID_DB|ID_TAB", + /* 150 */ "singlesrc ::= nm DOT ID_VIEW", + /* 151 */ "singlesrc ::= ID_DB|ID_VIEW", + /* 152 */ "joinconstr_opt ::= ON expr", + /* 153 */ "joinconstr_opt ::= USING LP inscollist RP", + /* 154 */ "joinconstr_opt ::=", + /* 155 */ "dbnm ::=", + /* 156 */ "dbnm ::= DOT nm", + /* 157 */ "fullname ::= nm dbnm", + /* 158 */ "joinop ::= COMMA", + /* 159 */ "joinop ::= JOIN", + /* 160 */ "joinop ::= JOIN_KW JOIN", + /* 161 */ "joinop ::= JOIN_KW nm JOIN", + /* 162 */ "joinop ::= JOIN_KW nm nm JOIN", + /* 163 */ "joinop ::= ID_JOIN_OPTS", + /* 164 */ "orderby_opt ::=", + /* 165 */ "orderby_opt ::= ORDER BY sortlist", + /* 166 */ "sortlist ::= sortlist COMMA collate expr sortorder", + /* 167 */ "sortlist ::= expr collate sortorder", + /* 168 */ "collate ::=", + /* 169 */ "collate ::= COLLATE id", + /* 170 */ "sortorder ::= ASC", + /* 171 */ "sortorder ::= DESC", + /* 172 */ "sortorder ::=", + /* 173 */ "groupby_opt ::=", + /* 174 */ "groupby_opt ::= GROUP BY nexprlist", + /* 175 */ "groupby_opt ::= GROUP BY", + /* 176 */ "having_opt ::=", + /* 177 */ "having_opt ::= HAVING expr", + /* 178 */ "limit_opt ::=", + /* 179 */ "limit_opt ::= LIMIT signed", + /* 180 */ "limit_opt ::= LIMIT signed OFFSET signed", + /* 181 */ "limit_opt ::= LIMIT signed COMMA signed", + /* 182 */ "cmd ::= delete_stmt", + /* 183 */ "delete_stmt ::= DELETE FROM fullname where_opt", + /* 184 */ "delete_stmt ::= DELETE FROM", + /* 185 */ "delete_stmt ::= DELETE FROM nm DOT", + /* 186 */ "delete_stmt ::= DELETE FROM nm DOT ID_TAB", + /* 187 */ "delete_stmt ::= DELETE FROM ID_DB|ID_TAB", + /* 188 */ "where_opt ::=", + /* 189 */ "where_opt ::= WHERE expr", + /* 190 */ "where_opt ::= WHERE", + /* 191 */ "cmd ::= update_stmt", + /* 192 */ "update_stmt ::= UPDATE orconf fullname SET setlist where_opt", + /* 193 */ "update_stmt ::= UPDATE orconf", + /* 194 */ "update_stmt ::= UPDATE orconf nm DOT", + /* 195 */ "update_stmt ::= UPDATE orconf nm DOT ID_TAB", + /* 196 */ "update_stmt ::= UPDATE orconf ID_DB|ID_TAB", + /* 197 */ "setlist ::= setlist COMMA nm EQ expr", + /* 198 */ "setlist ::= nm EQ expr", + /* 199 */ "setlist ::=", + /* 200 */ "setlist ::= setlist COMMA", + /* 201 */ "setlist ::= setlist COMMA ID_COL", + /* 202 */ "setlist ::= ID_COL", + /* 203 */ "cmd ::= insert_stmt", + /* 204 */ "insert_stmt ::= insert_cmd INTO fullname inscollist_opt VALUES LP exprlist RP", + /* 205 */ "insert_stmt ::= insert_cmd INTO fullname inscollist_opt select", + /* 206 */ "insert_stmt ::= insert_cmd INTO", + /* 207 */ "insert_stmt ::= insert_cmd INTO nm DOT", + /* 208 */ "insert_stmt ::= insert_cmd INTO ID_DB|ID_TAB", + /* 209 */ "insert_stmt ::= insert_cmd INTO nm DOT ID_TAB", + /* 210 */ "insert_cmd ::= INSERT orconf", + /* 211 */ "insert_cmd ::= REPLACE", + /* 212 */ "inscollist_opt ::=", + /* 213 */ "inscollist_opt ::= LP inscollist RP", + /* 214 */ "inscollist ::= inscollist COMMA nm", + /* 215 */ "inscollist ::= nm", + /* 216 */ "inscollist ::=", + /* 217 */ "inscollist ::= inscollist COMMA ID_COL", + /* 218 */ "inscollist ::= ID_COL", + /* 219 */ "exprx ::= NULL", + /* 220 */ "exprx ::= INTEGER", + /* 221 */ "exprx ::= FLOAT", + /* 222 */ "exprx ::= STRING", + /* 223 */ "exprx ::= LP expr RP", + /* 224 */ "exprx ::= id", + /* 225 */ "exprx ::= JOIN_KW", + /* 226 */ "exprx ::= nm DOT nm", + /* 227 */ "exprx ::= nm DOT nm DOT nm", + /* 228 */ "exprx ::= VARIABLE", + /* 229 */ "exprx ::= ID LP exprlist RP", + /* 230 */ "exprx ::= ID LP STAR RP", + /* 231 */ "exprx ::= expr AND expr", + /* 232 */ "exprx ::= expr OR expr", + /* 233 */ "exprx ::= expr LT|GT|GE|LE expr", + /* 234 */ "exprx ::= expr EQ|NE expr", + /* 235 */ "exprx ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr", + /* 236 */ "exprx ::= expr PLUS|MINUS expr", + /* 237 */ "exprx ::= expr STAR|SLASH|REM expr", + /* 238 */ "exprx ::= expr CONCAT expr", + /* 239 */ "exprx ::= expr not_opt likeop expr", + /* 240 */ "exprx ::= expr ISNULL|NOTNULL", + /* 241 */ "exprx ::= expr NOT NULL", + /* 242 */ "exprx ::= expr IS not_opt expr", + /* 243 */ "exprx ::= NOT expr", + /* 244 */ "exprx ::= BITNOT expr", + /* 245 */ "exprx ::= MINUS expr", + /* 246 */ "exprx ::= PLUS expr", + /* 247 */ "exprx ::= expr not_opt BETWEEN expr AND expr", + /* 248 */ "exprx ::= expr not_opt IN LP exprlist RP", + /* 249 */ "exprx ::= expr not_opt IN LP select RP", + /* 250 */ "exprx ::= expr not_opt IN nm dbnm", + /* 251 */ "exprx ::= LP select RP", + /* 252 */ "exprx ::= CASE case_operand case_exprlist case_else END", + /* 253 */ "exprx ::= RAISE LP raisetype COMMA nm RP", + /* 254 */ "exprx ::= RAISE LP IGNORE RP", + /* 255 */ "exprx ::= nm DOT", + /* 256 */ "exprx ::= nm DOT nm DOT", + /* 257 */ "exprx ::= expr not_opt BETWEEN expr", + /* 258 */ "exprx ::= CASE case_operand case_exprlist case_else", + /* 259 */ "exprx ::= expr not_opt IN LP exprlist", + /* 260 */ "exprx ::= expr not_opt IN ID_DB", + /* 261 */ "exprx ::= expr not_opt IN nm DOT ID_TAB", + /* 262 */ "exprx ::= ID_DB|ID_TAB|ID_COL|ID_FN", + /* 263 */ "exprx ::= nm DOT ID_TAB|ID_COL", + /* 264 */ "exprx ::= nm DOT nm DOT ID_COL", + /* 265 */ "exprx ::= RAISE LP raisetype COMMA ID_ERR_MSG RP", + /* 266 */ "expr ::= exprx", + /* 267 */ "expr ::=", + /* 268 */ "not_opt ::=", + /* 269 */ "not_opt ::= NOT", + /* 270 */ "likeop ::= LIKE|GLOB", + /* 271 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", + /* 272 */ "case_exprlist ::= WHEN expr THEN expr", + /* 273 */ "case_else ::= ELSE expr", + /* 274 */ "case_else ::=", + /* 275 */ "case_operand ::= exprx", + /* 276 */ "case_operand ::=", + /* 277 */ "exprlist ::= nexprlist", + /* 278 */ "exprlist ::=", + /* 279 */ "nexprlist ::= nexprlist COMMA expr", + /* 280 */ "nexprlist ::= exprx", + /* 281 */ "cmd ::= CREATE uniqueflag INDEX nm ON nm dbnm LP idxlist RP onconf", + /* 282 */ "cmd ::= CREATE uniqueflag INDEX nm dbnm ON ID_TAB", + /* 283 */ "cmd ::= CREATE uniqueflag INDEX nm DOT ID_IDX_NEW", + /* 284 */ "cmd ::= CREATE uniqueflag INDEX ID_DB|ID_IDX_NEW", + /* 285 */ "uniqueflag ::= UNIQUE", + /* 286 */ "uniqueflag ::=", + /* 287 */ "idxlist_opt ::=", + /* 288 */ "idxlist_opt ::= LP idxlist RP", + /* 289 */ "idxlist ::= idxlist COMMA idxlist_single", + /* 290 */ "idxlist ::= idxlist_single", + /* 291 */ "idxlist_single ::= nm sortorder", + /* 292 */ "idxlist_single ::= ID_COL", + /* 293 */ "cmd ::= DROP INDEX fullname", + /* 294 */ "cmd ::= DROP INDEX nm DOT ID_IDX", + /* 295 */ "cmd ::= DROP INDEX ID_DB|ID_IDX", + /* 296 */ "cmd ::= COPY orconf nm dbnm FROM nm USING DELIMITERS STRING", + /* 297 */ "cmd ::= COPY orconf nm dbnm FROM nm", + /* 298 */ "cmd ::= VACUUM", + /* 299 */ "cmd ::= VACUUM nm", + /* 300 */ "cmd ::= PRAGMA ids", + /* 301 */ "cmd ::= PRAGMA nm EQ nmnum", + /* 302 */ "cmd ::= PRAGMA nm LP nmnum RP", + /* 303 */ "cmd ::= PRAGMA nm EQ minus_num", + /* 304 */ "cmd ::= PRAGMA nm LP minus_num RP", + /* 305 */ "cmd ::= PRAGMA nm DOT ID_PRAGMA", + /* 306 */ "cmd ::= PRAGMA ID_DB|ID_PRAGMA", + /* 307 */ "nmnum ::= plus_num", + /* 308 */ "nmnum ::= nm", + /* 309 */ "nmnum ::= ON", + /* 310 */ "nmnum ::= DELETE", + /* 311 */ "nmnum ::= DEFAULT", + /* 312 */ "plus_num ::= PLUS number", + /* 313 */ "plus_num ::= number", + /* 314 */ "minus_num ::= MINUS number", + /* 315 */ "number ::= INTEGER", + /* 316 */ "number ::= FLOAT", + /* 317 */ "cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause BEGIN trigger_cmd_list END", + /* 318 */ "cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause", + /* 319 */ "cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause BEGIN trigger_cmd_list", + /* 320 */ "cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON ID_TAB|ID_DB", + /* 321 */ "cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON nm DOT ID_TAB", + /* 322 */ "cmd ::= CREATE temp TRIGGER ID_TRIG_NEW", + /* 323 */ "trigger_time ::= BEFORE", + /* 324 */ "trigger_time ::= AFTER", + /* 325 */ "trigger_time ::= INSTEAD OF", + /* 326 */ "trigger_time ::=", + /* 327 */ "trigger_event ::= DELETE", + /* 328 */ "trigger_event ::= INSERT", + /* 329 */ "trigger_event ::= UPDATE", + /* 330 */ "trigger_event ::= UPDATE OF inscollist", + /* 331 */ "foreach_clause ::=", + /* 332 */ "foreach_clause ::= FOR EACH ROW", + /* 333 */ "foreach_clause ::= FOR EACH STATEMENT", + /* 334 */ "when_clause ::=", + /* 335 */ "when_clause ::= WHEN expr", + /* 336 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", + /* 337 */ "trigger_cmd_list ::= trigger_cmd SEMI", + /* 338 */ "trigger_cmd ::= update_stmt", + /* 339 */ "trigger_cmd ::= insert_stmt", + /* 340 */ "trigger_cmd ::= delete_stmt", + /* 341 */ "trigger_cmd ::= select_stmt", + /* 342 */ "raisetype ::= ROLLBACK|ABORT|FAIL", + /* 343 */ "cmd ::= DROP TRIGGER fullname", + /* 344 */ "cmd ::= DROP TRIGGER nm DOT ID_TRIG", + /* 345 */ "cmd ::= DROP TRIGGER ID_DB|ID_TRIG", + /* 346 */ "cmd ::= ATTACH database_kw_opt ids AS ids key_opt", + /* 347 */ "key_opt ::=", + /* 348 */ "key_opt ::= USING ids", + /* 349 */ "database_kw_opt ::= DATABASE", + /* 350 */ "database_kw_opt ::=", + /* 351 */ "cmd ::= DETACH database_kw_opt nm", +}; +#endif /* NDEBUG */ + + +#if YYSTACKDEPTH<=0 +/* +** Try to increase the size of the parser stack. +*/ +static void yyGrowStack(yyParser *p){ + int newSize; + yyStackEntry *pNew; + + newSize = p->yystksz*2 + 100; + pNew = realloc(p->yystack, newSize*sizeof(pNew[0])); + if( pNew ){ + p->yystack = pNew; + p->yystksz = newSize; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack grows to %d entries!\n", + yyTracePrompt, p->yystksz); + } +#endif + } +} +#endif + +/* +** This function allocates a new parser. +** The only argument is a pointer to a function which works like +** malloc. +** +** Inputs: +** A pointer to the function used to allocate memory. +** +** Outputs: +** A pointer to a parser. This pointer is used in subsequent calls +** to sqlite2_parse and sqlite2_parseFree. +*/ +void *sqlite2_parseAlloc(void *(*mallocProc)(size_t)){ + yyParser *pParser; + pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) ); + if( pParser ){ + pParser->yyidx = -1; +#ifdef YYTRACKMAXSTACKDEPTH + pParser->yyidxMax = 0; +#endif +#if YYSTACKDEPTH<=0 + pParser->yystack = NULL; + pParser->yystksz = 0; + yyGrowStack(pParser); +#endif + } + return pParser; +} + +/* The following function deletes the value associated with a +** symbol. The symbol can be either a terminal or nonterminal. +** "yymajor" is the symbol code, and "yypminor" is a pointer to +** the value. +*/ +static void yy_destructor( + yyParser *yypParser, /* The parser */ + YYCODETYPE yymajor, /* Type code for object to destroy */ + YYMINORTYPE *yypminor /* The object to be destroyed */ +){ + sqlite2_parseARG_FETCH; + if (parserContext->executeRules) + { + switch( yymajor ){ + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are not used + ** inside the C code. + */ + case 148: /* cmd */ + case 151: /* ecmd */ + case 153: /* cmdx */ + case 191: /* select_stmt */ + case 214: /* delete_stmt */ + case 215: /* update_stmt */ + case 217: /* insert_stmt */ + case 237: /* trigger_cmd */ +{ +delete (yypminor->yy203); +} + break; + case 152: /* explain */ +{ +delete (yypminor->yy91); +} + break; + case 154: /* trans_opt */ +{ +delete (yypminor->yy404); +} + break; + case 155: /* onconf */ + case 189: /* resolvetype */ + case 190: /* orconf */ +{ +delete (yypminor->yy418); +} + break; + case 156: /* nm */ + case 163: /* columnid */ + case 166: /* id */ + case 167: /* ids */ + case 169: /* typename */ + case 209: /* dbnm */ +{ +delete (yypminor->yy319); +} + break; + case 157: /* temp */ + case 194: /* distinct */ +{ +delete (yypminor->yy226); +} + break; + case 158: /* fullname */ +{ +delete (yypminor->yy120); +} + break; + case 159: /* columnlist */ +{ +delete (yypminor->yy42); +} + break; + case 160: /* conslist_opt */ + case 184: /* conslist */ +{ +delete (yypminor->yy13); +} + break; + case 161: /* select */ +{ +delete (yypminor->yy153); +} + break; + case 162: /* column */ +{ +delete (yypminor->yy147); +} + break; + case 164: /* type */ + case 168: /* typetoken */ +{ +delete (yypminor->yy57); +} + break; + case 165: /* carglist */ +{ +delete (yypminor->yy371); +} + break; + case 170: /* signed */ + case 171: /* plus_num */ + case 172: /* minus_num */ + case 230: /* nmnum */ + case 231: /* number */ +{ +delete (yypminor->yy69); +} + break; + case 173: /* ccons */ + case 174: /* ccons_nm */ + case 175: /* carg */ +{ +delete (yypminor->yy304); +} + break; + case 176: /* sortorder */ +{ +delete (yypminor->yy389); +} + break; + case 177: /* expr */ + case 197: /* where_opt */ + case 199: /* having_opt */ + case 221: /* exprx */ + case 224: /* case_operand */ + case 226: /* case_else */ +{ +delete (yypminor->yy192); +} + break; + case 178: /* idxlist_opt */ + case 187: /* idxlist */ +{ +delete (yypminor->yy63); +} + break; + case 179: /* refargs */ +{ +delete (yypminor->yy264); +} + break; + case 180: /* defer_subclause */ + case 188: /* defer_subclause_opt */ +{ +delete (yypminor->yy329); +} + break; + case 181: /* refarg */ +{ +delete (yypminor->yy187); +} + break; + case 182: /* refact */ +{ +delete (yypminor->yy424); +} + break; + case 183: /* init_deferred_pred_opt */ +{ +delete (yypminor->yy312); +} + break; + case 185: /* tconscomma */ + case 222: /* not_opt */ + case 228: /* uniqueflag */ + case 238: /* database_kw_opt */ +{ +delete (yypminor->yy291); +} + break; + case 186: /* tcons */ +{ +delete (yypminor->yy406); +} + break; + case 192: /* oneselect */ +{ +delete (yypminor->yy150); +} + break; + case 193: /* multiselect_op */ +{ +delete (yypminor->yy382); +} + break; + case 195: /* selcollist */ + case 202: /* sclp */ +{ +delete (yypminor->yy213); +} + break; + case 196: /* from */ + case 204: /* joinsrc */ +{ +delete (yypminor->yy31); +} + break; + case 198: /* groupby_opt */ + case 213: /* nexprlist */ + case 220: /* exprlist */ + case 225: /* case_exprlist */ +{ +delete (yypminor->yy231); +} + break; + case 200: /* orderby_opt */ + case 211: /* sortlist */ +{ +delete (yypminor->yy243); +} + break; + case 201: /* limit_opt */ +{ +delete (yypminor->yy324); +} + break; + case 203: /* as */ +{ +delete (yypminor->yy40); +} + break; + case 205: /* singlesrc */ +{ +delete (yypminor->yy121); +} + break; + case 206: /* seltablist */ +{ +delete (yypminor->yy131); +} + break; + case 207: /* joinop */ +{ +delete (yypminor->yy221); +} + break; + case 208: /* joinconstr_opt */ +{ +delete (yypminor->yy455); +} + break; + case 210: /* inscollist */ + case 219: /* inscollist_opt */ +{ +delete (yypminor->yy207); +} + break; + case 212: /* collate */ +{ +if ((yypminor->yy319)) delete (yypminor->yy319); +} + break; + case 216: /* setlist */ +{ +delete (yypminor->yy201); +} + break; + case 218: /* insert_cmd */ +{ +delete (yypminor->yy344); +} + break; + case 223: /* likeop */ +{ +delete (yypminor->yy41); +} + break; + case 229: /* idxlist_single */ +{ +delete (yypminor->yy428); +} + break; + case 232: /* trigger_time */ +{ +delete (yypminor->yy372); +} + break; + case 233: /* trigger_event */ +{ +delete (yypminor->yy151); +} + break; + case 234: /* foreach_clause */ +{ +delete (yypminor->yy83); +} + break; + case 235: /* when_clause */ + case 239: /* key_opt */ +{ +if ((yypminor->yy192)) delete (yypminor->yy192); +} + break; + case 236: /* trigger_cmd_list */ +{ +delete (yypminor->yy270); +} + break; + default: break; /* If no destructor action specified: do nothing */ + } + } +} + +/* +** Pop the parser's stack once. +** +** If there is a destructor routine associated with the token which +** is popped from the stack, then call it. +** +** Return the major token number for the symbol popped. +*/ +static int yy_pop_parser_stack(yyParser *pParser){ + YYCODETYPE yymajor; + yyStackEntry *yytos = &pParser->yystack[pParser->yyidx]; + + /* There is no mechanism by which the parser stack can be popped below + ** empty in SQLite. */ + if( pParser->yyidx<0 ) return 0; +#ifndef NDEBUG + if( yyTraceFILE && pParser->yyidx>=0 ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + yymajor = yytos->major; + yy_destructor(pParser, yymajor, &yytos->minor); + delete yytos->tokens; + yytos->tokens = nullptr; + pParser->yyidx--; + return yymajor; +} + +/* +** Deallocate and destroy a parser. Destructors are all called for +** all stack elements before shutting the parser down. +** +** Inputs: +** <ul> +** <li> A pointer to the parser. This should be a pointer +** obtained from sqlite2_parseAlloc. +** <li> A pointer to a function used to reclaim memory obtained +** from malloc. +** </ul> +*/ +void sqlite2_parseFree( + void *p, /* The parser to be deleted */ + void (*freeProc)(void*) /* Function used to reclaim memory */ +){ + yyParser *pParser = (yyParser*)p; + /* In SQLite, we never try to destroy a parser that was not successfully + ** created in the first place. */ + if( pParser==0 ) return; + while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser); +#if YYSTACKDEPTH<=0 + free(pParser->yystack); +#endif + (*freeProc)((void*)pParser); +} + +/* +** Return the peak depth of the stack for a parser. +*/ +#ifdef YYTRACKMAXSTACKDEPTH +int sqlite2_parseStackPeak(void *p){ + yyParser *pParser = (yyParser*)p; + return pParser->yyidxMax; +} +#endif + +/* +** Find the appropriate action for a parser given the terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_shift_action( + yyParser *pParser, /* The parser */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; + int stateno = pParser->yystack[pParser->yyidx].stateno; + GET_CONTEXT; + + if( stateno>YY_SHIFT_COUNT + || (i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT ){ + return yy_default[stateno]; + } + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + if( iLookAhead>0 ){ +#ifdef YYFALLBACK + YYCODETYPE iFallback; /* Fallback token */ + if( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0]) + && (iFallback = yyFallback[iLookAhead])!=0 + && parserContext->doFallbacks ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } +#endif + return yy_find_shift_action(pParser, iFallback); + } +#endif +#ifdef YYWILDCARD + { + int j = i - iLookAhead + YYWILDCARD; + if( +#if YY_SHIFT_MIN+YYWILDCARD<0 + j>=0 && +#endif +#if YY_SHIFT_MAX+YYWILDCARD>=YY_ACTTAB_COUNT + j<YY_ACTTAB_COUNT && +#endif + yy_lookahead[j]==YYWILDCARD + ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[YYWILDCARD]); + } +#endif /* NDEBUG */ + return yy_action[j]; + } + } +#endif /* YYWILDCARD */ + } + return yy_default[stateno]; + }else{ + return yy_action[i]; + } +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_reduce_action( + int stateno, /* Current state number */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; +#ifdef YYERRORSYMBOL + if( stateno>YY_REDUCE_COUNT ){ + return yy_default[stateno]; + } +#else + assert( stateno<=YY_REDUCE_COUNT ); +#endif + i = yy_reduce_ofst[stateno]; + assert( i!=YY_REDUCE_USE_DFLT ); + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; +#ifdef YYERRORSYMBOL + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + return yy_default[stateno]; + } +#else + assert( i>=0 && i<YY_ACTTAB_COUNT ); + assert( yy_lookahead[i]==iLookAhead ); +#endif + return yy_action[i]; +} + +/* +** The following routine is called if the stack overflows. +*/ +static void yyStackOverflow(yyParser *yypParser, YYMINORTYPE *yypMinor){ + sqlite2_parseARG_FETCH; + yypParser->yyidx--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ + + UNUSED_PARAMETER(yypMinor); + parserContext->error(QObject::tr("Parser stack overflow")); + sqlite2_parseARG_STORE; /* Suppress warning about unused %extra_argument var */ +} + +/* +** Perform a shift action. +*/ +static void yy_shift( + yyParser *yypParser, /* The parser to be shifted */ + int yyNewState, /* The new state to shift in */ + int yyMajor, /* The major token to shift in */ + YYMINORTYPE *yypMinor /* Pointer to the minor token to shift in */ +){ + yyStackEntry *yytos; + yypParser->yyidx++; +#ifdef YYTRACKMAXSTACKDEPTH + if( yypParser->yyidx>yypParser->yyidxMax ){ + yypParser->yyidxMax = yypParser->yyidx; + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yyidx>=YYSTACKDEPTH ){ + yyStackOverflow(yypParser, yypMinor); + return; + } +#else + if( yypParser->yyidx>=yypParser->yystksz ){ + yyGrowStack(yypParser); + if( yypParser->yyidx>=yypParser->yystksz ){ + yyStackOverflow(yypParser, yypMinor); + return; + } + } +#endif + yytos = &yypParser->yystack[yypParser->yyidx]; + yytos->stateno = (YYACTIONTYPE)yyNewState; + yytos->major = (YYCODETYPE)yyMajor; + yytos->minor = *yypMinor; + yytos->tokens = new QList<Token*>(); +#ifndef NDEBUG + if( yyTraceFILE && yypParser->yyidx>0 ){ + int i; + fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState); + fprintf(yyTraceFILE,"%sStack:",yyTracePrompt); + for(i=1; i<=yypParser->yyidx; i++) + fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]); + fprintf(yyTraceFILE,"\n"); + } +#endif +} + +/* The following table contains information about every rule that +** is used during the reduce. +*/ +static const struct { + YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ + unsigned char nrhs; /* Number of right-hand side symbols in the rule */ +} yyRuleInfo[] = { + { 149, 1 }, + { 150, 2 }, + { 150, 1 }, + { 151, 1 }, + { 151, 3 }, + { 152, 0 }, + { 152, 1 }, + { 153, 1 }, + { 148, 3 }, + { 154, 0 }, + { 154, 1 }, + { 154, 2 }, + { 154, 2 }, + { 148, 2 }, + { 148, 2 }, + { 148, 2 }, + { 148, 8 }, + { 148, 6 }, + { 148, 6 }, + { 148, 4 }, + { 157, 1 }, + { 157, 0 }, + { 159, 3 }, + { 159, 1 }, + { 162, 3 }, + { 163, 1 }, + { 163, 1 }, + { 166, 1 }, + { 167, 1 }, + { 156, 1 }, + { 156, 1 }, + { 156, 1 }, + { 164, 0 }, + { 164, 1 }, + { 168, 1 }, + { 168, 4 }, + { 168, 6 }, + { 169, 1 }, + { 169, 2 }, + { 169, 1 }, + { 170, 1 }, + { 170, 1 }, + { 165, 2 }, + { 165, 3 }, + { 165, 2 }, + { 165, 0 }, + { 175, 2 }, + { 175, 2 }, + { 175, 2 }, + { 175, 3 }, + { 175, 3 }, + { 175, 2 }, + { 175, 3 }, + { 175, 3 }, + { 175, 2 }, + { 174, 2 }, + { 173, 2 }, + { 173, 3 }, + { 173, 4 }, + { 173, 2 }, + { 173, 5 }, + { 173, 4 }, + { 173, 1 }, + { 173, 2 }, + { 173, 3 }, + { 179, 0 }, + { 179, 2 }, + { 181, 2 }, + { 181, 3 }, + { 181, 3 }, + { 181, 3 }, + { 181, 2 }, + { 182, 2 }, + { 182, 2 }, + { 182, 1 }, + { 182, 1 }, + { 180, 3 }, + { 180, 2 }, + { 183, 0 }, + { 183, 2 }, + { 183, 2 }, + { 160, 0 }, + { 160, 2 }, + { 184, 3 }, + { 184, 1 }, + { 185, 1 }, + { 185, 0 }, + { 186, 2 }, + { 186, 6 }, + { 186, 5 }, + { 186, 5 }, + { 186, 10 }, + { 186, 2 }, + { 186, 7 }, + { 186, 4 }, + { 188, 0 }, + { 188, 1 }, + { 155, 0 }, + { 155, 3 }, + { 190, 0 }, + { 190, 2 }, + { 189, 1 }, + { 189, 1 }, + { 189, 1 }, + { 189, 1 }, + { 189, 1 }, + { 148, 3 }, + { 148, 5 }, + { 148, 3 }, + { 148, 6 }, + { 148, 4 }, + { 148, 3 }, + { 148, 3 }, + { 148, 1 }, + { 191, 1 }, + { 161, 1 }, + { 161, 3 }, + { 193, 1 }, + { 193, 2 }, + { 193, 1 }, + { 193, 1 }, + { 192, 9 }, + { 194, 1 }, + { 194, 1 }, + { 194, 0 }, + { 202, 2 }, + { 202, 0 }, + { 195, 3 }, + { 195, 2 }, + { 195, 4 }, + { 195, 1 }, + { 195, 4 }, + { 203, 2 }, + { 203, 1 }, + { 203, 2 }, + { 203, 1 }, + { 203, 0 }, + { 196, 0 }, + { 196, 2 }, + { 204, 2 }, + { 204, 0 }, + { 206, 4 }, + { 206, 0 }, + { 205, 3 }, + { 205, 4 }, + { 205, 4 }, + { 205, 0 }, + { 205, 2 }, + { 205, 3 }, + { 205, 1 }, + { 205, 3 }, + { 205, 1 }, + { 208, 2 }, + { 208, 4 }, + { 208, 0 }, + { 209, 0 }, + { 209, 2 }, + { 158, 2 }, + { 207, 1 }, + { 207, 1 }, + { 207, 2 }, + { 207, 3 }, + { 207, 4 }, + { 207, 1 }, + { 200, 0 }, + { 200, 3 }, + { 211, 5 }, + { 211, 3 }, + { 212, 0 }, + { 212, 2 }, + { 176, 1 }, + { 176, 1 }, + { 176, 0 }, + { 198, 0 }, + { 198, 3 }, + { 198, 2 }, + { 199, 0 }, + { 199, 2 }, + { 201, 0 }, + { 201, 2 }, + { 201, 4 }, + { 201, 4 }, + { 148, 1 }, + { 214, 4 }, + { 214, 2 }, + { 214, 4 }, + { 214, 5 }, + { 214, 3 }, + { 197, 0 }, + { 197, 2 }, + { 197, 1 }, + { 148, 1 }, + { 215, 6 }, + { 215, 2 }, + { 215, 4 }, + { 215, 5 }, + { 215, 3 }, + { 216, 5 }, + { 216, 3 }, + { 216, 0 }, + { 216, 2 }, + { 216, 3 }, + { 216, 1 }, + { 148, 1 }, + { 217, 8 }, + { 217, 5 }, + { 217, 2 }, + { 217, 4 }, + { 217, 3 }, + { 217, 5 }, + { 218, 2 }, + { 218, 1 }, + { 219, 0 }, + { 219, 3 }, + { 210, 3 }, + { 210, 1 }, + { 210, 0 }, + { 210, 3 }, + { 210, 1 }, + { 221, 1 }, + { 221, 1 }, + { 221, 1 }, + { 221, 1 }, + { 221, 3 }, + { 221, 1 }, + { 221, 1 }, + { 221, 3 }, + { 221, 5 }, + { 221, 1 }, + { 221, 4 }, + { 221, 4 }, + { 221, 3 }, + { 221, 3 }, + { 221, 3 }, + { 221, 3 }, + { 221, 3 }, + { 221, 3 }, + { 221, 3 }, + { 221, 3 }, + { 221, 4 }, + { 221, 2 }, + { 221, 3 }, + { 221, 4 }, + { 221, 2 }, + { 221, 2 }, + { 221, 2 }, + { 221, 2 }, + { 221, 6 }, + { 221, 6 }, + { 221, 6 }, + { 221, 5 }, + { 221, 3 }, + { 221, 5 }, + { 221, 6 }, + { 221, 4 }, + { 221, 2 }, + { 221, 4 }, + { 221, 4 }, + { 221, 4 }, + { 221, 5 }, + { 221, 4 }, + { 221, 6 }, + { 221, 1 }, + { 221, 3 }, + { 221, 5 }, + { 221, 6 }, + { 177, 1 }, + { 177, 0 }, + { 222, 0 }, + { 222, 1 }, + { 223, 1 }, + { 225, 5 }, + { 225, 4 }, + { 226, 2 }, + { 226, 0 }, + { 224, 1 }, + { 224, 0 }, + { 220, 1 }, + { 220, 0 }, + { 213, 3 }, + { 213, 1 }, + { 148, 11 }, + { 148, 7 }, + { 148, 6 }, + { 148, 4 }, + { 228, 1 }, + { 228, 0 }, + { 178, 0 }, + { 178, 3 }, + { 187, 3 }, + { 187, 1 }, + { 229, 2 }, + { 229, 1 }, + { 148, 3 }, + { 148, 5 }, + { 148, 3 }, + { 148, 9 }, + { 148, 6 }, + { 148, 1 }, + { 148, 2 }, + { 148, 2 }, + { 148, 4 }, + { 148, 5 }, + { 148, 4 }, + { 148, 5 }, + { 148, 4 }, + { 148, 2 }, + { 230, 1 }, + { 230, 1 }, + { 230, 1 }, + { 230, 1 }, + { 230, 1 }, + { 171, 2 }, + { 171, 1 }, + { 172, 2 }, + { 231, 1 }, + { 231, 1 }, + { 148, 14 }, + { 148, 11 }, + { 148, 13 }, + { 148, 8 }, + { 148, 10 }, + { 148, 4 }, + { 232, 1 }, + { 232, 1 }, + { 232, 2 }, + { 232, 0 }, + { 233, 1 }, + { 233, 1 }, + { 233, 1 }, + { 233, 3 }, + { 234, 0 }, + { 234, 3 }, + { 234, 3 }, + { 235, 0 }, + { 235, 2 }, + { 236, 3 }, + { 236, 2 }, + { 237, 1 }, + { 237, 1 }, + { 237, 1 }, + { 237, 1 }, + { 227, 1 }, + { 148, 3 }, + { 148, 5 }, + { 148, 3 }, + { 148, 6 }, + { 239, 0 }, + { 239, 2 }, + { 238, 1 }, + { 238, 0 }, + { 148, 3 }, +}; + +static void yy_accept(yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +*/ +static void yy_reduce( + yyParser *yypParser, /* The parser */ + int yyruleno /* Number of the rule by which to reduce */ +){ + int yygoto; /* The next state */ + int yyact; /* The next action */ + YYMINORTYPE yygotominor; /* The LHS of the rule reduced */ + yyStackEntry *yymsp; /* The top of the parser's stack */ + int yysize; /* Amount to pop the stack */ + sqlite2_parseARG_FETCH; + SqliteStatement* objectForTokens = 0; + QStringList noTokenInheritanceFields; + yymsp = &yypParser->yystack[yypParser->yyidx]; +#ifndef NDEBUG + if( yyTraceFILE && yyruleno>=0 + && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){ + fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt, + yyRuleName[yyruleno]); + } +#endif /* NDEBUG */ + + /* Silence complaints from purify about yygotominor being uninitialized + ** in some cases when it is copied into the stack after the following + ** switch. yygotominor is uninitialized when a rule reduces that does + ** not set the value of its left-hand side nonterminal. Leaving the + ** value of the nonterminal uninitialized is utterly harmless as long + ** as the value is never used. So really the only thing this code + ** accomplishes is to quieten purify. + ** + ** 2007-01-16: The wireshark project (www.wireshark.org) reports that + ** without this code, their parser segfaults. I'm not sure what there + ** parser is doing to make this happen. This is the second bug report + ** from wireshark this week. Clearly they are stressing Lemon in ways + ** that it has not been previously stressed... (SQLite ticket #2172) + */ + /*memset(&yygotominor, 0, sizeof(yygotominor));*/ + yygotominor = yyzerominor; + + + if (parserContext->executeRules) + { + switch( yyruleno ){ + /* Beginning here are the reduction cases. A typical example + ** follows: + ** case 0: + ** #line <lineno> <grammarfile> + ** { ... } // User supplied code + ** #line <lineno> <thisfile> + ** break; + */ + case 1: /* cmdlist ::= cmdlist ecmd */ +{parserContext->addQuery(yymsp[0].minor.yy203); DONT_INHERIT_TOKENS("cmdlist");} + break; + case 2: /* cmdlist ::= ecmd */ +{parserContext->addQuery(yymsp[0].minor.yy203);} + break; + case 3: /* ecmd ::= SEMI */ +{yygotominor.yy203 = new SqliteEmptyQuery();} + break; + case 4: /* ecmd ::= explain cmdx SEMI */ +{ + yygotominor.yy203 = yymsp[-1].minor.yy203; + yygotominor.yy203->explain = yymsp[-2].minor.yy91->explain; + yygotominor.yy203->queryPlan = yymsp[-2].minor.yy91->queryPlan; + delete yymsp[-2].minor.yy91; + objectForTokens = yygotominor.yy203; + } + break; + case 5: /* explain ::= */ +{yygotominor.yy91 = new ParserStubExplain(false, false);} + break; + case 6: /* explain ::= EXPLAIN */ +{yygotominor.yy91 = new ParserStubExplain(true, false);} + break; + case 7: /* cmdx ::= cmd */ + case 338: /* trigger_cmd ::= update_stmt */ yytestcase(yyruleno==338); + case 339: /* trigger_cmd ::= insert_stmt */ yytestcase(yyruleno==339); + case 340: /* trigger_cmd ::= delete_stmt */ yytestcase(yyruleno==340); + case 341: /* trigger_cmd ::= select_stmt */ yytestcase(yyruleno==341); +{yygotominor.yy203 = yymsp[0].minor.yy203;} + break; + case 8: /* cmd ::= BEGIN trans_opt onconf */ +{ + yygotominor.yy203 = new SqliteBeginTrans( + yymsp[-1].minor.yy404->transactionKw, + yymsp[-1].minor.yy404->name, + *(yymsp[0].minor.yy418) + ); + delete yymsp[-1].minor.yy404; + delete yymsp[0].minor.yy418; + objectForTokens = yygotominor.yy203; + } + break; + case 9: /* trans_opt ::= */ +{yygotominor.yy404 = new ParserStubTransDetails();} + break; + case 10: /* trans_opt ::= TRANSACTION */ +{ + yygotominor.yy404 = new ParserStubTransDetails(); + yygotominor.yy404->transactionKw = true; + } + break; + case 11: /* trans_opt ::= TRANSACTION nm */ + case 12: /* trans_opt ::= TRANSACTION ID_TRANS */ yytestcase(yyruleno==12); +{ + yygotominor.yy404 = new ParserStubTransDetails(); + yygotominor.yy404->transactionKw = true; + yygotominor.yy404->name = *(yymsp[0].minor.yy319); + delete yymsp[0].minor.yy319; + } + break; + case 13: /* cmd ::= COMMIT trans_opt */ +{ + yygotominor.yy203 = new SqliteCommitTrans( + yymsp[0].minor.yy404->transactionKw, + yymsp[0].minor.yy404->name, + false + ); + delete yymsp[0].minor.yy404; + objectForTokens = yygotominor.yy203; + } + break; + case 14: /* cmd ::= END trans_opt */ +{ + yygotominor.yy203 = new SqliteCommitTrans( + yymsp[0].minor.yy404->transactionKw, + yymsp[0].minor.yy404->name, + true + ); + delete yymsp[0].minor.yy404; + objectForTokens = yygotominor.yy203; + } + break; + case 15: /* cmd ::= ROLLBACK trans_opt */ +{ + yygotominor.yy203 = new SqliteRollback( + yymsp[0].minor.yy404->transactionKw, + yymsp[0].minor.yy404->name + ); + delete yymsp[0].minor.yy404; + objectForTokens = yygotominor.yy203; + } + break; + case 16: /* cmd ::= CREATE temp TABLE fullname LP columnlist conslist_opt RP */ +{ + yygotominor.yy203 = new SqliteCreateTable( + *(yymsp[-6].minor.yy226), + false, + yymsp[-4].minor.yy120->name1, + yymsp[-4].minor.yy120->name2, + *(yymsp[-2].minor.yy42), + *(yymsp[-1].minor.yy13) + ); + delete yymsp[-6].minor.yy226; + delete yymsp[-2].minor.yy42; + delete yymsp[-1].minor.yy13; + delete yymsp[-4].minor.yy120; + objectForTokens = yygotominor.yy203; + } + break; + case 17: /* cmd ::= CREATE temp TABLE fullname AS select */ +{ + yygotominor.yy203 = new SqliteCreateTable( + *(yymsp[-4].minor.yy226), + false, + yymsp[-2].minor.yy120->name1, + yymsp[-2].minor.yy120->name2, + yymsp[0].minor.yy153 + ); + delete yymsp[-4].minor.yy226; + delete yymsp[-2].minor.yy120; + objectForTokens = yygotominor.yy203; + } + break; + case 18: /* cmd ::= CREATE temp TABLE nm DOT ID_TAB_NEW */ +{ yy_destructor(yypParser,157,&yymsp[-4].minor); + yy_destructor(yypParser,156,&yymsp[-2].minor); +} + break; + case 19: /* cmd ::= CREATE temp TABLE ID_DB|ID_TAB_NEW */ + case 110: /* cmd ::= CREATE temp VIEW ID_VIEW_NEW */ yytestcase(yyruleno==110); + case 322: /* cmd ::= CREATE temp TRIGGER ID_TRIG_NEW */ yytestcase(yyruleno==322); +{ yy_destructor(yypParser,157,&yymsp[-2].minor); +} + break; + case 20: /* temp ::= TEMP */ +{yygotominor.yy226 = new int( (yymsp[0].minor.yy0->value.length() > 4) ? 2 : 1 );} + break; + case 21: /* temp ::= */ + case 124: /* distinct ::= */ yytestcase(yyruleno==124); +{yygotominor.yy226 = new int(0);} + break; + case 22: /* columnlist ::= columnlist COMMA column */ +{ + yymsp[-2].minor.yy42->append(yymsp[0].minor.yy147); + yygotominor.yy42 = yymsp[-2].minor.yy42; + DONT_INHERIT_TOKENS("columnlist"); + } + break; + case 23: /* columnlist ::= column */ +{ + yygotominor.yy42 = new ParserCreateTableColumnList(); + yygotominor.yy42->append(yymsp[0].minor.yy147); + } + break; + case 24: /* column ::= columnid type carglist */ +{ + yygotominor.yy147 = new SqliteCreateTable::Column(*(yymsp[-2].minor.yy319), yymsp[-1].minor.yy57, *(yymsp[0].minor.yy371)); + delete yymsp[-2].minor.yy319; + delete yymsp[0].minor.yy371; + objectForTokens = yygotominor.yy147; + } + break; + case 25: /* columnid ::= nm */ + case 26: /* columnid ::= ID_COL_NEW */ yytestcase(yyruleno==26); + case 29: /* nm ::= id */ yytestcase(yyruleno==29); + case 37: /* typename ::= ids */ yytestcase(yyruleno==37); + case 156: /* dbnm ::= DOT nm */ yytestcase(yyruleno==156); + case 169: /* collate ::= COLLATE id */ yytestcase(yyruleno==169); +{yygotominor.yy319 = yymsp[0].minor.yy319;} + break; + case 27: /* id ::= ID */ +{ + yygotominor.yy319 = new QString( + stripObjName( + yymsp[0].minor.yy0->value, + parserContext->dialect + ) + ); + } + break; + case 28: /* ids ::= ID|STRING */ + case 31: /* nm ::= JOIN_KW */ yytestcase(yyruleno==31); +{yygotominor.yy319 = new QString(yymsp[0].minor.yy0->value);} + break; + case 30: /* nm ::= STRING */ +{yygotominor.yy319 = new QString(stripString(yymsp[0].minor.yy0->value));} + break; + case 32: /* type ::= */ +{yygotominor.yy57 = nullptr;} + break; + case 33: /* type ::= typetoken */ +{yygotominor.yy57 = yymsp[0].minor.yy57;} + break; + case 34: /* typetoken ::= typename */ +{ + yygotominor.yy57 = new SqliteColumnType(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy57; + } + break; + case 35: /* typetoken ::= typename LP signed RP */ +{ + yygotominor.yy57 = new SqliteColumnType(*(yymsp[-3].minor.yy319), *(yymsp[-1].minor.yy69)); + delete yymsp[-3].minor.yy319; + delete yymsp[-1].minor.yy69; + objectForTokens = yygotominor.yy57; + } + break; + case 36: /* typetoken ::= typename LP signed COMMA signed RP */ +{ + yygotominor.yy57 = new SqliteColumnType(*(yymsp[-5].minor.yy319), *(yymsp[-3].minor.yy69), *(yymsp[-1].minor.yy69)); + delete yymsp[-5].minor.yy319; + delete yymsp[-3].minor.yy69; + delete yymsp[-1].minor.yy69; + objectForTokens = yygotominor.yy57; + } + break; + case 38: /* typename ::= typename ids */ + case 39: /* typename ::= ID_COL_TYPE */ yytestcase(yyruleno==39); +{ + yymsp[-1].minor.yy319->append(" " + *(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + yygotominor.yy319 = yymsp[-1].minor.yy319; + } + break; + case 40: /* signed ::= plus_num */ + case 41: /* signed ::= minus_num */ yytestcase(yyruleno==41); + case 307: /* nmnum ::= plus_num */ yytestcase(yyruleno==307); + case 312: /* plus_num ::= PLUS number */ yytestcase(yyruleno==312); + case 313: /* plus_num ::= number */ yytestcase(yyruleno==313); +{yygotominor.yy69 = yymsp[0].minor.yy69;} + break; + case 42: /* carglist ::= carglist ccons */ + case 44: /* carglist ::= carglist carg */ yytestcase(yyruleno==44); +{ + yymsp[-1].minor.yy371->append(yymsp[0].minor.yy304); + yygotominor.yy371 = yymsp[-1].minor.yy371; + DONT_INHERIT_TOKENS("carglist"); + } + break; + case 43: /* carglist ::= carglist ccons_nm ccons */ +{ + yymsp[-2].minor.yy371->append(yymsp[-1].minor.yy304); + yymsp[-2].minor.yy371->append(yymsp[0].minor.yy304); + yygotominor.yy371 = yymsp[-2].minor.yy371; + DONT_INHERIT_TOKENS("carglist"); + } + break; + case 45: /* carglist ::= */ +{yygotominor.yy371 = new ParserCreateTableColumnConstraintList();} + break; + case 46: /* carg ::= DEFAULT STRING */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initDefId(stripObjName( + yymsp[0].minor.yy0->value, + parserContext->dialect + )); + objectForTokens = yygotominor.yy304; + } + break; + case 47: /* carg ::= DEFAULT ID */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initDefId(stripObjName( + yymsp[0].minor.yy0->value, + parserContext->dialect + )); + objectForTokens = yygotominor.yy304; + + } + break; + case 48: /* carg ::= DEFAULT INTEGER */ + case 49: /* carg ::= DEFAULT PLUS INTEGER */ yytestcase(yyruleno==49); +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(yymsp[0].minor.yy0->value).toLongLong(); + yygotominor.yy304->initDefTerm(val, false); + objectForTokens = yygotominor.yy304; + } + break; + case 50: /* carg ::= DEFAULT MINUS INTEGER */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(yymsp[0].minor.yy0->value).toLongLong(); + yygotominor.yy304->initDefTerm(val, true); + objectForTokens = yygotominor.yy304; + } + break; + case 51: /* carg ::= DEFAULT FLOAT */ + case 52: /* carg ::= DEFAULT PLUS FLOAT */ yytestcase(yyruleno==52); +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(yymsp[0].minor.yy0->value).toDouble(); + yygotominor.yy304->initDefTerm(val, false); + objectForTokens = yygotominor.yy304; + } + break; + case 53: /* carg ::= DEFAULT MINUS FLOAT */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(yymsp[0].minor.yy0->value).toDouble(); + yygotominor.yy304->initDefTerm(val, true); + objectForTokens = yygotominor.yy304; + } + break; + case 54: /* carg ::= DEFAULT NULL */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initDefTerm(QVariant(), false); + objectForTokens = yygotominor.yy304; + } + break; + case 55: /* ccons_nm ::= CONSTRAINT nm */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initDefNameOnly(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy304; + } + break; + case 56: /* ccons ::= NULL onconf */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initNull(*(yymsp[0].minor.yy418)); + delete yymsp[0].minor.yy418; + objectForTokens = yygotominor.yy304; + } + break; + case 57: /* ccons ::= NOT NULL onconf */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initNotNull(*(yymsp[0].minor.yy418)); + delete yymsp[0].minor.yy418; + objectForTokens = yygotominor.yy304; + } + break; + case 58: /* ccons ::= PRIMARY KEY sortorder onconf */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initPk(*(yymsp[-1].minor.yy389), *(yymsp[0].minor.yy418), false); + delete yymsp[-1].minor.yy389; + delete yymsp[0].minor.yy418; + objectForTokens = yygotominor.yy304; + } + break; + case 59: /* ccons ::= UNIQUE onconf */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initUnique(*(yymsp[0].minor.yy418)); + delete yymsp[0].minor.yy418; + objectForTokens = yygotominor.yy304; + } + break; + case 60: /* ccons ::= CHECK LP expr RP onconf */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initCheck(yymsp[-2].minor.yy192, *(yymsp[0].minor.yy418)); + delete yymsp[0].minor.yy418; + objectForTokens = yygotominor.yy304; + } + break; + case 61: /* ccons ::= REFERENCES nm idxlist_opt refargs */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initFk(*(yymsp[-2].minor.yy319), *(yymsp[-1].minor.yy63), *(yymsp[0].minor.yy264)); + delete yymsp[-2].minor.yy319; + delete yymsp[0].minor.yy264; + delete yymsp[-1].minor.yy63; + objectForTokens = yygotominor.yy304; + } + break; + case 62: /* ccons ::= defer_subclause */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initDefer(yymsp[0].minor.yy329->initially, yymsp[0].minor.yy329->deferrable); + delete yymsp[0].minor.yy329; + objectForTokens = yygotominor.yy304; + } + break; + case 63: /* ccons ::= COLLATE id */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initColl(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy304; + } + break; + case 64: /* ccons ::= CHECK LP RP */ +{ + yygotominor.yy304 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy304->initCheck(); + objectForTokens = yygotominor.yy304; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 65: /* refargs ::= */ +{yygotominor.yy264 = new ParserFkConditionList();} + break; + case 66: /* refargs ::= refargs refarg */ +{ + yymsp[-1].minor.yy264->append(yymsp[0].minor.yy187); + yygotominor.yy264 = yymsp[-1].minor.yy264; + DONT_INHERIT_TOKENS("refargs"); + } + break; + case 67: /* refarg ::= MATCH nm */ +{ + yygotominor.yy187 = new SqliteForeignKey::Condition(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + } + break; + case 68: /* refarg ::= ON INSERT refact */ +{yygotominor.yy187 = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::INSERT, *(yymsp[0].minor.yy424)); delete yymsp[0].minor.yy424;} + break; + case 69: /* refarg ::= ON DELETE refact */ +{yygotominor.yy187 = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::DELETE, *(yymsp[0].minor.yy424)); delete yymsp[0].minor.yy424;} + break; + case 70: /* refarg ::= ON UPDATE refact */ + case 71: /* refarg ::= MATCH ID_FK_MATCH */ yytestcase(yyruleno==71); +{yygotominor.yy187 = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::UPDATE, *(yymsp[0].minor.yy424)); delete yymsp[0].minor.yy424;} + break; + case 72: /* refact ::= SET NULL */ +{yygotominor.yy424 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::SET_NULL);} + break; + case 73: /* refact ::= SET DEFAULT */ +{yygotominor.yy424 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::SET_DEFAULT);} + break; + case 74: /* refact ::= CASCADE */ +{yygotominor.yy424 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::CASCADE);} + break; + case 75: /* refact ::= RESTRICT */ +{yygotominor.yy424 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::RESTRICT);} + break; + case 76: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ +{ + yygotominor.yy329 = new ParserDeferSubClause(SqliteDeferrable::NOT_DEFERRABLE, *(yymsp[0].minor.yy312)); + delete yymsp[0].minor.yy312; + } + break; + case 77: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ +{ + yygotominor.yy329 = new ParserDeferSubClause(SqliteDeferrable::DEFERRABLE, *(yymsp[0].minor.yy312)); + delete yymsp[0].minor.yy312; + } + break; + case 78: /* init_deferred_pred_opt ::= */ +{yygotominor.yy312 = new SqliteInitially(SqliteInitially::null);} + break; + case 79: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */ +{yygotominor.yy312 = new SqliteInitially(SqliteInitially::DEFERRED);} + break; + case 80: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ +{yygotominor.yy312 = new SqliteInitially(SqliteInitially::IMMEDIATE);} + break; + case 81: /* conslist_opt ::= */ +{yygotominor.yy13 = new ParserCreateTableConstraintList();} + break; + case 82: /* conslist_opt ::= COMMA conslist */ +{yygotominor.yy13 = yymsp[0].minor.yy13;} + break; + case 83: /* conslist ::= conslist tconscomma tcons */ +{ + yymsp[0].minor.yy406->afterComma = *(yymsp[-1].minor.yy291); + yymsp[-2].minor.yy13->append(yymsp[0].minor.yy406); + yygotominor.yy13 = yymsp[-2].minor.yy13; + delete yymsp[-1].minor.yy291; + DONT_INHERIT_TOKENS("conslist"); + } + break; + case 84: /* conslist ::= tcons */ +{ + yygotominor.yy13 = new ParserCreateTableConstraintList(); + yygotominor.yy13->append(yymsp[0].minor.yy406); + } + break; + case 85: /* tconscomma ::= COMMA */ + case 269: /* not_opt ::= NOT */ yytestcase(yyruleno==269); + case 285: /* uniqueflag ::= UNIQUE */ yytestcase(yyruleno==285); + case 349: /* database_kw_opt ::= DATABASE */ yytestcase(yyruleno==349); +{yygotominor.yy291 = new bool(true);} + break; + case 86: /* tconscomma ::= */ + case 268: /* not_opt ::= */ yytestcase(yyruleno==268); + case 286: /* uniqueflag ::= */ yytestcase(yyruleno==286); + case 350: /* database_kw_opt ::= */ yytestcase(yyruleno==350); +{yygotominor.yy291 = new bool(false);} + break; + case 87: /* tcons ::= CONSTRAINT nm */ +{ + yygotominor.yy406 = new SqliteCreateTable::Constraint(); + yygotominor.yy406->initNameOnly(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy406; + } + break; + case 88: /* tcons ::= PRIMARY KEY LP idxlist RP onconf */ +{ + yygotominor.yy406 = new SqliteCreateTable::Constraint(); + yygotominor.yy406->initPk(*(yymsp[-2].minor.yy63), false, *(yymsp[0].minor.yy418)); + delete yymsp[0].minor.yy418; + delete yymsp[-2].minor.yy63; + objectForTokens = yygotominor.yy406; + } + break; + case 89: /* tcons ::= UNIQUE LP idxlist RP onconf */ +{ + yygotominor.yy406 = new SqliteCreateTable::Constraint(); + yygotominor.yy406->initUnique(*(yymsp[-2].minor.yy63), *(yymsp[0].minor.yy418)); + delete yymsp[0].minor.yy418; + delete yymsp[-2].minor.yy63; + objectForTokens = yygotominor.yy406; + } + break; + case 90: /* tcons ::= CHECK LP expr RP onconf */ +{ + yygotominor.yy406 = new SqliteCreateTable::Constraint(); + yygotominor.yy406->initCheck(yymsp[-2].minor.yy192, *(yymsp[0].minor.yy418)); + objectForTokens = yygotominor.yy406; + } + break; + case 91: /* tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt */ + case 92: /* tcons ::= CONSTRAINT ID_CONSTR */ yytestcase(yyruleno==92); + case 93: /* tcons ::= FOREIGN KEY LP idxlist RP REFERENCES ID_TAB */ yytestcase(yyruleno==93); +{ + yygotominor.yy406 = new SqliteCreateTable::Constraint(); + yygotominor.yy406->initFk( + *(yymsp[-6].minor.yy63), + *(yymsp[-3].minor.yy319), + *(yymsp[-2].minor.yy63), + *(yymsp[-1].minor.yy264), + yymsp[0].minor.yy329->initially, + yymsp[0].minor.yy329->deferrable + ); + delete yymsp[-3].minor.yy319; + delete yymsp[-1].minor.yy264; + delete yymsp[0].minor.yy329; + delete yymsp[-2].minor.yy63; + delete yymsp[-6].minor.yy63; + objectForTokens = yygotominor.yy406; + } + break; + case 94: /* tcons ::= CHECK LP RP onconf */ +{ + yygotominor.yy406 = new SqliteCreateTable::Constraint(); + yygotominor.yy406->initCheck(); + objectForTokens = yygotominor.yy406; + parserContext->minorErrorAfterLastToken("Syntax error"); + yy_destructor(yypParser,155,&yymsp[0].minor); +} + break; + case 95: /* defer_subclause_opt ::= */ +{yygotominor.yy329 = new ParserDeferSubClause(SqliteDeferrable::null, SqliteInitially::null);} + break; + case 96: /* defer_subclause_opt ::= defer_subclause */ +{yygotominor.yy329 = yymsp[0].minor.yy329;} + break; + case 97: /* onconf ::= */ + case 99: /* orconf ::= */ yytestcase(yyruleno==99); +{yygotominor.yy418 = new SqliteConflictAlgo(SqliteConflictAlgo::null);} + break; + case 98: /* onconf ::= ON CONFLICT resolvetype */ + case 100: /* orconf ::= OR resolvetype */ yytestcase(yyruleno==100); +{yygotominor.yy418 = yymsp[0].minor.yy418;} + break; + case 101: /* resolvetype ::= ROLLBACK */ + case 102: /* resolvetype ::= ABORT */ yytestcase(yyruleno==102); + case 103: /* resolvetype ::= FAIL */ yytestcase(yyruleno==103); + case 104: /* resolvetype ::= IGNORE */ yytestcase(yyruleno==104); + case 105: /* resolvetype ::= REPLACE */ yytestcase(yyruleno==105); +{yygotominor.yy418 = new SqliteConflictAlgo(sqliteConflictAlgo(yymsp[0].minor.yy0->value));} + break; + case 106: /* cmd ::= DROP TABLE fullname */ +{ + yygotominor.yy203 = new SqliteDropTable(false, yymsp[0].minor.yy120->name1, yymsp[0].minor.yy120->name2); + delete yymsp[0].minor.yy120; + objectForTokens = yygotominor.yy203; + } + break; + case 107: /* cmd ::= DROP TABLE nm DOT ID_TAB */ + case 108: /* cmd ::= DROP TABLE ID_DB|ID_TAB */ yytestcase(yyruleno==108); + case 148: /* singlesrc ::= nm DOT ID_TAB */ yytestcase(yyruleno==148); + case 149: /* singlesrc ::= ID_DB|ID_TAB */ yytestcase(yyruleno==149); + case 150: /* singlesrc ::= nm DOT ID_VIEW */ yytestcase(yyruleno==150); + case 151: /* singlesrc ::= ID_DB|ID_VIEW */ yytestcase(yyruleno==151); + case 186: /* delete_stmt ::= DELETE FROM nm DOT ID_TAB */ yytestcase(yyruleno==186); + case 187: /* delete_stmt ::= DELETE FROM ID_DB|ID_TAB */ yytestcase(yyruleno==187); + case 195: /* update_stmt ::= UPDATE orconf nm DOT ID_TAB */ yytestcase(yyruleno==195); + case 196: /* update_stmt ::= UPDATE orconf ID_DB|ID_TAB */ yytestcase(yyruleno==196); + case 263: /* exprx ::= nm DOT ID_TAB|ID_COL */ yytestcase(yyruleno==263); + case 283: /* cmd ::= CREATE uniqueflag INDEX nm DOT ID_IDX_NEW */ yytestcase(yyruleno==283); + case 284: /* cmd ::= CREATE uniqueflag INDEX ID_DB|ID_IDX_NEW */ yytestcase(yyruleno==284); + case 294: /* cmd ::= DROP INDEX nm DOT ID_IDX */ yytestcase(yyruleno==294); + case 295: /* cmd ::= DROP INDEX ID_DB|ID_IDX */ yytestcase(yyruleno==295); + case 305: /* cmd ::= PRAGMA nm DOT ID_PRAGMA */ yytestcase(yyruleno==305); + case 306: /* cmd ::= PRAGMA ID_DB|ID_PRAGMA */ yytestcase(yyruleno==306); + case 344: /* cmd ::= DROP TRIGGER nm DOT ID_TRIG */ yytestcase(yyruleno==344); + case 345: /* cmd ::= DROP TRIGGER ID_DB|ID_TRIG */ yytestcase(yyruleno==345); +{ yy_destructor(yypParser,156,&yymsp[-2].minor); +} + break; + case 109: /* cmd ::= CREATE temp VIEW nm AS select */ +{ + yygotominor.yy203 = new SqliteCreateView(*(yymsp[-4].minor.yy226), false, *(yymsp[-2].minor.yy319), QString::null, yymsp[0].minor.yy153); + delete yymsp[-4].minor.yy226; + delete yymsp[-2].minor.yy319; + objectForTokens = yygotominor.yy203; + } + break; + case 111: /* cmd ::= DROP VIEW nm */ + case 112: /* cmd ::= DROP VIEW ID_VIEW */ yytestcase(yyruleno==112); +{ + yygotominor.yy203 = new SqliteDropView(false, *(yymsp[0].minor.yy319), QString::null); + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy203; + } + break; + case 113: /* cmd ::= select_stmt */ + case 182: /* cmd ::= delete_stmt */ yytestcase(yyruleno==182); + case 191: /* cmd ::= update_stmt */ yytestcase(yyruleno==191); + case 203: /* cmd ::= insert_stmt */ yytestcase(yyruleno==203); +{ + yygotominor.yy203 = yymsp[0].minor.yy203; + objectForTokens = yygotominor.yy203; + } + break; + case 114: /* select_stmt ::= select */ +{ + yygotominor.yy203 = yymsp[0].minor.yy153; + // since it's used in trigger: + objectForTokens = yygotominor.yy203; + } + break; + case 115: /* select ::= oneselect */ +{ + yygotominor.yy153 = SqliteSelect::append(yymsp[0].minor.yy150); + objectForTokens = yygotominor.yy153; + } + break; + case 116: /* select ::= select multiselect_op oneselect */ +{ + yygotominor.yy153 = SqliteSelect::append(yymsp[-2].minor.yy153, *(yymsp[-1].minor.yy382), yymsp[0].minor.yy150); + delete yymsp[-1].minor.yy382; + objectForTokens = yygotominor.yy153; + } + break; + case 117: /* multiselect_op ::= UNION */ +{yygotominor.yy382 = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::UNION);} + break; + case 118: /* multiselect_op ::= UNION ALL */ +{yygotominor.yy382 = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::UNION_ALL);} + break; + case 119: /* multiselect_op ::= EXCEPT */ +{yygotominor.yy382 = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::EXCEPT);} + break; + case 120: /* multiselect_op ::= INTERSECT */ +{yygotominor.yy382 = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::INTERSECT);} + break; + case 121: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ +{ + yygotominor.yy150 = new SqliteSelect::Core( + *(yymsp[-7].minor.yy226), + *(yymsp[-6].minor.yy213), + yymsp[-5].minor.yy31, + yymsp[-4].minor.yy192, + *(yymsp[-3].minor.yy231), + yymsp[-2].minor.yy192, + *(yymsp[-1].minor.yy243), + yymsp[0].minor.yy324 + ); + delete yymsp[-6].minor.yy213; + delete yymsp[-7].minor.yy226; + delete yymsp[-3].minor.yy231; + delete yymsp[-1].minor.yy243; + objectForTokens = yygotominor.yy150; + } + break; + case 122: /* distinct ::= DISTINCT */ +{yygotominor.yy226 = new int(1);} + break; + case 123: /* distinct ::= ALL */ +{yygotominor.yy226 = new int(2);} + break; + case 125: /* sclp ::= selcollist COMMA */ +{yygotominor.yy213 = yymsp[-1].minor.yy213;} + break; + case 126: /* sclp ::= */ +{yygotominor.yy213 = new ParserResultColumnList();} + break; + case 127: /* selcollist ::= sclp expr as */ +{ + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn( + yymsp[-1].minor.yy192, + yymsp[0].minor.yy40 ? yymsp[0].minor.yy40->asKw : false, + yymsp[0].minor.yy40 ? yymsp[0].minor.yy40->name : QString::null + ); + + yymsp[-2].minor.yy213->append(obj); + yygotominor.yy213 = yymsp[-2].minor.yy213; + delete yymsp[0].minor.yy40; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } + break; + case 128: /* selcollist ::= sclp STAR */ +{ + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn(true); + + yymsp[-1].minor.yy213->append(obj); + yygotominor.yy213 = yymsp[-1].minor.yy213; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } + break; + case 129: /* selcollist ::= sclp nm DOT STAR */ +{ + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn( + true, + *(yymsp[-2].minor.yy319) + ); + yymsp[-3].minor.yy213->append(obj); + yygotominor.yy213 = yymsp[-3].minor.yy213; + delete yymsp[-2].minor.yy319; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } + break; + case 130: /* selcollist ::= sclp */ + case 131: /* selcollist ::= sclp ID_TAB DOT STAR */ yytestcase(yyruleno==131); +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy213 = yymsp[0].minor.yy213; + } + break; + case 132: /* as ::= AS nm */ +{ + yygotominor.yy40 = new ParserStubAlias(*(yymsp[0].minor.yy319), true); + delete yymsp[0].minor.yy319; + } + break; + case 133: /* as ::= ids */ + case 134: /* as ::= AS ID_ALIAS */ yytestcase(yyruleno==134); + case 135: /* as ::= ID_ALIAS */ yytestcase(yyruleno==135); +{ + yygotominor.yy40 = new ParserStubAlias(*(yymsp[0].minor.yy319), false); + delete yymsp[0].minor.yy319; + } + break; + case 136: /* as ::= */ +{yygotominor.yy40 = nullptr;} + break; + case 137: /* from ::= */ +{yygotominor.yy31 = nullptr;} + break; + case 138: /* from ::= FROM joinsrc */ +{yygotominor.yy31 = yymsp[0].minor.yy31;} + break; + case 139: /* joinsrc ::= singlesrc seltablist */ +{ + yygotominor.yy31 = new SqliteSelect::Core::JoinSource( + yymsp[-1].minor.yy121, + *(yymsp[0].minor.yy131) + ); + delete yymsp[0].minor.yy131; + objectForTokens = yygotominor.yy31; + } + break; + case 140: /* joinsrc ::= */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy31 = new SqliteSelect::Core::JoinSource(); + objectForTokens = yygotominor.yy31; + } + break; + case 141: /* seltablist ::= seltablist joinop singlesrc joinconstr_opt */ +{ + SqliteSelect::Core::JoinSourceOther* src = + new SqliteSelect::Core::JoinSourceOther(yymsp[-2].minor.yy221, yymsp[-1].minor.yy121, yymsp[0].minor.yy455); + + yymsp[-3].minor.yy131->append(src); + yygotominor.yy131 = yymsp[-3].minor.yy131; + objectForTokens = src; + DONT_INHERIT_TOKENS("seltablist"); + } + break; + case 142: /* seltablist ::= */ +{ + yygotominor.yy131 = new ParserOtherSourceList(); + } + break; + case 143: /* singlesrc ::= nm dbnm as */ +{ + yygotominor.yy121 = new SqliteSelect::Core::SingleSource( + *(yymsp[-2].minor.yy319), + *(yymsp[-1].minor.yy319), + yymsp[0].minor.yy40 ? yymsp[0].minor.yy40->asKw : false, + yymsp[0].minor.yy40 ? yymsp[0].minor.yy40->name : QString::null, + false, + QString::null + ); + delete yymsp[-2].minor.yy319; + delete yymsp[-1].minor.yy319; + delete yymsp[0].minor.yy40; + objectForTokens = yygotominor.yy121; + } + break; + case 144: /* singlesrc ::= LP select RP as */ +{ + yygotominor.yy121 = new SqliteSelect::Core::SingleSource( + yymsp[-2].minor.yy153, + yymsp[0].minor.yy40 ? yymsp[0].minor.yy40->asKw : false, + yymsp[0].minor.yy40 ? yymsp[0].minor.yy40->name : QString::null + ); + delete yymsp[0].minor.yy40; + objectForTokens = yygotominor.yy121; + } + break; + case 145: /* singlesrc ::= LP joinsrc RP as */ +{ + yygotominor.yy121 = new SqliteSelect::Core::SingleSource( + yymsp[-2].minor.yy31, + yymsp[0].minor.yy40 ? yymsp[0].minor.yy40->asKw : false, + yymsp[0].minor.yy40 ? yymsp[0].minor.yy40->name : QString::null + ); + delete yymsp[0].minor.yy40; + objectForTokens = yygotominor.yy121; + } + break; + case 146: /* singlesrc ::= */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy121 = new SqliteSelect::Core::SingleSource(); + objectForTokens = yygotominor.yy121; + } + break; + case 147: /* singlesrc ::= nm DOT */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy121 = new SqliteSelect::Core::SingleSource(); + yygotominor.yy121->database = *(yymsp[-1].minor.yy319); + delete yymsp[-1].minor.yy319; + objectForTokens = yygotominor.yy121; + } + break; + case 152: /* joinconstr_opt ::= ON expr */ +{ + yygotominor.yy455 = new SqliteSelect::Core::JoinConstraint(yymsp[0].minor.yy192); + objectForTokens = yygotominor.yy455; + } + break; + case 153: /* joinconstr_opt ::= USING LP inscollist RP */ +{ + yygotominor.yy455 = new SqliteSelect::Core::JoinConstraint(*(yymsp[-1].minor.yy207)); + delete yymsp[-1].minor.yy207; + objectForTokens = yygotominor.yy455; + } + break; + case 154: /* joinconstr_opt ::= */ +{yygotominor.yy455 = nullptr;} + break; + case 155: /* dbnm ::= */ +{yygotominor.yy319 = new QString();} + break; + case 157: /* fullname ::= nm dbnm */ +{ + yygotominor.yy120 = new ParserFullName(); + yygotominor.yy120->name1 = *(yymsp[-1].minor.yy319); + yygotominor.yy120->name2 = *(yymsp[0].minor.yy319); + delete yymsp[-1].minor.yy319; + delete yymsp[0].minor.yy319; + } + break; + case 158: /* joinop ::= COMMA */ +{ + yygotominor.yy221 = new SqliteSelect::Core::JoinOp(true); + objectForTokens = yygotominor.yy221; + } + break; + case 159: /* joinop ::= JOIN */ +{ + yygotominor.yy221 = new SqliteSelect::Core::JoinOp(false); + objectForTokens = yygotominor.yy221; + } + break; + case 160: /* joinop ::= JOIN_KW JOIN */ +{ + yygotominor.yy221 = new SqliteSelect::Core::JoinOp(yymsp[-1].minor.yy0->value); + objectForTokens = yygotominor.yy221; + } + break; + case 161: /* joinop ::= JOIN_KW nm JOIN */ +{ + yygotominor.yy221 = new SqliteSelect::Core::JoinOp(yymsp[-2].minor.yy0->value, *(yymsp[-1].minor.yy319)); + delete yymsp[-1].minor.yy319; + objectForTokens = yygotominor.yy221; + } + break; + case 162: /* joinop ::= JOIN_KW nm nm JOIN */ + case 163: /* joinop ::= ID_JOIN_OPTS */ yytestcase(yyruleno==163); +{ + yygotominor.yy221 = new SqliteSelect::Core::JoinOp(yymsp[-3].minor.yy0->value, *(yymsp[-2].minor.yy319), *(yymsp[-1].minor.yy319)); + delete yymsp[-2].minor.yy319; + delete yymsp[-2].minor.yy319; + objectForTokens = yygotominor.yy221; + } + break; + case 164: /* orderby_opt ::= */ +{yygotominor.yy243 = new ParserOrderByList();} + break; + case 165: /* orderby_opt ::= ORDER BY sortlist */ +{yygotominor.yy243 = yymsp[0].minor.yy243;} + break; + case 166: /* sortlist ::= sortlist COMMA collate expr sortorder */ +{ + SqliteOrderBy* obj; + if (yymsp[-2].minor.yy319) + { + SqliteExpr* coll = new SqliteExpr(); + coll->initCollate(yymsp[-1].minor.yy192, *(yymsp[-2].minor.yy319)); + delete yymsp[-2].minor.yy319; + obj = new SqliteOrderBy(coll, *(yymsp[0].minor.yy389)); + } + else + { + obj = new SqliteOrderBy(yymsp[-1].minor.yy192, *(yymsp[0].minor.yy389)); + } + yymsp[-4].minor.yy243->append(obj); + yygotominor.yy243 = yymsp[-4].minor.yy243; + delete yymsp[0].minor.yy389; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sortlist"); + } + break; + case 167: /* sortlist ::= expr collate sortorder */ +{ + SqliteOrderBy* obj; + if (yymsp[-1].minor.yy319) + { + SqliteExpr* coll = new SqliteExpr(); + coll->initCollate(yymsp[-2].minor.yy192, *(yymsp[-1].minor.yy319)); + delete yymsp[-1].minor.yy319; + obj = new SqliteOrderBy(coll, *(yymsp[0].minor.yy389)); + } + else + { + obj = new SqliteOrderBy(yymsp[-2].minor.yy192, *(yymsp[0].minor.yy389)); + } + yygotominor.yy243 = new ParserOrderByList(); + yygotominor.yy243->append(obj); + delete yymsp[0].minor.yy389; + objectForTokens = obj; + } + break; + case 168: /* collate ::= */ +{yygotominor.yy319 = nullptr;} + break; + case 170: /* sortorder ::= ASC */ +{yygotominor.yy389 = new SqliteSortOrder(SqliteSortOrder::ASC);} + break; + case 171: /* sortorder ::= DESC */ +{yygotominor.yy389 = new SqliteSortOrder(SqliteSortOrder::DESC);} + break; + case 172: /* sortorder ::= */ +{yygotominor.yy389 = new SqliteSortOrder(SqliteSortOrder::null);} + break; + case 173: /* groupby_opt ::= */ + case 278: /* exprlist ::= */ yytestcase(yyruleno==278); +{yygotominor.yy231 = new ParserExprList();} + break; + case 174: /* groupby_opt ::= GROUP BY nexprlist */ + case 277: /* exprlist ::= nexprlist */ yytestcase(yyruleno==277); +{yygotominor.yy231 = yymsp[0].minor.yy231;} + break; + case 175: /* groupby_opt ::= GROUP BY */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy231 = new ParserExprList(); + } + break; + case 176: /* having_opt ::= */ + case 188: /* where_opt ::= */ yytestcase(yyruleno==188); + case 274: /* case_else ::= */ yytestcase(yyruleno==274); + case 276: /* case_operand ::= */ yytestcase(yyruleno==276); + case 334: /* when_clause ::= */ yytestcase(yyruleno==334); + case 347: /* key_opt ::= */ yytestcase(yyruleno==347); +{yygotominor.yy192 = nullptr;} + break; + case 177: /* having_opt ::= HAVING expr */ + case 189: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==189); + case 266: /* expr ::= exprx */ yytestcase(yyruleno==266); + case 273: /* case_else ::= ELSE expr */ yytestcase(yyruleno==273); + case 275: /* case_operand ::= exprx */ yytestcase(yyruleno==275); + case 335: /* when_clause ::= WHEN expr */ yytestcase(yyruleno==335); +{yygotominor.yy192 = yymsp[0].minor.yy192;} + break; + case 178: /* limit_opt ::= */ +{yygotominor.yy324 = nullptr;} + break; + case 179: /* limit_opt ::= LIMIT signed */ +{ + yygotominor.yy324 = new SqliteLimit(*(yymsp[0].minor.yy69)); + delete yymsp[0].minor.yy69; + objectForTokens = yygotominor.yy324; + } + break; + case 180: /* limit_opt ::= LIMIT signed OFFSET signed */ +{ + SqliteExpr* expr1 = new SqliteExpr(); + expr1->initLiteral(*(yymsp[-2].minor.yy69)); + expr1->setParent(yygotominor.yy324); + + SqliteExpr* expr2 = new SqliteExpr(); + expr1->initLiteral(*(yymsp[0].minor.yy69)); + expr1->setParent(yygotominor.yy324); + + yygotominor.yy324 = new SqliteLimit(expr1, expr2, true); + + TokenPtr limitToken = TokenPtr::create(Token::INTEGER, yymsp[-2].minor.yy69->toString()); + parserContext->addManagedToken(limitToken); + expr1->tokens << limitToken; + expr1->tokensMap["term"] << limitToken; + + TokenPtr offsetToken = TokenPtr::create(Token::INTEGER, yymsp[0].minor.yy69->toString()); + parserContext->addManagedToken(offsetToken); + expr2->tokens << offsetToken; + expr2->tokensMap["term"] << offsetToken; + + delete yymsp[-2].minor.yy69; + delete yymsp[0].minor.yy69; + objectForTokens = yygotominor.yy324; + } + break; + case 181: /* limit_opt ::= LIMIT signed COMMA signed */ +{ + SqliteExpr* expr1 = new SqliteExpr(); + expr1->initLiteral(*(yymsp[-2].minor.yy69)); + expr1->setParent(yygotominor.yy324); + + SqliteExpr* expr2 = new SqliteExpr(); + expr1->initLiteral(*(yymsp[0].minor.yy69)); + expr1->setParent(yygotominor.yy324); + + yygotominor.yy324 = new SqliteLimit(expr1, expr2, false); + + TokenPtr limitToken = TokenPtr::create(Token::INTEGER, yymsp[-2].minor.yy69->toString()); + parserContext->addManagedToken(limitToken); + expr1->tokens << limitToken; + expr1->tokensMap["term"] << limitToken; + + TokenPtr offsetToken = TokenPtr::create(Token::INTEGER, yymsp[0].minor.yy69->toString()); + parserContext->addManagedToken(offsetToken); + expr2->tokens << offsetToken; + expr2->tokensMap["term"] << offsetToken; + + delete yymsp[-2].minor.yy69; + delete yymsp[0].minor.yy69; + objectForTokens = yygotominor.yy324; + } + break; + case 183: /* delete_stmt ::= DELETE FROM fullname where_opt */ +{ + yygotominor.yy203 = new SqliteDelete( + yymsp[-1].minor.yy120->name1, + yymsp[-1].minor.yy120->name2, + false, + yymsp[0].minor.yy192, + nullptr + ); + delete yymsp[-1].minor.yy120; + // since it's used in trigger: + objectForTokens = yygotominor.yy203; + } + break; + case 184: /* delete_stmt ::= DELETE FROM */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteDelete* q = new SqliteDelete(); + yygotominor.yy203 = q; + objectForTokens = yygotominor.yy203; + } + break; + case 185: /* delete_stmt ::= DELETE FROM nm DOT */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteDelete* q = new SqliteDelete(); + q->database = *(yymsp[-1].minor.yy319); + yygotominor.yy203 = q; + objectForTokens = yygotominor.yy203; + delete yymsp[-1].minor.yy319; + } + break; + case 190: /* where_opt ::= WHERE */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy192 = new SqliteExpr(); + } + break; + case 192: /* update_stmt ::= UPDATE orconf fullname SET setlist where_opt */ +{ + yygotominor.yy203 = new SqliteUpdate( + *(yymsp[-4].minor.yy418), + yymsp[-3].minor.yy120->name1, + yymsp[-3].minor.yy120->name2, + false, + QString::null, + *(yymsp[-1].minor.yy201), + yymsp[0].minor.yy192, + nullptr + ); + delete yymsp[-4].minor.yy418; + delete yymsp[-3].minor.yy120; + delete yymsp[-1].minor.yy201; + // since it's used in trigger: + objectForTokens = yygotominor.yy203; + } + break; + case 193: /* update_stmt ::= UPDATE orconf */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy203 = new SqliteUpdate(); + objectForTokens = yygotominor.yy203; + delete yymsp[0].minor.yy418; + } + break; + case 194: /* update_stmt ::= UPDATE orconf nm DOT */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteUpdate* q = new SqliteUpdate(); + q->database = *(yymsp[-1].minor.yy319); + yygotominor.yy203 = q; + objectForTokens = yygotominor.yy203; + delete yymsp[-2].minor.yy418; + delete yymsp[-1].minor.yy319; + } + break; + case 197: /* setlist ::= setlist COMMA nm EQ expr */ +{ + yymsp[-4].minor.yy201->append(ParserSetValue(*(yymsp[-2].minor.yy319), yymsp[0].minor.yy192)); + yygotominor.yy201 = yymsp[-4].minor.yy201; + delete yymsp[-2].minor.yy319; + DONT_INHERIT_TOKENS("setlist"); + } + break; + case 198: /* setlist ::= nm EQ expr */ +{ + yygotominor.yy201 = new ParserSetValueList(); + yygotominor.yy201->append(ParserSetValue(*(yymsp[-2].minor.yy319), yymsp[0].minor.yy192)); + delete yymsp[-2].minor.yy319; + } + break; + case 199: /* setlist ::= */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy201 = new ParserSetValueList(); + } + break; + case 200: /* setlist ::= setlist COMMA */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy201 = yymsp[-1].minor.yy201; + } + break; + case 201: /* setlist ::= setlist COMMA ID_COL */ + case 202: /* setlist ::= ID_COL */ yytestcase(yyruleno==202); +{ yy_destructor(yypParser,216,&yymsp[-2].minor); +} + break; + case 204: /* insert_stmt ::= insert_cmd INTO fullname inscollist_opt VALUES LP exprlist RP */ +{ + yygotominor.yy203 = new SqliteInsert( + yymsp[-7].minor.yy344->replace, + yymsp[-7].minor.yy344->orConflict, + yymsp[-5].minor.yy120->name1, + yymsp[-5].minor.yy120->name2, + *(yymsp[-4].minor.yy207), + *(yymsp[-1].minor.yy231), + nullptr + ); + delete yymsp[-5].minor.yy120; + delete yymsp[-7].minor.yy344; + delete yymsp[-1].minor.yy231; + delete yymsp[-4].minor.yy207; + // since it's used in trigger: + objectForTokens = yygotominor.yy203; + } + break; + case 205: /* insert_stmt ::= insert_cmd INTO fullname inscollist_opt select */ +{ + yygotominor.yy203 = new SqliteInsert( + yymsp[-4].minor.yy344->replace, + yymsp[-4].minor.yy344->orConflict, + yymsp[-2].minor.yy120->name1, + yymsp[-2].minor.yy120->name2, + *(yymsp[-1].minor.yy207), + yymsp[0].minor.yy153, + nullptr + ); + delete yymsp[-2].minor.yy120; + delete yymsp[-4].minor.yy344; + delete yymsp[-1].minor.yy207; + // since it's used in trigger: + objectForTokens = yygotominor.yy203; + } + break; + case 206: /* insert_stmt ::= insert_cmd INTO */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteInsert* q = new SqliteInsert(); + q->replaceKw = yymsp[-1].minor.yy344->replace; + q->onConflict = yymsp[-1].minor.yy344->orConflict; + yygotominor.yy203 = q; + objectForTokens = yygotominor.yy203; + delete yymsp[-1].minor.yy344; + } + break; + case 207: /* insert_stmt ::= insert_cmd INTO nm DOT */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteInsert* q = new SqliteInsert(); + q->replaceKw = yymsp[-3].minor.yy344->replace; + q->onConflict = yymsp[-3].minor.yy344->orConflict; + q->database = *(yymsp[-1].minor.yy319); + yygotominor.yy203 = q; + objectForTokens = yygotominor.yy203; + delete yymsp[-3].minor.yy344; + delete yymsp[-1].minor.yy319; + } + break; + case 208: /* insert_stmt ::= insert_cmd INTO ID_DB|ID_TAB */ +{ yy_destructor(yypParser,218,&yymsp[-2].minor); +} + break; + case 209: /* insert_stmt ::= insert_cmd INTO nm DOT ID_TAB */ +{ yy_destructor(yypParser,218,&yymsp[-4].minor); + yy_destructor(yypParser,156,&yymsp[-2].minor); +} + break; + case 210: /* insert_cmd ::= INSERT orconf */ +{ + yygotominor.yy344 = new ParserStubInsertOrReplace(false, *(yymsp[0].minor.yy418)); + delete yymsp[0].minor.yy418; + } + break; + case 211: /* insert_cmd ::= REPLACE */ +{yygotominor.yy344 = new ParserStubInsertOrReplace(true);} + break; + case 212: /* inscollist_opt ::= */ +{yygotominor.yy207 = new ParserStringList();} + break; + case 213: /* inscollist_opt ::= LP inscollist RP */ +{yygotominor.yy207 = yymsp[-1].minor.yy207;} + break; + case 214: /* inscollist ::= inscollist COMMA nm */ +{ + yymsp[-2].minor.yy207->append(*(yymsp[0].minor.yy319)); + yygotominor.yy207 = yymsp[-2].minor.yy207; + delete yymsp[0].minor.yy319; + DONT_INHERIT_TOKENS("inscollist"); + } + break; + case 215: /* inscollist ::= nm */ +{ + yygotominor.yy207 = new ParserStringList(); + yygotominor.yy207->append(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + } + break; + case 216: /* inscollist ::= */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy207 = new ParserStringList(); + } + break; + case 217: /* inscollist ::= inscollist COMMA ID_COL */ + case 218: /* inscollist ::= ID_COL */ yytestcase(yyruleno==218); +{ yy_destructor(yypParser,210,&yymsp[-2].minor); +} + break; + case 219: /* exprx ::= NULL */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initNull(); + objectForTokens = yygotominor.yy192; + } + break; + case 220: /* exprx ::= INTEGER */ +{ + yygotominor.yy192 = new SqliteExpr(); + QVariant val = QVariant(yymsp[0].minor.yy0->value).toLongLong(); + yygotominor.yy192->initLiteral(val); + objectForTokens = yygotominor.yy192; + } + break; + case 221: /* exprx ::= FLOAT */ +{ + yygotominor.yy192 = new SqliteExpr(); + QVariant val = QVariant(yymsp[0].minor.yy0->value).toDouble(); + yygotominor.yy192->initLiteral(val); + objectForTokens = yygotominor.yy192; + } + break; + case 222: /* exprx ::= STRING */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initLiteral(QVariant(yymsp[0].minor.yy0->value)); + objectForTokens = yygotominor.yy192; + } + break; + case 223: /* exprx ::= LP expr RP */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initSubExpr(yymsp[-1].minor.yy192); + objectForTokens = yygotominor.yy192; + } + break; + case 224: /* exprx ::= id */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initId(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy192; + } + break; + case 225: /* exprx ::= JOIN_KW */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initId(yymsp[0].minor.yy0->value); + objectForTokens = yygotominor.yy192; + } + break; + case 226: /* exprx ::= nm DOT nm */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initId(*(yymsp[-2].minor.yy319), *(yymsp[0].minor.yy319)); + delete yymsp[-2].minor.yy319; + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy192; + } + break; + case 227: /* exprx ::= nm DOT nm DOT nm */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initId(*(yymsp[-4].minor.yy319), *(yymsp[-2].minor.yy319), *(yymsp[0].minor.yy319)); + delete yymsp[-4].minor.yy319; + delete yymsp[-2].minor.yy319; + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy192; + } + break; + case 228: /* exprx ::= VARIABLE */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initBindParam(yymsp[0].minor.yy0->value); + objectForTokens = yygotominor.yy192; + } + break; + case 229: /* exprx ::= ID LP exprlist RP */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initFunction(yymsp[-3].minor.yy0->value, false, *(yymsp[-1].minor.yy231)); + delete yymsp[-1].minor.yy231; + objectForTokens = yygotominor.yy192; + } + break; + case 230: /* exprx ::= ID LP STAR RP */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initFunction(yymsp[-3].minor.yy0->value, true); + objectForTokens = yygotominor.yy192; + } + break; + case 231: /* exprx ::= expr AND expr */ + case 232: /* exprx ::= expr OR expr */ yytestcase(yyruleno==232); + case 233: /* exprx ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==233); + case 234: /* exprx ::= expr EQ|NE expr */ yytestcase(yyruleno==234); + case 235: /* exprx ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==235); + case 236: /* exprx ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==236); + case 237: /* exprx ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==237); + case 238: /* exprx ::= expr CONCAT expr */ yytestcase(yyruleno==238); +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initBinOp(yymsp[-2].minor.yy192, yymsp[-1].minor.yy0->value, yymsp[0].minor.yy192); + objectForTokens = yygotominor.yy192; + } + break; + case 239: /* exprx ::= expr not_opt likeop expr */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initLike(yymsp[-3].minor.yy192, *(yymsp[-2].minor.yy291), *(yymsp[-1].minor.yy41), yymsp[0].minor.yy192); + delete yymsp[-2].minor.yy291; + delete yymsp[-1].minor.yy41; + objectForTokens = yygotominor.yy192; + } + break; + case 240: /* exprx ::= expr ISNULL|NOTNULL */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initNull(yymsp[-1].minor.yy192, yymsp[0].minor.yy0->value); + objectForTokens = yygotominor.yy192; + } + break; + case 241: /* exprx ::= expr NOT NULL */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initNull(yymsp[-2].minor.yy192, "NOT NULL"); + objectForTokens = yygotominor.yy192; + } + break; + case 242: /* exprx ::= expr IS not_opt expr */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initIs(yymsp[-3].minor.yy192, *(yymsp[-1].minor.yy291), yymsp[0].minor.yy192); + delete yymsp[-1].minor.yy291; + objectForTokens = yygotominor.yy192; + } + break; + case 243: /* exprx ::= NOT expr */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initUnaryOp(yymsp[0].minor.yy192, yymsp[-1].minor.yy0->value); + } + break; + case 244: /* exprx ::= BITNOT expr */ + case 245: /* exprx ::= MINUS expr */ yytestcase(yyruleno==245); + case 246: /* exprx ::= PLUS expr */ yytestcase(yyruleno==246); +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initUnaryOp(yymsp[0].minor.yy192, yymsp[-1].minor.yy0->value); + objectForTokens = yygotominor.yy192; + } + break; + case 247: /* exprx ::= expr not_opt BETWEEN expr AND expr */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initBetween(yymsp[-5].minor.yy192, *(yymsp[-4].minor.yy291), yymsp[-2].minor.yy192, yymsp[0].minor.yy192); + delete yymsp[-4].minor.yy291; + objectForTokens = yygotominor.yy192; + } + break; + case 248: /* exprx ::= expr not_opt IN LP exprlist RP */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initIn(yymsp[-5].minor.yy192, *(yymsp[-4].minor.yy291), *(yymsp[-1].minor.yy231)); + delete yymsp[-4].minor.yy291; + delete yymsp[-1].minor.yy231; + objectForTokens = yygotominor.yy192; + } + break; + case 249: /* exprx ::= expr not_opt IN LP select RP */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initIn(yymsp[-5].minor.yy192, *(yymsp[-4].minor.yy291), yymsp[-1].minor.yy153); + delete yymsp[-4].minor.yy291; + objectForTokens = yygotominor.yy192; + } + break; + case 250: /* exprx ::= expr not_opt IN nm dbnm */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initIn(yymsp[-4].minor.yy192, yymsp[-3].minor.yy291, *(yymsp[-1].minor.yy319), *(yymsp[0].minor.yy319)); + delete yymsp[-3].minor.yy291; + delete yymsp[-1].minor.yy319; + objectForTokens = yygotominor.yy192; + } + break; + case 251: /* exprx ::= LP select RP */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initSubSelect(yymsp[-1].minor.yy153); + objectForTokens = yygotominor.yy192; + } + break; + case 252: /* exprx ::= CASE case_operand case_exprlist case_else END */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initCase(yymsp[-3].minor.yy192, *(yymsp[-2].minor.yy231), yymsp[-1].minor.yy192); + delete yymsp[-2].minor.yy231; + objectForTokens = yygotominor.yy192; + } + break; + case 253: /* exprx ::= RAISE LP raisetype COMMA nm RP */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initRaise(yymsp[-3].minor.yy0->value, *(yymsp[-1].minor.yy319)); + delete yymsp[-1].minor.yy319; + objectForTokens = yygotominor.yy192; + } + break; + case 254: /* exprx ::= RAISE LP IGNORE RP */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initRaise(yymsp[-1].minor.yy0->value); + objectForTokens = yygotominor.yy192; + } + break; + case 255: /* exprx ::= nm DOT */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initId(*(yymsp[-1].minor.yy319), QString::null, QString::null); + delete yymsp[-1].minor.yy319; + objectForTokens = yygotominor.yy192; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 256: /* exprx ::= nm DOT nm DOT */ +{ + yygotominor.yy192 = new SqliteExpr(); + yygotominor.yy192->initId(*(yymsp[-3].minor.yy319), *(yymsp[-1].minor.yy319), QString::null); + delete yymsp[-3].minor.yy319; + delete yymsp[-1].minor.yy319; + objectForTokens = yygotominor.yy192; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 257: /* exprx ::= expr not_opt BETWEEN expr */ +{ + yygotominor.yy192 = new SqliteExpr(); + delete yymsp[-2].minor.yy291; + delete yymsp[-3].minor.yy192; + delete yymsp[0].minor.yy192; + objectForTokens = yygotominor.yy192; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 258: /* exprx ::= CASE case_operand case_exprlist case_else */ +{ + yygotominor.yy192 = new SqliteExpr(); + delete yymsp[-1].minor.yy231; + delete yymsp[-2].minor.yy192; + delete yymsp[0].minor.yy192; + objectForTokens = yygotominor.yy192; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 259: /* exprx ::= expr not_opt IN LP exprlist */ +{ + yygotominor.yy192 = new SqliteExpr(); + delete yymsp[-3].minor.yy291; + delete yymsp[0].minor.yy231; + delete yymsp[-4].minor.yy192; + objectForTokens = yygotominor.yy192; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 260: /* exprx ::= expr not_opt IN ID_DB */ +{ yy_destructor(yypParser,177,&yymsp[-3].minor); +} + break; + case 261: /* exprx ::= expr not_opt IN nm DOT ID_TAB */ + case 262: /* exprx ::= ID_DB|ID_TAB|ID_COL|ID_FN */ yytestcase(yyruleno==262); +{ yy_destructor(yypParser,177,&yymsp[-5].minor); + yy_destructor(yypParser,156,&yymsp[-2].minor); +} + break; + case 264: /* exprx ::= nm DOT nm DOT ID_COL */ + case 265: /* exprx ::= RAISE LP raisetype COMMA ID_ERR_MSG RP */ yytestcase(yyruleno==265); +{ yy_destructor(yypParser,156,&yymsp[-4].minor); + yy_destructor(yypParser,156,&yymsp[-2].minor); +} + break; + case 267: /* expr ::= */ +{ + yygotominor.yy192 = new SqliteExpr(); + objectForTokens = yygotominor.yy192; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 270: /* likeop ::= LIKE|GLOB */ +{yygotominor.yy41 = new SqliteExpr::LikeOp(SqliteExpr::likeOp(yymsp[0].minor.yy0->value));} + break; + case 271: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ +{ + yymsp[-4].minor.yy231->append(yymsp[-2].minor.yy192); + yymsp[-4].minor.yy231->append(yymsp[0].minor.yy192); + yygotominor.yy231 = yymsp[-4].minor.yy231; + } + break; + case 272: /* case_exprlist ::= WHEN expr THEN expr */ +{ + yygotominor.yy231 = new ParserExprList(); + yygotominor.yy231->append(yymsp[-2].minor.yy192); + yygotominor.yy231->append(yymsp[0].minor.yy192); + } + break; + case 279: /* nexprlist ::= nexprlist COMMA expr */ +{ + yymsp[-2].minor.yy231->append(yymsp[0].minor.yy192); + yygotominor.yy231 = yymsp[-2].minor.yy231; + DONT_INHERIT_TOKENS("nexprlist"); + } + break; + case 280: /* nexprlist ::= exprx */ +{ + yygotominor.yy231 = new ParserExprList(); + yygotominor.yy231->append(yymsp[0].minor.yy192); + } + break; + case 281: /* cmd ::= CREATE uniqueflag INDEX nm ON nm dbnm LP idxlist RP onconf */ +{ + yygotominor.yy203 = new SqliteCreateIndex( + *(yymsp[-9].minor.yy291), + false, + *(yymsp[-7].minor.yy319), + *(yymsp[-5].minor.yy319), + *(yymsp[-4].minor.yy319), + *(yymsp[-2].minor.yy63), + *(yymsp[0].minor.yy418) + ); + delete yymsp[-9].minor.yy291; + delete yymsp[-7].minor.yy319; + delete yymsp[-5].minor.yy319; + delete yymsp[-4].minor.yy319; + delete yymsp[-2].minor.yy63; + delete yymsp[0].minor.yy418; + objectForTokens = yygotominor.yy203; + } + break; + case 282: /* cmd ::= CREATE uniqueflag INDEX nm dbnm ON ID_TAB */ +{ yy_destructor(yypParser,156,&yymsp[-3].minor); +} + break; + case 287: /* idxlist_opt ::= */ +{yygotominor.yy63 = new ParserIndexedColumnList();} + break; + case 288: /* idxlist_opt ::= LP idxlist RP */ +{yygotominor.yy63 = yymsp[-1].minor.yy63;} + break; + case 289: /* idxlist ::= idxlist COMMA idxlist_single */ +{ + yymsp[-2].minor.yy63->append(yymsp[0].minor.yy428); + yygotominor.yy63 = yymsp[-2].minor.yy63; + DONT_INHERIT_TOKENS("idxlist"); + } + break; + case 290: /* idxlist ::= idxlist_single */ +{ + yygotominor.yy63 = new ParserIndexedColumnList(); + yygotominor.yy63->append(yymsp[0].minor.yy428); + } + break; + case 291: /* idxlist_single ::= nm sortorder */ + case 292: /* idxlist_single ::= ID_COL */ yytestcase(yyruleno==292); +{ + SqliteIndexedColumn* obj = + new SqliteIndexedColumn( + *(yymsp[-1].minor.yy319), + QString::null, + *(yymsp[0].minor.yy389) + ); + yygotominor.yy428 = obj; + delete yymsp[0].minor.yy389; + delete yymsp[-1].minor.yy319; + objectForTokens = yygotominor.yy428; + } + break; + case 293: /* cmd ::= DROP INDEX fullname */ +{ + yygotominor.yy203 = new SqliteDropIndex(false, yymsp[0].minor.yy120->name1, yymsp[0].minor.yy120->name2); + delete yymsp[0].minor.yy120; + objectForTokens = yygotominor.yy203; + } + break; + case 296: /* cmd ::= COPY orconf nm dbnm FROM nm USING DELIMITERS STRING */ +{ + yygotominor.yy203 = new SqliteCopy( + *(yymsp[-7].minor.yy418), + *(yymsp[-6].minor.yy319), + *(yymsp[-5].minor.yy319), + *(yymsp[-3].minor.yy319), + yymsp[0].minor.yy0->value + ); + delete yymsp[-7].minor.yy418; + delete yymsp[-6].minor.yy319; + delete yymsp[-5].minor.yy319; + delete yymsp[-3].minor.yy319; + objectForTokens = yygotominor.yy203; + } + break; + case 297: /* cmd ::= COPY orconf nm dbnm FROM nm */ +{ + yygotominor.yy203 = new SqliteCopy( + *(yymsp[-4].minor.yy418), + *(yymsp[-3].minor.yy319), + *(yymsp[-2].minor.yy319), + *(yymsp[0].minor.yy319) + ); + delete yymsp[-4].minor.yy418; + delete yymsp[-3].minor.yy319; + delete yymsp[-2].minor.yy319; + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy203; + } + break; + case 298: /* cmd ::= VACUUM */ +{ + yygotominor.yy203 = new SqliteVacuum(); + objectForTokens = yygotominor.yy203; + } + break; + case 299: /* cmd ::= VACUUM nm */ +{ + yygotominor.yy203 = new SqliteVacuum(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy203; + } + break; + case 300: /* cmd ::= PRAGMA ids */ +{ + yygotominor.yy203 = new SqlitePragma(*(yymsp[0].minor.yy319), QString::null); + delete yymsp[0].minor.yy319; + objectForTokens = yygotominor.yy203; + } + break; + case 301: /* cmd ::= PRAGMA nm EQ nmnum */ + case 303: /* cmd ::= PRAGMA nm EQ minus_num */ yytestcase(yyruleno==303); +{ + yygotominor.yy203 = new SqlitePragma(*(yymsp[-2].minor.yy319), QString::null, *(yymsp[0].minor.yy69), true); + delete yymsp[-2].minor.yy319; + delete yymsp[0].minor.yy69; + objectForTokens = yygotominor.yy203; + } + break; + case 302: /* cmd ::= PRAGMA nm LP nmnum RP */ + case 304: /* cmd ::= PRAGMA nm LP minus_num RP */ yytestcase(yyruleno==304); +{ + yygotominor.yy203 = new SqlitePragma(*(yymsp[-3].minor.yy319), QString::null, *(yymsp[-1].minor.yy69), false); + delete yymsp[-3].minor.yy319; + delete yymsp[-1].minor.yy69; + objectForTokens = yygotominor.yy203; + } + break; + case 308: /* nmnum ::= nm */ +{ + yygotominor.yy69 = new QVariant(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + } + break; + case 309: /* nmnum ::= ON */ + case 310: /* nmnum ::= DELETE */ yytestcase(yyruleno==310); + case 311: /* nmnum ::= DEFAULT */ yytestcase(yyruleno==311); +{yygotominor.yy69 = new QVariant(yymsp[0].minor.yy0->value);} + break; + case 314: /* minus_num ::= MINUS number */ +{ + if (yymsp[0].minor.yy69->type() == QVariant::Double) + *(yymsp[0].minor.yy69) = -(yymsp[0].minor.yy69->toDouble()); + else if (yymsp[0].minor.yy69->type() == QVariant::LongLong) + *(yymsp[0].minor.yy69) = -(yymsp[0].minor.yy69->toLongLong()); + else + Q_ASSERT_X(true, "producing minus number", "QVariant is neither of Double or LongLong."); + + yygotominor.yy69 = yymsp[0].minor.yy69; + } + break; + case 315: /* number ::= INTEGER */ +{yygotominor.yy69 = new QVariant(QVariant(yymsp[0].minor.yy0->value).toLongLong());} + break; + case 316: /* number ::= FLOAT */ +{yygotominor.yy69 = new QVariant(QVariant(yymsp[0].minor.yy0->value).toDouble());} + break; + case 317: /* cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause BEGIN trigger_cmd_list END */ +{ + yygotominor.yy203 = new SqliteCreateTrigger( + *(yymsp[-12].minor.yy226), + false, + *(yymsp[-10].minor.yy319), + *(yymsp[-6].minor.yy319), + *(yymsp[-5].minor.yy319), + *(yymsp[-9].minor.yy372), + yymsp[-8].minor.yy151, + *(yymsp[-4].minor.yy83), + yymsp[-3].minor.yy192, + *(yymsp[-1].minor.yy270), + 2 + ); + delete yymsp[-12].minor.yy226; + delete yymsp[-9].minor.yy372; + delete yymsp[-4].minor.yy83; + delete yymsp[-6].minor.yy319; + delete yymsp[-10].minor.yy319; + delete yymsp[-5].minor.yy319; + delete yymsp[-1].minor.yy270; + objectForTokens = yygotominor.yy203; + } + break; + case 318: /* cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause */ +{ + QList<SqliteQuery *> CL; + + yygotominor.yy203 = new SqliteCreateTrigger( + *(yymsp[-9].minor.yy226), + false, + *(yymsp[-7].minor.yy319), + *(yymsp[-3].minor.yy319), + *(yymsp[-2].minor.yy319), + *(yymsp[-6].minor.yy372), + yymsp[-5].minor.yy151, + *(yymsp[-1].minor.yy83), + yymsp[0].minor.yy192, + CL, + 2 + ); + delete yymsp[-9].minor.yy226; + delete yymsp[-6].minor.yy372; + delete yymsp[-1].minor.yy83; + delete yymsp[-3].minor.yy319; + delete yymsp[-7].minor.yy319; + delete yymsp[-2].minor.yy319; + objectForTokens = yygotominor.yy203; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 319: /* cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause BEGIN trigger_cmd_list */ +{ + yygotominor.yy203 = new SqliteCreateTrigger( + *(yymsp[-11].minor.yy226), + false, + *(yymsp[-9].minor.yy319), + *(yymsp[-5].minor.yy319), + *(yymsp[-4].minor.yy319), + *(yymsp[-8].minor.yy372), + yymsp[-7].minor.yy151, + *(yymsp[-3].minor.yy83), + yymsp[-2].minor.yy192, + *(yymsp[0].minor.yy270), + 2 + ); + delete yymsp[-11].minor.yy226; + delete yymsp[-8].minor.yy372; + delete yymsp[-3].minor.yy83; + delete yymsp[-5].minor.yy319; + delete yymsp[-9].minor.yy319; + delete yymsp[-4].minor.yy319; + delete yymsp[0].minor.yy270; + objectForTokens = yygotominor.yy203; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 320: /* cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON ID_TAB|ID_DB */ +{ yy_destructor(yypParser,157,&yymsp[-6].minor); + yy_destructor(yypParser,156,&yymsp[-4].minor); + yy_destructor(yypParser,232,&yymsp[-3].minor); + yy_destructor(yypParser,233,&yymsp[-2].minor); +} + break; + case 321: /* cmd ::= CREATE temp TRIGGER nm trigger_time trigger_event ON nm DOT ID_TAB */ +{ yy_destructor(yypParser,157,&yymsp[-8].minor); + yy_destructor(yypParser,156,&yymsp[-6].minor); + yy_destructor(yypParser,232,&yymsp[-5].minor); + yy_destructor(yypParser,233,&yymsp[-4].minor); + yy_destructor(yypParser,156,&yymsp[-2].minor); +} + break; + case 323: /* trigger_time ::= BEFORE */ +{yygotominor.yy372 = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::BEFORE);} + break; + case 324: /* trigger_time ::= AFTER */ +{yygotominor.yy372 = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::AFTER);} + break; + case 325: /* trigger_time ::= INSTEAD OF */ +{yygotominor.yy372 = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::INSTEAD_OF);} + break; + case 326: /* trigger_time ::= */ +{yygotominor.yy372 = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::null);} + break; + case 327: /* trigger_event ::= DELETE */ +{ + yygotominor.yy151 = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::DELETE); + objectForTokens = yygotominor.yy151; + } + break; + case 328: /* trigger_event ::= INSERT */ +{ + yygotominor.yy151 = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::INSERT); + objectForTokens = yygotominor.yy151; + } + break; + case 329: /* trigger_event ::= UPDATE */ +{ + yygotominor.yy151 = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::UPDATE); + objectForTokens = yygotominor.yy151; + } + break; + case 330: /* trigger_event ::= UPDATE OF inscollist */ +{ + yygotominor.yy151 = new SqliteCreateTrigger::Event(*(yymsp[0].minor.yy207)); + delete yymsp[0].minor.yy207; + objectForTokens = yygotominor.yy151; + } + break; + case 331: /* foreach_clause ::= */ +{yygotominor.yy83 = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::null);} + break; + case 332: /* foreach_clause ::= FOR EACH ROW */ +{yygotominor.yy83 = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::FOR_EACH_ROW);} + break; + case 333: /* foreach_clause ::= FOR EACH STATEMENT */ +{yygotominor.yy83 = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::FOR_EACH_STATEMENT);} + break; + case 336: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ +{ + yymsp[-2].minor.yy270->append(yymsp[-1].minor.yy203); + yygotominor.yy270 = yymsp[-2].minor.yy270; + DONT_INHERIT_TOKENS("trigger_cmd_list"); + } + break; + case 337: /* trigger_cmd_list ::= trigger_cmd SEMI */ +{ + yygotominor.yy270 = new ParserQueryList(); + yygotominor.yy270->append(yymsp[-1].minor.yy203); + } + break; + case 342: /* raisetype ::= ROLLBACK|ABORT|FAIL */ +{yygotominor.yy0 = yymsp[0].minor.yy0;} + break; + case 343: /* cmd ::= DROP TRIGGER fullname */ +{ + yygotominor.yy203 = new SqliteDropTrigger(false, yymsp[0].minor.yy120->name1, yymsp[0].minor.yy120->name2); + delete yymsp[0].minor.yy120; + objectForTokens = yygotominor.yy203; + } + break; + case 346: /* cmd ::= ATTACH database_kw_opt ids AS ids key_opt */ +{ + SqliteExpr* e1 = new SqliteExpr(); + SqliteExpr* e2 = new SqliteExpr(); + e1->initLiteral(*(yymsp[-3].minor.yy319)); + e2->initLiteral(*(yymsp[-1].minor.yy319)); + yygotominor.yy203 = new SqliteAttach(*(yymsp[-4].minor.yy291), e1, e2, yymsp[0].minor.yy192); + delete yymsp[-4].minor.yy291; + delete yymsp[-3].minor.yy319; + delete yymsp[-1].minor.yy319; + objectForTokens = yygotominor.yy203; + } + break; + case 348: /* key_opt ::= USING ids */ +{ + SqliteExpr* e = new SqliteExpr(); + e->initLiteral(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + yygotominor.yy192 = e; + } + break; + case 351: /* cmd ::= DETACH database_kw_opt nm */ +{ + SqliteExpr* e = new SqliteExpr(); + e->initLiteral(*(yymsp[0].minor.yy319)); + delete yymsp[0].minor.yy319; + yygotominor.yy203 = new SqliteDetach(*(yymsp[-1].minor.yy291), e); + delete yymsp[-1].minor.yy291; + objectForTokens = yygotominor.yy203; + } + break; + default: + /* (0) input ::= cmdlist */ yytestcase(yyruleno==0); + break; + }; + } + assert( yyruleno>=0 && yyruleno<(int)(sizeof(yyRuleInfo)/sizeof(yyRuleInfo[0])) ); + yygoto = yyRuleInfo[yyruleno].lhs; + yysize = yyRuleInfo[yyruleno].nrhs; + + // Store tokens for the rule in parser context + QList<Token*> allTokens; + QList<Token*> allTokensWithAllInherited; + QString keyForTokensMap; + int tokensMapKeyCnt; + if (parserContext->setupTokens) + { + if (objectForTokens) + { + // In case this is a list with recurrent references we need + // to clear tokens before adding the new and extended list. + objectForTokens->tokens.clear(); + } + + QList<Token*> tokens; + for (int i = yypParser->yyidx - yysize + 1; i <= yypParser->yyidx; i++) + { + tokens.clear(); + const char* fieldName = yyTokenName[yypParser->yystack[i].major]; + if (parserContext->isManagedToken(yypParser->yystack[i].minor.yy0)) + tokens += yypParser->yystack[i].minor.yy0; + + tokens += *(yypParser->yystack[i].tokens); + + if (!noTokenInheritanceFields.contains(fieldName)) + { + if (objectForTokens) + { + keyForTokensMap = fieldName; + tokensMapKeyCnt = 2; + while (objectForTokens->tokensMap.contains(keyForTokensMap)) + keyForTokensMap = fieldName + QString::number(tokensMapKeyCnt++); + + objectForTokens->tokensMap[keyForTokensMap] = parserContext->getTokenPtrList(tokens); + } + + allTokens += tokens; + } + else + { + // If field is mentioned only once, then only one occurance of it will be ignored. + // Second one should be inherited. See "anylist" definition for explanation why. + noTokenInheritanceFields.removeOne(fieldName); + } + allTokensWithAllInherited += tokens; + } + if (objectForTokens) + { + objectForTokens->tokens += parserContext->getTokenPtrList(allTokens); + } + } + + // Clear token lists + for (int i = yypParser->yyidx - yysize + 1; i <= yypParser->yyidx; i++) + { + delete yypParser->yystack[i].tokens; + yypParser->yystack[i].tokens = nullptr; + } + + yypParser->yyidx -= yysize; + yyact = yy_find_reduce_action(yymsp[-yysize].stateno,(YYCODETYPE)yygoto); + if( yyact < YYNSTATE ){ +#ifdef NDEBUG + /* If we are not debugging and the reduce action popped at least + ** one element off the stack, then we can push the new element back + ** onto the stack here, and skip the stack overflow test in yy_shift(). + ** That gives a significant speed improvement. */ + if( yysize ){ + yypParser->yyidx++; + yymsp -= yysize-1; + yymsp->stateno = (YYACTIONTYPE)yyact; + yymsp->major = (YYCODETYPE)yygoto; + yymsp->minor = yygotominor; + if (parserContext->setupTokens) + *(yypParser->yystack[yypParser->yyidx].tokens) = allTokens; + }else +#endif + { + yy_shift(yypParser,yyact,yygoto,&yygotominor); + if (parserContext->setupTokens) + { + QList<Token*>* tokensPtr = yypParser->yystack[yypParser->yyidx].tokens; + *tokensPtr = allTokensWithAllInherited + *tokensPtr; + } + } + }else{ + assert( yyact == YYNSTATE + YYNRULE + 1 ); + yy_accept(yypParser); + } +} + +/* +** The following code executes when the parse fails +*/ +#ifndef YYNOERRORRECOVERY +static void yy_parse_failed( + yyParser *yypParser /* The parser */ +){ + sqlite2_parseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ + sqlite2_parseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} +#endif /* YYNOERRORRECOVERY */ + +/* +** The following code executes when a syntax error first occurs. +*/ +static void yy_syntax_error( + yyParser *yypParser, /* The parser */ + int yymajor, /* The major type of the error token */ + YYMINORTYPE yyminor /* The minor type of the error token */ +){ + sqlite2_parseARG_FETCH; +#define TOKEN (yyminor.yy0) + + UNUSED_PARAMETER(yymajor); + parserContext->error(TOKEN, QObject::tr("Syntax error")); + sqlite2_parseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + sqlite2_parseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser accepts */ + sqlite2_parseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "sqlite2_parseAlloc" which describes the current state of the parser. +** The second argument is the major token number. The third is +** the minor token. The fourth optional argument is whatever the +** user wants (and specified in the grammar) and is available for +** use by the action routines. +** +** Inputs: +** <ul> +** <li> A pointer to the parser (an opaque structure.) +** <li> The major token number. +** <li> The minor token number. +** <li> An option argument of a grammar-specified type. +** </ul> +** +** Outputs: +** None. +*/ +void sqlite2_parse( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + sqlite2_parseTOKENTYPE yyminor /* The value for the token */ + sqlite2_parseARG_PDECL /* Optional %extra_argument parameter */ +){ + YYMINORTYPE yyminorunion; + int yyact; /* The parser action. */ +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + int yyendofinput; /* True if we are at the end of input */ +#endif +#ifdef YYERRORSYMBOL + int yyerrorhit = 0; /* True if yymajor has invoked an error */ +#endif + yyParser *yypParser; /* The parser */ + + /* (re)initialize the parser, if necessary */ + yypParser = (yyParser*)yyp; + if( yypParser->yyidx<0 ){ +#if YYSTACKDEPTH<=0 + if( yypParser->yystksz <=0 ){ + /*memset(&yyminorunion, 0, sizeof(yyminorunion));*/ + yyminorunion = yyzerominor; + yyStackOverflow(yypParser, &yyminorunion); + return; + } +#endif + yypParser->yyidx = 0; + yypParser->yyerrcnt = -1; + yypParser->yystack[0].stateno = 0; + yypParser->yystack[0].major = 0; + yypParser->yystack[0].tokens = new QList<Token*>(); + } + yyminorunion.yy0 = yyminor; +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + yyendofinput = (yymajor==0); +#endif + sqlite2_parseARG_STORE; + +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sInput %s [%s] (lemon type: %s)\n", + yyTracePrompt, + yyminor->value.toLatin1().data(), + yyminor->typeString().toLatin1().data(), + yyTokenName[yymajor]); } +#endif + + do{ + yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor); + if( yyact<YYNSTATE ){ + yy_shift(yypParser,yyact,yymajor,&yyminorunion); + yypParser->yyerrcnt--; + yymajor = YYNOCODE; + }else if( yyact < YYNSTATE + YYNRULE ){ + yy_reduce(yypParser,yyact-YYNSTATE); + }else{ + assert( yyact == YY_ERROR_ACTION ); +#ifdef YYERRORSYMBOL + int yymx; +#endif +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); + } +#endif +#ifdef YYERRORSYMBOL + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if( yypParser->yyerrcnt<0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yymx = yypParser->yystack[yypParser->yyidx].major; + if( yymx==YYERRORSYMBOL || yyerrorhit ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sDiscard input token %s\n", + yyTracePrompt,yyTokenName[yymajor]); + } +#endif + yy_destructor(yypParser, (YYCODETYPE)yymajor,&yyminorunion); + yymajor = YYNOCODE; + }else{ + while( + yypParser->yyidx >= 0 && + yymx != YYERRORSYMBOL && + (yyact = yy_find_reduce_action( + yypParser->yystack[yypParser->yyidx].stateno, + YYERRORSYMBOL)) >= YYNSTATE + ){ + yy_pop_parser_stack(yypParser); + } + if( yypParser->yyidx < 0 || yymajor==0 ){ + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yy_parse_failed(yypParser); + yymajor = YYNOCODE; + }else if( yymx!=YYERRORSYMBOL ){ + YYMINORTYPE u2; + u2.YYERRSYMDT = 0; + yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2); + } + } + yypParser->yyerrcnt = 1; // not 3 valid tokens, but 1 + yyerrorhit = 1; +#elif defined(YYNOERRORRECOVERY) + /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to + ** do any kind of error recovery. Instead, simply invoke the syntax + ** error routine and continue going as if nothing had happened. + ** + ** Applications can set this macro (for example inside %include) if + ** they intend to abandon the parse upon the first syntax error seen. + */ + yy_syntax_error(yypParser,yymajor,yyminorunion); + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yymajor = YYNOCODE; + +#else /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if( yypParser->yyerrcnt<=0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yypParser->yyerrcnt = 1; // not 3 valid tokens, but 1 + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + if( yyendofinput ){ + yy_parse_failed(yypParser); + } + yymajor = YYNOCODE; +#endif + } + }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 ); + return; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.h b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.h new file mode 100644 index 0000000..058d397 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.h @@ -0,0 +1,146 @@ +#define TK2_ILLEGAL 1 +#define TK2_COMMENT 2 +#define TK2_SPACE 3 +#define TK2_ID 4 +#define TK2_ABORT 5 +#define TK2_AFTER 6 +#define TK2_ASC 7 +#define TK2_ATTACH 8 +#define TK2_BEFORE 9 +#define TK2_BEGIN 10 +#define TK2_CASCADE 11 +#define TK2_CLUSTER 12 +#define TK2_CONFLICT 13 +#define TK2_COPY 14 +#define TK2_DATABASE 15 +#define TK2_DEFERRED 16 +#define TK2_DELIMITERS 17 +#define TK2_DESC 18 +#define TK2_DETACH 19 +#define TK2_EACH 20 +#define TK2_END 21 +#define TK2_EXPLAIN 22 +#define TK2_FAIL 23 +#define TK2_FOR 24 +#define TK2_GLOB 25 +#define TK2_IGNORE 26 +#define TK2_IMMEDIATE 27 +#define TK2_INITIALLY 28 +#define TK2_INSTEAD 29 +#define TK2_LIKE 30 +#define TK2_MATCH 31 +#define TK2_KEY 32 +#define TK2_OF 33 +#define TK2_OFFSET 34 +#define TK2_PRAGMA 35 +#define TK2_RAISE 36 +#define TK2_REPLACE 37 +#define TK2_RESTRICT 38 +#define TK2_ROW 39 +#define TK2_STATEMENT 40 +#define TK2_TEMP 41 +#define TK2_TRIGGER 42 +#define TK2_VACUUM 43 +#define TK2_VIEW 44 +#define TK2_OR 45 +#define TK2_AND 46 +#define TK2_NOT 47 +#define TK2_EQ 48 +#define TK2_NE 49 +#define TK2_ISNULL 50 +#define TK2_NOTNULL 51 +#define TK2_IS 52 +#define TK2_BETWEEN 53 +#define TK2_IN 54 +#define TK2_GT 55 +#define TK2_GE 56 +#define TK2_LT 57 +#define TK2_LE 58 +#define TK2_BITAND 59 +#define TK2_BITOR 60 +#define TK2_LSHIFT 61 +#define TK2_RSHIFT 62 +#define TK2_PLUS 63 +#define TK2_MINUS 64 +#define TK2_STAR 65 +#define TK2_SLASH 66 +#define TK2_REM 67 +#define TK2_CONCAT 68 +#define TK2_UMINUS 69 +#define TK2_UPLUS 70 +#define TK2_BITNOT 71 +#define TK2_SEMI 72 +#define TK2_TRANSACTION 73 +#define TK2_ID_TRANS 74 +#define TK2_COMMIT 75 +#define TK2_ROLLBACK 76 +#define TK2_CREATE 77 +#define TK2_TABLE 78 +#define TK2_LP 79 +#define TK2_RP 80 +#define TK2_AS 81 +#define TK2_DOT 82 +#define TK2_ID_TAB_NEW 83 +#define TK2_ID_DB 84 +#define TK2_COMMA 85 +#define TK2_ID_COL_NEW 86 +#define TK2_STRING 87 +#define TK2_JOIN_KW 88 +#define TK2_ID_COL_TYPE 89 +#define TK2_DEFAULT 90 +#define TK2_INTEGER 91 +#define TK2_FLOAT 92 +#define TK2_NULL 93 +#define TK2_CONSTRAINT 94 +#define TK2_PRIMARY 95 +#define TK2_UNIQUE 96 +#define TK2_CHECK 97 +#define TK2_REFERENCES 98 +#define TK2_COLLATE 99 +#define TK2_ON 100 +#define TK2_INSERT 101 +#define TK2_DELETE 102 +#define TK2_UPDATE 103 +#define TK2_ID_FK_MATCH 104 +#define TK2_SET 105 +#define TK2_DEFERRABLE 106 +#define TK2_FOREIGN 107 +#define TK2_ID_CONSTR 108 +#define TK2_ID_TAB 109 +#define TK2_DROP 110 +#define TK2_ID_VIEW_NEW 111 +#define TK2_ID_VIEW 112 +#define TK2_UNION 113 +#define TK2_ALL 114 +#define TK2_EXCEPT 115 +#define TK2_INTERSECT 116 +#define TK2_SELECT 117 +#define TK2_DISTINCT 118 +#define TK2_ID_ALIAS 119 +#define TK2_FROM 120 +#define TK2_USING 121 +#define TK2_JOIN 122 +#define TK2_ID_JOIN_OPTS 123 +#define TK2_ORDER 124 +#define TK2_BY 125 +#define TK2_GROUP 126 +#define TK2_HAVING 127 +#define TK2_LIMIT 128 +#define TK2_WHERE 129 +#define TK2_ID_COL 130 +#define TK2_INTO 131 +#define TK2_VALUES 132 +#define TK2_VARIABLE 133 +#define TK2_LIKE_KW 134 +#define TK2_CASE 135 +#define TK2_ID_FN 136 +#define TK2_ID_ERR_MSG 137 +#define TK2_WHEN 138 +#define TK2_THEN 139 +#define TK2_ELSE 140 +#define TK2_INDEX 141 +#define TK2_ID_IDX_NEW 142 +#define TK2_ID_IDX 143 +#define TK2_ID_PRAGMA 144 +#define TK2_ID_TRIG_NEW 145 +#define TK2_ID_TRIG 146 diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.y b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.y new file mode 100644 index 0000000..e44e9b8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite2_parse.y @@ -0,0 +1,2068 @@ +%token_prefix TK2_ +%token_type {Token*} +%default_type {Token*} +%extra_argument {ParserContext* parserContext} +%name sqlite2_parse + +%syntax_error { + UNUSED_PARAMETER(yymajor); + parserContext->error(TOKEN, QObject::tr("Syntax error")); +} + +%stack_overflow { + UNUSED_PARAMETER(yypMinor); + parserContext->error(QObject::tr("Parser stack overflow")); +} + +%include { +#include "token.h" +#include "parsercontext.h" +#include "parser/ast/sqlitealtertable.h" +#include "parser/ast/sqliteanalyze.h" +#include "parser/ast/sqliteattach.h" +#include "parser/ast/sqlitebegintrans.h" +#include "parser/ast/sqlitecommittrans.h" +#include "parser/ast/sqlitecopy.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqlitecreatevirtualtable.h" +#include "parser/ast/sqlitedelete.h" +#include "parser/ast/sqlitedetach.h" +#include "parser/ast/sqlitedropindex.h" +#include "parser/ast/sqlitedroptable.h" +#include "parser/ast/sqlitedroptrigger.h" +#include "parser/ast/sqlitedropview.h" +#include "parser/ast/sqliteemptyquery.h" +#include "parser/ast/sqliteinsert.h" +#include "parser/ast/sqlitepragma.h" +#include "parser/ast/sqlitereindex.h" +#include "parser/ast/sqliterelease.h" +#include "parser/ast/sqliterollback.h" +#include "parser/ast/sqlitesavepoint.h" +#include "parser/ast/sqliteselect.h" +#include "parser/ast/sqliteupdate.h" +#include "parser/ast/sqlitevacuum.h" +#include "parser/ast/sqliteexpr.h" +#include "parser/ast/sqlitecolumntype.h" +#include "parser/ast/sqliteconflictalgo.h" +#include "parser/ast/sqlitesortorder.h" +#include "parser/ast/sqliteindexedcolumn.h" +#include "parser/ast/sqliteforeignkey.h" +#include "parser_helper_stubs.h" +#include "common/utils_sql.h" +#include <QObject> +#include <QDebug> + +#define assert(X) Q_ASSERT(X) +#define UNUSED_PARAMETER(X) (void)(X) +#define DONT_INHERIT_TOKENS(X) noTokenInheritanceFields << X +} + +// These are extra tokens used by the lexer but never seen by the +// parser. We put them in a rule so that the parser generator will +// add them to the parse.h output file. + +%nonassoc ILLEGAL COMMENT SPACE. + +// The following directive causes tokens ABORT, AFTER, ASC, etc. to +// fallback to ID if they will not parse as their original value. +// This obviates the need for the "id" nonterminal. + +%fallback ID + ABORT AFTER ASC ATTACH BEFORE BEGIN CASCADE CLUSTER CONFLICT + COPY DATABASE DEFERRED DELIMITERS DESC DETACH EACH END EXPLAIN FAIL FOR + GLOB IGNORE IMMEDIATE INITIALLY INSTEAD LIKE MATCH KEY + OF OFFSET PRAGMA RAISE REPLACE RESTRICT ROW STATEMENT + TEMP TRIGGER VACUUM VIEW. + +// Define operator precedence early so that this is the first occurance +// of the operator tokens in the grammer. Keeping the operators together +// causes them to be assigned integer values that are close together, +// which keeps parser tables smaller. + +%left OR. +%left AND. +%right NOT. +%left EQ NE ISNULL NOTNULL IS LIKE GLOB BETWEEN IN. +%left GT GE LT LE. +%left BITAND BITOR LSHIFT RSHIFT. +%left PLUS MINUS. +%left STAR SLASH REM. +%left CONCAT. +%right UMINUS UPLUS BITNOT. + +// Input is a single SQL command +%type cmd {SqliteQuery*} +%destructor cmd {delete $$;} + +input ::= cmdlist. + +cmdlist ::= cmdlist ecmd(C). {parserContext->addQuery(C); DONT_INHERIT_TOKENS("cmdlist");} +cmdlist ::= ecmd(C). {parserContext->addQuery(C);} + +%type ecmd {SqliteQuery*} +%destructor ecmd {delete $$;} +ecmd(X) ::= SEMI. {X = new SqliteEmptyQuery();} +ecmd(X) ::= explain(E) cmdx(C) SEMI. { + X = C; + X->explain = E->explain; + X->queryPlan = E->queryPlan; + delete E; + objectForTokens = X; + } + +%type explain {ParserStubExplain*} +%destructor explain {delete $$;} +explain(X) ::= . {X = new ParserStubExplain(false, false);} +explain(X) ::= EXPLAIN. {X = new ParserStubExplain(true, false);} + +%type cmdx {SqliteQuery*} +%destructor cmdx {delete $$;} +cmdx(X) ::= cmd(C). {X = C;} + +///////////////////// Begin and end transactions. //////////////////////////// + +cmd(X) ::= BEGIN trans_opt(TO) onconf(C). { + X = new SqliteBeginTrans( + TO->transactionKw, + TO->name, + *(C) + ); + delete TO; + delete C; + objectForTokens = X; + } + +%type trans_opt {ParserStubTransDetails*} +%destructor trans_opt {delete $$;} +trans_opt(X) ::= . {X = new ParserStubTransDetails();} +trans_opt(X) ::= TRANSACTION. { + X = new ParserStubTransDetails(); + X->transactionKw = true; + } +trans_opt(X) ::= TRANSACTION nm(N). { + X = new ParserStubTransDetails(); + X->transactionKw = true; + X->name = *(N); + delete N; + } +trans_opt ::= TRANSACTION ID_TRANS. {} + +cmd(X) ::= COMMIT trans_opt(T). { + X = new SqliteCommitTrans( + T->transactionKw, + T->name, + false + ); + delete T; + objectForTokens = X; + } +cmd(X) ::= END trans_opt(T). { + X = new SqliteCommitTrans( + T->transactionKw, + T->name, + true + ); + delete T; + objectForTokens = X; + } +cmd(X) ::= ROLLBACK trans_opt(T). { + X = new SqliteRollback( + T->transactionKw, + T->name + ); + delete T; + objectForTokens = X; + } + +///////////////////// The CREATE TABLE statement //////////////////////////// + +cmd(X) ::= CREATE temp(T) TABLE + fullname(N) + LP columnlist(CL) + conslist_opt(CS) RP. { + X = new SqliteCreateTable( + *(T), + false, + N->name1, + N->name2, + *(CL), + *(CS) + ); + delete T; + delete CL; + delete CS; + delete N; + objectForTokens = X; + } +cmd(X) ::= CREATE temp(T) TABLE + fullname(N) + AS select(S). { + X = new SqliteCreateTable( + *(T), + false, + N->name1, + N->name2, + S + ); + delete T; + delete N; + objectForTokens = X; + } +cmd ::= CREATE temp TABLE + nm DOT ID_TAB_NEW. {} +cmd ::= CREATE temp TABLE + ID_DB|ID_TAB_NEW. {} + +%type temp {int*} +%destructor temp {delete $$;} +temp(X) ::= TEMP(T). {X = new int( (T->value.length() > 4) ? 2 : 1 );} +temp(X) ::= . {X = new int(0);} + +%type columnlist {ParserCreateTableColumnList*} +%destructor columnlist {delete $$;} +columnlist(X) ::= columnlist(L) + COMMA column(C). { + L->append(C); + X = L; + DONT_INHERIT_TOKENS("columnlist"); + } +columnlist(X) ::= column(C). { + X = new ParserCreateTableColumnList(); + X->append(C); + } + +%type column {SqliteCreateTable::Column*} +%destructor column {delete $$;} +column(X) ::= columnid(C) type(T) + carglist(L). { + X = new SqliteCreateTable::Column(*(C), T, *(L)); + delete C; + delete L; + objectForTokens = X; + } + +%type columnid {QString*} +%destructor columnid {delete $$;} +columnid(X) ::= nm(N). {X = N;} +columnid ::= ID_COL_NEW. {} + +// An IDENTIFIER can be a generic identifier, or one of several +// keywords. Any non-standard keyword can also be an identifier. + +%type id {QString*} +%destructor id {delete $$;} +id(X) ::= ID(T). { + X = new QString( + stripObjName( + T->value, + parserContext->dialect + ) + ); + } + +// And "ids" is an identifer-or-string. + +%type ids {QString*} +%destructor ids {delete $$;} +ids(X) ::= ID|STRING(T). {X = new QString(T->value);} + +// The name of a column or table can be any of the following: + +%type nm {QString*} +%destructor nm {delete $$;} +nm(X) ::= id(N). {X = N;} +nm(X) ::= STRING(N). {X = new QString(stripString(N->value));} +nm(X) ::= JOIN_KW(N). {X = new QString(N->value);} + +%type type {SqliteColumnType*} +%destructor type {delete $$;} +type(X) ::= . {X = nullptr;} +type(X) ::= typetoken(T). {X = T;} + +%type typetoken {SqliteColumnType*} +%destructor typetoken {delete $$;} +typetoken(X) ::= typename(N). { + X = new SqliteColumnType(*(N)); + delete N; + objectForTokens = X; + } +typetoken(X) ::= typename(N) + LP signed(P) RP. { + X = new SqliteColumnType(*(N), *(P)); + delete N; + delete P; + objectForTokens = X; + } +typetoken(X) ::= typename(N) LP signed(P) + COMMA signed(S) RP. { + X = new SqliteColumnType(*(N), *(P), *(S)); + delete N; + delete P; + delete S; + objectForTokens = X; + } + +%type typename {QString*} +%destructor typename {delete $$;} +typename(X) ::= ids(I). {X = I;} +typename(X) ::= typename(T) ids(I). { + T->append(" " + *(I)); + delete I; + X = T; + } +typename ::= ID_COL_TYPE. {} + +%type signed {QVariant*} +%destructor signed {delete $$;} +signed(X) ::= plus_num(N). {X = N;} +signed(X) ::= minus_num(N). {X = N;} + +%type carglist {ParserCreateTableColumnConstraintList*} +%destructor carglist {delete $$;} +carglist(X) ::= carglist(L) ccons(C). { + L->append(C); + X = L; + DONT_INHERIT_TOKENS("carglist"); + } +carglist(X) ::= carglist(L) ccons_nm(N) + ccons(C). { + L->append(N); + L->append(C); + X = L; + DONT_INHERIT_TOKENS("carglist"); + } +carglist(X) ::= carglist(L) carg(C). { + L->append(C); + X = L; + DONT_INHERIT_TOKENS("carglist"); + } +carglist(X) ::= . {X = new ParserCreateTableColumnConstraintList();} + +%type carg {SqliteCreateTable::Column::Constraint*} +%destructor carg {delete $$;} +carg(X) ::= DEFAULT STRING(S). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefId(stripObjName( + S->value, + parserContext->dialect + )); + objectForTokens = X; + } +carg(X) ::= DEFAULT ID(I). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefId(stripObjName( + I->value, + parserContext->dialect + )); + objectForTokens = X; + + } +carg(X) ::= DEFAULT INTEGER(I). { + X = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(I->value).toLongLong(); + X->initDefTerm(val, false); + objectForTokens = X; + } +carg(X) ::= DEFAULT PLUS INTEGER(I). { + X = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(I->value).toLongLong(); + X->initDefTerm(val, false); + objectForTokens = X; + } +carg(X) ::= DEFAULT MINUS INTEGER(I). { + X = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(I->value).toLongLong(); + X->initDefTerm(val, true); + objectForTokens = X; + } +carg(X) ::= DEFAULT FLOAT(F). { + X = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(F->value).toDouble(); + X->initDefTerm(val, false); + objectForTokens = X; + } +carg(X) ::= DEFAULT PLUS FLOAT(F). { + X = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(F->value).toDouble(); + X->initDefTerm(val, false); + objectForTokens = X; + } +carg(X) ::= DEFAULT MINUS FLOAT(F). { + X = new SqliteCreateTable::Column::Constraint(); + QVariant val = QVariant(F->value).toDouble(); + X->initDefTerm(val, true); + objectForTokens = X; + } +carg(X) ::= DEFAULT NULL. { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefTerm(QVariant(), false); + objectForTokens = X; + } + +// In addition to the type name, we also care about the primary key and +// UNIQUE constraints. + +%type ccons_nm {SqliteCreateTable::Column::Constraint*} +%destructor ccons_nm {delete $$;} +ccons_nm(X) ::= CONSTRAINT nm(N). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefNameOnly(*(N)); + delete N; + objectForTokens = X; + } + +%type ccons {SqliteCreateTable::Column::Constraint*} +%destructor ccons {delete $$;} +ccons(X) ::= NULL onconf(C). { + X = new SqliteCreateTable::Column::Constraint(); + X->initNull(*(C)); + delete C; + objectForTokens = X; + } +ccons(X) ::= NOT NULL onconf(C). { + X = new SqliteCreateTable::Column::Constraint(); + X->initNotNull(*(C)); + delete C; + objectForTokens = X; + } +ccons(X) ::= PRIMARY KEY sortorder(O) + onconf(C). { + X = new SqliteCreateTable::Column::Constraint(); + X->initPk(*(O), *(C), false); + delete O; + delete C; + objectForTokens = X; + } +ccons(X) ::= UNIQUE onconf(C). { + X = new SqliteCreateTable::Column::Constraint(); + X->initUnique(*(C)); + delete C; + objectForTokens = X; + } +ccons(X) ::= CHECK LP expr(E) RP onconf(C). { + X = new SqliteCreateTable::Column::Constraint(); + X->initCheck(E, *(C)); + delete C; + objectForTokens = X; + } +ccons(X) ::= REFERENCES nm(N) + idxlist_opt(I) refargs(A). { + X = new SqliteCreateTable::Column::Constraint(); + X->initFk(*(N), *(I), *(A)); + delete N; + delete A; + delete I; + objectForTokens = X; + } +ccons(X) ::= defer_subclause(D). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefer(D->initially, D->deferrable); + delete D; + objectForTokens = X; + } +ccons(X) ::= COLLATE id(I). { + X = new SqliteCreateTable::Column::Constraint(); + X->initColl(*(I)); + delete I; + objectForTokens = X; + } +ccons(X) ::= CHECK LP RP. { + X = new SqliteCreateTable::Column::Constraint(); + X->initCheck(); + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +// The next group of rules parses the arguments to a REFERENCES clause +// that determine if the referential integrity checking is deferred or +// or immediate and which determine what action to take if a ref-integ +// check fails. + +%type refargs {ParserFkConditionList*} +%destructor refargs {delete $$;} +refargs(X) ::= . {X = new ParserFkConditionList();} +refargs(X) ::= refargs(L) refarg(A). { + L->append(A); + X = L; + DONT_INHERIT_TOKENS("refargs"); + } + +%type refarg {SqliteForeignKey::Condition*} +%destructor refarg {delete $$;} +refarg(X) ::= MATCH nm(N). { + X = new SqliteForeignKey::Condition(*(N)); + delete N; + } +refarg(X) ::= ON INSERT refact(R). {X = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::INSERT, *(R)); delete R;} +refarg(X) ::= ON DELETE refact(R). {X = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::DELETE, *(R)); delete R;} +refarg(X) ::= ON UPDATE refact(R). {X = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::UPDATE, *(R)); delete R;} +refarg ::= MATCH ID_FK_MATCH. {} + +%type refact {SqliteForeignKey::Condition::Reaction*} +%destructor refact {delete $$;} +refact(X) ::= SET NULL. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::SET_NULL);} +refact(X) ::= SET DEFAULT. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::SET_DEFAULT);} +refact(X) ::= CASCADE. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::CASCADE);} +refact(X) ::= RESTRICT. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::RESTRICT);} + +%type defer_subclause {ParserDeferSubClause*} +%destructor defer_subclause {delete $$;} +defer_subclause(X) ::= NOT DEFERRABLE + init_deferred_pred_opt(I). { + X = new ParserDeferSubClause(SqliteDeferrable::NOT_DEFERRABLE, *(I)); + delete I; + } +defer_subclause(X) ::= DEFERRABLE + init_deferred_pred_opt(I). { + X = new ParserDeferSubClause(SqliteDeferrable::DEFERRABLE, *(I)); + delete I; + } + +%type init_deferred_pred_opt {SqliteInitially*} +%destructor init_deferred_pred_opt {delete $$;} +init_deferred_pred_opt(X) ::= . {X = new SqliteInitially(SqliteInitially::null);} +init_deferred_pred_opt(X) ::= INITIALLY + DEFERRED. {X = new SqliteInitially(SqliteInitially::DEFERRED);} +init_deferred_pred_opt(X) ::= INITIALLY + IMMEDIATE. {X = new SqliteInitially(SqliteInitially::IMMEDIATE);} + + +%type conslist_opt {ParserCreateTableConstraintList*} +%destructor conslist_opt {delete $$;} +conslist_opt(X) ::= . {X = new ParserCreateTableConstraintList();} +conslist_opt(X) ::= COMMA conslist(L). {X = L;} + +%type conslist {ParserCreateTableConstraintList*} +%destructor conslist {delete $$;} +conslist(X) ::= conslist(L) tconscomma(CM) + tcons(C). { + C->afterComma = *(CM); + L->append(C); + X = L; + delete CM; + DONT_INHERIT_TOKENS("conslist"); + } +conslist(X) ::= tcons(C). { + X = new ParserCreateTableConstraintList(); + X->append(C); + } + +%type tconscomma {bool*} +%destructor tconscomma {delete $$;} +tconscomma(X) ::= COMMA. {X = new bool(true);} +tconscomma(X) ::= . {X = new bool(false);} + +%type tcons {SqliteCreateTable::Constraint*} +%destructor tcons {delete $$;} +tcons(X) ::= CONSTRAINT nm(N). { + X = new SqliteCreateTable::Constraint(); + X->initNameOnly(*(N)); + delete N; + objectForTokens = X; + } +tcons(X) ::= PRIMARY KEY LP idxlist(L) + RP onconf(C). { + X = new SqliteCreateTable::Constraint(); + X->initPk(*(L), false, *(C)); + delete C; + delete L; + objectForTokens = X; + } +tcons(X) ::= UNIQUE LP idxlist(L) RP + onconf(C). { + X = new SqliteCreateTable::Constraint(); + X->initUnique(*(L), *(C)); + delete C; + delete L; + objectForTokens = X; + } +tcons(X) ::= CHECK LP expr(E) RP onconf(C). { + X = new SqliteCreateTable::Constraint(); + X->initCheck(E, *(C)); + objectForTokens = X; + } +tcons(X) ::= FOREIGN KEY LP idxlist(L) RP + REFERENCES nm(N) idxlist_opt(IL) + refargs(R) defer_subclause_opt(D). { + X = new SqliteCreateTable::Constraint(); + X->initFk( + *(L), + *(N), + *(IL), + *(R), + D->initially, + D->deferrable + ); + delete N; + delete R; + delete D; + delete IL; + delete L; + objectForTokens = X; + } + +tcons ::= CONSTRAINT ID_CONSTR. {} +tcons ::= FOREIGN KEY LP idxlist RP + REFERENCES ID_TAB. {} +tcons(X) ::= CHECK LP RP onconf. { + X = new SqliteCreateTable::Constraint(); + X->initCheck(); + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +%type defer_subclause_opt {ParserDeferSubClause*} +%destructor defer_subclause_opt {delete $$;} +defer_subclause_opt(X) ::= . {X = new ParserDeferSubClause(SqliteDeferrable::null, SqliteInitially::null);} +defer_subclause_opt(X) ::= + defer_subclause(D). {X = D;} + +// The following is a non-standard extension that allows us to declare the +// default behavior when there is a constraint conflict. +%type onconf {SqliteConflictAlgo*} +%destructor onconf {delete $$;} +onconf(X) ::= . {X = new SqliteConflictAlgo(SqliteConflictAlgo::null);} +onconf(X) ::= ON CONFLICT resolvetype(R). {X = R;} + +%type orconf {SqliteConflictAlgo*} +%destructor orconf {delete $$;} +orconf(X) ::= . {X = new SqliteConflictAlgo(SqliteConflictAlgo::null);} +orconf(X) ::= OR resolvetype(R). {X = R;} + +%type resolvetype {SqliteConflictAlgo*} +%destructor resolvetype {delete $$;} +resolvetype(X) ::= ROLLBACK(V). {X = new SqliteConflictAlgo(sqliteConflictAlgo(V->value));} +resolvetype(X) ::= ABORT(V). {X = new SqliteConflictAlgo(sqliteConflictAlgo(V->value));} +resolvetype(X) ::= FAIL(V). {X = new SqliteConflictAlgo(sqliteConflictAlgo(V->value));} +resolvetype(X) ::= IGNORE(V). {X = new SqliteConflictAlgo(sqliteConflictAlgo(V->value));} +resolvetype(X) ::= REPLACE(V). {X = new SqliteConflictAlgo(sqliteConflictAlgo(V->value));} + +////////////////////////// The DROP TABLE ///////////////////////////////////// + +cmd(X) ::= DROP TABLE fullname(N). { + X = new SqliteDropTable(false, N->name1, N->name2); + delete N; + objectForTokens = X; + } + +cmd ::= DROP TABLE nm DOT + ID_TAB. {} +cmd ::= DROP TABLE ID_DB|ID_TAB. {} + +///////////////////// The CREATE VIEW statement ///////////////////////////// + +cmd(X) ::= CREATE temp(T) VIEW + nm(N) + AS select(S). { + X = new SqliteCreateView(*(T), false, *(N), QString::null, S); + delete T; + delete N; + objectForTokens = X; + } + +cmd ::= CREATE temp VIEW ID_VIEW_NEW. {} + +cmd(X) ::= DROP VIEW nm(N). { + X = new SqliteDropView(false, *(N), QString::null); + delete N; + objectForTokens = X; + } + +cmd ::= DROP VIEW ID_VIEW. {} + +//////////////////////// The SELECT statement ///////////////////////////////// + +cmd(X) ::= select_stmt(S). { + X = S; + objectForTokens = X; + } + +%type select_stmt {SqliteQuery*} +%destructor select_stmt {delete $$;} +select_stmt(X) ::= select(S). { + X = S; + // since it's used in trigger: + objectForTokens = X; + } + +%type select {SqliteSelect*} +%destructor select {delete $$;} +select(X) ::= oneselect(S). { + X = SqliteSelect::append(S); + objectForTokens = X; + } +select(X) ::= select(S1) multiselect_op(O) + oneselect(S2). { + X = SqliteSelect::append(S1, *(O), S2); + delete O; + objectForTokens = X; + } + +%type multiselect_op {SqliteSelect::CompoundOperator*} +%destructor multiselect_op {delete $$;} +multiselect_op(X) ::= UNION. {X = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::UNION);} +multiselect_op(X) ::= UNION ALL. {X = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::UNION_ALL);} +multiselect_op(X) ::= EXCEPT. {X = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::EXCEPT);} +multiselect_op(X) ::= INTERSECT. {X = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::INTERSECT);} + +%type oneselect {SqliteSelect::Core*} +%destructor oneselect {delete $$;} +oneselect(X) ::= SELECT distinct(D) + selcollist(L) from(F) + where_opt(W) groupby_opt(G) + having_opt(H) + orderby_opt(O) + limit_opt(LI). { + X = new SqliteSelect::Core( + *(D), + *(L), + F, + W, + *(G), + H, + *(O), + LI + ); + delete L; + delete D; + delete G; + delete O; + objectForTokens = X; + } + +%type distinct {int*} +%destructor distinct {delete $$;} +distinct(X) ::= DISTINCT. {X = new int(1);} +distinct(X) ::= ALL. {X = new int(2);} +distinct(X) ::= . {X = new int(0);} + +// selcollist is a list of expressions that are to become the return +// values of the SELECT statement. The "*" in statements like +// "SELECT * FROM ..." is encoded as a special expression with an +// opcode of TK_ALL. + +%type sclp {ParserResultColumnList*} +%destructor sclp {delete $$;} +sclp(X) ::= selcollist(L) COMMA. {X = L;} +sclp(X) ::= . {X = new ParserResultColumnList();} + +%type selcollist {ParserResultColumnList*} +%destructor selcollist {delete $$;} +selcollist(X) ::= sclp(L) expr(E) as(N). { + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn( + E, + N ? N->asKw : false, + N ? N->name : QString::null + ); + + L->append(obj); + X = L; + delete N; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } +selcollist(X) ::= sclp(L) STAR. { + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn(true); + + L->append(obj); + X = L; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } +selcollist(X) ::= sclp(L) nm(N) DOT STAR. { + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn( + true, + *(N) + ); + L->append(obj); + X = L; + delete N; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } +selcollist(X) ::= sclp(L). { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = L; + } +selcollist ::= sclp ID_TAB DOT STAR. {} + +// An option "AS <id>" phrase that can follow one of the expressions that +// define the result set, or one of the tables in the FROM clause. + +%type as {ParserStubAlias*} +%destructor as {delete $$;} +as(X) ::= AS nm(N). { + X = new ParserStubAlias(*(N), true); + delete N; + } +as(X) ::= ids(N). { + X = new ParserStubAlias(*(N), false); + delete N; + } +as ::= AS ID_ALIAS. {} +as ::= ID_ALIAS. {} +as(X) ::= . {X = nullptr;} + +// A complete FROM clause. +%type from {SqliteSelect::Core::JoinSource*} +%destructor from {delete $$;} +from(X) ::= . {X = nullptr;} +from(X) ::= FROM joinsrc(L). {X = L;} + +%type joinsrc {SqliteSelect::Core::JoinSource*} +%destructor joinsrc {delete $$;} +joinsrc(X) ::= singlesrc(S) seltablist(L). { + X = new SqliteSelect::Core::JoinSource( + S, + *(L) + ); + delete L; + objectForTokens = X; + } +joinsrc(X) ::= . { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteSelect::Core::JoinSource(); + objectForTokens = X; + } + +%type seltablist {ParserOtherSourceList*} +%destructor seltablist {delete $$;} +seltablist(X) ::= seltablist(L) joinop(O) + singlesrc(S) + joinconstr_opt(C). { + SqliteSelect::Core::JoinSourceOther* src = + new SqliteSelect::Core::JoinSourceOther(O, S, C); + + L->append(src); + X = L; + objectForTokens = src; + DONT_INHERIT_TOKENS("seltablist"); + } +seltablist(X) ::= . { + X = new ParserOtherSourceList(); + } + +%type singlesrc {SqliteSelect::Core::SingleSource*} +%destructor singlesrc {delete $$;} +singlesrc(X) ::= nm(N1) dbnm(N2) as(A). { + X = new SqliteSelect::Core::SingleSource( + *(N1), + *(N2), + A ? A->asKw : false, + A ? A->name : QString::null, + false, + QString::null + ); + delete N1; + delete N2; + delete A; + objectForTokens = X; + } +singlesrc(X) ::= LP select(S) RP as(A). { + X = new SqliteSelect::Core::SingleSource( + S, + A ? A->asKw : false, + A ? A->name : QString::null + ); + delete A; + objectForTokens = X; + } +singlesrc(X) ::= LP joinsrc(J) RP as(A). { + X = new SqliteSelect::Core::SingleSource( + J, + A ? A->asKw : false, + A ? A->name : QString::null + ); + delete A; + objectForTokens = X; + } +singlesrc(X) ::= . { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteSelect::Core::SingleSource(); + objectForTokens = X; + } +singlesrc(X) ::= nm(N) DOT. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteSelect::Core::SingleSource(); + X->database = *(N); + delete N; + objectForTokens = X; + } + +singlesrc ::= nm DOT ID_TAB. {} +singlesrc ::= ID_DB|ID_TAB. {} +singlesrc ::= nm DOT ID_VIEW. {} +singlesrc ::= ID_DB|ID_VIEW. {} + +%type joinconstr_opt {SqliteSelect::Core::JoinConstraint*} +%destructor joinconstr_opt {delete $$;} +joinconstr_opt(X) ::= ON expr(E). { + X = new SqliteSelect::Core::JoinConstraint(E); + objectForTokens = X; + } +joinconstr_opt(X) ::= USING LP + inscollist(L) RP. { + X = new SqliteSelect::Core::JoinConstraint(*(L)); + delete L; + objectForTokens = X; + } +joinconstr_opt(X) ::= . {X = nullptr;} + +%type dbnm {QString*} +%destructor dbnm {delete $$;} +dbnm(X) ::= . {X = new QString();} +dbnm(X) ::= DOT nm(N). {X = N;} + +%type fullname {ParserFullName*} +%destructor fullname {delete $$;} +fullname(X) ::= nm(N1) dbnm(N2). { + X = new ParserFullName(); + X->name1 = *(N1); + X->name2 = *(N2); + delete N1; + delete N2; + } + +%type joinop {SqliteSelect::Core::JoinOp*} +%destructor joinop {delete $$;} +joinop(X) ::= COMMA. { + X = new SqliteSelect::Core::JoinOp(true); + objectForTokens = X; + } +joinop(X) ::= JOIN. { + X = new SqliteSelect::Core::JoinOp(false); + objectForTokens = X; + } +joinop(X) ::= JOIN_KW(K) JOIN. { + X = new SqliteSelect::Core::JoinOp(K->value); + objectForTokens = X; + } +joinop(X) ::= JOIN_KW(K) nm(N) JOIN. { + X = new SqliteSelect::Core::JoinOp(K->value, *(N)); + delete N; + objectForTokens = X; + } +joinop(X) ::= JOIN_KW(K) nm(N1) nm(N2) + JOIN. { + X = new SqliteSelect::Core::JoinOp(K->value, *(N1), *(N2)); + delete N1; + delete N1; + objectForTokens = X; + } + +joinop ::= ID_JOIN_OPTS. {} + +%type orderby_opt {ParserOrderByList*} +%destructor orderby_opt {delete $$;} +orderby_opt(X) ::= . {X = new ParserOrderByList();} +orderby_opt(X) ::= ORDER BY sortlist(L). {X = L;} + +%type sortlist {ParserOrderByList*} +%destructor sortlist {delete $$;} +sortlist(X) ::= sortlist(L) COMMA + collate(C) + expr(E) sortorder(O). { + SqliteOrderBy* obj; + if (C) + { + SqliteExpr* coll = new SqliteExpr(); + coll->initCollate(E, *(C)); + delete C; + obj = new SqliteOrderBy(coll, *(O)); + } + else + { + obj = new SqliteOrderBy(E, *(O)); + } + L->append(obj); + X = L; + delete O; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sortlist"); + } +sortlist(X) ::= expr(E) collate(C) + sortorder(O). { + SqliteOrderBy* obj; + if (C) + { + SqliteExpr* coll = new SqliteExpr(); + coll->initCollate(E, *(C)); + delete C; + obj = new SqliteOrderBy(coll, *(O)); + } + else + { + obj = new SqliteOrderBy(E, *(O)); + } + X = new ParserOrderByList(); + X->append(obj); + delete O; + objectForTokens = obj; + } + +%type collate {QString*} +%destructor collate {if ($$) delete $$;} +collate(X) ::= . {X = nullptr;} +collate(X) ::= COLLATE id(I). {X = I;} + +%type sortorder {SqliteSortOrder*} +%destructor sortorder {delete $$;} +sortorder(X) ::= ASC. {X = new SqliteSortOrder(SqliteSortOrder::ASC);} +sortorder(X) ::= DESC. {X = new SqliteSortOrder(SqliteSortOrder::DESC);} +sortorder(X) ::= . {X = new SqliteSortOrder(SqliteSortOrder::null);} + +%type groupby_opt {ParserExprList*} +%destructor groupby_opt {delete $$;} +groupby_opt(X) ::= . {X = new ParserExprList();} +groupby_opt(X) ::= GROUP BY nexprlist(L). {X = L;} +groupby_opt(X) ::= GROUP BY. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new ParserExprList(); + } +%type having_opt {SqliteExpr*} +%destructor having_opt {delete $$;} +having_opt(X) ::= . {X = nullptr;} +having_opt(X) ::= HAVING expr(E). {X = E;} + +%type limit_opt {SqliteLimit*} +%destructor limit_opt {delete $$;} +limit_opt(X) ::= . {X = nullptr;} +limit_opt(X) ::= LIMIT signed(V). { + X = new SqliteLimit(*(V)); + delete V; + objectForTokens = X; + } +limit_opt(X) ::= LIMIT signed(V1) OFFSET + signed(V2). { + SqliteExpr* expr1 = new SqliteExpr(); + expr1->initLiteral(*(V1)); + expr1->setParent(X); + + SqliteExpr* expr2 = new SqliteExpr(); + expr1->initLiteral(*(V2)); + expr1->setParent(X); + + X = new SqliteLimit(expr1, expr2, true); + + TokenPtr limitToken = TokenPtr::create(Token::INTEGER, V1->toString()); + parserContext->addManagedToken(limitToken); + expr1->tokens << limitToken; + expr1->tokensMap["term"] << limitToken; + + TokenPtr offsetToken = TokenPtr::create(Token::INTEGER, V2->toString()); + parserContext->addManagedToken(offsetToken); + expr2->tokens << offsetToken; + expr2->tokensMap["term"] << offsetToken; + + delete V1; + delete V2; + objectForTokens = X; + } +limit_opt(X) ::= LIMIT signed(V1) COMMA + signed(V2). { + SqliteExpr* expr1 = new SqliteExpr(); + expr1->initLiteral(*(V1)); + expr1->setParent(X); + + SqliteExpr* expr2 = new SqliteExpr(); + expr1->initLiteral(*(V2)); + expr1->setParent(X); + + X = new SqliteLimit(expr1, expr2, false); + + TokenPtr limitToken = TokenPtr::create(Token::INTEGER, V1->toString()); + parserContext->addManagedToken(limitToken); + expr1->tokens << limitToken; + expr1->tokensMap["term"] << limitToken; + + TokenPtr offsetToken = TokenPtr::create(Token::INTEGER, V2->toString()); + parserContext->addManagedToken(offsetToken); + expr2->tokens << offsetToken; + expr2->tokensMap["term"] << offsetToken; + + delete V1; + delete V2; + objectForTokens = X; + } + +/////////////////////////// The DELETE statement ///////////////////////////// + +cmd(X) ::= delete_stmt(S). { + X = S; + objectForTokens = X; + } + +%type delete_stmt {SqliteQuery*} +%destructor delete_stmt {delete $$;} +delete_stmt(X) ::= DELETE FROM fullname(N) + where_opt(W). { + X = new SqliteDelete( + N->name1, + N->name2, + false, + W, + nullptr + ); + delete N; + // since it's used in trigger: + objectForTokens = X; + } + +delete_stmt(X) ::= DELETE FROM. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteDelete* q = new SqliteDelete(); + X = q; + objectForTokens = X; + } +delete_stmt(X) ::= DELETE FROM nm(N) DOT. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteDelete* q = new SqliteDelete(); + q->database = *(N); + X = q; + objectForTokens = X; + delete N; + } +delete_stmt ::= DELETE FROM nm DOT ID_TAB. {} +delete_stmt ::= DELETE FROM ID_DB|ID_TAB. {} + +%type where_opt {SqliteExpr*} +%destructor where_opt {delete $$;} +where_opt(X) ::= . {X = nullptr;} +where_opt(X) ::= WHERE expr(E). {X = E;} +where_opt(X) ::= WHERE. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteExpr(); + } + + +////////////////////////// The UPDATE command //////////////////////////////// + +cmd(X) ::= update_stmt(S). { + X = S; + objectForTokens = X; + } + +%type update_stmt {SqliteQuery*} +%destructor update_stmt {delete $$;} +update_stmt(X) ::= UPDATE orconf(C) + fullname(N) SET + setlist(L) where_opt(W). { + X = new SqliteUpdate( + *(C), + N->name1, + N->name2, + false, + QString::null, + *(L), + W, + nullptr + ); + delete C; + delete N; + delete L; + // since it's used in trigger: + objectForTokens = X; + } + +update_stmt(X) ::= UPDATE + orconf(C). { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteUpdate(); + objectForTokens = X; + delete C; + } +update_stmt(X) ::= UPDATE + orconf(C) nm(N) DOT. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteUpdate* q = new SqliteUpdate(); + q->database = *(N); + X = q; + objectForTokens = X; + delete C; + delete N; + } +update_stmt ::= UPDATE orconf nm DOT + ID_TAB. {} +update_stmt ::= UPDATE orconf ID_DB|ID_TAB. {} + +%type setlist {ParserSetValueList*} +%destructor setlist {delete $$;} +setlist(X) ::= setlist(L) COMMA nm(N) EQ + expr(E). { + L->append(ParserSetValue(*(N), E)); + X = L; + delete N; + DONT_INHERIT_TOKENS("setlist"); + } +setlist(X) ::= nm(N) EQ expr(E). { + X = new ParserSetValueList(); + X->append(ParserSetValue(*(N), E)); + delete N; + } +setlist(X) ::= . { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new ParserSetValueList(); + } +setlist(X) ::= setlist(L) COMMA. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = L; + } + +setlist ::= setlist COMMA ID_COL. {} +setlist ::= ID_COL. {} + +////////////////////////// The INSERT command ///////////////////////////////// + +cmd(X) ::= insert_stmt(S). { + X = S; + objectForTokens = X; + } + +%type insert_stmt {SqliteQuery*} +%destructor insert_stmt {delete $$;} +insert_stmt(X) ::= insert_cmd(C) INTO + fullname(N) inscollist_opt(I) + VALUES LP exprlist(L) RP. { + X = new SqliteInsert( + C->replace, + C->orConflict, + N->name1, + N->name2, + *(I), + *(L), + nullptr + ); + delete N; + delete C; + delete L; + delete I; + // since it's used in trigger: + objectForTokens = X; + } +insert_stmt(X) ::= insert_cmd(C) INTO + fullname(N) inscollist_opt(I) + select(S). { + X = new SqliteInsert( + C->replace, + C->orConflict, + N->name1, + N->name2, + *(I), + S, + nullptr + ); + delete N; + delete C; + delete I; + // since it's used in trigger: + objectForTokens = X; + } + +insert_stmt(X) ::= insert_cmd(C) INTO. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteInsert* q = new SqliteInsert(); + q->replaceKw = C->replace; + q->onConflict = C->orConflict; + X = q; + objectForTokens = X; + delete C; + } +insert_stmt(X) ::= insert_cmd(C) INTO nm(N) + DOT. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteInsert* q = new SqliteInsert(); + q->replaceKw = C->replace; + q->onConflict = C->orConflict; + q->database = *(N); + X = q; + objectForTokens = X; + delete C; + delete N; + } +insert_stmt ::= insert_cmd INTO + ID_DB|ID_TAB. {} +insert_stmt ::= insert_cmd INTO + nm DOT ID_TAB. {} + +%type insert_cmd {ParserStubInsertOrReplace*} +%destructor insert_cmd {delete $$;} +insert_cmd(X) ::= INSERT orconf(C). { + X = new ParserStubInsertOrReplace(false, *(C)); + delete C; + } +insert_cmd(X) ::= REPLACE. {X = new ParserStubInsertOrReplace(true);} + +%type inscollist_opt {ParserStringList*} +%destructor inscollist_opt {delete $$;} +inscollist_opt(X) ::= . {X = new ParserStringList();} +inscollist_opt(X) ::= LP inscollist(L) RP. {X = L;} + +%type inscollist {ParserStringList*} +%destructor inscollist {delete $$;} +inscollist(X) ::= inscollist(L) COMMA + nm(N). { + L->append(*(N)); + X = L; + delete N; + DONT_INHERIT_TOKENS("inscollist"); + } +inscollist(X) ::= nm(N). { + X = new ParserStringList(); + X->append(*(N)); + delete N; + } +inscollist(X) ::= . { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new ParserStringList(); + } + +inscollist ::= inscollist COMMA ID_COL. {} +inscollist ::= ID_COL. {} + +/////////////////////////// Expression Processing ///////////////////////////// + +%type exprx {SqliteExpr*} +%destructor exprx {delete $$;} +exprx(X) ::= NULL. { + X = new SqliteExpr(); + X->initNull(); + objectForTokens = X; + } +exprx(X) ::= INTEGER(I). { + X = new SqliteExpr(); + QVariant val = QVariant(I->value).toLongLong(); + X->initLiteral(val); + objectForTokens = X; + } +exprx(X) ::= FLOAT(F). { + X = new SqliteExpr(); + QVariant val = QVariant(F->value).toDouble(); + X->initLiteral(val); + objectForTokens = X; + } +exprx(X) ::= STRING(S). { + X = new SqliteExpr(); + X->initLiteral(QVariant(S->value)); + objectForTokens = X; + } +exprx(X) ::= LP expr(E) RP. { + X = new SqliteExpr(); + X->initSubExpr(E); + objectForTokens = X; + } +exprx(X) ::= id(N). { + X = new SqliteExpr(); + X->initId(*(N)); + delete N; + objectForTokens = X; + } +exprx(X) ::= JOIN_KW(N). { + X = new SqliteExpr(); + X->initId(N->value); + objectForTokens = X; + } +exprx(X) ::= nm(N1) DOT nm(N2). { + X = new SqliteExpr(); + X->initId(*(N1), *(N2)); + delete N1; + delete N2; + objectForTokens = X; + } +exprx(X) ::= nm(N1) DOT nm(N2) DOT nm(N3). { + X = new SqliteExpr(); + X->initId(*(N1), *(N2), *(N3)); + delete N1; + delete N2; + delete N3; + objectForTokens = X; + } +exprx(X) ::= VARIABLE(V). { + X = new SqliteExpr(); + X->initBindParam(V->value); + objectForTokens = X; + } +exprx(X) ::= ID(I) LP exprlist(L) RP. { + X = new SqliteExpr(); + X->initFunction(I->value, false, *(L)); + delete L; + objectForTokens = X; + } +exprx(X) ::= ID(I) LP STAR RP. { + X = new SqliteExpr(); + X->initFunction(I->value, true); + objectForTokens = X; + } +exprx(X) ::= expr(E1) AND(O) expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) OR(O) expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) LT|GT|GE|LE(O) + expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) EQ|NE(O) expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) + BITAND|BITOR|LSHIFT|RSHIFT(O) + expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) PLUS|MINUS(O) + expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) STAR|SLASH|REM(O) + expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) CONCAT(O) expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) not_opt(N) likeop(L) + expr(E2). [LIKE_KW] { + X = new SqliteExpr(); + X->initLike(E1, *(N), *(L), E2); + delete N; + delete L; + objectForTokens = X; + } +exprx(X) ::= expr(E) ISNULL|NOTNULL(N). { + X = new SqliteExpr(); + X->initNull(E, N->value); + objectForTokens = X; + } +exprx(X) ::= expr(E) NOT NULL. { + X = new SqliteExpr(); + X->initNull(E, "NOT NULL"); + objectForTokens = X; + } +exprx(X) ::= expr(E1) IS not_opt(N) + expr(E2). { + X = new SqliteExpr(); + X->initIs(E1, *(N), E2); + delete N; + objectForTokens = X; + } +exprx(X) ::= NOT(O) expr(E). { + X = new SqliteExpr(); + X->initUnaryOp(E, O->value); + } +exprx(X) ::= BITNOT(O) expr(E). { + X = new SqliteExpr(); + X->initUnaryOp(E, O->value); + objectForTokens = X; + } +exprx(X) ::= MINUS(O) expr(E). [BITNOT] { + X = new SqliteExpr(); + X->initUnaryOp(E, O->value); + objectForTokens = X; + } +exprx(X) ::= PLUS(O) expr(E). [BITNOT] { + X = new SqliteExpr(); + X->initUnaryOp(E, O->value); + objectForTokens = X; + } +exprx(X) ::= expr(E1) not_opt(N) BETWEEN + expr(E2) AND + expr(E3). [BETWEEN] { + X = new SqliteExpr(); + X->initBetween(E1, *(N), E2, E3); + delete N; + objectForTokens = X; + } +exprx(X) ::= expr(E) not_opt(N) IN LP + exprlist(L) RP. [IN] { + X = new SqliteExpr(); + X->initIn(E, *(N), *(L)); + delete N; + delete L; + objectForTokens = X; + } +exprx(X) ::= expr(E) not_opt(N) IN LP + select(S) RP. [IN] { + X = new SqliteExpr(); + X->initIn(E, *(N), S); + delete N; + objectForTokens = X; + } +exprx(X) ::= expr(E) not_opt(N) IN nm(N1) + dbnm(N2). [IN] { + X = new SqliteExpr(); + X->initIn(E, N, *(N1), *(N2)); + delete N; + delete N1; + objectForTokens = X; + } +exprx(X) ::= LP select(S) RP. { + X = new SqliteExpr(); + X->initSubSelect(S); + objectForTokens = X; + } +exprx(X) ::= CASE case_operand(O) + case_exprlist(L) + case_else(E) END. { + X = new SqliteExpr(); + X->initCase(O, *(L), E); + delete L; + objectForTokens = X; + } +exprx(X) ::= RAISE LP raisetype(R) COMMA + nm(N) RP. { + X = new SqliteExpr(); + X->initRaise(R->value, *(N)); + delete N; + objectForTokens = X; + } +exprx(X) ::= RAISE LP IGNORE(R) RP. { + X = new SqliteExpr(); + X->initRaise(R->value); + objectForTokens = X; + } +exprx(X) ::= nm(N1) DOT. { + X = new SqliteExpr(); + X->initId(*(N1), QString::null, QString::null); + delete N1; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } +exprx(X) ::= nm(N1) DOT nm(N2) DOT. { + X = new SqliteExpr(); + X->initId(*(N1), *(N2), QString::null); + delete N1; + delete N2; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } +exprx(X) ::= expr(E1) not_opt(N) BETWEEN + expr(E2). [BETWEEN] { + X = new SqliteExpr(); + delete N; + delete E1; + delete E2; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } +exprx(X) ::= CASE case_operand(O) + case_exprlist(L) + case_else(E). { + X = new SqliteExpr(); + delete L; + delete O; + delete E; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } +exprx(X) ::= expr(E) not_opt(N) IN LP + exprlist(L). [IN] { + X = new SqliteExpr(); + delete N; + delete L; + delete E; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + +exprx ::= expr not_opt IN ID_DB. [IN] {} +exprx ::= expr not_opt IN nm DOT + ID_TAB. [IN] {} +exprx ::= ID_DB|ID_TAB|ID_COL|ID_FN. {} +exprx ::= nm DOT ID_TAB|ID_COL. {} +exprx ::= nm DOT nm DOT ID_COL. {} +exprx ::= RAISE LP raisetype COMMA + ID_ERR_MSG RP. {} + +%type expr {SqliteExpr*} +%destructor expr {delete $$;} +expr(X) ::= exprx(E). {X = E;} +expr(X) ::= . { + X = new SqliteExpr(); + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } +%type not_opt {bool*} +%destructor not_opt {delete $$;} +not_opt(X) ::= . {X = new bool(false);} +not_opt(X) ::= NOT. {X = new bool(true);} + +%type likeop {SqliteExpr::LikeOp*} +%destructor likeop {delete $$;} +likeop(X) ::= LIKE|GLOB(T). {X = new SqliteExpr::LikeOp(SqliteExpr::likeOp(T->value));} + +%type case_exprlist {ParserExprList*} +%destructor case_exprlist {delete $$;} +case_exprlist(X) ::= case_exprlist(L) WHEN + expr(E1) THEN expr(E2). { + L->append(E1); + L->append(E2); + X = L; + } +case_exprlist(X) ::= WHEN expr(E1) THEN + expr(E2). { + X = new ParserExprList(); + X->append(E1); + X->append(E2); + } + +%type case_else {SqliteExpr*} +%destructor case_else {delete $$;} +case_else(X) ::= ELSE expr(E). {X = E;} +case_else(X) ::= . {X = nullptr;} + +%type case_operand {SqliteExpr*} +%destructor case_operand {delete $$;} +case_operand(X) ::= exprx(E). {X = E;} +case_operand(X) ::= . {X = nullptr;} + +%type exprlist {ParserExprList*} +%destructor exprlist {delete $$;} +exprlist(X) ::= nexprlist(L). {X = L;} +exprlist(X) ::= . {X = new ParserExprList();} + +%type nexprlist {ParserExprList*} +%destructor nexprlist {delete $$;} +nexprlist(X) ::= nexprlist(L) COMMA + expr(E). { + L->append(E); + X = L; + DONT_INHERIT_TOKENS("nexprlist"); + } +nexprlist(X) ::= exprx(E). { + X = new ParserExprList(); + X->append(E); + } + +///////////////////////////// The CREATE INDEX command /////////////////////// + +cmd(X) ::= CREATE uniqueflag(U) INDEX + nm(N1) ON nm(N2) dbnm(N3) + LP idxlist(L) RP onconf(C). { + X = new SqliteCreateIndex( + *(U), + false, + *(N1), + *(N2), + *(N3), + *(L), + *(C) + ); + delete U; + delete N1; + delete N2; + delete N3; + delete L; + delete C; + objectForTokens = X; + } + +cmd ::= CREATE uniqueflag INDEX nm dbnm + ON ID_TAB. {} +cmd ::= CREATE uniqueflag INDEX nm DOT + ID_IDX_NEW. {} +cmd ::= CREATE uniqueflag INDEX + ID_DB|ID_IDX_NEW. {} + +%type uniqueflag {bool*} +%destructor uniqueflag {delete $$;} +uniqueflag(X) ::= UNIQUE. {X = new bool(true);} +uniqueflag(X) ::= . {X = new bool(false);} + +%type idxlist_opt {ParserIndexedColumnList*} +%destructor idxlist_opt {delete $$;} +idxlist_opt(X) ::= . {X = new ParserIndexedColumnList();} +idxlist_opt(X) ::= LP idxlist(I) RP. {X = I;} + +%type idxlist {ParserIndexedColumnList*} +%destructor idxlist {delete $$;} +idxlist(X) ::= idxlist(L) COMMA + idxlist_single(S). { + L->append(S); + X = L; + DONT_INHERIT_TOKENS("idxlist"); + } +idxlist(X) ::= idxlist_single(S). { + X = new ParserIndexedColumnList(); + X->append(S); + } + +%type idxlist_single {SqliteIndexedColumn*} +%destructor idxlist_single {delete $$;} +idxlist_single(X) ::= nm(N) sortorder(S). { + SqliteIndexedColumn* obj = + new SqliteIndexedColumn( + *(N), + QString::null, + *(S) + ); + X = obj; + delete S; + delete N; + objectForTokens = X; + } + +idxlist_single ::= ID_COL. {} + +///////////////////////////// The DROP INDEX command ///////////////////////// + +cmd(X) ::= DROP INDEX fullname(N). { + X = new SqliteDropIndex(false, N->name1, N->name2); + delete N; + objectForTokens = X; + } + +cmd ::= DROP INDEX nm DOT ID_IDX. {} +cmd ::= DROP INDEX ID_DB|ID_IDX. {} + +///////////////////////////// The COPY command /////////////////////////////// + +cmd(X) ::= COPY orconf(C) nm(N1) dbnm(N2) + FROM nm(N3) USING DELIMITERS + STRING(S). { + X = new SqliteCopy( + *(C), + *(N1), + *(N2), + *(N3), + S->value + ); + delete C; + delete N1; + delete N2; + delete N3; + objectForTokens = X; + } +cmd(X) ::= COPY orconf(C) nm(N1) dbnm(N2) + FROM nm(N3). { + X = new SqliteCopy( + *(C), + *(N1), + *(N2), + *(N3) + ); + delete C; + delete N1; + delete N2; + delete N3; + objectForTokens = X; + } + +///////////////////////////// The VACUUM command ///////////////////////////// + +cmd(X) ::= VACUUM. { + X = new SqliteVacuum(); + objectForTokens = X; + } +cmd(X) ::= VACUUM nm(N). { + X = new SqliteVacuum(*(N)); + delete N; + objectForTokens = X; + } + +///////////////////////////// The PRAGMA command ///////////////////////////// + +cmd(X) ::= PRAGMA ids(I). { + X = new SqlitePragma(*(I), QString::null); + delete I; + objectForTokens = X; + } + +cmd(X) ::= PRAGMA nm(N) EQ nmnum(V). { + X = new SqlitePragma(*(N), QString::null, *(V), true); + delete N; + delete V; + objectForTokens = X; + } +cmd(X) ::= PRAGMA nm(N) LP nmnum(V) RP. { + X = new SqlitePragma(*(N), QString::null, *(V), false); + delete N; + delete V; + objectForTokens = X; + } +cmd(X) ::= PRAGMA nm(N) EQ minus_num(V). { + X = new SqlitePragma(*(N), QString::null, *(V), true); + delete N; + delete V; + objectForTokens = X; + } +cmd(X) ::= PRAGMA nm(N) LP minus_num(V) RP. { + X = new SqlitePragma(*(N), QString::null, *(V), false); + delete N; + delete V; + objectForTokens = X; + } + +cmd ::= PRAGMA nm DOT ID_PRAGMA. {} +cmd ::= PRAGMA ID_DB|ID_PRAGMA. {} + +%type nmnum {QVariant*} +%destructor nmnum {delete $$;} +nmnum(X) ::= plus_num(N). {X = N;} +nmnum(X) ::= nm(N). { + X = new QVariant(*(N)); + delete N; + } +nmnum(X) ::= ON(T). {X = new QVariant(T->value);} +nmnum(X) ::= DELETE(T). {X = new QVariant(T->value);} +nmnum(X) ::= DEFAULT(T). {X = new QVariant(T->value);} + +%type plus_num {QVariant*} +%destructor plus_num {delete $$;} +plus_num(X) ::= PLUS number(N). {X = N;} +plus_num(X) ::= number(N). {X = N;} + +%type minus_num {QVariant*} +%destructor minus_num {delete $$;} +minus_num(X) ::= MINUS number(N). { + if (N->type() == QVariant::Double) + *(N) = -(N->toDouble()); + else if (N->type() == QVariant::LongLong) + *(N) = -(N->toLongLong()); + else + Q_ASSERT_X(true, "producing minus number", "QVariant is neither of Double or LongLong."); + + X = N; + } + +%type number {QVariant*} +%destructor number {delete $$;} +number(X) ::= INTEGER(N). {X = new QVariant(QVariant(N->value).toLongLong());} +number(X) ::= FLOAT(N). {X = new QVariant(QVariant(N->value).toDouble());} + +//////////////////////////// The CREATE TRIGGER command ///////////////////// + +cmd(X) ::= CREATE temp(T) TRIGGER + nm(N) trigger_time(TT) + trigger_event(EV) ON nm(N1) + dbnm(N2) foreach_clause(FC) + when_clause(WC) BEGIN + trigger_cmd_list(CL) END. { + X = new SqliteCreateTrigger( + *(T), + false, + *(N), + *(N1), + *(N2), + *(TT), + EV, + *(FC), + WC, + *(CL), + 2 + ); + delete T; + delete TT; + delete FC; + delete N1; + delete N; + delete N2; + delete CL; + objectForTokens = X; + } + +// Support full parsing when no BEGIN and END are present (for completion helper) +cmd(X) ::= CREATE temp(T) TRIGGER + nm(N) trigger_time(TT) + trigger_event(EV) ON nm(N1) + dbnm(N2) foreach_clause(FC) + when_clause(WC). { + QList<SqliteQuery *> CL; + + X = new SqliteCreateTrigger( + *(T), + false, + *(N), + *(N1), + *(N2), + *(TT), + EV, + *(FC), + WC, + CL, + 2 + ); + delete T; + delete TT; + delete FC; + delete N1; + delete N; + delete N2; + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +// Support full parsing when no END is present (for completion helper) +cmd(X) ::= CREATE temp(T) TRIGGER + nm(N) trigger_time(TT) + trigger_event(EV) ON nm(N1) + dbnm(N2) foreach_clause(FC) + when_clause(WC) BEGIN + trigger_cmd_list(CL). { + X = new SqliteCreateTrigger( + *(T), + false, + *(N), + *(N1), + *(N2), + *(TT), + EV, + *(FC), + WC, + *(CL), + 2 + ); + delete T; + delete TT; + delete FC; + delete N1; + delete N; + delete N2; + delete CL; + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +cmd ::= CREATE temp TRIGGER nm + trigger_time trigger_event + ON ID_TAB|ID_DB. {} +cmd ::= CREATE temp TRIGGER nm + trigger_time trigger_event + ON nm DOT ID_TAB. {} +cmd ::= CREATE temp TRIGGER ID_TRIG_NEW. {} + +%type trigger_time {SqliteCreateTrigger::Time*} +%destructor trigger_time {delete $$;} +trigger_time(X) ::= BEFORE. {X = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::BEFORE);} +trigger_time(X) ::= AFTER. {X = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::AFTER);} +trigger_time(X) ::= INSTEAD OF. {X = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::INSTEAD_OF);} +trigger_time(X) ::= . {X = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::null);} + +%type trigger_event {SqliteCreateTrigger::Event*} +%destructor trigger_event {delete $$;} +trigger_event(X) ::= DELETE. { + X = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::DELETE); + objectForTokens = X; + } +trigger_event(X) ::= INSERT. { + X = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::INSERT); + objectForTokens = X; + } +trigger_event(X) ::= UPDATE. { + X = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::UPDATE); + objectForTokens = X; + } +trigger_event(X) ::= UPDATE OF + inscollist(L). { + X = new SqliteCreateTrigger::Event(*(L)); + delete L; + objectForTokens = X; + } + +%type foreach_clause {SqliteCreateTrigger::Scope*} +%destructor foreach_clause {delete $$;} +foreach_clause(X) ::= . {X = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::null);} +foreach_clause(X) ::= FOR EACH ROW. {X = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::FOR_EACH_ROW);} +foreach_clause(X) ::= FOR EACH STATEMENT. {X = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::FOR_EACH_STATEMENT);} + +%type when_clause {SqliteExpr*} +%destructor when_clause {if ($$) delete $$;} +when_clause(X) ::= . {X = nullptr;} +when_clause(X) ::= WHEN expr(E). {X = E;} + +%type trigger_cmd_list {ParserQueryList*} +%destructor trigger_cmd_list {delete $$;} +trigger_cmd_list(X) ::= trigger_cmd_list(L) + trigger_cmd(C) SEMI. { + L->append(C); + X = L; + DONT_INHERIT_TOKENS("trigger_cmd_list"); + } +trigger_cmd_list(X) ::= trigger_cmd(C) + SEMI. { + X = new ParserQueryList(); + X->append(C); + } + +%type trigger_cmd {SqliteQuery*} +%destructor trigger_cmd {delete $$;} +trigger_cmd(X) ::= update_stmt(S). {X = S;} +trigger_cmd(X) ::= insert_stmt(S). {X = S;} +trigger_cmd(X) ::= delete_stmt(S). {X = S;} +trigger_cmd(X) ::= select_stmt(S). {X = S;} + +%type raisetype {Token*} +raisetype(X) ::= ROLLBACK|ABORT|FAIL(V). {X = V;} + +//////////////////////// DROP TRIGGER statement ////////////////////////////// +cmd(X) ::= DROP TRIGGER fullname(N). { + X = new SqliteDropTrigger(false, N->name1, N->name2); + delete N; + objectForTokens = X; + } + +cmd ::= DROP TRIGGER nm DOT ID_TRIG. {} +cmd ::= DROP TRIGGER ID_DB|ID_TRIG. {} + +//////////////////////// ATTACH DATABASE file AS name ///////////////////////// +cmd(X) ::= ATTACH database_kw_opt(D) + ids(I1) AS ids(I2) key_opt(K). { + SqliteExpr* e1 = new SqliteExpr(); + SqliteExpr* e2 = new SqliteExpr(); + e1->initLiteral(*(I1)); + e2->initLiteral(*(I2)); + X = new SqliteAttach(*(D), e1, e2, K); + delete D; + delete I1; + delete I2; + objectForTokens = X; + } + +%type key_opt {SqliteExpr*} +%destructor key_opt {if ($$) delete $$;} +key_opt(X) ::= . {X = nullptr;} +key_opt(X) ::= USING ids(I). { + SqliteExpr* e = new SqliteExpr(); + e->initLiteral(*(I)); + delete I; + X = e; + } + +%type database_kw_opt {bool*} +%destructor database_kw_opt {delete $$;} +database_kw_opt(X) ::= DATABASE. {X = new bool(true);} +database_kw_opt(X) ::= . {X = new bool(false);} + + +//////////////////////// DETACH DATABASE name ///////////////////////////////// +cmd(X) ::= DETACH database_kw_opt(D) + nm(N). { + SqliteExpr* e = new SqliteExpr(); + e->initLiteral(*(N)); + delete N; + X = new SqliteDetach(*(D), e); + delete D; + objectForTokens = X; + } diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.cpp new file mode 100644 index 0000000..3bdd9a4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.cpp @@ -0,0 +1,5262 @@ +/* Driver template for the LEMON parser generator. +** The author disclaims copyright to this source code. +** +** This version of "lempar.c" is modified, slightly, for use by SQLite. +** The only modifications are the addition of a couple of NEVER() +** macros to disable tests that are needed in the case of a general +** LALR(1) grammar but which are always false in the +** specific grammar used by SQLite. +*/ +/* First off, code is included that follows the "include" declaration +** in the input grammar file. */ +#include <stdio.h> + +#include "token.h" +#include "parsercontext.h" +#include "parser_helper_stubs.h" +#include "common/utils_sql.h" +#include "parser/ast/sqlitealtertable.h" +#include "parser/ast/sqliteanalyze.h" +#include "parser/ast/sqliteattach.h" +#include "parser/ast/sqlitebegintrans.h" +#include "parser/ast/sqlitecommittrans.h" +#include "parser/ast/sqlitecopy.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqlitecreatevirtualtable.h" +#include "parser/ast/sqlitedelete.h" +#include "parser/ast/sqlitedetach.h" +#include "parser/ast/sqlitedropindex.h" +#include "parser/ast/sqlitedroptable.h" +#include "parser/ast/sqlitedroptrigger.h" +#include "parser/ast/sqlitedropview.h" +#include "parser/ast/sqliteemptyquery.h" +#include "parser/ast/sqliteinsert.h" +#include "parser/ast/sqlitepragma.h" +#include "parser/ast/sqlitereindex.h" +#include "parser/ast/sqliterelease.h" +#include "parser/ast/sqliterollback.h" +#include "parser/ast/sqlitesavepoint.h" +#include "parser/ast/sqliteselect.h" +#include "parser/ast/sqliteupdate.h" +#include "parser/ast/sqlitevacuum.h" +#include "parser/ast/sqliteexpr.h" +#include "parser/ast/sqlitecolumntype.h" +#include "parser/ast/sqliteconflictalgo.h" +#include "parser/ast/sqlitesortorder.h" +#include "parser/ast/sqliteindexedcolumn.h" +#include "parser/ast/sqliteforeignkey.h" +#include "parser/ast/sqlitewith.h" +#include <QObject> +#include <QDebug> + +#define assert(X) Q_ASSERT(X) +#define UNUSED_PARAMETER(X) (void)(X) +#define DONT_INHERIT_TOKENS(X) noTokenInheritanceFields << X +/* Next is all token values, in a form suitable for use by makeheaders. +** This section will be null unless lemon is run with the -m switch. +*/ +/* +** These constants (all generated automatically by the parser generator) +** specify the various kinds of tokens (terminals) that the parser +** understands. +** +** Each symbol here is a terminal symbol in the grammar. +*/ +/* Make sure the INTERFACE macro is defined. +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/* The next thing included is series of defines which control +** various aspects of the generated parser. +** YYCODETYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 terminals +** and nonterminals. "int" is used otherwise. +** YYNOCODE is a number of type YYCODETYPE which corresponds +** to no legal terminal or nonterminal number. This +** number is used to fill in empty slots of the hash +** table. +** YYFALLBACK If defined, this indicates that one or more tokens +** have fall-back values which should be used if the +** original value of the token will not parse. +** YYACTIONTYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 rules and +** states combined. "int" is used otherwise. +** sqlite3_parseTOKENTYPE is the data type used for minor tokens given +** directly to the parser from the tokenizer. +** YYMINORTYPE is the data type used for all minor tokens. +** This is typically a union of many types, one of +** which is sqlite3_parseTOKENTYPE. The entry in the union +** for base tokens is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. If +** zero the stack is dynamically sized using realloc() +** sqlite3_parseARG_SDECL A static variable declaration for the %extra_argument +** sqlite3_parseARG_PDECL A parameter declaration for the %extra_argument +** sqlite3_parseARG_STORE Code to store %extra_argument into yypParser +** sqlite3_parseARG_FETCH Code to extract %extra_argument from yypParser +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +*/ +#define YYCODETYPE unsigned short int +#define YYNOCODE 278 +#define YYACTIONTYPE unsigned short int +#define YYWILDCARD 61 +#define sqlite3_parseTOKENTYPE Token* +typedef union { + int yyinit; + sqlite3_parseTOKENTYPE yy0; + SqliteCreateTable::Column::Constraint* yy4; + SqliteCreateTable::Constraint* yy8; + ParserExprList* yy13; + QVariant* yy21; + ParserStubAlias* yy28; + SqliteConflictAlgo* yy30; + ParserFullName* yy66; + ParserCreateTableConstraintList* yy87; + SqliteIndexedColumn* yy90; + ParserFkConditionList* yy108; + SqliteSelect::Core::JoinConstraint* yy117; + ParserCreateTableColumnList* yy118; + SqliteSelect* yy123; + SqliteLimit* yy128; + ParserDeferSubClause* yy131; + ParserIndexedColumnList* yy139; + SqliteCreateTrigger::Time* yy152; + SqliteSelect::CompoundOperator* yy168; + SqliteSelect::Core::SingleSource* yy173; + QString* yy211; + ParserQueryList* yy214; + ParserStubExplain* yy225; + SqliteSortOrder* yy226; + bool* yy237; + ParserStubInsertOrReplace* yy250; + ParserResultColumnList* yy263; + SqliteForeignKey::Condition* yy271; + SqliteColumnType* yy299; + ParserStubTransDetails* yy300; + SqliteCreateTrigger::Event* yy309; + SqliteForeignKey::Condition::Reaction* yy312; + ParserOtherSourceList* yy359; + SqliteWith* yy367; + SqliteSelect::Core::JoinSource* yy373; + SqliteExpr::LikeOp* yy374; + int* yy376; + ParserSetValueList* yy381; + SqliteQuery* yy399; + SqliteCreateTrigger::Scope* yy409; + ParserExprNestedList* yy416; + SqliteCreateTable::Column* yy425; + ParserStringList* yy445; + ParserCreateTableColumnConstraintList* yy449; + SqliteSelect::Core* yy468; + ParserIndexedBy* yy472; + SqliteSelect::Core::JoinOp* yy473; + SqliteExpr* yy490; + ParserOrderByList* yy495; + SqliteInitially* yy498; +} YYMINORTYPE; +#ifndef YYSTACKDEPTH +#define YYSTACKDEPTH 100 +#endif +#define sqlite3_parseARG_SDECL ParserContext* parserContext; +#define sqlite3_parseARG_PDECL ,ParserContext* parserContext +#define sqlite3_parseARG_FETCH ParserContext* parserContext = yypParser->parserContext +#define sqlite3_parseARG_STORE yypParser->parserContext = parserContext +#define YYNSTATE 724 +#define YYNRULE 424 +#define YYFALLBACK 1 +#define YY_NO_ACTION (YYNSTATE+YYNRULE+2) +#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1) +#define YY_ERROR_ACTION (YYNSTATE+YYNRULE) + +#define GET_CONTEXT yyParser* yypParser = pParser; sqlite3_parseARG_FETCH + +/* The yyzerominor constant is used to initialize instances of +** YYMINORTYPE objects to zero. */ +static const YYMINORTYPE yyzerominor = { 0 }; + +/* Define the yytestcase() macro to be a no-op if is not already defined +** otherwise. +** +** Applications can choose to define yytestcase() in the %include section +** to a macro that can assist in verifying code coverage. For production +** code the yytestcase() macro should be turned off. But it is useful +** for testing. +*/ +#ifndef yytestcase +# define yytestcase(X) +#endif + + +/* Next are the tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N < YYNSTATE Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE. +** +** N == YYNSTATE+YYNRULE A syntax error has occurred. +** +** N == YYNSTATE+YYNRULE+1 The parser accepts its input. +** +** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused +** slots in the yy_action[] table. +** +** The action table is constructed as a single large table named yy_action[]. +** Given state S and lookahead X, the action is computed as +** +** yy_action[ yy_shift_ofst[S] + X ] +** +** If the index value yy_shift_ofst[S]+X is out of range or if the value +** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S] +** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table +** and that yy_default[S] should be used instead. +** +** The formula above is for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the yy_reduce_ofst[] array is used in place of +** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of +** YY_SHIFT_USE_DFLT. +** +** The following are the tables generated in this section: +** +** yy_action[] A single table containing all actions. +** yy_lookahead[] A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** yy_shift_ofst[] For each state, the offset into yy_action for +** shifting terminals. +** yy_reduce_ofst[] For each state, the offset into yy_action for +** shifting non-terminals after a reduce. +** yy_default[] Default action for each state. +*/ +#define YY_ACTTAB_COUNT (2221) +static const YYACTIONTYPE yy_action[] = { + /* 0 */ 431, 49, 49, 48, 48, 48, 47, 216, 716, 339, + /* 10 */ 643, 425, 52, 52, 52, 52, 45, 50, 50, 50, + /* 20 */ 50, 49, 49, 48, 48, 48, 47, 216, 721, 1026, + /* 30 */ 1026, 643, 131, 580, 52, 52, 52, 52, 411, 50, + /* 40 */ 50, 50, 50, 49, 49, 48, 48, 48, 47, 216, + /* 50 */ 579, 81, 58, 643, 157, 685, 301, 282, 1026, 1026, + /* 60 */ 42, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, + /* 70 */ 1026, 1026, 563, 1026, 1026, 1026, 1026, 39, 40, 1026, + /* 80 */ 1026, 1026, 1026, 1026, 41, 431, 528, 385, 716, 595, + /* 90 */ 594, 280, 4, 377, 716, 630, 425, 642, 608, 422, + /* 100 */ 12, 134, 687, 429, 562, 609, 483, 690, 331, 279, + /* 110 */ 714, 713, 564, 565, 642, 689, 688, 687, 235, 506, + /* 120 */ 60, 320, 610, 411, 48, 48, 48, 47, 216, 122, + /* 130 */ 243, 213, 247, 59, 1142, 1142, 486, 609, 607, 603, + /* 140 */ 685, 306, 485, 584, 716, 42, 507, 509, 642, 508, + /* 150 */ 676, 9, 642, 144, 95, 281, 379, 276, 378, 132, + /* 160 */ 297, 716, 39, 40, 601, 200, 199, 7, 355, 41, + /* 170 */ 884, 307, 1134, 274, 249, 716, 17, 4, 884, 1134, + /* 180 */ 57, 717, 642, 431, 422, 884, 329, 687, 429, 716, + /* 190 */ 687, 643, 690, 687, 425, 690, 714, 713, 690, 642, + /* 200 */ 689, 688, 687, 689, 688, 687, 689, 688, 687, 98, + /* 210 */ 682, 240, 643, 218, 410, 884, 486, 884, 884, 483, + /* 220 */ 716, 411, 239, 884, 303, 582, 512, 581, 884, 884, + /* 230 */ 884, 884, 884, 642, 643, 676, 9, 642, 685, 217, + /* 240 */ 245, 673, 102, 42, 287, 300, 714, 713, 67, 302, + /* 250 */ 148, 307, 1133, 151, 306, 484, 81, 715, 97, 1133, + /* 260 */ 39, 40, 551, 714, 713, 771, 130, 41, 946, 376, + /* 270 */ 373, 372, 447, 47, 216, 4, 946, 714, 713, 334, + /* 280 */ 642, 682, 422, 946, 606, 687, 429, 371, 448, 447, + /* 290 */ 690, 714, 713, 304, 265, 146, 267, 642, 689, 688, + /* 300 */ 687, 287, 68, 677, 691, 255, 362, 259, 359, 692, + /* 310 */ 1027, 1027, 682, 946, 715, 946, 946, 447, 698, 234, + /* 320 */ 386, 715, 714, 713, 773, 651, 946, 946, 946, 946, + /* 330 */ 110, 642, 317, 676, 9, 642, 222, 677, 299, 53, + /* 340 */ 54, 426, 289, 1027, 1027, 675, 675, 51, 51, 52, + /* 350 */ 52, 52, 52, 716, 50, 50, 50, 50, 49, 49, + /* 360 */ 48, 48, 48, 47, 216, 431, 428, 340, 716, 335, + /* 370 */ 671, 670, 287, 283, 716, 138, 425, 209, 219, 430, + /* 380 */ 268, 395, 651, 682, 336, 715, 715, 686, 186, 53, + /* 390 */ 54, 426, 289, 715, 452, 675, 675, 51, 51, 52, + /* 400 */ 52, 52, 52, 411, 50, 50, 50, 50, 49, 49, + /* 410 */ 48, 48, 48, 47, 216, 91, 953, 716, 619, 712, + /* 420 */ 685, 403, 382, 130, 710, 42, 376, 373, 372, 711, + /* 430 */ 233, 953, 394, 311, 210, 593, 666, 384, 428, 16, + /* 440 */ 316, 659, 39, 40, 371, 231, 230, 716, 89, 41, + /* 450 */ 931, 430, 716, 658, 716, 714, 713, 4, 931, 686, + /* 460 */ 92, 143, 642, 358, 422, 931, 674, 687, 429, 14, + /* 470 */ 714, 713, 690, 131, 456, 551, 714, 713, 953, 642, + /* 480 */ 689, 688, 687, 668, 667, 210, 593, 458, 384, 457, + /* 490 */ 576, 88, 1027, 1027, 13, 931, 672, 931, 931, 55, + /* 500 */ 575, 678, 43, 368, 38, 401, 36, 381, 931, 1, + /* 510 */ 931, 931, 641, 642, 634, 676, 9, 642, 661, 714, + /* 520 */ 713, 53, 54, 426, 289, 1027, 1027, 675, 675, 51, + /* 530 */ 51, 52, 52, 52, 52, 660, 50, 50, 50, 50, + /* 540 */ 49, 49, 48, 48, 48, 47, 216, 657, 648, 714, + /* 550 */ 713, 496, 542, 569, 714, 713, 714, 713, 656, 691, + /* 560 */ 543, 614, 320, 30, 692, 27, 716, 585, 274, 682, + /* 570 */ 160, 1027, 1027, 426, 289, 693, 613, 675, 675, 51, + /* 580 */ 51, 52, 52, 52, 52, 398, 50, 50, 50, 50, + /* 590 */ 49, 49, 48, 48, 48, 47, 216, 1025, 1025, 81, + /* 600 */ 53, 54, 426, 289, 1027, 1027, 675, 675, 51, 51, + /* 610 */ 52, 52, 52, 52, 496, 50, 50, 50, 50, 49, + /* 620 */ 49, 48, 48, 48, 47, 216, 1025, 1025, 1025, 1025, + /* 630 */ 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, + /* 640 */ 716, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, + /* 650 */ 1025, 1025, 1027, 1027, 357, 50, 50, 50, 50, 49, + /* 660 */ 49, 48, 48, 48, 47, 216, 288, 552, 714, 713, + /* 670 */ 495, 682, 298, 662, 346, 153, 538, 69, 694, 715, + /* 680 */ 715, 53, 54, 426, 289, 1027, 1027, 675, 675, 51, + /* 690 */ 51, 52, 52, 52, 52, 1094, 50, 50, 50, 50, + /* 700 */ 49, 49, 48, 48, 48, 47, 216, 53, 54, 426, + /* 710 */ 289, 418, 511, 675, 675, 51, 51, 52, 52, 52, + /* 720 */ 52, 159, 50, 50, 50, 50, 49, 49, 48, 48, + /* 730 */ 48, 47, 216, 490, 954, 315, 482, 482, 663, 553, + /* 740 */ 215, 650, 714, 713, 81, 53, 54, 426, 289, 954, + /* 750 */ 414, 675, 675, 51, 51, 52, 52, 52, 52, 397, + /* 760 */ 50, 50, 50, 50, 49, 49, 48, 48, 48, 47, + /* 770 */ 216, 158, 1094, 21, 716, 627, 459, 716, 1079, 716, + /* 780 */ 647, 1045, 140, 89, 716, 1149, 154, 435, 2, 715, + /* 790 */ 424, 671, 670, 396, 460, 461, 954, 53, 54, 426, + /* 800 */ 289, 573, 716, 675, 675, 51, 51, 52, 52, 52, + /* 810 */ 52, 321, 50, 50, 50, 50, 49, 49, 48, 48, + /* 820 */ 48, 47, 216, 431, 1108, 81, 206, 205, 204, 53, + /* 830 */ 54, 426, 289, 716, 425, 675, 675, 51, 51, 52, + /* 840 */ 52, 52, 52, 344, 50, 50, 50, 50, 49, 49, + /* 850 */ 48, 48, 48, 47, 216, 597, 715, 666, 600, 462, + /* 860 */ 666, 411, 31, 716, 657, 90, 12, 894, 720, 668, + /* 870 */ 667, 609, 724, 434, 81, 656, 714, 713, 685, 714, + /* 880 */ 713, 714, 713, 42, 528, 272, 714, 713, 610, 349, + /* 890 */ 528, 450, 89, 677, 12, 633, 633, 338, 636, 609, + /* 900 */ 39, 40, 649, 609, 714, 713, 716, 41, 1142, 1142, + /* 910 */ 716, 524, 682, 581, 716, 4, 610, 468, 60, 450, + /* 920 */ 642, 208, 422, 506, 60, 687, 429, 677, 33, 109, + /* 930 */ 690, 609, 500, 501, 352, 714, 713, 642, 689, 688, + /* 940 */ 687, 428, 900, 900, 467, 466, 552, 465, 421, 383, + /* 950 */ 507, 509, 142, 508, 430, 440, 69, 1142, 1142, 715, + /* 960 */ 444, 722, 686, 182, 646, 714, 713, 645, 231, 230, + /* 970 */ 437, 642, 356, 676, 9, 642, 417, 444, 53, 54, + /* 980 */ 426, 289, 91, 91, 675, 675, 51, 51, 52, 52, + /* 990 */ 52, 52, 644, 50, 50, 50, 50, 49, 49, 48, + /* 1000 */ 48, 48, 47, 216, 1034, 444, 668, 667, 714, 713, + /* 1010 */ 91, 453, 714, 713, 682, 641, 714, 713, 324, 202, + /* 1020 */ 53, 54, 426, 289, 446, 680, 675, 675, 51, 51, + /* 1030 */ 52, 52, 52, 52, 639, 50, 50, 50, 50, 49, + /* 1040 */ 49, 48, 48, 48, 47, 216, 605, 53, 54, 426, + /* 1050 */ 289, 716, 446, 675, 675, 51, 51, 52, 52, 52, + /* 1060 */ 52, 459, 50, 50, 50, 50, 49, 49, 48, 48, + /* 1070 */ 48, 47, 216, 453, 715, 37, 663, 423, 215, 460, + /* 1080 */ 341, 369, 592, 53, 54, 426, 289, 638, 89, 675, + /* 1090 */ 675, 51, 51, 52, 52, 52, 52, 31, 50, 50, + /* 1100 */ 50, 50, 49, 49, 48, 48, 48, 47, 216, 413, + /* 1110 */ 723, 2, 11, 53, 54, 426, 289, 34, 588, 675, + /* 1120 */ 675, 51, 51, 52, 52, 52, 52, 624, 50, 50, + /* 1130 */ 50, 50, 49, 49, 48, 48, 48, 47, 216, 515, + /* 1140 */ 715, 537, 29, 91, 342, 666, 140, 8, 571, 53, + /* 1150 */ 54, 426, 289, 714, 713, 675, 675, 51, 51, 52, + /* 1160 */ 52, 52, 52, 548, 50, 50, 50, 50, 49, 49, + /* 1170 */ 48, 48, 48, 47, 216, 91, 252, 234, 386, 53, + /* 1180 */ 54, 426, 289, 89, 271, 675, 675, 51, 51, 52, + /* 1190 */ 52, 52, 52, 333, 50, 50, 50, 50, 49, 49, + /* 1200 */ 48, 48, 48, 47, 216, 532, 81, 682, 696, 338, + /* 1210 */ 87, 53, 54, 426, 289, 22, 557, 675, 675, 51, + /* 1220 */ 51, 52, 52, 52, 52, 615, 50, 50, 50, 50, + /* 1230 */ 49, 49, 48, 48, 48, 47, 216, 682, 1109, 91, + /* 1240 */ 504, 716, 53, 54, 426, 289, 604, 137, 675, 675, + /* 1250 */ 51, 51, 52, 52, 52, 52, 136, 50, 50, 50, + /* 1260 */ 50, 49, 49, 48, 48, 48, 47, 216, 431, 1107, + /* 1270 */ 135, 488, 388, 722, 53, 54, 426, 289, 308, 425, + /* 1280 */ 675, 675, 51, 51, 52, 52, 52, 52, 620, 50, + /* 1290 */ 50, 50, 50, 49, 49, 48, 48, 48, 47, 216, + /* 1300 */ 428, 552, 624, 428, 283, 716, 411, 517, 404, 682, + /* 1310 */ 697, 69, 829, 430, 715, 715, 430, 715, 464, 540, + /* 1320 */ 653, 686, 186, 685, 686, 186, 99, 428, 42, 716, + /* 1330 */ 390, 715, 226, 223, 599, 539, 5, 534, 534, 682, + /* 1340 */ 430, 91, 91, 714, 713, 39, 40, 682, 686, 186, + /* 1350 */ 555, 431, 41, 620, 620, 403, 405, 598, 403, 387, + /* 1360 */ 4, 570, 425, 629, 311, 642, 428, 422, 326, 10, + /* 1370 */ 687, 429, 716, 624, 316, 690, 716, 316, 428, 430, + /* 1380 */ 716, 620, 642, 689, 688, 687, 715, 686, 186, 411, + /* 1390 */ 716, 430, 498, 596, 716, 632, 499, 602, 612, 686, + /* 1400 */ 186, 406, 491, 591, 285, 631, 685, 714, 713, 266, + /* 1410 */ 590, 42, 716, 365, 209, 589, 642, 350, 676, 9, + /* 1420 */ 642, 332, 715, 261, 107, 611, 530, 221, 39, 40, + /* 1430 */ 225, 714, 713, 399, 431, 41, 715, 716, 283, 351, + /* 1440 */ 316, 138, 716, 4, 270, 425, 209, 286, 642, 682, + /* 1450 */ 422, 715, 316, 714, 429, 716, 209, 530, 690, 716, + /* 1460 */ 715, 19, 561, 491, 716, 642, 689, 688, 687, 525, + /* 1470 */ 375, 65, 411, 519, 714, 713, 273, 475, 714, 713, + /* 1480 */ 64, 370, 714, 713, 619, 18, 525, 514, 129, 685, + /* 1490 */ 519, 479, 714, 713, 42, 716, 714, 713, 227, 642, + /* 1500 */ 428, 676, 9, 642, 514, 63, 286, 428, 479, 475, + /* 1510 */ 622, 39, 40, 430, 714, 713, 431, 718, 41, 715, + /* 1520 */ 430, 686, 181, 715, 279, 716, 4, 425, 686, 163, + /* 1530 */ 715, 642, 366, 422, 472, 156, 687, 429, 533, 714, + /* 1540 */ 713, 690, 125, 3, 714, 713, 416, 79, 642, 689, + /* 1550 */ 688, 687, 469, 85, 411, 258, 438, 714, 713, 220, + /* 1560 */ 26, 714, 713, 25, 706, 407, 714, 713, 715, 77, + /* 1570 */ 704, 685, 510, 438, 641, 516, 42, 715, 716, 472, + /* 1580 */ 353, 641, 642, 715, 676, 9, 642, 503, 1035, 428, + /* 1590 */ 625, 536, 505, 39, 40, 83, 119, 714, 713, 428, + /* 1600 */ 41, 621, 430, 497, 428, 420, 254, 62, 4, 1037, + /* 1610 */ 686, 172, 430, 642, 140, 422, 469, 430, 687, 429, + /* 1620 */ 686, 189, 152, 690, 337, 686, 187, 714, 713, 251, + /* 1630 */ 642, 689, 688, 687, 161, 54, 426, 289, 716, 558, + /* 1640 */ 675, 675, 51, 51, 52, 52, 52, 52, 478, 50, + /* 1650 */ 50, 50, 50, 49, 49, 48, 48, 48, 47, 216, + /* 1660 */ 431, 428, 455, 641, 642, 705, 676, 9, 642, 111, + /* 1670 */ 716, 425, 428, 641, 430, 428, 702, 454, 641, 428, + /* 1680 */ 714, 713, 686, 196, 207, 430, 428, 72, 430, 715, + /* 1690 */ 428, 436, 430, 686, 195, 716, 686, 197, 411, 430, + /* 1700 */ 686, 201, 716, 430, 428, 699, 428, 686, 232, 428, + /* 1710 */ 209, 686, 290, 96, 203, 685, 150, 430, 715, 430, + /* 1720 */ 42, 224, 430, 86, 319, 686, 190, 686, 194, 428, + /* 1730 */ 686, 193, 257, 82, 695, 641, 256, 39, 40, 318, + /* 1740 */ 714, 713, 430, 719, 41, 715, 641, 715, 149, 641, + /* 1750 */ 686, 185, 4, 641, 707, 679, 287, 642, 709, 422, + /* 1760 */ 641, 567, 687, 429, 641, 15, 428, 690, 715, 715, + /* 1770 */ 367, 428, 714, 713, 642, 689, 688, 687, 641, 430, + /* 1780 */ 641, 716, 703, 641, 430, 147, 287, 686, 188, 701, + /* 1790 */ 708, 328, 686, 314, 428, 432, 428, 714, 713, 715, + /* 1800 */ 700, 428, 145, 641, 714, 713, 408, 430, 642, 430, + /* 1810 */ 676, 9, 642, 428, 430, 686, 313, 686, 312, 715, + /* 1820 */ 428, 327, 686, 184, 684, 428, 430, 433, 494, 294, + /* 1830 */ 428, 637, 234, 430, 686, 171, 428, 651, 430, 17, + /* 1840 */ 641, 686, 170, 430, 715, 641, 686, 183, 293, 430, + /* 1850 */ 428, 686, 169, 626, 716, 400, 428, 686, 192, 292, + /* 1860 */ 428, 287, 291, 430, 28, 44, 715, 651, 641, 430, + /* 1870 */ 641, 686, 191, 430, 715, 641, 428, 686, 168, 428, + /* 1880 */ 402, 686, 167, 714, 713, 56, 716, 641, 683, 430, + /* 1890 */ 428, 216, 430, 428, 641, 428, 325, 686, 93, 641, + /* 1900 */ 686, 166, 716, 430, 641, 428, 430, 428, 430, 640, + /* 1910 */ 641, 686, 164, 66, 686, 174, 686, 173, 430, 716, + /* 1920 */ 430, 716, 428, 616, 641, 716, 686, 175, 686, 178, + /* 1930 */ 641, 229, 419, 214, 641, 430, 228, 415, 428, 716, + /* 1940 */ 35, 428, 651, 686, 94, 617, 716, 635, 141, 716, + /* 1950 */ 641, 430, 428, 641, 430, 428, 714, 713, 715, 686, + /* 1960 */ 177, 139, 686, 176, 641, 430, 108, 641, 430, 641, + /* 1970 */ 428, 133, 392, 686, 180, 716, 686, 179, 716, 641, + /* 1980 */ 716, 641, 531, 430, 715, 430, 716, 583, 714, 713, + /* 1990 */ 389, 686, 165, 686, 70, 393, 641, 716, 248, 716, + /* 2000 */ 541, 716, 481, 246, 714, 713, 380, 477, 715, 716, + /* 2010 */ 386, 715, 641, 578, 716, 641, 715, 716, 577, 716, + /* 2020 */ 330, 714, 713, 714, 713, 275, 641, 714, 713, 641, + /* 2030 */ 244, 716, 242, 502, 473, 527, 470, 277, 715, 523, + /* 2040 */ 574, 714, 713, 715, 641, 715, 641, 573, 714, 713, + /* 2050 */ 310, 714, 713, 236, 568, 549, 269, 322, 572, 554, + /* 2060 */ 518, 263, 529, 492, 547, 546, 715, 715, 715, 715, + /* 2070 */ 545, 489, 360, 347, 715, 544, 309, 714, 713, 128, + /* 2080 */ 714, 713, 714, 713, 442, 715, 715, 521, 714, 713, + /* 2090 */ 80, 127, 480, 427, 106, 284, 262, 715, 535, 714, + /* 2100 */ 713, 714, 713, 714, 713, 441, 715, 212, 715, 476, + /* 2110 */ 126, 714, 713, 443, 354, 648, 714, 713, 24, 714, + /* 2120 */ 713, 714, 713, 264, 253, 363, 526, 250, 474, 241, + /* 2130 */ 237, 238, 124, 714, 713, 78, 715, 715, 105, 123, + /* 2140 */ 715, 121, 715, 715, 715, 84, 513, 155, 104, 348, + /* 2150 */ 120, 493, 103, 345, 118, 76, 343, 117, 471, 116, + /* 2160 */ 463, 75, 652, 115, 114, 623, 74, 323, 73, 113, + /* 2170 */ 23, 451, 20, 449, 101, 445, 100, 112, 439, 520, + /* 2180 */ 162, 61, 655, 295, 669, 412, 278, 198, 665, 569, + /* 2190 */ 618, 522, 374, 305, 6, 628, 364, 681, 664, 654, + /* 2200 */ 260, 361, 211, 556, 409, 71, 296, 566, 560, 559, + /* 2210 */ 487, 81, 587, 1150, 46, 586, 1150, 1150, 1150, 1150, + /* 2220 */ 550, +}; +static const YYCODETYPE yy_lookahead[] = { + /* 0 */ 4, 81, 82, 83, 84, 85, 86, 87, 4, 58, + /* 10 */ 5, 15, 72, 73, 74, 75, 76, 77, 78, 79, + /* 20 */ 80, 81, 82, 83, 84, 85, 86, 87, 89, 33, + /* 30 */ 34, 26, 34, 28, 72, 73, 74, 75, 42, 77, + /* 40 */ 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + /* 50 */ 45, 55, 96, 48, 98, 59, 93, 104, 62, 63, + /* 60 */ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + /* 70 */ 74, 75, 14, 77, 78, 79, 80, 81, 82, 83, + /* 80 */ 84, 85, 86, 87, 88, 4, 185, 134, 4, 136, + /* 90 */ 137, 111, 96, 35, 4, 104, 15, 101, 104, 103, + /* 100 */ 96, 107, 106, 107, 46, 101, 61, 111, 64, 129, + /* 110 */ 106, 107, 110, 111, 118, 119, 120, 121, 167, 218, + /* 120 */ 219, 123, 118, 42, 83, 84, 85, 86, 87, 104, + /* 130 */ 51, 87, 53, 142, 138, 139, 61, 133, 144, 145, + /* 140 */ 59, 96, 97, 59, 4, 64, 245, 246, 152, 248, + /* 150 */ 154, 155, 156, 109, 110, 111, 112, 113, 114, 115, + /* 160 */ 97, 4, 81, 82, 83, 81, 82, 266, 267, 88, + /* 170 */ 89, 96, 97, 129, 95, 4, 151, 96, 97, 104, + /* 180 */ 96, 91, 101, 4, 103, 104, 128, 106, 107, 4, + /* 190 */ 106, 5, 111, 106, 15, 111, 106, 107, 111, 118, + /* 200 */ 119, 120, 121, 119, 120, 121, 119, 120, 121, 8, + /* 210 */ 4, 10, 26, 12, 28, 134, 61, 136, 137, 61, + /* 220 */ 4, 42, 21, 142, 23, 214, 45, 216, 147, 148, + /* 230 */ 149, 150, 151, 152, 48, 154, 155, 156, 59, 96, + /* 240 */ 161, 97, 41, 64, 177, 44, 106, 107, 104, 48, + /* 250 */ 49, 96, 97, 52, 96, 97, 55, 190, 57, 104, + /* 260 */ 81, 82, 105, 106, 107, 99, 109, 88, 89, 112, + /* 270 */ 113, 114, 101, 86, 87, 96, 97, 106, 107, 212, + /* 280 */ 101, 4, 103, 104, 144, 106, 107, 130, 117, 118, + /* 290 */ 111, 106, 107, 92, 51, 94, 53, 118, 119, 120, + /* 300 */ 121, 177, 96, 118, 135, 124, 125, 126, 177, 140, + /* 310 */ 33, 34, 106, 134, 190, 136, 137, 146, 102, 138, + /* 320 */ 139, 190, 106, 107, 99, 258, 147, 148, 149, 150, + /* 330 */ 99, 152, 131, 154, 155, 156, 212, 152, 95, 62, + /* 340 */ 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, + /* 350 */ 73, 74, 75, 4, 77, 78, 79, 80, 81, 82, + /* 360 */ 83, 84, 85, 86, 87, 4, 177, 166, 4, 194, + /* 370 */ 195, 196, 177, 177, 4, 98, 15, 252, 247, 190, + /* 380 */ 177, 185, 258, 106, 181, 190, 190, 198, 199, 62, + /* 390 */ 63, 64, 65, 190, 269, 68, 69, 70, 71, 72, + /* 400 */ 73, 74, 75, 42, 77, 78, 79, 80, 81, 82, + /* 410 */ 83, 84, 85, 86, 87, 219, 89, 4, 141, 19, + /* 420 */ 59, 232, 233, 109, 24, 64, 112, 113, 114, 29, + /* 430 */ 225, 104, 236, 237, 220, 221, 261, 223, 177, 234, + /* 440 */ 251, 97, 81, 82, 130, 81, 82, 4, 104, 88, + /* 450 */ 89, 190, 4, 258, 4, 106, 107, 96, 97, 198, + /* 460 */ 199, 99, 101, 249, 103, 104, 111, 106, 107, 71, + /* 470 */ 106, 107, 111, 34, 110, 105, 106, 107, 151, 118, + /* 480 */ 119, 120, 121, 119, 120, 220, 221, 123, 223, 125, + /* 490 */ 19, 43, 33, 34, 96, 134, 97, 136, 137, 40, + /* 500 */ 29, 152, 96, 64, 158, 244, 160, 83, 147, 96, + /* 510 */ 149, 150, 251, 152, 101, 154, 155, 156, 97, 106, + /* 520 */ 107, 62, 63, 64, 65, 66, 67, 68, 69, 70, + /* 530 */ 71, 72, 73, 74, 75, 97, 77, 78, 79, 80, + /* 540 */ 81, 82, 83, 84, 85, 86, 87, 9, 100, 106, + /* 550 */ 107, 101, 205, 206, 106, 107, 106, 107, 20, 135, + /* 560 */ 213, 118, 123, 104, 140, 123, 4, 190, 129, 4, + /* 570 */ 96, 33, 34, 64, 65, 198, 133, 68, 69, 70, + /* 580 */ 71, 72, 73, 74, 75, 143, 77, 78, 79, 80, + /* 590 */ 81, 82, 83, 84, 85, 86, 87, 33, 34, 55, + /* 600 */ 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + /* 610 */ 72, 73, 74, 75, 164, 77, 78, 79, 80, 81, + /* 620 */ 82, 83, 84, 85, 86, 87, 62, 63, 64, 65, + /* 630 */ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, + /* 640 */ 4, 77, 78, 79, 80, 81, 82, 83, 84, 85, + /* 650 */ 86, 87, 33, 34, 110, 77, 78, 79, 80, 81, + /* 660 */ 82, 83, 84, 85, 86, 87, 177, 177, 106, 107, + /* 670 */ 50, 106, 182, 108, 54, 104, 186, 187, 116, 190, + /* 680 */ 190, 62, 63, 64, 65, 66, 67, 68, 69, 70, + /* 690 */ 71, 72, 73, 74, 75, 12, 77, 78, 79, 80, + /* 700 */ 81, 82, 83, 84, 85, 86, 87, 62, 63, 64, + /* 710 */ 65, 98, 23, 68, 69, 70, 71, 72, 73, 74, + /* 720 */ 75, 96, 77, 78, 79, 80, 81, 82, 83, 84, + /* 730 */ 85, 86, 87, 113, 89, 188, 100, 101, 191, 192, + /* 740 */ 193, 97, 106, 107, 55, 62, 63, 64, 65, 104, + /* 750 */ 98, 68, 69, 70, 71, 72, 73, 74, 75, 30, + /* 760 */ 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, + /* 770 */ 87, 96, 89, 38, 4, 97, 177, 4, 89, 4, + /* 780 */ 23, 161, 104, 104, 4, 170, 171, 172, 173, 190, + /* 790 */ 194, 195, 196, 64, 195, 196, 151, 62, 63, 64, + /* 800 */ 65, 122, 4, 68, 69, 70, 71, 72, 73, 74, + /* 810 */ 75, 179, 77, 78, 79, 80, 81, 82, 83, 84, + /* 820 */ 85, 86, 87, 4, 89, 55, 124, 125, 126, 62, + /* 830 */ 63, 64, 65, 4, 15, 68, 69, 70, 71, 72, + /* 840 */ 73, 74, 75, 177, 77, 78, 79, 80, 81, 82, + /* 850 */ 83, 84, 85, 86, 87, 97, 190, 261, 83, 260, + /* 860 */ 261, 42, 104, 4, 9, 98, 96, 97, 169, 119, + /* 870 */ 120, 101, 0, 174, 55, 20, 106, 107, 59, 106, + /* 880 */ 107, 106, 107, 64, 185, 97, 106, 107, 118, 257, + /* 890 */ 185, 118, 104, 118, 96, 33, 34, 25, 118, 101, + /* 900 */ 81, 82, 97, 133, 106, 107, 4, 88, 138, 139, + /* 910 */ 4, 214, 4, 216, 4, 96, 118, 218, 219, 146, + /* 920 */ 101, 96, 103, 218, 219, 106, 107, 152, 66, 67, + /* 930 */ 111, 133, 124, 125, 126, 106, 107, 118, 119, 120, + /* 940 */ 121, 177, 144, 145, 245, 246, 177, 248, 185, 185, + /* 950 */ 245, 246, 104, 248, 190, 186, 187, 138, 139, 190, + /* 960 */ 101, 89, 198, 199, 97, 106, 107, 97, 81, 82, + /* 970 */ 271, 152, 267, 154, 155, 156, 185, 118, 62, 63, + /* 980 */ 64, 65, 219, 219, 68, 69, 70, 71, 72, 73, + /* 990 */ 74, 75, 97, 77, 78, 79, 80, 81, 82, 83, + /* 1000 */ 84, 85, 86, 87, 158, 146, 119, 120, 106, 107, + /* 1010 */ 219, 101, 106, 107, 106, 251, 106, 107, 272, 273, + /* 1020 */ 62, 63, 64, 65, 118, 117, 68, 69, 70, 71, + /* 1030 */ 72, 73, 74, 75, 97, 77, 78, 79, 80, 81, + /* 1040 */ 82, 83, 84, 85, 86, 87, 144, 62, 63, 64, + /* 1050 */ 65, 4, 146, 68, 69, 70, 71, 72, 73, 74, + /* 1060 */ 75, 177, 77, 78, 79, 80, 81, 82, 83, 84, + /* 1070 */ 85, 86, 87, 163, 190, 159, 191, 192, 193, 195, + /* 1080 */ 196, 97, 97, 62, 63, 64, 65, 97, 104, 68, + /* 1090 */ 69, 70, 71, 72, 73, 74, 75, 104, 77, 78, + /* 1100 */ 79, 80, 81, 82, 83, 84, 85, 86, 87, 185, + /* 1110 */ 172, 173, 13, 62, 63, 64, 65, 159, 97, 68, + /* 1120 */ 69, 70, 71, 72, 73, 74, 75, 177, 77, 78, + /* 1130 */ 79, 80, 81, 82, 83, 84, 85, 86, 87, 97, + /* 1140 */ 190, 185, 104, 219, 260, 261, 104, 13, 97, 62, + /* 1150 */ 63, 64, 65, 106, 107, 68, 69, 70, 71, 72, + /* 1160 */ 73, 74, 75, 116, 77, 78, 79, 80, 81, 82, + /* 1170 */ 83, 84, 85, 86, 87, 219, 97, 138, 139, 62, + /* 1180 */ 63, 64, 65, 104, 97, 68, 69, 70, 71, 72, + /* 1190 */ 73, 74, 75, 243, 77, 78, 79, 80, 81, 82, + /* 1200 */ 83, 84, 85, 86, 87, 185, 55, 4, 197, 25, + /* 1210 */ 96, 62, 63, 64, 65, 98, 205, 68, 69, 70, + /* 1220 */ 71, 72, 73, 74, 75, 30, 77, 78, 79, 80, + /* 1230 */ 81, 82, 83, 84, 85, 86, 87, 4, 89, 219, + /* 1240 */ 89, 4, 62, 63, 64, 65, 144, 13, 68, 69, + /* 1250 */ 70, 71, 72, 73, 74, 75, 97, 77, 78, 79, + /* 1260 */ 80, 81, 82, 83, 84, 85, 86, 87, 4, 89, + /* 1270 */ 97, 97, 104, 89, 62, 63, 64, 65, 104, 15, + /* 1280 */ 68, 69, 70, 71, 72, 73, 74, 75, 191, 77, + /* 1290 */ 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + /* 1300 */ 177, 177, 177, 177, 177, 4, 42, 185, 185, 106, + /* 1310 */ 186, 187, 97, 190, 190, 190, 190, 190, 177, 104, + /* 1320 */ 117, 198, 199, 59, 198, 199, 99, 177, 64, 4, + /* 1330 */ 99, 190, 235, 209, 83, 211, 96, 100, 101, 106, + /* 1340 */ 190, 219, 219, 106, 107, 81, 82, 4, 198, 199, + /* 1350 */ 117, 4, 88, 191, 191, 232, 233, 97, 232, 233, + /* 1360 */ 96, 97, 15, 236, 237, 101, 177, 103, 243, 96, + /* 1370 */ 106, 107, 4, 177, 251, 111, 4, 251, 177, 190, + /* 1380 */ 4, 191, 118, 119, 120, 121, 190, 198, 199, 42, + /* 1390 */ 4, 190, 7, 135, 4, 201, 11, 235, 235, 198, + /* 1400 */ 199, 251, 101, 97, 254, 201, 59, 106, 107, 177, + /* 1410 */ 97, 64, 4, 181, 252, 97, 152, 32, 154, 155, + /* 1420 */ 156, 232, 190, 177, 17, 235, 101, 181, 81, 82, + /* 1430 */ 38, 106, 107, 232, 4, 88, 190, 4, 177, 243, + /* 1440 */ 251, 98, 4, 96, 97, 15, 252, 177, 101, 106, + /* 1450 */ 103, 190, 251, 106, 107, 4, 252, 132, 111, 4, + /* 1460 */ 190, 96, 6, 162, 4, 118, 119, 120, 121, 101, + /* 1470 */ 38, 96, 42, 101, 106, 107, 97, 101, 106, 107, + /* 1480 */ 96, 38, 106, 107, 141, 96, 118, 101, 115, 59, + /* 1490 */ 118, 101, 106, 107, 64, 4, 106, 107, 237, 152, + /* 1500 */ 177, 154, 155, 156, 118, 96, 177, 177, 118, 133, + /* 1510 */ 177, 81, 82, 190, 106, 107, 4, 177, 88, 190, + /* 1520 */ 190, 198, 199, 190, 129, 4, 96, 15, 198, 199, + /* 1530 */ 190, 101, 64, 103, 101, 98, 106, 107, 103, 106, + /* 1540 */ 107, 111, 123, 12, 106, 107, 276, 142, 118, 119, + /* 1550 */ 120, 121, 101, 128, 42, 177, 101, 106, 107, 181, + /* 1560 */ 71, 106, 107, 71, 177, 157, 106, 107, 190, 153, + /* 1570 */ 177, 59, 89, 118, 251, 139, 64, 190, 4, 146, + /* 1580 */ 22, 251, 152, 190, 154, 155, 156, 47, 158, 177, + /* 1590 */ 152, 100, 89, 81, 82, 39, 123, 106, 107, 177, + /* 1600 */ 88, 141, 190, 39, 177, 276, 161, 96, 96, 97, + /* 1610 */ 198, 199, 190, 101, 104, 103, 165, 190, 106, 107, + /* 1620 */ 198, 199, 143, 111, 37, 198, 199, 106, 107, 95, + /* 1630 */ 118, 119, 120, 121, 96, 63, 64, 65, 4, 118, + /* 1640 */ 68, 69, 70, 71, 72, 73, 74, 75, 103, 77, + /* 1650 */ 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + /* 1660 */ 4, 177, 97, 251, 152, 91, 154, 155, 156, 93, + /* 1670 */ 4, 15, 177, 251, 190, 177, 177, 97, 251, 177, + /* 1680 */ 106, 107, 198, 199, 238, 190, 177, 95, 190, 190, + /* 1690 */ 177, 36, 190, 198, 199, 4, 198, 199, 42, 190, + /* 1700 */ 198, 199, 4, 190, 177, 177, 177, 198, 199, 177, + /* 1710 */ 252, 198, 199, 189, 273, 59, 90, 190, 190, 190, + /* 1720 */ 64, 204, 190, 204, 275, 198, 199, 198, 199, 177, + /* 1730 */ 198, 199, 177, 180, 177, 251, 181, 81, 82, 275, + /* 1740 */ 106, 107, 190, 176, 88, 190, 251, 190, 178, 251, + /* 1750 */ 198, 199, 96, 251, 49, 177, 177, 101, 176, 103, + /* 1760 */ 251, 127, 106, 107, 251, 104, 177, 111, 190, 190, + /* 1770 */ 60, 177, 106, 107, 118, 119, 120, 121, 251, 190, + /* 1780 */ 251, 4, 91, 251, 190, 178, 177, 198, 199, 91, + /* 1790 */ 176, 212, 198, 199, 177, 183, 177, 106, 107, 190, + /* 1800 */ 184, 177, 56, 251, 106, 107, 177, 190, 152, 190, + /* 1810 */ 154, 155, 156, 177, 190, 198, 199, 198, 199, 190, + /* 1820 */ 177, 212, 198, 199, 221, 177, 190, 176, 162, 227, + /* 1830 */ 177, 177, 138, 190, 198, 199, 177, 258, 190, 151, + /* 1840 */ 251, 198, 199, 190, 190, 251, 198, 199, 228, 190, + /* 1850 */ 177, 198, 199, 177, 4, 148, 177, 198, 199, 229, + /* 1860 */ 177, 177, 230, 190, 149, 252, 190, 258, 251, 190, + /* 1870 */ 251, 198, 199, 190, 190, 251, 177, 198, 199, 177, + /* 1880 */ 147, 198, 199, 106, 107, 150, 4, 251, 231, 190, + /* 1890 */ 177, 87, 190, 177, 251, 177, 212, 198, 199, 251, + /* 1900 */ 198, 199, 4, 190, 251, 177, 190, 177, 190, 64, + /* 1910 */ 251, 198, 199, 96, 198, 199, 198, 199, 190, 4, + /* 1920 */ 190, 4, 177, 146, 251, 4, 198, 199, 198, 199, + /* 1930 */ 251, 259, 203, 87, 251, 190, 255, 203, 177, 4, + /* 1940 */ 158, 177, 258, 198, 199, 177, 4, 241, 99, 4, + /* 1950 */ 251, 190, 177, 251, 190, 177, 106, 107, 190, 198, + /* 1960 */ 199, 241, 198, 199, 251, 190, 99, 251, 190, 251, + /* 1970 */ 177, 177, 177, 198, 199, 4, 198, 199, 4, 251, + /* 1980 */ 4, 251, 132, 190, 190, 190, 4, 200, 106, 107, + /* 1990 */ 223, 198, 199, 198, 199, 177, 251, 4, 177, 4, + /* 2000 */ 118, 4, 181, 177, 106, 107, 123, 181, 190, 4, + /* 2010 */ 139, 190, 251, 200, 4, 251, 190, 4, 208, 4, + /* 2020 */ 31, 106, 107, 106, 107, 177, 251, 106, 107, 251, + /* 2030 */ 177, 4, 177, 118, 181, 118, 181, 200, 190, 118, + /* 2040 */ 202, 106, 107, 190, 251, 190, 251, 122, 106, 107, + /* 2050 */ 203, 106, 107, 177, 177, 177, 177, 181, 200, 208, + /* 2060 */ 118, 177, 164, 118, 200, 200, 190, 190, 190, 190, + /* 2070 */ 200, 100, 177, 177, 190, 200, 203, 106, 107, 99, + /* 2080 */ 106, 107, 106, 107, 177, 190, 190, 152, 106, 107, + /* 2090 */ 180, 99, 118, 177, 180, 177, 264, 190, 241, 106, + /* 2100 */ 107, 106, 107, 106, 107, 100, 190, 241, 190, 133, + /* 2110 */ 99, 106, 107, 118, 27, 100, 106, 107, 158, 106, + /* 2120 */ 107, 106, 107, 177, 177, 265, 227, 177, 146, 177, + /* 2130 */ 177, 177, 99, 106, 107, 215, 190, 190, 62, 99, + /* 2140 */ 190, 99, 190, 190, 190, 96, 215, 250, 180, 241, + /* 2150 */ 99, 227, 180, 241, 99, 217, 60, 99, 165, 99, + /* 2160 */ 163, 217, 152, 99, 99, 152, 217, 241, 217, 99, + /* 2170 */ 268, 18, 268, 241, 99, 241, 99, 99, 16, 152, + /* 2180 */ 224, 270, 201, 226, 261, 256, 201, 210, 261, 206, + /* 2190 */ 242, 227, 202, 175, 224, 240, 263, 191, 191, 191, + /* 2200 */ 242, 242, 262, 191, 216, 239, 222, 207, 207, 207, + /* 2210 */ 274, 55, 198, 277, 253, 198, 277, 277, 277, 277, + /* 2220 */ 211, +}; +#define YY_SHIFT_USE_DFLT (-81) +#define YY_SHIFT_COUNT (434) +#define YY_SHIFT_MIN (-80) +#define YY_SHIFT_MAX (2162) +static const short yy_shift_ofst[] = { + /* 0 */ 1184, -4, 201, 1151, 819, 1512, 1512, 689, 361, 1430, + /* 10 */ 1656, 1656, 770, 364, 364, 157, 81, 179, 1347, 1264, + /* 20 */ 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, + /* 30 */ 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, + /* 40 */ 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, + /* 50 */ 1656, 1656, 1656, 1656, 1656, 1656, 1656, 1656, 370, 798, + /* 60 */ 181, 370, 2010, 2010, 2010, 2010, 2010, 887, 887, 565, + /* 70 */ 277, 4, 1455, 1451, 1433, 1376, 1390, 1386, 1372, 1368, + /* 80 */ 1325, 448, 1237, 2013, 2013, 2027, 439, 2013, 2015, 2010, + /* 90 */ 565, 1039, 538, 538, 735, 84, 44, 171, 859, 775, + /* 100 */ 906, 773, 910, 636, 1301, 5, 450, 5, 443, 413, + /* 110 */ 185, 2005, 1995, 1997, 1993, 1982, 1976, 1974, 1971, 1945, + /* 120 */ 1666, 1942, 1935, 1921, 1917, 1915, 1898, 1850, 1491, 1882, + /* 130 */ 1047, 1634, 1521, 902, 140, 1343, 1343, 1777, 1460, 1343, + /* 140 */ 1438, 780, 1408, 349, 562, 216, 620, 1698, 1691, 1574, + /* 150 */ 90, 829, 829, 829, 872, 544, 2156, 2156, 2156, 2156, + /* 160 */ 2156, -81, -81, 459, 619, 619, 619, 619, 619, 619, + /* 170 */ 619, 619, 619, 645, 327, 683, 1180, 1149, 1117, 1087, + /* 180 */ 1051, 1021, 985, 958, 916, 767, 1212, 1572, 509, 509, + /* 190 */ -60, -38, -38, -38, -38, -38, 578, -80, 314, 87, + /* 200 */ 87, 41, 155, 75, 58, 58, 58, -6, 186, 862, + /* 210 */ -47, 808, 1385, 1233, 1203, 206, 908, 424, 400, 25, + /* 220 */ 729, 729, 679, 1215, -2, 855, 729, 442, 346, 855, + /* 230 */ 750, 750, 187, -9, 169, 2162, 2078, 2077, 2075, 2153, + /* 240 */ 2153, 2070, 2065, 2096, 2064, 2096, 2060, 2096, 2058, 2096, + /* 250 */ 2055, 1710, 1688, 2051, 1710, 2076, 2049, 2042, 2040, 2076, + /* 260 */ 1688, 2033, 1960, 2087, 2011, 1710, 1992, 1710, 1980, 1817, + /* 270 */ 1883, 1883, 1883, 1883, 1989, 1817, 1883, 1925, 1883, 1989, + /* 280 */ 1883, 1883, 1871, 1867, 1849, 1782, 1817, 1846, 1817, 1845, + /* 290 */ 1804, 1735, 1733, 1715, 1707, 1688, 1694, 1746, 1661, 1710, + /* 300 */ 1705, 1705, 1626, 1626, 1626, 1626, -81, -81, -81, -81, + /* 310 */ -81, -81, -81, -81, -81, -81, 564, 79, 158, 45, + /* 320 */ 702, 243, -49, 398, 1174, 1079, 1042, 984, 788, 2, + /* 330 */ 471, -20, 758, 678, 344, 144, -44, 1655, 1587, 1576, + /* 340 */ 1592, 1580, 1565, 1545, 1538, 1479, 1534, 1511, 1473, 1445, + /* 350 */ 1564, 1510, 1556, 1540, 1558, 1503, 1483, 1436, 1416, 1492, + /* 360 */ 1489, 1425, 1405, 1531, 1419, 1437, 1435, 1468, 1395, 1373, + /* 370 */ 1409, 1443, 1389, 1384, 1379, 1375, 1432, 1456, 1365, 1392, + /* 380 */ 1407, 1318, 1313, 1306, 1168, 1258, 1273, 1260, 1240, 1168, + /* 390 */ 1251, 1231, 1227, 1102, 1173, 1159, 1195, 1234, 1114, 993, + /* 400 */ 1134, 1038, 1099, 993, 990, 937, 846, 895, 870, 848, + /* 410 */ 867, 825, 757, 805, 675, 652, 571, 644, 625, 613, + /* 420 */ 571, 438, 474, 421, 399, 406, 355, 362, 231, 225, + /* 430 */ 166, 143, 63, -37, -61, +}; +#define YY_REDUCE_USE_DFLT (-100) +#define YY_REDUCE_COUNT (315) +#define YY_REDUCE_MIN (-99) +#define YY_REDUCE_MAX (2018) +static const short yy_reduce_ofst[] = { + /* 0 */ 615, 1123, 699, -99, 764, 1126, 189, 705, 1201, 1150, + /* 10 */ 1189, 261, 196, 884, 599, 1124, 1795, 1793, 1778, 1775, + /* 20 */ 1764, 1761, 1745, 1730, 1728, 1718, 1716, 1713, 1702, 1699, + /* 30 */ 1683, 1679, 1673, 1659, 1653, 1648, 1643, 1636, 1624, 1619, + /* 40 */ 1617, 1594, 1589, 1552, 1532, 1529, 1527, 1513, 1509, 1502, + /* 50 */ 1498, 1495, 1484, 1427, 1422, 1412, 1330, 1323, 490, 1127, + /* 60 */ 214, 769, 1684, 1609, 1579, 124, 67, 596, 175, 547, + /* 70 */ 1162, 1261, 1876, 1855, 1853, 1826, 1821, 1555, 1378, 1246, + /* 80 */ 1232, 1329, 203, 1196, 1125, 131, 347, 950, 1270, 195, + /* 90 */ 885, 265, 1204, 1194, 125, 377, 1011, 1954, 1953, 1916, + /* 100 */ 1654, 1654, 1952, 1950, 1947, 697, 1946, 11, 1654, 1918, + /* 110 */ 1916, 1907, 1654, 1654, 1654, 1654, 1654, 1654, 1654, 1896, + /* 120 */ 1654, 1654, 1895, 1654, 1654, 1884, 1654, 1654, 1654, 1879, + /* 130 */ 1878, 1877, 1848, 1818, 1794, 1190, 1163, 1768, 1333, 1097, + /* 140 */ 1676, 1654, 1629, 1578, 1557, 1528, 632, 1499, 1393, 1387, + /* 150 */ 1340, 1141, 666, 489, 938, 1122, 1020, 956, 924, 791, + /* 160 */ 763, 746, 205, 1458, 1458, 1458, 1458, 1458, 1458, 1458, + /* 170 */ 1458, 1458, 1458, 1458, 1458, 1458, 1458, 1458, 1458, 1458, + /* 180 */ 1458, 1458, 1458, 1458, 1458, 1458, 1458, 1458, 1458, 1458, + /* 190 */ 1458, 1458, 1458, 1458, 1458, 1458, 1458, 1458, 2009, 2017, + /* 200 */ 2014, 1458, 1936, 1936, 2002, 2001, 2000, 1966, 1988, 1961, + /* 210 */ 1984, 1933, 1940, 2012, 2008, 2007, 2006, 1970, 2018, 1964, + /* 220 */ 1959, 1958, 1990, 1977, 1983, 1985, 1948, 1955, 1929, 1981, + /* 230 */ 1927, 1923, 1458, 1957, 1956, 1911, 1857, 1934, 1932, 1904, + /* 240 */ 1902, 1926, 1857, 1951, 1857, 1949, 1857, 1944, 1857, 1938, + /* 250 */ 1912, 1972, 1924, 1908, 1968, 1931, 1897, 1857, 1857, 1920, + /* 260 */ 1899, 1857, 1860, 1832, 1866, 1914, 1857, 1910, 1857, 1873, + /* 270 */ 1875, 1870, 1865, 1864, 1851, 1847, 1858, 1838, 1837, 1810, + /* 280 */ 1813, 1787, 1767, 1720, 1706, 1681, 1734, 1672, 1729, 1613, + /* 290 */ 1458, 1657, 1632, 1630, 1620, 1602, 1603, 1616, 1612, 1553, + /* 300 */ 1607, 1570, 1651, 1614, 1582, 1567, 1464, 1449, 1441, 1519, + /* 310 */ 1517, 1446, 1458, 1458, 1458, 1524, +}; +static const YYACTIONTYPE yy_default[] = { + /* 0 */ 729, 1037, 1142, 1142, 1026, 1026, 1026, 1142, 1026, 1026, + /* 10 */ 1026, 1026, 900, 1148, 1148, 1148, 1026, 1026, 1026, 1026, + /* 20 */ 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, + /* 30 */ 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, + /* 40 */ 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, + /* 50 */ 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1026, 1148, 894, + /* 60 */ 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 774, + /* 70 */ 890, 900, 1148, 1148, 1148, 1148, 1148, 962, 949, 940, + /* 80 */ 1148, 1148, 1148, 972, 972, 955, 842, 972, 1148, 1148, + /* 90 */ 1148, 1148, 928, 928, 1027, 1148, 766, 1112, 1117, 1013, + /* 100 */ 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 901, 1148, + /* 110 */ 1013, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, + /* 120 */ 1148, 963, 956, 950, 941, 1148, 1148, 1148, 1148, 1148, + /* 130 */ 1148, 1148, 1148, 1148, 1148, 890, 890, 1148, 1148, 890, + /* 140 */ 1148, 1148, 1148, 1014, 1148, 1148, 763, 1148, 1148, 1148, + /* 150 */ 735, 1058, 1148, 1148, 729, 1142, 1142, 1142, 1142, 1142, + /* 160 */ 1142, 1135, 880, 935, 906, 945, 933, 937, 1038, 1031, + /* 170 */ 1032, 1030, 936, 1027, 1027, 1027, 1027, 1027, 1027, 1027, + /* 180 */ 1027, 1027, 1027, 1027, 1027, 1027, 1027, 988, 1000, 987, + /* 190 */ 995, 1004, 1015, 999, 996, 990, 989, 991, 1148, 1148, + /* 200 */ 1148, 992, 1148, 1148, 1148, 1148, 1148, 893, 1148, 1148, + /* 210 */ 864, 1148, 1086, 1148, 1148, 776, 1148, 878, 738, 944, + /* 220 */ 918, 918, 809, 833, 798, 928, 918, 908, 1033, 928, + /* 230 */ 1148, 1148, 993, 891, 878, 1126, 909, 909, 909, 1111, + /* 240 */ 1111, 909, 909, 855, 909, 855, 909, 855, 909, 855, + /* 250 */ 909, 760, 944, 909, 760, 846, 968, 909, 909, 846, + /* 260 */ 944, 909, 1093, 1091, 909, 760, 909, 760, 909, 1046, + /* 270 */ 844, 844, 844, 844, 825, 1046, 844, 809, 844, 825, + /* 280 */ 844, 844, 1148, 909, 909, 1148, 1046, 1052, 1046, 1027, + /* 290 */ 994, 934, 922, 932, 929, 944, 1148, 757, 828, 760, + /* 300 */ 746, 746, 734, 734, 734, 734, 1139, 1139, 1135, 811, + /* 310 */ 811, 896, 1003, 1002, 1001, 785, 1039, 1148, 1148, 1148, + /* 320 */ 1148, 1148, 1148, 1060, 1148, 1148, 1148, 1148, 1148, 1148, + /* 330 */ 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 730, 1148, + /* 340 */ 1148, 1148, 1148, 1148, 1129, 1148, 1148, 1148, 1148, 1148, + /* 350 */ 1148, 1090, 1089, 1148, 1148, 1148, 1148, 1148, 1148, 1148, + /* 360 */ 1148, 1148, 1148, 1078, 1148, 1148, 1148, 1148, 1148, 1148, + /* 370 */ 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, + /* 380 */ 1148, 1148, 1148, 1148, 867, 869, 1148, 1148, 1148, 868, + /* 390 */ 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 1148, 930, + /* 400 */ 1148, 923, 1148, 1036, 1148, 1017, 1025, 1148, 1148, 1148, + /* 410 */ 1148, 1148, 1016, 1148, 1148, 1148, 1144, 1148, 1148, 1148, + /* 420 */ 1143, 1148, 1148, 1148, 1148, 1148, 1028, 980, 1148, 979, + /* 430 */ 978, 769, 1148, 744, 1148, 726, 731, 1128, 1125, 1127, + /* 440 */ 1122, 1123, 1121, 1124, 1120, 1118, 1119, 1116, 1114, 1113, + /* 450 */ 1115, 1110, 1106, 1066, 1064, 1062, 1071, 1070, 1069, 1068, + /* 460 */ 1067, 1063, 1061, 1065, 1059, 959, 947, 938, 862, 1105, + /* 470 */ 1103, 1104, 1057, 1055, 1056, 861, 860, 859, 854, 853, + /* 480 */ 852, 851, 1132, 1141, 1140, 1138, 1137, 1136, 1130, 1131, + /* 490 */ 1044, 1043, 1041, 1040, 1042, 762, 1082, 1085, 1084, 1083, + /* 500 */ 1088, 1087, 1080, 1092, 1097, 1096, 1101, 1100, 1099, 1098, + /* 510 */ 1095, 1077, 967, 966, 964, 969, 961, 960, 965, 952, + /* 520 */ 958, 957, 948, 951, 847, 943, 939, 942, 863, 1081, + /* 530 */ 858, 857, 856, 761, 756, 911, 755, 754, 765, 831, + /* 540 */ 832, 840, 843, 838, 841, 837, 836, 835, 839, 834, + /* 550 */ 830, 768, 767, 775, 824, 802, 800, 799, 803, 816, + /* 560 */ 815, 822, 821, 820, 819, 818, 814, 817, 813, 812, + /* 570 */ 804, 797, 796, 810, 795, 827, 826, 823, 794, 850, + /* 580 */ 849, 848, 845, 793, 792, 791, 790, 789, 788, 986, + /* 590 */ 985, 1006, 977, 865, 872, 871, 870, 874, 875, 885, + /* 600 */ 883, 882, 881, 917, 916, 915, 914, 913, 912, 905, + /* 610 */ 903, 899, 898, 904, 902, 920, 921, 919, 897, 889, + /* 620 */ 887, 888, 886, 974, 971, 973, 970, 907, 895, 892, + /* 630 */ 879, 925, 924, 1029, 1018, 1008, 1019, 910, 1007, 1005, + /* 640 */ 1028, 1025, 1020, 1102, 1024, 1012, 1011, 1010, 1147, 1145, + /* 650 */ 1146, 1049, 1051, 1054, 1053, 1050, 927, 926, 1048, 1047, + /* 660 */ 1009, 984, 781, 779, 780, 1074, 1073, 1076, 1075, 1072, + /* 670 */ 783, 782, 778, 777, 998, 997, 982, 1021, 1022, 981, + /* 680 */ 1023, 983, 770, 873, 866, 976, 975, 808, 807, 806, + /* 690 */ 805, 877, 876, 787, 801, 786, 784, 764, 759, 758, + /* 700 */ 753, 751, 748, 750, 747, 752, 749, 745, 743, 742, + /* 710 */ 741, 740, 739, 773, 772, 771, 769, 737, 736, 733, + /* 720 */ 732, 728, 727, 725, +}; + +/* The next table maps tokens into fallback tokens. If a construct +** like the following: +** +** %fallback ID X Y Z. +** +** appears in the grammar, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. +*/ +#ifdef YYFALLBACK +static const YYCODETYPE yyFallback[] = { + 0, /* $ => nothing */ + 0, /* ILLEGAL => nothing */ + 0, /* COMMENT => nothing */ + 0, /* SPACE => nothing */ + 0, /* ID => nothing */ + 4, /* ABORT => ID */ + 4, /* ACTION => ID */ + 4, /* AFTER => ID */ + 4, /* ANALYZE => ID */ + 4, /* ASC => ID */ + 4, /* ATTACH => ID */ + 4, /* BEFORE => ID */ + 4, /* BEGIN => ID */ + 4, /* BY => ID */ + 4, /* CASCADE => ID */ + 4, /* CAST => ID */ + 4, /* COLUMNKW => ID */ + 4, /* CONFLICT => ID */ + 4, /* DATABASE => ID */ + 4, /* DEFERRED => ID */ + 4, /* DESC => ID */ + 4, /* DETACH => ID */ + 4, /* EACH => ID */ + 4, /* END => ID */ + 4, /* EXCLUSIVE => ID */ + 4, /* EXPLAIN => ID */ + 4, /* FAIL => ID */ + 4, /* FOR => ID */ + 4, /* IGNORE => ID */ + 4, /* IMMEDIATE => ID */ + 4, /* INDEXED => ID */ + 4, /* INITIALLY => ID */ + 4, /* INSTEAD => ID */ + 4, /* LIKE_KW => ID */ + 4, /* MATCH => ID */ + 4, /* NO => ID */ + 4, /* PLAN => ID */ + 4, /* QUERY => ID */ + 4, /* KEY => ID */ + 4, /* OF => ID */ + 4, /* OFFSET => ID */ + 4, /* PRAGMA => ID */ + 4, /* RAISE => ID */ + 4, /* RECURSIVE => ID */ + 4, /* RELEASE => ID */ + 4, /* REPLACE => ID */ + 4, /* RESTRICT => ID */ + 4, /* ROW => ID */ + 4, /* ROLLBACK => ID */ + 4, /* SAVEPOINT => ID */ + 4, /* TEMP => ID */ + 4, /* TRIGGER => ID */ + 4, /* VACUUM => ID */ + 4, /* VIEW => ID */ + 4, /* VIRTUAL => ID */ + 4, /* WITH => ID */ + 4, /* WITHOUT => ID */ + 4, /* REINDEX => ID */ + 4, /* RENAME => ID */ + 4, /* CTIME_KW => ID */ + 4, /* IF => ID */ +}; +#endif /* YYFALLBACK */ + +/* The following structure represents a single element of the +** parser's stack. Information stored includes: +** +** + The state number for the parser at this level of the stack. +** +** + The value of the token stored at this level of the stack. +** (In other words, the "major" token.) +** +** + The semantic value stored at this level of the stack. This is +** the information used by the action routines in the grammar. +** It is sometimes called the "minor" token. +*/ +struct yyStackEntry { + YYACTIONTYPE stateno; /* The state-number */ + YYCODETYPE major; /* The major token value. This is the code + ** number for the token at this stack level */ + YYMINORTYPE minor; /* The user-supplied minor token value. This + ** is the value of the token */ + QList<Token*>* tokens = nullptr; +}; +typedef struct yyStackEntry yyStackEntry; + +/* The state of the parser is completely contained in an instance of +** the following structure */ +struct yyParser { + int yyidx; /* Index of top element in stack */ +#ifdef YYTRACKMAXSTACKDEPTH + int yyidxMax; /* Maximum value of yyidx */ +#endif + int yyerrcnt; /* Shifts left before out of the error */ + sqlite3_parseARG_SDECL /* A place to hold %extra_argument */ +#if YYSTACKDEPTH<=0 + int yystksz; /* Current side of the stack */ + yyStackEntry *yystack; /* The parser's stack */ +#else + yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ +#endif +}; +typedef struct yyParser yyParser; + +#ifndef NDEBUG +#include <stdio.h> +static FILE *yyTraceFILE = 0; +static char *yyTracePrompt = 0; +#endif /* NDEBUG */ + +void *sqlite3_parseCopyParserState(void* other) +{ + yyParser *pParser; + yyParser *otherParser = (yyParser*)other; + + // Copy parser + pParser = (yyParser*)malloc((size_t)sizeof(yyParser)); + memcpy(pParser, other, (size_t)sizeof(yyParser)); + +#if YYSTACKDEPTH<=0 + // Copy stack + int stackSize = sizeof(yyStackEntry) * pParser->yystksz; + pParser->yystack = malloc((size_t)stackSize); + memcpy(pParser->yystack, ((yyParser*)other)->yystack, (size_t)stackSize); +#endif + + for (int i = 0; i <= pParser->yyidx; i++) + { + pParser->yystack[i].tokens = new QList<Token*>(); + *(pParser->yystack[i].tokens) = *(otherParser->yystack[i].tokens); + } + + return pParser; +} + +void sqlite3_parseAddToken(void* other, Token* token) +{ + yyParser *otherParser = (yyParser*)other; + if (otherParser->yyidx < 0) + return; // Nothing on stack yet. Might happen when parsing just whitespaces, nothing else. + + otherParser->yystack[otherParser->yyidx].tokens->append(token); +} + +void sqlite3_parseRestoreParserState(void* saved, void* target) +{ + yyParser *pParser = (yyParser*)target; + yyParser *savedParser = (yyParser*)saved; + + for (int i = 0; i <= pParser->yyidx; i++) + delete pParser->yystack[i].tokens; + + memcpy(pParser, saved, (size_t)sizeof(yyParser)); + + for (int i = 0; i <= savedParser->yyidx; i++) + { + pParser->yystack[i].tokens = new QList<Token*>(); + *(pParser->yystack[i].tokens) = *(savedParser->yystack[i].tokens); + } + +#if YYSTACKDEPTH<=0 + // Copy stack + int stackSize = sizeof(yyStackEntry) * pParser->yystksz; + pParser->yystack = relloc(pParser->yystack, (size_t)stackSize); + memcpy(pParser->yystack, ((yyParser*)saved)->yystack, (size_t)stackSize); +#endif +} + +void sqlite3_parseFreeSavedState(void* other) +{ + yyParser *pParser = (yyParser*)other; + for (int i = 0; i <= pParser->yyidx; i++) + delete pParser->yystack[i].tokens; + +#if YYSTACKDEPTH<=0 + free(pParser->yystack); +#endif + free(other); +} + +#ifndef NDEBUG +/* +** Turn parser tracing on by giving a stream to which to write the trace +** and a prompt to preface each trace message. Tracing is turned off +** by making either argument NULL +** +** Inputs: +** <ul> +** <li> A FILE* to which trace output should be written. +** If NULL, then tracing is turned off. +** <li> A prefix string written at the beginning of every +** line of trace output. If NULL, then tracing is +** turned off. +** </ul> +** +** Outputs: +** None. +*/ +void sqlite3_parseTrace(FILE *TraceFILE, char *zTracePrompt){ + yyTraceFILE = TraceFILE; + yyTracePrompt = zTracePrompt; + if( yyTraceFILE==0 ) yyTracePrompt = 0; + else if( yyTracePrompt==0 ) yyTraceFILE = 0; +} +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *const yyTokenName[] = { + "$", "ILLEGAL", "COMMENT", "SPACE", + "ID", "ABORT", "ACTION", "AFTER", + "ANALYZE", "ASC", "ATTACH", "BEFORE", + "BEGIN", "BY", "CASCADE", "CAST", + "COLUMNKW", "CONFLICT", "DATABASE", "DEFERRED", + "DESC", "DETACH", "EACH", "END", + "EXCLUSIVE", "EXPLAIN", "FAIL", "FOR", + "IGNORE", "IMMEDIATE", "INDEXED", "INITIALLY", + "INSTEAD", "LIKE_KW", "MATCH", "NO", + "PLAN", "QUERY", "KEY", "OF", + "OFFSET", "PRAGMA", "RAISE", "RECURSIVE", + "RELEASE", "REPLACE", "RESTRICT", "ROW", + "ROLLBACK", "SAVEPOINT", "TEMP", "TRIGGER", + "VACUUM", "VIEW", "VIRTUAL", "WITH", + "WITHOUT", "REINDEX", "RENAME", "CTIME_KW", + "IF", "ANY", "OR", "AND", + "NOT", "IS", "BETWEEN", "IN", + "ISNULL", "NOTNULL", "NE", "EQ", + "GT", "LE", "LT", "GE", + "ESCAPE", "BITAND", "BITOR", "LSHIFT", + "RSHIFT", "PLUS", "MINUS", "STAR", + "SLASH", "REM", "CONCAT", "COLLATE", + "BITNOT", "SEMI", "TRANSACTION", "ID_TRANS", + "COMMIT", "TO", "CREATE", "TABLE", + "LP", "RP", "AS", "DOT", + "ID_TAB_NEW", "ID_DB", "CTX_ROWID_KW", "EXISTS", + "COMMA", "ID_COL_NEW", "STRING", "JOIN_KW", + "ID_COL_TYPE", "CONSTRAINT", "DEFAULT", "NULL", + "PRIMARY", "UNIQUE", "CHECK", "REFERENCES", + "ID_CONSTR", "ID_COLLATE", "ID_TAB", "INTEGER", + "FLOAT", "BLOB", "AUTOINCR", "ON", + "INSERT", "DELETE", "UPDATE", "ID_FK_MATCH", + "SET", "DEFERRABLE", "FOREIGN", "DROP", + "ID_VIEW_NEW", "ID_VIEW", "UNION", "ALL", + "EXCEPT", "INTERSECT", "SELECT", "VALUES", + "DISTINCT", "ID_ALIAS", "FROM", "USING", + "JOIN", "ID_JOIN_OPTS", "ID_IDX", "ORDER", + "GROUP", "HAVING", "LIMIT", "WHERE", + "ID_COL", "INTO", "VARIABLE", "CASE", + "ID_FN", "ID_ERR_MSG", "WHEN", "THEN", + "ELSE", "INDEX", "ID_IDX_NEW", "ID_PRAGMA", + "ID_TRIG_NEW", "ID_TRIG", "ALTER", "ADD", + "error", "cmd", "input", "cmdlist", + "ecmd", "explain", "cmdx", "transtype", + "trans_opt", "nm", "savepoint_opt", "temp", + "ifnotexists", "fullname", "columnlist", "conslist_opt", + "table_options", "select", "column", "columnid", + "type", "carglist", "id", "ids", + "typetoken", "typename", "signed", "plus_num", + "minus_num", "ccons", "term", "expr", + "onconf", "sortorder", "autoinc", "idxlist_opt", + "refargs", "defer_subclause", "refarg", "refact", + "init_deferred_pred_opt", "conslist", "tconscomma", "tcons", + "idxlist", "defer_subclause_opt", "resolvetype", "orconf", + "raisetype", "ifexists", "select_stmt", "with", + "selectnowith", "oneselect", "multiselect_op", "values", + "distinct", "selcollist", "from", "where_opt", + "groupby_opt", "having_opt", "orderby_opt", "limit_opt", + "nexprlist", "exprlist", "sclp", "as", + "joinsrc", "singlesrc", "seltablist", "joinop", + "joinconstr_opt", "dbnm", "indexed_opt", "inscollist", + "sortlist", "delete_stmt", "update_stmt", "setlist", + "insert_stmt", "insert_cmd", "inscollist_opt", "exprx", + "not_opt", "likeop", "case_operand", "case_exprlist", + "case_else", "uniqueflag", "idxlist_single", "collate", + "nmnum", "number", "trigger_time", "trigger_event", + "foreach_clause", "when_clause", "trigger_cmd_list", "trigger_cmd", + "database_kw_opt", "key_opt", "kwcolumn_opt", "create_vtab", + "vtabarglist", "vtabarg", "vtabargtoken", "anylist", + "wqlist", +}; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *const yyRuleName[] = { + /* 0 */ "input ::= cmdlist", + /* 1 */ "cmdlist ::= cmdlist ecmd", + /* 2 */ "cmdlist ::= ecmd", + /* 3 */ "ecmd ::= SEMI", + /* 4 */ "ecmd ::= explain cmdx SEMI", + /* 5 */ "explain ::=", + /* 6 */ "explain ::= EXPLAIN", + /* 7 */ "explain ::= EXPLAIN QUERY PLAN", + /* 8 */ "cmdx ::= cmd", + /* 9 */ "cmd ::= BEGIN transtype trans_opt", + /* 10 */ "trans_opt ::=", + /* 11 */ "trans_opt ::= TRANSACTION", + /* 12 */ "trans_opt ::= TRANSACTION nm", + /* 13 */ "trans_opt ::= TRANSACTION ID_TRANS", + /* 14 */ "transtype ::=", + /* 15 */ "transtype ::= DEFERRED", + /* 16 */ "transtype ::= IMMEDIATE", + /* 17 */ "transtype ::= EXCLUSIVE", + /* 18 */ "cmd ::= COMMIT trans_opt", + /* 19 */ "cmd ::= END trans_opt", + /* 20 */ "cmd ::= ROLLBACK trans_opt", + /* 21 */ "savepoint_opt ::= SAVEPOINT", + /* 22 */ "savepoint_opt ::=", + /* 23 */ "cmd ::= SAVEPOINT nm", + /* 24 */ "cmd ::= RELEASE savepoint_opt nm", + /* 25 */ "cmd ::= ROLLBACK trans_opt TO savepoint_opt nm", + /* 26 */ "cmd ::= SAVEPOINT ID_TRANS", + /* 27 */ "cmd ::= RELEASE savepoint_opt ID_TRANS", + /* 28 */ "cmd ::= ROLLBACK trans_opt TO savepoint_opt ID_TRANS", + /* 29 */ "cmd ::= CREATE temp TABLE ifnotexists fullname LP columnlist conslist_opt RP table_options", + /* 30 */ "cmd ::= CREATE temp TABLE ifnotexists fullname AS select", + /* 31 */ "cmd ::= CREATE temp TABLE ifnotexists nm DOT ID_TAB_NEW", + /* 32 */ "cmd ::= CREATE temp TABLE ifnotexists ID_DB|ID_TAB_NEW", + /* 33 */ "table_options ::=", + /* 34 */ "table_options ::= WITHOUT nm", + /* 35 */ "table_options ::= WITHOUT CTX_ROWID_KW", + /* 36 */ "ifnotexists ::=", + /* 37 */ "ifnotexists ::= IF NOT EXISTS", + /* 38 */ "temp ::= TEMP", + /* 39 */ "temp ::=", + /* 40 */ "columnlist ::= columnlist COMMA column", + /* 41 */ "columnlist ::= column", + /* 42 */ "column ::= columnid type carglist", + /* 43 */ "columnid ::= nm", + /* 44 */ "columnid ::= ID_COL_NEW", + /* 45 */ "id ::= ID", + /* 46 */ "ids ::= ID|STRING", + /* 47 */ "nm ::= id", + /* 48 */ "nm ::= STRING", + /* 49 */ "nm ::= JOIN_KW", + /* 50 */ "type ::=", + /* 51 */ "type ::= typetoken", + /* 52 */ "typetoken ::= typename", + /* 53 */ "typetoken ::= typename LP signed RP", + /* 54 */ "typetoken ::= typename LP signed COMMA signed RP", + /* 55 */ "typename ::= ids", + /* 56 */ "typename ::= typename ids", + /* 57 */ "typename ::= ID_COL_TYPE", + /* 58 */ "signed ::= plus_num", + /* 59 */ "signed ::= minus_num", + /* 60 */ "carglist ::= carglist ccons", + /* 61 */ "carglist ::=", + /* 62 */ "ccons ::= CONSTRAINT nm", + /* 63 */ "ccons ::= DEFAULT term", + /* 64 */ "ccons ::= DEFAULT LP expr RP", + /* 65 */ "ccons ::= DEFAULT PLUS term", + /* 66 */ "ccons ::= DEFAULT MINUS term", + /* 67 */ "ccons ::= DEFAULT id", + /* 68 */ "ccons ::= DEFAULT CTIME_KW", + /* 69 */ "ccons ::= NULL onconf", + /* 70 */ "ccons ::= NOT NULL onconf", + /* 71 */ "ccons ::= PRIMARY KEY sortorder onconf autoinc", + /* 72 */ "ccons ::= UNIQUE onconf", + /* 73 */ "ccons ::= CHECK LP expr RP", + /* 74 */ "ccons ::= REFERENCES nm idxlist_opt refargs", + /* 75 */ "ccons ::= defer_subclause", + /* 76 */ "ccons ::= COLLATE ids", + /* 77 */ "ccons ::= CONSTRAINT ID_CONSTR", + /* 78 */ "ccons ::= COLLATE ID_COLLATE", + /* 79 */ "ccons ::= REFERENCES ID_TAB", + /* 80 */ "ccons ::= CHECK LP RP", + /* 81 */ "term ::= NULL", + /* 82 */ "term ::= INTEGER", + /* 83 */ "term ::= FLOAT", + /* 84 */ "term ::= STRING|BLOB", + /* 85 */ "autoinc ::=", + /* 86 */ "autoinc ::= AUTOINCR", + /* 87 */ "refargs ::=", + /* 88 */ "refargs ::= refargs refarg", + /* 89 */ "refarg ::= MATCH nm", + /* 90 */ "refarg ::= ON INSERT refact", + /* 91 */ "refarg ::= ON DELETE refact", + /* 92 */ "refarg ::= ON UPDATE refact", + /* 93 */ "refarg ::= MATCH ID_FK_MATCH", + /* 94 */ "refact ::= SET NULL", + /* 95 */ "refact ::= SET DEFAULT", + /* 96 */ "refact ::= CASCADE", + /* 97 */ "refact ::= RESTRICT", + /* 98 */ "refact ::= NO ACTION", + /* 99 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt", + /* 100 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt", + /* 101 */ "init_deferred_pred_opt ::=", + /* 102 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED", + /* 103 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE", + /* 104 */ "conslist_opt ::=", + /* 105 */ "conslist_opt ::= COMMA conslist", + /* 106 */ "conslist ::= conslist tconscomma tcons", + /* 107 */ "conslist ::= tcons", + /* 108 */ "tconscomma ::= COMMA", + /* 109 */ "tconscomma ::=", + /* 110 */ "tcons ::= CONSTRAINT nm", + /* 111 */ "tcons ::= PRIMARY KEY LP idxlist autoinc RP onconf", + /* 112 */ "tcons ::= UNIQUE LP idxlist RP onconf", + /* 113 */ "tcons ::= CHECK LP expr RP onconf", + /* 114 */ "tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt", + /* 115 */ "tcons ::= CONSTRAINT ID_CONSTR", + /* 116 */ "tcons ::= FOREIGN KEY LP idxlist RP REFERENCES ID_TAB", + /* 117 */ "tcons ::= CHECK LP RP onconf", + /* 118 */ "defer_subclause_opt ::=", + /* 119 */ "defer_subclause_opt ::= defer_subclause", + /* 120 */ "onconf ::=", + /* 121 */ "onconf ::= ON CONFLICT resolvetype", + /* 122 */ "orconf ::=", + /* 123 */ "orconf ::= OR resolvetype", + /* 124 */ "resolvetype ::= raisetype", + /* 125 */ "resolvetype ::= IGNORE", + /* 126 */ "resolvetype ::= REPLACE", + /* 127 */ "cmd ::= DROP TABLE ifexists fullname", + /* 128 */ "cmd ::= DROP TABLE ifexists nm DOT ID_TAB", + /* 129 */ "cmd ::= DROP TABLE ifexists ID_DB|ID_TAB", + /* 130 */ "ifexists ::= IF EXISTS", + /* 131 */ "ifexists ::=", + /* 132 */ "cmd ::= CREATE temp VIEW ifnotexists fullname AS select", + /* 133 */ "cmd ::= CREATE temp VIEW ifnotexists nm DOT ID_VIEW_NEW", + /* 134 */ "cmd ::= CREATE temp VIEW ifnotexists ID_DB|ID_VIEW_NEW", + /* 135 */ "cmd ::= DROP VIEW ifexists fullname", + /* 136 */ "cmd ::= DROP VIEW ifexists nm DOT ID_VIEW", + /* 137 */ "cmd ::= DROP VIEW ifexists ID_DB|ID_VIEW", + /* 138 */ "cmd ::= select_stmt", + /* 139 */ "select_stmt ::= select", + /* 140 */ "select ::= with selectnowith", + /* 141 */ "selectnowith ::= oneselect", + /* 142 */ "selectnowith ::= selectnowith multiselect_op oneselect", + /* 143 */ "selectnowith ::= values", + /* 144 */ "selectnowith ::= selectnowith COMMA values", + /* 145 */ "multiselect_op ::= UNION", + /* 146 */ "multiselect_op ::= UNION ALL", + /* 147 */ "multiselect_op ::= EXCEPT", + /* 148 */ "multiselect_op ::= INTERSECT", + /* 149 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt", + /* 150 */ "values ::= VALUES LP nexprlist RP", + /* 151 */ "values ::= values COMMA LP exprlist RP", + /* 152 */ "distinct ::= DISTINCT", + /* 153 */ "distinct ::= ALL", + /* 154 */ "distinct ::=", + /* 155 */ "sclp ::= selcollist COMMA", + /* 156 */ "sclp ::=", + /* 157 */ "selcollist ::= sclp expr as", + /* 158 */ "selcollist ::= sclp STAR", + /* 159 */ "selcollist ::= sclp nm DOT STAR", + /* 160 */ "selcollist ::= sclp", + /* 161 */ "selcollist ::= sclp ID_TAB DOT STAR", + /* 162 */ "as ::= AS nm", + /* 163 */ "as ::= ids", + /* 164 */ "as ::= AS ID_ALIAS", + /* 165 */ "as ::= ID_ALIAS", + /* 166 */ "as ::=", + /* 167 */ "from ::=", + /* 168 */ "from ::= FROM joinsrc", + /* 169 */ "joinsrc ::= singlesrc seltablist", + /* 170 */ "joinsrc ::=", + /* 171 */ "seltablist ::= seltablist joinop singlesrc joinconstr_opt", + /* 172 */ "seltablist ::=", + /* 173 */ "singlesrc ::= nm dbnm as indexed_opt", + /* 174 */ "singlesrc ::= LP select RP as", + /* 175 */ "singlesrc ::= LP joinsrc RP as", + /* 176 */ "singlesrc ::=", + /* 177 */ "singlesrc ::= nm DOT", + /* 178 */ "singlesrc ::= nm DOT ID_TAB", + /* 179 */ "singlesrc ::= ID_DB|ID_TAB", + /* 180 */ "singlesrc ::= nm DOT ID_VIEW", + /* 181 */ "singlesrc ::= ID_DB|ID_VIEW", + /* 182 */ "joinconstr_opt ::= ON expr", + /* 183 */ "joinconstr_opt ::= USING LP inscollist RP", + /* 184 */ "joinconstr_opt ::=", + /* 185 */ "dbnm ::=", + /* 186 */ "dbnm ::= DOT nm", + /* 187 */ "fullname ::= nm dbnm", + /* 188 */ "joinop ::= COMMA", + /* 189 */ "joinop ::= JOIN", + /* 190 */ "joinop ::= JOIN_KW JOIN", + /* 191 */ "joinop ::= JOIN_KW nm JOIN", + /* 192 */ "joinop ::= JOIN_KW nm nm JOIN", + /* 193 */ "joinop ::= ID_JOIN_OPTS", + /* 194 */ "indexed_opt ::=", + /* 195 */ "indexed_opt ::= INDEXED BY nm", + /* 196 */ "indexed_opt ::= NOT INDEXED", + /* 197 */ "indexed_opt ::= INDEXED BY ID_IDX", + /* 198 */ "orderby_opt ::=", + /* 199 */ "orderby_opt ::= ORDER BY sortlist", + /* 200 */ "sortlist ::= sortlist COMMA expr sortorder", + /* 201 */ "sortlist ::= expr sortorder", + /* 202 */ "sortorder ::= ASC", + /* 203 */ "sortorder ::= DESC", + /* 204 */ "sortorder ::=", + /* 205 */ "groupby_opt ::=", + /* 206 */ "groupby_opt ::= GROUP BY nexprlist", + /* 207 */ "groupby_opt ::= GROUP BY", + /* 208 */ "having_opt ::=", + /* 209 */ "having_opt ::= HAVING expr", + /* 210 */ "limit_opt ::=", + /* 211 */ "limit_opt ::= LIMIT expr", + /* 212 */ "limit_opt ::= LIMIT expr OFFSET expr", + /* 213 */ "limit_opt ::= LIMIT expr COMMA expr", + /* 214 */ "cmd ::= delete_stmt", + /* 215 */ "delete_stmt ::= with DELETE FROM fullname indexed_opt where_opt", + /* 216 */ "delete_stmt ::= with DELETE FROM", + /* 217 */ "delete_stmt ::= with DELETE FROM nm DOT", + /* 218 */ "delete_stmt ::= with DELETE FROM nm DOT ID_TAB", + /* 219 */ "delete_stmt ::= with DELETE FROM ID_DB|ID_TAB", + /* 220 */ "where_opt ::=", + /* 221 */ "where_opt ::= WHERE expr", + /* 222 */ "where_opt ::= WHERE", + /* 223 */ "cmd ::= update_stmt", + /* 224 */ "update_stmt ::= with UPDATE orconf fullname indexed_opt SET setlist where_opt", + /* 225 */ "update_stmt ::= with UPDATE orconf", + /* 226 */ "update_stmt ::= with UPDATE orconf nm DOT", + /* 227 */ "update_stmt ::= with UPDATE orconf nm DOT ID_TAB", + /* 228 */ "update_stmt ::= with UPDATE orconf ID_DB|ID_TAB", + /* 229 */ "setlist ::= setlist COMMA nm EQ expr", + /* 230 */ "setlist ::= nm EQ expr", + /* 231 */ "setlist ::=", + /* 232 */ "setlist ::= setlist COMMA", + /* 233 */ "setlist ::= setlist COMMA ID_COL", + /* 234 */ "setlist ::= ID_COL", + /* 235 */ "cmd ::= insert_stmt", + /* 236 */ "insert_stmt ::= with insert_cmd INTO fullname inscollist_opt select", + /* 237 */ "insert_stmt ::= with insert_cmd INTO fullname inscollist_opt DEFAULT VALUES", + /* 238 */ "insert_stmt ::= with insert_cmd INTO", + /* 239 */ "insert_stmt ::= with insert_cmd INTO nm DOT", + /* 240 */ "insert_stmt ::= with insert_cmd INTO ID_DB|ID_TAB", + /* 241 */ "insert_stmt ::= with insert_cmd INTO nm DOT ID_TAB", + /* 242 */ "insert_cmd ::= INSERT orconf", + /* 243 */ "insert_cmd ::= REPLACE", + /* 244 */ "inscollist_opt ::=", + /* 245 */ "inscollist_opt ::= LP inscollist RP", + /* 246 */ "inscollist ::= inscollist COMMA nm", + /* 247 */ "inscollist ::= nm", + /* 248 */ "inscollist ::=", + /* 249 */ "inscollist ::= inscollist COMMA ID_COL", + /* 250 */ "inscollist ::= ID_COL", + /* 251 */ "exprx ::= term", + /* 252 */ "exprx ::= CTIME_KW", + /* 253 */ "exprx ::= LP expr RP", + /* 254 */ "exprx ::= id", + /* 255 */ "exprx ::= JOIN_KW", + /* 256 */ "exprx ::= nm DOT nm", + /* 257 */ "exprx ::= nm DOT nm DOT nm", + /* 258 */ "exprx ::= VARIABLE", + /* 259 */ "exprx ::= expr COLLATE ids", + /* 260 */ "exprx ::= CAST LP expr AS typetoken RP", + /* 261 */ "exprx ::= ID LP distinct exprlist RP", + /* 262 */ "exprx ::= ID LP STAR RP", + /* 263 */ "exprx ::= expr AND expr", + /* 264 */ "exprx ::= expr OR expr", + /* 265 */ "exprx ::= expr LT|GT|GE|LE expr", + /* 266 */ "exprx ::= expr EQ|NE expr", + /* 267 */ "exprx ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr", + /* 268 */ "exprx ::= expr PLUS|MINUS expr", + /* 269 */ "exprx ::= expr STAR|SLASH|REM expr", + /* 270 */ "exprx ::= expr CONCAT expr", + /* 271 */ "exprx ::= expr not_opt likeop expr", + /* 272 */ "exprx ::= expr not_opt likeop expr ESCAPE expr", + /* 273 */ "exprx ::= expr ISNULL|NOTNULL", + /* 274 */ "exprx ::= expr NOT NULL", + /* 275 */ "exprx ::= expr IS not_opt expr", + /* 276 */ "exprx ::= NOT expr", + /* 277 */ "exprx ::= BITNOT expr", + /* 278 */ "exprx ::= MINUS expr", + /* 279 */ "exprx ::= PLUS expr", + /* 280 */ "exprx ::= expr not_opt BETWEEN expr AND expr", + /* 281 */ "exprx ::= expr not_opt IN LP exprlist RP", + /* 282 */ "exprx ::= LP select RP", + /* 283 */ "exprx ::= expr not_opt IN LP select RP", + /* 284 */ "exprx ::= expr not_opt IN nm dbnm", + /* 285 */ "exprx ::= EXISTS LP select RP", + /* 286 */ "exprx ::= CASE case_operand case_exprlist case_else END", + /* 287 */ "exprx ::= RAISE LP IGNORE RP", + /* 288 */ "exprx ::= RAISE LP raisetype COMMA nm RP", + /* 289 */ "exprx ::= nm DOT", + /* 290 */ "exprx ::= nm DOT nm DOT", + /* 291 */ "exprx ::= expr not_opt BETWEEN expr", + /* 292 */ "exprx ::= CASE case_operand case_exprlist case_else", + /* 293 */ "exprx ::= expr not_opt IN LP exprlist", + /* 294 */ "exprx ::= expr not_opt IN ID_DB", + /* 295 */ "exprx ::= expr not_opt IN nm DOT ID_TAB", + /* 296 */ "exprx ::= ID_DB|ID_TAB|ID_COL|ID_FN", + /* 297 */ "exprx ::= nm DOT ID_TAB|ID_COL", + /* 298 */ "exprx ::= nm DOT nm DOT ID_COL", + /* 299 */ "exprx ::= expr COLLATE ID_COLLATE", + /* 300 */ "exprx ::= RAISE LP raisetype COMMA ID_ERR_MSG RP", + /* 301 */ "expr ::= exprx", + /* 302 */ "expr ::=", + /* 303 */ "not_opt ::=", + /* 304 */ "not_opt ::= NOT", + /* 305 */ "likeop ::= LIKE_KW|MATCH", + /* 306 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", + /* 307 */ "case_exprlist ::= WHEN expr THEN expr", + /* 308 */ "case_else ::= ELSE expr", + /* 309 */ "case_else ::=", + /* 310 */ "case_operand ::= exprx", + /* 311 */ "case_operand ::=", + /* 312 */ "exprlist ::= nexprlist", + /* 313 */ "exprlist ::=", + /* 314 */ "nexprlist ::= nexprlist COMMA expr", + /* 315 */ "nexprlist ::= exprx", + /* 316 */ "cmd ::= CREATE uniqueflag INDEX ifnotexists nm dbnm ON nm LP idxlist RP where_opt", + /* 317 */ "cmd ::= CREATE uniqueflag INDEX ifnotexists nm dbnm ON ID_TAB", + /* 318 */ "cmd ::= CREATE uniqueflag INDEX ifnotexists nm DOT ID_IDX_NEW", + /* 319 */ "cmd ::= CREATE uniqueflag INDEX ifnotexists ID_DB|ID_IDX_NEW", + /* 320 */ "uniqueflag ::= UNIQUE", + /* 321 */ "uniqueflag ::=", + /* 322 */ "idxlist_opt ::=", + /* 323 */ "idxlist_opt ::= LP idxlist RP", + /* 324 */ "idxlist ::= idxlist COMMA idxlist_single", + /* 325 */ "idxlist ::= idxlist_single", + /* 326 */ "idxlist_single ::= nm collate sortorder", + /* 327 */ "idxlist_single ::= ID_COL", + /* 328 */ "collate ::=", + /* 329 */ "collate ::= COLLATE ids", + /* 330 */ "collate ::= COLLATE ID_COLLATE", + /* 331 */ "cmd ::= DROP INDEX ifexists fullname", + /* 332 */ "cmd ::= DROP INDEX ifexists nm DOT ID_IDX", + /* 333 */ "cmd ::= DROP INDEX ifexists ID_DB|ID_IDX", + /* 334 */ "cmd ::= VACUUM", + /* 335 */ "cmd ::= VACUUM nm", + /* 336 */ "cmd ::= PRAGMA nm dbnm", + /* 337 */ "cmd ::= PRAGMA nm dbnm EQ nmnum", + /* 338 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP", + /* 339 */ "cmd ::= PRAGMA nm dbnm EQ minus_num", + /* 340 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP", + /* 341 */ "cmd ::= PRAGMA nm DOT ID_PRAGMA", + /* 342 */ "cmd ::= PRAGMA ID_DB|ID_PRAGMA", + /* 343 */ "nmnum ::= plus_num", + /* 344 */ "nmnum ::= nm", + /* 345 */ "nmnum ::= ON", + /* 346 */ "nmnum ::= DELETE", + /* 347 */ "nmnum ::= DEFAULT", + /* 348 */ "plus_num ::= PLUS number", + /* 349 */ "plus_num ::= number", + /* 350 */ "minus_num ::= MINUS number", + /* 351 */ "number ::= INTEGER", + /* 352 */ "number ::= FLOAT", + /* 353 */ "cmd ::= CREATE temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON nm foreach_clause when_clause BEGIN trigger_cmd_list END", + /* 354 */ "cmd ::= CREATE temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON nm foreach_clause when_clause", + /* 355 */ "cmd ::= CREATE temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON nm foreach_clause when_clause BEGIN trigger_cmd_list", + /* 356 */ "cmd ::= CREATE temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON ID_TAB", + /* 357 */ "cmd ::= CREATE temp TRIGGER ifnotexists nm DOT ID_TRIG_NEW", + /* 358 */ "cmd ::= CREATE temp TRIGGER ifnotexists ID_DB|ID_TRIG_NEW", + /* 359 */ "trigger_time ::= BEFORE", + /* 360 */ "trigger_time ::= AFTER", + /* 361 */ "trigger_time ::= INSTEAD OF", + /* 362 */ "trigger_time ::=", + /* 363 */ "trigger_event ::= DELETE", + /* 364 */ "trigger_event ::= INSERT", + /* 365 */ "trigger_event ::= UPDATE", + /* 366 */ "trigger_event ::= UPDATE OF inscollist", + /* 367 */ "foreach_clause ::=", + /* 368 */ "foreach_clause ::= FOR EACH ROW", + /* 369 */ "when_clause ::=", + /* 370 */ "when_clause ::= WHEN expr", + /* 371 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", + /* 372 */ "trigger_cmd_list ::= trigger_cmd SEMI", + /* 373 */ "trigger_cmd_list ::= SEMI", + /* 374 */ "trigger_cmd ::= update_stmt", + /* 375 */ "trigger_cmd ::= insert_stmt", + /* 376 */ "trigger_cmd ::= delete_stmt", + /* 377 */ "trigger_cmd ::= select_stmt", + /* 378 */ "raisetype ::= ROLLBACK|ABORT|FAIL", + /* 379 */ "cmd ::= DROP TRIGGER ifexists fullname", + /* 380 */ "cmd ::= DROP TRIGGER ifexists nm DOT ID_TRIG", + /* 381 */ "cmd ::= DROP TRIGGER ifexists ID_DB|ID_TRIG", + /* 382 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt", + /* 383 */ "cmd ::= DETACH database_kw_opt expr", + /* 384 */ "key_opt ::=", + /* 385 */ "key_opt ::= KEY expr", + /* 386 */ "database_kw_opt ::= DATABASE", + /* 387 */ "database_kw_opt ::=", + /* 388 */ "cmd ::= REINDEX", + /* 389 */ "cmd ::= REINDEX nm dbnm", + /* 390 */ "cmd ::= REINDEX ID_COLLATE", + /* 391 */ "cmd ::= REINDEX nm DOT ID_TAB|ID_IDX", + /* 392 */ "cmd ::= REINDEX ID_DB|ID_IDX|ID_TAB", + /* 393 */ "cmd ::= ANALYZE", + /* 394 */ "cmd ::= ANALYZE nm dbnm", + /* 395 */ "cmd ::= ANALYZE nm DOT ID_TAB|ID_IDX", + /* 396 */ "cmd ::= ANALYZE ID_DB|ID_IDX|ID_TAB", + /* 397 */ "cmd ::= ALTER TABLE fullname RENAME TO nm", + /* 398 */ "cmd ::= ALTER TABLE fullname ADD kwcolumn_opt column", + /* 399 */ "cmd ::= ALTER TABLE fullname RENAME TO ID_TAB_NEW", + /* 400 */ "cmd ::= ALTER TABLE nm DOT ID_TAB", + /* 401 */ "cmd ::= ALTER TABLE ID_DB|ID_TAB", + /* 402 */ "kwcolumn_opt ::=", + /* 403 */ "kwcolumn_opt ::= COLUMNKW", + /* 404 */ "cmd ::= create_vtab", + /* 405 */ "create_vtab ::= CREATE VIRTUAL TABLE ifnotexists nm dbnm USING nm", + /* 406 */ "create_vtab ::= CREATE VIRTUAL TABLE ifnotexists nm dbnm USING nm LP vtabarglist RP", + /* 407 */ "create_vtab ::= CREATE VIRTUAL TABLE ifnotexists nm DOT ID_TAB_NEW", + /* 408 */ "create_vtab ::= CREATE VIRTUAL TABLE ifnotexists ID_DB|ID_TAB_NEW", + /* 409 */ "vtabarglist ::= vtabarg", + /* 410 */ "vtabarglist ::= vtabarglist COMMA vtabarg", + /* 411 */ "vtabarg ::=", + /* 412 */ "vtabarg ::= vtabarg vtabargtoken", + /* 413 */ "vtabargtoken ::= ANY", + /* 414 */ "vtabargtoken ::= LP anylist RP", + /* 415 */ "anylist ::=", + /* 416 */ "anylist ::= anylist LP anylist RP", + /* 417 */ "anylist ::= anylist ANY", + /* 418 */ "with ::=", + /* 419 */ "with ::= WITH wqlist", + /* 420 */ "with ::= WITH RECURSIVE wqlist", + /* 421 */ "wqlist ::= nm idxlist_opt AS LP select RP", + /* 422 */ "wqlist ::= wqlist COMMA nm idxlist_opt AS LP select RP", + /* 423 */ "wqlist ::= ID_TAB_NEW", +}; +#endif /* NDEBUG */ + + +#if YYSTACKDEPTH<=0 +/* +** Try to increase the size of the parser stack. +*/ +static void yyGrowStack(yyParser *p){ + int newSize; + yyStackEntry *pNew; + + newSize = p->yystksz*2 + 100; + pNew = realloc(p->yystack, newSize*sizeof(pNew[0])); + if( pNew ){ + p->yystack = pNew; + p->yystksz = newSize; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack grows to %d entries!\n", + yyTracePrompt, p->yystksz); + } +#endif + } +} +#endif + +/* +** This function allocates a new parser. +** The only argument is a pointer to a function which works like +** malloc. +** +** Inputs: +** A pointer to the function used to allocate memory. +** +** Outputs: +** A pointer to a parser. This pointer is used in subsequent calls +** to sqlite3_parse and sqlite3_parseFree. +*/ +void *sqlite3_parseAlloc(void *(*mallocProc)(size_t)){ + yyParser *pParser; + pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) ); + if( pParser ){ + pParser->yyidx = -1; +#ifdef YYTRACKMAXSTACKDEPTH + pParser->yyidxMax = 0; +#endif +#if YYSTACKDEPTH<=0 + pParser->yystack = NULL; + pParser->yystksz = 0; + yyGrowStack(pParser); +#endif + } + return pParser; +} + +/* The following function deletes the value associated with a +** symbol. The symbol can be either a terminal or nonterminal. +** "yymajor" is the symbol code, and "yypminor" is a pointer to +** the value. +*/ +static void yy_destructor( + yyParser *yypParser, /* The parser */ + YYCODETYPE yymajor, /* Type code for object to destroy */ + YYMINORTYPE *yypminor /* The object to be destroyed */ +){ + sqlite3_parseARG_FETCH; + if (parserContext->executeRules) + { + switch( yymajor ){ + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are not used + ** inside the C code. + */ + case 169: /* cmd */ + case 172: /* ecmd */ + case 174: /* cmdx */ + case 218: /* select_stmt */ + case 245: /* delete_stmt */ + case 246: /* update_stmt */ + case 248: /* insert_stmt */ + case 267: /* trigger_cmd */ + case 271: /* create_vtab */ +{ +delete (yypminor->yy399); +} + break; + case 173: /* explain */ +{ +delete (yypminor->yy225); +} + break; + case 175: /* transtype */ + case 176: /* trans_opt */ +{ +delete (yypminor->yy300); +} + break; + case 177: /* nm */ + case 184: /* table_options */ + case 187: /* columnid */ + case 190: /* id */ + case 191: /* ids */ + case 193: /* typename */ + case 241: /* dbnm */ + case 259: /* collate */ + case 273: /* vtabarg */ + case 274: /* vtabargtoken */ + case 275: /* anylist */ +{ +delete (yypminor->yy211); +} + break; + case 178: /* savepoint_opt */ + case 180: /* ifnotexists */ + case 202: /* autoinc */ + case 210: /* tconscomma */ + case 217: /* ifexists */ + case 252: /* not_opt */ + case 257: /* uniqueflag */ + case 268: /* database_kw_opt */ + case 270: /* kwcolumn_opt */ +{ +delete (yypminor->yy237); +} + break; + case 179: /* temp */ + case 224: /* distinct */ +{ +delete (yypminor->yy376); +} + break; + case 181: /* fullname */ +{ +delete (yypminor->yy66); +} + break; + case 182: /* columnlist */ +{ +delete (yypminor->yy118); +} + break; + case 183: /* conslist_opt */ + case 209: /* conslist */ +{ +delete (yypminor->yy87); +} + break; + case 185: /* select */ + case 220: /* selectnowith */ +{ +delete (yypminor->yy123); +} + break; + case 186: /* column */ +{ +delete (yypminor->yy425); +} + break; + case 188: /* type */ + case 192: /* typetoken */ +{ +delete (yypminor->yy299); +} + break; + case 189: /* carglist */ +{ +delete (yypminor->yy449); +} + break; + case 194: /* signed */ + case 195: /* plus_num */ + case 196: /* minus_num */ + case 198: /* term */ + case 260: /* nmnum */ + case 261: /* number */ +{ +delete (yypminor->yy21); +} + break; + case 197: /* ccons */ +{ +delete (yypminor->yy4); +} + break; + case 199: /* expr */ + case 227: /* where_opt */ + case 229: /* having_opt */ + case 251: /* exprx */ + case 254: /* case_operand */ + case 256: /* case_else */ +{ +delete (yypminor->yy490); +} + break; + case 200: /* onconf */ + case 214: /* resolvetype */ + case 215: /* orconf */ +{ +delete (yypminor->yy30); +} + break; + case 201: /* sortorder */ +{ +delete (yypminor->yy226); +} + break; + case 203: /* idxlist_opt */ + case 212: /* idxlist */ +{ +delete (yypminor->yy139); +} + break; + case 204: /* refargs */ +{ +delete (yypminor->yy108); +} + break; + case 205: /* defer_subclause */ + case 213: /* defer_subclause_opt */ +{ +delete (yypminor->yy131); +} + break; + case 206: /* refarg */ +{ +delete (yypminor->yy271); +} + break; + case 207: /* refact */ +{ +delete (yypminor->yy312); +} + break; + case 208: /* init_deferred_pred_opt */ +{ +delete (yypminor->yy498); +} + break; + case 211: /* tcons */ +{ +delete (yypminor->yy8); +} + break; + case 219: /* with */ + case 276: /* wqlist */ +{ +delete (yypminor->yy367); +} + break; + case 221: /* oneselect */ +{ +delete (yypminor->yy468); +} + break; + case 222: /* multiselect_op */ +{ +delete (yypminor->yy168); +} + break; + case 223: /* values */ +{ +delete (yypminor->yy416); +} + break; + case 225: /* selcollist */ + case 234: /* sclp */ +{ +delete (yypminor->yy263); +} + break; + case 226: /* from */ + case 236: /* joinsrc */ +{ +delete (yypminor->yy373); +} + break; + case 228: /* groupby_opt */ + case 232: /* nexprlist */ + case 233: /* exprlist */ + case 255: /* case_exprlist */ +{ +delete (yypminor->yy13); +} + break; + case 230: /* orderby_opt */ + case 244: /* sortlist */ +{ +delete (yypminor->yy495); +} + break; + case 231: /* limit_opt */ +{ +delete (yypminor->yy128); +} + break; + case 235: /* as */ +{ +delete (yypminor->yy28); +} + break; + case 237: /* singlesrc */ +{ +delete (yypminor->yy173); +} + break; + case 238: /* seltablist */ +{ +delete (yypminor->yy359); +} + break; + case 239: /* joinop */ +{ +delete (yypminor->yy473); +} + break; + case 240: /* joinconstr_opt */ +{ +delete (yypminor->yy117); +} + break; + case 242: /* indexed_opt */ +{ +delete (yypminor->yy472); +} + break; + case 243: /* inscollist */ + case 250: /* inscollist_opt */ + case 272: /* vtabarglist */ +{ +delete (yypminor->yy445); +} + break; + case 247: /* setlist */ +{ +delete (yypminor->yy381); +} + break; + case 249: /* insert_cmd */ +{ +delete (yypminor->yy250); +} + break; + case 253: /* likeop */ +{ +delete (yypminor->yy374); +} + break; + case 258: /* idxlist_single */ +{ +delete (yypminor->yy90); +} + break; + case 262: /* trigger_time */ +{ +delete (yypminor->yy152); +} + break; + case 263: /* trigger_event */ +{ +delete (yypminor->yy309); +} + break; + case 264: /* foreach_clause */ +{ +delete (yypminor->yy409); +} + break; + case 265: /* when_clause */ + case 269: /* key_opt */ +{ +if ((yypminor->yy490)) delete (yypminor->yy490); +} + break; + case 266: /* trigger_cmd_list */ +{ +delete (yypminor->yy214); +} + break; + default: break; /* If no destructor action specified: do nothing */ + } + } +} + +/* +** Pop the parser's stack once. +** +** If there is a destructor routine associated with the token which +** is popped from the stack, then call it. +** +** Return the major token number for the symbol popped. +*/ +static int yy_pop_parser_stack(yyParser *pParser){ + YYCODETYPE yymajor; + yyStackEntry *yytos = &pParser->yystack[pParser->yyidx]; + + /* There is no mechanism by which the parser stack can be popped below + ** empty in SQLite. */ + if( pParser->yyidx<0 ) return 0; +#ifndef NDEBUG + if( yyTraceFILE && pParser->yyidx>=0 ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + yymajor = yytos->major; + yy_destructor(pParser, yymajor, &yytos->minor); + delete yytos->tokens; + yytos->tokens = nullptr; + pParser->yyidx--; + return yymajor; +} + +/* +** Deallocate and destroy a parser. Destructors are all called for +** all stack elements before shutting the parser down. +** +** Inputs: +** <ul> +** <li> A pointer to the parser. This should be a pointer +** obtained from sqlite3_parseAlloc. +** <li> A pointer to a function used to reclaim memory obtained +** from malloc. +** </ul> +*/ +void sqlite3_parseFree( + void *p, /* The parser to be deleted */ + void (*freeProc)(void*) /* Function used to reclaim memory */ +){ + yyParser *pParser = (yyParser*)p; + /* In SQLite, we never try to destroy a parser that was not successfully + ** created in the first place. */ + if( pParser==0 ) return; + while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser); +#if YYSTACKDEPTH<=0 + free(pParser->yystack); +#endif + (*freeProc)((void*)pParser); +} + +/* +** Return the peak depth of the stack for a parser. +*/ +#ifdef YYTRACKMAXSTACKDEPTH +int sqlite3_parseStackPeak(void *p){ + yyParser *pParser = (yyParser*)p; + return pParser->yyidxMax; +} +#endif + +/* +** Find the appropriate action for a parser given the terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_shift_action( + yyParser *pParser, /* The parser */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; + int stateno = pParser->yystack[pParser->yyidx].stateno; + GET_CONTEXT; + + if( stateno>YY_SHIFT_COUNT + || (i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT ){ + return yy_default[stateno]; + } + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + if( iLookAhead>0 ){ +#ifdef YYFALLBACK + YYCODETYPE iFallback; /* Fallback token */ + if( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0]) + && (iFallback = yyFallback[iLookAhead])!=0 + && parserContext->doFallbacks ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } +#endif + return yy_find_shift_action(pParser, iFallback); + } +#endif +#ifdef YYWILDCARD + { + int j = i - iLookAhead + YYWILDCARD; + if( +#if YY_SHIFT_MIN+YYWILDCARD<0 + j>=0 && +#endif +#if YY_SHIFT_MAX+YYWILDCARD>=YY_ACTTAB_COUNT + j<YY_ACTTAB_COUNT && +#endif + yy_lookahead[j]==YYWILDCARD + ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[YYWILDCARD]); + } +#endif /* NDEBUG */ + return yy_action[j]; + } + } +#endif /* YYWILDCARD */ + } + return yy_default[stateno]; + }else{ + return yy_action[i]; + } +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_reduce_action( + int stateno, /* Current state number */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; +#ifdef YYERRORSYMBOL + if( stateno>YY_REDUCE_COUNT ){ + return yy_default[stateno]; + } +#else + assert( stateno<=YY_REDUCE_COUNT ); +#endif + i = yy_reduce_ofst[stateno]; + assert( i!=YY_REDUCE_USE_DFLT ); + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; +#ifdef YYERRORSYMBOL + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + return yy_default[stateno]; + } +#else + assert( i>=0 && i<YY_ACTTAB_COUNT ); + assert( yy_lookahead[i]==iLookAhead ); +#endif + return yy_action[i]; +} + +/* +** The following routine is called if the stack overflows. +*/ +static void yyStackOverflow(yyParser *yypParser, YYMINORTYPE *yypMinor){ + sqlite3_parseARG_FETCH; + yypParser->yyidx--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ + + UNUSED_PARAMETER(yypMinor); + parserContext->error(QObject::tr("Parser stack overflow")); + sqlite3_parseARG_STORE; /* Suppress warning about unused %extra_argument var */ +} + +/* +** Perform a shift action. +*/ +static void yy_shift( + yyParser *yypParser, /* The parser to be shifted */ + int yyNewState, /* The new state to shift in */ + int yyMajor, /* The major token to shift in */ + YYMINORTYPE *yypMinor /* Pointer to the minor token to shift in */ +){ + yyStackEntry *yytos; + yypParser->yyidx++; +#ifdef YYTRACKMAXSTACKDEPTH + if( yypParser->yyidx>yypParser->yyidxMax ){ + yypParser->yyidxMax = yypParser->yyidx; + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yyidx>=YYSTACKDEPTH ){ + yyStackOverflow(yypParser, yypMinor); + return; + } +#else + if( yypParser->yyidx>=yypParser->yystksz ){ + yyGrowStack(yypParser); + if( yypParser->yyidx>=yypParser->yystksz ){ + yyStackOverflow(yypParser, yypMinor); + return; + } + } +#endif + yytos = &yypParser->yystack[yypParser->yyidx]; + yytos->stateno = (YYACTIONTYPE)yyNewState; + yytos->major = (YYCODETYPE)yyMajor; + yytos->minor = *yypMinor; + yytos->tokens = new QList<Token*>(); +#ifndef NDEBUG + if( yyTraceFILE && yypParser->yyidx>0 ){ + int i; + fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState); + fprintf(yyTraceFILE,"%sStack:",yyTracePrompt); + for(i=1; i<=yypParser->yyidx; i++) + fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]); + fprintf(yyTraceFILE,"\n"); + } +#endif +} + +/* The following table contains information about every rule that +** is used during the reduce. +*/ +static const struct { + YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ + unsigned char nrhs; /* Number of right-hand side symbols in the rule */ +} yyRuleInfo[] = { + { 170, 1 }, + { 171, 2 }, + { 171, 1 }, + { 172, 1 }, + { 172, 3 }, + { 173, 0 }, + { 173, 1 }, + { 173, 3 }, + { 174, 1 }, + { 169, 3 }, + { 176, 0 }, + { 176, 1 }, + { 176, 2 }, + { 176, 2 }, + { 175, 0 }, + { 175, 1 }, + { 175, 1 }, + { 175, 1 }, + { 169, 2 }, + { 169, 2 }, + { 169, 2 }, + { 178, 1 }, + { 178, 0 }, + { 169, 2 }, + { 169, 3 }, + { 169, 5 }, + { 169, 2 }, + { 169, 3 }, + { 169, 5 }, + { 169, 10 }, + { 169, 7 }, + { 169, 7 }, + { 169, 5 }, + { 184, 0 }, + { 184, 2 }, + { 184, 2 }, + { 180, 0 }, + { 180, 3 }, + { 179, 1 }, + { 179, 0 }, + { 182, 3 }, + { 182, 1 }, + { 186, 3 }, + { 187, 1 }, + { 187, 1 }, + { 190, 1 }, + { 191, 1 }, + { 177, 1 }, + { 177, 1 }, + { 177, 1 }, + { 188, 0 }, + { 188, 1 }, + { 192, 1 }, + { 192, 4 }, + { 192, 6 }, + { 193, 1 }, + { 193, 2 }, + { 193, 1 }, + { 194, 1 }, + { 194, 1 }, + { 189, 2 }, + { 189, 0 }, + { 197, 2 }, + { 197, 2 }, + { 197, 4 }, + { 197, 3 }, + { 197, 3 }, + { 197, 2 }, + { 197, 2 }, + { 197, 2 }, + { 197, 3 }, + { 197, 5 }, + { 197, 2 }, + { 197, 4 }, + { 197, 4 }, + { 197, 1 }, + { 197, 2 }, + { 197, 2 }, + { 197, 2 }, + { 197, 2 }, + { 197, 3 }, + { 198, 1 }, + { 198, 1 }, + { 198, 1 }, + { 198, 1 }, + { 202, 0 }, + { 202, 1 }, + { 204, 0 }, + { 204, 2 }, + { 206, 2 }, + { 206, 3 }, + { 206, 3 }, + { 206, 3 }, + { 206, 2 }, + { 207, 2 }, + { 207, 2 }, + { 207, 1 }, + { 207, 1 }, + { 207, 2 }, + { 205, 3 }, + { 205, 2 }, + { 208, 0 }, + { 208, 2 }, + { 208, 2 }, + { 183, 0 }, + { 183, 2 }, + { 209, 3 }, + { 209, 1 }, + { 210, 1 }, + { 210, 0 }, + { 211, 2 }, + { 211, 7 }, + { 211, 5 }, + { 211, 5 }, + { 211, 10 }, + { 211, 2 }, + { 211, 7 }, + { 211, 4 }, + { 213, 0 }, + { 213, 1 }, + { 200, 0 }, + { 200, 3 }, + { 215, 0 }, + { 215, 2 }, + { 214, 1 }, + { 214, 1 }, + { 214, 1 }, + { 169, 4 }, + { 169, 6 }, + { 169, 4 }, + { 217, 2 }, + { 217, 0 }, + { 169, 7 }, + { 169, 7 }, + { 169, 5 }, + { 169, 4 }, + { 169, 6 }, + { 169, 4 }, + { 169, 1 }, + { 218, 1 }, + { 185, 2 }, + { 220, 1 }, + { 220, 3 }, + { 220, 1 }, + { 220, 3 }, + { 222, 1 }, + { 222, 2 }, + { 222, 1 }, + { 222, 1 }, + { 221, 9 }, + { 223, 4 }, + { 223, 5 }, + { 224, 1 }, + { 224, 1 }, + { 224, 0 }, + { 234, 2 }, + { 234, 0 }, + { 225, 3 }, + { 225, 2 }, + { 225, 4 }, + { 225, 1 }, + { 225, 4 }, + { 235, 2 }, + { 235, 1 }, + { 235, 2 }, + { 235, 1 }, + { 235, 0 }, + { 226, 0 }, + { 226, 2 }, + { 236, 2 }, + { 236, 0 }, + { 238, 4 }, + { 238, 0 }, + { 237, 4 }, + { 237, 4 }, + { 237, 4 }, + { 237, 0 }, + { 237, 2 }, + { 237, 3 }, + { 237, 1 }, + { 237, 3 }, + { 237, 1 }, + { 240, 2 }, + { 240, 4 }, + { 240, 0 }, + { 241, 0 }, + { 241, 2 }, + { 181, 2 }, + { 239, 1 }, + { 239, 1 }, + { 239, 2 }, + { 239, 3 }, + { 239, 4 }, + { 239, 1 }, + { 242, 0 }, + { 242, 3 }, + { 242, 2 }, + { 242, 3 }, + { 230, 0 }, + { 230, 3 }, + { 244, 4 }, + { 244, 2 }, + { 201, 1 }, + { 201, 1 }, + { 201, 0 }, + { 228, 0 }, + { 228, 3 }, + { 228, 2 }, + { 229, 0 }, + { 229, 2 }, + { 231, 0 }, + { 231, 2 }, + { 231, 4 }, + { 231, 4 }, + { 169, 1 }, + { 245, 6 }, + { 245, 3 }, + { 245, 5 }, + { 245, 6 }, + { 245, 4 }, + { 227, 0 }, + { 227, 2 }, + { 227, 1 }, + { 169, 1 }, + { 246, 8 }, + { 246, 3 }, + { 246, 5 }, + { 246, 6 }, + { 246, 4 }, + { 247, 5 }, + { 247, 3 }, + { 247, 0 }, + { 247, 2 }, + { 247, 3 }, + { 247, 1 }, + { 169, 1 }, + { 248, 6 }, + { 248, 7 }, + { 248, 3 }, + { 248, 5 }, + { 248, 4 }, + { 248, 6 }, + { 249, 2 }, + { 249, 1 }, + { 250, 0 }, + { 250, 3 }, + { 243, 3 }, + { 243, 1 }, + { 243, 0 }, + { 243, 3 }, + { 243, 1 }, + { 251, 1 }, + { 251, 1 }, + { 251, 3 }, + { 251, 1 }, + { 251, 1 }, + { 251, 3 }, + { 251, 5 }, + { 251, 1 }, + { 251, 3 }, + { 251, 6 }, + { 251, 5 }, + { 251, 4 }, + { 251, 3 }, + { 251, 3 }, + { 251, 3 }, + { 251, 3 }, + { 251, 3 }, + { 251, 3 }, + { 251, 3 }, + { 251, 3 }, + { 251, 4 }, + { 251, 6 }, + { 251, 2 }, + { 251, 3 }, + { 251, 4 }, + { 251, 2 }, + { 251, 2 }, + { 251, 2 }, + { 251, 2 }, + { 251, 6 }, + { 251, 6 }, + { 251, 3 }, + { 251, 6 }, + { 251, 5 }, + { 251, 4 }, + { 251, 5 }, + { 251, 4 }, + { 251, 6 }, + { 251, 2 }, + { 251, 4 }, + { 251, 4 }, + { 251, 4 }, + { 251, 5 }, + { 251, 4 }, + { 251, 6 }, + { 251, 1 }, + { 251, 3 }, + { 251, 5 }, + { 251, 3 }, + { 251, 6 }, + { 199, 1 }, + { 199, 0 }, + { 252, 0 }, + { 252, 1 }, + { 253, 1 }, + { 255, 5 }, + { 255, 4 }, + { 256, 2 }, + { 256, 0 }, + { 254, 1 }, + { 254, 0 }, + { 233, 1 }, + { 233, 0 }, + { 232, 3 }, + { 232, 1 }, + { 169, 12 }, + { 169, 8 }, + { 169, 7 }, + { 169, 5 }, + { 257, 1 }, + { 257, 0 }, + { 203, 0 }, + { 203, 3 }, + { 212, 3 }, + { 212, 1 }, + { 258, 3 }, + { 258, 1 }, + { 259, 0 }, + { 259, 2 }, + { 259, 2 }, + { 169, 4 }, + { 169, 6 }, + { 169, 4 }, + { 169, 1 }, + { 169, 2 }, + { 169, 3 }, + { 169, 5 }, + { 169, 6 }, + { 169, 5 }, + { 169, 6 }, + { 169, 4 }, + { 169, 2 }, + { 260, 1 }, + { 260, 1 }, + { 260, 1 }, + { 260, 1 }, + { 260, 1 }, + { 195, 2 }, + { 195, 1 }, + { 196, 2 }, + { 261, 1 }, + { 261, 1 }, + { 169, 15 }, + { 169, 12 }, + { 169, 14 }, + { 169, 10 }, + { 169, 7 }, + { 169, 5 }, + { 262, 1 }, + { 262, 1 }, + { 262, 2 }, + { 262, 0 }, + { 263, 1 }, + { 263, 1 }, + { 263, 1 }, + { 263, 3 }, + { 264, 0 }, + { 264, 3 }, + { 265, 0 }, + { 265, 2 }, + { 266, 3 }, + { 266, 2 }, + { 266, 1 }, + { 267, 1 }, + { 267, 1 }, + { 267, 1 }, + { 267, 1 }, + { 216, 1 }, + { 169, 4 }, + { 169, 6 }, + { 169, 4 }, + { 169, 6 }, + { 169, 3 }, + { 269, 0 }, + { 269, 2 }, + { 268, 1 }, + { 268, 0 }, + { 169, 1 }, + { 169, 3 }, + { 169, 2 }, + { 169, 4 }, + { 169, 2 }, + { 169, 1 }, + { 169, 3 }, + { 169, 4 }, + { 169, 2 }, + { 169, 6 }, + { 169, 6 }, + { 169, 6 }, + { 169, 5 }, + { 169, 3 }, + { 270, 0 }, + { 270, 1 }, + { 169, 1 }, + { 271, 8 }, + { 271, 11 }, + { 271, 7 }, + { 271, 5 }, + { 272, 1 }, + { 272, 3 }, + { 273, 0 }, + { 273, 2 }, + { 274, 1 }, + { 274, 3 }, + { 275, 0 }, + { 275, 4 }, + { 275, 2 }, + { 219, 0 }, + { 219, 2 }, + { 219, 3 }, + { 276, 6 }, + { 276, 8 }, + { 276, 1 }, +}; + +static void yy_accept(yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +*/ +static void yy_reduce( + yyParser *yypParser, /* The parser */ + int yyruleno /* Number of the rule by which to reduce */ +){ + int yygoto; /* The next state */ + int yyact; /* The next action */ + YYMINORTYPE yygotominor; /* The LHS of the rule reduced */ + yyStackEntry *yymsp; /* The top of the parser's stack */ + int yysize; /* Amount to pop the stack */ + sqlite3_parseARG_FETCH; + SqliteStatement* objectForTokens = 0; + QStringList noTokenInheritanceFields; + yymsp = &yypParser->yystack[yypParser->yyidx]; +#ifndef NDEBUG + if( yyTraceFILE && yyruleno>=0 + && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){ + fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt, + yyRuleName[yyruleno]); + } +#endif /* NDEBUG */ + + /* Silence complaints from purify about yygotominor being uninitialized + ** in some cases when it is copied into the stack after the following + ** switch. yygotominor is uninitialized when a rule reduces that does + ** not set the value of its left-hand side nonterminal. Leaving the + ** value of the nonterminal uninitialized is utterly harmless as long + ** as the value is never used. So really the only thing this code + ** accomplishes is to quieten purify. + ** + ** 2007-01-16: The wireshark project (www.wireshark.org) reports that + ** without this code, their parser segfaults. I'm not sure what there + ** parser is doing to make this happen. This is the second bug report + ** from wireshark this week. Clearly they are stressing Lemon in ways + ** that it has not been previously stressed... (SQLite ticket #2172) + */ + /*memset(&yygotominor, 0, sizeof(yygotominor));*/ + yygotominor = yyzerominor; + + + if (parserContext->executeRules) + { + switch( yyruleno ){ + /* Beginning here are the reduction cases. A typical example + ** follows: + ** case 0: + ** #line <lineno> <grammarfile> + ** { ... } // User supplied code + ** #line <lineno> <thisfile> + ** break; + */ + case 1: /* cmdlist ::= cmdlist ecmd */ +{parserContext->addQuery(yymsp[0].minor.yy399); DONT_INHERIT_TOKENS("cmdlist");} + break; + case 2: /* cmdlist ::= ecmd */ +{parserContext->addQuery(yymsp[0].minor.yy399);} + break; + case 3: /* ecmd ::= SEMI */ +{yygotominor.yy399 = new SqliteEmptyQuery();} + break; + case 4: /* ecmd ::= explain cmdx SEMI */ +{ + yygotominor.yy399 = yymsp[-1].minor.yy399; + yygotominor.yy399->explain = yymsp[-2].minor.yy225->explain; + yygotominor.yy399->queryPlan = yymsp[-2].minor.yy225->queryPlan; + delete yymsp[-2].minor.yy225; + objectForTokens = yygotominor.yy399; + } + break; + case 5: /* explain ::= */ +{yygotominor.yy225 = new ParserStubExplain(false, false);} + break; + case 6: /* explain ::= EXPLAIN */ +{yygotominor.yy225 = new ParserStubExplain(true, false);} + break; + case 7: /* explain ::= EXPLAIN QUERY PLAN */ +{yygotominor.yy225 = new ParserStubExplain(true, true);} + break; + case 8: /* cmdx ::= cmd */ + case 374: /* trigger_cmd ::= update_stmt */ yytestcase(yyruleno==374); + case 375: /* trigger_cmd ::= insert_stmt */ yytestcase(yyruleno==375); + case 376: /* trigger_cmd ::= delete_stmt */ yytestcase(yyruleno==376); + case 377: /* trigger_cmd ::= select_stmt */ yytestcase(yyruleno==377); + case 404: /* cmd ::= create_vtab */ yytestcase(yyruleno==404); +{yygotominor.yy399 = yymsp[0].minor.yy399;} + break; + case 9: /* cmd ::= BEGIN transtype trans_opt */ +{ + yygotominor.yy399 = new SqliteBeginTrans( + yymsp[-1].minor.yy300->type, + yymsp[0].minor.yy300->transactionKw, + yymsp[0].minor.yy300->name + ); + delete yymsp[0].minor.yy300; + delete yymsp[-1].minor.yy300; + objectForTokens = yygotominor.yy399; + } + break; + case 10: /* trans_opt ::= */ + case 14: /* transtype ::= */ yytestcase(yyruleno==14); +{yygotominor.yy300 = new ParserStubTransDetails();} + break; + case 11: /* trans_opt ::= TRANSACTION */ +{ + yygotominor.yy300 = new ParserStubTransDetails(); + yygotominor.yy300->transactionKw = true; + } + break; + case 12: /* trans_opt ::= TRANSACTION nm */ + case 13: /* trans_opt ::= TRANSACTION ID_TRANS */ yytestcase(yyruleno==13); +{ + yygotominor.yy300 = new ParserStubTransDetails(); + yygotominor.yy300->transactionKw = true; + yygotominor.yy300->name = *(yymsp[0].minor.yy211); + delete yymsp[0].minor.yy211; + } + break; + case 15: /* transtype ::= DEFERRED */ +{ + yygotominor.yy300 = new ParserStubTransDetails(); + yygotominor.yy300->type = SqliteBeginTrans::Type::DEFERRED; + } + break; + case 16: /* transtype ::= IMMEDIATE */ +{ + yygotominor.yy300 = new ParserStubTransDetails(); + yygotominor.yy300->type = SqliteBeginTrans::Type::IMMEDIATE; + } + break; + case 17: /* transtype ::= EXCLUSIVE */ +{ + yygotominor.yy300 = new ParserStubTransDetails(); + yygotominor.yy300->type = SqliteBeginTrans::Type::EXCLUSIVE; + } + break; + case 18: /* cmd ::= COMMIT trans_opt */ +{ + yygotominor.yy399 = new SqliteCommitTrans( + yymsp[0].minor.yy300->transactionKw, + yymsp[0].minor.yy300->name, + false + ); + delete yymsp[0].minor.yy300; + objectForTokens = yygotominor.yy399; + } + break; + case 19: /* cmd ::= END trans_opt */ +{ + yygotominor.yy399 = new SqliteCommitTrans( + yymsp[0].minor.yy300->transactionKw, + yymsp[0].minor.yy300->name, + true + ); + delete yymsp[0].minor.yy300; + objectForTokens = yygotominor.yy399; + } + break; + case 20: /* cmd ::= ROLLBACK trans_opt */ +{ + yygotominor.yy399 = new SqliteRollback( + yymsp[0].minor.yy300->transactionKw, + yymsp[0].minor.yy300->name + ); + delete yymsp[0].minor.yy300; + objectForTokens = yygotominor.yy399; + } + break; + case 21: /* savepoint_opt ::= SAVEPOINT */ + case 37: /* ifnotexists ::= IF NOT EXISTS */ yytestcase(yyruleno==37); + case 86: /* autoinc ::= AUTOINCR */ yytestcase(yyruleno==86); + case 108: /* tconscomma ::= COMMA */ yytestcase(yyruleno==108); + case 130: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==130); + case 304: /* not_opt ::= NOT */ yytestcase(yyruleno==304); + case 320: /* uniqueflag ::= UNIQUE */ yytestcase(yyruleno==320); + case 386: /* database_kw_opt ::= DATABASE */ yytestcase(yyruleno==386); + case 402: /* kwcolumn_opt ::= */ yytestcase(yyruleno==402); +{yygotominor.yy237 = new bool(true);} + break; + case 22: /* savepoint_opt ::= */ + case 36: /* ifnotexists ::= */ yytestcase(yyruleno==36); + case 85: /* autoinc ::= */ yytestcase(yyruleno==85); + case 109: /* tconscomma ::= */ yytestcase(yyruleno==109); + case 131: /* ifexists ::= */ yytestcase(yyruleno==131); + case 303: /* not_opt ::= */ yytestcase(yyruleno==303); + case 321: /* uniqueflag ::= */ yytestcase(yyruleno==321); + case 387: /* database_kw_opt ::= */ yytestcase(yyruleno==387); + case 403: /* kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==403); +{yygotominor.yy237 = new bool(false);} + break; + case 23: /* cmd ::= SAVEPOINT nm */ +{ + yygotominor.yy399 = new SqliteSavepoint(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy399; + } + break; + case 24: /* cmd ::= RELEASE savepoint_opt nm */ +{ + yygotominor.yy399 = new SqliteRelease(*(yymsp[-1].minor.yy237), *(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy399; + } + break; + case 25: /* cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */ + case 26: /* cmd ::= SAVEPOINT ID_TRANS */ yytestcase(yyruleno==26); +{ + yygotominor.yy399 = new SqliteRollback( + yymsp[-3].minor.yy300->transactionKw, + *(yymsp[-1].minor.yy237), + *(yymsp[0].minor.yy211) + ); + delete yymsp[-1].minor.yy237; + delete yymsp[-3].minor.yy300; + objectForTokens = yygotominor.yy399; + } + break; + case 27: /* cmd ::= RELEASE savepoint_opt ID_TRANS */ + case 28: /* cmd ::= ROLLBACK trans_opt TO savepoint_opt ID_TRANS */ yytestcase(yyruleno==28); +{ yy_destructor(yypParser,178,&yymsp[-1].minor); +} + break; + case 29: /* cmd ::= CREATE temp TABLE ifnotexists fullname LP columnlist conslist_opt RP table_options */ +{ + yygotominor.yy399 = new SqliteCreateTable( + *(yymsp[-8].minor.yy376), + *(yymsp[-6].minor.yy237), + yymsp[-5].minor.yy66->name1, + yymsp[-5].minor.yy66->name2, + *(yymsp[-3].minor.yy118), + *(yymsp[-2].minor.yy87), + *(yymsp[0].minor.yy211) + ); + delete yymsp[-6].minor.yy237; + delete yymsp[-8].minor.yy376; + delete yymsp[-3].minor.yy118; + delete yymsp[-2].minor.yy87; + delete yymsp[-5].minor.yy66; + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy399; + } + break; + case 30: /* cmd ::= CREATE temp TABLE ifnotexists fullname AS select */ +{ + yygotominor.yy399 = new SqliteCreateTable( + *(yymsp[-5].minor.yy376), + *(yymsp[-3].minor.yy237), + yymsp[-2].minor.yy66->name1, + yymsp[-2].minor.yy66->name2, + yymsp[0].minor.yy123 + ); + delete yymsp[-3].minor.yy237; + delete yymsp[-5].minor.yy376; + delete yymsp[-2].minor.yy66; + objectForTokens = yygotominor.yy399; + } + break; + case 31: /* cmd ::= CREATE temp TABLE ifnotexists nm DOT ID_TAB_NEW */ + case 133: /* cmd ::= CREATE temp VIEW ifnotexists nm DOT ID_VIEW_NEW */ yytestcase(yyruleno==133); + case 357: /* cmd ::= CREATE temp TRIGGER ifnotexists nm DOT ID_TRIG_NEW */ yytestcase(yyruleno==357); +{ yy_destructor(yypParser,179,&yymsp[-5].minor); + yy_destructor(yypParser,177,&yymsp[-2].minor); +} + break; + case 32: /* cmd ::= CREATE temp TABLE ifnotexists ID_DB|ID_TAB_NEW */ + case 134: /* cmd ::= CREATE temp VIEW ifnotexists ID_DB|ID_VIEW_NEW */ yytestcase(yyruleno==134); + case 358: /* cmd ::= CREATE temp TRIGGER ifnotexists ID_DB|ID_TRIG_NEW */ yytestcase(yyruleno==358); +{ yy_destructor(yypParser,179,&yymsp[-3].minor); +} + break; + case 33: /* table_options ::= */ + case 185: /* dbnm ::= */ yytestcase(yyruleno==185); + case 328: /* collate ::= */ yytestcase(yyruleno==328); + case 411: /* vtabarg ::= */ yytestcase(yyruleno==411); + case 415: /* anylist ::= */ yytestcase(yyruleno==415); +{yygotominor.yy211 = new QString();} + break; + case 34: /* table_options ::= WITHOUT nm */ + case 35: /* table_options ::= WITHOUT CTX_ROWID_KW */ yytestcase(yyruleno==35); +{ + if (yymsp[0].minor.yy211->toLower() != "rowid") + parserContext->errorAtToken(QString("Invalid table option: %1").arg(*(yymsp[0].minor.yy211))); + + yygotominor.yy211 = yymsp[0].minor.yy211; + } + break; + case 38: /* temp ::= TEMP */ +{yygotominor.yy376 = new int( (yymsp[0].minor.yy0->value.length() > 4) ? 2 : 1 );} + break; + case 39: /* temp ::= */ + case 154: /* distinct ::= */ yytestcase(yyruleno==154); +{yygotominor.yy376 = new int(0);} + break; + case 40: /* columnlist ::= columnlist COMMA column */ +{ + yymsp[-2].minor.yy118->append(yymsp[0].minor.yy425); + yygotominor.yy118 = yymsp[-2].minor.yy118; + DONT_INHERIT_TOKENS("columnlist"); + } + break; + case 41: /* columnlist ::= column */ +{ + yygotominor.yy118 = new ParserCreateTableColumnList(); + yygotominor.yy118->append(yymsp[0].minor.yy425); + } + break; + case 42: /* column ::= columnid type carglist */ +{ + yygotominor.yy425 = new SqliteCreateTable::Column(*(yymsp[-2].minor.yy211), yymsp[-1].minor.yy299, *(yymsp[0].minor.yy449)); + delete yymsp[-2].minor.yy211; + delete yymsp[0].minor.yy449; + objectForTokens = yygotominor.yy425; + } + break; + case 43: /* columnid ::= nm */ + case 44: /* columnid ::= ID_COL_NEW */ yytestcase(yyruleno==44); + case 47: /* nm ::= id */ yytestcase(yyruleno==47); + case 55: /* typename ::= ids */ yytestcase(yyruleno==55); + case 186: /* dbnm ::= DOT nm */ yytestcase(yyruleno==186); + case 329: /* collate ::= COLLATE ids */ yytestcase(yyruleno==329); + case 330: /* collate ::= COLLATE ID_COLLATE */ yytestcase(yyruleno==330); +{yygotominor.yy211 = yymsp[0].minor.yy211;} + break; + case 45: /* id ::= ID */ +{ + yygotominor.yy211 = new QString( + stripObjName( + yymsp[0].minor.yy0->value, + parserContext->dialect + ) + ); + } + break; + case 46: /* ids ::= ID|STRING */ + case 49: /* nm ::= JOIN_KW */ yytestcase(yyruleno==49); +{yygotominor.yy211 = new QString(yymsp[0].minor.yy0->value);} + break; + case 48: /* nm ::= STRING */ +{yygotominor.yy211 = new QString(stripString(yymsp[0].minor.yy0->value));} + break; + case 50: /* type ::= */ +{yygotominor.yy299 = nullptr;} + break; + case 51: /* type ::= typetoken */ +{yygotominor.yy299 = yymsp[0].minor.yy299;} + break; + case 52: /* typetoken ::= typename */ +{ + yygotominor.yy299 = new SqliteColumnType(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy299; + } + break; + case 53: /* typetoken ::= typename LP signed RP */ +{ + yygotominor.yy299 = new SqliteColumnType(*(yymsp[-3].minor.yy211), *(yymsp[-1].minor.yy21)); + delete yymsp[-3].minor.yy211; + delete yymsp[-1].minor.yy21; + objectForTokens = yygotominor.yy299; + } + break; + case 54: /* typetoken ::= typename LP signed COMMA signed RP */ +{ + yygotominor.yy299 = new SqliteColumnType(*(yymsp[-5].minor.yy211), *(yymsp[-3].minor.yy21), *(yymsp[-1].minor.yy21)); + delete yymsp[-5].minor.yy211; + delete yymsp[-3].minor.yy21; + delete yymsp[-1].minor.yy21; + objectForTokens = yygotominor.yy299; + } + break; + case 56: /* typename ::= typename ids */ + case 57: /* typename ::= ID_COL_TYPE */ yytestcase(yyruleno==57); +{ + yymsp[-1].minor.yy211->append(" " + *(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + yygotominor.yy211 = yymsp[-1].minor.yy211; + } + break; + case 58: /* signed ::= plus_num */ + case 59: /* signed ::= minus_num */ yytestcase(yyruleno==59); + case 343: /* nmnum ::= plus_num */ yytestcase(yyruleno==343); + case 348: /* plus_num ::= PLUS number */ yytestcase(yyruleno==348); + case 349: /* plus_num ::= number */ yytestcase(yyruleno==349); +{yygotominor.yy21 = yymsp[0].minor.yy21;} + break; + case 60: /* carglist ::= carglist ccons */ +{ + yymsp[-1].minor.yy449->append(yymsp[0].minor.yy4); + yygotominor.yy449 = yymsp[-1].minor.yy449; + DONT_INHERIT_TOKENS("carglist"); + } + break; + case 61: /* carglist ::= */ +{yygotominor.yy449 = new ParserCreateTableColumnConstraintList();} + break; + case 62: /* ccons ::= CONSTRAINT nm */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initDefNameOnly(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy4; + } + break; + case 63: /* ccons ::= DEFAULT term */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initDefTerm(*(yymsp[0].minor.yy21)); + delete yymsp[0].minor.yy21; + objectForTokens = yygotominor.yy4; + } + break; + case 64: /* ccons ::= DEFAULT LP expr RP */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initDefExpr(yymsp[-1].minor.yy490); + objectForTokens = yygotominor.yy4; + } + break; + case 65: /* ccons ::= DEFAULT PLUS term */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initDefTerm(*(yymsp[0].minor.yy21), false); + delete yymsp[0].minor.yy21; + objectForTokens = yygotominor.yy4; + } + break; + case 66: /* ccons ::= DEFAULT MINUS term */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initDefTerm(*(yymsp[0].minor.yy21), true); + delete yymsp[0].minor.yy21; + objectForTokens = yygotominor.yy4; + } + break; + case 67: /* ccons ::= DEFAULT id */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initDefId(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy4; + } + break; + case 68: /* ccons ::= DEFAULT CTIME_KW */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initDefCTime(yymsp[0].minor.yy0->value); + objectForTokens = yygotominor.yy4; + } + break; + case 69: /* ccons ::= NULL onconf */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initNull(*(yymsp[0].minor.yy30)); + delete yymsp[0].minor.yy30; + objectForTokens = yygotominor.yy4; + } + break; + case 70: /* ccons ::= NOT NULL onconf */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initNotNull(*(yymsp[0].minor.yy30)); + delete yymsp[0].minor.yy30; + objectForTokens = yygotominor.yy4; + } + break; + case 71: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initPk(*(yymsp[-2].minor.yy226), *(yymsp[-1].minor.yy30), *(yymsp[0].minor.yy237)); + delete yymsp[-2].minor.yy226; + delete yymsp[0].minor.yy237; + delete yymsp[-1].minor.yy30; + objectForTokens = yygotominor.yy4; + } + break; + case 72: /* ccons ::= UNIQUE onconf */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initUnique(*(yymsp[0].minor.yy30)); + delete yymsp[0].minor.yy30; + objectForTokens = yygotominor.yy4; + } + break; + case 73: /* ccons ::= CHECK LP expr RP */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initCheck(yymsp[-1].minor.yy490); + objectForTokens = yygotominor.yy4; + } + break; + case 74: /* ccons ::= REFERENCES nm idxlist_opt refargs */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initFk(*(yymsp[-2].minor.yy211), *(yymsp[-1].minor.yy139), *(yymsp[0].minor.yy108)); + delete yymsp[-2].minor.yy211; + delete yymsp[0].minor.yy108; + delete yymsp[-1].minor.yy139; + objectForTokens = yygotominor.yy4; + } + break; + case 75: /* ccons ::= defer_subclause */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initDefer(yymsp[0].minor.yy131->initially, yymsp[0].minor.yy131->deferrable); + delete yymsp[0].minor.yy131; + objectForTokens = yygotominor.yy4; + } + break; + case 76: /* ccons ::= COLLATE ids */ + case 77: /* ccons ::= CONSTRAINT ID_CONSTR */ yytestcase(yyruleno==77); + case 78: /* ccons ::= COLLATE ID_COLLATE */ yytestcase(yyruleno==78); + case 79: /* ccons ::= REFERENCES ID_TAB */ yytestcase(yyruleno==79); +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initColl(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy4; + } + break; + case 80: /* ccons ::= CHECK LP RP */ +{ + yygotominor.yy4 = new SqliteCreateTable::Column::Constraint(); + yygotominor.yy4->initCheck(); + objectForTokens = yygotominor.yy4; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 81: /* term ::= NULL */ +{ + yygotominor.yy21 = new QVariant(); + } + break; + case 82: /* term ::= INTEGER */ +{ + int base = 10; + if (yymsp[0].minor.yy0->value.startsWith("0x", Qt::CaseInsensitive)) + base = 16; + + yygotominor.yy21 = new QVariant(yymsp[0].minor.yy0->value.toLongLong(nullptr, base)); + } + break; + case 83: /* term ::= FLOAT */ +{ + yygotominor.yy21 = new QVariant(QVariant(yymsp[0].minor.yy0->value).toDouble()); + } + break; + case 84: /* term ::= STRING|BLOB */ + case 345: /* nmnum ::= ON */ yytestcase(yyruleno==345); + case 346: /* nmnum ::= DELETE */ yytestcase(yyruleno==346); + case 347: /* nmnum ::= DEFAULT */ yytestcase(yyruleno==347); +{yygotominor.yy21 = new QVariant(yymsp[0].minor.yy0->value);} + break; + case 87: /* refargs ::= */ +{yygotominor.yy108 = new ParserFkConditionList();} + break; + case 88: /* refargs ::= refargs refarg */ +{ + yymsp[-1].minor.yy108->append(yymsp[0].minor.yy271); + yygotominor.yy108 = yymsp[-1].minor.yy108; + DONT_INHERIT_TOKENS("refargs"); + } + break; + case 89: /* refarg ::= MATCH nm */ +{ + yygotominor.yy271 = new SqliteForeignKey::Condition(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + } + break; + case 90: /* refarg ::= ON INSERT refact */ +{yygotominor.yy271 = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::INSERT, *(yymsp[0].minor.yy312)); delete yymsp[0].minor.yy312;} + break; + case 91: /* refarg ::= ON DELETE refact */ +{yygotominor.yy271 = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::DELETE, *(yymsp[0].minor.yy312)); delete yymsp[0].minor.yy312;} + break; + case 92: /* refarg ::= ON UPDATE refact */ + case 93: /* refarg ::= MATCH ID_FK_MATCH */ yytestcase(yyruleno==93); +{yygotominor.yy271 = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::UPDATE, *(yymsp[0].minor.yy312)); delete yymsp[0].minor.yy312;} + break; + case 94: /* refact ::= SET NULL */ +{yygotominor.yy312 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::SET_NULL);} + break; + case 95: /* refact ::= SET DEFAULT */ +{yygotominor.yy312 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::SET_DEFAULT);} + break; + case 96: /* refact ::= CASCADE */ +{yygotominor.yy312 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::CASCADE);} + break; + case 97: /* refact ::= RESTRICT */ +{yygotominor.yy312 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::RESTRICT);} + break; + case 98: /* refact ::= NO ACTION */ +{yygotominor.yy312 = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::NO_ACTION);} + break; + case 99: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ +{ + yygotominor.yy131 = new ParserDeferSubClause(SqliteDeferrable::NOT_DEFERRABLE, *(yymsp[0].minor.yy498)); + delete yymsp[0].minor.yy498; + } + break; + case 100: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ +{ + yygotominor.yy131 = new ParserDeferSubClause(SqliteDeferrable::DEFERRABLE, *(yymsp[0].minor.yy498)); + delete yymsp[0].minor.yy498; + } + break; + case 101: /* init_deferred_pred_opt ::= */ +{yygotominor.yy498 = new SqliteInitially(SqliteInitially::null);} + break; + case 102: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */ +{yygotominor.yy498 = new SqliteInitially(SqliteInitially::DEFERRED);} + break; + case 103: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ +{yygotominor.yy498 = new SqliteInitially(SqliteInitially::IMMEDIATE);} + break; + case 104: /* conslist_opt ::= */ +{yygotominor.yy87 = new ParserCreateTableConstraintList();} + break; + case 105: /* conslist_opt ::= COMMA conslist */ +{yygotominor.yy87 = yymsp[0].minor.yy87;} + break; + case 106: /* conslist ::= conslist tconscomma tcons */ +{ + yymsp[0].minor.yy8->afterComma = *(yymsp[-1].minor.yy237); + yymsp[-2].minor.yy87->append(yymsp[0].minor.yy8); + yygotominor.yy87 = yymsp[-2].minor.yy87; + delete yymsp[-1].minor.yy237; + DONT_INHERIT_TOKENS("conslist"); + } + break; + case 107: /* conslist ::= tcons */ +{ + yygotominor.yy87 = new ParserCreateTableConstraintList(); + yygotominor.yy87->append(yymsp[0].minor.yy8); + } + break; + case 110: /* tcons ::= CONSTRAINT nm */ +{ + yygotominor.yy8 = new SqliteCreateTable::Constraint(); + yygotominor.yy8->initNameOnly(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy8; + } + break; + case 111: /* tcons ::= PRIMARY KEY LP idxlist autoinc RP onconf */ +{ + yygotominor.yy8 = new SqliteCreateTable::Constraint(); + yygotominor.yy8->initPk(*(yymsp[-3].minor.yy139), *(yymsp[-2].minor.yy237), *(yymsp[0].minor.yy30)); + delete yymsp[-2].minor.yy237; + delete yymsp[0].minor.yy30; + delete yymsp[-3].minor.yy139; + objectForTokens = yygotominor.yy8; + } + break; + case 112: /* tcons ::= UNIQUE LP idxlist RP onconf */ +{ + yygotominor.yy8 = new SqliteCreateTable::Constraint(); + yygotominor.yy8->initUnique(*(yymsp[-2].minor.yy139), *(yymsp[0].minor.yy30)); + delete yymsp[0].minor.yy30; + delete yymsp[-2].minor.yy139; + objectForTokens = yygotominor.yy8; + } + break; + case 113: /* tcons ::= CHECK LP expr RP onconf */ +{ + yygotominor.yy8 = new SqliteCreateTable::Constraint(); + yygotominor.yy8->initCheck(yymsp[-2].minor.yy490, *(yymsp[0].minor.yy30)); + objectForTokens = yygotominor.yy8; + } + break; + case 114: /* tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt */ + case 115: /* tcons ::= CONSTRAINT ID_CONSTR */ yytestcase(yyruleno==115); + case 116: /* tcons ::= FOREIGN KEY LP idxlist RP REFERENCES ID_TAB */ yytestcase(yyruleno==116); +{ + yygotominor.yy8 = new SqliteCreateTable::Constraint(); + yygotominor.yy8->initFk( + *(yymsp[-6].minor.yy139), + *(yymsp[-3].minor.yy211), + *(yymsp[-2].minor.yy139), + *(yymsp[-1].minor.yy108), + yymsp[0].minor.yy131->initially, + yymsp[0].minor.yy131->deferrable + ); + delete yymsp[-3].minor.yy211; + delete yymsp[-1].minor.yy108; + delete yymsp[0].minor.yy131; + delete yymsp[-2].minor.yy139; + delete yymsp[-6].minor.yy139; + objectForTokens = yygotominor.yy8; + } + break; + case 117: /* tcons ::= CHECK LP RP onconf */ +{ + yygotominor.yy8 = new SqliteCreateTable::Constraint(); + yygotominor.yy8->initCheck(); + objectForTokens = yygotominor.yy8; + parserContext->minorErrorAfterLastToken("Syntax error"); + yy_destructor(yypParser,200,&yymsp[0].minor); +} + break; + case 118: /* defer_subclause_opt ::= */ +{yygotominor.yy131 = new ParserDeferSubClause(SqliteDeferrable::null, SqliteInitially::null);} + break; + case 119: /* defer_subclause_opt ::= defer_subclause */ +{yygotominor.yy131 = yymsp[0].minor.yy131;} + break; + case 120: /* onconf ::= */ + case 122: /* orconf ::= */ yytestcase(yyruleno==122); +{yygotominor.yy30 = new SqliteConflictAlgo(SqliteConflictAlgo::null);} + break; + case 121: /* onconf ::= ON CONFLICT resolvetype */ + case 123: /* orconf ::= OR resolvetype */ yytestcase(yyruleno==123); +{yygotominor.yy30 = yymsp[0].minor.yy30;} + break; + case 124: /* resolvetype ::= raisetype */ + case 125: /* resolvetype ::= IGNORE */ yytestcase(yyruleno==125); + case 126: /* resolvetype ::= REPLACE */ yytestcase(yyruleno==126); +{yygotominor.yy30 = new SqliteConflictAlgo(sqliteConflictAlgo(yymsp[0].minor.yy0->value));} + break; + case 127: /* cmd ::= DROP TABLE ifexists fullname */ +{ + yygotominor.yy399 = new SqliteDropTable(*(yymsp[-1].minor.yy237), yymsp[0].minor.yy66->name1, yymsp[0].minor.yy66->name2); + delete yymsp[-1].minor.yy237; + delete yymsp[0].minor.yy66; + objectForTokens = yygotominor.yy399; + } + break; + case 128: /* cmd ::= DROP TABLE ifexists nm DOT ID_TAB */ + case 129: /* cmd ::= DROP TABLE ifexists ID_DB|ID_TAB */ yytestcase(yyruleno==129); + case 136: /* cmd ::= DROP VIEW ifexists nm DOT ID_VIEW */ yytestcase(yyruleno==136); + case 137: /* cmd ::= DROP VIEW ifexists ID_DB|ID_VIEW */ yytestcase(yyruleno==137); + case 178: /* singlesrc ::= nm DOT ID_TAB */ yytestcase(yyruleno==178); + case 179: /* singlesrc ::= ID_DB|ID_TAB */ yytestcase(yyruleno==179); + case 180: /* singlesrc ::= nm DOT ID_VIEW */ yytestcase(yyruleno==180); + case 181: /* singlesrc ::= ID_DB|ID_VIEW */ yytestcase(yyruleno==181); + case 297: /* exprx ::= nm DOT ID_TAB|ID_COL */ yytestcase(yyruleno==297); + case 318: /* cmd ::= CREATE uniqueflag INDEX ifnotexists nm DOT ID_IDX_NEW */ yytestcase(yyruleno==318); + case 319: /* cmd ::= CREATE uniqueflag INDEX ifnotexists ID_DB|ID_IDX_NEW */ yytestcase(yyruleno==319); + case 332: /* cmd ::= DROP INDEX ifexists nm DOT ID_IDX */ yytestcase(yyruleno==332); + case 333: /* cmd ::= DROP INDEX ifexists ID_DB|ID_IDX */ yytestcase(yyruleno==333); + case 341: /* cmd ::= PRAGMA nm DOT ID_PRAGMA */ yytestcase(yyruleno==341); + case 342: /* cmd ::= PRAGMA ID_DB|ID_PRAGMA */ yytestcase(yyruleno==342); + case 380: /* cmd ::= DROP TRIGGER ifexists nm DOT ID_TRIG */ yytestcase(yyruleno==380); + case 381: /* cmd ::= DROP TRIGGER ifexists ID_DB|ID_TRIG */ yytestcase(yyruleno==381); + case 391: /* cmd ::= REINDEX nm DOT ID_TAB|ID_IDX */ yytestcase(yyruleno==391); + case 392: /* cmd ::= REINDEX ID_DB|ID_IDX|ID_TAB */ yytestcase(yyruleno==392); + case 395: /* cmd ::= ANALYZE nm DOT ID_TAB|ID_IDX */ yytestcase(yyruleno==395); + case 396: /* cmd ::= ANALYZE ID_DB|ID_IDX|ID_TAB */ yytestcase(yyruleno==396); + case 400: /* cmd ::= ALTER TABLE nm DOT ID_TAB */ yytestcase(yyruleno==400); + case 401: /* cmd ::= ALTER TABLE ID_DB|ID_TAB */ yytestcase(yyruleno==401); + case 407: /* create_vtab ::= CREATE VIRTUAL TABLE ifnotexists nm DOT ID_TAB_NEW */ yytestcase(yyruleno==407); + case 408: /* create_vtab ::= CREATE VIRTUAL TABLE ifnotexists ID_DB|ID_TAB_NEW */ yytestcase(yyruleno==408); +{ yy_destructor(yypParser,177,&yymsp[-2].minor); +} + break; + case 132: /* cmd ::= CREATE temp VIEW ifnotexists fullname AS select */ +{ + yygotominor.yy399 = new SqliteCreateView(*(yymsp[-5].minor.yy376), *(yymsp[-3].minor.yy237), yymsp[-2].minor.yy66->name1, yymsp[-2].minor.yy66->name2, yymsp[0].minor.yy123); + delete yymsp[-5].minor.yy376; + delete yymsp[-3].minor.yy237; + delete yymsp[-2].minor.yy66; + objectForTokens = yygotominor.yy399; + } + break; + case 135: /* cmd ::= DROP VIEW ifexists fullname */ +{ + yygotominor.yy399 = new SqliteDropView(*(yymsp[-1].minor.yy237), yymsp[0].minor.yy66->name1, yymsp[0].minor.yy66->name2); + delete yymsp[-1].minor.yy237; + delete yymsp[0].minor.yy66; + objectForTokens = yygotominor.yy399; + } + break; + case 138: /* cmd ::= select_stmt */ + case 214: /* cmd ::= delete_stmt */ yytestcase(yyruleno==214); + case 223: /* cmd ::= update_stmt */ yytestcase(yyruleno==223); + case 235: /* cmd ::= insert_stmt */ yytestcase(yyruleno==235); +{ + yygotominor.yy399 = yymsp[0].minor.yy399; + objectForTokens = yygotominor.yy399; + } + break; + case 139: /* select_stmt ::= select */ +{ + yygotominor.yy399 = yymsp[0].minor.yy123; + // since it's used in trigger: + objectForTokens = yygotominor.yy399; + } + break; + case 140: /* select ::= with selectnowith */ +{ + yygotominor.yy123 = yymsp[0].minor.yy123; + yymsp[0].minor.yy123->setWith(yymsp[-1].minor.yy367); + objectForTokens = yygotominor.yy123; + } + break; + case 141: /* selectnowith ::= oneselect */ +{ + yygotominor.yy123 = SqliteSelect::append(yymsp[0].minor.yy468); + objectForTokens = yygotominor.yy123; + } + break; + case 142: /* selectnowith ::= selectnowith multiselect_op oneselect */ +{ + yygotominor.yy123 = SqliteSelect::append(yymsp[-2].minor.yy123, *(yymsp[-1].minor.yy168), yymsp[0].minor.yy468); + delete yymsp[-1].minor.yy168; + objectForTokens = yygotominor.yy123; + } + break; + case 143: /* selectnowith ::= values */ +{ + yygotominor.yy123 = SqliteSelect::append(*(yymsp[0].minor.yy416)); + delete yymsp[0].minor.yy416; + objectForTokens = yygotominor.yy123; + } + break; + case 144: /* selectnowith ::= selectnowith COMMA values */ +{ + yygotominor.yy123 = SqliteSelect::append(yymsp[-2].minor.yy123, SqliteSelect::CompoundOperator::UNION_ALL, *(yymsp[0].minor.yy416)); + delete yymsp[0].minor.yy416; + objectForTokens = yygotominor.yy123; + } + break; + case 145: /* multiselect_op ::= UNION */ +{yygotominor.yy168 = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::UNION);} + break; + case 146: /* multiselect_op ::= UNION ALL */ +{yygotominor.yy168 = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::UNION_ALL);} + break; + case 147: /* multiselect_op ::= EXCEPT */ +{yygotominor.yy168 = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::EXCEPT);} + break; + case 148: /* multiselect_op ::= INTERSECT */ +{yygotominor.yy168 = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::INTERSECT);} + break; + case 149: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ +{ + yygotominor.yy468 = new SqliteSelect::Core( + *(yymsp[-7].minor.yy376), + *(yymsp[-6].minor.yy263), + yymsp[-5].minor.yy373, + yymsp[-4].minor.yy490, + *(yymsp[-3].minor.yy13), + yymsp[-2].minor.yy490, + *(yymsp[-1].minor.yy495), + yymsp[0].minor.yy128 + ); + delete yymsp[-6].minor.yy263; + delete yymsp[-7].minor.yy376; + delete yymsp[-3].minor.yy13; + delete yymsp[-1].minor.yy495; + objectForTokens = yygotominor.yy468; + } + break; + case 150: /* values ::= VALUES LP nexprlist RP */ +{ + yygotominor.yy416 = new ParserExprNestedList(); + yygotominor.yy416->append(*(yymsp[-1].minor.yy13)); + delete yymsp[-1].minor.yy13; + } + break; + case 151: /* values ::= values COMMA LP exprlist RP */ +{ + yymsp[-4].minor.yy416->append(*(yymsp[-1].minor.yy13)); + yygotominor.yy416 = yymsp[-4].minor.yy416; + delete yymsp[-1].minor.yy13; + DONT_INHERIT_TOKENS("values"); + } + break; + case 152: /* distinct ::= DISTINCT */ +{yygotominor.yy376 = new int(1);} + break; + case 153: /* distinct ::= ALL */ +{yygotominor.yy376 = new int(2);} + break; + case 155: /* sclp ::= selcollist COMMA */ +{yygotominor.yy263 = yymsp[-1].minor.yy263;} + break; + case 156: /* sclp ::= */ +{yygotominor.yy263 = new ParserResultColumnList();} + break; + case 157: /* selcollist ::= sclp expr as */ +{ + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn( + yymsp[-1].minor.yy490, + yymsp[0].minor.yy28 ? yymsp[0].minor.yy28->asKw : false, + yymsp[0].minor.yy28 ? yymsp[0].minor.yy28->name : QString::null + ); + + yymsp[-2].minor.yy263->append(obj); + yygotominor.yy263 = yymsp[-2].minor.yy263; + delete yymsp[0].minor.yy28; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } + break; + case 158: /* selcollist ::= sclp STAR */ +{ + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn(true); + + yymsp[-1].minor.yy263->append(obj); + yygotominor.yy263 = yymsp[-1].minor.yy263; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } + break; + case 159: /* selcollist ::= sclp nm DOT STAR */ +{ + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn( + true, + *(yymsp[-2].minor.yy211) + ); + yymsp[-3].minor.yy263->append(obj); + yygotominor.yy263 = yymsp[-3].minor.yy263; + delete yymsp[-2].minor.yy211; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } + break; + case 160: /* selcollist ::= sclp */ + case 161: /* selcollist ::= sclp ID_TAB DOT STAR */ yytestcase(yyruleno==161); +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy263 = yymsp[0].minor.yy263; + } + break; + case 162: /* as ::= AS nm */ +{ + yygotominor.yy28 = new ParserStubAlias(*(yymsp[0].minor.yy211), true); + delete yymsp[0].minor.yy211; + } + break; + case 163: /* as ::= ids */ + case 164: /* as ::= AS ID_ALIAS */ yytestcase(yyruleno==164); + case 165: /* as ::= ID_ALIAS */ yytestcase(yyruleno==165); +{ + yygotominor.yy28 = new ParserStubAlias(*(yymsp[0].minor.yy211), false); + delete yymsp[0].minor.yy211; + } + break; + case 166: /* as ::= */ +{yygotominor.yy28 = nullptr;} + break; + case 167: /* from ::= */ +{yygotominor.yy373 = nullptr;} + break; + case 168: /* from ::= FROM joinsrc */ +{yygotominor.yy373 = yymsp[0].minor.yy373;} + break; + case 169: /* joinsrc ::= singlesrc seltablist */ +{ + yygotominor.yy373 = new SqliteSelect::Core::JoinSource( + yymsp[-1].minor.yy173, + *(yymsp[0].minor.yy359) + ); + delete yymsp[0].minor.yy359; + objectForTokens = yygotominor.yy373; + } + break; + case 170: /* joinsrc ::= */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy373 = new SqliteSelect::Core::JoinSource(); + objectForTokens = yygotominor.yy373; + } + break; + case 171: /* seltablist ::= seltablist joinop singlesrc joinconstr_opt */ +{ + SqliteSelect::Core::JoinSourceOther* src = + new SqliteSelect::Core::JoinSourceOther(yymsp[-2].minor.yy473, yymsp[-1].minor.yy173, yymsp[0].minor.yy117); + + yymsp[-3].minor.yy359->append(src); + yygotominor.yy359 = yymsp[-3].minor.yy359; + objectForTokens = src; + DONT_INHERIT_TOKENS("seltablist"); + } + break; + case 172: /* seltablist ::= */ +{ + yygotominor.yy359 = new ParserOtherSourceList(); + } + break; + case 173: /* singlesrc ::= nm dbnm as indexed_opt */ +{ + yygotominor.yy173 = new SqliteSelect::Core::SingleSource( + *(yymsp[-3].minor.yy211), + *(yymsp[-2].minor.yy211), + yymsp[-1].minor.yy28 ? yymsp[-1].minor.yy28->asKw : false, + yymsp[-1].minor.yy28 ? yymsp[-1].minor.yy28->name : QString::null, + yymsp[0].minor.yy472 ? yymsp[0].minor.yy472->notIndexedKw : false, + yymsp[0].minor.yy472 ? yymsp[0].minor.yy472->indexedBy : QString::null + ); + delete yymsp[-3].minor.yy211; + delete yymsp[-2].minor.yy211; + delete yymsp[-1].minor.yy28; + if (yymsp[0].minor.yy472) + delete yymsp[0].minor.yy472; + objectForTokens = yygotominor.yy173; + } + break; + case 174: /* singlesrc ::= LP select RP as */ +{ + yygotominor.yy173 = new SqliteSelect::Core::SingleSource( + yymsp[-2].minor.yy123, + yymsp[0].minor.yy28 ? yymsp[0].minor.yy28->asKw : false, + yymsp[0].minor.yy28 ? yymsp[0].minor.yy28->name : QString::null + ); + delete yymsp[0].minor.yy28; + objectForTokens = yygotominor.yy173; + } + break; + case 175: /* singlesrc ::= LP joinsrc RP as */ +{ + yygotominor.yy173 = new SqliteSelect::Core::SingleSource( + yymsp[-2].minor.yy373, + yymsp[0].minor.yy28 ? yymsp[0].minor.yy28->asKw : false, + yymsp[0].minor.yy28 ? yymsp[0].minor.yy28->name : QString::null + ); + delete yymsp[0].minor.yy28; + objectForTokens = yygotominor.yy173; + } + break; + case 176: /* singlesrc ::= */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy173 = new SqliteSelect::Core::SingleSource(); + objectForTokens = yygotominor.yy173; + } + break; + case 177: /* singlesrc ::= nm DOT */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy173 = new SqliteSelect::Core::SingleSource(); + yygotominor.yy173->database = *(yymsp[-1].minor.yy211); + delete yymsp[-1].minor.yy211; + objectForTokens = yygotominor.yy173; + } + break; + case 182: /* joinconstr_opt ::= ON expr */ +{ + yygotominor.yy117 = new SqliteSelect::Core::JoinConstraint(yymsp[0].minor.yy490); + objectForTokens = yygotominor.yy117; + } + break; + case 183: /* joinconstr_opt ::= USING LP inscollist RP */ +{ + yygotominor.yy117 = new SqliteSelect::Core::JoinConstraint(*(yymsp[-1].minor.yy445)); + delete yymsp[-1].minor.yy445; + objectForTokens = yygotominor.yy117; + } + break; + case 184: /* joinconstr_opt ::= */ +{yygotominor.yy117 = nullptr;} + break; + case 187: /* fullname ::= nm dbnm */ +{ + yygotominor.yy66 = new ParserFullName(); + yygotominor.yy66->name1 = *(yymsp[-1].minor.yy211); + yygotominor.yy66->name2 = *(yymsp[0].minor.yy211); + delete yymsp[-1].minor.yy211; + delete yymsp[0].minor.yy211; + } + break; + case 188: /* joinop ::= COMMA */ +{ + yygotominor.yy473 = new SqliteSelect::Core::JoinOp(true); + objectForTokens = yygotominor.yy473; + } + break; + case 189: /* joinop ::= JOIN */ +{ + yygotominor.yy473 = new SqliteSelect::Core::JoinOp(false); + objectForTokens = yygotominor.yy473; + } + break; + case 190: /* joinop ::= JOIN_KW JOIN */ +{ + yygotominor.yy473 = new SqliteSelect::Core::JoinOp(yymsp[-1].minor.yy0->value); + objectForTokens = yygotominor.yy473; + } + break; + case 191: /* joinop ::= JOIN_KW nm JOIN */ +{ + yygotominor.yy473 = new SqliteSelect::Core::JoinOp(yymsp[-2].minor.yy0->value, *(yymsp[-1].minor.yy211)); + delete yymsp[-1].minor.yy211; + objectForTokens = yygotominor.yy473; + } + break; + case 192: /* joinop ::= JOIN_KW nm nm JOIN */ + case 193: /* joinop ::= ID_JOIN_OPTS */ yytestcase(yyruleno==193); +{ + yygotominor.yy473 = new SqliteSelect::Core::JoinOp(yymsp[-3].minor.yy0->value, *(yymsp[-2].minor.yy211), *(yymsp[-1].minor.yy211)); + delete yymsp[-2].minor.yy211; + delete yymsp[-2].minor.yy211; + objectForTokens = yygotominor.yy473; + } + break; + case 194: /* indexed_opt ::= */ +{yygotominor.yy472 = nullptr;} + break; + case 195: /* indexed_opt ::= INDEXED BY nm */ +{ + yygotominor.yy472 = new ParserIndexedBy(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + } + break; + case 196: /* indexed_opt ::= NOT INDEXED */ + case 197: /* indexed_opt ::= INDEXED BY ID_IDX */ yytestcase(yyruleno==197); +{yygotominor.yy472 = new ParserIndexedBy(true);} + break; + case 198: /* orderby_opt ::= */ +{yygotominor.yy495 = new ParserOrderByList();} + break; + case 199: /* orderby_opt ::= ORDER BY sortlist */ +{yygotominor.yy495 = yymsp[0].minor.yy495;} + break; + case 200: /* sortlist ::= sortlist COMMA expr sortorder */ +{ + SqliteOrderBy* obj = new SqliteOrderBy(yymsp[-1].minor.yy490, *(yymsp[0].minor.yy226)); + yymsp[-3].minor.yy495->append(obj); + yygotominor.yy495 = yymsp[-3].minor.yy495; + delete yymsp[0].minor.yy226; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sortlist"); + } + break; + case 201: /* sortlist ::= expr sortorder */ +{ + SqliteOrderBy* obj = new SqliteOrderBy(yymsp[-1].minor.yy490, *(yymsp[0].minor.yy226)); + yygotominor.yy495 = new ParserOrderByList(); + yygotominor.yy495->append(obj); + delete yymsp[0].minor.yy226; + objectForTokens = obj; + } + break; + case 202: /* sortorder ::= ASC */ +{yygotominor.yy226 = new SqliteSortOrder(SqliteSortOrder::ASC);} + break; + case 203: /* sortorder ::= DESC */ +{yygotominor.yy226 = new SqliteSortOrder(SqliteSortOrder::DESC);} + break; + case 204: /* sortorder ::= */ +{yygotominor.yy226 = new SqliteSortOrder(SqliteSortOrder::null);} + break; + case 205: /* groupby_opt ::= */ + case 313: /* exprlist ::= */ yytestcase(yyruleno==313); +{yygotominor.yy13 = new ParserExprList();} + break; + case 206: /* groupby_opt ::= GROUP BY nexprlist */ + case 312: /* exprlist ::= nexprlist */ yytestcase(yyruleno==312); +{yygotominor.yy13 = yymsp[0].minor.yy13;} + break; + case 207: /* groupby_opt ::= GROUP BY */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy13 = new ParserExprList(); + } + break; + case 208: /* having_opt ::= */ + case 220: /* where_opt ::= */ yytestcase(yyruleno==220); + case 309: /* case_else ::= */ yytestcase(yyruleno==309); + case 311: /* case_operand ::= */ yytestcase(yyruleno==311); + case 369: /* when_clause ::= */ yytestcase(yyruleno==369); + case 384: /* key_opt ::= */ yytestcase(yyruleno==384); +{yygotominor.yy490 = nullptr;} + break; + case 209: /* having_opt ::= HAVING expr */ + case 221: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==221); + case 301: /* expr ::= exprx */ yytestcase(yyruleno==301); + case 308: /* case_else ::= ELSE expr */ yytestcase(yyruleno==308); + case 310: /* case_operand ::= exprx */ yytestcase(yyruleno==310); + case 370: /* when_clause ::= WHEN expr */ yytestcase(yyruleno==370); + case 385: /* key_opt ::= KEY expr */ yytestcase(yyruleno==385); +{yygotominor.yy490 = yymsp[0].minor.yy490;} + break; + case 210: /* limit_opt ::= */ +{yygotominor.yy128 = nullptr;} + break; + case 211: /* limit_opt ::= LIMIT expr */ +{ + yygotominor.yy128 = new SqliteLimit(yymsp[0].minor.yy490); + objectForTokens = yygotominor.yy128; + } + break; + case 212: /* limit_opt ::= LIMIT expr OFFSET expr */ +{ + yygotominor.yy128 = new SqliteLimit(yymsp[-2].minor.yy490, yymsp[0].minor.yy490, true); + objectForTokens = yygotominor.yy128; + } + break; + case 213: /* limit_opt ::= LIMIT expr COMMA expr */ +{ + yygotominor.yy128 = new SqliteLimit(yymsp[-2].minor.yy490, yymsp[0].minor.yy490, false); + objectForTokens = yygotominor.yy128; + } + break; + case 215: /* delete_stmt ::= with DELETE FROM fullname indexed_opt where_opt */ +{ + if (yymsp[-1].minor.yy472) + { + if (!yymsp[-1].minor.yy472->indexedBy.isNull()) + { + yygotominor.yy399 = new SqliteDelete( + yymsp[-2].minor.yy66->name1, + yymsp[-2].minor.yy66->name2, + yymsp[-1].minor.yy472->indexedBy, + yymsp[0].minor.yy490, + yymsp[-5].minor.yy367 + ); + } + else + { + yygotominor.yy399 = new SqliteDelete( + yymsp[-2].minor.yy66->name1, + yymsp[-2].minor.yy66->name2, + yymsp[-1].minor.yy472->notIndexedKw, + yymsp[0].minor.yy490, + yymsp[-5].minor.yy367 + ); + } + delete yymsp[-1].minor.yy472; + } + else + { + yygotominor.yy399 = new SqliteDelete( + yymsp[-2].minor.yy66->name1, + yymsp[-2].minor.yy66->name2, + false, + yymsp[0].minor.yy490, + yymsp[-5].minor.yy367 + ); + } + delete yymsp[-2].minor.yy66; + // since it's used in trigger: + objectForTokens = yygotominor.yy399; + } + break; + case 216: /* delete_stmt ::= with DELETE FROM */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteDelete* q = new SqliteDelete(); + q->with = yymsp[-2].minor.yy367; + yygotominor.yy399 = q; + objectForTokens = yygotominor.yy399; + } + break; + case 217: /* delete_stmt ::= with DELETE FROM nm DOT */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteDelete* q = new SqliteDelete(); + q->with = yymsp[-4].minor.yy367; + q->database = *(yymsp[-1].minor.yy211); + yygotominor.yy399 = q; + objectForTokens = yygotominor.yy399; + delete yymsp[-1].minor.yy211; + } + break; + case 218: /* delete_stmt ::= with DELETE FROM nm DOT ID_TAB */ + case 227: /* update_stmt ::= with UPDATE orconf nm DOT ID_TAB */ yytestcase(yyruleno==227); +{ yy_destructor(yypParser,219,&yymsp[-5].minor); + yy_destructor(yypParser,177,&yymsp[-2].minor); +} + break; + case 219: /* delete_stmt ::= with DELETE FROM ID_DB|ID_TAB */ + case 228: /* update_stmt ::= with UPDATE orconf ID_DB|ID_TAB */ yytestcase(yyruleno==228); +{ yy_destructor(yypParser,219,&yymsp[-3].minor); +} + break; + case 222: /* where_opt ::= WHERE */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy490 = new SqliteExpr(); + } + break; + case 224: /* update_stmt ::= with UPDATE orconf fullname indexed_opt SET setlist where_opt */ +{ + yygotominor.yy399 = new SqliteUpdate( + *(yymsp[-5].minor.yy30), + yymsp[-4].minor.yy66->name1, + yymsp[-4].minor.yy66->name2, + yymsp[-3].minor.yy472 ? yymsp[-3].minor.yy472->notIndexedKw : false, + yymsp[-3].minor.yy472 ? yymsp[-3].minor.yy472->indexedBy : QString::null, + *(yymsp[-1].minor.yy381), + yymsp[0].minor.yy490, + yymsp[-7].minor.yy367 + ); + delete yymsp[-5].minor.yy30; + delete yymsp[-4].minor.yy66; + delete yymsp[-1].minor.yy381; + if (yymsp[-3].minor.yy472) + delete yymsp[-3].minor.yy472; + // since it's used in trigger: + objectForTokens = yygotominor.yy399; + } + break; + case 225: /* update_stmt ::= with UPDATE orconf */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteUpdate* q = new SqliteUpdate(); + q->with = yymsp[-2].minor.yy367; + yygotominor.yy399 = q; + objectForTokens = yygotominor.yy399; + delete yymsp[0].minor.yy30; + } + break; + case 226: /* update_stmt ::= with UPDATE orconf nm DOT */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteUpdate* q = new SqliteUpdate(); + q->with = yymsp[-4].minor.yy367; + q->database = *(yymsp[-1].minor.yy211); + yygotominor.yy399 = q; + objectForTokens = yygotominor.yy399; + delete yymsp[-2].minor.yy30; + delete yymsp[-1].minor.yy211; + } + break; + case 229: /* setlist ::= setlist COMMA nm EQ expr */ +{ + yymsp[-4].minor.yy381->append(ParserSetValue(*(yymsp[-2].minor.yy211), yymsp[0].minor.yy490)); + yygotominor.yy381 = yymsp[-4].minor.yy381; + delete yymsp[-2].minor.yy211; + DONT_INHERIT_TOKENS("setlist"); + } + break; + case 230: /* setlist ::= nm EQ expr */ +{ + yygotominor.yy381 = new ParserSetValueList(); + yygotominor.yy381->append(ParserSetValue(*(yymsp[-2].minor.yy211), yymsp[0].minor.yy490)); + delete yymsp[-2].minor.yy211; + } + break; + case 231: /* setlist ::= */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy381 = new ParserSetValueList(); + } + break; + case 232: /* setlist ::= setlist COMMA */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy381 = yymsp[-1].minor.yy381; + } + break; + case 233: /* setlist ::= setlist COMMA ID_COL */ + case 234: /* setlist ::= ID_COL */ yytestcase(yyruleno==234); +{ yy_destructor(yypParser,247,&yymsp[-2].minor); +} + break; + case 236: /* insert_stmt ::= with insert_cmd INTO fullname inscollist_opt select */ +{ + yygotominor.yy399 = new SqliteInsert( + yymsp[-4].minor.yy250->replace, + yymsp[-4].minor.yy250->orConflict, + yymsp[-2].minor.yy66->name1, + yymsp[-2].minor.yy66->name2, + *(yymsp[-1].minor.yy445), + yymsp[0].minor.yy123, + yymsp[-5].minor.yy367 + ); + delete yymsp[-2].minor.yy66; + delete yymsp[-4].minor.yy250; + delete yymsp[-1].minor.yy445; + // since it's used in trigger: + objectForTokens = yygotominor.yy399; + } + break; + case 237: /* insert_stmt ::= with insert_cmd INTO fullname inscollist_opt DEFAULT VALUES */ +{ + yygotominor.yy399 = new SqliteInsert( + yymsp[-5].minor.yy250->replace, + yymsp[-5].minor.yy250->orConflict, + yymsp[-3].minor.yy66->name1, + yymsp[-3].minor.yy66->name2, + *(yymsp[-2].minor.yy445), + yymsp[-6].minor.yy367 + ); + delete yymsp[-3].minor.yy66; + delete yymsp[-5].minor.yy250; + delete yymsp[-2].minor.yy445; + // since it's used in trigger: + objectForTokens = yygotominor.yy399; + } + break; + case 238: /* insert_stmt ::= with insert_cmd INTO */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteInsert* q = new SqliteInsert(); + q->replaceKw = yymsp[-1].minor.yy250->replace; + q->onConflict = yymsp[-1].minor.yy250->orConflict; + q->with = yymsp[-2].minor.yy367; + yygotominor.yy399 = q; + objectForTokens = yygotominor.yy399; + delete yymsp[-1].minor.yy250; + } + break; + case 239: /* insert_stmt ::= with insert_cmd INTO nm DOT */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteInsert* q = new SqliteInsert(); + q->replaceKw = yymsp[-3].minor.yy250->replace; + q->onConflict = yymsp[-3].minor.yy250->orConflict; + q->with = yymsp[-4].minor.yy367; + q->database = *(yymsp[-1].minor.yy211); + yygotominor.yy399 = q; + objectForTokens = yygotominor.yy399; + delete yymsp[-3].minor.yy250; + delete yymsp[-1].minor.yy211; + } + break; + case 240: /* insert_stmt ::= with insert_cmd INTO ID_DB|ID_TAB */ +{ yy_destructor(yypParser,219,&yymsp[-3].minor); + yy_destructor(yypParser,249,&yymsp[-2].minor); +} + break; + case 241: /* insert_stmt ::= with insert_cmd INTO nm DOT ID_TAB */ +{ yy_destructor(yypParser,219,&yymsp[-5].minor); + yy_destructor(yypParser,249,&yymsp[-4].minor); + yy_destructor(yypParser,177,&yymsp[-2].minor); +} + break; + case 242: /* insert_cmd ::= INSERT orconf */ +{ + yygotominor.yy250 = new ParserStubInsertOrReplace(false, *(yymsp[0].minor.yy30)); + delete yymsp[0].minor.yy30; + } + break; + case 243: /* insert_cmd ::= REPLACE */ +{yygotominor.yy250 = new ParserStubInsertOrReplace(true);} + break; + case 244: /* inscollist_opt ::= */ +{yygotominor.yy445 = new ParserStringList();} + break; + case 245: /* inscollist_opt ::= LP inscollist RP */ +{yygotominor.yy445 = yymsp[-1].minor.yy445;} + break; + case 246: /* inscollist ::= inscollist COMMA nm */ +{ + yymsp[-2].minor.yy445->append(*(yymsp[0].minor.yy211)); + yygotominor.yy445 = yymsp[-2].minor.yy445; + delete yymsp[0].minor.yy211; + DONT_INHERIT_TOKENS("inscollist"); + } + break; + case 247: /* inscollist ::= nm */ +{ + yygotominor.yy445 = new ParserStringList(); + yygotominor.yy445->append(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + } + break; + case 248: /* inscollist ::= */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy445 = new ParserStringList(); + } + break; + case 249: /* inscollist ::= inscollist COMMA ID_COL */ + case 250: /* inscollist ::= ID_COL */ yytestcase(yyruleno==250); +{ yy_destructor(yypParser,243,&yymsp[-2].minor); +} + break; + case 251: /* exprx ::= term */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initLiteral(*(yymsp[0].minor.yy21)); + delete yymsp[0].minor.yy21; + objectForTokens = yygotominor.yy490; + } + break; + case 252: /* exprx ::= CTIME_KW */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initCTime(yymsp[0].minor.yy0->value); + objectForTokens = yygotominor.yy490; + } + break; + case 253: /* exprx ::= LP expr RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initSubExpr(yymsp[-1].minor.yy490); + objectForTokens = yygotominor.yy490; + } + break; + case 254: /* exprx ::= id */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initId(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy490; + } + break; + case 255: /* exprx ::= JOIN_KW */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initId(yymsp[0].minor.yy0->value); + objectForTokens = yygotominor.yy490; + } + break; + case 256: /* exprx ::= nm DOT nm */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initId(*(yymsp[-2].minor.yy211), *(yymsp[0].minor.yy211)); + delete yymsp[-2].minor.yy211; + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy490; + } + break; + case 257: /* exprx ::= nm DOT nm DOT nm */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initId(*(yymsp[-4].minor.yy211), *(yymsp[-2].minor.yy211), *(yymsp[0].minor.yy211)); + delete yymsp[-4].minor.yy211; + delete yymsp[-2].minor.yy211; + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy490; + } + break; + case 258: /* exprx ::= VARIABLE */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initBindParam(yymsp[0].minor.yy0->value); + objectForTokens = yygotominor.yy490; + } + break; + case 259: /* exprx ::= expr COLLATE ids */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initCollate(yymsp[-2].minor.yy490, *(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy490; + } + break; + case 260: /* exprx ::= CAST LP expr AS typetoken RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initCast(yymsp[-3].minor.yy490, yymsp[-1].minor.yy299); + objectForTokens = yygotominor.yy490; + } + break; + case 261: /* exprx ::= ID LP distinct exprlist RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initFunction(yymsp[-4].minor.yy0->value, *(yymsp[-2].minor.yy376), *(yymsp[-1].minor.yy13)); + delete yymsp[-2].minor.yy376; + delete yymsp[-1].minor.yy13; + objectForTokens = yygotominor.yy490; + } + break; + case 262: /* exprx ::= ID LP STAR RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initFunction(yymsp[-3].minor.yy0->value, true); + objectForTokens = yygotominor.yy490; + } + break; + case 263: /* exprx ::= expr AND expr */ + case 264: /* exprx ::= expr OR expr */ yytestcase(yyruleno==264); + case 265: /* exprx ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==265); + case 266: /* exprx ::= expr EQ|NE expr */ yytestcase(yyruleno==266); + case 267: /* exprx ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==267); + case 268: /* exprx ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==268); + case 269: /* exprx ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==269); + case 270: /* exprx ::= expr CONCAT expr */ yytestcase(yyruleno==270); +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initBinOp(yymsp[-2].minor.yy490, yymsp[-1].minor.yy0->value, yymsp[0].minor.yy490); + objectForTokens = yygotominor.yy490; + } + break; + case 271: /* exprx ::= expr not_opt likeop expr */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initLike(yymsp[-3].minor.yy490, *(yymsp[-2].minor.yy237), *(yymsp[-1].minor.yy374), yymsp[0].minor.yy490); + delete yymsp[-2].minor.yy237; + delete yymsp[-1].minor.yy374; + objectForTokens = yygotominor.yy490; + } + break; + case 272: /* exprx ::= expr not_opt likeop expr ESCAPE expr */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initLike(yymsp[-5].minor.yy490, *(yymsp[-4].minor.yy237), *(yymsp[-3].minor.yy374), yymsp[-2].minor.yy490, yymsp[0].minor.yy490); + delete yymsp[-4].minor.yy237; + delete yymsp[-3].minor.yy374; + objectForTokens = yygotominor.yy490; + } + break; + case 273: /* exprx ::= expr ISNULL|NOTNULL */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initNull(yymsp[-1].minor.yy490, yymsp[0].minor.yy0->value); + objectForTokens = yygotominor.yy490; + } + break; + case 274: /* exprx ::= expr NOT NULL */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initNull(yymsp[-2].minor.yy490, "NOT NULL"); + objectForTokens = yygotominor.yy490; + } + break; + case 275: /* exprx ::= expr IS not_opt expr */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initIs(yymsp[-3].minor.yy490, *(yymsp[-1].minor.yy237), yymsp[0].minor.yy490); + delete yymsp[-1].minor.yy237; + objectForTokens = yygotominor.yy490; + } + break; + case 276: /* exprx ::= NOT expr */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initUnaryOp(yymsp[0].minor.yy490, yymsp[-1].minor.yy0->value); + } + break; + case 277: /* exprx ::= BITNOT expr */ + case 278: /* exprx ::= MINUS expr */ yytestcase(yyruleno==278); + case 279: /* exprx ::= PLUS expr */ yytestcase(yyruleno==279); +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initUnaryOp(yymsp[0].minor.yy490, yymsp[-1].minor.yy0->value); + objectForTokens = yygotominor.yy490; + } + break; + case 280: /* exprx ::= expr not_opt BETWEEN expr AND expr */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initBetween(yymsp[-5].minor.yy490, *(yymsp[-4].minor.yy237), yymsp[-2].minor.yy490, yymsp[0].minor.yy490); + delete yymsp[-4].minor.yy237; + objectForTokens = yygotominor.yy490; + } + break; + case 281: /* exprx ::= expr not_opt IN LP exprlist RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initIn(yymsp[-5].minor.yy490, *(yymsp[-4].minor.yy237), *(yymsp[-1].minor.yy13)); + delete yymsp[-4].minor.yy237; + delete yymsp[-1].minor.yy13; + objectForTokens = yygotominor.yy490; + } + break; + case 282: /* exprx ::= LP select RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initSubSelect(yymsp[-1].minor.yy123); + objectForTokens = yygotominor.yy490; + } + break; + case 283: /* exprx ::= expr not_opt IN LP select RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initIn(yymsp[-5].minor.yy490, *(yymsp[-4].minor.yy237), yymsp[-1].minor.yy123); + delete yymsp[-4].minor.yy237; + objectForTokens = yygotominor.yy490; + } + break; + case 284: /* exprx ::= expr not_opt IN nm dbnm */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initIn(yymsp[-4].minor.yy490, yymsp[-3].minor.yy237, *(yymsp[-1].minor.yy211), *(yymsp[0].minor.yy211)); + delete yymsp[-3].minor.yy237; + delete yymsp[-1].minor.yy211; + objectForTokens = yygotominor.yy490; + } + break; + case 285: /* exprx ::= EXISTS LP select RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initExists(yymsp[-1].minor.yy123); + objectForTokens = yygotominor.yy490; + } + break; + case 286: /* exprx ::= CASE case_operand case_exprlist case_else END */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initCase(yymsp[-3].minor.yy490, *(yymsp[-2].minor.yy13), yymsp[-1].minor.yy490); + delete yymsp[-2].minor.yy13; + objectForTokens = yygotominor.yy490; + } + break; + case 287: /* exprx ::= RAISE LP IGNORE RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initRaise(yymsp[-1].minor.yy0->value); + objectForTokens = yygotominor.yy490; + } + break; + case 288: /* exprx ::= RAISE LP raisetype COMMA nm RP */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initRaise(yymsp[-3].minor.yy0->value, *(yymsp[-1].minor.yy211)); + delete yymsp[-1].minor.yy211; + objectForTokens = yygotominor.yy490; + } + break; + case 289: /* exprx ::= nm DOT */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initId(*(yymsp[-1].minor.yy211), QString::null, QString::null); + delete yymsp[-1].minor.yy211; + objectForTokens = yygotominor.yy490; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 290: /* exprx ::= nm DOT nm DOT */ +{ + yygotominor.yy490 = new SqliteExpr(); + yygotominor.yy490->initId(*(yymsp[-3].minor.yy211), *(yymsp[-1].minor.yy211), QString::null); + delete yymsp[-3].minor.yy211; + delete yymsp[-1].minor.yy211; + objectForTokens = yygotominor.yy490; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 291: /* exprx ::= expr not_opt BETWEEN expr */ +{ + yygotominor.yy490 = new SqliteExpr(); + delete yymsp[-2].minor.yy237; + delete yymsp[-3].minor.yy490; + delete yymsp[0].minor.yy490; + objectForTokens = yygotominor.yy490; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 292: /* exprx ::= CASE case_operand case_exprlist case_else */ +{ + yygotominor.yy490 = new SqliteExpr(); + delete yymsp[-1].minor.yy13; + delete yymsp[-2].minor.yy490; + delete yymsp[0].minor.yy490; + objectForTokens = yygotominor.yy490; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 293: /* exprx ::= expr not_opt IN LP exprlist */ +{ + yygotominor.yy490 = new SqliteExpr(); + delete yymsp[-3].minor.yy237; + delete yymsp[0].minor.yy13; + delete yymsp[-4].minor.yy490; + objectForTokens = yygotominor.yy490; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + break; + case 294: /* exprx ::= expr not_opt IN ID_DB */ +{ yy_destructor(yypParser,199,&yymsp[-3].minor); +} + break; + case 295: /* exprx ::= expr not_opt IN nm DOT ID_TAB */ + case 296: /* exprx ::= ID_DB|ID_TAB|ID_COL|ID_FN */ yytestcase(yyruleno==296); +{ yy_destructor(yypParser,199,&yymsp[-5].minor); + yy_destructor(yypParser,177,&yymsp[-2].minor); +} + break; + case 298: /* exprx ::= nm DOT nm DOT ID_COL */ +{ yy_destructor(yypParser,177,&yymsp[-4].minor); + yy_destructor(yypParser,177,&yymsp[-2].minor); +} + break; + case 299: /* exprx ::= expr COLLATE ID_COLLATE */ + case 300: /* exprx ::= RAISE LP raisetype COMMA ID_ERR_MSG RP */ yytestcase(yyruleno==300); +{ yy_destructor(yypParser,199,&yymsp[-2].minor); +} + break; + case 302: /* expr ::= */ +{ + yygotominor.yy490 = new SqliteExpr(); + objectForTokens = yygotominor.yy490; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 305: /* likeop ::= LIKE_KW|MATCH */ +{yygotominor.yy374 = new SqliteExpr::LikeOp(SqliteExpr::likeOp(yymsp[0].minor.yy0->value));} + break; + case 306: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ +{ + yymsp[-4].minor.yy13->append(yymsp[-2].minor.yy490); + yymsp[-4].minor.yy13->append(yymsp[0].minor.yy490); + yygotominor.yy13 = yymsp[-4].minor.yy13; + } + break; + case 307: /* case_exprlist ::= WHEN expr THEN expr */ +{ + yygotominor.yy13 = new ParserExprList(); + yygotominor.yy13->append(yymsp[-2].minor.yy490); + yygotominor.yy13->append(yymsp[0].minor.yy490); + } + break; + case 314: /* nexprlist ::= nexprlist COMMA expr */ +{ + yymsp[-2].minor.yy13->append(yymsp[0].minor.yy490); + yygotominor.yy13 = yymsp[-2].minor.yy13; + DONT_INHERIT_TOKENS("nexprlist"); + } + break; + case 315: /* nexprlist ::= exprx */ +{ + yygotominor.yy13 = new ParserExprList(); + yygotominor.yy13->append(yymsp[0].minor.yy490); + } + break; + case 316: /* cmd ::= CREATE uniqueflag INDEX ifnotexists nm dbnm ON nm LP idxlist RP where_opt */ +{ + yygotominor.yy399 = new SqliteCreateIndex( + *(yymsp[-10].minor.yy237), + *(yymsp[-8].minor.yy237), + *(yymsp[-7].minor.yy211), + *(yymsp[-6].minor.yy211), + *(yymsp[-4].minor.yy211), + *(yymsp[-2].minor.yy139), + yymsp[0].minor.yy490 + ); + delete yymsp[-8].minor.yy237; + delete yymsp[-10].minor.yy237; + delete yymsp[-7].minor.yy211; + delete yymsp[-6].minor.yy211; + delete yymsp[-4].minor.yy211; + delete yymsp[-2].minor.yy139; + objectForTokens = yygotominor.yy399; + } + break; + case 317: /* cmd ::= CREATE uniqueflag INDEX ifnotexists nm dbnm ON ID_TAB */ +{ yy_destructor(yypParser,177,&yymsp[-3].minor); +} + break; + case 322: /* idxlist_opt ::= */ +{yygotominor.yy139 = new ParserIndexedColumnList();} + break; + case 323: /* idxlist_opt ::= LP idxlist RP */ +{yygotominor.yy139 = yymsp[-1].minor.yy139;} + break; + case 324: /* idxlist ::= idxlist COMMA idxlist_single */ +{ + yymsp[-2].minor.yy139->append(yymsp[0].minor.yy90); + yygotominor.yy139 = yymsp[-2].minor.yy139; + DONT_INHERIT_TOKENS("idxlist"); + } + break; + case 325: /* idxlist ::= idxlist_single */ +{ + yygotominor.yy139 = new ParserIndexedColumnList(); + yygotominor.yy139->append(yymsp[0].minor.yy90); + } + break; + case 326: /* idxlist_single ::= nm collate sortorder */ + case 327: /* idxlist_single ::= ID_COL */ yytestcase(yyruleno==327); +{ + SqliteIndexedColumn* obj = + new SqliteIndexedColumn( + *(yymsp[-2].minor.yy211), + *(yymsp[-1].minor.yy211), + *(yymsp[0].minor.yy226) + ); + yygotominor.yy90 = obj; + delete yymsp[0].minor.yy226; + delete yymsp[-2].minor.yy211; + delete yymsp[-1].minor.yy211; + objectForTokens = yygotominor.yy90; + } + break; + case 331: /* cmd ::= DROP INDEX ifexists fullname */ +{ + yygotominor.yy399 = new SqliteDropIndex(*(yymsp[-1].minor.yy237), yymsp[0].minor.yy66->name1, yymsp[0].minor.yy66->name2); + delete yymsp[-1].minor.yy237; + delete yymsp[0].minor.yy66; + objectForTokens = yygotominor.yy399; + } + break; + case 334: /* cmd ::= VACUUM */ +{ + yygotominor.yy399 = new SqliteVacuum(); + objectForTokens = yygotominor.yy399; + } + break; + case 335: /* cmd ::= VACUUM nm */ +{ + yygotominor.yy399 = new SqliteVacuum(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy399; + } + break; + case 336: /* cmd ::= PRAGMA nm dbnm */ +{ + yygotominor.yy399 = new SqlitePragma(*(yymsp[-1].minor.yy211), *(yymsp[0].minor.yy211)); + delete yymsp[-1].minor.yy211; + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy399; + } + break; + case 337: /* cmd ::= PRAGMA nm dbnm EQ nmnum */ + case 339: /* cmd ::= PRAGMA nm dbnm EQ minus_num */ yytestcase(yyruleno==339); +{ + yygotominor.yy399 = new SqlitePragma(*(yymsp[-3].minor.yy211), *(yymsp[-2].minor.yy211), *(yymsp[0].minor.yy21), true); + delete yymsp[-3].minor.yy211; + delete yymsp[-2].minor.yy211; + delete yymsp[0].minor.yy21; + objectForTokens = yygotominor.yy399; + } + break; + case 338: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */ + case 340: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */ yytestcase(yyruleno==340); +{ + yygotominor.yy399 = new SqlitePragma(*(yymsp[-4].minor.yy211), *(yymsp[-3].minor.yy211), *(yymsp[-1].minor.yy21), false); + delete yymsp[-4].minor.yy211; + delete yymsp[-3].minor.yy211; + delete yymsp[-1].minor.yy21; + objectForTokens = yygotominor.yy399; + } + break; + case 344: /* nmnum ::= nm */ +{ + yygotominor.yy21 = new QVariant(*(yymsp[0].minor.yy211)); + delete yymsp[0].minor.yy211; + } + break; + case 350: /* minus_num ::= MINUS number */ +{ + if (yymsp[0].minor.yy21->type() == QVariant::Double) + *(yymsp[0].minor.yy21) = -(yymsp[0].minor.yy21->toDouble()); + else if (yymsp[0].minor.yy21->type() == QVariant::LongLong) + *(yymsp[0].minor.yy21) = -(yymsp[0].minor.yy21->toLongLong()); + else + Q_ASSERT_X(true, "producing minus number", "QVariant is neither of Double or LongLong."); + + yygotominor.yy21 = yymsp[0].minor.yy21; + } + break; + case 351: /* number ::= INTEGER */ +{yygotominor.yy21 = new QVariant(QVariant(yymsp[0].minor.yy0->value).toLongLong());} + break; + case 352: /* number ::= FLOAT */ +{yygotominor.yy21 = new QVariant(QVariant(yymsp[0].minor.yy0->value).toDouble());} + break; + case 353: /* cmd ::= CREATE temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON nm foreach_clause when_clause BEGIN trigger_cmd_list END */ +{ + yygotominor.yy399 = new SqliteCreateTrigger( + *(yymsp[-13].minor.yy376), + *(yymsp[-11].minor.yy237), + *(yymsp[-10].minor.yy211), + *(yymsp[-9].minor.yy211), + *(yymsp[-5].minor.yy211), + *(yymsp[-8].minor.yy152), + yymsp[-7].minor.yy309, + *(yymsp[-4].minor.yy409), + yymsp[-3].minor.yy490, + *(yymsp[-1].minor.yy214), + 3 + ); + delete yymsp[-11].minor.yy237; + delete yymsp[-13].minor.yy376; + delete yymsp[-8].minor.yy152; + delete yymsp[-4].minor.yy409; + delete yymsp[-10].minor.yy211; + delete yymsp[-5].minor.yy211; + delete yymsp[-9].minor.yy211; + delete yymsp[-1].minor.yy214; + objectForTokens = yygotominor.yy399; + } + break; + case 354: /* cmd ::= CREATE temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON nm foreach_clause when_clause */ +{ + QList<SqliteQuery *> CL; + + yygotominor.yy399 = new SqliteCreateTrigger( + *(yymsp[-10].minor.yy376), + *(yymsp[-8].minor.yy237), + *(yymsp[-7].minor.yy211), + *(yymsp[-6].minor.yy211), + *(yymsp[-2].minor.yy211), + *(yymsp[-5].minor.yy152), + yymsp[-4].minor.yy309, + *(yymsp[-1].minor.yy409), + yymsp[0].minor.yy490, + CL, + 3 + ); + delete yymsp[-8].minor.yy237; + delete yymsp[-10].minor.yy376; + delete yymsp[-5].minor.yy152; + delete yymsp[-1].minor.yy409; + delete yymsp[-7].minor.yy211; + delete yymsp[-2].minor.yy211; + delete yymsp[-6].minor.yy211; + objectForTokens = yygotominor.yy399; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 355: /* cmd ::= CREATE temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON nm foreach_clause when_clause BEGIN trigger_cmd_list */ +{ + yygotominor.yy399 = new SqliteCreateTrigger( + *(yymsp[-12].minor.yy376), + *(yymsp[-10].minor.yy237), + *(yymsp[-9].minor.yy211), + *(yymsp[-8].minor.yy211), + *(yymsp[-4].minor.yy211), + *(yymsp[-7].minor.yy152), + yymsp[-6].minor.yy309, + *(yymsp[-3].minor.yy409), + yymsp[-2].minor.yy490, + *(yymsp[0].minor.yy214), + 3 + ); + delete yymsp[-10].minor.yy237; + delete yymsp[-12].minor.yy376; + delete yymsp[-7].minor.yy152; + delete yymsp[-3].minor.yy409; + delete yymsp[-9].minor.yy211; + delete yymsp[-4].minor.yy211; + delete yymsp[-8].minor.yy211; + delete yymsp[0].minor.yy214; + objectForTokens = yygotominor.yy399; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 356: /* cmd ::= CREATE temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON ID_TAB */ +{ yy_destructor(yypParser,179,&yymsp[-8].minor); + yy_destructor(yypParser,177,&yymsp[-5].minor); + yy_destructor(yypParser,262,&yymsp[-3].minor); + yy_destructor(yypParser,263,&yymsp[-2].minor); +} + break; + case 359: /* trigger_time ::= BEFORE */ +{yygotominor.yy152 = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::BEFORE);} + break; + case 360: /* trigger_time ::= AFTER */ +{yygotominor.yy152 = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::AFTER);} + break; + case 361: /* trigger_time ::= INSTEAD OF */ +{yygotominor.yy152 = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::INSTEAD_OF);} + break; + case 362: /* trigger_time ::= */ +{yygotominor.yy152 = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::null);} + break; + case 363: /* trigger_event ::= DELETE */ +{ + yygotominor.yy309 = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::DELETE); + objectForTokens = yygotominor.yy309; + } + break; + case 364: /* trigger_event ::= INSERT */ +{ + yygotominor.yy309 = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::INSERT); + objectForTokens = yygotominor.yy309; + } + break; + case 365: /* trigger_event ::= UPDATE */ +{ + yygotominor.yy309 = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::UPDATE); + objectForTokens = yygotominor.yy309; + } + break; + case 366: /* trigger_event ::= UPDATE OF inscollist */ +{ + yygotominor.yy309 = new SqliteCreateTrigger::Event(*(yymsp[0].minor.yy445)); + delete yymsp[0].minor.yy445; + objectForTokens = yygotominor.yy309; + } + break; + case 367: /* foreach_clause ::= */ +{yygotominor.yy409 = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::null);} + break; + case 368: /* foreach_clause ::= FOR EACH ROW */ +{yygotominor.yy409 = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::FOR_EACH_ROW);} + break; + case 371: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ +{ + yymsp[-2].minor.yy214->append(yymsp[-1].minor.yy399); + yygotominor.yy214 = yymsp[-2].minor.yy214; + DONT_INHERIT_TOKENS("trigger_cmd_list"); + } + break; + case 372: /* trigger_cmd_list ::= trigger_cmd SEMI */ +{ + yygotominor.yy214 = new ParserQueryList(); + yygotominor.yy214->append(yymsp[-1].minor.yy399); + } + break; + case 373: /* trigger_cmd_list ::= SEMI */ +{ + yygotominor.yy214 = new ParserQueryList(); + parserContext->minorErrorAfterLastToken("Syntax error"); + } + break; + case 378: /* raisetype ::= ROLLBACK|ABORT|FAIL */ +{yygotominor.yy0 = yymsp[0].minor.yy0;} + break; + case 379: /* cmd ::= DROP TRIGGER ifexists fullname */ +{ + yygotominor.yy399 = new SqliteDropTrigger(*(yymsp[-1].minor.yy237), yymsp[0].minor.yy66->name1, yymsp[0].minor.yy66->name2); + delete yymsp[-1].minor.yy237; + delete yymsp[0].minor.yy66; + objectForTokens = yygotominor.yy399; + } + break; + case 382: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ +{ + yygotominor.yy399 = new SqliteAttach(*(yymsp[-4].minor.yy237), yymsp[-3].minor.yy490, yymsp[-1].minor.yy490, yymsp[0].minor.yy490); + delete yymsp[-4].minor.yy237; + objectForTokens = yygotominor.yy399; + } + break; + case 383: /* cmd ::= DETACH database_kw_opt expr */ +{ + yygotominor.yy399 = new SqliteDetach(*(yymsp[-1].minor.yy237), yymsp[0].minor.yy490); + delete yymsp[-1].minor.yy237; + objectForTokens = yygotominor.yy399; + } + break; + case 388: /* cmd ::= REINDEX */ +{yygotominor.yy399 = new SqliteReindex();} + break; + case 389: /* cmd ::= REINDEX nm dbnm */ + case 390: /* cmd ::= REINDEX ID_COLLATE */ yytestcase(yyruleno==390); +{ + yygotominor.yy399 = new SqliteReindex(*(yymsp[-1].minor.yy211), *(yymsp[0].minor.yy211)); + delete yymsp[-1].minor.yy211; + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy399; + } + break; + case 393: /* cmd ::= ANALYZE */ +{ + yygotominor.yy399 = new SqliteAnalyze(); + objectForTokens = yygotominor.yy399; + } + break; + case 394: /* cmd ::= ANALYZE nm dbnm */ +{ + yygotominor.yy399 = new SqliteAnalyze(*(yymsp[-1].minor.yy211), *(yymsp[0].minor.yy211)); + delete yymsp[-1].minor.yy211; + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy399; + } + break; + case 397: /* cmd ::= ALTER TABLE fullname RENAME TO nm */ +{ + yygotominor.yy399 = new SqliteAlterTable( + yymsp[-3].minor.yy66->name1, + yymsp[-3].minor.yy66->name2, + *(yymsp[0].minor.yy211) + ); + delete yymsp[0].minor.yy211; + delete yymsp[-3].minor.yy66; + objectForTokens = yygotominor.yy399; + } + break; + case 398: /* cmd ::= ALTER TABLE fullname ADD kwcolumn_opt column */ +{ + yygotominor.yy399 = new SqliteAlterTable( + yymsp[-3].minor.yy66->name1, + yymsp[-3].minor.yy66->name2, + *(yymsp[-1].minor.yy237), + yymsp[0].minor.yy425 + ); + delete yymsp[-1].minor.yy237; + delete yymsp[-3].minor.yy66; + objectForTokens = yygotominor.yy399; + } + break; + case 399: /* cmd ::= ALTER TABLE fullname RENAME TO ID_TAB_NEW */ +{ yy_destructor(yypParser,181,&yymsp[-3].minor); +} + break; + case 405: /* create_vtab ::= CREATE VIRTUAL TABLE ifnotexists nm dbnm USING nm */ +{ + yygotominor.yy399 = new SqliteCreateVirtualTable( + *(yymsp[-4].minor.yy237), + *(yymsp[-3].minor.yy211), + *(yymsp[-2].minor.yy211), + *(yymsp[0].minor.yy211) + ); + delete yymsp[-4].minor.yy237; + delete yymsp[-3].minor.yy211; + delete yymsp[-2].minor.yy211; + delete yymsp[0].minor.yy211; + objectForTokens = yygotominor.yy399; + } + break; + case 406: /* create_vtab ::= CREATE VIRTUAL TABLE ifnotexists nm dbnm USING nm LP vtabarglist RP */ +{ + yygotominor.yy399 = new SqliteCreateVirtualTable( + *(yymsp[-7].minor.yy237), + *(yymsp[-6].minor.yy211), + *(yymsp[-5].minor.yy211), + *(yymsp[-3].minor.yy211), + *(yymsp[-1].minor.yy445) + ); + delete yymsp[-6].minor.yy211; + delete yymsp[-5].minor.yy211; + delete yymsp[-3].minor.yy211; + delete yymsp[-7].minor.yy237; + delete yymsp[-1].minor.yy445; + objectForTokens = yygotominor.yy399; + } + break; + case 409: /* vtabarglist ::= vtabarg */ +{ + yygotominor.yy445 = new ParserStringList(); + yygotominor.yy445->append((yymsp[0].minor.yy211)->mid(1)); // mid(1) to skip the first whitespace added in vtabarg + delete yymsp[0].minor.yy211; + } + break; + case 410: /* vtabarglist ::= vtabarglist COMMA vtabarg */ +{ + yymsp[-2].minor.yy445->append((yymsp[0].minor.yy211)->mid(1)); // mid(1) to skip the first whitespace added in vtabarg + yygotominor.yy445 = yymsp[-2].minor.yy445; + delete yymsp[0].minor.yy211; + DONT_INHERIT_TOKENS("vtabarglist"); + } + break; + case 412: /* vtabarg ::= vtabarg vtabargtoken */ +{ + yymsp[-1].minor.yy211->append(" "+ *(yymsp[0].minor.yy211)); + yygotominor.yy211 = yymsp[-1].minor.yy211; + delete yymsp[0].minor.yy211; + } + break; + case 413: /* vtabargtoken ::= ANY */ +{ + yygotominor.yy211 = new QString(yymsp[0].minor.yy0->value); + } + break; + case 414: /* vtabargtoken ::= LP anylist RP */ +{ + yygotominor.yy211 = new QString("("); + yygotominor.yy211->append(*(yymsp[-1].minor.yy211)); + yygotominor.yy211->append(")"); + delete yymsp[-1].minor.yy211; + } + break; + case 416: /* anylist ::= anylist LP anylist RP */ +{ + yygotominor.yy211 = yymsp[-3].minor.yy211; + yygotominor.yy211->append("("); + yygotominor.yy211->append(*(yymsp[-1].minor.yy211)); + yygotominor.yy211->append(")"); + delete yymsp[-1].minor.yy211; + DONT_INHERIT_TOKENS("anylist"); + } + break; + case 417: /* anylist ::= anylist ANY */ +{ + yygotominor.yy211 = yymsp[-1].minor.yy211; + yygotominor.yy211->append(yymsp[0].minor.yy0->value); + DONT_INHERIT_TOKENS("anylist"); + } + break; + case 418: /* with ::= */ +{yygotominor.yy367 = nullptr;} + break; + case 419: /* with ::= WITH wqlist */ +{ + yygotominor.yy367 = yymsp[0].minor.yy367; + objectForTokens = yygotominor.yy367; + } + break; + case 420: /* with ::= WITH RECURSIVE wqlist */ +{ + yygotominor.yy367 = yymsp[0].minor.yy367; + yygotominor.yy367->recursive = true; + objectForTokens = yygotominor.yy367; + } + break; + case 421: /* wqlist ::= nm idxlist_opt AS LP select RP */ +{ + yygotominor.yy367 = SqliteWith::append(*(yymsp[-5].minor.yy211), *(yymsp[-4].minor.yy139), yymsp[-1].minor.yy123); + delete yymsp[-5].minor.yy211; + delete yymsp[-4].minor.yy139; + } + break; + case 422: /* wqlist ::= wqlist COMMA nm idxlist_opt AS LP select RP */ +{ + yygotominor.yy367 = SqliteWith::append(yymsp[-7].minor.yy367, *(yymsp[-5].minor.yy211), *(yymsp[-4].minor.yy139), yymsp[-1].minor.yy123); + delete yymsp[-5].minor.yy211; + delete yymsp[-4].minor.yy139; + DONT_INHERIT_TOKENS("wqlist"); + } + break; + case 423: /* wqlist ::= ID_TAB_NEW */ +{ + parserContext->minorErrorBeforeNextToken("Syntax error"); + yygotominor.yy367 = new SqliteWith(); + } + break; + default: + /* (0) input ::= cmdlist */ yytestcase(yyruleno==0); + break; + }; + } + assert( yyruleno>=0 && yyruleno<(int)(sizeof(yyRuleInfo)/sizeof(yyRuleInfo[0])) ); + yygoto = yyRuleInfo[yyruleno].lhs; + yysize = yyRuleInfo[yyruleno].nrhs; + + // Store tokens for the rule in parser context + QList<Token*> allTokens; + QList<Token*> allTokensWithAllInherited; + QString keyForTokensMap; + int tokensMapKeyCnt; + if (parserContext->setupTokens) + { + if (objectForTokens) + { + // In case this is a list with recurrent references we need + // to clear tokens before adding the new and extended list. + objectForTokens->tokens.clear(); + } + + QList<Token*> tokens; + for (int i = yypParser->yyidx - yysize + 1; i <= yypParser->yyidx; i++) + { + tokens.clear(); + const char* fieldName = yyTokenName[yypParser->yystack[i].major]; + if (parserContext->isManagedToken(yypParser->yystack[i].minor.yy0)) + tokens += yypParser->yystack[i].minor.yy0; + + tokens += *(yypParser->yystack[i].tokens); + + if (!noTokenInheritanceFields.contains(fieldName)) + { + if (objectForTokens) + { + keyForTokensMap = fieldName; + tokensMapKeyCnt = 2; + while (objectForTokens->tokensMap.contains(keyForTokensMap)) + keyForTokensMap = fieldName + QString::number(tokensMapKeyCnt++); + + objectForTokens->tokensMap[keyForTokensMap] = parserContext->getTokenPtrList(tokens); + } + + allTokens += tokens; + } + else + { + // If field is mentioned only once, then only one occurance of it will be ignored. + // Second one should be inherited. See "anylist" definition for explanation why. + noTokenInheritanceFields.removeOne(fieldName); + } + allTokensWithAllInherited += tokens; + } + if (objectForTokens) + { + objectForTokens->tokens += parserContext->getTokenPtrList(allTokens); + } + } + + // Clear token lists + for (int i = yypParser->yyidx - yysize + 1; i <= yypParser->yyidx; i++) + { + delete yypParser->yystack[i].tokens; + yypParser->yystack[i].tokens = nullptr; + } + + yypParser->yyidx -= yysize; + yyact = yy_find_reduce_action(yymsp[-yysize].stateno,(YYCODETYPE)yygoto); + if( yyact < YYNSTATE ){ +#ifdef NDEBUG + /* If we are not debugging and the reduce action popped at least + ** one element off the stack, then we can push the new element back + ** onto the stack here, and skip the stack overflow test in yy_shift(). + ** That gives a significant speed improvement. */ + if( yysize ){ + yypParser->yyidx++; + yymsp -= yysize-1; + yymsp->stateno = (YYACTIONTYPE)yyact; + yymsp->major = (YYCODETYPE)yygoto; + yymsp->minor = yygotominor; + if (parserContext->setupTokens) + *(yypParser->yystack[yypParser->yyidx].tokens) = allTokens; + }else +#endif + { + yy_shift(yypParser,yyact,yygoto,&yygotominor); + if (parserContext->setupTokens) + { + QList<Token*>* tokensPtr = yypParser->yystack[yypParser->yyidx].tokens; + *tokensPtr = allTokensWithAllInherited + *tokensPtr; + } + } + }else{ + assert( yyact == YYNSTATE + YYNRULE + 1 ); + yy_accept(yypParser); + } +} + +/* +** The following code executes when the parse fails +*/ +#ifndef YYNOERRORRECOVERY +static void yy_parse_failed( + yyParser *yypParser /* The parser */ +){ + sqlite3_parseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ + sqlite3_parseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} +#endif /* YYNOERRORRECOVERY */ + +/* +** The following code executes when a syntax error first occurs. +*/ +static void yy_syntax_error( + yyParser *yypParser, /* The parser */ + int yymajor, /* The major type of the error token */ + YYMINORTYPE yyminor /* The minor type of the error token */ +){ + sqlite3_parseARG_FETCH; +#define TOKEN (yyminor.yy0) + + UNUSED_PARAMETER(yymajor); + parserContext->error(TOKEN, QObject::tr("Syntax error")); + //qDebug() << "near " << TOKEN->toString() << ": syntax error"; + sqlite3_parseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + sqlite3_parseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser accepts */ + sqlite3_parseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "sqlite3_parseAlloc" which describes the current state of the parser. +** The second argument is the major token number. The third is +** the minor token. The fourth optional argument is whatever the +** user wants (and specified in the grammar) and is available for +** use by the action routines. +** +** Inputs: +** <ul> +** <li> A pointer to the parser (an opaque structure.) +** <li> The major token number. +** <li> The minor token number. +** <li> An option argument of a grammar-specified type. +** </ul> +** +** Outputs: +** None. +*/ +void sqlite3_parse( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + sqlite3_parseTOKENTYPE yyminor /* The value for the token */ + sqlite3_parseARG_PDECL /* Optional %extra_argument parameter */ +){ + YYMINORTYPE yyminorunion; + int yyact; /* The parser action. */ +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + int yyendofinput; /* True if we are at the end of input */ +#endif +#ifdef YYERRORSYMBOL + int yyerrorhit = 0; /* True if yymajor has invoked an error */ +#endif + yyParser *yypParser; /* The parser */ + + /* (re)initialize the parser, if necessary */ + yypParser = (yyParser*)yyp; + if( yypParser->yyidx<0 ){ +#if YYSTACKDEPTH<=0 + if( yypParser->yystksz <=0 ){ + /*memset(&yyminorunion, 0, sizeof(yyminorunion));*/ + yyminorunion = yyzerominor; + yyStackOverflow(yypParser, &yyminorunion); + return; + } +#endif + yypParser->yyidx = 0; + yypParser->yyerrcnt = -1; + yypParser->yystack[0].stateno = 0; + yypParser->yystack[0].major = 0; + yypParser->yystack[0].tokens = new QList<Token*>(); + } + yyminorunion.yy0 = yyminor; +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + yyendofinput = (yymajor==0); +#endif + sqlite3_parseARG_STORE; + +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sInput %s [%s] (lemon type: %s)\n", + yyTracePrompt, + yyminor->value.toLatin1().data(), + yyminor->typeString().toLatin1().data(), + yyTokenName[yymajor]); } +#endif + + do{ + yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor); + if( yyact<YYNSTATE ){ + yy_shift(yypParser,yyact,yymajor,&yyminorunion); + yypParser->yyerrcnt--; + yymajor = YYNOCODE; + }else if( yyact < YYNSTATE + YYNRULE ){ + yy_reduce(yypParser,yyact-YYNSTATE); + }else{ + assert( yyact == YY_ERROR_ACTION ); +#ifdef YYERRORSYMBOL + int yymx; +#endif +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); + } +#endif +#ifdef YYERRORSYMBOL + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if( yypParser->yyerrcnt<0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yymx = yypParser->yystack[yypParser->yyidx].major; + if( yymx==YYERRORSYMBOL || yyerrorhit ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sDiscard input token %s\n", + yyTracePrompt,yyTokenName[yymajor]); + } +#endif + yy_destructor(yypParser, (YYCODETYPE)yymajor,&yyminorunion); + yymajor = YYNOCODE; + }else{ + while( + yypParser->yyidx >= 0 && + yymx != YYERRORSYMBOL && + (yyact = yy_find_reduce_action( + yypParser->yystack[yypParser->yyidx].stateno, + YYERRORSYMBOL)) >= YYNSTATE + ){ + yy_pop_parser_stack(yypParser); + } + if( yypParser->yyidx < 0 || yymajor==0 ){ + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yy_parse_failed(yypParser); + yymajor = YYNOCODE; + }else if( yymx!=YYERRORSYMBOL ){ + YYMINORTYPE u2; + u2.YYERRSYMDT = 0; + yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2); + } + } + yypParser->yyerrcnt = 1; // not 3 valid tokens, but 1 + yyerrorhit = 1; +#elif defined(YYNOERRORRECOVERY) + /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to + ** do any kind of error recovery. Instead, simply invoke the syntax + ** error routine and continue going as if nothing had happened. + ** + ** Applications can set this macro (for example inside %include) if + ** they intend to abandon the parse upon the first syntax error seen. + */ + yy_syntax_error(yypParser,yymajor,yyminorunion); + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yymajor = YYNOCODE; + +#else /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if( yypParser->yyerrcnt<=0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yypParser->yyerrcnt = 1; // not 3 valid tokens, but 1 + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + if( yyendofinput ){ + yy_parse_failed(yypParser); + } + yymajor = YYNOCODE; +#endif + } + }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 ); + return; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.h b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.h new file mode 100644 index 0000000..d18c81c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.h @@ -0,0 +1,167 @@ +#define TK3_ILLEGAL 1 +#define TK3_COMMENT 2 +#define TK3_SPACE 3 +#define TK3_ID 4 +#define TK3_ABORT 5 +#define TK3_ACTION 6 +#define TK3_AFTER 7 +#define TK3_ANALYZE 8 +#define TK3_ASC 9 +#define TK3_ATTACH 10 +#define TK3_BEFORE 11 +#define TK3_BEGIN 12 +#define TK3_BY 13 +#define TK3_CASCADE 14 +#define TK3_CAST 15 +#define TK3_COLUMNKW 16 +#define TK3_CONFLICT 17 +#define TK3_DATABASE 18 +#define TK3_DEFERRED 19 +#define TK3_DESC 20 +#define TK3_DETACH 21 +#define TK3_EACH 22 +#define TK3_END 23 +#define TK3_EXCLUSIVE 24 +#define TK3_EXPLAIN 25 +#define TK3_FAIL 26 +#define TK3_FOR 27 +#define TK3_IGNORE 28 +#define TK3_IMMEDIATE 29 +#define TK3_INDEXED 30 +#define TK3_INITIALLY 31 +#define TK3_INSTEAD 32 +#define TK3_LIKE_KW 33 +#define TK3_MATCH 34 +#define TK3_NO 35 +#define TK3_PLAN 36 +#define TK3_QUERY 37 +#define TK3_KEY 38 +#define TK3_OF 39 +#define TK3_OFFSET 40 +#define TK3_PRAGMA 41 +#define TK3_RAISE 42 +#define TK3_RECURSIVE 43 +#define TK3_RELEASE 44 +#define TK3_REPLACE 45 +#define TK3_RESTRICT 46 +#define TK3_ROW 47 +#define TK3_ROLLBACK 48 +#define TK3_SAVEPOINT 49 +#define TK3_TEMP 50 +#define TK3_TRIGGER 51 +#define TK3_VACUUM 52 +#define TK3_VIEW 53 +#define TK3_VIRTUAL 54 +#define TK3_WITH 55 +#define TK3_WITHOUT 56 +#define TK3_REINDEX 57 +#define TK3_RENAME 58 +#define TK3_CTIME_KW 59 +#define TK3_IF 60 +#define TK3_ANY 61 +#define TK3_OR 62 +#define TK3_AND 63 +#define TK3_NOT 64 +#define TK3_IS 65 +#define TK3_BETWEEN 66 +#define TK3_IN 67 +#define TK3_ISNULL 68 +#define TK3_NOTNULL 69 +#define TK3_NE 70 +#define TK3_EQ 71 +#define TK3_GT 72 +#define TK3_LE 73 +#define TK3_LT 74 +#define TK3_GE 75 +#define TK3_ESCAPE 76 +#define TK3_BITAND 77 +#define TK3_BITOR 78 +#define TK3_LSHIFT 79 +#define TK3_RSHIFT 80 +#define TK3_PLUS 81 +#define TK3_MINUS 82 +#define TK3_STAR 83 +#define TK3_SLASH 84 +#define TK3_REM 85 +#define TK3_CONCAT 86 +#define TK3_COLLATE 87 +#define TK3_BITNOT 88 +#define TK3_SEMI 89 +#define TK3_TRANSACTION 90 +#define TK3_ID_TRANS 91 +#define TK3_COMMIT 92 +#define TK3_TO 93 +#define TK3_CREATE 94 +#define TK3_TABLE 95 +#define TK3_LP 96 +#define TK3_RP 97 +#define TK3_AS 98 +#define TK3_DOT 99 +#define TK3_ID_TAB_NEW 100 +#define TK3_ID_DB 101 +#define TK3_CTX_ROWID_KW 102 +#define TK3_EXISTS 103 +#define TK3_COMMA 104 +#define TK3_ID_COL_NEW 105 +#define TK3_STRING 106 +#define TK3_JOIN_KW 107 +#define TK3_ID_COL_TYPE 108 +#define TK3_CONSTRAINT 109 +#define TK3_DEFAULT 110 +#define TK3_NULL 111 +#define TK3_PRIMARY 112 +#define TK3_UNIQUE 113 +#define TK3_CHECK 114 +#define TK3_REFERENCES 115 +#define TK3_ID_CONSTR 116 +#define TK3_ID_COLLATE 117 +#define TK3_ID_TAB 118 +#define TK3_INTEGER 119 +#define TK3_FLOAT 120 +#define TK3_BLOB 121 +#define TK3_AUTOINCR 122 +#define TK3_ON 123 +#define TK3_INSERT 124 +#define TK3_DELETE 125 +#define TK3_UPDATE 126 +#define TK3_ID_FK_MATCH 127 +#define TK3_SET 128 +#define TK3_DEFERRABLE 129 +#define TK3_FOREIGN 130 +#define TK3_DROP 131 +#define TK3_ID_VIEW_NEW 132 +#define TK3_ID_VIEW 133 +#define TK3_UNION 134 +#define TK3_ALL 135 +#define TK3_EXCEPT 136 +#define TK3_INTERSECT 137 +#define TK3_SELECT 138 +#define TK3_VALUES 139 +#define TK3_DISTINCT 140 +#define TK3_ID_ALIAS 141 +#define TK3_FROM 142 +#define TK3_USING 143 +#define TK3_JOIN 144 +#define TK3_ID_JOIN_OPTS 145 +#define TK3_ID_IDX 146 +#define TK3_ORDER 147 +#define TK3_GROUP 148 +#define TK3_HAVING 149 +#define TK3_LIMIT 150 +#define TK3_WHERE 151 +#define TK3_ID_COL 152 +#define TK3_INTO 153 +#define TK3_VARIABLE 154 +#define TK3_CASE 155 +#define TK3_ID_FN 156 +#define TK3_ID_ERR_MSG 157 +#define TK3_WHEN 158 +#define TK3_THEN 159 +#define TK3_ELSE 160 +#define TK3_INDEX 161 +#define TK3_ID_IDX_NEW 162 +#define TK3_ID_PRAGMA 163 +#define TK3_ID_TRIG_NEW 164 +#define TK3_ID_TRIG 165 +#define TK3_ALTER 166 +#define TK3_ADD 167 diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.y b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.y new file mode 100644 index 0000000..31a66a2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/sqlite3_parse.y @@ -0,0 +1,2406 @@ +%token_prefix TK3_ +%token_type {Token*} +%default_type {Token*} +%extra_argument {ParserContext* parserContext} +%name sqlite3_parse +%start_symbol input + +%syntax_error { + UNUSED_PARAMETER(yymajor); + parserContext->error(TOKEN, QObject::tr("Syntax error")); + //qDebug() << "near " << TOKEN->toString() << ": syntax error"; +} + +%stack_overflow { + UNUSED_PARAMETER(yypMinor); + parserContext->error(QObject::tr("Parser stack overflow")); +} + +%include { +#include "token.h" +#include "parsercontext.h" +#include "parser_helper_stubs.h" +#include "common/utils_sql.h" +#include "parser/ast/sqlitealtertable.h" +#include "parser/ast/sqliteanalyze.h" +#include "parser/ast/sqliteattach.h" +#include "parser/ast/sqlitebegintrans.h" +#include "parser/ast/sqlitecommittrans.h" +#include "parser/ast/sqlitecopy.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqlitecreatevirtualtable.h" +#include "parser/ast/sqlitedelete.h" +#include "parser/ast/sqlitedetach.h" +#include "parser/ast/sqlitedropindex.h" +#include "parser/ast/sqlitedroptable.h" +#include "parser/ast/sqlitedroptrigger.h" +#include "parser/ast/sqlitedropview.h" +#include "parser/ast/sqliteemptyquery.h" +#include "parser/ast/sqliteinsert.h" +#include "parser/ast/sqlitepragma.h" +#include "parser/ast/sqlitereindex.h" +#include "parser/ast/sqliterelease.h" +#include "parser/ast/sqliterollback.h" +#include "parser/ast/sqlitesavepoint.h" +#include "parser/ast/sqliteselect.h" +#include "parser/ast/sqliteupdate.h" +#include "parser/ast/sqlitevacuum.h" +#include "parser/ast/sqliteexpr.h" +#include "parser/ast/sqlitecolumntype.h" +#include "parser/ast/sqliteconflictalgo.h" +#include "parser/ast/sqlitesortorder.h" +#include "parser/ast/sqliteindexedcolumn.h" +#include "parser/ast/sqliteforeignkey.h" +#include "parser/ast/sqlitewith.h" +#include <QObject> +#include <QDebug> + +#define assert(X) Q_ASSERT(X) +#define UNUSED_PARAMETER(X) (void)(X) +#define DONT_INHERIT_TOKENS(X) noTokenInheritanceFields << X +} + +// These are extra tokens used by the lexer but never seen by the +// parser. We put them in a rule so that the parser generator will +// add them to the parse.h output file. + +%nonassoc ILLEGAL COMMENT SPACE. + +// The following directive causes tokens ABORT, AFTER, ASC, etc. to +// fallback to ID if they will not parse as their original value. +// This obviates the need for the "id" nonterminal. +// Those keywords: EXCEPT INTERSECT UNION +// are allowed for fallback if compound selects are disabled, +// which is not this case. +%fallback ID + ABORT ACTION AFTER ANALYZE ASC ATTACH BEFORE BEGIN BY CASCADE CAST COLUMNKW + CONFLICT DATABASE DEFERRED DESC DETACH EACH END EXCLUSIVE EXPLAIN FAIL FOR + IGNORE IMMEDIATE INDEXED INITIALLY INSTEAD LIKE_KW MATCH NO PLAN + QUERY KEY OF OFFSET PRAGMA RAISE RECURSIVE RELEASE REPLACE RESTRICT ROW ROLLBACK + SAVEPOINT TEMP TRIGGER VACUUM VIEW VIRTUAL WITH WITHOUT + REINDEX RENAME CTIME_KW IF + . +%wildcard ANY. + +// Define operator precedence early so that this is the first occurance +// of the operator tokens in the grammer. Keeping the operators together +// causes them to be assigned integer values that are close together, +// which keeps parser tables smaller. +// +// The token values assigned to these symbols is determined by the order +// in which lemon first sees them. It must be the case that ISNULL/NOTNULL, +// NE/EQ, GT/LE, and GE/LT are separated by only a single value. See +// the sqlite3ExprIfFalse() routine for additional information on this +// constraint. +%left OR. +%left AND. +%right NOT. +%left IS MATCH LIKE_KW BETWEEN IN ISNULL NOTNULL NE EQ. +%left GT LE LT GE. +%right ESCAPE. +%left BITAND BITOR LSHIFT RSHIFT. +%left PLUS MINUS. +%left STAR SLASH REM. +%left CONCAT. +%left COLLATE. +%right BITNOT. + +// Input is a single SQL command +%type cmd {SqliteQuery*} +%destructor cmd {delete $$;} + +input ::= cmdlist. + +cmdlist ::= cmdlist ecmd(C). {parserContext->addQuery(C); DONT_INHERIT_TOKENS("cmdlist");} +cmdlist ::= ecmd(C). {parserContext->addQuery(C);} + +%type ecmd {SqliteQuery*} +%destructor ecmd {delete $$;} +ecmd(X) ::= SEMI. {X = new SqliteEmptyQuery();} +ecmd(X) ::= explain(E) cmdx(C) SEMI. { + X = C; + X->explain = E->explain; + X->queryPlan = E->queryPlan; + delete E; + objectForTokens = X; + } + +%type explain {ParserStubExplain*} +%destructor explain {delete $$;} +explain(X) ::= . {X = new ParserStubExplain(false, false);} +explain(X) ::= EXPLAIN. {X = new ParserStubExplain(true, false);} +explain(X) ::= EXPLAIN QUERY PLAN. {X = new ParserStubExplain(true, true);} + +%type cmdx {SqliteQuery*} +%destructor cmdx {delete $$;} +cmdx(X) ::= cmd(C). {X = C;} + +///////////////////// Begin and end transaction. //////////////////////////// + +cmd(X) ::= BEGIN transtype(TT) + trans_opt(TO). { + X = new SqliteBeginTrans( + TT->type, + TO->transactionKw, + TO->name + ); + delete TO; + delete TT; + objectForTokens = X; + } + +%type trans_opt {ParserStubTransDetails*} +%destructor trans_opt {delete $$;} +trans_opt(X) ::= . {X = new ParserStubTransDetails();} +trans_opt(X) ::= TRANSACTION. { + X = new ParserStubTransDetails(); + X->transactionKw = true; + } +trans_opt(X) ::= TRANSACTION nm(N). { + X = new ParserStubTransDetails(); + X->transactionKw = true; + X->name = *(N); + delete N; + } +trans_opt ::= TRANSACTION ID_TRANS. {} + +%type transtype {ParserStubTransDetails*} +%destructor transtype {delete $$;} +transtype(X) ::= . {X = new ParserStubTransDetails();} +transtype(X) ::= DEFERRED. { + X = new ParserStubTransDetails(); + X->type = SqliteBeginTrans::Type::DEFERRED; + } +transtype(X) ::= IMMEDIATE. { + X = new ParserStubTransDetails(); + X->type = SqliteBeginTrans::Type::IMMEDIATE; + } +transtype(X) ::= EXCLUSIVE. { + X = new ParserStubTransDetails(); + X->type = SqliteBeginTrans::Type::EXCLUSIVE; + } +cmd(X) ::= COMMIT trans_opt(T). { + X = new SqliteCommitTrans( + T->transactionKw, + T->name, + false + ); + delete T; + objectForTokens = X; + } +cmd(X) ::= END trans_opt(T). { + X = new SqliteCommitTrans( + T->transactionKw, + T->name, + true + ); + delete T; + objectForTokens = X; + } +cmd(X) ::= ROLLBACK trans_opt(T). { + X = new SqliteRollback( + T->transactionKw, + T->name + ); + delete T; + objectForTokens = X; + } + +%type savepoint_opt {bool*} +%destructor savepoint_opt {delete $$;} +savepoint_opt(X) ::= SAVEPOINT. {X = new bool(true);} +savepoint_opt(X) ::= . {X = new bool(false);} + +cmd(X) ::= SAVEPOINT nm(N). { + X = new SqliteSavepoint(*(N)); + delete N; + objectForTokens = X; + } +cmd(X) ::= RELEASE savepoint_opt(S) nm(N). { + X = new SqliteRelease(*(S), *(N)); + delete N; + objectForTokens = X; + } +cmd(X) ::= ROLLBACK trans_opt(T) TO + savepoint_opt(S) nm(N). { + X = new SqliteRollback( + T->transactionKw, + *(S), + *(N) + ); + delete S; + delete T; + objectForTokens = X; + } +cmd ::= SAVEPOINT ID_TRANS. {} +cmd ::= RELEASE savepoint_opt ID_TRANS. {} +cmd ::= ROLLBACK trans_opt TO savepoint_opt + ID_TRANS. {} + +///////////////////// The CREATE TABLE statement //////////////////////////// + +cmd(X) ::= CREATE temp(T) TABLE + ifnotexists(E) fullname(N) + LP columnlist(CL) + conslist_opt(CS) RP + table_options(F). { + X = new SqliteCreateTable( + *(T), + *(E), + N->name1, + N->name2, + *(CL), + *(CS), + *(F) + ); + delete E; + delete T; + delete CL; + delete CS; + delete N; + delete F; + objectForTokens = X; + } +cmd(X) ::= CREATE temp(T) TABLE + ifnotexists(E) fullname(N) + AS select(S). { + X = new SqliteCreateTable( + *(T), + *(E), + N->name1, + N->name2, + S + ); + delete E; + delete T; + delete N; + objectForTokens = X; + } +cmd ::= CREATE temp TABLE ifnotexists + nm DOT ID_TAB_NEW. {} +cmd ::= CREATE temp TABLE ifnotexists + ID_DB|ID_TAB_NEW. {} + +%type table_options {QString*} +%destructor table_options {delete $$;} +table_options(X) ::= . {X = new QString();} +table_options(X) ::= WITHOUT nm(N). { + if (N->toLower() != "rowid") + parserContext->errorAtToken(QString("Invalid table option: %1").arg(*(N))); + + X = N; + } +table_options ::= WITHOUT CTX_ROWID_KW. {} + +%type ifnotexists {bool*} +%destructor ifnotexists {delete $$;} +ifnotexists(X) ::= . {X = new bool(false);} +ifnotexists(X) ::= IF NOT EXISTS. {X = new bool(true);} + +%type temp {int*} +%destructor temp {delete $$;} +temp(X) ::= TEMP(T). {X = new int( (T->value.length() > 4) ? 2 : 1 );} +temp(X) ::= . {X = new int(0);} + +%type columnlist {ParserCreateTableColumnList*} +%destructor columnlist {delete $$;} +columnlist(X) ::= columnlist(L) + COMMA column(C). { + L->append(C); + X = L; + DONT_INHERIT_TOKENS("columnlist"); + } +columnlist(X) ::= column(C). { + X = new ParserCreateTableColumnList(); + X->append(C); + } + +// A "column" is a complete description of a single column in a +// CREATE TABLE statement. This includes the column name, its +// datatype, and other keywords such as PRIMARY KEY, UNIQUE, REFERENCES, +// NOT NULL and so forth. + +%type column {SqliteCreateTable::Column*} +%destructor column {delete $$;} +column(X) ::= columnid(C) type(T) + carglist(L). { + X = new SqliteCreateTable::Column(*(C), T, *(L)); + delete C; + delete L; + objectForTokens = X; + } + +%type columnid {QString*} +%destructor columnid {delete $$;} +columnid(X) ::= nm(N). {X = N;} +columnid ::= ID_COL_NEW. {} + + +// An IDENTIFIER can be a generic identifier, or one of several +// keywords. Any non-standard keyword can also be an identifier. +%type id {QString*} +%destructor id {delete $$;} +id(X) ::= ID(T). { + X = new QString( + stripObjName( + T->value, + parserContext->dialect + ) + ); + } + +// Why would INDEXED be defined individually like this? I don't know. +// It was like this in the original SQLite grammar, but it doesn't +// make any sense, since we have a fallback mechanism for such things. +// Anyway, this makes "INDEXED" appear in weird places of completion +// suggestions, so I remove it for now. Will see how it works. +// id(X) ::= INDEXED(T). {X = new QString(T->value);} + +// And "ids" is an identifer-or-string. +%type ids {QString*} +%destructor ids {delete $$;} +ids(X) ::= ID|STRING(T). {X = new QString(T->value);} + +// The name of a column or table can be any of the following: +%type nm {QString*} +%destructor nm {delete $$;} +nm(X) ::= id(N). {X = N;} +nm(X) ::= STRING(N). {X = new QString(stripString(N->value));} +nm(X) ::= JOIN_KW(N). {X = new QString(N->value);} + +// A typetoken is really one or more tokens that form a type name such +// as can be found after the column name in a CREATE TABLE statement. +// Multiple tokens are concatenated to form the value of the typetoken. +%type type {SqliteColumnType*} +%destructor type {delete $$;} +type(X) ::= . {X = nullptr;} +type(X) ::= typetoken(T). {X = T;} + +%type typetoken {SqliteColumnType*} +%destructor typetoken {delete $$;} +typetoken(X) ::= typename(N). { + X = new SqliteColumnType(*(N)); + delete N; + objectForTokens = X; + } +typetoken(X) ::= typename(N) + LP signed(P) RP. { + X = new SqliteColumnType(*(N), *(P)); + delete N; + delete P; + objectForTokens = X; + } +typetoken(X) ::= typename(N) LP signed(P) + COMMA signed(S) RP. { + X = new SqliteColumnType(*(N), *(P), *(S)); + delete N; + delete P; + delete S; + objectForTokens = X; + } + +%type typename {QString*} +%destructor typename {delete $$;} +typename(X) ::= ids(I). {X = I;} +typename(X) ::= typename(T) ids(I). { + T->append(" " + *(I)); + delete I; + X = T; + } +typename ::= ID_COL_TYPE. {} + +%type signed {QVariant*} +%destructor signed {delete $$;} +signed(X) ::= plus_num(N). {X = N;} +signed(X) ::= minus_num(N). {X = N;} + +// "carglist" is a list of additional constraints that come after the +// column name and column type in a CREATE TABLE statement. +%type carglist {ParserCreateTableColumnConstraintList*} +%destructor carglist {delete $$;} +carglist(X) ::= carglist(L) ccons(C). { + L->append(C); + X = L; + DONT_INHERIT_TOKENS("carglist"); + } +carglist(X) ::= . {X = new ParserCreateTableColumnConstraintList();} + +%type ccons {SqliteCreateTable::Column::Constraint*} +%destructor ccons {delete $$;} +ccons(X) ::= CONSTRAINT nm(N). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefNameOnly(*(N)); + delete N; + objectForTokens = X; + } +ccons(X) ::= DEFAULT term(T). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefTerm(*(T)); + delete T; + objectForTokens = X; + } +ccons(X) ::= DEFAULT LP expr(E) RP. { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefExpr(E); + objectForTokens = X; + } +ccons(X) ::= DEFAULT PLUS term(T). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefTerm(*(T), false); + delete T; + objectForTokens = X; + } +ccons(X) ::= DEFAULT MINUS term(T). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefTerm(*(T), true); + delete T; + objectForTokens = X; + } +ccons(X) ::= DEFAULT id(I). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefId(*(I)); + delete I; + objectForTokens = X; + } +ccons(X) ::= DEFAULT CTIME_KW(K). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefCTime(K->value); + objectForTokens = X; + } + +// In addition to the type name, we also care about the primary key and +// UNIQUE constraints. +ccons(X) ::= NULL onconf(C). { + X = new SqliteCreateTable::Column::Constraint(); + X->initNull(*(C)); + delete C; + objectForTokens = X; + } +ccons(X) ::= NOT NULL onconf(C). { + X = new SqliteCreateTable::Column::Constraint(); + X->initNotNull(*(C)); + delete C; + objectForTokens = X; + } +ccons(X) ::= PRIMARY KEY sortorder(O) + onconf(C) autoinc(A). { + X = new SqliteCreateTable::Column::Constraint(); + X->initPk(*(O), *(C), *(A)); + delete O; + delete A; + delete C; + objectForTokens = X; + } +ccons(X) ::= UNIQUE onconf(C). { + X = new SqliteCreateTable::Column::Constraint(); + X->initUnique(*(C)); + delete C; + objectForTokens = X; + } +ccons(X) ::= CHECK LP expr(E) RP. { + X = new SqliteCreateTable::Column::Constraint(); + X->initCheck(E); + objectForTokens = X; + } +ccons(X) ::= REFERENCES nm(N) + idxlist_opt(I) refargs(A). { + X = new SqliteCreateTable::Column::Constraint(); + X->initFk(*(N), *(I), *(A)); + delete N; + delete A; + delete I; + objectForTokens = X; + } +ccons(X) ::= defer_subclause(D). { + X = new SqliteCreateTable::Column::Constraint(); + X->initDefer(D->initially, D->deferrable); + delete D; + objectForTokens = X; + } +ccons(X) ::= COLLATE ids(I). { + X = new SqliteCreateTable::Column::Constraint(); + X->initColl(*(I)); + delete I; + objectForTokens = X; + } + +ccons ::= CONSTRAINT ID_CONSTR. {} +ccons ::= COLLATE ID_COLLATE. {} +ccons ::= REFERENCES ID_TAB. {} +ccons(X) ::= CHECK LP RP. { + X = new SqliteCreateTable::Column::Constraint(); + X->initCheck(); + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +%type term {QVariant*} +%destructor term {delete $$;} +term(X) ::= NULL. { + X = new QVariant(); + } +term(X) ::= INTEGER(N). { + int base = 10; + if (N->value.startsWith("0x", Qt::CaseInsensitive)) + base = 16; + + X = new QVariant(N->value.toLongLong(nullptr, base)); + } +term(X) ::= FLOAT(N). { + X = new QVariant(QVariant(N->value).toDouble()); + } +term(X) ::= STRING|BLOB(S). {X = new QVariant(S->value);} + +// The optional AUTOINCREMENT keyword +%type autoinc {bool*} +%destructor autoinc {delete $$;} +autoinc(X) ::= . {X = new bool(false);} +autoinc(X) ::= AUTOINCR. {X = new bool(true);} + +// The next group of rules parses the arguments to a REFERENCES clause +// that determine if the referential integrity checking is deferred or +// or immediate and which determine what action to take if a ref-integ +// check fails. +%type refargs {ParserFkConditionList*} +%destructor refargs {delete $$;} +refargs(X) ::= . {X = new ParserFkConditionList();} +refargs(X) ::= refargs(L) refarg(A). { + L->append(A); + X = L; + DONT_INHERIT_TOKENS("refargs"); + } + +%type refarg {SqliteForeignKey::Condition*} +%destructor refarg {delete $$;} +refarg(X) ::= MATCH nm(N). { + X = new SqliteForeignKey::Condition(*(N)); + delete N; + } +refarg(X) ::= ON INSERT refact(R). {X = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::INSERT, *(R)); delete R;} +refarg(X) ::= ON DELETE refact(R). {X = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::DELETE, *(R)); delete R;} +refarg(X) ::= ON UPDATE refact(R). {X = new SqliteForeignKey::Condition(SqliteForeignKey::Condition::UPDATE, *(R)); delete R;} +refarg ::= MATCH ID_FK_MATCH. {} + +%type refact {SqliteForeignKey::Condition::Reaction*} +%destructor refact {delete $$;} +refact(X) ::= SET NULL. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::SET_NULL);} +refact(X) ::= SET DEFAULT. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::SET_DEFAULT);} +refact(X) ::= CASCADE. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::CASCADE);} +refact(X) ::= RESTRICT. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::RESTRICT);} +refact(X) ::= NO ACTION. {X = new SqliteForeignKey::Condition::Reaction(SqliteForeignKey::Condition::NO_ACTION);} + +%type defer_subclause {ParserDeferSubClause*} +%destructor defer_subclause {delete $$;} +defer_subclause(X) ::= NOT DEFERRABLE + init_deferred_pred_opt(I). { + X = new ParserDeferSubClause(SqliteDeferrable::NOT_DEFERRABLE, *(I)); + delete I; + } +defer_subclause(X) ::= DEFERRABLE + init_deferred_pred_opt(I). { + X = new ParserDeferSubClause(SqliteDeferrable::DEFERRABLE, *(I)); + delete I; + } + +%type init_deferred_pred_opt {SqliteInitially*} +%destructor init_deferred_pred_opt {delete $$;} +init_deferred_pred_opt(X) ::= . {X = new SqliteInitially(SqliteInitially::null);} +init_deferred_pred_opt(X) ::= INITIALLY + DEFERRED. {X = new SqliteInitially(SqliteInitially::DEFERRED);} +init_deferred_pred_opt(X) ::= INITIALLY + IMMEDIATE. {X = new SqliteInitially(SqliteInitially::IMMEDIATE);} + +%type conslist_opt {ParserCreateTableConstraintList*} +%destructor conslist_opt {delete $$;} +conslist_opt(X) ::= . {X = new ParserCreateTableConstraintList();} +conslist_opt(X) ::= COMMA conslist(L). {X = L;} + +%type conslist {ParserCreateTableConstraintList*} +%destructor conslist {delete $$;} +conslist(X) ::= conslist(L) tconscomma(CM) + tcons(C). { + C->afterComma = *(CM); + L->append(C); + X = L; + delete CM; + DONT_INHERIT_TOKENS("conslist"); + } +conslist(X) ::= tcons(C). { + X = new ParserCreateTableConstraintList(); + X->append(C); + } + +%type tconscomma {bool*} +%destructor tconscomma {delete $$;} +tconscomma(X) ::= COMMA. {X = new bool(true);} +tconscomma(X) ::= . {X = new bool(false);} + +%type tcons {SqliteCreateTable::Constraint*} +%destructor tcons {delete $$;} +tcons(X) ::= CONSTRAINT nm(N). { + X = new SqliteCreateTable::Constraint(); + X->initNameOnly(*(N)); + delete N; + objectForTokens = X; + } +tcons(X) ::= PRIMARY KEY LP idxlist(L) + autoinc(I) RP onconf(C). { + X = new SqliteCreateTable::Constraint(); + X->initPk(*(L), *(I), *(C)); + delete I; + delete C; + delete L; + objectForTokens = X; + } +tcons(X) ::= UNIQUE LP idxlist(L) RP + onconf(C). { + X = new SqliteCreateTable::Constraint(); + X->initUnique(*(L), *(C)); + delete C; + delete L; + objectForTokens = X; + } +tcons(X) ::= CHECK LP expr(E) RP onconf(C). { + X = new SqliteCreateTable::Constraint(); + X->initCheck(E, *(C)); + objectForTokens = X; + } +tcons(X) ::= FOREIGN KEY LP idxlist(L) RP + REFERENCES nm(N) idxlist_opt(IL) + refargs(R) defer_subclause_opt(D). { + X = new SqliteCreateTable::Constraint(); + X->initFk( + *(L), + *(N), + *(IL), + *(R), + D->initially, + D->deferrable + ); + delete N; + delete R; + delete D; + delete IL; + delete L; + objectForTokens = X; + } + +tcons ::= CONSTRAINT ID_CONSTR. {} +tcons ::= FOREIGN KEY LP idxlist RP + REFERENCES ID_TAB. {} +tcons(X) ::= CHECK LP RP onconf. { + X = new SqliteCreateTable::Constraint(); + X->initCheck(); + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +%type defer_subclause_opt {ParserDeferSubClause*} +%destructor defer_subclause_opt {delete $$;} +defer_subclause_opt(X) ::= . {X = new ParserDeferSubClause(SqliteDeferrable::null, SqliteInitially::null);} +defer_subclause_opt(X) ::= + defer_subclause(D). {X = D;} + +// The following is a non-standard extension that allows us to declare the +// default behavior when there is a constraint conflict. + +%type onconf {SqliteConflictAlgo*} +%destructor onconf {delete $$;} +onconf(X) ::= . {X = new SqliteConflictAlgo(SqliteConflictAlgo::null);} +onconf(X) ::= ON CONFLICT resolvetype(R). {X = R;} + +%type orconf {SqliteConflictAlgo*} +%destructor orconf {delete $$;} +orconf(X) ::= . {X = new SqliteConflictAlgo(SqliteConflictAlgo::null);} +orconf(X) ::= OR resolvetype(R). {X = R;} + +%type resolvetype {SqliteConflictAlgo*} +%destructor resolvetype {delete $$;} +resolvetype(X) ::= raisetype(V). {X = new SqliteConflictAlgo(sqliteConflictAlgo(V->value));} +resolvetype(X) ::= IGNORE(V). {X = new SqliteConflictAlgo(sqliteConflictAlgo(V->value));} +resolvetype(X) ::= REPLACE(V). {X = new SqliteConflictAlgo(sqliteConflictAlgo(V->value));} + +////////////////////////// The DROP TABLE ///////////////////////////////////// + +cmd(X) ::= DROP TABLE ifexists(E) + fullname(N). { + X = new SqliteDropTable(*(E), N->name1, N->name2); + delete E; + delete N; + objectForTokens = X; + } + +cmd ::= DROP TABLE ifexists nm DOT + ID_TAB. {} +cmd ::= DROP TABLE ifexists ID_DB|ID_TAB. {} + +%type ifexists {bool*} +%destructor ifexists {delete $$;} +ifexists(X) ::= IF EXISTS. {X = new bool(true);} +ifexists(X) ::= . {X = new bool(false);} + +///////////////////// The CREATE VIEW statement ///////////////////////////// + +cmd(X) ::= CREATE temp(T) VIEW + ifnotexists(E) fullname(N) + AS select(S). { + X = new SqliteCreateView(*(T), *(E), N->name1, N->name2, S); + delete T; + delete E; + delete N; + objectForTokens = X; + } + +cmd ::= CREATE temp VIEW ifnotexists + nm DOT ID_VIEW_NEW. {} +cmd ::= CREATE temp VIEW ifnotexists + ID_DB|ID_VIEW_NEW. {} + +cmd(X) ::= DROP VIEW ifexists(E) + fullname(N). { + X = new SqliteDropView(*(E), N->name1, N->name2); + delete E; + delete N; + objectForTokens = X; + } + +cmd ::= DROP VIEW ifexists nm DOT ID_VIEW. {} +cmd ::= DROP VIEW ifexists ID_DB|ID_VIEW. {} + +//////////////////////// The SELECT statement ///////////////////////////////// + +cmd(X) ::= select_stmt(S). { + X = S; + objectForTokens = X; + } + +%type select_stmt {SqliteQuery*} +%destructor select_stmt {delete $$;} +select_stmt(X) ::= select(S). { + X = S; + // since it's used in trigger: + objectForTokens = X; + } + +%type select {SqliteSelect*} +%destructor select {delete $$;} +select(X) ::= with(W) selectnowith(S). { + X = S; + S->setWith(W); + objectForTokens = X; + } + +%type selectnowith {SqliteSelect*} +%destructor selectnowith {delete $$;} +selectnowith(X) ::= oneselect(S). { + X = SqliteSelect::append(S); + objectForTokens = X; + } +selectnowith(X) ::= selectnowith(S1) + multiselect_op(O) + oneselect(S2). { + X = SqliteSelect::append(S1, *(O), S2); + delete O; + objectForTokens = X; + } +selectnowith(X) ::= values(V). { + X = SqliteSelect::append(*(V)); + delete V; + objectForTokens = X; + } +selectnowith(X) ::= selectnowith(S1) + COMMA + values(V). { + X = SqliteSelect::append(S1, SqliteSelect::CompoundOperator::UNION_ALL, *(V)); + delete V; + objectForTokens = X; + } + +%type multiselect_op {SqliteSelect::CompoundOperator*} +%destructor multiselect_op {delete $$;} +multiselect_op(X) ::= UNION. {X = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::UNION);} +multiselect_op(X) ::= UNION ALL. {X = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::UNION_ALL);} +multiselect_op(X) ::= EXCEPT. {X = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::EXCEPT);} +multiselect_op(X) ::= INTERSECT. {X = new SqliteSelect::CompoundOperator(SqliteSelect::CompoundOperator::INTERSECT);} + +%type oneselect {SqliteSelect::Core*} +%destructor oneselect {delete $$;} +oneselect(X) ::= SELECT distinct(D) + selcollist(L) from(F) + where_opt(W) groupby_opt(G) + having_opt(H) orderby_opt(O) + limit_opt(LI). { + X = new SqliteSelect::Core( + *(D), + *(L), + F, + W, + *(G), + H, + *(O), + LI + ); + delete L; + delete D; + delete G; + delete O; + objectForTokens = X; + } + +%type values {ParserExprNestedList*} +%destructor values {delete $$;} +values(X) ::= VALUES LP nexprlist(E) RP. { + X = new ParserExprNestedList(); + X->append(*(E)); + delete E; + } +values(X) ::= values(L) COMMA LP + exprlist(E) RP. { + L->append(*(E)); + X = L; + delete E; + DONT_INHERIT_TOKENS("values"); + } + +%type distinct {int*} +%destructor distinct {delete $$;} +distinct(X) ::= DISTINCT. {X = new int(1);} +distinct(X) ::= ALL. {X = new int(2);} +distinct(X) ::= . {X = new int(0);} + +%type sclp {ParserResultColumnList*} +%destructor sclp {delete $$;} +sclp(X) ::= selcollist(L) COMMA. {X = L;} +sclp(X) ::= . {X = new ParserResultColumnList();} + +%type selcollist {ParserResultColumnList*} +%destructor selcollist {delete $$;} +selcollist(X) ::= sclp(L) expr(E) as(N). { + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn( + E, + N ? N->asKw : false, + N ? N->name : QString::null + ); + + L->append(obj); + X = L; + delete N; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } +selcollist(X) ::= sclp(L) STAR. { + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn(true); + + L->append(obj); + X = L; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } +selcollist(X) ::= sclp(L) nm(N) DOT STAR. { + SqliteSelect::Core::ResultColumn* obj = + new SqliteSelect::Core::ResultColumn( + true, + *(N) + ); + L->append(obj); + X = L; + delete N; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sclp"); + } +selcollist(X) ::= sclp(L). { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = L; + } +selcollist ::= sclp ID_TAB DOT STAR. {} + +// An option "AS <id>" phrase that can follow one of the expressions that +// define the result set, or one of the tables in the FROM clause. + +%type as {ParserStubAlias*} +%destructor as {delete $$;} +as(X) ::= AS nm(N). { + X = new ParserStubAlias(*(N), true); + delete N; + } +as(X) ::= ids(N). { + X = new ParserStubAlias(*(N), false); + delete N; + } +as ::= AS ID_ALIAS. {} +as ::= ID_ALIAS. {} +as(X) ::= . {X = nullptr;} + +// A complete FROM clause. + +%type from {SqliteSelect::Core::JoinSource*} +%destructor from {delete $$;} +from(X) ::= . {X = nullptr;} +from(X) ::= FROM joinsrc(L). {X = L;} + +%type joinsrc {SqliteSelect::Core::JoinSource*} +%destructor joinsrc {delete $$;} +joinsrc(X) ::= singlesrc(S) seltablist(L). { + X = new SqliteSelect::Core::JoinSource( + S, + *(L) + ); + delete L; + objectForTokens = X; + } +joinsrc(X) ::= . { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteSelect::Core::JoinSource(); + objectForTokens = X; + } + +%type seltablist {ParserOtherSourceList*} +%destructor seltablist {delete $$;} +seltablist(X) ::= seltablist(L) joinop(O) + singlesrc(S) + joinconstr_opt(C). { + SqliteSelect::Core::JoinSourceOther* src = + new SqliteSelect::Core::JoinSourceOther(O, S, C); + + L->append(src); + X = L; + objectForTokens = src; + DONT_INHERIT_TOKENS("seltablist"); + } +seltablist(X) ::= . { + X = new ParserOtherSourceList(); + } + +%type singlesrc {SqliteSelect::Core::SingleSource*} +%destructor singlesrc {delete $$;} +singlesrc(X) ::= nm(N1) dbnm(N2) as(A) + indexed_opt(I). { + X = new SqliteSelect::Core::SingleSource( + *(N1), + *(N2), + A ? A->asKw : false, + A ? A->name : QString::null, + I ? I->notIndexedKw : false, + I ? I->indexedBy : QString::null + ); + delete N1; + delete N2; + delete A; + if (I) + delete I; + objectForTokens = X; + } +singlesrc(X) ::= LP select(S) RP as(A). { + X = new SqliteSelect::Core::SingleSource( + S, + A ? A->asKw : false, + A ? A->name : QString::null + ); + delete A; + objectForTokens = X; + } +singlesrc(X) ::= LP joinsrc(J) RP as(A). { + X = new SqliteSelect::Core::SingleSource( + J, + A ? A->asKw : false, + A ? A->name : QString::null + ); + delete A; + objectForTokens = X; + } +singlesrc(X) ::= . { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteSelect::Core::SingleSource(); + objectForTokens = X; + } +singlesrc(X) ::= nm(N) DOT. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteSelect::Core::SingleSource(); + X->database = *(N); + delete N; + objectForTokens = X; + } + +singlesrc ::= nm DOT ID_TAB. {} +singlesrc ::= ID_DB|ID_TAB. {} +singlesrc ::= nm DOT ID_VIEW. {} +singlesrc ::= ID_DB|ID_VIEW. {} + +%type joinconstr_opt {SqliteSelect::Core::JoinConstraint*} +%destructor joinconstr_opt {delete $$;} +joinconstr_opt(X) ::= ON expr(E). { + X = new SqliteSelect::Core::JoinConstraint(E); + objectForTokens = X; + } +joinconstr_opt(X) ::= USING LP + inscollist(L) RP. { + X = new SqliteSelect::Core::JoinConstraint(*(L)); + delete L; + objectForTokens = X; + } +joinconstr_opt(X) ::= . {X = nullptr;} + +%type dbnm {QString*} +%destructor dbnm {delete $$;} +dbnm(X) ::= . {X = new QString();} +dbnm(X) ::= DOT nm(N). {X = N;} + +%type fullname {ParserFullName*} +%destructor fullname {delete $$;} +fullname(X) ::= nm(N1) dbnm(N2). { + X = new ParserFullName(); + X->name1 = *(N1); + X->name2 = *(N2); + delete N1; + delete N2; + } + +%type joinop {SqliteSelect::Core::JoinOp*} +%destructor joinop {delete $$;} +joinop(X) ::= COMMA. { + X = new SqliteSelect::Core::JoinOp(true); + objectForTokens = X; + } +joinop(X) ::= JOIN. { + X = new SqliteSelect::Core::JoinOp(false); + objectForTokens = X; + } +joinop(X) ::= JOIN_KW(K) JOIN. { + X = new SqliteSelect::Core::JoinOp(K->value); + objectForTokens = X; + } +joinop(X) ::= JOIN_KW(K) nm(N) JOIN. { + X = new SqliteSelect::Core::JoinOp(K->value, *(N)); + delete N; + objectForTokens = X; + } +joinop(X) ::= JOIN_KW(K) nm(N1) nm(N2) + JOIN. { + X = new SqliteSelect::Core::JoinOp(K->value, *(N1), *(N2)); + delete N1; + delete N1; + objectForTokens = X; + } + +joinop ::= ID_JOIN_OPTS. {} + +// Note that this block abuses the Token type just a little. If there is +// no "INDEXED BY" clause, the returned token is empty (z==0 && n==0). If +// there is an INDEXED BY clause, then the token is populated as per normal, +// with z pointing to the token data and n containing the number of bytes +// in the token. +// +// If there is a "NOT INDEXED" clause, then (z==0 && n==1), which is +// normally illegal. The sqlite3SrcListIndexedBy() function +// recognizes and interprets this as a special case. +%type indexed_opt {ParserIndexedBy*} +%destructor indexed_opt {delete $$;} +indexed_opt(X) ::= . {X = nullptr;} +indexed_opt(X) ::= INDEXED BY nm(N). { + X = new ParserIndexedBy(*(N)); + delete N; + } +indexed_opt(X) ::= NOT INDEXED. {X = new ParserIndexedBy(true);} + +indexed_opt ::= INDEXED BY ID_IDX. {} + +%type orderby_opt {ParserOrderByList*} +%destructor orderby_opt {delete $$;} +orderby_opt(X) ::= . {X = new ParserOrderByList();} +orderby_opt(X) ::= ORDER BY sortlist(L). {X = L;} + +// SQLite3 documentation says it's allowed for "COLLATE name" and expr itself handles this. +%type sortlist {ParserOrderByList*} +%destructor sortlist {delete $$;} +sortlist(X) ::= sortlist(L) COMMA expr(E) + sortorder(O). { + SqliteOrderBy* obj = new SqliteOrderBy(E, *(O)); + L->append(obj); + X = L; + delete O; + objectForTokens = obj; + DONT_INHERIT_TOKENS("sortlist"); + } +sortlist(X) ::= expr(E) sortorder(O). { + SqliteOrderBy* obj = new SqliteOrderBy(E, *(O)); + X = new ParserOrderByList(); + X->append(obj); + delete O; + objectForTokens = obj; + } + +%type sortorder {SqliteSortOrder*} +%destructor sortorder {delete $$;} +sortorder(X) ::= ASC. {X = new SqliteSortOrder(SqliteSortOrder::ASC);} +sortorder(X) ::= DESC. {X = new SqliteSortOrder(SqliteSortOrder::DESC);} +sortorder(X) ::= . {X = new SqliteSortOrder(SqliteSortOrder::null);} + +%type groupby_opt {ParserExprList*} +%destructor groupby_opt {delete $$;} +groupby_opt(X) ::= . {X = new ParserExprList();} +groupby_opt(X) ::= GROUP BY nexprlist(L). {X = L;} +groupby_opt(X) ::= GROUP BY. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new ParserExprList(); + } + +%type having_opt {SqliteExpr*} +%destructor having_opt {delete $$;} +having_opt(X) ::= . {X = nullptr;} +having_opt(X) ::= HAVING expr(E). {X = E;} + +%type limit_opt {SqliteLimit*} +%destructor limit_opt {delete $$;} +limit_opt(X) ::= . {X = nullptr;} +limit_opt(X) ::= LIMIT expr(E). { + X = new SqliteLimit(E); + objectForTokens = X; + } +limit_opt(X) ::= LIMIT expr(E1) OFFSET + expr(E2). { + X = new SqliteLimit(E1, E2, true); + objectForTokens = X; + } +limit_opt(X) ::= LIMIT expr(E1) COMMA + expr(E2). { + X = new SqliteLimit(E1, E2, false); + objectForTokens = X; + } + +/////////////////////////// The DELETE statement ///////////////////////////// + +//%ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT +//cmd ::= DELETE FROM fullname indexed_opt where_opt +// orderby_opt(O) limit_opt. +//%endif +//%ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT +cmd(X) ::= delete_stmt(S). { + X = S; + objectForTokens = X; + } + +%type delete_stmt {SqliteQuery*} +%destructor delete_stmt {delete $$;} +delete_stmt(X) ::= with(WI) DELETE FROM + fullname(N) + indexed_opt(I) + where_opt(W). { + if (I) + { + if (!I->indexedBy.isNull()) + { + X = new SqliteDelete( + N->name1, + N->name2, + I->indexedBy, + W, + WI + ); + } + else + { + X = new SqliteDelete( + N->name1, + N->name2, + I->notIndexedKw, + W, + WI + ); + } + delete I; + } + else + { + X = new SqliteDelete( + N->name1, + N->name2, + false, + W, + WI + ); + } + delete N; + // since it's used in trigger: + objectForTokens = X; + } +//%endif + +delete_stmt(X) ::= with(W) DELETE FROM. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteDelete* q = new SqliteDelete(); + q->with = W; + X = q; + objectForTokens = X; + } +delete_stmt(X) ::= with(W) DELETE FROM + nm(N) DOT. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteDelete* q = new SqliteDelete(); + q->with = W; + q->database = *(N); + X = q; + objectForTokens = X; + delete N; + } +delete_stmt ::= with DELETE FROM + nm DOT ID_TAB. {} +delete_stmt ::= with DELETE FROM + ID_DB|ID_TAB. {} + +%type where_opt {SqliteExpr*} +%destructor where_opt {delete $$;} +where_opt(X) ::= . {X = nullptr;} +where_opt(X) ::= WHERE expr(E). {X = E;} +where_opt(X) ::= WHERE. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteExpr(); + } + +////////////////////////// The UPDATE command //////////////////////////////// + +//%ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT +///cmd ::= UPDATE orconf fullname indexed_opt SET setlist where_opt orderby_opt(O) limit_opt. +//%endif +//%ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT +cmd(X) ::= update_stmt(S). { + X = S; + objectForTokens = X; + } + +%type update_stmt {SqliteQuery*} +%destructor update_stmt {delete $$;} +update_stmt(X) ::= with(WI) UPDATE orconf(C) + fullname(N) indexed_opt(I) SET + setlist(L) where_opt(W). { + X = new SqliteUpdate( + *(C), + N->name1, + N->name2, + I ? I->notIndexedKw : false, + I ? I->indexedBy : QString::null, + *(L), + W, + WI + ); + delete C; + delete N; + delete L; + if (I) + delete I; + // since it's used in trigger: + objectForTokens = X; + } +//%endif + +update_stmt(X) ::= with(WI) UPDATE + orconf(C). { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteUpdate* q = new SqliteUpdate(); + q->with = WI; + X = q; + objectForTokens = X; + delete C; + } +update_stmt(X) ::= with(WI) UPDATE + orconf(C) nm(N) DOT. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteUpdate* q = new SqliteUpdate(); + q->with = WI; + q->database = *(N); + X = q; + objectForTokens = X; + delete C; + delete N; + } +update_stmt ::= with UPDATE orconf nm DOT + ID_TAB. {} +update_stmt ::= with UPDATE orconf + ID_DB|ID_TAB. {} + +%type setlist {ParserSetValueList*} +%destructor setlist {delete $$;} +setlist(X) ::= setlist(L) COMMA nm(N) EQ + expr(E). { + L->append(ParserSetValue(*(N), E)); + X = L; + delete N; + DONT_INHERIT_TOKENS("setlist"); + } +setlist(X) ::= nm(N) EQ expr(E). { + X = new ParserSetValueList(); + X->append(ParserSetValue(*(N), E)); + delete N; + } +setlist(X) ::= . { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new ParserSetValueList(); + } +setlist(X) ::= setlist(L) COMMA. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = L; + } + +setlist ::= setlist COMMA ID_COL. {} +setlist ::= ID_COL. {} + +////////////////////////// The INSERT command ///////////////////////////////// + +cmd(X) ::= insert_stmt(S). { + X = S; + objectForTokens = X; + } + +%type insert_stmt {SqliteQuery*} +%destructor insert_stmt {delete $$;} + +insert_stmt(X) ::= with(W) insert_cmd(C) + INTO fullname(N) + inscollist_opt(I) select(S). { + X = new SqliteInsert( + C->replace, + C->orConflict, + N->name1, + N->name2, + *(I), + S, + W + ); + delete N; + delete C; + delete I; + // since it's used in trigger: + objectForTokens = X; + } +insert_stmt(X) ::= with(W) insert_cmd(C) + INTO fullname(N) + inscollist_opt(I) DEFAULT + VALUES. { + X = new SqliteInsert( + C->replace, + C->orConflict, + N->name1, + N->name2, + *(I), + W + ); + delete N; + delete C; + delete I; + // since it's used in trigger: + objectForTokens = X; + } + +insert_stmt(X) ::= with(W) insert_cmd(C) + INTO. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteInsert* q = new SqliteInsert(); + q->replaceKw = C->replace; + q->onConflict = C->orConflict; + q->with = W; + X = q; + objectForTokens = X; + delete C; + } +insert_stmt(X) ::= with(W) insert_cmd(C) + INTO nm(N) DOT. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + SqliteInsert* q = new SqliteInsert(); + q->replaceKw = C->replace; + q->onConflict = C->orConflict; + q->with = W; + q->database = *(N); + X = q; + objectForTokens = X; + delete C; + delete N; + } +insert_stmt ::= with insert_cmd INTO + ID_DB|ID_TAB. {} +insert_stmt ::= with insert_cmd INTO + nm DOT ID_TAB. {} + +%type insert_cmd {ParserStubInsertOrReplace*} +%destructor insert_cmd {delete $$;} +insert_cmd(X) ::= INSERT orconf(C). { + X = new ParserStubInsertOrReplace(false, *(C)); + delete C; + } +insert_cmd(X) ::= REPLACE. {X = new ParserStubInsertOrReplace(true);} + +%type inscollist_opt {ParserStringList*} +%destructor inscollist_opt {delete $$;} +inscollist_opt(X) ::= . {X = new ParserStringList();} +inscollist_opt(X) ::= LP inscollist(L) RP. {X = L;} + +%type inscollist {ParserStringList*} +%destructor inscollist {delete $$;} +inscollist(X) ::= inscollist(L) COMMA + nm(N). { + L->append(*(N)); + X = L; + delete N; + DONT_INHERIT_TOKENS("inscollist"); + } +inscollist(X) ::= nm(N). { + X = new ParserStringList(); + X->append(*(N)); + delete N; + } +inscollist(X) ::= . { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new ParserStringList(); + } + +inscollist ::= inscollist COMMA ID_COL. {} +inscollist ::= ID_COL. {} + +/////////////////////////// Expression Processing ///////////////////////////// + +%type exprx {SqliteExpr*} +%destructor exprx {delete $$;} +exprx(X) ::= term(T). { + X = new SqliteExpr(); + X->initLiteral(*(T)); + delete T; + objectForTokens = X; + } +exprx(X) ::= CTIME_KW(K). { + X = new SqliteExpr(); + X->initCTime(K->value); + objectForTokens = X; + } +exprx(X) ::= LP expr(E) RP. { + X = new SqliteExpr(); + X->initSubExpr(E); + objectForTokens = X; + } +exprx(X) ::= id(N). { + X = new SqliteExpr(); + X->initId(*(N)); + delete N; + objectForTokens = X; + } +exprx(X) ::= JOIN_KW(N). { + X = new SqliteExpr(); + X->initId(N->value); + objectForTokens = X; + } +exprx(X) ::= nm(N1) DOT nm(N2). { + X = new SqliteExpr(); + X->initId(*(N1), *(N2)); + delete N1; + delete N2; + objectForTokens = X; + } +exprx(X) ::= nm(N1) DOT nm(N2) DOT nm(N3). { + X = new SqliteExpr(); + X->initId(*(N1), *(N2), *(N3)); + delete N1; + delete N2; + delete N3; + objectForTokens = X; + } +exprx(X) ::= VARIABLE(V). { + X = new SqliteExpr(); + X->initBindParam(V->value); + objectForTokens = X; + } +exprx(X) ::= expr(E) COLLATE ids(I). { + X = new SqliteExpr(); + X->initCollate(E, *(I)); + delete I; + objectForTokens = X; + } +exprx(X) ::= CAST LP expr(E) AS typetoken(T) + RP. { + X = new SqliteExpr(); + X->initCast(E, T); + objectForTokens = X; + } +exprx(X) ::= ID(I) LP distinct(D) + exprlist(L) RP. { + X = new SqliteExpr(); + X->initFunction(I->value, *(D), *(L)); + delete D; + delete L; + objectForTokens = X; + } +exprx(X) ::= ID(I) LP STAR RP. { + X = new SqliteExpr(); + X->initFunction(I->value, true); + objectForTokens = X; + } +exprx(X) ::= expr(E1) AND(O) expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) OR(O) expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) LT|GT|GE|LE(O) + expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) EQ|NE(O) expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) + BITAND|BITOR|LSHIFT|RSHIFT(O) + expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) PLUS|MINUS(O) + expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) STAR|SLASH|REM(O) + expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) CONCAT(O) expr(E2). { + X = new SqliteExpr(); + X->initBinOp(E1, O->value, E2); + objectForTokens = X; + } +exprx(X) ::= expr(E1) not_opt(N) likeop(L) + expr(E2). [LIKE_KW] { + X = new SqliteExpr(); + X->initLike(E1, *(N), *(L), E2); + delete N; + delete L; + objectForTokens = X; + } +exprx(X) ::= expr(E1) not_opt(N) likeop(L) + expr(E2) ESCAPE + expr(E3). [LIKE_KW] { + X = new SqliteExpr(); + X->initLike(E1, *(N), *(L), E2, E3); + delete N; + delete L; + objectForTokens = X; + } +exprx(X) ::= expr(E) ISNULL|NOTNULL(N). { + X = new SqliteExpr(); + X->initNull(E, N->value); + objectForTokens = X; + } +exprx(X) ::= expr(E) NOT NULL. { + X = new SqliteExpr(); + X->initNull(E, "NOT NULL"); + objectForTokens = X; + } +exprx(X) ::= expr(E1) IS not_opt(N) + expr(E2). { + X = new SqliteExpr(); + X->initIs(E1, *(N), E2); + delete N; + objectForTokens = X; + } +exprx(X) ::= NOT(O) expr(E). { + X = new SqliteExpr(); + X->initUnaryOp(E, O->value); + } +exprx(X) ::= BITNOT(O) expr(E). { + X = new SqliteExpr(); + X->initUnaryOp(E, O->value); + objectForTokens = X; + } +exprx(X) ::= MINUS(O) expr(E). [BITNOT] { + X = new SqliteExpr(); + X->initUnaryOp(E, O->value); + objectForTokens = X; + } +exprx(X) ::= PLUS(O) expr(E). [BITNOT] { + X = new SqliteExpr(); + X->initUnaryOp(E, O->value); + objectForTokens = X; + } +exprx(X) ::= expr(E1) not_opt(N) BETWEEN + expr(E2) AND + expr(E3). [BETWEEN] { + X = new SqliteExpr(); + X->initBetween(E1, *(N), E2, E3); + delete N; + objectForTokens = X; + } +exprx(X) ::= expr(E) not_opt(N) IN LP + exprlist(L) RP. [IN] { + X = new SqliteExpr(); + X->initIn(E, *(N), *(L)); + delete N; + delete L; + objectForTokens = X; + } +exprx(X) ::= LP select(S) RP. { + X = new SqliteExpr(); + X->initSubSelect(S); + objectForTokens = X; + } +exprx(X) ::= expr(E) not_opt(N) IN LP + select(S) RP. [IN] { + X = new SqliteExpr(); + X->initIn(E, *(N), S); + delete N; + objectForTokens = X; + } +exprx(X) ::= expr(E) not_opt(N) IN nm(N1) + dbnm(N2). [IN] { + X = new SqliteExpr(); + X->initIn(E, N, *(N1), *(N2)); + delete N; + delete N1; + objectForTokens = X; + } +exprx(X) ::= EXISTS LP select(S) RP. { + X = new SqliteExpr(); + X->initExists(S); + objectForTokens = X; + } +exprx(X) ::= CASE case_operand(O) + case_exprlist(L) + case_else(E) END. { + X = new SqliteExpr(); + X->initCase(O, *(L), E); + delete L; + objectForTokens = X; + } + +exprx(X) ::= RAISE LP IGNORE(R) RP. { + X = new SqliteExpr(); + X->initRaise(R->value); + objectForTokens = X; + } +exprx(X) ::= RAISE LP raisetype(R) COMMA + nm(N) RP. { + X = new SqliteExpr(); + X->initRaise(R->value, *(N)); + delete N; + objectForTokens = X; + } +exprx(X) ::= nm(N1) DOT. { + X = new SqliteExpr(); + X->initId(*(N1), QString::null, QString::null); + delete N1; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } +exprx(X) ::= nm(N1) DOT nm(N2) DOT. { + X = new SqliteExpr(); + X->initId(*(N1), *(N2), QString::null); + delete N1; + delete N2; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } +exprx(X) ::= expr(E1) not_opt(N) BETWEEN + expr(E2). [BETWEEN] { + X = new SqliteExpr(); + delete N; + delete E1; + delete E2; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } +exprx(X) ::= CASE case_operand(O) + case_exprlist(L) + case_else(E). { + X = new SqliteExpr(); + delete L; + delete O; + delete E; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } +exprx(X) ::= expr(E) not_opt(N) IN LP + exprlist(L). [IN] { + X = new SqliteExpr(); + delete N; + delete L; + delete E; + objectForTokens = X; + parserContext->minorErrorBeforeNextToken("Syntax error"); + } + +exprx ::= expr not_opt IN ID_DB. [IN] {} +exprx ::= expr not_opt IN nm DOT + ID_TAB. [IN] {} +exprx ::= ID_DB|ID_TAB|ID_COL|ID_FN. {} +exprx ::= nm DOT ID_TAB|ID_COL. {} +exprx ::= nm DOT nm DOT ID_COL. {} +exprx ::= expr COLLATE ID_COLLATE. {} +exprx ::= RAISE LP raisetype COMMA + ID_ERR_MSG RP. {} + +%type expr {SqliteExpr*} +%destructor expr {delete $$;} +expr(X) ::= exprx(E). {X = E;} +expr(X) ::= . { + X = new SqliteExpr(); + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +%type not_opt {bool*} +%destructor not_opt {delete $$;} +not_opt(X) ::= . {X = new bool(false);} +not_opt(X) ::= NOT. {X = new bool(true);} + +%type likeop {SqliteExpr::LikeOp*} +%destructor likeop {delete $$;} +likeop(X) ::= LIKE_KW|MATCH(T). {X = new SqliteExpr::LikeOp(SqliteExpr::likeOp(T->value));} + +%type case_exprlist {ParserExprList*} +%destructor case_exprlist {delete $$;} +case_exprlist(X) ::= case_exprlist(L) WHEN + expr(E1) THEN expr(E2). { + L->append(E1); + L->append(E2); + X = L; + } +case_exprlist(X) ::= WHEN expr(E1) THEN + expr(E2). { + X = new ParserExprList(); + X->append(E1); + X->append(E2); + } + +%type case_else {SqliteExpr*} +%destructor case_else {delete $$;} +case_else(X) ::= ELSE expr(E). {X = E;} +case_else(X) ::= . {X = nullptr;} + +%type case_operand {SqliteExpr*} +%destructor case_operand {delete $$;} +case_operand(X) ::= exprx(E). {X = E;} +case_operand(X) ::= . {X = nullptr;} + +%type exprlist {ParserExprList*} +%destructor exprlist {delete $$;} +exprlist(X) ::= nexprlist(L). {X = L;} +exprlist(X) ::= . {X = new ParserExprList();} + +%type nexprlist {ParserExprList*} +%destructor nexprlist {delete $$;} +nexprlist(X) ::= nexprlist(L) COMMA + expr(E). { + L->append(E); + X = L; + DONT_INHERIT_TOKENS("nexprlist"); + } +nexprlist(X) ::= exprx(E). { + X = new ParserExprList(); + X->append(E); + } + +///////////////////////////// The CREATE INDEX command /////////////////////// + +cmd(X) ::= CREATE uniqueflag(U) INDEX + ifnotexists(E) nm(N1) dbnm(N2) + ON nm(N3) LP idxlist(L) RP + where_opt(W). { + X = new SqliteCreateIndex( + *(U), + *(E), + *(N1), + *(N2), + *(N3), + *(L), + W + ); + delete E; + delete U; + delete N1; + delete N2; + delete N3; + delete L; + objectForTokens = X; + } + +cmd ::= CREATE uniqueflag INDEX ifnotexists + nm dbnm ON ID_TAB. {} +cmd ::= CREATE uniqueflag INDEX ifnotexists + nm DOT ID_IDX_NEW. {} +cmd ::= CREATE uniqueflag INDEX ifnotexists + ID_DB|ID_IDX_NEW. {} + + +%type uniqueflag {bool*} +%destructor uniqueflag {delete $$;} +uniqueflag(X) ::= UNIQUE. {X = new bool(true);} +uniqueflag(X) ::= . {X = new bool(false);} + +%type idxlist_opt {ParserIndexedColumnList*} +%destructor idxlist_opt {delete $$;} +idxlist_opt(X) ::= . {X = new ParserIndexedColumnList();} +idxlist_opt(X) ::= LP idxlist(I) RP. {X = I;} + +%type idxlist {ParserIndexedColumnList*} +%destructor idxlist {delete $$;} +idxlist(X) ::= idxlist(L) COMMA + idxlist_single(S). { + L->append(S); + X = L; + DONT_INHERIT_TOKENS("idxlist"); + } +idxlist(X) ::= idxlist_single(S). { + X = new ParserIndexedColumnList(); + X->append(S); + } + +%type idxlist_single {SqliteIndexedColumn*} +%destructor idxlist_single {delete $$;} +idxlist_single(X) ::= nm(N) collate(C) + sortorder(S). { + SqliteIndexedColumn* obj = + new SqliteIndexedColumn( + *(N), + *(C), + *(S) + ); + X = obj; + delete S; + delete N; + delete C; + objectForTokens = X; + } + +idxlist_single ::= ID_COL. {} + +%type collate {QString*} +%destructor collate {delete $$;} +collate(X) ::= . {X = new QString();} +collate(X) ::= COLLATE ids(I). {X = I;} +collate ::= COLLATE ID_COLLATE. {} + + +///////////////////////////// The DROP INDEX command ///////////////////////// + +cmd(X) ::= DROP INDEX ifexists(I) + fullname(N). { + X = new SqliteDropIndex(*(I), N->name1, N->name2); + delete I; + delete N; + objectForTokens = X; + } + +cmd ::= DROP INDEX ifexists nm DOT ID_IDX. {} +cmd ::= DROP INDEX ifexists ID_DB|ID_IDX. {} + +///////////////////////////// The VACUUM command ///////////////////////////// + +cmd(X) ::= VACUUM. { + X = new SqliteVacuum(); + objectForTokens = X; + } +cmd(X) ::= VACUUM nm(N). { + X = new SqliteVacuum(*(N)); + delete N; + objectForTokens = X; + } + +///////////////////////////// The PRAGMA command ///////////////////////////// + +cmd(X) ::= PRAGMA nm(N1) dbnm(N2). { + X = new SqlitePragma(*(N1), *(N2)); + delete N1; + delete N2; + objectForTokens = X; + } +cmd(X) ::= PRAGMA nm(N1) dbnm(N2) EQ + nmnum(V). { + X = new SqlitePragma(*(N1), *(N2), *(V), true); + delete N1; + delete N2; + delete V; + objectForTokens = X; + } +cmd(X) ::= PRAGMA nm(N1) dbnm(N2) LP + nmnum(V) RP. { + X = new SqlitePragma(*(N1), *(N2), *(V), false); + delete N1; + delete N2; + delete V; + objectForTokens = X; + } +cmd(X) ::= PRAGMA nm(N1) dbnm(N2) EQ + minus_num(V). { + X = new SqlitePragma(*(N1), *(N2), *(V), true); + delete N1; + delete N2; + delete V; + objectForTokens = X; + } +cmd(X) ::= PRAGMA nm(N1) dbnm(N2) LP + minus_num(V) RP. { + X = new SqlitePragma(*(N1), *(N2), *(V), false); + delete N1; + delete N2; + delete V; + objectForTokens = X; + } + +cmd ::= PRAGMA nm DOT ID_PRAGMA. {} +cmd ::= PRAGMA ID_DB|ID_PRAGMA. {} + +%type nmnum {QVariant*} +%destructor nmnum {delete $$;} +nmnum(X) ::= plus_num(N). {X = N;} +nmnum(X) ::= nm(N). { + X = new QVariant(*(N)); + delete N; + } +nmnum(X) ::= ON(T). {X = new QVariant(T->value);} +nmnum(X) ::= DELETE(T). {X = new QVariant(T->value);} +nmnum(X) ::= DEFAULT(T). {X = new QVariant(T->value);} + +%type plus_num {QVariant*} +%destructor plus_num {delete $$;} +plus_num(X) ::= PLUS number(N). {X = N;} +plus_num(X) ::= number(N). {X = N;} + +%type minus_num {QVariant*} +%destructor minus_num {delete $$;} +minus_num(X) ::= MINUS number(N). { + if (N->type() == QVariant::Double) + *(N) = -(N->toDouble()); + else if (N->type() == QVariant::LongLong) + *(N) = -(N->toLongLong()); + else + Q_ASSERT_X(true, "producing minus number", "QVariant is neither of Double or LongLong."); + + X = N; + } + +%type number {QVariant*} +%destructor number {delete $$;} +number(X) ::= INTEGER(N). {X = new QVariant(QVariant(N->value).toLongLong());} +number(X) ::= FLOAT(N). {X = new QVariant(QVariant(N->value).toDouble());} + +//////////////////////////// The CREATE TRIGGER command ///////////////////// + +// Sqlite grammar uses 'fullname' for table name, but it's forbidden anyway, +// because you cannot create trigger for table in different database. +// We use 'nm' instead, as it's more proper. Will see if it truns out to be a problem. +cmd(X) ::= CREATE temp(T) TRIGGER + ifnotexists(IE) nm(N1) dbnm(N2) + trigger_time(TT) trigger_event(EV) + ON nm(N) foreach_clause(FC) + when_clause(WC) BEGIN + trigger_cmd_list(CL) END. { + X = new SqliteCreateTrigger( + *(T), + *(IE), + *(N1), + *(N2), + *(N), + *(TT), + EV, + *(FC), + WC, + *(CL), + 3 + ); + delete IE; + delete T; + delete TT; + delete FC; + delete N1; + delete N; + delete N2; + delete CL; + objectForTokens = X; + } + +// Support full parsing when no BEGIN and END are present (for completion helper) +cmd(X) ::= CREATE temp(T) TRIGGER + ifnotexists(IE) nm(N1) dbnm(N2) + trigger_time(TT) trigger_event(EV) + ON nm(N) foreach_clause(FC) + when_clause(WC). { + QList<SqliteQuery *> CL; + + X = new SqliteCreateTrigger( + *(T), + *(IE), + *(N1), + *(N2), + *(N), + *(TT), + EV, + *(FC), + WC, + CL, + 3 + ); + delete IE; + delete T; + delete TT; + delete FC; + delete N1; + delete N; + delete N2; + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +// Support full parsing when no END is present (for completion helper) +cmd(X) ::= CREATE temp(T) TRIGGER + ifnotexists(IE) nm(N1) dbnm(N2) + trigger_time(TT) trigger_event(EV) + ON nm(N) foreach_clause(FC) + when_clause(WC) BEGIN + trigger_cmd_list(CL). { + X = new SqliteCreateTrigger( + *(T), + *(IE), + *(N1), + *(N2), + *(N), + *(TT), + EV, + *(FC), + WC, + *(CL), + 3 + ); + delete IE; + delete T; + delete TT; + delete FC; + delete N1; + delete N; + delete N2; + delete CL; + objectForTokens = X; + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +cmd ::= CREATE temp TRIGGER ifnotexists nm + dbnm trigger_time trigger_event + ON ID_TAB. {} +cmd ::= CREATE temp TRIGGER ifnotexists nm + DOT ID_TRIG_NEW. {} +cmd ::= CREATE temp TRIGGER ifnotexists + ID_DB|ID_TRIG_NEW. {} + +%type trigger_time {SqliteCreateTrigger::Time*} +%destructor trigger_time {delete $$;} +trigger_time(X) ::= BEFORE. {X = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::BEFORE);} +trigger_time(X) ::= AFTER. {X = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::AFTER);} +trigger_time(X) ::= INSTEAD OF. {X = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::INSTEAD_OF);} +trigger_time(X) ::= . {X = new SqliteCreateTrigger::Time(SqliteCreateTrigger::Time::null);} + +%type trigger_event {SqliteCreateTrigger::Event*} +%destructor trigger_event {delete $$;} +trigger_event(X) ::= DELETE. { + X = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::DELETE); + objectForTokens = X; + } +trigger_event(X) ::= INSERT. { + X = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::INSERT); + objectForTokens = X; + } +trigger_event(X) ::= UPDATE. { + X = new SqliteCreateTrigger::Event(SqliteCreateTrigger::Event::UPDATE); + objectForTokens = X; + } +trigger_event(X) ::= UPDATE OF + inscollist(L). { + X = new SqliteCreateTrigger::Event(*(L)); + delete L; + objectForTokens = X; + } + +%type foreach_clause {SqliteCreateTrigger::Scope*} +%destructor foreach_clause {delete $$;} +foreach_clause(X) ::= . {X = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::null);} +foreach_clause(X) ::= FOR EACH ROW. {X = new SqliteCreateTrigger::Scope(SqliteCreateTrigger::Scope::FOR_EACH_ROW);} + +%type when_clause {SqliteExpr*} +%destructor when_clause {if ($$) delete $$;} +when_clause(X) ::= . {X = nullptr;} +when_clause(X) ::= WHEN expr(E). {X = E;} + +%type trigger_cmd_list {ParserQueryList*} +%destructor trigger_cmd_list {delete $$;} +trigger_cmd_list(X) ::= trigger_cmd_list(L) + trigger_cmd(C) SEMI. { + L->append(C); + X = L; + DONT_INHERIT_TOKENS("trigger_cmd_list"); + } +trigger_cmd_list(X) ::= trigger_cmd(C) + SEMI. { + X = new ParserQueryList(); + X->append(C); + } +trigger_cmd_list(X) ::= SEMI. { + X = new ParserQueryList(); + parserContext->minorErrorAfterLastToken("Syntax error"); + } + +%type trigger_cmd {SqliteQuery*} +%destructor trigger_cmd {delete $$;} +trigger_cmd(X) ::= update_stmt(S). {X = S;} +trigger_cmd(X) ::= insert_stmt(S). {X = S;} +trigger_cmd(X) ::= delete_stmt(S). {X = S;} +trigger_cmd(X) ::= select_stmt(S). {X = S;} + +%type raisetype {Token*} +raisetype(X) ::= ROLLBACK|ABORT|FAIL(V). {X = V;} + + +//////////////////////// DROP TRIGGER statement ////////////////////////////// +cmd(X) ::= DROP TRIGGER ifexists(E) + fullname(N). { + X = new SqliteDropTrigger(*(E), N->name1, N->name2); + delete E; + delete N; + objectForTokens = X; + } + +cmd ::= DROP TRIGGER ifexists nm DOT + ID_TRIG. {} +cmd ::= DROP TRIGGER ifexists + ID_DB|ID_TRIG. {} + +//////////////////////// ATTACH DATABASE file AS name ///////////////////////// +cmd(X) ::= ATTACH database_kw_opt(D) + expr(E1) AS expr(E2) key_opt(K). { + X = new SqliteAttach(*(D), E1, E2, K); + delete D; + objectForTokens = X; + } +cmd(X) ::= DETACH database_kw_opt(D) + expr(E). { + X = new SqliteDetach(*(D), E); + delete D; + objectForTokens = X; + } + +%type key_opt {SqliteExpr*} +%destructor key_opt {if ($$) delete $$;} +key_opt(X) ::= . {X = nullptr;} +key_opt(X) ::= KEY expr(E). {X = E;} + +%type database_kw_opt {bool*} +%destructor database_kw_opt {delete $$;} +database_kw_opt(X) ::= DATABASE. {X = new bool(true);} +database_kw_opt(X) ::= . {X = new bool(false);} + +////////////////////////// REINDEX collation ////////////////////////////////// +cmd(X) ::= REINDEX. {X = new SqliteReindex();} +cmd(X) ::= REINDEX nm(N1) dbnm(N2). { + X = new SqliteReindex(*(N1), *(N2)); + delete N1; + delete N2; + objectForTokens = X; + } + +cmd ::= REINDEX ID_COLLATE. {} +cmd ::= REINDEX nm DOT ID_TAB|ID_IDX. {} +cmd ::= REINDEX ID_DB|ID_IDX|ID_TAB. {} + +/////////////////////////////////// ANALYZE /////////////////////////////////// +cmd(X) ::= ANALYZE. { + X = new SqliteAnalyze(); + objectForTokens = X; + } +cmd(X) ::= ANALYZE nm(N1) dbnm(N2). { + X = new SqliteAnalyze(*(N1), *(N2)); + delete N1; + delete N2; + objectForTokens = X; + } + +cmd ::= ANALYZE nm DOT ID_TAB|ID_IDX. {} +cmd ::= ANALYZE ID_DB|ID_IDX|ID_TAB. {} + +//////////////////////// ALTER TABLE table ... //////////////////////////////// +cmd(X) ::= ALTER TABLE fullname(FN) RENAME + TO nm(N). { + X = new SqliteAlterTable( + FN->name1, + FN->name2, + *(N) + ); + delete N; + delete FN; + objectForTokens = X; + } +cmd(X) ::= ALTER TABLE fullname(FN) ADD + kwcolumn_opt(K) column(C). { + X = new SqliteAlterTable( + FN->name1, + FN->name2, + *(K), + C + ); + delete K; + delete FN; + objectForTokens = X; + } + +cmd ::= ALTER TABLE fullname RENAME TO + ID_TAB_NEW. {} +cmd ::= ALTER TABLE nm DOT ID_TAB. {} +cmd ::= ALTER TABLE ID_DB|ID_TAB. {} + +%type kwcolumn_opt {bool*} +%destructor kwcolumn_opt {delete $$;} +kwcolumn_opt(X) ::= . {X = new bool(true);} +kwcolumn_opt(X) ::= COLUMNKW. {X = new bool(false);} + +//////////////////////// CREATE VIRTUAL TABLE ... ///////////////////////////// +cmd(X) ::= create_vtab(C). {X = C;} + +%type create_vtab {SqliteQuery*} +%destructor create_vtab {delete $$;} +create_vtab(X) ::= CREATE VIRTUAL TABLE + ifnotexists(E) nm(N1) + dbnm(N2) USING nm(N3). { + X = new SqliteCreateVirtualTable( + *(E), + *(N1), + *(N2), + *(N3) + ); + delete E; + delete N1; + delete N2; + delete N3; + objectForTokens = X; + } +create_vtab(X) ::= CREATE VIRTUAL TABLE + ifnotexists(E) nm(N1) + dbnm(N2) USING nm(N3) LP + vtabarglist(A) RP. { + X = new SqliteCreateVirtualTable( + *(E), + *(N1), + *(N2), + *(N3), + *(A) + ); + delete N1; + delete N2; + delete N3; + delete E; + delete A; + objectForTokens = X; + } + +create_vtab ::= CREATE VIRTUAL TABLE + ifnotexists nm DOT + ID_TAB_NEW. {} +create_vtab ::= CREATE VIRTUAL TABLE + ifnotexists + ID_DB|ID_TAB_NEW. {} + +%type vtabarglist {ParserStringList*} +%destructor vtabarglist {delete $$;} +vtabarglist(X) ::= vtabarg(A). { + X = new ParserStringList(); + X->append((A)->mid(1)); // mid(1) to skip the first whitespace added in vtabarg + delete A; + } +vtabarglist(X) ::= vtabarglist(L) COMMA + vtabarg(A). { + L->append((A)->mid(1)); // mid(1) to skip the first whitespace added in vtabarg + X = L; + delete A; + DONT_INHERIT_TOKENS("vtabarglist"); + } + +%type vtabarg {QString*} +%destructor vtabarg {delete $$;} +vtabarg(X) ::= . {X = new QString();} +vtabarg(X) ::= vtabarg(A) vtabargtoken(T). { + A->append(" "+ *(T)); + X = A; + delete T; + } + +%type vtabargtoken {QString*} +%destructor vtabargtoken {delete $$;} +vtabargtoken(X) ::= ANY(A). { + X = new QString(A->value); + } +vtabargtoken(X) ::= LP anylist(L) RP. { + X = new QString("("); + X->append(*(L)); + X->append(")"); + delete L; + } + +%type anylist {QString*} +%destructor anylist {delete $$;} +anylist(X) ::= . {X = new QString();} +anylist(X) ::= anylist(L1) LP anylist(L2) + RP. { + X = L1; + X->append("("); + X->append(*(L2)); + X->append(")"); + delete L2; + DONT_INHERIT_TOKENS("anylist"); + } +anylist(X) ::= anylist(L) ANY(A). { + X = L; + X->append(A->value); + DONT_INHERIT_TOKENS("anylist"); + } + +//////////////////////// COMMON TABLE EXPRESSIONS //////////////////////////// +%type with {SqliteWith*} +%type wqlist {SqliteWith*} +%destructor with {delete $$;} +%destructor wqlist {delete $$;} + +with(X) ::= . {X = nullptr;} +with(X) ::= WITH wqlist(W). { + X = W; + objectForTokens = X; + } +with(X) ::= WITH RECURSIVE wqlist(W). { + X = W; + X->recursive = true; + objectForTokens = X; + } + +wqlist(X) ::= nm(N) idxlist_opt(IL) AS + LP select(S) RP. { + X = SqliteWith::append(*(N), *(IL), S); + delete N; + delete IL; + } +wqlist(X) ::= wqlist(WL) COMMA nm(N) + idxlist_opt(IL) AS + LP select(S) RP. { + X = SqliteWith::append(WL, *(N), *(IL), S); + delete N; + delete IL; + DONT_INHERIT_TOKENS("wqlist"); + } +wqlist(X) ::= ID_TAB_NEW. { + parserContext->minorErrorBeforeNextToken("Syntax error"); + X = new SqliteWith(); + } diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/statementtokenbuilder.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/statementtokenbuilder.cpp new file mode 100644 index 0000000..4f248b0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/statementtokenbuilder.cpp @@ -0,0 +1,206 @@ +#include "statementtokenbuilder.h" +#include "parser/ast/sqlitestatement.h" +#include "common/utils_sql.h" +#include <QVariant> + +StatementTokenBuilder& StatementTokenBuilder::withKeyword(const QString& value) +{ + return with(Token::KEYWORD, value); +} + +StatementTokenBuilder& StatementTokenBuilder::withOther(const QString& value) +{ + return with(Token::OTHER, value); +} + +StatementTokenBuilder& StatementTokenBuilder::withOther(const QString& value, Dialect dialect) +{ + return withOther(wrapObjIfNeeded(value, dialect)); +} + +StatementTokenBuilder&StatementTokenBuilder::withStringPossiblyOther(const QString& value, Dialect dialect) +{ + if (value.contains("\"")) + return withOther(wrapObjIfNeeded(value, dialect)); + else + return withOther(wrapObjName(value, NameWrapper::DOUBLE_QUOTE)); +} + +StatementTokenBuilder& StatementTokenBuilder::withOtherList(const QList<QString>& value, Dialect dialect, const QString& separator) +{ + bool first = true; + foreach (const QString& str, value) + { + if (!first) + { + if (!separator.isEmpty()) + withOperator(separator); + + withSpace(); + } + withOther(str, dialect); + first = false; + } + return *this; +} + +StatementTokenBuilder& StatementTokenBuilder::withOtherList(const QList<QString>& value, const QString& separator) +{ + bool first = true; + foreach (const QString& str, value) + { + if (!first) + { + if (!separator.isEmpty()) + withOperator(separator); + + withSpace(); + } + withOther(str); + first = false; + } + return *this; +} + +StatementTokenBuilder& StatementTokenBuilder::withOperator(const QString& value) +{ + return with(Token::OPERATOR, value); +} + +StatementTokenBuilder& StatementTokenBuilder::withComment(const QString& value) +{ + return with(Token::COMMENT, value); +} + +StatementTokenBuilder& StatementTokenBuilder::withFloat(double value) +{ + return with(Token::FLOAT, QString::number(value)); +} + +StatementTokenBuilder& StatementTokenBuilder::withInteger(int value) +{ + return with(Token::INTEGER, QString::number(value)); +} + +StatementTokenBuilder& StatementTokenBuilder::withBindParam(const QString& value) +{ + return with(Token::BIND_PARAM, value); +} + +StatementTokenBuilder& StatementTokenBuilder::withParLeft() +{ + return with(Token::PAR_LEFT, "("); +} + +StatementTokenBuilder& StatementTokenBuilder::withParRight() +{ + return with(Token::PAR_RIGHT, ")"); +} + +StatementTokenBuilder& StatementTokenBuilder::withSpace() +{ + return with(Token::SPACE, " "); +} + +StatementTokenBuilder& StatementTokenBuilder::withBlob(const QString& value) +{ + return with(Token::BLOB, value); +} + +StatementTokenBuilder& StatementTokenBuilder::withString(const QString& value) +{ + return with(Token::STRING, wrapStringIfNeeded(value)); +} + +StatementTokenBuilder& StatementTokenBuilder::withConflict(SqliteConflictAlgo onConflict) +{ + if (onConflict != SqliteConflictAlgo::null) + return withSpace().withKeyword("ON").withSpace().withKeyword("CONFLICT") + .withSpace().withKeyword(sqliteConflictAlgo(onConflict)); + + return *this; +} + +StatementTokenBuilder& StatementTokenBuilder::withSortOrder(SqliteSortOrder sortOrder) +{ + if (sortOrder != SqliteSortOrder::null) + return withSpace().withKeyword(sqliteSortOrder(sortOrder)); + + return *this; +} + +StatementTokenBuilder& StatementTokenBuilder::withStatement(SqliteStatement* stmt) +{ + if (!stmt) + return *this; + + stmt->rebuildTokens(); + if (stmt->tokens.size() > 0) + { + if (tokens.size() > 0 && !tokens.last()->isWhitespace() && tokens.last()->type != Token::PAR_LEFT) + withSpace(); + + tokens += stmt->tokens; + tokens.trimRight(Token::OPERATOR, ";"); + } + return *this; +} + +StatementTokenBuilder& StatementTokenBuilder::withTokens(TokenList tokens) +{ + this->tokens += tokens; + return *this; +} + +StatementTokenBuilder& StatementTokenBuilder::withLiteralValue(const QVariant& value) +{ + if (value.isNull()) + return *this; + + bool ok; + if (value.userType() == QVariant::Double) + { + value.toDouble(&ok); + if (ok) + { + withFloat(value.toDouble()); + return *this; + } + } + + value.toInt(&ok); + if (ok) + { + withInteger(value.toInt()); + return *this; + } + + QString str = value.toString(); + if (str.startsWith("x'", Qt::CaseInsensitive) && str.endsWith("'")) + { + withBlob(str); + return *this; + } + + withString(str); + return *this; +} + +TokenList StatementTokenBuilder::build() const +{ + return tokens; +} + +void StatementTokenBuilder::clear() +{ + tokens.clear(); + currentIdx = 0; +} + +StatementTokenBuilder& StatementTokenBuilder::with(Token::Type type, const QString& value) +{ + int size = value.size(); + tokens << TokenPtr::create(type, value, currentIdx, currentIdx + size - 1); + currentIdx += size; + return *this; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/statementtokenbuilder.h b/SQLiteStudio3/coreSQLiteStudio/parser/statementtokenbuilder.h new file mode 100644 index 0000000..fcf23be --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/statementtokenbuilder.h @@ -0,0 +1,290 @@ +#ifndef STATEMENTTOKENBUILDER_H +#define STATEMENTTOKENBUILDER_H + +#include "token.h" +#include "ast/sqliteconflictalgo.h" +#include "ast/sqlitesortorder.h" +#include "dialect.h" + +class SqliteStatement; + +/** + * @brief Builder producing token list basing on certain inputs. + * + * This builder provides several methods to build list of tokens from various input values. It can produce + * token list for entire AST objects, or it can produce token list for list of names, etc. + * + * Token builder is used in SqliteStatement derived classes to rebuild SqliteStatement::tokens basing on the + * values in their class members. + * + * Typical use case: + * @code + * TokenList SqliteCreateView::rebuildTokensFromContents() + * { + * StatementTokenBuilder builder; + * + * builder.withKeyword("CREATE").withSpace(); + * if (tempKw) + * builder.withKeyword("TEMP").withSpace(); + * else if (temporaryKw) + * builder.withKeyword("TEMPORARY").withSpace(); + * + * builder.withKeyword("VIEW").withSpace(); + * if (ifNotExists) + * builder.withKeyword("IF").withSpace().withKeyword("NOT").withSpace().withKeyword("EXISTS").withSpace(); + * + * if (dialect == Dialect::Sqlite3 && !database.isNull()) + * builder.withOther(database, dialect).withOperator("."); + * + * builder.withOther(view, dialect).withSpace().withKeyword("AS").withStatement(select); + * + * return builder.build(); + * } + * @endcode + */ +class StatementTokenBuilder +{ + public: + /** + * @brief Adds keyword token. + * @param value Value of the keyword token. + * @return Reference to the builder for the further building. + * + * Keyword \p value gets converted to upper case. + */ + StatementTokenBuilder& withKeyword(const QString& value); + + /** + * @brief Adds "other" token (some object name, or other word). + * @param value Value for the token. + * @return Reference to the builder for the further building. + * + * This is used for table names, etc. The \p value is quoted just as passed. + */ + StatementTokenBuilder& withOther(const QString& value); + + /** + * @brief Adds "other" token (some object name, or other word). + * @param value Value for the token. + * @param dialect Dialect used for wrapping the value. + * @return Reference to the builder for the further building. + * + * The \p value is wrapped with the proper wrapper using wrapObjIfNeeded(). + * + * @overload + */ + StatementTokenBuilder& withOther(const QString& value, Dialect dialect); + + /** + * @brief Adds string using double-quote wrapping. + * @param value Value for the token. + * @param dialect Dialect used for wrapping the value if double-quote could not be used. + * @return Reference to the builder for the further building. + * + * The \p value is wrapped with double quote, but if it's not possible then the proper wrapper is used by wrapObjIfNeeded(). + * + * @overload + */ + StatementTokenBuilder& withStringPossiblyOther(const QString& value, Dialect dialect); + + /** + * @brief Adds list of "other" tokens. + * @param value List of values for tokens. + * @param dialect Dialect used for wrapping values. + * @param separator Optional value for separator tokens. + * @return Reference to the builder for the further building. + * + * Given the input \p value, this method produces list of tokens. Additionally it can put extra separator + * token between all produced tokens using the \p separator value. To skip separator tokens pass + * an empty string as the separator value. + */ + StatementTokenBuilder& withOtherList(const QList<QString>& value, Dialect dialect, const QString& separator = ","); + + /** + * @brief Adds list of "other" tokens. + * @param value List of values for tokens. + * @param separator Optional value for separator tokens. + * @return Reference to the builder for the further building. + * + * Works just like the other withOtherList() method, except it doesn't wrap values with wrapObjIfNeeded(). + * + * @overload + */ + StatementTokenBuilder& withOtherList(const QList<QString>& value, const QString& separator = ","); + + /** + * @brief Adds operator token. + * @param value Value of the operator (";", "+", etc). + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withOperator(const QString& value); + + /** + * @brief Adds comment token. + * @param value Comment value, including start/end characters of the comment. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withComment(const QString& value); + + /** + * @brief Adds decimal number token. + * @param value Value for the token. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withFloat(double value); + + /** + * @brief Add integer numer token. + * @param value Value for the token. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withInteger(int value); + + /** + * @brief Adds bind parameter token. + * @param value Name of the bind parameter, including ":" or "@" at the begining. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withBindParam(const QString& value); + + /** + * @brief Adds left parenthesis token (<tt>"("</tt>). + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withParLeft(); + + /** + * @brief Adds right parenthesis token (<tt>")"</tt>). + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withParRight(); + + /** + * @brief Adds a single whitespace token. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withSpace(); + + /** + * @brief Adds BLOB value token. + * @param value BLOB value for the token. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withBlob(const QString& value); + + /** + * @brief Adds string value token. + * @param value Value for the token. + * @return Reference to the builder for the further building. + * + * The string is wrapped with single quote characters if it's not wrapped yet. + */ + StatementTokenBuilder& withString(const QString& value); + + /** + * @brief Adds set of tokens represeting "ON CONFLICT" statement. + * @param onConflict Conflict resolution algorithm to build for. + * @return Reference to the builder for the further building. + * + * If algorithm is SqliteConflictAlgo::null, no tokens are added. + */ + StatementTokenBuilder& withConflict(SqliteConflictAlgo onConflict); + + /** + * @brief Adds space and <tt>"ASC"/"DESC"</tt> token. + * @param sortOrder Sort order to use. + * @return Reference to the builder for the further building. + * + * If the sort order is SqliteSortOrder::null, no tokens are added. + */ + StatementTokenBuilder& withSortOrder(SqliteSortOrder sortOrder); + + /** + * @brief Adds set of tokens representing entire statement. + * @param stmt Statement to add tokens for. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withStatement(SqliteStatement* stmt); + + /** + * @brief Adds already defined list of tokens to this builder. + * @param tokens Tokens to add. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& withTokens(TokenList tokens); + + /** + * @brief Adds literal value token (integer, decimal, string, BLOB). + * @param value Value for the token. + * @return Reference to the builder for the further building. + * + * This method tries to convert given \p value to integer, + * then to double, then it checks if the value has format <tt>X'...'</tt> + * and if if succeeded at any of those steps, then it adds appropriate + * token. If none of above succeeded, then the string token is added. + */ + StatementTokenBuilder& withLiteralValue(const QVariant& value); + + /** + * @brief Adds tokens representing list of entire statements. + * @param stmtList List of statements to add tokens for. + * @param separator Optional separator to be used for separator tokens. + * @return Reference to the builder for the further building. + * + * This method is very similar to withOtherList(), except it works + * on the entire statements. + */ + template <class T> + StatementTokenBuilder& withStatementList(QList<T*> stmtList, const QString& separator = ",") + { + bool first = true; + foreach (T* stmt, stmtList) + { + if (!first) + { + if (!separator.isEmpty()) + withOperator(separator); + + withSpace(); + } + withStatement(stmt); + first = false; + } + return *this; + } + + /** + * @brief Provides all tokens added so far as a compat token list. + * @return List of tokens built so far. + */ + TokenList build() const; + + /** + * @brief Cleans up all tokens added so far. + */ + void clear(); + + private: + /** + * @brief Adds token of given type and value. + * @param type Type of the token to add. + * @param value Value for the token to add. + * @return Reference to the builder for the further building. + */ + StatementTokenBuilder& with(Token::Type type, const QString& value); + + /** + * @brief List of tokens added so far. + */ + TokenList tokens; + + /** + * @brief Current character position index. + * + * This index is used to generate proper values for Token::start and Token::end. + * Each added token increments this index by the value length. + */ + int currentIdx = 0; +}; + +#endif // STATEMENTTOKENBUILDER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/token.cpp b/SQLiteStudio3/coreSQLiteStudio/parser/token.cpp new file mode 100644 index 0000000..5e186dd --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/token.cpp @@ -0,0 +1,621 @@ +#include "token.h" +#include "lexer.h" +#include <stdio.h> +#include <QStringList> + +Token::Token() + : lemonType(0), type(INVALID), value(QString::null), start(-1), end(-1) +{ +} + +Token::Token(int lemonType, Type type, QString value, qint64 start, qint64 end) + : lemonType(lemonType), type(type), value(value), start(start), end(end) +{} + +Token::Token(int lemonType, Type type, QChar value, qint64 start, qint64 end) + : lemonType(lemonType), type(type), value(value), start(start), end(end) +{} + +Token::Token(int lemonType, Token::Type type, QString value) + : lemonType(lemonType), type(type), value(value), start(-1), end(-1) +{ +} + +Token::Token(QString value) + : lemonType(0), type(INVALID), value(value), start(0), end(0) +{} + +Token::Token(Token::Type type, QString value) + : lemonType(0), type(type), value(value), start(0), end(0) +{ +} + +Token::Token(Token::Type type, QString value, qint64 start, qint64 end) + : lemonType(0), type(type), value(value), start(start), end(end) +{ +} + +Token::~Token() +{ +} + +QString Token::toString() +{ + return "{" + + typeToString(type) + + " " + + value + + " " + + QString::number(start) + + " " + + QString::number(end) + + "}"; +} + +const QString Token::typeToString(Token::Type type) +{ + switch (type) + { + case Token::CTX_ROWID_KW: + return "CTX_ROWID_KW"; + case Token::CTX_NEW_KW: + return "CTX_NEW_KW"; + case Token::CTX_OLD_KW: + return "CTX_OLD_KW"; + case Token::CTX_TABLE_NEW: + return "CTX_TABLE_NEW"; + case Token::CTX_INDEX_NEW: + return "CTX_INDEX_NEW"; + case Token::CTX_VIEW_NEW: + return "CTX_VIEW_NEW"; + case Token::CTX_TRIGGER_NEW: + return "CTX_TRIGGER_NEW"; + case Token::CTX_ALIAS: + return "CTX_ALIAS"; + case Token::CTX_TRANSACTION: + return "CTX_transaction"; + case Token::CTX_COLUMN_NEW: + return "CTX_COLUMN_NEW"; + case Token::CTX_COLUMN_TYPE: + return "CTX_COLUMN_TYPE"; + case Token::CTX_CONSTRAINT: + return "CTX_CONSTRAINT"; + case Token::CTX_FK_MATCH: + return "CTX_FK_MATCH"; + case Token::CTX_PRAGMA: + return "CTX_PRAGMA"; + case Token::CTX_ERROR_MESSAGE: + return "CTX_ERROR_MESSAGE"; + case Token::CTX_COLUMN: + return "CTX_COLUMN"; + case Token::CTX_TABLE: + return "CTX_TABLE"; + case Token::CTX_DATABASE: + return "CTX_DATABASE"; + case Token::CTX_FUNCTION: + return "CTX_FUNCTION"; + case Token::CTX_COLLATION: + return "CTX_COLLATION"; + case Token::CTX_INDEX: + return "CTX_INDEX"; + case Token::CTX_TRIGGER: + return "CTX_TRIGGER"; + case Token::CTX_VIEW: + return "CTX_VIEW"; + case Token::CTX_JOIN_OPTS: + return "CTX_JOIN_OPTS"; + case Token::INVALID: + return "INVALID"; + case Token::OTHER: + return "OTHER"; + case Token::STRING: + return "STRING"; + case Token::COMMENT: + return "COMMENT"; + case Token::FLOAT: + return "FLOAT"; + case Token::INTEGER: + return "INTEGER"; + case Token::BIND_PARAM: + return "BIND_PARAM"; + case Token::OPERATOR: + return "OPERATOR"; + case Token::PAR_LEFT: + return "PAR_LEFT"; + case Token::PAR_RIGHT: + return "PAR_RIGHT"; + case Token::SPACE: + return "SPACE"; + case Token::BLOB: + return "BLOB"; + case Token::KEYWORD: + return "KEYWORD"; + } + + return ""; +} + +Range Token::getRange() +{ + return Range(start, end); +} + +bool Token::isWhitespace() const +{ + return (type == SPACE || type == COMMENT); +} + +bool Token::isSeparating() const +{ + switch (type) + { + case Token::SPACE: + case Token::PAR_LEFT: + case Token::PAR_RIGHT: + case Token::OPERATOR: + return true; + default: + break; + } + return false; +} + +bool Token::isDbObjectType() const +{ + return ((type & TOKEN_TYPE_MASK_DB_OBJECT) == TOKEN_TYPE_MASK_DB_OBJECT); +} + +QString Token::typeString() const +{ + return typeToString(type); +} + +int Token::operator ==(const Token &other) +{ + return type == other.type && value == other.value && start == other.start && end == other.end; +} + +bool Token::operator <(const Token &other) const +{ + if (start == other.start) + return end < other.end; + else + return start < other.start; +} + +uint qHash(const TokenPtr& token) +{ + // This doesn't look nice, but it's good enough to satisfy a hash table. + // It's fast and quite distinguishable. + // It's rare to have two pointers with the same int type representation, + // and if that happens, there's always a comparision operator. + return (uint)reinterpret_cast<qint64>(token.data()); +} + +TokenList::TokenList() + : QList<TokenPtr>() +{ +} + +TokenList::TokenList(const QList<TokenPtr>& other) + : QList<TokenPtr>(other) +{ +} + +QString TokenList::toString() const +{ + return toStringList().join(" "); +} + +QStringList TokenList::toStringList() const +{ + QStringList strList; + TokenPtr t; + foreach (t, *this) + strList << t->toString(); + + return strList; +} + +int TokenList::indexOf(TokenPtr token) const +{ + return QList<TokenPtr>::indexOf(token); +} + +int TokenList::indexOf(Token::Type type) const +{ + int i; + findFirst(type, &i); + return i; +} + +int TokenList::indexOf(Token::Type type, const QString &value, Qt::CaseSensitivity caseSensitivity) const +{ + int i; + findFirst(type, value, caseSensitivity, &i); + return i; +} + +int TokenList::indexOf(const QString &value, Qt::CaseSensitivity caseSensitivity) const +{ + int i; + findFirst(value, caseSensitivity, &i); + return i; +} + +int TokenList::lastIndexOf(TokenPtr token) const +{ + return QList<TokenPtr>::lastIndexOf(token); +} + +int TokenList::lastIndexOf(Token::Type type) const +{ + int i; + findLast(type, &i); + return i; +} + +int TokenList::lastIndexOf(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity) const +{ + int i; + findLast(type, value, caseSensitivity, &i); + return i; +} + +int TokenList::lastIndexOf(const QString& value, Qt::CaseSensitivity caseSensitivity) const +{ + int i; + findLast(value, caseSensitivity, &i); + return i; +} + +TokenPtr TokenList::find(Token::Type type) const +{ + return findFirst(type, nullptr); +} + +TokenPtr TokenList::find(Token::Type type, const QString &value, Qt::CaseSensitivity caseSensitivity) const +{ + return findFirst(type, value, caseSensitivity, nullptr); +} + +TokenPtr TokenList::find(const QString &value, Qt::CaseSensitivity caseSensitivity) const +{ + return findFirst(value, caseSensitivity, nullptr); +} + +TokenPtr TokenList::findLast(Token::Type type) const +{ + return findLast(type, nullptr); +} + +TokenPtr TokenList::findLast(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity) const +{ + return findLast(type, value, caseSensitivity, nullptr); +} + +TokenPtr TokenList::findLast(const QString& value, Qt::CaseSensitivity caseSensitivity) const +{ + return findLast(value, caseSensitivity, nullptr); +} + +TokenPtr TokenList::atCursorPosition(quint64 cursorPosition) const +{ + foreach (TokenPtr token, *this) + { + if (token->getRange().contains(cursorPosition)) + return token; + } + return TokenPtr(); +} + +void TokenList::insert(int i, const TokenList &list) +{ + foreach (TokenPtr token, list) + QList<TokenPtr>::insert(i++, token); +} + +void TokenList::insert(int i, TokenPtr token) +{ + QList<TokenPtr>::insert(i, token); +} + +TokenList &TokenList::operator =(const QList<TokenPtr> &other) +{ + QList<TokenPtr>::operator =(other); + return *this; +} + +QString TokenList::detokenize() const +{ + return Lexer::detokenize(*this); +} + +void TokenList::replace(int startIdx, int length, const TokenList& newTokens) +{ + for (int i = 0; i < length; i++) + removeAt(startIdx); + + insert(startIdx, newTokens); +} + +void TokenList::replace(int startIdx, int length, TokenPtr newToken) +{ + for (int i = 0; i < length; i++) + removeAt(startIdx); + + insert(startIdx, newToken); +} + +void TokenList::replace(int startIdx, TokenPtr newToken) +{ + QList<TokenPtr>::replace(startIdx, newToken); +} + +void TokenList::replace(int startIdx, const TokenList& newTokens) +{ + replace(startIdx, 1, newTokens); +} + +int TokenList::replace(TokenPtr startToken, TokenPtr endToken, const TokenList& newTokens) +{ + int startIdx = indexOf(startToken); + if (startIdx < 0) + return 0; + + int endIdx = indexOf(endToken); + if (endIdx < 0) + return 0; + + replace(startIdx, endIdx - startIdx, newTokens); + return endIdx - startIdx; +} + +int TokenList::replace(TokenPtr startToken, TokenPtr endToken, TokenPtr newToken) +{ + int startIdx = indexOf(startToken); + if (startIdx < 0) + return 0; + + int endIdx = indexOf(endToken); + if (endIdx < 0) + return 0; + + replace(startIdx, endIdx - startIdx, newToken); + return endIdx - startIdx; +} + +bool TokenList::replace(TokenPtr oldToken, TokenPtr newToken) +{ + int idx = indexOf(oldToken); + if (idx < 0) + return false; + + replace(idx, newToken); + return true; +} + +bool TokenList::replace(TokenPtr oldToken, const TokenList& newTokens) +{ + int idx = indexOf(oldToken); + if (idx < 0) + return false; + + replace(idx, newTokens); + return true; +} + +bool TokenList::remove(TokenPtr startToken, TokenPtr endToken) +{ + int startIdx = indexOf(startToken); + if (startIdx < 0) + return false; + + int endIdx = indexOf(endToken); + if (endIdx < 0) + return false; + + if (endIdx < startIdx) + return false; + + for (int i = startIdx; i < endIdx; i++) + removeAt(startIdx); + + return true; +} + +bool TokenList::remove(Token::Type type) +{ + int idx = indexOf(type); + if (idx == -1) + return false; + + removeAt(idx); + return true; +} + +void TokenList::trimLeft() +{ + while (size() > 0 && first()->isWhitespace()) + removeFirst(); +} + +void TokenList::trimRight() +{ + while (size() > 0 && last()->isWhitespace()) + removeLast(); +} + +void TokenList::trim() +{ + trimLeft(); + trimRight(); +} + +void TokenList::trimLeft(Token::Type type, const QString& alsoTrim) +{ + while (size() > 0 && (first()->isWhitespace() || (first()->type == type && first()->value == alsoTrim))) + removeFirst(); +} + +void TokenList::trimRight(Token::Type type, const QString& alsoTrim) +{ + while (size() > 0 && (last()->isWhitespace() || (last()->type == type && last()->value == alsoTrim))) + removeLast(); +} + +void TokenList::trim(Token::Type type, const QString& alsoTrim) +{ + trimLeft(type, alsoTrim); + trimRight(type, alsoTrim); +} + +TokenList TokenList::filter(Token::Type type) const +{ + TokenList filtered; + foreach (TokenPtr token, *this) + if (token->type == type) + filtered << token; + + return filtered; +} + +TokenList TokenList::filterWhiteSpaces() const +{ + TokenList filtered; + foreach (TokenPtr token, *this) + if (!token->isWhitespace()) + filtered << token; + + return filtered; +} + +TokenList TokenList::mid(int pos, int length) const +{ + TokenList newList = QList<TokenPtr>::mid(pos, length); + return newList; +} + +TokenPtr TokenList::findFirst(Token::Type type, int *idx) const +{ + int i = -1; + TokenPtr token; + QListIterator<TokenPtr> it(*this); + while (it.hasNext()) + { + token = it.next(); + i++; + if (token->type == type) + { + if (idx) (*idx) = i; + return token; + } + } + if (idx) (*idx) = -1; + return TokenPtr(); +} + +TokenPtr TokenList::findFirst(Token::Type type, const QString &value, Qt::CaseSensitivity caseSensitivity, int *idx) const +{ + int i = -1; + TokenPtr token; + QListIterator<TokenPtr> it(*this); + while (it.hasNext()) + { + token = it.next(); + i++; + if (token->type != type) + continue; + + if (token->value.compare(value, caseSensitivity) == 0) + { + if (idx) (*idx) = i; + return token; + } + } + if (idx) (*idx) = -1; + return TokenPtr(); +} + +TokenPtr TokenList::findFirst(const QString &value, Qt::CaseSensitivity caseSensitivity, int *idx) const +{ + int i = -1; + TokenPtr token; + QListIterator<TokenPtr> it(*this); + while (it.hasNext()) + { + token = it.next(); + i++; + if (token->value.compare(value, caseSensitivity) == 0) + { + if (idx) (*idx) = i; + return token; + } + } + if (idx) (*idx) = -1; + return TokenPtr(); +} + + +TokenPtr TokenList::findLast(Token::Type type, int* idx) const +{ + int i = size(); + TokenPtr token; + QListIterator<TokenPtr> it(*this); + it.toBack(); + while (it.hasPrevious()) + { + token = it.previous(); + i--; + if (token->type == type) + { + if (idx) (*idx) = i; + return token; + } + } + if (idx) (*idx) = -1; + return TokenPtr(); +} + +TokenPtr TokenList::findLast(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity, int* idx) const +{ + int i = size(); + TokenPtr token; + QListIterator<TokenPtr> it(*this); + it.toBack(); + while (it.hasPrevious()) + { + token = it.previous(); + i--; + if (token->type != type) + continue; + + if (token->value.compare(value, caseSensitivity) == 0) + { + if (idx) (*idx) = i; + return token; + } + } + if (idx) (*idx) = -1; + return TokenPtr(); +} + +TokenPtr TokenList::findLast(const QString& value, Qt::CaseSensitivity caseSensitivity, int* idx) const +{ + int i = size(); + TokenPtr token; + QListIterator<TokenPtr> it(*this); + it.toBack(); + while (it.hasPrevious()) + { + token = it.previous(); + i--; + if (token->value.compare(value, caseSensitivity) == 0) + { + if (idx) (*idx) = i; + return token; + } + } + if (idx) (*idx) = -1; + return TokenPtr(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/parser/token.h b/SQLiteStudio3/coreSQLiteStudio/parser/token.h new file mode 100644 index 0000000..1090bd4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/parser/token.h @@ -0,0 +1,733 @@ +#ifndef TOKEN_H +#define TOKEN_H + +#include "common/utils.h" +#include <QString> +#include <QList> +#include <QSharedPointer> + +/** @file */ + +/** + * @def TOKEN_TYPE_MASK_DB_OBJECT + * + * Bit mask used to test Token::Type for representing any database object name, in any form. + * It's used in Token::isDbObjectType(). + */ +#define TOKEN_TYPE_MASK_DB_OBJECT 0x1000 + +struct Token; + +/** + * @brief Shared pointer to the Token. + */ +typedef QSharedPointer<Token> TokenPtr; + +/** + * @brief SQL query entity representing isolated part of the query. + * + * Tokens are generated by Lexer. Each token represents isolated part of the query, + * like a name, a number, an operator, a string, a keyword, or a comment, etc. + * + * In other words, tokenizing SQL query is splitting it into logical parts. + * + * Each token has a type, a value and position indexes of where it starts and where it ends (in the query string). + * + * Tokens are used mainly by the Parser to perform syntax analysis, but they can be also very helpful + * in other areas. They provide easy way for safe manipulation on the query string, without worrying + * about counting open or close characters of the string, etc. If the string has a single-quote used twice inside, + * this is still a regular SQL string and in that case there will be only a single string token representing it. + * + * If you're constructing Token outside of the Lemon parser, you should be interested only in 4 constructors: + * Token(), Token(QString value), Token(Type type, QString value) + * and Token(Type type, QString value, qint64 start, qint64 end). + * Rest of the constructors are to be used from the Lemon parser, as they require Lemon token ID to be provided. + * + * You will usually have the most to do with tokens when dealing with SqliteStatement and its 2 members: + * SqliteStatement::tokens and SqliteStatement::tokenMap. + */ +struct API_EXPORT Token +{ + /** + * @brief Token type. + * + * There are 2 kind of types - regular and context-oriented. + * + * Regular types are those defined by the SQL grammar and they represent real tokens. A special token type + * from group of regular types is the Type::INVALID, which means, that the character(s) encountered + * by the Lexer are invalid in SQL syntax understanding, or when there was no more query characters to read + * (which in this case means that tokenizing ended before this token). + * + * The context-oriented types are special meta types used by the Parser to probe potential candidates + * for next valid token when Parser::getNextTokenCandidates() is called. They are then processed by + * CompletionHelper. You won't deal with this kind of token types on regular basis. Context-oriented + * types are those starting with <tt>CTX_</tt>. + */ + enum Type + { + INVALID = 0x0001, /**< Invalid token, or no more tokens available from Lexer. */ + OTHER = 0x1002, /**< A name, a word. */ + STRING = 0x0003, /**< A string (value will be stripped of the surrounding quotes). */ + COMMENT = 0x0004, /**< A comment, including starting/ending markers. */ + FLOAT = 0x0005, /**< A decimal number. */ + INTEGER = 0x0006, /**< An integer number. */ + BIND_PARAM = 0x0007, /**< A bind parameter (<tt>:param</tt>, <tt>\@param</tt>, or <tt>?</tt>). */ + OPERATOR = 0x0008, /**< An operator (like <tt>";"</tt>, <tt>"+"</tt>, <tt>","</tt>, etc). */ + PAR_LEFT = 0x0009, /**< A left parenthesis (<tt>"("</tt>). */ + PAR_RIGHT = 0x0010, /**< A right parenthesis (<tt>")"</tt>). */ + SPACE = 0x0011, /**< White space(s), including new line characters and tabs. */ + BLOB = 0x0012, /**< Literal BLOB value (<tt>X'...'</tt> or <tt>x'...'</tt>). */ + KEYWORD = 0x0013, /**< A keyword (see getKeywords3() and getKeywords2()). */ + CTX_COLUMN = 0x1014, /**< Existing column name is valid at this token position. */ + CTX_TABLE = 0x1015, /**< Existing table name is valid at this token potision. */ + CTX_DATABASE = 0x1016, /**< Database name is valid at this token position. */ + CTX_FUNCTION = 0x0017, /**< Function name is valid at this token position. */ + CTX_COLLATION = 0x0018, /**< Collation name is valid at this token position. */ + CTX_INDEX = 0x1019, /**< Existing index name is valid at this token position. */ + CTX_TRIGGER = 0x1020, /**< Existing trigger name is valid at this token position. */ + CTX_VIEW = 0x1021, /**< View name is valid at this token position. */ + CTX_JOIN_OPTS = 0x0022, /**< JOIN keywords are valid at this token position (see getJoinKeywords()). */ + CTX_TABLE_NEW = 0x0023, /**< Name for new table is valid at this token position. */ + CTX_INDEX_NEW = 0x0024, /**< Name for new index is valid at this token position. */ + CTX_VIEW_NEW = 0x0025, /**< Name for new view is valid at this token position. */ + CTX_TRIGGER_NEW = 0x0026, /**< Name for new trigger is valid at this token position. */ + CTX_ALIAS = 0x0027, /**< Alias name is valid at this token position. */ + CTX_TRANSACTION = 0x0028, /**< Transaction name is valid at this token position. */ + CTX_COLUMN_NEW = 0x0029, /**< Name for the new column is valid at this token position. */ + CTX_COLUMN_TYPE = 0x0030, /**< Data type for the new column is valid at this token position. */ + CTX_CONSTRAINT = 0x0031, /**< Name for the new constraint is valid at this token position. */ + CTX_FK_MATCH = 0x0032, /**< MATCH keywords are valid at this token position (see getFkMatchKeywords()). */ + CTX_PRAGMA = 0x0033, /**< Pragma name is valid at this token position. */ + CTX_ROWID_KW = 0x0034, /**< ROWID keywords is valid at this token position (see isRowIdKeyword()). */ + CTX_NEW_KW = 0x0035, /**< The <tt>NEW</tt> keyword is valid at this token position. */ + CTX_OLD_KW = 0x0036, /**< The <tt>OLD</tt> keyword is valid at this token position. */ + CTX_ERROR_MESSAGE = 0x0037 /**< Error message string is valid at this token position. */ + }; + + /** + * @brief Creates empty token with type Type::INVALID. + * + * Lemon token ID is set to 0 and start/end positions are set to -1. + */ + Token(); + + /** + * @brief Creates fully defined token. + * @param lemonType Lemon token ID to use (see sqlite2_parser.h and sqlite3_parser.h). + * @param type Token type. + * @param value Value of the token. + * @param start Start position of the token (index of the first character in the query). + * @param end End position of the token (index of last character in the query). + * + * This constructor is intended to be used from Lemon parser only. For other use cases + * use constructors without the \p lemonType parameter, unless you need it and you know what you're doing. + */ + Token(int lemonType, Type type, QString value, qint64 start, qint64 end); + + /** + * @overload + */ + Token(int lemonType, Type type, QChar value, qint64 start, qint64 end); + + /** + * @overload + */ + Token(int lemonType, Type type, QString value); + + /** + * @brief Creates token with type Type::INVALID and given value. + * @param value Value for the token. + * + * Start/end positions are set to -1. + */ + explicit Token(QString value); + + /** + * @brief Creates token of given type and with given value. + * @param type Type for the token. + * @param value Value for the token. + * + * Start/end positions are set to -1. + */ + Token(Type type, QString value); + + /** + * @brief Creates fully defined token. + * @param type Type of the token. + * @param value Value for the token. + * @param start Start position of the token (index of the first character in the query). + * @param end End position of the token (index of last character in the query). + */ + Token(Type type, QString value, qint64 start, qint64 end); + + /** + * @brief Destructor declared as virtual. Does nothing in this implementation. + */ + virtual ~Token(); + + /** + * @brief Serializes token to human readable form. + * @return Token values in format: <tt>{type value start end}</tt> + */ + QString toString(); + + /** + * @brief Converts given token type into its string representation. + * @param type Type to convert. + * @return Type as a string (same as textual representation of the enum in the code). + */ + static const QString typeToString(Type type); + + /** + * @brief Provides range of the token in the query. + * @return Start and end character index in relation to the query it comes from. + */ + Range getRange(); + + /** + * @brief Tests whether this token represents any kind of whitespace. + * @return true if it's a whitespace, or false otherwise. + * + * Note, that from SQL perspective also comments are whitespaces. + */ + bool isWhitespace() const; + + /** + * @brief Tests whether this token represents separating value (like an operator, or parenthesis) in SQL understanding. + * @return true if it's a separating token, or false otherwise. + */ + bool isSeparating() const; + + /** + * @brief Tests whether this token is representing any kind of database object name. + * @return true if the token is the name an object, or false otherwise. + * + * From regular token types only the Type::OTHER represents. + * For context-oriented types there are several types representing object name. + * Use this method to find out which is and which is not. + * + * You won't need to use this method in most cases. It's useful only to CompletionHelper + * for now. + */ + bool isDbObjectType() const; + + /** + * @brief Converts token's type into a string representation. + * @return Token type as a string. + */ + QString typeString() const; + + /** + * @brief Compares other token to this token. + * @param other Other token to compare. + * @return 1 if tokens are equal, 0 if they're different. + * + * Tokens are equal then 4 members are equal: type, value, start and end. + * The lemonType member is ignored by this operator. + */ + int operator==(const Token& other); + + /** + * @brief Compares other token to this token and tells which one is greater. + * @param other Other token to compare. + * @return true if the other token is greater than this one, or false if it's smaller or equal. + * + * This operator compares only 2 members: the start and the end indexes. This operator + * is used to sort tokens by the character position they occurred at. + * + * The start value has higher precedence than the end value, but if start values are equal, + * then the end value is conclusive. + */ + bool operator<(const Token& other) const; + + /** + * @brief Lemon token ID. Used by the Parser class only. + */ + int lemonType; + + /** + * @brief Token type, describing general class of the token. + */ + Type type; + + /** + * @brief Literal value of the token, captured directly from the query. + */ + QString value = QString::null; + + /** + * @brief Start position (first character index) of the token in the query. + */ + qint64 start; + + /** + * @brief End position (last character index) of the token in the query. + */ + qint64 end; +}; + +/** + * @brief qHash implementation for TokenPtr, so it can be used as a key in QHash and QSet. + * @param token Token that the hash code is calculated for. + * @return Unique integer value for the token. + */ +uint qHash(const TokenPtr& token); + +struct TolerantToken; +/** + * @brief Shared pointer to TolerantToken. + */ +typedef QSharedPointer<TolerantToken> TolerantTokenPtr; + +/** + * @brief Variation of token that has additional "invalid" flag. + * + * TolerantToken is used by Lexer to tolerate unfinished comments, like when you start the + * comment at the end of the query, but you never close the comment. This is tolerable case, + * while not entire correct by the syntax. + * + * In such cases the syntax highlighter must be aware of the token being invalid, so the proper + * state is marked for the paragraph. + */ +struct TolerantToken : public Token +{ + /** + * @brief Invalid state flag for the token. + */ + bool invalid = false; +}; + +/** + * @brief Ordered list of tokens. + * + * This is pretty much a QList of Token pointers, but it also provides some + * utility methods regarding this collection, which is very common in SQLiteStudio. + */ +class API_EXPORT TokenList : public QList<TokenPtr> +{ + public: + /** + * @brief Creates empty list. + */ + TokenList(); + + /** + * @brief Creates list filled with the same entries as the other list. + * @param other List to copy pointers from. + */ + TokenList(const QList<TokenPtr>& other); + + /** + * @brief Serializes contents of the list into readable form. + * @return Contents in format: <tt>{type1 value1 start1 end1} {type2 value2 start2 end2} ...</tt>. + * + * Tokens are serialized with Token::toString(), then all serialized values are joined with single whitespace + * into the QString. + */ + QString toString() const; + + /** + * @brief Serializes tokens from the list into strings. + * @return List of tokens serialized into strings. + * + * Tokens are serialized with Token::toString(). + */ + QStringList toStringList() const; + + /** + * @brief Provides index of first occurrence of the token in the list. + * @param token Token to look for. + * @return Index of the token, or -1 if token was not found. + */ + int indexOf(TokenPtr token) const; + + /** + * @brief Provides index of first occurrence of the token with given type. + * @param type Toke type to look for. + * @return Index of the token, or -1 if token was not found. + */ + int indexOf(Token::Type type) const; + + /** + * @brief Provides index of first occurrence of the token with given type and value. + * @param type Token type to look for. + * @param value Token value to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @return Index of the token, or -1 if token was not found. + */ + int indexOf(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive) const; + + /** + * @brief Provides index of first occurrence of the token with given value. + * @param value Value to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @return Index of the token, or -1 if token was not found. + */ + int indexOf(const QString& value, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive) const; + + /** + * @brief Provides index of last occurrence of the token in the list. + * @param token Token to look for. + * @return Index of the token, or -1 if token was not found. + */ + int lastIndexOf(TokenPtr token) const; + + /** + * @brief Provides index of last occurrence of the token with given type. + * @param type Token type to look for. + * @return Index of the token, or -1 if token was not found. + */ + int lastIndexOf(Token::Type type) const; + + /** + * @brief Provides index of last occurrence of the token with given type and value. + * @param type Token type to look for. + * @param value Token value to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @return Index of the token, or -1 if token was not found. + */ + int lastIndexOf(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive) const; + + /** + * @brief Provides index of last occurrence of the token with given value. + * @param value Value to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @return Index of the token, or -1 if token was not found. + */ + int lastIndexOf(const QString& value, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive) const; + + /** + * @brief Finds first token with given type in the list. + * @param type Type to look for. + * @return Token found, or null pointer if it was not found. + */ + TokenPtr find(Token::Type type) const; + + /** + * @brief Finds first token with given type and value. + * @param type Type to look for. + * @param value Token value to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @return Token found, or null pointer if it was not found. + */ + TokenPtr find(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive) const; + + /** + * @brief Finds first token with given type and value. + * @param value Token value to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @return Token found, or null pointer if it was not found. + */ + TokenPtr find(const QString& value, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive) const; + + /** + * @brief Finds last token with given type in the list. + * @param type Type to look for. + * @return Token found, or null pointer if it was not found. + */ + TokenPtr findLast(Token::Type type) const; + + /** + * @brief Finds last token with given type and value. + * @param type Type to look for. + * @param value Token value to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @return Token found, or null pointer if it was not found. + */ + TokenPtr findLast(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive) const; + + /** + * @brief Finds last token with given value. + * @param value Token value to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @return Token found, or null pointer if it was not found. + */ + TokenPtr findLast(const QString& value, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive) const; + + /** + * @brief Finds token that according to its start and end values covers given character position. + * @param cursorPosition Position to get token for. + * @return Token found, or null pointer if it was not found. + */ + TokenPtr atCursorPosition(quint64 cursorPosition) const; + + /** + * @brief Inserts tokens at given position in this list. + * @param i Position to insert at. + * @param list List of tokens to insert. + */ + void insert(int i, const TokenList& list); + + /** + * @brief Inserts a token at given position in this list. + * @param i Position to insert at. + * @param token Token to insert. + */ + void insert(int i, TokenPtr token); + + /** + * @brief Puts all tokens from the other list to this list. + * @param other List to get tokens from. + * @return Reference to this list. + * + * It erases any previous entries in this list, just like you would normally expect for the <tt>=</tt> operator. + */ + TokenList& operator=(const QList<TokenPtr>& other); + + /** + * @brief Detokenizes all tokens from this list. + * @return String from all tokens. + * + * This is just a convenient method to call Lexer::detokenize() on this list. + */ + QString detokenize() const; + + /** + * @brief Replaces tokens on this list with other tokens. + * @param startIdx Position of the first token in this list to replace. + * @param length Number of tokens to replace. + * @param newTokens New tokens to put in place of removed ones. + */ + void replace(int startIdx, int length, const TokenList& newTokens); + + /** + * @brief Replaces tokens on this list with another token. + * @param startIdx Position of the first token in this list to replace. + * @param length Number of tokens to replace. + * @param newToken New token to put in place of removed ones. + */ + void replace(int startIdx, int length, TokenPtr newToken); + + /** + * @brief Replaces token with another token. + * @param startIdx Position of the token in this list to replace. + * @param newToken New token to put in place of removed one. + */ + void replace(int startIdx, TokenPtr newToken); + + /** + * @brief Replaces token on this list with other tokens. + * @param startIdx Position of the token in this list to replace. + * @param newTokens New tokens to put in place of removed ones. + */ + void replace(int startIdx, const TokenList& newTokens); + + /** + * @brief Replaces tokens on this list with other tokens. + * @param startToken First token to replace. + * @param endToken Last token to replace. + * @param newTokens New tokens to put in place of removed ones. + * @return Number of tokens replaced. + * + * If either \p startToken or \p endToken were not found on the list, this method does nothing + * and returns 0. + */ + int replace(TokenPtr startToken, TokenPtr endToken, const TokenList& newTokens); + + /** + * @brief Replaces tokens on this list with other tokens. + * @param startToken First token to replace. + * @param endToken Last token to replace. + * @param newToken New token to put in place of removed ones. + * @return Number of tokens replaced. + * + * If either \p startToken or \p endToken were not found on the list, this method does nothing + * and returns 0. + */ + int replace(TokenPtr startToken, TokenPtr endToken, TokenPtr newToken); + + /** + * @brief Replaces token on this list with another token. + * @param oldToken Token to replace. + * @param newToken Token to replace with. + * @return true if \p oldToken was found and replaced, or false otherwise. + */ + bool replace(TokenPtr oldToken, TokenPtr newToken); + + /** + * @brief Replaces token on this list with other tokens. + * @param oldToken Token to replace. + * @param newTokens Tokens to replace with. + * @return true if \p oldToken was found and replaced, or false otherwise. + */ + bool replace(TokenPtr oldToken, const TokenList& newTokens); + + /** + * @brief Removes tokens from the list. + * @param startToken First token to remove. + * @param endToken Last token to remove. + * @return true if \p startToken and \p endToken were found in the list and removed, or false otherwise. + * + * In case \p startToken is placed after \p endToken, this method does nothing and returns false. + */ + bool remove(TokenPtr startToken, TokenPtr endToken); + + /** + * @brief Removes first token of given type from the list. + * @param type Token type to remove. + * @return true if token was located and removed, or false otherwise. + */ + bool remove(Token::Type type); + + /** + * @brief Removes all white-space tokens from the beginning of the list. + * + * White-space tokens are tested with Token::isWhitespace(). + */ + void trimLeft(); + + /** + * @brief Removes all white-space tokens from the end of the list. + * + * White-space tokens are tested with Token::isWhitespace(). + */ + void trimRight(); + + /** + * @brief Removes all white-space tokens from both the beginning and the end of the list. + * + * White-space tokens are tested with Token::isWhitespace(). + */ + void trim(); + + /** + * @brief Removes all tokens that match given criteria from the beginning of the list. + * @param type Token type to remove. + * @param alsoTrim Token value to remove. + * + * This method is an extension to the regular trimLeft(). It removes white-space tokens, + * as well as tokens that are of given \p type and have given \p value (both conditions must be met). + */ + void trimLeft(Token::Type type, const QString& alsoTrim); + + /** + * @brief Removes all tokens that match given criteria from the end of the list. + * @param type Token type to remove. + * @param alsoTrim Token value to remove. + * + * This method is an extension to the regular trimRight(). It removes white-space tokens, + * as well as tokens that are of given \p type and have given \p value (both conditions must be met). + */ + void trimRight(Token::Type type, const QString& alsoTrim); + + /** + * @brief Removes all tokens that match given criteria from the beginning and the end of the list. + * @param type Token type to remove. + * @param alsoTrim Token value to remove. + * + * This method is an extension to the regular trim(). It removes white-space tokens, + * as well as tokens that are of given \p type and have given \p value (both conditions must be met). + */ + void trim(Token::Type type, const QString& alsoTrim); + + /** + * @brief Creates list of tokens from this list, letting through only tokens of given type. + * @param type Type of tokens to provide in the new list. + * @return List of tokens from this list matching given \p type. + */ + TokenList filter(Token::Type type) const; + + /** + * @brief Creates list of tokens from this list, letting through only tokens that are not a whitespace. + * @return List of tokens from this list that are not a whitespace. + * + * The condition to test if tokens is a whitespace is a call to Token::isWhitespace(). + */ + TokenList filterWhiteSpaces() const; + + /** + * @brief Returns sub-list of tokens from this list. + * @param pos Position to start sublist from. + * @param length Number of tokens to get from this list. If -1 (default), then all from the \p pos to the end. + * @return Sub-list of tokens from this list. + */ + TokenList mid(int pos, int length = -1) const; + + private: + /** + * @brief Finds first occurrence of token with given type. + * @param type Type of token to look for. + * @param idx Pointer to integer variable to store position in. + * @return Token found, or null pointer if token was not found. + * + * If \p idx is not null, then the position of token found is stored in it. If token was not found, + * then -1 is stored in the \p idx. + * + * This method is used by the public findFirst() and indexOf() methods, as they share common logic. + */ + TokenPtr findFirst(Token::Type type, int* idx) const; + + /** + * @brief Finds first occurrence of token with given type and value. + * @param type Type of token to look for. + * @param value Value of the token to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @param idx Pointer to integer variable to store position in. + * @return Token found, or null pointer if token was not found. + * + * If \p idx is not null, then the position of token found is stored in it. If token was not found, + * then -1 is stored in the \p idx. + * + * This method is used by the public findFirst() and indexOf() methods, as they share common logic. + */ + TokenPtr findFirst(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity, int* idx) const; + + /** + * @brief Finds first occurrence of token with given value. + * @param value Value of the token to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @param idx Pointer to integer variable to store position in. + * @return Token found, or null pointer if token was not found. + * + * If \p idx is not null, then the position of token found is stored in it. If token was not found, + * then -1 is stored in the \p idx. + * + * This method is used by the public findFirst() and indexOf() methods, as they share common logic. + */ + TokenPtr findFirst(const QString& value, Qt::CaseSensitivity caseSensitivity, int* idx) const; + + /** + * @brief Finds last occurrence of token with given type. + * @param type Type of token to look for. + * @param idx Pointer to integer variable to store position in. + * @return Token found, or null pointer if token was not found. + * + * If \p idx is not null, then the position of token found is stored in it. If token was not found, + * then -1 is stored in the \p idx. + * + * This method is used by the public findLast() and lastIndexOf() methods, as they share common logic. + */ + TokenPtr findLast(Token::Type type, int* idx) const; + + /** + * @brief Finds last occurrence of token with given type and value. + * @param type Type of token to look for. + * @param value Value of the token to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @param idx Pointer to integer variable to store position in. + * @return Token found, or null pointer if token was not found. + * + * If \p idx is not null, then the position of token found is stored in it. If token was not found, + * then -1 is stored in the \p idx. + * + * This method is used by the public findLast() and lastIndexOf() methods, as they share common logic. + */ + TokenPtr findLast(Token::Type type, const QString& value, Qt::CaseSensitivity caseSensitivity, int* idx) const; + + /** + * @brief Finds last occurrence of token with given value. + * @param value Value of the token to look for. + * @param caseSensitivity Should value lookup be case sensitive? + * @param idx Pointer to integer variable to store position in. + * @return Token found, or null pointer if token was not found. + * + * If \p idx is not null, then the position of token found is stored in it. If token was not found, + * then -1 is stored in the \p idx. + * + * This method is used by the public findLast() and lastIndexOf() methods, as they share common logic. + */ + TokenPtr findLast(const QString& value, Qt::CaseSensitivity caseSensitivity, int* idx) const; +}; + + +#endif // TOKEN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/pluginloader.cpp b/SQLiteStudio3/coreSQLiteStudio/pluginloader.cpp new file mode 100644 index 0000000..007e54c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/pluginloader.cpp @@ -0,0 +1,35 @@ +#include "plugin.h" +#include "pluginloader.h" +#include "unused.h" + +AbstractPluginLoader::~AbstractPluginLoader() +{ + foreach (Plugin* plugin, plugins.values()) + { + plugin->deinit(); + } + + foreach (QPluginLoader* loader, plugins.keys()) + { + if (!loader->unload()) + qDebug() << "Could not unload plugin" << loader->fileName() << ":" << loader->errorString(); + + delete loader; + } + plugins.clear(); +} + +bool AbstractPluginLoader::add(QPluginLoader* loader, Plugin* plugin) +{ + if (!plugin->init()) + return false; + + plugins[loader] = plugin; + return true; +} + + +QList<Plugin*> AbstractPluginLoader::getPlugins() const +{ + return plugins.values(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/pluginloader.h b/SQLiteStudio3/coreSQLiteStudio/pluginloader.h new file mode 100644 index 0000000..5f18c9a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/pluginloader.h @@ -0,0 +1,35 @@ +#ifndef PLUGINLOADER_H +#define PLUGINLOADER_H + +#include "coreSQLiteStudio_global.h" +#include <QPluginLoader> +#include <QList> +#include <QHash> +#include <QDebug> + +class Plugin; + +class API_EXPORT AbstractPluginLoader +{ + public: + virtual ~AbstractPluginLoader(); + + virtual bool add(QPluginLoader* loader, Plugin* plugin); + virtual bool test(Plugin* plugin) = 0; + QList<Plugin*> getPlugins() const; + + private: + QHash<QPluginLoader*,Plugin*> plugins; +}; + +template <class T> +class PluginLoader : public AbstractPluginLoader +{ + public: + bool test(Plugin* plugin) + { + return (dynamic_cast<T*>(plugin) != nullptr); + } +}; + +#endif // PLUGINLOADER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.cpp new file mode 100644 index 0000000..29397e0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.cpp @@ -0,0 +1,58 @@ +#include "builtinplugin.h" +#include "services/pluginmanager.h" +#include <QMetaClassInfo> + +QString BuiltInPlugin::getName() const +{ + return metaObject()->className(); +} + +QString BuiltInPlugin::getTitle() const +{ + const char *title = getMetaInfo("title"); + if (!title) + return getName(); + + return title; +} + +QString BuiltInPlugin::getDescription() const +{ + return getMetaInfo("description"); +} + +int BuiltInPlugin::getVersion() const +{ + return QString(getMetaInfo("version")).toInt(); +} + +QString BuiltInPlugin::getPrintableVersion() const +{ + return PLUGINS->toPrintableVersion(getVersion()); +} + +QString BuiltInPlugin::getAuthor() const +{ + return getMetaInfo("author"); +} + +bool BuiltInPlugin::init() +{ + return true; +} + +void BuiltInPlugin::deinit() +{ +} + +const char* BuiltInPlugin::getMetaInfo(const QString& key) const +{ + for (int i = 0; i < metaObject()->classInfoCount(); i++) + { + if (key != metaObject()->classInfo(i).name()) + continue; + + return metaObject()->classInfo(i).value(); + } + return nullptr; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.h new file mode 100644 index 0000000..ccb5c3d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.h @@ -0,0 +1,147 @@ +#ifndef BUILTINPLUGIN_H +#define BUILTINPLUGIN_H + +#include "coreSQLiteStudio_global.h" +#include "plugins/plugin.h" + +/** + * @brief Helper class for implementing built-in plugins + * + * This class can be inherited, so most of the abstract methods from Plugin interface get implemented. + * All details (description, name, title, author, ...) are defined using Q_CLASSINFO. + * + * There are macros defined to help you with defining those details. You don't need to define + * Q_CLASSINFO and know all required keys. You can use following macros: + * <ul> + * <li>::SQLITESTUDIO_PLUGIN_TITLE</li> + * <li>::SQLITESTUDIO_PLUGIN_DESC</li> + * <li>::SQLITESTUDIO_PLUGIN_UI</li> + * <li>::SQLITESTUDIO_PLUGIN_VERSION</li> + * <li>::SQLITESTUDIO_PLUGIN_AUTHOR</li> + * </ul> + * + * Most of plugin implementations will use this class as a base, because it simplifies process + * of plugin development. Using this class you don't have to implement any of virtual methods + * from Plugin interface. It's enough to define meta information, like this: + * @code + * class MyPlugin : GenericPlugin + * { + * Q_OBJECT + * + * SQLITESTUDIO_PLUGIN + * SQLITESTUDIO_PLUGIN_TITLE("My plugin") + * SQLITESTUDIO_PLUGIN_DESC("Does nothing. It's an example plugin.") + * SQLITESTUDIO_PLUGIN_UI("formObjectName") + * SQLITESTUDIO_PLUGIN_VERSION(10000) + * SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + * }; + * @endcode + */class API_EXPORT BuiltInPlugin : public QObject, public virtual Plugin +{ + Q_OBJECT + Q_INTERFACES(Plugin) + + public: + /** + * @brief Provides plugin internal name. + * @return Plugin class name. + */ + QString getName() const; + + /** + * @brief Provides plugin title. + * @return Title defined in plugin's metadata file with key "title" or (if not defined) the same value as getName(). + */ + QString getTitle() const; + + /** + * @brief Provides plugin description. + * @return Description as defined in plugin's metadata file with key "description", or null QString if not defined. + */ + QString getDescription() const; + + /** + * @brief Provides plugin numeric version. + * @return Version number as defined in plugin's metadata file with key "version", or 0 if not defined. + */ + int getVersion() const; + + /** + * @brief Converts plugin version to human readable form. + * @return Version in format X.Y.Z. + */ + QString getPrintableVersion() const; + + /** + * @brief Provides an author name. + * @return Author name as defined with in plugin's metadata file with key "author", or null QString if not defined. + */ + QString getAuthor() const; + + /** + * @brief Does nothing. + * @return Always true. + * + * This is a default (empty) implementation of init() for plugins. + */ + bool init(); + + /** + * @brief Does nothing. + * + * This is a default (empty) implementation of init() for plugins. + */ + void deinit(); + + private: + /** + * @brief Extracts class meta information with given key. + * @param key Key to extract. + * @return Value of the meta information, or null if there's no information with given key. + * + * This is a helper method which queries Qt's meta object subsystem for class meta information defined with Q_CLASSINFO. + */ + const char* getMetaInfo(const QString& key) const; +}; + +/** + * @def SQLITESTUDIO_PLUGIN_TITLE + * @brief Defines plugin title. + * + * This is a built-in plugin replacement for "title" key in external plugin's json metadata file. + */ +#define SQLITESTUDIO_PLUGIN_TITLE(Title) Q_CLASSINFO("title", Title) + +/** + * @def SQLITESTUDIO_PLUGIN_DESC + * @brief Defines plugin description. + * + * This is a built-in plugin replacement for "description" key in external plugin's json metadata file. + */ +#define SQLITESTUDIO_PLUGIN_DESC(Desc) Q_CLASSINFO("description", Desc) + +/** + * @def SQLITESTUDIO_PLUGIN_UI + * @brief Defines Qt Designer Form object name to be used in configuration dialog. + * + * This is a built-in plugin replacement for "ui" key in external plugin's json metadata file. + */ +#define SQLITESTUDIO_PLUGIN_UI(FormName) Q_CLASSINFO("ui", FormName) + +/** + * @def SQLITESTUDIO_PLUGIN_VERSION + * @brief Defines plugin version. + * + * This is a built-in plugin replacement for "version" key in external plugin's json metadata file. + */ +#define SQLITESTUDIO_PLUGIN_VERSION(Ver) Q_CLASSINFO("version", #Ver) + +/** + * @def SQLITESTUDIO_PLUGIN_AUTHOR + * @brief Defines an author of the plugin. + * + * This is a built-in plugin replacement for "author" key in external plugin's json metadata file. + */ +#define SQLITESTUDIO_PLUGIN_AUTHOR(Author) Q_CLASSINFO("author", Author) + +#endif // BUILTINPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/codeformatterplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/codeformatterplugin.h new file mode 100644 index 0000000..093c20e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/codeformatterplugin.h @@ -0,0 +1,16 @@ +#ifndef CODEFORMATTERPLUGIN_H +#define CODEFORMATTERPLUGIN_H + +#include "coreSQLiteStudio_global.h" +#include "plugin.h" + +class Db; + +class API_EXPORT CodeFormatterPlugin : virtual public Plugin +{ + public: + virtual QString getLanguage() const = 0; + virtual QString format(const QString& code, Db* contextDb) = 0; +}; + +#endif // CODEFORMATTERPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/confignotifiableplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/confignotifiableplugin.h new file mode 100644 index 0000000..d8a8f9b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/confignotifiableplugin.h @@ -0,0 +1,14 @@ +#ifndef CONFIGNOTIFIABLEPLUGIN_H +#define CONFIGNOTIFIABLEPLUGIN_H + +#include <QVariant> + +class CfgEntry; + +class ConfigNotifiablePlugin +{ + public: + virtual void configModified(CfgEntry* key, const QVariant& value) = 0; +}; + +#endif // CONFIGNOTIFIABLEPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/dbplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/dbplugin.h new file mode 100644 index 0000000..2f2e62e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/dbplugin.h @@ -0,0 +1,73 @@ +#ifndef DBPLUGIN_H +#define DBPLUGIN_H + +#include "db/db.h" +#include "db/dbpluginoption.h" +#include "plugins/plugin.h" + +/** + * @brief Interface for database plugins + * + * This is a specialization of Plugin interface, which all database plugins have to implement. + */ +class API_EXPORT DbPlugin : virtual public Plugin +{ + public: + /** + * @brief Creates database instance defined by the plugin. + * @param name Name for the database. + * @param path Path to the database file. + * @param options Options for the database passed while registering the database in the application. + * @param errorMessage If the result is null (on failure) and this pointer is not null, the error message will be stored in it. + * @return Database instance on success, or null pointer on failure. + * + * Options can contain for example password for an encrypted database, or other connection options. + */ + virtual Db* getInstance(const QString& name, const QString& path, const QHash<QString,QVariant> &options, QString* errorMessage = 0) = 0; + + /** + * @brief Provides label of what type is the database. + * @return Type label. + * + * The label is used for presenting to the user what kind of database this is. It's used on GUI + * to display database type in databases dialog. It's usually either "SQLite3" or "SQLite2", + * but it may be something else, like for example encrypted database might provide "Encrypted SQLite3", + * or something similar. + */ + virtual QString getLabel() const = 0; + + /** + * @brief Provides list of options configurable for this database plugin. + * @return List of options. + * + * DbDialog uses this to provide GUI interface, so user can configure connection options. + * For each element in the list DbDialog adds QLabel and the input widget for entering option value. + * Option values entered by user are later passed to getInstance() as second argument. + */ + virtual QList<DbPluginOption> getOptionsList() const = 0; + + /** + * @brief Generates suggestion for database name. + * @param baseValue Value enterd as file path in DbDialog. + * @return Generated name suggestion. + * + * This can be simply string representation of \p baseValue, + * but the plugin may add something characteristic for the plugin. + */ + virtual QString generateDbName(const QVariant& baseValue) = 0; + + /** + * @brief Tests if the given database support is provided by this plugin. + * @param db Database to test. + * @return true if the database is supported by this plugin, or false otherwise. + * + * Implementation of this method should check if given database object + * is of the same type, that those returned from getInstance(). + * + * This method is used by DbManager to find out which databases are supported by which plugins, + * so when some plugin is about to be unloaded, all its databases are closed properly first. + */ + virtual bool checkIfDbServedByPlugin(Db* db) const = 0; +}; + +#endif // DBPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.cpp new file mode 100644 index 0000000..0f16098 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.cpp @@ -0,0 +1,47 @@ +#include "dbpluginsqlite3.h" +#include "db/dbsqlite3.h" +#include "common/unused.h" +#include <QFileInfo> + +Db* DbPluginSqlite3::getInstance(const QString& name, const QString& path, const QHash<QString, QVariant>& options, QString* errorMessage) +{ + UNUSED(errorMessage); + Db* db = new DbSqlite3(name, path, options); + + if (!db->openForProbing()) + { + delete db; + return nullptr; + } + + SqlQueryPtr results = db->exec("SELECT * FROM sqlite_master"); + if (results->isError()) + { + delete db; + return nullptr; + } + + db->closeQuiet(); + return db; +} + +QString DbPluginSqlite3::getLabel() const +{ + return "SQLite 3"; +} + +QList<DbPluginOption> DbPluginSqlite3::getOptionsList() const +{ + return QList<DbPluginOption>(); +} + +QString DbPluginSqlite3::generateDbName(const QVariant& baseValue) +{ + QFileInfo file(baseValue.toString()); + return file.baseName(); +} + +bool DbPluginSqlite3::checkIfDbServedByPlugin(Db* db) const +{ + return (db && dynamic_cast<DbSqlite3*>(db)); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.h b/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.h new file mode 100644 index 0000000..ee522d7 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.h @@ -0,0 +1,24 @@ +#ifndef DBPLUGINSQLITE3_H +#define DBPLUGINSQLITE3_H + +#include "dbplugin.h" +#include "builtinplugin.h" + +class DbPluginSqlite3 : public BuiltInPlugin, public DbPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("SQLite 3") + SQLITESTUDIO_PLUGIN_DESC("SQLite 3 databases support.") + SQLITESTUDIO_PLUGIN_VERSION(10000) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + Db* getInstance(const QString& name, const QString& path, const QHash<QString, QVariant>& options, QString* errorMessage); + QString getLabel() const; + QList<DbPluginOption> getOptionsList() const; + QString generateDbName(const QVariant& baseValue); + bool checkIfDbServedByPlugin(Db* db) const; +}; + +#endif // DBPLUGINSQLITE3_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/exportplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/exportplugin.h new file mode 100644 index 0000000..06aded7 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/exportplugin.h @@ -0,0 +1,372 @@ +#ifndef EXPORTPLUGIN_H +#define EXPORTPLUGIN_H + +#include "plugin.h" +#include "db/sqlquery.h" +#include "db/queryexecutor.h" +#include "services/exportmanager.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqlitecreatevirtualtable.h" + +class CfgMain; + +/** + * @brief Provides support for particular export format. + * + * All export methods in this class should report any warnings, error messages, etc through the NotifyManager, + * that is by using notifyError() and its family methods. + */ +class ExportPlugin : virtual public Plugin +{ + public: + /** + * @brief Provides name of the format that the plugin exports to. + * @return Format name that will be displayed on UI. + * + * Format must be a single word name. + */ + virtual QString getFormatName() const = 0; + + /** + * @brief Tells what standard exporting options should be displayed to the user on UI. + * @return OR-ed set of option enum values. + */ + virtual ExportManager::StandardConfigFlags standardOptionsToEnable() const = 0; + + /** + * @brief Tells which character encoding use by default in export dialog. + * @return Name of the encoding. + * + * If the plugin doesn't return ExportManager::CODEC in results from standardOptionsToEnable(), then result + * of this function is ignored and it can return null string. + */ + virtual QString getDefaultEncoding() const = 0; + + /** + * @brief Provides set of modes supported by this export plugin. + * @return OR-ed set of supported modes. + * + * Some export plugins might not support some of exporting modes. For example CSV export plugin + * will not support DATABASE exporting, because CSV cannot represent schema of the database. + * + * If a plugin doesn't return some mode in this method, then that plugin will be excluded + * from list of available formats to export, when user requests to export in this mode. + */ + virtual ExportManager::ExportModes getSupportedModes() const = 0; + + /** + * @brief Provides set of flags for additional information that needs to be provided for this plugin. + * @return OR-ed set of flags. + * + * Some plugins might need to know what is a total number of rows that are expected to be + * exported for each table or query results. Other plugins might want to know + * what is the maximum number of characters/bytes in each exported table column, + * so they can calculate column widths when drawing them in the exported files, documents, etc. + * + * Those additional information are not provided by default, because they are gathered with extra queries + * to the database and for huge tables it might cause the table to be exported much longer, even if + * those information wasn't required by some plugin. + * + * See ExportManager::ExportProviderFlags for list of possible flags and what they mean. + */ + virtual ExportManager::ExportProviderFlags getProviderFlags() const = 0; + + /** + * @brief Provides config object that holds configuration for exporting. + * @return Config object, or null if the exporting with this plugin is not configurable. + */ + virtual CfgMain* getConfig() = 0; + + /** + * @brief Provides name of the form to use for configuration of exporting in given mode. + * @param mode Mode for which the form is requested for. + * @return Name of the form (toplevel QWidget in the ui file). + * + * If exporting with this plugin is not configurable (i.e. getConfig() returns null), + * then this method is not even called, so it can return anything, just to satisfy method + * return type. In that case good idea is to always return QString::null. + * + * @see FormManager + */ + virtual QString getExportConfigFormName() const = 0; + + /** + * @brief Tells plugin what is going to be the next export mode. + * @param mode Mode that the next export is going to be performed for. + * + * Plugin should remember this and use later when some logic is depended on what the mode is. + * For example getConfigFormName() (as well as getConfig()) might return different config forms + * for different modes. + */ + virtual void setExportMode(ExportManager::ExportMode mode) = 0; + + /** + * @brief Called when the UI expects any configuration options to be re-validated. + * + * When user interacts with the UI in a way that it doesn't change the config values, + * but it still requires some options to be re-validated, this method is called. + * + * It should validate any configuration values defined with CFG_CATEGORY and CFG_ENTRY + * and post the validation results by calling EXPORT_MANAGER->handleValidationFromPlugin() + * for every validated CfgEntry. + * + * This is also a good idea to connect to the CfgEntry::changed() signal for entries that should be validated + * and call this method from the slot, so any changes to the configuration values will be + * immediately validated and reflected on the UI. + */ + virtual void validateOptions() = 0; + + /** + * @brief Provides usual file name extension used with this format. + * @return File name extension (like ".csv"). + * + * This extension will be automatically appended to file name when user picked file name + * with file dialog, but the file extension part in the selected file was ommited. + */ + virtual QString defaultFileExtension() const = 0; + + /** + * @brief Mime type for when exporting binary format to clipboard. + * @return Mime type, like "image/png". + * + * Value returned from this method is used to set mime type when the exporting is done into the system clipboard. + * The clipboard needs mime type to identify what kind of data is in it. + * + * See details http://qt-project.org/doc/qt-5/qmimedata.html#setData + * + * If the plugin exports just a string, then this method can return QString::null and SqliteStudio will assume + * that the data is of "text/plain" type. + */ + virtual QString getMimeType() const = 0; + + /** + * @brief Tells if the data being exported is a binary or text. + * @return true for binary data, false for textual data. + * + * This is used by the SQLiteStudio to configure output device properly. For example CSV format is textual, + * but PNG is considered binary. + */ + virtual bool isBinaryData() const = 0; + + /** + * @brief Provides common state values before the export process begins. + * @param db Database that the export will be performed on. + * @param output Output device to write exporting data to. + * @param config Common exporting configuration, like file name, codec, etc. + * @return true for success, or false in case of a fatal error. + * + * This is called exactly once before every export process (that is once per each export called by user). + * Use it to remember database, output device, config for further method calls, or write a file header. + * This method will be followed by any of *export*() methods from this interface. + * + * There's a convenient class GenericExportPlugin, which you can extend instead of ExportPlugin. If you use + * GenericExportPlugin for a base class of exprt plugin, then this method is already implemented there + * and it stores all these parameters in protected class members so you can use them in other methods. + */ + virtual bool initBeforeExport(Db* db, QIODevice* output, const ExportManager::StandardExportConfig& config) = 0; + + /** + * @brief Does initial entry for exported query results. + * @param query Query that was executed to get the results. + * @param columns Columns returned from the query. + * @param providedData All data entries requested by the plugin in the return value of getProviderFlags(). + * @return true for success, or false in case of a fatal error. + * + * It's called just before actual data entries are exported (with exportQueryResultsRow()). + * It's called exactly once for single query results export. + */ + virtual bool beforeExportQueryResults(const QString& query, QList<QueryExecutor::ResultColumnPtr>& columns, + const QHash<ExportManager::ExportProviderFlag,QVariant> providedData) = 0; + + /** + * @brief Does export entry for a single row of data. + * @param row Single data row. + * @return true for success, or false in case of a fatal error. + * + * It's called for each data row returned from the query. + */ + virtual bool exportQueryResultsRow(SqlResultsRowPtr row) = 0; + + /** + * @brief Does final entry for exported query results. + * @return true for success, or false in case of a fatal error. + * + * It's called once after all data from the query was exported. + */ + virtual bool afterExportQueryResults() = 0; + + /** + * @brief Prepares for exporting tables from database. + * @return true for success, or false in case of a fatal error. + * + * This is called only for database export. For single table export only exportTable() is called. + */ + virtual bool beforeExportTables() = 0; + + /** + * @brief Does initial entry for exported table. + * @param database "Attach" name of the database that the table belongs to. Can be "main", "temp", or any attach name. + * @param table Name of the table to export. + * @param columnNames Name of columns in the table, in order they will appear in the rows passed to exportTableRow(). + * @param ddl The DDL of the table. + * @param createTable Table DDL parsed into an object. + * @param providedData All data entries requested by the plugin in the return value of getProviderFlags(). + * @return true for success, or false in case of a fatal error. + */ + virtual bool exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, + const QHash<ExportManager::ExportProviderFlag,QVariant> providedData) = 0; + + /** + * @brief Does initial entry for exported virtual table. + * @param database "Attach" name of the database that the table belongs to. Can be "main", "temp", or any attach name. + * @param table Name of the table to export. + * @param columnNames Name of columns in the table, in order they will appear in the rows passed to exportTableRow(). + * @param ddl The DDL of the table. + * @param createTable Table DDL parsed into an object. + * @param providedData All data entries requested by the plugin in the return value of getProviderFlags(). + * @return true for success, or false in case of a fatal error. + */ + virtual bool exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, + SqliteCreateVirtualTablePtr createTable, const QHash<ExportManager::ExportProviderFlag,QVariant> providedData) = 0; + + /** + * @brief Does export entry for a single row of data. + * @param data Single data row. + * @return true for success, or false in case of a fatal error. + * + * This method will be called only if StandardExportConfig::exportData in initBeforeExport() was true. + */ + virtual bool exportTableRow(SqlResultsRowPtr data) = 0; + + /** + * @brief Does final entry for exported table, after its data was exported. + * @return true for success, or false in case of a fatal error. + */ + virtual bool afterExportTable() = 0; + + /** + * @brief Does final entries after all tables have been exported. + * @return true for success, or false in case of a fatal error. + * + * This is called only for database export. For single table export only exportTable() is called. + * After table exporting also an afterExport() is called, so you can use that for any postprocessing. + */ + virtual bool afterExportTables() = 0; + + /** + * @brief Does initial entry for the entire database export. + * @param database Database name (as listed in database list). + * @return true for success, or false in case of a fatal error. + * + * It's called just once, before each database object gets exported. + * This method will be followed by calls to (in this order): beforeExportTables(), exportTable(), exportVirtualTable(), exportTableRow(), afterExportTable(), + * afterExportTables(), beforeExportIndexes(), exportIndex(), afterExportIndexes(), beforeExportTriggers(), exportTrigger(), afterExportTriggers(), + * beforeExportViews(), exportView(), afterExportViews() and afterExportDatabase(). + * Note, that exportTableRow() will be called only if StandardExportConfig::exportData in initBeforeExport() was true. + */ + virtual bool beforeExportDatabase(const QString& database) = 0; + + /** + * @brief Prepares for exporting indexes from database. + * @return true for success, or false in case of a fatal error. + */ + virtual bool beforeExportIndexes() = 0; + + /** + * @brief Does entire export entry for an index. + * @param database "Attach" name of the database that the index belongs to. Can be "main", "temp", or any attach name. + * @param table Name of the index to export. + * @param ddl The DDL of the index. + * @param createIndex Index DDL parsed into an object. + * @return true for success, or false in case of a fatal error. + * + * This is the only method called for index export. + */ + virtual bool exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex) = 0; + + /** + * @brief Does final entries after all indexes have been exported. + * @return true for success, or false in case of a fatal error. + */ + virtual bool afterExportIndexes() = 0; + + /** + * @brief Prepares for exporting triggers from database. + * @return true for success, or false in case of a fatal error. + */ + virtual bool beforeExportTriggers() = 0; + + /** + * @brief Does entire export entry for an trigger. + * @param database "Attach" name of the database that the trigger belongs to. Can be "main", "temp", or any attach name. + * @param table Name of the trigger to export. + * @param ddl The DDL of the trigger. + * @param createTrigger Trigger DDL parsed into an object. + * @return true for success, or false in case of a fatal error. + * + * This is the only method called for trigger export. + */ + virtual bool exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger) = 0; + + /** + * @brief Does final entries after all triggers have been exported. + * @return true for success, or false in case of a fatal error. + */ + virtual bool afterExportTriggers() = 0; + + /** + * @brief Prepares for exporting views from database. + * @return true for success, or false in case of a fatal error. + */ + virtual bool beforeExportViews() = 0; + + /** + * @brief Does entire export entry for an view. + * @param database "Attach" name of the database that the view belongs to. Can be "main", "temp", or any attach name. + * @param table Name of the trigger to view. + * @param ddl The DDL of the view. + * @param createView View DDL parsed into an object. + * @return true for success, or false in case of a fatal error. + * + * This is the only method called for view export. + */ + virtual bool exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr view) = 0; + + /** + * @brief Does final entries after all views have been exported. + * @return true for success, or false in case of a fatal error. + */ + virtual bool afterExportViews() = 0; + + /** + * @brief Does final entry for the entire database export. + * @return true for success, or false in case of a fatal error. + * + * It's called just once, after all database object get exported. + */ + virtual bool afterExportDatabase() = 0; + + /** + * @brief Does final entry for any export process. + * @return true for success, or false in case of a fatal error. + * + * This is similar to afterExportDatabase() when the export mode is database, + * but this is called at the end for any export mode, not only for database export. + * + * Use it to write a footer, or anything like that. + */ + virtual bool afterExport() = 0; + + /** + * @brief Called after every export, even failed one. + * + * Implementation of this method should cleanup any resources used during each single export process. + * This method is guaranteed to be executed, no matter if export was successful or not. + */ + virtual void cleanupAfterExport() = 0; +}; + +#endif // EXPORTPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/generalpurposeplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/generalpurposeplugin.h new file mode 100644 index 0000000..b50834a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/generalpurposeplugin.h @@ -0,0 +1,20 @@ +#ifndef GENERALPURPOSEPLUGIN_H
+#define GENERALPURPOSEPLUGIN_H
+
+#include "plugin.h"
+
+/**
+ * @brief The general purpose plugin interface.
+ *
+ * General purpose plugins are not designated for some specific function.
+ * They rely on init() and deinit() implementations to add some menubar entries,
+ * or toolbar entries (or anything else), so user can interact with the plugin.
+ *
+ * @see Plugin
+ * @see GenericPlugin
+ */
+class API_EXPORT GeneralPurposePlugin : virtual public Plugin
+{
+};
+
+#endif // GENERALPURPOSEPLUGIN_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.cpp new file mode 100644 index 0000000..d191ae4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.cpp @@ -0,0 +1,160 @@ +#include "genericexportplugin.h" +#include "common/utils.h" +#include "services/notifymanager.h" +#include "common/unused.h" +#include "config_builder.h" +#include <QTextCodec> + +bool GenericExportPlugin::initBeforeExport(Db* db, QIODevice* output, const ExportManager::StandardExportConfig& config) +{ + this->db = db; + this->output = output; + this->config = &config; + + if (standardOptionsToEnable().testFlag(ExportManager::CODEC)) + { + codec = codecForName(this->config->codec); + if (!codec) + { + codec = defaultCodec(); + notifyWarn(tr("Could not initialize text codec for exporting. Using default codec: %1").arg(QString::fromLatin1(codec->name()))); + } + } + + return beforeExport(); +} + +ExportManager::ExportModes GenericExportPlugin::getSupportedModes() const +{ + return ExportManager::FILE|ExportManager::CLIPBOARD|ExportManager::DATABASE|ExportManager::TABLE|ExportManager::QUERY_RESULTS; +} + +ExportManager::ExportProviderFlags GenericExportPlugin::getProviderFlags() const +{ + return ExportManager::NONE; +} + +QString GenericExportPlugin::getExportConfigFormName() const +{ + return QString(); +} + +CfgMain* GenericExportPlugin::getConfig() +{ + return nullptr; +} + +QString GenericExportPlugin::getConfigFormName(ExportManager::ExportMode mode) const +{ + UNUSED(mode); + return QString::null; +} + +QString GenericExportPlugin::getMimeType() const +{ + return QString::null; +} + +QString GenericExportPlugin::getDefaultEncoding() const +{ + return QString(); +} + +bool GenericExportPlugin::isBinaryData() const +{ + return false; +} + +void GenericExportPlugin::setExportMode(ExportManager::ExportMode mode) +{ + this->exportMode = mode; +} + +bool GenericExportPlugin::afterExportQueryResults() +{ + return true; +} + +bool GenericExportPlugin::afterExportTable() +{ + return true; +} + +bool GenericExportPlugin::initBeforeExport() +{ + return true; +} + +void GenericExportPlugin::write(const QString& str) +{ + output->write(codec->fromUnicode(str)); +} + +void GenericExportPlugin::writeln(const QString& str) +{ + write(str + "\n"); +} + +bool GenericExportPlugin::isTableExport() const +{ + return exportMode == ExportManager::TABLE; +} + +bool GenericExportPlugin::beforeExportTables() +{ + return true; +} + +bool GenericExportPlugin::afterExportTables() +{ + return true; +} + +bool GenericExportPlugin::beforeExportIndexes() +{ + return true; +} + +bool GenericExportPlugin::afterExportIndexes() +{ + return true; +} + +bool GenericExportPlugin::beforeExportTriggers() +{ + return true; +} + +bool GenericExportPlugin::afterExportTriggers() +{ + return true; +} + +bool GenericExportPlugin::beforeExportViews() +{ + return true; +} + +bool GenericExportPlugin::afterExportViews() +{ + return true; +} + +bool GenericExportPlugin::afterExportDatabase() +{ + return true; +} + +bool GenericExportPlugin::afterExport() +{ + return true; +} + +void GenericExportPlugin::cleanupAfterExport() +{ +} + +bool GenericExportPlugin::beforeExport() +{ + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.h new file mode 100644 index 0000000..1719baa --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.h @@ -0,0 +1,56 @@ +#ifndef GENERICEXPORTPLUGIN_H +#define GENERICEXPORTPLUGIN_H + +#include "exportplugin.h" +#include "genericplugin.h" + +class API_EXPORT GenericExportPlugin : virtual public GenericPlugin, public ExportPlugin +{ + public: + bool initBeforeExport(Db* db, QIODevice* output, const ExportManager::StandardExportConfig& config); + ExportManager::ExportModes getSupportedModes() const; + ExportManager::ExportProviderFlags getProviderFlags() const; + QString getExportConfigFormName() const; + CfgMain* getConfig(); + QString getConfigFormName(ExportManager::ExportMode exportMode) const; + QString getMimeType() const; + QString getDefaultEncoding() const; + bool isBinaryData() const; + void setExportMode(ExportManager::ExportMode exportMode); + bool afterExportQueryResults(); + bool afterExportTable(); + bool beforeExportTables(); + bool afterExportTables(); + bool beforeExportIndexes(); + bool afterExportIndexes(); + bool beforeExportTriggers(); + bool afterExportTriggers(); + bool beforeExportViews(); + bool afterExportViews(); + bool afterExportDatabase(); + bool afterExport(); + void cleanupAfterExport(); + + /** + * @brief Does the initial entry in the export. + * @return true for success, or false in case of a fatal error. + * + * Use it to write the header, or something like that. Default implementation does nothing. + * This is called from initBeforeExport(), after exportMode and other settings are already prepared. + */ + virtual bool beforeExport(); + + protected: + virtual bool initBeforeExport(); + void write(const QString& str); + void writeln(const QString& str); + bool isTableExport() const; + + Db* db = nullptr; + QIODevice* output = nullptr; + const ExportManager::StandardExportConfig* config = nullptr; + QTextCodec* codec = nullptr; + ExportManager::ExportMode exportMode = ExportManager::UNDEFINED; +}; + +#endif // GENERICEXPORTPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.cpp new file mode 100644 index 0000000..899691c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.cpp @@ -0,0 +1,67 @@ +#include "genericplugin.h" +#include "services/pluginmanager.h" +#include <QMetaClassInfo> + +QString GenericPlugin::getName() const +{ + return metaData["name"].toString(); +} + +QString GenericPlugin::getTitle() const +{ + if (!metaData["title"].isValid()) + return getName(); + + return metaData["title"].toString(); +} + +CfgMain* GenericPlugin::getMainUiConfig() +{ + return nullptr; +} + +QString GenericPlugin::getDescription() const +{ + return metaData["description"].toString(); +} + +int GenericPlugin::getVersion() const +{ + return metaData["version"].toInt(); +} + +QString GenericPlugin::getPrintableVersion() const +{ + return PLUGINS->toPrintableVersion(getVersion()); +} + +bool GenericPlugin::init() +{ + return true; +} + +void GenericPlugin::deinit() +{ +} + +void GenericPlugin::loadMetaData(const QJsonObject& metaData) +{ + this->metaData = PLUGINS->readMetaData(metaData); +} + +const char* GenericPlugin::getMetaInfo(const QString& key) const +{ + for (int i = 0; i < metaObject()->classInfoCount(); i++) + { + if (key != metaObject()->classInfo(i).name()) + continue; + + return metaObject()->classInfo(i).value(); + } + return nullptr; +} + +QString GenericPlugin::getAuthor() const +{ + return metaData["author"].toString(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.h new file mode 100644 index 0000000..ce008e0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.h @@ -0,0 +1,126 @@ +#ifndef GENERICPLUGIN_H +#define GENERICPLUGIN_H + +#include "plugin.h" +#include <QObject> +#include <QtPlugin> +#include <QHash> +#include <QVariant> + +/** @file */ + +/** + * @brief Helper class for implementing plugins + * + * This class can be inherited, so most of the abstract methods from Plugin interface get implemented. + * All details (description, name, title, author, ...) are defined in separate json file. + * + * Most of plugin implementations will use this class as a base, because it simplifies process + * of plugin development. Using this class you don't have to implement any of virtual methods + * from Plugin interface. It's enough to define meta information in the json file, like this: + * @code + * { + * "type": "ScriptingPlugin", + * "title": "My plugin", + * "description": "Does nothing. It's an example plugin.", + * "version": 10000 + * "author": "sqlitestudio.pl" + * }; + * @endcode + * + * and then just declare the class as SQLiteStudio plugin, pointing the json file you just created: + * @code + * class MyPlugin : public GenericPlugin, public ScriptingPlugin + * { + * Q_OBJECT + * SQLITESTUDIO_PLUGIN("myplugin.json") + * + * // rest of the class + * }; + * @endcode + */ +class API_EXPORT GenericPlugin : public QObject, public virtual Plugin +{ + Q_OBJECT + Q_INTERFACES(Plugin) + + public: + /** + * @brief Provides plugin internal name. + * @return Plugin class name. + */ + QString getName() const; + + /** + * @brief Provides plugin title. + * @return Title defined in plugin's metadata file with key "title" or (if not defined) the same value as getName(). + */ + QString getTitle() const; + + /** + * @brief Provides configuration object to use in ConfigDialog. + * @return This implementation always returns null. + */ + CfgMain* getMainUiConfig(); + + /** + * @brief Provides plugin description. + * @return Description as defined in plugin's metadata file with key "description", or null QString if not defined. + */ + QString getDescription() const; + + /** + * @brief Provides plugin numeric version. + * @return Version number as defined in plugin's metadata file with key "version", or 0 if not defined. + */ + int getVersion() const; + + /** + * @brief Converts plugin version to human readable format. + * @return Version in format X.Y.Z. + */ + QString getPrintableVersion() const; + + /** + * @brief Provides an author name. + * @return Author name as defined with in plugin's metadata file with key "author", or null QString if not defined. + */ + QString getAuthor() const; + + /** + * @brief Does nothing. + * @return Always true. + * + * This is a default (empty) implementation of init() for plugins. + */ + bool init(); + + /** + * @brief Does nothing. + * + * This is a default (empty) implementation of init() for plugins. + */ + void deinit(); + + /** + * @brief Loads metadata from given Json object. + * @param The metadata from json file. + * + * This is called by PluginManager. + */ + void loadMetaData(const QJsonObject& metaData); + + private: + /** + * @brief Extracts class meta information with given key. + * @param key Key to extract. + * @return Value of the meta information, or null if there's no information with given key. + * + * This is a helper method which queries Qt's meta object subsystem for class meta information defined with Q_CLASSINFO. + */ + const char* getMetaInfo(const QString& key) const; + + QHash<QString,QVariant> metaData; +}; + +#endif // GENERICPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/importplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/importplugin.h new file mode 100644 index 0000000..ff520cd --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/importplugin.h @@ -0,0 +1,132 @@ +#ifndef IMPORTPLUGIN_H +#define IMPORTPLUGIN_H + +#include "plugin.h" +#include "services/importmanager.h" + +class QIODevice; +class CfgMain; + +/** + * @brief Provides support for particular import format. + * + * All import methods in this class should report any warnings, error messages, etc through the NotifyManager, + * that is by using notifyError() and its family methods. + */ +class ImportPlugin : virtual public Plugin +{ + public: + /** + * @brief Pair of column name and its data type. + */ + typedef QPair<QString,QString> ColumnDefinition; + + /** + * @brief Used to show this plugin in the combo of data source types in the import dialog. + * @return String representing this plugin in the import dialog. + */ + virtual QString getDataSourceTypeName() const = 0; + + /** + * @brief Tells which standard import options should be available on to the user. + * @return OR-ed set of standard option enums. + */ + virtual ImportManager::StandardConfigFlags standardOptionsToEnable() const = 0; + + /** + * @brief Provides file name filter for file dialog. + * @return Filter compliant with QFileDialog documentation. + * + * If your plugin does not return ImportManager::FILE_NAME, this method can simply return QString::null. + * If your plugin does use input file name, then this method can (but don't have to) return file name filter + * to match expected files when user browses for the input file. + * + * The filter value (if not null) is passed directly to the QFileDialog. + */ + virtual QString getFileFilter() const = 0; + + /** + * @brief Called before each import that user makes. + * @param config Standard options configured by user in the import dialog. + * @return true if everything looks fine from plugin's perspective, or false otherwise. + * + * In case there were some problems at this step, plugin should return false, but before that it should tell what was wrong using NotifyManager's global shortcut + * method: notifyError(). + */ + virtual bool beforeImport(const ImportManager::StandardImportConfig& config) = 0; + + /** + * @brief Called after import process has been finished (successfully or not). + * + * Implement this method to clean up any resources that the plugin has initialized before. + */ + virtual void afterImport() = 0; + + /** + * @brief Provides list of columns (with their datatypes) for the data to be imported. + * @return List of columns, each column consists of column name and its data type definition. + * + * The ColumnDefinition is actually a QPair of two QString types. First in the pair is column name, second is column's data type, + * as a string representation. + * + * Let's say your plugin wants to import data that fits into 2 columns, first of <tt>INTEGER</tt> type and the second of <tt>VARCHAR(0, 5)</tt> type. + * You would write it like this: + * @code + * QList<ColumnDefinition> list; + * list << ColumnDefinition("column1", "INTEGER"); + * list << ColumnDefinition("column2", "VARCHAR (0, 5)"); + * return list; + * @endcode + */ + virtual QList<ColumnDefinition> getColumns() const = 0; + + /** + * @brief Provides next set of data from the data source. + * @return List of values, where number of elements must be equal to number of columns returned from getColumns(). + * + * This is essential import plugin method. It provides the data. + * This method simply provides next row of the data for a table. + * It will be called again and again, until it returns empty list, which will be interpreted as the end of data to import. + */ + virtual QList<QVariant> next() = 0; + + /** + * @brief Provides config object that holds configuration for importing. + * @return Config object, or null if the importing with this plugin is not configurable. + */ + virtual CfgMain* getConfig() = 0; + + /** + * @brief Provides name of the form to use for configuration of import dialog. + * @return Name of the form (toplevel QWidget in the ui file). + * + * If importing with this plugin is not configurable (i.e. getConfig() returns null), + * then this method is not even called, so it can return anything, just to satisfy method + * return type. In that case good idea is to always return QString::null. + * + * @see FormManager + */ + virtual QString getImportConfigFormName() const = 0; + + /** + * @brief Called when the UI expects any configuration options to be re-validated. + * @return true when validation was successful, or false if any error occured. + * + * When user interacts with the UI in a way that it doesn't change the config values, + * but it still requires some options to be re-validated, this method is called. + * + * It should validate any configuration values defined with CFG_CATEGORY and CFG_ENTRY + * and post the validation results by calling IMPORT_MANAGER->handleValidationFromPlugin() + * for every validated CfgEntry. + * + * This is also a good idea to connect to the CfgEntry::changed() signal for entries that should be validated + * and call this method from the slot, so any changes to the configuration values will be + * immediately validated and reflected on the UI. + * + * In this method you can also call IMPORT_MANAGER->configStateUpdateFromPlugin() to adjust options UI + * to the current config values. + */ + virtual bool validateOptions() = 0; +}; + +#endif // IMPORTPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/plugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/plugin.h new file mode 100644 index 0000000..183adf7 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/plugin.h @@ -0,0 +1,182 @@ +#ifndef PLUGIN_H +#define PLUGIN_H + +#include "coreSQLiteStudio_global.h" +#include <QString> +#include <QHash> +#include <QtPlugin> + +class PluginType; +class CfgMain; + +/** @file */ + +/** + * @brief General plugin interface. + * + * This is the top-most generic interface for SQLiteStudio plugins. + * It's based in Qt's plugins framework. Every SQLiteStudio plugin must + * implement this (or its descendant) interface. + * + * SQLiteStudio plugin is basicly class implementing this interface, + * compiled as shared library (*.dll, *.so, *.dylib). + * + * Apart from implementing Plugin interface, the plugin class must also declare ::SQLITESTUDIO_PLUGIN macro, like this: + * @code + * class MyPlugin : Plugin + * { + * Q_OBJECT + * + * SQLITESTUDIO_PLUGIN + * + * public: + * // ... + * }; + * @endcode + * + * Full tutorial for writting plugins is at: http://wiki.sqlitestudio.pl/index.php/Writting_plugins + * + * SQLiteStudio looks for plugins in following directories: + * <ul> + * <li><tt>{current_executable_dir}/plugins</tt> - a "plugins" subdirectory of the directory where application binary is placed,</li> + * <li><tt>{configuration_dir}/plugins</tt> - a "plugins" subdirectory of configuration directory detected and defined in Config,</li> + * <li><tt>{env_var:SQLITESTUDIO_PLUGINS}</tt> - environment variable with name "SQLITESTUDIO_PLUGINS",</li> + * <li><tt>{compile_time:PLUGINS_DIR}</tt> - compile time defined parameter's value of parameter with the name "PLUGINS_DIR".</li> + * </ul> + */ +class API_EXPORT Plugin +{ + public: + /** + * @brief Virtual destructor to make sure all plugins are destroyed correctly. + */ + virtual ~Plugin() {} + + /** + * @brief Gets name of the plugin. + * @return Name of the plugin. + * + * The name of the plugin is a kind of primary key for plugins. It has to be unique across all loaded plugins. + * An attempt to load two plugins with the same name will result in failed load of the second plugin. + * + * The name is a kind of internal plugin's name. It's designated for presenting to the user + * - for that purpose there is a getTitle(). + * + * It's a good practice to keep it as single word. Providing plugin's class name can be a good idea. + * + * BUG: Currently this implementation of this method has to always return the name of the plugin's main implementation class + * (like DbSqlite2), otherwise SQLiteStudio will either unable to load it, or dependencies to this plugin will fail. + * This has to do with PluginManagerImpl relying on "className" entry returned from QPluginLoader's metadata. + */ + virtual QString getName() const = 0; + + /** + * @brief Gets title for the plugin. + * @return Plugin title. + * + * This is plugin's name to be presented to the user. It can be multiple words name. It should be localized (translatable) text. + * It's used solely for presenting plugin to the user, nothing more. + */ + virtual QString getTitle() const = 0; + + /** + * @brief Provides name of the plugin's author. + * @return Author name. + * + * This is displayed in ConfigDialog when user clicks on Details button of the plugin. + */ + virtual QString getAuthor() const = 0; + + /** + * @brief Provides some details on what does the plugin. + * @return Plugin description. + * + * This is displayed in ConfigDialog when user clicks on Details button of the plugin. + */ + virtual QString getDescription() const = 0; + + /** + * @brief Provides plugin version number. + * @return Version number. + * + * Version number format can be picked by plugin developer, but it is recommended + * to use XXYYZZ, where XX is major version, YY is minor version and ZZ is patch version. + * Of course the XX can be single X if major version is less then 10. + * + * This would result in versions like: 10000 (for version 1.0.0), or 10102 (for version 1.1.2), + * or 123200 (for version 12.32.0). + * + * This is of course just a suggestion, you don't have to stick to it. Just keep in mind, + * that this number is used by SQLiteStudio to compare plugin versions. If there's a plugin with higher version, + * SQLiteStudio will propose to update it. + * + * The suggested format is also easier to convert to printable (string) version later in getPrintableVersion(). + */ + virtual int getVersion() const = 0; + + /** + * @brief Provides formatted version string. + * @return Version string. + * + * It provides string that represents version returned from getVersion() in a human-readable form. + * It's a good practice to return versions like "1.3.2", or "1.5", as they are easy to read. + * + * This version string is presented to the user. + */ + virtual QString getPrintableVersion() const = 0; + + /** + * @brief Initializes plugin just after it was loaded. + * @return true on success, or false otherwise. + * + * This is called as a first, just after plugin was loaded. If it returns false, + * then plugin loading is considered to be failed and gets unloaded. + * + * If this method returns false, then deinit() is not called. + */ + virtual bool init() = 0; + + /** + * @brief Deinitializes plugin that is about to be unloaded. + * + * This is called just before plugin is unloaded. It's called only when plugin was loaded + * successfully. It's NOT called when init() returned false. + */ + virtual void deinit() = 0; +}; + +/** + * @def SqliteStudioPluginInterface + * @brief SQLiteStudio plugin interface ID. + * + * This is an ID string for Qt's plugins framework. It's used by ::SQLITESTUDIO_PLUGIN macro. + * No need to use it directly. + */ +#define SqliteStudioPluginInterface "pl.sqlitestudio.Plugin/1.0" + +/** + * @def SQLITESTUDIO_PLUGIN + * @brief Defines class as a SQLiteStudio plugin + * + * Every class implementing SQLiteStudio plugin must have this declaration, + * otherwise SQLiteStudio won't be able to load the plugin. + * + * It has to be placed in class declaration: + * @code + * class MyPlugin : public QObject, public Plugin + * { + * Q_OBJECT + * SQLITESTUDIO_PLUGIN + * + * public: + * // ... + * } + * @endcode + */ +#define SQLITESTUDIO_PLUGIN(file)\ + Q_PLUGIN_METADATA(IID SqliteStudioPluginInterface FILE file) \ + Q_INTERFACES(Plugin) + +Q_DECLARE_INTERFACE(Plugin, SqliteStudioPluginInterface) + +#endif // PLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.cpp new file mode 100644 index 0000000..39988bd --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.cpp @@ -0,0 +1,45 @@ +#include "pluginsymbolresolver.h" +#include <QCoreApplication> +#include <QDir> + +PluginSymbolResolver::PluginSymbolResolver() +{ +} + +void PluginSymbolResolver::addFileNameMask(const QString &mask) +{ + nameFilters << mask; +} + +void PluginSymbolResolver::addLookupSubFolder(const QString &name) +{ + subFolders << name; +} + +bool PluginSymbolResolver::load() +{ + QStringList paths = qApp->libraryPaths(); + foreach (QString path, paths) + foreach (QString subFolder, subFolders) + paths << path + "/" + subFolder; + + foreach (QString path, paths) + { + QDir dir(path); + foreach (QString file, dir.entryList(nameFilters)) + { + lib.setFileName(path+"/"+file); + if (lib.load()) + break; + } + if (lib.isLoaded()) + break; + } + + return lib.isLoaded(); +} + +QFunctionPointer PluginSymbolResolver::resolve(const char *symbol) +{ + return lib.resolve(symbol); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.h b/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.h new file mode 100644 index 0000000..da6c62e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.h @@ -0,0 +1,24 @@ +#ifndef PLUGINSYMBOLRESOLVER_H +#define PLUGINSYMBOLRESOLVER_H + +#include "coreSQLiteStudio_global.h" +#include <QStringList> +#include <QLibrary> + +class API_EXPORT PluginSymbolResolver +{ + public: + PluginSymbolResolver(); + + void addFileNameMask(const QString& mask); + void addLookupSubFolder(const QString& name); + bool load(); + QFunctionPointer resolve(const char* symbol); + + private: + QStringList nameFilters; + QStringList subFolders; + QLibrary lib; +}; + +#endif // PLUGINSYMBOLRESOLVER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.cpp new file mode 100644 index 0000000..57c69c0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.cpp @@ -0,0 +1,52 @@ +#include "plugin.h" +#include "plugintype.h" +#include "services/pluginmanager.h" +#include <QDebug> + +PluginType::PluginType(const QString& title, const QString& form) : + title(title), configUiForm(form) +{ +} + + +PluginType::~PluginType() +{ +} + +QString PluginType::getName() const +{ + return name; +} + +void PluginType::setNativeName(const QString& nativeName) +{ + name = nativeName; + while (name.at(0).isDigit()) + name = name.mid(1); +} +QString PluginType::getTitle() const +{ + return title; +} + +QString PluginType::getConfigUiForm() const +{ + return configUiForm; +} + +QList<Plugin*> PluginType::getLoadedPlugins() const +{ + PluginType* type = const_cast<PluginType*>(this); + return PLUGINS->getLoadedPlugins(type); +} + +QStringList PluginType::getAllPluginNames() const +{ + PluginType* type = const_cast<PluginType*>(this); + return PLUGINS->getAllPluginNames(type); +} + +bool PluginType::nameLessThan(PluginType* type1, PluginType* type2) +{ + return type1->title.compare(type2->title) < 0; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.h b/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.h new file mode 100644 index 0000000..1002ac8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.h @@ -0,0 +1,63 @@ +#ifndef PLUGINTYPE_H +#define PLUGINTYPE_H + +#include "coreSQLiteStudio_global.h" +#include <QList> +#include <QString> +#include <QObject> + +class Plugin; + +template <class T> +class DefinedPluginType; + +class API_EXPORT PluginType +{ + public: + virtual ~PluginType(); + + QString getName() const; + QString getTitle() const; + QString getConfigUiForm() const; + QList<Plugin*> getLoadedPlugins() const; + QStringList getAllPluginNames() const; + + virtual bool test(Plugin* plugin) = 0; + + template <class T> + bool isForPluginType() + { + return dynamic_cast<const DefinedPluginType<T>*>(this) != nullptr; + } + + static bool nameLessThan(PluginType* type1, PluginType* type2); + + protected: + PluginType(const QString& title, const QString& form); + void setNativeName(const QString& nativeName); + + QString title; + QString configUiForm; + QString name; +}; + + +template <class T> +class DefinedPluginType : public PluginType +{ + friend class PluginManager; + + public: + bool test(Plugin* plugin) + { + return (dynamic_cast<T*>(plugin) != nullptr); + } + + protected: + DefinedPluginType(const QString& title, const QString& form) : PluginType(title, form) + { + setNativeName(typeid(T).name()); + } +}; + +#endif // PLUGINTYPE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.cpp new file mode 100644 index 0000000..75c213f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.cpp @@ -0,0 +1,48 @@ +#include "populateconstant.h" +#include "common/unused.h" + +PopulateConstant::PopulateConstant() +{ +} + +QString PopulateConstant::getTitle() const +{ + return tr("Constant", "populate constant plugin name"); +} + +PopulateEngine*PopulateConstant::createEngine() +{ + return new PopulateConstantEngine(); +} + +bool PopulateConstantEngine::beforePopulating(Db* db, const QString& table) +{ + UNUSED(db); + UNUSED(table); + return true; +} + +QVariant PopulateConstantEngine::nextValue(bool& nextValueError) +{ + UNUSED(nextValueError); + return cfg.PopulateConstant.Value.get(); +} + +void PopulateConstantEngine::afterPopulating() +{ +} + +CfgMain*PopulateConstantEngine::getConfig() +{ + return &cfg; +} + +QString PopulateConstantEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateConstantConfig"); +} + +bool PopulateConstantEngine::validateOptions() +{ + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.h new file mode 100644 index 0000000..1061519 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.h @@ -0,0 +1,44 @@ +#ifndef POPULATECONSTANT_H +#define POPULATECONSTANT_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" + +CFG_CATEGORIES(PopulateConstantConfig, + CFG_CATEGORY(PopulateConstant, + CFG_ENTRY(QString, Value, QString()) + ) +) + +class PopulateConstant : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Constant") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with a constant value.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateConstant(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateConstantEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateConstantConfig, cfg) +}; + +#endif // POPULATECONSTANT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.ui new file mode 100644 index 0000000..39e80e1 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.ui @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PopulateConstantConfig</class> + <widget class="QWidget" name="PopulateConstantConfig"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>77</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="valueGroup"> + <property name="title"> + <string>Constant value:</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLineEdit" name="valueEdit"> + <property name="cfg" stdset="0"> + <string>PopulateConstant.Value</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.cpp new file mode 100644 index 0000000..3d78de5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.cpp @@ -0,0 +1,94 @@ +#include "populatedictionary.h" +#include "services/populatemanager.h" +#include "services/notifymanager.h" +#include "common/unused.h" +#include <QFileInfo> +#include <QFile> +#include <QTextStream> + +PopulateDictionary::PopulateDictionary() +{ +} + +QString PopulateDictionary::getTitle() const +{ + return tr("Dictionary", "dictionary populating plugin name"); +} + +PopulateEngine*PopulateDictionary::createEngine() +{ + return new PopulateDictionaryEngine(); +} + +bool PopulateDictionaryEngine::beforePopulating(Db* db, const QString& table) +{ + UNUSED(db); + UNUSED(table); + QFile file(cfg.PopulateDictionary.File.get()); + if (!file.open(QIODevice::ReadOnly)) + { + notifyError(QObject::tr("Could not open dictionary file %1 for reading.").arg(cfg.PopulateDictionary.File.get())); + return false; + } + QTextStream stream(&file); + QString dataStr = stream.readAll(); + file.close(); + + if (cfg.PopulateDictionary.Lines.get()) + dictionary = dataStr.split("\n"); + else + dictionary = dataStr.split(QRegExp("\\s+")); + + if (dictionary.size() == 0) + dictionary << QString(); + + dictionaryPos = 0; + dictionarySize = dictionary.size(); + if (cfg.PopulateDictionary.Random.get()) + qsrand(QDateTime::currentDateTime().toTime_t()); + + return true; +} + +QVariant PopulateDictionaryEngine::nextValue(bool& nextValueError) +{ + UNUSED(nextValueError); + if (cfg.PopulateDictionary.Random.get()) + { + int r = qrand() % dictionarySize; + return dictionary[r]; + } + else + { + if (dictionaryPos >= dictionarySize) + dictionaryPos = 0; + + return dictionary[dictionaryPos++]; + } +} + +void PopulateDictionaryEngine::afterPopulating() +{ + dictionary.clear(); + dictionarySize = 0; + dictionaryPos = 0; +} + +CfgMain* PopulateDictionaryEngine::getConfig() +{ + return &cfg; +} + +QString PopulateDictionaryEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateDictionaryConfig"); +} + +bool PopulateDictionaryEngine::validateOptions() +{ + QFileInfo fi(cfg.PopulateDictionary.File.get()); + bool fileValid = fi.exists() && fi.isReadable() && !fi.isDir(); + POPULATE_MANAGER->handleValidationFromPlugin(fileValid, cfg.PopulateDictionary.File, QObject::tr("Dictionary file must exist and be readable.")); + + return fileValid; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.h new file mode 100644 index 0000000..f5e754f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.h @@ -0,0 +1,52 @@ +#ifndef POPULATEDICTIONARY_H +#define POPULATEDICTIONARY_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" + +class QFile; +class QTextStream; + +CFG_CATEGORIES(PopulateDictionaryConfig, + CFG_CATEGORY(PopulateDictionary, + CFG_ENTRY(QString, File, QString()) + CFG_ENTRY(bool, Lines, false) + CFG_ENTRY(bool, Random, false) + ) +) + +class PopulateDictionary : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Dictionary") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with values from a dictionary file.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateDictionary(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateDictionaryEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateDictionaryConfig, cfg) + QStringList dictionary; + int dictionarySize = 0; + int dictionaryPos = 0; +}; + +#endif // POPULATEDICTIONARY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.ui new file mode 100644 index 0000000..f99491f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.ui @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PopulateDictionaryConfig</class> + <widget class="QWidget" name="PopulateDictionaryConfig"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>307</width> + <height>255</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="fileGroup"> + <property name="title"> + <string>Dictionary file</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="FileEdit" name="fileEdit" native="true"> + <property name="cfg" stdset="0"> + <string>PopulateDictionary.File</string> + </property> + <property name="dialogTitle" stdset="0"> + <string>Pick dictionary file</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="separatorGroup"> + <property name="title"> + <string>Word separator</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="ConfigRadioButton" name="whitespaceRadio"> + <property name="cfg" stdset="0"> + <string>PopulateDictionary.Lines</string> + </property> + <property name="text"> + <string>Whitespace</string> + </property> + <property name="assignedValue" stdset="0"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="ConfigRadioButton" name="libeBreakRadio"> + <property name="cfg" stdset="0"> + <string>PopulateDictionary.Lines</string> + </property> + <property name="text"> + <string>Line break</string> + </property> + <property name="assignedValue" stdset="0"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="methodGroup"> + <property name="title"> + <string>Method of using words</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="ConfigRadioButton" name="orderedRadio"> + <property name="cfg" stdset="0"> + <string>PopulateDictionary.Random</string> + </property> + <property name="text"> + <string>Ordered</string> + </property> + <property name="assignedValue" stdset="0"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="ConfigRadioButton" name="randomlyRadio"> + <property name="cfg" stdset="0"> + <string>PopulateDictionary.Random</string> + </property> + <property name="text"> + <string>Randomly</string> + </property> + <property name="assignedValue" stdset="0"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ConfigRadioButton</class> + <extends>QRadioButton</extends> + <header>common/configradiobutton.h</header> + </customwidget> + <customwidget> + <class>FileEdit</class> + <extends>QWidget</extends> + <header>common/fileedit.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populateplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populateplugin.h new file mode 100644 index 0000000..1a1db43 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populateplugin.h @@ -0,0 +1,70 @@ +#ifndef POPULATEPLUGIN_H +#define POPULATEPLUGIN_H + +#include "coreSQLiteStudio_global.h" +#include "plugins/plugin.h" + +class CfgMain; +class PopulateEngine; +class Db; + +class API_EXPORT PopulatePlugin : virtual public Plugin +{ + public: + virtual PopulateEngine* createEngine() = 0; +}; + +class API_EXPORT PopulateEngine +{ + public: + virtual ~PopulateEngine() {} + + virtual bool beforePopulating(Db* db, const QString& table) = 0; + virtual QVariant nextValue(bool& nextValueError) = 0; + virtual void afterPopulating() = 0; + + /** + * @brief Provides config object that holds configuration for populating. + * @return Config object, or null if the importing with this plugin is not configurable. + */ + virtual CfgMain* getConfig() = 0; + + /** + * @brief Provides name of the form to use for configuration of this plugin in the populate dialog. + * @return Name of the form (toplevel QWidget in the ui file). + * + * If populating with this plugin is not configurable (i.e. getConfig() returns null), + * then this method is not even called, so it can return anything, just to satisfy method + * return type. In that case good idea is to always return QString::null. + * + * @see FormManager + */ + virtual QString getPopulateConfigFormName() const = 0; + + /** + * @brief Called when the UI expects any configuration options to be re-validated. + * @return true if the validation was successful, or false otherwise. + * + * When user interacts with the UI in a way that it doesn't change the config values, + * but it still requires some options to be re-validated, this method is called. + * + * It should validate any configuration values defined with CFG_CATEGORY and CFG_ENTRY + * and post the validation results by calling POPULATE_MANAGER->handleValidationFromPlugin() + * for every validated CfgEntry. + * + * This is also a good idea to connect to the CfgEntry::changed() signal for entries that should be validated + * and call this method from the slot, so any changes to the configuration values will be + * immediately validated and reflected on the UI. + * + * In this method you can also call POPULATE_MANAGER->configStateUpdateFromPlugin() to adjust options UI + * to the current config values. + * + * Apart from calling POPULATE_MANAGER with validation results, it should also return true or false, + * according to validation results. The return value is used by the PopulateDialog to tell if the plugin + * is currently configured correctly, without going into details, without handling signals from POPULATE_MANAGER. + */ + virtual bool validateOptions() = 0; +}; + + +#endif // POPULATEPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.cpp new file mode 100644 index 0000000..3258bbc --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.cpp @@ -0,0 +1,55 @@ +#include "populaterandom.h" +#include "services/populatemanager.h" +#include "common/unused.h" +#include <QDateTime> + +PopulateRandom::PopulateRandom() +{ +} + +QString PopulateRandom::getTitle() const +{ + return tr("Random number"); +} + +PopulateEngine* PopulateRandom::createEngine() +{ + return new PopulateRandomEngine(); +} + +bool PopulateRandomEngine::beforePopulating(Db* db, const QString& table) +{ + UNUSED(db); + UNUSED(table); + qsrand(QDateTime::currentDateTime().toTime_t()); + range = cfg.PopulateRandom.MaxValue.get() - cfg.PopulateRandom.MinValue.get() + 1; + return (range > 0); +} + +QVariant PopulateRandomEngine::nextValue(bool& nextValueError) +{ + UNUSED(nextValueError); + QString randValue = QString::number((qrand() % range) + cfg.PopulateRandom.MinValue.get()); + return (cfg.PopulateRandom.Prefix.get() + randValue + cfg.PopulateRandom.Suffix.get()); +} + +void PopulateRandomEngine::afterPopulating() +{ +} + +CfgMain* PopulateRandomEngine::getConfig() +{ + return &cfg; +} + +QString PopulateRandomEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateRandomConfig"); +} + +bool PopulateRandomEngine::validateOptions() +{ + bool valid = (cfg.PopulateRandom.MinValue.get() <= cfg.PopulateRandom.MaxValue.get()); + POPULATE_MANAGER->handleValidationFromPlugin(valid, cfg.PopulateRandom.MaxValue, QObject::tr("Maximum value cannot be less than minimum value.")); + return valid; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.h new file mode 100644 index 0000000..f4e9feb --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.h @@ -0,0 +1,47 @@ +#ifndef POPULATERANDOM_H +#define POPULATERANDOM_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" + +CFG_CATEGORIES(PopulateRandomConfig, + CFG_CATEGORY(PopulateRandom, + CFG_ENTRY(int, MinValue, 0) + CFG_ENTRY(int, MaxValue, 99999999) + CFG_ENTRY(QString, Prefix, QString()) + CFG_ENTRY(QString, Suffix, QString()) + ) +) + +class PopulateRandom : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Random") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with random numbers.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateRandom(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateRandomEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateRandomConfig, cfg) + int range; +}; +#endif // POPULATERANDOM_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.ui new file mode 100644 index 0000000..fb304ea --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.ui @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PopulateRandomConfig</class> + <widget class="QWidget" name="PopulateRandomConfig"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>144</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="2" column="0"> + <widget class="QGroupBox" name="prefixGroup"> + <property name="title"> + <string>Constant prefix</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QLineEdit" name="prefixEdit"> + <property name="cfg" stdset="0"> + <string>PopulateRandom.Prefix</string> + </property> + <property name="placeholderText"> + <string>No prefix</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="0"> + <widget class="QGroupBox" name="minGroup"> + <property name="title"> + <string>Minimum value</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QSpinBox" name="minSpin"> + <property name="cfg" stdset="0"> + <string>PopulateRandom.MinValue</string> + </property> + <property name="minimum"> + <number>-999999999</number> + </property> + <property name="maximum"> + <number>999999999</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="1"> + <widget class="QGroupBox" name="maxGroup"> + <property name="title"> + <string>Maximum value</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QSpinBox" name="maxSpin"> + <property name="cfg" stdset="0"> + <string>PopulateRandom.MaxValue</string> + </property> + <property name="minimum"> + <number>-999999999</number> + </property> + <property name="maximum"> + <number>999999999</number> + </property> + <property name="value"> + <number>999999999</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="1"> + <widget class="QGroupBox" name="suffixGroup"> + <property name="title"> + <string>Constant suffix</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLineEdit" name="suffixEdit"> + <property name="cfg" stdset="0"> + <string>PopulateRandom.Suffix</string> + </property> + <property name="placeholderText"> + <string>No suffix</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.cpp new file mode 100644 index 0000000..d9f148a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.cpp @@ -0,0 +1,91 @@ +#include "populaterandomtext.h" +#include "common/utils.h" +#include "common/unused.h" +#include "services/populatemanager.h" + +PopulateRandomText::PopulateRandomText() +{ +} + +QString PopulateRandomText::getTitle() const +{ + return tr("Random text"); +} + +PopulateEngine* PopulateRandomText::createEngine() +{ + return new PopulateRandomTextEngine(); +} + +bool PopulateRandomTextEngine::beforePopulating(Db* db, const QString& table) +{ + UNUSED(db); + UNUSED(table); + qsrand(QDateTime::currentDateTime().toTime_t()); + range = cfg.PopulateRandomText.MaxLength.get() - cfg.PopulateRandomText.MinLength.get() + 1; + + chars = ""; + + if (cfg.PopulateRandomText.UseCustomSets.get()) + { + chars = cfg.PopulateRandomText.CustomCharacters.get(); + } + else if (cfg.PopulateRandomText.IncludeBinary.get()) + { + for (int i = 0; i < 256; i++) + chars.append(QChar((char)i)); + } + else + { + if (cfg.PopulateRandomText.IncludeAlpha.get()) + chars += QStringLiteral("abcdefghijklmnopqrstuvwxyz"); + + if (cfg.PopulateRandomText.IncludeNumeric.get()) + chars += QStringLiteral("0123456789"); + + if (cfg.PopulateRandomText.IncludeWhitespace.get()) + chars += QStringLiteral(" \t\n"); + } + + return !chars.isEmpty(); +} + +QVariant PopulateRandomTextEngine::nextValue(bool& nextValueError) +{ + UNUSED(nextValueError); + int lgt = (qrand() % range) + cfg.PopulateRandomText.MinLength.get(); + return randStr(lgt, chars); +} + +void PopulateRandomTextEngine::afterPopulating() +{ +} + +CfgMain* PopulateRandomTextEngine::getConfig() +{ + return &cfg; +} + +QString PopulateRandomTextEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateRandomTextConfig"); +} + +bool PopulateRandomTextEngine::validateOptions() +{ + bool rangeValid = (cfg.PopulateRandomText.MinLength.get() <= cfg.PopulateRandomText.MaxLength.get()); + POPULATE_MANAGER->handleValidationFromPlugin(rangeValid, cfg.PopulateRandomText.MaxLength, QObject::tr("Maximum length cannot be less than minimum length.")); + + bool useCustom = cfg.PopulateRandomText.UseCustomSets.get(); + bool useBinary = cfg.PopulateRandomText.IncludeBinary.get(); + POPULATE_MANAGER->updateVisibilityAndEnabled(cfg.PopulateRandomText.IncludeAlpha, true, !useCustom && !useBinary); + POPULATE_MANAGER->updateVisibilityAndEnabled(cfg.PopulateRandomText.IncludeNumeric, true, !useCustom && !useBinary); + POPULATE_MANAGER->updateVisibilityAndEnabled(cfg.PopulateRandomText.IncludeWhitespace, true, !useCustom && !useBinary); + POPULATE_MANAGER->updateVisibilityAndEnabled(cfg.PopulateRandomText.IncludeBinary, true, !useCustom); + POPULATE_MANAGER->updateVisibilityAndEnabled(cfg.PopulateRandomText.CustomCharacters, true, useCustom); + + bool customValid = !useCustom || !cfg.PopulateRandomText.CustomCharacters.get().isEmpty(); + POPULATE_MANAGER->handleValidationFromPlugin(customValid, cfg.PopulateRandomText.CustomCharacters, QObject::tr("Custom character set cannot be empty.")); + + return rangeValid && customValid; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.h new file mode 100644 index 0000000..892b302 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.h @@ -0,0 +1,52 @@ +#ifndef POPULATERANDOMTEXT_H +#define POPULATERANDOMTEXT_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" + +CFG_CATEGORIES(PopulateRandomTextConfig, + CFG_CATEGORY(PopulateRandomText, + CFG_ENTRY(int, MinLength, 4) + CFG_ENTRY(int, MaxLength, 20) + CFG_ENTRY(bool, IncludeAlpha, true) + CFG_ENTRY(bool, IncludeNumeric, true) + CFG_ENTRY(bool, IncludeWhitespace, true) + CFG_ENTRY(bool, IncludeBinary, false) + CFG_ENTRY(bool, UseCustomSets, false) + CFG_ENTRY(QString, CustomCharacters, QString()) + ) +) +class PopulateRandomText : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Random text") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with random characters.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateRandomText(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateRandomTextEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateRandomTextConfig, cfg) + int range; + QString chars; +}; + +#endif // POPULATERANDOMTEXT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.ui new file mode 100644 index 0000000..28febde --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.ui @@ -0,0 +1,181 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PopulateRandomTextConfig</class> + <widget class="QWidget" name="PopulateRandomTextConfig"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>367</width> + <height>291</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0" colspan="2"> + <widget class="ConfigRadioButton" name="commonSetRadio"> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.UseCustomSets</string> + </property> + <property name="text"> + <string>Use characters from common sets:</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="assignedValue" stdset="0"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QGroupBox" name="minLengthGroup"> + <property name="title"> + <string>Minimum length</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QSpinBox" name="minLengthSpin"> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.MinLength</string> + </property> + <property name="maximum"> + <number>999999999</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QFrame" name="commonSetFrame"> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QCheckBox" name="alphaCheck"> + <property name="toolTip"> + <string>Letters from a to z.</string> + </property> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.IncludeAlpha</string> + </property> + <property name="text"> + <string>Alpha</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="numericCheck"> + <property name="toolTip"> + <string>Numbers from 0 to 9.</string> + </property> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.IncludeNumeric</string> + </property> + <property name="text"> + <string>Numeric</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="whitespaceCheck"> + <property name="toolTip"> + <string>A whitespace, a tab and a new line character.</string> + </property> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.IncludeWhitespace</string> + </property> + <property name="text"> + <string>Whitespace</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="binaryCheck"> + <property name="toolTip"> + <string>Includes all above and all others.</string> + </property> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.IncludeBinary</string> + </property> + <property name="text"> + <string>Binary</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <widget class="ConfigRadioButton" name="customSetRadio"> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.UseCustomSets</string> + </property> + <property name="text"> + <string>Use characters from my custom set:</string> + </property> + <property name="assignedValue" stdset="0"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QGroupBox" name="maxLengthGroup"> + <property name="title"> + <string>Maximum length</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QSpinBox" name="maxLengthSpin"> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.MaxLength</string> + </property> + <property name="maximum"> + <number>999999999</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="4" column="0" colspan="2"> + <widget class="QFrame" name="customSetFrame"> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLineEdit" name="customSetEdit"> + <property name="toolTip"> + <string>If you type some character multiple times, it's more likely to be used.</string> + </property> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.CustomCharacters</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ConfigRadioButton</class> + <extends>QRadioButton</extends> + <header>common/configradiobutton.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.cpp new file mode 100644 index 0000000..79a8ac1 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.cpp @@ -0,0 +1,125 @@ +#include "populatescript.h" +#include "common/unused.h" +#include "services/populatemanager.h" +#include "services/pluginmanager.h" +#include "services/notifymanager.h" + +PopulateScript::PopulateScript() +{ +} + +QString PopulateScript::getTitle() const +{ + return tr("Script"); +} + +PopulateEngine* PopulateScript::createEngine() +{ + return new PopulateScriptEngine(); +} + +bool PopulateScriptEngine::beforePopulating(Db* db, const QString& table) +{ + this->db = db; + this->table = table; + + evalArgs = {db->getName(), table}; + + scriptingPlugin = nullptr; + for (ScriptingPlugin* plugin : PLUGINS->getLoadedPlugins<ScriptingPlugin>()) + { + if (plugin->getLanguage() == cfg.PopulateScript.Language.get()) + { + scriptingPlugin = plugin; + break; + } + } + + if (!scriptingPlugin) + { + notifyError(QObject::tr("Could not find plugin to support scripting language: %1").arg(cfg.PopulateScript.Language.get())); + return false; + } + + dbAwarePlugin = dynamic_cast<DbAwareScriptingPlugin*>(scriptingPlugin); + + context = scriptingPlugin->createContext(); + + QString initCode = cfg.PopulateScript.InitCode.get(); + if (!initCode.trimmed().isEmpty()) + { + if (dbAwarePlugin) + dbAwarePlugin->evaluate(context, initCode, evalArgs, db); + else + scriptingPlugin->evaluate(context, initCode, evalArgs); + + if (scriptingPlugin->hasError(context)) + { + notifyError(QObject::tr("Error while executing populating initial code: %1").arg(scriptingPlugin->getErrorMessage(context))); + releaseContext(); + return false; + } + } + + rowCnt = 1; + evalArgs << rowCnt; + + return true; +} + +QVariant PopulateScriptEngine::nextValue(bool& nextValueError) +{ + QVariant result; + if (dbAwarePlugin) + result = dbAwarePlugin->evaluate(context, cfg.PopulateScript.Code.get(), evalArgs, db); + else + result = scriptingPlugin->evaluate(context, cfg.PopulateScript.Code.get(), evalArgs); + + if (scriptingPlugin->hasError(context)) + { + notifyError(QObject::tr("Error while executing populating code: %1").arg(scriptingPlugin->getErrorMessage(context))); + releaseContext(); + nextValueError = true; + return QVariant(); + } + + evalArgs[2] = ++rowCnt; + + return result; +} + +void PopulateScriptEngine::afterPopulating() +{ + releaseContext(); +} + +CfgMain* PopulateScriptEngine::getConfig() +{ + return &cfg; +} + +QString PopulateScriptEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateScriptConfig"); +} + +bool PopulateScriptEngine::validateOptions() +{ + bool langValid = !cfg.PopulateScript.Language.get().isEmpty(); + bool codeValid = !cfg.PopulateScript.Code.get().trimmed().isEmpty(); + QString lang = cfg.PopulateScript.Language.get(); + + POPULATE_MANAGER->handleValidationFromPlugin(langValid, cfg.PopulateScript.Language, QObject::tr("Select implementation language.")); + POPULATE_MANAGER->handleValidationFromPlugin(codeValid, cfg.PopulateScript.Code, QObject::tr("Implementation code cannot be empty.")); + + POPULATE_MANAGER->propertySetFromPlugin(cfg.PopulateScript.InitCode, PluginServiceBase::LANG_PROPERTY_NAME, lang); + POPULATE_MANAGER->propertySetFromPlugin(cfg.PopulateScript.Code, PluginServiceBase::LANG_PROPERTY_NAME, lang); + + return langValid && codeValid; +} + +void PopulateScriptEngine::releaseContext() +{ + scriptingPlugin->releaseContext(context); + context = nullptr; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.h new file mode 100644 index 0000000..8870b67 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.h @@ -0,0 +1,63 @@ +#ifndef POPULATESCRIPT_H +#define POPULATESCRIPT_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" +#include "scriptingplugin.h" + +CFG_CATEGORIES(PopulateScriptConfig, + CFG_CATEGORY(PopulateScript, + CFG_ENTRY(QString, Language, QString()) + CFG_ENTRY(QString, InitCode, QString()) + CFG_ENTRY(QString, Code, QString()) + ) +) + +/** + * @brief Populate from evaluated script code + * + * Initial code evaluation gets 2 arguments - the db name and the table name. + * Each evaluation of per-step code gets 3 arguments, 2 of them just like above + * and the 3rd is number of the row being currently populated (starting from 1). + */ +class PopulateScript : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Constant") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with a constant value.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateScript(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateScriptEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateScriptConfig, cfg) + void releaseContext(); + + ScriptingPlugin* scriptingPlugin = nullptr; + DbAwareScriptingPlugin* dbAwarePlugin = nullptr; + ScriptingPlugin::Context* context = nullptr; + Db* db = nullptr; + QString table; + int rowCnt = 0; + QList<QVariant> evalArgs; +}; + +#endif // POPULATESCRIPT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.ui new file mode 100644 index 0000000..8d37994 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.ui @@ -0,0 +1,112 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PopulateScriptConfig</class> + <widget class="QWidget" name="PopulateScriptConfig"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>443</width> + <height>362</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <property name="initialSize" stdset="0"> + <size> + <width>440</width> + <height>360</height> + </size> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0" colspan="2"> + <widget class="QGroupBox" name="initCodeGroup"> + <property name="title"> + <string>Initialization code (optional)</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QPlainTextEdit" name="initCodeEdit"> + <property name="cfg" stdset="0"> + <string>PopulateScript.InitCode</string> + </property> + <property name="scriptingEdit" stdset="0"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QGroupBox" name="codeGroup"> + <property name="title"> + <string>Per step code</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QPlainTextEdit" name="codeEdit"> + <property name="cfg" stdset="0"> + <string>PopulateScript.Code</string> + </property> + <property name="scriptingEdit" stdset="0"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="0"> + <widget class="QGroupBox" name="langGroup"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Language</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QComboBox" name="langCombo"> + <property name="cfg" stdset="0"> + <string>PopulateScript.Language</string> + </property> + <property name="ScriptingLangCombo" stdset="0"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="1"> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Help</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QToolButton" name="toolButton"> + <property name="text"> + <string>...</string> + </property> + <property name="openUrl" stdset="0"> + <string>http://sqlitestudio.pl/wiki/index.php/Official_plugins#Script_.28built-in.29</string> + </property> + <property name="customIcon" stdset="0"> + <string>help</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.cpp new file mode 100644 index 0000000..a0ad94e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.cpp @@ -0,0 +1,53 @@ +#include "populatesequence.h" +#include "common/global.h" +#include "services/populatemanager.h" +#include "common/unused.h" +#include <QVariant> + +PopulateSequence::PopulateSequence() +{ +} + +QString PopulateSequence::getTitle() const +{ + return tr("Sequence"); +} + +PopulateEngine* PopulateSequence::createEngine() +{ + return new PopulateSequenceEngine(); +} + +bool PopulateSequenceEngine::beforePopulating(Db* db, const QString& table) +{ + UNUSED(db); + UNUSED(table); + seq = cfg.PopulateSequence.StartValue.get(); + step = cfg.PopulateSequence.Step.get(); + return true; +} + +QVariant PopulateSequenceEngine::nextValue(bool& nextValueError) +{ + UNUSED(nextValueError); + return seq += step; +} + +void PopulateSequenceEngine::afterPopulating() +{ +} + +CfgMain* PopulateSequenceEngine::getConfig() +{ + return &cfg; +} + +QString PopulateSequenceEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateSequenceConfig"); +} + +bool PopulateSequenceEngine::validateOptions() +{ + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.h new file mode 100644 index 0000000..5435477 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.h @@ -0,0 +1,47 @@ +#ifndef POPULATESEQUENCE_H +#define POPULATESEQUENCE_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" + +CFG_CATEGORIES(PopulateSequenceConfig, + CFG_CATEGORY(PopulateSequence, + CFG_ENTRY(int, StartValue, 0) + CFG_ENTRY(int, Step, 1) + ) +) + +class PopulateSequence : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Sequence") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with sequenced values.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateSequence(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateSequenceEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateSequenceConfig, cfg) + qint64 seq = 0; + qint64 step = 1; +}; + +#endif // POPULATESEQUENCE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.ui new file mode 100644 index 0000000..231af85 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.ui @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PopulateSequenceConfig</class> + <widget class="QWidget" name="PopulateSequenceConfig"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>279</width> + <height>67</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="1"> + <widget class="QSpinBox" name="startValueSpin"> + <property name="cfg" stdset="0"> + <string>PopulateSequence.StartValue</string> + </property> + <property name="minimum"> + <number>-99999999</number> + </property> + <property name="maximum"> + <number>99999999</number> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="startValueLabel"> + <property name="text"> + <string>Start value:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="stepSpin"> + <property name="cfg" stdset="0"> + <string>PopulateSequence.Step</string> + </property> + <property name="minimum"> + <number>-999999</number> + </property> + <property name="maximum"> + <number>999999</number> + </property> + <property name="value"> + <number>1</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="stepLabel"> + <property name="text"> + <string>Step:</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingplugin.h new file mode 100644 index 0000000..d081073 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingplugin.h @@ -0,0 +1,50 @@ +#ifndef SCRIPTINGPLUGIN_H +#define SCRIPTINGPLUGIN_H + +#include "plugin.h" +#include <QVariant> + +class Db; + +class ScriptingPlugin : virtual public Plugin +{ + public: + class Context + { + public: + virtual ~Context() {} + }; + + virtual QString getLanguage() const = 0; + virtual Context* createContext() = 0; + virtual void releaseContext(Context* context) = 0; + virtual void resetContext(Context* context) = 0; + virtual void setVariable(Context* context, const QString& name, const QVariant& value) = 0; + virtual QVariant getVariable(Context* context, const QString& name) = 0; + virtual QVariant evaluate(Context* context, const QString& code, const QList<QVariant>& args = QList<QVariant>()) = 0; + virtual bool hasError(Context* context) const = 0; + virtual QString getErrorMessage(Context* context) const = 0; + virtual QVariant evaluate(const QString& code, const QList<QVariant>& args = QList<QVariant>(), QString* errorMessage = nullptr) = 0; + virtual QString getIconPath() const = 0; +}; + +class DbAwareScriptingPlugin : public ScriptingPlugin +{ + public: + virtual QVariant evaluate(Context* context, const QString& code, const QList<QVariant>& args, Db* db, bool locking = false) = 0; + virtual QVariant evaluate(const QString& code, const QList<QVariant>& args, Db* db, bool locking = false, QString* errorMessage = nullptr) = 0; + + QVariant evaluate(Context* context, const QString& code, const QList<QVariant>& args) + { + return evaluate(context, code, args, nullptr, true); + } + + QVariant evaluate(const QString& code, const QList<QVariant>& args, QString* errorMessage = nullptr) + { + return evaluate(code, args, nullptr, true, errorMessage); + } +}; + +Q_DECLARE_METATYPE(ScriptingPlugin::Context*) + +#endif // SCRIPTINGPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.cpp new file mode 100644 index 0000000..334c0cc --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.cpp @@ -0,0 +1,276 @@ +#include "scriptingqt.h" +#include "common/unused.h" +#include "common/global.h" +#include "scriptingqtdbproxy.h" +#include <QScriptEngine> +#include <QMutex> +#include <QMutexLocker> +#include <QDebug> + +static QScriptValue scriptingQtDebug(QScriptContext *context, QScriptEngine *engine) +{ + UNUSED(engine); + QStringList args; + for (int i = 0; i < context->argumentCount(); i++) + args << context->argument(i).toString(); + + qDebug() << "[ScriptingQt]" << args; + return QScriptValue(); +} + +ScriptingQt::ScriptingQt() +{ + mainEngineMutex = new QMutex(); +} + +ScriptingQt::~ScriptingQt() +{ + safe_delete(mainEngineMutex); +} + +QString ScriptingQt::getLanguage() const +{ + return QStringLiteral("QtScript"); +} + +ScriptingPlugin::Context* ScriptingQt::createContext() +{ + ContextQt* ctx = new ContextQt; + ctx->engine->pushContext(); + contexts << ctx; + return ctx; +} + +void ScriptingQt::releaseContext(ScriptingPlugin::Context* context) +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return; + + contexts.removeOne(ctx); + delete ctx; +} + +void ScriptingQt::resetContext(ScriptingPlugin::Context* context) +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return; + + ctx->engine->popContext(); + ctx->engine->pushContext(); +} + +QVariant ScriptingQt::evaluate(const QString& code, const QList<QVariant>& args, Db* db, bool locking, QString* errorMessage) +{ + QMutexLocker locker(mainEngineMutex); + + // Enter a new context + QScriptContext* engineContext = mainContext->engine->pushContext(); + + // Call the function + QVariant result = evaluate(mainContext, engineContext, code, args, db, locking); + + // Handle errors + if (!mainContext->error.isEmpty()) + *errorMessage = mainContext->error; + + // Leave the context to reset "this". + mainContext->engine->popContext(); + + return result; +} + +QVariant ScriptingQt::evaluate(ScriptingPlugin::Context* context, const QString& code, const QList<QVariant>& args, Db* db, bool locking) +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return QVariant(); + + return evaluate(ctx, ctx->engine->currentContext(), code, args, db, locking); +} + +QVariant ScriptingQt::evaluate(ContextQt* ctx, QScriptContext* engineContext, const QString& code, const QList<QVariant>& args, Db* db, bool locking) +{ + // Define function to call + QScriptValue functionValue = getFunctionValue(ctx, code); + + // Db for this evaluation + ctx->dbProxy->setDb(db); + ctx->dbProxy->setUseDbLocking(locking); + + // Call the function + QScriptValue result; + if (args.size() > 0) + result = functionValue.call(engineContext->activationObject(), ctx->engine->toScriptValue(args)); + else + result = functionValue.call(engineContext->activationObject()); + + // Handle errors + ctx->error.clear(); + if (ctx->engine->hasUncaughtException()) + ctx->error = ctx->engine->uncaughtException().toString(); + + ctx->dbProxy->setDb(nullptr); + ctx->dbProxy->setUseDbLocking(false); + + return convertVariant(result.toVariant()); +} + +QVariant ScriptingQt::convertVariant(const QVariant& value, bool wrapStrings) +{ + switch (value.type()) + { + case QVariant::Hash: + { + QHash<QString, QVariant> hash = value.toHash(); + QHashIterator<QString, QVariant> it(hash); + QStringList list; + while (it.hasNext()) + { + it.next(); + list << it.key() + ": " + convertVariant(it.value(), true).toString(); + } + return "{" + list.join(", ") + "}"; + } + case QVariant::Map: + { + QMap<QString, QVariant> map = value.toMap(); + QMapIterator<QString, QVariant> it(map); + QStringList list; + while (it.hasNext()) + { + it.next(); + list << it.key() + ": " + convertVariant(it.value(), true).toString(); + } + return "{" + list.join(", ") + "}"; + } + case QVariant::List: + { + QStringList list; + for (const QVariant& var : value.toList()) + list << convertVariant(var, true).toString(); + + return "[" + list.join(", ") + "]"; + } + case QVariant::StringList: + { + return "[\"" + value.toStringList().join("\", \"") + "\"]"; + } + case QVariant::String: + { + if (wrapStrings) + return "\"" + value.toString() + "\""; + + break; + } + default: + break; + } + return value; +} + +void ScriptingQt::setVariable(ScriptingPlugin::Context* context, const QString& name, const QVariant& value) +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return; + + ctx->engine->globalObject().setProperty(name, ctx->engine->newVariant(value)); +} + +QVariant ScriptingQt::getVariable(ScriptingPlugin::Context* context, const QString& name) +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return QVariant(); + + QScriptValue value = ctx->engine->globalObject().property(name); + return convertVariant(value.toVariant()); +} + +bool ScriptingQt::hasError(ScriptingPlugin::Context* context) const +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return false; + + return !ctx->error.isEmpty(); +} + +QString ScriptingQt::getErrorMessage(ScriptingPlugin::Context* context) const +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return QString::null; + + return ctx->error; +} + +QString ScriptingQt::getIconPath() const +{ + return ":/images/plugins/scriptingqt.png"; +} + +bool ScriptingQt::init() +{ + QMutexLocker locker(mainEngineMutex); + mainContext = new ContextQt; + return true; +} + +void ScriptingQt::deinit() +{ + foreach (Context* ctx, contexts) + delete ctx; + + contexts.clear(); + + QMutexLocker locker(mainEngineMutex); + safe_delete(mainContext); +} + +ScriptingQt::ContextQt* ScriptingQt::getContext(ScriptingPlugin::Context* context) const +{ + ContextQt* ctx = dynamic_cast<ContextQt*>(context); + if (!ctx) + qDebug() << "Invalid context passed to ScriptingQt:" << context; + + return ctx; +} + +QScriptValue ScriptingQt::getFunctionValue(ContextQt* ctx, const QString& code) +{ + static const QString fnDef = QStringLiteral("(function () {%1\n})"); + + QScriptProgram* prog = nullptr; + if (!ctx->scriptCache.contains(code)) + { + prog = new QScriptProgram(fnDef.arg(code)); + ctx->scriptCache.insert(code, prog); + } + else + { + prog = ctx->scriptCache[code]; + } + return ctx->engine->evaluate(*prog); +} + +ScriptingQt::ContextQt::ContextQt() +{ + engine = new QScriptEngine(); + + dbProxy = new ScriptingQtDbProxy(); + dbProxyScriptValue = engine->newQObject(dbProxy, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater); + + engine->globalObject().setProperty("debug", engine->newFunction(scriptingQtDebug)); + engine->globalObject().setProperty("db", dbProxyScriptValue); + + scriptCache.setMaxCost(cacheSize); +} + +ScriptingQt::ContextQt::~ContextQt() +{ + safe_delete(engine); + safe_delete(dbProxy); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.h b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.h new file mode 100644 index 0000000..125789a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.h @@ -0,0 +1,72 @@ +#ifndef SCRIPTINGQT_H +#define SCRIPTINGQT_H + +#include "builtinplugin.h" +#include "scriptingplugin.h" +#include <QHash> +#include <QVariant> +#include <QCache> +#include <QScriptValue> +#include <QScriptProgram> + +class QScriptEngine; +class QMutex; +class QScriptContext; +class ScriptingQtDbProxy; + +class ScriptingQt : public BuiltInPlugin, public DbAwareScriptingPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Qt scripting") + SQLITESTUDIO_PLUGIN_DESC("Qt scripting support.") + SQLITESTUDIO_PLUGIN_VERSION(10000) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + ScriptingQt(); + ~ScriptingQt(); + + QString getLanguage() const; + Context* createContext(); + void releaseContext(Context* context); + void resetContext(Context* context); + QVariant evaluate(const QString& code, const QList<QVariant>& args, Db* db, bool locking = false, QString* errorMessage = nullptr); + QVariant evaluate(Context* context, const QString& code, const QList<QVariant>& args, Db* db, bool locking = false); + void setVariable(Context* context, const QString& name, const QVariant& value); + QVariant getVariable(Context* context, const QString& name); + bool hasError(Context* context) const; + QString getErrorMessage(Context* context) const; + QString getIconPath() const; + bool init(); + void deinit(); + + private: + using DbAwareScriptingPlugin::evaluate; + + class ContextQt : public ScriptingPlugin::Context + { + public: + ContextQt(); + ~ContextQt(); + + QScriptEngine* engine = nullptr; + QCache<QString,QScriptProgram> scriptCache; + QString error; + ScriptingQtDbProxy* dbProxy = nullptr; + QScriptValue dbProxyScriptValue; + }; + + ContextQt* getContext(ScriptingPlugin::Context* context) const; + QScriptValue getFunctionValue(ContextQt* ctx, const QString& code); + QVariant evaluate(ContextQt* ctx, QScriptContext* engineContext, const QString& code, const QList<QVariant>& args, Db* db, bool locking); + QVariant convertVariant(const QVariant& value, bool wrapStrings = false); + + static const constexpr int cacheSize = 5; + + ContextQt* mainContext = nullptr; + QList<Context*> contexts; + QMutex* mainEngineMutex = nullptr; +}; + +#endif // SCRIPTINGQT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.png b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.png Binary files differnew file mode 100644 index 0000000..220ea27 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.png diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.cpp new file mode 100644 index 0000000..ff3c7ee --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.cpp @@ -0,0 +1,145 @@ +#include "scriptingqtdbproxy.h" +#include "db/db.h" +#include "db/sqlquery.h" +#include <QScriptContext> +#include <QScriptEngine> + +ScriptingQtDbProxy::ScriptingQtDbProxy(QObject *parent) : + QObject(parent) +{ +} +Db* ScriptingQtDbProxy::getDb() const +{ + return db; +} + +void ScriptingQtDbProxy::setDb(Db* value) +{ + db = value; +} +bool ScriptingQtDbProxy::getUseDbLocking() const +{ + return useDbLocking; +} + +void ScriptingQtDbProxy::setUseDbLocking(bool value) +{ + useDbLocking = value; +} + +QHash<QString, QVariant> ScriptingQtDbProxy::mapToHash(const QMap<QString, QVariant>& map) +{ + QHash<QString, QVariant> hash; + QMapIterator<QString, QVariant> it(map); + while (it.hasNext()) + { + it.next(); + hash[it.key()] = it.value(); + } + return hash; +} + +QVariant ScriptingQtDbProxy::evalInternal(const QString& sql, const QList<QVariant>& listArgs, const QMap<QString, QVariant>& mapArgs, + bool singleCell, const QScriptValue* funcPtr) +{ + if (!db) + { + QString funcName = singleCell ? QStringLiteral("db.onecolumn()") : QStringLiteral("db.eval()"); + context()->throwError(tr("No database available in current context, while called QtScript's %1 command.").arg(funcName)); + return evalInternalErrorResult(singleCell); + } + + Db::Flags flags; + if (!useDbLocking) + flags |= Db::Flag::NO_LOCK; + + SqlQueryPtr results; + if (listArgs.size() > 0) + results = db->exec(sql, listArgs, flags); + else + results = db->exec(sql, mapToHash(mapArgs), flags); + + if (results->isError()) + { + QString funcName = singleCell ? QStringLiteral("db.onecolumn()") : QStringLiteral("db.eval()"); + context()->throwError(tr("Error from %1: %2").arg(funcName, results->getErrorText())); + return evalInternalErrorResult(singleCell); + } + + if (singleCell) + { + return results->getSingleCell(); + } + else if (funcPtr) + { + QScriptValue func(*funcPtr); + SqlResultsRowPtr row; + QScriptValue funcArgs; + QScriptValue funcResult; + while (results->hasNext()) + { + row = results->next(); + funcArgs = context()->engine()->toScriptValue(row->valueList()); + funcResult = func.call(context()->thisObject(), funcArgs); + if (!funcResult.isUndefined()) + break; + } + return funcResult.toVariant(); + } + else + { + QList<QVariant> evalResults; + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + evalResults << QVariant(row->valueList()); + } + return evalResults; + } +} + +QVariant ScriptingQtDbProxy::evalInternalErrorResult(bool singleCell) +{ + QList<QVariant> result; + if (singleCell) + result << QVariant(); + + return result; +} + +QVariant ScriptingQtDbProxy::eval(const QString& sql) +{ + return evalInternal(sql, QList<QVariant>(), QMap<QString, QVariant>(), false); +} + +QVariant ScriptingQtDbProxy::eval(const QString& sql, const QList<QVariant>& args) +{ + return evalInternal(sql, args, QMap<QString, QVariant>(), false); +} + +QVariant ScriptingQtDbProxy::eval(const QString& sql, const QMap<QString, QVariant>& args) +{ + return evalInternal(sql, QList<QVariant>(), args, false); +} + +QVariant ScriptingQtDbProxy::eval(const QString& sql, const QList<QVariant>& args, const QScriptValue& func) +{ + return evalInternal(sql, args, QMap<QString, QVariant>(), false, &func); +} + +QVariant ScriptingQtDbProxy::eval(const QString& sql, const QMap<QString, QVariant>& args, const QScriptValue& func) +{ + return evalInternal(sql, QList<QVariant>(), args, false, &func); +} + +QVariant ScriptingQtDbProxy::onecolumn(const QString& sql, const QList<QVariant>& args) +{ + return evalInternal(sql, args, QMap<QString, QVariant>(), true); +} + +QVariant ScriptingQtDbProxy::onecolumn(const QString& sql, const QMap<QString, QVariant>& args) +{ + return evalInternal(sql, QList<QVariant>(), args, true); +} + diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.h b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.h new file mode 100644 index 0000000..add9540 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.h @@ -0,0 +1,44 @@ +#ifndef SCRIPTINGQTDBPROXY_H +#define SCRIPTINGQTDBPROXY_H + +#include <QObject> +#include <QScriptable> +#include <QHash> +#include <QList> +#include <QVariant> + +class Db; + +class ScriptingQtDbProxy : public QObject, protected QScriptable +{ + Q_OBJECT + public: + explicit ScriptingQtDbProxy(QObject *parent = 0); + + Db* getDb() const; + void setDb(Db* value); + + bool getUseDbLocking() const; + void setUseDbLocking(bool value); + + private: + QVariant evalInternal(const QString& sql, const QList<QVariant>& listArgs, const QMap<QString, QVariant>& mapArgs, bool singleCell, + const QScriptValue* funcPtr = nullptr); + QVariant evalInternalErrorResult(bool singleCell); + + static QHash<QString, QVariant> mapToHash(const QMap<QString, QVariant>& map); + + Db* db = nullptr; + bool useDbLocking = false; + + public slots: + QVariant eval(const QString& sql); + QVariant eval(const QString& sql, const QList<QVariant>& args); + QVariant eval(const QString& sql, const QMap<QString, QVariant>& args); + QVariant eval(const QString& sql, const QList<QVariant>& args, const QScriptValue& func); + QVariant eval(const QString& sql, const QMap<QString, QVariant>& args, const QScriptValue& func); + QVariant onecolumn(const QString& sql, const QList<QVariant>& args); + QVariant onecolumn(const QString& sql, const QMap<QString, QVariant>& args); +}; + +#endif // SCRIPTINGQTDBPROXY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.cpp new file mode 100644 index 0000000..93a6d91 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.cpp @@ -0,0 +1,146 @@ +#include "scriptingsql.h" +#include "common/unused.h" +#include "db/db.h" +#include "db/sqlquery.h" +#include "services/dbmanager.h" + +ScriptingSql::ScriptingSql() +{ +} + +ScriptingSql::~ScriptingSql() +{ +} + +QString ScriptingSql::getLanguage() const +{ + return "SQL"; +} + +ScriptingPlugin::Context* ScriptingSql::createContext() +{ + SqlContext* ctx = new SqlContext(); + contexts << ctx; + return ctx; +} + +void ScriptingSql::releaseContext(ScriptingPlugin::Context* context) +{ + if (!contexts.contains(context)) + return; + + delete context; + contexts.removeOne(context); +} + +void ScriptingSql::resetContext(ScriptingPlugin::Context* context) +{ + dynamic_cast<SqlContext*>(context)->errorText.clear(); +} + +QVariant ScriptingSql::evaluate(ScriptingPlugin::Context* context, const QString& code, const QList<QVariant>& args, Db* db, bool locking) +{ + SqlContext* ctx = dynamic_cast<SqlContext*>(context); + ctx->errorText.clear(); + + Db* theDb = nullptr; + if (db && db->isValid()) + theDb = db; + else if (memDb) + theDb = memDb; + else + return QVariant(); + + Db::Flags execFlags; + if (!locking) + execFlags |= Db::Flag::NO_LOCK; + + QString sql = code; + if (ctx->variables.size() > 0) + { + QString value; + for (const QString& key : ctx->variables.keys()) + { + value = "'" + ctx->variables[key].toString() + "'"; + sql.replace(":" + key, value).replace("@" + key, value).replace("$" + key, value); + } + } + + SqlQueryPtr result = theDb->exec(sql, args, execFlags); + if (result->isError()) + { + dynamic_cast<SqlContext*>(context)->errorText = result->getErrorText(); + return QVariant(); + } + + return result->getSingleCell(); +} + +QVariant ScriptingSql::evaluate(const QString& code, const QList<QVariant>& args, Db* db, bool locking, QString* errorMessage) +{ + Db* theDb = nullptr; + + if (db && db->isValid()) + theDb = db; + else if (memDb) + theDb = memDb; + else + return QVariant(); + + Db::Flags execFlags; + if (!locking) + execFlags |= Db::Flag::NO_LOCK; + + SqlQueryPtr result = theDb->exec(code, args, execFlags); + if (result->isError()) + { + *errorMessage = result->getErrorText(); + return QVariant(); + } + + return result->getSingleCell(); +} + +void ScriptingSql::setVariable(ScriptingPlugin::Context* context, const QString& name, const QVariant& value) +{ + dynamic_cast<SqlContext*>(context)->variables[name] = value; +} + +QVariant ScriptingSql::getVariable(ScriptingPlugin::Context* context, const QString& name) +{ + if (dynamic_cast<SqlContext*>(context)->variables.contains(name)) + return dynamic_cast<SqlContext*>(context)->variables[name]; + + return QVariant(); +} + +bool ScriptingSql::hasError(ScriptingPlugin::Context* context) const +{ + return !getErrorMessage(context).isNull(); +} + +QString ScriptingSql::getErrorMessage(ScriptingPlugin::Context* context) const +{ + return dynamic_cast<SqlContext*>(context)->errorText; +} + +QString ScriptingSql::getIconPath() const +{ + return ":/images/plugins/scriptingsql.png"; +} + +bool ScriptingSql::init() +{ + memDb = DBLIST->createInMemDb(); + return memDb != nullptr; +} + +void ScriptingSql::deinit() +{ + for (Context* context : contexts) + delete context; + + contexts.clear(); + + safe_delete(memDb); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.h b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.h new file mode 100644 index 0000000..7b8fd3b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.h @@ -0,0 +1,46 @@ +#ifndef SCRIPTINGSQL_H +#define SCRIPTINGSQL_H + +#include "builtinplugin.h" +#include "scriptingplugin.h" + +class ScriptingSql : public BuiltInPlugin, public DbAwareScriptingPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("SQL scripting") + SQLITESTUDIO_PLUGIN_DESC("SQL scripting support.") + SQLITESTUDIO_PLUGIN_VERSION(10000) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + class SqlContext : public Context + { + public: + QString errorText; + QHash<QString,QVariant> variables; + }; + + ScriptingSql(); + ~ScriptingSql(); + + QString getLanguage() const; + Context* createContext(); + void releaseContext(Context* context); + void resetContext(Context* context); + QVariant evaluate(Context* context, const QString& code, const QList<QVariant>& args, Db* db, bool locking); + QVariant evaluate(const QString& code, const QList<QVariant>& args, Db* db, bool locking, QString* errorMessage); + void setVariable(Context* context, const QString& name, const QVariant& value); + QVariant getVariable(Context* context, const QString& name); + bool hasError(Context* context) const; + QString getErrorMessage(Context* context) const; + QString getIconPath() const; + bool init(); + void deinit(); + + private: + QList<Context*> contexts; + Db* memDb = nullptr; +}; + +#endif // SCRIPTINGSQL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.png b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.png Binary files differnew file mode 100644 index 0000000..ea232a7 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.png diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.cpp new file mode 100644 index 0000000..5e1d610 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.cpp @@ -0,0 +1,29 @@ +#include "sqlformatterplugin.h" +#include "parser/parser.h" +#include "db/db.h" +#include <QDebug> + +QString SqlFormatterPlugin::format(const QString& code, Db* contextDb) +{ + Dialect dialect = Dialect::Sqlite3; + if (contextDb && contextDb->isValid()) + contextDb->getDialect(); + + Parser parser(dialect); + if (!parser.parse(code)) + { + qWarning() << "Could not parse SQL in order to format it. The SQL was:" << code; + return code; + } + + QStringList formattedQueries; + foreach (SqliteQueryPtr query, parser.getQueries()) + formattedQueries << format(query); + + return formattedQueries.join("\n"); +} + +QString SqlFormatterPlugin::getLanguage() const +{ + return "sql"; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.h new file mode 100644 index 0000000..cb500f6 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.h @@ -0,0 +1,16 @@ +#ifndef SQLFORMATTERPLUGIN_H
+#define SQLFORMATTERPLUGIN_H
+
+#include "coreSQLiteStudio_global.h"
+#include "codeformatterplugin.h"
+#include "parser/ast/sqlitequery.h"
+
+class API_EXPORT SqlFormatterPlugin : public CodeFormatterPlugin
+{
+ public:
+ QString format(const QString& code, Db* contextDb);
+ QString getLanguage() const;
+ virtual QString format(SqliteQueryPtr query) = 0;
+};
+
+#endif // SQLFORMATTERPLUGIN_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/uiconfiguredplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/uiconfiguredplugin.h new file mode 100644 index 0000000..c9af8e0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/uiconfiguredplugin.h @@ -0,0 +1,56 @@ +#ifndef UICONFIGUREDPLUGIN_H +#define UICONFIGUREDPLUGIN_H + +#include "coreSQLiteStudio_global.h" + +class API_EXPORT UiConfiguredPlugin +{ + public: + /** + * @brief Gets name of the configuration UI form. + * @return Name of the form object. + * + * Some plugins may link (during compilation) only to the coreSQLiteStudio part of the application, but they can still + * benefit from SQLiteStudio GUI application by providing UI form that will be used in ConfigDialog. + * + * This method should return the object name of the top-most widget found in the provided *.ui file. + * + * For more details see: http://wiki.sqlitestudio.pl/index.php/Plugin_UI_forms + */ + virtual QString getConfigUiForm() const = 0; + + /** + * @brief Provides config object for ConfigDialog. + * @return Config used by the plugin, or null when there's no config, or when config should not be configured with property binding. + * + * When this method returns null, but getConfigUiForm() returns existing form, then configuration is assumed to be kept + * in global CfgMain object (which is known to application, as all global CfgMain objects). In this case configuration is loaded/stored + * using initial and final calls to load/store values from the form. This is different from when this method returns not null. Keep reading. + * + * When this method returns pointer to an object, then ConfigDialog uses ConfigMapper to bind widgets from getConfigUiForm() with + * config values from CfgMain returned by this method. See ConfigMapper for details about binding. + * In this case ConfigDialog uses CfgMain::begin(), CfgMain::commit() and CfgMain::rollback() methods to make changes to the config + * transactional (when users clicks "cancel" or "apply"). + */ + virtual CfgMain* getMainUiConfig() = 0; + + /** + * @brief Notifies about ConfigDialog being just open. + * + * This is called just after the config dialog was open and all its contents are already initialized. + * This is a good moment to connect to plugin's CfgMain configuration object to listen for changes, + * so all uncommited (yet) configuration changes can be reflected by this plugin. + */ + virtual void configDialogOpen() = 0; + + /** + * @brief Notifies about ConfigDialog being closed. + * + * This is called just before the config dialog gets closed. + * This is a good moment to disconnect from configuration object and not listen to changes in the configuration anymore + * (couse config can change for example when application is starting and loading entire configuration, etc). + */ + virtual void configDialogClosed() = 0; +}; + +#endif // UICONFIGUREDPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/pluginservicebase.cpp b/SQLiteStudio3/coreSQLiteStudio/pluginservicebase.cpp new file mode 100644 index 0000000..85d1da8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/pluginservicebase.cpp @@ -0,0 +1,21 @@ +#include "pluginservicebase.h" + +PluginServiceBase::PluginServiceBase(QObject *parent) : + QObject(parent) +{ +} + +void PluginServiceBase::handleValidationFromPlugin(bool configValid, CfgEntry* key, const QString& errorMessage) +{ + emit validationResultFromPlugin(configValid, key, errorMessage); +} + +void PluginServiceBase::propertySetFromPlugin(CfgEntry* key, const QString& propertyName, const QVariant& value) +{ + emit widgetPropertyFromPlugin(key, propertyName, value); +} + +void PluginServiceBase::updateVisibilityAndEnabled(CfgEntry* key, bool visible, bool enabled) +{ + emit stateUpdateRequestFromPlugin(key, visible, enabled); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/pluginservicebase.h b/SQLiteStudio3/coreSQLiteStudio/pluginservicebase.h new file mode 100644 index 0000000..56bb7a2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/pluginservicebase.h @@ -0,0 +1,95 @@ +#ifndef PLUGINSERVICEBASE_H +#define PLUGINSERVICEBASE_H + +#include "common/global.h" +#include "coreSQLiteStudio_global.h" +#include <QObject> + +class CfgEntry; + +class API_EXPORT PluginServiceBase : public QObject +{ + Q_OBJECT + + public: + /** + * @brief Name of property to store scripting language. + * + * This property is used by plugins to store scripting language associated with given widget. + * Upon update of this property, the higlighter can be dynamically changed. + * Having this in a dynamic property we can keep plugins independent from UI, but they still + * can interact with the UI. + */ + static_char* LANG_PROPERTY_NAME = "language"; + + explicit PluginServiceBase(QObject *parent = 0); + + /** + * @brief Available for the plugins to report validation errors on their UI forms. + * @param configValid If the config value is valid or not. + * @param key The config key that was validated. + * @param errorMessage if the \p valid is false, then the \p errorMessage can carry the details of the validation result. + * + * Since import plugins themself are independet from QtGui, they still can provide *.ui files + * and they can use CFG_CATEGORIES to bind with *.ui files, then they can validate values + * stored in the CFG_CATEGORIES. In case that some value is invalid, they should call + * this method to let the UI know, that the widget should be marked for invalid value. + */ + void handleValidationFromPlugin(bool configValid, CfgEntry* key, const QString& errorMessage = QString()); + + /** + * @brief Available for the plugins to set custom properties on their UI forms. + * @param key The config key that the property reffers to (it must be bind to the UI widget). + * @param propertyName Name of the property to set. + * @param value Value for the property. + * + * This method is here for similar purpose as handleValidationFromPlugin(), just handles different action from the plugin. + */ + void propertySetFromPlugin(CfgEntry* key, const QString& propertyName, const QVariant& value); + + /** + * @brief Available for the plugins to update UI of their options accordingly to the config values. + * @param key The config key that the update is about. + * @param visible The visibility for the widget. + * @param enabled Enabled/disabled state for the widget. + * + * This method is here for the same reason that the handleValidationFromPlugin() is. + */ + void updateVisibilityAndEnabled(CfgEntry* key, bool visible, bool enabled); + + signals: + /** + * @brief Emitted when the plugin performed its configuration validation. + * @param valid true if plugin accepts its configuration. + * @param key a key that cause valid/invalid state. + * @param errorMessage if the \p valid is false, then the \p errorMessage can carry the details of the validation result. + * + * Slot handling this signal should update UI to reflect the configuration state. + */ + void validationResultFromPlugin(bool valid, CfgEntry* key, const QString& errorMessage); + + /** + * @brief Emitted when plugin wants to set custom property value for the UI widget. + * @param key a key that cause valid/invalid state. + * @param propertyName Name of the property to set. + * @param value Value for the property. + * + * Slot handling this signal should set the property to the widget which is bind to the given key. + */ + void widgetPropertyFromPlugin(CfgEntry* key, const QString& propertyName, const QVariant& value); + + /** + * @brief Emitted when the plugin wants to update UI according to config values. + * @param key The config key that the update is about. + * @param visible The visibility for the widget. + * @param enabled Enabled/disabled state for the widget. + * + * Slot handling this signal should update UI to reflect the state provided in parameters. + */ + void stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled); + + public slots: + +}; + +#endif // PLUGINSERVICEBASE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/populateworker.cpp b/SQLiteStudio3/coreSQLiteStudio/populateworker.cpp new file mode 100644 index 0000000..71ab9a4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/populateworker.cpp @@ -0,0 +1,110 @@ +#include "populateworker.h" +#include "common/utils_sql.h" +#include "db/db.h" +#include "db/sqlquery.h" +#include "plugins/populateplugin.h" +#include "services/notifymanager.h" + +PopulateWorker::PopulateWorker(Db* db, const QString& table, const QStringList& columns, const QList<PopulateEngine*>& engines, qint64 rows, QObject* parent) : + QObject(parent), db(db), table(table), columns(columns), engines(engines), rows(rows) +{ +} + +PopulateWorker::~PopulateWorker() +{ +} + +void PopulateWorker::run() +{ + static const QString insertSql = QStringLiteral("INSERT INTO %1 (%2) VALUES (%3);"); + + if (!db->begin()) + { + notifyError(tr("Could not start transaction in order to perform table populating. Error details: %1").arg(db->getErrorText())); + emit finished(false); + return; + } + + Dialect dialect = db->getDialect(); + QString wrappedTable = wrapObjIfNeeded(table, dialect); + + QStringList cols; + QStringList argList; + for (const QString& column : columns) + { + cols << wrapObjIfNeeded(column, dialect); + argList << "?"; + } + + QString finalSql = insertSql.arg(wrappedTable, cols.join(", "), argList.join(", ")); + SqlQueryPtr query = db->prepare(finalSql); + + QList<QVariant> args; + bool nextValueError = false; + for (qint64 i = 0; i < rows; i++) + { + if (i == 0 && !beforePopulating()) + return; + + args.clear(); + for (PopulateEngine* engine : engines) + { + args << engine->nextValue(nextValueError); + db->rollback(); + emit finished(false); + return; + } + + query->setArgs(args); + if (!query->execute()) + { + notifyError(tr("Error while populating table: %1").arg(query->getErrorText())); + db->rollback(); + emit finished(false); + return; + } + } + + if (!db->commit()) + { + notifyError(tr("Could not commit transaction after table populating. Error details: %1").arg(db->getErrorText())); + db->rollback(); + emit finished(false); + return; + } + + afterPopulating(); + emit finished(true); +} + +bool PopulateWorker::isInterrupted() +{ + QMutexLocker locker(&interruptMutex); + return interrupted; +} + +bool PopulateWorker::beforePopulating() +{ + for (PopulateEngine* engine : engines) + { + if (!engine->beforePopulating(db, table)) + { + db->rollback(); + emit finished(false); + return false; + } + } + return true; +} + +void PopulateWorker::afterPopulating() +{ + for (PopulateEngine* engine : engines) + engine->afterPopulating(); +} + +void PopulateWorker::interrupt() +{ + QMutexLocker locker(&interruptMutex); + interrupted = true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/populateworker.h b/SQLiteStudio3/coreSQLiteStudio/populateworker.h new file mode 100644 index 0000000..f0a39f8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/populateworker.h @@ -0,0 +1,41 @@ +#ifndef POPULATEWORKER_H +#define POPULATEWORKER_H + +#include <QMutex> +#include <QObject> +#include <QRunnable> +#include <QStringList> + +class Db; +class PopulateEngine; + +class PopulateWorker : public QObject, public QRunnable +{ + Q_OBJECT + public: + explicit PopulateWorker(Db* db, const QString& table, const QStringList& columns, const QList<PopulateEngine*>& engines, qint64 rows, QObject *parent = 0); + ~PopulateWorker(); + + void run(); + + private: + bool isInterrupted(); + bool beforePopulating(); + void afterPopulating(); + + Db* db = nullptr; + QString table; + QStringList columns; + QList<PopulateEngine*> engines; + qint64 rows; + bool interrupted = false; + QMutex interruptMutex; + + public slots: + void interrupt(); + + signals: + void finished(bool result); +}; + +#endif // POPULATEWORKER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/qio.cpp b/SQLiteStudio3/coreSQLiteStudio/qio.cpp new file mode 100644 index 0000000..24f95fe --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/qio.cpp @@ -0,0 +1,5 @@ +#include "qio.h" + +QTextStream qOut(stdout); +QTextStream qIn(stdin); +QTextStream qErr(stderr); diff --git a/SQLiteStudio3/coreSQLiteStudio/qio.h b/SQLiteStudio3/coreSQLiteStudio/qio.h new file mode 100644 index 0000000..f97962c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/qio.h @@ -0,0 +1,33 @@ +#ifndef QIO_H +#define QIO_H + +#include "coreSQLiteStudio_global.h" +#include <QTextStream> + +/** @file */ + +/** + * @brief Standard output stream. + * + * This is pretty much the same as std::cout, except it's based on QTextStream, + * therefore accepts larger range of data types. + */ +API_EXPORT extern QTextStream qOut; + +/** + * @brief Standard input stream. + * + * This is pretty much the same as std::cin, except it's based on QTextStream, + * therefore accepts larger range of data types. + */ +API_EXPORT extern QTextStream qIn; + +/** + * @brief Standard error stream. + * + * This is pretty much the same as std::cerr, except it's based on QTextStream, + * therefore accepts larger range of data types. + */ +API_EXPORT extern QTextStream qErr; + +#endif // QIO_H diff --git a/SQLiteStudio3/coreSQLiteStudio/querymodel.cpp b/SQLiteStudio3/coreSQLiteStudio/querymodel.cpp new file mode 100644 index 0000000..8d20b70 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/querymodel.cpp @@ -0,0 +1,63 @@ +#include "querymodel.h" +#include "db/db.h" +#include "common/unused.h" + +QueryModel::QueryModel(Db* db, QObject *parent) : + QAbstractTableModel(parent), db(db) +{ +} + +void QueryModel::refresh() +{ + if (!db || !db->isOpen()) + return; + + beginResetModel(); + loadedRows.clear(); + SqlQueryPtr results = db->exec(query); + for (SqlResultsRowPtr row : results->getAll()) + loadedRows += row; + + columns = results->columnCount(); + endResetModel(); + + emit refreshed(); +} + +QVariant QueryModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (role != Qt::DisplayRole) + return QVariant(); + + int rowIdx = index.row(); + if (rowIdx < loadedRows.size()) + return loadedRows[rowIdx]->value(index.column()); + + return QVariant(); +} + +int QueryModel::rowCount(const QModelIndex& parent) const +{ + UNUSED(parent); + return loadedRows.size(); +} + +int QueryModel::columnCount(const QModelIndex& parent) const +{ + UNUSED(parent); + return columns; +} + +QString QueryModel::getQuery() const +{ + return query; +} + +void QueryModel::setQuery(const QString& value) +{ + query = value; + refresh(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/querymodel.h b/SQLiteStudio3/coreSQLiteStudio/querymodel.h new file mode 100644 index 0000000..6c7e48a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/querymodel.h @@ -0,0 +1,42 @@ +#ifndef SQLQUERYMODEL_H +#define SQLQUERYMODEL_H + +#include "db/sqlquery.h" +#include "db/sqlresultsrow.h" +#include <QList> +#include <QAbstractTableModel> + +class Db; + +class API_EXPORT QueryModel : public QAbstractTableModel +{ + Q_OBJECT + + public: + using QAbstractItemModel::fetchMore; + using QAbstractItemModel::canFetchMore; + + QueryModel(Db* db, QObject *parent = nullptr); + + virtual void refresh(); + QVariant data(const QModelIndex& index, int role) const; + int rowCount(const QModelIndex& parent) const; + int columnCount(const QModelIndex& parent) const; + + QString getQuery() const; + void setQuery(const QString& value); + + private: + void fetchMore(); + bool canFetchMore() const; + + QString query; + Db* db = nullptr; + QList<SqlResultsRowPtr> loadedRows; + int columns = 0; + + signals: + void refreshed(); +}; + +#endif // SQLQUERYMODEL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/returncode.cpp b/SQLiteStudio3/coreSQLiteStudio/returncode.cpp new file mode 100644 index 0000000..80edbe4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/returncode.cpp @@ -0,0 +1,51 @@ +#include "returncode.h" + +ReturnCode::ReturnCode() + : retCode(0) +{ +} + +ReturnCode::ReturnCode(QString errorMessage) + : retCode(0) +{ + errorMessages << errorMessage; +} + +ReturnCode::ReturnCode(quint16 code, QString errorMessage) + : retCode(code) +{ + errorMessages << errorMessage; +} + +void ReturnCode::addMessage(QString errorMessage) +{ + errorMessages << errorMessage; +} + +bool ReturnCode::isOk() +{ + return retCode != 0; +} + +QList<QString> &ReturnCode::getMessages() +{ + return errorMessages; +} + +QString ReturnCode::message() +{ + if (errorMessages.size() > 0) + return errorMessages.at(0); + else + return QString::null; +} + +void ReturnCode::setCode(quint16 code) +{ + retCode = code; +} + +quint16 ReturnCode::code() +{ + return retCode; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/returncode.h b/SQLiteStudio3/coreSQLiteStudio/returncode.h new file mode 100644 index 0000000..71af885 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/returncode.h @@ -0,0 +1,26 @@ +#ifndef RETURNCODE_H +#define RETURNCODE_H + +#include <QList> +#include <QString> + +class ReturnCode +{ + private: + quint16 retCode; + QList<QString> errorMessages; + + public: + ReturnCode(); + explicit ReturnCode(QString errorMessage); + ReturnCode(quint16 code, QString errorMessage); + + void addMessage(QString errorMessage); + bool isOk(); + QList<QString>& getMessages(); + QString message(); + void setCode(quint16 code); + quint16 code(); +}; + +#endif // RETURNCODE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.cpp b/SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.cpp new file mode 100644 index 0000000..0579add --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.cpp @@ -0,0 +1,1151 @@ +/* ****************************************************************************
+ *
+ * Copyright 2013 Nedim Srndic
+ *
+ * This file is part of rsa - the RSA implementation in C++.
+ *
+ * rsa is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * rsa is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rsa. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * BigInt.cpp
+ *
+ * Author: Nedim Srndic
+ * Release date: 14th of March 2008
+ *
+ * This file contains the implementation for the BigInt class.
+ *
+ * There are two static constants defined in this file:
+ *
+ * - ULongMax : ULONG_MAX (defined in climits) of type BigInt
+ * Mainly used for speedup in the multiply() private member function.
+ * Represents the largest unsigned long integer a particular platform can
+ * handle. If a BigInt is <= ULongMax, it can be converted to unsigned
+ * long int. This is platform-specific.
+ * - SqrtUlongMax : sqrt(ULONG_MAX) of type BigInt
+ * Mainly used for speedup in the multiply() private member function.
+ * Represents the square root of the largest unsigned long integer a
+ * particular platform can handle. If two BigInts are <= SqrtULongMax,
+ * they can be converted to unsigned long int and safely multiplied
+ * by the CPU. This is platform-specific.
+ *
+ * ****************************************************************************
+ */
+
+//comment the following line if you want to use long multiplication
+//#define KARATSUBA
+
+#include "BigInt.h"
+#include <cstring> //strlen()
+#include <climits> //ULONG_MAX
+#include <vector> //vector<bool>
+#include <string> //operator std::string()
+#include <algorithm> //reverse_copy(), copy(), copy_backward(),
+ //fill(), fill_n()
+
+using std::cout;
+using std::endl;
+
+//define and initialize BigInt::FACTOR
+const double BigInt::FACTOR = 1.6;
+
+//A BigInt number with the value of ULONG_MAX
+static const BigInt ULongMax(ULONG_MAX);
+//A BigInt number with the value of sqrt(ULONG_MAX)
+static const BigInt SqrtULongMax
+ (static_cast<unsigned long int>(sqrt(static_cast<double>(ULONG_MAX))));
+
+/* Transforms the number from unsigned long int to unsigned char[]
+ * and pads the result with zeroes. Returns the number of digits. */
+unsigned long int BigInt::int2uchar(unsigned long int number,
+ unsigned char *digits,
+ unsigned long int padding = 0L)
+{
+ int i(0);
+ do
+ {
+ //the number is stored in reverse
+ //(i.e. long int 456 is stored as unsigned char[] {[6][5][4]})
+ digits[i++] = (unsigned char) (number % 10);
+ number /= 10;
+ } while (number > 0L);
+
+ std::fill_n(digits + i, padding, 0);
+ return i;
+}
+
+/* Converts ASCII digits to equivalent unsigned char numeric values. */
+void BigInt::char2uchar(unsigned char *array,
+ unsigned long int length)
+{
+ for (unsigned long int i(0L); i < length; i++)
+ array[i] -= '0';
+}
+
+/* Check if all ASCII values are digits '0' to '9'. */
+bool BigInt::allCharsAreDigits( const char *array,
+ unsigned long int length)
+{
+ for (unsigned long int i(0L); i < length; i++)
+ if (array[i] < '0' || array[i] > '9')
+ return false;
+
+ return true;
+}
+
+/* Compares two BigInt. If the last two arguments are
+ * omitted, the comparison is sign-insensitive (comparison by
+ * absolute value). Returns 0 if a == b, 1 if a > b, 2 if a < b. */
+int BigInt::compareNumbers( unsigned char *a, unsigned long int na,
+ unsigned char *b, unsigned long int nb,
+ bool aPositive, bool bPositive)
+{
+ if (na < nb || (!aPositive && bPositive))
+ //a < b
+ return 2;
+ else if (na > nb || (aPositive && !bPositive))
+ //a > b
+ return 1;
+
+ //check the digits one by one starting from the most significant one
+ for (long int i = na - 1; i >= 0L; i--)
+ //compare the digits
+ if (a[i] != b[i])
+ {
+ if (a[i] < b[i]) // |a| < |b|
+ if (aPositive)
+ return 2; // a < b
+ else
+ return 1; // a > b
+ else // |a| > |b|
+ if (aPositive)
+ return 1; // a > b
+ else
+ return 2; // a < b
+ }
+
+ //a == b
+ return 0;
+}
+
+/* Multiplies two unsigned char[] using the Divide and Conquer
+ * a.k.a. Karatsuba algorithm .*/
+void BigInt::karatsubaMultiply( unsigned char *a, unsigned char *b,
+ unsigned long int n, unsigned char *buf1)
+{
+ //if *a <= SqrtULongMax && *b <= SqrtULongMax,
+ //the CPU can do the multiplication
+ if (compareNumbers(a, n, SqrtULongMax.digits, SqrtULongMax.digitCount) != 1
+ &&
+ compareNumbers(b, n, SqrtULongMax.digits, SqrtULongMax.digitCount) != 1
+ )
+ {
+ int2uchar(toInt(a, n) * toInt(b, n), buf1, n << 1);
+ return;
+ }
+
+ //nh = higher half digits, nl = lower half digits
+ //nh == nl || nh + 1 == nl
+ //nt is used to avoid too much nl + 1 addition operations
+ unsigned long int nh(n >> 1), nl(n - nh), nt(nl + 1);
+ //t1 is a temporary pointer, points to p1
+ unsigned char *t1(buf1 + (n << 1));
+
+ BigInt::add(a + nl, nh, a, nl, buf1, nt);
+ BigInt::add(b + nl, nh, b, nl, buf1 + nt, nt);
+ BigInt::karatsubaMultiply(a + nl, b + nl, nh, t1); //p1
+ BigInt::karatsubaMultiply(a, b, nl, t1 + (nh << 1)); //p2
+ BigInt::karatsubaMultiply(buf1, buf1 + nt, nt, t1 + (n << 1));//p3
+
+ //for leftshifting p3 and p1
+ unsigned long int power(n);
+ if (power & 1)
+ power++;
+ //since the original multiplier is not needed any more, we can reuse a
+ a = buf1 + (power >> 1);
+ //copy and shift left p3 by power / 2 and pad right to n * 2 with zeroes
+ std::fill(buf1, a, 0);
+ std::copy(t1 + (n << 1), t1 + ((n + nl) << 1) + 1, a);
+ std::fill(a + (nl << 1) + 1, t1, 0);
+
+ //shifted p3 -= p2
+ //a = shifted p3, b = p2
+ BigInt::quickSub(a, t1 + (nh << 1), t1, nl);
+
+ //shifted p3 -= p1
+ //a = shifted p3, b = p1
+ BigInt::quickSub(a, t1, t1, nh);
+
+ //shifted p3 += shifted p1
+ //a = p3[power], b = p1
+ a = buf1 + power;
+ BigInt::quickAdd(a, t1, nh);
+
+ //p3 += p2
+ //a = p3, b = p2
+ unsigned char carry = BigInt::quickAdd(buf1, t1 + (nh << 1), nl);
+ a = buf1 + (nl << 1);
+ for (unsigned long int i(0L); carry; i++)
+ {
+ a[i] += 1;
+ carry = a[i] / 10;
+ a[i] %= 10;
+ }
+}
+
+/* Multiplies two unsigned char[] the long way. */
+void BigInt::longMultiply( unsigned char *a, unsigned long int na,
+ unsigned char *b, unsigned long int nb,
+ unsigned char *result)
+{
+ std::fill_n(result, na + nb, 0);
+ unsigned char mult(0);
+ int carry(0);
+
+ for (unsigned long int i(0L); i < na; i++)
+ {
+ for (unsigned long int j(0L); j < nb; j++)
+ {
+ mult = a[i] * b[j] + result[i + j] + carry;
+ result[i + j] = static_cast<int>(mult) % 10;
+ carry = static_cast<int>(mult) / 10;
+ }
+ if (carry)
+ {
+ result[i + nb] += carry;
+ carry = 0;
+ }
+ }
+}
+
+/* Simple addition, used by the multiply function.
+ * Returns the remaining carry. */
+unsigned char BigInt::quickAdd( unsigned char *a, unsigned char *b,
+ unsigned long int n)
+{
+ unsigned char carry(0), sum(0);
+ for (unsigned long int i(0L); i < (n << 1); i++)
+ {
+ sum = a[i] + b[i] + carry;
+ carry = sum / 10;
+ a[i] = sum % 10;
+ }
+ return carry;
+}
+
+/* Simple subtraction, used by the multiply function. */
+void BigInt::quickSub( unsigned char *a, unsigned char *b,
+ unsigned char *end, unsigned long int n)
+{
+ unsigned char carry(0), sum(0);
+ for (unsigned long int i(0L); i < (n << 1); i++)
+ {
+ sum = 10 + a[i] - (b[i] + carry);
+ if (sum < 10) //carry
+ {
+ a[i] = sum;
+ carry = 1;
+ }
+ else
+ {
+ a[i] = sum % 10;
+ carry = 0;
+ }
+ }
+ a = &a[n << 1];
+ for (; carry && a < end; a++)
+ if (*a)
+ {
+ (*a)--;
+ break;
+ }
+ else
+ *a = 9;
+}
+
+/* Divides two BigInt numbers by the formula
+ * dividend = divisor * quotient + remainder*/
+void BigInt::divide(const BigInt ÷nd, const BigInt &divisor,
+ BigInt "ient, BigInt &remainder)
+{
+ BigInt Z1, R, X(dividend.Abs());
+ /* Make sure quotient and remainder are zero.
+ * The lack of this assignment introduces a bug if the actual parameters
+ * are not zero when calling this function. */
+ quotient = BigIntZero;
+ remainder = BigIntZero;
+
+ // while |X| >= |divisor|
+ while (BigInt::compareNumbers( X.digits, X.digitCount,
+ divisor.digits, divisor.digitCount,
+ true, true) != 2)
+ {
+ unsigned long int O(X.digitCount - divisor.digitCount);
+ if (O <= ULongMax.digitCount - 2)
+ {
+ unsigned long int i;
+ if (X.digitCount > ULongMax.digitCount - 1)
+ i = ULongMax.digitCount - 1;
+ else
+ i = X.digitCount;
+ unsigned long int j(i - O);
+ Z1 = toInt(X.digits + X.digitCount - i, i) /
+ toInt(divisor.digits + divisor.digitCount - j, j);
+ }
+ else
+ {
+ unsigned long int i(ULongMax.digitCount - 1);
+ unsigned long int j;
+ if (divisor.digitCount > ULongMax.digitCount - 2)
+ j = ULongMax.digitCount - 2;
+ else
+ j = divisor.digitCount;
+ Z1 = toInt(X.digits + X.digitCount - i, i) /
+ toInt(divisor.digits + divisor.digitCount - j, j);
+ Z1.shiftLeft(O - Z1.digitCount);
+ }
+
+ predictZ1:
+ R = (Z1 * divisor).Abs();
+
+ if (X >= R)
+ {
+ X = X - R;
+ quotient += Z1;
+ }
+ else
+ {
+ if (Z1.digitCount > 1)
+ Z1.shiftRight(1);
+ else
+ --Z1;
+ goto predictZ1;
+ }
+ }
+
+ remainder = X;
+}
+
+/* Returns the value of the specified unsigned char[] as long int. */
+unsigned long int BigInt::toInt(unsigned char *digits, int n)
+{
+ unsigned long int newInt(0L);
+ unsigned long int powerOf10(1);
+ for (int i(0); i < n; i++)
+ {
+ newInt += digits[i] * powerOf10;
+ powerOf10 *= 10;
+ }
+ return newInt;
+}
+
+/* Saves the sum of two unsigned char* shorter and longer into result.
+ * It must be nShorter <= nLonger. If doFill == true, it fills the
+ * remaining free places with zeroes (used in KaratsubaMultiply()).
+ * Returns true if there was an overflow at the end (meaning that
+ * the result.digitCount was longer.digitCount + 1. */
+bool BigInt::add(unsigned char *shorter, unsigned long int nShorter,
+ unsigned char *longer, unsigned long int nLonger,
+ unsigned char *result, int nResult, bool doFill)
+{
+ //single digitwise sum and carry
+ unsigned char subSum(0);
+ unsigned char subCarry(0);
+
+ //count the digits
+ unsigned long int i(0L);
+
+ //add the digits
+ for (; i < nShorter; i++)
+ {
+ subSum = longer[i] + shorter[i] + subCarry;
+ subCarry = subSum / 10;
+ result[i] = subSum % 10;
+ }
+
+ for (; i < nLonger; i++)
+ {
+ subSum = longer[i] + subCarry;
+ subCarry = subSum / 10;
+ result[i] = subSum % 10;
+ }
+
+ if (doFill)
+ std::fill_n(result + i, nResult - i, 0);
+
+ if (subCarry)
+ {
+ result[i++] = 1;
+ return true;
+ }
+ return false;
+}
+
+/* Shifts the digits n places left. */
+BigInt &BigInt::shiftLeft(unsigned long int n)
+{
+ //if the number is 0, we won't shift it
+ if (EqualsZero())
+ return *this;
+ if (length <= digitCount + n + 2)
+ expandTo(digitCount + n + 2);
+
+ std::copy_backward(digits, digits + digitCount, digits + n + digitCount);
+ std::fill_n(digits, n, 0);
+ digitCount += n;
+ return *this;
+}
+
+/* Shifts the digits n places right. */
+BigInt &BigInt::shiftRight(unsigned long int n)
+{
+ if (n >= digitCount)
+ throw "Error BIGINT00: Overflow on shift right.";
+
+ std::copy_backward( digits + n, digits + digitCount,
+ digits + digitCount - n);
+ digitCount -= n;
+ return *this;
+}
+
+/* Expands the digits* to n. */
+void BigInt::expandTo(unsigned long int n)
+{
+ unsigned long int oldLength(length);
+ length = n;
+ unsigned char *oldDigits(digits);
+ try
+ {
+ digits = new unsigned char[length];
+ }
+ catch (...)
+ {
+ delete[] digits;
+ digits = oldDigits;
+ length = oldLength;
+ throw "Error BIGINT01: BigInt creation error (out of memory?).";
+ }
+
+ std::copy(oldDigits, oldDigits + digitCount, digits);
+ delete[] oldDigits;
+}
+
+BigInt::BigInt() : digits(0), length(10), digitCount(1), positive(true)
+{
+ try
+ {
+ digits = new unsigned char[length];
+ }
+ catch (...)
+ {
+ delete[] digits;
+ throw "Error BIGINT02: BigInt creation error (out of memory?).";
+ }
+
+ //initialize to 0
+ digits[0] = 0;
+}
+
+BigInt::BigInt(const char * charNum) : digits(0)
+{
+ digitCount = (unsigned long int) strlen(charNum);
+
+ if (digitCount == 0L)
+ throw "Error BIGINT03: Input string empty.";
+ else
+ {
+ switch (charNum[0])
+ {
+ case '+':
+ digitCount--;
+ charNum++;
+ positive = true;
+ break;
+ case '-':
+ digitCount--;
+ charNum++;
+ positive = false;
+ break;
+ default:
+ positive = true;
+ }
+ }
+
+ //get rid of the leading zeroes
+ while (charNum[0] == '0')
+ {
+ charNum++;
+ digitCount --;
+ }
+
+ //check if the string contains only decimal digits
+ if (! BigInt::allCharsAreDigits(charNum, digitCount))
+ throw "Error BIGINT04: Input string contains characters"
+ " other than digits.";
+
+ //the input string was like ('+' or '-')"00...00\0"
+ if (charNum[0] == '\0')
+ {
+ digitCount = 1;
+ charNum--;
+ positive = true;
+ }
+
+ length = (unsigned long int)(digitCount * BigInt::FACTOR + 1);
+
+ try
+ {
+ digits = new unsigned char[length];
+ }
+ catch (...)
+ {
+ delete[] digits;
+ throw "Error BIGINT05: BigInt creation error (out of memory?).";
+ }
+
+ //copy the digits backwards to the new BigInt
+ std::reverse_copy(charNum, charNum + digitCount, digits);
+ //convert them to unsigned char
+ BigInt::char2uchar(digits, digitCount);
+}
+
+BigInt::BigInt(unsigned long int intNum) : digits(0)
+{
+ positive = true;
+
+ //we don't know how many digits there are in intNum since
+ //sizeof(long int) is platform dependent (2^128 ~ 39 digits), so we'll
+ //first save them in a temporary unsigned char[], and later copy them
+ unsigned char tempDigits[40] = {0};
+
+ digitCount = int2uchar(intNum, tempDigits);
+ length = (unsigned long int)(digitCount * BigInt::FACTOR + 1);
+
+ try
+ {
+ digits = new unsigned char[length];
+ }
+ catch (...)
+ {
+ delete [] digits;
+ throw "Error BIGINT06: BigInt creation error (out of memory?).";
+ }
+
+ std::copy(tempDigits, tempDigits + digitCount, digits);
+}
+
+BigInt::BigInt(const std::string &str) : digits(0), length(10),
+ digitCount(1), positive(true)
+{
+ try
+ {
+ digits = new unsigned char[length];
+ }
+ catch (...)
+ {
+ delete[] digits;
+ throw "Error BIGINT07: BigInt creation error (out of memory?).";
+ }
+
+ //initialize to 0
+ digits[0] = 0;
+ BigInt a(str.c_str());
+ *this = a;
+}
+
+BigInt::BigInt(const BigInt &rightNumber) : length(rightNumber.length),
+digitCount(rightNumber.digitCount), positive(rightNumber.positive)
+{
+ //make sure we have just enough space
+ if (length <= digitCount + 2 || length > (digitCount << 2))
+ length = (unsigned long int) (digitCount * BigInt::FACTOR + 1);
+ try
+ {
+ digits = new unsigned char[length];
+ }
+ catch (...)
+ {
+ delete[] digits;
+ throw "Error BIGINT08: BigInt creation error (out of memory?).";
+ }
+
+ std::copy(rightNumber.digits, rightNumber.digits + digitCount, digits);
+}
+
+BigInt::operator std::string() const
+{
+ return ToString();
+}
+
+BigInt &BigInt::operator =(const BigInt &rightNumber)
+{
+ //if the right-hand operand is longer than the left-hand one or
+ //twice as small
+ if (length < rightNumber.digitCount + 2 ||
+ length > (rightNumber.digitCount << 2))
+ {
+ length = (unsigned long int)
+ (rightNumber.digitCount * BigInt::FACTOR + 1);
+ //keep a pointer to the current digits, in case
+ //there is not enough memory to allocate for the new digits
+ unsigned char *tempDigits(digits);
+
+ try
+ {
+ digits = new unsigned char[length];
+ }
+ catch (...)
+ {
+ //clean up the mess
+ delete[] digits;
+ //restore the digits
+ digits = tempDigits;
+ throw "Error BIGINT09: BigInt assignment error (out of memory?).";
+ }
+ //it turns out we don't need this any more
+ delete[] tempDigits;
+ }
+ //destructive self-assignment protection
+ else if (this == &rightNumber)
+ return *this;
+
+ //copy the values
+ digitCount = rightNumber.digitCount;
+ positive = rightNumber.positive;
+ std::copy(rightNumber.digits, rightNumber.digits + digitCount, digits);
+ return *this;
+}
+
+std::ostream &operator <<(std::ostream &cout, const BigInt &number)
+{
+ if (!number.positive)
+ cout << '-';
+ for (int i = number.digitCount - 1; i >= 0; i--)
+ cout << (int(number.digits[i]));
+
+ return cout;
+}
+
+std::istream &operator >>(std::istream &cin, BigInt &number)
+{
+ std::string newNumber;
+ std::cin >> std::ws >> newNumber;
+ if (!cin)
+ {
+ cin.clear();
+ throw "Error BIGINT16: Input stream error.";
+ }
+
+ number = newNumber;
+ return cin;
+}
+
+bool operator <(const BigInt &a, const BigInt &b)
+{
+ if (BigInt::compareNumbers( a.digits, a.digitCount,
+ b.digits, b.digitCount,
+ a.positive, b.positive) == 2)
+ return true;
+ return false;
+}
+
+bool operator <=(const BigInt &a, const BigInt &b)
+{
+ if (BigInt::compareNumbers( a.digits, a.digitCount,
+ b.digits, b.digitCount,
+ a.positive, b.positive) == 1)
+ return false;
+ return true;
+}
+
+bool operator >(const BigInt &a, const BigInt &b)
+{
+ if (BigInt::compareNumbers( a.digits, a.digitCount,
+ b.digits, b.digitCount,
+ a.positive, b.positive) == 1)
+ return true;
+ return false;
+}
+
+bool operator >=(const BigInt &a, const BigInt &b)
+{
+ if (BigInt::compareNumbers( a.digits, a.digitCount,
+ b.digits, b.digitCount,
+ a.positive, b.positive) == 2)
+ return false;
+ return true;
+}
+
+bool operator ==(const BigInt &a, const BigInt &b)
+{
+ if (BigInt::compareNumbers( a.digits, a.digitCount,
+ b.digits, b.digitCount,
+ a.positive, b.positive))
+ return false;
+ return true;
+}
+
+bool operator !=(const BigInt &a, const BigInt &b)
+{
+ if (BigInt::compareNumbers( a.digits, a.digitCount,
+ b.digits, b.digitCount,
+ a.positive, b.positive))
+ return true;
+ return false;
+}
+
+BigInt operator +(const BigInt &a, const BigInt &b)
+{
+ if (a.positive && !b.positive)
+ return a - (-b);
+ else if (!a.positive && b.positive)
+ return b - (-a);
+
+ //find the longer of the operands
+ const BigInt *shorter, *longer;
+ if (BigInt::compareNumbers( a.digits, a.digitCount,
+ b.digits, b.digitCount) == 1)
+ {
+ shorter = &b;
+ longer = &a;
+ }
+ else
+ {
+ shorter = &a;
+ longer = &b;
+ }
+
+ //Copies the "positive" field too. That is good because now either a and b
+ //are both positive or both negative, so the result has the same sign.
+ BigInt sum(*longer);
+
+ bool overflow = BigInt::add(shorter->digits, shorter->digitCount,
+ longer->digits, longer->digitCount,
+ sum.digits, 0, false);
+ if (overflow)
+ sum.digitCount++;
+
+ return sum;
+}
+
+/*overloaded ++ operator, prefix version*/
+BigInt &BigInt::operator++()
+{
+ return *this += BigIntOne;
+}
+
+/*overloaded ++ operator, postfix version*/
+BigInt BigInt::operator++(int)
+{
+ BigInt temp(*this);
+ *this += BigIntOne;
+ return temp;
+}
+
+BigInt &BigInt::operator+=(const BigInt &number)
+{
+ *this = *this + number;
+ return *this;
+}
+
+BigInt BigInt::operator-() const
+{
+ if (!this->EqualsZero())
+ {
+ BigInt temp(*this);
+ temp.positive = !temp.positive;
+ return temp;
+ }
+ return *this;
+}
+
+BigInt operator-(const BigInt &a, const BigInt &b)
+{
+ if (!a.positive && b.positive)
+ {
+ return -((-a) + b);
+ }
+ if (a.positive && !b.positive)
+ {
+ return a + (-b);
+ }
+
+ const int cmpAbs = BigInt::compareNumbers( a.digits, a.digitCount,
+ b.digits, b.digitCount);
+ //if a == b
+ if ((cmpAbs == 0) && (a.positive == b.positive))
+ {
+ return BigIntZero;
+ }
+
+ //find the longer of the operands (bigger by absolute value)
+ const BigInt *shorter, *longer;
+ bool sign(a.positive); //the sign of the result
+ if (cmpAbs != 2) // a >= b
+ {
+ shorter = &b;
+ longer = &a;
+ }
+ else
+ {
+ shorter = &a;
+ longer = &b;
+ sign = !sign;
+ }
+
+ BigInt result(*longer);
+ result.positive = sign;
+ //temporary variable
+ const BigInt shorterCopy(*shorter);
+ //often used temporary variable
+ const int rDigits(shorterCopy.digitCount);
+ //in case of longer digitwise carry, overflow = true
+ bool overflow(false);
+
+ for (int i(0); i < rDigits; i++)
+ {
+ overflow = (longer->digits[i] - shorterCopy.digits[i]) < 0;
+ if (overflow)
+ {
+ result.digits[i] = longer->digits[i] + 10 - shorterCopy.digits[i];
+ //transfer carry
+ shorterCopy.digits[i+1]++;
+ }
+ else
+ //make the digitwise subtraction
+ result.digits[i] = longer->digits[i] - shorterCopy.digits[i];
+ }
+
+ //if there is a carry and the following digit is 0 => there will
+ //be a carry again...
+ if (overflow && result.digits[rDigits] == 0)
+ {
+ result.digits[rDigits] = 9;
+
+ int i(rDigits + 1);
+ for (; result.digits[i] == 0; i++)
+ result.digits[i] = 9;
+
+ result.digits[i] -= 1;
+ } //there is a carry but there will be no more carries
+ else if (overflow)
+ result.digits[rDigits]--;
+
+ //get rid of the leading zeroes
+ for (int i(result.digitCount - 1); i > 0; i--)
+ if (result.digits[i] == 0)
+ result.digitCount--;
+ else
+ break;
+
+ return result;
+}
+
+/*overloaded -- operator, prefix version*/
+BigInt &BigInt::operator--()
+{
+ *this = *this - BigIntOne;
+ return *this;
+}
+
+/*overloaded -- operator, postfix version*/
+BigInt BigInt::operator--(int)
+{
+ BigInt temp(*this);
+ *this = *this - BigIntOne;
+ return temp;
+}
+
+BigInt &BigInt::operator-=(const BigInt &number)
+{
+ *this = *this - number;
+ return *this;
+}
+
+BigInt operator*(const BigInt &a, const BigInt &b)
+{
+ if (a.EqualsZero() || b.EqualsZero())
+ return BigIntZero;
+
+ //this controls wether Karatsuba algorithm will be used for multiplication
+#ifdef KARATSUBA
+ int n((a.digitCount < b.digitCount ? b.digitCount : a.digitCount));
+
+ //we will use a temporary buffer for multiplication
+ unsigned char *buffer(0);
+
+ try
+ {
+ buffer = new unsigned char[11 * n];
+ }
+ catch (...)
+ {
+ delete[] buffer;
+ throw "Error BIGINT10: Not enough memory?";
+ }
+
+ unsigned char *bb(buffer + n), *bc(bb + n);
+
+ std::copy(a.digits, a.digits + a.digitCount, buffer);
+ std::fill(buffer + a.digitCount, buffer + n, 0);
+ std::copy(b.digits, b.digits + b.digitCount, bb);
+ std::fill(bb + b.digitCount, bb + n, 0);
+
+ BigInt::karatsubaMultiply(buffer, bb, n, bc);
+
+ n <<= 1;
+#else
+ int n = a.digitCount + b.digitCount;
+
+ unsigned char *buffer = new unsigned char[n];
+
+ BigInt::longMultiply( a.digits, a.digitCount,
+ b.digits, b.digitCount, buffer);
+
+ unsigned char *bc(buffer);
+#endif /*KARATSUBA*/
+
+ BigInt bigIntResult; //we assume it's a positive number
+ if (a.positive != b.positive)
+ bigIntResult.positive = false;
+ bigIntResult.expandTo(n + 10);
+ std::copy(bc, bc + n, bigIntResult.digits);
+ for (unsigned long int i = n - 1; i > 0L; i--)
+ {
+ if (bigIntResult.digits[i])
+ {
+ bigIntResult.digitCount = i + 1;
+ break;
+ }
+ }
+ delete[] buffer;
+
+ return bigIntResult;
+}
+
+BigInt &BigInt::operator*=(const BigInt &number)
+{
+ *this = *this * number;
+ return *this;
+}
+
+BigInt operator /(const BigInt &a, const BigInt &b)
+{
+ if (b.EqualsZero())
+ throw "Error BIGINT11: Attempt to divide by zero.";
+
+ //we don't want to call this function twice
+ int comparison(BigInt::compareNumbers( a.digits, a.digitCount,
+ b.digits, b.digitCount));
+
+ //if a == 0 or |a| < |b|
+ if (a.EqualsZero() || comparison == 2)
+ return BigIntZero;
+
+ //if a == b
+ if (comparison == 0)
+ {
+ if (a.positive == b.positive)
+ return BigIntOne;
+ else
+ return -BigIntOne;
+ }
+
+ BigInt quotient, remainder;
+ BigInt::divide(a, b, quotient, remainder);
+ //adjust the sign (positive by default)
+ if (a.positive != b.positive)
+ quotient.positive = false;
+ return quotient;
+}
+
+BigInt &BigInt::operator /=(const BigInt &number)
+{
+ *this = *this / number;
+ return *this;
+}
+
+BigInt operator%(const BigInt &a, const BigInt &b)
+{
+ if (b.EqualsZero())
+ throw "Error BIGINT12: Attempt to divide by zero.";
+
+ //we don't want to call this function twice
+ int comparison(BigInt::compareNumbers( a.digits, a.digitCount,
+ b.digits, b.digitCount));
+
+ //a == b
+ if (comparison == 0)
+ return BigIntZero;
+
+ //if a < b
+ if (comparison == 2 && a.positive)
+ return a;
+
+ BigInt quotient, remainder;
+ BigInt::divide(a, b, quotient, remainder);
+ if (!a.positive && !remainder.EqualsZero())
+ remainder.positive = false;
+ return remainder;
+}
+
+BigInt &BigInt::operator%=(const BigInt &number)
+{
+ *this = *this % number;
+ return *this;
+}
+
+/* Returns *this to the power of n
+ * using the fast Square and Multiply algorithm. */
+BigInt BigInt::GetPower(unsigned long int n) const
+{
+ BigInt result(BigIntOne);
+ BigInt base(*this);
+
+ while (n)
+ {
+ //if n is odd
+ if (n & 1)
+ {
+ result = result * base;
+ n--;
+ }
+ n /= 2;
+ base = base * base;
+ }
+
+ //number was negative and the exponent is odd, the result is negative
+ if (!positive && (n & 1))
+ result.positive = false;
+ return result;
+}
+
+/* *this = *this to the power of n. */
+void BigInt::SetPower(unsigned long int n)
+{
+ *this = (*this).GetPower(n);
+}
+
+/* Returns *this to the power of n
+ * using the fast Square and Multiply algorithm. */
+BigInt BigInt::GetPower(BigInt n) const
+{
+ if (!n.positive)
+ throw "Error BIGINT13: Negative exponents not supported!";
+
+ BigInt result(BigIntOne);
+ BigInt base(*this);
+ BigInt bigIntTwo(BigIntOne + BigIntOne);
+
+ while (!n.EqualsZero())
+ {
+ //if n is odd
+ if (n.digits[0] & 1)
+ {
+ result = result * base;
+ n--;
+ }
+ n = n / bigIntTwo;
+ base = base * base;
+ }
+
+ //number was negative and the exponent is odd, the result is negative
+ if (!positive && (n.digits[0] & 1))
+ result.positive = false;
+ return result;
+}
+
+/* *this = *this to the power of n. */
+void BigInt::SetPower(BigInt n)
+{
+ *this = (*this).GetPower(n);
+}
+
+/* Returns (*this to the power of b) mod n. */
+BigInt BigInt::GetPowerMod(const BigInt &b, const BigInt &n) const
+{
+ BigInt a(*this);
+ a.SetPowerMod(b, n);
+ return a;
+}
+
+/* *this = (*this to the power of b) mod n. */
+void BigInt::SetPowerMod(const BigInt &b, const BigInt &n)
+{
+ if (!b.positive)
+ throw "Error BIGINT14: Negative exponent not supported.";
+ //we will need this value later, since *this is going to change
+ const BigInt a(*this);
+ //temporary variables
+ BigInt bCopy(b), q, r;
+ const BigInt two(BigIntOne + BigIntOne);
+
+ //first we will find the binary representation of b
+ std::vector<bool> bits;
+ while (!bCopy.EqualsZero())
+ {
+ BigInt::divide(bCopy, two, q, r);
+ bCopy = q;
+ if (r.EqualsZero())
+ bits.push_back(false);
+ else
+ bits.push_back(true);
+ }
+
+ //do the exponentiating
+ *this = BigIntOne;
+ for (int i = (int) bits.size() - 1; i >= 0; i--)
+ {
+ BigInt::divide(*this * *this, n, q, *this);
+ if (bits[i])
+ BigInt::divide(*this * a, n, q, *this);
+ }
+}
+
+/* Returns the nth digit read-only, zero-based, right-to-left. */
+unsigned char BigInt::GetDigit(unsigned long int index) const
+{
+ if (index >= digitCount)
+ throw "Error BIGINT15: Index out of range.";
+
+ return digits[index];
+}
+
+/* Returns the nth digit, zero-based, right-to-left. */
+void BigInt::SetDigit(unsigned long int index, unsigned char value)
+{
+ if (index >= digitCount)
+ throw "Error BIGINT15: Index out of range.";
+ if (value > 9)
+ throw "Error BIGINT16: Digit value out of range.";
+
+ digits[index] = value;
+}
+
+/* Returns the value of BigInt as std::string. */
+std::string BigInt::ToString(bool forceSign) const
+{
+ (void)forceSign; // avoid unused warning
+ std::string number;
+ if (!positive)
+ number.push_back('-');
+ for (int i = digitCount - 1; i >= 0; i--)
+ number.push_back(char(digits[i]) + '0');
+
+ return number;
+}
+
+/* Returns the absolute value. */
+BigInt BigInt::Abs() const
+{
+ return ((positive) ? *this : -(*this));
+}
diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.h b/SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.h new file mode 100644 index 0000000..c78dc11 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/BigInt.h @@ -0,0 +1,294 @@ +/* ****************************************************************************
+ *
+ * Copyright 2013 Nedim Srndic
+ *
+ * This file is part of rsa - the RSA implementation in C++.
+ *
+ * rsa is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * rsa is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rsa. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * BigInt.h
+ *
+ * Author: Nedim Srndic
+ * Release date: 14th of March 2008
+ *
+ * A class representing a positive or negative integer that may
+ * be too large to fit in any of the standard C++ integer types
+ * (i. e. 2^128 is "just" 39 digits long).
+ * The digits are stored in a dinamic array of tipe unsigned char*,
+ * with values from 0 to 9 (not '0' to '9'), so that the CPU can
+ * add/subtract individual digits.
+ *
+ * The array has "length" memory locations, one byte each (the size of
+ * unsigned char is probably one byte). There are "digitCount" digits actually
+ * in use, the rest is spare space.
+ * The number of digits is constrained by available memory and the limit of the
+ * unsigned long int type used for indexing (the "length" property).
+ * The individual digits are stored right-to-left, to speed up computing and
+ * allow for faster growth of numbers (no need to reallocate memory when
+ * the digitCount grows).
+ *
+ * The class handles its own memory management. There are no memory leaks
+ * reported until this date.
+ * When creating a BigInt from const char* or unsigned long int,
+ * copying from an other BigInt with (digitCount + 2 <= length)
+ * (soon to be full), new memory is allocated and
+ * length is adjusted to (length * FACTOR + 1). This is done to expand the
+ * capacity of the digits array to accomodate potential new digits.
+ * When assigning a BigInt "bInt" that is twice as small or bigger than *this,
+ * the length is set to (bInt.length + 2).
+ *
+ * BigInt supports:
+ *
+ * - addition (unary +, binary +, +=, prefix ++, postfix ++)
+ *
+ * - subtraction (unary -, binary -, -=, prefix --, postfix --)
+ *
+ * - multiplication (*, *=)
+ * For multiplication, one can choose between the Square and multiply
+ * or Karatsuba algorithm, or long multiplication at compile time
+ * (this can be done by defining or undefining the macro "KARATSUBA"
+ * in BigInt.cpp).
+ * The Karatsuba algorithm multiplies integers in O(n^log2(3))
+ * complexity. log2(3) is approximately 1.585, so this should be
+ * significantly faster than long multiplication, if the numbers are
+ * big enough. Currently, the long multiplication is better implemented,
+ * and runs faster than the Karatsuba multiplication for numbers shorter
+ * than about 100 digits.
+ *
+ * - C-style integer division (/, /=)
+ *
+ * - C-style integer division remainder (%, %=)
+ * When calculating the remainder, the number is first divided.
+ *
+ * - comparison (==, !=, <, <=, >, >=)
+ * All of the <, <=, >, >= operators are equally fast.
+ *
+ * - exponentiation (GetPower(), SetPower(), GetPowerMod(), SetPowerMod())
+ * For exponentiation, the Exponantiation by squaring
+ * (or Square and multiply or Binary exponentiation) algorithm is used.
+ * It uses O(log(n)) multiplications and therefore is significantly faster
+ * than multiplying x with itself n-1 times.
+ *
+ * In addition to mathematical operations, BigInt supports:
+ *
+ * - automatic conversion from const char *, std::string and unsigned long int
+ * - safe construction, copying, assignment and destruction
+ * - automatic conversion to std::string
+ * - writing to the standard output (operator <<(std::ostream, BigInt))
+ * - reading from the standard input (operator >>(std::istream, BigInt))
+ * - getting and setting individual digits (GetDigit(), SetDigit())
+ * - returning the number of digits (Length())
+ * - returning a string of digits (ToString())
+ * This can be useful for human-readable output.
+ * - returning a value indicating wether the number is odd (IsOdd())
+ * - returning a value indicating wether the number is positive (IsPositive())
+ * - returning a value indicating wether the BigInt equals zero (EqualsZero())
+ * The fastest way to determine this.
+ * - returning absolute value (Abs())
+ *
+ * There are a few static constants defined in this file:
+ *
+ * - BigIntZero : a zero of type BigInt
+ * If you need a zero fast, use this.
+ * - BigIntOne : a one of type BigInt
+ * If you need a one fast, use this.
+ *
+ * ****************************************************************************
+*/
+
+#ifndef BIGINT_H_
+#define BIGINT_H_
+
+#include <iostream> //ostream, istream
+#include <cmath> //sqrt()
+#include <string> //ToString(), BigInt(std::string)
+
+class BigInt
+{
+ private:
+ /* An array of digits stored right to left,
+ * i.e. int 345 = unsigned char {[5], [4], [3]} */
+ unsigned char *digits;
+ // The total length of the allocated memory
+ unsigned long int length;
+ // Number of digits
+ unsigned long int digitCount;
+ // Sign
+ bool positive;
+ /* Multiplication factor for the length property
+ * when creating or copying objects. */
+ static const double FACTOR;
+ /* Transforms the number from unsigned long int to unsigned char[]
+ * and pads the result with zeroes. Returns the number of digits. */
+ static unsigned long int int2uchar( unsigned long int number,
+ unsigned char *digits,
+ unsigned long int padding);
+ /* Converts ASCII digits to equivalent unsigned char numeric values. */
+ static void char2uchar( unsigned char *array,
+ unsigned long int length);
+ /* Check if all ASCII values are digits '0' to '9'. */
+ static bool allCharsAreDigits( const char *array,
+ unsigned long int length);
+ /* Compares two BigInt. If the last two arguments are
+ * omitted, the comparison is sign-insensitive (comparison by
+ * absolute value). Returns 0 if a == b, 1 if a > b, 2 if a < b. */
+ static int compareNumbers( unsigned char *a, unsigned long int na,
+ unsigned char *b, unsigned long int nb,
+ bool aPositive = true,
+ bool bPositive = true);
+ /* Multiplies two unsigned char[] using the Divide and Conquer
+ * a.k.a. Karatsuba algorithm .*/
+ static void karatsubaMultiply( unsigned char *a, unsigned char *b,
+ unsigned long int n,
+ unsigned char *buffer);
+ /* Multiplies two unsigned char[] the long way. */
+ static void longMultiply( unsigned char *a, unsigned long int na,
+ unsigned char *b, unsigned long int nb,
+ unsigned char *result);
+ /* Simple addition, used by the multiply function.
+ * Returns the remaining carry. */
+ static unsigned char quickAdd( unsigned char *a, unsigned char *b,
+ unsigned long int n);
+ /* Simple subtraction, used by the multiply function. */
+ static void quickSub( unsigned char *a, unsigned char *b,
+ unsigned char *end, unsigned long int n);
+ /* Divides two BigInt numbers. */
+ static void divide( const BigInt ÷nd, const BigInt &divisor,
+ BigInt "ient, BigInt &remainder);
+ /* Returns the value of the specified unsigned char[] as long int. */
+ static unsigned long int toInt(unsigned char *digits, int n);
+ /* Saves the sum of two unsigned char* shorter and longer into result.
+ * It must be nShorter <= nLonger. If doFill == true, it fills the
+ * remaining free places with zeroes (used in KaratsubaMultiply()).
+ * Returns true if there was an overflow at the end (meaning that
+ * the result.digitCount was longer.digitCount + 1. */
+ static bool add(unsigned char *shorter, unsigned long int nShorter,
+ unsigned char *longer, unsigned long int nLonger,
+ unsigned char *result, int nResult,
+ bool doFill = true);
+ /* Shifts the digits n places left. */
+ BigInt &shiftLeft(unsigned long int n);
+ /* Shifts the digits n places right. */
+ BigInt &shiftRight(unsigned long int n);
+ /* Expands the digits* to n. */
+ void expandTo(unsigned long int n);
+ public:
+ BigInt();
+ BigInt(const char *charNum);
+ BigInt(unsigned long int intNum);
+ BigInt(const std::string &str);
+ BigInt(const BigInt &number);
+ BigInt &operator =(const BigInt &rightNumber);
+ ~BigInt();
+ operator std::string() const;
+ friend std::ostream &operator <<( std::ostream &cout,
+ const BigInt &number);
+ friend std::istream &operator >>( std::istream &cin,
+ BigInt &number);
+ friend bool operator <(const BigInt &a, const BigInt &b);
+ friend bool operator <=(const BigInt &a, const BigInt &b);
+ friend bool operator >(const BigInt &a, const BigInt &b);
+ friend bool operator >=(const BigInt &a, const BigInt &b);
+ friend bool operator ==(const BigInt &a, const BigInt &b);
+ friend bool operator !=(const BigInt &a, const BigInt &b);
+ friend BigInt operator + (const BigInt &a, const BigInt &b);
+ BigInt &operator+();
+ BigInt &operator++();
+ BigInt operator++(int);
+ BigInt &operator+=(const BigInt &number);
+ BigInt operator-() const;
+ friend BigInt operator-(const BigInt &a, const BigInt &b);
+ BigInt &operator--();
+ BigInt operator--(int);
+ BigInt &operator-=(const BigInt &number);
+ friend BigInt operator*(const BigInt &a, const BigInt &b);
+ BigInt &operator*=(const BigInt &number);
+ friend BigInt operator/(const BigInt &a, const BigInt &b);
+ BigInt &operator/=(const BigInt &number);
+ friend BigInt operator%(const BigInt &a, const BigInt &b);
+ BigInt &operator%=(const BigInt &number);
+ /* Returns *this to the power of n
+ * using the fast Square and Multiply algorithm. */
+ BigInt GetPower(unsigned long int n) const;
+ /* *this = *this to the power of n. */
+ void SetPower(unsigned long int n);
+ /* Returns *this to the power of n
+ * using the fast Square and Multiply algorithm. */
+ BigInt GetPower(BigInt n) const;
+ /* *this = *this to the power of n. */
+ void SetPower(BigInt n);
+ /* Returns (*this to the power of b) mod n. */
+ BigInt GetPowerMod(const BigInt &b, const BigInt &n) const;
+ /* *this = (*this to the power of b) mod n. */
+ void SetPowerMod(const BigInt &b, const BigInt &n);
+ /* Returns the 'index'th digit (zero-based, right-to-left). */
+ unsigned char GetDigit(unsigned long int index) const;
+ /* Sets the value of 'index'th digit
+ * (zero-based, right-to-left) to 'value'. */
+ void SetDigit(unsigned long int index, unsigned char value);
+ /* Returns the number of digits. */
+ unsigned long int Length() const;
+ /* Returns true if *this is positive, otherwise false. */
+ bool IsPositive() const;
+ /* Returns true if *this is odd, otherwise false. */
+ bool IsOdd() const;
+ /* Returns the value of BigInt as std::string. */
+ std::string ToString(bool forceSign = false) const;
+ /* Returns a value indicating whether *this equals 0. */
+ bool EqualsZero() const;
+ /* Returns the absolute value. */
+ BigInt Abs() const;
+};
+
+inline BigInt::~BigInt()
+{
+ delete[] digits;
+}
+
+inline BigInt &BigInt::operator+()
+{
+ return *this;
+}
+
+/* Returns the number of digits. */
+inline unsigned long int BigInt::Length() const
+{
+ return digitCount;
+}
+
+/* Returns true if *this is positive, otherwise false. */
+inline bool BigInt::IsPositive() const
+{
+ return positive;
+}
+
+/* Returns true if *this is odd, otherwise false. */
+inline bool BigInt::IsOdd() const
+{
+ return digits[0] & 1;
+}
+
+/* Returns a value indicating whether *this equals 0. */
+inline bool BigInt::EqualsZero() const
+{
+ return digitCount == 1 && digits[0] == 0;
+}
+
+// A BigInt number with the value of 0.
+static const BigInt BigIntZero;
+// A BigInt number with the value of 1.
+static const BigInt BigIntOne(1L);
+
+
+#endif /*BIGINT_H_*/
diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/Key.cpp b/SQLiteStudio3/coreSQLiteStudio/rsa/Key.cpp new file mode 100644 index 0000000..adc4a1f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/Key.cpp @@ -0,0 +1,37 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see <http://www.gnu.org/licenses/>. + * + * Key.cpp + * + * Author: Nedim Srndic + * Release date: 5th of September 2008 + * + * This file contains the implementation for the Key class. + * + * **************************************************************************** + */ + +#include "Key.h" + +std::ostream &operator<<(std::ostream &, const Key &key) +{ + return std::cout + << "Modulus: " << key.GetModulus() << std::endl + << "Exponent: " << key.GetExponent(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/Key.h b/SQLiteStudio3/coreSQLiteStudio/rsa/Key.h new file mode 100644 index 0000000..b193e2c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/Key.h @@ -0,0 +1,59 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see <http://www.gnu.org/licenses/>. + * + * Key.h + * + * Author: Nedim Srndic + * Release date: 16th of June 2008 + * + * A class representing a public or private RSA key. + * + * A public or private RSA key consists of a modulus and an exponent. In this + * implementation an object of type BigInt is used to store those values. + * + * **************************************************************************** + */ + +#ifndef KEY_H_ +#define KEY_H_ + +#include "BigInt.h" +#include <iostream> + +class Key +{ + private: + BigInt modulus; + BigInt exponent; + public: + Key(const BigInt &modulus, const BigInt &exponent) : + modulus(modulus), exponent(exponent) + {} + const BigInt &GetModulus() const + { + return modulus; + } + const BigInt &GetExponent() const + { + return exponent; + } + friend std::ostream &operator<<(std::ostream &, const Key &key); +}; + +#endif /*KEY_H_*/ diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.cpp b/SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.cpp new file mode 100644 index 0000000..7780288 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.cpp @@ -0,0 +1,37 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see <http://www.gnu.org/licenses/>. + * + * KeyPair.cpp + * + * Author: Nedim Srndic + * Release date: 22th of July 2008 + * + * This file contains the implementation for the KeyPair class. + * + * **************************************************************************** + */ + +#include "KeyPair.h" + +std::ostream &operator <<(std::ostream &, const KeyPair &k) +{ + return std::cout + << "Private key:" << std::endl << k.GetPrivateKey() << std::endl + << "Public key:" << std::endl << k.GetPublicKey(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.h b/SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.h new file mode 100644 index 0000000..929ffe9 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/KeyPair.h @@ -0,0 +1,58 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see <http://www.gnu.org/licenses/>. + * + * KeyPair.h + * + * Author: Nedim Srndic + * Release date: 17th of June 2008 + * + * A class representing a public/private RSA keypair. + * + * A keypair consists of a public key and a matching private key. + * + * **************************************************************************** + */ + +#ifndef KEYPAIR_H_ +#define KEYPAIR_H_ + +#include "Key.h" +#include <iostream> + +class KeyPair +{ + private: + const Key privateKey; + const Key publicKey; + public: + KeyPair(Key privateKey, Key publicKey): + privateKey(privateKey), publicKey(publicKey) + {} + const Key &GetPrivateKey() const + { + return privateKey; + } + const Key &GetPublicKey() const + { + return publicKey; + } + friend std::ostream &operator <<(std::ostream &, const KeyPair &k); +}; + +#endif /*KEYPAIR_H_*/ diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/PrimeGenerator.cpp b/SQLiteStudio3/coreSQLiteStudio/rsa/PrimeGenerator.cpp new file mode 100644 index 0000000..7f38f55 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/PrimeGenerator.cpp @@ -0,0 +1,198 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see <http://www.gnu.org/licenses/>. + * + * PrimeGenerator.cpp + * + * Author: Nedim Srndic + * Release date: 14th of March 2008 + * + * This file contains the implementation for the PrimeGenerator class. + * + * There is a static constant defined in this file: + * + * - RandMax : RAND_MAX (defined in cstdlib) of type BigInt + * Mainly used for speedup in the Generate member function. + * Represents the largest random unsigned long integer that a particular + * platform can generate. This is platform-specific. + * + * **************************************************************************** + */ + +#include "PrimeGenerator.h" +#include <string> +#include <cstdlib> // rand() + +/* Generates a random number with digitCount digits. + * Returns it by reference in the "number" parameter. */ +void PrimeGenerator::MakeRandom(BigInt &number, unsigned long int digitCount) +{ + //the new number will be created using a string object (newNum), + //and later converted into a BigInt + std::string newNum; + newNum.resize(digitCount); + unsigned long int tempDigitCount(0); + + //generate random digits + while (tempDigitCount < digitCount) + { + unsigned long int newRand(std::rand()); + + //10 is chosen to skip the first digit, because it might be + //statistically <= n, where n is the first digit of RAND_MAX + while (newRand >= 10) + { + newNum[tempDigitCount++] = (newRand % 10) + '0'; + newRand /= 10; + if (tempDigitCount == digitCount) + break; + } + } + + //make sure the leading digit is not zero + if (newNum[0] == '0') + newNum[0] = (std::rand() % 9) + 1 + '0'; + number = newNum; +} + +/* Generates a random number such as 1 <= number < 'top'. + * Returns it by reference in the 'number' parameter. */ +void PrimeGenerator::makeRandom(BigInt &number, const BigInt &top) +{ + //randomly select the number of digits for the random number + unsigned long int newDigitCount = (rand() % top.Length()) + 1; + MakeRandom(number, newDigitCount); + //make sure number < top + while (number >= top) + MakeRandom(number, newDigitCount); +} + +/* Creates an odd BigInt with the specified number of digits. + * Returns it by reference in the "number" parameter. */ +void PrimeGenerator::makePrimeCandidate(BigInt &number, + unsigned long int digitCount) +{ + PrimeGenerator::MakeRandom(number, digitCount); + //make the number odd + if (!number.IsOdd()) + number.SetDigit(0, number.GetDigit(0) + 1); + //make sure the leading digit is not a zero + if (number.GetDigit(number.Length() - 1) == 0) + number.SetDigit(number.Length() - 1, (std::rand() % 9) + 1); +} + +/* Tests the primality of the given _odd_ number using the + * Miller-Rabin probabilistic primality test. Returns true if + * the tested argument "number" is a probable prime with a + * probability of at least 1 - 4^(-k), otherwise false. */ +bool PrimeGenerator::isProbablePrime( const BigInt &number, + unsigned long int k) +{ + //first we need to calculate such a and b, that + //number - 1 = 2^a * b, a and b are integers, b is odd + BigInt numberMinusOne(number - BigIntOne); + unsigned long int a(0); + BigInt temp(numberMinusOne); + BigInt b, quotient; + static const BigInt two(BigIntOne + BigIntOne); + + while (b.EqualsZero()) + { + //temp = quotient * 2 + remainder + + //PrimeGenerator used to be a friend of BigInt, so the following + //statement produced the result in one call to BigInt::divide() +// BigInt::divide(temp, two, quotient, b); + //That doesn't work any more, so we have to use two calls + quotient = temp / two; + b = temp % two; + temp = quotient; + a++; + } + b = temp * two + b; + a--; + + //test with k different possible witnesses to ensure that the probability + //that "number" is prime is at least 1 - 4^(-k) + for (unsigned long int i = 0; i < k; i++) + { + PrimeGenerator::makeRandom(temp, number); + + if (isWitness(temp, number, b, a, numberMinusOne)) + return false; //definitely a composite number + } + return true; //a probable prime +} + +/* Returns true if "candidate" is a witness for the compositeness + * of "number", false if "candidate" is a strong liar. "exponent" + * and "squareCount" are used for computation */ +bool PrimeGenerator::isWitness( BigInt candidate, + const BigInt &number, + const BigInt &exponent, + unsigned long int squareCount, + const BigInt &numberMinusOne) +{ + //calculate candidate = (candidate to the power of exponent) mod number + candidate.SetPowerMod(exponent, number); + //temporary variable, used to call the divide function + BigInt quotient; + + for (unsigned long int i = 0; i < squareCount; i++) + { + bool maybeWitness(false); + if (candidate != BigIntOne && candidate != numberMinusOne) + maybeWitness = true; + + //PrimeGenerator used to be a friend of BigInt, so the following + //statement produced the result in one call to BigInt::divide() +// BigInt::divide(candidate * candidate, number, quotient, candidate); + //That doesn't work any more, so we have to use two calls + candidate = candidate * candidate; + quotient = (candidate) / number; + candidate = (candidate) % number; + if (maybeWitness && candidate == BigIntOne) + return true; //definitely a composite number + } + + if (candidate != BigIntOne) + return true; //definitely a composite number + + return false; //probable prime +} + +/* Returns a probable prime number "digitCount" digits long, + * with a probability of at least 1 - 4^(-k) that it is prime. */ +BigInt PrimeGenerator::Generate(unsigned long int digitCount, + unsigned long int k) +{ + if (digitCount < 3) + throw "Error PRIMEGENERATOR00: Primes less than 3 digits long " + "not supported."; + + BigInt primeCandidate; + PrimeGenerator::makePrimeCandidate(primeCandidate, digitCount); + while (!isProbablePrime(primeCandidate, k)) + { + //select the next odd number and try again + primeCandidate = primeCandidate + 2; + if (primeCandidate.Length() != digitCount) + PrimeGenerator::makePrimeCandidate(primeCandidate, digitCount); + } + return primeCandidate; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/PrimeGenerator.h b/SQLiteStudio3/coreSQLiteStudio/rsa/PrimeGenerator.h new file mode 100644 index 0000000..8a9dfac --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/PrimeGenerator.h @@ -0,0 +1,71 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see <http://www.gnu.org/licenses/>. + * + * PrimeGenerator.h + * + * A class used to generate large prime or random numbers. + * + * Author: Nedim Srndic + * Release date: 14th of March 2008 + * + * **************************************************************************** + */ + +#ifndef PRIMEGENERATOR_H_ +#define PRIMEGENERATOR_H_ + +#include "BigInt.h" + +class PrimeGenerator +{ + private: + /* Generates a random "number" such as 1 <= "number" < "top". + * Returns it by reference in the "number" parameter. */ + static void makeRandom( BigInt &number, + const BigInt &top); + /* Creates an odd BigInt with the specified number of digits. + * Returns it by reference in the "number" parameter. */ + static void makePrimeCandidate( BigInt &number, + unsigned long int digitCount); + /* Tests the primality of the given _odd_ number using the + * Miller-Rabin probabilistic primality test. Returns true if + * the tested argument "number" is a probable prime with a + * probability of at least 1 - 4^(-k), otherwise false. */ + static bool isProbablePrime(const BigInt &number, + unsigned long int k); + /* Returns true if "candidate" is a witness for the compositeness + * of "number", false if "candidate" is a strong liar. "exponent" + * and "squareCount" are used for computation */ + static bool isWitness( BigInt candidate, + const BigInt &number, + const BigInt &exponent, + unsigned long int squareCount, + const BigInt &numberMinusOne); + public: + /* Generates a random number with digitCount digits. + * Returns it by reference in the "number" parameter. */ + static void MakeRandom( BigInt &number, + unsigned long int digitCount); + /* Returns a probable prime number "digitCount" digits long, + * with a probability of at least 1 - 4^(-k) that it is prime. */ + static BigInt Generate( unsigned long int digitCount, + unsigned long int k = 3); +}; + +#endif /*PRIMEGENERATOR_H_*/ diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/RSA.cpp b/SQLiteStudio3/coreSQLiteStudio/rsa/RSA.cpp new file mode 100644 index 0000000..0d8e921 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/RSA.cpp @@ -0,0 +1,394 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see <http://www.gnu.org/licenses/>. + * + * RSA.cpp + * + * Author: Nedim Srndic + * Release date: 16th of June 2008 + * + * This file contains the implementation for the RSA class. + * + * **************************************************************************** + */ + +#include "RSA.h" +#include "Key.h" //Key +#include "KeyPair.h" //KeyPair +#include "PrimeGenerator.h" //Generate() +#include <string> //string +#include <fstream> //ifstream, ofstream + +using std::string; + +/* Returns the greatest common divisor of the two arguments + * "a" and "b", using the Euclidean algorithm. */ +BigInt RSA::GCD(const BigInt &a, const BigInt &b) +{ + if (b.EqualsZero()) + return a; + else + return RSA::GCD(b, a % b); +} + +/* Solves the equation + * d = ax + by + * given a and b, and returns d, x and y by reference. + * It uses the Extended Euclidean Algorithm */ +void RSA::extendedEuclideanAlgorithm( const BigInt &a, const BigInt &b, + BigInt &d, BigInt &x, BigInt &y) +{ + if (b.EqualsZero()) + { + d = a; + x = BigIntOne; + y = BigIntZero; + return; + } + RSA::extendedEuclideanAlgorithm(b, a % b, d, x, y); + BigInt temp(x); + x = y; + y = temp - a / b * y; +} + +/* Solves the equation + * ax is congruent to b (mod n), + * given a, b and n finds x. */ +BigInt RSA::solveModularLinearEquation( const BigInt &a, + const BigInt &b, + const BigInt &n) +{ + BigInt p, q, r; + RSA::extendedEuclideanAlgorithm(a, n, p, q, r); + if ((b % p).EqualsZero()) // This has to evaluate to 'true'. + return (q * (b / p)) % n; + else + throw "Error RSA00: Error in key generation."; // Detect mistakes. +} + +/* Throws an exception if "key" is too short to be used. */ +void RSA::checkKeyLength(const Key &key) +{ + // Minimum required key length is around 24 bits. (In-house requirement) + if (key.GetModulus().Length() < 8) + throw "Error RSA01: Keys must be at least 8 digits long."; +} + +/* Transforms a std::string message into a BigInt message. + * Every ASCII character of the original message is replaced by it's + * ASCII value and appended to the end of the newly created BigInt object + * 'decoded' as a three-digit number, from left to right. */ +BigInt RSA::encode(const string &message) +{ + // The new number will be created using a string object (encoded), + // and converted into a BigInt on return. + string encoded; + encoded.resize(message.length() * 3 + 1); + unsigned long int index = message.length() * 3; + for (unsigned long int i(0); i < message.length(); i++) + { + // Encode the characters using their ASCII values' digits as + // BigInt digits. + unsigned char ASCII = message[i]; + encoded[index - 2] = (ASCII % 10) + '0'; + ASCII /= 10; + encoded[index - 1] = (ASCII % 10) + '0'; + encoded[index] = (ASCII / 10) + '0'; + index -= 3; + } + // We add an special symbol '1' to the beginning of the string 'encoded' + // to make sure that the returned BigInt doesn't begin with a zero. We also + // need to make sure we remove that '1' when decoding (see RSA::decode()). + encoded[0] = '1'; + return encoded; +} + +/* Transforms a BigInt cyphertext into a std::string cyphertext. */ +string RSA::decode(const BigInt &message) +{ + string decoded; + // The special symbol '1' we added to the beginning of the encoded message + // will now be positioned at message[message.Length() - 1], and + // message.Length() -1 must be divisible by 3 without remainder. Thus we + // can ignore the special symbol by only using digits in the range + // from message[0] to message[message.Length() - 2]. + for (unsigned long int i(0); i < message.Length() / 3; i++) + { + // Decode the characters using the ASCII values in the BigInt digits. + char ASCII = 100 * char(message.GetDigit(i * 3)); + ASCII += 10 * char(message.GetDigit(i * 3 + 1)); + decoded.push_back(ASCII + char(message.GetDigit(i * 3 + 2))); + } + return decoded; +} + +/* Encrypts a "chunk" (a small part of a message) using "key" */ +string RSA::encryptChunk(const string &chunk, const Key &key) +{ + // First encode the chunk, to make sure it is represented as an integer. + BigInt a = RSA::encode(chunk); + // The RSA encryption algorithm is a congruence equation. + a.SetPowerMod(key.GetExponent(), key.GetModulus()); + return a.ToString(); +} + +/* Decrypts a "chunk" (a small part of a message) using "key" */ +string RSA::decryptChunk(const BigInt &chunk, const Key &key) +{ + BigInt a = chunk; + // The RSA decryption algorithm is a congruence equation. + a.SetPowerMod(key.GetExponent(), key.GetModulus()); + // Decode the message to a readable form. + return RSA::decode(a); +} + +/* Encrypts a string "message" using "key". */ +std::string RSA::encryptString(const std::string &message, const Key &key) +{ + //partition the message into biggest possible encryptable chunks + const unsigned long int chunkSize(((key.GetModulus().Length() - 2) / 3)); + const unsigned long int chunkCount = message.length() / chunkSize; + + string cypherText; + for (unsigned long int i(0); i < chunkCount; i++) + { + // Get the next chunk. + string chunk(message.substr(i * chunkSize, chunkSize)); + chunk = RSA::encryptChunk(chunk, key); + // Put a ' ' between the chunks so that we can separate them later. + cypherText.append(chunk.append(" ")); + } + // If the last chunk has the same size as the others, we are finished. + if (chunkSize * chunkCount == message.length()) + return cypherText; + + // Handle the last chunk. It is smaller than the others. + const unsigned long int lastChunkSize = message.length() % chunkSize; + string lastChunk(message.substr(chunkCount * chunkSize, lastChunkSize)); + lastChunk = RSA::encryptChunk(lastChunk, key); + return cypherText.append(lastChunk.append(" ")); +} + +/* Decrypts a string "message" using "key". */ +std::string RSA::decryptString(const std::string &cypherText, const Key &key) +{ + // Partition the cypherText into chunks. They are seperated by ' '. + string message; + long int i(0), j(0); + while ((j = cypherText.find(' ', i)) != -1) + { + // Get the chunk. + BigInt chunk(cypherText.substr(i, j - i)); + if (chunk >= key.GetModulus()) + throw "Error RSA02: Chunk too large."; + + // Decrypt the chunk and store the message. + string text = RSA::decryptChunk(chunk, key); + message.append(text); + i = j + 1; + } + return message; +} + +/* Tests the file for 'eof', 'bad ' errors and throws an exception. */ +void RSA::fileError(bool eof, bool bad) +{ + if (eof) + throw "Error RSA03: Unexpected end of file."; + else if (bad) + throw "Error RSA04: Bad file?"; + else + throw "Error RSA05: File contains unexpected data."; +} + +/* Returns the string "message" RSA-encrypted using the key "key". */ +string RSA::Encrypt(const string &message, const Key &key) +{ + RSA::checkKeyLength(key); + + return RSA::encryptString(message, key); +} + +/* Encrypts the file "sourceFile" using the key "key" and saves + * the result into the file "destFile". */ +void RSA::Encrypt( const char *sourceFile, const char *destFile, + const Key &key) +{ + RSA::checkKeyLength(key); + + //open the input and output files + std::ifstream source(sourceFile, std::ios::in | std::ios::binary); + if (!source) + throw "Error RSA06: Opening file \"sourceFile\" failed."; + std::ofstream dest(destFile, std::ios::out | std::ios::binary); + if (!dest) + throw "Error RSA07: Creating file \"destFile\" failed."; + + //find the source file length + source.seekg(0, std::ios::end); + const unsigned long int fileSize = source.tellg(); + source.seekg(0, std::ios::beg); + + //create an input buffer + const unsigned long int bufferSize = 4096; + char buffer[bufferSize]; + + //encrypt file chunks + const unsigned long int chunkCount = fileSize / bufferSize; + for (unsigned long int i(0); i <= chunkCount; i++) + { + unsigned long int readLength; + //read the chunk + if (i == chunkCount) //if it's the last one + readLength = fileSize % bufferSize; + else + readLength = sizeof buffer; + source.read(buffer, readLength); + if (!source) + RSA::fileError(source.eof(), source.bad()); + + //encrypt the chunk + std::string chunk(buffer, readLength); + chunk = RSA::encryptString(chunk, key); + //write the chunk + dest.write(chunk.c_str(), chunk.length()); + if (!dest) + RSA::fileError(dest.eof(), dest.bad()); + } + + source.close(); + dest.close(); +} + +/* Returns the string "cypherText" RSA-decrypted using the key "key". */ +string RSA::Decrypt(const string &cypherText, const Key &key) +{ + RSA::checkKeyLength(key); + + return RSA::decryptString(cypherText, key); +} + +/* Decrypts the file "sourceFile" using the key "key" and saves + * the result into the file "destFile". */ +void RSA::Decrypt( const char *sourceFile, const char *destFile, + const Key &key) +{ + RSA::checkKeyLength(key); + + //open the input and output files + std::ifstream source(sourceFile, std::ios::in | std::ios::binary); + if (!source) + throw "Error RSA08: Opening file \"sourceFile\" failed."; + std::ofstream dest(destFile, std::ios::out | std::ios::binary); + if (!dest) + throw "Error RSA09: Creating file \"destFile\" failed."; + + //find the source file length + source.seekg(0, std::ios::end); + const unsigned long int fileSize = source.tellg(); + source.seekg(0, std::ios::beg); + + //create an input buffer + const unsigned long int bufferSize = 8192; + char buffer[bufferSize]; + unsigned long int readCount = 0; + + while (readCount < fileSize) + { + unsigned long int readLength; + //read new data + if (fileSize - readCount >= bufferSize) //if it's not the last one + readLength = sizeof buffer; + else + readLength = fileSize - readCount; + source.read(buffer, readLength); + if (!source) + RSA::fileError(source.eof(), source.bad()); + + //find the next chunk + std::string chunk(buffer, readLength); + chunk = chunk.substr(0, chunk.find_last_of(' ', chunk.length()) + 1); + readCount += chunk.length(); + source.seekg(readCount, std::ios::beg); + //decrypt the chunk + chunk = RSA::decryptString(chunk, key); + //write the chunk + dest.write(chunk.c_str(), chunk.length()); + if (!dest) + RSA::fileError(dest.eof(), dest.bad()); + } + + source.close(); + dest.close(); +} + +/* Generates a public/private keypair. The keys are retured in a + * KeyPair. The generated keys are 'digitCount' or + * 'digitCount' + 1 digits long. */ +KeyPair RSA::GenerateKeyPair( unsigned long int digitCount, + unsigned long int k) +{ + if (digitCount < 8) + throw "Error RSA10: Keys must be at least 8 digits long."; + + //generate two random numbers p and q + BigInt p(PrimeGenerator::Generate(digitCount / 2 + 2, k)); + BigInt q(PrimeGenerator::Generate(digitCount / 2 - 1, k)); + + //make sure they are different + while (p == q) + { + p = PrimeGenerator::Generate(digitCount / 2 + 1, k); + } + + //calculate the modulus of both the public and private keys, n + BigInt n(p * q); + + //calculate the totient phi + BigInt phi((p - BigIntOne) * (q - BigIntOne)); + + //select a small odd integer e that is coprime with phi and e < phi + //usually 65537 is used, and we will use it too if it fits + //it is recommended that this be the least possible value for e + BigInt e("65537"); + + //make sure the requirements are met + while (RSA::GCD(phi, e) != BigIntOne || e < "65537" || !e.IsOdd()) + { + PrimeGenerator::MakeRandom(e, 5); + } + + //now we have enough information to create the public key + //e is the public key exponent, n is the modulus + Key publicKey(n, e); + + //calculate d, d * e = 1 (mod phi) + BigInt d(RSA::solveModularLinearEquation(e, BigIntOne, phi)); + + //we need a positive private exponent + if (!d.IsPositive()) + return RSA::GenerateKeyPair(digitCount, k); + + //we can create the private key + //d is the private key exponent, n is the modulus + Key privateKey(n, d); + + //finally, the keypair is created and returned + KeyPair newKeyPair(privateKey, publicKey); + return newKeyPair; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/rsa/RSA.h b/SQLiteStudio3/coreSQLiteStudio/rsa/RSA.h new file mode 100644 index 0000000..501261e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/rsa/RSA.h @@ -0,0 +1,130 @@ +/* **************************************************************************** + * + * Copyright 2013 Nedim Srndic + * + * This file is part of rsa - the RSA implementation in C++. + * + * rsa is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rsa is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rsa. If not, see <http://www.gnu.org/licenses/>. + * + * RSA.h + * + * Author: Nedim Srndic + * Release date: 16th of June 2008 + * + * An implementation of the RSA public-key cryptography algorithm. + * + * RSA supports: + * + * - Message encryption (string and file) (Encrypt()) + * - Message decryption (string and file) (Decrypt()) + * - Public/private keypair generation (GenerateKeyPair()) + * + * NOTE: All methods are static. Instantiation, copying and assignment of + * objects of type RSA is forbidden. + * + * NOTE: it is highly recommended to call + * std::srand(time(NULL)); + * once when the program starts and before any use of methods provided by the + * RSA class. Calling the srand() function randomizes the standard C++ + * pseudorandom number generator, so that it provides different series of + * pseudorandom numbers every time the program is run. This greatly improves + * security. + * + * **************************************************************************** + */ + +#ifndef RSA_H_ +#define RSA_H_ + +#include <string> +#include <fstream> +#include "KeyPair.h" +#include "Key.h" +#include "BigInt.h" +#include "coreSQLiteStudio_global.h" + +class API_EXPORT RSA +{ + private: + /* Instantiation of objects of type RSA is forbidden. */ + RSA() + {} + /* Copying of objects of type RSA is forbidden. */ + RSA(const RSA &rsa); + /* Assignment of objects of type RSA is forbidden. */ + RSA &operator=(const RSA &rsa); + /* Returns the greatest common divisor of the two arguments + * "a" and "b", using the Euclidean algorithm. */ + static BigInt GCD(const BigInt &a, const BigInt &b); + /* Solves the equation + * d = ax + by + * given a and b, and returns d, x and y by reference. + * It uses the Extended Euclidean Algorithm */ + static void extendedEuclideanAlgorithm( const BigInt &a, + const BigInt &b, + BigInt &d, + BigInt &x, + BigInt &y); + /* Solves the equation + * ax is congruent to b (mod n), + * given a, b and n finds x. */ + static BigInt solveModularLinearEquation( const BigInt &a, + const BigInt &b, + const BigInt &n); + /* Throws an exception if "key" is too short to be used. */ + static void checkKeyLength(const Key &key); + /* Transforms a std::string message into a BigInt message. */ + static BigInt encode(const std::string &message); + /* Transforms a BigInt cyphertext into a std::string cyphertext. */ + static std::string decode(const BigInt &message); + /* Encrypts a "chunk" (a small part of a message) using "key" */ + static std::string encryptChunk(const std::string &chunk, + const Key &key); + /* Decrypts a "chunk" (a small part of a message) using "key" */ + static std::string decryptChunk(const BigInt &chunk, + const Key &key); + /* Encrypts a string "message" using "key". */ + static std::string encryptString( const std::string &message, + const Key &key); + /* Decrypts a string "message" using "key". */ + static std::string decryptString( const std::string &cypherText, + const Key &key); + /* Tests the file for 'eof', 'bad ' errors and throws an exception. */ + static void fileError(bool eof, bool bad); + public: + /* Returns the string "message" RSA-encrypted using the key "key". */ + static std::string Encrypt( const std::string &message, + const Key &key); + /* Encrypts the file "sourceFile" using the key "key" and saves + * the result into the file "destFile". */ + static void Encrypt(const char *sourceFile, + const char *destFile, + const Key &key); + /* Decrypts the file "sourceFile" using the key "key" and saves + * the result into the file "destFile". */ + static void Decrypt(const char *sourceFile, + const char *destFile, + const Key &key); + /* Returns the string "cypherText" RSA-decrypted + * using the key "key". */ + static std::string Decrypt( const std::string &cypherText, + const Key &key); + /* Generates a public/private keypair. The keys are retured in a + * KeyPair. The generated keys are 'digitCount' or + * 'digitCount' + 1 digits long. */ + static KeyPair GenerateKeyPair( unsigned long int digitCount, + unsigned long int k = 3); +}; + +#endif /*RSA_H_*/ diff --git a/SQLiteStudio3/coreSQLiteStudio/schemaresolver.cpp b/SQLiteStudio3/coreSQLiteStudio/schemaresolver.cpp new file mode 100644 index 0000000..a1ceca5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/schemaresolver.cpp @@ -0,0 +1,910 @@ +#include "schemaresolver.h" +#include "db/db.h" +#include "parser/parsererror.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqlitecreatevirtualtable.h" +#include "parser/ast/sqlitetablerelatedddl.h" +#include <QDebug> + +const char* sqliteMasterDdl = + "CREATE TABLE sqlite_master (type text, name text, tbl_name text, rootpage integer, sql text)"; +const char* sqliteTempMasterDdl = + "CREATE TABLE sqlite_temp_master (type text, name text, tbl_name text, rootpage integer, sql text)"; + +SchemaResolver::SchemaResolver(Db *db) + : db(db) +{ + parser = new Parser(db->getDialect()); +} + +SchemaResolver::~SchemaResolver() +{ + delete parser; +} + +QStringList SchemaResolver::getTables(const QString &database) +{ + QStringList tables = getObjects(database, "table"); + if (!ignoreSystemObjects) + tables << "sqlite_master" << "sqlite_temp_master"; + + return tables; +} + +QStringList SchemaResolver::getIndexes(const QString &database) +{ + QStringList indexes = getObjects(database, "index"); + if (ignoreSystemObjects) + filterSystemIndexes(indexes); + + return indexes; +} + +QStringList SchemaResolver::getTriggers(const QString &database) +{ + return getObjects(database, "trigger"); +} + +QStringList SchemaResolver::getViews(const QString &database) +{ + return getObjects(database, "view"); +} + +StrHash<QStringList> SchemaResolver::getGroupedIndexes(const QString &database) +{ + QStringList allIndexes = getIndexes(database); + return getGroupedObjects(database, allIndexes, SqliteQueryType::CreateIndex); +} + +StrHash<QStringList> SchemaResolver::getGroupedTriggers(const QString &database) +{ + QStringList allTriggers = getTriggers(database); + return getGroupedObjects(database, allTriggers, SqliteQueryType::CreateTrigger); +} + +StrHash< QStringList> SchemaResolver::getGroupedObjects(const QString &database, const QStringList &inputList, SqliteQueryType type) +{ + QString strType = sqliteQueryTypeToString(type); + StrHash< QStringList> groupedTriggers; + + SqliteQueryPtr parsedQuery; + SqliteTableRelatedDdlPtr tableRelatedDdl; + + foreach (QString object, inputList) + { + parsedQuery = getParsedObject(database, object, ANY); + if (!parsedQuery) + { + qWarning() << "Could not get parsed object for " << strType << ":" << object; + continue; + } + + tableRelatedDdl = parsedQuery.dynamicCast<SqliteTableRelatedDdl>(); + if (!tableRelatedDdl) + { + qWarning() << "Parsed object is not of expected type. Expected" << strType + << ", but got" << sqliteQueryTypeToString(parsedQuery->queryType); + continue; + } + + groupedTriggers[tableRelatedDdl->getTargetTable()] << object; + } + + return groupedTriggers; +} + +bool SchemaResolver::isFilteredOut(const QString& value, const QString& type) +{ + if (ignoreSystemObjects) + { + if (type == "table" && isSystemTable(value)) + return true; + + if (type == "index" && isSystemIndex(value, db->getDialect())) + return true; + } + + return false; +} + +QSet<QString> SchemaResolver::getDatabases() +{ + return db->getAllAttaches(); +} + +QStringList SchemaResolver::getTableColumns(const QString& table) +{ + return getTableColumns("main", table); +} + +QStringList SchemaResolver::getTableColumns(const QString &database, const QString &table) +{ + QStringList columns; // result + + SqliteQueryPtr query = getParsedObject(database, table, TABLE); + if (!query) + return columns; + + SqliteCreateTablePtr createTable = query.dynamicCast<SqliteCreateTable>(); + SqliteCreateVirtualTablePtr createVirtualTable = query.dynamicCast<SqliteCreateVirtualTable>(); + if (!createTable && !createVirtualTable) + { + qDebug() << "Parsed DDL is neither a CREATE TABLE or CREATE VIRTUAL TABLE statement. It's: " + << sqliteQueryTypeToString(query->queryType); + + return columns; + } + + // If we parsed virtual table, then we have to create temporary regular table to extract columns. + if (createVirtualTable) + { + createTable = virtualTableAsRegularTable(database, table); + if (!createTable) + return columns; + } + + // Now we have a regular table, let's extract columns. + foreach (SqliteCreateTable::Column* column, createTable->columns) + columns << column->name; + + return columns; +} + +QList<DataType> SchemaResolver::getTableColumnDataTypes(const QString& table, int expectedNumberOfTypes) +{ + return getTableColumnDataTypes("main", table, expectedNumberOfTypes); +} + +QList<DataType> SchemaResolver::getTableColumnDataTypes(const QString& database, const QString& table, int expectedNumberOfTypes) +{ + QList<DataType> dataTypes; + SqliteCreateTablePtr createTable = getParsedObject(database, table, TABLE).dynamicCast<SqliteCreateTable>(); + if (!createTable) + { + for (int i = 0; i < expectedNumberOfTypes; i++) + dataTypes << DataType(); + + return dataTypes; + } + + for (SqliteCreateTable::Column* col : createTable->columns) + { + if (!col->type) + { + dataTypes << DataType(); + continue; + } + + dataTypes << col->type->toDataType(); + } + + for (int i = dataTypes.size(); i < expectedNumberOfTypes; i++) + dataTypes << DataType(); + + return dataTypes; +} + +StrHash<QStringList> SchemaResolver::getAllTableColumns(const QString &database) +{ + StrHash< QStringList> tableColumns; + foreach (QString table, getTables(database)) + tableColumns[table] = getTableColumns(database, table); + + return tableColumns; +} + +QStringList SchemaResolver::getViewColumns(const QString& view) +{ + return getViewColumns("main", view); +} + +QStringList SchemaResolver::getViewColumns(const QString& database, const QString& view) +{ + QList<SelectResolver::Column> resolvedColumns = getViewColumnObjects(database, view); + QStringList columns; + foreach (const SelectResolver::Column& col, resolvedColumns) + columns << col.displayName; + + return columns; +} + +QList<SelectResolver::Column> SchemaResolver::getViewColumnObjects(const QString& view) +{ + return getViewColumnObjects("main", view); +} + +QList<SelectResolver::Column> SchemaResolver::getViewColumnObjects(const QString& database, const QString& view) +{ + QList<SelectResolver::Column> results; + SqliteQueryPtr query = getParsedObject(database, view, VIEW); + if (!query) + return results; + + SqliteCreateViewPtr createView = query.dynamicCast<SqliteCreateView>(); + if (!createView) + { + qDebug() << "Parsed query is not CREATE VIEW statement as expected."; + return results; + } + + SelectResolver resolver(db, createView->select->detokenize()); + QList<QList<SelectResolver::Column> > resolvedColumns = resolver.resolve(createView->select); + if (resolvedColumns.size() == 0) + { + qDebug() << "Could not resolve any results column from the view object."; + return results; + } + return resolvedColumns.first(); +} + +SqliteCreateTablePtr SchemaResolver::virtualTableAsRegularTable(const QString &database, const QString &table) +{ + Dialect dialect = db->getDialect(); + QString strippedName = stripObjName(table, dialect); + QString dbName = getPrefixDb(database, dialect); + + // Create temp table to see columns. + QString newTable = db->getUniqueNewObjectName(strippedName); + QString origTable = wrapObjName(strippedName, dialect); + db->exec(QString("CREATE TEMP TABLE %1 AS SELECT * FROM %2.%3 LIMIT 0;").arg(newTable, dbName, origTable), dbFlags); + + // Get parsed DDL of the temp table. + SqliteQueryPtr query = getParsedObject("temp", newTable, TABLE); + if (!query) + return SqliteCreateTablePtr(); + + SqliteCreateTablePtr createTable = query.dynamicCast<SqliteCreateTable>(); + + // Getting rid of the temp table. + db->exec(QString("DROP TABLE %1;").arg(newTable), dbFlags); + + // Returning results. Might be null. + return createTable; +} + +QString SchemaResolver::getObjectDdl(const QString& name, ObjectType type) +{ + return getObjectDdl("main", name, type); +} + +QString SchemaResolver::getObjectDdl(const QString &database, const QString &name, ObjectType type) +{ + if (name.isNull()) + return QString::null; + + Dialect dialect = db->getDialect(); + // In case of sqlite_master or sqlite_temp_master we have static definitions + QString lowerName = stripObjName(name, dialect).toLower(); + if (lowerName == "sqlite_master") + return getSqliteMasterDdl(false); + else if (lowerName == "sqlite_temp_master") + return getSqliteMasterDdl(true); + + // Prepare db prefix. + QString dbName = getPrefixDb(database, dialect); + + // Get the DDL + QVariant results; + if (type != ANY) + { + results = db->exec(QString( + "SELECT sql FROM %1.sqlite_master WHERE lower(name) = '%2' AND type = '%3';").arg(dbName, escapeString(lowerName), objectTypeToString(type)), + dbFlags + )->getSingleCell(); + } + else + { + results = db->exec(QString( + "SELECT sql FROM %1.sqlite_master WHERE lower(name) = '%2';").arg(dbName, escapeString(lowerName)), + dbFlags + )->getSingleCell(); + } + + // Validate query results + if (!results.isValid() || results.isNull()) + { + qDebug() << "Could not get object's DDL:" << database << "." << name; + return QString::null; + } + + // The DDL string + QString resStr = results.toString(); + + // If the DDL doesn't have semicolon at the end (usually the case), add it. + if (!resStr.trimmed().endsWith(";")) + resStr += ";"; + + // Return the DDL + return resStr; +} + +SqliteQueryPtr SchemaResolver::getParsedObject(const QString &name, ObjectType type) +{ + return getParsedObject("main", name, type); +} + +SqliteQueryPtr SchemaResolver::getParsedObject(const QString &database, const QString &name, ObjectType type) +{ + // Get DDL + QString ddl = getObjectDdl(database, name, type); + if (ddl.isNull()) + return SqliteQueryPtr(); + + // Parse DDL + return getParsedDdl(ddl); +} + +StrHash< SqliteQueryPtr> SchemaResolver::getAllParsedObjects() +{ + return getAllParsedObjects("main"); +} + +StrHash< SqliteQueryPtr> SchemaResolver::getAllParsedObjects(const QString& database) +{ + return getAllParsedObjectsForType<SqliteQuery>(database, QString::null); +} + +StrHash< SqliteCreateTablePtr> SchemaResolver::getAllParsedTables() +{ + return getAllParsedTables("main"); +} + +StrHash< SqliteCreateTablePtr> SchemaResolver::getAllParsedTables(const QString& database) +{ + return getAllParsedObjectsForType<SqliteCreateTable>(database, "table"); +} + +StrHash< SqliteCreateIndexPtr> SchemaResolver::getAllParsedIndexes() +{ + return getAllParsedIndexes("main"); +} + +StrHash< SqliteCreateIndexPtr> SchemaResolver::getAllParsedIndexes(const QString& database) +{ + return getAllParsedObjectsForType<SqliteCreateIndex>(database, "index"); +} + +StrHash< SqliteCreateTriggerPtr> SchemaResolver::getAllParsedTriggers() +{ + return getAllParsedTriggers("main"); +} + +StrHash< SqliteCreateTriggerPtr> SchemaResolver::getAllParsedTriggers(const QString& database) +{ + return getAllParsedObjectsForType<SqliteCreateTrigger>(database, "trigger"); +} + +StrHash< SqliteCreateViewPtr> SchemaResolver::getAllParsedViews() +{ + return getAllParsedViews("main"); +} + +StrHash< SqliteCreateViewPtr> SchemaResolver::getAllParsedViews(const QString& database) +{ + return getAllParsedObjectsForType<SqliteCreateView>(database, "view"); +} + +SqliteQueryPtr SchemaResolver::getParsedDdl(const QString& ddl) +{ + if (!parser->parse(ddl)) + { + qDebug() << "Could not parse DDL for parsing object by SchemaResolver. Errors are:"; + foreach (ParserError* err, parser->getErrors()) + qDebug() << err->getMessage(); + + return SqliteQueryPtr(); + } + + // Validate parsed DDL + QList<SqliteQueryPtr> queries = parser->getQueries(); + if (queries.size() == 0) + { + qDebug() << "No parsed query while getting temp table columns."; + return SqliteQueryPtr(); + } + + // Preparing results + return queries[0]; +} + +QStringList SchemaResolver::getObjects(const QString &type) +{ + return getObjects(QString::null, type); +} + +QStringList SchemaResolver::getObjects(const QString &database, const QString &type) +{ + QStringList resList; + QString dbName = getPrefixDb(database, db->getDialect()); + + SqlQueryPtr results = db->exec(QString("SELECT name FROM %1.sqlite_master WHERE type = ?;").arg(dbName), {type}, dbFlags); + + QString value; + foreach (SqlResultsRowPtr row, results->getAll()) + { + value = row->value(0).toString(); + if (!isFilteredOut(value, type)) + resList << value; + } + + return resList; +} + +QStringList SchemaResolver::getAllObjects() +{ + return getAllObjects(QString::null); +} + +QStringList SchemaResolver::getAllObjects(const QString& database) +{ + QStringList resList; + QString dbName = getPrefixDb(database, db->getDialect()); + + SqlQueryPtr results = db->exec(QString("SELECT name, type FROM %1.sqlite_master;").arg(dbName), dbFlags); + + QString value; + QString type; + foreach (SqlResultsRowPtr row, results->getAll()) + { + value = row->value("name").toString(); + type = row->value("type").toString(); + if (!isFilteredOut(value, type)) + resList << value; + } + + return resList; +} + +QString SchemaResolver::getUniqueName(const QString& database, const QString& namePrefix) +{ + QStringList allObjects = getAllObjects(database); + QString baseName = namePrefix; + QString name = baseName; + for (int i = 0; allObjects.contains(name); i++) + name = baseName + QString::number(i); + + return name; +} + +QString SchemaResolver::getUniqueName(const QString& namePrefix) +{ + return getUniqueName("main", namePrefix); +} + +QStringList SchemaResolver::getFkReferencingTables(const QString& table) +{ + return getFkReferencingTables("main", table); +} + +QStringList SchemaResolver::getFkReferencingTables(const QString& database, const QString& table) +{ + Dialect dialect = db->getDialect(); + if (dialect == Dialect::Sqlite2) + return QStringList(); + + // Get all tables + StrHash<SqliteCreateTablePtr> parsedTables = getAllParsedTables(database); + + // Exclude queried table from the list + parsedTables.remove(table); + + // Resolve referencing tables + return getFkReferencingTables(table, parsedTables.values()); +} + +QStringList SchemaResolver::getFkReferencingTables(const QString& table, const QList<SqliteCreateTablePtr>& allParsedTables) +{ + QStringList tables; + + QList<SqliteCreateTable::Constraint*> tableFks; + QList<SqliteCreateTable::Column::Constraint*> fks; + bool result = false; + for (SqliteCreateTablePtr createTable : allParsedTables) + { + // Check table constraints + tableFks = createTable->getForeignKeysByTable(table); + result = contains<SqliteCreateTable::Constraint*>(tableFks, [&table](SqliteCreateTable::Constraint* fk) + { + return fk->foreignKey->foreignTable == table; + }); + + if (result) + { + tables << createTable->table; + continue; + } + + // Check column constraints + for (SqliteCreateTable::Column* column : createTable->columns) + { + fks = column->getForeignKeysByTable(table); + result = contains<SqliteCreateTable::Column::Constraint*>(fks, [&table](SqliteCreateTable::Column::Constraint* fk) + { + return fk->foreignKey->foreignTable == table; + }); + + if (result) + { + tables << createTable->table; + break; + } + } + } + + return tables; +} + +QStringList SchemaResolver::getIndexesForTable(const QString& database, const QString& table) +{ + QStringList names; + foreach (SqliteCreateIndexPtr idx, getParsedIndexesForTable(database, table)) + names << idx->index; + + return names; +} + +QStringList SchemaResolver::getIndexesForTable(const QString& table) +{ + return getIndexesForTable("main", table); +} + +QStringList SchemaResolver::getTriggersForTable(const QString& database, const QString& table) +{ + QStringList names; + foreach (SqliteCreateTriggerPtr trig, getParsedTriggersForTable(database, table)) + names << trig->trigger; + + return names; +} + +QStringList SchemaResolver::getTriggersForTable(const QString& table) +{ + return getTriggersForTable("main", table); +} + +QStringList SchemaResolver::getTriggersForView(const QString& database, const QString& view) +{ + QStringList names; + foreach (SqliteCreateTriggerPtr trig, getParsedTriggersForView(database, view)) + names << trig->trigger; + + return names; +} + +QStringList SchemaResolver::getTriggersForView(const QString& view) +{ + return getTriggersForView("main", view); +} + +QStringList SchemaResolver::getViewsForTable(const QString& database, const QString& table) +{ + QStringList names; + foreach (SqliteCreateViewPtr view, getParsedViewsForTable(database, table)) + names << view->view; + + return names; +} + +QStringList SchemaResolver::getViewsForTable(const QString& table) +{ + return getViewsForTable("main", table); +} + +StrHash<SchemaResolver::ObjectDetails> SchemaResolver::getAllObjectDetails() +{ + return getAllObjectDetails("main"); +} + +StrHash<SchemaResolver::ObjectDetails> SchemaResolver::getAllObjectDetails(const QString& database) +{ + StrHash< ObjectDetails> details; + ObjectDetails detail; + QString type; + + SqlQueryPtr results = db->exec(QString("SELECT name, type, sql FROM %1.sqlite_master").arg(getPrefixDb(database, db->getDialect())), dbFlags); + if (results->isError()) + { + qCritical() << "Error while getting all object details in SchemaResolver:" << results->getErrorCode(); + return details; + } + + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + type = row->value("type").toString(); + detail.type = stringToObjectType(type); + if (detail.type == ANY) + qCritical() << "Unhlandled db object type:" << type; + + detail.ddl = row->value("sql").toString(); + details[row->value("name").toString()] = detail; + } + return details; +} + +QList<SqliteCreateIndexPtr> SchemaResolver::getParsedIndexesForTable(const QString& database, const QString& table) +{ + QList<SqliteCreateIndexPtr> createIndexList; + + QStringList indexes = getIndexes(database); + SqliteQueryPtr query; + SqliteCreateIndexPtr createIndex; + foreach (const QString& index, indexes) + { + query = getParsedObject(database, index, INDEX); + if (!query) + continue; + + createIndex = query.dynamicCast<SqliteCreateIndex>(); + if (!createIndex) + { + qWarning() << "Parsed DDL was not a CREATE INDEX statement, while queried for indexes."; + continue; + } + + if (createIndex->table.compare(table, Qt::CaseInsensitive) == 0) + createIndexList << createIndex; + } + return createIndexList; +} + +QList<SqliteCreateIndexPtr> SchemaResolver::getParsedIndexesForTable(const QString& table) +{ + return getParsedIndexesForTable("main", table); +} + +QList<SqliteCreateTriggerPtr> SchemaResolver::getParsedTriggersForTable(const QString& database, const QString& table, bool includeContentReferences) +{ + return getParsedTriggersForTableOrView(database, table, includeContentReferences, true); +} + +QList<SqliteCreateTriggerPtr> SchemaResolver::getParsedTriggersForTable(const QString& table, bool includeContentReferences) +{ + return getParsedTriggersForTable("main", table, includeContentReferences); +} + +QList<SqliteCreateTriggerPtr> SchemaResolver::getParsedTriggersForView(const QString& database, const QString& view, bool includeContentReferences) +{ + return getParsedTriggersForTableOrView(database, view, includeContentReferences, false); +} + +QList<SqliteCreateTriggerPtr> SchemaResolver::getParsedTriggersForView(const QString& view, bool includeContentReferences) +{ + return getParsedTriggersForView("main", view, includeContentReferences); +} + +QList<SqliteCreateTriggerPtr> SchemaResolver::getParsedTriggersForTableOrView(const QString& database, const QString& tableOrView, + bool includeContentReferences, bool table) +{ + QList<SqliteCreateTriggerPtr> createTriggerList; + + QStringList triggers = getTriggers(database); + SqliteQueryPtr query; + SqliteCreateTriggerPtr createTrigger; + foreach (const QString& trig, triggers) + { + query = getParsedObject(database, trig, TRIGGER); + if (!query) + continue; + + createTrigger = query.dynamicCast<SqliteCreateTrigger>(); + if (!createTrigger) + { + qWarning() << "Parsed DDL was not a CREATE TRIGGER statement, while queried for triggers." << createTrigger.data(); + continue; + } + + // The condition below checks: + // 1. if this is a call for table triggers and event time is INSTEAD_OF - skip this iteration + // 2. if this is a call for view triggers and event time is _not_ INSTEAD_OF - skip this iteration + // In other words, it's a logical XOR for "table" flag and "eventTime == INSTEAD_OF" condition. + if (table == (createTrigger->eventTime == SqliteCreateTrigger::Time::INSTEAD_OF)) + continue; + + if (createTrigger->table.compare(tableOrView, Qt::CaseInsensitive) == 0) + createTriggerList << createTrigger; + else if (includeContentReferences && indexOf(createTrigger->getContextTables(), tableOrView, Qt::CaseInsensitive) > -1) + createTriggerList << createTrigger; + + } + return createTriggerList; +} + +QString SchemaResolver::objectTypeToString(SchemaResolver::ObjectType type) +{ + switch (type) + { + case TABLE: + return "table"; + case INDEX: + return "index"; + case TRIGGER: + return "trigger"; + case VIEW: + return "view"; + case ANY: + return QString(); + } + return QString(); +} + +SchemaResolver::ObjectType SchemaResolver::stringToObjectType(const QString& type) +{ + if (type == "table") + return SchemaResolver::TABLE; + else if (type == "index") + return SchemaResolver::INDEX; + else if (type == "trigger") + return SchemaResolver::TRIGGER; + else if (type == "view") + return SchemaResolver::VIEW; + else + return SchemaResolver::ANY; +} + +QList<SqliteCreateViewPtr> SchemaResolver::getParsedViewsForTable(const QString& database, const QString& table) +{ + QList<SqliteCreateViewPtr> createViewList; + + QStringList views = getViews(database); + SqliteQueryPtr query; + SqliteCreateViewPtr createView; + foreach (const QString& view, views) + { + query = getParsedObject(database, view, VIEW); + if (!query) + continue; + + createView = query.dynamicCast<SqliteCreateView>(); + if (!createView) + { + qWarning() << "Parsed DDL was not a CREATE VIEW statement, while queried for views."; + continue; + } + + if (indexOf(createView->getContextTables(), table, Qt::CaseInsensitive) > -1) + createViewList << createView; + } + return createViewList; +} + +QList<SqliteCreateViewPtr> SchemaResolver::getParsedViewsForTable(const QString& table) +{ + return getParsedViewsForTable("main", table); +} + +void SchemaResolver::filterSystemIndexes(QStringList& indexes) +{ + Dialect dialect = db->getDialect(); + QMutableListIterator<QString> it(indexes); + while (it.hasNext()) + { + if (isSystemIndex(it.next(), dialect)) + it.remove(); + } +} + +bool SchemaResolver::isWithoutRowIdTable(const QString& table) +{ + return isWithoutRowIdTable("main", table); +} + +bool SchemaResolver::isWithoutRowIdTable(const QString& database, const QString& table) +{ + SqliteQueryPtr query = getParsedObject(database, table, TABLE); + if (!query) + return false; + + SqliteCreateTablePtr createTable = query.dynamicCast<SqliteCreateTable>(); + if (!createTable) + return false; + + return !createTable->withOutRowId.isNull(); +} + +bool SchemaResolver::isVirtualTable(const QString& database, const QString& table) +{ + SqliteQueryPtr query = getParsedObject(database, table, TABLE); + if (!query) + return false; + + SqliteCreateVirtualTablePtr createVirtualTable = query.dynamicCast<SqliteCreateVirtualTable>(); + return !createVirtualTable.isNull(); +} + +bool SchemaResolver::isVirtualTable(const QString& table) +{ + return isVirtualTable("main", table); +} + +SqliteCreateTablePtr SchemaResolver::resolveVirtualTableAsRegularTable(const QString& table) +{ + return resolveVirtualTableAsRegularTable("maine", table); +} + +SqliteCreateTablePtr SchemaResolver::resolveVirtualTableAsRegularTable(const QString& database, const QString& table) +{ + return virtualTableAsRegularTable(database, table); +} + +QStringList SchemaResolver::getWithoutRowIdTableColumns(const QString& table) +{ + return getWithoutRowIdTableColumns("main", table); +} + +QStringList SchemaResolver::getWithoutRowIdTableColumns(const QString& database, const QString& table) +{ + QStringList columns; + + SqliteQueryPtr query = getParsedObject(database, table, TABLE); + if (!query) + return columns; + + SqliteCreateTablePtr createTable = query.dynamicCast<SqliteCreateTable>(); + if (!createTable) + return columns; + + if (createTable->withOutRowId.isNull()) + return columns; // it's not WITHOUT ROWID table + + return createTable->getPrimaryKeyColumns(); +} + +QString SchemaResolver::getSqliteMasterDdl(bool temp) +{ + if (temp) + return sqliteTempMasterDdl; + + return sqliteMasterDdl; +} + +QStringList SchemaResolver::getCollations() +{ + QStringList list; + if (db->getDialect() != Dialect::Sqlite3) + return list; + + SqlQueryPtr results = db->exec("PRAGMA collation_list", dbFlags); + if (results->isError()) + { + qWarning() << "Could not read collation list from the database:" << results->getErrorText(); + return list; + } + + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + list << row->value("name").toString(); + } + + return list; +} + +bool SchemaResolver::getIgnoreSystemObjects() const +{ + return ignoreSystemObjects; +} + +void SchemaResolver::setIgnoreSystemObjects(bool value) +{ + ignoreSystemObjects = value; +} + +bool SchemaResolver::getNoDbLocking() const +{ + return dbFlags.testFlag(Db::Flag::NO_LOCK); +} + +void SchemaResolver::setNoDbLocking(bool value) +{ + if (value) + dbFlags |= Db::Flag::NO_LOCK; + else + dbFlags ^= Db::Flag::NO_LOCK; +} + diff --git a/SQLiteStudio3/coreSQLiteStudio/schemaresolver.h b/SQLiteStudio3/coreSQLiteStudio/schemaresolver.h new file mode 100644 index 0000000..e1a8d5d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/schemaresolver.h @@ -0,0 +1,220 @@ +#ifndef SCHEMARESOLVER_H +#define SCHEMARESOLVER_H + +#include "parser/parser.h" +#include "parser/ast/sqlitequerytype.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "coreSQLiteStudio_global.h" +#include "common/utils_sql.h" +#include "selectresolver.h" +#include "db/sqlresultsrow.h" +#include "db/sqlquery.h" +#include "db/db.h" +#include "common/strhash.h" +#include <QStringList> + +class SqliteCreateTable; + +// TODO add cache + +class API_EXPORT SchemaResolver +{ + public: + enum ObjectType + { + TABLE, + INDEX, + TRIGGER, + VIEW, + ANY + }; + + struct ObjectDetails + { + ObjectType type; + QString ddl; + }; + + explicit SchemaResolver(Db* db); + virtual ~SchemaResolver(); + + QStringList getTables(const QString& database = QString::null); + QStringList getIndexes(const QString& database = QString::null); + QStringList getTriggers(const QString& database = QString::null); + QStringList getViews(const QString& database = QString::null); + StrHash<QStringList> getGroupedIndexes(const QString& database = QString::null); + StrHash<QStringList> getGroupedTriggers(const QString& database = QString::null); + QSet<QString> getDatabases(); + QStringList getObjects(const QString& type); + QStringList getObjects(const QString& database, const QString& type); + QStringList getAllObjects(); + QStringList getAllObjects(const QString& database); + QString getUniqueName(const QString& database, const QString& namePrefix); + QString getUniqueName(const QString& namePrefix = QString::null); + QStringList getFkReferencingTables(const QString& table); + QStringList getFkReferencingTables(const QString& database, const QString& table); + + QStringList getIndexesForTable(const QString& database, const QString& table); + QStringList getIndexesForTable(const QString& table); + QStringList getTriggersForTable(const QString& database, const QString& table); + QStringList getTriggersForTable(const QString& table); + QStringList getTriggersForView(const QString& database, const QString& view); + QStringList getTriggersForView(const QString& view); + QStringList getViewsForTable(const QString& database, const QString& table); + QStringList getViewsForTable(const QString& table); + + StrHash<ObjectDetails> getAllObjectDetails(); + StrHash<ObjectDetails> getAllObjectDetails(const QString& database); + + QList<SqliteCreateIndexPtr> getParsedIndexesForTable(const QString& database, const QString& table); + QList<SqliteCreateIndexPtr> getParsedIndexesForTable(const QString& table); + QList<SqliteCreateTriggerPtr> getParsedTriggersForTable(const QString& database, const QString& table, bool includeContentReferences = false); + QList<SqliteCreateTriggerPtr> getParsedTriggersForTable(const QString& table, bool includeContentReferences = false); + QList<SqliteCreateTriggerPtr> getParsedTriggersForView(const QString& database, const QString& view, bool includeContentReferences = false); + QList<SqliteCreateTriggerPtr> getParsedTriggersForView(const QString& view, bool includeContentReferences = false); + QList<SqliteCreateViewPtr> getParsedViewsForTable(const QString& database, const QString& table); + QList<SqliteCreateViewPtr> getParsedViewsForTable(const QString& table); + + bool isWithoutRowIdTable(const QString& table); + bool isWithoutRowIdTable(const QString& database, const QString& table); + bool isVirtualTable(const QString& database, const QString& table); + bool isVirtualTable(const QString& table); + SqliteCreateTablePtr resolveVirtualTableAsRegularTable(const QString& table); + SqliteCreateTablePtr resolveVirtualTableAsRegularTable(const QString& database, const QString& table); + + QStringList getWithoutRowIdTableColumns(const QString& table); + QStringList getWithoutRowIdTableColumns(const QString& database, const QString& table); + + /** + * @brief getTableColumns Get column names for a table. + * @param table Table to query. + * @return List of column names of the table. + * This does something similar to getting list of columns with + * PRAGMA table_info(), but the pragma in Sqlite2 doesn't support + * queries for attached databases, therefore this method is provided + * to make this possible. It finds table's DDL and parses it, + * then extracts list of columns from parsing results. + */ + QStringList getTableColumns(const QString& table); + + /** + * @brief getTableColumns Get column names for a table. + * @param database Attached database name. + * @param table Table to query. + * @return List of column names of the table. + * @overload + */ + QStringList getTableColumns(const QString& database, const QString& table); + + QList<DataType> getTableColumnDataTypes(const QString& table, int expectedNumberOfTypes = -1); + QList<DataType> getTableColumnDataTypes(const QString& database, const QString& table, int expectedNumberOfTypes = -1); + + StrHash<QStringList> getAllTableColumns(const QString& database = QString::null); + + QStringList getViewColumns(const QString& view); + QStringList getViewColumns(const QString& database, const QString& view); + QList<SelectResolver::Column> getViewColumnObjects(const QString& view); + QList<SelectResolver::Column> getViewColumnObjects(const QString& database, const QString& view); + + QString getObjectDdl(const QString& name, ObjectType type); + QString getObjectDdl(const QString& database, const QString& name, ObjectType type); + + /** + * @brief Parses given object's DDL. + * @param name Name of the object in the database. + * @return Parsed object, or null pointer if named object was not in the database, or parsing error occured. + * + * Returned query has to be deleted outside! + */ + SqliteQueryPtr getParsedObject(const QString& name, ObjectType type); + + /** + * @brief Parses given object's DDL. + * @param database Database that the object is in (the attach name of the database). + * @param name Name of the object in the database. + * @return Parsed object, or null pointer if named object was not in the database, or parsing error occured. + * @overload + */ + SqliteQueryPtr getParsedObject(const QString& database, const QString& name, ObjectType type); + + StrHash<SqliteQueryPtr> getAllParsedObjects(); + StrHash<SqliteQueryPtr> getAllParsedObjects(const QString& database); + StrHash<SqliteCreateTablePtr> getAllParsedTables(); + StrHash<SqliteCreateTablePtr> getAllParsedTables(const QString& database); + StrHash<SqliteCreateIndexPtr> getAllParsedIndexes(); + StrHash<SqliteCreateIndexPtr> getAllParsedIndexes(const QString& database); + StrHash<SqliteCreateTriggerPtr> getAllParsedTriggers(); + StrHash<SqliteCreateTriggerPtr> getAllParsedTriggers(const QString& database); + StrHash<SqliteCreateViewPtr> getAllParsedViews(); + StrHash<SqliteCreateViewPtr> getAllParsedViews(const QString& database); + + static QString getSqliteMasterDdl(bool temp = false); + static QStringList getFkReferencingTables(const QString& table, const QList<SqliteCreateTablePtr>& allParsedTables); + + QStringList getCollations(); + + bool getIgnoreSystemObjects() const; + void setIgnoreSystemObjects(bool value); + + bool getNoDbLocking() const; + void setNoDbLocking(bool value); + + static QString objectTypeToString(ObjectType type); + static ObjectType stringToObjectType(const QString& type); + + private: + SqliteQueryPtr getParsedDdl(const QString& ddl); + SqliteCreateTablePtr virtualTableAsRegularTable(const QString& database, const QString& table); + StrHash< QStringList> getGroupedObjects(const QString &database, const QStringList& inputList, SqliteQueryType type); + bool isFilteredOut(const QString& value, const QString& type); + void filterSystemIndexes(QStringList& indexes); + QList<SqliteCreateTriggerPtr> getParsedTriggersForTableOrView(const QString& database, const QString& tableOrView, bool includeContentReferences, bool table); + + template <class T> + StrHash<QSharedPointer<T>> getAllParsedObjectsForType(const QString& database, const QString& type); + + Db* db = nullptr; + Parser* parser = nullptr; + bool ignoreSystemObjects = false; + Db::Flags dbFlags; +}; + +template <class T> +StrHash<QSharedPointer<T>> SchemaResolver::getAllParsedObjectsForType(const QString& database, const QString& type) +{ + StrHash< QSharedPointer<T>> parsedObjects; + + QString dbName = getPrefixDb(database, db->getDialect()); + + SqlQueryPtr results; + + if (type.isNull()) + results = db->exec(QString("SELECT name, type, sql FROM %1.sqlite_master;").arg(dbName)); + else + results = db->exec(QString("SELECT name, type, sql FROM %1.sqlite_master WHERE type = '%2';").arg(dbName, type)); + + QString name; + SqliteQueryPtr parsedObject; + QSharedPointer<T> castedObject; + foreach (SqlResultsRowPtr row, results->getAll()) + { + name = row->value("name").toString(); + parsedObject = getParsedDdl(row->value("sql").toString()); + if (!parsedObject) + continue; + + if (isFilteredOut(name, row->value("type").toString())) + continue; + + castedObject = parsedObject.dynamicCast<T>(); + if (castedObject) + parsedObjects[name] = castedObject; + } + + return parsedObjects; +} + +#endif // SCHEMARESOLVER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/selectresolver.cpp b/SQLiteStudio3/coreSQLiteStudio/selectresolver.cpp new file mode 100644 index 0000000..9e425a2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/selectresolver.cpp @@ -0,0 +1,673 @@ +#include "selectresolver.h" +#include "parser/token.h" +#include "parser/lexer.h" +#include "parser/keywords.h" +#include "schemaresolver.h" +#include "parser/ast/sqlitecreateview.h" +#include "common/global.h" +#include <QDebug> +#include <QHash> +#include <QHashIterator> +#include <QString> + +SelectResolver::SelectResolver(Db *db, const QString& originalQuery) +{ + this->db = db; + this->query = originalQuery; + schemaResolver = new SchemaResolver(db); +} + +SelectResolver::SelectResolver(Db* db, const QString& originalQuery, const BiStrHash& dbNameToAttach) : + SelectResolver(db, originalQuery) +{ + this->dbNameToAttach = dbNameToAttach; +} + +SelectResolver::~SelectResolver() +{ + safe_delete(schemaResolver); +} + +QList<SelectResolver::Column> SelectResolver::resolve(SqliteSelect::Core *selectCore) +{ + errors.clear(); + return resolveCore(selectCore); +} + +QList<QList<SelectResolver::Column> > SelectResolver::resolve(SqliteSelect *select) +{ + errors.clear(); + QList<QList<SelectResolver::Column> > results; + foreach (SqliteSelect::Core* core, select->coreSelects) + { + results << resolveCore(core); + currentCoreResults.clear(); + } + + return results; +} + +QList<SelectResolver::Column> SelectResolver::resolveAvailableColumns(SqliteSelect::Core *selectCore) +{ + errors.clear(); + return resolveAvailableCoreColumns(selectCore); +} + +QList<QList<SelectResolver::Column> > SelectResolver::resolveAvailableColumns(SqliteSelect *select) +{ + errors.clear(); + QList<QList<SelectResolver::Column> > results; + foreach (SqliteSelect::Core* core, select->coreSelects) + results << resolveAvailableCoreColumns(core); + + return results; +} + +QSet<SelectResolver::Table> SelectResolver::resolveTables(SqliteSelect::Core *selectCore) +{ + QSet<Table> tables; + QList<Column> columns = resolveAvailableColumns(selectCore); + foreach (Column col, columns) + tables << col.getTable(); + + return tables; +} + +QList<QSet<SelectResolver::Table> > SelectResolver::resolveTables(SqliteSelect *select) +{ + QList<QSet<Table> > results; + QList<QList<Column> > columnLists = resolveAvailableColumns(select); + foreach (QList<Column> columns, columnLists) + { + QSet<Table> tables; + foreach (Column col, columns) + tables << col.getTable(); + + results << tables; + } + + return results; +} + +QList<SelectResolver::Column> SelectResolver::translateToColumns(SqliteSelect* select, const TokenList& columnTokens) +{ + errors.clear(); + QList<SelectResolver::Column> results; + foreach (TokenPtr token, columnTokens) + results << translateTokenToColumn(select, token); + + return results; +} + +SelectResolver::Column SelectResolver::translateToColumns(SqliteSelect* select, TokenPtr token) +{ + errors.clear(); + return translateTokenToColumn(select, token); +} + +bool SelectResolver::hasErrors() const +{ + return !errors.isEmpty(); +} + +const QStringList& SelectResolver::getErrors() const +{ + return errors; +} + +QList<SelectResolver::Column> SelectResolver::resolveCore(SqliteSelect::Core* selectCore) +{ + if (selectCore->from) + currentCoreSourceColumns = resolveJoinSource(selectCore->from); + + foreach (SqliteSelect::Core::ResultColumn* resCol, selectCore->resultColumns) + resolve(resCol); + + if (selectCore->distinctKw) + markDistinctColumns(); + + if (selectCore->groupBy.size() > 0) + markGroupedColumns(); + + fixColumnNames(); + + SqliteSelect* select = dynamic_cast<SqliteSelect*>(selectCore->parentStatement()); + if (select && select->coreSelects.size() > 1) + markCompoundColumns(); + + if (select && select->with) + markCteColumns(); + + return currentCoreResults; +} + +QList<SelectResolver::Column> SelectResolver::resolveAvailableCoreColumns(SqliteSelect::Core* selectCore) +{ + QList<Column> columns; + if (selectCore->from) + columns = resolveJoinSource(selectCore->from); + + SqliteSelect* select = dynamic_cast<SqliteSelect*>(selectCore->parentStatement()); + if (select && select->with) + markCteColumns(); + + return columns; +} + +SelectResolver::Column SelectResolver::translateTokenToColumn(SqliteSelect* select, TokenPtr token) +{ + // Default result + Column notTranslatedColumn; + notTranslatedColumn.type = Column::OTHER; + notTranslatedColumn.column = token->value; + + // Find containing statement + SqliteStatement* parentStmt = select->findStatementWithToken(token); + if (!parentStmt) + { + qDebug() << "Could not find containing statement for given token while translating column token:" << token->toString() + << "Select tokens:" << select->tokens.toString(); + + return notTranslatedColumn; + } + + // Go through all select cores, from the most deep, to the most shallow + SqliteSelect::Core* core = nullptr; + while (parentStmt) + { + // Find nearest SELECT core. + while (parentStmt && !(core = dynamic_cast<SqliteSelect::Core*>(parentStmt))) + parentStmt = parentStmt->parentStatement(); + + if (!core) + { + qDebug() << "Could not find SqliteSelect::Core object for given token while translating column token:" << token->toString() + << "Select:" << select->detokenize(); + + return notTranslatedColumn; + } + + // Search through available columns + foreach (const Column& availableColumn, resolveAvailableColumns(core)) + { + if (availableColumn.type == Column::COLUMN && availableColumn.column.compare(token->value, Qt::CaseInsensitive) == 0) + return availableColumn; + } + + // Not in this core. See if there is any core upper (if this was a subselect). + parentStmt = parentStmt->parentStatement(); + } + + return notTranslatedColumn; +} + +void SelectResolver::markDistinctColumns() +{ + markCurrentColumnsWithFlag(FROM_DISTINCT_SELECT); +} + +void SelectResolver::markCompoundColumns() +{ + markCurrentColumnsWithFlag(FROM_COMPOUND_SELECT); +} + +void SelectResolver::markCteColumns() +{ + markCurrentColumnsWithFlag(FROM_CTE_SELECT); +} + +void SelectResolver::markGroupedColumns() +{ + markCurrentColumnsWithFlag(FROM_GROUPED_SELECT); +} + +void SelectResolver::fixColumnNames() +{ + QSet<QString> existingDisplayNames; + QString originalName; + int i; + + QMutableListIterator<Column> it(currentCoreResults); + while (it.hasNext()) + { + originalName = it.next().displayName; + for (i = 1; existingDisplayNames.contains(it.value().displayName); i++) + it.value().displayName = originalName + ":" + QString::number(i); + + existingDisplayNames << it.value().displayName; + } +} + +void SelectResolver::markCurrentColumnsWithFlag(SelectResolver::Flag flag) +{ + QMutableListIterator<Column> it(currentCoreResults); + while (it.hasNext()) + it.next().flags |= flag; +} + +void SelectResolver::resolve(SqliteSelect::Core::ResultColumn *resCol) +{ + if (resCol->star) + resolveStar(resCol); + else + resolveExpr(resCol); +} + +void SelectResolver::resolveStar(SqliteSelect::Core::ResultColumn *resCol) +{ + bool foundAtLeastOne = false; + foreach (SelectResolver::Column column, currentCoreSourceColumns) + { + if (!resCol->table.isNull()) + { + /* + * Star was prefixed with table or table alias. + * The "FROM" clause allows to use alias name the same as + * some other table real name in the very same "FROM". + * Their columns concatenate, so here we allow any column that + * prefix matches either alias or table from data source list. + * For example it's correct to query: + * SELECT test.* FROM test, otherTable AS test; + * This case is simpler then in resolveDbAndTable(), + * because here's no database allowed. + * + * Also, if the table has an alias specified, + * then the alias has a precedence before table's name, + * therefore we match table name only if the table alias + * is null. + */ + if ( + ( + !column.tableAlias.isNull() && + resCol->table.compare(column.tableAlias, Qt::CaseInsensitive) != 0 + ) || + ( + column.tableAlias.isNull() && + resCol->table.compare(column.table, Qt::CaseInsensitive) != 0 + ) + + ) + { + continue; + } + } + + // If source column name is aliased, use it + if (!column.alias.isNull()) + column.displayName = column.alias; + else + column.displayName = column.column; + + column.originalColumn = resCol; + currentCoreResults << column; + foundAtLeastOne = true; + } + + if (!foundAtLeastOne) + errors << QObject::tr("Could not resolve data source for column: %1").arg(resCol->detokenize()); +} + +void SelectResolver::resolveExpr(SqliteSelect::Core::ResultColumn *resCol) +{ + SelectResolver::Column column; + column.alias = resCol->alias; + column.originalColumn = resCol; + column.column = getResColTokensWithoutAlias(resCol).detokenize().trimmed(); + column.displayName = !resCol->alias.isNull() ? column.alias : column.column; + + SqliteExpr* expr = resCol->expr; + if (expr->mode != SqliteExpr::Mode::ID) + { + // Not a simple column, but some expression + column.type = Column::OTHER; + currentCoreResults << column; + + // In this case we end it here. + return; + } + + // Now we know we're dealing with db.table.column (with db and table optional) + resolveDbAndTable(resCol); +} + +void SelectResolver::resolveDbAndTable(SqliteSelect::Core::ResultColumn *resCol) +{ + SqliteExpr* expr = resCol->expr; + + // Basic info + Column col; + col.alias = resCol->alias; + col.column = expr->column; + col.originalColumn = resCol; + col.type = Column::COLUMN; + + // Display name + if (col.alias.isNull()) + col.displayName = expr->column; + else + col.displayName = col.alias; + + // Looking for table relation + Column matched; + if (isRowIdKeyword(expr->column)) + matched = resolveRowIdColumn(expr); + else if (!expr->database.isNull()) + matched = resolveExplicitColumn(expr->database, expr->table, expr->column); + else if (!expr->table.isNull()) + matched = resolveExplicitColumn(expr->table, expr->column); + else + matched = resolveExplicitColumn(expr->column); + + + if (!matched.table.isNull()) + { + col.database = matched.database; + col.originalDatabase = resolveDatabase(matched.database); + col.table = matched.table; + col.tableAlias = matched.tableAlias; + col.flags = matched.flags; + } + else if (!ignoreInvalidNames) + { + qDebug() << "Source table for column '" << expr->detokenize() + << "' not matched while resolving select: " << query; + } + + currentCoreResults << col; +} + +SelectResolver::Column SelectResolver::resolveRowIdColumn(SqliteExpr *expr) +{ + // Looking for first source that can provide ROWID. + foreach (Column column, currentCoreSourceColumns) + { + if (column.table.isNull()) + continue; // ROWID cannot be related to source with no table + + if (!expr->table.isNull() && matchTable(column, expr->table)) + return column; + } + return Column(); +} + +SelectResolver::Column SelectResolver::resolveExplicitColumn(const QString &columnName) +{ + foreach (const Column& column, currentCoreSourceColumns) + { + if (columnName.compare(column.column, Qt::CaseInsensitive) != 0 && columnName.compare(column.alias, Qt::CaseInsensitive) != 0) + continue; + + return column; + } + return Column(); +} + +SelectResolver::Column SelectResolver::resolveExplicitColumn(const QString &table, const QString &columnName) +{ + foreach (const Column& column, currentCoreSourceColumns) + { + if (columnName.compare(column.column, Qt::CaseInsensitive) != 0 && columnName.compare(column.alias, Qt::CaseInsensitive) != 0) + continue; + + if (!matchTable(column, table)) + continue; + + return column; + } + return Column(); +} + +SelectResolver::Column SelectResolver::resolveExplicitColumn(const QString &database, const QString &table, const QString &columnName) +{ + foreach (const Column& column, currentCoreSourceColumns) + { + if (columnName.compare(column.column, Qt::CaseInsensitive) != 0 && columnName.compare(column.alias, Qt::CaseInsensitive) != 0) + continue; + + if (!matchTable(column, table)) + continue; + + if (database.compare(column.database, Qt::CaseInsensitive) != 0) + continue; + + return column; + } + return Column(); +} + +bool SelectResolver::matchTable(const SelectResolver::Column &sourceColumn, const QString &table) +{ + // First check by tableAlias if it's present + if (!sourceColumn.tableAlias.isNull()) + return (sourceColumn.tableAlias.compare(table, Qt::CaseInsensitive) == 0); + + return (sourceColumn.table.compare(table, Qt::CaseInsensitive) == 0); +} + +TokenList SelectResolver::getResColTokensWithoutAlias(SqliteSelect::Core::ResultColumn *resCol) +{ + TokenList allTokens = resCol->tokens; + if (!resCol->alias.isNull()) + { + int depth = 0; + int idx = -1; + int idxCandidate = -1; + for (const TokenPtr& token : allTokens) + { + idxCandidate++; + if (token->type == Token::PAR_LEFT) + { + depth++; + } + else if (token->type == Token::PAR_RIGHT) + { + depth--; + } + else if (token->type == Token::KEYWORD && token->value.compare("AS", Qt::CaseInsensitive) && depth <= 0) + { + idx = idxCandidate; + break; + } + } + + if (idx > -1) + allTokens = allTokens.mid(0, idx - 1); + } + + return allTokens; +} + +QList<SelectResolver::Column> SelectResolver::resolveJoinSource(SqliteSelect::Core::JoinSource *joinSrc) +{ + QList<SelectResolver::Column> columnSources; + columnSources += resolveSingleSource(joinSrc->singleSource); + foreach (SqliteSelect::Core::JoinSourceOther* otherSrc, joinSrc->otherSources) + columnSources += resolveOtherSource(otherSrc); + + return columnSources; +} + +QList<SelectResolver::Column> SelectResolver::resolveSingleSource(SqliteSelect::Core::SingleSource *joinSrc) +{ + if (!joinSrc) + return QList<Column>(); + + if (joinSrc->select) + return resolveSingleSourceSubSelect(joinSrc); + + if (joinSrc->joinSource) + return resolveJoinSource(joinSrc->joinSource); + + if (isView(joinSrc->database, joinSrc->table)) + return resolveView(joinSrc->database, joinSrc->table, joinSrc->alias); + + QList<Column> columnSources; + QStringList columns = getTableColumns(joinSrc->database, joinSrc->table, joinSrc->alias); + Column column; + foreach (QString columnName, columns) + { + column.type = Column::COLUMN; + column.column = columnName; + column.table = joinSrc->table;; + column.database = joinSrc->database; + column.originalDatabase = resolveDatabase(joinSrc->database); + if (!joinSrc->alias.isNull()) + column.tableAlias = joinSrc->alias; + + columnSources << column; + } + + return columnSources; +} + +QList<SelectResolver::Column> SelectResolver::resolveSingleSourceSubSelect(SqliteSelect::Core::SingleSource *joinSrc) +{ + QList<Column> columnSources = resolveSubSelect(joinSrc->select); + applySubSelectAlias(columnSources, joinSrc->alias); + return columnSources; +} + +QList<SelectResolver::Column> SelectResolver::resolveOtherSource(SqliteSelect::Core::JoinSourceOther *otherSrc) +{ + return resolveSingleSource(otherSrc->singleSource); +} + +QList<SelectResolver::Column> SelectResolver::resolveSubSelect(SqliteSelect *select) +{ + QList<Column> columnSources; + Q_ASSERT(select->coreSelects.size() > 0); + + bool compound = (select->coreSelects.size() > 1); + + if (compound && !resolveMultiCore) + return columnSources; + + SelectResolver internalResolver(db, query); + columnSources += internalResolver.resolve(select->coreSelects[0]); + + if (compound) + { + QMutableListIterator<Column> it(columnSources); + while (it.hasNext()) + it.next().flags |= FROM_COMPOUND_SELECT; + } + + return columnSources; +} + +QList<SelectResolver::Column> SelectResolver::resolveView(const QString& database, const QString& name, const QString& alias) +{ + QList<Column> results; + SqliteQueryPtr query = schemaResolver->getParsedObject(database, name, SchemaResolver::VIEW); + if (!query) + { + qDebug() << "Could not get parsed CREATE VIEW in SelectResolver::resolveView()."; + return results; + } + + SqliteCreateViewPtr createView = query.dynamicCast<SqliteCreateView>(); + if (!createView) + { + qDebug() << "Parsed object not a CREATE VIEW as expected, but instead it's:" << sqliteQueryTypeToString(query->queryType); + return results; + } + + results = resolveSubSelect(createView->select); + applySubSelectAlias(results, (!alias.isNull() ? alias : name)); + + return results; +} + +bool SelectResolver::isView(const QString& database, const QString& name) +{ + return schemaResolver->getViews(database).contains(name, Qt::CaseInsensitive); +} + +QStringList SelectResolver::getTableColumns(const QString &database, const QString &table, const QString& alias) +{ + Table dbTable; + dbTable.database = database; + dbTable.table = table; + dbTable.alias = alias; + + if (tableColumnsCache.contains(dbTable)) + return tableColumnsCache.value(dbTable); + else + { + QStringList columns = schemaResolver->getTableColumns(database, table); + tableColumnsCache[dbTable] = columns; + return columns; + } +} + +void SelectResolver::applySubSelectAlias(QList<SelectResolver::Column>& columns, const QString& alias) +{ + // If this subselect is aliased, then all source columns should be considered as from aliased table + QMutableListIterator<Column> it(columns); + if (!alias.isNull()) + { + while (it.hasNext()) + { + it.next().tableAlias = alias; + it.value().flags &= ~FROM_ANONYMOUS_SELECT; // remove anonymous flag + } + } + else + { + // Otherwise, mark column as being from anonymous subselect. + // This is used by QueryExecutorColumns step to avoid prefixing result column with table + // when it comes from anonymous subselect (which SQLite needs this to be not prefixed column). + while (it.hasNext()) + it.next().flags |= FROM_ANONYMOUS_SELECT; + } +} + +QString SelectResolver::resolveDatabase(const QString& database) +{ + if (dbNameToAttach.containsRight(database, Qt::CaseInsensitive)) + return dbNameToAttach.valueByRight(database, Qt::CaseInsensitive); + + return database; +} + +int SelectResolver::Table::operator ==(const SelectResolver::Table &other) +{ + return table == other.table && database == other.database && alias == other.alias; +} + +int operator==(const SelectResolver::Table& t1, const SelectResolver::Table& t2) +{ + return t1.table == t2.table && t1.database == t2.database && t1.alias == t2.alias; +} + +uint qHash(const SelectResolver::Table& table) +{ + return qHash(table.database + "." + table.table + "." + table.alias); +} + +int SelectResolver::Column::operator ==(const SelectResolver::Column &other) +{ + return table == other.table && database == other.database && column == other.column && tableAlias == other.tableAlias; +} + +SelectResolver::Table SelectResolver::Column::getTable() +{ + Table resTable; + resTable.table = table; + resTable.database = database; + resTable.originalDatabase = originalDatabase; + resTable.alias = tableAlias; + resTable.flags = flags; + return resTable; +} + +int operator ==(const SelectResolver::Column &c1, const SelectResolver::Column &c2) +{ + return c1.table == c2.table && c1.database == c2.database && c1.column == c2.column && c1.tableAlias == c2.tableAlias; +} + + +uint qHash(const SelectResolver::Column &column) +{ + return qHash(column.database + "." + column.table + "." + column.column + "/" + column.tableAlias); +} 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 diff --git a/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.cpp b/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.cpp new file mode 100644 index 0000000..54b0905 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.cpp @@ -0,0 +1,202 @@ +#include "bugreporter.h" +#include "services/config.h" +#include "services/notifymanager.h" +#include <QNetworkAccessManager> +#include <QNetworkReply> +#include <QNetworkRequest> +#include <QUrlQuery> + +BugReporter::BugReporter(QObject *parent) : + QObject(parent) +{ + networkManager = new QNetworkAccessManager(this); + connect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(finished(QNetworkReply*))); +} + +QUrl BugReporter::getReporterEmailHelpUrl() const +{ + return QUrl(QString::fromLatin1(reporterEmailHelpUrl)); +} + +QUrl BugReporter::getReporterUserAndPasswordHelpUrl() const +{ + return QUrl(QString::fromLatin1(reporterUserPassHelpUrl)); +} + +void BugReporter::validateBugReportCredentials(const QString& login, const QString& password) +{ + if (credentialsValidationInProgress) + { + credentialsValidationInProgress->abort(); + credentialsValidationInProgress->deleteLater(); + } + + QUrlQuery query; + query.addQueryItem("validateUser", login); + query.addQueryItem("password", password); + + QUrl url = QUrl(QString::fromLatin1(bugReportServiceUrl) + "?" + query.query(QUrl::FullyEncoded)); + QNetworkRequest request(url); + credentialsValidationInProgress = networkManager->get(request); + replyToHandler[credentialsValidationInProgress] = [this](bool success, const QString& data) + { + if (success && data.trimmed() != "OK") + { + success = false; + emit credentialsValidationResult(success, tr("Invalid login or password")); + } + else + { + emit credentialsValidationResult(success, success ? QString() : data); + } + }; +} + +void BugReporter::abortCredentialsValidation() +{ + if (credentialsValidationInProgress) + { + credentialsValidationInProgress->abort(); + credentialsValidationInProgress->deleteLater(); + credentialsValidationInProgress = nullptr; + } +} + +void BugReporter::useBugReportCredentials(const QString& login, const QString& password) +{ + CFG_CORE.Internal.BugReportUser.set(login); + CFG_CORE.Internal.BugReportPassword.set(password); +} + +void BugReporter::clearBugReportCredentials() +{ + CFG_CORE.Internal.BugReportUser.set(QString()); + CFG_CORE.Internal.BugReportPassword.set(QString()); +} + +void BugReporter::reportBug(const QString& title, const QString& details, const QString& version, const QString& os, const QString& plugins, BugReporter::ResponseHandler responseHandler, const QString& urlSuffix) +{ + static_qstring(contentsTpl, "%1\n\n<b>Plugins loaded:</b>\n%2\n\n<b>Version:</b>\n%3\n\n<b>Operating System:</b>\n%4"); + QString contents = contentsTpl.arg(escapeParam(details), plugins, version, os); + + QUrlQuery query; + query.addQueryItem("brief", escapeParam(title)); + query.addQueryItem("contents", contents); + query.addQueryItem("os", os); + query.addQueryItem("version", version); + query.addQueryItem("featureRequest", "0"); + + QUrl url = QUrl(QString::fromLatin1(bugReportServiceUrl) + "?" + escapeUrl(query.query(QUrl::FullyEncoded) + urlSuffix)); + QNetworkRequest request(url); + QNetworkReply* reply = networkManager->get(request); + if (responseHandler) + replyToHandler[reply] = responseHandler; + + replyToTypeAndTitle[reply] = QPair<bool,QString>(false, title); +} + +void BugReporter::requestFeature(const QString& title, const QString& details, BugReporter::ResponseHandler responseHandler, const QString& urlSuffix) +{ + QUrlQuery query; + query.addQueryItem("brief", escapeParam(title)); + query.addQueryItem("contents", escapeParam(details)); + query.addQueryItem("featureRequest", "1"); + + QUrl url = QUrl(QString::fromLatin1(bugReportServiceUrl) + "?" + escapeUrl(query.query(QUrl::FullyEncoded) + urlSuffix)); + QNetworkRequest request(url); + QNetworkReply* reply = networkManager->get(request); + if (responseHandler) + replyToHandler[reply] = responseHandler; + + replyToTypeAndTitle[reply] = QPair<bool,QString>(true, title); +} + +QString BugReporter::escapeParam(const QString &input) +{ + return input.toHtmlEscaped(); +} + +QString BugReporter::escapeUrl(const QString &input) +{ + // For some reason the ";" character is not encodedy by QUrlQuery when using FullEncoded. Pity. We have to do it manually. + QString copy = input; + return copy.replace(";", "%3B"); +} + +void BugReporter::finished(QNetworkReply* reply) +{ + if (reply == credentialsValidationInProgress) + credentialsValidationInProgress = nullptr; + + if (!replyToHandler.contains(reply)) + { + reply->deleteLater(); + return; + } + + bool success = (reply->error() == QNetworkReply::NoError); + QString data; + if (success) + data = QString::fromLatin1(reply->readAll()); + else + data = reply->errorString(); + + replyToHandler[reply](success, data); + replyToHandler.remove(reply); + + if (replyToTypeAndTitle.contains(reply)) + { + if (success) + CFG->addReportHistory(replyToTypeAndTitle[reply].first, replyToTypeAndTitle[reply].second, data); + + replyToTypeAndTitle.remove(reply); + } + + reply->deleteLater(); +} + +void BugReporter::reportBug(const QString& email, const QString& title, const QString& details, const QString& version, const QString& os, const QString& plugins, + ResponseHandler responseHandler) +{ + QUrlQuery query; + query.addQueryItem("byEmail", email); + QString urlSuffix = "&" + query.query(QUrl::FullyEncoded); + + reportBug(title, details, version, os, plugins, responseHandler, urlSuffix); +} + +void BugReporter::reportBug(const QString& title, const QString& details, const QString& version, const QString& os, + const QString& plugins, ResponseHandler responseHandler) +{ + QString user = CFG_CORE.Internal.BugReportUser.get(); + QString pass = CFG_CORE.Internal.BugReportPassword.get(); + + QUrlQuery query; + query.addQueryItem("byUser", user); + query.addQueryItem("password", pass); + QString urlSuffix = "&" + query.query(QUrl::FullyEncoded); + + reportBug(title, details, version, os, plugins, responseHandler, urlSuffix); +} + +void BugReporter::requestFeature(const QString& email, const QString& title, const QString& details, ResponseHandler responseHandler) +{ + QUrlQuery query; + query.addQueryItem("byEmail", email); + QString urlSuffix = "&" + query.query(QUrl::FullyEncoded); + + requestFeature(title, details, responseHandler, urlSuffix); +} + +void BugReporter::requestFeature(const QString& title, const QString& details, ResponseHandler responseHandler) +{ + QString user = CFG_CORE.Internal.BugReportUser.get(); + QString pass = CFG_CORE.Internal.BugReportPassword.get(); + + QUrlQuery query; + query.addQueryItem("byUser", user); + query.addQueryItem("password", pass); + QString urlSuffix = "&" + query.query(QUrl::FullyEncoded); + + requestFeature(title, details, responseHandler, urlSuffix); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.h b/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.h new file mode 100644 index 0000000..3e8eb8d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.h @@ -0,0 +1,62 @@ +#ifndef BUGREPORTER_H +#define BUGREPORTER_H + +#include "common/global.h" +#include "sqlitestudio.h" +#include <QObject> +#include <QHash> + +class QNetworkAccessManager; +class QNetworkReply; + +class API_EXPORT BugReporter : public QObject +{ + Q_OBJECT + + public: + typedef std::function<void(bool success, const QString& data)> ResponseHandler; + + explicit BugReporter(QObject *parent = 0); + + QUrl getReporterEmailHelpUrl() const; + QUrl getReporterUserAndPasswordHelpUrl() const; + void validateBugReportCredentials(const QString& login, const QString& password); + void abortCredentialsValidation(); + void useBugReportCredentials(const QString& login, const QString& password); + void clearBugReportCredentials(); + + private: + void reportBug(const QString& title, const QString& details, const QString& version, const QString& os, const QString& plugins, + ResponseHandler responseHandler, const QString& urlSuffix); + void requestFeature(const QString& title, const QString& details, ResponseHandler responseHandler, const QString& urlSuffix); + + static QString escapeParam(const QString& input); + static QString escapeUrl(const QString& input); + + QNetworkAccessManager* networkManager = nullptr; + QHash<QNetworkReply*,ResponseHandler> replyToHandler; + QHash<QNetworkReply*,QPair<bool,QString>> replyToTypeAndTitle; + QNetworkReply* credentialsValidationInProgress = nullptr; + + static_char* bugReportServiceUrl = "http://sqlitestudio.pl/report_bug3.rvt"; + static_char* reporterEmailHelpUrl = "http://wiki.sqlitestudio.pl/index.php/User_Manual#Reporter_email_address"; + static_char* reporterUserPassHelpUrl = "http://wiki.sqlitestudio.pl/index.php/User_Manual#Reporter_user_and_password"; + + signals: + void credentialsValidationResult(bool success, const QString& errorMessage); + + private slots: + void finished(QNetworkReply* reply); + + public slots: + void reportBug(const QString& email, const QString& title, const QString& details, const QString& version, const QString& os, const QString& plugins, + ResponseHandler responseHandler = nullptr); + void reportBug(const QString& title, const QString& details, const QString& version, const QString& os, const QString& plugins, + ResponseHandler responseHandler = nullptr); + void requestFeature(const QString& email, const QString& title, const QString& details, ResponseHandler responseHandler = nullptr); + void requestFeature(const QString& title, const QString& details, ResponseHandler responseHandler = nullptr); +}; + +#define BUGS SQLITESTUDIO->getBugReporter() + +#endif // BUGREPORTER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/codeformatter.cpp b/SQLiteStudio3/coreSQLiteStudio/services/codeformatter.cpp new file mode 100644 index 0000000..e02508f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/codeformatter.cpp @@ -0,0 +1,91 @@ +#include "codeformatter.h"
+#include "parser/parser.h"
+#include "plugins/codeformatterplugin.h"
+#include "services/pluginmanager.h"
+#include <QDebug>
+
+void CodeFormatter::setFormatter(const QString& lang, CodeFormatterPlugin *formatterPlugin)
+{
+ currentFormatter[lang] = formatterPlugin;
+}
+
+CodeFormatterPlugin* CodeFormatter::getFormatter(const QString& lang)
+{
+ if (hasFormatter(lang))
+ return currentFormatter[lang];
+
+ return nullptr;
+}
+
+bool CodeFormatter::hasFormatter(const QString& lang)
+{
+ return currentFormatter.contains(lang);
+}
+
+void CodeFormatter::fullUpdate()
+{
+ availableFormatters.clear();
+ QList<CodeFormatterPlugin*> formatterPlugins = PLUGINS->getLoadedPlugins<CodeFormatterPlugin>();
+ for (CodeFormatterPlugin* plugin : formatterPlugins)
+ availableFormatters[plugin->getLanguage()][plugin->getName()] = plugin;
+
+ updateCurrent();
+}
+
+void CodeFormatter::updateCurrent()
+{
+ if (modifyingConfig)
+ return;
+
+ modifyingConfig = true;
+
+ bool modified = false;
+ currentFormatter.clear();
+ QHash<QString,QVariant> config = CFG_CORE.General.ActiveCodeFormatter.get();
+ QString name;
+ QStringList names = availableFormatters.keys();
+ qSort(names);
+ for (const QString& lang : names)
+ {
+ name = config[lang].toString();
+ if (config.contains(lang) && availableFormatters[lang].contains(name))
+ {
+ currentFormatter[lang] = availableFormatters[lang][name];
+ }
+ else
+ {
+ currentFormatter[lang] = availableFormatters[lang].begin().value();
+ config[lang] = currentFormatter[lang]->getName();
+ modified = true;
+ }
+ }
+
+ if (modified)
+ CFG_CORE.General.ActiveCodeFormatter.set(config);
+
+ modifyingConfig = false;
+}
+
+void CodeFormatter::storeCurrentSettings()
+{
+ QHash<QString,QVariant> config = CFG_CORE.General.ActiveCodeFormatter.get();
+ QHashIterator<QString,CodeFormatterPlugin*> it(currentFormatter);
+ while (it.hasNext())
+ {
+ it.next();
+ config[it.key()] = it.value()->getName();
+ }
+
+ CFG_CORE.General.ActiveCodeFormatter.set(config);
+}
+
+QString CodeFormatter::format(const QString& lang, const QString& code, Db* contextDb)
+{
+ if (!hasFormatter(lang))
+ {
+ qWarning() << "No formatter plugin defined for CodeFormatter for language:" << lang;
+ return code;
+ }
+
+ return currentFormatter[lang]->format(code, contextDb);
+}
diff --git a/SQLiteStudio3/coreSQLiteStudio/services/codeformatter.h b/SQLiteStudio3/coreSQLiteStudio/services/codeformatter.h new file mode 100644 index 0000000..015bbfe --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/codeformatter.h @@ -0,0 +1,30 @@ +#ifndef CODEFORMATTER_H
+#define CODEFORMATTER_H
+
+#include "coreSQLiteStudio_global.h"
+#include "sqlitestudio.h"
+
+class CodeFormatterPlugin;
+class Db;
+
+class API_EXPORT CodeFormatter
+{
+ public:
+ QString format(const QString& lang, const QString& code, Db* contextDb);
+
+ void setFormatter(const QString& lang, CodeFormatterPlugin* formatterPlugin);
+ CodeFormatterPlugin* getFormatter(const QString& lang);
+ bool hasFormatter(const QString& lang);
+ void fullUpdate();
+ void updateCurrent();
+ void storeCurrentSettings();
+
+ private:
+ QHash<QString,QHash<QString,CodeFormatterPlugin*>> availableFormatters;
+ QHash<QString,CodeFormatterPlugin*> currentFormatter;
+ bool modifyingConfig = false;
+};
+
+#define FORMATTER SQLITESTUDIO->getCodeFormatter()
+
+#endif // CODEFORMATTER_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/services/collationmanager.h b/SQLiteStudio3/coreSQLiteStudio/services/collationmanager.h new file mode 100644 index 0000000..5a05b0f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/collationmanager.h @@ -0,0 +1,41 @@ +#ifndef COLLATIONMANAGER_H +#define COLLATIONMANAGER_H + +#include "coreSQLiteStudio_global.h" +#include "common/global.h" +#include <QList> +#include <QSharedPointer> +#include <QObject> +#include <QStringList> + +class Db; + +class API_EXPORT CollationManager : public QObject +{ + Q_OBJECT + + public: + struct API_EXPORT Collation + { + QString name; + QString lang; + QString code; + QStringList databases; + bool allDatabases = true; + }; + + typedef QSharedPointer<Collation> CollationPtr; + + virtual void setCollations(const QList<CollationPtr>& newCollations) = 0; + virtual QList<CollationPtr> getAllCollations() const = 0; + virtual QList<CollationPtr> getCollationsForDatabase(const QString& dbName) const = 0; + virtual int evaluate(const QString& name, const QString& value1, const QString& value2) = 0; + virtual int evaluateDefault(const QString& value1, const QString& value2) = 0; + + signals: + void collationListChanged(); +}; + +#define COLLATIONS SQLITESTUDIO->getCollationManager() + +#endif // COLLATIONMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/config.cpp b/SQLiteStudio3/coreSQLiteStudio/services/config.cpp new file mode 100644 index 0000000..1fef317 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/config.cpp @@ -0,0 +1,9 @@ +#include "services/config.h" + +CFG_DEFINE(Core) + +static const QString DB_FILE_NAME = QStringLiteral("settings3"); + +Config::~Config() +{ +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/config.h b/SQLiteStudio3/coreSQLiteStudio/services/config.h new file mode 100644 index 0000000..5a3f594 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/config.h @@ -0,0 +1,178 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include "coreSQLiteStudio_global.h" +#include "config_builder.h" +#include "services/functionmanager.h" +#include "collationmanager.h" +#include "sqlitestudio.h" +#include "common/utils.h" +#include <QObject> +#include <QVariant> +#include <QHash> +#include <QStringList> +#include <QSharedPointer> +#include <QDateTime> + +const int SQLITESTUDIO_CONFIG_VERSION = 1; + +CFG_CATEGORIES(Core, + CFG_CATEGORY(General, + CFG_ENTRY(int, SqlHistorySize, 10000) + CFG_ENTRY(int, DdlHistorySize, 1000) + CFG_ENTRY(QString, LoadedPlugins, "") + CFG_ENTRY(QVariantHash, ActiveCodeFormatter, QVariantHash()) + CFG_ENTRY(bool, CheckUpdatesOnStartup, true) + ) + CFG_CATEGORY(Console, + CFG_ENTRY(int, HistorySize, 100) + ) + CFG_CATEGORY(Internal, + CFG_ENTRY(QVariantList, Functions, QVariantList()) + CFG_ENTRY(QVariantList, Collations, QVariantList()) + CFG_ENTRY(QString, BugReportUser, QString()) + CFG_ENTRY(QString, BugReportPassword, QString()) + CFG_ENTRY(QString, BugReportRecentTitle, QString()) + CFG_ENTRY(QString, BugReportRecentContents, QString()) + CFG_ENTRY(bool, BugReportRecentError, false) + ) +) + +#define CFG_CORE CFG_INSTANCE(Core) + +class QAbstractItemModel; +class DdlHistoryModel; + +class API_EXPORT Config : public QObject +{ + Q_OBJECT + + public: + virtual ~Config(); + + struct CfgDb + { + QString name; + QString path; + QHash<QString,QVariant> options; + }; + + typedef QSharedPointer<CfgDb> CfgDbPtr; + + struct DbGroup; + typedef QSharedPointer<DbGroup> DbGroupPtr; + + struct DbGroup + { + qint64 id; + QString referencedDbName; + QString name; + QList<DbGroupPtr> childs; + int order; + bool open = false; + }; + + struct SqlHistoryEntry + { + QString query; + QString dbName; + int rowsAffected; + int unixtime; + }; + + typedef QSharedPointer<SqlHistoryEntry> SqlHistoryEntryPtr; + + struct DdlHistoryEntry + { + QString dbName; + QString dbFile; + QDateTime timestamp; + QString queries; + }; + + typedef QSharedPointer<DdlHistoryEntry> DdlHistoryEntryPtr; + + struct ReportHistoryEntry + { + int id = 0; + bool isFeatureRequest = false; + int timestamp = 0; + QString title; + QString url; + }; + + typedef QSharedPointer<ReportHistoryEntry> ReportHistoryEntryPtr; + + virtual void init() = 0; + virtual void cleanUp() = 0; + virtual const QString& getConfigDir() const = 0; + virtual QString getConfigFilePath() const = 0; + + virtual void beginMassSave() = 0; + virtual void commitMassSave() = 0; + virtual void rollbackMassSave() = 0; + virtual bool isMassSaving() const = 0; + virtual void set(const QString& group, const QString& key, const QVariant& value) = 0; + virtual QVariant get(const QString& group, const QString& key) = 0; + virtual QHash<QString,QVariant> getAll() = 0; + + virtual bool addDb(const QString& name, const QString& path, const QHash<QString, QVariant> &options) = 0; + virtual bool updateDb(const QString& name, const QString &newName, const QString& path, const QHash<QString, QVariant> &options) = 0; + virtual bool removeDb(const QString& name) = 0; + virtual bool isDbInConfig(const QString& name) = 0; + virtual QString getLastErrorString() const = 0; + + /** + * @brief Provides list of all registered databases. + * @return List of database entries. + * + * Registered databases are those that user added to the application. They are not necessary valid or supported. + * They can be inexisting or unsupported, but they are kept in registry in case user fixes file path, + * or loads plugin to support it. + */ + virtual QList<CfgDbPtr> dbList() = 0; + virtual CfgDbPtr getDb(const QString& dbName) = 0; + + virtual void storeGroups(const QList<DbGroupPtr>& groups) = 0; + virtual QList<DbGroupPtr> getGroups() = 0; + virtual DbGroupPtr getDbGroup(const QString& dbName) = 0; + + virtual qint64 addSqlHistory(const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected) = 0; + virtual void updateSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected) = 0; + virtual void clearSqlHistory() = 0; + virtual QAbstractItemModel* getSqlHistoryModel() = 0; + + virtual void addCliHistory(const QString& text) = 0; + virtual void applyCliHistoryLimit() = 0; + virtual void clearCliHistory() = 0; + virtual QStringList getCliHistory() const = 0; + + virtual void addDdlHistory(const QString& queries, const QString& dbName, const QString& dbFile) = 0; + virtual QList<DdlHistoryEntryPtr> getDdlHistoryFor(const QString& dbName, const QString& dbFile, const QDate& date) = 0; + virtual DdlHistoryModel* getDdlHistoryModel() = 0; + virtual void clearDdlHistory() = 0; + + virtual void addReportHistory(bool isFeatureRequest, const QString& title, const QString& url) = 0; + virtual QList<ReportHistoryEntryPtr> getReportHistory() = 0; + virtual void deleteReport(int id) = 0; + virtual void clearReportHistory() = 0; + + virtual void begin() = 0; + virtual void commit() = 0; + virtual void rollback() = 0; + + signals: + void massSaveBegins(); + void massSaveCommited(); + void sqlHistoryRefreshNeeded(); + void ddlHistoryRefreshNeeded(); + void reportsHistoryRefreshNeeded(); + + public slots: + virtual void refreshSqlHistory() = 0; + virtual void refreshDdlHistory() = 0; +}; + +#define CFG SQLITESTUDIO->getConfig() + +#endif // CONFIG_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.cpp new file mode 100644 index 0000000..91aff79 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.cpp @@ -0,0 +1,17 @@ +#include "dbmanager.h" +#include <QFileInfo> + +DbManager::DbManager(QObject *parent) : + QObject(parent) +{ +} + +DbManager::~DbManager() +{ +} + +QString DbManager::generateDbName(const QString &filePath) +{ + QFileInfo fi(filePath); + return fi.completeBaseName(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.h b/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.h new file mode 100644 index 0000000..5a56151 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.h @@ -0,0 +1,288 @@ +#ifndef DBMANAGER_H +#define DBMANAGER_H + +#include "db/db.h" +#include "coreSQLiteStudio_global.h" +#include "common/global.h" +#include "sqlitestudio.h" +#include <QObject> +#include <QList> +#include <QHash> + +/** @file */ + +class DbPlugin; +class Config; +class Plugin; +class PluginType; + +/** + * @brief Database registry manager. + * + * Manages list of databases in SQLiteStudio core. + * + * It's a singleton asseccible with DBLIST macro. + */ +class API_EXPORT DbManager : public QObject +{ + Q_OBJECT + + public: + /** + * @brief Creates database manager. + * @param parent Parent object passed to QObject constructor. + */ + explicit DbManager(QObject *parent = 0); + + /** + * @brief Default destructor. + */ + ~DbManager(); + + /** + * @brief Adds database to the manager. + * @param name Symbolic name of the database, as it will be presented in the application. + * @param path Path to the database file. + * @param options Key-value custom options for database, that can be used in the DbPlugin implementation, like connection password, etc. + * @param permanent If true, then the database will be remembered in configuration, otherwise it will be disappear after application restart. + * @return true if the database has been successfully added, or false otherwise. + * + * The method can return false if given database file exists, but is not supported SQLite version (including invalid files, + * that are not SQLite database). It basicly returns false if DbPlugin#getInstance() returned null for given database parameters. + */ + virtual bool addDb(const QString &name, const QString &path, const QHash<QString, QVariant> &options, bool permanent = true) = 0; + + /** + * @overload + */ + virtual bool addDb(const QString &name, const QString &path, bool permanent = true) = 0; + + /** + * @brief Adds database as temporary, with generated name. + * @param path Path to database. + * @param options Key-value custom options for database. + * @return Added database name, if the database has been successfully added, or null string otherwise. + * + * This method is used for example when database was passed as argument to application command line arguments. + */ + virtual QString quickAddDb(const QString &path, const QHash<QString, QVariant> &options) = 0; + + /** + * @brief Updates registered database with new data. + * @param db Registered database. + * @param name New symbolic name for the database. + * @param path New database file path. + * @param options New database options. See addDb() for details. + * @param permanent True to make the database stored in configuration, false to make it disappear after application restart. + * @return true if the database was successfully updated, or false otherwise. + */ + virtual bool updateDb(Db* db, const QString &name, const QString &path, const QHash<QString, QVariant> &options, bool permanent) = 0; + + /** + * @brief Removes database from application. + * @param name Symbolic name of the database. + * @param cs Should the name be compare with case sensitivity? + */ + virtual void removeDbByName(const QString& name, Qt::CaseSensitivity cs = Qt::CaseSensitive) = 0; + + /** + * @brief Removes database from application. + * @param path Database file path as it was passed to addDb() or updateDb(). + */ + virtual void removeDbByPath(const QString& path) = 0; + + /** + * @brief Removes database from application. + * @param db Database to be removed. + */ + virtual void removeDb(Db* db) = 0; + + /** + * @brief Gives list of databases registered in the application. + * @return List of databases, no matter if database is open or not. + * + * The results list includes invalid databases (not supported by driver plugin, or with no read access, etc). + */ + virtual QList<Db*> getDbList() = 0; + + /** + * @brief Gives list of valid databases. + * @return List of open databases. + */ + virtual QList<Db*> getValidDbList() = 0; + + /** + * @brief Gives list of currently open databases. + * @return List of open databases. + */ + virtual QList<Db*> getConnectedDbList() = 0; + + /** + * @brief Gives list of database names. + * @return List of database names that are registered in the application. + */ + virtual QStringList getDbNames() = 0; + + /** + * @brief Gives database object by its name. + * @param name Symbolic name of the database. + * @param cs Should the \p name be compared with case sensitivity? + * @return Database object, or null pointer if the database could not be found. + * + * This method is fast, as it uses hash table lookup. + */ + virtual Db* getByName(const QString& name, Qt::CaseSensitivity cs = Qt::CaseSensitive) = 0; + + /** + * @brief Gives database object by its file path. + * @param path Database file path as it was passed to addDb() or updateDb(). + * @return Database matched by file path, or null if no database was found. + * + * This method is fast, as it uses hash table lookup. + */ + virtual Db* getByPath(const QString& path) = 0; + + /** + * @brief Creates in-memory SQLite3 database. + * @return Created database. + * + * Created database can be used for any purpose. Note that DbManager doesn't own created + * database and it's up to the caller to delete the database when it's no longer needed. + */ + virtual Db* createInMemDb() = 0; + + /** + * @brief Tells if given database is temporary. + * @param db Database to check. + * @return true if database is temporary, or false if it's stored in the configuration. + * + * Temporary databases are databases that are not stored in configuration and will not be restored + * upon next SQLiteStudio start. This can be decided by user on UI when he edits database registration info + * (there is a checkbox for that). + */ + virtual bool isTemporary(Db* db) = 0; + + /** + * @brief Generates database name. + * @param filePath Database file path. + * @return A name, using database file name as a hint for a name. + * + * This method doesn't care about uniqueness of the name. It just gets the file name from provided path + * and uses it as a name. + */ + static QString generateDbName(const QString& filePath); + + public slots: + /** + * @brief Rescans configuration for new database entries. + * + * Looks into the configuration for new databases. If there are any, adds them to list of managed databases. + */ + virtual void scanForNewDatabasesInConfig() = 0; + + /** + * @brief Sends signal to all interested entities, that databases are loaded. + * + * This is called by the managing entity (the SQLiteStudio instance) to let all know, + * that all db-related plugins and configuration related to databases are now loaded + * and list of databases in the manager is complete. + */ + virtual void notifyDatabasesAreLoaded() = 0; + + signals: + /** + * @brief Application just connected to the database. + * @param db Database object that the connection was made to. + * + * Emitted just after application has connected to the database. + */ + void dbConnected(Db* db); + + /** + * @brief Application just disconnected from the database. + * @param db Database object that the connection was closed with. + */ + void dbDisconnected(Db* db); + + /** + * @brief The database is about to be disconnected and user can still deny it. + * @param db Database to be closed. + * @param deny If set to true, then disconnecting will be aborted. + */ + void dbAboutToBeDisconnected(Db* db, bool& deny); + + /** + * @brief A database has been added to the application. + * @param db Database added. + * Emitted from addDb() methods in case of success. + */ + void dbAdded(Db* db); + + /** + * @brief A database has been removed from the application. + * @param db Database object that was removed. The object still exists, but will be removed soon after this signal is handled. + * + * Emitted from removeDb(). As the argument is a smart pointer, the object will be deleted after last reference to the pointer + * is deleted, which is very likely that the pointer instance in this signal is the last one. + */ + void dbRemoved(Db* db); + + /** + * @brief A database registration data has been updated. + * @param oldName The name of the database before the update - in case the name was updated. + * @param db Database object that was updated. + * + * Emitted from updateDb() after successful update. + * + * The name of the database is a key for tables related to the databases, so if it changed, we dbUpdated() provides + * the original name before update, so any tables can be updated basing on the old name. + */ + void dbUpdated(const QString& oldName, Db* db); + + /** + * @brief Loaded plugin to support the database. + * @param db Database object handled by the plugin. + * + * Emitted after a plugin was loaded and it turned out to handle the database that was already registered in the application, + * but wasn't managed by database manager, because no handler plugin was loaded earlier. + * + * Also emitted when database details were edited and saved, which fixes database configuration (for example path). + */ + void dbLoaded(Db* db); + + /** + * @brief Plugin supporting the database is about to be unloaded. + * @param db Database object to be removed from the manager. + * @param plugin Plugin that handles the database. + * + * Emitted when PluginManager is about to unload the plugin which is handling the database. + * All classes using this database object should stop using it immediately, or the application may crash. + * + * The plugin itself should not use this signal. Instead it should implement Plugin::deinit() method + * to perform deinitialization before unloading. The Plugin::deinit() method is called before this signal is emitted. + */ + void dbAboutToBeUnloaded(Db* db, DbPlugin* plugin); + + /** + * @brief Plugins supporting the database was just unloaded. + * @param db The new database object (InvalidDb) that replaced the previous one. + * + * This is emitted after the plugin for the database was unloaded. The \p db object is now a different object. + * It is of InvalidDb class and it represents a database in an invalid state. It still has name, path and connection options, + * but no operation can be performed on the database. + */ + void dbUnloaded(Db* db); + + /** + * @brief Emited when the initial database list has been loaded. + */ + void dbListLoaded(); +}; + +/** + * @brief Database manager. + * Provides direct access to the database manager. + */ +#define DBLIST SQLITESTUDIO->getDbManager() + +#endif // DBMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/exportmanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/exportmanager.cpp new file mode 100644 index 0000000..6f916bc --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/exportmanager.cpp @@ -0,0 +1,283 @@ +#include "exportmanager.h" +#include "services/pluginmanager.h" +#include "plugins/exportplugin.h" +#include "services/notifymanager.h" +#include "db/queryexecutor.h" +#include "exportworker.h" +#include <QThreadPool> +#include <QTextCodec> +#include <QBuffer> +#include <QDebug> +#include <QDir> +#include <QFile> + +ExportManager::ExportManager(QObject *parent) : + PluginServiceBase(parent) +{ +} + +ExportManager::~ExportManager() +{ + safe_delete(config); +} + +QStringList ExportManager::getAvailableFormats(ExportMode exportMode) const +{ + QStringList formats; + for (ExportPlugin* plugin : PLUGINS->getLoadedPlugins<ExportPlugin>()) + { + if (exportMode == UNDEFINED || plugin->getSupportedModes().testFlag(exportMode)) + formats << plugin->getFormatName(); + } + + return formats; +} + +void ExportManager::configure(const QString& format, const StandardExportConfig& config) +{ + configure(format, new StandardExportConfig(config)); +} + +void ExportManager::configure(const QString& format, StandardExportConfig* config) +{ + if (exportInProgress) + { + qWarning() << "Tried to configure export while another export is in progress."; + return; + } + + plugin = getPluginForFormat(format); + if (!plugin) + { + invalidFormat(format); + return; + } + + safe_delete(this->config); + this->config = config; +} + +bool ExportManager::isExportInProgress() const +{ + return exportInProgress; +} + +void ExportManager::exportQueryResults(Db* db, const QString& query) +{ + if (!checkInitialConditions()) + return; + + if (!plugin->getSupportedModes().testFlag(QUERY_RESULTS)) + { + notifyError(tr("Export plugin %1 doesn't support exporing query results.").arg(plugin->getFormatName())); + emit exportFailed(); + emit exportFinished(); + return; + } + + exportInProgress = true; + mode = QUERY_RESULTS; + + ExportWorker* worker = prepareExport(); + if (!worker) + return; + + worker->prepareExportQueryResults(db, query); + QThreadPool::globalInstance()->start(worker); +} + +void ExportManager::exportTable(Db* db, const QString& database, const QString& table) +{ + static const QString sql = QStringLiteral("SELECT * FROM %1"); + + if (!checkInitialConditions()) + return; + + if (!plugin->getSupportedModes().testFlag(TABLE)) + { + notifyError(tr("Export plugin %1 doesn't support exporing tables.").arg(plugin->getFormatName())); + emit exportFailed(); + emit exportFinished(); + return; + } + + exportInProgress = true; + mode = TABLE; + + ExportWorker* worker = prepareExport(); + if (!worker) + return; + + worker->prepareExportTable(db, database, table); + QThreadPool::globalInstance()->start(worker); +} + +void ExportManager::exportDatabase(Db* db, const QStringList& objectListToExport) +{ + if (!checkInitialConditions()) + return; + + if (!plugin->getSupportedModes().testFlag(DATABASE)) + { + notifyError(tr("Export plugin %1 doesn't support exporing databases.").arg(plugin->getFormatName())); + emit exportFailed(); + emit exportFinished(); + return; + } + + exportInProgress = true; + mode = DATABASE; + + ExportWorker* worker = prepareExport(); + if (!worker) + return; + + worker->prepareExportDatabase(db, objectListToExport); + QThreadPool::globalInstance()->start(worker); +} + +void ExportManager::interrupt() +{ + emit orderWorkerToInterrupt(); +} + +ExportPlugin* ExportManager::getPluginForFormat(const QString& formatName) const +{ + for (ExportPlugin* plugin : PLUGINS->getLoadedPlugins<ExportPlugin>()) + if (plugin->getFormatName() == formatName) + return plugin; + + return nullptr; +} + +void ExportManager::invalidFormat(const QString& format) +{ + notifyError(tr("Export format '%1' is not supported. Supported formats are: %2.").arg(format).arg(getAvailableFormats().join(", "))); +} + +bool ExportManager::checkInitialConditions() +{ + if (exportInProgress) + { + qWarning() << "Tried to call export while another export is in progress."; + emit exportFailed(); + emit exportFinished(); + return false; + } + + if (!plugin) + { + qWarning() << "Tried to call export while no export plugin was configured."; + emit exportFailed(); + emit exportFinished(); + return false; + } + + return true; +} + +ExportWorker* ExportManager::prepareExport() +{ + bool usesOutput = plugin->getSupportedModes().testFlag(FILE) || plugin->getSupportedModes().testFlag(CLIPBOARD); + QIODevice* output = nullptr; + if (usesOutput) + { + output = getOutputStream(); + if (!output) + { + emit exportFailed(); + emit exportFinished(); + exportInProgress = false; + return nullptr; + } + } + + ExportWorker* worker = new ExportWorker(plugin, config, output); + connect(worker, SIGNAL(finished(bool,QIODevice*)), this, SLOT(finalizeExport(bool,QIODevice*))); + connect(this, SIGNAL(orderWorkerToInterrupt()), worker, SLOT(interrupt())); + return worker; +} + +void ExportManager::handleClipboardExport() +{ + if (plugin->getMimeType().isNull()) + { + QString str = codecForName(config->codec)->toUnicode(bufferForClipboard->buffer()); + emit storeInClipboard(str); + } + else + emit storeInClipboard(bufferForClipboard->buffer(), plugin->getMimeType()); +} + +void ExportManager::finalizeExport(bool result, QIODevice* output) +{ + if (result) + { + if (config->intoClipboard) + { + notifyInfo(tr("Export to the clipboard was successful.")); + handleClipboardExport(); + } + else if (!config->outputFileName.isEmpty()) + notifyInfo(tr("Export to the file '%1' was successful.").arg(config->outputFileName)); + else + notifyInfo(tr("Export to was successful.").arg(config->outputFileName)); + + emit exportSuccessful(); + } + else + { + emit exportFailed(); + } + emit exportFinished(); + + if (output) + { + output->close(); + delete output; + } + + bufferForClipboard = nullptr; + exportInProgress = false; +} + +QIODevice* ExportManager::getOutputStream() +{ + QFile::OpenMode openMode; + if (config->intoClipboard) + { + openMode = QIODevice::WriteOnly; + if (!plugin->isBinaryData()) + openMode |= QIODevice::Text; + + bufferForClipboard = new QBuffer(); + bufferForClipboard->open(openMode); + return bufferForClipboard; + } + else if (!config->outputFileName.trimmed().isEmpty()) + { + openMode = QIODevice::WriteOnly|QIODevice::Truncate; + if (!plugin->isBinaryData()) + openMode |= QIODevice::Text; + + QFile* file = new QFile(config->outputFileName); + if (!file->open(openMode)) + { + notifyError(tr("Could not export to file %1. File cannot be open for writting.").arg(config->outputFileName)); + delete file; + return nullptr; + } + return file; + } + else + { + qCritical() << "ExportManager::getOutputStream(): neither clipboard or output file was specified"; + } + + return nullptr; +} + +bool ExportManager::isAnyPluginAvailable() +{ + return !PLUGINS->getLoadedPlugins<ExportPlugin>().isEmpty(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/exportmanager.h b/SQLiteStudio3/coreSQLiteStudio/services/exportmanager.h new file mode 100644 index 0000000..64d1136 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/exportmanager.h @@ -0,0 +1,234 @@ +#ifndef EXPORTMANAGER_H +#define EXPORTMANAGER_H + +#include "coreSQLiteStudio_global.h" +#include "db/sqlquery.h" +#include "db/db.h" +#include "pluginservicebase.h" +#include "sqlitestudio.h" +#include <QObject> + +class ExportPlugin; +class QueryExecutor; +class ExportWorker; +class QBuffer; +class CfgEntry; + +/** + * @brief Provides database exporting capabilities. + * + * ExportManager is not thread-safe. Use it from single thread. + */ +class API_EXPORT ExportManager : public PluginServiceBase +{ + Q_OBJECT + public: + enum ExportMode + { + UNDEFINED = 0x00, + CLIPBOARD = 0x01, + DATABASE = 0x02, + TABLE = 0x04, + QUERY_RESULTS = 0x08, + FILE = 0x10 + }; + + Q_DECLARE_FLAGS(ExportModes, ExportMode) + + /** + * @brief Flags for requesting additional information for exporting by plugins. + * + * Each plugin implementation might ask ExportWorker to provide additional information for exporting. + * Such information is usually expensive operation (an additional database query to execute), therefore + * they are not enabled by default for all plugins. Each plugin has to ask for them individually + * by returning this enum values from ExportPlugin::getProviderFlags(). + * + * For each enum value returned from the ExportPlugin::getProviderFlags(), a single QHash entry will be prepared + * and that hash will be then passed to one of ExportPlugin::beforeExportQueryResults(), ExportPlugin::exportTable(), + * or ExportPlugin::exportVirtualTable(). If no flags were returned from ExportPlugin::getProviderFlags(), then + * empty hash will be passed to those methods. + * + * Each entry in the QHash has a key equal to one of values from this enum. Values from the hash are of QVariant type + * and therefore they need to be casted (by QVariant means) into desired type. For each enum value its description + * will tell you what actually is stored in the QVariant, so you can extract the information. + */ + enum ExportProviderFlag + { + NONE = 0x00, /**< This is a default. Nothing will be stored in the hash. */ + DATA_LENGTHS = 0x01, /**< + * Will provide maximum number of characters or bytes (depending on column type) + * for each exported table or qurey result column. It will be a <tt>QList<int></tt>. + */ + ROW_COUNT = 0x02 /**< + * Will provide total number of rows that will be exported for the table or query results. + * It will be an integer value. + */ + }; + + Q_DECLARE_FLAGS(ExportProviderFlags, ExportProviderFlag) + + struct ExportObject + { + enum Type + { + TABLE, + INDEX, + TRIGGER, + VIEW + }; + + Type type; + QString database; // TODO fill when dbnames are fully supported + QString name; + QString ddl; + SqlQueryPtr data; + QHash<ExportManager::ExportProviderFlag,QVariant> providerData; + }; + + typedef QSharedPointer<ExportObject> ExportObjectPtr; + + /** + * @brief Standard configuration for all exporting processes. + * + * Object of this type is passed to all exporting processes. + * It is configured with standard UI config for export. + */ + struct StandardExportConfig + { + /** + * @brief Text encoding. + * + * Always one of QTextCodec::availableCodecs(). + */ + QString codec; + + /** + * @brief Name of the file that the export being done to. + * + * This is provided just for information to the export process, + * but the plugin should use data stream provided to each called export method, + * instead of opening the file from this name. + * + * It will be null string if exporting is not performed into a file, but somewhere else + * (for example into a clipboard). + */ + QString outputFileName; + + /** + * @brief Indicates exporting to clipboard. + * + * This is just for an information, like outputFileName. Exporting methods will + * already have stream prepared for exporting to clipboard. + * + * Default is false. + */ + bool intoClipboard = false; + + /** + * @brief When exporting table or database, this indicates if table data should also be exported. + * + * Default is true. + */ + bool exportData = true; + + /** + * @brief When exporting only a table, this indicates if indexes related to that table should also be exported. + * + * Default is true. + */ + bool exportTableIndexes = true; + + /** + * @brief When exporting only a table, this indicates if triggers related to that table should also be exported. + * + * Default is true. + */ + bool exportTableTriggers = true; + }; + + /** + * @brief Standard export configuration options to be displayed on UI. + * + * Each of enum represents single property of StandardExportConfig which will be + * available on UI to configure. + */ + enum StandardConfigFlag + { + CODEC = 0x01, /**< Text encoding (see StandardExportConfig::codec). */ + }; + + Q_DECLARE_FLAGS(StandardConfigFlags, StandardConfigFlag) + + + explicit ExportManager(QObject *parent = 0); + ~ExportManager(); + + QStringList getAvailableFormats(ExportMode exportMode = UNDEFINED) const; + ExportPlugin* getPluginForFormat(const QString& formatName) const; + + /** + * @brief Configures export service for export. + * @param format Format to be used in upcoming export. + * @param config Standard configuration options to be used in upcoming export. + * + * ExportManager takes ownership of the config object. + * + * Call this method just befor any of export*() methods is called to prepare ExportManager for upcoming export process. + * Otherwise the export process will use settings from last configure() call. + * + * If any export is already in progress, this method reports error in logs and does nothing. + * If plugin for specified format cannot be found, then this method reports warning in logs and does nothing. + */ + void configure(const QString& format, StandardExportConfig* config); + + /** + * @brief Configures export service for export. + * @param format Format to be used in upcoming export. + * @param config Standard configuration options to be used in upcoming export. + * + * Same as method above, except it makes its own copy of the config object. + */ + void configure(const QString& format, const StandardExportConfig& config); + bool isExportInProgress() const; + void exportQueryResults(Db* db, const QString& query); + void exportTable(Db* db, const QString& database, const QString& table); + void exportDatabase(Db* db, const QStringList& objectListToExport); + + static bool isAnyPluginAvailable(); + + private: + void invalidFormat(const QString& format); + bool checkInitialConditions(); + QIODevice* getOutputStream(); + ExportWorker* prepareExport(); + void handleClipboardExport(); + + bool exportInProgress = false; + ExportMode mode; + StandardExportConfig* config = nullptr; + QString format; + ExportPlugin* plugin = nullptr; + QBuffer* bufferForClipboard = nullptr; + + public slots: + void interrupt(); + + private slots: + void finalizeExport(bool result, QIODevice* output); + + signals: + void exportFinished(); + void exportSuccessful(); + void exportFailed(); + void storeInClipboard(const QString& str); + void storeInClipboard(const QByteArray& bytes, const QString& mimeType); + void orderWorkerToInterrupt(); +}; + +#define EXPORT_MANAGER SQLITESTUDIO->getExportManager() + +Q_DECLARE_OPERATORS_FOR_FLAGS(ExportManager::StandardConfigFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(ExportManager::ExportModes) +Q_DECLARE_OPERATORS_FOR_FLAGS(ExportManager::ExportProviderFlags) + +#endif // EXPORTMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/extralicensemanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/extralicensemanager.cpp new file mode 100644 index 0000000..23fb513 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/extralicensemanager.cpp @@ -0,0 +1,28 @@ +#include "extralicensemanager.h" + +ExtraLicenseManager::ExtraLicenseManager() +{ +} + +bool ExtraLicenseManager::addLicense(const QString& title, const QString& filePath) +{ + if (licenses.contains(title)) + return false; + + licenses[title] = filePath; + return true; +} + +bool ExtraLicenseManager::removeLicense(const QString& title) +{ + if (!licenses.contains(title)) + return false; + + licenses.remove(title); + return true; +} + +const QHash<QString, QString>&ExtraLicenseManager::getLicenses() const +{ + return licenses; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/extralicensemanager.h b/SQLiteStudio3/coreSQLiteStudio/services/extralicensemanager.h new file mode 100644 index 0000000..fcf1203 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/extralicensemanager.h @@ -0,0 +1,21 @@ +#ifndef EXTRALICENSEMANAGER_H +#define EXTRALICENSEMANAGER_H + +#include "coreSQLiteStudio_global.h" +#include <QString> +#include <QHash> + +class API_EXPORT ExtraLicenseManager +{ + public: + ExtraLicenseManager(); + + bool addLicense(const QString& title, const QString& filePath); + bool removeLicense(const QString& title); + const QHash<QString,QString>& getLicenses() const; + + private: + QHash<QString,QString> licenses; +}; + +#endif // EXTRALISENCEMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/functionmanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/functionmanager.cpp new file mode 100644 index 0000000..10db318 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/functionmanager.cpp @@ -0,0 +1,39 @@ +#include "services/functionmanager.h" + +FunctionManager::FunctionBase::FunctionBase() +{ +} + +FunctionManager::FunctionBase::~FunctionBase() +{ +} + +QString FunctionManager::FunctionBase::toString() const +{ + static const QString format = "%1(%2)"; + QString args = undefinedArgs ? "..." : arguments.join(", "); + return format.arg(name).arg(args); +} + +QString FunctionManager::FunctionBase::typeString(Type type) +{ + switch (type) + { + case ScriptFunction::SCALAR: + return "SCALAR"; + case ScriptFunction::AGGREGATE: + return "AGGREGATE"; + } + return QString::null; +} + +FunctionManager::ScriptFunction::Type FunctionManager::FunctionBase::typeString(const QString& type) +{ + if (type == "SCALAR") + return ScriptFunction::SCALAR; + + if (type == "AGGREGATE") + return ScriptFunction::AGGREGATE; + + return ScriptFunction::SCALAR; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/functionmanager.h b/SQLiteStudio3/coreSQLiteStudio/services/functionmanager.h new file mode 100644 index 0000000..b848c93 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/functionmanager.h @@ -0,0 +1,74 @@ +#ifndef FUNCTIONMANAGER_H +#define FUNCTIONMANAGER_H + +#include "coreSQLiteStudio_global.h" +#include "common/global.h" +#include <QList> +#include <QSharedPointer> +#include <QObject> +#include <QStringList> + +class Db; + +class API_EXPORT FunctionManager : public QObject +{ + Q_OBJECT + + public: + struct API_EXPORT FunctionBase + { + enum Type + { + SCALAR = 0, + AGGREGATE = 1 + }; + + FunctionBase(); + virtual ~FunctionBase(); + + virtual QString toString() const; + + static QString typeString(Type type); + static Type typeString(const QString& type); + + QString name; + QStringList arguments; + Type type = SCALAR; + bool undefinedArgs = true; + }; + + struct API_EXPORT ScriptFunction : public FunctionBase + { + QString lang; + QString code; + QString initCode; + QString finalCode; + QStringList databases; + bool allDatabases = true; + }; + + struct API_EXPORT NativeFunction : public FunctionBase + { + typedef std::function<QVariant(const QList<QVariant>& args, Db* db, bool& ok)> ImplementationFunction; + + ImplementationFunction functionPtr; + }; + + virtual void setScriptFunctions(const QList<ScriptFunction*>& newFunctions) = 0; + virtual QList<ScriptFunction*> getAllScriptFunctions() const = 0; + virtual QList<ScriptFunction*> getScriptFunctionsForDatabase(const QString& dbName) const = 0; + virtual QList<NativeFunction*> getAllNativeFunctions() const = 0; + + virtual QVariant evaluateScalar(const QString& name, int argCount, const QList<QVariant>& args, Db* db, bool& ok) = 0; + virtual void evaluateAggregateInitial(const QString& name, int argCount, Db* db, QHash<QString, QVariant>& aggregateStorage) = 0; + virtual void evaluateAggregateStep(const QString& name, int argCount, const QList<QVariant>& args, Db* db, + QHash<QString, QVariant>& aggregateStorage) = 0; + virtual QVariant evaluateAggregateFinal(const QString& name, int argCount, Db* db, bool& ok, QHash<QString, QVariant>& aggregateStorage) = 0; + + signals: + void functionListChanged(); +}; + +#define FUNCTIONS SQLITESTUDIO->getFunctionManager() + +#endif // FUNCTIONMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.cpp new file mode 100644 index 0000000..5876021 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.cpp @@ -0,0 +1,125 @@ +#include "collationmanagerimpl.h" +#include "services/pluginmanager.h" +#include "plugins/scriptingplugin.h" +#include "services/notifymanager.h" +#include "services/dbmanager.h" +#include "common/utils.h" +#include <QDebug> + +CollationManagerImpl::CollationManagerImpl() +{ + init(); +} + +void CollationManagerImpl::setCollations(const QList<CollationManager::CollationPtr>& newCollations) +{ + collations = newCollations; + refreshCollationsByKey(); + storeInConfig(); + emit collationListChanged(); +} + +QList<CollationManager::CollationPtr> CollationManagerImpl::getAllCollations() const +{ + return collations; +} + +QList<CollationManager::CollationPtr> CollationManagerImpl::getCollationsForDatabase(const QString& dbName) const +{ + QList<CollationPtr> results; + foreach (const CollationPtr& coll, collations) + { + if (coll->allDatabases || coll->databases.contains(dbName, Qt::CaseInsensitive)) + results << coll; + } + return results; +} + +int CollationManagerImpl::evaluate(const QString& name, const QString& value1, const QString& value2) +{ + if (!collationsByKey.contains(name)) + { + qWarning() << "Could not find requested collation" << name << ", so using default collation."; + return evaluateDefault(value1, value2); + } + + ScriptingPlugin* plugin = PLUGINS->getScriptingPlugin(collationsByKey[name]->lang); + if (!plugin) + { + qWarning() << "Plugin for collation" << name << ", not loaded, so using default collation."; + return evaluateDefault(value1, value2); + } + + QString err; + QVariant result = plugin->evaluate(collationsByKey[name]->code, {value1, value2}, &err); + + if (!err.isNull()) + { + qWarning() << "Error while evaluating collation:" << err; + return evaluateDefault(value1, value2); + } + + bool ok; + int intResult = result.toInt(&ok); + if (!ok) + { + qWarning() << "Not integer result from collation:" << result.toString(); + return evaluateDefault(value1, value2); + } + + return intResult; +} + +int CollationManagerImpl::evaluateDefault(const QString& value1, const QString& value2) +{ + return value1.compare(value2, Qt::CaseInsensitive); +} + +void CollationManagerImpl::init() +{ + loadFromConfig(); + refreshCollationsByKey(); +} + +void CollationManagerImpl::storeInConfig() +{ + QVariantList list; + QHash<QString,QVariant> collHash; + for (CollationPtr coll : collations) + { + collHash["name"] = coll->name; + collHash["lang"] = coll->lang; + collHash["code"] = coll->code; + collHash["allDatabases"] = coll->allDatabases; + collHash["databases"] =common(DBLIST->getDbNames(), coll->databases); + list << collHash; + } + CFG_CORE.Internal.Collations.set(list); +} + +void CollationManagerImpl::loadFromConfig() +{ + collations.clear(); + + QVariantList list = CFG_CORE.Internal.Collations.get(); + QHash<QString,QVariant> collHash; + CollationPtr coll; + for (const QVariant& var : list) + { + collHash = var.toHash(); + coll = CollationPtr::create(); + coll->name = collHash["name"].toString(); + coll->lang = collHash["lang"].toString(); + coll->code = collHash["code"].toString(); + coll->databases = collHash["databases"].toStringList(); + coll->allDatabases = collHash["allDatabases"].toBool(); + collations << coll; + } +} + +void CollationManagerImpl::refreshCollationsByKey() +{ + collationsByKey.clear(); + foreach (CollationPtr collation, collations) + collationsByKey[collation->name] = collation; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.h b/SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.h new file mode 100644 index 0000000..f5a5937 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.h @@ -0,0 +1,36 @@ +#ifndef COLLATIONMANAGERIMPL_H +#define COLLATIONMANAGERIMPL_H + +#include "services/collationmanager.h" + +class ScriptingPlugin; +class Plugin; +class PluginType; + +class API_EXPORT CollationManagerImpl : public CollationManager +{ + public: + CollationManagerImpl(); + + void setCollations(const QList<CollationPtr>& newCollations); + QList<CollationPtr> getAllCollations() const; + QList<CollationPtr> getCollationsForDatabase(const QString& dbName) const; + int evaluate(const QString& name, const QString& value1, const QString& value2); + int evaluateDefault(const QString& value1, const QString& value2); + + private: + void init(); + void storeInConfig(); + void loadFromConfig(); + void refreshCollationsByKey(); + + QList<CollationPtr> collations; + QHash<QString,CollationPtr> collationsByKey; + QHash<QString,ScriptingPlugin*> scriptingPlugins; + + private slots: + void pluginLoaded(Plugin* plugin, PluginType* type); + void pluginUnloaded(Plugin* plugin, PluginType* type); +}; + +#endif // COLLATIONMANAGERIMPL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.cpp new file mode 100644 index 0000000..e210f01 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.cpp @@ -0,0 +1,770 @@ +#include "configimpl.h" +#include "sqlhistorymodel.h" +#include "ddlhistorymodel.h" +#include "services/notifymanager.h" +#include "sqlitestudio.h" +#include "db/dbsqlite3.h" +#include <QtGlobal> +#include <QDebug> +#include <QList> +#include <QDir> +#include <QFileInfo> +#include <QDataStream> +#include <QRegExp> +#include <QDateTime> +#include <QSysInfo> +#include <QtConcurrent/QtConcurrentRun> + +static_qstring(DB_FILE_NAME, "settings3"); +qint64 ConfigImpl::sqlHistoryId = -1; + +ConfigImpl::~ConfigImpl() +{ + cleanUp(); +} + +void ConfigImpl::init() +{ + initDbFile(); + initTables(); + + connect(this, SIGNAL(sqlHistoryRefreshNeeded()), this, SLOT(refreshSqlHistory())); + connect(this, SIGNAL(ddlHistoryRefreshNeeded()), this, SLOT(refreshDdlHistory())); +} + +void ConfigImpl::cleanUp() +{ + if (db->isOpen()) + db->close(); + + safe_delete(db); +} + +const QString &ConfigImpl::getConfigDir() const +{ + return configDir; +} + +QString ConfigImpl::getConfigFilePath() const +{ + if (!db) + return QString(); + + return db->getPath(); +} + +void ConfigImpl::beginMassSave() +{ + if (isMassSaving()) + return; + + emit massSaveBegins(); + db->exec("BEGIN;"); + massSaving = true; +} + +void ConfigImpl::commitMassSave() +{ + if (!isMassSaving()) + return; + + db->exec("COMMIT;"); + emit massSaveCommited(); + massSaving = false; +} + +void ConfigImpl::rollbackMassSave() +{ + if (!isMassSaving()) + return; + + db->exec("ROLLBACK;"); + massSaving = false; +} + +bool ConfigImpl::isMassSaving() const +{ + return massSaving; +} + +void ConfigImpl::set(const QString &group, const QString &key, const QVariant &value) +{ + QByteArray bytes; + QDataStream stream(&bytes, QIODevice::WriteOnly); + stream << value; + + db->exec("INSERT OR REPLACE INTO settings VALUES (?, ?, ?)", {group, key, bytes}); +} + +QVariant ConfigImpl::get(const QString &group, const QString &key) +{ + SqlQueryPtr results = db->exec("SELECT value FROM settings WHERE [group] = ? AND [key] = ?", {group, key}); + return deserializeValue(results->getSingleCell()); +} + +QHash<QString,QVariant> ConfigImpl::getAll() +{ + SqlQueryPtr results = db->exec("SELECT [group], [key], value FROM settings"); + + QHash<QString,QVariant> cfg; + QString key; + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + key = row->value("group").toString() + "." + row->value("key").toString(); + cfg[key] = deserializeValue(row->value("value")); + } + return cfg; +} + +bool ConfigImpl::storeErrorAndReturn(SqlQueryPtr results) +{ + if (results->isError()) + { + lastQueryError = results->getErrorText(); + return true; + } + else + return false; +} + +void ConfigImpl::printErrorIfSet(SqlQueryPtr results) +{ + if (results && results->isError()) + { + qCritical() << "Config error while executing query:" << results->getErrorText(); + storeErrorAndReturn(results); + } +} + +bool ConfigImpl::addDb(const QString& name, const QString& path, const QHash<QString,QVariant>& options) +{ + QByteArray optBytes = hashToBytes(options); + SqlQueryPtr results = db->exec("INSERT INTO dblist VALUES (?, ?, ?)", {name, path, optBytes}); + return !storeErrorAndReturn(results); +} + +bool ConfigImpl::updateDb(const QString &name, const QString &newName, const QString &path, const QHash<QString,QVariant> &options) +{ + QByteArray optBytes = hashToBytes(options); + SqlQueryPtr results = db->exec("UPDATE dblist SET name = ?, path = ?, options = ? WHERE name = ?", + {newName, path, optBytes, name}); + + return (!storeErrorAndReturn(results) && results->rowsAffected() > 0); +} + +bool ConfigImpl::removeDb(const QString &name) +{ + SqlQueryPtr results = db->exec("DELETE FROM dblist WHERE name = ?", {name}); + return (!storeErrorAndReturn(results) && results->rowsAffected() > 0); +} + +bool ConfigImpl::isDbInConfig(const QString &name) +{ + SqlQueryPtr results = db->exec("SELECT * FROM dblist WHERE name = ?", {name}); + return (!storeErrorAndReturn(results) && results->hasNext()); +} + +QString ConfigImpl::getLastErrorString() const +{ + QString msg = db->getErrorText(); + if (msg.trimmed().isEmpty()) + return lastQueryError; + + return msg; +} + +QList<ConfigImpl::CfgDbPtr> ConfigImpl::dbList() +{ + QList<CfgDbPtr> entries; + SqlQueryPtr results = db->exec("SELECT name, path, options FROM dblist"); + CfgDbPtr cfgDb; + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + cfgDb = CfgDbPtr::create(); + cfgDb->name = row->value("name").toString(); + cfgDb->path = row->value("path").toString(); + cfgDb->options = deserializeValue(row->value("options")).toHash(); + entries += cfgDb; + } + + return entries; +} + +ConfigImpl::CfgDbPtr ConfigImpl::getDb(const QString& dbName) +{ + SqlQueryPtr results = db->exec("SELECT path, options FROM dblist WHERE name = ?", {dbName}); + + if (!results->hasNext()) + return CfgDbPtr(); + + SqlResultsRowPtr row = results->next(); + + CfgDbPtr cfgDb = CfgDbPtr::create(); + cfgDb->name = dbName; + cfgDb->path = row->value("path").toString(); + cfgDb->options = deserializeValue(row->value("options")).toHash(); + return cfgDb; +} + +void ConfigImpl::storeGroups(const QList<DbGroupPtr>& groups) +{ + db->begin(); + db->exec("DELETE FROM groups"); + + foreach (const DbGroupPtr& group, groups) + storeGroup(group); + + db->commit(); +} + +void ConfigImpl::storeGroup(const ConfigImpl::DbGroupPtr &group, qint64 parentId) +{ + QVariant parent = QVariant(QVariant::LongLong); + if (parentId > -1) + parent = parentId; + + SqlQueryPtr results = db->exec("INSERT INTO groups (name, [order], parent, open, dbname) VALUES (?, ?, ?, ?, ?)", + {group->name, group->order, parent, group->open, group->referencedDbName}); + + qint64 newParentId = results->getRegularInsertRowId(); + foreach (const DbGroupPtr& childGroup, group->childs) + storeGroup(childGroup, newParentId); +} + +QList<ConfigImpl::DbGroupPtr> ConfigImpl::getGroups() +{ + DbGroupPtr topGroup = DbGroupPtr::create(); + topGroup->id = -1; + readGroupRecursively(topGroup); + return topGroup->childs; +} + +ConfigImpl::DbGroupPtr ConfigImpl::getDbGroup(const QString& dbName) +{ + SqlQueryPtr results = db->exec("SELECT id, name, [order], open, dbname FROM groups WHERE dbname = ? LIMIT 1", {dbName}); + + DbGroupPtr group = DbGroupPtr::create(); + group->referencedDbName = dbName; + + if (!results->hasNext()) + return group; + + SqlResultsRowPtr row = results->next(); + group->id = row->value("id").toULongLong(); + group->name = row->value("name").toString(); + group->order = row->value("order").toInt(); + group->open = row->value("open").toBool(); + return group; +} + +qint64 ConfigImpl::addSqlHistory(const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected) +{ + if (sqlHistoryId < 0) + { + SqlQueryPtr results = db->exec("SELECT max(id) FROM sqleditor_history"); + if (results->isError()) + { + qCritical() << "Cannot add SQL history, because cannot determinate sqleditor_history max(id):" << results->getErrorText(); + return -1; + } + + if (results->hasNext()) + sqlHistoryId = results->getSingleCell().toLongLong() + 1; + else + sqlHistoryId = 0; + } + + QtConcurrent::run(this, &ConfigImpl::asyncAddSqlHistory, sqlHistoryId, sql, dbName, timeSpentMillis, rowsAffected); + sqlHistoryId++; + return sqlHistoryId; +} + +void ConfigImpl::updateSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected) +{ + QtConcurrent::run(this, &ConfigImpl::asyncUpdateSqlHistory, id, sql, dbName, timeSpentMillis, rowsAffected); +} + +void ConfigImpl::clearSqlHistory() +{ + QtConcurrent::run(this, &ConfigImpl::asyncClearSqlHistory); +} + +QAbstractItemModel* ConfigImpl::getSqlHistoryModel() +{ + if (!sqlHistoryModel) + sqlHistoryModel = new SqlHistoryModel(db, this); + + return sqlHistoryModel; +} + +void ConfigImpl::addCliHistory(const QString& text) +{ + QtConcurrent::run(this, &ConfigImpl::asyncAddCliHistory, text); +} + +void ConfigImpl::applyCliHistoryLimit() +{ + QtConcurrent::run(this, &ConfigImpl::asyncApplyCliHistoryLimit); +} + +void ConfigImpl::clearCliHistory() +{ + QtConcurrent::run(this, &ConfigImpl::asyncClearCliHistory); +} + +QStringList ConfigImpl::getCliHistory() const +{ + static_qstring(selectQuery, "SELECT text FROM cli_history ORDER BY id"); + + SqlQueryPtr results = db->exec(selectQuery); + if (results->isError()) + qWarning() << "Error while getting CLI history:" << db->getErrorText(); + + return results->columnAsList<QString>("text"); +} + +void ConfigImpl::addDdlHistory(const QString& queries, const QString& dbName, const QString& dbFile) +{ + QtConcurrent::run(this, &ConfigImpl::asyncAddDdlHistory, queries, dbName, dbFile); +} + +QList<ConfigImpl::DdlHistoryEntryPtr> ConfigImpl::getDdlHistoryFor(const QString& dbName, const QString& dbFile, const QDate& date) +{ + static_qstring(sql, + "SELECT timestamp," + " queries" + " FROM ddl_history" + " WHERE dbname = ?" + " AND file = ?" + " AND date(timestamp, 'unixepoch') = ?"); + + SqlQueryPtr results = db->exec(sql, {dbName, dbFile, date.toString("yyyy-MM-dd")}); + + QList<DdlHistoryEntryPtr> entries; + DdlHistoryEntryPtr entry; + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + entry = DdlHistoryEntryPtr::create(); + entry->dbName = dbName; + entry->dbFile = dbFile; + entry->timestamp = QDateTime::fromTime_t(row->value("timestamp").toUInt()); + entry->queries = row->value("queries").toString(); + entries << entry; + } + return entries; +} + +DdlHistoryModel* ConfigImpl::getDdlHistoryModel() +{ + if (!ddlHistoryModel) + ddlHistoryModel = new DdlHistoryModel(db, this); + + return ddlHistoryModel; +} + +void ConfigImpl::clearDdlHistory() +{ + QtConcurrent::run(this, &ConfigImpl::asyncClearDdlHistory); +} + +void ConfigImpl::addReportHistory(bool isFeatureRequest, const QString& title, const QString& url) +{ + QtConcurrent::run(this, &ConfigImpl::asyncAddReportHistory, isFeatureRequest, title, url); +} + +QList<Config::ReportHistoryEntryPtr> ConfigImpl::getReportHistory() +{ + static_qstring(sql, "SELECT id, timestamp, title, url, feature_request FROM reports_history"); + + SqlQueryPtr results = db->exec(sql); + + QList<ReportHistoryEntryPtr> entries; + SqlResultsRowPtr row; + ReportHistoryEntryPtr entry; + while (results->hasNext()) + { + row = results->next(); + entry = ReportHistoryEntryPtr::create(); + entry->id = row->value("id").toInt(); + entry->timestamp = row->value("timestamp").toInt(); + entry->title = row->value("title").toString(); + entry->url = row->value("url").toString(); + entry->isFeatureRequest = row->value("feature_request").toBool(); + entries << entry; + } + return entries; +} + +void ConfigImpl::deleteReport(int id) +{ + QtConcurrent::run(this, &ConfigImpl::asyncDeleteReport, id); +} + +void ConfigImpl::clearReportHistory() +{ + QtConcurrent::run(this, &ConfigImpl::asyncClearReportHistory); +} + +void ConfigImpl::readGroupRecursively(ConfigImpl::DbGroupPtr group) +{ + SqlQueryPtr results; + if (group->id < 0) + results = db->exec("SELECT id, name, [order], open, dbname FROM groups WHERE parent IS NULL ORDER BY [order]"); + else + results = db->exec("SELECT id, name, [order], open, dbname FROM groups WHERE parent = ? ORDER BY [order]", {group->id}); + + DbGroupPtr childGroup; + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + childGroup = DbGroupPtr::create(); + childGroup->id = row->value("id").toULongLong(); + childGroup->name = row->value("name").toString(); + childGroup->order = row->value("order").toInt(); + childGroup->open = row->value("open").toBool(); + childGroup->referencedDbName = row->value("dbname").toString(); + group->childs += childGroup; + } + + for (int i = 0; i < group->childs.size(); i++) + readGroupRecursively(group->childs[i]); +} + +void ConfigImpl::begin() +{ + db->begin(); +} + +void ConfigImpl::commit() +{ + db->commit(); +} + +void ConfigImpl::rollback() +{ + db->rollback(); +} + +QString ConfigImpl::getConfigPath() +{ +#ifdef Q_OS_WIN + if (QSysInfo::windowsVersion() & QSysInfo::WV_NT_based) + return SQLITESTUDIO->getEnv("APPDATA")+"/sqlitestudio"; + else + return SQLITESTUDIO->getEnv("HOME")+"/sqlitestudio"; +#else + return SQLITESTUDIO->getEnv("HOME")+"/.config/sqlitestudio"; +#endif +} + +QString ConfigImpl::getPortableConfigPath() +{ + QFileInfo file; + QDir dir("./sqlitestudio-cfg"); + + file = QFileInfo(dir.absolutePath()); + if (!file.exists()) + return dir.absolutePath(); + + if (!file.isDir() || !file.isReadable() || !file.isWritable()) + return QString::null; + + foreach (file, dir.entryInfoList()) + { + if (!file.isReadable() || !file.isWritable()) + return QString::null; + } + + return dir.absolutePath(); +} + +void ConfigImpl::initTables() +{ + SqlQueryPtr results = db->exec("SELECT lower(name) AS name FROM sqlite_master WHERE type = 'table'"); + QList<QString> tables = results->columnAsList<QString>(0); + + if (!tables.contains("version")) + { + QString table; + foreach (table, tables) + db->exec("DROP TABLE "+table); + + tables.clear(); + db->exec("CREATE TABLE version (version NUMERIC)"); + db->exec("INSERT INTO version VALUES ("+QString::number(SQLITESTUDIO_CONFIG_VERSION)+")"); + } + + if (!tables.contains("settings")) + db->exec("CREATE TABLE settings ([group] TEXT, [key] TEXT, value, PRIMARY KEY([group], [key]))"); + + if (!tables.contains("sqleditor_history")) + db->exec("CREATE TABLE sqleditor_history (id INTEGER PRIMARY KEY, dbname TEXT, date INTEGER, time_spent INTEGER, rows INTEGER, sql TEXT)"); + + if (!tables.contains("dblist")) + db->exec("CREATE TABLE dblist (name TEXT PRIMARY KEY, path TEXT UNIQUE, options TEXT)"); + + if (!tables.contains("groups")) + db->exec("CREATE TABLE groups (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, parent INTEGER REFERENCES groups(id), " + "[order] INTEGER, open INTEGER DEFAULT 0, dbname TEXT UNIQUE REFERENCES dblist(name) ON UPDATE CASCADE ON DELETE CASCADE, " + "UNIQUE(name, parent))"); + + if (!tables.contains("ddl_history")) + db->exec("CREATE TABLE ddl_history (id INTEGER PRIMARY KEY AUTOINCREMENT, dbname TEXT, file TEXT, timestamp INTEGER, " + "queries TEXT)"); + + if (!tables.contains("cli_history")) + db->exec("CREATE TABLE cli_history (id INTEGER PRIMARY KEY AUTOINCREMENT, text TEXT)"); + + if (!tables.contains("reports_history")) + db->exec("CREATE TABLE reports_history (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER, feature_request BOOLEAN, title TEXT, url TEXT)"); +} + +void ConfigImpl::initDbFile() +{ + // Determinate global config location and portable one + QString globalPath = getConfigPath(); + QString portablePath = getPortableConfigPath(); + + QStringList paths; + if (!globalPath.isNull() && !portablePath.isNull()) + { + if (QFileInfo(portablePath).exists()) + { + paths << portablePath+"/"+DB_FILE_NAME; + paths << globalPath+"/"+DB_FILE_NAME; + } + else + { + paths << globalPath+"/"+DB_FILE_NAME; + paths << portablePath+"/"+DB_FILE_NAME; + } + } + else if (!globalPath.isNull()) + { + paths << globalPath+"/"+DB_FILE_NAME; + } + else if (!portablePath.isNull()) + { + paths << portablePath+"/"+DB_FILE_NAME; + } + + // Create global config directory if not existing + QDir dir; + if (!globalPath.isNull()) + { + dir = QDir(globalPath); + if (!dir.exists()) + QDir::root().mkpath(globalPath); + } + + // A fallback to in-memory db + paths << ":memory:"; + + // Go through all candidates and pick one + QString path; + foreach (path, paths) + { + dir = QDir(path); + if (path != ":memory:") + dir.cdUp(); + + if (tryInitDbFile(path)) + { + configDir = dir.absolutePath(); + break; + } + } + + // We ended up with in-memory one? That's not good. + if (configDir == ":memory:") + { + paths.removeLast(); + notifyError(QObject::tr("Could not initialize configuration file. Any configuration changes and queries history will be lost after application restart." + " Tried to initialize the file at following localizations: %1.").arg(paths.join(", "))); + } + + qDebug() << "Using configuration directory:" << configDir; + db->exec("PRAGMA foreign_keys = 1;"); +} + +bool ConfigImpl::tryInitDbFile(const QString &dbPath) +{ + db = new DbSqlite3("SQLiteStudio settings", dbPath, {{DB_PURE_INIT, true}}); + if (!db->open()) + { + safe_delete(db); + return false; + } + + SqlQueryPtr results = db->exec("SELECT * FROM sqlite_master"); + if (results->isError()) + { + safe_delete(db); + return false; + } + + return true; +} + +QVariant ConfigImpl::deserializeValue(const QVariant &value) +{ + if (!value.isValid()) + return QVariant(); + + QByteArray bytes = value.toByteArray(); + if (bytes.isNull()) + return QVariant(); + + QVariant deserializedValue; + QDataStream stream(bytes); + stream >> deserializedValue; + return deserializedValue; +} + +void ConfigImpl::asyncAddSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected) +{ + db->begin(); + SqlQueryPtr results = db->exec("INSERT INTO sqleditor_history (id, dbname, date, time_spent, rows, sql) VALUES (?, ?, ?, ?, ?, ?)", + {id, dbName, (QDateTime::currentMSecsSinceEpoch() / 1000), timeSpentMillis, rowsAffected, sql}); + + if (results->isError()) + { + qDebug() << "Error adding SQL history:" << results->getErrorText(); + db->rollback(); + return; + } + + int maxHistorySize = CFG_CORE.General.SqlHistorySize.get(); + + results = db->exec("SELECT count(*) FROM sqleditor_history"); + if (results->hasNext() && results->getSingleCell().toInt() > maxHistorySize) + { + results = db->exec(QString("SELECT id FROM sqleditor_history ORDER BY id DESC LIMIT 1 OFFSET %1").arg(maxHistorySize)); + if (results->hasNext()) + { + int id = results->getSingleCell().toInt(); + if (id > 0) // it will be 0 on fail conversion, but we won't delete id <= 0 ever. + db->exec("DELETE FROM sqleditor_history WHERE id <= ?", {id}); + } + } + db->commit(); + + emit sqlHistoryRefreshNeeded(); +} + +void ConfigImpl::asyncUpdateSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected) +{ + db->exec("UPDATE sqleditor_history SET dbname = ?, time_spent = ?, rows = ?, sql = ? WHERE id = ?", + {dbName, timeSpentMillis, rowsAffected, sql, id}); + + emit sqlHistoryRefreshNeeded(); +} + +void ConfigImpl::asyncClearSqlHistory() +{ + db->exec("DELETE FROM sqleditor_history"); + emit sqlHistoryRefreshNeeded(); +} + +void ConfigImpl::asyncAddCliHistory(const QString& text) +{ + static_qstring(insertQuery, "INSERT INTO cli_history (text) VALUES (?)"); + + SqlQueryPtr results = db->exec(insertQuery, {text}); + if (results->isError()) + qWarning() << "Error while adding CLI history:" << results->getErrorText(); + + applyCliHistoryLimit(); +} + +void ConfigImpl::asyncApplyCliHistoryLimit() +{ + static_qstring(limitQuery, "DELETE FROM cli_history WHERE id >= (SELECT id FROM cli_history ORDER BY id LIMIT 1 OFFSET %1)"); + + SqlQueryPtr results = db->exec(limitQuery.arg(CFG_CORE.Console.HistorySize.get())); + if (results->isError()) + qWarning() << "Error while limiting CLI history:" << db->getErrorText(); +} + +void ConfigImpl::asyncClearCliHistory() +{ + static_qstring(clearQuery, "DELETE FROM cli_history"); + + SqlQueryPtr results = db->exec(clearQuery); + if (results->isError()) + qWarning() << "Error while clearing CLI history:" << db->getErrorText(); +} + +void ConfigImpl::asyncAddDdlHistory(const QString& queries, const QString& dbName, const QString& dbFile) +{ + static_qstring(insert, "INSERT INTO ddl_history (dbname, file, timestamp, queries) VALUES (?, ?, ?, ?)"); + static_qstring(countSql, "SELECT count(*) FROM ddl_history"); + static_qstring(idSql, "SELECT id FROM ddl_history ORDER BY id DESC LIMIT 1 OFFSET %1"); + static_qstring(deleteSql, "DELETE FROM ddl_history WHERE id <= ?"); + + db->begin(); + db->exec(insert, {dbName, dbFile, QDateTime::currentDateTime().toTime_t(), queries}); + + int maxHistorySize = CFG_CORE.General.DdlHistorySize.get(); + + SqlQueryPtr results = db->exec(countSql); + if (results->hasNext() && results->getSingleCell().toInt() > maxHistorySize) + { + results = db->exec(QString(idSql).arg(maxHistorySize), Db::Flag::NO_LOCK); + if (results->hasNext()) + { + int id = results->getSingleCell().toInt(); + if (id > 0) // it will be 0 on fail conversion, but we won't delete id <= 0 ever. + db->exec(deleteSql, {id}); + } + } + db->commit(); + + emit ddlHistoryRefreshNeeded(); +} + +void ConfigImpl::asyncClearDdlHistory() +{ + db->exec("DELETE FROM ddl_history"); + emit ddlHistoryRefreshNeeded(); +} + +void ConfigImpl::asyncAddReportHistory(bool isFeatureRequest, const QString& title, const QString& url) +{ + static_qstring(sql, "INSERT INTO reports_history (feature_request, timestamp, title, url) VALUES (?, ?, ?, ?)"); + db->exec(sql, {(isFeatureRequest ? 1 : 0), QDateTime::currentDateTime().toTime_t(), title, url}); + emit reportsHistoryRefreshNeeded(); +} + +void ConfigImpl::asyncDeleteReport(int id) +{ + static_qstring(sql, "DELETE FROM reports_history WHERE id = ?"); + db->exec(sql, {id}); + emit reportsHistoryRefreshNeeded(); +} + +void ConfigImpl::asyncClearReportHistory() +{ + static_qstring(sql, "DELETE FROM reports_history"); + db->exec(sql); + emit reportsHistoryRefreshNeeded(); +} + +void ConfigImpl::refreshSqlHistory() +{ + if (sqlHistoryModel) + sqlHistoryModel->refresh(); +} + +void ConfigImpl::refreshDdlHistory() +{ + if (ddlHistoryModel) + ddlHistoryModel->refresh(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.h b/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.h new file mode 100644 index 0000000..ec32e8d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.h @@ -0,0 +1,127 @@ +#ifndef CONFIGIMPL_H +#define CONFIGIMPL_H + +#include "coreSQLiteStudio_global.h" +#include "services/config.h" +#include "db/sqlquery.h" + +class AsyncConfigHandler; +class SqlHistoryModel; + +class API_EXPORT ConfigImpl : public Config +{ + Q_OBJECT + + friend class AsyncConfigHandler; + + public: + virtual ~ConfigImpl(); + + void init(); + void cleanUp(); + const QString& getConfigDir() const; + QString getConfigFilePath() const; + + void beginMassSave(); + void commitMassSave(); + void rollbackMassSave(); + bool isMassSaving() const; + void set(const QString& group, const QString& key, const QVariant& value); + QVariant get(const QString& group, const QString& key); + QHash<QString,QVariant> getAll(); + + bool addDb(const QString& name, const QString& path, const QHash<QString, QVariant> &options); + bool updateDb(const QString& name, const QString &newName, const QString& path, const QHash<QString, QVariant> &options); + bool removeDb(const QString& name); + bool isDbInConfig(const QString& name); + QString getLastErrorString() const; + + /** + * @brief Provides list of all registered databases. + * @return List of database entries. + * + * Registered databases are those that user added to the application. They are not necessary valid or supported. + * They can be inexisting or unsupported, but they are kept in registry in case user fixes file path, + * or loads plugin to support it. + */ + QList<CfgDbPtr> dbList(); + CfgDbPtr getDb(const QString& dbName); + + void storeGroups(const QList<DbGroupPtr>& groups); + QList<DbGroupPtr> getGroups(); + DbGroupPtr getDbGroup(const QString& dbName); + + qint64 addSqlHistory(const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected); + void updateSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected); + void clearSqlHistory(); + QAbstractItemModel* getSqlHistoryModel(); + + void addCliHistory(const QString& text); + void applyCliHistoryLimit(); + void clearCliHistory(); + QStringList getCliHistory() const; + + void addDdlHistory(const QString& queries, const QString& dbName, const QString& dbFile); + QList<DdlHistoryEntryPtr> getDdlHistoryFor(const QString& dbName, const QString& dbFile, const QDate& date); + DdlHistoryModel* getDdlHistoryModel(); + void clearDdlHistory(); + + void addReportHistory(bool isFeatureRequest, const QString& title, const QString& url); + QList<ReportHistoryEntryPtr> getReportHistory(); + void deleteReport(int id); + void clearReportHistory(); + + void begin(); + void commit(); + void rollback(); + + private: + /** + * @brief Stores error from query in class member. + * @param query Query to get error from. + * @return true if the query had any error set, or false if not. + * + * If the error was defined in the query, its message is stored in lastQueryError. + */ + bool storeErrorAndReturn(SqlQueryPtr results); + void printErrorIfSet(SqlQueryPtr results); + void storeGroup(const DbGroupPtr& group, qint64 parentId = -1); + void readGroupRecursively(DbGroupPtr group); + QString getConfigPath(); + QString getPortableConfigPath(); + void initTables(); + void initDbFile(); + bool tryInitDbFile(const QString& dbPath); + QVariant deserializeValue(const QVariant& value); + + void asyncAddSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected); + void asyncUpdateSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected); + void asyncClearSqlHistory(); + + void asyncAddCliHistory(const QString& text); + void asyncApplyCliHistoryLimit(); + void asyncClearCliHistory(); + + void asyncAddDdlHistory(const QString& queries, const QString& dbName, const QString& dbFile); + void asyncClearDdlHistory(); + + void asyncAddReportHistory(bool isFeatureRequest, const QString& title, const QString& url); + void asyncDeleteReport(int id); + void asyncClearReportHistory(); + + static Config* instance; + static qint64 sqlHistoryId; + + Db* db = nullptr; + QString configDir; + QString lastQueryError; + bool massSaving = false; + SqlHistoryModel* sqlHistoryModel = nullptr; + DdlHistoryModel* ddlHistoryModel = nullptr; + + public slots: + void refreshDdlHistory(); + void refreshSqlHistory(); +}; + +#endif // CONFIGIMPL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.cpp new file mode 100644 index 0000000..43fc953 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.cpp @@ -0,0 +1,524 @@ +#include "dbmanagerimpl.h" +#include "db/db.h" +#include "services/config.h" +#include "plugins//dbplugin.h" +#include "services/pluginmanager.h" +#include "services/notifymanager.h" +#include "common/utils.h" +#include <QCoreApplication> +#include <QFileInfo> +#include <QHash> +#include <QHashIterator> +#include <QPluginLoader> +#include <QDebug> +#include <QUrl> +#include <db/invaliddb.h> + +DbManagerImpl::DbManagerImpl(QObject *parent) : + DbManager(parent) +{ + init(); +} + +DbManagerImpl::~DbManagerImpl() +{ + foreach (Db* db, dbList) + { + disconnect(db, SIGNAL(disconnected()), this, SLOT(dbDisconnectedSlot())); + disconnect(db, SIGNAL(aboutToDisconnect(bool&)), this, SLOT(dbAboutToDisconnect(bool&))); + if (db->isOpen()) + db->close(); + + delete db; + } + dbList.clear(); + nameToDb.clear(); + pathToDb.clear(); +} + +bool DbManagerImpl::addDb(const QString &name, const QString &path, bool permanent) +{ + return addDb(name, path, QHash<QString,QVariant>(), permanent); +} + +bool DbManagerImpl::addDb(const QString &name, const QString &path, const QHash<QString,QVariant>& options, bool permanent) +{ + if (getByName(name)) + { + qWarning() << "Tried to add database with name that was already on the list:" << name; + return false; // db with this name exists + } + + QString errorMessage; + Db* db = createDb(name, path, options, &errorMessage); + if (!db) + { + notifyError(tr("Could not add database %1: %2").arg(path).arg(errorMessage)); + return false; + } + + listLock.lockForWrite(); + addDbInternal(db, permanent); + listLock.unlock(); + + emit dbAdded(db); + + return true; +} + +bool DbManagerImpl::updateDb(Db* db, const QString &name, const QString &path, const QHash<QString, QVariant> &options, bool permanent) +{ + if (db->isOpen()) + { + if (!db->close()) + return false; + } + + listLock.lockForWrite(); + nameToDb.remove(db->getName(), Qt::CaseInsensitive); + pathToDb.remove(db->getPath()); + + bool pathDifferent = db->getPath() != path; + + QString oldName = db->getName(); + db->setName(name); + db->setPath(path); + db->setConnectionOptions(options); + + bool result = false; + if (permanent) + { + if (CFG->isDbInConfig(oldName)) + result = CFG->updateDb(oldName, name, path, options); + else + result = CFG->addDb(name, path, options); + } + else if (CFG->isDbInConfig(name)) // switched "permanent" off? + result = CFG->removeDb(name); + + InvalidDb* invalidDb = dynamic_cast<InvalidDb*>(db); + Db* reloadedDb = db; + if (pathDifferent && invalidDb) + reloadedDb = tryToLoadDb(invalidDb); + + if (reloadedDb) // reloading was not necessary (was not invalid) or it was successful + db = reloadedDb; + + nameToDb[name] = db; + pathToDb[path] = db; + + listLock.unlock(); + + if (result && reloadedDb) + emit dbUpdated(oldName, db); + else if (reloadedDb) // database reloaded correctly, but update failed + notifyError(tr("Database %1 could not be updated, because of an error: %2").arg(oldName).arg(CFG->getLastErrorString())); + + return result; +} + +void DbManagerImpl::removeDbByName(const QString &name, Qt::CaseSensitivity cs) +{ + listLock.lockForRead(); + bool contains = nameToDb.contains(name, cs); + listLock.unlock(); + + if (!contains) + return; + + listLock.lockForWrite(); + Db* db = nameToDb[name]; + removeDbInternal(db); + listLock.unlock(); + + emit dbRemoved(db); + + delete db; +} + +void DbManagerImpl::removeDbByPath(const QString &path) +{ + listLock.lockForRead(); + bool contains = pathToDb.contains(path); + listLock.unlock(); + if (!contains) + return; + + listLock.lockForWrite(); + Db* db = pathToDb[path]; + removeDbInternal(db); + listLock.unlock(); + + emit dbRemoved(db); + + delete db; +} + +void DbManagerImpl::removeDb(Db* db) +{ + db->close(); + + listLock.lockForWrite(); + removeDbInternal(db); + listLock.unlock(); + + emit dbRemoved(db); + delete db; +} + +void DbManagerImpl::removeDbInternal(Db* db, bool alsoFromConfig) +{ + QString name = db->getName(); + if (alsoFromConfig) + CFG->removeDb(name); + + nameToDb.remove(name); + pathToDb.remove(db->getPath()); + dbList.removeOne(db); + disconnect(db, SIGNAL(connected()), this, SLOT(dbConnectedSlot())); + disconnect(db, SIGNAL(disconnected()), this, SLOT(dbDisconnectedSlot())); + disconnect(db, SIGNAL(aboutToDisconnect(bool&)), this, SLOT(dbAboutToDisconnect(bool&))); +} + +QList<Db*> DbManagerImpl::getDbList() +{ + listLock.lockForRead(); + QList<Db*> list = dbList; + listLock.unlock(); + return list; +} + +QList<Db*> DbManagerImpl::getValidDbList() +{ + QList<Db*> list = getDbList(); + QMutableListIterator<Db*> it(list); + while (it.hasNext()) + { + it.next(); + if (!it.value()->isValid()) + it.remove(); + } + + return list; +} + +QList<Db*> DbManagerImpl::getConnectedDbList() +{ + QList<Db*> list = getDbList(); + QMutableListIterator<Db*> it(list); + while (it.hasNext()) + { + it.next(); + if (!it.value()->isOpen()) + it.remove(); + } + + return list; +} + +QStringList DbManagerImpl::getDbNames() +{ + QReadLocker lock(&listLock); + return nameToDb.keys(); +} + +Db* DbManagerImpl::getByName(const QString &name, Qt::CaseSensitivity cs) +{ + QReadLocker lock(&listLock); + return nameToDb.value(name, cs); +} + +Db* DbManagerImpl::getByPath(const QString &path) +{ + return pathToDb.value(path); +} + +Db* DbManagerImpl::createInMemDb() +{ + if (!inMemDbCreatorPlugin) + return nullptr; + + return inMemDbCreatorPlugin->getInstance("", ":memory:", {}); +} + +bool DbManagerImpl::isTemporary(Db* db) +{ + return CFG->getDb(db->getName()).isNull(); +} + +QString DbManagerImpl::quickAddDb(const QString& path, const QHash<QString, QVariant>& options) +{ + QString newName = DbManager::generateDbName(path); + newName = generateUniqueName(newName, DBLIST->getDbNames()); + if (!DBLIST->addDb(newName, path, options, false)) + return QString::null; + + return newName; +} + +void DbManagerImpl::setInMemDbCreatorPlugin(DbPlugin* plugin) +{ + inMemDbCreatorPlugin = plugin; +} + +void DbManagerImpl::init() +{ + Q_ASSERT(PLUGINS); + + loadInitialDbList(); + + connect(PLUGINS, SIGNAL(aboutToUnload(Plugin*,PluginType*)), this, SLOT(aboutToUnload(Plugin*,PluginType*))); + connect(PLUGINS, SIGNAL(loaded(Plugin*,PluginType*)), this, SLOT(loaded(Plugin*,PluginType*))); +} + +void DbManagerImpl::loadInitialDbList() +{ + QUrl url; + InvalidDb* db = nullptr; + foreach (const Config::CfgDbPtr& cfgDb, CFG->dbList()) + { + db = new InvalidDb(cfgDb->name, cfgDb->path, cfgDb->options); + + url = QUrl::fromUserInput(cfgDb->path); + if (url.isLocalFile() && !QFile::exists(cfgDb->path)) + db->setError(tr("Database file doesn't exist.")); + else + db->setError(tr("No supporting plugin loaded.")); + + addDbInternal(db, false); + } +} + +void DbManagerImpl::notifyDatabasesAreLoaded() +{ + // Any databases were already loaded by loaded() slot, which is called when DbPlugin was loaded. + emit dbListLoaded(); +} + +void DbManagerImpl::scanForNewDatabasesInConfig() +{ + QList<Config::CfgDbPtr> cfgDbList = CFG->dbList(); + + QUrl url; + InvalidDb* db = nullptr; + for (const Config::CfgDbPtr& cfgDb : cfgDbList) + { + if (getByName(cfgDb->name) || getByPath(cfgDb->path)) + continue; + + db = new InvalidDb(cfgDb->name, cfgDb->path, cfgDb->options); + + url = QUrl::fromUserInput(cfgDb->path); + if (url.isLocalFile() && !QFile::exists(cfgDb->path)) + db->setError(tr("Database file doesn't exist.")); + else + db->setError(tr("No supporting plugin loaded.")); + + addDbInternal(db); + tryToLoadDb(db); + } +} + +void DbManagerImpl::addDbInternal(Db* db, bool alsoToConfig) +{ + if (alsoToConfig) + CFG->addDb(db->getName(), db->getPath(), db->getConnectionOptions()); + + dbList << db; + nameToDb[db->getName()] = db; + pathToDb[db->getPath()] = db; + connect(db, SIGNAL(connected()), this, SLOT(dbConnectedSlot())); + connect(db, SIGNAL(disconnected()), this, SLOT(dbDisconnectedSlot())); + connect(db, SIGNAL(aboutToDisconnect(bool&)), this, SLOT(dbAboutToDisconnect(bool&))); +} + +QList<Db*> DbManagerImpl::getInvalidDatabases() const +{ + return filter<Db*>(dbList, [](Db* db) -> bool + { + return !db->isValid(); + }); +} + +Db* DbManagerImpl::tryToLoadDb(InvalidDb* invalidDb) +{ + QUrl url = QUrl::fromUserInput(invalidDb->getPath()); + if (url.isLocalFile() && !QFile::exists(invalidDb->getPath())) + return nullptr; + + Db* db = createDb(invalidDb->getName(), invalidDb->getPath(), invalidDb->getConnectionOptions()); + if (!db) + return nullptr; + + removeDbInternal(invalidDb, false); + delete invalidDb; + + addDbInternal(db, false); + + if (CFG->getDbGroup(db->getName())->open) + db->open(); + + emit dbLoaded(db); + return db; +} + +Db* DbManagerImpl::createDb(const QString &name, const QString &path, const QHash<QString,QVariant> &options, QString* errorMessages) +{ + QList<DbPlugin*> dbPlugins = PLUGINS->getLoadedPlugins<DbPlugin>(); + DbPlugin* dbPlugin = nullptr; + Db* db = nullptr; + QStringList messages; + QString message; + foreach (dbPlugin, dbPlugins) + { + if (options.contains("plugin") && options["plugin"] != dbPlugin->getName()) + continue; + + db = dbPlugin->getInstance(name, path, options, &message); + if (!db) + { + messages << message; + continue; + } + + if (!db->initAfterCreated()) + { + messages << tr("Database could not be initialized."); + continue; + } + + return db; + } + + if (errorMessages) + { + if (messages.size() == 0) + messages << tr("No suitable database driver plugin found."); + + *errorMessages = messages.join("; "); + } + + return nullptr; +} + + +void DbManagerImpl::dbConnectedSlot() +{ + QObject* sdr = sender(); + Db* db = dynamic_cast<Db*>(sdr); + if (!db) + { + qWarning() << "Received connected() signal but could not cast it to Db!"; + return; + } + emit dbConnected(db); +} + +void DbManagerImpl::dbDisconnectedSlot() +{ + QObject* sdr = sender(); + Db* db = dynamic_cast<Db*>(sdr); + if (!db) + { + qWarning() << "Received disconnected() signal but could not cast it to Db!"; + return; + } + emit dbDisconnected(db); +} + +void DbManagerImpl::dbAboutToDisconnect(bool& deny) +{ + QObject* sdr = sender(); + Db* db = dynamic_cast<Db*>(sdr); + if (!db) + { + qWarning() << "Received dbAboutToDisconnect() signal but could not cast it to Db!"; + return; + } + emit dbAboutToBeDisconnected(db, deny); +} + +void DbManagerImpl::aboutToUnload(Plugin* plugin, PluginType* type) +{ + if (!type->isForPluginType<DbPlugin>()) + return; + + InvalidDb* invalidDb = nullptr; + DbPlugin* dbPlugin = dynamic_cast<DbPlugin*>(plugin); + QList<Db*> toRemove; + for (Db* db : dbList) + { + if (!dbPlugin->checkIfDbServedByPlugin(db)) + continue; + + toRemove << db; + } + + for (Db* db : toRemove) + { + emit dbAboutToBeUnloaded(db, dbPlugin); + + if (db->isOpen()) + db->close(); + + removeDbInternal(db, false); + + invalidDb = new InvalidDb(db->getName(), db->getPath(), db->getConnectionOptions()); + invalidDb->setError(tr("No supporting plugin loaded.")); + addDbInternal(invalidDb, false); + + delete db; + + emit dbUnloaded(invalidDb); + } +} + +void DbManagerImpl::loaded(Plugin* plugin, PluginType* type) +{ + if (!type->isForPluginType<DbPlugin>()) + return; + + DbPlugin* dbPlugin = dynamic_cast<DbPlugin*>(plugin); + Db* db = nullptr; + + QUrl url; + for (Db* invalidDb : getInvalidDatabases()) + { + if (invalidDb->getConnectionOptions().contains(DB_PLUGIN) && invalidDb->getConnectionOptions()[DB_PLUGIN].toString() != dbPlugin->getName()) + continue; + + url = QUrl::fromUserInput(invalidDb->getPath()); + if (url.isLocalFile() && !QFile::exists(invalidDb->getPath())) + continue; + + db = createDb(invalidDb->getName(), invalidDb->getPath(), invalidDb->getConnectionOptions()); + if (!db) + continue; // For this db driver was not loaded yet. + + if (!dbPlugin->checkIfDbServedByPlugin(db)) + { + qDebug() << "Managed to load database" << db->getPath() << " (" << db->getName() << ")" + << "but it doesn't use DbPlugin that was just loaded, so it will not be loaded to the db manager"; + + delete db; + continue; + } + + removeDbInternal(invalidDb, false); + delete invalidDb; + + addDbInternal(db, false); + + if (!db->getConnectionOptions().contains(DB_PLUGIN)) + { + db->getConnectionOptions()[DB_PLUGIN] = dbPlugin->getName(); + if (!CFG->updateDb(db->getName(), db->getName(), db->getPath(), db->getConnectionOptions())) + qWarning() << "Could not store handling plugin in options for database" << db->getName(); + } + + if (CFG->getDbGroup(db->getName())->open) + db->open(); + + emit dbLoaded(db); + } +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.h b/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.h new file mode 100644 index 0000000..8e28080 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.h @@ -0,0 +1,183 @@ +#ifndef DBMANAGERIMPL_H +#define DBMANAGERIMPL_H + +#include "db/db.h" +#include "coreSQLiteStudio_global.h" +#include "common/strhash.h" +#include "common/global.h" +#include "services/dbmanager.h" +#include <QObject> +#include <QList> +#include <QHash> +#include <QReadWriteLock> +#include <QSharedPointer> + +class InvalidDb; + +class API_EXPORT DbManagerImpl : public DbManager +{ + Q_OBJECT + + public: + /** + * @brief Creates database manager. + * @param parent Parent object passed to QObject constructor. + */ + explicit DbManagerImpl(QObject *parent = 0); + + /** + * @brief Default destructor. + */ + ~DbManagerImpl(); + + bool addDb(const QString &name, const QString &path, const QHash<QString, QVariant> &options, bool permanent = true); + bool addDb(const QString &name, const QString &path, bool permanent = true); + bool updateDb(Db* db, const QString &name, const QString &path, const QHash<QString, QVariant> &options, bool permanent); + void removeDbByName(const QString& name, Qt::CaseSensitivity cs = Qt::CaseSensitive); + void removeDbByPath(const QString& path); + void removeDb(Db* db); + QList<Db*> getDbList(); + QList<Db*> getValidDbList(); + QList<Db*> getConnectedDbList(); + QStringList getDbNames(); + Db* getByName(const QString& name, Qt::CaseSensitivity cs = Qt::CaseSensitive); + Db* getByPath(const QString& path); + Db* createInMemDb(); + bool isTemporary(Db* db); + QString quickAddDb(const QString &path, const QHash<QString, QVariant> &options); + + /** + * @brief Defines database plugin used for creating in-memory databases. + * @param plugin Plugin to use. + */ + void setInMemDbCreatorPlugin(DbPlugin* plugin); + + private: + /** + * @brief Internal manager initialization. + * + * Called from any constructor. + */ + void init(); + + /** + * @brief Loads initial list of databases. + * + * Loaded databases are initially the invalid databases. + * They are turned into valid databases once their plugins are loaded. + */ + void loadInitialDbList(); + + /** + * @brief Removes database from application. + * @param db Database to be removed. + * @param alsoFromConfig If true, database will also be removed from configuration file, otherwise it's just from the manager. + * + * This method is internally called by public methods, as they all do pretty much the same thing, + * except they accept different input parameter. Then this method does the actual job. + */ + void removeDbInternal(Db* db, bool alsoFromConfig = true); + + /** + * @brief Adds database to the application. + * @param db Database to be added. + * @param alsoToConfig If true, the database will also be added to configuration file, otherwise it will be onle to the manager. + * + * When addDb() is called, it calls DbPlugin#getInstance() and if it returns object, then this method + * is called to register the database object in dbList variable. + */ + void addDbInternal(Db* db, bool alsoToConfig = true); + + /** + * @brief Filters invalid databases from all managed databases. + * @return Only invalid databases from this manager. + */ + QList<Db*> getInvalidDatabases() const; + + Db* tryToLoadDb(InvalidDb* invalidDb); + + /** + * @brief Creates database object. + * @param name Symbolic name of the database. + * @param path Database file path. + * @param options Database options, such as password, etc. + * @param errorMessages If not null, then the error messages from DbPlugins are stored in that string (in case this method returns null). + * @return Database object, or null pointer. + * + * This method is used internally by addDb() methods. It goes through all DbPlugin instances + * and checks if any of them supports given file path and options and returns a database object. + * First plugin that provides database object is accepted and its result is returned from the method. + */ + static Db* createDb(const QString &name, const QString &path, const QHash<QString, QVariant> &options, QString* errorMessages = nullptr); + + /** + * @brief Registered databases list. Both permanent and transient databases. + */ + QList<Db*> dbList; + + /** + * @brief Database ame to database instance mapping, with keys being case insensitive. + */ + StrHash<Db*> nameToDb; + + /** + * @brief Mapping from file path to the database. + * + * Mapping from database file path (as passed to addDb() or updateDb()) to the actual database object. + */ + QHash<QString,Db*> pathToDb; + + /** + * @brief Lock for dbList. + * Lock for dbList, so the list can be accessed from multiple threads. + */ + QReadWriteLock listLock; + + /** + * @brief Database plugin used to create in-memory databases. + */ + DbPlugin* inMemDbCreatorPlugin = nullptr; + + private slots: + /** + * @brief Slot called when connected to db. + * + * The slot is connected to the database object, therefore the database object has to be extracted from signal sender + * and converted to database type, then passed to the dbConnected(Db* db) signal. + */ + void dbConnectedSlot(); + /** + * @brief Slot called when connected to db. + * + * The slot is connected to the database object, therefore the database object has to be extracted from signal sender + * and converted to database type, then passed to the dbConnected(Db* db) signal. + */ + void dbDisconnectedSlot(); + + /** + * @brief Passes Db::aboutToDisconnect() signal to dbAboutToBeDisconnected() signal. + */ + void dbAboutToDisconnect(bool& deny); + + /** + * @brief Removes databases handled by the plugin from the list. + * @param plugin DbPlugin (any other will be ignored). + * @param type DbPlugin type. + * It removes all databases handled by the plugin being unloaded from the list of managed databases. + */ + void aboutToUnload(Plugin* plugin, PluginType* type); + + /** + * @brief Adds all configured databases handled by the plugin to managed list. + * @param plugin DbPlugin (any other will be ignored). + * @param type DbPlugin type. + * Checks configuration for any databases managed by the plugin and if there is any, it's loaded into the managed list. + */ + void loaded(Plugin* plugin, PluginType* type); + + public slots: + void notifyDatabasesAreLoaded(); + void scanForNewDatabasesInConfig(); +}; + +#endif // DBMANAGERIMPL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.cpp new file mode 100644 index 0000000..c94e4c2 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.cpp @@ -0,0 +1,694 @@ +#include "functionmanagerimpl.h" +#include "services/config.h" +#include "services/pluginmanager.h" +#include "services/notifymanager.h" +#include "plugins/scriptingplugin.h" +#include "common/unused.h" +#include "common/utils.h" +#include "common/utils_sql.h" +#include "services/dbmanager.h" +#include "db/queryexecutor.h" +#include "db/sqlquery.h" +#include <QVariantList> +#include <QHash> +#include <QDebug> +#include <QRegularExpression> +#include <QFile> +#include <QUrl> + +FunctionManagerImpl::FunctionManagerImpl() +{ + init(); +} + +void FunctionManagerImpl::setScriptFunctions(const QList<ScriptFunction*>& newFunctions) +{ + clearFunctions(); + functions = newFunctions; + refreshFunctionsByKey(); + storeInConfig(); + emit functionListChanged(); +} + +QList<FunctionManager::ScriptFunction*> FunctionManagerImpl::getAllScriptFunctions() const +{ + return functions; +} + +QList<FunctionManager::ScriptFunction*> FunctionManagerImpl::getScriptFunctionsForDatabase(const QString& dbName) const +{ + QList<ScriptFunction*> results; + foreach (ScriptFunction* func, functions) + { + if (func->allDatabases || func->databases.contains(dbName, Qt::CaseInsensitive)) + results << func; + } + return results; +} + +QVariant FunctionManagerImpl::evaluateScalar(const QString& name, int argCount, const QList<QVariant>& args, Db* db, bool& ok) +{ + Key key; + key.name = name; + key.argCount = argCount; + key.type = ScriptFunction::SCALAR; + if (functionsByKey.contains(key)) + { + ScriptFunction* function = functionsByKey[key]; + return evaluateScriptScalar(function, name, argCount, args, db, ok); + } + else if (nativeFunctionsByKey.contains(key)) + { + NativeFunction* function = nativeFunctionsByKey[key]; + return evaluateNativeScalar(function, args, db, ok); + } + + ok = false; + return cannotFindFunctionError(name, argCount); +} + +void FunctionManagerImpl::evaluateAggregateInitial(const QString& name, int argCount, Db* db, QHash<QString,QVariant>& aggregateStorage) +{ + Key key; + key.name = name; + key.argCount = argCount; + key.type = ScriptFunction::AGGREGATE; + if (functionsByKey.contains(key)) + { + ScriptFunction* function = functionsByKey[key]; + evaluateScriptAggregateInitial(function, db, aggregateStorage); + } +} + +void FunctionManagerImpl::evaluateAggregateStep(const QString& name, int argCount, const QList<QVariant>& args, Db* db, QHash<QString,QVariant>& aggregateStorage) +{ + Key key; + key.name = name; + key.argCount = argCount; + key.type = ScriptFunction::AGGREGATE; + if (functionsByKey.contains(key)) + { + ScriptFunction* function = functionsByKey[key]; + evaluateScriptAggregateStep(function, args, db, aggregateStorage); + } +} + +QVariant FunctionManagerImpl::evaluateAggregateFinal(const QString& name, int argCount, Db* db, bool& ok, QHash<QString,QVariant>& aggregateStorage) +{ + Key key; + key.name = name; + key.argCount = argCount; + key.type = ScriptFunction::AGGREGATE; + if (functionsByKey.contains(key)) + { + ScriptFunction* function = functionsByKey[key]; + return evaluateScriptAggregateFinal(function, name, argCount, db, ok, aggregateStorage); + } + + ok = false; + return cannotFindFunctionError(name, argCount); +} + +QVariant FunctionManagerImpl::evaluateScriptScalar(ScriptFunction* func, const QString& name, int argCount, const QList<QVariant>& args, Db* db, bool& ok) +{ + ScriptingPlugin* plugin = PLUGINS->getScriptingPlugin(func->lang); + if (!plugin) + { + ok = false; + return langUnsupportedError(name, argCount, func->lang); + } + DbAwareScriptingPlugin* dbAwarePlugin = dynamic_cast<DbAwareScriptingPlugin*>(plugin); + + QString error; + QVariant result; + + if (dbAwarePlugin) + result = dbAwarePlugin->evaluate(func->code, args, db, false, &error); + else + result = plugin->evaluate(func->code, args, &error); + + if (!error.isEmpty()) + { + ok = false; + return error; + } + return result; +} + +void FunctionManagerImpl::evaluateScriptAggregateInitial(ScriptFunction* func, Db* db, QHash<QString, QVariant>& aggregateStorage) +{ + ScriptingPlugin* plugin = PLUGINS->getScriptingPlugin(func->lang); + if (!plugin) + return; + + DbAwareScriptingPlugin* dbAwarePlugin = dynamic_cast<DbAwareScriptingPlugin*>(plugin); + + ScriptingPlugin::Context* ctx = plugin->createContext(); + aggregateStorage["context"] = QVariant::fromValue(ctx); + + if (dbAwarePlugin) + dbAwarePlugin->evaluate(ctx, func->code, {}, db, false); + else + plugin->evaluate(ctx, func->code, {}); + + if (plugin->hasError(ctx)) + { + aggregateStorage["error"] = true; + aggregateStorage["errorMessage"] = plugin->getErrorMessage(ctx); + } +} + +void FunctionManagerImpl::evaluateScriptAggregateStep(ScriptFunction* func, const QList<QVariant>& args, Db* db, QHash<QString, QVariant>& aggregateStorage) +{ + ScriptingPlugin* plugin = PLUGINS->getScriptingPlugin(func->lang); + if (!plugin) + return; + + if (aggregateStorage.contains("error")) + return; + + DbAwareScriptingPlugin* dbAwarePlugin = dynamic_cast<DbAwareScriptingPlugin*>(plugin); + + ScriptingPlugin::Context* ctx = aggregateStorage["context"].value<ScriptingPlugin::Context*>(); + if (dbAwarePlugin) + dbAwarePlugin->evaluate(ctx, func->code, args, db, false); + else + plugin->evaluate(ctx, func->code, args); + + if (plugin->hasError(ctx)) + { + aggregateStorage["error"] = true; + aggregateStorage["errorMessage"] = plugin->getErrorMessage(ctx); + } +} + +QVariant FunctionManagerImpl::evaluateScriptAggregateFinal(ScriptFunction* func, const QString& name, int argCount, Db* db, bool& ok, QHash<QString, QVariant>& aggregateStorage) +{ + ScriptingPlugin* plugin = PLUGINS->getScriptingPlugin(func->lang); + if (!plugin) + { + ok = false; + return langUnsupportedError(name, argCount, func->lang); + } + + ScriptingPlugin::Context* ctx = aggregateStorage["context"].value<ScriptingPlugin::Context*>(); + if (aggregateStorage.contains("error")) + { + ok = false; + plugin->releaseContext(ctx); + return aggregateStorage["errorMessage"]; + } + + DbAwareScriptingPlugin* dbAwarePlugin = dynamic_cast<DbAwareScriptingPlugin*>(plugin); + + QVariant result; + if (dbAwarePlugin) + result = dbAwarePlugin->evaluate(ctx, func->code, {}, db, false); + else + result = plugin->evaluate(ctx, func->code, {}); + + if (plugin->hasError(ctx)) + { + ok = false; + QString msg = plugin->getErrorMessage(ctx); + plugin->releaseContext(ctx); + return msg; + } + + plugin->releaseContext(ctx); + return result; +} + +QList<FunctionManager::NativeFunction*> FunctionManagerImpl::getAllNativeFunctions() const +{ + return nativeFunctions; +} + +QVariant FunctionManagerImpl::evaluateNativeScalar(NativeFunction* func, const QList<QVariant>& args, Db* db, bool& ok) +{ + if (!func->undefinedArgs && args.size() != func->arguments.size()) + { + ok = false; + return tr("Invalid number of arguments to function '%1'. Expected %2, but got %3.").arg(func->name, QString::number(func->arguments.size()), + QString::number(args.size())); + } + + return func->functionPtr(args, db, ok); +} + +void FunctionManagerImpl::init() +{ + loadFromConfig(); + initNativeFunctions(); + refreshFunctionsByKey(); +} + +void FunctionManagerImpl::initNativeFunctions() +{ + registerNativeFunction("regexp", {"pattern", "arg"}, FunctionManagerImpl::nativeRegExp); + registerNativeFunction("sqlfile", {"file"}, FunctionManagerImpl::nativeSqlFile); + registerNativeFunction("readfile", {"file"}, FunctionManagerImpl::nativeReadFile); + registerNativeFunction("writefile", {"file", "data"}, FunctionManagerImpl::nativeWriteFile); + registerNativeFunction("langs", {}, FunctionManagerImpl::nativeLangs); + registerNativeFunction("script", {"language", "code"}, FunctionManagerImpl::nativeScript); + registerNativeFunction("html_escape", {"string"}, FunctionManagerImpl::nativeHtmlEscape); + registerNativeFunction("url_encode", {"string"}, FunctionManagerImpl::nativeUrlEncode); + registerNativeFunction("url_decode", {"string"}, FunctionManagerImpl::nativeUrlDecode); + registerNativeFunction("base64_encode", {"data"}, FunctionManagerImpl::nativeBase64Encode); + registerNativeFunction("base64_decode", {"data"}, FunctionManagerImpl::nativeBase64Decode); + registerNativeFunction("md4_bin", {"data"}, FunctionManagerImpl::nativeMd4); + registerNativeFunction("md4", {"data"}, FunctionManagerImpl::nativeMd4Hex); + registerNativeFunction("md5_bin", {"data"}, FunctionManagerImpl::nativeMd5); + registerNativeFunction("md5", {"data"}, FunctionManagerImpl::nativeMd5Hex); + registerNativeFunction("sha1", {"data"}, FunctionManagerImpl::nativeSha1); + registerNativeFunction("sha224", {"data"}, FunctionManagerImpl::nativeSha224); + registerNativeFunction("sha256", {"data"}, FunctionManagerImpl::nativeSha256); + registerNativeFunction("sha384", {"data"}, FunctionManagerImpl::nativeSha384); + registerNativeFunction("sha512", {"data"}, FunctionManagerImpl::nativeSha512); + registerNativeFunction("sha3_224", {"data"}, FunctionManagerImpl::nativeSha3_224); + registerNativeFunction("sha3_256", {"data"}, FunctionManagerImpl::nativeSha3_256); + registerNativeFunction("sha3_384", {"data"}, FunctionManagerImpl::nativeSha3_384); + registerNativeFunction("sha3_512", {"data"}, FunctionManagerImpl::nativeSha3_512); +} + +void FunctionManagerImpl::refreshFunctionsByKey() +{ + functionsByKey.clear(); + foreach (ScriptFunction* func, functions) + functionsByKey[Key(func)] = func; + + foreach (NativeFunction* func, nativeFunctions) + nativeFunctionsByKey[Key(func)] = func; +} + +void FunctionManagerImpl::storeInConfig() +{ + QVariantList list; + QHash<QString,QVariant> fnHash; + foreach (ScriptFunction* func, functions) + { + fnHash["name"] = func->name; + fnHash["lang"] = func->lang; + fnHash["code"] = func->code; + fnHash["initCode"] = func->initCode; + fnHash["finalCode"] = func->finalCode; + fnHash["databases"] = common(DBLIST->getDbNames(), func->databases); + fnHash["arguments"] = func->arguments; + fnHash["type"] = static_cast<int>(func->type); + fnHash["undefinedArgs"] = func->undefinedArgs; + fnHash["allDatabases"] = func->allDatabases; + list << fnHash; + } + CFG_CORE.Internal.Functions.set(list); +} + +void FunctionManagerImpl::loadFromConfig() +{ + clearFunctions(); + + QVariantList list = CFG_CORE.Internal.Functions.get(); + QHash<QString,QVariant> fnHash; + ScriptFunction* func = nullptr; + for (const QVariant& var : list) + { + fnHash = var.toHash(); + func = new ScriptFunction(); + func->name = fnHash["name"].toString(); + func->lang = fnHash["lang"].toString(); + func->code = fnHash["code"].toString(); + func->initCode = fnHash["initCode"].toString(); + func->finalCode = fnHash["finalCode"].toString(); + func->databases = fnHash["databases"].toStringList(); + func->arguments = fnHash["arguments"].toStringList(); + func->type = static_cast<ScriptFunction::Type>(fnHash["type"].toInt()); + func->undefinedArgs = fnHash["undefinedArgs"].toBool(); + func->allDatabases = fnHash["allDatabases"].toBool(); + functions << func; + } +} + +void FunctionManagerImpl::clearFunctions() +{ + for (ScriptFunction* fn : functions) + delete fn; + + functions.clear(); +} + +QString FunctionManagerImpl::cannotFindFunctionError(const QString& name, int argCount) +{ + QStringList argMarkers = getArgMarkers(argCount); + return tr("No such function registered in SQLiteStudio: %1(%2)").arg(name).arg(argMarkers.join(",")); +} + +QString FunctionManagerImpl::langUnsupportedError(const QString& name, int argCount, const QString& lang) +{ + QStringList argMarkers = getArgMarkers(argCount); + return tr("Function %1(%2) was registered with language %3, but the plugin supporting that language is not currently loaded.") + .arg(name).arg(argMarkers.join(",")).arg(lang); +} + +QVariant FunctionManagerImpl::nativeRegExp(const QList<QVariant>& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 2) + { + ok = false; + return QVariant(); + } + + QRegularExpression re(args[0].toString()); + if (!re.isValid()) + { + ok = false; + return tr("Invalid regular expression pattern: %1").arg(args[0].toString()); + } + + QRegularExpressionMatch match = re.match(args[1].toString()); + return match.hasMatch(); +} + +QVariant FunctionManagerImpl::nativeSqlFile(const QList<QVariant>& args, Db* db, bool& ok) +{ + if (args.size() != 1) + { + ok = false; + return QVariant(); + } + + QFile file(args[0].toString()); + if (!file.open(QIODevice::ReadOnly)) + { + ok = false; + return tr("Could not open file %1 for reading: %2").arg(args[0].toString(), file.errorString()); + } + + QTextStream stream(&file); + QString sql = stream.readAll(); + file.close(); + + QueryExecutor executor(db); + executor.setAsyncMode(false); + executor.exec(sql); + SqlQueryPtr results = executor.getResults(); + if (results->isError()) + { + ok = false; + return results->getErrorText(); + } + return results->getSingleCell(); +} + +QVariant FunctionManagerImpl::nativeReadFile(const QList<QVariant>& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 1) + { + ok = false; + return QVariant(); + } + + QFile file(args[0].toString()); + if (!file.open(QIODevice::ReadOnly)) + { + ok = false; + return tr("Could not open file %1 for reading: %2").arg(args[0].toString(), file.errorString()); + } + + QByteArray data = file.readAll(); + file.close(); + return data; +} + +QVariant FunctionManagerImpl::nativeWriteFile(const QList<QVariant>& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 2) + { + ok = false; + return QVariant(); + } + + QFile file(args[0].toString()); + if (!file.open(QIODevice::WriteOnly|QIODevice::Truncate)) + { + ok = false; + return tr("Could not open file %1 for writting: %2").arg(args[0].toString(), file.errorString()); + } + + QByteArray data; + switch (args[1].type()) + { + case QVariant::String: + data = args[1].toString().toLocal8Bit(); + break; + default: + data = args[1].toByteArray(); + break; + } + + int res = file.write(data); + file.close(); + + if (res < 0) + { + ok = false; + return tr("Error while writting to file %1: %2").arg(args[0].toString(), file.errorString()); + } + + return res; +} + +QVariant FunctionManagerImpl::nativeScript(const QList<QVariant>& args, Db* db, bool& ok) +{ + if (args.size() != 2) + { + ok = false; + return QVariant(); + } + + ScriptingPlugin* plugin = PLUGINS->getScriptingPlugin(args[0].toString()); + if (!plugin) + { + ok = false; + return tr("Unsupported scripting language: %1").arg(args[0].toString()); + } + DbAwareScriptingPlugin* dbAwarePlugin = dynamic_cast<DbAwareScriptingPlugin*>(plugin); + + QString error; + QVariant result; + + if (dbAwarePlugin) + result = dbAwarePlugin->evaluate(args[1].toString(), QList<QVariant>(), db, false, &error); + else + result = plugin->evaluate(args[1].toString(), QList<QVariant>(), &error); + + if (!error.isEmpty()) + { + ok = false; + return error; + } + return result; +} + +QVariant FunctionManagerImpl::nativeLangs(const QList<QVariant>& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 0) + { + ok = false; + return QVariant(); + } + + QStringList names; + for (ScriptingPlugin* plugin : PLUGINS->getLoadedPlugins<ScriptingPlugin>()) + names << plugin->getLanguage(); + + return names.join(", "); +} + +QVariant FunctionManagerImpl::nativeHtmlEscape(const QList<QVariant>& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 1) + { + ok = false; + return QVariant(); + } + + return args[0].toString().toHtmlEscaped(); +} + +QVariant FunctionManagerImpl::nativeUrlEncode(const QList<QVariant>& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 1) + { + ok = false; + return QVariant(); + } + + return QUrl::toPercentEncoding(args[0].toString()); +} + +QVariant FunctionManagerImpl::nativeUrlDecode(const QList<QVariant>& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 1) + { + ok = false; + return QVariant(); + } + + return QUrl::fromPercentEncoding(args[0].toString().toLocal8Bit()); +} + +QVariant FunctionManagerImpl::nativeBase64Encode(const QList<QVariant>& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 1) + { + ok = false; + return QVariant(); + } + + return args[0].toByteArray().toBase64(); +} + +QVariant FunctionManagerImpl::nativeBase64Decode(const QList<QVariant>& args, Db* db, bool& ok) +{ + UNUSED(db); + + if (args.size() != 1) + { + ok = false; + return QVariant(); + } + + return QByteArray::fromBase64(args[0].toByteArray()); +} + +QVariant FunctionManagerImpl::nativeCryptographicFunction(const QList<QVariant>& args, Db* db, bool& ok, QCryptographicHash::Algorithm algo) +{ + UNUSED(db); + + if (args.size() != 1) + { + ok = false; + return QVariant(); + } + + return QCryptographicHash::hash(args[0].toByteArray(), algo); +} + +QVariant FunctionManagerImpl::nativeMd4(const QList<QVariant>& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Md4); +} + +QVariant FunctionManagerImpl::nativeMd4Hex(const QList<QVariant>& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Md4).toByteArray().toHex(); +} + +QVariant FunctionManagerImpl::nativeMd5(const QList<QVariant>& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Md5); +} + +QVariant FunctionManagerImpl::nativeMd5Hex(const QList<QVariant>& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Md5).toByteArray().toHex(); +} + +QVariant FunctionManagerImpl::nativeSha1(const QList<QVariant>& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha1); +} + +QVariant FunctionManagerImpl::nativeSha224(const QList<QVariant>& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha224); +} + +QVariant FunctionManagerImpl::nativeSha256(const QList<QVariant>& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha256); +} + +QVariant FunctionManagerImpl::nativeSha384(const QList<QVariant>& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha384); +} + +QVariant FunctionManagerImpl::nativeSha512(const QList<QVariant>& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha512); +} + +QVariant FunctionManagerImpl::nativeSha3_224(const QList<QVariant>& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha3_224); +} + +QVariant FunctionManagerImpl::nativeSha3_256(const QList<QVariant>& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha3_256); +} + +QVariant FunctionManagerImpl::nativeSha3_384(const QList<QVariant>& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha3_384); +} + +QVariant FunctionManagerImpl::nativeSha3_512(const QList<QVariant>& args, Db* db, bool& ok) +{ + return nativeCryptographicFunction(args, db, ok, QCryptographicHash::Sha3_512); +} + +QStringList FunctionManagerImpl::getArgMarkers(int argCount) +{ + QStringList argMarkers; + for (int i = 0; i < argCount; i++) + argMarkers << "?"; + + return argMarkers; +} + +void FunctionManagerImpl::registerNativeFunction(const QString& name, const QStringList& args, FunctionManager::NativeFunction::ImplementationFunction funcPtr) +{ + NativeFunction* nf = new NativeFunction(); + nf->name = name; + nf->arguments = args; + nf->type = FunctionBase::SCALAR; + nf->undefinedArgs = false; + nf->functionPtr = funcPtr; + nativeFunctions << nf; +} + +int qHash(const FunctionManagerImpl::Key& key) +{ + return qHash(key.name) ^ key.argCount ^ static_cast<int>(key.type); +} + +bool operator==(const FunctionManagerImpl::Key& key1, const FunctionManagerImpl::Key& key2) +{ + return key1.name == key2.name && key1.type == key2.type && key1.argCount == key2.argCount; +} + +FunctionManagerImpl::Key::Key() +{ +} + +FunctionManagerImpl::Key::Key(FunctionBase* function) : + name(function->name), argCount(function->undefinedArgs ? -1 : function->arguments.size()), type(function->type) +{ +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.h b/SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.h new file mode 100644 index 0000000..d8734e6 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.h @@ -0,0 +1,96 @@ +#ifndef FUNCTIONMANAGERIMPL_H +#define FUNCTIONMANAGERIMPL_H + +#include "services/functionmanager.h" +#include <QCryptographicHash> + +class SqlFunctionPlugin; +class Plugin; +class PluginType; + +class API_EXPORT FunctionManagerImpl : public FunctionManager +{ + Q_OBJECT + + public: + FunctionManagerImpl(); + + void setScriptFunctions(const QList<ScriptFunction*>& newFunctions); + QList<ScriptFunction*> getAllScriptFunctions() const; + QList<ScriptFunction*> getScriptFunctionsForDatabase(const QString& dbName) const; + QList<NativeFunction*> getAllNativeFunctions() const; + QVariant evaluateScalar(const QString& name, int argCount, const QList<QVariant>& args, Db* db, bool& ok); + void evaluateAggregateInitial(const QString& name, int argCount, Db* db, QHash<QString, QVariant>& aggregateStorage); + void evaluateAggregateStep(const QString& name, int argCount, const QList<QVariant>& args, Db* db, QHash<QString, QVariant>& aggregateStorage); + QVariant evaluateAggregateFinal(const QString& name, int argCount, Db* db, bool& ok, QHash<QString, QVariant>& aggregateStorage); + QVariant evaluateScriptScalar(ScriptFunction* func, const QString& name, int argCount, const QList<QVariant>& args, Db* db, bool& ok); + void evaluateScriptAggregateInitial(ScriptFunction* func, Db* db, + QHash<QString, QVariant>& aggregateStorage); + void evaluateScriptAggregateStep(ScriptFunction* func, const QList<QVariant>& args, Db* db, + QHash<QString, QVariant>& aggregateStorage); + QVariant evaluateScriptAggregateFinal(ScriptFunction* func, const QString& name, int argCount, Db* db, bool& ok, + QHash<QString, QVariant>& aggregateStorage); + QVariant evaluateNativeScalar(NativeFunction* func, const QList<QVariant>& args, Db* db, bool& ok); + + private: + struct Key + { + Key(); + Key(FunctionBase* function); + + QString name; + int argCount; + FunctionBase::Type type; + }; + + friend int qHash(const FunctionManagerImpl::Key& key); + friend bool operator==(const FunctionManagerImpl::Key& key1, const FunctionManagerImpl::Key& key2); + + void init(); + void initNativeFunctions(); + void refreshFunctionsByKey(); + void refreshNativeFunctionsByKey(); + void storeInConfig(); + void loadFromConfig(); + void clearFunctions(); + QString cannotFindFunctionError(const QString& name, int argCount); + QString langUnsupportedError(const QString& name, int argCount, const QString& lang); + void registerNativeFunction(const QString& name, const QStringList& args, NativeFunction::ImplementationFunction funcPtr); + + static QStringList getArgMarkers(int argCount); + static QVariant nativeRegExp(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeSqlFile(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeReadFile(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeWriteFile(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeScript(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeLangs(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeHtmlEscape(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeUrlEncode(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeUrlDecode(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeBase64Encode(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeBase64Decode(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeCryptographicFunction(const QList<QVariant>& args, Db* db, bool& ok, QCryptographicHash::Algorithm algo); + static QVariant nativeMd4(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeMd4Hex(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeMd5(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeMd5Hex(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeSha1(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeSha224(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeSha256(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeSha384(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeSha512(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeSha3_224(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeSha3_256(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeSha3_384(const QList<QVariant>& args, Db* db, bool& ok); + static QVariant nativeSha3_512(const QList<QVariant>& args, Db* db, bool& ok); + + QList<ScriptFunction*> functions; + QHash<Key,ScriptFunction*> functionsByKey; + QList<NativeFunction*> nativeFunctions; + QHash<Key,NativeFunction*> nativeFunctionsByKey; +}; + +int qHash(const FunctionManagerImpl::Key& key); +bool operator==(const FunctionManagerImpl::Key& key1, const FunctionManagerImpl::Key& key2); + +#endif // FUNCTIONMANAGERIMPL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.cpp new file mode 100644 index 0000000..5d7a517 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.cpp @@ -0,0 +1,820 @@ +#include "pluginmanagerimpl.h" +#include "plugins/scriptingplugin.h" +#include "plugins/genericplugin.h" +#include "services/notifymanager.h" +#include "common/unused.h" +#include <QCoreApplication> +#include <QDir> +#include <QDebug> +#include <QJsonArray> +#include <QJsonValue> + +PluginManagerImpl::PluginManagerImpl() +{ +} + +PluginManagerImpl::~PluginManagerImpl() +{ +} + +void PluginManagerImpl::init() +{ + pluginDirs += qApp->applicationDirPath() + "/plugins"; + pluginDirs += QDir(CFG->getConfigDir()).absoluteFilePath("plugins"); + + QString envDirs = SQLITESTUDIO->getEnv("SQLITESTUDIO_PLUGINS"); + if (!envDirs.isNull()) + pluginDirs += envDirs.split(PATH_LIST_SEPARATOR); + +#ifdef PLUGINS_DIR + pluginDirs += STRINGIFY(PLUGINS_DIR); +#endif + +#ifdef SYS_PLUGINS_DIR + pluginDirs += STRINGIFY(SYS_PLUGINS_DIR); +#endif + +#ifdef Q_OS_MACX + pluginDirs += QCoreApplication::applicationDirPath()+"/../PlugIns"; +#endif + + scanPlugins(); + loadPlugins(); +} + +void PluginManagerImpl::deinit() +{ + emit aboutToQuit(); + + // Plugin containers and their plugins + foreach (PluginContainer* container, pluginContainer.values()) + { + if (container->builtIn) + { + container->plugin->deinit(); + delete container->plugin; + } + else + unload(container->name); + } + + foreach (PluginContainer* container, pluginContainer.values()) + delete container; + + pluginContainer.clear(); + + // Types + foreach (PluginType* type, registeredPluginTypes) + delete type; + + registeredPluginTypes.clear(); + pluginCategories.clear(); +} + +QList<PluginType*> PluginManagerImpl::getPluginTypes() const +{ + return registeredPluginTypes; +} + +QStringList PluginManagerImpl::getPluginDirs() const +{ + return pluginDirs; +} + +QString PluginManagerImpl::getFilePath(Plugin* plugin) const +{ + if (!pluginContainer.contains(plugin->getName())) + return QString::null; + + return pluginContainer[plugin->getName()]->filePath; +} + +bool PluginManagerImpl::loadBuiltInPlugin(Plugin* plugin) +{ + bool res = initPlugin(plugin); + res &= plugin->init(); + return res; +} + +PluginType* PluginManagerImpl::getPluginType(Plugin* plugin) const +{ + if (!pluginContainer.contains(plugin->getName())) + return nullptr; + + return pluginContainer[plugin->getName()]->type; +} + +void PluginManagerImpl::scanPlugins() +{ + QStringList nameFilters; + nameFilters << "*.so" << "*.dll" << "*.dylib"; + + QPluginLoader* loader = nullptr; + foreach (QString pluginDirPath, pluginDirs) + { + QDir pluginDir(pluginDirPath); + foreach (QString fileName, pluginDir.entryList(nameFilters, QDir::Files)) + { + fileName = pluginDir.absoluteFilePath(fileName); + loader = new QPluginLoader(fileName); + loader->setLoadHints(QLibrary::ExportExternalSymbolsHint|QLibrary::ResolveAllSymbolsHint); + + if (!initPlugin(loader, fileName)) + { + qDebug() << "File" << fileName << "was loaded as plugin, but SQLiteStudio couldn't initialize plugin."; + delete loader; + } + } + } + + QStringList names; + for (PluginContainer* container : pluginContainer.values()) + { + if (!container->builtIn) + names << container->name; + } + + qDebug() << "Following plugins found:" << names; +} + +void PluginManagerImpl::loadPlugins() +{ + QStringList alreadyAttempted; + for (const QString& pluginName : pluginContainer.keys()) + { + if (shouldAutoLoad(pluginName)) + load(pluginName, alreadyAttempted); + } + + pluginsAreInitiallyLoaded = true; + emit pluginsInitiallyLoaded(); +} + +bool PluginManagerImpl::initPlugin(QPluginLoader* loader, const QString& fileName) +{ + QJsonObject pluginMetaData = loader->metaData(); + QString pluginTypeName = pluginMetaData.value("MetaData").toObject().value("type").toString(); + PluginType* pluginType = nullptr; + foreach (PluginType* type, registeredPluginTypes) + { + if (type->getName() == pluginTypeName) + { + pluginType = type; + break; + } + } + + if (!pluginType) + { + qWarning() << "Could not load plugin" + fileName + "because its type was not recognized:" << pluginTypeName; + return false; + } + + QString pluginName = pluginMetaData.value("className").toString(); + QJsonObject metaObject = pluginMetaData.value("MetaData").toObject(); + + if (!checkPluginRequirements(pluginName, metaObject)) + return false; + + PluginContainer* container = new PluginContainer; + container->type = pluginType; + container->filePath = fileName; + container->loaded = false; + container->loader = loader; + pluginCategories[pluginType] << container; + pluginContainer[pluginName] = container; + + if (!readDependencies(pluginName, container, metaObject.value("dependencies"))) + return false; + + if (!readConflicts(pluginName, container, metaObject.value("conflicts"))) + return false; + + if (!readMetaData(container)) + { + delete container; + return false; + } + + return true; +} + +bool PluginManagerImpl::checkPluginRequirements(const QString& pluginName, const QJsonObject& metaObject) +{ + if (metaObject.value("gui").toBool(false) && !SQLITESTUDIO->isGuiAvailable()) + { + qDebug() << "Plugin" << pluginName << "skipped, because it requires GUI and this is not GUI client running."; + return false; + } + + int minVer = metaObject.value("minQtVersion").toInt(0); + if (QT_VERSION_CHECK(minVer / 10000, minVer / 100 % 100, minVer % 10000) > QT_VERSION) + { + qDebug() << "Plugin" << pluginName << "skipped, because it requires at least Qt version" << toPrintableVersion(minVer) << ", but got" << QT_VERSION_STR; + return false; + } + + int maxVer = metaObject.value("maxQtVersion").toInt(999999); + if (QT_VERSION_CHECK(maxVer / 10000, maxVer / 100 % 100, maxVer % 10000) < QT_VERSION) + { + qDebug() << "Plugin" << pluginName << "skipped, because it requires at most Qt version" << toPrintableVersion(maxVer) << ", but got" << QT_VERSION_STR; + return false; + } + + minVer = metaObject.value("minAppVersion").toInt(0); + if (SQLITESTUDIO->getVersion() < minVer) + { + qDebug() << "Plugin" << pluginName << "skipped, because it requires at least SQLiteStudio version" << toPrintableVersion(minVer) << ", but got" + << SQLITESTUDIO->getVersionString(); + return false; + } + + maxVer = metaObject.value("maxAppVersion").toInt(999999); + if (SQLITESTUDIO->getVersion() > maxVer) + { + qDebug() << "Plugin" << pluginName << "skipped, because it requires at most SQLiteStudio version" << toPrintableVersion(maxVer) << ", but got" + << SQLITESTUDIO->getVersionString(); + return false; + } + + return true; +} + +bool PluginManagerImpl::readDependencies(const QString& pluginName, PluginManagerImpl::PluginContainer* container, const QJsonValue& depsValue) +{ + if (depsValue.isUndefined()) + return true; + + QJsonArray depsArray; + if (depsValue.type() == QJsonValue::Array) + depsArray = depsValue.toArray(); + else + depsArray.append(depsValue); + + PluginDependency dep; + QJsonObject depObject; + for (const QJsonValue& value : depsArray) + { + if (value.type() == QJsonValue::Object) + { + depObject = value.toObject(); + if (!depObject.contains("name")) + { + qWarning() << "Invalid dependency entry in plugin" << pluginName << " - doesn't contain 'name' of the dependency."; + return false; + } + + dep.name = depObject.value("name").toString(); + dep.minVersion = depObject.value("minVersion").toInt(0); + dep.maxVersion = depObject.value("maxVersion").toInt(0); + } + else + { + dep.maxVersion = 0; + dep.minVersion = 0; + dep.name = value.toString(); + } + container->dependencies << dep; + } + return true; +} + +bool PluginManagerImpl::readConflicts(const QString& pluginName, PluginManagerImpl::PluginContainer* container, const QJsonValue& confValue) +{ + UNUSED(pluginName); + + if (confValue.isUndefined()) + return true; + + QJsonArray confArray; + if (confValue.type() == QJsonValue::Array) + confArray = confValue.toArray(); + else + confArray.append(confValue); + + for (const QJsonValue& value : confArray) + container->conflicts << value.toString(); + + return true; +} + +bool PluginManagerImpl::initPlugin(Plugin* plugin) +{ + QString pluginName = plugin->getName(); + PluginType* pluginType = nullptr; + foreach (PluginType* type, registeredPluginTypes) + { + if (type->test(plugin)) + { + pluginType = type; + break; + } + } + + if (!pluginType) + { + qWarning() << "Could not load built-in plugin" + pluginName + "because its type was not recognized."; + return false; + } + + PluginContainer* container = new PluginContainer; + container->type = pluginType; + container->loaded = true; + container->builtIn = true; + container->plugin = plugin; + pluginCategories[pluginType] << container; + pluginContainer[pluginName] = container; + if (!readMetaData(container)) + { + delete container; + return false; + } + + pluginLoaded(container); + return true; +} + +bool PluginManagerImpl::shouldAutoLoad(const QString& pluginName) +{ + QStringList loadedPlugins = CFG_CORE.General.LoadedPlugins.get().split(",", QString::SkipEmptyParts); + QStringList pair; + foreach (const QString& loadedPlugin, loadedPlugins) + { + pair = loadedPlugin.split("="); + if (pair.size() != 2) + { + qWarning() << "Invalid entry in config General.LoadedPlugins:" << loadedPlugin; + continue; + } + + if (pair[0] == pluginName) + return (bool)pair[1].toInt(); + } + + return true; +} + +QStringList PluginManagerImpl::getAllPluginNames(PluginType* type) const +{ + QStringList names; + if (!pluginCategories.contains(type)) + return names; + + foreach (PluginContainer* container, pluginCategories[type]) + names << container->name; + + return names; +} + +QStringList PluginManagerImpl::getAllPluginNames() const +{ + return pluginContainer.keys(); +} + +PluginType* PluginManagerImpl::getPluginType(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return nullptr; + + return pluginContainer[pluginName]->type; +} + +QString PluginManagerImpl::getAuthor(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return QString::null; + + return pluginContainer[pluginName]->author; +} + +QString PluginManagerImpl::getTitle(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return QString::null; + + return pluginContainer[pluginName]->title; +} + +QString PluginManagerImpl::getPrintableVersion(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return QString::null; + + return pluginContainer[pluginName]->printableVersion; +} + +int PluginManagerImpl::getVersion(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return 0; + + return pluginContainer[pluginName]->version; +} + +QString PluginManagerImpl::getDescription(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return QString::null; + + return pluginContainer[pluginName]->description; +} + +void PluginManagerImpl::unload(Plugin* plugin) +{ + if (!plugin) + return; + + unload(plugin->getName()); +} + +void PluginManagerImpl::unload(const QString& pluginName) +{ + if (!pluginContainer.contains(pluginName)) + { + qWarning() << "No such plugin in containers:" << pluginName << "while trying to unload plugin."; + return; + } + + // Checking preconditions + PluginContainer* container = pluginContainer[pluginName]; + if (container->builtIn) + return; + + if (!container->loaded) + return; + + // Unloading depdendent plugins + for (PluginContainer* otherContainer : pluginContainer.values()) + { + if (otherContainer == container) + continue; + + for (const PluginDependency& dep : otherContainer->dependencies) + { + if (dep.name == pluginName) + { + unload(otherContainer->name); + break; + } + } + } + + // Removing from fast-lookup collections + removePluginFromCollections(container->plugin); + + // Deinitializing and unloading plugin + emit aboutToUnload(container->plugin, container->type); + container->plugin->deinit(); + + QPluginLoader* loader = container->loader; + if (!loader->isLoaded()) + { + qWarning() << "QPluginLoader says the plugin is not loaded. Weird."; + emit unloaded(container->name, container->type); + return; + } + + loader->unload(); + + container->plugin = nullptr; + container->loaded = false; + + emit unloaded(container->name, container->type); + + qDebug() << pluginName << "unloaded:" << container->filePath; +} + +bool PluginManagerImpl::load(const QString& pluginName) +{ + QStringList alreadyAttempted; + bool res = load(pluginName, alreadyAttempted); + if (!res) + emit failedToLoad(pluginName); + + return res; +} + +bool PluginManagerImpl::load(const QString& pluginName, QStringList& alreadyAttempted, int minVersion, int maxVersion) +{ + if (alreadyAttempted.contains(pluginName)) + return false; + + // Checking initial conditions + if (!pluginContainer.contains(pluginName)) + { + qWarning() << "No such plugin in containers:" << pluginName << "while trying to load plugin."; + alreadyAttempted.append(pluginName); + return false; + } + + PluginContainer* container = pluginContainer[pluginName]; + + if (minVersion > 0 && container->version < minVersion) + { + qWarning() << "Requested plugin" << pluginName << "in version at least" << minVersion << "but have:" << container->version; + return false; + } + + if (maxVersion > 0 && container->version > maxVersion) + { + qWarning() << "Requested plugin" << pluginName << "in version at most" << maxVersion << "but have:" << container->version; + return false; + } + + if (container->builtIn) + return true; + + QPluginLoader* loader = container->loader; + if (loader->isLoaded()) + return true; + + // Checking for conflicting plugins + for (PluginContainer* otherContainer : pluginContainer.values()) + { + if (!otherContainer->loaded || otherContainer->name == pluginName) + continue; + + if (container->conflicts.contains(otherContainer->name) || otherContainer->conflicts.contains(pluginName)) + { + notifyWarn(tr("Cannot load plugin %1, because it's in conflict with plugin %2.").arg(pluginName, otherContainer->name)); + alreadyAttempted.append(pluginName); + return false; + } + } + + // Loading depended plugins + for (const PluginDependency& dep : container->dependencies) + { + if (!load(dep.name, alreadyAttempted, dep.minVersion, dep.maxVersion)) + { + notifyWarn(tr("Cannot load plugin %1, because its dependency was not loaded: %2.").arg(pluginName, dep.name)); + alreadyAttempted.append(pluginName); + return false; + } + } + + // Loading pluginName + if (!loader->load()) + { + notifyWarn(tr("Cannot load plugin %1. Error details: %2").arg(pluginName, loader->errorString())); + alreadyAttempted.append(pluginName); + return false; + } + + // Initializing loaded plugin + Plugin* plugin = dynamic_cast<Plugin*>(container->loader->instance()); + GenericPlugin* genericPlugin = dynamic_cast<GenericPlugin*>(plugin); + if (genericPlugin) + { + genericPlugin->loadMetaData(container->loader->metaData()); + } + + if (!plugin->init()) + { + loader->unload(); + notifyWarn(tr("Cannot load plugin %1 (error while initializing plugin).").arg(pluginName)); + alreadyAttempted.append(pluginName); + return false; + } + + pluginLoaded(container); + + return true; +} + +void PluginManagerImpl::pluginLoaded(PluginManagerImpl::PluginContainer* container) +{ + if (!container->builtIn) + { + container->plugin = dynamic_cast<Plugin*>(container->loader->instance()); + container->loaded = true; + } + addPluginToCollections(container->plugin); + + emit loaded(container->plugin, container->type); + if (!container->builtIn) + qDebug() << container->name << "loaded:" << container->filePath; +} + +void PluginManagerImpl::addPluginToCollections(Plugin* plugin) +{ + ScriptingPlugin* scriptingPlugin = dynamic_cast<ScriptingPlugin*>(plugin); + if (scriptingPlugin) + scriptingPlugins[scriptingPlugin->getLanguage()] = scriptingPlugin; +} + +void PluginManagerImpl::removePluginFromCollections(Plugin* plugin) +{ + ScriptingPlugin* scriptingPlugin = dynamic_cast<ScriptingPlugin*>(plugin); + if (scriptingPlugin && scriptingPlugins.contains(scriptingPlugin->getLanguage())) + scriptingPlugins.remove(plugin->getName()); +} + +bool PluginManagerImpl::readMetaData(PluginManagerImpl::PluginContainer* container) +{ + if (container->loader) + { + QHash<QString, QVariant> metaData = readMetaData(container->loader->metaData()); + container->name = metaData["name"].toString(); + container->version = metaData["version"].toInt(); + container->printableVersion = toPrintableVersion(metaData["version"].toInt()); + container->author = metaData["author"].toString(); + container->description = metaData["description"].toString(); + container->title = metaData["title"].toString(); + } + else if (container->plugin) + { + container->name = container->plugin->getName(); + container->version = container->plugin->getVersion(); + container->printableVersion = container->plugin->getPrintableVersion(); + container->author = container->plugin->getAuthor(); + container->description = container->plugin->getDescription(); + container->title = container->plugin->getTitle(); + } + else + { + qCritical() << "Could not read metadata for some plugin. It has no loader or plugin object defined."; + return false; + } + return true; +} + +bool PluginManagerImpl::isLoaded(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + { + qWarning() << "No such plugin in containers:" << pluginName << "while trying to get plugin 'loaded' status."; + return false; + } + + return pluginContainer[pluginName]->loaded; +} + +bool PluginManagerImpl::isBuiltIn(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + { + qWarning() << "No such plugin in containers:" << pluginName << "while trying to get plugin 'builtIn' status."; + return false; + } + + return pluginContainer[pluginName]->builtIn; +} + +Plugin* PluginManagerImpl::getLoadedPlugin(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return nullptr; + + if (!pluginContainer[pluginName]->loaded) + return nullptr; + + return pluginContainer[pluginName]->plugin; +} + +QList<Plugin*> PluginManagerImpl::getLoadedPlugins(PluginType* type) const +{ + QList<Plugin*> list; + if (!pluginCategories.contains(type)) + return list; + + foreach (PluginContainer* container, pluginCategories[type]) + { + if (container->loaded) + list << container->plugin; + } + + return list; +} + +ScriptingPlugin* PluginManagerImpl::getScriptingPlugin(const QString& languageName) const +{ + if (scriptingPlugins.contains(languageName)) + return scriptingPlugins[languageName]; + + return nullptr; +} + +QHash<QString, QVariant> PluginManagerImpl::readMetaData(const QJsonObject& metaData) +{ + QHash<QString, QVariant> results; + results["name"] = metaData.value("className").toString(); + + QJsonObject root = metaData.value("MetaData").toObject(); + results["type"] = root.value("type").toString(); + results["title"] = root.value("title").toString(); + results["description"] = root.value("description").toString(); + results["author"] = root.value("author").toString(); + results["version"] = root.value("version").toInt(); + results["ui"] = root.value("ui").toString(); + return results; +} + +QString PluginManagerImpl::toPrintableVersion(int version) const +{ + static const QString versionStr = QStringLiteral("%1.%2.%3"); + return versionStr.arg(version / 10000) + .arg(version / 100 % 100) + .arg(version % 100); +} + +QStringList PluginManagerImpl::getDependencies(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return QStringList(); + + static const QString verTpl = QStringLiteral(" (%1)"); + QString minVerTpl = tr("min: %1", "plugin dependency version"); + QString maxVerTpl = tr("max: %1", "plugin dependency version"); + QStringList outputList; + QString depStr; + QStringList depVerList; + for (const PluginDependency& dep : pluginContainer[pluginName]->dependencies) + { + depStr = dep.name; + if (dep.minVersion > 0 || dep.maxVersion > 0) + { + depVerList.clear(); + if (dep.minVersion > 0) + depVerList << minVerTpl.arg(toPrintableVersion(dep.minVersion)); + + if (dep.maxVersion > 0) + depVerList << minVerTpl.arg(toPrintableVersion(dep.maxVersion)); + + depStr += verTpl.arg(depVerList.join(", ")); + } + outputList << depStr; + } + + return outputList; +} + +QStringList PluginManagerImpl::getConflicts(const QString& pluginName) const +{ + if (!pluginContainer.contains(pluginName)) + return QStringList(); + + return pluginContainer[pluginName]->conflicts; +} + +bool PluginManagerImpl::arePluginsInitiallyLoaded() const +{ + return pluginsAreInitiallyLoaded; +} + +QList<Plugin*> PluginManagerImpl::getLoadedPlugins() const +{ + QList<Plugin*> plugins; + foreach (PluginContainer* container, pluginContainer.values()) + { + if (container->loaded) + plugins << container->plugin; + } + return plugins; +} + +QStringList PluginManagerImpl::getLoadedPluginNames() const +{ + QStringList names; + foreach (PluginContainer* container, pluginContainer.values()) + { + if (container->loaded) + names << container->name; + } + return names; +} + +QList<PluginManager::PluginDetails> PluginManagerImpl::getAllPluginDetails() const +{ + QList<PluginManager::PluginDetails> results; + PluginManager::PluginDetails details; + foreach (PluginContainer* container, pluginContainer.values()) + { + details.name = container->name; + details.title = container->title; + details.description = container->description; + details.builtIn = container->builtIn; + details.version = container->version; + details.filePath = container->filePath; + details.versionString = formatVersion(container->version); + results << details; + } + return results; +} + +QList<PluginManager::PluginDetails> PluginManagerImpl::getLoadedPluginDetails() const +{ + QList<PluginManager::PluginDetails> results = getAllPluginDetails(); + QMutableListIterator<PluginManager::PluginDetails> it(results); + while (it.hasNext()) + { + if (!isLoaded(it.next().name)) + it.remove(); + } + return results; +} + +void PluginManagerImpl::registerPluginType(PluginType* type) +{ + registeredPluginTypes << type; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.h b/SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.h new file mode 100644 index 0000000..6968cab --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.h @@ -0,0 +1,321 @@ +#ifndef PLUGINMANAGERIMPL_H +#define PLUGINMANAGERIMPL_H + +#include "services/pluginmanager.h" +#include <QPluginLoader> +#include <QHash> + +class API_EXPORT PluginManagerImpl : public PluginManager +{ + Q_OBJECT + + public: + /** + * @brief Creates plugin manager. + */ + PluginManagerImpl(); + + /** + * @brief Deletes plugin manager. + */ + ~PluginManagerImpl(); + + void init(); + void deinit(); + QList<PluginType*> getPluginTypes() const; + QStringList getPluginDirs() const; + QString getFilePath(Plugin* plugin) const; + bool loadBuiltInPlugin(Plugin* plugin); + bool load(const QString& pluginName); + void unload(const QString& pluginName); + void unload(Plugin* plugin); + bool isLoaded(const QString& pluginName) const; + bool isBuiltIn(const QString& pluginName) const; + Plugin* getLoadedPlugin(const QString& pluginName) const; + QStringList getAllPluginNames(PluginType* type) const; + QStringList getAllPluginNames() const; + PluginType* getPluginType(const QString& pluginName) const; + QString getAuthor(const QString& pluginName) const; + QString getTitle(const QString& pluginName) const; + QString getPrintableVersion(const QString& pluginName) const; + int getVersion(const QString& pluginName) const; + QString getDescription(const QString& pluginName) const; + PluginType* getPluginType(Plugin* plugin) const; + QList<Plugin*> getLoadedPlugins(PluginType* type) const; + ScriptingPlugin* getScriptingPlugin(const QString& languageName) const; + QHash<QString,QVariant> readMetaData(const QJsonObject& metaData); + QString toPrintableVersion(int version) const; + QStringList getDependencies(const QString& pluginName) const; + QStringList getConflicts(const QString& pluginName) const; + bool arePluginsInitiallyLoaded() const; + QList<Plugin*> getLoadedPlugins() const; + QStringList getLoadedPluginNames() const; + QList<PluginDetails> getAllPluginDetails() const; + QList<PluginDetails> getLoadedPluginDetails() const; + + protected: + void registerPluginType(PluginType* type); + + private: + struct PluginDependency + { + QString name; + int minVersion = 0; + int maxVersion = 0; + }; + + /** + * @brief Container for plugin related data. + * + * The container is used to represent plugin available to the application, + * no matter if it's loaded or not. It keeps all plugin related data, + * so it's available even the plugin is not loaded. + */ + struct PluginContainer + { + /** + * @brief Name of the plugin. + */ + QString name; + + /** + * @brief Title of the plugin, used on UI. + */ + QString title; + + /** + * @brief Plugin's detailed description. + */ + QString description; + + /** + * @brief Plugin's author. + */ + QString author; + + /** + * @brief Numeric verion of the plugin. + */ + int version; + + /** + * @brief Human-readable version. + */ + QString printableVersion; + + /** + * @brief Type of the plugin. + */ + PluginType* type = nullptr; + + /** + * @brief Full path to the plugin's file. + */ + QString filePath; + + /** + * @brief Plugin's loaded state flag. + */ + bool loaded; + + /** + * @brief Qt's plugin framework loaded for this plugin. + */ + QPluginLoader* loader = nullptr; + + /** + * @brief Plugin object. + * + * It's null when plugin is not loaded. + */ + Plugin* plugin = nullptr; + + /** + * @brief Flag indicating that the plugin is built in. + * + * Plugins built-in are classes implementing plugin's interface, + * but they are compiled and statically linked to the main application binary. + * They cannot be loaded or unloaded - they are loaded by default. + */ + bool builtIn = false; + + /** + * @brief Names of plugnis that this plugin depends on. + */ + QList<PluginDependency> dependencies; + + /** + * @brief Names of plugins that this plugin conflicts with. + */ + QStringList conflicts; + }; + + /** + * @brief List of plugins, both loaded and unloaded. + */ + typedef QList<PluginContainer*> PluginContainerList; + + /** + * @brief Scans plugin directories to find out available plugins. + * + * It looks in the following locations: + * <ul> + * <li> application_directory/plugins/ + * <li> application_config_directory/plugins/ + * <li> directory pointed by the SQLITESTUDIO_PLUGINS environment variable + * <li> directory compiled in as PLUGINS_DIR parameter of the compilation + * </ul> + * + * The application_directory is a directory where the application executable is. + * The application_config_directory can be different, see ConfigImpl::initDbFile() for details. + * The SQLITESTUDIO_PLUGINS variable can contain several paths, separated by : (for Unix/Mac) or ; (for Windows). + */ + void scanPlugins(); + + /** + * @brief Loads plugins defined in configuration. + * + * It loads all plugins that are available to the application + * and are not marked to not load in the configuration. + * + * In other words, every plugin will load by default, unless it was + * explicitly unloaded previously and that was saved in the configuration + * (when application was closing). + */ + void loadPlugins(); + + /** + * @brief Loads given plugin. + * @param pluginName Name of the plugin to load. + * @param alreadyAttempted List of plugin names that were already attempted to be loaded. + * @param minVersion Minimum required version of the plugin to load. + * @param maxVersion Maximum required version of the plugin to load. + * @return true on success, false on failure. + * + * This is pretty much what the public load() method does, except this one tracks what plugins were already + * attempted to be loaded (and failed), so it doesn't warn twice about the same plugin if it failed + * to load while it was a dependency for some other plugins. + * + * It also allows to define minimum and maximum plugin version, so if SQLiteStudio has the plugin available, + * but the version is out of required range, it will also fail to load. + */ + bool load(const QString& pluginName, QStringList& alreadyAttempted, int minVersion = 0, int maxVersion = 0); + + /** + * @brief Executes standard routines after plugin was loaded. + * @param container Container for the loaded plugin. + * + * It fills all members of the plugin container and emits loaded() signal. + */ + void pluginLoaded(PluginContainer* container); + + /** + * @brief Stores some specific plugin types in internal collections for faster access. + * @param plugin Plugin that was just loaded. + * + * This is called after we are sure we have a Plugin instance. + * + * The method stores certain plugin types in internal collections, so they can be accessed + * faster, instead of calling getLoadedPlugin<T>(), which is not as fast. + * + * The internal collections are used for plugins that are likely to be accessed frequently, + * like ScriptingPlugin. + */ + void addPluginToCollections(Plugin* plugin); + + /** + * @brief Removes plugin from internal collections. + * @param plugin Plugin that is about to be unloaded. + * + * This is the reverse operation to what addPluginToCollections(Plugin*) does. + */ + void removePluginFromCollections(Plugin* plugin); + + /** + * @brief Reads title, description, author, etc. from the plugin. + * @param plugin Plugin to read data from. + * @param container Container to put the data to. + * @return true on success, false on problems (with details in logs) + * + * It does the reading by calling all related methods from Plugin interface, + * then stores those information in given \p container. + * + * The built-in plugins define those methods using their class metadata. + * + * External plugins provide this information in their file metadata + * and this method uses QPluginLoader to read this metadata. + */ + bool readMetaData(PluginContainer* container); + + /** + * @brief Creates plugin container and initializes it. + * @param loader Qt's plugin framework loader used to load this plugin. + * For built-in plugins (statically linked) this must be null. + * @param fileName Plugin's file path. For built-in plugins it's ignored. + * @param plugin Plugin object from loaded plugin. + * @return true if the initialization succeeded, or false otherwise. + * + * It assigns plugin type to the plugin, creates plugin container and fills + * all necessary data for the plugin. If the plugin was configured to not load, + * then this method unloads the file, before plugin was initialized (with Plugin::init()). + * + * All plugins are loaded at the start, but before they are fully initialized + * and enabled, they are simply queried for metadata, then either unloaded + * (when configured to not load at startup), or the initialization proceeds. + */ + bool initPlugin(QPluginLoader* loader, const QString& fileName); + + bool checkPluginRequirements(const QString& pluginName, const QJsonObject& metaObject); + bool readDependencies(const QString& pluginName, PluginContainer* container, const QJsonValue& depsValue); + bool readConflicts(const QString& pluginName, PluginContainer* container, const QJsonValue& confValue); + + /** + * @brief Creates plugin container and initializes it. + * @param plugin Built-in plugin object. + * @return true if the initialization succeeded, or false otherwise. + * + * This is pretty much the same as the other initPlugin() method, but this one is for built-in plugins. + */ + bool initPlugin(Plugin* plugin); + + /** + * @brief Tests if given plugin is configured to be loaded at startup. + * @param plugin Tested plugin object. + * @return true if plugin should be loaded at startup, or false otherwise. + * + * This method checks General.LoadedPlugins configuration entry to see if plugin + * was explicitly disabled for loading at startup. + */ + bool shouldAutoLoad(const QString& pluginName); + + /** + * @brief List of plugin directories (not necessarily absolute paths). + */ + QStringList pluginDirs; + + /** + * @brief List of registered plugin types. + */ + QList<PluginType*> registeredPluginTypes; + + /** + * @brief Table with plugin types as keys and list of plugins assigned for each type. + */ + QHash<PluginType*,PluginContainerList> pluginCategories; + + /** + * @brief Table with plugin names and containers assigned for each plugin. + */ + QHash<QString,PluginContainer*> pluginContainer; + + /** + * @brief Internal list of scripting plugins, updated on load/unload of plugins. + * + * Keys are scripting language name. It's a separate table to optimize querying scripting plugins. + */ + QHash<QString,ScriptingPlugin*> scriptingPlugins; + + bool pluginsAreInitiallyLoaded = false; +}; + +#endif // PLUGINMANAGERIMPL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/importmanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/importmanager.cpp new file mode 100644 index 0000000..53803e5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/importmanager.cpp @@ -0,0 +1,104 @@ +#include "importmanager.h" +#include "services/pluginmanager.h" +#include "services/notifymanager.h" +#include "plugins/importplugin.h" +#include "importworker.h" +#include "db/db.h" +#include "common/unused.h" +#include <QThreadPool> +#include <QDebug> + +ImportManager::ImportManager() +{ +} + +QStringList ImportManager::getImportDataSourceTypes() const +{ + QStringList types; + for (ImportPlugin* plugin : PLUGINS->getLoadedPlugins<ImportPlugin>()) + types << plugin->getDataSourceTypeName(); + + return types; +} + +ImportPlugin* ImportManager::getPluginForDataSourceType(const QString& dataSourceType) const +{ + for (ImportPlugin* plugin : PLUGINS->getLoadedPlugins<ImportPlugin>()) + { + if (plugin->getDataSourceTypeName() == dataSourceType) + return plugin; + } + + return nullptr; +} + +void ImportManager::configure(const QString& dataSourceType, const ImportManager::StandardImportConfig& config) +{ + plugin = getPluginForDataSourceType(dataSourceType); + importConfig = config; +} + +void ImportManager::importToTable(Db* db, const QString& table) +{ + this->db = db; + this->table = table; + + if (importInProgress) + { + emit importFailed(); + qCritical() << "Tried to import while other import was in progress."; + return; + } + + if (!db->isOpen()) + { + emit importFailed(); + qCritical() << "Tried to import into closed database."; + return; + } + + if (!plugin) + { + emit importFailed(); + qCritical() << "Tried to import, while ImportPlugin was null."; + return; + } + + importInProgress = true; + + ImportWorker* worker = new ImportWorker(plugin, &importConfig, db, table); + connect(worker, SIGNAL(finished(bool)), this, SLOT(finalizeImport(bool))); + connect(worker, SIGNAL(createdTable(Db*,QString)), this, SLOT(handleTableCreated(Db*,QString))); + connect(this, SIGNAL(orderWorkerToInterrupt()), worker, SLOT(interrupt())); + + QThreadPool::globalInstance()->start(worker); +} + +void ImportManager::interrupt() +{ + emit orderWorkerToInterrupt(); +} + +bool ImportManager::isAnyPluginAvailable() +{ + return PLUGINS->getLoadedPlugins<ImportPlugin>().size() > 0; +} + +void ImportManager::finalizeImport(bool result) +{ + importInProgress = false; + emit importFinished(); + if (result) + { + notifyInfo(tr("Imported data to the table '%1' successfully.").arg(table)); + emit importSuccessful(); + } + else + emit importFailed(); +} + +void ImportManager::handleTableCreated(Db* db, const QString& table) +{ + UNUSED(table); + emit schemaModified(db); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/importmanager.h b/SQLiteStudio3/coreSQLiteStudio/services/importmanager.h new file mode 100644 index 0000000..6f13826 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/importmanager.h @@ -0,0 +1,85 @@ +#ifndef IMPORTMANAGER_H +#define IMPORTMANAGER_H + +#include "pluginservicebase.h" +#include "coreSQLiteStudio_global.h" +#include <QFlags> +#include <QStringList> + +class ImportPlugin; +class Db; +class CfgEntry; + +class API_EXPORT ImportManager : public PluginServiceBase +{ + Q_OBJECT + + public: + struct StandardImportConfig + { + /** + * @brief Text encoding. + * + * Always one of QTextCodec::availableCodecs(). + * Codec is important for text-based data. For binary data it should irrelevant to the import plugin. + */ + QString codec; + + /** + * @brief Name of the file that the import is being done from. + * + * This is provided just for information to the import process, + * but the plugin should use data stream provided to each called import method, + * instead of opening the file from this name. + * + * It will be null string if importing is not performed from a file, but from somewhere else + * (for example from a clipboard). + */ + QString inputFileName; + }; + + enum StandardConfigFlag + { + CODEC = 0x01, /**< Text encoding (see StandardImportConfig::codec). */ + FILE_NAME = 0x02, /**< Input file (see StandardImportConfig::inputFileName). */ + }; + + Q_DECLARE_FLAGS(StandardConfigFlags, StandardConfigFlag) + + ImportManager(); + + QStringList getImportDataSourceTypes() const; + ImportPlugin* getPluginForDataSourceType(const QString& dataSourceType) const; + + void configure(const QString& dataSourceType, const StandardImportConfig& config); + void importToTable(Db* db, const QString& table); + + static bool isAnyPluginAvailable(); + + private: + StandardImportConfig importConfig; + ImportPlugin* plugin = nullptr; + bool importInProgress = false; + Db* db = nullptr; + QString table; + + public slots: + void interrupt(); + + private slots: + void finalizeImport(bool result); + void handleTableCreated(Db* db, const QString& table); + + signals: + void importFinished(); + void importSuccessful(); + void importFailed(); + void orderWorkerToInterrupt(); + void schemaModified(Db* db); +}; + +#define IMPORT_MANAGER SQLITESTUDIO->getImportManager() + +Q_DECLARE_OPERATORS_FOR_FLAGS(ImportManager::StandardConfigFlags) + +#endif // IMPORTMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/notifymanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/notifymanager.cpp new file mode 100644 index 0000000..0980399 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/notifymanager.cpp @@ -0,0 +1,85 @@ +#include "services/notifymanager.h"
+
+DEFINE_SINGLETON(NotifyManager)
+
+NotifyManager::NotifyManager(QObject *parent) :
+ QObject(parent)
+{
+}
+
+void NotifyManager::error(const QString &msg)
+{
+ addToRecentList(recentErrors, msg);
+ emit notifyError(msg);
+}
+
+void NotifyManager::warn(const QString &msg)
+{
+ addToRecentList(recentWarnings, msg);
+ emit notifyWarning(msg);
+}
+
+void NotifyManager::info(const QString &msg)
+{
+ addToRecentList(recentInfos, msg);
+ emit notifyInfo(msg);
+}
+
+void NotifyManager::modified(Db* db, const QString& database, const QString& object)
+{
+ emit objectModified(db, database, object);
+}
+
+void NotifyManager::deleted(Db* db, const QString& database, const QString& object)
+{
+ emit objectDeleted(db, database, object);
+}
+
+void NotifyManager::createded(Db* db, const QString& database, const QString& object)
+{
+ emit objectCreated(db, database, object);
+}
+
+void NotifyManager::renamed(Db* db, const QString& database, const QString& oldObject, const QString& newObject)
+{
+ emit objectRenamed(db, database, oldObject, newObject);
+}
+
+void NotifyManager::addToRecentList(QStringList& list, const QString &message)
+{
+ list << message;
+ if (list.size() <= maxRecentMessages)
+ return;
+
+ list = list.mid(list.length() - maxRecentMessages);
+}
+
+QList<QString> NotifyManager::getRecentInfos() const
+{
+ return recentInfos;
+}
+
+QList<QString> NotifyManager::getRecentWarnings() const
+{
+ return recentWarnings;
+}
+
+QList<QString> NotifyManager::getRecentErrors() const
+{
+ return recentErrors;
+}
+
+void notifyError(const QString &msg)
+{
+ NotifyManager::getInstance()->error(msg);
+}
+
+void notifyWarn(const QString &msg)
+{
+ NotifyManager::getInstance()->warn(msg);
+}
+
+void notifyInfo(const QString &msg)
+{
+ NotifyManager::getInstance()->info(msg);
+}
diff --git a/SQLiteStudio3/coreSQLiteStudio/services/notifymanager.h b/SQLiteStudio3/coreSQLiteStudio/services/notifymanager.h new file mode 100644 index 0000000..5bb4571 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/notifymanager.h @@ -0,0 +1,58 @@ +#ifndef NOTIFYMANAGER_H
+#define NOTIFYMANAGER_H
+
+#include "db/db.h"
+#include "common/global.h"
+#include <QStringList>
+#include <QObject>
+
+class API_EXPORT NotifyManager : public QObject
+{
+ Q_OBJECT
+
+ DECLARE_SINGLETON(NotifyManager)
+
+ public:
+ explicit NotifyManager(QObject *parent = 0);
+
+ QList<QString> getRecentErrors() const;
+ QList<QString> getRecentWarnings() const;
+ QList<QString> getRecentInfos() const;
+
+ signals:
+ void notifyError(const QString& msg);
+ void notifyWarning(const QString& msg);
+ void notifyInfo(const QString& msg);
+
+ void objectModified(Db* db, const QString& database, const QString& object);
+ void objectDeleted(Db* db, const QString& database, const QString& object);
+ void objectCreated(Db* db, const QString& database, const QString& object);
+ void objectRenamed(Db* db, const QString& database, const QString& oldObject, const QString& newObject);
+
+ public slots:
+ void error(const QString& msg);
+ void warn(const QString& msg);
+ void info(const QString& msg);
+
+ void modified(Db* db, const QString& database, const QString& object);
+ void deleted(Db* db, const QString& database, const QString& object);
+ void createded(Db* db, const QString& database, const QString& object);
+ void renamed(Db* db, const QString& database, const QString& oldObject, const QString& newObject);
+
+ private:
+ void addToRecentList(QStringList& list, const QString& message);
+
+ static const constexpr int maxRecentMessages = 10;
+
+ QStringList recentErrors;
+ QStringList recentWarnings;
+ QStringList recentInfos;
+};
+
+#define NOTIFY_MANAGER NotifyManager::getInstance()
+
+void API_EXPORT notifyError(const QString& msg);
+void API_EXPORT notifyWarn(const QString& msg);
+void API_EXPORT notifyInfo(const QString& msg);
+
+#endif // NOTIFYMANAGER_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/services/pluginmanager.h b/SQLiteStudio3/coreSQLiteStudio/services/pluginmanager.h new file mode 100644 index 0000000..4f822bc --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/pluginmanager.h @@ -0,0 +1,528 @@ +#ifndef PLUGINMANAGER_H +#define PLUGINMANAGER_H + +#include "coreSQLiteStudio_global.h" +#include "plugins/plugin.h" +#include "plugins/plugintype.h" +#include "common/global.h" +#include "sqlitestudio.h" +#include <QStringList> + +class Plugin; +class ScriptingPlugin; + +/** @file */ + +/** + * @brief The plugin manager. + * + * It's a singleton accessible with PLUGINS macro. + * + * It provides methods to load, unload and query plugins. It stores loaded + * plugins in configuration on application close and loads that plugins during next startup. + * If there's a plugin which was not defined if it was loaded or not - it is loaded by default. + * + * Description of Plugin interface contains list of directories scanned for plugins. + * + * There's a macro for global access to the PluginManager - ::PLUGINS. It actually calls + * SQLiteStudio::getInstance() and from there it calls SQLiteStudio::getPluginManager(). + * + * Plugins in PluginManager are organized by types. The manager has a list of types and for each type + * there's a list of plugins of that type. Plugin types are represented by PluginType class. + * + * @section querying_plugins Querying available and loaded plugins + * + * To query all plugins available to the application (including those not loaded) use getAllPluginNames(). + * + * To query if certain plugin is loaded use isLoaded(). + * + * To query all plugins loaded to the application use getLoadedPlugins(). It requires either PluginType, + * or plugin interface class (for template method version) to determinate what group of plugins you're + * interested in. To return all plugins (no matter what type), use template method version with Plugin + * as an interface type for parameter. An example of getting all SQL formatter plugins: + * @code + * QList<SqlFormatterPlugin*> formatterPlugins = PLUGINS->getLoadedPlugins<SqlFormatterPlugin>(); + * @endcode + * + * To get list of plugin types use getPluginTypes(). + * + * To get PluginType for given plugin interface use getPluginType<PluginInterfaceClass>(). + * + * These are just the most important methods to query plugins. See full list of methods for more. + * + * @section load_unload Loading and unloading plugins + * + * To load plugin use load(). + * + * To unload plugin use unload(). + * + * Apart from that, all plugins are loaded initially (unless they were unloaded last time during + * application close). + * + * @section plugin_types Specialized plugin types + * + * Each plugin must implement Plugin interface, but it also can implement other interfaces, + * which makes them suitable for fulfilling certain functionalities. For example all plugins + * implementing SqlFormatterPlugin will automatically be available to SqlFormatter object, + * because PluginManager knows which plugins implement SqlFormatterPlugin and can provide full + * list of those plugins to SqlFormatter. This is done by call to registerPluginType(). + * + * The registerPluginType() registers new type of plugins that will be recognizable by PluginManager. + * Once the new interface is registered with this method, all plugins will be tested against + * implementation for that type and those which implement the interface will be stored + * in the proper collection assigned for that plugin type. + * + * This way PluginManager can provide list of all plugins implementing given interface + * with getLoadedPlugins(). + * + * All registered plugin types can be queries by getPluginTypes() method. + */ +class API_EXPORT PluginManager : public QObject +{ + Q_OBJECT + + public: + struct PluginDetails + { + QString name; + QString title; + QString description; + bool builtIn = false; + int version = 0; + QString versionString; + QString filePath; + }; + + /** + * @brief Loads all plugins. + * + * Scans all plugin directories and tries to load all plugins found there. For list of directories + * see description of Plugin class. + */ + virtual void init() = 0; + + /** + * @brief Unloads all loaded plugins. + * + * Also deregisters all plugin types. + */ + virtual void deinit() = 0; + + /** + * @brief Provides list of registered plugin types. + * @return List of registered plugin types. + */ + virtual QList<PluginType*> getPluginTypes() const = 0; + + /** + * @brief Provides list of plugin directories. + * @return List of directory paths (not necessarily absolute paths). + */ + virtual QStringList getPluginDirs() const = 0; + + /** + * @brief Provides absolute path to the plugin's file. + * @param plugin Loaded plugin. + * @return Absolute path to the plugin file. + */ + virtual QString getFilePath(Plugin* plugin) const = 0; + + /** + * @brief Loads instance of built-in plugin into the manager. + * @param plugin Plugin instance. + * @return true on success or false on failure (plugin's type could not be matched to registered plugin types). + * + * Built-in plugins are classes that implement plugin interface, but they are not in separate library. + * Instead they are classes compiled and linked to the main application. Such classes should be instantiated + * and passed to this method, so the PluginManager can treat it as any other plugin. + * + * @note Built-in plugins cannot be loaded or unloaded, so calls to load() or unload() will make no effect. + */ + virtual bool loadBuiltInPlugin(Plugin* plugin) = 0; + + /** + * @brief Loads the plugin. + * @param pluginName Name of the plugin to load. + * @return true on success, or false on failure. + * + * When loading a plugin, PluginManager loads the plugin file and resolves all its symbols inside. + * If that failed, file gets unloaded and the method returns false. + * + * Qt plugins framework will require that the loaded plugin will provide exactly one Plugin interface + * implementation. Otherwise file will be unloaded and this method will return false. + * + * Then the Plugin::init() method is called. It it returns false, then plugin is unloaded + * and this method returns false. + * + * Then meta information is read from the plugin (title, version, author, etc) - see Plugin for details. + * + * Then loaded plugin passes several tests against all registered plugin types. If it implements + * any type, it's added to the plugin list of that type. + * + * Then the loaded() signal is emitted. Finally, the true value is returned. + */ + virtual bool load(const QString& pluginName) = 0; + + /** + * @brief Unloads plugin. + * @param pluginName Plugin name to be unloaded. + * + * If the plugin is not loaded, this method does nothing. + * First the aboutToUnload() signal is emitted. Then Plugin::deinit() is called. + * Then the plugin library is unloaded (which causes Qt's plugins framework to delete the object + * implementing Plugin interface before the actual unloading). + * + * Finally, the unloaded() signal is emitted. + */ + virtual void unload(const QString& pluginName) = 0; + + /** + * @brief Unloads plugin. + * @param plugin Loaded plugin to be unloaded. + * @overload + */ + virtual void unload(Plugin* plugin) = 0; + + /** + * @brief Tests if given plugin is loaded. + * @param pluginName Name of the plugin to test. + * @return true if the plugin is loaded, or false otherwise. + */ + virtual bool isLoaded(const QString& pluginName) const = 0; + + /** + * @brief Tests whether given plugin is one of built-in plugins. + * @param pluginName Name of the plugin to test. + * @return true if the plugin is the built-in one, or false otherwise. + * + * @see loadBuiltInPlugin() + */ + virtual bool isBuiltIn(const QString& pluginName) const = 0; + + /** + * @brief Finds loaded plugin by name. + * @param pluginName Plugin name to look for. + * @return Loaded plugin object, or null of the plugin is not loaded. + */ + virtual Plugin* getLoadedPlugin(const QString& pluginName) const = 0; + + /** + * @brief Provides list of plugin names of given type. + * @param type Type of plugins to get names for. + * @return List of names. + * + * It returns names for all plugins available for the application, + * no matter they're currently loaded or not. + */ + virtual QStringList getAllPluginNames(PluginType* type) const = 0; + + virtual QList<PluginDetails> getAllPluginDetails() const = 0; + virtual QList<PluginDetails> getLoadedPluginDetails() const = 0; + + /** + * @brief Provides list of all plugin names. + * @return All available plugin names, no matter if loaded or not. + */ + virtual QStringList getAllPluginNames() const = 0; + + /** + * @brief Finds plugin's type. + * @param pluginName Plugin name (can be unloaded plugin). + * @return Type of the plugin, or null if plugin was not found by the name. + */ + virtual PluginType* getPluginType(const QString& pluginName) const = 0; + + /** + * @brief Provides plugin's author. + * @param pluginName Name of the plugin (can be unloaded plugin). + * @return Author string defined in the plugin. + */ + virtual QString getAuthor(const QString& pluginName) const = 0; + + /** + * @brief Provides plugin's title. + * @param pluginName Name of the plugin (can be unloaded plugin). + * @return Title string defined in the plugin. + */ + virtual QString getTitle(const QString& pluginName) const = 0; + + /** + * @brief Provides human-readable version of the plugin. + * @param pluginName Name of the plugin (can be unloaded plugin). + * @return Version string defined in the plugin. + */ + virtual QString getPrintableVersion(const QString& pluginName) const = 0; + + /** + * @brief Provides numeric version of the plugin. + * @param pluginName Name of the plugin (can be unloaded plugin). + * @return Numeric version defined in the plugin. + */ + virtual int getVersion(const QString& pluginName) const = 0; + + /** + * @brief Provides detailed description about the plugin. + * @param pluginName Name of the plugin (can be unloaded plugin). + * @return Description defined in the plugin. + */ + virtual QString getDescription(const QString& pluginName) const = 0; + + /** + * @brief Tells plugin's type. + * @param plugin Loaded plugin. + * @return Type of the plugin. + */ + virtual PluginType* getPluginType(Plugin* plugin) const = 0; + + /** + * @brief Provides list of plugins for given plugin type. + * @param type Type of plugins. + * @return List of plugins for given type. + * + * This version of the method takes plugin type object as an discriminator. + * This way you can iterate through all types (using getPluginTypes()) + * and then for each type get list of plugins for that type, using this method. + */ + virtual QList<Plugin*> getLoadedPlugins(PluginType* type) const = 0; + + /** + * @brief Provides list of all loaded plugins. + * @return List of plugins. + */ + virtual QList<Plugin*> getLoadedPlugins() const = 0; + + /** + * @brief Provides names of all loaded plugins. + * @return List of plugin names. + */ + virtual QStringList getLoadedPluginNames() const = 0; + + /** + * @brief Provides scripting plugin for given scripting language if available. + * @param languageName Scripting language name to get plugin for. + * @return Plugin object or null if proper plugin was not found. + * + * Calling this function is similar in results to call to getLoadedPlugins<ScriptingPlugin>() + * and then extracting a single plugin with desired scripting language support, except + * calling this function is much faster. PluginManager keeps scripting language plugins + * internally in hash table with language names as keys, so getting scripting plugin + * for desired language is way faster when using this method. + */ + virtual ScriptingPlugin* getScriptingPlugin(const QString& languageName) const = 0; + + /** + * @brief Loads metadata from given Json object. + * @param The metadata from json file. + * @return Metadata with keys: type, name, title, description, version, author, ui (optional). + */ + virtual QHash<QString,QVariant> readMetaData(const QJsonObject& metaData) = 0; + + /** + * @brief Converts integer version to string version. + * @param version Integer version in XXYYZZ standard (see Plugin::getVersion() for details). + * @return Printable version string. + */ + virtual QString toPrintableVersion(int version) const = 0; + + /** + * @brief Provides list of plugin names that the queried plugin depends on. + * @param pluginName Queried plugin name. + * @return List of plugin names, usually an empty list. + * + * This is the list that is declared in plugins metadata under the "dependencies" key. + * The plugin can be loaded only if all its dependencies were successfully loaded. + */ + virtual QStringList getDependencies(const QString& pluginName) const = 0; + + /** + * @brief Provides list of plugin names that are declared to be in conflict with queries plugin. + * @param pluginName Queried plugin name, + * @return List of plugin names, usually an empty list. + * + * If a plugin declares other plugin (by name) to be its conflict (a "conflicts" key in plugin's metadata), + * then those 2 plugins cannot be loaded at the same time. SQLiteStudio will always refuse to load + * the other one, if the first one is already loaded - and vice versa. + * + * Declaring conflicts for a plugin can be useful for example if somebody wants to proivde an alternative + * implementation of SQLite2 database plugin, etc. In that case SQLiteStudio won't get confused in + * deciding which plugin to use for supporting such databases. + */ + virtual QStringList getConflicts(const QString& pluginName) const = 0; + + /** + * @brief Tells if plugins were already loaded on startup, or is this yet to happen. + * @return true if plugins were loaded, false if they are going to be loaded. + */ + virtual bool arePluginsInitiallyLoaded() const = 0; + + /** + * @brief registerPluginType Registers plugin type for loading and managing. + * @tparam T Interface class (as defined by Qt plugins standard) + * @param form Optional name of form object. + * @param title Optional title for configuration dialog. + * The form object name is different if you register new type by general type plugin. + * Built-in types are defined as the name of page from ConfigDialog. + * Types registered from plugins should use top widget name defined in the ui file. + * The title parameter is required if the configuration form was defined outside (in plugin). + * Title will be used for configuration dialog to display plugin type category (on the left of the dialog). + */ + template <class T> + void registerPluginType(const QString& title, const QString& form = QString()) + { + registerPluginType(new DefinedPluginType<T>(title, form)); + } + + /** + * @brief Gets plugin type for given plugin interface. + * @tparam T Interface class of the plugin. + * @return Type of the plugin for given interface if registered, or null otherwise. + */ + template <class T> + PluginType* getPluginType() const + { + foreach (PluginType* type, getPluginTypes()) + { + if (!dynamic_cast<DefinedPluginType<T>*>(type)) + continue; + + return type; + } + return nullptr; + } + + /** + * @brief Provide list of plugins of given type. + * @tparam T Interface class of plugins, that we want to get. + * + * This method version gets plugin interface type as template parameter, + * so it returns list of loaded plugins that are already casted to requested + * interface type. + */ + template <class T> + QList<T*> getLoadedPlugins() const + { + QList<T*> typedPlugins; + PluginType* type = getPluginType<T>(); + if (!type) + return typedPlugins; + + foreach (Plugin* plugin, getLoadedPlugins(type)) + typedPlugins << dynamic_cast<T*>(plugin); + + return typedPlugins; + } + + /** + * @brief Provide list of plugin names of given type. + * @tparam T Interface class of plugins, that we want to get names for. + * + * This method version gets plugin interface type as template parameter, + * so it returns list of names of loaded plugins. + */ + template <class T> + QStringList getLoadedPluginNames() const + { + QStringList names; + PluginType* type = getPluginType<T>(); + if (!type) + return names; + + foreach (Plugin* plugin, getLoadedPlugins(type)) + names << plugin->getName(); + + return names; + } + + protected: + /** + * @brief Adds given type to registered plugins list. + * @param type Type instance. + * + * This is a helper method for registerPluginType<T>() template function. + * The implementation should register given plugin type, that is - add it to a list of registered types. + */ + virtual void registerPluginType(PluginType* type) = 0; + + signals: + /** + * @brief Emitted just before plugin is unloaded. + * @param plugin Plugin object to be unloaded. + * @param type Type of the plugin. + * + * It's emitted just before call to Plugin::deinit(), destroying plugin object + * and unloading the plugin file. + * + * Any code using certain plugin should listen for this signal and stop using + * the plugin immediately when received this signal. Otherwise application may crash. + */ + void aboutToUnload(Plugin* plugin, PluginType* type); + + /** + * @brief Emitted just after plugin was loaded. + * @param plugin Plugin object from loaded plugin. + * @param type Plugin type. + * + * It's emitted after plugin was loaded and successfully initialized (which includes + * successful Plugin::init() call). + */ + void loaded(Plugin* plugin, PluginType* type); + + /** + * @brief Emitted after plugin was unloaded. + * @param pluginName Name of the plugin that was unloaded. + * @param type Type of the plugin. + * + * Emitted after plugin was deinitialized and unloaded. At this stage a plugin object + * is no longer available, only it's name and other metadata (like description, version, etc). + */ + void unloaded(const QString& pluginName, PluginType* type); + + /** + * @brief Emitted after initial plugin set was loaded. + * + * The initial load is performed at application startup. Any code that relies on + * some plugins being loaded (like for example code that loads list of databases relies on + * database support plugins) should listen to this signal. + */ + void pluginsInitiallyLoaded(); + + /** + * @brief Emitted when the plugin manager is deinitializing and will unload all plugins in a moment. + * + * It's emitted when user closes application, so the plugin manager deinitializes and unloads all plugins. + * This signal is emitted just before plugins get unloaded. + * If some signal handler is not interested in mass plugin unloading, then it can handle this signal + * and disconnect from unloaded() signal. + */ + void aboutToQuit(); + + /** + * @brief Emitted when plugin load was requested, but it failed. + * @param pluginName Name of the plugin that failed to load. + * + * It's used for example by ConfigDialog to uncheck plugin that was requested to load (checked) and it failed. + */ + void failedToLoad(const QString& pluginName); +}; + +/** + * @def PLUGINS + * @brief PluginsManager instance access macro. + * + * Since SQLiteStudio creates only one instance of PluginsManager, + * there is a standard method for accessing it, using code: + * @code + * QList<PluginType*> types = SQLiteStudio::getInstance()->getPluginManager()->getPluginTypes(); + * @endcode + * or there's a slightly simpler way: + * @code + * QList<PluginType*> types = SQLITESTUDIO->getPluginManager()->getPluginTypes(); + * @endcode + * or there is a very simplified method, using this macro: + * @code + * QList<PluginType*> types = PLUGINS->getPluginTypes(); + * @endcode + */ +#define PLUGINS SQLITESTUDIO->getPluginManager() + +#endif // PLUGINMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.cpp new file mode 100644 index 0000000..237667d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.cpp @@ -0,0 +1,93 @@ +#include "populatemanager.h" +#include "services/pluginmanager.h" +#include "plugins/populateplugin.h" +#include "services/notifymanager.h" +#include "populateworker.h" +#include "plugins/populatesequence.h" +#include "plugins/populaterandom.h" +#include "plugins/populaterandomtext.h" +#include "plugins/populateconstant.h" +#include "plugins/populatedictionary.h" +#include "plugins/populatescript.h" +#include <QDebug> +#include <QThreadPool> + +PopulateManager::PopulateManager(QObject *parent) : + PluginServiceBase(parent) +{ + PLUGINS->loadBuiltInPlugin(new PopulateSequence()); + PLUGINS->loadBuiltInPlugin(new PopulateRandom()); + PLUGINS->loadBuiltInPlugin(new PopulateRandomText()); + PLUGINS->loadBuiltInPlugin(new PopulateConstant()); + PLUGINS->loadBuiltInPlugin(new PopulateDictionary()); + PLUGINS->loadBuiltInPlugin(new PopulateScript()); +} + +void PopulateManager::populate(Db* db, const QString& table, const QHash<QString, PopulateEngine*>& engines, qint64 rows) +{ + if (workInProgress) + { + error(); + qCritical() << "Tried to call second populating process at the same time."; + return; + } + + if (!db->isOpen()) + { + error(); + qCritical() << "Tried to populate table in closed database."; + return; + } + + workInProgress = true; + + columns.clear(); + engineList.clear(); + for (const QString& column : engines.keys()) + { + columns << column; + engineList << engines[column]; + } + + + this->db = db; + this->table = table; + + PopulateWorker* worker = new PopulateWorker(db, table, columns, engineList, rows); + connect(worker, SIGNAL(finished(bool)), this, SLOT(finalizePopulating(bool))); + connect(this, SIGNAL(orderWorkerToInterrupt()), worker, SLOT(interrupt())); + + QThreadPool::globalInstance()->start(worker); + +} + +void PopulateManager::error() +{ + emit populatingFinished(); + emit populatingFailed(); +} + +void PopulateManager::deleteEngines(const QList<PopulateEngine*>& engines) +{ + for (PopulateEngine* engine : engines) + delete engine; +} + +void PopulateManager::interrupt() +{ + emit orderWorkerToInterrupt(); +} + +void PopulateManager::finalizePopulating(bool result) +{ + workInProgress = false; + + emit populatingFinished(); + if (result) + { + notifyInfo(tr("Table '%1' populated successfully.").arg(table)); + emit populatingSuccessful(); + } + else + emit populatingFailed(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.h b/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.h new file mode 100644 index 0000000..05b1f82 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/populatemanager.h @@ -0,0 +1,48 @@ +#ifndef POPULATEMANAGER_H +#define POPULATEMANAGER_H + +#include "pluginservicebase.h" +#include "sqlitestudio.h" +#include <QObject> +#include <QHash> +#include <QStringList> + +class PopulatePlugin; +class PopulateEngine; +class Db; + +class API_EXPORT PopulateManager : public PluginServiceBase +{ + Q_OBJECT + + public: + explicit PopulateManager(QObject *parent = 0); + + void populate(Db* db, const QString& table, const QHash<QString, PopulateEngine*>& engines, qint64 rows); + + private: + void error(); + void deleteEngines(const QList<PopulateEngine*>& engines); + + bool workInProgress = false; + Db* db = nullptr; + QString table; + QStringList columns; + QList<PopulateEngine*> engineList; + + public slots: + void interrupt(); + + private slots: + void finalizePopulating(bool result); + + signals: + void populatingFinished(); + void populatingSuccessful(); + void populatingFailed(); + void orderWorkerToInterrupt(); +}; + +#define POPULATE_MANAGER SQLITESTUDIO->getPopulateManager() + +#endif // POPULATEMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp new file mode 100644 index 0000000..dae8238 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp @@ -0,0 +1,1058 @@ +#include "updatemanager.h" +#include "services/pluginmanager.h" +#include "services/notifymanager.h" +#include "common/unused.h" +#include <QTemporaryDir> +#include <QNetworkAccessManager> +#include <QNetworkReply> +#include <QNetworkRequest> +#include <QUrl> +#include <QUrlQuery> +#include <QDebug> +#include <QCoreApplication> +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonArray> +#include <QJsonValue> +#include <QProcess> +#include <QThread> +#include <QtConcurrent/QtConcurrent> + +#ifdef Q_OS_WIN32 +#include "JlCompress.h" +#include <windows.h> +#include <shellapi.h> +#endif + +// Note on creating update packages: +// Packages for Linux and MacOSX should be an archive of _contents_ of SQLiteStudio directory, +// while for Windows it should be an archive of SQLiteStudio directory itself. + +QString UpdateManager::staticErrorMessage; +UpdateManager::RetryFunction UpdateManager::retryFunction = nullptr; + +UpdateManager::UpdateManager(QObject *parent) : + QObject(parent) +{ + networkManager = new QNetworkAccessManager(this); + connect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(finished(QNetworkReply*))); + connect(this, SIGNAL(updatingError(QString)), NOTIFY_MANAGER, SLOT(error(QString))); +} + +UpdateManager::~UpdateManager() +{ + cleanup(); +} + +void UpdateManager::checkForUpdates() +{ + getUpdatesMetadata(updatesCheckReply); +} + +void UpdateManager::update() +{ + if (updatesGetUrlsReply || updatesInProgress) + return; + + getUpdatesMetadata(updatesGetUrlsReply); +} + +QString UpdateManager::getPlatformForUpdate() const +{ +#if defined(Q_OS_LINUX) + if (QSysInfo::WordSize == 64) + return "linux64"; + else + return "linux32"; +#elif defined(Q_OS_WIN) + return "win32"; +#elif defined(Q_OS_OSX) + return "macosx"; +#else + return QString(); +#endif +} + +QString UpdateManager::getCurrentVersions() const +{ + QJsonArray versionsArray; + + QJsonObject arrayEntry; + arrayEntry["component"] = "SQLiteStudio"; + arrayEntry["version"] = SQLITESTUDIO->getVersionString(); + versionsArray.append(arrayEntry); + + for (const PluginManager::PluginDetails& details : PLUGINS->getAllPluginDetails()) + { + if (details.builtIn) + continue; + + arrayEntry["component"] = details.name; + arrayEntry["version"] = details.versionString; + versionsArray.append(arrayEntry); + } + + QJsonObject topObj; + topObj["versions"] = versionsArray; + + QJsonDocument doc(topObj); + return QString::fromLatin1(doc.toJson(QJsonDocument::Compact)); +} + +bool UpdateManager::isPlatformEligibleForUpdate() const +{ + return !getPlatformForUpdate().isNull() && getDistributionType() != DistributionType::OS_MANAGED; +} + +#if defined(Q_OS_WIN32) +bool UpdateManager::executePreFinalStepWin(const QString &tempDir, const QString &backupDir, const QString &appDir, bool reqAdmin) +{ + bool res; + if (reqAdmin) + res = executeFinalStepAsRootWin(tempDir, backupDir, appDir); + else + res = executeFinalStep(tempDir, backupDir, appDir); + + if (res) + { + QFileInfo path(qApp->applicationFilePath()); + QProcess::startDetached(appDir + "/" + path.fileName(), {WIN_POST_FINAL_UPDATE_OPTION_NAME, tempDir}); + } + return res; +} +#endif + +void UpdateManager::handleAvailableUpdatesReply(QNetworkReply* reply) +{ + if (reply->error() != QNetworkReply::NoError) + { + updatingFailed(tr("An error occurred while checking for updates: %1.").arg(reply->errorString())); + reply->deleteLater(); + return; + } + + QJsonParseError err; + QByteArray data = reply->readAll(); + reply->deleteLater(); + + QJsonDocument doc = QJsonDocument::fromJson(data, &err); + if (err.error != QJsonParseError::NoError) + { + qWarning() << "Invalid response from update service:" << err.errorString() << "\n" << "The data was:" << QString::fromLatin1(data); + notifyWarn(tr("Could not check available updates, because server responded with invalid message format. It is safe to ignore this warning.")); + return; + } + + QList<UpdateEntry> updates = readMetadata(doc); + if (updates.size() > 0) + emit updatesAvailable(updates); + else + emit noUpdatesAvailable(); +} + +void UpdateManager::getUpdatesMetadata(QNetworkReply*& replyStoragePointer) +{ +#ifndef NO_AUTO_UPDATES + if (!isPlatformEligibleForUpdate() || replyStoragePointer) + return; + + QUrlQuery query; + query.addQueryItem("platform", getPlatformForUpdate()); + query.addQueryItem("data", getCurrentVersions()); + + QUrl url(QString::fromLatin1(updateServiceUrl) + "?" + query.query(QUrl::FullyEncoded)); + QNetworkRequest request(url); + replyStoragePointer = networkManager->get(request); +#endif +} + +void UpdateManager::handleUpdatesMetadata(QNetworkReply* reply) +{ + if (reply->error() != QNetworkReply::NoError) + { + updatingFailed(tr("An error occurred while reading updates metadata: %1.").arg(reply->errorString())); + reply->deleteLater(); + return; + } + + QJsonParseError err; + QByteArray data = reply->readAll(); + reply->deleteLater(); + + QJsonDocument doc = QJsonDocument::fromJson(data, &err); + if (err.error != QJsonParseError::NoError) + { + qWarning() << "Invalid response from update service for getting metadata:" << err.errorString() << "\n" << "The data was:" << QString::fromLatin1(data); + notifyWarn(tr("Could not download updates, because server responded with invalid message format. " + "You can try again later or download and install updates manually. See <a href=\"%1\">User Manual</a> for details.").arg(manualUpdatesHelpUrl)); + return; + } + + tempDir = new QTemporaryDir(); + if (!tempDir->isValid()) { + notifyWarn(tr("Could not create temporary directory for downloading the update. Updating aborted.")); + return; + } + + updatesInProgress = true; + updatesToDownload = readMetadata(doc); + totalDownloadsCount = updatesToDownload.size(); + totalPercent = 0; + + if (totalDownloadsCount == 0) + { + updatingFailed(tr("There was no updates to download. Updating aborted.")); + return; + } + + downloadUpdates(); +} + +QList<UpdateManager::UpdateEntry> UpdateManager::readMetadata(const QJsonDocument& doc) +{ + QList<UpdateEntry> updates; + UpdateEntry entry; + QJsonObject obj = doc.object(); + QJsonArray versionsArray = obj["newVersions"].toArray(); + QJsonObject entryObj; + for (const QJsonValue& value : versionsArray) + { + entryObj = value.toObject(); + entry.compontent = entryObj["component"].toString(); + entry.version = entryObj["version"].toString(); + entry.url = entryObj["url"].toString(); + updates << entry; + } + + return updates; +} + +void UpdateManager::downloadUpdates() +{ + if (updatesToDownload.size() == 0) + { + QtConcurrent::run(this, &UpdateManager::installUpdates); + return; + } + + UpdateEntry entry = updatesToDownload.takeFirst(); + currentJobTitle = tr("Downloading: %1").arg(entry.compontent); + emit updatingProgress(currentJobTitle, 0, totalPercent); + + QStringList parts = entry.url.split("/"); + if (parts.size() < 1) + { + updatingFailed(tr("Could not determinate file name from update URL: %1. Updating aborted.").arg(entry.url)); + return; + } + + QString path = tempDir->path() + QLatin1Char('/') + parts.last(); + currentDownloadFile = new QFile(path); + if (!currentDownloadFile->open(QIODevice::WriteOnly)) + { + updatingFailed(tr("Failed to open file '%1' for writting: %2. Updating aborted.").arg(path, currentDownloadFile->errorString())); + return; + } + + updatesToInstall[entry.compontent] = path; + + QNetworkRequest request(QUrl(entry.url)); + updatesGetReply = networkManager->get(request); + connect(updatesGetReply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(downloadProgress(qint64,qint64))); + connect(updatesGetReply, SIGNAL(readyRead()), this, SLOT(readDownload())); +} + +void UpdateManager::updatingFailed(const QString& errMsg) +{ + cleanup(); + updatesInProgress = false; + emit updatingError(errMsg); +} + +void UpdateManager::installUpdates() +{ + currentJobTitle = tr("Installing updates."); + totalPercent = (totalDownloadsCount - updatesToDownload.size()) * 100 / (totalDownloadsCount + 1); + emit updatingProgress(currentJobTitle, 0, totalPercent); + + requireAdmin = doRequireAdminPrivileges(); + + QTemporaryDir installTempDir; + QString appDirName = QDir(getAppDirPath()).dirName(); + QString targetDir = installTempDir.path() + QLatin1Char('/') + appDirName; + if (!copyRecursively(getAppDirPath(), targetDir)) + { + updatingFailed(tr("Could not copy current application directory into %1 directory.").arg(installTempDir.path())); + return; + } + emit updatingProgress(currentJobTitle, 40, totalPercent); + + int i = 0; + int updatesCnt = updatesToInstall.size(); + for (const QString& component : updatesToInstall.keys()) + { + if (!installComponent(component, targetDir)) + { + cleanup(); + updatesInProgress = false; + return; + } + i++; + emit updatingProgress(currentJobTitle, (30 + (50 / updatesCnt * i)), totalPercent); + } + + if (!executeFinalStep(targetDir)) + { + cleanup(); + updatesInProgress = false; + return; + } + + currentJobTitle = QString(); + totalPercent = 100; + emit updatingProgress(currentJobTitle, 100, totalPercent); + cleanup(); + updatesInProgress = false; +#ifdef Q_OS_WIN32 + installTempDir.setAutoRemove(false); +#endif + + SQLITESTUDIO->setImmediateQuit(true); + qApp->exit(0); +} + +bool UpdateManager::executeFinalStep(const QString& tempDir, const QString& backupDir, const QString& appDir) +{ + bool isWin = false; +#ifdef Q_OS_WIN32 + isWin = true; + + // Windows needs to wait for previus process to exit + QThread::sleep(3); + + QDir dir(backupDir); + QString dirName = dir.dirName(); + dir.cdUp(); + if (!dir.mkdir(dirName)) + { + staticUpdatingFailed(tr("Could not create directory %1.").arg(backupDir)); + return false; + } +#endif + while (!moveDir(appDir, backupDir, isWin)) + { + if (!retryFunction) + { + staticUpdatingFailed(tr("Could not rename directory %1 to %2.\nDetails: %3").arg(appDir, backupDir, staticErrorMessage)); + return false; + } + + if (!retryFunction(tr("Cannot not rename directory %1 to %2.\nDetails: %3").arg(appDir, backupDir, staticErrorMessage))) + return false; + } + + if (!moveDir(tempDir, appDir, isWin)) + { + if (!moveDir(backupDir, appDir, isWin)) + { + staticUpdatingFailed(tr("Could not move directory %1 to %2 and also failed to restore original directory, " + "so the original SQLiteStudio directory is now located at: %3").arg(tempDir, appDir, backupDir)); + } + else + { + staticUpdatingFailed(tr("Could not rename directory %1 to %2. Rolled back to the original SQLiteStudio version.").arg(tempDir, appDir)); + } + deleteDir(backupDir); + return false; + } + + deleteDir(backupDir); + return true; +} + +bool UpdateManager::handleUpdateOptions(const QStringList& argList, int& returnCode) +{ + if (argList.size() == 5 && argList[1] == UPDATE_OPTION_NAME) + { + bool result = UpdateManager::executeFinalStep(argList[2], argList[3], argList[4]); + if (result) + returnCode = 0; + else + returnCode = 1; + + return true; + } + +#ifdef Q_OS_WIN32 + if (argList.size() == 6 && argList[1] == WIN_PRE_FINAL_UPDATE_OPTION_NAME) + { + bool result = UpdateManager::executePreFinalStepWin(argList[2], argList[3], argList[4], (bool)argList[5].toInt()); + if (result) + returnCode = 0; + else + returnCode = -1; + + return true; + } + + if (argList.size() == 3 && argList[1] == WIN_POST_FINAL_UPDATE_OPTION_NAME) + { + QThread::sleep(1); // to make sure that the previous process has quit + returnCode = 0; + UpdateManager::executePostFinalStepWin(argList[2]); + return true; + } +#endif + + return false; +} + +QString UpdateManager::getStaticErrorMessage() +{ + return staticErrorMessage; +} + +bool UpdateManager::executeFinalStep(const QString& tempDir) +{ + QString appDir = getAppDirPath(); + + // Find inexisting dir name next to app dir + QDir backupDir(getBackupDir(appDir)); + +#if defined(Q_OS_WIN32) + return runAnotherInstanceForUpdate(tempDir, backupDir.absolutePath(), qApp->applicationDirPath(), requireAdmin); +#else + bool res; + if (requireAdmin) + res = executeFinalStepAsRoot(tempDir, backupDir.absolutePath(), appDir); + else + res = executeFinalStep(tempDir, backupDir.absolutePath(), appDir); + + if (res) + QProcess::startDetached(qApp->applicationFilePath(), QStringList()); + + return res; +#endif +} + +bool UpdateManager::installComponent(const QString& component, const QString& tempDir) +{ + if (!unpackToDir(updatesToInstall[component], tempDir)) + { + updatingFailed(tr("Could not unpack component %1 into %2 directory.").arg(component, tempDir)); + return false; + } + + // In future here we might also delete/change some files, according to some update script. + return true; +} + +void UpdateManager::cleanup() +{ + safe_delete(currentDownloadFile); + safe_delete(tempDir); + updatesToDownload.clear(); + updatesToInstall.clear(); + requireAdmin = false; +} + +bool UpdateManager::waitForProcess(QProcess& proc) +{ + if (!proc.waitForFinished(-1)) + { + qDebug() << "Update QProcess timed out."; + return false; + } + + if (proc.exitStatus() == QProcess::CrashExit) + { + qDebug() << "Update QProcess finished by crashing."; + return false; + } + + if (proc.exitCode() != 0) + { + qDebug() << "Update QProcess finished with code:" << proc.exitCode(); + return false; + } + + return true; +} + +QString UpdateManager::readError(QProcess& proc, bool reverseOrder) +{ + QString err = QString::fromLocal8Bit(reverseOrder ? proc.readAllStandardOutput() : proc.readAllStandardError()); + if (err.isEmpty()) + err = QString::fromLocal8Bit(reverseOrder ? proc.readAllStandardError() : proc.readAllStandardOutput()); + + QString errStr = proc.errorString(); + if (!errStr.isEmpty()) + err += "\n" + errStr; + + return err; +} + +void UpdateManager::staticUpdatingFailed(const QString& errMsg) +{ +#if defined(Q_OS_WIN32) + staticErrorMessage = errMsg; +#else + UPDATES->handleStaticFail(errMsg); +#endif + qCritical() << errMsg; +} + +bool UpdateManager::executeFinalStepAsRoot(const QString& tempDir, const QString& backupDir, const QString& appDir) +{ +#if defined(Q_OS_LINUX) + return executeFinalStepAsRootLinux(tempDir, backupDir, appDir); +#elif defined(Q_OS_WIN32) + return executeFinalStepAsRootWin(tempDir, backupDir, appDir); +#elif defined(Q_OS_MACX) + return executeFinalStepAsRootMac(tempDir, backupDir, appDir); +#else + qCritical() << "Unknown update platform in UpdateManager::executeFinalStepAsRoot() for package" << packagePath; + return false; +#endif +} + +#if defined(Q_OS_LINUX) +bool UpdateManager::executeFinalStepAsRootLinux(const QString& tempDir, const QString& backupDir, const QString& appDir) +{ + QStringList args = {qApp->applicationFilePath(), UPDATE_OPTION_NAME, tempDir, backupDir, appDir}; + + QProcess proc; + LinuxPermElevator elevator = findPermElevatorForLinux(); + switch (elevator) + { + case LinuxPermElevator::KDESU: + proc.setProgram("kdesu"); + args.prepend("-t"); + proc.setArguments(args); + break; + case LinuxPermElevator::GKSU: + proc.setProgram("gksu"); // TODO test gksu updates + proc.setArguments(args); + break; + case LinuxPermElevator::PKEXEC: + { + // We call CLI for doing final step, because pkexec runs cmd completly in root env, so there's no X server. + args[0] += "cli"; + + QStringList newArgs; + for (const QString& arg : args) + newArgs << wrapCmdLineArgument(arg); + + QString cmd = "cd " + wrapCmdLineArgument(qApp->applicationDirPath()) +"; " + newArgs.join(" "); + + proc.setProgram("pkexec"); + proc.setArguments({"sh", "-c", cmd}); + } + break; + case LinuxPermElevator::NONE: + updatingFailed(tr("Could not find permissions elevator application to run update as a root. Looked for: %1").arg("kdesu, gksu, pkexec")); + return false; + } + + proc.start(); + if (!waitForProcess(proc)) + { + updatingFailed(tr("Could not execute final updating steps as root: %1").arg(readError(proc, (elevator == LinuxPermElevator::KDESU)))); + return false; + } + + return true; +} +#endif + +#ifdef Q_OS_MACX +bool UpdateManager::executeFinalStepAsRootMac(const QString& tempDir, const QString& backupDir, const QString& appDir) +{ + // Prepare script for updater + // osascript -e "do shell script \"stufftorunasroot\" with administrator privileges" + QStringList args = {wrapCmdLineArgument(qApp->applicationFilePath() + "cli"), + UPDATE_OPTION_NAME, + wrapCmdLineArgument(tempDir), + wrapCmdLineArgument(backupDir), + wrapCmdLineArgument(appDir)}; + QProcess proc; + + QString innerCmd = wrapCmdLineArgument(args.join(" ")); + + static_qstring(scriptTpl, "do shell script %1 with administrator privileges"); + QString scriptCmd = scriptTpl.arg(innerCmd); + + // Prepare updater temporary directory + QTemporaryDir updaterDir; + if (!updaterDir.isValid()) + { + updatingFailed(tr("Could not execute final updating steps as admin: %1").arg(tr("Cannot create temporary directory for updater."))); + return false; + } + + // Create updater script + QString scriptPath = updaterDir.path() + "/UpdateSQLiteStudio.scpt"; + QFile updaterScript(scriptPath); + if (!updaterScript.open(QIODevice::WriteOnly)) + { + updatingFailed(tr("Could not execute final updating steps as admin: %1").arg(tr("Cannot create updater script file."))); + return false; + } + updaterScript.write(scriptCmd.toLocal8Bit()); + updaterScript.close(); + + // Compile script to updater application + QString updaterApp = updaterDir.path() + "/UpdateSQLiteStudio.app"; + proc.setProgram("osacompile"); + proc.setArguments({"-o", updaterApp, scriptPath}); + proc.start(); + if (!waitForProcess(proc)) + { + updatingFailed(tr("Could not execute final updating steps as admin: %1").arg(readError(proc))); + return false; + } + + // Execute updater + proc.setProgram(updaterApp + "/Contents/MacOS/applet"); + proc.setArguments({}); + proc.start(); + if (!waitForProcess(proc)) + { + updatingFailed(tr("Could not execute final updating steps as admin: %1").arg(readError(proc))); + return false; + } + + // Validating update + // The updater script will not return error if the user canceled the password prompt. + // We need to check if the update was actually made and return true only then. + if (QDir(tempDir).exists()) + { + // Temp dir still exists, so it was not moved by root process + updatingFailed(tr("Updating canceled.")); + return false; + } + + return true; +} +#endif + +#ifdef Q_OS_WIN32 +bool UpdateManager::executeFinalStepAsRootWin(const QString& tempDir, const QString& backupDir, const QString& appDir) +{ + QString updateBin = qApp->applicationDirPath() + "/" + WIN_UPDATER_BINARY; + + QString installFilePath = tempDir + "/" + WIN_INSTALL_FILE; + QFile installFile(installFilePath); + installFile.open(QIODevice::WriteOnly); + QString nl("\n"); + installFile.write(UPDATE_OPTION_NAME); + installFile.write(nl.toLocal8Bit()); + installFile.write(backupDir.toLocal8Bit()); + installFile.write(nl.toLocal8Bit()); + installFile.write(appDir.toLocal8Bit()); + installFile.write(nl.toLocal8Bit()); + installFile.close(); + + int res = (int)::ShellExecuteA(0, "runas", updateBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL); + if (res < 32) + { + staticUpdatingFailed(tr("Could not execute final updating steps as administrator.")); + return false; + } + + // Since I suck as a developer and I cannot implement a simple synchronous app call under Windows + // (QProcess does it somehow, but I'm too lazy to look it up and probably the solution wouldn't be compatible + // with our "privileges elevation" trick above... so after all I think we're stuck with this solution for now), + // I do the workaround here, which makes this process wait for the other process to create the "done" + // file when it's done, so this process knows when the other has ended. This way we can proceed with this + // process and we will delete some directories later on, which were required by that other process. + if (!waitForFileToDisappear(installFilePath, 10)) + { + staticUpdatingFailed(tr("Could not execute final updating steps as administrator. Updater startup timed out.")); + return false; + } + + if (!waitForFileToAppear(appDir + QLatin1Char('/') + WIN_UPDATE_DONE_FILE, 30)) + { + staticUpdatingFailed(tr("Could not execute final updating steps as administrator. Updater operation timed out.")); + return false; + } + + return true; +} +#endif + +#if defined(Q_OS_WIN32) +bool UpdateManager::executePostFinalStepWin(const QString &tempDir) +{ + QString doneFile = qApp->applicationDirPath() + QLatin1Char('/') + WIN_UPDATE_DONE_FILE; + QFile::remove(doneFile); + + QDir dir(tempDir); + dir.cdUp(); + if (!deleteDir(dir.absolutePath())) + staticUpdatingFailed(tr("Could not clean up temporary directory %1. You can delete it manually at any time.").arg(dir.absolutePath())); + + QProcess::startDetached(qApp->applicationFilePath(), QStringList()); + return true; +} + +bool UpdateManager::waitForFileToDisappear(const QString &filePath, int seconds) +{ + QFile file(filePath); + while (file.exists() && seconds > 0) + { + QThread::sleep(1); + seconds--; + } + + return !file.exists(); +} + +bool UpdateManager::waitForFileToAppear(const QString &filePath, int seconds) +{ + QFile file(filePath); + while (!file.exists() && seconds > 0) + { + QThread::sleep(1); + seconds--; + } + + return file.exists(); +} + +bool UpdateManager::runAnotherInstanceForUpdate(const QString &tempDir, const QString &backupDir, const QString &appDir, bool reqAdmin) +{ + bool res = QProcess::startDetached(tempDir + "/SQLiteStudio.exe", {WIN_PRE_FINAL_UPDATE_OPTION_NAME, tempDir, backupDir, appDir, + QString::number((int)reqAdmin)}); + if (!res) + { + updatingFailed(tr("Could not run new version for continuing update.")); + return false; + } + + return true; +} +#endif + +UpdateManager::LinuxPermElevator UpdateManager::findPermElevatorForLinux() +{ +#if defined(Q_OS_LINUX) + QProcess proc; + proc.setProgram("which"); + + if (!SQLITESTUDIO->getEnv("DISPLAY").isEmpty()) + { + proc.setArguments({"kdesu"}); + proc.start(); + if (waitForProcess(proc)) + return LinuxPermElevator::KDESU; + + proc.setArguments({"gksu"}); + proc.start(); + if (waitForProcess(proc)) + return LinuxPermElevator::GKSU; + } + + proc.setArguments({"pkexec"}); + proc.start(); + if (waitForProcess(proc)) + return LinuxPermElevator::PKEXEC; +#endif + + return LinuxPermElevator::NONE; +} + +QString UpdateManager::wrapCmdLineArgument(const QString& arg) +{ + return "\"" + escapeCmdLineArgument(arg) + "\""; +} + +QString UpdateManager::escapeCmdLineArgument(const QString& arg) +{ + if (!arg.contains("\\") && !arg.contains("\"")) + return arg; + + QString str = arg; + return str.replace("\\", "\\\\").replace("\"", "\\\""); +} + +QString UpdateManager::getBackupDir(const QString &appDir) +{ + static_qstring(bakDirTpl, "%1.old%2"); + QDir backupDir(bakDirTpl.arg(appDir, "")); + int cnt = 1; + while (backupDir.exists()) + backupDir = QDir(bakDirTpl.arg(appDir, QString::number(cnt))); + + return backupDir.absolutePath(); +} + +bool UpdateManager::unpackToDir(const QString& packagePath, const QString& outputDir) +{ +#if defined(Q_OS_LINUX) + return unpackToDirLinux(packagePath, outputDir); +#elif defined(Q_OS_WIN32) + return unpackToDirWin(packagePath, outputDir); +#elif defined(Q_OS_MACX) + return unpackToDirMac(packagePath, outputDir); +#else + qCritical() << "Unknown update platform in UpdateManager::unpackToDir() for package" << packagePath; + return false; +#endif +} + +#if defined(Q_OS_LINUX) +bool UpdateManager::unpackToDirLinux(const QString &packagePath, const QString &outputDir) +{ + QProcess proc; + proc.setWorkingDirectory(outputDir); + proc.setStandardOutputFile(QProcess::nullDevice()); + proc.setStandardErrorFile(QProcess::nullDevice()); + + if (!packagePath.endsWith("tar.gz")) + { + updatingFailed(tr("Package not in tar.gz format, cannot install: %1").arg(packagePath)); + return false; + } + + proc.start("mv", {packagePath, outputDir}); + if (!waitForProcess(proc)) + { + updatingFailed(tr("Package %1 cannot be installed, because cannot move it to directory: %2").arg(packagePath, outputDir)); + return false; + } + + QString fileName = packagePath.split("/").last(); + QString newPath = outputDir + "/" + fileName; + proc.start("tar", {"-xzf", newPath}); + if (!waitForProcess(proc)) + { + updatingFailed(tr("Package %1 cannot be installed, because cannot unpack it: %2").arg(packagePath, readError(proc))); + return false; + } + + QProcess::execute("rm", {"-f", newPath}); + return true; +} +#endif + +#if defined(Q_OS_MACX) +bool UpdateManager::unpackToDirMac(const QString &packagePath, const QString &outputDir) +{ + QProcess proc; + proc.setWorkingDirectory(outputDir); + proc.setStandardOutputFile(QProcess::nullDevice()); + proc.setStandardErrorFile(QProcess::nullDevice()); + + if (!packagePath.endsWith("zip")) + { + updatingFailed(tr("Package not in zip format, cannot install: %1").arg(packagePath)); + return false; + } + + proc.start("unzip", {"-o", "-d", outputDir, packagePath}); + if (!waitForProcess(proc)) + { + updatingFailed(tr("Package %1 cannot be installed, because cannot unzip it to directory %2: %3") + .arg(packagePath, outputDir, readError(proc))); + return false; + } + + return true; +} +#endif + +#if defined(Q_OS_WIN32) +bool UpdateManager::unpackToDirWin(const QString& packagePath, const QString& outputDir) +{ + if (JlCompress::extractDir(packagePath, outputDir + "/..").isEmpty()) + { + updatingFailed(tr("Package %1 cannot be installed, because cannot unzip it to directory: %2").arg(packagePath, outputDir)); + return false; + } + + return true; +} +#endif + +void UpdateManager::handleStaticFail(const QString& errMsg) +{ + emit updatingFailed(errMsg); +} + +QString UpdateManager::getAppDirPath() const +{ + static QString appDir; + if (appDir.isNull()) + { + appDir = qApp->applicationDirPath(); +#ifdef Q_OS_MACX + QDir tmpAppDir(appDir); + tmpAppDir.cdUp(); + tmpAppDir.cdUp(); + appDir = tmpAppDir.absolutePath(); +#endif + } + return appDir; +} + +bool UpdateManager::moveDir(const QString& src, const QString& dst, bool contentsOnly) +{ + // If we're doing a rename in the very same parent directory then we don't want + // the 'move between partitions' to be involved, cause any failure to rename + // is due to permissions or file lock. + QFileInfo srcFi(src); + QFileInfo dstFi(dst); + bool sameParentDir = (srcFi.dir() == dstFi.dir()); + + QDir dir; + if (contentsOnly) + { + QString localSrc; + QString localDst; + QDir srcDir(src); + for (const QFileInfo& entry : srcDir.entryInfoList(QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot|QDir::Hidden|QDir::System)) + { + localSrc = entry.absoluteFilePath(); + localDst = dst + "/" + entry.fileName(); + if (!dir.rename(localSrc, localDst) && (sameParentDir || !renameBetweenPartitions(localSrc, localDst))) + { + staticUpdatingFailed(tr("Could not rename directory %1 to %2.").arg(localSrc, localDst)); + return false; + } + } + } + else + { + if (!dir.rename(src, dst) && (sameParentDir || !renameBetweenPartitions(src, dst))) + { + staticUpdatingFailed(tr("Could not rename directory %1 to %2.").arg(src, dst)); + return false; + } + } + + return true; +} + +bool UpdateManager::deleteDir(const QString& path) +{ + QDir dir(path); + if (!dir.removeRecursively()) + { + staticUpdatingFailed(tr("Could not delete directory %1.").arg(path)); + return false; + } + + return true; +} + +bool UpdateManager::execCmd(const QString& cmd, const QStringList& args, QString* errorMsg) +{ + QProcess proc; + proc.start(cmd, args); + QString cmdString = QString("%1 \"%2\"").arg(cmd, args.join("\\\" \\\"")); + + if (!waitForProcess(proc)) + { + if (errorMsg) + *errorMsg = tr("Error executing update command: %1\nError message: %2").arg(cmdString).arg(readError(proc)); + + return false; + } + + return true; +} + +void UpdateManager::setRetryFunction(const RetryFunction &value) +{ + retryFunction = value; +} + +bool UpdateManager::doRequireAdminPrivileges() +{ + QString appDirPath = getAppDirPath(); + QDir appDir(appDirPath); + bool isWritable = isWritableRecursively(appDir.absolutePath()); + + appDir.cdUp(); + QFileInfo fi(appDir.absolutePath()); + isWritable &= fi.isWritable(); + + if (isWritable) + { + QDir backupDir(getBackupDir(appDirPath)); + QString backupDirName = backupDir.dirName(); + backupDir.cdUp(); + if (backupDir.mkdir(backupDirName)) + backupDir.rmdir(backupDirName); + else + isWritable = false; + } + + return !isWritable; +} + +void UpdateManager::finished(QNetworkReply* reply) +{ + if (reply == updatesCheckReply) + { + updatesCheckReply = nullptr; + handleAvailableUpdatesReply(reply); + return; + } + + if (reply == updatesGetUrlsReply) + { + updatesGetUrlsReply = nullptr; + handleUpdatesMetadata(reply); + return; + } + + if (reply == updatesGetReply) + { + handleDownloadReply(reply); + if (reply == updatesGetReply) // if no new download is requested + updatesGetReply = nullptr; + + return; + } +} + +void UpdateManager::handleDownloadReply(QNetworkReply* reply) +{ + if (reply->error() != QNetworkReply::NoError) + { + updatingFailed(tr("An error occurred while downloading updates: %1. Updating aborted.").arg(reply->errorString())); + reply->deleteLater(); + return; + } + + totalPercent = (totalDownloadsCount - updatesToDownload.size()) * 100 / (totalDownloadsCount + 1); + + readDownload(); + currentDownloadFile->close(); + + safe_delete(currentDownloadFile); + + reply->deleteLater(); + downloadUpdates(); +} + +void UpdateManager::downloadProgress(qint64 bytesReceived, qint64 totalBytes) +{ + int perc; + if (totalBytes < 0) + perc = -1; + else if (totalBytes == 0) + perc = 100; + else + perc = bytesReceived * 100 / totalBytes; + + emit updatingProgress(currentJobTitle, perc, totalPercent); +} + +void UpdateManager::readDownload() +{ + currentDownloadFile->write(updatesGetReply->readAll()); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.h b/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.h new file mode 100644 index 0000000..b8e6006 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.h @@ -0,0 +1,137 @@ +#ifndef UPDATEMANAGER_H +#define UPDATEMANAGER_H + +#include "common/global.h" +#include "sqlitestudio.h" +#include <QObject> +#include <functional> +#include <QProcess> + +class QNetworkAccessManager; +class QNetworkReply; +class QTemporaryDir; +class QFile; + +class API_EXPORT UpdateManager : public QObject +{ + Q_OBJECT + public: + typedef std::function<bool(const QString& msg)> RetryFunction; + + struct UpdateEntry + { + QString compontent; + QString version; + QString url; + }; + + explicit UpdateManager(QObject *parent = 0); + ~UpdateManager(); + + void checkForUpdates(); + void update(); + bool isPlatformEligibleForUpdate() const; + static bool executeFinalStep(const QString& tempDir, const QString& backupDir, const QString& appDir); + static bool handleUpdateOptions(const QStringList& argList, int& returnCode); + static QString getStaticErrorMessage(); + + static void setRetryFunction(const RetryFunction &value); + + static_char* UPDATE_OPTION_NAME = "--update-final-step"; + static_char* WIN_INSTALL_FILE = "install.dat"; + static_char* WIN_UPDATE_DONE_FILE = "UpdateFinished.lck"; + + private: + enum class LinuxPermElevator + { + KDESU, + GKSU, + PKEXEC, + NONE + }; + + QString getPlatformForUpdate() const; + QString getCurrentVersions() const; + void handleAvailableUpdatesReply(QNetworkReply* reply); + void handleDownloadReply(QNetworkReply* reply); + void getUpdatesMetadata(QNetworkReply*& replyStoragePointer); + void handleUpdatesMetadata(QNetworkReply* reply); + QList<UpdateEntry> readMetadata(const QJsonDocument& doc); + void downloadUpdates(); + void updatingFailed(const QString& errMsg); + void installUpdates(); + bool installComponent(const QString& component, const QString& tempDir); + bool executeFinalStep(const QString& tempDir); + bool executeFinalStepAsRoot(const QString& tempDir, const QString& backupDir, const QString& appDir); +#if defined(Q_OS_LINUX) + bool executeFinalStepAsRootLinux(const QString& tempDir, const QString& backupDir, const QString& appDir); + bool unpackToDirLinux(const QString& packagePath, const QString& outputDir); +#elif defined(Q_OS_MACX) + bool unpackToDirMac(const QString& packagePath, const QString& outputDir); + bool executeFinalStepAsRootMac(const QString& tempDir, const QString& backupDir, const QString& appDir); +#elif defined(Q_OS_WIN32) + bool runAnotherInstanceForUpdate(const QString& tempDir, const QString& backupDir, const QString& appDir, bool reqAdmin); + bool unpackToDirWin(const QString& packagePath, const QString& outputDir); +#endif + bool doRequireAdminPrivileges(); + bool unpackToDir(const QString& packagePath, const QString& outputDir); + void handleStaticFail(const QString& errMsg); + QString getAppDirPath() const; + void cleanup(); + + static bool moveDir(const QString& src, const QString& dst, bool contentsOnly = false); + static bool deleteDir(const QString& path); + static bool execCmd(const QString& cmd, const QStringList& args, QString* errorMsg = nullptr); + static bool waitForProcess(QProcess& proc); + static QString readError(QProcess& proc, bool reverseOrder = false); + static void staticUpdatingFailed(const QString& errMsg); + static LinuxPermElevator findPermElevatorForLinux(); + static QString wrapCmdLineArgument(const QString& arg); + static QString escapeCmdLineArgument(const QString& arg); + static QString getBackupDir(const QString& appDir); +#if defined(Q_OS_WIN32) + static bool executePreFinalStepWin(const QString& tempDir, const QString& backupDir, const QString& appDir, bool reqAdmin); + static bool executeFinalStepAsRootWin(const QString& tempDir, const QString& backupDir, const QString& appDir); + static bool executePostFinalStepWin(const QString& tempDir); + static bool waitForFileToDisappear(const QString& filePath, int seconds); + static bool waitForFileToAppear(const QString& filePath, int seconds); +#endif + + QNetworkAccessManager* networkManager = nullptr; + QNetworkReply* updatesCheckReply = nullptr; + QNetworkReply* updatesGetUrlsReply = nullptr; + QNetworkReply* updatesGetReply = nullptr; + bool updatesInProgress = false; + QList<UpdateEntry> updatesToDownload; + QHash<QString,QString> updatesToInstall; + QTemporaryDir* tempDir = nullptr; + QFile* currentDownloadFile = nullptr; + int totalPercent = 0; + int totalDownloadsCount = 0; + QString currentJobTitle; + bool requireAdmin = false; + static RetryFunction retryFunction; + + static QString staticErrorMessage; + static_char* WIN_PRE_FINAL_UPDATE_OPTION_NAME = "--update-pre-final-step"; + static_char* WIN_POST_FINAL_UPDATE_OPTION_NAME = "--update-post-final-step"; + static_char* WIN_UPDATER_BINARY = "UpdateSQLiteStudio.exe"; + static_char* updateServiceUrl = "http://sqlitestudio.pl/updates3.rvt"; + static_char* manualUpdatesHelpUrl = "http://wiki.sqlitestudio.pl/index.php/User_Manual#Manual"; + + private slots: + void finished(QNetworkReply* reply); + void downloadProgress(qint64 bytesReceived, qint64 totalBytes); + void readDownload(); + + signals: + void updatesAvailable(const QList<UpdateManager::UpdateEntry>& updates); + void noUpdatesAvailable(); + void updatingProgress(const QString& jobTitle, int jobPercent, int totalPercent); + void updatingFinished(); + void updatingError(const QString& errorMessage); +}; + +#define UPDATES SQLITESTUDIO->getUpdateManager() + +#endif // UPDATEMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.cpp b/SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.cpp new file mode 100644 index 0000000..201dc8b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.cpp @@ -0,0 +1,42 @@ +#include "sqlhistorymodel.h" +#include "common/global.h" +#include "db/db.h" + +SqlHistoryModel::SqlHistoryModel(Db* db, QObject *parent) : + QueryModel(db, parent) +{ + static_char* query = "SELECT dbname, datetime(date, 'unixepoch'), (time_spent / 1000.0)||'s', rows, sql " + "FROM sqleditor_history ORDER BY date DESC"; + + setQuery(query); +} + +QVariant SqlHistoryModel::data(const QModelIndex& index, int role) const +{ + if (role == Qt::TextAlignmentRole && (index.column() == 2 || index.column() == 3)) + return (int)(Qt::AlignRight|Qt::AlignVCenter); + + return QueryModel::data(index, role); +} + +QVariant SqlHistoryModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal || role != Qt::DisplayRole) + return QueryModel::headerData(section, orientation, role); + + switch (section) + { + case 0: + return tr("Database", "sql history header"); + case 1: + return tr("Execution date", "sql history header"); + case 2: + return tr("Time spent", "sql history header"); + case 3: + return tr("Rows affected", "sql history header"); + case 4: + return tr("SQL", "sql history header"); + } + + return QueryModel::headerData(section, orientation, role); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.h b/SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.h new file mode 100644 index 0000000..0f6b25d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/sqlhistorymodel.h @@ -0,0 +1,17 @@ +#ifndef SQLHISTORYMODEL_H +#define SQLHISTORYMODEL_H + +#include "querymodel.h" + +class Db; + +class SqlHistoryModel : public QueryModel +{ + public: + SqlHistoryModel(Db* db, QObject *parent = nullptr); + + QVariant data(const QModelIndex& index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; +}; + +#endif // SQLHISTORYMODEL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp b/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp new file mode 100644 index 0000000..10d1bd5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.cpp @@ -0,0 +1,390 @@ +#include "sqlitestudio.h" +#include "plugins/plugin.h" +#include "services/pluginmanager.h" +#include "common/utils.h" +#include "common/utils_sql.h" +#include "completionhelper.h" +#include "parser/keywords.h" +#include "parser/lexer.h" +#include "services/notifymanager.h" +#include "plugins/codeformatterplugin.h" +#include "services/codeformatter.h" +#include "plugins/generalpurposeplugin.h" +#include "plugins/dbplugin.h" +#include "common/unused.h" +#include "services/functionmanager.h" +#include "plugins/scriptingplugin.h" +#include "plugins/exportplugin.h" +#include "plugins/scriptingqt.h" +#include "plugins/dbpluginsqlite3.h" +#include "services/impl/configimpl.h" +#include "services/impl/dbmanagerimpl.h" +#include "services/impl/functionmanagerimpl.h" +#include "services/impl/collationmanagerimpl.h" +#include "services/impl/pluginmanagerimpl.h" +#include "services/updatemanager.h" +#include "impl/dbattacherimpl.h" +#include "services/exportmanager.h" +#include "services/importmanager.h" +#include "services/populatemanager.h" +#include "plugins/scriptingsql.h" +#include "plugins/importplugin.h" +#include "plugins/populateplugin.h" +#include "services/bugreporter.h" +#include "services/extralicensemanager.h" +#include <QProcessEnvironment> +#include <QThreadPool> +#include <QCoreApplication> + +DEFINE_SINGLETON(SQLiteStudio) + +static const int sqlitestudioVersion = 29906; + +SQLiteStudio::SQLiteStudio() +{ + if (qApp) // qApp is null in unit tests + connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(cleanUp())); +} + +SQLiteStudio::~SQLiteStudio() +{ +} + +ExtraLicenseManager* SQLiteStudio::getExtraLicenseManager() const +{ + return extraLicenseManager; +} + +void SQLiteStudio::setExtraLicenseManager(ExtraLicenseManager* value) +{ + extraLicenseManager = value; +} + + +bool SQLiteStudio::getImmediateQuit() const +{ + return immediateQuit; +} + +void SQLiteStudio::setImmediateQuit(bool value) +{ + immediateQuit = value; +} + +UpdateManager* SQLiteStudio::getUpdateManager() const +{ + return updateManager; +} + +void SQLiteStudio::setUpdateManager(UpdateManager* value) +{ + updateManager = value; +} + +BugReporter* SQLiteStudio::getBugReporter() const +{ + return bugReporter; +} + +void SQLiteStudio::setBugReporter(BugReporter* value) +{ + bugReporter = value; +} + +PopulateManager* SQLiteStudio::getPopulateManager() const +{ + return populateManager; +} + +void SQLiteStudio::setPopulateManager(PopulateManager* value) +{ + populateManager = value; +} + +CodeFormatter* SQLiteStudio::getCodeFormatter() const +{ + return codeFormatter; +} + +void SQLiteStudio::setCodeFormatter(CodeFormatter* codeFormatter) +{ + this->codeFormatter = codeFormatter; +} + +QString SQLiteStudio::getHomePage() const +{ + static const QString url = QStringLiteral("http://sqlitestudio.pl"); + return url; +} + +QString SQLiteStudio::getForumPage() const +{ + static const QString url = QStringLiteral("http://forum.sqlitestudio.pl"); + return url; +} + +QString SQLiteStudio::getUserManualPage() const +{ + static const QString url = QStringLiteral("http://wiki.sqlitestudio.pl/index.php/User_Manual"); + return url; +} + +QString SQLiteStudio::getSqliteDocsPage() const +{ + static const QString url = QStringLiteral("http://sqlite.org/lang.html"); + return url; +} + +ImportManager* SQLiteStudio::getImportManager() const +{ + return importManager; +} + +void SQLiteStudio::setImportManager(ImportManager* value) +{ + importManager = value; +} + +ExportManager* SQLiteStudio::getExportManager() const +{ + return exportManager; +} + +void SQLiteStudio::setExportManager(ExportManager* value) +{ + exportManager = value; +} + +int SQLiteStudio::getVersion() const +{ + return sqlitestudioVersion; +} + +QString SQLiteStudio::getVersionString() const +{ + int ver = getVersion(); + int majorVer = ver / 10000; + int minorVer = ver % 10000 / 100; + int patchVer = ver % 100; + return QString::number(majorVer) + "." + QString::number(minorVer) + "." + QString::number(patchVer); +} + +CollationManager* SQLiteStudio::getCollationManager() const +{ + return collationManager; +} + +void SQLiteStudio::setCollationManager(CollationManager* value) +{ + safe_delete(collationManager); + collationManager = value; +} + +DbAttacherFactory* SQLiteStudio::getDbAttacherFactory() const +{ + return dbAttacherFactory; +} + +void SQLiteStudio::setDbAttacherFactory(DbAttacherFactory* value) +{ + safe_delete(dbAttacherFactory); + dbAttacherFactory = value; +} + +PluginManager* SQLiteStudio::getPluginManager() const +{ + return pluginManager; +} + +void SQLiteStudio::setPluginManager(PluginManager* value) +{ + safe_delete(pluginManager); + pluginManager = value; +} + +FunctionManager* SQLiteStudio::getFunctionManager() const +{ + return functionManager; +} + +void SQLiteStudio::setFunctionManager(FunctionManager* value) +{ + safe_delete(functionManager); + functionManager = value; +} + +DbManager* SQLiteStudio::getDbManager() const +{ + return dbManager; +} + +void SQLiteStudio::setDbManager(DbManager* value) +{ + safe_delete(dbManager); + dbManager = value; +} + +Config* SQLiteStudio::getConfig() const +{ + return config; +} + +void SQLiteStudio::setConfig(Config* value) +{ + safe_delete(config); + config = value; +} + +void SQLiteStudio::init(const QStringList& cmdListArguments, bool guiAvailable) +{ + env = new QProcessEnvironment(QProcessEnvironment::systemEnvironment()); + this->guiAvailable = guiAvailable; + + QThreadPool::globalInstance()->setMaxThreadCount(10); + + Q_INIT_RESOURCE(coresqlitestudio); + + CfgLazyInitializer::init(); + + initUtils(); + CfgMain::staticInit(); + Db::metaInit(); + initUtilsSql(); + initKeywords(); + Lexer::staticInit(); + CompletionHelper::init(); + + qRegisterMetaType<ScriptingPlugin::Context*>(); + + NotifyManager::getInstance(); + + dbAttacherFactory = new DbAttacherDefaultFactory(); + + config = new ConfigImpl(); + config->init(); + + pluginManager = new PluginManagerImpl(); + dbManager = new DbManagerImpl(); + + pluginManager->registerPluginType<GeneralPurposePlugin>(QObject::tr("General purpose", "plugin category name")); + pluginManager->registerPluginType<DbPlugin>(QObject::tr("Database support", "plugin category name")); + pluginManager->registerPluginType<CodeFormatterPlugin>(QObject::tr("Code formatter", "plugin category name"), "formatterPluginsPage"); + pluginManager->registerPluginType<ScriptingPlugin>(QObject::tr("Scripting languages", "plugin category name")); + pluginManager->registerPluginType<ExportPlugin>(QObject::tr("Exporting", "plugin category name")); + pluginManager->registerPluginType<ImportPlugin>(QObject::tr("Importing", "plugin category name")); + pluginManager->registerPluginType<PopulatePlugin>(QObject::tr("Table populating", "plugin category name")); + + codeFormatter = new CodeFormatter(); + connect(CFG_CORE.General.ActiveCodeFormatter, SIGNAL(changed(QVariant)), this, SLOT(updateCurrentCodeFormatter())); + connect(pluginManager, SIGNAL(pluginsInitiallyLoaded()), this, SLOT(updateCodeFormatter())); + + // FunctionManager needs to be set up before DbManager, cause when DbManager starts up, databases make their + // connections and register functions. + functionManager = new FunctionManagerImpl(); + + collationManager = new CollationManagerImpl(); + + cmdLineArgs = cmdListArguments; + + connect(pluginManager, SIGNAL(pluginsInitiallyLoaded()), DBLIST, SLOT(notifyDatabasesAreLoaded())); + + DbPluginSqlite3* sqlite3plugin = new DbPluginSqlite3; + dynamic_cast<DbManagerImpl*>(dbManager)->setInMemDbCreatorPlugin(sqlite3plugin); + + pluginManager->loadBuiltInPlugin(new ScriptingQt); + pluginManager->loadBuiltInPlugin(new ScriptingSql); + pluginManager->loadBuiltInPlugin(sqlite3plugin); + + exportManager = new ExportManager(); + importManager = new ImportManager(); + populateManager = new PopulateManager(); + bugReporter = new BugReporter(); + updateManager = new UpdateManager(); + extraLicenseManager = new ExtraLicenseManager(); + + extraLicenseManager->addLicense("SQLiteStudio license (GPL v3)", ":/docs/licenses/sqlitestudio_license.txt"); + extraLicenseManager->addLicense("Fugue icons", ":/docs/licenses/fugue_icons.txt"); + extraLicenseManager->addLicense("Qt, QHexEdit (LGPL v2.1)", ":/docs/licenses/lgpl.txt"); + extraLicenseManager->addLicense("diff_match (Apache License v2.0)", ":/docs/licenses/diff_match.txt"); + extraLicenseManager->addLicense("RSA library (GPL v3)", ":/docs/licenses/gpl.txt"); + +} + +void SQLiteStudio::initPlugins() +{ + pluginManager->init(); + + connect(pluginManager, SIGNAL(loaded(Plugin*,PluginType*)), this, SLOT(pluginLoaded(Plugin*,PluginType*))); + connect(pluginManager, SIGNAL(aboutToUnload(Plugin*,PluginType*)), this, SLOT(pluginToBeUnloaded(Plugin*,PluginType*))); + connect(pluginManager, SIGNAL(unloaded(QString,PluginType*)), this, SLOT(pluginUnloaded(QString,PluginType*))); +} + +void SQLiteStudio::cleanUp() +{ + disconnect(pluginManager, SIGNAL(aboutToUnload(Plugin*,PluginType*)), this, SLOT(pluginToBeUnloaded(Plugin*,PluginType*))); + disconnect(pluginManager, SIGNAL(unloaded(QString,PluginType*)), this, SLOT(pluginUnloaded(QString,PluginType*))); + if (!immediateQuit) + { + pluginManager->deinit(); + safe_delete(pluginManager); // PluginManager before DbManager, so Db objects are deleted while DbManager still exists + safe_delete(updateManager); + safe_delete(bugReporter); + safe_delete(populateManager); + safe_delete(importManager); + safe_delete(exportManager); + safe_delete(functionManager); + safe_delete(extraLicenseManager); + safe_delete(dbManager); + safe_delete(config); + safe_delete(codeFormatter); + safe_delete(dbAttacherFactory); + safe_delete(env); + NotifyManager::destroy(); + } + Q_CLEANUP_RESOURCE(coresqlitestudio); +} + +void SQLiteStudio::updateCodeFormatter() +{ + codeFormatter->fullUpdate(); +} + +void SQLiteStudio::updateCurrentCodeFormatter() +{ + codeFormatter->updateCurrent(); +} + +void SQLiteStudio::pluginLoaded(Plugin* plugin, PluginType* pluginType) +{ + UNUSED(plugin); + if (pluginType->isForPluginType<CodeFormatterPlugin>()) // TODO move this to slot of CodeFormatter + updateCodeFormatter(); +} + +void SQLiteStudio::pluginToBeUnloaded(Plugin* plugin, PluginType* pluginType) +{ + UNUSED(plugin); + UNUSED(pluginType); +} + +void SQLiteStudio::pluginUnloaded(const QString& pluginName, PluginType* pluginType) +{ + UNUSED(pluginName); + if (pluginType->isForPluginType<CodeFormatterPlugin>()) // TODO move this to slot of CodeFormatter + updateCodeFormatter(); +} + +QString SQLiteStudio::getEnv(const QString &name, const QString &defaultValue) +{ + return env->value(name, defaultValue); +} + +DbAttacher* SQLiteStudio::createDbAttacher(Db* db) +{ + return dbAttacherFactory->create(db); +} + +bool SQLiteStudio::isGuiAvailable() const +{ + return guiAvailable; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.h b/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.h new file mode 100644 index 0000000..a41867a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/sqlitestudio.h @@ -0,0 +1,258 @@ +#ifndef SQLITESTUDIO_H +#define SQLITESTUDIO_H + +#include "coreSQLiteStudio_global.h" +#include "common/global.h" +#include "services/config.h" +#include <QString> +#include <QStringList> +#include <QObject> + +class DbManager; +class Config; +class QProcessEnvironment; +class PluginManager; +class QThreadPool; +class NotifyManager; +class CodeFormatter; +class Plugin; +class PluginType; +class FunctionManager; +class DbAttacherFactory; +class DbAttacher; +class ExportManager; +class ImportManager; +class PopulateManager; +class PluginLoadingHandler; +class BugReporter; +class UpdateManager; +class ExtraLicenseManager; + +/** @file */ + +/** + * @mainpage + * SQLiteStudio is SQLite 2 and 3 manager for Windows, MacOS X and Linux. + * + * Global variables and macros: + * <ul> + * <li>#SQLITESTUDIO - access point to all services (singleton instance of SQLiteStudio)</li> + * <li>#PLUGINS - quick access to PluginManager</li> + * <li>#DBLIST - quick access to DbManager</li> + * <li>#FUNCTIONS - quick access to FunctionManager</li> + * <li>#CFG - quick access to Config</li> + * </ul> + */ + + +/** + * @brief Main application class. + * This is an entry point for all services. + * Get all managers, services, etc from this class. + * It is a singleton. + */ +class API_EXPORT SQLiteStudio : public QObject +{ + Q_OBJECT + + DECLARE_SINGLETON(SQLiteStudio) + + public: + /** + * @brief Initializes SQLiteStudio object. + * @param cmdListArguments Command line arguments. + * @param pluginLoadingHandler Factory for producing plugin loader. + * + * Initialization process involves creating of all internal objects (managers, etc.) + * and reading necessary configuration. It also interpreted command line arguments + * and applies them. + * + * The plugin loader factory (handler) is used to solve issue with GUI symbols visibility. while loading code being placed in the core shared library. + * It should be null when starting SQLiteStudio in CLI mode and not null when starting GUI client. See PluginLoadingHandler for more details on that. + * + * See parseCmdLineArgs() for details on supported options. + */ + void init(const QStringList& cmdListArguments, bool guiAvailable); + + void initPlugins(); + + /** + * @brief Gets environment variable value. + * @param name Name of the environment variable. + * @param defaultValue Default value to be returned if the environment variable is not defined. + * @return Either value of the environment variable - if defined - or the value passed as defaultValue. + * + * This method provides cross-platform way to get environment variable value. + * Internally it uses QProcessEnvironment, but while it's expensive to initialize it every time you access environment, + * it keeps the single instance of that object and lets you query variables by name. + */ + QString getEnv(const QString& name, const QString& defaultValue = QString()); + + /** + * @brief Creates new DbAttacher instance for given database. + * @param db Database to create attacher for. + * @return Attacher instance. + */ + DbAttacher* createDbAttacher(Db* db); + + bool isGuiAvailable() const; + + Config* getConfig() const; + void setConfig(Config* value); + + DbManager* getDbManager() const; + void setDbManager(DbManager* value); + + FunctionManager* getFunctionManager() const; + void setFunctionManager(FunctionManager* value); + + PluginManager* getPluginManager() const; + void setPluginManager(PluginManager* value); + + DbAttacherFactory* getDbAttacherFactory() const; + void setDbAttacherFactory(DbAttacherFactory* value); + + CollationManager* getCollationManager() const; + void setCollationManager(CollationManager* value); + + ExportManager* getExportManager() const; + void setExportManager(ExportManager* value); + + int getVersion() const; + QString getVersionString() const; + + ImportManager* getImportManager() const; + void setImportManager(ImportManager* value); + + PopulateManager* getPopulateManager() const; + void setPopulateManager(PopulateManager* value); + + CodeFormatter* getCodeFormatter() const; + void setCodeFormatter(CodeFormatter* codeFormatter); + + BugReporter* getBugReporter() const; + void setBugReporter(BugReporter* value); + + QString getHomePage() const; + QString getForumPage() const; + QString getUserManualPage() const; + QString getSqliteDocsPage() const; + + UpdateManager* getUpdateManager() const; + void setUpdateManager(UpdateManager* value); + + bool getImmediateQuit() const; + void setImmediateQuit(bool value); + + ExtraLicenseManager* getExtraLicenseManager() const; + void setExtraLicenseManager(ExtraLicenseManager* value); + + private: + /** + * @brief Creates singleton instance. + * + * It doesn't initialize anything, just constructs object. + * Initialization of member data is done by init() method. + */ + SQLiteStudio(); + + /** + * @brief Deinitializes object. + * + * Calls cleanUp(). + */ + ~SQLiteStudio(); + + /** + * @brief Code formatter service. + */ + CodeFormatter* codeFormatter = nullptr; + + /** + * @brief The application environment. + * + * This variable represents environment of the application. + * It provides access to environment variables. + */ + QProcessEnvironment* env = nullptr; + + /** + * @brief List of command line arguments. + * + * It's a copy of arguments passed to application in command line. + */ + QStringList cmdLineArgs; + + bool guiAvailable = false; + bool immediateQuit = false; + Config* config = nullptr; + DbManager* dbManager = nullptr; + FunctionManager* functionManager = nullptr; + PluginManager* pluginManager = nullptr; + DbAttacherFactory* dbAttacherFactory = nullptr; + CollationManager* collationManager = nullptr; + ExportManager* exportManager = nullptr; + ImportManager* importManager = nullptr; + PopulateManager* populateManager = nullptr; + BugReporter* bugReporter = nullptr; + UpdateManager* updateManager = nullptr; + ExtraLicenseManager* extraLicenseManager = nullptr; + + private slots: + void pluginLoaded(Plugin* plugin,PluginType* pluginType); + void pluginToBeUnloaded(Plugin* plugin,PluginType* pluginType); + void pluginUnloaded(const QString& pluginName,PluginType* pluginType); + + /** + * @brief Cleans up all internal objects. + * + * Deletes all internal objects. It's called from destructor. + */ + void cleanUp(); + + public slots: + /** + * @brief Updates code formatter with available plugins. + * + * Calls CodeFormatter's fullUpdate() method to read available formatters. + * This also reads formatters selected in config. + */ + void updateCodeFormatter(); + + /** + * @brief Updates code formater with selected plugins. + * + * Doesn't change list of available formatters, but reads new selected formatters from config. + */ + void updateCurrentCodeFormatter(); +}; + +/** + * @def SQLITESTUDIO + * @brief Global entry point for application services. + * + * This macro actually calls SQLiteStudio::getInstance(), which returns singleton instance + * of the main class, which is SQLiteStudio. Use this class as starting point + * to access all services of the application (database manager, plugins manager, etc). + * This singleton instance is created at the very begining of application start (in main()) + * and so can be used from pretty much everywhere in the code. + * + * Quick example of getting all databases registered in the application, iterating through them and printing + * their name to standard output: + * @code + #include "qio.h" + #include "sqlitestudio.h" + + void someFunction() + { + QList<Db*> dblist = SQLITESTUDIO->getDbManager()->getDbList(); + foreach (Db* db, dblist) + { + qOut << db->getName(); + } + } + @endcode + */ +#define SQLITESTUDIO SQLiteStudio::getInstance() + +#endif // SQLITESTUDIO_H diff --git a/SQLiteStudio3/coreSQLiteStudio/tablemodifier.cpp b/SQLiteStudio3/coreSQLiteStudio/tablemodifier.cpp new file mode 100644 index 0000000..7555714 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/tablemodifier.cpp @@ -0,0 +1,695 @@ +#include "tablemodifier.h" +#include "common/utils_sql.h" +#include "parser/parser.h" +#include "schemaresolver.h" +#include "selectresolver.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqliteselect.h" +#include "parser/ast/sqliteupdate.h" +#include "parser/ast/sqliteinsert.h" +#include "parser/ast/sqlitedelete.h" +#include <QDebug> + +// TODO no attach/temp db name support in this entire class +// mainly in calls to schema resolver, but maybe other stuff too + +TableModifier::TableModifier(Db* db, const QString& table) : + db(db), + table(table) +{ + init(); +} + +TableModifier::TableModifier(Db* db, const QString& database, const QString& table) : + db(db), + database(database), + table(table) +{ + init(); +} + +void TableModifier::alterTable(SqliteCreateTablePtr newCreateTable) +{ + tableColMap = newCreateTable->getModifiedColumnsMap(true); + existingColumns = newCreateTable->getColumnNames(); + newName = newCreateTable->table; + + QString tempTableName; + if (table.compare(newName, Qt::CaseInsensitive) == 0) + tempTableName = renameToTemp(); + + newCreateTable->rebuildTokens(); + sqls << newCreateTable->detokenize(); + copyDataTo(newCreateTable); + + // If temp table was created, it means that table name hasn't changed. In that case we need to cleanup temp table (drop it). + // Otherwise, the table name has changed, therefor there still remains the old table which we copied data from - we need to drop it here. + sqls << QString("DROP TABLE %1;").arg(wrapObjIfNeeded(tempTableName.isNull() ? originalTable : tempTableName, dialect)); + + handleFks(); + handleIndexes(); + handleTriggers(); + handleViews(); +} + +void TableModifier::renameTo(const QString& newName) +{ + if (!createTable) + return; + + if (dialect == Dialect::Sqlite3) + { + sqls << QString("ALTER TABLE %1 RENAME TO %2;").arg(wrapObjIfNeeded(table, dialect), wrapObjIfNeeded(newName, dialect)); + } + else + { + sqls << QString("CREATE TABLE %1 AS SELECT * FROM %2;").arg(wrapObjIfNeeded(newName, dialect), wrapObjIfNeeded(table, dialect)) + << QString("DROP TABLE %1;").arg(wrapObjIfNeeded(table, dialect)); + } + + table = newName; + createTable->table = newName; +} + +QString TableModifier::renameToTemp() +{ + QString name = getTempTableName(); + renameTo(name); + return name; +} + +void TableModifier::copyDataTo(const QString& targetTable) +{ + SchemaResolver resolver(db); + QStringList targetColumns = resolver.getTableColumns(targetTable); + QStringList colsToCopy; + foreach (SqliteCreateTable::Column* column, createTable->columns) + if (targetColumns.contains(column->name, Qt::CaseInsensitive)) + colsToCopy << wrapObjIfNeeded(column->name, dialect); + + copyDataTo(targetTable, colsToCopy, colsToCopy); +} + +void TableModifier::handleFks() +{ + SchemaResolver resolver(db); + + QStringList fkTables = resolver.getFkReferencingTables(originalTable); + + foreach (const QString& fkTable, fkTables) + { + TableModifier subModifier(db, fkTable); + if (!subModifier.isValid()) + { + warnings << QObject::tr("Table %1 is referencing table %2, but the foreign key definition will not be updated for new table definition " + "due to problems while parsing DDL of the table %3.").arg(fkTable, originalTable, fkTable); + continue; + } + + subModifier.tableColMap = tableColMap; + subModifier.existingColumns = existingColumns; + subModifier.newName = newName; + subModifier.subHandleFks(originalTable); + sqls += subModifier.generateSqls(); + modifiedTables << fkTable; + + modifiedTables += subModifier.getModifiedTables(); + modifiedIndexes += subModifier.getModifiedIndexes(); + modifiedTriggers += subModifier.getModifiedTriggers(); + modifiedViews += subModifier.getModifiedViews(); + + warnings += subModifier.getWarnings(); + errors += subModifier.getErrors(); + } +} + +void TableModifier::subHandleFks(const QString& oldName) +{ + bool modified = false; + foreach (SqliteCreateTable::Constraint* fk, createTable->getForeignKeysByTable(oldName)) + { + if (subHandleFks(fk->foreignKey, oldName)) + modified = true; + } + + foreach (SqliteCreateTable::Column::Constraint* fk, createTable->getColumnForeignKeysByTable(oldName)) + { + if (subHandleFks(fk->foreignKey, oldName)) + modified = true; + } + + if (!modified) + return; + + QString tempName = renameToTemp(); + + createTable->table = originalTable; + createTable->rebuildTokens(); + sqls << createTable->detokenize(); + + copyDataTo(originalTable); + + sqls << QString("DROP TABLE %1;").arg(wrapObjIfNeeded(tempName, dialect)); + + simpleHandleIndexes(); + simpleHandleTriggers(); +} + +bool TableModifier::subHandleFks(SqliteForeignKey* fk, const QString& oldName) +{ + bool modified = false; + + // Table + if (handleName(oldName, fk->foreignTable)) + modified = true; + + // Columns + if (handleIndexedColumns(fk->indexedColumns)) + modified = true; + + return modified; +} + +bool TableModifier::handleName(const QString& oldName, QString& valueToUpdate) +{ + if (newName.compare(oldName, Qt::CaseInsensitive) == 0) + return false; + + if (valueToUpdate.compare(oldName, Qt::CaseInsensitive) == 0) + { + valueToUpdate = newName; + return true; + } + return false; +} + +bool TableModifier::handleIndexedColumns(QList<SqliteIndexedColumn*>& columnsToUpdate) +{ + bool modified = false; + QString lowerName; + QMutableListIterator<SqliteIndexedColumn*> it(columnsToUpdate); + while (it.hasNext()) + { + SqliteIndexedColumn* idxCol = it.next(); + + // If column was modified, assign new name + lowerName = idxCol->name.toLower(); + if (tableColMap.contains(lowerName)) + { + idxCol->name = tableColMap[lowerName]; + modified = true; + continue; + } + + // It wasn't modified, but it's not on existing columns list? Remove it. + if (indexOf(existingColumns, idxCol->name, Qt::CaseInsensitive) == -1) + { + it.remove(); + modified = true; + } + } + return modified; +} + +bool TableModifier::handleColumnNames(QStringList& columnsToUpdate) +{ + bool modified = false; + QString lowerName; + QMutableStringListIterator it(columnsToUpdate); + while (it.hasNext()) + { + it.next(); + + // If column was modified, assign new name + lowerName = it.value().toLower(); + if (tableColMap.contains(lowerName)) + { + it.value() = tableColMap[lowerName]; + modified = true; + continue; + } + + // It wasn't modified, but it's not on existing columns list? Remove it. + if (indexOf(existingColumns, it.value(), Qt::CaseInsensitive) == -1) + { + it.remove(); + modified = true; + } + } + return modified; +} + +bool TableModifier::handleColumnTokens(TokenList& columnsToUpdate) +{ + bool modified = false; + QString lowerName; + QMutableListIterator<TokenPtr> it(columnsToUpdate); + while (it.hasNext()) + { + TokenPtr token = it.next(); + + // If column was modified, assign new name + lowerName = token->value.toLower(); + if (tableColMap.contains(lowerName)) + { + token->value = tableColMap[lowerName]; + modified = true; + continue; + } + + // It wasn't modified, but it's not on existing columns list? + // In case of SELECT it's complicated to remove that token from anywhere + // in the statement. Replacing it with NULL is a kind of compromise. + if (indexOf(existingColumns, token->value, Qt::CaseInsensitive) == -1) + { + token->value = "NULL"; + modified = true; + } + } + return modified; +} + +bool TableModifier::handleUpdateColumns(SqliteUpdate* update) +{ + bool modified = false; + QString lowerName; + QMutableListIterator<SqliteUpdate::ColumnAndValue> it(update->keyValueMap); + while (it.hasNext()) + { + it.next(); + + // If column was modified, assign new name + lowerName = it.value().first.toLower(); + if (tableColMap.contains(lowerName)) + { + it.value().first = tableColMap[lowerName]; + modified = true; + continue; + } + + // It wasn't modified, but it's not on existing columns list? Remove it. + if (indexOf(existingColumns, it.value().first, Qt::CaseInsensitive) == -1) + { + it.remove(); + modified = true; + } + } + return modified; +} + +QStringList TableModifier::getModifiedViews() const +{ + return modifiedViews; +} + +bool TableModifier::hasMessages() const +{ + return errors.size() > 0 || warnings.size() > 0; +} + +QStringList TableModifier::getModifiedTriggers() const +{ + return modifiedTriggers; +} + +QStringList TableModifier::getModifiedIndexes() const +{ + return modifiedIndexes; +} + +QStringList TableModifier::getModifiedTables() const +{ + return modifiedTables; +} + +void TableModifier::copyDataTo(SqliteCreateTablePtr newCreateTable) +{ + QStringList existingColumns = createTable->getColumnNames(); + + QStringList srcCols; + QStringList dstCols; + foreach (SqliteCreateTable::Column* column, newCreateTable->columns) + { + if (!existingColumns.contains(column->originalName)) + continue; // not copying columns that didn't exist before + + srcCols << wrapObjIfNeeded(column->originalName, dialect); + dstCols << wrapObjIfNeeded(column->name, dialect); + } + + copyDataTo(newCreateTable->table, srcCols, dstCols); +} + +void TableModifier::handleIndexes() +{ + SchemaResolver resolver(db); + QList<SqliteCreateIndexPtr> parsedIndexesForTable = resolver.getParsedIndexesForTable(originalTable); + foreach (SqliteCreateIndexPtr index, parsedIndexesForTable) + handleIndex(index); +} + +void TableModifier::handleIndex(SqliteCreateIndexPtr index) +{ + handleName(originalTable, index->table); + handleIndexedColumns(index->indexedColumns); + index->rebuildTokens(); + sqls << index->detokenize(); + modifiedIndexes << index->index; + + // TODO partial index needs handling expr here +} + +void TableModifier::handleTriggers() +{ + SchemaResolver resolver(db); + QList<SqliteCreateTriggerPtr> parsedTriggersForTable = resolver.getParsedTriggersForTable(originalTable, true); + foreach (SqliteCreateTriggerPtr trig, parsedTriggersForTable) + handleTrigger(trig); +} + +void TableModifier::handleTrigger(SqliteCreateTriggerPtr trigger) +{ + handleName(originalTable, trigger->table); + if (trigger->event->type == SqliteCreateTrigger::Event::UPDATE_OF) + handleColumnNames(trigger->event->columnNames); + + SqliteQuery* newQuery = nullptr; + QList<SqliteQuery*> newQueries; + foreach (SqliteQuery* query, trigger->queries) + { + // The handleTriggerQuery() may delete the input query object. Don't refer to it later. + newQuery = handleTriggerQuery(query, trigger->trigger); + if (newQuery) + newQueries << newQuery; + else + errors << QObject::tr("Cannot not update trigger %1 according to table %2 modification.").arg(trigger->trigger, originalTable); + } + trigger->queries = newQueries; + + trigger->rebuildTokens(); + sqls << trigger->detokenize(); + modifiedTriggers << trigger->trigger; +} + +void TableModifier::handleViews() +{ + SchemaResolver resolver(db); + QList<SqliteCreateViewPtr> parsedViewsForTable = resolver.getParsedViewsForTable(originalTable); + foreach (SqliteCreateViewPtr view, parsedViewsForTable) + handleView(view); +} + +void TableModifier::handleView(SqliteCreateViewPtr view) +{ + SqliteSelect* newSelect = handleSelect(view->select); + if (!newSelect) + { + errors << QObject::tr("Cannot not update view %1 according to table %2 modifications.\nThe view will remain as it is.").arg(view->view, originalTable); + return; + } + + delete view->select; + view->select = newSelect; + view->select->setParent(view.data()); + view->rebuildTokens(); + + sqls << QString("DROP VIEW %1;").arg(wrapObjIfNeeded(view->view, dialect)); + sqls << view->detokenize(); + + simpleHandleTriggers(view->view); + + modifiedViews << view->view; +} + +SqliteQuery* TableModifier::handleTriggerQuery(SqliteQuery* query, const QString& trigName) +{ + SqliteSelect* select = dynamic_cast<SqliteSelect*>(query); + if (select) + return handleSelect(select); + + SqliteUpdate* update = dynamic_cast<SqliteUpdate*>(query); + if (update) + return handleTriggerUpdate(update, trigName); + + SqliteInsert* insert = dynamic_cast<SqliteInsert*>(query); + if (insert) + return handleTriggerInsert(insert, trigName); + + SqliteDelete* del = dynamic_cast<SqliteDelete*>(query); + if (del) + return handleTriggerDelete(del, trigName); + + return nullptr; +} + +SqliteSelect* TableModifier::handleSelect(SqliteSelect* select) +{ + // Table name + TokenList tableTokens = select->getContextTableTokens(false); + foreach (TokenPtr token, tableTokens) + { + if (token->value.compare(originalTable, Qt::CaseInsensitive) == 0) + token->value = newName; + } + + // Column names + TokenList columnTokens = select->getContextColumnTokens(false); + SelectResolver selectResolver(db, select->detokenize()); + QList<SelectResolver::Column> columns = selectResolver.translateToColumns(select, columnTokens); + + TokenList columnTokensToChange; + for (int i = 0; i < columnTokens.size(); i++) + { + if (columns[i].type != SelectResolver::Column::COLUMN) + continue; + + if (originalTable.compare(columns[i].table, Qt::CaseInsensitive) == 0) + columnTokensToChange << columnTokens[i]; + } + + handleColumnTokens(columnTokensToChange); + + // Rebuilding modified tokens into the select object + QString selectSql = select->detokenize(); + SqliteQueryPtr queryPtr = parseQuery(selectSql); + if (!queryPtr) + { + qCritical() << "Could not parse modified SELECT in TableModifier::handleSelect()."; + return nullptr; + } + + SqliteSelectPtr selectPtr = queryPtr.dynamicCast<SqliteSelect>(); + if (!selectPtr) + { + qCritical() << "Could cast into SELECT in TableModifier::handleSelect()."; + return nullptr; + } + + return new SqliteSelect(*selectPtr.data()); +} + +SqliteUpdate* TableModifier::handleTriggerUpdate(SqliteUpdate* update, const QString& trigName) +{ + // Table name + if (update->table.compare(originalTable, Qt::CaseInsensitive) == 0) + update->table = newName; + + // Column names + handleUpdateColumns(update); + + // Any embedded selects + bool embedSelectsOk = handleSubSelects(update); + if (!embedSelectsOk) + { + warnings << QObject::tr("There is a problem with updating an %1 statement within %2 trigger. " + "One of the SELECT substatements which might be referring to table %3 cannot be properly modified. " + "Manual update of the trigger may be necessary.").arg("UPDATE").arg(trigName).arg(originalTable); + } + + return update; +} + +SqliteInsert* TableModifier::handleTriggerInsert(SqliteInsert* insert, const QString& trigName) +{ + // Table name + if (insert->table.compare(originalTable, Qt::CaseInsensitive) == 0) + insert->table = newName; + + // Column names + handleColumnNames(insert->columnNames); + + // Any embedded selects + bool embedSelectsOk = handleSubSelects(insert); + if (!embedSelectsOk) + { + warnings << QObject::tr("There is a problem with updating an %1 statement within %2 trigger. " + "One of the SELECT substatements which might be referring to table %3 cannot be properly modified. " + "Manual update of the trigger may be necessary.").arg("INSERT", trigName, originalTable); + } + + return insert; +} + +SqliteDelete* TableModifier::handleTriggerDelete(SqliteDelete* del, const QString& trigName) +{ + // Table name + if (del->table.compare(originalTable, Qt::CaseInsensitive) == 0) + del->table = newName; + + // Any embedded selects + bool embedSelectsOk = handleSubSelects(del); + if (!embedSelectsOk) + { + warnings << QObject::tr("There is a problem with updating an %1 statement within %2 trigger. " + "One of the SELECT substatements which might be referring to table %3 cannot be properly modified. " + "Manual update of the trigger may be necessary.").arg("DELETE", trigName, originalTable); + } + + return del; +} + +bool TableModifier::handleSubSelects(SqliteStatement* stmt) +{ + bool embedSelectsOk = true; + QList<SqliteSelect*> selects = stmt->getAllTypedStatements<SqliteSelect>(); + SqliteExpr* expr = nullptr; + foreach (SqliteSelect* select, selects) + { + expr = dynamic_cast<SqliteExpr*>(select->parentStatement()); + if (!expr) + { + embedSelectsOk = false; + continue; + } + + if (!handleExprWithSelect(expr)) + embedSelectsOk = false; + } + return embedSelectsOk; +} + +bool TableModifier::handleExprWithSelect(SqliteExpr* expr) +{ + if (!expr->select) + { + qCritical() << "No SELECT in TableModifier::handleExprWithSelect()"; + return false; + } + + SqliteSelect* newSelect = handleSelect(expr->select); + if (!newSelect) + { + qCritical() << "Could not generate new SELECT in TableModifier::handleExprWithSelect()"; + return false; + } + + delete expr->select; + expr->select = newSelect; + expr->select->setParent(expr); + return true; +} + +void TableModifier::simpleHandleIndexes() +{ + SchemaResolver resolver(db); + QList<SqliteCreateIndexPtr> parsedIndexesForTable = resolver.getParsedIndexesForTable(originalTable); + foreach (SqliteCreateIndexPtr index, parsedIndexesForTable) + sqls << index->detokenize(); +} + +void TableModifier::simpleHandleTriggers(const QString& view) +{ + SchemaResolver resolver(db); + QList<SqliteCreateTriggerPtr> parsedTriggers ; + if (!view.isNull()) + parsedTriggers = resolver.getParsedTriggersForView(view); + else + parsedTriggers = resolver.getParsedTriggersForTable(originalTable); + + foreach (SqliteCreateTriggerPtr trig, parsedTriggers) + sqls << trig->detokenize(); +} + +SqliteQueryPtr TableModifier::parseQuery(const QString& ddl) +{ + Parser parser(dialect); + if (!parser.parse(ddl) || parser.getQueries().size() == 0) + return SqliteQueryPtr(); + + return parser.getQueries().first(); +} + +void TableModifier::copyDataTo(const QString& targetTable, const QStringList& srcCols, const QStringList& dstCols) +{ + sqls << QString("INSERT INTO %1 (%2) SELECT %3 FROM %4;").arg(wrapObjIfNeeded(targetTable, dialect), dstCols.join(", "), srcCols.join(", "), + wrapObjIfNeeded(table, dialect)); +} + +QStringList TableModifier::generateSqls() const +{ + return sqls; +} + +bool TableModifier::isValid() const +{ + return !createTable.isNull(); +} + +QStringList TableModifier::getErrors() const +{ + return errors; +} + +QStringList TableModifier::getWarnings() const +{ + return warnings; +} + +void TableModifier::init() +{ + dialect = db->getDialect(); + originalTable = table; + parseDdl(); +} + +void TableModifier::parseDdl() +{ + SchemaResolver resolver(db); + QString ddl = resolver.getObjectDdl(database, table, SchemaResolver::TABLE); + if (ddl.isNull()) + { + qCritical() << "Could not find object's ddl while parsing table ddl in the TableModifier."; + return; + } + + Parser parser(dialect); + if (!parser.parse(ddl)) + { + qCritical() << "Could not parse table's' ddl in the TableModifier. The ddl is:" << ddl; + return; + } + + if (parser.getQueries().size() != 1) + { + qCritical() << "Parsed ddl produced more or less than 1 query in the TableModifier. The ddl is:" << ddl; + return; + } + + SqliteQueryPtr query = parser.getQueries().first(); + SqliteCreateTablePtr createTable = query.dynamicCast<SqliteCreateTable>(); + if (!createTable) + { + qCritical() << "Parsed ddl produced something else than CreateTable statement in the TableModifier. The ddl is:" << ddl; + return; + } + + this->createTable = createTable; +} + +QString TableModifier::getTempTableName() const +{ + SchemaResolver resolver(db); + return resolver.getUniqueName("sqlitestudio_temp_table"); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/tablemodifier.h b/SQLiteStudio3/coreSQLiteStudio/tablemodifier.h new file mode 100644 index 0000000..6a39b33 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/tablemodifier.h @@ -0,0 +1,114 @@ +#ifndef TABLEMODIFIER_H +#define TABLEMODIFIER_H + +#include "db/db.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqliteupdate.h" +#include "parser/ast/sqliteinsert.h" +#include "parser/ast/sqlitedelete.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqlitecreateview.h" + +class API_EXPORT TableModifier +{ + public: + TableModifier(Db* db, const QString& table); + TableModifier(Db* db, const QString& database, const QString& table); + + void alterTable(SqliteCreateTablePtr newCreateTable); + + QStringList generateSqls() const; + bool isValid() const; + QStringList getErrors() const; + QStringList getWarnings() const; + QStringList getModifiedTables() const; + QStringList getModifiedIndexes() const; + QStringList getModifiedTriggers() const; + QStringList getModifiedViews() const; + bool hasMessages() const; + + private: + void init(); + void parseDdl(); + QString getTempTableName() const; + void copyDataTo(const QString& targetTable, const QStringList& srcCols, const QStringList& dstCols); + void renameTo(const QString& newName); + QString renameToTemp(); + void copyDataTo(const QString& table); + void copyDataTo(SqliteCreateTablePtr newCreateTable); + + void handleIndexes(); + void handleIndex(SqliteCreateIndexPtr index); + void handleTriggers(); + void handleTrigger(SqliteCreateTriggerPtr trigger); + void handleViews(); + void handleView(SqliteCreateViewPtr view); + SqliteQuery* handleTriggerQuery(SqliteQuery* query, const QString& trigName); + SqliteSelect* handleSelect(SqliteSelect* select); + SqliteUpdate* handleTriggerUpdate(SqliteUpdate* update, const QString& trigName); + SqliteInsert* handleTriggerInsert(SqliteInsert* insert, const QString& trigName); + SqliteDelete* handleTriggerDelete(SqliteDelete* del, const QString& trigName); + bool handleSubSelects(SqliteStatement* stmt); + bool handleExprWithSelect(SqliteExpr* expr); + void simpleHandleIndexes(); + void simpleHandleTriggers(const QString& view = QString::null); + SqliteQueryPtr parseQuery(const QString& ddl); + + /** + * @brief alterTableHandleFks + * @param newCreateTable + * Finds all tables referencing currently modified table and updates their referenced table name and columns. + */ + void handleFks(); + void subHandleFks(const QString& oldName); + bool subHandleFks(SqliteForeignKey* fk, const QString& oldName); + + bool handleName(const QString& oldName, QString& valueToUpdate); + bool handleIndexedColumns(QList<SqliteIndexedColumn*>& columnsToUpdate); + bool handleColumnNames(QStringList& columnsToUpdate); + bool handleColumnTokens(TokenList& columnsToUpdate); + bool handleUpdateColumns(SqliteUpdate* update); + + Db* db = nullptr; + Dialect dialect; + + /** + * @brief database Database name. The "main" is default. + * Other databases (temp, attached...) are not supported at the moment. + */ + QString database; + + /** + * @brief table Current table name (after renaming) + */ + QString table; + + /** + * @brief originalTable Initial table name, before any renaming. + */ + QString originalTable; + + /** + * @brief createTable Original DDL. + */ + SqliteCreateTablePtr createTable; + + /** + * @brief sqls Statements to be executed to make changes real. + */ + QStringList sqls; + + QStringList warnings; + QStringList errors; + + QString newName; + QStringList existingColumns; + QHash<QString, QString> tableColMap; + QStringList modifiedTables; + QStringList modifiedIndexes; + QStringList modifiedTriggers; + QStringList modifiedViews; +}; + +#endif // TABLEMODIFIER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/tsvserializer.cpp b/SQLiteStudio3/coreSQLiteStudio/tsvserializer.cpp new file mode 100644 index 0000000..486763b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/tsvserializer.cpp @@ -0,0 +1,103 @@ +#include "tsvserializer.h" + +#ifdef Q_OS_MACX +QString TsvSerializer::rowSeparator = "\r"; +#else +QString TsvSerializer::rowSeparator = "\n"; +#endif +QString TsvSerializer::columnSeparator = "\t"; + +QString TsvSerializer::serialize(const QList<QStringList>& data) +{ + QStringList outputRows; + + foreach (const QStringList& dataRow, data) + outputRows << serialize(dataRow); + + return outputRows.join(rowSeparator); +} + +QString TsvSerializer::serialize(const QStringList& data) +{ + QString value; + bool hasQuote; + QStringList outputCells; + foreach (const QString& rowValue, data) + { + value = rowValue; + + hasQuote = value.contains("\""); + if (value.contains(columnSeparator) || value.contains(rowSeparator)) + { + if (hasQuote) + value.replace("\"", "\"\""); + + value = "\""+value+"\""; + } + + outputCells << value; + } + + return outputCells.join(columnSeparator); +} + +QList<QStringList> TsvSerializer::deserialize(const QString& data) +{ + QList<QStringList> rows; + QStringList cells; + + int pos = 0; + int lgt = data.length(); + bool quotes = false; + QString field = ""; + QChar c; + + while (pos < lgt) + { + c = data[pos]; + if (!quotes && c == '"' ) + { + if (field.isEmpty()) + quotes = true; + else + field += c; + } + else if (quotes && c == '"' ) + { + if (pos + 1 < data.length() && data[pos+1] == '"' ) + { + field += c; + pos++; + } + else + { + quotes = false; + } + } + else if (!quotes && c == columnSeparator) + { + cells << field; + field.clear(); + } + else if (!quotes && c == rowSeparator) + { + cells << field; + rows << cells; + cells.clear(); + field.clear(); + } + else + { + field += c; + } + pos++; + } + + if (field.size() > 0) + cells << field; + + if (cells.size() > 0) + rows << cells; + + return rows; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/tsvserializer.h b/SQLiteStudio3/coreSQLiteStudio/tsvserializer.h new file mode 100644 index 0000000..efb3934 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/tsvserializer.h @@ -0,0 +1,20 @@ +#ifndef TSVSERIALIZER_H +#define TSVSERIALIZER_H + +#include "coreSQLiteStudio_global.h" +#include "common/global.h" +#include <QStringList> + +class API_EXPORT TsvSerializer +{ + public: + static QString serialize(const QList<QStringList>& data); + static QString serialize(const QStringList& data); + static QList<QStringList> deserialize(const QString& data); + + private: + static QString rowSeparator; + static QString columnSeparator; +}; + +#endif // TSVSERIALIZER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/viewmodifier.cpp b/SQLiteStudio3/coreSQLiteStudio/viewmodifier.cpp new file mode 100644 index 0000000..36ab457 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/viewmodifier.cpp @@ -0,0 +1,127 @@ +#include "viewmodifier.h" +#include "common/utils_sql.h" +#include "parser/parser.h" +#include "schemaresolver.h" +#include "selectresolver.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "common/unused.h" + +ViewModifier::ViewModifier(Db* db, const QString& view) : + ViewModifier(db, "main", view) +{ +} + +ViewModifier::ViewModifier(Db* db, const QString& database, const QString& view) : + db(db), database(database), view(view) +{ + dialect = db->getDialect(); +} + +void ViewModifier::alterView(const QString& newView) +{ + Parser parser(dialect); + if (!parser.parse(newView) || parser.getQueries().size() == 0) + { + errors << QObject::tr("Could not parse DDL of the view to be created. Details: %1").arg(parser.getErrorString()); + return; + } + + SqliteQueryPtr query = parser.getQueries().first(); + createView = query.dynamicCast<SqliteCreateView>(); + + if (!createView) + { + errors << QObject::tr("Parsed query is not CREATE VIEW. It's: %1").arg(sqliteQueryTypeToString(query->queryType)); + return; + } + + alterView(createView); +} + +void ViewModifier::alterView(SqliteCreateViewPtr newView) +{ + createView = newView; + + addMandatorySql(QString("DROP VIEW %1").arg(wrapObjIfNeeded(view, dialect))); + addMandatorySql(newView->detokenize()); + + collectNewColumns(); + handleTriggers(); + + // TODO handle other views selecting from this view +} + +void ViewModifier::handleTriggers() +{ + SchemaResolver resolver(db); + QList<SqliteCreateTriggerPtr> triggers = resolver.getParsedTriggersForView(view, true); + foreach (SqliteCreateTriggerPtr trigger, triggers) + { + addOptionalSql(QString("DROP TRIGGER %1").arg(wrapObjIfNeeded(trigger->trigger, dialect))); + + if (!handleNewColumns(trigger)) + continue; + + addOptionalSql(trigger->detokenize()); + } +} + +bool ViewModifier::handleNewColumns(SqliteCreateTriggerPtr trigger) +{ + UNUSED(trigger); + // TODO update all occurances of columns in "UPDATE OF" and statements inside, just like it would be done in TableModifier. + return true; +} + +void ViewModifier::collectNewColumns() +{ + SelectResolver resolver(db, createView->select->detokenize()); + QList<QList<SelectResolver::Column> > multiColumns = resolver.resolve(createView->select); + if (multiColumns.size() < 1) + { + warnings << QObject::tr("SQLiteStudio was unable to resolve columns returned by the new view, " + "therefore it won't be able to tell which triggers might fail during the recreation process."); + return; + } + + foreach (const SelectResolver::Column& col, multiColumns.first()) + newColumns << col.column; +} + +void ViewModifier::addMandatorySql(const QString& sql) +{ + sqls << sql; + sqlMandatoryFlags << true; +} + +void ViewModifier::addOptionalSql(const QString& sql) +{ + + sqls << sql; + sqlMandatoryFlags << false; +} + +QStringList ViewModifier::generateSqls() const +{ + return sqls; +} + +QList<bool> ViewModifier::getMandatoryFlags() const +{ + return sqlMandatoryFlags; +} + +QStringList ViewModifier::getWarnings() const +{ + return warnings; +} + +QStringList ViewModifier::getErrors() const +{ + return errors; +} + +bool ViewModifier::hasMessages() const +{ + return errors.size() > 0 || warnings.size() > 0; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/viewmodifier.h b/SQLiteStudio3/coreSQLiteStudio/viewmodifier.h new file mode 100644 index 0000000..6d7c770 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/viewmodifier.h @@ -0,0 +1,53 @@ +#ifndef VIEWMODIFIER_H +#define VIEWMODIFIER_H + +#include "db/db.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include <QString> + +class API_EXPORT ViewModifier +{ + public: + ViewModifier(Db* db, const QString& view); + ViewModifier(Db* db, const QString& database, const QString& view); + + void alterView(const QString& newView); + void alterView(SqliteCreateViewPtr newView); + + QStringList generateSqls() const; + QList<bool> getMandatoryFlags() const; + QStringList getWarnings() const; + QStringList getErrors() const; + bool hasMessages() const; + + private: + void handleTriggers(); + void collectNewColumns(); + void addMandatorySql(const QString& sql); + void addOptionalSql(const QString& sql); + bool handleNewColumns(SqliteCreateTriggerPtr trigger); + + Db* db = nullptr; + Dialect dialect; + QString database; + QString view; + + /** + * @brief sqls Statements to be executed to make changes real. + */ + QStringList sqls; + QList<bool> sqlMandatoryFlags; + + QStringList warnings; + QStringList errors; + + /** + * @brief createView Original DDL. + */ + SqliteCreateViewPtr createView; + + QStringList newColumns; +}; + +#endif // VIEWMODIFIER_H |
