Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to support Edge Server [API] #2202

Merged
merged 27 commits into from
Feb 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9caec87
C4Collection: Added getPurgeCount()
snej Nov 15, 2024
962a15a
C4Collection: createIndex() now returns bool
snej Feb 4, 2025
a06e3b1
C4Database: Added Transaction::isActive()
snej Feb 4, 2025
4a72c32
C4Database: Just added [[nodiscard]] to a few methods
snej Feb 4, 2025
4228d4e
C4Document: Exception handling in checkNewRev()
snej Feb 4, 2025
1f777a5
C4DocEnumerator: Added `startKey` option
snej Feb 4, 2025
919b1e6
C4Query: Added parameterNames()
snej Feb 4, 2025
5dbad67
c4ReplicatorTypes: Improved doc-comments
snej Feb 5, 2025
550c379
C4Test: Fixed missing info from ERROR_INFO and WITH_ERROR
snej Feb 4, 2025
136821a
Certificate: better error when public/private keys don't match
snej Feb 4, 2025
a7668ba
StringUtil: bug-fix and improvements
snej Feb 4, 2025
1d1450d
Xcode: Don't break on annoying internal exceptions in Security framework
snej Feb 4, 2025
ff1732f
Moved `TestFixture::sTempDir` to a different source file
snej Feb 4, 2025
961d0d0
HTTPLogic: Defensive check in parseHeaders()
snej Feb 5, 2025
3170f2d
HTTPTypes: Added StatusFromError() and a few more codes
snej Feb 5, 2025
48bb173
Headers: Fixed bugs and added set()
snej Feb 5, 2025
04342fc
Downgrade mbedTLS logging
snej Feb 5, 2025
28b7f5e
Cleaned up TCPSocket::wrapTLS()
snej Feb 5, 2025
be687de
Updated mbedTLS to fix thread-safety
snej Feb 5, 2025
334064f
TCPSocket: Improve error handling
snej Feb 5, 2025
49859e4
Added DatabasePool class
snej Feb 5, 2025
7857842
Fixed some bugs with DBs opened read-only
snej Feb 4, 2025
03358ae
Use DatabasePool in replicator
snej Feb 5, 2025
2364eec
Fixed leak in ReplicatorLoopbackTest
snej Feb 5, 2025
99eaef0
Removed REST API from listener [API CHANGES]
snej Feb 5, 2025
5b868b8
Updated Fleece
snej Feb 5, 2025
7391250
The remaining CMake & Jenkins changes for the Edge Server PR
snej Feb 5, 2025
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
5 changes: 0 additions & 5 deletions C/Cpp_include/c4Base.hh
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,6 @@ namespace litecore {
class PublicKey;
} // namespace crypto

namespace REST {
class Listener;
class RESTListener;
} // namespace REST

namespace websocket {
class WebSocket;
}
Expand Down
4 changes: 3 additions & 1 deletion C/Cpp_include/c4Collection.hh
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ struct C4Collection
virtual uint64_t getDocumentCount() const = 0;

virtual C4SequenceNumber getLastSequence() const = 0;
virtual uint64_t getPurgeCount() const = 0;

C4ExtraInfo& extraInfo() noexcept { return _extraInfo; }

Expand Down Expand Up @@ -93,7 +94,8 @@ struct C4Collection
/// Same as the C4Database method, but the query will refer to this collection by default.
Retained<C4Query> newQuery(C4QueryLanguage language, slice queryExpr, int* outErrorPos) const;

