1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-02-09 09:39:53 +00:00

Queries. Data structures and lua bindings.

This commit is contained in:
Petr Mikheev 2021-04-26 23:20:16 +02:00
parent 479856f812
commit b53667d555
7 changed files with 444 additions and 0 deletions

View File

@ -19,6 +19,7 @@ if (GTEST_FOUND AND GMOCK_FOUND)
lua/test_scriptscontainer.cpp
lua/test_utilpackage.cpp
lua/test_serialization.cpp
lua/test_querypackage.cpp
misc/test_stringops.cpp
misc/test_endianness.cpp

View File

@ -0,0 +1,29 @@
#include "gmock/gmock.h"
#include <gtest/gtest.h>
#include <components/queries/luabindings.hpp>
namespace
{
using namespace testing;
TEST(LuaQueryPackageTest, basic)
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::string);
Queries::registerQueryBindings(lua);
lua["query"] = Queries::Query("test");
lua["fieldX"] = Queries::Field({ "x" }, typeid(std::string));
lua["fieldY"] = Queries::Field({ "y" }, typeid(int));
lua.safe_script("t = query:where(fieldX:eq('abc') + fieldX:like('%abcd%'))");
lua.safe_script("t = t:where(fieldY:gt(5))");
lua.safe_script("t = t:orderBy(fieldX)");
lua.safe_script("t = t:orderByDesc(fieldY)");
lua.safe_script("t = t:groupBy(fieldY)");
lua.safe_script("t = t:limit(10):offset(5)");
EXPECT_EQ(
lua.safe_script("return tostring(t)").get<std::string>(),
"SELECT test WHERE ((x == \"abc\") OR (x LIKE \"%abcd%\")) AND (y > 5) ORDER BY x, y DESC GROUP BY y LIMIT 10 OFFSET 5");
}
}

View File

