Skip to content

Commit

Permalink
Merge pull request #3987 from realm/js/primitive-list-nulls
Browse files Browse the repository at this point in the history
fix list of primitives is_null() for optional float and double
  • Loading branch information
ironage authored Oct 9, 2020
2 parents e7b6530 + 6886a40 commit ff2f8be
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 13 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

### Fixed
* <How to hit and notice issue? what was the impact?> ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?)
* None.
* Fix list of primitives for Optional<Float> and Optional<Double> always returning false for `Lst::is_null(ndx)` even on null values, (since v6.0.0).

### Breaking changes
* None.
Expand Down
6 changes: 6 additions & 0 deletions src/realm/column_type_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <realm/data_type.hpp>
#include <realm/array.hpp>
#include <realm/keys.hpp>
#include <realm/utilities.hpp>

namespace realm {

Expand Down Expand Up @@ -191,6 +192,11 @@ struct ColumnTypeTraits<Lst<T>> {
static const ColumnType column_id = ColumnTypeTraits<T>::column_id;
};

template <typename T>
struct ObjectTypeTraits {
constexpr static bool self_contained_null = realm::is_any<T, StringData, BinaryData, Timestamp>::value;
};

template <DataType, bool Nullable>
struct GetLeafType;
template <>
Expand Down
2 changes: 1 addition & 1 deletion src/realm/list.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ class ConstLstIf : public virtual ConstLstBase {
}
bool is_null(size_t ndx) const final
{
return m_nullable && get(ndx) == BPlusTree<T>::default_value(true);
return m_nullable && value_is_null(get(ndx));
}
Mixed get_any(size_t ndx) const final
{
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ set(REALM_TEST_HEADERS
test_all.hpp
test_string_types.hpp
test_table_helper.hpp
test_types_helper.hpp
testsettings.hpp
) # REALM_TEST_HEADERS

Expand Down
44 changes: 33 additions & 11 deletions test/test_table.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
using namespace std::chrono;

#include <realm.hpp>
#include <realm/column_type_traits.hpp>
#include <realm/history.hpp>
#include <realm/util/buffer.hpp>
#include <realm/util/to_string.hpp>
Expand All @@ -44,6 +45,7 @@ using namespace std::chrono;

#include "test.hpp"
#include "test_table_helper.hpp"
#include "test_types_helper.hpp"

// #include <valgrind/callgrind.h>
// #define PERFORMACE_TESTING
Expand Down Expand Up @@ -3092,30 +3094,50 @@ TEST(Table_StableIteration)
CHECK_EQUAL(list[2], 2);
}

TEST(Table_ListOps)
TEST_TYPES(Table_ListOps, Prop<Int>, Prop<Float>, Prop<Double>, Prop<Timestamp>, Prop<String>, Prop<Binary>,
Prop<Bool>, Nullable<Int>, Nullable<Float>, Nullable<Double>, Nullable<Timestamp>, Nullable<String>,
Nullable<Binary>, Nullable<Bool>)
{
using underlying_type = typename TEST_TYPE::underlying_type;
using type = typename TEST_TYPE::type;
TestValueGenerator gen;
Table table;
ColKey col = table.add_column_list(type_Int, "integers");
ColKey col = table.add_column_list(TEST_TYPE::data_type, "values", TEST_TYPE::is_nullable);

Obj obj = table.create_object();
Obj obj1 = obj;
Lst<Int> list = obj.get_list<Int>(col);
list.add(1);
list.add(2);
Lst<type> list = obj.get_list<type>(col);
list.add(gen.convert_for_test<underlying_type>(1));
list.add(gen.convert_for_test<underlying_type>(2));
list.swap(0, 1);
CHECK_EQUAL(list.get(0), 2);
CHECK_EQUAL(list.get(1), 1);

Lst<Int> list1;
CHECK_EQUAL(list.get(0), gen.convert_for_test<underlying_type>(2));
CHECK_EQUAL(list.get(1), gen.convert_for_test<underlying_type>(1));
CHECK_EQUAL(list.find_first(gen.convert_for_test<underlying_type>(2)), 0);
CHECK_EQUAL(list.find_first(gen.convert_for_test<underlying_type>(1)), 1);
CHECK(!list.is_null(0));
CHECK(!list.is_null(1));

Lst<type> list1;
CHECK_EQUAL(list1.size(), 0);
list1 = list;
CHECK_EQUAL(list1.size(), 2);
list.add(3);
list.add(gen.convert_for_test<underlying_type>(3));
CHECK_EQUAL(list.size(), 3);
CHECK_EQUAL(list1.size(), 3);

Lst<Int> list2 = list;
Lst<type> list2 = list;
CHECK_EQUAL(list2.size(), 3);
list2.clear();
CHECK_EQUAL(list2.size(), 0);

if (TEST_TYPE::is_nullable) {
list2.insert_null(0);
CHECK_EQUAL(list.size(), 1);
type item0 = list2.get(0);
CHECK(value_is_null(item0));
CHECK(list.is_null(0));
CHECK(list.get_any(0).is_null());
}
}

TEST(Table_ListOfPrimitives)
Expand Down
153 changes: 153 additions & 0 deletions test/test_types_helper.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*************************************************************************
*
* Copyright 2020 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_TEST_TYPES_HELPER_HPP
#define REALM_TEST_TYPES_HELPER_HPP

#include <realm.hpp>
#include <realm/column_type_traits.hpp>

namespace realm {
namespace test_util {

struct TestValueGenerator {
template <typename T>
inline T convert_for_test(int64_t v)
{
return static_cast<T>(v);
}

template <typename T>
std::vector<T> values_from_int(const std::vector<int64_t>& values)
{
std::vector<T> ret;
for (size_t i = 0; i < values.size(); ++i) {
ret.push_back(convert_for_test<typename util::RemoveOptional<T>::type>(values[i]));
}
return ret;
}

private:
std::vector<util::StringBuffer> m_buffer_space;
};


template <>
inline bool TestValueGenerator::convert_for_test<bool>(int64_t v)
{
return v % 2 == 0;
}

template <>
inline Timestamp TestValueGenerator::convert_for_test<Timestamp>(int64_t v)
{
return Timestamp{v, 0};
}

template <>
inline StringData TestValueGenerator::convert_for_test<StringData>(int64_t t)
{
std::string str = util::format("string %1", t);
util::StringBuffer b;
b.append(str);
m_buffer_space.emplace_back(std::move(b));
return StringData(m_buffer_space[m_buffer_space.size() - 1].data(), str.size());
}

template <>
inline BinaryData TestValueGenerator::convert_for_test<BinaryData>(int64_t t)
{
std::string str = util::format("string %1", t);
util::StringBuffer b;
b.append(str);
m_buffer_space.emplace_back(std::move(b));
return BinaryData(m_buffer_space[m_buffer_space.size() - 1].data(), str.size());
}

enum class ColumnState { Normal = 0, Nullable = 1, Indexed = 2, NullableIndexed = 3 };

template <ColumnState s>
constexpr static bool col_state_is_nullable = (s == ColumnState::Nullable || s == ColumnState::NullableIndexed);

template <ColumnState s>
constexpr static bool col_state_is_indexed = (s == ColumnState::Indexed || s == ColumnState::NullableIndexed);

template <typename T, ColumnState state = ColumnState::Normal, typename Enable = void>
struct Prop {
static constexpr bool is_nullable = col_state_is_nullable<state>;
static constexpr bool is_indexed = col_state_is_indexed<state>;
static constexpr DataType data_type = ColumnTypeTraits<T>::id;
using type = T;
using underlying_type = type;
static type default_value()
{
return ColumnTypeTraits<type>::cluster_leaf_type::default_value(is_nullable);
}
static underlying_type default_non_nullable_value()
{
return ColumnTypeTraits<underlying_type>::cluster_leaf_type::default_value(false);
}
};

template <typename T, ColumnState state>
struct Prop<T, state,
std::enable_if_t<col_state_is_nullable<state> && !ObjectTypeTraits<T>::self_contained_null, void>> {
static constexpr bool is_nullable = col_state_is_nullable<state>;
static constexpr bool is_indexed = col_state_is_indexed<state>;
static constexpr DataType data_type = ColumnTypeTraits<T>::id;
using type = typename util::Optional<T>;
using underlying_type = T;
static type default_value()
{
if (realm::is_any<type, util::Optional<float>, util::Optional<double>>::value) {
return type(); // optional float/double would return NaN and for consistency we want to operate on null
}
return ColumnTypeTraits<type>::cluster_leaf_type::default_value(is_nullable);
}
static underlying_type default_non_nullable_value()
{
return ColumnTypeTraits<underlying_type>::cluster_leaf_type::default_value(false);
}
};

template <typename T>
using Nullable = Prop<T, ColumnState::Nullable>;
template <typename T>
using Indexed = Prop<T, ColumnState::Indexed>;
template <typename T>
using NullableIndexed = Prop<T, ColumnState::NullableIndexed>;

struct less {
template <typename T>
auto operator()(T&& a, T&& b) const noexcept
{
return Mixed(a).compare(Mixed(b)) < 0;
}
};
struct greater {
template <typename T>
auto operator()(T&& a, T&& b) const noexcept
{
return Mixed(a).compare(Mixed(b)) > 0;
}
};

} // namespace test_util
} // namespace realm

#endif // REALM_TEST_TYPES_HELPER

0 comments on commit ff2f8be

Please sign in to comment.