diff options
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio/common')
21 files changed, 3327 insertions, 0 deletions
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 |
