diff --git a/src/Couchbase.Lite.Shared/API/Query/FullTextIndexConfiguration.cs b/src/Couchbase.Lite.Shared/API/Query/FullTextIndexConfiguration.cs
index c8eb16095..3bb3105c0 100644
--- a/src/Couchbase.Lite.Shared/API/Query/FullTextIndexConfiguration.cs
+++ b/src/Couchbase.Lite.Shared/API/Query/FullTextIndexConfiguration.cs
@@ -28,7 +28,6 @@ namespace Couchbase.Lite.Query
///
public sealed class FullTextIndexConfiguration : IndexConfiguration
{
- #region Properties
///
/// Gets whether or not to ignore accents when performing
@@ -42,13 +41,17 @@ public sealed class FullTextIndexConfiguration : IndexConfiguration
///
public string Language { get; } = CultureInfo.CurrentCulture.TwoLetterISOLanguageName;
+ ///
+ /// A predicate expression defining conditions for indexing documents.
+ /// Only documents satisfying the predicate are included, enabling partial indexes.
+ ///
+ public string? Where { get; set; }
+
internal override C4IndexOptions Options => new C4IndexOptions {
ignoreDiacritics = IgnoreAccents,
- language = Language
+ language = Language,
+ where = Where
};
- #endregion
-
- #region Constructors
///
/// Starts the creation of an index based on a full text search
@@ -58,10 +61,24 @@ public sealed class FullTextIndexConfiguration : IndexConfiguration
/// The locale to use when performing full text searching
/// The beginning of an FTS based index
public FullTextIndexConfiguration(string[] expressions, bool ignoreAccents = false,
+ string? locale = null)
+ : this(expressions, null, ignoreAccents, locale)
+ {
+ }
+
+ ///
+ /// Starts the creation of an index based on a full text search
+ ///
+ /// The expressions to use to create the index
+ /// The boolean value to ignore accents when performing the full text search
+ /// The locale to use when performing full text searching
+ /// The beginning of an FTS based index
+ public FullTextIndexConfiguration(string[] expressions, string? where = null, bool ignoreAccents = false,
string? locale = null)
: base(C4IndexType.FullTextIndex, expressions)
{
IgnoreAccents = ignoreAccents;
+ Where = where;
if (!string.IsNullOrEmpty(locale)) {
Language = locale!;
}
@@ -76,7 +93,5 @@ public FullTextIndexConfiguration(params string[] expressions)
: base(C4IndexType.FullTextIndex, expressions)
{
}
-
- #endregion
}
}
diff --git a/src/Couchbase.Lite.Shared/API/Query/ValueIndexConfiguration.cs b/src/Couchbase.Lite.Shared/API/Query/ValueIndexConfiguration.cs
index dd1e6af78..44b8b1a1d 100644
--- a/src/Couchbase.Lite.Shared/API/Query/ValueIndexConfiguration.cs
+++ b/src/Couchbase.Lite.Shared/API/Query/ValueIndexConfiguration.cs
@@ -18,30 +18,46 @@
using Couchbase.Lite.Internal.Query;
using LiteCore.Interop;
+using System.Collections.Generic;
+using System.Linq;
namespace Couchbase.Lite.Query
{
///
- /// An class for an index based on a simple property value
+ /// An class for an index based on one or more simple property values
///
public sealed class ValueIndexConfiguration : IndexConfiguration
{
- #region Properties
- internal override C4IndexOptions Options => new C4IndexOptions();
- #endregion
+ internal override C4IndexOptions Options => new C4IndexOptions
+ {
+ where = Where
+ };
- #region Constructors
+ ///
+ /// A predicate expression defining conditions for indexing documents.
+ /// Only documents satisfying the predicate are included, enabling partial indexes.
+ ///
+ public string? Where { get; set; }
///
- /// Starts the creation of an index based on a simple property
+ /// Starts the creation of an index based on one or more simple property values
///
/// The expressions to use to create the index
- /// The beginning of a value based index
public ValueIndexConfiguration(params string[] expressions)
: base(C4IndexType.ValueIndex, expressions)
{
}
- #endregion
+ ///
+ /// Starts the creation of an index based on one or more simple property values,
+ /// and a predicate for enabling partial indexes.
+ ///
+ /// The expressions to use to create the index
+ /// A where clause used to determine whether or not to include a particular doc
+ public ValueIndexConfiguration(IEnumerable expressions, string? where = null)
+ : base(C4IndexType.ValueIndex, expressions.ToArray())
+ {
+ Where = where;
+ }
}
}
diff --git a/src/Couchbase.Lite.Tests.Shared/Couchbase.Lite.Tests.Shared.projitems b/src/Couchbase.Lite.Tests.Shared/Couchbase.Lite.Tests.Shared.projitems
index d13d1b6da..93b31716c 100644
--- a/src/Couchbase.Lite.Tests.Shared/Couchbase.Lite.Tests.Shared.projitems
+++ b/src/Couchbase.Lite.Tests.Shared/Couchbase.Lite.Tests.Shared.projitems
@@ -27,6 +27,7 @@
+
diff --git a/src/Couchbase.Lite.Tests.Shared/MmapTest.cs b/src/Couchbase.Lite.Tests.Shared/MmapTest.cs
index 7bc77f8d8..9aa27d22f 100644
--- a/src/Couchbase.Lite.Tests.Shared/MmapTest.cs
+++ b/src/Couchbase.Lite.Tests.Shared/MmapTest.cs
@@ -104,7 +104,7 @@ public unsafe void TestDatabaseWithConfiguredMMap(bool useMmap)
var nativeConfig = TestNative.c4db_getConfig2(c4db!.RawDatabase);
var hasFlag = (nativeConfig->flags & C4DatabaseFlags.MmapDisabled) == C4DatabaseFlags.MmapDisabled;
hasFlag.Should().Be(!useMmap, "because the flag in LiteCore should match MmapEnabled (but flipped)");
- }
#endif
+ }
}
}
\ No newline at end of file
diff --git a/src/Couchbase.Lite.Tests.Shared/PartialIndexTest.cs b/src/Couchbase.Lite.Tests.Shared/PartialIndexTest.cs
new file mode 100644
index 000000000..e39871f50
--- /dev/null
+++ b/src/Couchbase.Lite.Tests.Shared/PartialIndexTest.cs
@@ -0,0 +1,118 @@
+//
+// PartialIndexTest.cs
+//
+// Copyright (c) 2024 Couchbase, Inc All rights reserved.
+//
+// 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.
+//
+
+using Couchbase.Lite;
+using Couchbase.Lite.Query;
+using FluentAssertions;
+using System.Linq;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Test;
+
+[ImplementsTestSpec("T0007-Partial-Index", "1.0.3")]
+public class PartialIndexTest : TestCase
+{
+ public PartialIndexTest(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ ///
+ /// Test that a partial value index is successfully created.
+ ///
+ /// Steps
+ /// 1. Create a partial value index named "numIndex" in the default collection.
+ /// - expression: "num"
+ /// - where: "type = 'number'"
+ /// 2. Check that the index is successfully created.
+ /// 3. Create a query object with an SQL++ string:
+ /// - SELECt* FROM _ WHERE type = 'number' AND num > 1000
+ /// 4. Get the query plan from the query object and check that the plan contains
+ /// "USING INDEX numIndex" string.
+ /// 5. Create a query object with an SQL++ string:
+ /// - SELECt* FROM _ WHERE type = 'foo' AND num > 1000
+ /// 6. Get the query plan from the query object and check that the plan doesn't contain
+ /// "USING INDEX numIndex" string.
+ ///
+ [Fact]
+ public void TestCreatePartialValueIndex()
+ {
+ // Step 1
+ var indexConfig = new ValueIndexConfiguration(["num"], "type = 'number'");
+ DefaultCollection.CreateIndex("numIndex", indexConfig);
+
+ // Step 2
+ DefaultCollection.GetIndexes().Should().Contain("numIndex", "because the index was just created");
+
+ // Step 3
+ using var partialQuery = Db.CreateQuery("SELECT * FROM _ WHERE type = 'number' AND num > 1000");
+
+ // Step 4
+ partialQuery.Explain().Should().Contain("USING INDEX numIndex", "because the partial index should be applied to this query");
+
+ // Step 5
+ using var nonPartialQuery = Db.CreateQuery("SELECT * FROM _ WHERE type = 'foo' AND num > 1000");
+
+ // Step 6
+ nonPartialQuery.Explain().Should().NotContain("USING INDEX numIndex", "because the partial index should not be applied to this query");
+ }
+
+ ///
+ /// Test that a partial full text index is successfully created.
+ ///
+ /// Steps
+ /// 1. Create following two documents with the following bodies in the default collection.
+ /// - { "content" : "Couchbase Lite is a database." }
+ /// - { "content" : "Couchbase Lite is a NoSQL syncable database." }
+ /// 2. Create a partial full text index named "contentIndex" in the default collection.
+ /// - expression: "content"
+ /// - where: "length(content) > 30"
+ /// 3. Check that the index is successfully created.
+ /// 4. Create a query object with an SQL++ string:
+ /// - SELECt content FROM _ WHERE match(contentIndex, "database")
+ /// 5. Execute the query and check that:
+ /// - There is one result returned
+ /// - The returned content is "Couchbase Lite is a NoSQL syncable database.".
+ ///
+ [Fact]
+ public void TestCreatePartialFullTextIndex()
+ {
+ // Step 1
+ using var doc1 = new MutableDocument();
+ using var doc2 = new MutableDocument();
+ doc1.SetString("content", "Couchbase Lite is a database.");
+ doc2.SetString("content", "Couchbase Lite is a NoSQL syncable database.");
+ DefaultCollection.Save(doc1);
+ DefaultCollection.Save(doc2);
+
+ // Step 2
+ var indexConfig = new FullTextIndexConfiguration(["content"], "length(content) > 30");
+ DefaultCollection.CreateIndex("contentIndex", indexConfig);
+
+ // Step 3
+ DefaultCollection.GetIndexes().Should().Contain("contentIndex", "because the index was just created");
+
+ // Step 4
+ using var query = Db.CreateQuery("SELECT content FROM _ WHERE match(contentIndex, 'database')");
+
+ // Step 5
+ var results = query.Execute().ToList();
+ results.Should().HaveCount(1, "because only one document matches the partial index criteria");
+ results[0].GetString("content").Should().Be("Couchbase Lite is a NoSQL syncable database.", "because this is the document that matches the query");
+ }
+}
\ No newline at end of file
diff --git a/src/Couchbase.Lite.Tests.Shared/ReplicationTest.cs b/src/Couchbase.Lite.Tests.Shared/ReplicationTest.cs
index 4085050d9..5621b63f1 100644
--- a/src/Couchbase.Lite.Tests.Shared/ReplicationTest.cs
+++ b/src/Couchbase.Lite.Tests.Shared/ReplicationTest.cs
@@ -2102,7 +2102,7 @@ public void TestDisposeRunningReplicator()
stoppedWait.Wait(TimeSpan.FromSeconds(5)).Should().BeTrue("because otherwise the replicator didn't stop");
}
- #if __IOS__ && !SANITY_ONLY
+ #if __IOS__ && !MACCATALYST && !SANITY_ONLY
[SkippableFact]
public void TestSwitchBackgroundForeground()
{
diff --git a/src/LiteCore/src/LiteCore.Shared/Interop/C4IndexTypes_defs.cs b/src/LiteCore/src/LiteCore.Shared/Interop/C4IndexTypes_defs.cs
index 537c56299..60282891e 100644
--- a/src/LiteCore/src/LiteCore.Shared/Interop/C4IndexTypes_defs.cs
+++ b/src/LiteCore/src/LiteCore.Shared/Interop/C4IndexTypes_defs.cs
@@ -1,7 +1,7 @@
//
// C4IndexTypes_defs.cs
//
-// Copyright (c) 2024 Couchbase, Inc All rights reserved.
+// Copyright (c) 2025 Couchbase, Inc All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -115,6 +115,7 @@ internal unsafe partial struct C4IndexOptions
private IntPtr _stopWords;
private IntPtr _unnestPath;
public C4VectorIndexOptions vector;
+ private IntPtr _where;
public string? language
{
@@ -168,6 +169,17 @@ public string? unnestPath
Marshal.FreeHGlobal(old);
}
}
+
+ public string? where
+ {
+ get {
+ return Marshal.PtrToStringAnsi(_where);
+ }
+ set {
+ var old = Interlocked.Exchange(ref _where, Marshal.StringToHGlobalAnsi(value));
+ Marshal.FreeHGlobal(old);
+ }
+ }
}
}
diff --git a/src/LiteCore/src/LiteCore.Shared/Interop/C4Query.cs b/src/LiteCore/src/LiteCore.Shared/Interop/C4Query.cs
index 744245164..a5715014b 100644
--- a/src/LiteCore/src/LiteCore.Shared/Interop/C4Query.cs
+++ b/src/LiteCore/src/LiteCore.Shared/Interop/C4Query.cs
@@ -43,6 +43,11 @@ public void Dispose()
if (old != IntPtr.Zero) {
Marshal.FreeHGlobal(old);
}
+
+ old = Interlocked.Exchange(ref _where, IntPtr.Zero);
+ if(old != IntPtr.Zero) {
+ Marshal.FreeHGlobal(old);
+ }
}
}