Skip to content

Commit

Permalink
CBL-5191 : Implement the partial index API (#596)
Browse files Browse the repository at this point in the history
* Implement the partial index API and tests.

* Ensure to include null-terminated char when converting from FLString to const char* when using LiteCore C4 Index API (CBL-6669).

* Update LiteCore to 3.2.2-3.
  • Loading branch information
pasin authored Jan 28, 2025
1 parent 97e5361 commit 2acfed3
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 15 deletions.
27 changes: 20 additions & 7 deletions include/cbl/CBLQueryIndexTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,28 @@ CBL_CAPI_BEGIN

/** Value Index Configuration. */
typedef struct {
/** The language used in the expressions. */
/** The language used in the expressions (Required). */
CBLQueryLanguage expressionLanguage;

/** The expressions describing each coloumn of the index. The expressions could be specified
in a JSON Array or in N1QL syntax using comma delimiter. */
/** The expressions describing each coloumn of the index (Required).
The expressions could be specified in a JSON Array or in N1QL syntax
using comma delimiter, depending on expressionLanguage. */
FLString expressions;

/** A predicate expression defining conditions for indexing documents.
Only documents satisfying the predicate are included, enabling partial indexes.
The expression can be JSON or N1QL/SQL++ syntax, depending on expressionLanguage. */
FLString where;
} CBLValueIndexConfiguration;

/** Full-Text Index Configuration. */
typedef struct {
/** The language used in the expressions (Required). */
CBLQueryLanguage expressionLanguage;

/** The expressions describing each coloumn of the index. The expressions could be specified
in a JSON Array or in N1QL syntax using comma delimiter. (Required) */
/** The expressions describing each coloumn of the index (Required).
The expressions could be specified in a JSON Array or in N1QL syntax
using comma delimiter, depending on expressionLanguage. */
FLString expressions;

/** Should diacritical marks (accents) be ignored?
Expand All @@ -64,6 +71,11 @@ typedef struct {
If left null, or set to an unrecognized language, no language-specific behaviors
such as stemming and stop-word removal occur. */
FLString language;

/** A predicate expression defining conditions for indexing documents.
Only documents satisfying the predicate are included, enabling partial indexes.
The expression can be JSON or N1QL/SQL++ syntax, depending on expressionLanguage. */
FLString where;
} CBLFullTextIndexConfiguration;

/** Array Index Configuration for indexing property values within arrays
Expand All @@ -80,8 +92,9 @@ typedef struct {

/** Optional expressions representing the values within the array to be
indexed. The expressions could be specified in a JSON Array or in N1QL syntax
using comma delimiter. If the array specified by the path contains scalar values,
the expressions should be left unset or set to null. */
using comma delimiter, depending on expressionLanguage.
If the array specified by the path contains scalar values, the expressions
should be left unset or set to null. */
FLString expressions;
} CBLArrayIndexConfiguration;

Expand Down
30 changes: 23 additions & 7 deletions src/CBLCollection_Internal.hh
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,34 @@ public:
#pragma mark - INDEXES:

void createValueIndex(slice name, CBLValueIndexConfiguration config) {
C4IndexOptions options = {};
C4IndexOptions options {};

alloc_slice whereAlloc;
if (config.where.buf) {
whereAlloc = alloc_slice::nullPaddedString(slice(config.where));
options.where = (const char*)whereAlloc.buf;
}

_c4col.useLocked()->createIndex(name, config.expressions,
(C4QueryLanguage)config.expressionLanguage,
kC4ValueIndex, &options);
}

