载入中...
搜索中...
未找到
Database.cpp
浏览该文件的文档.
1#include "database/Database.h"
2
3#include <Poco/Data/RecordSet.h>
4#include <Poco/Data/SQLite/Connector.h>
5#include <Poco/Data/Session.h>
6#include <Poco/Dynamic/Var.h>
7#include <Poco/JSON/Array.h>
8#include <Poco/JSON/Object.h>
9#include <Poco/JSON/Parser.h>
10#include <Poco/JSON/Stringifier.h>
11#include <simplesquirrel/simplesquirrel.hpp>
12
13#include <algorithm>
14#include <sstream>
15#include <stdexcept>
16
17namespace eve::database {
18namespace {
19
20std::string quoteIdentifier(const std::string& name) {
21 if (name.empty()) throw std::invalid_argument("database identifier must not be empty");
22 std::string quoted = "\"";
23 for (char c : name) {
24 if (c == '\0') throw std::invalid_argument("database identifier contains NUL");
25 quoted += c == '"' ? "\"\"" : std::string(1, c);
26 }
27 return quoted + "\"";
28}
29
30std::string literal(const Value& value) {
31 if (std::holds_alternative<std::monostate>(value)) return "NULL";
32 if (auto p = std::get_if<std::int64_t>(&value)) return std::to_string(*p);
33 if (auto p = std::get_if<double>(&value)) return std::to_string(*p);
34 if (auto p = std::get_if<bool>(&value)) return *p ? "1" : "0";
35 std::string escaped;
36 for (char c : std::get<std::string>(value)) escaped += c == '\'' ? "''" : std::string(1, c);
37 return "'" + escaped + "'";
38}
39
40Value fromDynamic(const Poco::Dynamic::Var& value) {
41 if (value.isEmpty()) return std::monostate{};
42 if (value.isBoolean()) return value.convert<bool>();
43 if (value.isInteger()) return value.convert<Poco::Int64>();
44 if (value.isNumeric()) return value.convert<double>();
45 return value.convert<std::string>();
46}
47
48Row parseObject(const std::string& json) {
49 auto object = Poco::JSON::Parser().parse(json).extract<Poco::JSON::Object::Ptr>();
50 Row row;
51 for (const auto& name : object->getNames()) row[name] = fromDynamic(object->get(name));
52 return row;
53}
54
55Poco::Dynamic::Var toDynamic(const Value& value);
56Value parseScalar(const std::string& json) {
57 auto wrapper = Poco::JSON::Parser().parse("[" + json + "]").extract<Poco::JSON::Array::Ptr>();
58 if (wrapper->size() != 1) throw std::invalid_argument("ORM id must be one JSON scalar");
59 auto value = wrapper->get(0);
60 if (!value.isEmpty() && !value.isBoolean() && !value.isNumeric() && !value.isString())
61 throw std::invalid_argument("ORM id must be a JSON scalar");
62 return fromDynamic(value);
63}
64
65std::string rowsToJson(const std::vector<Row>& source) {
66 Poco::JSON::Array rows;
67 for (const auto& row : source) {
68 Poco::JSON::Object object;
69 for (const auto& [key, value] : row) object.set(key, toDynamic(value));
70 rows.add(object);
71 }
72 std::ostringstream output;
73 Poco::JSON::Stringifier::stringify(rows, output);
74 return output.str();
75}
76
77Poco::Dynamic::Var toDynamic(const Value& value) {
78 if (std::holds_alternative<std::monostate>(value)) return {};
79 if (auto p = std::get_if<std::int64_t>(&value)) return Poco::Dynamic::Var(Poco::Int64(*p));
80 if (auto p = std::get_if<double>(&value)) return Poco::Dynamic::Var(*p);
81 if (auto p = std::get_if<bool>(&value)) return Poco::Dynamic::Var(*p);
82 return Poco::Dynamic::Var(std::get<std::string>(value));
83}
84
85} // namespace
86
87Connection::Connection(std::string connector, std::string connectionString) {
88 Poco::Data::SQLite::Connector::registerConnector();
89 session_ = std::make_unique<Poco::Data::Session>(connector, connectionString);
90}
91
93bool Connection::isConnected() const { return session_ && session_->isConnected(); }
95 if (session_ && session_->isConnected()) session_->close();
96}
97
98int Connection::execute(const std::string& sql) {
99 if (!isConnected()) throw std::runtime_error("database connection is closed");
100 return static_cast<int>((*session_ << sql).execute());
101}
102
103std::vector<Row> Connection::query(const std::string& sql) {
104 if (!isConnected()) throw std::runtime_error("database connection is closed");
105 Poco::Data::Statement statement(*session_);
106 statement << sql;
107 statement.execute();
108 Poco::Data::RecordSet records(statement);
109 std::vector<Row> result;
110 for (std::size_t rowIndex = 0; rowIndex < records.rowCount(); ++rowIndex) {
111 Row row;
112 for (std::size_t column = 0; column < records.columnCount(); ++column)
113 row[records.columnName(column)] = fromDynamic(records.value(column, rowIndex));
114 result.push_back(std::move(row));
115 }
116 return result;
117}
118
119std::string Connection::queryJson(const std::string& sql) { return rowsToJson(query(sql)); }
120
121int Connection::insert(const std::string& table, const Row& values) {
122 if (values.empty()) throw std::invalid_argument("insert values must not be empty");
123 std::string columns, data;
124 for (const auto& [key, value] : values) {
125 if (!columns.empty()) {
126 columns += ',';
127 data += ',';
128 }
129 columns += quoteIdentifier(key);
130 data += literal(value);
131 }
132 return execute("INSERT INTO " + quoteIdentifier(table) + " (" + columns + ") VALUES (" + data + ")");
133}
134
135int Connection::update(const std::string& table, const Row& values, const std::string& whereClause) {
136 if (values.empty()) throw std::invalid_argument("update values must not be empty");
137 std::string assignments;
138 for (const auto& [key, value] : values) {
139 if (!assignments.empty()) assignments += ',';
140 assignments += quoteIdentifier(key) + '=' + literal(value);
141 }
142 return execute("UPDATE " + quoteIdentifier(table) + " SET " + assignments +
143 (whereClause.empty() ? "" : " WHERE " + whereClause));
144}
145
146int Connection::remove(const std::string& table, const std::string& whereClause) {
147 return execute("DELETE FROM " + quoteIdentifier(table) + (whereClause.empty() ? "" : " WHERE " + whereClause));
148}
149
150int Connection::insertJson(const std::string& table, const std::string& json) {
151 return insert(table, parseObject(json));
152}
153int Connection::updateJson(const std::string& table, const std::string& json, const std::string& whereClause) {
154 return update(table, parseObject(json), whereClause);
155}
156
157int Connection::updateByKey(const std::string& table, const Row& values, const std::string& key, const Value& id) {
158 return update(table, values, quoteIdentifier(key) + "=" + literal(id));
159}
160
161int Connection::removeByKey(const std::string& table, const std::string& key, const Value& id) {
162 return remove(table, quoteIdentifier(key) + "=" + literal(id));
163}
164
165std::vector<Row> Connection::queryByKey(const std::string& table, const std::string& key, const Value& id) {
166 return query("SELECT * FROM " + quoteIdentifier(table) + " WHERE " + quoteIdentifier(key) + "=" + literal(id) +
167 " LIMIT 1");
168}
169
170Model* Connection::model(std::string table, std::string primaryKey) {
171 return new Model(this, std::move(table), std::move(primaryKey));
172}
173Query* Connection::from(std::string table) { return new Query(this, std::move(table)); }
174
175Query::Query(Connection* connection, std::string table) : connection_(connection), table_(std::move(table)) {
176 if (!connection_) throw std::invalid_argument("query requires a connection");
177}
178
179Query* Query::where(const std::string& column, const std::string& op, Value value) {
180 static const std::vector<std::string> allowed{"=", "!=", "<", "<=", ">", ">=", "LIKE"};
181 if (std::find(allowed.begin(), allowed.end(), op) == allowed.end())
182 throw std::invalid_argument("unsupported query operator: " + op);
183 predicates_.push_back(quoteIdentifier(column) + " " + op + " " + literal(value));
184 return this;
185}
186
187Query* Query::whereJson(const std::string& column, const std::string& op, const std::string& valueJson) {
188 return where(column, op, parseScalar(valueJson));
189}
190
191Query* Query::orderBy(const std::string& column, bool ascending) {
192 ordering_.push_back(quoteIdentifier(column) + (ascending ? " ASC" : " DESC"));
193 return this;
194}
195
196Query* Query::limit(int count) {
197 if (count < 0) throw std::invalid_argument("query limit must not be negative");
198 limit_ = count;
199 return this;
200}
201
202Query* Query::offset(int count) {
203 if (count < 0) throw std::invalid_argument("query offset must not be negative");
204 offset_ = count;
205 return this;
206}
207
208std::string Query::selectSql(bool countOnly, int forcedLimit) const {
209 std::string sql = countOnly ? "SELECT COUNT(*) AS count FROM " : "SELECT * FROM ";
210 sql += quoteIdentifier(table_);
211 for (std::size_t i = 0; i < predicates_.size(); ++i) sql += (i == 0 ? " WHERE " : " AND ") + predicates_[i];
212 if (!countOnly && !ordering_.empty()) {
213 sql += " ORDER BY ";
214 for (std::size_t i = 0; i < ordering_.size(); ++i) sql += (i ? "," : "") + ordering_[i];
215 }
216 int effectiveLimit = forcedLimit >= 0 ? forcedLimit : limit_;
217 if (!countOnly && effectiveLimit >= 0)
218 sql += " LIMIT " + std::to_string(effectiveLimit);
219 else if (!countOnly && offset_ > 0)
220 sql += " LIMIT -1";
221 if (!countOnly && offset_ > 0) sql += " OFFSET " + std::to_string(offset_);
222 return sql;
223}
224
225std::vector<Row> Query::all() { return connection_->query(selectSql(false)); }
226std::string Query::allJson() { return rowsToJson(all()); }
227std::string Query::firstJson() { return rowsToJson(connection_->query(selectSql(false, 1))); }
229 auto rows = connection_->query(selectSql(true));
230 if (rows.empty()) return 0;
231 const Value& value = rows.front().at("count");
232 if (auto integer = std::get_if<std::int64_t>(&value)) return static_cast<int>(*integer);
233 if (auto number = std::get_if<double>(&value)) return static_cast<int>(*number);
234 if (auto text = std::get_if<std::string>(&value)) return std::stoi(*text);
235 throw std::runtime_error("database COUNT returned a non-numeric value");
236}
237
238Model::Model(Connection* connection, std::string table, std::string primaryKey)
239 : connection_(connection), table_(std::move(table)), primaryKey_(std::move(primaryKey)) {
240 if (!connection_) throw std::invalid_argument("ORM model requires a connection");
241}
242
243int Model::insertJson(const std::string& json) { return connection_->insertJson(table_, json); }
244std::string Model::allJson() { return connection_->queryJson("SELECT * FROM " + quoteIdentifier(table_)); }
245std::string Model::findJson(const std::string& idJson) {
246 return rowsToJson(connection_->queryByKey(table_, primaryKey_, parseScalar(idJson)));
247}
248int Model::updateJson(const std::string& idJson, const std::string& json) {
249 return connection_->updateByKey(table_, parseObject(json), primaryKey_, parseScalar(idJson));
250}
251int Model::remove(const std::string& idJson) {
252 return connection_->removeByKey(table_, primaryKey_, parseScalar(idJson));
253}
254Query* Model::query() { return new Query(connection_, table_); }
255
257Connection* Database::connect(std::string connector, std::string connectionString) {
258 return new Connection(std::move(connector), std::move(connectionString));
259}
260Connection* Database::connectSQLite(std::string path) { return connect("SQLite", std::move(path)); }
261
262void Database::expose(ssq::Table& table) {
263 auto module = table.addClass(name, Database::create, false);
264 expose(module);
265 auto connection = table.addClass<Connection>(
266 "DatabaseConnection", std::function<Connection*()>([] { return new Connection("SQLite", ":memory:"); }), true);
267 connection.addFunc("isConnected", &Connection::isConnected);
268 connection.addFunc("close", &Connection::close);
269 connection.addFunc("execute", &Connection::execute);
270 connection.addFunc("queryJson", &Connection::queryJson);
271 connection.addFunc("insertJson", &Connection::insertJson);
272 connection.addFunc("updateJson", &Connection::updateJson);
273 connection.addFunc("remove", &Connection::remove);
274 connection.addFunc("model", &Connection::model);
275 connection.addFunc("from", &Connection::from);
276
277 auto query =
278 table.addClass<Query>("DatabaseQuery", std::function<Query*()>([]() -> Query* { return nullptr; }), true);
279 query.addFunc("where", &Query::whereJson);
280 query.addFunc("orderBy", &Query::orderBy);
281 query.addFunc("limit", &Query::limit);
282 query.addFunc("offset", &Query::offset);
283 query.addFunc("allJson", &Query::allJson);
284 query.addFunc("firstJson", &Query::firstJson);
285 query.addFunc("count", &Query::count);
286
287 auto model =
288 table.addClass<Model>("DatabaseModel", std::function<Model*()>([]() -> Model* { return nullptr; }), true);
289 model.addFunc("insertJson", &Model::insertJson);
290 model.addFunc("allJson", &Model::allJson);
291 model.addFunc("findJson", &Model::findJson);
292 model.addFunc("updateJson", &Model::updateJson);
293 model.addFunc("remove", &Model::remove);
294 model.addFunc("query", &Model::query);
295}
296
297void Database::expose(ssq::Class& cls) {
298 cls.addFunc("getName", &Database::getName);
299 cls.addFunc("connect", &Database::connect);
300 cls.addFunc("connectSQLite", &Database::connectSQLite);
301}
302
303} // namespace eve::database
std::string value
HSQOBJECT cls
Definition ECS.cpp:21
std::map< std::string, Var > values
uint32_t c
#define Module_IMPL(ModuleName, newExpr)
Definition Module.h:24
glm::vec4 p[6]
glm::mat4 model
Light2D::Data * data
const char * name
Definition RockMesh.cpp:21
int columns
virtual std::string getName() const =0
SQLite connection with a small ORM layer on top of Poco::Data. Tables/columns are quoted identifiers;...
Definition Database.h:62
std::string queryJson(const std::string &sql)
Runs query() and serializes the rows to a JSON array.
Definition Database.cpp:119
std::vector< Row > query(const std::string &sql)
Runs a SELECT and returns every row as name/value maps.
Definition Database.cpp:103
void close()
Closes the session (idempotent).
Definition Database.cpp:94
Query * from(std::string table)
Builds a fluent Query builder for a table.
Definition Database.cpp:173
std::vector< Row > queryByKey(const std::string &table, const std::string &key, const Value &id)
Selects rows where key == id.
Definition Database.cpp:165
int update(const std::string &table, const Row &values, const std::string &whereClause)
Updates rows matching whereClause; returns the affected row count.
Definition Database.cpp:135
int insert(const std::string &table, const Row &values)
Inserts a row map; returns the affected row count.
Definition Database.cpp:121
int remove(const std::string &table, const std::string &whereClause)
Deletes rows matching whereClause; returns the affected row count.
Definition Database.cpp:146
int removeByKey(const std::string &table, const std::string &key, const Value &id)
Deletes a single row by primary key value.
Definition Database.cpp:161
bool isConnected() const
True while the underlying session is open.
Definition Database.cpp:93
Connection(std::string connector, std::string connectionString)
Opens a Poco::Data session; connector is e.g. "SQLite".
Definition Database.cpp:87
Model * model(std::string table, std::string primaryKey="id")
Builds a script-facing Model repository for a table.
Definition Database.cpp:170
int execute(const std::string &sql)
Executes a statement; returns the affected row count.
Definition Database.cpp:98
int updateJson(const std::string &table, const std::string &json, const std::string &whereClause)
Script-friendly update; whereClause is raw SQL.
Definition Database.cpp:153
int insertJson(const std::string &table, const std::string &json)
Script-friendly insert: JSON must be an object of column/value pairs.
Definition Database.cpp:150
int updateByKey(const std::string &table, const Row &values, const std::string &key, const Value &id)
Updates a single row by primary key value.
Definition Database.cpp:157
Database module (eve.Database): SQLite connection factory. Script: db <- eve.Database(); conn <- db....
Definition Database.h:242
Connection * connectSQLite(std::string path)
Opens a SQLite database file (":memory:" is supported).
Definition Database.cpp:260
Connection * connect(std::string connector, std::string connectionString)
Opens a connection with an explicit Poco connector name.
Definition Database.cpp:257
Runtime ORM model exposed to Squirrel.
Definition Database.h:222
std::string allJson()
Definition Database.cpp:244
int updateJson(const std::string &idJson, const std::string &json)
Definition Database.cpp:248
int insertJson(const std::string &json)
Definition Database.cpp:243
int remove(const std::string &idJson)
Definition Database.cpp:251
Model(Connection *connection, std::string table, std::string primaryKey)
Definition Database.cpp:238
std::string findJson(const std::string &idJson)
Definition Database.cpp:245
Composable filtered/sorted/paged query shared by C++ and scripts.
Definition Database.h:133
Query(Connection *connection, std::string table)
Definition Database.cpp:175
std::string firstJson()
Definition Database.cpp:227
std::vector< Row > all()
Definition Database.cpp:225
Query * limit(int count)
Definition Database.cpp:196
Query * offset(int count)
Definition Database.cpp:202
Query * orderBy(const std::string &column, bool ascending=true)
Definition Database.cpp:191
Query * where(const std::string &column, const std::string &op, Value value)
Definition Database.cpp:179
Query * whereJson(const std::string &column, const std::string &op, const std::string &valueJson)
Definition Database.cpp:187
std::string allJson()
Definition Database.cpp:226
std::unordered_map< std::string, Value > Row
Definition Database.h:25
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:24