virtual void createIndex(slice name, slice indexSpec, C4QueryLanguage indexLanguage, C4IndexType indexType,
/// Returns true if it created or replaced the index, false if it already exists.
virtual bool createIndex(slice name, slice indexSpec, C4QueryLanguage indexLanguage, C4IndexType indexType,
const C4IndexOptions* C4NULLABLE indexOptions = nullptr) = 0;

virtual Retained<C4Index> getIndex(slice name) = 0;
Expand Down
10 changes: 6 additions & 4 deletions C/Cpp_include/c4Database.hh
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ struct C4Database
/** Attempts to discover and verify the named extension in the provided path */
static void enableExtension(slice name, slice path);

static bool exists(slice name, slice inDirectory);
static void copyNamed(slice sourcePath, slice destinationName, const Config&);
static bool deleteNamed(slice name, slice inDirectory);
static bool deleteAtPath(slice path);
static bool exists(slice name, slice inDirectory);
static void copyNamed(slice sourcePath, slice destinationName, const Config&);
[[nodiscard]] static bool deleteNamed(slice name, slice inDirectory);
[[nodiscard]] static bool deleteAtPath(slice path);

static Retained<C4Database> openNamed(slice name, const Config&);

Expand Down Expand Up @@ -184,6 +184,8 @@ struct C4Database
db->endTransaction(false);
}

bool isActive() const noexcept { return _db != nullptr; }

~Transaction() {
if ( _db ) _db->endTransaction(false);
}
Expand Down
7 changes: 7 additions & 0 deletions C/Cpp_include/c4DocEnumerator.hh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ struct C4DocEnumerator
explicit C4DocEnumerator(C4Collection* collection,
const C4EnumeratorOptions& options = kC4DefaultEnumeratorOptions);

/// Creates an enumerator on a collection, beginning at `startKey`.
/// (This means that if the order is descending, `startKey` will be the maximum key.)
/// If `startKey` is null, it's ignored and all documents are returned.
/// You must first call \ref next to step to the first document.
explicit C4DocEnumerator(C4Collection* collection, slice startKey,
const C4EnumeratorOptions& options = kC4DefaultEnumeratorOptions);

/// Creates an enumerator on a collection, ordered by sequence.
/// You must first call \ref next to step to the first document.
explicit C4DocEnumerator(C4Collection* collection, C4SequenceNumber since,
Expand Down
74 changes: 55 additions & 19 deletions C/Cpp_include/c4Listener.hh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#pragma once
#include "c4Base.hh"
#include "c4ListenerTypes.h"
#include "fleece/FLBase.h"
#include "fleece/InstanceCounted.hh"
#include <vector>

Expand All @@ -24,41 +25,76 @@ C4_ASSUME_NONNULL_BEGIN
// the dynamic library only exports the C API.
// ************************************************************************

namespace litecore::REST {
class HTTPListener;
}

/** A lightweight server that shares databases over the network for replication.
@note This class is not ref-counted. Instances must be explicitly deleted/destructed. */
struct C4Listener final
: public fleece::InstanceCounted
, C4Base {
static C4ListenerAPIs availableAPIs();

explicit C4Listener(C4ListenerConfig config);

~C4Listener() override;

bool shareDB(slice name, C4Database* db);

/// Constructor. Starts the listener (asynchronously) but does not share any databases.
explicit C4Listener(C4ListenerConfig const& config);

~C4Listener() noexcept override;

/// Stops the listener. If you don't call this, the destructor will do it for you.
C4Error stop() noexcept;

/// Shares a database, and its default collection.
/// @param name The URI name (first path component) in the HTTP API.
/// If `nullslice`, the C4Database's name will be used (possibly URL-escaped).
/// The name may not include '/', '.', control characters, or non-ASCII characters.
/// @param db The database to share. On success this instance is now managed by the Listener
/// and should not be used again by the caller.
/// @param dbConfig Optional configuration for this database. Overrides the C4ListenerConfig.
/// @returns True on success, false if the name is already in use.
[[nodiscard]] bool shareDB(slice name, C4Database* db,
C4ListenerDatabaseConfig const* C4NULLABLE dbConfig = nullptr);

/// Stops sharing a database. `db` need not be the exact instance that was registered;
/// any instance on the same database file will work.
bool unshareDB(C4Database* db);

bool shareCollection(slice name, C4Collection* coll);

bool unshareCollection(slice name, C4Collection* coll);

/// Adds a collection to be shared.
/// @note A database's default collection is automatically shared.
/// @param name The URI name the database is registered by.
/// @param collection The collection instance to share.
/// @returns True on success, false if `name` is not registered. */
[[nodiscard]] bool shareCollection(slice name, C4Collection* collection);

/// Stops sharing a collection.
/// @note Call this after \ref registerDatabase if you don't want to share the default collection.
/// @param name The URI name the database is registered by.
/// @param collection The collection instance.
/// @returns True on success, false if the database name or collection is not registered. */
bool unshareCollection(slice name, C4Collection* collection);

/// The TCP port number for incoming connections.
[[nodiscard]] uint16_t port() const;

/// Returns first the number of connections, and second the number of active connections.
[[nodiscard]] std::pair<unsigned, unsigned> connectionStatus() const;

std::vector<std::string> URLs(C4Database* C4NULLABLE db, C4ListenerAPIs api) const;
/// Returns the URL(s) of a database being shared, or of the root.
/// The URLs will differ only in their hostname -- there will be one for each IP address or known
/// hostname of the computer, or of the network interface.
[[nodiscard]] std::vector<std::string> URLs(C4Database* C4NULLABLE db) const;

static std::string URLNameFromPath(slice path);
/// A convenience that, given a filesystem path to a database, returns the database name
/// for use in an HTTP URI path.
[[nodiscard]] static std::string URLNameFromPath(slice path);

C4Listener(const C4Listener&) = delete;

// internal use only
C4Listener(C4ListenerConfig const& config, Retained<litecore::REST::HTTPListener> impl);

private:
// For some reason, MSVC on Jenkins will not compile this with noexcept (everything else will)
C4Listener(C4Listener&&); // NOLINT(performance-noexcept-move-constructor)
C4Listener(C4Listener&&) noexcept;

Retained<litecore::REST::RESTListener> _impl;
C4ListenerHTTPAuthCallback C4NULLABLE _httpAuthCallback;
void* C4NULLABLE _callbackContext;
Retained<litecore::REST::HTTPListener> _impl;
};

C4_ASSUME_NONNULL_END
12 changes: 7 additions & 5 deletions C/Cpp_include/c4Query.hh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <utility>

C4_ASSUME_NONNULL_BEGIN
Expand Down Expand Up @@ -45,11 +46,12 @@ struct C4Query final
int* C4NULLABLE outErrorPos);