@ -156,6 +156,10 @@ add_component_dir (fallback
fallback validate
)
add_component_dir (queries
query luabindings
)
if(WIN32)
add_component_dir (crashcatcher
windows_crashcatcher

View File

@ -0,0 +1,118 @@
#include "luabindings.hpp"
namespace sol
{
template <>
struct is_automagical<Queries::Field> : std::false_type {};
template <>
struct is_automagical<Queries::Filter> : std::false_type {};
template <>
struct is_automagical<Queries::Query> : std::false_type {};
}
namespace Queries
{
template <Condition::Type type>
struct CondBuilder
{
Filter operator()(const Field& field, const sol::object& o)
{
FieldValue value;
if (field.type() == typeid(bool) && o.is<bool>())
value = o.as<bool>();
else if (field.type() == typeid(int32_t) && o.is<int32_t>())
value = o.as<int32_t>();
else if (field.type() == typeid(int64_t) && o.is<int64_t>())
value = o.as<int64_t>();
else if (field.type() == typeid(float) && o.is<float>())
value = o.as<float>();
else if (field.type() == typeid(double) && o.is<double>())
value = o.as<double>();
else if (field.type() == typeid(std::string) && o.is<std::string>())
value = o.as<std::string>();
else
throw std::logic_error("Invalid value for field " + field.toString());
Filter filter;
filter.add({&field, type, value});
return filter;
}
};
void registerQueryBindings(sol::state& lua)
{
sol::usertype<Field> field = lua.new_usertype<Field>("QueryField");
sol::usertype<Filter> filter = lua.new_usertype<Filter>("QueryFilter");
sol::usertype<Query> query = lua.new_usertype<Query>("Query");
field[sol::meta_function::to_string] = [](const Field& f) { return f.toString(); };
field["eq"] = CondBuilder<Condition::EQUAL>();
field["neq"] = CondBuilder<Condition::NOT_EQUAL>();
field["lt"] = CondBuilder<Condition::LESSER>();
field["lte"] = CondBuilder<Condition::LESSER_OR_EQUAL>();
field["gt"] = CondBuilder<Condition::GREATER>();
field["gte"] = CondBuilder<Condition::GREATER_OR_EQUAL>();
field["like"] = CondBuilder<Condition::LIKE>();
filter[sol::meta_function::to_string] = [](const Filter& filter) { return filter.toString(); };
filter[sol::meta_function::multiplication] = [](const Filter& a, const Filter& b)
{
Filter res = a;
res.add(b, Operation::AND);
return res;
};
filter[sol::meta_function::addition] = [](const Filter& a, const Filter& b)
{
Filter res = a;
res.add(b, Operation::OR);
return res;
};
filter[sol::meta_function::unary_minus] = [](const Filter& a)
{
Filter res = a;
if (!a.mConditions.empty())
res.mOperations.push_back({Operation::NOT, 0});
return res;
};
query[sol::meta_function::to_string] = [](const Query& q) { return q.toString(); };
query["where"] = [](const Query& q, const Filter& filter)
{
Query res = q;
res.mFilter.add(filter, Operation::AND);
return res;
};
query["orderBy"] = [](const Query& q, const Field& field)
{
Query res = q;
res.mOrderBy.push_back({&field, false});
return res;
};
query["orderByDesc"] = [](const Query& q, const Field& field)
{
Query res = q;
res.mOrderBy.push_back({&field, true});
return res;
};
query["groupBy"] = [](const Query& q, const Field& field)
{
Query res = q;
res.mGroupBy.push_back(&field);
return res;
};
query["offset"] = [](const Query& q, int64_t offset)
{
Query res = q;
res.mOffset = offset;
return res;
};
query["limit"] = [](const Query& q, int64_t limit)
{
Query res = q;
res.mLimit = limit;
return res;
};
}
}

View File

@ -0,0 +1,8 @@
#include <sol/sol.hpp>
#include "query.hpp"
namespace Queries
{
void registerQueryBindings(sol::state& lua);
}

View File

@ -0,0 +1,185 @@
#include "query.hpp"
#include <sstream>
#include <iomanip>
namespace Queries
{
Field::Field(std::vector<std::string> path, std::type_index type)
: mPath(std::move(path))
, mType(type) {}
std::string Field::toString() const
{
std::string result;
for (const std::string& segment : mPath)
{
if (!result.empty())
result += ".";
result += segment;
}
return result;
}
std::string toString(const FieldValue& value)
{
return std::visit([](auto&& arg) -> std::string
{
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::string>)
{
std::ostringstream oss;
oss << std::quoted(arg);
return oss.str();
}
else if constexpr (std::is_same_v<T, bool>)
return arg ? "true" : "false";
else
return std::to_string(arg);
}, value);
}
std::string Condition::toString() const
{
std::string res;
res += mField->toString();
switch (mType)
{
case Condition::EQUAL: res += " == "; break;
case Condition::NOT_EQUAL: res += " != "; break;
case Condition::LESSER: res += " < "; break;
case Condition::LESSER_OR_EQUAL: res += " <= "; break;
case Condition::GREATER: res += " > "; break;
case Condition::GREATER_OR_EQUAL: res += " >= "; break;
case Condition::LIKE: res += " LIKE "; break;
}
res += Queries::toString(mValue);
return res;
}
void Filter::add(const Condition& c, Operation::Type op)
{
mOperations.push_back({Operation::PUSH, mConditions.size()});
mConditions.push_back(c);
if (mConditions.size() > 1)
mOperations.push_back({op, 0});
}
void Filter::add(const Filter& f, Operation::Type op)
{
size_t conditionOffset = mConditions.size();
size_t operationsBefore = mOperations.size();
mConditions.insert(mConditions.end(), f.mConditions.begin(), f.mConditions.end());
mOperations.insert(mOperations.end(), f.mOperations.begin(), f.mOperations.end());
for (size_t i = operationsBefore; i < mOperations.size(); ++i)
mOperations[i].mConditionIndex += conditionOffset;
if (conditionOffset > 0 && !f.mConditions.empty())
mOperations.push_back({op, 0});
}
std::string Filter::toString() const
{
if(mOperations.empty())
return "";
std::vector<std::string> stack;
auto pop = [&stack](){ auto v = stack.back(); stack.pop_back(); return v; };
auto push = [&stack](const std::string& s) { stack.push_back(s); };
for (const Operation& op : mOperations)
{
if(op.mType == Operation::PUSH)
push(mConditions[op.mConditionIndex].toString());
else if(op.mType == Operation::AND)
{
auto rhs = pop();
auto lhs = pop();
std::string res;
res += "(";
res += lhs;
res += ") AND (";
res += rhs;
res += ")";
push(res);
}
else if (op.mType == Operation::OR)
{
auto rhs = pop();
auto lhs = pop();
std::string res;
res += "(";
res += lhs;
res += ") OR (";
res += rhs;
res += ")";
push(res);
}
else if (op.mType == Operation::NOT)
{
std::string res;
res += "NOT (";
res += pop();
res += ")";
push(res);
}
else
throw std::logic_error("Unknown operation type!");
}
return pop();
}
std::string Query::toString() const
{
std::string res;
res += "SELECT ";
res += mQueryType;
std::string filter = mFilter.toString();
if(!filter.empty())
{
res += " WHERE ";
res += filter;
}
std::string order;
for(const OrderBy& ord : mOrderBy)
{
if(!order.empty())
order += ", ";
order += ord.mField->toString();
if(ord.mDescending)
order += " DESC";
}
if (!order.empty())
{
res += " ORDER BY ";
res += order;
}
std::string group;
for (const Field* f: mGroupBy)
{
if (!group.empty())
group += " ,";
group += f->toString();
}
if (!group.empty())
{
res += " GROUP BY ";
res += group;
}
if (mLimit != sNoLimit)
{
res += " LIMIT ";
res += std::to_string(mLimit);
}
if (mOffset != 0)
{
res += " OFFSET ";
res += std::to_string(mOffset);
}
return res;
}
}

