#include "pch.h"
#include "System/Data/Internal/InMemoryDatabaseBackend.h"
#include <algorithm>
#include <sstream>
#include <vector>
#include <string>
#include <unordered_map>
namespace DotNetDupe {
namespace System {
namespace Data {
namespace Internal {
struct StdRow {
std::vector<std::string> Values;
};
struct StdTable {
std::vector<std::string> Columns;
std::vector<StdRow> Rows;
};
struct InMemoryDatabaseBackend::Impl {
std::unordered_map<std::string, StdTable> m_tables;
void ExecuteCreate(const std::vector<std::string>& tokens) {
if (tokens.size() > 2 && ToUpper(tokens[1]) == "TABLE") {
std::string tableName = tokens[2];
StdTable t;
size_t idx = 3;
while (idx < tokens.size()) {
if (tokens[idx] == "(" || tokens[idx] == ")") {
idx++;
continue;
}
std::string colName = tokens[idx];
idx++;
if (idx < tokens.size() && tokens[idx] != "," && tokens[idx] != ")") {
idx++;
}
t.Columns.push_back(colName);
if (idx < tokens.size() && tokens[idx] == ",") {
idx++;
}
}
m_tables[tableName] = t;
}
}
void ExecuteInsert(const std::vector<std::string>& tokens,
const std::unordered_map<std::string, std::string>& parameters,
int& rowsAffected) {
if (tokens.size() > 2 && ToUpper(tokens[1]) == "INTO") {
std::string tableName = tokens[2];
auto tIt = m_tables.find(tableName);
if (tIt == m_tables.end()) return;
StdTable& t = tIt->second;
std::vector<std::string> cols;
std::vector<std::string> vals;
size_t idx = 3;
if (idx < tokens.size() && tokens[idx] == "(") {
idx++;
while (idx < tokens.size() && tokens[idx] != ")") {
cols.push_back(tokens[idx]);
idx++;
}
if (idx < tokens.size()) idx++;
}
if (idx < tokens.size() && ToUpper(tokens[idx]) == "VALUES") {
idx++;
}
if (idx < tokens.size() && tokens[idx] == "(") {
idx++;
while (idx < tokens.size() && tokens[idx] != ")") {
vals.push_back(ResolveValue(tokens[idx], parameters));
idx++;
}
}
StdRow r;
r.Values.resize(t.Columns.size());
if (cols.empty()) {
for (size_t i = 0; i < t.Columns.size() && i < vals.size(); ++i) {
r.Values[i] = vals[i];
}
} else {
for (size_t i = 0; i < cols.size() && i < vals.size(); ++i) {
auto colName = cols[i];
auto cIt = std::find(t.Columns.begin(), t.Columns.end(), colName);
if (cIt != t.Columns.end()) {
size_t colIdx = std::distance(t.Columns.begin(), cIt);
r.Values[colIdx] = vals[i];
}
}
}
t.Rows.push_back(r);
rowsAffected = 1;
}
}
std::vector<StdRow> ExecuteSelect(const std::vector<std::string>& tokens,
const std::unordered_map<std::string, std::string>& parameters,
std::vector<std::string>& columnNames) {
std::vector<std::string> selectCols;
size_t idx = 1;
while (idx < tokens.size() && ToUpper(tokens[idx]) != "FROM") {
selectCols.push_back(tokens[idx]);
idx++;
}
if (idx >= tokens.size()) return {};
idx++;
if (idx >= tokens.size()) return {};
std::string tableName = tokens[idx];
idx++;
auto tIt = m_tables.find(tableName);
if (tIt == m_tables.end()) return {};
StdTable& t = tIt->second;
std::string whereCol;
std::string whereVal;
if (idx < tokens.size() && ToUpper(tokens[idx]) == "WHERE") {
idx++;
if (idx < tokens.size()) whereCol = tokens[idx];
idx++;
if (idx < tokens.size() && tokens[idx] == "=") {
idx++;
if (idx < tokens.size()) whereVal = ResolveValue(tokens[idx], parameters);
}
}
std::vector<size_t> colIndices;
if (selectCols.size() == 1 && selectCols[0] == "*") {
columnNames = t.Columns;
for (size_t i = 0; i < t.Columns.size(); ++i) colIndices.push_back(i);
} else {
for (auto const& selCol : selectCols) {
auto cIt = std::find(t.Columns.begin(), t.Columns.end(), selCol);
if (cIt != t.Columns.end()) {
columnNames.push_back(selCol);
colIndices.push_back(std::distance(t.Columns.begin(), cIt));
}
}
}
int whereColIdx = -1;
if (!whereCol.empty()) {
auto cIt = std::find(t.Columns.begin(), t.Columns.end(), whereCol);
if (cIt != t.Columns.end()) {
whereColIdx = static_cast<int>(std::distance(t.Columns.begin(), cIt));
}
}
std::vector<StdRow> resultRows;
for (auto const& row : t.Rows) {
if (whereColIdx != -1) {
if (row.Values[whereColIdx] != whereVal) {
continue;
}
}
StdRow newRow;
for (auto index : colIndices) {
newRow.Values.push_back(row.Values[index]);
}
resultRows.push_back(newRow);
}
return resultRows;
}
void ExecuteUpdate(const std::vector<std::string>& tokens,
const std::unordered_map<std::string, std::string>& parameters,
int& rowsAffected) {
std::string tableName = tokens[1];
auto tIt = m_tables.find(tableName);
if (tIt == m_tables.end()) return;
StdTable& t = tIt->second;
size_t idx = 2;
if (idx < tokens.size() && ToUpper(tokens[idx]) == "SET") {
idx++;
}
std::vector<std::pair<std::string, std::string>> updates;
while (idx < tokens.size() && ToUpper(tokens[idx]) != "WHERE") {
std::string colName = tokens[idx];
idx++;
if (idx < tokens.size() && tokens[idx] == "=") {
idx++;
std::string val = ResolveValue(tokens[idx], parameters);
updates.push_back({colName, val});
idx++;
}
}
std::string whereCol;
std::string whereVal;
if (idx < tokens.size() && ToUpper(tokens[idx]) == "WHERE") {
idx++;
if (idx < tokens.size()) whereCol = tokens[idx];
idx++;
if (idx < tokens.size() && tokens[idx] == "=") {
idx++;
if (idx < tokens.size()) whereVal = ResolveValue(tokens[idx], parameters);
}
}
int whereColIdx = -1;
if (!whereCol.empty()) {
auto cIt = std::find(t.Columns.begin(), t.Columns.end(), whereCol);
if (cIt != t.Columns.end()) {
whereColIdx = static_cast<int>(std::distance(t.Columns.begin(), cIt));
}
}
for (auto& row : t.Rows) {
if (whereColIdx != -1) {
if (row.Values[whereColIdx] != whereVal) {
continue;
}
}
for (auto const& update : updates) {
auto cIt = std::find(t.Columns.begin(), t.Columns.end(), update.first);
if (cIt != t.Columns.end()) {
size_t updateIdx = std::distance(t.Columns.begin(), cIt);
row.Values[updateIdx] = update.second;
}
}
rowsAffected++;
}
}
void ExecuteDelete(const std::vector<std::string>& tokens,
const std::unordered_map<std::string, std::string>& parameters,
int& rowsAffected) {
if (tokens.size() > 2 && ToUpper(tokens[1]) == "FROM") {
std::string tableName = tokens[2];
auto tIt = m_tables.find(tableName);
if (tIt == m_tables.end()) return;
StdTable& t = tIt->second;
size_t idx = 3;
std::string whereCol;
std::string whereVal;
if (idx < tokens.size() && ToUpper(tokens[idx]) == "WHERE") {
idx++;
if (idx < tokens.size()) whereCol = tokens[idx];
idx++;
if (idx < tokens.size() && tokens[idx] == "=") {
idx++;
if (idx < tokens.size()) whereVal = ResolveValue(tokens[idx], parameters);
}
}
int whereColIdx = -1;
if (!whereCol.empty()) {
auto cIt = std::find(t.Columns.begin(), t.Columns.end(), whereCol);
if (cIt != t.Columns.end()) {
whereColIdx = static_cast<int>(std::distance(t.Columns.begin(), cIt));
}
}
auto it = t.Rows.begin();
while (it != t.Rows.end()) {
if (whereColIdx != -1) {
if (it->Values[whereColIdx] == whereVal) {
it = t.Rows.erase(it);
rowsAffected++;
continue;
}
} else {
it = t.Rows.erase(it);
rowsAffected++;
continue;
}
++it;
}
}
}
std::vector<std::string> Tokenize(const std::string& s) const {
std::vector<std::string> tokens;
std::string token;
bool inQuotes = false;
for (size_t i = 0; i < s.length(); ++i) {
char c = s[i];
if (c == '\'') {
inQuotes = !inQuotes;
token += c;
} else if (!inQuotes && (c == ' ' || c == ',' || c == '(' || c == ')' || c == '=')) {
if (!token.empty()) {
tokens.push_back(token);
token.clear();
}
if (c == '(' || c == ')' || c == '=') {
tokens.push_back(std::string(1, c));
}
} else {
token += c;
}
}
if (!token.empty()) {
tokens.push_back(token);
}
return tokens;
}
std::string ToUpper(std::string s) const {
std::transform(s.begin(), s.end(), s.begin(), ::toupper);
return s;
}
std::string ResolveValue(const std::string& val, const std::unordered_map<std::string, std::string>& parameters) const {
if (!val.empty() && val[0] == '@') {
auto it = parameters.find(val);
if (it != parameters.end()) {
return it->second;
}
return std::string("");
}
if (val.length() >= 2 && val.front() == '\'' && val.back() == '\'') {
return val.substr(1, val.length() - 2);
}
return val;
}
std::vector<StdRow> DispatchCommand(const std::string& cmd, const std::vector<std::string>& tokens, const std::unordered_map<std::string, std::string>& stdParams, std::vector<std::string>& stdColNames, int& rowsAffected) {
if (cmd == "CREATE") ExecuteCreate(tokens);
else if (cmd == "INSERT") ExecuteInsert(tokens, stdParams, rowsAffected);
else if (cmd == "SELECT") return ExecuteSelect(tokens, stdParams, stdColNames);
else if (cmd == "UPDATE") ExecuteUpdate(tokens, stdParams, rowsAffected);
else if (cmd == "DELETE") ExecuteDelete(tokens, stdParams, rowsAffected);
return {};
}
};
InMemoryDatabaseBackend::InMemoryDatabaseBackend() : m_pImpl(DotNetDupe::System::SmartPointer<Impl>::NewShared()) {}
InMemoryDatabaseBackend::~InMemoryDatabaseBackend() = default;
void InMemoryDatabaseBackend::ClearDatabase() {
m_pImpl->m_tables.clear();
}
static std::unordered_map<std::string, std::string> ConvertParamsMap(const Collections::Generic::Dictionary<String, String>& parameters) {
std::unordered_map<std::string, std::string> stdParams;
auto keys = parameters.GetKeys();
for (int i = 0; i < keys.GetLength(); ++i) {
String val;
parameters.TryGetValue(keys[i], val);
stdParams[keys[i].GetRawString()] = val.GetRawString();
}
return stdParams;
}
static Collections::Generic::List<Row> ConvertResultRows(const std::vector<StdRow>& stdResRows, const std::vector<std::string>& stdColNames, Collections::Generic::List<String>& columnNames) {
for (const auto& col : stdColNames) columnNames.Add(String(col.c_str()));
Collections::Generic::List<Row> resultRows;
for (const auto& sr : stdResRows) {
Row r;
for (const auto& v : sr.Values) r.Values.Add(String(v.c_str()));
resultRows.Add(r);
}
return resultRows;
}
Collections::Generic::List<Row> InMemoryDatabaseBackend::Execute(
const String& sql,
const Collections::Generic::Dictionary<String, String>& parameters,
Collections::Generic::List<String>& columnNames,
int& rowsAffected
) {
rowsAffected = 0;
columnNames.Clear();
std::unordered_map<std::string, std::string> stdParams = ConvertParamsMap(parameters);
std::vector<std::string> tokens = m_pImpl->Tokenize(sql.GetRawString());
if (tokens.empty()) return Collections::Generic::List<Row>();
std::vector<std::string> stdColNames;
std::vector<StdRow> stdResRows = m_pImpl->DispatchCommand(m_pImpl->ToUpper(tokens[0]), tokens, stdParams, stdColNames, rowsAffected);
return ConvertResultRows(stdResRows, stdColNames, columnNames);
}
}
}
}
}