Merge pull request #29 from realm/al-js-fixes

Outstanding ObjectStore changes from the JS branch
This commit is contained in:
Ari Lazier 2016-01-19 10:32:58 -08:00
commit dd2c87c3b7
13 changed files with 1593 additions and 51 deletions

View File

@ -60,6 +60,11 @@ void List::remove(std::size_t row_ndx) {
m_link_view->remove(row_ndx);
}
Query List::get_query() {
verify_attached();
return m_link_view->get_target_table().where(m_link_view);
}
void List::verify_valid_row(std::size_t row_ndx, bool insertion) {
size_t size = m_link_view->size();
if (row_ndx > size || (!insertion && row_ndx == size)) {

View File

@ -25,9 +25,9 @@
namespace realm {
class List {
public:
List(SharedRealm &r, const ObjectSchema &s, LinkViewRef l) : object_schema(s), m_realm(r), m_link_view(l) {}
List(SharedRealm &r, const ObjectSchema &s, LinkViewRef l) : m_realm(r), m_object_schema(&s), m_link_view(l) {}
const ObjectSchema &object_schema;
const ObjectSchema &get_object_schema() const { return *m_object_schema; }
SharedRealm realm() { return m_realm; }
size_t size();
@ -47,16 +47,17 @@ namespace realm {
template<typename ValueType, typename ContextType>
void set(ContextType ctx, ValueType value, size_t list_ndx);
Query get_query();
void verify_valid_row(std::size_t row_ndx, bool insertion = false);
void verify_attached();
void verify_in_tranaction();
private:
SharedRealm m_realm;
const ObjectSchema *m_object_schema;
LinkViewRef m_link_view;
};
}
#endif /* REALM_LIST_HPP */

View File

@ -7,13 +7,14 @@
#include <string>
#include "shared_realm.hpp"
#include "schema.hpp"
#include "list.hpp"
namespace realm {
class Object {
public:
Object(SharedRealm r, const ObjectSchema &s, Row o) : m_realm(r), object_schema(s), m_row(o) {}
Object(SharedRealm r, const ObjectSchema &s, Row o) : m_realm(r), m_object_schema(&s), m_row(o) {}
// property getter/setter
template<typename ValueType, typename ContextType>
@ -24,14 +25,15 @@ namespace realm {
// create an Object from a native representation
template<typename ValueType, typename ContextType>
static inline Object create(ContextType ctx, SharedRealm realm, ObjectSchema &object_schema, ValueType value, bool try_update);
static inline Object create(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, ValueType value, bool try_update);
const ObjectSchema &object_schema;
SharedRealm realm() { return m_realm; }
const ObjectSchema &get_object_schema() { return *m_object_schema; }
Row row() { return m_row; }
private:
SharedRealm m_realm;
const ObjectSchema *m_object_schema;
Row m_row;
template<typename ValueType, typename ContextType>
@ -76,6 +78,9 @@ namespace realm {
static size_t to_object_index(ContextType ctx, SharedRealm realm, ValueType &val, const std::string &type, bool try_update);
static ValueType from_object(ContextType ctx, Object);
// object index for an existing object
static size_t to_existing_object_index(ContextType ctx, ValueType &val);
// list value acessors
static size_t list_size(ContextType ctx, ValueType &val);
static ValueType list_value_at_index(ContextType ctx, ValueType &val, size_t index);
@ -115,10 +120,10 @@ namespace realm {
template <typename ValueType, typename ContextType>
inline void Object::set_property_value(ContextType ctx, std::string prop_name, ValueType value, bool try_update)
{
const Property *prop = object_schema.property_for_name(prop_name);
const Property *prop = m_object_schema->property_for_name(prop_name);
if (!prop) {
throw InvalidPropertyException(object_schema.name, prop_name,
"Setting invalid property '" + prop_name + "' on object '" + object_schema.name + "'.");
throw InvalidPropertyException(m_object_schema->name, prop_name,
"Setting invalid property '" + prop_name + "' on object '" + m_object_schema->name + "'.");
}
set_property_value_impl(ctx, *prop, value, try_update);
};
@ -126,10 +131,10 @@ namespace realm {
template <typename ValueType, typename ContextType>
inline ValueType Object::get_property_value(ContextType ctx, std::string prop_name)
{
const Property *prop = object_schema.property_for_name(prop_name);
const Property *prop = m_object_schema->property_for_name(prop_name);
if (!prop) {
throw InvalidPropertyException(object_schema.name, prop_name,
"Getting invalid property '" + prop_name + "' on object '" + object_schema.name + "'.");
throw InvalidPropertyException(m_object_schema->name, prop_name,
"Getting invalid property '" + prop_name + "' on object '" + m_object_schema->name + "'.");
}
return get_property_value_impl<ValueType>(ctx, *prop);
};
@ -239,7 +244,7 @@ namespace realm {
}
template<typename ValueType, typename ContextType>
inline Object Object::create(ContextType ctx, SharedRealm realm, ObjectSchema &object_schema, ValueType value, bool try_update)
inline Object Object::create(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, ValueType value, bool try_update)
{
using Accessor = NativeAccessor<ValueType, ContextType>;
@ -279,7 +284,7 @@ namespace realm {
// populate
Object object(realm, object_schema, table->get(row_index));
for (Property &prop : object_schema.properties) {
for (const Property &prop : object_schema.properties) {
if (created || !prop.is_primary) {
if (Accessor::dict_has_value_for_key(ctx, value, prop.name)) {
object.set_property_value_impl(ctx, prop, Accessor::dict_value_for_key(ctx, value, prop.name), try_update);
@ -304,19 +309,19 @@ namespace realm {
template<typename ValueType, typename ContextType>
void List::add(ContextType ctx, ValueType value)
{
add(NativeAccessor<ValueType, ContextType>::to_object_index(ctx, m_realm, value, object_schema.name, false));
add(NativeAccessor<ValueType, ContextType>::to_object_index(ctx, m_realm, value, get_object_schema().name, false));
}
template<typename ValueType, typename ContextType>
void List::insert(ContextType ctx, ValueType value, size_t list_ndx)
{
insert(list_ndx, NativeAccessor<ValueType, ContextType>::to_object_index(ctx, m_realm, value, object_schema.name, false));
insert(list_ndx, NativeAccessor<ValueType, ContextType>::to_object_index(ctx, m_realm, value, get_object_schema().name, false));
}
template<typename ValueType, typename ContextType>
void List::set(ContextType ctx, ValueType value, size_t list_ndx)
{
set(list_ndx, NativeAccessor<ValueType, ContextType>::to_object_index(ctx, m_realm, value, object_schema.name, false));
set(list_ndx, NativeAccessor<ValueType, ContextType>::to_object_index(ctx, m_realm, value, get_object_schema().name, false));
}
}

344
parser/parser.cpp Normal file
View File

@ -0,0 +1,344 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#include "parser.hpp"
#include <iostream>
#include <pegtl.hh>
#include <pegtl/analyze.hh>
#include <pegtl/trace.hh>
using namespace pegtl;
namespace realm {
namespace parser {
// strings
struct unicode : list< seq< one< 'u' >, rep< 4, must< xdigit > > >, one< '\\' > > {};
struct escaped_char : one< '"', '\'', '\\', '/', 'b', 'f', 'n', 'r', 't', '0' > {};
struct escaped : sor< escaped_char, unicode > {};
struct unescaped : utf8::range< 0x20, 0x10FFFF > {};
struct chars : if_then_else< one< '\\' >, must< escaped >, unescaped > {};
struct dq_string_content : until< at< one< '"' > >, must< chars > > {};
struct dq_string : seq< one< '"' >, must< dq_string_content >, any > {};
struct sq_string_content : until< at< one< '\'' > >, must< chars > > {};
struct sq_string : seq< one< '\'' >, must< sq_string_content >, any > {};
// numbers
struct minus : opt< one< '-' > > {};
struct dot : one< '.' > {};
struct float_num : sor<
seq< plus< digit >, dot, star< digit > >,
seq< star< digit >, dot, plus< digit > >
> {};
struct hex_num : seq< one< '0' >, one< 'x', 'X' >, plus< xdigit > > {};
struct int_num : plus< digit > {};
struct number : seq< minus, sor< float_num, hex_num, int_num > > {};
struct true_value : pegtl_istring_t("true") {};
struct false_value : pegtl_istring_t("false") {};
// key paths
struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {};
// argument
struct argument_index : plus< digit > {};
struct argument : seq< one< '$' >, must< argument_index > > {};
// expressions and operators
struct expr : sor< dq_string, sq_string, number, argument, true_value, false_value, key_path > {};
struct eq : sor< two< '=' >, one< '=' > > {};
struct noteq : pegtl::string< '!', '=' > {};
struct lteq : pegtl::string< '<', '=' > {};
struct lt : one< '<' > {};
struct gteq : pegtl::string< '>', '=' > {};
struct gt : one< '>' > {};
struct contains : pegtl_istring_t("contains") {};
struct begins : pegtl_istring_t("beginswith") {};
struct ends : pegtl_istring_t("endswith") {};
template<typename A, typename B>
struct pad_plus : seq< plus< B >, A, plus< B > > {};
struct padded_oper : pad_plus< sor< contains, begins, ends >, blank > {};
struct symbolic_oper : pad< sor< eq, noteq, lteq, lt, gteq, gt >, blank > {};
// predicates
struct comparison_pred : seq< expr, sor< padded_oper, symbolic_oper >, expr > {};
struct pred;
struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {};
struct true_pred : pegtl_istring_t("truepredicate") {};
struct false_pred : pegtl_istring_t("falsepredicate") {};
struct not_pre : seq< sor< one< '!' >, pegtl_istring_t("not") > > {};
struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, true_pred, false_pred, comparison_pred >, blank > > {};
struct and_op : pad< sor< two< '&' >, pegtl_istring_t("and") >, blank > {};
struct or_op : pad< sor< two< '|' >, pegtl_istring_t("or") >, blank > {};
struct or_ext : if_must< or_op, pred > {};
struct and_ext : if_must< and_op, pred > {};
struct and_pred : seq< atom_pred, star< and_ext > > {};
struct pred : seq< and_pred, star< or_ext > > {};
// state
struct ParserState
{
std::vector<Predicate *> group_stack;
Predicate *current_group()
{
return group_stack.back();
}
Predicate *last_predicate()
{
Predicate *pred = current_group();
while (pred->type != Predicate::Type::Comparison && pred->cpnd.sub_predicates.size()) {
pred = &pred->cpnd.sub_predicates.back();
}
return pred;
}
void add_predicate_to_current_group(Predicate::Type type)
{
current_group()->cpnd.sub_predicates.emplace_back(type, negate_next);
negate_next = false;
if (current_group()->cpnd.sub_predicates.size() > 1) {
if (next_type == Predicate::Type::Or) {
apply_or();
}
else {
apply_and();
}
}
}
bool negate_next = false;
Predicate::Type next_type = Predicate::Type::And;
void add_expression(Expression && exp)
{
Predicate *current = last_predicate();
if (current->type == Predicate::Type::Comparison && current->cmpr.expr[1].type == parser::Expression::Type::None) {
current->cmpr.expr[1] = std::move(exp);
}
else {
add_predicate_to_current_group(Predicate::Type::Comparison);
last_predicate()->cmpr.expr[0] = std::move(exp);
}
}
void apply_or()
{
Predicate *group = current_group();
if (group->type == Predicate::Type::Or) {
return;
}
// convert to OR
group->type = Predicate::Type::Or;
if (group->cpnd.sub_predicates.size() > 2) {
// split the current group into an AND group ORed with the last subpredicate
Predicate new_sub(Predicate::Type::And);
new_sub.cpnd.sub_predicates = std::move(group->cpnd.sub_predicates);
group->cpnd.sub_predicates = { new_sub, std::move(new_sub.cpnd.sub_predicates.back()) };
group->cpnd.sub_predicates[0].cpnd.sub_predicates.pop_back();
}
}
void apply_and()
{
if (current_group()->type == Predicate::Type::And) {
return;
}
auto &sub_preds = current_group()->cpnd.sub_predicates;
auto second_last = sub_preds.end() - 2;
if (second_last->type == Predicate::Type::And && !second_last->negate) {
// make a new and group populated with the last two predicates
second_last->cpnd.sub_predicates.push_back(std::move(sub_preds.back()));
sub_preds.pop_back();
}
else {
// otherwise combine last two into a new AND group
Predicate pred(Predicate::Type::And);
pred.cpnd.sub_predicates.insert(pred.cpnd.sub_predicates.begin(), second_last, sub_preds.end());
sub_preds.erase(second_last, sub_preds.end());
sub_preds.emplace_back(std::move(pred));
}
}
};
// rules
template< typename Rule >
struct action : nothing< Rule > {};
#ifdef REALM_PARSER_PRINT_TOKENS
#define DEBUG_PRINT_TOKEN(string) std::cout << string << std::endl
#else
#define DEBUG_PRINT_TOKEN(string)
#endif
template<> struct action< and_op >
{
static void apply( const input & in, ParserState & state )
{
DEBUG_PRINT_TOKEN("<and>");
state.next_type = Predicate::Type::And;
}
};
template<> struct action< or_op >
{
static void apply( const input & in, ParserState & state )
{
DEBUG_PRINT_TOKEN("<or>");
state.next_type = Predicate::Type::Or;
}
};
#define EXPRESSION_ACTION(rule, type) \
template<> struct action< rule > { \
static void apply( const input & in, ParserState & state ) { \
DEBUG_PRINT_TOKEN(in.string()); \
state.add_expression(Expression{type, in.string()}); }};
EXPRESSION_ACTION(dq_string_content, Expression::Type::String)
EXPRESSION_ACTION(sq_string_content, Expression::Type::String)
EXPRESSION_ACTION(key_path, Expression::Type::KeyPath)
EXPRESSION_ACTION(number, Expression::Type::Number)
EXPRESSION_ACTION(true_value, Expression::Type::True)
EXPRESSION_ACTION(false_value, Expression::Type::False)
EXPRESSION_ACTION(argument_index, Expression::Type::Argument)
template<> struct action< true_pred >
{
static void apply( const input & in, ParserState & state )
{
DEBUG_PRINT_TOKEN(in.string());
state.current_group()->cpnd.sub_predicates.emplace_back(Predicate::Type::True);
}
};
template<> struct action< false_pred >
{
static void apply( const input & in, ParserState & state )
{
DEBUG_PRINT_TOKEN(in.string());
state.current_group()->cpnd.sub_predicates.emplace_back(Predicate::Type::False);
}
};
#define OPERATOR_ACTION(rule, oper) \
template<> struct action< rule > { \
static void apply( const input & in, ParserState & state ) { \
DEBUG_PRINT_TOKEN(in.string()); \
state.last_predicate()->cmpr.op = oper; }};
OPERATOR_ACTION(eq, Predicate::Operator::Equal)
OPERATOR_ACTION(noteq, Predicate::Operator::NotEqual)
OPERATOR_ACTION(gteq, Predicate::Operator::GreaterThanOrEqual)
OPERATOR_ACTION(gt, Predicate::Operator::GreaterThan)
OPERATOR_ACTION(lteq, Predicate::Operator::LessThanOrEqual)
OPERATOR_ACTION(lt, Predicate::Operator::LessThan)
OPERATOR_ACTION(begins, Predicate::Operator::BeginsWith)
OPERATOR_ACTION(ends, Predicate::Operator::EndsWith)
OPERATOR_ACTION(contains, Predicate::Operator::Contains)
template<> struct action< one< '(' > >
{
static void apply( const input & in, ParserState & state )
{
DEBUG_PRINT_TOKEN("<begin_group>");
state.add_predicate_to_current_group(Predicate::Type::And);
state.group_stack.push_back(state.last_predicate());
}
};
template<> struct action< group_pred >
{
static void apply( const input & in, ParserState & state )
{
DEBUG_PRINT_TOKEN("<end_group>");
state.group_stack.pop_back();
}
};
template<> struct action< not_pre >
{
static void apply( const input & in, ParserState & state )
{
DEBUG_PRINT_TOKEN("<not>");
state.negate_next = true;
}
};
template< typename Rule >
struct error_message_control : pegtl::normal< Rule >
{
static const std::string error_message;
template< typename Input, typename ... States >
static void raise( const Input & in, States && ... )
{
throw pegtl::parse_error( error_message, in );
}
};
template<>
const std::string error_message_control< chars >::error_message = "Invalid characters in string constant.";
template< typename Rule>
const std::string error_message_control< Rule >::error_message = "Invalid predicate.";
Predicate parse(const std::string &query)
{
DEBUG_PRINT_TOKEN(query);
Predicate out_predicate(Predicate::Type::And);
ParserState state;
state.group_stack.push_back(&out_predicate);
pegtl::parse< must< pred, eof >, action, error_message_control >(query, query, state);
if (out_predicate.type == Predicate::Type::And && out_predicate.cpnd.sub_predicates.size() == 1) {
return std::move(out_predicate.cpnd.sub_predicates.back());
}
return std::move(out_predicate);
}
void analyze_grammar()
{
analyze<pred>();
}
}}

86
parser/parser.hpp Normal file
View File

@ -0,0 +1,86 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#ifndef REALM_PARSER_HPP
#define REALM_PARSER_HPP
#include <vector>
#include <string>
namespace realm {
class Schema;
namespace parser {
struct Expression
{
enum class Type { None, Number, String, KeyPath, Argument, True, False } type = Type::None;
std::string s;
};
struct Predicate
{
enum class Type
{
Comparison,
Or,
And,
True,
False
} type = Type::And;
enum class Operator
{
None,
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
BeginsWith,
EndsWith,
Contains
};
struct Comparison
{
Operator op = Operator::None;
Expression expr[2];
};
struct Compound
{
std::vector<Predicate> sub_predicates;
};
Comparison cmpr;
Compound cpnd;
bool negate = false;
Predicate(Type t, bool n = false) : type(t), negate(n) {}
};
Predicate parse(const std::string &query);
void analyze_grammar();
bool test_grammar();
}
}
#endif // REALM_PARSER_HPP

322
parser/queryTests.json Normal file
View File

@ -0,0 +1,322 @@
{
"dateTests" : {
"schema" : [{
"name": "DateObject",
"properties": [{ "name": "date", "type": "date" }]
}],
"objects": [
{ "type": "DateObject", "value": [10000] },
{ "type": "DateObject", "value": [10001] },
{ "type": "DateObject", "value": [10002] }
],
"tests": [
["QueryCount", 2, "DateObject", "date < $0", [2, "date"]],
["QueryCount", 3, "DateObject", "date <= $0", [2, "date"]],
["QueryCount", 2, "DateObject", "date > $0", [0, "date"]],
["QueryCount", 3, "DateObject", "date >= $0", [0, "date"]],
["QueryCount", 1, "DateObject", "date == $0", [0, "date"]],
["QueryCount", 2, "DateObject", "date != $0", [0, "date"]],
["QueryThrows", "DateObject", "date == 'not a date'"],
["QueryThrows", "DateObject", "date == 1"],
["QueryThrows", "DateObject", "date == $0", 1]
]
},
"boolTests" : {
"schema" : [{
"name": "BoolObject",
"properties": [{ "name": "boolCol", "type": "bool" }]
}],
"objects": [
{ "type": "BoolObject", "value": [false] },
{ "type": "BoolObject", "value": [true] },
{ "type": "BoolObject", "value": [true] }
],
"tests": [
["QueryCount", 2, "BoolObject", "boolCol == true"],
["QueryCount", 1, "BoolObject", "boolCol==false"],
["QueryCount", 1, "BoolObject", "boolCol != true"],
["QueryCount", 2, "BoolObject", "true == boolCol"],
["QueryCount", 2, "BoolObject", "boolCol == TRUE"],
["QueryCount", 1, "BoolObject", "boolCol == FALSE"],
["QueryCount", 2, "BoolObject", "boolCol == $0", true],
["QueryCount", 1, "BoolObject", "boolCol == $0", false],
["QueryCount", 0, "BoolObject", "boolCol == true && boolCol == false"],
["QueryCount", 3, "BoolObject", "boolCol == true || boolCol == false"],
["QueryThrows", "BoolObject", "boolCol == 0"],
["QueryThrows", "BoolObject", "boolCol == 1"],
["QueryThrows", "BoolObject", "boolCol == 'not a bool'"],
["QueryThrows", "BoolObject", "boolCol == $0", "not a bool"],
["QueryThrows", "BoolObject", "boolCol > true"],
["QueryThrows", "BoolObject", "boolCol >= true"],
["QueryThrows", "BoolObject", "boolCol < true"],
["QueryThrows", "BoolObject", "boolCol <= true"],
["QueryThrows", "BoolObject", "boolCol BEGINSWITH true"],
["QueryThrows", "BoolObject", "boolCol CONTAINS true"],
["QueryThrows", "BoolObject", "boolCol ENDSWITH true"]
]
},
"intTests" : {
"schema" : [{
"name": "IntObject",
"properties": [{ "name": "intCol", "type": "int" }]
}],
"objects": [
{ "type": "IntObject", "value": [-1] },
{ "type": "IntObject", "value": [0] },
{ "type": "IntObject", "value": [100] }
],
"tests": [
["QueryCount", 1, "IntObject", "intCol == -1"],
["QueryCount", 1, "IntObject", "intCol==0"],
["QueryCount", 0, "IntObject", "1 == intCol"],
["QueryCount", 2, "IntObject", "intCol != 0"],
["QueryCount", 2, "IntObject", "intCol > -1"],
["QueryCount", 3, "IntObject", "intCol >= -1"],
["QueryCount", 2, "IntObject", "intCol < 100"],
["QueryCount", 3, "IntObject", "intCol <= 100"],
["QueryCount", 1, "IntObject", "intCol > 0x1F"],
["QueryCount", 1, "IntObject", "intCol == $0", 100],
["QueryThrows", "IntObject", "intCol == 'not an int'"],
["QueryThrows", "IntObject", "intCol == true"],
["QueryThrows", "IntObject", "intCol == $0", "not an int"],
["QueryThrows", "IntObject", "intCol BEGINSWITH 1"],
["QueryThrows", "IntObject", "intCol CONTAINS 1"],
["QueryThrows", "IntObject", "intCol ENDSWITH 1"]
]
},
"floatTests" : {
"schema" : [{
"name": "FloatObject",
"properties": [{ "name": "floatCol", "type": "float" }]
}],
"objects": [
{ "type": "FloatObject", "value": [-1.001] },
{ "type": "FloatObject", "value": [0.0] },
{ "type": "FloatObject", "value": [100.2] }
],
"tests": [
["QueryCount", 1, "FloatObject", "floatCol == -1.001"],
["QueryCount", 1, "FloatObject", "floatCol = 0"],
["QueryCount", 0, "FloatObject", "1 == floatCol"],
["QueryCount", 2, "FloatObject", "floatCol != 0"],
["QueryCount", 2, "FloatObject", "floatCol > -1.001"],
["QueryCount", 3, "FloatObject", "floatCol >= -1.001"],
["QueryCount", 2, "FloatObject", "floatCol < 100.2"],
["QueryCount", 3, "FloatObject", "floatCol <= 100.2"],
["QueryCount", 1, "FloatObject", "floatCol > 0x1F"],
["QueryCount", 1, "FloatObject", "floatCol == $0", 100.2],
["QueryThrows", "FloatObject", "floatCol == 'not a float'"],
["QueryThrows", "FloatObject", "floatCol == true"],
["QueryThrows", "FloatObject", "floatCol == $0", "not a float"],
["QueryThrows", "FloatObject", "floatCol BEGINSWITH 1"],
["QueryThrows", "FloatObject", "floatCol CONTAINS 1"],
["QueryThrows", "FloatObject", "floatCol ENDSWITH 1"],
["Disabled", "QueryThrows", "FloatObject", "floatCol = 3.5e+38"],
["Disabled", "QueryThrows", "FloatObject", "floatCol = -3.5e+38"]
]
},
"doubleTests" : {
"schema" : [{
"name": "DoubleObject",
"properties": [{ "name": "doubleCol", "type": "double" }]
}],
"objects": [
{ "type": "DoubleObject", "value": [-1.001] },
{ "type": "DoubleObject", "value": [0.0] },
{ "type": "DoubleObject", "value": [100.2] }
],
"tests": [
["QueryCount", 1, "DoubleObject", "doubleCol == -1.001"],
["QueryCount", 1, "DoubleObject", "doubleCol == 0"],
["QueryCount", 0, "DoubleObject", "1 == doubleCol"],
["QueryCount", 2, "DoubleObject", "doubleCol != 0"],
["QueryCount", 2, "DoubleObject", "doubleCol > -1.001"],
["QueryCount", 3, "DoubleObject", "doubleCol >= -1.001"],
["QueryCount", 2, "DoubleObject", "doubleCol < 100.2"],
["QueryCount", 3, "DoubleObject", "doubleCol <= 100.2"],
["QueryCount", 1, "DoubleObject", "doubleCol > 0x1F"],
["QueryCount", 1, "DoubleObject", "doubleCol == $0", 100.2],
["QueryThrows", "DoubleObject", "doubleCol == 'not a double'"],
["QueryThrows", "DoubleObject", "doubleCol == true"],
["QueryThrows", "DoubleObject", "doubleCol == $0", "not a double"],
["QueryThrows", "DoubleObject", "doubleCol BEGINSWITH 1"],
["QueryThrows", "DoubleObject", "doubleCol CONTAINS 1"],
["QueryThrows", "DoubleObject", "doubleCol ENDSWITH 1"]
]
},
"stringTests" : {
"schema" : [{
"name": "StringObject",
"properties": [{ "name": "stringCol", "type": "string" }]
}],
"objects": [
{ "type": "StringObject", "value": ["A"] },
{ "type": "StringObject", "value": ["a"] },
{ "type": "StringObject", "value": ["a"] },
{ "type": "StringObject", "value": ["C"] },
{ "type": "StringObject", "value": ["c"] },
{ "type": "StringObject", "value": ["abc"] },
{ "type": "StringObject", "value": ["ABC"] },
{ "type": "StringObject", "value": [""] },
{ "type": "StringObject", "value": ["\\\"\\n\\0\\r\\\\'"] }
],
"tests": [
["QueryCount", 2, "StringObject", "stringCol == 'a'"],
["QueryCount", 1, "StringObject", "'c' == stringCol"],
["QueryCount", 2, "StringObject", "stringCol == \"a\""],
["QueryCount", 1, "StringObject", "stringCol=='abc'"],
["QueryCount", 1, "StringObject", "stringCol == ''"],
["QueryCount", 8, "StringObject", "stringCol != ''"],
["QueryCount", 1, "StringObject", "stringCol == \"\\\"\\n\\0\\r\\\\'\""],
["QueryCount", 3, "StringObject", "stringCol BEGINSWITH 'a'"],
["QueryCount", 1, "StringObject", "stringCol beginswith 'ab'"],
["QueryCount", 0, "StringObject", "stringCol BEGINSWITH 'abcd'"],
["QueryCount", 2, "StringObject", "stringCol BEGINSWITH 'A'"],
["QueryCount", 2, "StringObject", "stringCol ENDSWITH 'c'"],
["QueryCount", 1, "StringObject", "stringCol endswith 'bc'"],
["QueryCount", 9, "StringObject", "stringCol ENDSWITH ''"],
["QueryCount", 1, "StringObject", "stringCol CONTAINS 'b'"],
["QueryCount", 2, "StringObject", "stringCol contains 'c'"],
["QueryCount", 9, "StringObject", "stringCol CONTAINS ''"],
["QueryCount", 2, "StringObject", "stringCol == $0", "a"],
["QueryCount", 2, "StringObject", "stringCol ENDSWITH $0", "c"],
["QueryThrows", "StringObject", "stringCol == true"],
["QueryThrows", "StringObject", "stringCol == 123"],
["QueryThrows", "StringObject", "stringCol CONTAINS $0", 1],
["Disabled", "QueryCount", 3, "StringObject", "stringCol ==[c] 'a'"],
["Disabled", "QueryCount", 5, "StringObject", "stringCol BEGINSWITH[c] 'A'"],
["Disabled", "QueryCount", 4, "StringObject", "stringCol ENDSWITH[c] 'c'"],
["Disabled", "QueryCount", 2, "StringObject", "stringCol CONTAINS[c] 'B'"]
]
},
"binaryTests" : {
"schema" : [{
"name": "BinaryObject",
"properties": [{ "name": "binaryCol", "type": "data" }]
}],
"objects": [
{ "type": "BinaryObject", "value": [[1, 100, 233, 255, 0]] },
{ "type": "BinaryObject", "value": [[1, 100]] },
{ "type": "BinaryObject", "value": [[100]] },
{ "type": "BinaryObject", "value": [[]] },
{ "type": "BinaryObject", "value": [[255, 0]] }
],
"tests": [
["QueryCount", 1, "BinaryObject", "binaryCol == $0", [1, "binaryCol"]],
["QueryCount", 1, "BinaryObject", "$0 == binaryCol", [2, "binaryCol"]],
["QueryCount", 4, "BinaryObject", "binaryCol != $0", [0, "binaryCol"]],
["QueryCount", 1, "BinaryObject", "binaryCol BEGINSWITH $0", [0, "binaryCol"]],
["QueryCount", 2, "BinaryObject", "binaryCol BEGINSWITH $0", [1, "binaryCol"]],
["QueryCount", 2, "BinaryObject", "binaryCol ENDSWITH $0", [4, "binaryCol"]],
["QueryCount", 3, "BinaryObject", "binaryCol CONTAINS $0", [2, "binaryCol"]]
]
},
"objectTests" : {
"schema" : [
{ "name": "IntObject", "properties": [
{ "name": "intCol", "type": "int" }
]},
{ "name": "LinkObject", "properties": [
{ "name": "linkCol", "type": "object", "objectType": "IntObject" }
]}
],
"objects": [
{ "type": "LinkObject", "value": [[1]] },
{ "type": "LinkObject", "value": [[2]] },
{ "type": "LinkObject", "value": [null] }
],
"tests": [
["QueryCount", 1, "LinkObject", "linkCol == $0", [0, "linkCol"]],
["QueryCount", 1, "LinkObject", "$0 == linkCol", [1, "linkCol"]],
["QueryCount", 2, "LinkObject", "linkCol != $0", [0, "linkCol"]],
["QueryThrows", "LinkObject", "linkCol > $0", [0, "linkCol"]],
["QueryThrows", "LinkObject", "intCol = $0", [0, "linkCol"]]
]
},
"compoundTests" : {
"schema" : [
{ "name": "IntObject",
"properties": [{ "name": "intCol", "type": "int" }],
"primaryKey" : "intCol" }
],
"objects": [
{ "type": "IntObject", "value": [0] },
{ "type": "IntObject", "value": [1] },
{ "type": "IntObject", "value": [2] },
{ "type": "IntObject", "value": [3] }
],
"tests": [
["ObjectSet", [], "IntObject", "intCol == 0 && intCol == 1"],
["ObjectSet", [0, 1], "IntObject", "intCol == 0 || intCol == 1"],
["ObjectSet", [0], "IntObject", "intCol == 0 && intCol != 1"],
["ObjectSet", [2, 3], "IntObject", "intCol >= 2 && intCol < 4"],
["ObjectSet", [0], "IntObject", "intCol == 0 && NOT intCol != 0"],
["ObjectSet", [0, 3], "IntObject", "intCol == 0 || NOT (intCol == 1 || intCol == 2)"],
["ObjectSet", [1], "IntObject", "(intCol == 0 || intCol == 1) && intCol >= 1"],
["ObjectSet", [1], "IntObject", "intCol >= 1 && (intCol == 0 || intCol == 1)"],
["ObjectSet", [0, 1], "IntObject", "intCol == 0 || (intCol == 1 && intCol >= 1)"],
["ObjectSet", [0, 1], "IntObject", "(intCol == 1 && intCol >= 1) || intCol == 0"],
["ObjectSet", [0, 1], "IntObject", "intCol == 0 || intCol == 1 && intCol >= 1"],
["ObjectSet", [0, 1, 2],"IntObject", "intCol == 0 || intCol == 1 || intCol <= 2"],
["ObjectSet", [0, 1], "IntObject", "intCol == 1 && intCol >= 1 || intCol == 0"],
["ObjectSet", [0, 1], "IntObject", "intCol == 1 || intCol == 0 && intCol <= 0 && intCol >= 0"],
["ObjectSet", [0, 1], "IntObject", "intCol == 0 || NOT (intCol == 3 && intCol >= 0) && intCol == 1"]
]
},
"keyPathTests" : {
"schema" : [
{
"name": "BasicTypesObject",
"properties": [
{ "name": "intCol", "type": "int" },
{ "name": "floatCol", "type": "float" },
{ "name": "doubleCol", "type": "double" },
{ "name": "stringCol", "type": "string" },
{ "name": "dateCol", "type": "date" },
{ "name": "dataCol", "type": "data" }
]
},
{
"name": "LinkTypesObject",
"primaryKey": "primaryKey",
"properties": [
{ "name": "primaryKey", "type": "int" },
{ "name": "basicLink", "type": "object", "objectType": "BasicTypesObject" },
{ "name": "linkLink", "type": "object", "objectType": "LinkTypesObject" }
]
}],
"objects": [
{ "type": "LinkTypesObject", "value": [0, [1, 0.1, 0.001, "1", 1, [1, 10, 100]], null] },
{ "type": "LinkTypesObject", "value": [1, null, [2, [1, 0.1, 0.001, "1", 1, [1, 10, 100]], null]] },
{ "type": "LinkTypesObject", "value": [3, null, [4, [2, 0.2, 0.002, "2", 2, [2, 20, 200]], null]] }
],
"tests": [
["ObjectSet", [0, 2], "LinkTypesObject", "basicLink.intCol == 1"],
["ObjectSet", [1], "LinkTypesObject", "linkLink.basicLink.intCol == 1"],
["ObjectSet", [1, 3], "LinkTypesObject", "linkLink.basicLink.intCol > 0"],
["ObjectSet", [0, 2], "LinkTypesObject", "basicLink.floatCol == 0.1"],
["ObjectSet", [1], "LinkTypesObject", "linkLink.basicLink.floatCol == 0.1"],
["ObjectSet", [1, 3], "LinkTypesObject", "linkLink.basicLink.floatCol > 0"]
]
}
}

496
parser/query_builder.cpp Normal file
View File

@ -0,0 +1,496 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#include "query_builder.hpp"
#include "parser.hpp"
#include <realm.hpp>
#include "object_store.hpp"
#include "schema.hpp"
#include <assert.h>
namespace realm {
namespace query_builder {
using namespace parser;
// check a precondition and throw an exception if it is not met
// this should be used iff the condition being false indicates a bug in the caller
// of the function checking its preconditions
#define precondition(condition, message) if (!__builtin_expect(condition, 1)) { throw std::runtime_error(message); }
// FIXME: TrueExpression and FalseExpression should be supported by core in some way
struct TrueExpression : realm::Expression {
size_t find_first(size_t start, size_t end) const override
{
if (start != end)
return start;
return not_found;
}
void set_table() override {}
const Table* get_table() const override { return nullptr; }
};
struct FalseExpression : realm::Expression {
size_t find_first(size_t, size_t) const override { return not_found; }
void set_table() override {}
const Table* get_table() const override { return nullptr; }
};
using KeyPath = std::vector<std::string>;
KeyPath key_path_from_string(const std::string &s) {
std::stringstream ss(s);
std::string item;
KeyPath key_path;
while (std::getline(ss, item, '.')) {
key_path.push_back(item);
}
return key_path;
}
struct PropertyExpression
{
const Property *prop = nullptr;
std::vector<size_t> indexes;
std::function<Table *()> table_getter;
PropertyExpression(Query &query, const Schema &schema, Schema::const_iterator desc, const std::string &key_path_string)
{
KeyPath key_path = key_path_from_string(key_path_string);
for (size_t index = 0; index < key_path.size(); index++) {
if (prop) {
precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray,
(std::string)"Property '" + key_path[index] + "' is not a link in object of type '" + desc->name + "'");
indexes.push_back(prop->table_column);
}
prop = desc->property_for_name(key_path[index]);
precondition(prop != nullptr, "No property '" + key_path[index] + "' on object of type '" + desc->name + "'");
if (prop->object_type.size()) {
desc = schema.find(prop->object_type);
}
}
table_getter = [&] {
TableRef& tbl = query.get_table();
for (size_t col : indexes) {
tbl->link(col); // mutates m_link_chain on table
}
return tbl.get();
};
}
};
// add a clause for numeric constraints based on operator type
template <typename A, typename B>
void add_numeric_constraint_to_query(Query& query,
Predicate::Operator operatorType,
A lhs,
B rhs)
{
switch (operatorType) {
case Predicate::Operator::LessThan:
query.and_query(lhs < rhs);
break;
case Predicate::Operator::LessThanOrEqual:
query.and_query(lhs <= rhs);
break;
case Predicate::Operator::GreaterThan:
query.and_query(lhs > rhs);
break;
case Predicate::Operator::GreaterThanOrEqual:
query.and_query(lhs >= rhs);
break;
case Predicate::Operator::Equal:
query.and_query(lhs == rhs);
break;
case Predicate::Operator::NotEqual:
query.and_query(lhs != rhs);
break;
default:
throw std::runtime_error("Unsupported operator for numeric queries.");
}
}
template <typename A, typename B>
void add_bool_constraint_to_query(Query &query, Predicate::Operator operatorType, A lhs, B rhs) {
switch (operatorType) {
case Predicate::Operator::Equal:
query.and_query(lhs == rhs);
break;
case Predicate::Operator::NotEqual:
query.and_query(lhs != rhs);
break;
default:
throw std::runtime_error("Unsupported operator for numeric queries.");
}
}
void add_string_constraint_to_query(Query &query,
Predicate::Operator op,
Columns<String> &&column,
std::string &&value) {
bool case_sensitive = true;
switch (op) {
case Predicate::Operator::BeginsWith:
query.and_query(column.begins_with(value, case_sensitive));
break;
case Predicate::Operator::EndsWith:
query.and_query(column.ends_with(value, case_sensitive));
break;
case Predicate::Operator::Contains:
query.and_query(column.contains(value, case_sensitive));
break;
case Predicate::Operator::Equal:
query.and_query(column.equal(value, case_sensitive));
break;
case Predicate::Operator::NotEqual:
query.and_query(column.not_equal(value, case_sensitive));
break;
default:
throw std::runtime_error("Unsupported operator for string queries.");
}
}
void add_string_constraint_to_query(realm::Query &query,
Predicate::Operator op,
std::string &&value,
Columns<String> &&column) {
bool case_sensitive = true;
switch (op) {
case Predicate::Operator::Equal:
query.and_query(column.equal(value, case_sensitive));
break;
case Predicate::Operator::NotEqual:
query.and_query(column.not_equal(value, case_sensitive));
break;
default:
throw std::runtime_error("Substring comparison not supported for keypath substrings.");
}
}
void add_binary_constraint_to_query(Query &query,
Predicate::Operator op,
Columns<Binary> &&column,
std::string &&value) {
switch (op) {
case Predicate::Operator::BeginsWith:
query.begins_with(column.m_column, BinaryData(value));
break;
case Predicate::Operator::EndsWith:
query.ends_with(column.m_column, BinaryData(value));
break;
case Predicate::Operator::Contains:
query.contains(column.m_column, BinaryData(value));
break;
case Predicate::Operator::Equal:
query.equal(column.m_column, BinaryData(value));
break;
case Predicate::Operator::NotEqual:
query.not_equal(column.m_column, BinaryData(value));
break;
default:
throw std::runtime_error("Unsupported operator for binary queries.");
}
}
void add_binary_constraint_to_query(realm::Query &query,
Predicate::Operator op,
std::string value,
Columns<Binary> &&column) {
switch (op) {
case Predicate::Operator::Equal:
query.equal(column.m_column, BinaryData(value));
break;
case Predicate::Operator::NotEqual:
query.not_equal(column.m_column, BinaryData(value));
break;
default:
throw std::runtime_error("Substring comparison not supported for keypath substrings.");
}
}
void add_link_constraint_to_query(realm::Query &query,
Predicate::Operator op,
const PropertyExpression &prop_expr,
size_t row_index) {
precondition(prop_expr.indexes.empty(), "KeyPath queries not supported for object comparisons.");
switch (op) {
case Predicate::Operator::NotEqual:
query.Not();
case Predicate::Operator::Equal:
query.links_to(prop_expr.prop->table_column, row_index);
break;
default:
throw std::runtime_error("Only 'equal' and 'not equal' operators supported for object comparison.");
}
}
void add_link_constraint_to_query(realm::Query &query,
Predicate::Operator op,
PropertyExpression &prop_expr,
realm::null) {
precondition(prop_expr.indexes.empty(), "KeyPath queries not supported for object comparisons.");
switch (op) {
case Predicate::Operator::NotEqual:
query.Not();
case Predicate::Operator::Equal:
query.and_query(query.get_table()->column<Link>(prop_expr.prop->table_column).is_null());
break;
default:
throw std::runtime_error("Only 'equal' and 'not equal' operators supported for object comparison.");
}
}
auto link_argument(const PropertyExpression &propExpr, const parser::Expression &argExpr, Arguments &args)
{
return args.object_index_for_argument(std::stoi(argExpr.s));
}
auto link_argument(const parser::Expression &argExpr, const PropertyExpression &propExpr, Arguments &args)
{
return args.object_index_for_argument(std::stoi(argExpr.s));
}
template <typename RetType, typename TableGetter>
struct ColumnGetter {
static Columns<RetType> convert(TableGetter&& table, const PropertyExpression & expr, Arguments &args)
{
return table()->template column<RetType>(expr.prop->table_column);
}
};
template <typename RequestedType, typename TableGetter>
struct ValueGetter;
template <typename TableGetter>
struct ValueGetter<DateTime, TableGetter> {
static Int convert(TableGetter&&, const parser::Expression & value, Arguments &args)
{
if (value.type != parser::Expression::Type::Argument) {
throw std::runtime_error("You must pass in a date argument to compare");
}
DateTime dt = args.datetime_for_argument(std::stoi(value.s));
return dt.get_datetime();
}
};
template <typename TableGetter>
struct ValueGetter<bool, TableGetter> {
static bool convert(TableGetter&&, const parser::Expression & value, Arguments &args)
{
if (value.type == parser::Expression::Type::Argument) {
return args.bool_for_argument(std::stoi(value.s));
}
if (value.type != parser::Expression::Type::True && value.type != parser::Expression::Type::False) {
throw std::runtime_error("Attempting to compare bool property to a non-bool value");
}
return value.type == parser::Expression::Type::True;
}
};
template <typename TableGetter>
struct ValueGetter<Double, TableGetter> {
static Double convert(TableGetter&&, const parser::Expression & value, Arguments &args)
{
if (value.type == parser::Expression::Type::Argument) {
return args.double_for_argument(std::stoi(value.s));
}
return std::stod(value.s);
}
};
template <typename TableGetter>
struct ValueGetter<Float, TableGetter> {
static Float convert(TableGetter&&, const parser::Expression & value, Arguments &args)
{
if (value.type == parser::Expression::Type::Argument) {
return args.float_for_argument(std::stoi(value.s));
}
return std::stof(value.s);
}
};
template <typename TableGetter>
struct ValueGetter<Int, TableGetter> {
static Int convert(TableGetter&&, const parser::Expression & value, Arguments &args)
{
if (value.type == parser::Expression::Type::Argument) {
return args.long_for_argument(std::stoi(value.s));
}
return std::stoll(value.s);
}
};
template <typename TableGetter>
struct ValueGetter<String, TableGetter> {
static std::string convert(TableGetter&&, const parser::Expression & value, Arguments &args)
{
if (value.type == parser::Expression::Type::Argument) {
return args.string_for_argument(std::stoi(value.s));
}
if (value.type != parser::Expression::Type::String) {
throw std::runtime_error("Attempting to compare String property to a non-String value");
}
return value.s;
}
};
template <typename TableGetter>
struct ValueGetter<Binary, TableGetter> {
static std::string convert(TableGetter&&, const parser::Expression & value, Arguments &args)
{
if (value.type == parser::Expression::Type::Argument) {
return args.binary_for_argument(std::stoi(value.s));
}
throw std::runtime_error("Binary properties must be compared against a binary argument.");
}
};
template <typename RetType, typename Value, typename TableGetter>
auto value_of_type_for_query(TableGetter&& tables, Value&& value, Arguments &args)
{
const bool isColumn = std::is_same<PropertyExpression, typename std::remove_reference<Value>::type>::value;
using helper = std::conditional_t<isColumn, ColumnGetter<RetType, TableGetter>, ValueGetter<RetType, TableGetter>>;
return helper::convert(tables, value, args);
}
template <typename A, typename B>
void do_add_comparison_to_query(Query &query, const Schema &schema, const ObjectSchema &object_schema, Predicate::Operator op,
const PropertyExpression &expr, A &lhs, B &rhs, Arguments &args)
{
auto type = expr.prop->type;
switch (type) {
case PropertyTypeBool:
add_bool_constraint_to_query(query, op, value_of_type_for_query<bool>(expr.table_getter, lhs, args),
value_of_type_for_query<bool>(expr.table_getter, rhs, args));
break;
case PropertyTypeDate:
add_numeric_constraint_to_query(query, op, value_of_type_for_query<DateTime>(expr.table_getter, lhs, args),
value_of_type_for_query<DateTime>(expr.table_getter, rhs, args));
break;
case PropertyTypeDouble:
add_numeric_constraint_to_query(query, op, value_of_type_for_query<Double>(expr.table_getter, lhs, args),
value_of_type_for_query<Double>(expr.table_getter, rhs, args));
break;
case PropertyTypeFloat:
add_numeric_constraint_to_query(query, op, value_of_type_for_query<Float>(expr.table_getter, lhs, args),
value_of_type_for_query<Float>(expr.table_getter, rhs, args));
break;
case PropertyTypeInt:
add_numeric_constraint_to_query(query, op, value_of_type_for_query<Int>(expr.table_getter, lhs, args),
value_of_type_for_query<Int>(expr.table_getter, rhs, args));
break;
case PropertyTypeString:
add_string_constraint_to_query(query, op, value_of_type_for_query<String>(expr.table_getter, lhs, args),
value_of_type_for_query<String>(expr.table_getter, rhs, args));
break;
case PropertyTypeData:
add_binary_constraint_to_query(query, op, value_of_type_for_query<Binary>(expr.table_getter, lhs, args),
value_of_type_for_query<Binary>(expr.table_getter, rhs, args));
break;
case PropertyTypeObject:
case PropertyTypeArray:
add_link_constraint_to_query(query, op, expr, link_argument(lhs, rhs, args));
break;
default: {
throw std::runtime_error((std::string)"Object type " + string_for_property_type(type) + " not supported");
}
}
}
void add_comparison_to_query(Query &query, const Predicate &pred, Arguments &args, const Schema &schema, const std::string &type)
{
const Predicate::Comparison &cmpr = pred.cmpr;
auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type;
auto object_schema = schema.find(type);
if (t0 == parser::Expression::Type::KeyPath && t1 != parser::Expression::Type::KeyPath) {
PropertyExpression expr(query, schema, object_schema, cmpr.expr[0].s);
do_add_comparison_to_query(query, schema, *object_schema, cmpr.op, expr, expr, cmpr.expr[1], args);
}
else if (t0 != parser::Expression::Type::KeyPath && t1 == parser::Expression::Type::KeyPath) {
PropertyExpression expr(query, schema, object_schema, cmpr.expr[1].s);
do_add_comparison_to_query(query, schema, *object_schema, cmpr.op, expr, cmpr.expr[0], expr, args);
}
else {
throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value");
}
}
void update_query_with_predicate(Query &query, const Predicate &pred, Arguments &arguments, const Schema &schema, const std::string &type)
{
if (pred.negate) {
query.Not();
}
switch (pred.type) {
case Predicate::Type::And:
query.group();
for (auto &sub : pred.cpnd.sub_predicates) {
update_query_with_predicate(query, sub, arguments, schema, type);
}
if (!pred.cpnd.sub_predicates.size()) {
query.and_query(new TrueExpression);
}
query.end_group();
break;
case Predicate::Type::Or:
query.group();
for (auto &sub : pred.cpnd.sub_predicates) {
query.Or();
update_query_with_predicate(query, sub, arguments, schema, type);
}
if (!pred.cpnd.sub_predicates.size()) {
query.and_query(new FalseExpression);
}
query.end_group();
break;
case Predicate::Type::Comparison: {
add_comparison_to_query(query, pred, arguments, schema, type);
break;
}
case Predicate::Type::True:
query.and_query(new TrueExpression);
break;
case Predicate::Type::False:
query.and_query(new FalseExpression);
break;
default:
throw std::runtime_error("Invalid predicate type");
break;
}
}
void apply_predicate(Query &query, const Predicate &predicate, Arguments &arguments, const Schema &schema, const std::string &objectType)
{
update_query_with_predicate(query, predicate, arguments, schema, objectType);
// Test the constructed query in core
std::string validateMessage = query.validate();
precondition(validateMessage.empty(), validateMessage.c_str());
}
}}

80
parser/query_builder.hpp Normal file
View File

@ -0,0 +1,80 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#ifndef REALM_QUERY_BUILDER_HPP
#define REALM_QUERY_BUILDER_HPP
#include <string>
#include "parser.hpp"
#include "object_accessor.hpp"
namespace realm {
class Query;
class Schema;
namespace query_builder {
class Arguments;
void apply_predicate(Query &query, const parser::Predicate &predicate, Arguments &arguments, const Schema &schema, const std::string &objectType);
class Arguments
{
public:
virtual bool bool_for_argument(size_t argument_index) = 0;
virtual long long long_for_argument(size_t argument_index) = 0;
virtual float float_for_argument(size_t argument_index) = 0;
virtual double double_for_argument(size_t argument_index) = 0;
virtual std::string string_for_argument(size_t argument_index) = 0;
virtual std::string binary_for_argument(size_t argument_index) = 0;
virtual DateTime datetime_for_argument(size_t argument_index) = 0;
virtual size_t object_index_for_argument(size_t argument_index) = 0;
virtual bool is_argument_null(size_t argument_index) = 0;
};
template<typename ValueType, typename ContextType>
class ArgumentConverter : public Arguments
{
public:
ArgumentConverter(ContextType context, std::vector<ValueType> arguments) : m_arguments(arguments), m_ctx(context) {};
using Accessor = realm::NativeAccessor<ValueType, ContextType>;
virtual bool bool_for_argument(size_t argument_index) { return Accessor::to_bool(m_ctx, argument_at(argument_index)); }
virtual long long long_for_argument(size_t argument_index) { return Accessor::to_long(m_ctx, argument_at(argument_index)); }
virtual float float_for_argument(size_t argument_index) { return Accessor::to_float(m_ctx, argument_at(argument_index)); }
virtual double double_for_argument(size_t argument_index) { return Accessor::to_double(m_ctx, argument_at(argument_index)); }
virtual std::string string_for_argument(size_t argument_index) { return Accessor::to_string(m_ctx, argument_at(argument_index)); }
virtual std::string binary_for_argument(size_t argument_index) { return Accessor::to_binary(m_ctx, argument_at(argument_index)); }
virtual DateTime datetime_for_argument(size_t argument_index) { return Accessor::to_datetime(m_ctx, argument_at(argument_index)); }
virtual size_t object_index_for_argument(size_t argument_index) { return Accessor::to_existing_object_index(m_ctx, argument_at(argument_index)); }
virtual bool is_argument_null(size_t argument_index) { return Accessor::is_null(m_ctx, argument_at(argument_index)); }
private:
std::vector<ValueType> m_arguments;
ContextType m_ctx;
ValueType &argument_at(size_t index) {
if (index >= m_arguments.size()) {
throw std::out_of_range((std::string)"Argument index " + std::to_string(index) + " out of range 0.." + std::to_string(m_arguments.size()-1));
}
return m_arguments[index];
}
};
}
}
#endif // REALM_QUERY_BUILDER_HPP

166
parser/test.cpp Normal file
View File

@ -0,0 +1,166 @@
#include "parser.hpp"
#include <vector>
#include <string>
#include <exception>
#include <iostream>
static std::vector<std::string> valid_queries = {
// true/false predicates
"truepredicate",
"falsepredicate",
" TRUEPREDICATE ",
" FALSEPREDICATE ",
// characters/strings
"\"\" = ''",
"'azAZ09/ :()[]{}<>,.^@-+=*&~`' = '\\\" \\' \\\\ \\/ \\b \\f \\n \\r \\t \\0'",
"\"azAZ09/\" = \"\\\" \\' \\\\ \\/ \\b \\f \\n \\r \\t \\0\"",
"'\\uffFf' = '\\u0020'",
"'\\u01111' = 'asdf\\u0111asdf'",
// expressions (numbers, bools, keypaths, arguments)
"-1 = 12",
"0 = 001",
"0x0 = -0X398235fcAb",
"10. = -.034",
"10.0 = 5.034",
"true = false",
"_ = a",
"_a = _.aZ",
"a09._br.z = __-__.Z-9",
"$0 = $19",
"$0=$0",
// operators
"0=0",
"0 = 0",
"0!=0",
"0 != 0",
"0==0",
"0 == 0",
"0>0",
"0 > 0",
"0>=0",
"0 >= 0",
"0<0",
"0 < 0",
"0<=0",
"0 <= 0",
"0 contains 0",
"0 BeGiNsWiTh 0",
"0 ENDSWITH 0",
// atoms/groups
"(0=0)",
"( 0=0 )",
"((0=0))",
"!0=0",
"! 0=0",
"!(0=0)",
"! (0=0)",
"NOT0=0", // keypath NOT0
"not 0=0",
"NOT(0=0)",
"not (0=0)",
"NOT (!0=0)",
// compound
"a==a && a==a",
"a==a || a==a",
"a==a&&a==a||a=a",
"a==a and a==a",
"a==a OR a==a",
"and=='AND'&&'or'=='||'",
"and == or && ORE > GRAND",
"a=1AND NOTb=2",
};
static std::vector<std::string> invalid_queries = {
"predicate",
"'\\a' = ''", // invalid escape
// invalid unicode
"'\\u0' = ''",
// invalid strings
"\"' = ''",
"\" = ''",
"' = ''",
// expressions
"03a = 1",
"1..0 = 1",
"1.0. = 1",
"1-0 = 1",
"0x = 1",
"truey = false",
"- = a",
"a..b = a",
"a$a = a",
"{} = $0",
"$-1 = $0",
"$a = $0",
"$ = $",
// operators
"0===>0",
"0 <> 0",
"0 contains1",
"endswith 0",
// atoms/groups
"0=0)",
"(0=0",
"(0=0))",
"! =0",
"NOTNOT(0=0)",
"(!!0=0)",
"0=0 !",
// compound
"a==a & a==a",
"a==a | a==a",
"a==a &| a==a",
"a==a && OR a==a",
"a==aORa==a",
//"a=1ANDNOT b=2",
"truepredicate &&",
"truepredicate & truepredicate",
};
namespace realm {
namespace parser {
bool test_grammar()
{
bool success = true;
for (auto &query : valid_queries) {
std::cout << "valid query: " << query << std::endl;
try {
realm::parser::parse(query);
} catch (std::exception &ex) {
std::cout << "FAILURE - " << ex.what() << std::endl;
success = false;
}
}
for (auto &query : invalid_queries) {
std::cout << "invalid query: " << query << std::endl;
try {
realm::parser::parse(query);
} catch (std::exception &ex) {
// std::cout << "message: " << ex.what() << std::endl;
continue;
}
std::cout << "FAILURE - query should throw an exception" << std::endl;
success = false;
}
return success;
}
}
}

3
parser/test.sh Executable file
View File

@ -0,0 +1,3 @@
# /bin/sh
llvm-g++ -std=c++11 -I ../../../vendor/PEGTL/ -o test test.cpp parser.cpp
./test

View File

@ -34,8 +34,9 @@ using namespace realm;
#define REALM_FALLTHROUGH
#endif
Results::Results(SharedRealm r, Query q, SortOrder s)
Results::Results(SharedRealm r, const ObjectSchema &o, Query q, SortOrder s)
: m_realm(std::move(r))
, m_object_schema(&o)
, m_query(std::move(q))
, m_table(m_query.get_table().get())
, m_sort(std::move(s))
@ -43,8 +44,9 @@ Results::Results(SharedRealm r, Query q, SortOrder s)
{
}
Results::Results(SharedRealm r, Table& table)
Results::Results(SharedRealm r, const ObjectSchema &o, Table& table)
: m_realm(std::move(r))
, m_object_schema(&o)
, m_table(&table)
, m_mode(Mode::Table)
{
@ -65,6 +67,17 @@ void Results::validate_write() const
throw InvalidTransactionException("Must be in a write transaction");
}
void Results::set_live(bool live)
{
if (!live && m_mode == Mode::Table) {
m_query = m_table->where();
m_mode = Mode::Query;
}
update_tableview();
m_live = live;
}
size_t Results::size()
{
validate_read();
@ -92,7 +105,7 @@ RowExpr Results::get(size_t row_ndx)
case Mode::TableView:
update_tableview();
if (row_ndx < m_table_view.size())
return m_table_view.get(row_ndx);
return (!m_live && !m_table_view.is_row_attached(row_ndx)) ? RowExpr() : m_table_view.get(row_ndx);
break;
}
@ -146,7 +159,9 @@ void Results::update_tableview()
m_mode = Mode::TableView;
break;
case Mode::TableView:
m_table_view.sync_if_needed();
if (m_live) {
m_table_view.sync_if_needed();
}
break;
}
}
@ -158,9 +173,10 @@ size_t Results::index_of(Row const& row)
throw DetatchedAccessorException{};
}
if (m_table && row.get_table() != m_table) {
throw IncorrectTableException{
ObjectStore::object_type_for_table_name(m_table->get_name()),
ObjectStore::object_type_for_table_name(row.get_table()->get_name())};
throw IncorrectTableException(m_object_schema->name,
ObjectStore::object_type_for_table_name(row.get_table()->get_name()),
"Attempting to get the index of a Row of the wrong type"
);
}
return index_of(row.get_index());
}
@ -310,23 +326,20 @@ TableView Results::get_tableview()
REALM_UNREACHABLE();
}
StringData Results::get_object_type() const noexcept
{
return ObjectStore::object_type_for_table_name(m_table->get_name());
}
Results Results::sort(realm::SortOrder&& sort) const
{
return Results(m_realm, get_query(), std::move(sort));
return Results(m_realm, get_object_schema(), get_query(), std::move(sort));
}
Results Results::filter(Query&& q) const
{
return Results(m_realm, get_query().and_query(std::move(q)), get_sort());
return Results(m_realm, get_object_schema(), get_query().and_query(std::move(q)), get_sort());
}
Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table) {
column_index = column;
column_name = table->get_column_name(column);
column_type = table->get_column_type(column);
Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table)
: std::runtime_error((std::string)"Operation not supported on '" + table->get_column_name(column).data() + "' columns")
, column_index(column)
, column_name(table->get_column_name(column))
, column_type(table->get_column_type(column))
{
}

View File

@ -46,15 +46,21 @@ public:
// or a wrapper around a query and a sort order which creates and updates
// the tableview as needed
Results() = default;
Results(SharedRealm r, Table& table);
Results(SharedRealm r, Query q, SortOrder s = {});
Results(SharedRealm r, const ObjectSchema& o, Table& table);
Results(SharedRealm r, const ObjectSchema& o, Query q, SortOrder s = {});
// Results is copyable and moveable
Results(Results const&) = default;
Results(Results&&) = default;
Results& operator=(Results const&) = default;
Results& operator=(Results&&) = default;
Results& operator=(Results const&) = default;
// Get the Realm
SharedRealm get_realm() const { return m_realm; }
// Object schema describing the vendored object type
const ObjectSchema &get_object_schema() const { return *m_object_schema; }
// Get a query which will match the same rows as is contained in this Results
// Returned query will not be valid if the current mode is Empty
Query get_query() const;
@ -66,7 +72,10 @@ public:
TableView get_tableview();
// Get the object type which will be returned by get()
StringData get_object_type() const noexcept;
StringData get_object_type() const noexcept { return get_object_schema().name; }
// Set whether the TableView should sync if needed before accessing results
void set_live(bool live);
// Get the size of this results
// Can be either O(1) or O(N) depending on the state of things
@ -118,25 +127,37 @@ public:
// The Results object has been invalidated (due to the Realm being invalidated)
// All non-noexcept functions can throw this
struct InvalidatedException {};
struct InvalidatedException : public std::runtime_error
{
InvalidatedException() : std::runtime_error("Access to invalidated Results objects") {}
};
// The input index parameter was out of bounds
struct OutOfBoundsIndexException {
size_t requested;
size_t valid_count;
struct OutOfBoundsIndexException : public std::out_of_range
{
OutOfBoundsIndexException(size_t r, size_t c) :
std::out_of_range((std::string)"Requested index " + std::to_string(r) +
" greater than max " + std::to_string(c)),
requested(r), valid_count(c) {}
const size_t requested;
const size_t valid_count;
};
// The input Row object is not attached
struct DetatchedAccessorException { };
struct DetatchedAccessorException : public std::runtime_error {
DetatchedAccessorException() : std::runtime_error("Atempting to access an invalid object") {}
};
// The input Row object belongs to a different table
struct IncorrectTableException {
StringData expected;
StringData actual;
struct IncorrectTableException : public std::runtime_error {
IncorrectTableException(StringData e, StringData a, const std::string &error) :
std::runtime_error(error), expected(e), actual(a) {}
const StringData expected;
const StringData actual;
};
// The requested aggregate operation is not supported for the column type
struct UnsupportedColumnTypeException {
struct UnsupportedColumnTypeException : public std::runtime_error {
size_t column_index;
StringData column_name;
DataType column_type;
@ -146,10 +167,12 @@ public:
private:
SharedRealm m_realm;
const ObjectSchema *m_object_schema;
Query m_query;
TableView m_table_view;
Table* m_table = nullptr;
SortOrder m_sort;
bool m_live = true;
Mode m_mode = Mode::Empty;

View File

@ -402,8 +402,6 @@ uint64_t Realm::get_schema_version(const realm::Realm::Config &config)
void Realm::close()
{
invalidate();
if (m_notifier) {
m_notifier->remove_realm(this);
}