DotNetDupe 4.0.6
C++17/20 Implementation of the .NET Base Class Library (BCL)
Loading...
Searching...
No Matches
InMemoryDatabaseBackend.cpp
Go to the documentation of this file.
1#include "pch.h"
3#include <algorithm>
4#include <sstream>
5#include <vector>
6#include <string>
7#include <unordered_map>
8
9namespace DotNetDupe {
10 namespace System {
11 namespace Data {
12 namespace Internal {
13
14 struct StdRow {
15 std::vector<std::string> Values;
16 };
17
18 struct StdTable {
19 std::vector<std::string> Columns;
20 std::vector<StdRow> Rows;
21 };
22
23 struct InMemoryDatabaseBackend::Impl {
24 std::unordered_map<std::string, StdTable> m_tables;
25
26 void ExecuteCreate(const std::vector<std::string>& tokens) {
28 if (tokens.size() > 2 && ToUpper(tokens[1]) == "TABLE") {
29 std::string tableName = tokens[2];
30 StdTable t;
31 size_t idx = 3;
32 while (idx < tokens.size()) {
33 if (tokens[idx] == "(" || tokens[idx] == ")") {
34 idx++;
35 continue;
36 }
37 std::string colName = tokens[idx];
38 idx++;
39 if (idx < tokens.size() && tokens[idx] != "," && tokens[idx] != ")") {
40 idx++;
41 }
42 t.Columns.push_back(colName);
43 if (idx < tokens.size() && tokens[idx] == ",") {
44 idx++;
45 }
46 }
47 m_tables[tableName] = t;
48 }
49 }
50
51 void ExecuteInsert(const std::vector<std::string>& tokens,
52 const std::unordered_map<std::string, std::string>& parameters,
53 int& rowsAffected) {
55 if (tokens.size() > 2 && ToUpper(tokens[1]) == "INTO") {
56 std::string tableName = tokens[2];
57 auto tIt = m_tables.find(tableName);
58 if (tIt == m_tables.end()) return;
59
60 StdTable& t = tIt->second;
61 std::vector<std::string> cols;
62 std::vector<std::string> vals;
63
64 size_t idx = 3;
65 if (idx < tokens.size() && tokens[idx] == "(") {
66 idx++;
67 while (idx < tokens.size() && tokens[idx] != ")") {
68 cols.push_back(tokens[idx]);
69 idx++;
70 }
71 if (idx < tokens.size()) idx++;
72 }
73
74 if (idx < tokens.size() && ToUpper(tokens[idx]) == "VALUES") {
75 idx++;
76 }
77
78 if (idx < tokens.size() && tokens[idx] == "(") {
79 idx++;
80 while (idx < tokens.size() && tokens[idx] != ")") {
81 vals.push_back(ResolveValue(tokens[idx], parameters));
82 idx++;
83 }
84 }
85
86 StdRow r;
87 r.Values.resize(t.Columns.size());
88
89 if (cols.empty()) {
90 for (size_t i = 0; i < t.Columns.size() && i < vals.size(); ++i) {
91 r.Values[i] = vals[i];
92 }
93 } else {
94 for (size_t i = 0; i < cols.size() && i < vals.size(); ++i) {
95 auto colName = cols[i];
96 auto cIt = std::find(t.Columns.begin(), t.Columns.end(), colName);
97 if (cIt != t.Columns.end()) {
98 size_t colIdx = std::distance(t.Columns.begin(), cIt);
99 r.Values[colIdx] = vals[i];
100 }
101 }
102 }
103 t.Rows.push_back(r);
104 rowsAffected = 1;
105 }
106 }
107
108 std::vector<StdRow> ExecuteSelect(const std::vector<std::string>& tokens,
109 const std::unordered_map<std::string, std::string>& parameters,
110 std::vector<std::string>& columnNames) {
112 std::vector<std::string> selectCols;
113 size_t idx = 1;
114 while (idx < tokens.size() && ToUpper(tokens[idx]) != "FROM") {
115 selectCols.push_back(tokens[idx]);
116 idx++;
117 }
118
119 if (idx >= tokens.size()) return {};
120 idx++;
121 if (idx >= tokens.size()) return {};
122 std::string tableName = tokens[idx];
123 idx++;
124
125 auto tIt = m_tables.find(tableName);
126 if (tIt == m_tables.end()) return {};
127
128 StdTable& t = tIt->second;
129
131 std::string whereCol;
132 std::string whereVal;
133 if (idx < tokens.size() && ToUpper(tokens[idx]) == "WHERE") {
134 idx++;
135 if (idx < tokens.size()) whereCol = tokens[idx];
136 idx++;
137 if (idx < tokens.size() && tokens[idx] == "=") {
138 idx++;
139 if (idx < tokens.size()) whereVal = ResolveValue(tokens[idx], parameters);
140 }
141 }
142
144 std::vector<size_t> colIndices;
145 if (selectCols.size() == 1 && selectCols[0] == "*") {
146 columnNames = t.Columns;
147 for (size_t i = 0; i < t.Columns.size(); ++i) colIndices.push_back(i);
148 } else {
149 for (auto const& selCol : selectCols) {
150 auto cIt = std::find(t.Columns.begin(), t.Columns.end(), selCol);
151 if (cIt != t.Columns.end()) {
152 columnNames.push_back(selCol);
153 colIndices.push_back(std::distance(t.Columns.begin(), cIt));
154 }
155 }
156 }
157
158 int whereColIdx = -1;
159 if (!whereCol.empty()) {
160 auto cIt = std::find(t.Columns.begin(), t.Columns.end(), whereCol);
161 if (cIt != t.Columns.end()) {
162 whereColIdx = static_cast<int>(std::distance(t.Columns.begin(), cIt));
163 }
164 }
165
167 std::vector<StdRow> resultRows;
168 for (auto const& row : t.Rows) {
169 if (whereColIdx != -1) {
170 if (row.Values[whereColIdx] != whereVal) {
171 continue;
172 }
173 }
174 StdRow newRow;
175 for (auto index : colIndices) {
176 newRow.Values.push_back(row.Values[index]);
177 }
178 resultRows.push_back(newRow);
179 }
180 return resultRows;
181 }
182
183 void ExecuteUpdate(const std::vector<std::string>& tokens,
184 const std::unordered_map<std::string, std::string>& parameters,
185 int& rowsAffected) {
187 std::string tableName = tokens[1];
188 auto tIt = m_tables.find(tableName);
189 if (tIt == m_tables.end()) return;
190
191 StdTable& t = tIt->second;
192
193 size_t idx = 2;
194 if (idx < tokens.size() && ToUpper(tokens[idx]) == "SET") {
195 idx++;
196 }
197
198 std::vector<std::pair<std::string, std::string>> updates;
199 while (idx < tokens.size() && ToUpper(tokens[idx]) != "WHERE") {
200 std::string colName = tokens[idx];
201 idx++;
202 if (idx < tokens.size() && tokens[idx] == "=") {
203 idx++;
204 std::string val = ResolveValue(tokens[idx], parameters);
205 updates.push_back({colName, val});
206 idx++;
207 }
208 }
209
211 std::string whereCol;
212 std::string whereVal;
213 if (idx < tokens.size() && ToUpper(tokens[idx]) == "WHERE") {
214 idx++;
215 if (idx < tokens.size()) whereCol = tokens[idx];
216 idx++;
217 if (idx < tokens.size() && tokens[idx] == "=") {
218 idx++;
219 if (idx < tokens.size()) whereVal = ResolveValue(tokens[idx], parameters);
220 }
221 }
222
223 int whereColIdx = -1;
224 if (!whereCol.empty()) {
225 auto cIt = std::find(t.Columns.begin(), t.Columns.end(), whereCol);
226 if (cIt != t.Columns.end()) {
227 whereColIdx = static_cast<int>(std::distance(t.Columns.begin(), cIt));
228 }
229 }
230
232 for (auto& row : t.Rows) {
233 if (whereColIdx != -1) {
234 if (row.Values[whereColIdx] != whereVal) {
235 continue;
236 }
237 }
238 for (auto const& update : updates) {
239 auto cIt = std::find(t.Columns.begin(), t.Columns.end(), update.first);
240 if (cIt != t.Columns.end()) {
241 size_t updateIdx = std::distance(t.Columns.begin(), cIt);
242 row.Values[updateIdx] = update.second;
243 }
244 }
245 rowsAffected++;
246 }
247 }
248
249 void ExecuteDelete(const std::vector<std::string>& tokens,
250 const std::unordered_map<std::string, std::string>& parameters,
251 int& rowsAffected) {
253 if (tokens.size() > 2 && ToUpper(tokens[1]) == "FROM") {
254 std::string tableName = tokens[2];
255 auto tIt = m_tables.find(tableName);
256 if (tIt == m_tables.end()) return;
257
258 StdTable& t = tIt->second;
259
260 size_t idx = 3;
261 std::string whereCol;
262 std::string whereVal;
263 if (idx < tokens.size() && ToUpper(tokens[idx]) == "WHERE") {
264 idx++;
265 if (idx < tokens.size()) whereCol = tokens[idx];
266 idx++;
267 if (idx < tokens.size() && tokens[idx] == "=") {
268 idx++;
269 if (idx < tokens.size()) whereVal = ResolveValue(tokens[idx], parameters);
270 }
271 }
272
273 int whereColIdx = -1;
274 if (!whereCol.empty()) {
275 auto cIt = std::find(t.Columns.begin(), t.Columns.end(), whereCol);
276 if (cIt != t.Columns.end()) {
277 whereColIdx = static_cast<int>(std::distance(t.Columns.begin(), cIt));
278 }
279 }
280
282 auto it = t.Rows.begin();
283 while (it != t.Rows.end()) {
284 if (whereColIdx != -1) {
285 if (it->Values[whereColIdx] == whereVal) {
286 it = t.Rows.erase(it);
287 rowsAffected++;
288 continue;
289 }
290 } else {
291 it = t.Rows.erase(it);
292 rowsAffected++;
293 continue;
294 }
295 ++it;
296 }
297 }
298 }
299
300 std::vector<std::string> Tokenize(const std::string& s) const {
302 std::vector<std::string> tokens;
303 std::string token;
304 bool inQuotes = false;
305 for (size_t i = 0; i < s.length(); ++i) {
306 char c = s[i];
307 if (c == '\'') {
308 inQuotes = !inQuotes;
309 token += c;
310 } else if (!inQuotes && (c == ' ' || c == ',' || c == '(' || c == ')' || c == '=')) {
311 if (!token.empty()) {
312 tokens.push_back(token);
313 token.clear();
314 }
315 if (c == '(' || c == ')' || c == '=') {
316 tokens.push_back(std::string(1, c));
317 }
318 } else {
319 token += c;
320 }
321 }
322 if (!token.empty()) {
323 tokens.push_back(token);
324 }
325 return tokens;
326 }
327
328 std::string ToUpper(std::string s) const {
330 std::transform(s.begin(), s.end(), s.begin(), ::toupper);
331 return s;
332 }
333
334 std::string ResolveValue(const std::string& val, const std::unordered_map<std::string, std::string>& parameters) const {
336 if (!val.empty() && val[0] == '@') {
337 auto it = parameters.find(val);
338 if (it != parameters.end()) {
339 return it->second;
340 }
341 return std::string("");
342 }
343 if (val.length() >= 2 && val.front() == '\'' && val.back() == '\'') {
344 return val.substr(1, val.length() - 2);
345 }
346 return val;
347 }
348
349 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) {
351 if (cmd == "CREATE") ExecuteCreate(tokens);
352 else if (cmd == "INSERT") ExecuteInsert(tokens, stdParams, rowsAffected);
353 else if (cmd == "SELECT") return ExecuteSelect(tokens, stdParams, stdColNames);
354 else if (cmd == "UPDATE") ExecuteUpdate(tokens, stdParams, rowsAffected);
355 else if (cmd == "DELETE") ExecuteDelete(tokens, stdParams, rowsAffected);
356 return {};
357 }
358 };
359
362
365 m_pImpl->m_tables.clear();
366 }
367
368 static std::unordered_map<std::string, std::string> ConvertParamsMap(const Collections::Generic::Dictionary<String, String>& parameters) {
370 std::unordered_map<std::string, std::string> stdParams;
371 auto keys = parameters.GetKeys();
372 for (int i = 0; i < keys.GetLength(); ++i) {
373 String val;
374 parameters.TryGetValue(keys[i], val);
375 stdParams[keys[i].GetRawString()] = val.GetRawString();
376 }
377 return stdParams;
378 }
379
380 static Collections::Generic::List<Row> ConvertResultRows(const std::vector<StdRow>& stdResRows, const std::vector<std::string>& stdColNames, Collections::Generic::List<String>& columnNames) {
382 for (const auto& col : stdColNames) columnNames.Add(String(col.c_str()));
383
386 for (const auto& sr : stdResRows) {
387 Row r;
388 for (const auto& v : sr.Values) r.Values.Add(String(v.c_str()));
389 resultRows.Add(r);
390 }
391 return resultRows;
392 }
393
395 const String& sql,
398 int& rowsAffected
399 ) {
401 rowsAffected = 0;
402 columnNames.Clear();
403
405 std::unordered_map<std::string, std::string> stdParams = ConvertParamsMap(parameters);
406 std::vector<std::string> tokens = m_pImpl->Tokenize(sql.GetRawString());
407 if (tokens.empty()) return Collections::Generic::List<Row>();
408
410 std::vector<std::string> stdColNames;
411 std::vector<StdRow> stdResRows = m_pImpl->DispatchCommand(m_pImpl->ToUpper(tokens[0]), tokens, stdParams, stdColNames, rowsAffected);
412 return ConvertResultRows(stdResRows, stdColNames, columnNames);
413 }
414
415 }
416 }
417 }
418}
Represents a collection of keys and values.
Definition Dictionary.h:66
bool TryGetValue(const TKey &key, TValue &value) const
Definition Dictionary.h:285
Represents a strongly typed list of objects accessible by index.
Definition List.h:29
void Add(const T &item)
Adds an object to the end of the List.
Definition List.h:138
void Clear()
Removes all elements from the List.
Definition List.h:160
Collections::Generic::List< Row > Execute(const String &sql, const Collections::Generic::Dictionary< String, String > &parameters, Collections::Generic::List< String > &columnNames, int &rowsAffected) override
Executes a SQL statement (CREATE, INSERT, SELECT, UPDATE, DELETE) in memory.
~InMemoryDatabaseBackend() override
Destructor releasing internal in-memory tables.
void ClearDatabase() override
Clears all tables and rows in the in-memory database.
InMemoryDatabaseBackend()
Initializes a new instance of InMemoryDatabaseBackend.
A unified smart pointer that supports both unique and shared ownership semantics.
Represents text as a sequence of UTF-8 code units with culture-invariant operations.
Definition String.h:74
String()
Initializes a new instance of the String class to an empty string.
Definition String.cpp:64
const char * GetRawString() const
Definition String.cpp:230
static Collections::Generic::List< Row > ConvertResultRows(const std::vector< StdRow > &stdResRows, const std::vector< std::string > &stdColNames, Collections::Generic::List< String > &columnNames)
static std::unordered_map< std::string, std::string > ConvertParamsMap(const Collections::Generic::Dictionary< String, String > &parameters)
Represents an internal data row consisting of a list of column values.
Collections::Generic::List< String > Values
Ordered list of column values as strings.