2017-01-21 18:16:57 +01:00
|
|
|
|
#include "util.h"
|
2017-08-23 13:27:23 +02:00
|
|
|
|
#include "CsvWriter.h"
|
2019-08-19 13:52:23 +02:00
|
|
|
|
#include "SqlLexer.h"
|
2017-02-04 11:54:18 +01:00
|
|
|
|
#include <QApplication>
|
|
|
|
|
|
#include <QTextStream>
|
|
|
|
|
|
#include <QClipboard>
|
2017-02-05 08:23:06 +01:00
|
|
|
|
#include <sstream>
|
2017-02-26 19:29:50 +01:00
|
|
|
|
#include <stdexcept>
|
2017-01-21 18:16:57 +01:00
|
|
|
|
|
|
|
|
|
|
// Supported range from microseconds to seconds
|
|
|
|
|
|
// min:sec to hours::min::sec
|
|
|
|
|
|
QString msfloatToHumanReadableString(float ms)
|
|
|
|
|
|
{
|
|
|
|
|
|
QString unit;
|
|
|
|
|
|
float val;
|
|
|
|
|
|
int deci = 2;
|
|
|
|
|
|
if (ms < 1.0f) {
|
|
|
|
|
|
val = ms * 1000.f;
|
|
|
|
|
|
//result = QString::asprintf("%0.3f", ms * 1000.0f);
|
|
|
|
|
|
unit = u8"μs";
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (ms >= 1000.0) {
|
|
|
|
|
|
val = ms / 1000.0f;
|
|
|
|
|
|
unit = "s";
|
|
|
|
|
|
if (val >= 60.0) {
|
|
|
|
|
|
int secs = val;
|
|
|
|
|
|
int min = secs / 60.0;
|
|
|
|
|
|
secs -= min * 60;
|
|
|
|
|
|
if (min >= 60) {
|
|
|
|
|
|
int hour = min / 60;
|
|
|
|
|
|
min -= hour * 60;
|
|
|
|
|
|
return QString::asprintf("%d:%02d:%02d", hour, min, secs);
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
return QString::asprintf("%02d:%02d", min, secs);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
val = ms;
|
|
|
|
|
|
unit = "ms";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// if (val >= 1000.f) {
|
|
|
|
|
|
// deci = 0;
|
|
|
|
|
|
// }
|
|
|
|
|
|
// else
|
|
|
|
|
|
if (val >= 100.f) {
|
|
|
|
|
|
deci = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (val >= 10.f) {
|
|
|
|
|
|
deci = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
QString result = QString::asprintf("%0.*f", deci, val);
|
|
|
|
|
|
return result + unit;
|
|
|
|
|
|
}
|
2017-02-04 11:54:18 +01:00
|
|
|
|
|
2017-02-05 13:35:41 +01:00
|
|
|
|
void exportTable(const QTableView *view, QTextStream &out)
|
|
|
|
|
|
{
|
|
|
|
|
|
auto model = view->model();
|
|
|
|
|
|
if (model) {
|
|
|
|
|
|
CsvWriter csv(&out);
|
|
|
|
|
|
csv.setSeperator('\t');
|
|
|
|
|
|
csv.setQuote('"');
|
|
|
|
|
|
|
2018-02-05 21:42:54 +01:00
|
|
|
|
auto cols = model->columnCount();
|
|
|
|
|
|
auto rows = model->rowCount();
|
2021-04-10 09:48:48 +02:00
|
|
|
|
|
|
|
|
|
|
// header
|
|
|
|
|
|
for (int col = 0; col < cols; ++col) {
|
|
|
|
|
|
auto display_text = model->headerData(col, Qt::Horizontal, Qt::DisplayRole).toString();
|
|
|
|
|
|
csv.writeField(display_text);
|
|
|
|
|
|
}
|
|
|
|
|
|
csv.nextRow();
|
|
|
|
|
|
|
2018-02-05 21:42:54 +01:00
|
|
|
|
for (auto row = 0; row < rows; ++row) {
|
2017-02-05 13:35:41 +01:00
|
|
|
|
for (int col = 0; col < cols; ++col) {
|
|
|
|
|
|
auto idx = model->index(row, col);
|
|
|
|
|
|
auto display_text = idx.data(Qt::DisplayRole).toString();
|
|
|
|
|
|
csv.writeField(display_text);
|
|
|
|
|
|
}
|
|
|
|
|
|
csv.nextRow();
|
|
|
|
|
|
}
|
|
|
|
|
|
out.flush();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-02-04 11:54:18 +01:00
|
|
|
|
void copySelectionToClipboard(const QTableView *view)
|
|
|
|
|
|
{
|
|
|
|
|
|
//QAbstractItemModel * model = resultModel; //view->model();
|
|
|
|
|
|
QItemSelectionModel * selection = view->selectionModel();
|
|
|
|
|
|
|
|
|
|
|
|
QModelIndexList selectedIndexes = selection->selectedIndexes();
|
|
|
|
|
|
|
|
|
|
|
|
if (selectedIndexes.count() > 0) {
|
|
|
|
|
|
QString clipboard_string;
|
2021-03-06 13:13:31 +01:00
|
|
|
|
QTextStream out(&clipboard_string, QTextStream::WriteOnly);
|
2017-02-04 11:54:18 +01:00
|
|
|
|
CsvWriter csv(&out);
|
|
|
|
|
|
csv.setSeperator('\t');
|
|
|
|
|
|
csv.setQuote('"');
|
|
|
|
|
|
|
|
|
|
|
|
int previous_row = selectedIndexes[0].row();
|
|
|
|
|
|
// for (int i = 0; i < selectedIndexes.count(); ++i) {
|
|
|
|
|
|
// QModelIndex current = selectedIndexes[i];
|
|
|
|
|
|
for (auto current : selectedIndexes) {
|
|
|
|
|
|
if (current.row() > previous_row) {
|
|
|
|
|
|
csv.nextRow();
|
|
|
|
|
|
previous_row = current.row();
|
|
|
|
|
|
}
|
|
|
|
|
|
QString display_text = current.data(Qt::DisplayRole).toString();
|
|
|
|
|
|
csv.writeField(display_text);
|
|
|
|
|
|
}
|
|
|
|
|
|
out.flush();
|
|
|
|
|
|
QApplication::clipboard()->setText(clipboard_string);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2017-02-05 08:23:06 +01:00
|
|
|
|
|
2019-11-02 14:54:55 +01:00
|
|
|
|
QString EscapeForCString(QString qi)
|
|
|
|
|
|
{
|
|
|
|
|
|
int escape_count = 0;
|
|
|
|
|
|
for (QChar c : qi)
|
|
|
|
|
|
if (c == '"' || c == '\\') ++escape_count;
|
|
|
|
|
|
|
|
|
|
|
|
QString out;
|
|
|
|
|
|
out.reserve(qi.size() + escape_count);
|
|
|
|
|
|
for (QChar c : qi)
|
|
|
|
|
|
if (c == '"' || c == '\\') {
|
|
|
|
|
|
out += "\\";
|
|
|
|
|
|
out += c;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
out += c;
|
|
|
|
|
|
return out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-19 13:52:23 +02:00
|
|
|
|
QString ConvertToMultiLineCString(const QString &in_)
|
2017-02-05 08:23:06 +01:00
|
|
|
|
{
|
2017-08-23 13:27:23 +02:00
|
|
|
|
// We need to atleast escape " and \ and also any multi byte utf8 char
|
2017-02-05 08:23:06 +01:00
|
|
|
|
|
2019-08-19 13:52:23 +02:00
|
|
|
|
// remove empty lines at start
|
|
|
|
|
|
int last_nl_idx = 0;
|
|
|
|
|
|
for (int idx = 0; idx < in_.length(); ++idx) {
|
|
|
|
|
|
QChar c = in_[idx];
|
|
|
|
|
|
if (c == '\n') last_nl_idx = idx+1;
|
|
|
|
|
|
if (!c.isSpace()) {
|
|
|
|
|
|
break;
|
2017-02-05 08:23:06 +01:00
|
|
|
|
}
|
2019-08-19 13:52:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
QString in = in_.right(in_.length() - last_nl_idx);
|
|
|
|
|
|
int idx;
|
|
|
|
|
|
for (idx = in.length() - 1; idx >= 0 && in[idx].isSpace(); --idx) ;
|
|
|
|
|
|
++idx;
|
|
|
|
|
|
in.truncate(idx);
|
|
|
|
|
|
|
|
|
|
|
|
SqlLexer lexer(in, LexerState::Null, true);
|
|
|
|
|
|
QString out;
|
|
|
|
|
|
QString line = "\"";
|
|
|
|
|
|
QString comment;
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
SqlToken token = lexer.nextBasicToken();
|
|
|
|
|
|
if (token.ok) {
|
|
|
|
|
|
if (token.tokenType == BasicTokenType::Comment) {
|
|
|
|
|
|
// save comment is seperate variable
|
2021-03-06 13:13:31 +01:00
|
|
|
|
comment = "//" + token.out.right(token.out.length()-2);
|
2019-08-19 13:52:23 +02:00
|
|
|
|
// Trim whitespace on right
|
|
|
|
|
|
int idx;
|
|
|
|
|
|
for (idx = comment.length() - 1; idx >= 0 && comment[idx].isSpace(); --idx) ;
|
|
|
|
|
|
++idx;
|
|
|
|
|
|
comment.truncate(idx);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (token.tokenType == BasicTokenType::End || token.tokenType == BasicTokenType::NewLine) {
|
|
|
|
|
|
// trim right
|
|
|
|
|
|
{
|
|
|
|
|
|
int idx;
|
|
|
|
|
|
for (idx = line.length() - 1; idx >= 0 && line[idx].isSpace(); --idx) ;
|
|
|
|
|
|
++idx;
|
|
|
|
|
|
if (!comment.isEmpty()) {
|
|
|
|
|
|
// put the whitespace in front of the comment so it will be outside the contents of the string literal but alignment of comments is preserved
|
2021-03-06 13:13:31 +01:00
|
|
|
|
comment = line.right(line.length() - (idx)) + comment;
|
2019-08-19 13:52:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
line.truncate(idx);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
out += line;
|
|
|
|
|
|
if (token.tokenType == BasicTokenType::End) {
|
|
|
|
|
|
out += "\"";
|
|
|
|
|
|
out += comment;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
out += "\\n\"";
|
|
|
|
|
|
out += comment;
|
|
|
|
|
|
out += "\n";
|
|
|
|
|
|
line = "\"";
|
|
|
|
|
|
}
|
|
|
|
|
|
comment.clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
2019-11-02 14:54:55 +01:00
|
|
|
|
line += EscapeForCString(token.out);
|
2019-08-19 13:52:23 +02:00
|
|
|
|
}
|
2017-02-05 08:23:06 +01:00
|
|
|
|
}
|
|
|
|
|
|
else {
|
2019-08-19 13:52:23 +02:00
|
|
|
|
// error during lexical analysis, need to recover
|
|
|
|
|
|
throw std::runtime_error("Unrecognized input");
|
2017-02-05 08:23:06 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return out;
|
|
|
|
|
|
}
|
2017-02-26 19:29:50 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-10-05 16:02:06 +02:00
|
|
|
|
QString ConvertToMultiLineRawCppString(const QString &in)
|
|
|
|
|
|
{
|
|
|
|
|
|
const QString delim = "__SQL__";
|
|
|
|
|
|
|
|
|
|
|
|
QString out;
|
|
|
|
|
|
out.append("R\"" + delim + "(\n");
|
|
|
|
|
|
out.append(in.trimmed());
|
|
|
|
|
|
out.append("\n)" + delim + "\"");
|
|
|
|
|
|
return out;
|
|
|
|
|
|
}
|
2019-08-19 11:38:04 +02:00
|
|
|
|
|
2021-04-01 14:58:42 +02:00
|
|
|
|
namespace {
|
|
|
|
|
|
|
2026-10-18 15:58:13 +02:00
|
|
|
|
// index points at the first char of the token. On success index is placed after the opening quote and quote is set to '"' or '\''.
|
|
|
|
|
|
bool readOpeningQuote(const QString &in, int &index, QChar "e)
|
|
|
|
|
|
{
|
|
|
|
|
|
QChar c = in[index];
|
|
|
|
|
|
if (c == '\'' || c == '\"') {
|
|
|
|
|
|
quote = c;
|
|
|
|
|
|
++index;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == 'L' || c == 'U') {
|
|
|
|
|
|
if (in.length() > index + 1 && in[index + 1] == '\"') {
|
|
|
|
|
|
quote = '\"';
|
|
|
|
|
|
index += 2;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == 'u') {
|
|
|
|
|
|
if (in.length() > index + 2 && in[index + 1] == '8' && in[index + 2] == '\"') {
|
|
|
|
|
|
quote = '\"';
|
|
|
|
|
|
index += 3;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (in.length() > index + 1 && in[index + 1] == '\"') {
|
|
|
|
|
|
quote = '\"';
|
|
|
|
|
|
index += 2;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int hexDigitValue(QChar c)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (c >= '0' && c <= '9') return c.unicode() - '0';
|
|
|
|
|
|
if (c >= 'a' && c <= 'f') return c.unicode() - 'a' + 10;
|
|
|
|
|
|
if (c >= 'A' && c <= 'F') return c.unicode() - 'A' + 10;
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Reads exactly `count` hex digits starting at `first`. Fails on out-of-range or non-hex input.
|
|
|
|
|
|
bool parseHexDigits(const QString &in, int first, int count, char32_t &value)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (in.length() < first + count) return false;
|
|
|
|
|
|
value = 0;
|
|
|
|
|
|
for (int i = first; i < first + count; ++i) {
|
|
|
|
|
|
int digit = hexDigitValue(in[i]);
|
|
|
|
|
|
if (digit < 0) return false;
|
|
|
|
|
|
value = value * 16 + static_cast<char32_t>(digit);
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Appends a code point as UTF-16. Rejects > 0x10FFFF and surrogates D800-DFFF.
|
|
|
|
|
|
bool appendCodePoint(QString &out, char32_t cp)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (cp > 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF)) return false;
|
|
|
|
|
|
if (QChar::requiresSurrogates(cp)) {
|
|
|
|
|
|
out += QChar(QChar::highSurrogate(cp));
|
|
|
|
|
|
out += QChar(QChar::lowSurrogate(cp));
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
out += QChar(static_cast<char16_t>(cp));
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// index points at the first octal digit. On success index points at the last consumed digit.
|
|
|
|
|
|
bool decodeOctalEscape(const QString &in, int &index, QString &out)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (in.length() <= index + 2) return false;
|
|
|
|
|
|
int value = 0;
|
|
|
|
|
|
for (int i = index; i <= index + 2; ++i) {
|
|
|
|
|
|
QChar digit = in[i];
|
|
|
|
|
|
if (digit < '0' || digit > '7') return false;
|
|
|
|
|
|
value = value * 8 + (digit.unicode() - '0');
|
|
|
|
|
|
}
|
|
|
|
|
|
if (value >= 0x80) return false;
|
|
|
|
|
|
out += static_cast<QChar>(static_cast<char>(value));
|
|
|
|
|
|
index += 2;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// index points at the char after the backslash. On success index points at the last consumed char.
|
|
|
|
|
|
bool decodeEscape(const QString &in, int &index, QString &out)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (in[index].unicode()) {
|
|
|
|
|
|
case 'a':
|
|
|
|
|
|
out += '\x07';
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'b':
|
|
|
|
|
|
out += '\x08';
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'f':
|
|
|
|
|
|
out += '\x0c';
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'n':
|
|
|
|
|
|
out += '\n';
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'r':
|
|
|
|
|
|
case 'v':
|
|
|
|
|
|
break;
|
|
|
|
|
|
case '0': case '1': case '2': case '3':
|
|
|
|
|
|
case '4': case '5': case '6': case '7':
|
|
|
|
|
|
return decodeOctalEscape(in, index, out);
|
|
|
|
|
|
case 'x': {
|
|
|
|
|
|
char32_t value = 0;
|
|
|
|
|
|
if (!parseHexDigits(in, index + 1, 2, value)) return false;
|
|
|
|
|
|
out += static_cast<QChar>(static_cast<char>(value));
|
|
|
|
|
|
index += 2;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
case 'u': {
|
|
|
|
|
|
char32_t value = 0;
|
|
|
|
|
|
if (!parseHexDigits(in, index + 1, 4, value)) return false;
|
|
|
|
|
|
out += QChar(static_cast<ushort>(value));
|
|
|
|
|
|
index += 4;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
case 'U': {
|
|
|
|
|
|
char32_t value = 0;
|
|
|
|
|
|
if (!parseHexDigits(in, index + 1, 8, value)) return false;
|
|
|
|
|
|
if (!appendCodePoint(out, value)) return false;
|
|
|
|
|
|
index += 8;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
default:
|
|
|
|
|
|
out += in[index];
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// index points at the first char after the opening quote. Stops at the closing quote (consumed), at newline (consumed) or at end of input.
|
|
|
|
|
|
bool readStringBody(const QString &in, int &index, QChar quote, QString &out)
|
|
|
|
|
|
{
|
|
|
|
|
|
while (index < in.length()) {
|
|
|
|
|
|
QChar c = in[index];
|
|
|
|
|
|
if (c == quote || c == '\n') {
|
|
|
|
|
|
++index;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == '\\') {
|
|
|
|
|
|
++index;
|
|
|
|
|
|
if (index == in.length()) return true;
|
|
|
|
|
|
if (!decodeEscape(in, index, out)) return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
out += c;
|
|
|
|
|
|
}
|
|
|
|
|
|
++index;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2021-04-01 14:58:42 +02:00
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-19 11:38:04 +02:00
|
|
|
|
QString ConvertLangToSqlString(const QString &in)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Assume mostly C++ for now but allow some other things like
|
|
|
|
|
|
// - single quotes (php)
|
|
|
|
|
|
// - concattenation operators . (php) and + (java)
|
|
|
|
|
|
|
|
|
|
|
|
// Allow cpp prefixes L u8 u U
|
|
|
|
|
|
|
|
|
|
|
|
// Parser flow, we start in whitespace state and search for prefix|opening quote
|
|
|
|
|
|
// parse string and process escapes
|
|
|
|
|
|
// if escape is \r strip it if \n go to new line
|
|
|
|
|
|
// until we reach matching end quote
|
|
|
|
|
|
// skip whitespace and + or .
|
|
|
|
|
|
|
|
|
|
|
|
QString output;
|
|
|
|
|
|
int index = 0;
|
|
|
|
|
|
QChar quote = '\0';
|
2026-10-18 15:58:13 +02:00
|
|
|
|
while (true) {
|
|
|
|
|
|
// skip all whitespace untill we encounter something else
|
|
|
|
|
|
// we also skip concatenation operators. Note this code is not trying to validate
|
|
|
|
|
|
// for correct syntax so it will quite happily accept many incorrect constructs
|
|
|
|
|
|
// that doesn't matter however as we are just trying to strip everything which is not SQL.
|
|
|
|
|
|
while (index < in.length() && (in[index].isSpace() || in[index] == '+' || in[index] == '.')) ++index;
|
|
|
|
|
|
if (index == in.length()) break;
|
|
|
|
|
|
if (!readOpeningQuote(in, index, quote)) break;
|
|
|
|
|
|
if (!readStringBody(in, index, quote, output)) break;
|
2019-08-19 11:38:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return output;
|
|
|
|
|
|
}
|