463 lines
11 KiB
C++
463 lines
11 KiB
C++
#include "util.h"
|
|
#include "CsvWriter.h"
|
|
#include "SqlLexer.h"
|
|
#include <QApplication>
|
|
#include <QTextStream>
|
|
#include <QClipboard>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
|
|
// 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;
|
|
}
|
|
|
|
void exportTable(const QTableView *view, QTextStream &out)
|
|
{
|
|
auto model = view->model();
|
|
if (model) {
|
|
CsvWriter csv(&out);
|
|
csv.setSeperator('\t');
|
|
csv.setQuote('"');
|
|
|
|
auto cols = model->columnCount();
|
|
auto rows = model->rowCount();
|
|
for (auto row = 0; row < rows; ++row) {
|
|
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();
|
|
}
|
|
}
|
|
|
|
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;
|
|
QTextStream out(&clipboard_string, QTextStream::WriteOnly);
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
QString ConvertToMultiLineCString(const QString &in_)
|
|
{
|
|
// We need to atleast escape " and \ and also any multi byte utf8 char
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
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
|
|
comment = "//" + token.out.right(token.out.length()-2);
|
|
// 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
|
|
comment = line.right(line.length() - (idx)) + comment;
|
|
}
|
|
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 {
|
|
line += EscapeForCString(token.out);
|
|
}
|
|
}
|
|
else {
|
|
// error during lexical analysis, need to recover
|
|
throw std::runtime_error("Unrecognized input");
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
namespace {
|
|
|
|
class Token {
|
|
public:
|
|
enum Type {
|
|
Code,
|
|
StringLiteral,
|
|
SingleLineComment,
|
|
MultiLineComment
|
|
};
|
|
|
|
const Type type;
|
|
/// Depends on type
|
|
/// Code: literal copy of the matched code
|
|
/// StringLiteral: the contents of the string literal, escapes have been unescaped
|
|
/// *Comment, the text in the comment
|
|
const QString data;
|
|
|
|
Token(Type type, QString data)
|
|
: type(type)
|
|
, data(data)
|
|
{}
|
|
};
|
|
|
|
/// Tokenizer to get SQL out of a piece of programming language code
|
|
///
|
|
/// It works by ignoring most input and only get's triggered by string literals
|
|
/// and comments. It does return tokens for the code in between just so
|
|
class ProgLangTokenizer {
|
|
public:
|
|
ProgLangTokenizer(const QString &in)
|
|
: input(in)
|
|
{}
|
|
|
|
private:
|
|
const QString input;
|
|
int position = 0;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
enum {
|
|
WHITESPACE,
|
|
PREFIX,
|
|
IN_STRING,
|
|
SingleLineComment,
|
|
END,
|
|
ERROR
|
|
} state = WHITESPACE;
|
|
int index = 0;
|
|
QChar quote = '\0';
|
|
while (state != ERROR && state != END && index < in.length()) {
|
|
if (state == WHITESPACE) {
|
|
// 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()) {
|
|
state = END;
|
|
break;
|
|
}
|
|
// Assume quotes can vary
|
|
if (in[index] == '\'' || in[index] == '\"') {
|
|
quote = in[index];
|
|
++index;
|
|
state = IN_STRING;
|
|
}
|
|
else {
|
|
state = PREFIX;
|
|
}
|
|
}
|
|
else if (state == PREFIX) {
|
|
auto c = in[index];
|
|
if (c == 'L' || c == 'U') {
|
|
// C++ prefix expect C++ double quote
|
|
if (in.length() > index+ 1 && in[index+1] == '"') {
|
|
index += 2;
|
|
state = IN_STRING;
|
|
}
|
|
else {
|
|
state = ERROR;
|
|
break;
|
|
}
|
|
}
|
|
if (c == 'u') {
|
|
// C++ prefix expect C++ double quote
|
|
if (in.length() > index+ 2 && in[index+1] == '8' && in[index+2] == '"') {
|
|
index += 3;
|
|
state = IN_STRING;
|
|
}
|
|
else if (in.length() > index+ 1 && in[index+1] == '"') {
|
|
index += 2;
|
|
state = IN_STRING;
|
|
}
|
|
else {
|
|
state = ERROR;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
state = ERROR;
|
|
break;
|
|
}
|
|
}
|
|
else if (state == IN_STRING) {
|
|
// scan contents of string and process any escapes encountered
|
|
bool escape = false;
|
|
while (state != ERROR && index < in.length()) {
|
|
QChar c = in[index];
|
|
if (escape) {
|
|
if (c == 'a') output += '\x07';
|
|
else if (c == 'a') output += '\x07';
|
|
else if (c == 'b') output += '\x08';
|
|
else if (c == 'f') output += '\x0c';
|
|
else if (c == 'n') output += '\n';
|
|
else if (c == 'r') ;
|
|
else if (c == 'v') ;
|
|
else if (c >= '0' && c <= '7') {
|
|
// process octal escape
|
|
if (in.length() > index + 2) {
|
|
char buf[4];
|
|
buf[0] = c.toLatin1();
|
|
buf[1] = in[++index].toLatin1();
|
|
buf[2] = in[++index].toLatin1();
|
|
buf[3] = 0;
|
|
long int v = strtol(buf, nullptr, 8);
|
|
if (v < 0x80) {
|
|
output += static_cast<QChar>(static_cast<char>(v));
|
|
}
|
|
else {
|
|
state = ERROR;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
state = ERROR;
|
|
break;
|
|
}
|
|
}
|
|
else if (c == 'x') {
|
|
// hex byte
|
|
if (in.length() > index + 2) {
|
|
char buf[3];
|
|
buf[0] = in[++index].toLatin1();
|
|
buf[1] = in[++index].toLatin1();
|
|
buf[2] = 0;
|
|
long int v = strtol(buf, nullptr, 16);
|
|
output += static_cast<QChar>(static_cast<char>(v));
|
|
}
|
|
else {
|
|
state = ERROR;
|
|
break;
|
|
}
|
|
}
|
|
else if (c == 'u') {
|
|
// 4 digit hax unicode codepoint
|
|
// hex byte
|
|
if (in.length() > index + 4) {
|
|
char buf[5];
|
|
buf[0] = in[++index].toLatin1();
|
|
buf[1] = in[++index].toLatin1();
|
|
buf[2] = in[++index].toLatin1();
|
|
buf[3] = in[++index].toLatin1();
|
|
buf[4] = 0;
|
|
long int v = strtol(buf, nullptr, 16);
|
|
output += static_cast<QChar>(static_cast<ushort>(v));
|
|
}
|
|
else {
|
|
state = ERROR;
|
|
break;
|
|
}
|
|
|
|
}
|
|
else if (c == 'U') {
|
|
// 8 digit hax unicode codepoint
|
|
if (in.length() > index + 8) {
|
|
char buf[9];
|
|
buf[0] = in[++index].toLatin1();
|
|
buf[1] = in[++index].toLatin1();
|
|
buf[2] = in[++index].toLatin1();
|
|
buf[3] = in[++index].toLatin1();
|
|
buf[4] = in[++index].toLatin1();
|
|
buf[5] = in[++index].toLatin1();
|
|
buf[6] = in[++index].toLatin1();
|
|
buf[7] = in[++index].toLatin1();
|
|
buf[8] = 0;
|
|
uint v = static_cast<uint>(strtol(buf, nullptr, 16));
|
|
if (QChar::requiresSurrogates(v)) {
|
|
output += QChar(QChar::highSurrogate(v));
|
|
output += QChar(QChar::lowSurrogate(v));
|
|
}
|
|
}
|
|
else {
|
|
state = ERROR;
|
|
break;
|
|
}
|
|
|
|
}
|
|
else {
|
|
output += c;
|
|
}
|
|
escape = false;
|
|
}
|
|
else {
|
|
if (c == quote) {
|
|
state = WHITESPACE;
|
|
++index;
|
|
break;
|
|
}
|
|
// Is there any language where string literals do not have to be terminated before the end of the line
|
|
// Not considering string literals that explicitly allow for multiline strings as these are often raw strings
|
|
// and can be copy pasted normally.
|
|
else if (c == '\n') {
|
|
state = WHITESPACE;
|
|
++index;
|
|
break;
|
|
}
|
|
else if (c == '\\') {
|
|
escape = true;
|
|
}
|
|
else {
|
|
output += c;
|
|
}
|
|
}
|
|
++index;
|
|
}
|
|
}
|
|
}
|
|
|
|
return output;
|
|
}
|