unsigned columnCount() const noexcept;
slice columnTitle(unsigned col) const;
slice columnTitle(unsigned col) const LIFETIMEBOUND;
alloc_slice explain() const;

alloc_slice parameters() const noexcept;
void setParameters(slice parameters);
const std::set<std::string>& parameterNames() const noexcept LIFETIMEBOUND;
alloc_slice parameters() const noexcept;
void setParameters(slice parameters);

alloc_slice fullTextMatched(const C4FullTextMatch&);

Expand All @@ -62,8 +64,8 @@ struct C4Query final
[[nodiscard]] int64_t rowCount() const;
void seek(int64_t rowIndex);

[[nodiscard]] FLArrayIterator columns() const;
[[nodiscard]] FLValue column(unsigned i) const;
[[nodiscard]] FLArrayIterator columns() const LIFETIMEBOUND;
[[nodiscard]] FLValue column(unsigned i) const LIFETIMEBOUND;

[[nodiscard]] unsigned fullTextMatchCount() const;
[[nodiscard]] C4FullTextMatch fullTextMatch(unsigned i) const;
Expand Down
3 changes: 1 addition & 2 deletions C/c4BlobStore.cc
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,7 @@ C4BlobStore::C4BlobStore(slice dirPath, C4DatabaseFlags flags, const C4Encryptio
FilePath dir(_dirPath, "");
if ( dir.exists() ) {
dir.mustExistAsDir();
} else {
if ( !(flags & kC4DB_Create) ) error::_throw(error::NotFound);
} else if ( !(flags & kC4DB_ReadOnly) ) {
dir.mkdir();
}
}
Expand Down
64 changes: 35 additions & 29 deletions C/c4DocEnumerator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,34 +24,36 @@ using namespace litecore;

