Extra lines before and after query are removed. Whitespace at end of line is removed. SQL comments are converted to cpp style comments and are outside the string literal. To achieve this the function now uses the SQLLexer to know what is comment. This also required the additional capability in the lexer to also return whitespace and newline tokens. Also a few bugs in the lexer were fixed.
402 lines
9.8 KiB
C++
402 lines
9.8 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, QIODevice::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 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.rightRef(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.rightRef(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 += 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;
|
|
}
|
|
|
|
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,
|
|
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;
|
|
}
|