View File

@ -0,0 +1,99 @@
#ifndef COMPONENTS_QUERIES_QUERY
#define COMPONENTS_QUERIES_QUERY
#include <string>
#include <vector>
#include <typeindex>
#include <variant>
#include <stdexcept>
namespace Queries
{
class Field
{
public:
Field(std::vector<std::string> path, std::type_index type);
const std::vector<std::string>& path() const { return mPath; }
const std::type_index type() const { return mType; }
std::string toString() const;
private:
std::vector<std::string> mPath;
std::type_index mType;
};
struct OrderBy
{
const Field* mField;
bool mDescending;
};
using FieldValue = std::variant<bool, int32_t, int64_t, float, double, std::string>;
std::string toString(const FieldValue& value);
struct Condition
{
enum Type
{
EQUAL = 0,
NOT_EQUAL = 1,
GREATER = 2,
GREATER_OR_EQUAL = 3,
LESSER = 4,
LESSER_OR_EQUAL = 5,
LIKE = 6,
};
std::string toString() const;
const Field* mField;
Type mType;
FieldValue mValue;
};
struct Operation
{
enum Type
{
PUSH = 0, // push condition on stack
NOT = 1, // invert top condition on stack
AND = 2, // logic AND for two top conditions
OR = 3, // logic OR for two top conditions
};
Type mType;
size_t mConditionIndex; // used only if mType == PUSH
};
struct Filter
{
std::string toString() const;
// combines with given condition or filter using operation `AND` or `OR`.
void add(const Condition& c, Operation::Type op = Operation::AND);
void add(const Filter& f, Operation::Type op = Operation::AND);
std::vector<Condition> mConditions;
std::vector<Operation> mOperations; // operations on conditions in reverse polish notation
};
struct Query
{
static constexpr int64_t sNoLimit = -1;
Query(std::string type) : mQueryType(std::move(type)) {}
std::string toString() const;
std::string mQueryType;
Filter mFilter;
std::vector<OrderBy> mOrderBy;
std::vector<const Field*> mGroupBy;
int64_t mOffset = 0;
int64_t mLimit = sNoLimit;
};
}
#endif // !COMPONENTS_QUERIES_QUERY