Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@
</a>
</p>

---

## Fork Notice

This is a fork of [WatermelonDB](https://github.com/Nozbe/WatermelonDB) created by [Nozbe](https://github.com/Nozbe).

**Original Project:** https://github.com/Nozbe/WatermelonDB
**Original Author:** [Radek Pietruszewski](https://github.com/radex) and [contributors](https://github.com/Nozbe/WatermelonDB/graphs/contributors)
**License:** MIT License - Copyright (c) Nozbe

This fork maintains the original MIT License. See the [LICENSE](./LICENSE) file for full license text.

---

| | WatermelonDB |
| - | ------------ |
| ⚡️ | **Launch your app instantly** no matter how much data you have |
Expand Down
19 changes: 16 additions & 3 deletions WatermelonDB.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require "json"

package = JSON.parse(File.read(File.join(__dir__, 'package.json')))

isEncryptedDB = $isEncryptedDB || false
Pod::Spec.new do |s|
s.name = "WatermelonDB"
s.version = package["version"]
Expand All @@ -11,7 +11,7 @@ Pod::Spec.new do |s|
s.license = package["license"]
s.author = { "author" => package["author"] }
s.platforms = { :ios => "12.0", :tvos => "12.0" }
s.source = { :git => "https://github.com/Nozbe/WatermelonDB.git", :tag => "v#{s.version}" }
s.source = { :git => "https://github.com/pinginc/watermelon-db.git", :tag => "v#{s.version}" }
s.source_files = "native/ios/**/*.{h,m,mm,swift,c,cpp}", "native/shared/**/*.{h,c,cpp}"
s.public_header_files = [
# FIXME: I don't think we should be exporting all headers as public
Expand All @@ -26,13 +26,26 @@ Pod::Spec.new do |s|
# I don't think this is a correct fix, but… seems to work?
# 'OTHER_SWIFT_FLAGS' => '-Xcc -Wno-error=non-modular-include-in-framework-module'
}


s.requires_arc = true
# simdjson is annoyingly slow without compiler optimization, disable for debugging
s.compiler_flags = '-Os'

s.dependency "React"

s.libraries = 'sqlite3'
# s.libraries = 'sqlite3'
if isEncryptedDB
print "Using encrypted DB\n"
s.xcconfig = {
'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1',
'OTHER_CFLAGS' => '$(inherited) -DSQLITE_HAS_CODEC=1 -DSQLITE_TEMP_STORE=2',
}
s.dependency "SQLCipher"
else
s.libraries = "sqlite3"
end


# NOTE: This dependency doesn't seem to be needed anymore (tested on RN 0.66, 0.71), file an issue
# if this causes issues for you
Expand Down
17 changes: 15 additions & 2 deletions native/android-jsi/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ def DEFAULT_BUILD_TOOLS_VERSION = "28.0.3"
def DEFAULT_MIN_SDK_VERSION = 16
def DEFAULT_TARGET_SDK_VERSION = 28
def DEFAULT_NDK_VERSION = "20.1.5948944"

def isUsingEncryptedDB = rootProject.hasProperty('encryptedDB') && rootProject.encryptedDB
android {
compileSdkVersion rootProject.hasProperty('compileSdkVersion') ? rootProject.compileSdkVersion : DEFAULT_COMPILE_SDK_VERSION
buildToolsVersion rootProject.hasProperty('buildToolsVersion') ? rootProject.buildToolsVersion : DEFAULT_BUILD_TOOLS_VERSION
ndkVersion rootProject.hasProperty('ndkVersion') ? rootProject.ndkVersion : DEFAULT_NDK_VERSION

namespace "com.nozbe.watermelondb.jsi"

buildFeatures {
prefab isUsingEncryptedDB
}

defaultConfig {
minSdkVersion rootProject.hasProperty('minSdkVersion') ? rootProject.minSdkVersion : DEFAULT_MIN_SDK_VERSION
targetSdkVersion rootProject.hasProperty('targetSdkVersion') ? rootProject.targetSdkVersion : DEFAULT_TARGET_SDK_VERSION
Expand All @@ -34,6 +38,10 @@ android {
// libwatermelondb-jsi.so (std::__ndk1::basic_ostream<char, std::__ndk1::char_traits<char>>::operator<<(long long)+124)
// libwatermelondb-jsi.so (std::__ndk1::basic_string<char, watermelondb::to_json_string<simdjson::fallback::ondemand::value&>::char_traits<char>, watermelondb::to_json_string<simdjson::fallback::ondemand::value&>::allocator<char>> watermelondb::to_json_string<simdjson::fallback::ondemand::value&>(simdjson::fallback::ondemand::value&&&)+3486)
// arguments "-DANDROID_STL=c++_shared"
if(isUsingEncryptedDB){
arguments '-DENCRYPTED_DB=1', '-DANDROID_STL=c++_shared'
cFlags '-DSQLITE_HAS_CODEC', '-DSQLITE_TEMP_STORE=2'
}
}
}
}
Expand All @@ -46,11 +54,16 @@ android {

packagingOptions {
// TODO: This only seems necessary if c++_shared is enabled in cmake
// pickFirst '**/libc++_shared.so'
if(isUsingEncryptedDB){
pickFirst '**/libc++_shared.so'
}
}
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
if (isUsingEncryptedDB) {
implementation 'com.android.ndk.thirdparty:openssl:1.1.1l-beta-1'
}
implementation 'com.facebook.react:react-native:+'
}
34 changes: 23 additions & 11 deletions native/android-jsi/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ endif()
# -------------------------------------------------
# Header search paths
# FIXME: <simdjson/simdjson.h> should work…

set(SQLITE_VERSION sqlite-amalgamation-3460000)
if(${ENCRYPTED_DB})
set(SQLITE_PATH ${NODE_MODULES_PATH_WM}@nozbe/watermelondb/native/sqlite-cipher-amalgamation/)
else()
set(SQLITE_PATH ${NODE_MODULES_PATH_WM}@nozbe/sqlite/sqlite-amalgamation-3460000/)
endif()

include_directories(
../../../../shared
${NODE_MODULES_PATH_WM}/@nozbe/sqlite/${SQLITE_VERSION}/
${SQLITE_PATH}
${NODE_MODULES_PATH_WM}/@nozbe/simdjson/src/
${NODE_MODULES_PATH_RN}/react-native/React
${NODE_MODULES_PATH_RN}/react-native/React/Base
Expand All @@ -52,12 +55,12 @@ include_directories(

# -------------------------------------------------
# Build configuration

#add_definitions(
# -DFOLLY_USE_LIBCPP=1
# -DFOLLY_NO_CONFIG=1
# -DFOLLY_HAVE_MEMRCHR=1
#)
if(${ENCRYPTED_DB})
add_definitions(
-DSQLITE_HAS_CODEC
-DSQLITE_TEMP_STORE=2
)
endif()

# simdjson is slow without optimization
set(CMAKE_CXX_FLAGS_DEBUG "-Os") # comment out for JSI debugging
Expand All @@ -71,10 +74,12 @@ set(CMAKE_CXX_FLAGS_RELEASE "-Os")

file(GLOB ANDROID_JSI_SRC_FILES ./*.cpp)
file(GLOB SHARED_SRC_FILES ../../../../shared/*.cpp)
# -------------------------------------------------

add_library(watermelondb-jsi SHARED
# vendor files
${NODE_MODULES_PATH_WM}/@nozbe/sqlite/${SQLITE_VERSION}/sqlite3.c
${SQLITE_PATH}/sqlite3.c
${SQLITE_PATH}/sqlite3.h
${NODE_MODULES_PATH_WM}/@nozbe/simdjson/src/simdjson.cpp
# our sources
${ANDROID_JSI_SRC_FILES}
Expand All @@ -86,7 +91,14 @@ add_library(watermelondb-jsi SHARED
# Enable Android 16kb native library alignment
target_link_options(watermelondb-jsi PRIVATE "-Wl,-z,max-page-size=16384")

if(${ENCRYPTED_DB})
find_package(openssl REQUIRED CONFIG)
set(openSSLLib openssl::crypto openssl::ssl)
endif()

target_link_libraries(watermelondb-jsi
# link with these libraries:
android
log)
log
${openSSLLib}
)
25 changes: 13 additions & 12 deletions native/shared/Database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,33 @@ namespace watermelondb {
using platform::consoleError;
using platform::consoleLog;

Database::Database(jsi::Runtime *runtime, std::string path, bool usesExclusiveLocking) : runtime_(runtime), mutex_() {
db_ = std::make_unique<SqliteDb>(path);
Database::Database(jsi::Runtime *runtime, std::string path, std::string password, bool usesExclusiveLocking)
: runtime_(runtime), mutex_() {
db_ = std::make_unique<SqliteDb>(path, password.c_str());

std::string initSql = "";

// FIXME: On Android, Watermelon often errors out on large batches with an IO error, because it
// can't find a temp store... I tried setting sqlite3_temp_directory to /tmp/something, but that
// didn't work. Setting temp_store to memory seems to fix the issue, but causes a significant
// slowdown, at least on iOS (not confirmed on Android). Worth investigating if the slowdown is
// also present on Android, and if so, investigate the root cause. Perhaps we need to set the temp
// directory by interacting with JNI and finding a path within the app's sandbox?
#ifdef ANDROID
// FIXME: On Android, Watermelon often errors out on large batches with an IO error, because it
// can't find a temp store... I tried setting sqlite3_temp_directory to /tmp/something, but that
// didn't work. Setting temp_store to memory seems to fix the issue, but causes a significant
// slowdown, at least on iOS (not confirmed on Android). Worth investigating if the slowdown is
// also present on Android, and if so, investigate the root cause. Perhaps we need to set the temp
// directory by interacting with JNI and finding a path within the app's sandbox?
#ifdef ANDROID
initSql += "pragma temp_store = memory;";
#endif
#endif

initSql += "pragma journal_mode = WAL;";

// set timeout before SQLITE_BUSY error is returned
initSql += "pragma busy_timeout = 5000;";

#ifdef ANDROID
#ifdef ANDROID
// NOTE: This was added in an attempt to fix mysterious `database disk image is malformed` issue when using
// headless JS services
// NOTE: This slows things down
initSql += "pragma synchronous = FULL;";
#endif
#endif
if (usesExclusiveLocking) {
// this seems to fix the headless JS service issue but breaks if you have multiple readers
initSql += "pragma locking_mode = EXCLUSIVE;";
Expand Down
12 changes: 6 additions & 6 deletions native/shared/Database.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#pragma once

#include <jsi/jsi.h>
#include <unordered_map>
#include <unordered_set>
#include <mutex>
#include <sqlite3.h>
#include <unordered_map>
#include <unordered_set>

// FIXME: Make these paths consistent across platforms
#if __ANDROID__
Expand All @@ -24,9 +24,9 @@ using namespace facebook;
namespace watermelondb {

class Database : public jsi::HostObject {
public:
public:
static void install(jsi::Runtime *runtime);
Database(jsi::Runtime *runtime, std::string path, bool usesExclusiveLocking);
Database(jsi::Runtime *runtime, std::string path, std::string password, bool usesExclusiveLocking);
~Database();
void destroy();

Expand All @@ -43,7 +43,7 @@ class Database : public jsi::HostObject {
jsi::Value getLocal(jsi::String &key);
void executeMultiple(std::string sql);

private:
private:
bool initialized_;
bool isDestroyed_;
std::mutex mutex_;
Expand All @@ -55,7 +55,7 @@ class Database : public jsi::HostObject {
jsi::Runtime &getRt();
jsi::JSError dbError(std::string description);

sqlite3_stmt* prepareQuery(std::string sql);
sqlite3_stmt *prepareQuery(std::string sql);
void bindArgs(sqlite3_stmt *statement, jsi::Array &arguments);
std::string bindArgsAndReturnId(sqlite3_stmt *statement, simdjson::ondemand::array &args);
SqliteStatement executeQuery(std::string sql, jsi::Array &arguments);
Expand Down
10 changes: 5 additions & 5 deletions native/shared/DatabaseBridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ using platform::consoleLog;
void Database::install(jsi::Runtime *runtime) {
jsi::Runtime &rt = *runtime;
auto globalObject = rt.global();
createMethod(rt, globalObject, "nativeWatermelonCreateAdapter", 2, [runtime](jsi::Runtime &rt, const jsi::Value *args) {
createMethod(rt, globalObject, "nativeWatermelonCreateAdapter", 3, [runtime](jsi::Runtime &rt, const jsi::Value *args) {
std::string dbPath = args[0].getString(rt).utf8(rt);
bool usesExclusiveLocking = args[1].getBool();
std::string password = args[1].getString(rt).utf8(rt);
bool usesExclusiveLocking = args[2].getBool();

jsi::Object adapter(rt);

std::shared_ptr<Database> database = std::make_shared<Database>(runtime, dbPath, usesExclusiveLocking);
std::shared_ptr<Database> database = std::make_shared<Database>(runtime, dbPath, password, usesExclusiveLocking);
adapter.setProperty(rt, "database", jsi::Object::createFromHostObject(rt, database));

// FIXME: Important hack!
Expand Down Expand Up @@ -187,7 +188,7 @@ void Database::install(jsi::Runtime *runtime) {
});
createMethod(rt, adapter, "unsafeLoadFromSync", 4, [database](jsi::Runtime &rt, const jsi::Value *args) {
assert(database->initialized_);
auto jsonId = (int) args[0].getNumber();
auto jsonId = (int)args[0].getNumber();
auto schema = args[1].getObject(rt);
auto preamble = args[2].getString(rt).utf8(rt);
auto postamble = args[3].getString(rt).utf8(rt);
Expand Down Expand Up @@ -228,4 +229,3 @@ void Database::install(jsi::Runtime *runtime) {


} // namespace watermelondb

60 changes: 56 additions & 4 deletions native/shared/Sqlite.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,41 @@
#include "Sqlite.h"
#include "DatabasePlatform.h"
#include <cassert>
#include <fstream>
#include <cstdio>
#include <cstring>

namespace watermelondb {

using platform::consoleError;
using platform::consoleLog;

static constexpr char SQLITE_HEADER[16] = {
'S','Q','L','i','t','e',' ','f','o','r','m','a','t',' ','3','\0'
};
static constexpr size_t SQLITE_HEADER_SIZE = 16;

static bool isPlaintextSqlite(const std::string &path) {
std::ifstream file(path, std::ios::binary);
if (!file.good()) {
return false; // File doesn't exist or can't be opened
}
char header[SQLITE_HEADER_SIZE];
file.read(header, SQLITE_HEADER_SIZE);
if (file.gcount() < static_cast<std::streamsize>(SQLITE_HEADER_SIZE)) {
return false;
}
return std::memcmp(header, SQLITE_HEADER, SQLITE_HEADER_SIZE) == 0;
}

static void wipeDbFiles(const std::string &path) {
consoleLog("Wiping plaintext database files at: " + path);
std::remove(path.c_str());
std::remove((path + "-wal").c_str());
std::remove((path + "-shm").c_str());
consoleLog("Plaintext database files wiped");
}

std::string resolveDatabasePath(std::string path) {
if (path == "" || path == ":memory:" || path.rfind("file:", 0) == 0 || path.rfind("/", 0) == 0) {
// These seem like paths/sqlite path-like strings
Expand All @@ -17,14 +46,24 @@ std::string resolveDatabasePath(std::string path) {
}
}

SqliteDb::SqliteDb(std::string path) {
SqliteDb::SqliteDb(std::string path, const char *password) {
consoleLog("Will open database...");
platform::initializeSqlite();
#ifndef ANDROID
#ifndef ANDROID
assert(sqlite3_threadsafe());
#endif
#endif

auto resolvedPath = resolveDatabasePath(path);

#ifdef SQLITE_HAS_CODEC
const bool encryptionEnabled = (password != nullptr && std::strlen(password) > 0);

if (encryptionEnabled && isPlaintextSqlite(resolvedPath)) {
consoleLog("Detected plaintext SQLite DB while encryption is enabled. Wiping: " + resolvedPath);
wipeDbFiles(resolvedPath);
}
#endif

int openResult = sqlite3_open(resolvedPath.c_str(), &sqlite);

if (openResult != SQLITE_OK) {
Expand All @@ -37,7 +76,20 @@ SqliteDb::SqliteDb(std::string path) {
}
}
assert(sqlite != nullptr);
Comment thread
dmytech462 marked this conversation as resolved.

#ifdef SQLITE_HAS_CODEC
if (password != nullptr && strlen(password) > 0) {
sqlite3_key(sqlite, password, (int)strlen(password));
int rc = sqlite3_exec(sqlite, "SELECT count(*) FROM sqlite_master;", NULL, NULL, NULL);
if (rc != SQLITE_OK) {
auto error = std::string(sqlite3_errmsg(sqlite));
consoleError("Failed to open encrypted database - " + error);
sqlite3_close(sqlite);
sqlite = nullptr;
throw std::runtime_error("Failed to open encrypted database - " + error);
}
}
#endif
assert(sqlite != nullptr);
consoleLog("Opened database at " + resolvedPath);
}

Expand Down
Loading