Merge pull request #339 from realm/al-indexed

Support for indexed properties
This commit is contained in:
Ari Lazier 2016-03-21 13:27:23 -07:00
commit 7e9df63777
26 changed files with 150 additions and 86 deletions

View File

@ -8,6 +8,7 @@
* Add common Array methods to List and Results
* Accept constructor in create() and objects() methods
* Support relative paths when opening realms
* Support for indexed bool, string, date, and int properties
### Bugfixes
* Fix for crash on Android when initializing the Realm module

View File

@ -150,6 +150,8 @@ Realm.defaultPath;
* @property {any} [default] - The default value for this property on creation when not
* otherwise specified.
* @property {boolean} [optional] - Signals if this property may be assigned `null` or `undefined`.
* @property {boolean} [indexed] - Signals if this property should be indexed. Only supported for
* `"string"`, `"int"`, and `"bool"` properties.
*/
/**

View File

@ -41,6 +41,7 @@
"scripts": {
"get-version": "echo $npm_package_version",
"set-version": "scripts/set-version.sh",
"get-core-version": "scripts/download-core.sh --version",
"jsdoc": "rm -rf docs/output && jsdoc -c docs/conf.json",
"lint": "eslint",
"test": "scripts/test.sh",

View File

@ -27,8 +27,8 @@ import org.apache.tools.ant.filters.ReplaceTokens
// We download various C++ open-source dependencies into downloads.
// We then copy both the downloaded code and our custom makefiles and headers into third-party-ndk.
// After that we build native code from src/main/jni with module path pointing at third-party-ndk.
ext.coreVersion = '0.96.2'
ext.coreVersion = "npm --silent run get-core-version".execute().text.trim()
def currentVersion = "npm --silent run get-version".execute().text.trim()
def downloadsDir = new File("$projectDir/downloads")
def jscDownloadDir = new File("$projectDir/src/main/jni/jsc")

View File

@ -3,7 +3,7 @@
set -e
set -o pipefail
: ${REALM_CORE_VERSION:=0.96.2} # set to "current" to always use the current build
: ${REALM_CORE_VERSION:=0.97.1} # set to "current" to always use the current build
# Start current working directory at the root of the project.
cd "$(dirname "$0")/.."
@ -46,6 +46,11 @@ check_release_notes() {
grep -Fqi "$REALM_CORE_VERSION RELEASE NOTES" "$@"
}
if [[ $1 = "--version" ]]; then
echo $REALM_CORE_VERSION
exit 0
fi
if [ ! -e core ]; then
download_core
elif [ -d core -a -d ../realm-core -a ! -L core ]; then

View File

@ -2,7 +2,8 @@
set -e
set -o pipefail
export REALM_CORE_VERSION=$(./scripts/download-core.sh --version)
echo "Core Version: $REALM_CORE_VERSION"
cd "$(dirname "$0")/.."
if [ -n "$REALM_BUILD_ANDROID" ]; then

View File

@ -47,6 +47,7 @@ JSObjectRef RJSSchemaCreate(JSContextRef ctx, Schema &schema) {
static inline Property RJSParseProperty(JSContextRef ctx, JSValueRef propertyAttributes, std::string propertyName, ObjectDefaults &objectDefaults) {
static JSStringRef defaultString = JSStringCreateWithUTF8CString("default");
static JSStringRef indexedString = JSStringCreateWithUTF8CString("indexed");
static JSStringRef typeString = JSStringCreateWithUTF8CString("type");
static JSStringRef objectTypeString = JSStringCreateWithUTF8CString("objectType");
static JSStringRef optionalString = JSStringCreateWithUTF8CString("optional");
@ -123,6 +124,11 @@ static inline Property RJSParseProperty(JSContextRef ctx, JSValueRef propertyAtt
JSValueProtect(ctx, defaultValue);
objectDefaults.emplace(prop.name, defaultValue);
}
JSValueRef indexedValue = RJSValidatedPropertyValue(ctx, propertyObject, indexedString);
if (!JSValueIsUndefined(ctx, indexedValue)) {
prop.is_indexed = JSValueToBoolean(ctx, indexedValue);
}
}
return prop;

View File

@ -7,7 +7,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake")
include(CompilerFlags)
include(RealmCore)
set(REALM_CORE_VERSION "0.96.2" CACHE STRING "")
set(REALM_CORE_VERSION "0.97.0" CACHE STRING "")
use_realm_core(${REALM_CORE_VERSION})
include_directories(${REALM_CORE_INCLUDE_DIR} src external/pegtl)

View File

@ -11,7 +11,7 @@ Cross-platform code used accross bindings. Binding developers can choose to use
The object store's build system currently only suports building for OS X. The object store itself can build for all Apple
platforms when integrated into a binding.
1. Install CMake. You can download an installer for OS X from the [CMake download page], or install via [Homebrew](http://brew.sh):
1. Install CMake. You can download an installer for OS X from the [CMake download page](https://cmake.org/download/), or install via [Homebrew](http://brew.sh):
```
brew install cmake
```

View File

@ -159,7 +159,7 @@ void AsyncQuery::run()
if (m_initial_run_complete) {
// Make an empty tableview from the query to get the table version, since
// Query doesn't expose it
if (m_query->find_all(0, 0, 0).outside_version() == m_handed_over_table_version) {
if (m_query->find_all(0, 0, 0).sync_if_needed() == m_handed_over_table_version) {
return;
}
}
@ -181,7 +181,7 @@ void AsyncQuery::prepare_handover()
REALM_ASSERT(m_tv.is_in_sync());
m_initial_run_complete = true;
m_handed_over_table_version = m_tv.outside_version();
m_handed_over_table_version = m_tv.sync_if_needed();
m_tv_handover = m_sg->export_for_handover(m_tv, MutableSourcePayload::Move);
// detach the TableView as we won't need it again and keeping it around
@ -231,7 +231,7 @@ bool AsyncQuery::deliver(SharedGroup& sg, std::exception_ptr err)
m_tv_handover->version = m_sg_version;
Results::Internal::set_table_view(*m_target_results,
std::move(*sg.import_from_handover(std::move(m_tv_handover))));
m_delievered_table_version = m_handed_over_table_version;
m_delivered_table_version = m_handed_over_table_version;
}
REALM_ASSERT(!m_tv_handover);
@ -259,8 +259,8 @@ std::function<void (std::exception_ptr)> AsyncQuery::next_callback()
std::lock_guard<std::mutex> callback_lock(m_callback_mutex);
for (++m_callback_index; m_callback_index < m_callbacks.size(); ++m_callback_index) {
auto& callback = m_callbacks[m_callback_index];
if (m_error || callback.delivered_version != m_delievered_table_version) {
callback.delivered_version = m_delievered_table_version;
if (m_error || callback.delivered_version != m_delivered_table_version) {
callback.delivered_version = m_delivered_table_version;
return callback.fn;
}
}

View File

@ -97,7 +97,7 @@ private:
SharedGroup* m_sg = nullptr;
uint_fast64_t m_handed_over_table_version = -1;
uint_fast64_t m_delievered_table_version = -1;
uint_fast64_t m_delivered_table_version = -1;
// Iteration variable for looping over callbacks
// remove_callback() updates this when needed

View File

@ -21,7 +21,7 @@
#include <future>
namespace realm {
class ClientHistory;
class Replication;
namespace _impl {
class RealmCoordinator;
@ -38,7 +38,7 @@ private:
RealmCoordinator& m_parent;
// A shared group used to listen for changes
std::unique_ptr<ClientHistory> m_history;
std::unique_ptr<Replication> m_history;
SharedGroup m_sg;
// The listener thread

View File

@ -385,7 +385,7 @@ void RealmCoordinator::move_new_queries_to_main()
void RealmCoordinator::advance_helper_shared_group_to_latest()
{
if (m_new_queries.empty()) {
LangBindHelper::advance_read(*m_query_sg, *m_query_history);
LangBindHelper::advance_read(*m_query_sg);
return;
}
@ -397,14 +397,13 @@ void RealmCoordinator::advance_helper_shared_group_to_latest()
// Import all newly added queries to our helper SG
for (auto& query : m_new_queries) {
LangBindHelper::advance_read(*m_advancer_sg, *m_advancer_history, query->version());
LangBindHelper::advance_read(*m_advancer_sg, query->version());
query->attach_to(*m_advancer_sg);
}
// Advance both SGs to the newest version
LangBindHelper::advance_read(*m_advancer_sg, *m_advancer_history);
LangBindHelper::advance_read(*m_query_sg, *m_query_history,
m_advancer_sg->get_version_of_current_transaction());
LangBindHelper::advance_read(*m_advancer_sg);
LangBindHelper::advance_read(*m_query_sg, m_advancer_sg->get_version_of_current_transaction());
// Transfer all new queries over to the main SG
for (auto& query : m_new_queries) {
@ -421,7 +420,6 @@ void RealmCoordinator::advance_to_ready(Realm& realm)
decltype(m_queries) queries;
auto& sg = Realm::Internal::get_shared_group(realm);
auto& history = Realm::Internal::get_history(realm);
auto get_query_version = [&] {
for (auto& query : m_queries) {
@ -440,8 +438,8 @@ void RealmCoordinator::advance_to_ready(Realm& realm)
}
// no async queries; just advance to latest
if (version.version == 0) {
transaction::advance(sg, history, realm.m_binding_context.get());
if (version.version == std::numeric_limits<uint_fast64_t>::max()) {
transaction::advance(sg, realm.m_binding_context.get());
return;
}
@ -453,14 +451,14 @@ void RealmCoordinator::advance_to_ready(Realm& realm)
while (true) {
// Advance to the ready version without holding any locks because it
// may end up calling user code (in did_change() notifications)
transaction::advance(sg, history, realm.m_binding_context.get(), version);
transaction::advance(sg, realm.m_binding_context.get(), version);
// Reacquire the lock and recheck the query version, as the queries may
// have advanced to a later version while we didn't hold the lock. If
// so, we need to release the lock and re-advance
std::lock_guard<std::mutex> lock(m_query_mutex);
version = get_query_version();
if (version.version == 0)
if (version.version == std::numeric_limits<uint_fast64_t>::max())
return;
if (version != sg.get_version_of_current_transaction())
continue;

View File

@ -25,7 +25,7 @@
namespace realm {
class AsyncQueryCallback;
class ClientHistory;
class Replication;
class Results;
class Schema;
class SharedGroup;
@ -102,13 +102,13 @@ private:
// SharedGroup used for actually running async queries
// Will have a read transaction iff m_queries is non-empty
std::unique_ptr<ClientHistory> m_query_history;
std::unique_ptr<Replication> m_query_history;
std::unique_ptr<SharedGroup> m_query_sg;
// SharedGroup used to advance queries in m_new_queries to the main shared
// group's transaction version
// Will have a read transaction iff m_new_queries is non-empty
std::unique_ptr<ClientHistory> m_advancer_history;
std::unique_ptr<Replication> m_advancer_history;
std::unique_ptr<SharedGroup> m_advancer_sg;
std::exception_ptr m_async_error;

View File

@ -432,23 +432,21 @@ public:
namespace realm {
namespace _impl {
namespace transaction {
void advance(SharedGroup& sg, ClientHistory& history, BindingContext* context,
SharedGroup::VersionID version)
void advance(SharedGroup& sg, BindingContext* context, SharedGroup::VersionID version)
{
TransactLogObserver(context, sg, [&](auto&&... args) {
LangBindHelper::advance_read(sg, history, std::move(args)...);
LangBindHelper::advance_read(sg, std::move(args)...);
}, true);
}
void begin(SharedGroup& sg, ClientHistory& history, BindingContext* context,
bool validate_schema_changes)
void begin(SharedGroup& sg, BindingContext* context, bool validate_schema_changes)
{
TransactLogObserver(context, sg, [&](auto&&... args) {
LangBindHelper::promote_to_write(sg, history, std::move(args)...);
LangBindHelper::promote_to_write(sg, std::move(args)...);
}, validate_schema_changes);
}
void commit(SharedGroup& sg, ClientHistory&, BindingContext* context)
void commit(SharedGroup& sg, BindingContext* context)
{
LangBindHelper::commit_and_continue_as_read(sg);
@ -457,10 +455,10 @@ void commit(SharedGroup& sg, ClientHistory&, BindingContext* context)
}
}
void cancel(SharedGroup& sg, ClientHistory& history, BindingContext* context)
void cancel(SharedGroup& sg, BindingContext* context)
{
TransactLogObserver(context, sg, [&](auto&&... args) {
LangBindHelper::rollback_and_continue_as_read(sg, history, std::move(args)...);
LangBindHelper::rollback_and_continue_as_read(sg, std::move(args)...);
}, false);
}

View File

@ -24,27 +24,26 @@
namespace realm {
class BindingContext;
class SharedGroup;
class ClientHistory;
namespace _impl {
namespace transaction {
// Advance the read transaction version, with change notifications sent to delegate
// Must not be called from within a write transaction.
void advance(SharedGroup& sg, ClientHistory& history, BindingContext* binding_context,
void advance(SharedGroup& sg, BindingContext* binding_context,
SharedGroup::VersionID version=SharedGroup::VersionID{});
// Begin a write transaction
// If the read transaction version is not up to date, will first advance to the
// most recent read transaction and sent notifications to delegate
void begin(SharedGroup& sg, ClientHistory& history, BindingContext* binding_context,
void begin(SharedGroup& sg, BindingContext* binding_context,
bool validate_schema_changes=true);
// Commit a write transaction
void commit(SharedGroup& sg, ClientHistory& history, BindingContext* binding_context);
void commit(SharedGroup& sg, BindingContext* binding_context);
// Cancel a write transaction and roll back all changes, with change notifications
// for reverting to the old values sent to delegate
void cancel(SharedGroup& sg, ClientHistory& history, BindingContext* binding_context);
void cancel(SharedGroup& sg, BindingContext* binding_context);
} // namespace transaction
} // namespace _impl
} // namespace realm

View File

@ -26,7 +26,7 @@
namespace realm {
class Group;
class Property;
struct Property;
class ObjectSchema {
public:

View File

@ -55,6 +55,7 @@ namespace realm {
size_t table_column = -1;
bool requires_index() const { return is_primary || is_indexed; }
bool is_indexable() const { return type == PropertyTypeInt || type == PropertyTypeBool || type == PropertyTypeString || type == PropertyTypeDate; }
};
static inline const char *string_for_property_type(PropertyType type) {

View File

@ -89,7 +89,7 @@ void Schema::validate() const
// check indexable
if (prop.is_indexed) {
if (prop.type != PropertyTypeString && prop.type != PropertyTypeInt) {
if (!prop.is_indexable()) {
exceptions.emplace_back(PropertyTypeNotIndexableException(object.name, prop));
}
}

View File

@ -72,7 +72,7 @@ Realm::Realm(Config config)
}
void Realm::open_with_config(const Config& config,
std::unique_ptr<ClientHistory>& history,
std::unique_ptr<Replication>& history,
std::unique_ptr<SharedGroup>& shared_group,
std::unique_ptr<Group>& read_only_group)
{
@ -211,9 +211,8 @@ void Realm::update_schema(std::unique_ptr<Schema> schema, uint64_t version)
}
read_group();
transaction::begin(*m_shared_group, *m_history, m_binding_context.get(),
transaction::begin(*m_shared_group, m_binding_context.get(),
/* error on schema changes */ false);
m_in_transaction = true;
struct WriteTransactionGuard {
Realm& realm;
@ -289,20 +288,27 @@ void Realm::verify_in_write() const
}
}
bool Realm::is_in_transaction() const noexcept
{
if (!m_shared_group) {
return false;
}
return m_shared_group->get_transact_stage() == SharedGroup::transact_Writing;
}
void Realm::begin_transaction()
{
check_read_write(this);
verify_thread();
if (m_in_transaction) {
if (is_in_transaction()) {
throw InvalidTransactionException("The Realm is already in a write transaction");
}
// make sure we have a read transaction
read_group();
transaction::begin(*m_shared_group, *m_history, m_binding_context.get());
m_in_transaction = true;
transaction::begin(*m_shared_group, m_binding_context.get());
}
void Realm::commit_transaction()
@ -310,12 +316,11 @@ void Realm::commit_transaction()
check_read_write(this);
verify_thread();
if (!m_in_transaction) {
if (!is_in_transaction()) {
throw InvalidTransactionException("Can't commit a non-existing write transaction");
}
m_in_transaction = false;
transaction::commit(*m_shared_group, *m_history, m_binding_context.get());
transaction::commit(*m_shared_group, m_binding_context.get());
m_coordinator->send_commit_notifications();
}
@ -324,18 +329,19 @@ void Realm::cancel_transaction()
check_read_write(this);
verify_thread();
if (!m_in_transaction) {
if (!is_in_transaction()) {
throw InvalidTransactionException("Can't cancel a non-existing write transaction");
}
m_in_transaction = false;
transaction::cancel(*m_shared_group, *m_history, m_binding_context.get());
transaction::cancel(*m_shared_group, m_binding_context.get());
}
void Realm::invalidate()
{
verify_thread();
if (m_in_transaction) {
check_read_write(this);
if (is_in_transaction()) {
cancel_transaction();
}
if (!m_group) {
@ -353,7 +359,7 @@ bool Realm::compact()
if (m_config.read_only) {
throw InvalidTransactionException("Can't compact a read-only Realm");
}
if (m_in_transaction) {
if (is_in_transaction()) {
throw InvalidTransactionException("Can't compact a Realm within a write transaction");
}
@ -395,7 +401,7 @@ bool Realm::refresh()
check_read_write(this);
// can't be any new changes if we're in a write transaction
if (m_in_transaction) {
if (is_in_transaction()) {
return false;
}
@ -405,7 +411,7 @@ bool Realm::refresh()
}
if (m_group) {
transaction::advance(*m_shared_group, *m_history, m_binding_context.get());
transaction::advance(*m_shared_group, m_binding_context.get());
m_coordinator->process_available_async(*this);
}
else {

View File

@ -32,7 +32,7 @@
namespace realm {
class BindingContext;
class ClientHistory;
class Replication;
class Group;
class Realm;
class RealmDelegate;
@ -114,7 +114,7 @@ namespace realm {
void begin_transaction();
void commit_transaction();
void cancel_transaction();
bool is_in_transaction() const { return m_in_transaction; }
bool is_in_transaction() const noexcept;
bool is_in_read_transaction() const { return !!m_group; }
bool refresh();
@ -149,7 +149,6 @@ namespace realm {
// AsyncQuery needs access to the SharedGroup to be able to call the
// handover functions, which are not very wrappable
static SharedGroup& get_shared_group(Realm& realm) { return *realm.m_shared_group; }
static ClientHistory& get_history(Realm& realm) { return *realm.m_history; }
// AsyncQuery needs to be able to access the owning coordinator to
// wake up the worker thread when a callback is added, and
@ -158,17 +157,16 @@ namespace realm {
};
static void open_with_config(const Config& config,
std::unique_ptr<ClientHistory>& history,
std::unique_ptr<Replication>& history,
std::unique_ptr<SharedGroup>& shared_group,
std::unique_ptr<Group>& read_only_group);
private:
Config m_config;
std::thread::id m_thread_id = std::this_thread::get_id();
bool m_in_transaction = false;
bool m_auto_refresh = true;
std::unique_ptr<ClientHistory> m_history;
std::unique_ptr<Replication> m_history;
std::unique_ptr<SharedGroup> m_shared_group;
std::unique_ptr<Group> m_read_only_group;

View File

@ -258,6 +258,54 @@ module.exports = BaseTest.extend({
});
},
testRealmWithIndexedProperties: function() {
var IndexedTypes = {
name: 'IndexedTypesObject',
properties: {
boolCol: {type: 'bool', indexed: true},
intCol: {type: 'int', indexed: true},
stringCol: {type: 'string', indexed: true},
dateCol: {type: 'date', indexed: true},
}
};
var realm = new Realm({schema: [IndexedTypes]});
realm.write(function() {
realm.create('IndexedTypesObject', {boolCol: true, intCol: 1, stringCol: '1', dateCol: new Date(1)});
});
var NotIndexed = {
name: 'NotIndexedObject',
properties: {
floatCol: {type: 'float', indexed: false}
}
};
new Realm({schema: [NotIndexed], path: '1'});
TestCase.assertThrows(function() {
IndexedTypes.properties = { floatCol: {type: 'float', indexed: true} }
new Realm({schema: [IndexedTypes], path: '2'});
});
TestCase.assertThrows(function() {
IndexedTypes.properties = { doubleCol: {type: 'double', indexed: true} }
new Realm({schema: [IndexedTypes], path: '3'});
});
TestCase.assertThrows(function() {
IndexedTypes.properties = { dataCol: {type: 'data', indexed: true} }
new Realm({schema: [IndexedTypes], path: '4'});
});
// primary key
IndexedTypes.primaryKey = 'boolCol';
IndexedTypes.properties = { boolCol: {type: 'bool', indexed: true} }
// Test this doesn't throw
new Realm({schema: [IndexedTypes], path: '5'});
},
testRealmCreateWithDefaults: function() {
var realm = new Realm({schema: [schemas.DefaultValues, schemas.TestObject]});