#include "util.h" #include "CsvWriter.h" #include "SqlLexer.h" #include #include #include #include #include // 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(); // 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(); 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 { // 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(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(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(static_cast(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(static_cast(value)); index += 2; break; } case 'u': { char32_t value = 0; if (!parseHexDigits(in, index + 1, 4, value)) return false; out += QChar(static_cast(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; } } 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'; 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; } return output; }