void createFullTextIndex(slice name, CBLFullTextIndexConfiguration config) {
C4IndexOptions options = {};
C4IndexOptions options {};

options.ignoreDiacritics = config.ignoreAccents;

std::string languageStr;
alloc_slice langAlloc;
if (config.language.buf) {
languageStr = std::string(config.language);
options.language = languageStr.c_str();
langAlloc = alloc_slice::nullPaddedString(slice(config.language));
options.language = (const char*)langAlloc.buf;
}

alloc_slice whereAlloc;
if (config.where.buf) {
whereAlloc = alloc_slice::nullPaddedString(slice(config.where));
options.where = (const char*)whereAlloc.buf;
}

_c4col.useLocked()->createIndex(name, config.expressions,
Expand All @@ -133,8 +147,10 @@ public:
}

void createArrayIndex(slice name, CBLArrayIndexConfiguration config) {
C4IndexOptions options = {};
options.unnestPath = (char*)config.path.buf;
C4IndexOptions options {};

alloc_slice pathAlloc = alloc_slice::nullPaddedString(slice(config.path));
options.unnestPath = (const char*)pathAlloc.buf;

auto exprs = config.expressions;
if (!exprs.buf && config.expressionLanguage == kCBLJSONLanguage) {
Expand Down
82 changes: 82 additions & 0 deletions test/QueryTest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,88 @@ TEST_CASE_METHOD(QueryTest, "TestCreateArrayIndexWithPathAndExpressions", "[Quer
CBLCollection_Release(collection);
}

#pragma mark - Partial Index

// Spec: https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0007-Partial-Index.md
// Version: 1.0.2

TEST_CASE_METHOD(QueryTest, "TestCreatePartialValueIndex", "[Query]") {
CBLValueIndexConfiguration config {};

SECTION("SQL++") {
config.expressionLanguage = kCBLN1QLLanguage;
config.expressions = "num"_sl;
config.where = "type = 'number'"_sl;
}

SECTION("JSON") {
config.expressionLanguage = kCBLJSONLanguage;
config.expressions = R"([[".num"]])"_sl;
config.where = R"(["=",[".type"],"number"])"_sl;
}

CBLError error {};
CHECK(CBLCollection_CreateValueIndex(defaultCollection, "num-index"_sl, config, &error));
CheckNoError(error);

string queryString = "SELECT * FROM _ WHERE type = 'number' AND number > 1000";
query = CBLDatabase_CreateQuery(db, kCBLN1QLLanguage, slice(queryString), nullptr, &error);
REQUIRE(query);

alloc_slice exp = CBLQuery_Explain(query);
CHECK(exp.find("USING INDEX num-index"_sl));

CBLQuery_Release(query);

queryString = "SELECT * FROM _ WHERE type = 'foo' AND number > 1000";
query = CBLDatabase_CreateQuery(db, kCBLN1QLLanguage, slice(queryString), nullptr, &error);
REQUIRE(query);

exp = CBLQuery_Explain(query);
CHECK(!exp.find("USING INDEX num-index"_sl));
}

TEST_CASE_METHOD(QueryTest, "TestCreatePartialFullTextIndex", "[Query]") {
createDocWithJSON(defaultCollection, "doc1", "{\"content\": \"Couchbase Lite is a database.\"}");
createDocWithJSON(defaultCollection, "doc2", "{\"content\": \"Couchbase Lite is a NoSQL syncable database.\"}");

CBLFullTextIndexConfiguration config {};

SECTION("SQL++") {
config.expressionLanguage = kCBLN1QLLanguage;
config.expressions = "content"_sl;
config.where = "length(content) > 30"_sl;
}

SECTION("JSON") {
config.expressionLanguage = kCBLJSONLanguage;
config.expressions = R"([[".content"]])"_sl;
config.where = R"-([">", ["length()", [".content"]], 30])-"_sl;
}

CBLError error {};
CHECK(CBLCollection_CreateFullTextIndex(defaultCollection, "contentIndex"_sl, config, &error));
CheckNoError(error);

string queryString = "SELECT content FROM _ WHERE match(contentIndex, 'database')";
query = CBLDatabase_CreateQuery(db, kCBLN1QLLanguage, slice(queryString), nullptr, &error);
REQUIRE(query);
CheckNoError(error);

results = CBLQuery_Execute(query, &error);
REQUIRE(results);

int n = 0;
while (CBLResultSet_Next(results))
{
FLDict result = CBLResultSet_ResultDict(results);
slice content = FLValue_AsString(FLDict_Get(result, "content"_sl));
CHECK(content == "Couchbase Lite is a NoSQL syncable database.");
++n;
}
CHECK(n == 1);
}

#ifdef COUCHBASE_ENTERPRISE

TEST_CASE_METHOD(QueryTest, "Query Encryptable", "[Query]") {
Expand Down

0 comments on commit 2acfed3

Please sign in to comment.