#pragma mark - DOC ENUMERATION:

CBL_CORE_API const C4EnumeratorOptions kC4DefaultEnumeratorOptions = {kC4IncludeNonConflicted | kC4IncludeBodies};
const C4EnumeratorOptions kC4DefaultEnumeratorOptions = {kC4IncludeNonConflicted | kC4IncludeBodies};

static RecordEnumerator::Options recordOptions(const C4EnumeratorOptions& c4options, slice startKey) {
RecordEnumerator::Options options;
if ( c4options.flags & kC4Descending ) options.sortOption = kDescending;
else if ( c4options.flags & kC4Unsorted )
options.sortOption = kUnsorted;
options.includeDeleted = (c4options.flags & kC4IncludeDeleted) != 0;
options.onlyConflicts = (c4options.flags & kC4IncludeNonConflicted) == 0;
if ( (c4options.flags & kC4IncludeBodies) == 0 ) options.contentOption = kMetaOnly;
else
options.contentOption = kEntireBody;
options.startKey = startKey;
return options;
}

static RecordEnumerator::Options recordOptions(const C4EnumeratorOptions& c4options, C4SequenceNumber since) {
auto options = recordOptions(c4options, nullslice);
options.minSequence = since + 1;
return options;
}

class C4DocEnumerator::Impl
: public RecordEnumerator
, public fleece::InstanceCounted {
, public InstanceCounted {
public:
Impl(C4Collection* collection, sequence_t since, const C4EnumeratorOptions& options)
: RecordEnumerator(asInternal(collection)->keyStore(), since, recordOptions(options))
Impl(C4Collection* collection, const C4EnumeratorOptions& c4Options, const RecordEnumerator::Options& options)
: RecordEnumerator(asInternal(collection)->keyStore(), options)
, _collection(asInternal(collection))
, _options(options) {}

Impl(C4Collection* collection, const C4EnumeratorOptions& options)
: RecordEnumerator(asInternal(collection)->keyStore(), recordOptions(options))
, _collection(asInternal(collection))
, _options(options) {}

static RecordEnumerator::Options recordOptions(const C4EnumeratorOptions& c4options) {
RecordEnumerator::Options options;
if ( c4options.flags & kC4Descending ) options.sortOption = kDescending;
else if ( c4options.flags & kC4Unsorted )
options.sortOption = kUnsorted;
options.includeDeleted = (c4options.flags & kC4IncludeDeleted) != 0;
options.onlyConflicts = (c4options.flags & kC4IncludeNonConflicted) == 0;
if ( (c4options.flags & kC4IncludeBodies) == 0 ) options.contentOption = kMetaOnly;
else
options.contentOption = kEntireBody;
return options;
}
, _c4Options(c4Options) {}

Retained<C4Document> getDoc() {
if ( !hasRecord() ) return nullptr;
Expand All @@ -62,7 +64,8 @@ class C4DocEnumerator::Impl
if ( !this->hasRecord() ) return false;

revid vers(record().version());
if ( (_options.flags & kC4IncludeRevHistory) && vers.isVersion() ) _docRevID = vers.asVersionVector().asASCII();
if ( (_c4Options.flags & kC4IncludeRevHistory) && vers.isVersion() )
_docRevID = vers.asVersionVector().asASCII();
else
_docRevID = vers.expanded();

Expand All @@ -78,15 +81,18 @@ class C4DocEnumerator::Impl

private:
litecore::CollectionImpl* _collection;
C4EnumeratorOptions const _options;
C4EnumeratorOptions const _c4Options;
alloc_slice _docRevID;
};

C4DocEnumerator::C4DocEnumerator(C4Collection* collection, C4SequenceNumber since, const C4EnumeratorOptions& options)
: _impl(new Impl(collection, since, options)) {}

C4DocEnumerator::C4DocEnumerator(C4Collection* collection, const C4EnumeratorOptions& options)
: _impl(new Impl(collection, options)) {}
: C4DocEnumerator(collection, nullslice, options) {}

C4DocEnumerator::C4DocEnumerator(C4Collection* collection, slice startKey, const C4EnumeratorOptions& options)
: _impl(new Impl(collection, options, recordOptions(options, startKey))) {}

C4DocEnumerator::C4DocEnumerator(C4Collection* collection, C4SequenceNumber since, const C4EnumeratorOptions& options)
: _impl(new Impl(collection, options, recordOptions(options, since))) {}

#ifndef C4_STRICT_COLLECTION_API
C4DocEnumerator::C4DocEnumerator(C4Database* database, const C4EnumeratorOptions& options)
Expand Down
45 changes: 25 additions & 20 deletions C/c4Document.cc
Original file line number Diff line number Diff line change
Expand Up @@ -208,30 +208,35 @@ Retained<C4Document> C4Document::update(slice revBody, C4RevisionFlags revFlags)
// Sanity checks a document update request before writing to the database.
bool C4Document::checkNewRev(slice parentRevID, C4RevisionFlags rqFlags, bool allowConflict,
C4Error* outError) noexcept {
int code = 0;
if ( parentRevID ) {
// Updating an existing revision; make sure it exists and is a leaf:
if ( !exists() ) code = kC4ErrorNotFound;
else if ( !selectRevision(parentRevID, false) )
code = allowConflict ? kC4ErrorNotFound : kC4ErrorConflict;
else if ( !allowConflict && !(_selected.flags & kRevLeaf) )
code = kC4ErrorConflict;
} else {
// No parent revision given:
if ( rqFlags & kRevDeleted ) {
// Didn't specify a revision to delete: NotFound or a Conflict, depending
code = ((_flags & kDocExists) ? kC4ErrorConflict : kC4ErrorNotFound);
} else if ( (_flags & kDocExists) && !(_selected.flags & kRevDeleted) ) {
// If doc exists, current rev must be a deletion or there will be a conflict:
code = kC4ErrorConflict;
try {
int code = 0;
if ( parentRevID ) {
// Updating an existing revision; make sure it exists and is a leaf:
if ( !exists() ) code = kC4ErrorNotFound;
else if ( !selectRevision(parentRevID, false) )
code = allowConflict ? kC4ErrorNotFound : kC4ErrorConflict;
else if ( !allowConflict && !(_selected.flags & kRevLeaf) )
code = kC4ErrorConflict;
} else {
// No parent revision given:
if ( rqFlags & kRevDeleted ) {
// Didn't specify a revision to delete: NotFound or a Conflict, depending
code = ((_flags & kDocExists) ? kC4ErrorConflict : kC4ErrorNotFound);
} else if ( (_flags & kDocExists) && !(_selected.flags & kRevDeleted) ) {
// If doc exists, current rev must be a deletion or there will be a conflict:
code = kC4ErrorConflict;
}
}
}

if ( code ) {
c4error_return(LiteCoreDomain, code, nullslice, outError);
if ( code ) {
c4error_return(LiteCoreDomain, code, nullslice, outError);
return false;
}
return true;
} catch ( ... ) {
if ( outError ) *outError = C4Error::fromCurrentException();
return false;
}
return true;
}

#pragma mark - CONFLICTS:
Expand Down
2 changes: 2 additions & 0 deletions C/c4Query.cc
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ alloc_slice C4Query::fullTextMatched(const C4FullTextMatch& term) {
return _query->getMatchedText((Query::FullTextTerm&)term);
}

const set<string>& C4Query::parameterNames() const noexcept { return _query->parameterNames(); }

alloc_slice C4Query::parameters() const noexcept {
LOCK(_mutex);
return _parameters;
Expand Down
1 change: 0 additions & 1 deletion C/c4_ee.exp
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,6 @@ __FLBuf_Release



_c4listener_availableAPIs
_c4listener_start
_c4listener_free
_c4listener_shareDB
Expand Down
Loading
Loading