diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index b026ad12e..016741fd6 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -56,7 +56,9 @@ jobs: sudo dpkg -i libicu71_71.1-3ubuntu1_amd64.deb rm libicu71_71.1-3ubuntu1_amd64.deb - name: Run tests - run: ./gradlew check + # Only run linuxX64Test for now, JVM tests are crashing on Linux + # https://www.couchbase.com/forums/t/java-sdk-native-crash-on-linux/41006 + run: ./gradlew linuxX64Test #./gradlew check - name: Upload test results uses: actions/upload-artifact@v4 if: failure() diff --git a/README.md b/README.md index 2b9b072f9..f55b2660e 100644 --- a/README.md +++ b/README.md @@ -19,15 +19,15 @@ standalone client database, or paired with [Couchbase Server](https://www.couchb Gateway](https://www.couchbase.com/products/sync-gateway/) or [Capella App Services]( https://www.couchbase.com/products/capella/app-services/) for cloud to edge data synchronization. Features include: -* [SQL++](https://www.couchbase.com/products/n1ql/), key/value, and full-text search queries +* [SQL++](https://www.couchbase.com/products/n1ql/), key/value, full-text search, and vector search queries * Observable queries, documents, databases, and replicators * Binary document attachments (blobs) * Peer-to-peer and cloud-to-edge data sync Kotbase provides full Enterprise and Community Edition API support for Android and JVM ([via Java SDK]( https://github.com/couchbase/couchbase-lite-java-ce-root)), native iOS and macOS ([via Objective-C SDK]( -https://github.com/couchbase/couchbase-lite-ios)), and experimental support for available APIs in native Linux and -Windows ([via C SDK](https://github.com/couchbase/couchbase-lite-C)). +https://github.com/couchbase/couchbase-lite-ios)), and experimental support for native Linux and Windows ([via C SDK]( +https://github.com/couchbase/couchbase-lite-C)). ## Installation @@ -89,8 +89,7 @@ as straightforward as changing the import package from `com.couchbase.lite` to ` * Java callback functional interfaces are implemented as Kotlin function types. * `File`, `URL`, and `URI` APIs are represented as strings. -* `Date` APIs use [kotlinx-datetime's `Instant`]( -https://kotlinlang.org/api/kotlinx-datetime/kotlinx-datetime/kotlinx.datetime/-instant/). +* `Date` APIs use Kotlin's `Instant`. * `InputStream` APIs use [kotlinx-io's `Source`]( https://kotlinlang.org/api/kotlinx-io/kotlinx-io-core/kotlinx.io/-source/). * `Executor` APIs use Kotlin's `CoroutineContext`. @@ -106,6 +105,8 @@ https://kotlinlang.org/api/kotlinx-datetime/kotlinx-datetime/kotlinx.datetime/-i https://docs.couchbase.com/mobile/3.1.10/couchbase-lite-swift/Classes/Fragment.html), [Objective-C]( https://docs.couchbase.com/mobile/3.1.10/couchbase-lite-objc/Protocols/CBLFragment.html), and [.NET]( https://docs.couchbase.com/mobile/3.1.10/couchbase-lite-net/api/Couchbase.Lite.IFragment.html). +* Configuration factory APIs from the Android KTX SDK have been deprecated in favor of using constructors directly, + which support Kotlin named arguments themselves. ## Extension Libraries @@ -169,8 +170,10 @@ implementation("dev.kotbase:couchbase-lite-ee-paging:3.1.11-1.1.2") * [ ] SwiftUI for Kotbase Notes * [x] Couchbase Lite [3.1 API](https://docs.couchbase.com/couchbase-lite/3.1/cbl-whatsnew.html) - Scopes and Collections * [x] Versioned docs -* [ ] Couchbase Lite [3.2 API](https://docs.couchbase.com/couchbase-lite/3.2/cbl-whatsnew.html) - [Vector Search]( +* [x] Couchbase Lite [3.2 API](https://docs.couchbase.com/couchbase-lite/3.2/cbl-whatsnew.html) - [Vector Search]( https://www.couchbase.com/products/vector-search/) +* [ ] Couchbase Lite [3.3 API](https://docs.couchbase.com/couchbase-lite/3.3/cbl-whatsnew.html) - Multipeer Replicator +* [ ] URLEndpointListener on Linux & MinGW platforms * [ ] Improve Swift API alignment with Couchbase Lite using [Swift export](https://youtrack.jetbrains.com/issue/KT-64572), `@ObjCName`, and/or `@ShouldRefineInSwift` * [ ] Async coroutines API diff --git a/buildSrc/src/main/kotlin/base-convention.gradle.kts b/buildSrc/src/main/kotlin/base-convention.gradle.kts index 46d0e0128..32f130a0e 100644 --- a/buildSrc/src/main/kotlin/base-convention.gradle.kts +++ b/buildSrc/src/main/kotlin/base-convention.gradle.kts @@ -59,6 +59,9 @@ kotlin { optIn("kotlinx.cinterop.BetaInteropApi") optIn("kotlinx.cinterop.ExperimentalForeignApi") } + if (name.endsWith("Test")) { + enableLanguageFeature("MultiDollarInterpolation") + } } } diff --git a/couchbase-lite-ee/api/couchbase-lite-ee.klib.api b/couchbase-lite-ee/api/couchbase-lite-ee.klib.api index bb7c61ba3..eef3e1af8 100644 --- a/couchbase-lite-ee/api/couchbase-lite-ee.klib.api +++ b/couchbase-lite-ee/api/couchbase-lite-ee.klib.api @@ -3308,8 +3308,6 @@ open class kotbase/DataSource { // kotbase/DataSource|null[0] // Targets: [linux, mingwX64] open class kotbase/Dictionary : kotbase/DictionaryInterface, kotlin.collections/Iterable { // kotbase/Dictionary|null[0] - final val release // kotbase/Dictionary.release|{}release[0] - final fun (): kotlin/Boolean // kotbase/Dictionary.release.|(){}[0] open val count // kotbase/Dictionary.count|{}count[0] open fun (): kotlin/Int // kotbase/Dictionary.count.|(){}[0] open val keys // kotbase/Dictionary.keys|{}keys[0] diff --git a/couchbase-lite-ee/src/commonTest/ee/kotbase/BaseVectorSearchTest.kt b/couchbase-lite-ee/src/commonTest/ee/kotbase/BaseVectorSearchTest.kt index 7829928c8..af1259af0 100644 --- a/couchbase-lite-ee/src/commonTest/ee/kotbase/BaseVectorSearchTest.kt +++ b/couchbase-lite-ee/src/commonTest/ee/kotbase/BaseVectorSearchTest.kt @@ -216,9 +216,9 @@ abstract class BaseVectorSearchTest : BaseDbTest() { val expr = vectorExpression ?: wordsQueryDefaultExpression() if (metric != null) { - append("ORDER BY APPROX_VECTOR_DISTANCE($expr, \$vector, \"$metric\") ") + append($$"""ORDER BY APPROX_VECTOR_DISTANCE($$expr, $vector, "$$metric") """) } else { - append("ORDER BY APPROX_VECTOR_DISTANCE($expr, \$vector) ") + append($$"ORDER BY APPROX_VECTOR_DISTANCE($$expr, $vector) ") } append("LIMIT $limit") diff --git a/couchbase-lite-ee/src/commonTest/ee/kotbase/PredictiveQueryTest.kt b/couchbase-lite-ee/src/commonTest/ee/kotbase/PredictiveQueryTest.kt index a405c227c..726db6cd5 100644 --- a/couchbase-lite-ee/src/commonTest/ee/kotbase/PredictiveQueryTest.kt +++ b/couchbase-lite-ee/src/commonTest/ee/kotbase/PredictiveQueryTest.kt @@ -18,6 +18,7 @@ package kotbase import kotbase.ext.nowMillis import kotbase.ext.toStringMillis import kotbase.test.IgnoreLinuxMingw +import kotbase.test.IgnoreMingw import kotbase.test.assertIntContentEquals import kotlin.time.Clock import kotlin.test.* @@ -74,6 +75,8 @@ class PredictiveQueryTest : BaseQueryTest() { } } + // TODO: frequently crashes during second query in mingw + @IgnoreMingw @Test fun testRegisterMultipleModelsWithSameName() { createDocument(listOf(1, 2, 3, 4, 5)) diff --git a/couchbase-lite-ee/src/commonTest/ee/kotbase/VectorSearchTestMain.kt b/couchbase-lite-ee/src/commonTest/ee/kotbase/VectorSearchTestMain.kt index 8fe248db2..10e17fc69 100644 --- a/couchbase-lite-ee/src/commonTest/ee/kotbase/VectorSearchTestMain.kt +++ b/couchbase-lite-ee/src/commonTest/ee/kotbase/VectorSearchTestMain.kt @@ -1078,7 +1078,7 @@ class VectorSearchTestMain : BaseVectorSearchTest() { createWordsIndex(config) assertThrowsCBLException(CBLError.Domain.CBLITE, CBLError.Code.INVALID_QUERY) { - executeWordsQuery(limit = 300, whereExpression = "APPROX_VECTOR_DISTANCE(vector, \$vector) < 0.5 OR catid = 'cat1'") + executeWordsQuery(limit = 300, whereExpression = $$"APPROX_VECTOR_DISTANCE(vector, $vector) < 0.5 OR catid = 'cat1'") } } diff --git a/couchbase-lite-ee/src/linuxMingwMain/ee/kotbase/PredictiveModel.linuxMingw.kt b/couchbase-lite-ee/src/linuxMingwMain/ee/kotbase/PredictiveModel.linuxMingw.kt index 257fc1594..a90871526 100644 --- a/couchbase-lite-ee/src/linuxMingwMain/ee/kotbase/PredictiveModel.linuxMingw.kt +++ b/couchbase-lite-ee/src/linuxMingwMain/ee/kotbase/PredictiveModel.linuxMingw.kt @@ -30,9 +30,12 @@ internal fun PredictiveModel.convert(): CValue { prediction = staticCFunction { ref, input -> with(ref.to()) { val output = predict(Dictionary(input!!, null, release = false)) - // output FLDict should not be released by the Dictionary object - FLDict_Retain(output?.actual) - output?.actual + output?.actual?.also { + if (output.release) { + // output FLDict should not be released by the Dictionary object + FLDict_Retain(it) + } + } } } unregistered = staticCFunction { ref -> diff --git a/couchbase-lite/api/couchbase-lite.klib.api b/couchbase-lite/api/couchbase-lite.klib.api index 7276f0188..f560dcbe8 100644 --- a/couchbase-lite/api/couchbase-lite.klib.api +++ b/couchbase-lite/api/couchbase-lite.klib.api @@ -2754,8 +2754,6 @@ open class kotbase/DataSource { // kotbase/DataSource|null[0] // Targets: [linux, mingwX64] open class kotbase/Dictionary : kotbase/DictionaryInterface, kotlin.collections/Iterable { // kotbase/Dictionary|null[0] - final val release // kotbase/Dictionary.release|{}release[0] - final fun (): kotlin/Boolean // kotbase/Dictionary.release.|(){}[0] open val count // kotbase/Dictionary.count|{}count[0] open fun (): kotlin/Int // kotbase/Dictionary.count.|(){}[0] open val keys // kotbase/Dictionary.keys|{}keys[0] diff --git a/couchbase-lite/src/commonTest/kotlin/kotbase/LegacyLogTest.kt b/couchbase-lite/src/commonTest/kotlin/kotbase/LegacyLogTest.kt index 0aa23b353..022fcc675 100644 --- a/couchbase-lite/src/commonTest/kotlin/kotbase/LegacyLogTest.kt +++ b/couchbase-lite/src/commonTest/kotlin/kotbase/LegacyLogTest.kt @@ -238,7 +238,7 @@ class LegacyLogTest : BaseDbTest(useLegacyLogging = true) { // @Test // fun testFileLoggingLogFilename() { // testWithConfiguration(LogLevel.DEBUG, LogFileConfiguration(scratchDirPath!!)) { -// Log.e(LogDomain.DATABASE, "$$\$TEST MESSAGE") +// Log.e(LogDomain.DATABASE, $$$$"$$$TEST MESSAGE") // // val files = logFiles // assertTrue(files.size >= 4) diff --git a/couchbase-lite/src/commonTest/kotlin/kotbase/ParameterTest.kt b/couchbase-lite/src/commonTest/kotlin/kotbase/ParameterTest.kt index 423e50b5e..661a59e43 100644 --- a/couchbase-lite/src/commonTest/kotlin/kotbase/ParameterTest.kt +++ b/couchbase-lite/src/commonTest/kotlin/kotbase/ParameterTest.kt @@ -31,10 +31,11 @@ class ParameterTest : BaseDbTest() { val params = Parameters() params.setString("param", "value") - val query = testDatabase.createQuery( - "SELECT meta().id" - + " FROM _default._default" - + " WHERE test = \$param" + val query = testDatabase.createQuery($$""" + SELECT meta().id + FROM _default._default + WHERE test = $param + """.trimIndent() ) query.parameters = params @@ -47,10 +48,11 @@ class ParameterTest : BaseDbTest() { val params = Parameters() params.setString("param", "value") - val query = testDatabase.createQuery( - "SELECT meta().id" - + " FROM _default._default" - + " WHERE test = \$param" + val query = testDatabase.createQuery($$""" + SELECT meta().id + FROM _default._default + WHERE test = $param + """.trimIndent() ) query.parameters = params @@ -63,10 +65,11 @@ class ParameterTest : BaseDbTest() { fun testParamContents() { val params = makeParams() - val query = testDatabase.createQuery( - "SELECT meta().id" - + " FROM _default._default" - + " WHERE test = \$param" + val query = testDatabase.createQuery($$""" + SELECT meta().id + FROM _default._default + WHERE test = $param + """.trimIndent() ) query.parameters = params diff --git a/couchbase-lite/src/commonTest/kotlin/kotbase/QueryTest.kt b/couchbase-lite/src/commonTest/kotlin/kotbase/QueryTest.kt index 82a9e60f2..07980a03a 100755 --- a/couchbase-lite/src/commonTest/kotlin/kotbase/QueryTest.kt +++ b/couchbase-lite/src/commonTest/kotlin/kotbase/QueryTest.kt @@ -3141,19 +3141,19 @@ class QueryTest : BaseQueryTest() { @Test fun testQueryDocumentWithDollarSign() { saveDocInCollection(MutableDocument() - .setString("\$type", "book") - .setString("\$description", "about cats") - .setString("\$price", "$100") + .setString($$"$type", "book") + .setString($$"$description", "about cats") + .setString($$"$price", "$100") ) saveDocInCollection(MutableDocument() - .setString("\$type", "book") - .setString("\$description", "about dogs") - .setString("\$price", "$95") + .setString($$"$type", "book") + .setString($$"$description", "about dogs") + .setString($$"$price", "$95") ) saveDocInCollection(MutableDocument() - .setString("\$type", "animal") - .setString("\$description", "puppy") - .setString("\$price", "$195") + .setString($$"$type", "animal") + .setString($$"$description", "puppy") + .setString($$"$price", "$195") ) var cheapBooks = 0 @@ -3161,16 +3161,16 @@ class QueryTest : BaseQueryTest() { val q = QueryBuilder.select( SelectResult.expression(Meta.id), - SelectResult.expression(Expression.property("\$type")), - SelectResult.expression(Expression.property("\$price")) + SelectResult.expression(Expression.property($$"$type")), + SelectResult.expression(Expression.property($$"$price")) ) .from(DataSource.collection(testCollection)) - .where(Expression.property("\$type").equalTo(Expression.string("book"))) + .where(Expression.property($$"$type").equalTo(Expression.string("book"))) q.execute().use { res -> for (r in res) { books++ - val p = r.getString("\$price")!! + val p = r.getString($$"$price")!! if (p.substring(1).toInt() < 100) { cheapBooks++ } } assertEquals(2, books) diff --git a/couchbase-lite/src/linuxMingwMain/kotlin/kotbase/Dictionary.linuxMingw.kt b/couchbase-lite/src/linuxMingwMain/kotlin/kotbase/Dictionary.linuxMingw.kt index 7da8df779..5c38c66bd 100644 --- a/couchbase-lite/src/linuxMingwMain/kotlin/kotbase/Dictionary.linuxMingw.kt +++ b/couchbase-lite/src/linuxMingwMain/kotlin/kotbase/Dictionary.linuxMingw.kt @@ -38,7 +38,7 @@ internal constructor( internal val actual: FLDict get() = memory.actual - protected val release: Boolean + internal val release: Boolean get() = memory.release internal open var dbContext: DbContext? = dbContext diff --git a/docs/site/active-peer.md b/docs/site/active-peer.md index 392bb9487..cc15644ad 100644 --- a/docs/site/active-peer.md +++ b/docs/site/active-peer.md @@ -53,29 +53,27 @@ You should configure and initialize a replicator for each Couchbase Lite databas ```kotlin val repl = Replicator( // initialize the replicator configuration - ReplicatorConfigurationFactory.newConfig( - target = URLEndpoint("wss://listener.com:8954"), - - collections = mapOf( - collections to CollectionConfiguration( + ReplicatorConfiguration(URLEndpoint("wss://listener.com:8954")) + .addCollections( + collections, + CollectionConfiguration( conflictResolver = ReplicatorConfiguration.DEFAULT_CONFLICT_RESOLVER ) - ), - - // Set replicator type - type = ReplicatorType.PUSH_AND_PULL, + ).apply { + // Set replicator type + type = ReplicatorType.PUSH_AND_PULL - // Configure Sync Mode - continuous = false, // default value + // Configure Sync Mode + isContinuous = false // default value - // Configure Server Authentication -- - // only accept self-signed certs - acceptOnlySelfSignedServerCertificate = true, + // Configure Server Authentication -- + // only accept self-signed certs + isAcceptOnlySelfSignedServerCertificate = true - // Configure the credentials the - // client will provide if prompted - authenticator = BasicAuthenticator("PRIVUSER", "let me in".toCharArray()) - ) + // Configure the credentials the + // client will provide if prompted + authenticator = BasicAuthenticator("PRIVUSER", "let me in".toCharArray()) + } ) // Optionally add a change listener @@ -151,10 +149,8 @@ configuration values, found in [`Defaults.Replicator`](/api/couchbase-lite-ee/ko ```kotlin // initialize the replicator configuration - val config = ReplicatorConfigurationFactory.newConfig( - target = URLEndpoint("wss://10.0.2.2:8954/travel-sample"), - collections = mapOf(collections to null) - ) + val config = ReplicatorConfiguration(URLEndpoint("wss://10.0.2.2:8954/travel-sample")) + .addCollections(collections) ``` Note use of the scheme prefix (`wss://` to ensure TLS encryption — strongly recommended in production — or `ws://`). @@ -206,7 +202,7 @@ limit (5 minutes). The REST API provides configurable control over this replication retry logic using a set of configurable properties — see [Table 1](#table-1). -**Table 1. Replication Retry Configuration Properties** +**Table 1. Replication Retry Configuration Properties** |
Property
| Use cases | Description | |:---------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -220,14 +216,14 @@ When necessary you can adjust any or all of those configurable values — see [E ```kotlin val repl = Replicator( - ReplicatorConfigurationFactory.newConfig( - target = URLEndpoint("ws://localhost:4984/mydatabase"), - collections = mapOf(collections to null), - // other config params as required . . - heartbeat = 150, - maxAttempts = 20, - maxAttemptWaitTime = 600 - ) + ReplicatorConfiguration(URLEndpoint("ws://localhost:4984/mydatabase")) + .addCollections(collections) + .apply { + // other config params as required . . + heartbeat = 150 + maxAttempts = 20 + maxAttemptWaitTime = 600 + } ) repl.start() this.replicator = repl @@ -367,29 +363,27 @@ starting the replicator running using [`start()`](/api/couchbase-lite-ee/kotbase // to prevent the Replicator from being GCed val repl = Replicator( // initialize the replicator configuration - ReplicatorConfigurationFactory.newConfig( - target = URLEndpoint("wss://listener.com:8954"), - - collections = mapOf(collections to null), - - // Set replicator type - type = ReplicatorType.PUSH_AND_PULL, - - // Configure Sync Mode - continuous = false, // default value - - // set auto-purge behavior - // (here we override default) - enableAutoPurge = false, - - // Configure Server Authentication -- - // only accept self-signed certs - acceptOnlySelfSignedServerCertificate = true, - - // Configure the credentials the - // client will provide if prompted - authenticator = BasicAuthenticator("PRIVUSER", "let me in".toCharArray()) - ) + ReplicatorConfiguration(URLEndpoint("wss://listener.com:8954")) + .addCollections(collections) + .apply { + // Set replicator type + type = ReplicatorType.PUSH_AND_PULL + + // Configure Sync Mode + isContinuous = false // default value + + // set auto-purge behavior + // (here we override default) + isAutoPurgeEnabled = false + + // Configure Server Authentication -- + // only accept self-signed certs + isAcceptOnlySelfSignedServerCertificate = true + + // Configure the credentials the + // client will provide if prompted + authenticator = BasicAuthenticator("PRIVUSER", "let me in".toCharArray()) + } ) // Start replicator @@ -554,11 +548,9 @@ methods: ```kotlin val repl = Replicator( - ReplicatorConfigurationFactory.newConfig( - target = URLEndpoint("ws://localhost:4984/mydatabase"), - collections = mapOf(setOf(collection) to null), - type = ReplicatorType.PUSH - ) + ReplicatorConfiguration(URLEndpoint("ws://localhost:4984/mydatabase")) + .addCollections(collections) + .setType(ReplicatorType.PUSH) ) val pendingDocs = repl.getPendingDocumentIds(collection) diff --git a/docs/site/databases.md b/docs/site/databases.md index 6c1bd9319..334c6120d 100644 --- a/docs/site/databases.md +++ b/docs/site/databases.md @@ -76,9 +76,8 @@ See also [Finding a Database File](#finding-a-database-file). ```kotlin val database = Database( "my-db", - DatabaseConfigurationFactory.newConfig( - "path/to/database" - ) + DatabaseConfiguration() + .setDirectory("path/to/database") ) ``` @@ -124,9 +123,8 @@ encryption key every time the database is opened — see [Example 3](#example-3) ```kotlin val db = Database( "my-db", - DatabaseConfigurationFactory.newConfig( - encryptionKey = EncryptionKey("PASSWORD") - ) + DatabaseConfiguration() + .setEncryptionKey(EncryptionKey("PASSWORD")) ) ``` @@ -209,10 +207,10 @@ You should use console logs as your first source of diagnostic information. If t level is insufficient you can focus it on database errors and generate more verbose messages — see [Example 6](#example-6). -For more on using Couchbase logs — see [Using Logs](using-logs.md). +For more on using Couchbase logs — see [Using Logs](new-logging-api.md). !!! example "Example 6. Increase Level of Database Log Messages" ```kotlin - Database.log.console.domains = setOf(LogDomain.DATABASE) + LogSinks.console = ConsoleLogSink(LogLevel.INFO, LogDomain.DATABASE) ``` diff --git a/docs/site/differences.md b/docs/site/differences.md index a3dcc3944..de5f6ad39 100644 --- a/docs/site/differences.md +++ b/docs/site/differences.md @@ -10,8 +10,7 @@ as straightforward as changing the import package from `com.couchbase.lite` to ` * Java callback functional interfaces are implemented as Kotlin function types. * `File`, `URL`, and `URI` APIs are represented as strings. -* `Date` APIs use [kotlinx-datetime's `Instant`]( - https://kotlinlang.org/api/kotlinx-datetime/kotlinx-datetime/kotlinx.datetime/-instant/). +* `Date` APIs use Kotlin's `Instant`. * `InputStream` APIs use [kotlinx-io's `Source`]( https://kotlinlang.org/api/kotlinx-io/kotlinx-io-core/kotlinx.io/-source/). * `Executor` APIs use Kotlin's `CoroutineContext`. @@ -27,3 +26,5 @@ as straightforward as changing the import package from `com.couchbase.lite` to ` https://docs.couchbase.com/mobile/3.1.10/couchbase-lite-swift/Classes/Fragment.html), [Objective-C]( https://docs.couchbase.com/mobile/3.1.10/couchbase-lite-objc/Protocols/CBLFragment.html), and [.NET]( https://docs.couchbase.com/mobile/3.1.10/couchbase-lite-net/api/Couchbase.Lite.IFragment.html). +* Configuration factory APIs from the Android KTX SDK have been deprecated in favor of using constructors directly, + which support Kotlin named arguments themselves. diff --git a/docs/site/handling-data-conflicts.md b/docs/site/handling-data-conflicts.md index 95053be9e..98b3c47be 100644 --- a/docs/site/handling-data-conflicts.md +++ b/docs/site/handling-data-conflicts.md @@ -183,12 +183,10 @@ the default conflict resolution will be applied. !!! example "Example 3. A Conflict Resolver" ```kotlin - val collectionConfig = CollectionConfigurationFactory.newConfig(conflictResolver = localWinsResolver) + val collectionConfig = CollectionConfiguration(conflictResolver = localWinsResolver) val repl = Replicator( - ReplicatorConfigurationFactory.newConfig( - target = URLEndpoint("ws://localhost:4984/mydatabase"), - collections = mapOf(srcCollections to collectionConfig) - ) + ReplicatorConfiguration(URLEndpoint("ws://localhost:4984/mydatabase")) + .addCollections(srcCollections, collectionConfig) ) // Start the replicator diff --git a/docs/site/index.md b/docs/site/index.md index 401747f85..ab1736565 100644 --- a/docs/site/index.md +++ b/docs/site/index.md @@ -13,10 +13,10 @@ standalone client database, or paired with [Couchbase Server](https://www.couchb Gateway](https://www.couchbase.com/products/sync-gateway/) or [Capella App Services]( https://www.couchbase.com/products/capella/app-services/) for cloud to edge data synchronization. Features include: -* [SQL++](https://www.couchbase.com/products/n1ql/), key/value, and full-text search queries +* [SQL++](https://www.couchbase.com/products/n1ql/), key/value, full-text search, and vector search queries * Observable queries, documents, databases, and replicators * Binary document attachments (blobs) * Peer-to-peer and cloud-to-edge data sync Kotbase provides full Enterprise and Community Edition API support for Android and JVM, native iOS and macOS, and -experimental support for available APIs in native Linux and Windows. +experimental support for native Linux and Windows. diff --git a/docs/site/indexing.md b/docs/site/indexing.md index bc70bdb6a..9e91587ac 100644 --- a/docs/site/indexing.md +++ b/docs/site/indexing.md @@ -28,11 +28,11 @@ You can use SQL++ or `QueryBuilder` syntaxes to create an index. ```json { - "_id": "hotel123", - "type": "hotel", - "name": "The Michigander", - "overview": "Ideally situated for exploration of the Motor City and the wider state of Michigan. Tripadvisor rated the hotel ...", - "state": "Michigan" + "_id": "hotel123", + "type": "hotel", + "name": "The Michigander", + "overview": "Ideally situated for exploration of the Motor City and the wider state of Michigan. Tripadvisor rated the hotel ...", + "state": "Michigan" } ``` @@ -68,3 +68,188 @@ The code to create the index will look something like this: ) ) ``` + +## Partial Index + +Couchbase Lite 3.2.2 introduces support for Partial Index - Partial Value and Partial Full-Text Indexes. The Partial +Index can create a smaller index, potentially improving index and query performance. You can use Partial Index to +specify a `WHERE` clause in your index configuration. If a where clause is specified, the database will index a document +only when the where clause condition is met. + +Couchbase’s query optimizer uses [SQLite’s Partial Index rules about queries using Partial Indexes]( +https://www.sqlite.org/partialindex.html) to determine whether to use partial index in the query or not. + +!!! example "Example 4. Couchbase and SQLite’s Partial Index Rules" + + In general, Couchbase Lite follows the two rules, with a modification to the second rule. + + Below is a summary of the two rules where: + + * `X` - The expression in the `WHERE` clause of a given Partial Index. + * `W` - The expression in the `WHERE` clause of a given query. + + A query can use the Partial Index if the following two rules are satisfied: + + 1. If `W` is AND-connected terms, and `X` is OR-connected terms and if any terms of `W` appears as a term of `X`, + the partial index is allowed to be used. + 2. If a term in `X` is of the form `"z IS NOT MISSING"` and if a term in `W` is a comparison operator on `z` other + than `"IS"`, the partial index is allowed to be used. The operators include `=`, `==`, `<`, `>`, `<=`, `>=`, + `<>`, `IN`, `LIKE`, and `BETWEEN`. + + !!! important + + If `X` is in the form of `"z is NOT NULL"` or `"z is VALUED"`, the first rule must be satisfied. + + For example, let the partial index be `c IS NOT NULL` when creating a partial index on collection named `col1` in + the default scope. + + Then any query that uses operators `=`, `<`, `>`, `<=`, `>=`, `<>`, `IN`, `LIKE`, or `BETWEEN` on column `c` would + be usable with the partial index, because those comparison operators are only true if `c` is not `NULL`. + + The following query could use the partial index: + + ```sql + SELECT * FROM col1 WHERE b=456 AND c<>0; -- uses partial index + ``` + + Whereas the next query can not use the partial index: + + ```sql + SELECT * FROM col1 WHERE b=456; -- cannot use partial index + ``` + +### Partial Value Index + +Partial Value Index is a form of Partial Index in which a value is used in the `WHERE` clause and the query is selected +by the query optimizer. + +```kotlin +val config = ValueIndexConfiguration("city").apply { + where = "type = \"hotel\"" +} +collection.createIndex("HotelCityIndex", config) +``` + +### Partial Full-Text Index + +A key difference between a Partial Value Index and a Partial Full-Text Index is that a Partial Full-Text Index is not +selected by SQLite’s query optimizer. Instead, it’s explicitly selected by the SQL++ `match(indexName, terms)` function, +which runs a Full-Text Search query using the indexed properties. This means that a Partial Full-Text Index will always +be used when the `match()` function is invoked, regardless of other predicates in the WHERE clause. For details, see +[Full-Text Search](full-text-search.md). + +```kotlin +val config = FullTextIndexConfiguration("description").apply { + where = "type = \"hotel\"" +} +collection.createIndex("HotelDescIndex", config) +``` + +## Array Indexing + +Couchbase Lite 3.2.1 introduces functionality to optimize querying arrays. [Array UNNEST]( +n1ql-query-strings.md#array-unnest) to unpack arrays within a document to allow joins with the parent object, and array +indexes for indexing unnested array’s values to allow more efficient queries with `UNNEST`. + +### The Array Index + +An array index is a new type of the index for indexing nested array’s properties to allow querying with the `UNNEST` +more efficiently. + +Below is an example array index configuration: + +```kotlin +val config: IndexConfiguration = ArrayIndexConfiguration("contacts", "type") +``` + +### Array Index Syntax + +The syntax for array index configuration is shown below: + +|
Name
|
Is Optional?
| Description | +|:---------------------------------------|:----------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `path` | :material-close: | Path to the array to be indexed. Use `[]` to represent a property that is an array of each nested array level. For a single array or the last level array, the `[]` is optional. For instance `contacts[].phones` to specify an array of phones within each contact. | +| `expressions` | :material-check: | An optional array of strings where each string represents values within the array to be indexed. In N1QL/SQL++ syntax, these expressions are separated by commas. In JSON syntax, as supported by Couchbase Lite for C, the expressions are represented as a JSON array. If the array specified by the path contains scalar values, the expressions should be left unset or set to null. | + +### Using Array Indexes with UNNEST + +For the following examples, you can assume we are querying results from the following document, shown below: + +```json +{ + "Name": "Sam", + "contacts": [ + { + "type": "primary", + "address": { "street": "1 St", "city": "San Pedro", "state": "CA" }, + "phones": [ + { "type": "home", "number": "310-123-4567" }, + { "type": "mobile", "number": "310-123-6789" } + ] + }, + { + "type": "secondary", + "address": { "street": "5 St", "city": "Seattle", "state": "WA" }, + "phones": [ + { "type": "home", "number": "206-123-4567" }, + { "type": "mobile", "number": "206-123-6789" } + ] + } + ], + "likes": ["soccer", "travel"] +} +``` + +Using the document above you can perform queries on a single nested array like so: + +```sql +SELECT name, interest FROM _ UNNEST likes as interest WHERE interest = "travel" +``` + +The query above produces the following output from the document: + +```json +{ "name": "Sam", "like": "travel" } +``` + +You can also perform the same operation using array indexes like so: + +```kotlin +collection.createIndex("myindex", ArrayIndexConfiguration("likes")) +``` + +You can perform similar operations on nested arrays: + +```sql +SELECT name, contact.type, phone.number +FROM profiles +UNNEST contacts as contact +UNNEST contact.phones as phone +WHERE phone.type = "mobile" +``` + +The query produces the following output: + +```json +{ "name": "Sam", "type": "primary", "number": "310-123-6789" } +{ "name": "Sam", "type": "secondary", "number": "206-123-6789" } +``` + +The output demonstrates retrieval of both primary and secondary contact numbers listed as type `"mobile"`. + +Here’s an example of creating an array index on a nested array containing dictionary values: + +```kotlin +collection.createIndex( + "myindex", + ArrayIndexConfiguration("contacts[].phones", "type") +) +``` + +The above snippet creates an array index to allow you to iterate through `contacts[].phones[].type` in the document, +namely `"home"` and `"mobile"`. + +!!! important + + Array literals are not supported in CBL {{ version_short }}. Attempting to create a query with array literals will + return an error. diff --git a/docs/site/installation.md b/docs/site/installation.md index efec84962..492e90455 100644 --- a/docs/site/installation.md +++ b/docs/site/installation.md @@ -62,3 +62,131 @@ Targeting JVM running on Linux or native Linux, both require a specific version an error such as `libLiteCore.so: libicuuc.so.71: cannot open shared object file: No such file or directory` indicating the expected version.) If the required version isn't available from your distribution's package manager, you can download it from [GitHub](https://github.com/unicode-org/icu/releases). + +## Vector Search + +!!! important "This is an [Enterprise Edition](https://www.couchbase.com/products/editions/mobile/) feature." + +Enterprise users can also download the Couchbase Lite [Vector Search](vector-search.md) extension library. + +!!! note + + To use Vector Search, you must have Couchbase Lite installed and add the Vector Search extension to your Couchbase + Lite application. Vector Search is available only for 64-bit architectures and Intel processors that support the + Advanced Vector Extensions 2 (AVX2) instruction set. To verify whether your device supports the AVX2 instructions + set, [follow these instructions]( + https://www.intel.com/content/www/us/en/support/articles/000090473/processors/intel-core-processors.html). + +### Install Extension Libraries + +Install the [Vector Search](vector-search.md) library for each of the platforms your KMP project targets. + +#### Android + +```kotlin title="build.gradle.kts" +kotlin { + sourceSets { + ... + androidMain.dependencies { + // All standard 64-bit ARM architectures + implementation("com.couchbase.lite:couchbase-lite-android-vector-search-arm64:{{ version_vector_search }}") + // For x86_64 architectures + implementation("com.couchbase.lite:couchbase-lite-android-vector-search-x86_64:{{ version_vector_search }}") + } + } +} +``` + +#### Java + +```kotlin title="build.gradle.kts" +kotlin { + sourceSets { + ... + jvmMain.dependencies { + implementation("com.couchbase.lite:couchbase-lite-java-vector-search:{{ version_vector_search }}") + } + } +} +``` + +#### iOS + macOS + +=== "Direct Download" + + 1. Download the binaries from here — [binary download link]( + https://packages.couchbase.com/releases/couchbase-lite-vector-search/{{ version_vector_search }}/couchbase-lite-vector-search_xcframework_{{ version_vector_search }}.zip). + 2. Unpack the download zip file into your Xcode project location. + 3. Select your target settings in Xcode and drag **CouchbaseLiteVectorSearch.xcframework** from your Finder to the + **Frameworks, Libraries, and Embedded Content** section. + 4. Start using Couchbase Lite Vector Search with Kotbase in your project. + +=== "CocoaPods" + + The [Kotlin CocoaPods Gradle plugin]( + https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-cocoapods-overview.html) can be used to + generate a [Podspec](https://guides.cocoapods.org/syntax/podspec.html) for your project that includes the + `CouchbaseLiteVectorSearch` dependency. Use `linkOnly = true` to link the dependency without generating Kotlin + Objective-C interop: + + ```kotlin title="build.gradle.kts" + plugins { + kotlin("multiplatform") + kotlin("native.cocoapods") + } + + kotlin { + cocoapods { + ... + pod("CouchbaseLiteVectorSearch", version = "{{ version_vector_search }}", linkOnly = true) + } + } + ``` + +=== "Swift Package Manager" + + !!! note + + Using Swift Package Manager to install `CouchbaseLiteVectorSearch` requires Xcode 12+. + + You can add `CouchbaseLiteVectorSearch` to your app using Swift Package Manger (SPM). + + 1. Open the project to which you are going to `add CouchbaseLiteVectorSearch` + 2. Open the Project Editor to add a dependency. + 1. In _Project Navigator_: + **Select** your XCode project file (for example, `HostApp` in the example) + Xcode opens the _Project Editor_ pane + 2. In the _Project Editor_ pane: + **Select Project > Swift Packages** and **[+]** to add the dependency + Xcode opens the _Choose Package Repository_ dialog + 3. In the _Choose Package Repository_ dialog: + **Enter** the appropriate Couchbase Lite URL, **[Next]** to continue + For Vector Search: https://github.com/couchbase/couchbase-lite-vector-search-spm.git + 4. **Enter** the required _Version_ ({{ version_vector_search }}) and **[Next]** to continue + 5. **[Finish]** to close the _Choose Package Repository_ dialog + + Xcode displays the name, version and URL of the added `CouchbaseLiteVectorSearch` package. + + +#### Linux + Mingw + +Before you can use Vector Search, you must [download and install the Vector Search library]( +https://docs.couchbase.com/couchbase-lite/3.2/c/gs-install.html#vs-release-1-0-0) to the location in your project where +the library can be accessed and loaded at run time. The Vector Search extension for the C platform ships with supported +prebuilt libraries containing the required dependencies. + +You need to set the `CBLITE_VECTOR_SEARCH_LIB_PATH` environment variable to the extension location instead of installing +the libraries yourself. If this environment variable is not set, then Kotbase will attempt to find the library in the +current directory. + +### Enable Extension + +Enable the vector search extension using the following snippet: + +```kotlin +Extension.enableVectorSearch() +``` + +!!! important + + You must enable the extension before you open your database. diff --git a/docs/site/integrate-custom-listener.md b/docs/site/integrate-custom-listener.md index 5e0cb8b90..094fd2c21 100644 --- a/docs/site/integrate-custom-listener.md +++ b/docs/site/integrate-custom-listener.md @@ -30,7 +30,7 @@ In addition to initializing the database, the Passive Peer must initialize the [ ```kotlin val listener = MessageEndpointListener( - MessageEndpointListenerConfigurationFactory.newConfig(collections, ProtocolType.MESSAGE_STREAM) + MessageEndpointListenerConfiguration(collections, ProtocolType.MESSAGE_STREAM) ) ``` @@ -86,10 +86,8 @@ Then, a [`Replicator`](/api/couchbase-lite-ee/kotbase/-replicator/) is instantia ```kotlin // Create the replicator object. val repl = Replicator( - ReplicatorConfigurationFactory.newConfig( - collections = mapOf(collections to null), - target = messageEndpoint - ) + ReplicatorConfiguration(messageEndpoint) + .addCollections(collections) ) // Start the replication. diff --git a/docs/site/intra-device-sync.md b/docs/site/intra-device-sync.md index c445dfebb..e7b76218c 100644 --- a/docs/site/intra-device-sync.md +++ b/docs/site/intra-device-sync.md @@ -12,11 +12,9 @@ damaged and its data is moved to a different device. ```kotlin val repl = Replicator( - ReplicatorConfigurationFactory.newConfig( - target = DatabaseEndpoint(targetDb), - collections = mapOf(srcCollections to null), - type = ReplicatorType.PUSH - ) + ReplicatorConfiguration(DatabaseEndpoint(targetDb)) + .setType(ReplicatorType.PUSH) + .addCollection(srcCollection) ) // Start the replicator diff --git a/docs/site/kotlin-extensions.md b/docs/site/kotlin-extensions.md index 96021f74e..da7732c81 100644 --- a/docs/site/kotlin-extensions.md +++ b/docs/site/kotlin-extensions.md @@ -2,190 +2,27 @@ _Couchbase Lite — Kotlin support_ ## Introduction -In addition to implementing the full Couchbase Lite Java SDK API, Kotbase also provides the additional APIs available in -the [Couchbase Lite Android KTX SDK](https://docs.couchbase.com/couchbase-lite/current/android/kotlin.html), which +In addition to implementing the full Couchbase Lite Java SDK API, Kotbase also provides some additional APIs available +in the [Couchbase Lite Android KTX SDK](https://docs.couchbase.com/couchbase-lite/current/android/kotlin.html), which includes a number of Kotlin-specific extensions. This includes: -* [Configuration factories](#configuration-factories) for the configuration of important Couchbase Lite objects such as - _Databases_, _Replicators_, and _Listeners_. * [Change Flows](#change-flows) that monitor key Couchbase Lite objects for change using Kotlin features such as, [coroutines]( https://kotlinlang.org/docs/coroutines-guide.html) and [Flows](https://kotlinlang.org/docs/flow.html). +!!! note + + The configuration factory APIs from the Couchbase Lite Android KTX SDK have been deprecated in Kotbase in favor of + using constructors directly, which support Kotlin named arguments themselves, or properties can be accessed using + the `apply` scope function. These APIs will be removed in a future release. + Additionally, while not available in the Java SDK, as Java doesn't support operator overloading, Kotbase adds support for [`Fragment` subscript APIs](#fragment-subscripts), similar to Couchbase Lite [Swift]( https://docs.couchbase.com/mobile/3.1.10/couchbase-lite-swift/Classes/Fragment.html), [Objective-C]( https://docs.couchbase.com/mobile/3.1.10/couchbase-lite-objc/Protocols/CBLFragment.html), and [.NET]( https://docs.couchbase.com/mobile/3.1.10/couchbase-lite-net/api/Couchbase.Lite.IFragment.html). -## Configuration Factories - -Couchbase Lite provides a set of [configuration factories](/api/couchbase-lite-ee/kotbase/new-config.html). These allow -use of named parameters to specify property settings. - -This makes it simple to create variant configurations, by simply overriding named parameters: - -!!! example "Example of overriding configuration" - - ```kotlin - val listener8080 = URLEndpointListenerConfigurationFactory.newConfig( - networkInterface = "en0", - port = 8080 - ) - val listener8081 = listener8080.newConfig(port = 8081) - ``` - -### Database - -Use [`DatabaseConfigurationFactory`](/api/couchbase-lite-ee/kotbase/-database-configuration-factory.html) to create a -[`DatabaseConfiguration`](/api/couchbase-lite-ee/kotbase/-database-configuration/) object, overriding the receiver’s -values with the passed parameters. - -=== "In Use" - - ```kotlin - val database = Database( - "getting-started", - DatabaseConfigurationFactory.newConfig() - ) - ``` - -=== "Definition" - - ```kotlin - val DatabaseConfigurationFactory: DatabaseConfiguration? = null - - fun DatabaseConfiguration?.newConfig( - databasePath: String? = null, - encryptionKey: EncryptionKey? = null - ): DatabaseConfiguration - ``` - -### Replication - -Use [`ReplicatorConfigurationFactory`](/api/couchbase-lite-ee/kotbase/-replicator-configuration-factory.html) to create -a [`ReplicatorConfiguration`](/api/couchbase-lite-ee/kotbase/-replicator-configuration/) object, overriding the -receiver’s values with the passed parameters. - -=== "In Use" - - ```kotlin - val replicator = Replicator( - ReplicatorConfigurationFactory.newConfig( - collections = mapOf(db.collections to null), - target = URLEndpoint("ws://localhost:4984/getting-started-db"), - type = ReplicatorType.PUSH_AND_PULL, - authenticator = BasicAuthenticator("sync-gateway", "password".toCharArray()) - ) - ) - ``` - -=== "Definition" - - ```kotlin - val ReplicatorConfigurationFactory: ReplicatorConfiguration? = null - - public fun ReplicatorConfiguration?.newConfig( - target: Endpoint? = null, - collections: Map, CollectionConfiguration?>? = null, - type: ReplicatorType? = null, - continuous: Boolean? = null, - authenticator: Authenticator? = null, - headers: Map? = null, - pinnedServerCertificate: ByteArray? = null, - maxAttempts: Int? = null, - maxAttemptWaitTime: Int? = null, - heartbeat: Int? = null, - enableAutoPurge: Boolean? = null, - acceptOnlySelfSignedServerCertificate: Boolean? = null, - acceptParentDomainCookies: Boolean? = null - ): ReplicatorConfiguration - ``` - -### Full Text Search - -Use [`FullTextIndexConfigurationFactory`](/api/couchbase-lite-ee/kotbase/-full-text-index-configuration-factory.html) to -create a [`FullTextIndexConfiguration`](/api/couchbase-lite-ee/kotbase/-full-text-index-configuration/) object, -overriding the receiver’s values with the passed parameters. - -=== "In Use" - - ```kotlin - collection.createIndex( - "overviewFTSIndex", - FullTextIndexConfigurationFactory.newConfig("overview") - ) - ``` - -=== "Definition" - - ```kotlin - val FullTextIndexConfigurationFactory: FullTextIndexConfiguration? = null - - fun FullTextIndexConfiguration?.newConfig( - vararg expressions: String = emptyArray(), - language: String? = null, - ignoreAccents: Boolean? = null - ): FullTextIndexConfiguration - ``` - -### Indexing - -Use [`ValueIndexConfigurationFactory`](/api/couchbase-lite-ee/kotbase/-value-index-configuration-factory.html) to create -a [`ValueIndexConfiguration`](/api/couchbase-lite-ee/kotbase/-value-index-configuration/) object, overriding the -receiver’s values with the passed parameters. - -=== "In Use" - - ```kotlin - collection.createIndex( - "TypeNameIndex", - ValueIndexConfigurationFactory.newConfig("type", "name") - ) - ``` - -=== "Definition" - - ```kotlin - val ValueIndexConfigurationFactory: ValueIndexConfiguration? = null - - fun ValueIndexConfiguration?.newConfig(vararg expressions: String = emptyArray()): ValueIndexConfiguration - ``` - -### Logs - -Use [`LogFileConfigurationFactory`](/api/couchbase-lite-ee/kotbase/-log-file-configuration-factory.html) to create a -[`LogFileConfiguration`](/api/couchbase-lite-ee/kotbase/-log-file-configuration/) object, overriding the receiver’s -values with the passed parameters. - -=== "In Use" - - ```kotlin - Database.log.file.apply { - config = LogFileConfigurationFactory.newConfig( - directory = "path/to/temp/logs", - maxSize = 10240, - maxRotateCount = 5, - usePlainText = false - ) - level = LogLevel.INFO - } - ``` - -=== "Definition" - - ```kotlin - val LogFileConfigurationFactory: LogFileConfiguration? = null - - fun LogFileConfiguration?.newConfig( - directory: String? = null, - maxSize: Long? = null, - maxRotateCount: Int? = null, - usePlainText: Boolean? = null - ): LogFileConfiguration - ``` - ## Change Flows These wrappers use [Flows](https://kotlinlang.org/docs/flow.html) to monitor for changes. diff --git a/docs/site/using-logs.md b/docs/site/legacy-logging-api.md similarity index 88% rename from docs/site/using-logs.md rename to docs/site/legacy-logging-api.md index c35373f7b..855235b99 100644 --- a/docs/site/using-logs.md +++ b/docs/site/legacy-logging-api.md @@ -17,7 +17,7 @@ Log output is split into the following streams: * [Console based logging](#console-based-logging)

You can independently configure and control console logs, which provides a convenient method of accessing diagnostic information during debugging scenarios. With console logging, you can fine-tune diagnostic output to suit specific - debug scenarios, without interfering with any logging required by Couchbase Support for the investigation of issues. + debug scenarios and capture them for Couchbase Support for the investigation of issues. * [File based logging](#file-based-logging)

Here logs are written to separate log files, filtered by log level, with each log level supporting individual retention policies. @@ -86,7 +86,7 @@ With file based logging you can also use the [`LogFileConfiguration`]( * Path to the directory to store the log files * Log file format - The default is binary. You can override that where necessary and output a plain text log. + The default is _binary_. You can override that where necessary and output a plain text log. * Maximum number of rotated log files to keep * Maximum size of the log file (bytes). Once this limit is exceeded a new log file is started. @@ -94,12 +94,11 @@ With file based logging you can also use the [`LogFileConfiguration`]( ```kotlin Database.log.file.apply { - config = LogFileConfigurationFactory.newConfig( - directory = "temp/cbl-logs", - maxSize = 10240, - maxRotateCount = 5, - usePlainText = false - ) + config = LogFileConfiguration(directory = "temp/cbl-logs").apply { + maxSize = 10240 + maxRotateCount = 5 + usesPlaintext = false + } level = LogLevel.INFO } ``` @@ -163,7 +162,13 @@ You can use the **cbl-log** tool to decode binary log files — see [Example 5]( Download the **cbl-log** tool using `wget`. ```title="console" - wget https://packages.couchbase.com/releases/couchbase-lite-log/3.1.1/couchbase-lite-log-3.1.1-macos.zip + wget https://packages.couchbase.com/releases/couchbase-lite-log/3.0.0/couchbase-lite-log-3.0.0-macos.zip + ``` + + Extract the downloaded zip file. + + ```title="console" + unzip couchbase-lite-log-3.0.0-macos.zip ``` Navigate to the **bin** directory and run the `cbl-log` executable. @@ -177,7 +182,13 @@ You can use the **cbl-log** tool to decode binary log files — see [Example 5]( Download the **cbl-log** tool using `wget`. ```title="console" - wget https://packages.couchbase.com/releases/couchbase-lite-log/3.1.1/couchbase-lite-log-3.1.1-centos.zip + wget https://packages.couchbase.com/releases/couchbase-lite-log/3.0.0/couchbase-lite-log-3.0.0-centos.zip + ``` + + Extract the downloaded zip file. + + ```title="console" + unzip couchbase-lite-log-3.0.0-centos.zip ``` Navigate to the **bin** directory and run the `cbl-log` executable. @@ -191,10 +202,16 @@ You can use the **cbl-log** tool to decode binary log files — see [Example 5]( Download the **cbl-log** tool using PowerShell. ```title="PowerShell" - Invoke-WebRequest https://packages.couchbase.com/releases/couchbase-lite-log/3.1.1/couchbase-lite-log-3.1.1-windows.zip -OutFile couchbase-lite-log-3.1.1-windows.zip + Invoke-WebRequest https://packages.couchbase.com/releases/couchbase-lite-log/3.0.0/couchbase-lite-log-3.0.0-windows.zip -OutFile couchbase-lite-log-3.0.0-windows.zip ``` - Navigate to the **bin** directory and run the `cbl-log` executable. + Extract the downloaded zip file. + + ```title="PowerShell" + Expand-Archive -Path couchbase-lite-log-3.0.0-windows.zip -DestinationPath . + ``` + + Run the cbl-log executable. ```title="PowerShell" .\cbl-log.exe logcat LOGFILE diff --git a/docs/site/n1ql-query-strings.md b/docs/site/n1ql-query-strings.md index bab5c8c03..96c31e110 100644 --- a/docs/site/n1ql-query-strings.md +++ b/docs/site/n1ql-query-strings.md @@ -106,7 +106,7 @@ When using the `SELECT *` option the column name (key) of the SQL++ string is on This behavior is inline with that of Couchbase Server SQL++ — see example in [Table 1](#table-1). -**Table 1. Example Column Names for SELECT \*** +**Table 1. Example Column Names for SELECT \*** | Query | Column Name | |:----------------------------|:------------| @@ -234,6 +234,104 @@ SELECT * FROM route r JOIN airline a ON r.airlineid = meta(a).id WHERE a.country SELECT * FROM travel-sample r JOIN travel-sample a ON r.airlineid = a.meta.id WHERE a.country = "France" ``` +## Array UNNEST + +### Purpose + +You can use `UNNEST` in queries to unpack arrays within a document into individual rows. This functionality makes it +possible to join them with its parent object in the query. + +`UNNEST` is used within the `FROM` clause and can be chained to perform multi-level `UNNEST`. + +You can also use a new type of index, the [Array Index](indexing.md#array-indexing), to allow querying with `UNNEST` +more efficiently. + +!!! note + + Couchbase Lite currently supports inner `UNNEST` only. + +### Syntax + +The syntax for `UNNEST` is shown below: + +```sql +unnestClause = UNNEST expr ( ‘AS’? alias)? +``` + +!!! warning "Caution" + + `"unnest"` will be defined as a new keyword in the SQL++ syntax. You cannot use the term as an identifier for a property name or data source unless you escape it using backticks. + +### Examples + +For examples of using Array Indexes in conjunction with `UNNEST`, see [Array Index](indexing.md#array-indexing). + +We are also accessing the current database using the shorthand notation `_` — see the [FROM](#from) clause for more on +data source selection and [Query Parameters](#query-parameters) for more on parameterized queries. + +The following examples will use the example JSON document below to query results from: + +```json +{ + "Name": "Sam", + "contacts": [ + { + "type": "primary", + "address": { "street": "1 St", "city": "San Pedro", "state": "CA" }, + "phones": [ + { "type": "home", "number": "310-123-4567" }, + { "type": "mobile", "number": "310-123-6789" } + ] + }, + { + "type": "secondary", + "address": { "street": "5 St", "city": "Seattle", "state": "WA" }, + "phones": [ + { "type": "home", "number": "206-123-4567" }, + { "type": "mobile", "number": "206-123-6789" } + ] + } + ], + "likes": ["soccer", "travel"] +} +``` + +Using the document above we can perform queries on a single nested array like so: + +```sql +SELECT name, interest FROM _ UNNEST likes as interest WHERE interest = "travel" +``` + +The query above will produce the following output from the document: + +```json +{ "name": "Sam", "like": "travel" } +``` + +You can perform similar operations on nested arrays: + +```sql +SELECT name, contact.type, phone.number +FROM profiles +UNNEST contacts as contact +UNNEST contact.phones as phone +WHERE phone.type = "mobile" +``` + +The query above will then produce the following output: + +```json +{ "name": "Sam", "type": "primary", "number": "310-123-6789" } +{ "name": "Sam", "type": "secondary", "number": "206-123-6789" } +``` + +The output demonstrates retrieval of both primary and secondary contact numbers listed as type `"mobile"`. + +!!! important + + Array literals are not supported in CBL {{ version_short }}. Attempting to create a query with array literals will + return an error. + ## WHERE statement ### Purpose @@ -709,7 +807,7 @@ $IDENTIFIER !!! example "Example 14. Using a Parameter" ```kotlin - val query = database.createQuery("SELECT name WHERE department = \$department") + val query = database.createQuery($$"SELECT name WHERE department = $department") query.parameters = Parameters().setValue("department", "E001") val result = query.execute() ``` @@ -1088,12 +1186,18 @@ parenExprs = '(' ( expression (_ ',' _ expression )* )? ')' **Table 13. Date and Time Functions** -|
Function
| Description | -|:--------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------| -| `STR_TO_MILLIS(expr)` | Returns the number of milliseconds since the unix epoch of the given ISO 8601 date input string. | -| `STR_TO_UTC(expr)` | Returns the ISO 8601 UTC date time string of the given ISO 8601 date input string. | -| `MILLIS_TO_STR(expr)` | Returns a ISO 8601 date time string in device local timezone of the given number of milliseconds since the unix epoch expression. | -| `MILLIS_TO_UTC(expr)` | Returns the UTC ISO 8601 date time string of the given number of milliseconds since the unix epoch expression. | +|
Function
| Arguments | Return Value | +|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `STR_TO_MILLIS(date1)`

Coverts a date string to Epoch/UNIX milliseconds. |
  • `date1` - A valid date string.
| Returns an integer containing the converted date string into Epoch/UNIX milliseconds. | +| `STR_TO_UTC(date1)`

Converts a date string into the equivalent date in UTC. |
  • `date1` - A valid date string.
| Returns a date string representing the date string converted to UTC.

The output date format follows the date format of the input date. Returns `null` if an invalid date format is provided. | +| `STR_TO_TZ(date1, tz)`

Converts a date string to it’s equivalent in the specified timezone. |
  • `date1` - A valid date string. This is converted to UTC.
  • `tz` - An integer that represents minutes offset from UTC. For example, `UTC-5` would be represented as `-300`.
| Returns a date string representing the date string converted to the specified timezone.

Returns `null` if an invalid date format is provided. | +| `MILLIS_TO_STR(date1)`

Converts an Epoch/UNIX timestamp into the specified date string format. |
  • `date1` - An integer representing an Epoch/UNIX timestamp in millseconds. | Returns a date string representing the local date.

    Returns `null` if an invalid timestamp is provided. | +| `MILLIS_TO_UTC(date1)`

    Converts an Epoch/UNIX timestamp into a local time date string. |
    • `date1` - An integer representing an Epoch/UNIX timestamp in millseconds.
    | Returns a date string representing the date in UTC.

    Returns `null` if an invalid timestamp is provided. | +| `MILLIS_TO_TZ(date1,tz, [fmt])`

    Converts an Epoch/UNIX timestamp into the specified time zone in the specified date string format. |
    • `date1` - An integer representing an Epoch/UNIX timestamp in milliseconds.
    • `tz` - An integer that represents minutes offset from UTC. For example, `UTC-5` would be represented as `-300`.
    • `fmt` - An optional string parameter representing a date format to output the result as.
    | Returns a date string representing the date in the specified timezone in the specified format.

    If `fmt` is not specified, the output default to the combined full date and time. | +| `DATE_DIFF_STR(date1, date2, part)`

    Finds the elapsed time between two date strings. This is measured from `date2` to `date1`. |
    • `date1` - A valid date string. This is converted to UTC.
    • `date2` - A valid date string. This is converted to UTC.
    • `part` - A string representing the date component units to return.
    | Returns an integer representing the elapsed time measured from `date2` to `date1` (in units based on the specified `part`) between both dates.

    The value is positive if `date1` is greater than `date2`, negative otherwise.

    Returns `null` if any of the parameters are invalid. | +| `DATE_DIFF_MILLIS(date1, date2, part)`

    Finds the elapsed time between two Epoch/UNIX timestamps. |
    • `date1` - An integer representing an Epoch/UNIX timestamp in milliseconds.
    • `date2` - An integer representing an Epoch/UNIX timestamp in milliseconds.
    • `part` - A string representing the date component units to return.
    | Returns an integer representing the elapsed time measured from `date2` to `date1` (in units based on the specified `part`) between both dates.

    The value is positive if `date1` is greater than `date2`, negative otherwise.

    Returns `null` if any of the parameters are invalid. | +| `DATE_ADD_STR(date1, n, part)`

    Performs date arithmetic on a date string. For example `DATE_ADD_STR("2024-03-20T15:43:01+0000", 3, "day")` adds 3 days to the provided date. |
    • `date1` - A valid date string. This is converted to UTC.
    • `n` - An integer or expression that evaluates to an integer. A positive value will increment the date component whereas a negative value will decrement the date component.
    • `part` - A string representing the component of the date to increment.
    | Returns an integer representing the calculation result as an Epoch/UNIX timestamp in milliseconds.

    Returns `null` if any of the parameters are invalid. | +| `DATE_ADD_MILLIS(date1, n, part)`

    Performs date arithmetic on a particular component of an Epoch/UNIX timestamp value. For example `DATE_ADD_STR(1710946158819, 3, 'day')` adds 3 days to the provided date. |
    • `date1` - An integer representing an Epoch/UNIX timestamp in milliseconds.
    • `n` - An integer or expression that evaluates to an integer. A positive value will increment the date component whereas a negative value will decrement the date component.
    • `part` - A string representing the component of the date to increment.
    | Returns an integer representing the calculation result as an Epoch/UNIX timestamp in milliseconds.

    Returns `null` if any of the parameters are invalid. | ### Full Text Search Functions @@ -1227,7 +1331,7 @@ To specify substitutable parameters within your query string prefix the name wit ```kotlin val query = database.createQuery( - "SELECT META().id AS id FROM _ WHERE type = \$type" + $$"SELECT META().id AS id FROM _ WHERE type = $type" ) query.parameters = Parameters().setString("type", "hotel") diff --git a/docs/site/new-logging-api.md b/docs/site/new-logging-api.md new file mode 100644 index 000000000..976efa57b --- /dev/null +++ b/docs/site/new-logging-api.md @@ -0,0 +1,155 @@ +_Couchbase Lite 3.2.2 introduces a new Logging API._ + +## Upgrading to the New CBL Logging API + +!!! warning + + Use of the deprecated and new Logging API at the same time is not supported. + +You can find information about the new Couchbase Lite Logging API introduced in Couchbase Lite 3.2.2. + +For information about the now deprecated earlier version of the Logging API, see [Legacy Logging API]( +legacy-logging-api.md). + +### LogSinks + +Couchbase Lite 3.2.2 introduces a new Logging API. The new Logging API has the following benefits: + +* Log sinks are now thread safe, removing risk of inconsistent states during initialization. +* Simplified API and reduced implementation complexity. + +The new logging API retains many of the core concepts of the previous API. + +The first thing to note is that the three destinations for logs have been renamed as `LogSinks`, in keeping with common +source/sink terminology. + +The `FileLogSink`, the `ConsoleLogSink` and the `CustomLogSink` are all to be installed in the `LogSinks` object. + +Only `ConsoleLogSink` is enabled for all logging domains at the warning level by default. To enable a specific type of +log sink, create a log sink object of that type, set its minimum log level and domains, and assign it to `LogSinks`. To +disable a log sink, set it to null or use a log sink with `LogLevel.NONE`. + +Couchbase still logs its messages in a handful of named domains and at common log levels: `LogLevel.DEBUG` the most +verbose, and `LogLevel.ERROR` only for serious failures. + +The biggest difference between the new and the old API is that `LogSinks` are immutable: you set the level and domain at +which they log in their constructors. For example, you can only change the level at which the `ConsoleLogSink` forwards +messages to the console by installing a new one created for the new log level. + +Log output is split into the following streams: + +* [Logging to the Couchbase File Log](#logging-to-the-couchbase-file-log) + Each log level writes to a separate file and there is a single retention policy for all files. +* [Logging to the Console](#logging-to-the-console) + You can independently configure and control console logs, which provides a convenient method of accessing diagnostic + information during debugging scenarios. + + With console logging, you can fine-tune diagnostic output to suit specific debug scenarios, without interfering with + any logging required by Couchbase Support for the investigation of issues. +* [Using a Custom Logger](#using-a-custom-logger) + For greater flexibility you can implement a custom logging class. + +### Logging to the Console + +The changes necessary convert the installation of a console logger from the old to the new API are minimal. Create an +instance of `ConsoleLogSink` initialized with the desired log level and domains and install it. + +**Old API** + +```kotlin +Database.log.console.domains = LogDomain.ALL_DOMAINS +Database.log.console.level = LogLevel.WARNING +``` + +**New API** + +```kotlin +LogSinks.console = ConsoleLogSink(LogLevel.WARNING) +``` + +### Logging to the Couchbase File Log + +The changes necessary to convert the installation of a file logger are also similar. Instead of configuring a +`FileLogger` using a `LogFileConfiguration`, create a new `FileLogSink` with the desired properties and install it. + +!!! note + + `setRotateCount` from the old API is slightly different from `setMaxKeptFiles`. `setMaxKeptFiles` is the maximum + number of log files that will exist at any time and is the count of rotated files (`setRotateCount`) plus one. + +**Old API** + +```kotlin +Database.log.file.apply { + config = LogFileConfiguration(directory = "path/to/temp/logs").apply { + maxSize = 10240 + maxRotateCount = 5 + usesPlaintext = false + } + level = LogLevel.INFO +} +``` + +**New API** + +```kotlin +LogSinks.file = FileLogSink( + directory = "path/to/temp/logs", + maxKeptFiles = 12, + isPlainText = true +) +``` + +### Using a Custom Logger + +Installing a custom log sink with the new API is also streamlined: create an instance of your custom sink, and to +install it use `LogSinks.custom`. + +As with the other log sinks, you will have to specify the level and domain at which Couchbase logs are forwarded to your +custom sink at its creation. + +Your custom log sink code will have to change as well. A custom log sink implements the `LogSink` functional interface +and is assigned to a `CustomLogSink` instance during creation. + +A second important change is that your logger will receive only logs at the level and domain for which it is +initialized. There is no need to record or filter the logs forwarded to the `writeLog` method which replaces the `log` +method from the old API. + +Related to this last point, the Couchbase `Logger`s, now `LogSink`s are meant to support logging by the Couchbase Lite +platform. They were never meant as a general framework for logging. + +!!! important + + With the new API, customer code can no longer log, directly, to any of the Couchbase log sinks. The Console and File + log sinks cannot be subclassed and do not publish methods that allow writing logs. If you need to log to the console + for example, you’ll have to create your own way of doing so. + +**Old API Implementing The Custom Logger Interface** + +```kotlin +class LogTestLogger(override val level: LogLevel) : Logger { + override fun log(level: LogLevel, domain: LogDomain, message: String) { + // this method will never be called if param level < this.level + // handle the message, for example piping it to a third party framework + } +} +``` + +**Old API Enable Custom Logger** + +```kotlin +// this custom logger will not log an event with a log level < WARNING +Database.log.custom = LogTestLogger(LogLevel.WARNING) +``` + +**New API** + +```kotlin +LogSinks.custom = CustomLogSink( + LogLevel.WARNING, + LogDomain.NETWORK, LogDomain.REPLICATOR +) { level, domain, message -> + // will be called only with messages from the NETWORK and REPLICATOR + // domains with a log level of WARNING or higher. +} +``` diff --git a/docs/site/passive-peer.md b/docs/site/passive-peer.md index d56a88a42..41e996592 100644 --- a/docs/site/passive-peer.md +++ b/docs/site/passive-peer.md @@ -50,15 +50,14 @@ Subsequent sections provide additional details and examples for the main configu ## Configuration Summary -You should configure and initialize a listener for each Couchbase Lite database instance you want to sync. There is no -limit on the number of listeners you may configure — [Example 1](#example-1) shows a simple initialization and -configuration process. +You should configure and initialize the Listener with a list of collections to sync. There is no limit on the number of +Listeners you may configure — [Example 1](#example-1) shows a simple initialization and configuration process. !!! example "Example 1. Listener configuration and initialization" ```kotlin val listener = URLEndpointListener( - URLEndpointListenerConfigurationFactory.newConfig( + URLEndpointListenerConfiguration( collections = collections, port = 55990, networkInterface = "wlan0", @@ -83,7 +82,7 @@ configuration process. listener.start() ``` -1. Identify the collections from the local database to be used — see [Initialize the Listener +1. Identify the local database and the collections to be used — see [Initialize the Listener Configuration](#initialize-the-listener-configuration) 2. Optionally, choose a port to use. By default, the system will automatically assign a port — to override this, see [Set Port and Network Interface](#set-port-and-network-interface) @@ -116,23 +115,17 @@ the Passive Peer has authenticated and accepted an Active Peer’s invitation. ## Initialize the Listener Configuration -Initialize the listener configuration with the collections to sync from the local database — see [Example -2](#example-2). All other configuration values take their default setting. +Initialize the Listener configuration with a list of collections from the local database — see [Example 2](#example-2). +All other configuration values take their default setting. -Each listener instance serves one Couchbase Lite database. Couchbase sets no hard limit on the number of listeners you -can initialize. - -!!! example "Example 2. Specify Local Database" +!!! example "Example 2. Specify Local Collections" ```kotlin collections = collections, ``` -Set the local database using the [`URLEndpointListenerConfiguration`]( -/api/couchbase-lite-ee/kotbase/-u-r-l-endpoint-listener-configuration/)'s constructor -[`URLEndpointListenerConfiguration(Database)`]( -/api/couchbase-lite-ee/kotbase/-u-r-l-endpoint-listener-configuration/-u-r-l-endpoint-listener-configuration.html). -The database must be opened before the listener is started. +Set the list of local collections using the [`URLEndpointListenerConfiguration`]( +/api/couchbase-lite-ee/kotbase/-u-r-l-endpoint-listener-configuration/). ## Set Port and Network Interface @@ -238,7 +231,7 @@ re-generate it. !!! note Typically, you will configure the listener’s TLS Identity once during the initial launch and re-use it (from secure - storage on any subsequent starts. + storage) on any subsequent starts. Here are some example code snippets showing: @@ -385,8 +378,8 @@ Valid options are: * [`ListenerCertificateAuthenticator`](/api/couchbase-lite-ee/kotbase/-listener-certificate-authenticator/) — which authenticates the client using a client supplied chain of one or more certificates. You should initialize the authenticator using one of the following constructors: - * A list of one or more root certificates — the client supplied certificate must end at a certificate in this list - if it is to be authenticated + * A root certificate, or a list of intermediate certificates and a root certificate — the client supplied + certificate must end at a certificate in this list if it is to be authenticated. * A block of code that assumes total responsibility for authentication — it must return a boolean response (`true` for an authenticated client, or `false` for a failed authentication). @@ -439,7 +432,7 @@ There are two ways to authenticate a client: // accept only clients signed by the corp cert val listener = URLEndpointListener( - URLEndpointListenerConfigurationFactory.newConfig( + URLEndpointListenerConfiguration( // get the identity collections = collections, identity = validId, @@ -512,7 +505,7 @@ running — see [Example 13](#example-13). ```kotlin // Initialize the listener val listener = URLEndpointListener( - URLEndpointListenerConfigurationFactory.newConfig( + URLEndpointListenerConfiguration( collections = collections, port = 55990, networkInterface = "wlan0", diff --git a/docs/site/peer-to-peer-sync.md b/docs/site/peer-to-peer-sync.md index e9c495ea8..7b7ed4e80 100644 --- a/docs/site/peer-to-peer-sync.md +++ b/docs/site/peer-to-peer-sync.md @@ -55,14 +55,14 @@ https://docs.couchbase.com/tutorials/cbl-p2p-sync-websockets/swift/cbl-p2p-sync- Couchbase Lite’s Peer-to-Peer synchronization solution provides support for cross-platform synchronization, for example, between Android and iOS devices. -Each listener instance serves one Couchbase Lite database. However, there is no hard limit on the number of listener -instances you can associate with a database. +Each listener instance serves a single Couchbase Lite database, enabling synchronization for documents within specified +collections of that database. -Having a listener on a database still allows you to open replications to the other clients. For example, a listener can -actively begin replicating to other listeners while listening for connections. These replications can be for the same or +Having a Listener on a database still allows you to open replications to the other clients. For example, a Listener can +actively begin replicating to other Listeners while listening for connections. These replications can be for the same or a different database. -The listener will automatically select a port to use or a user-specified port. It will also listen on all available +The Listener will automatically select a port to use or a user-specified port. It will also listen on all available networks, unless you specify a specific network. ### Security @@ -107,7 +107,7 @@ reconnection. Optional delta-sync support is provided but is inactive by default. Delta-sync can be enabled on a per-replication basis provided that the databases involved are also configured to permit -it. Statistics on delta-sync usage are available, including the total number of revisions sent as deltas. +it. ### Conflict Resolution @@ -126,7 +126,7 @@ You can configure a Peer-to-Peer synchronization with just a short amount of cod ```kotlin val listener = URLEndpointListener( - URLEndpointListenerConfigurationFactory.newConfig( + URLEndpointListenerConfiguration( collections = db.collections, authenticator = ListenerPasswordAuthenticator { user, pwd -> (user == "daniel") && (pwd.concatToString() == "123") @@ -150,12 +150,12 @@ You can configure a Peer-to-Peer synchronization with just a short amount of cod ```kotlin val listenerEndpoint = URLEndpoint("wss://10.0.2.2:4984/db") val repl = Replicator( - ReplicatorConfigurationFactory.newConfig( - collections = mapOf(collections to null), - target = listenerEndpoint, - authenticator = BasicAuthenticator("valid.user", "valid.password.string".toCharArray()), - acceptOnlySelfSignedServerCertificate = true - ) + ReplicatorConfiguration(listenerEndpoint) + .addCollections(collections) + .apply { + authenticator = BasicAuthenticator("valid.user", "valid.password.string".toCharArray()) + isAcceptOnlySelfSignedServerCertificate = true + } ) repl.start() this.replicator = repl @@ -257,43 +257,161 @@ level. The default value is `false`. ## Security -### Authentication +Couchbase Lite’s Peer-to-Peer synchronization ensures secure communication through TLS and supports multiple +authentication mechanisms. + +### TLS Identity -Peer-to-Peer sync supports [Basic Authentication](/api/couchbase-lite-ee/kotbase/-listener-password-authenticator/) and -[TLS Authentication](/api/couchbase-lite-ee/kotbase/-listener-certificate-authenticator/). For anything other than test -deployments, we strongly encourage the use of TLS. In fact, Peer-to-Peer sync using `URLEndpointListener` is encrypted -using TLS by default. +The URLEndpointListener uses a TLS identity to establish secure connections. (A TLS identity is an RSA public/private +key pair and certificate.) The identity can include either a certificate signed by a trusted Certificate Authority (CA), +or a self-signed certificate. If no identity is specified, the listener automatically generates an anonymous, +self-signed certificate, which is primarily used for encryption, but not for authentication. -The authentication mechanism is defined at the endpoint level, meaning that it is independent of the database being -replicated. For example, you may use basic authentication on one instance and TLS authentication on another when -replicating multiple database instances. +When replicating with a listener that uses a self-signed certificate, the replicator (client) can be configured to skip +certificate validation. This option is useful for development or testing, but not recommended for production. !!! note The minimum supported version of TLS is TLS 1.2. -Peer-to-Peer synchronization using `URLEndpointListener` supports certificate based authentication of the server and-or -listener: +### Authentication Mechanisms -* Replicator certificates can be: self-signed, from trusted CA, or anonymous (system generated). -* Listeners certificates may be: self-signed or trusted CA signed.

    - Where a TLS certificate is not explicitly specified for the listener, the listener implementation will generate - anonymous certificate to use for encryption. -* The `URLEndpointListener` supports the ability to opt out of TLS encryption communication.

    - Active clients replicating with a URLEndpointListener have the option to skip validation of server certificates when - the listener is configured with self-signed certificates.

    - This option is ignored when dealing with CA certificates. +The URLEndpointListener supports two authentication mechanisms: + +* Basic Authentication, using a username and password. +* Certificate Authentication, which authenticates clients using client certificates, and is only available when TLS is + enabled. ### Using Secure Storage TLS and its associated keys and certificates might require using secure storage to minimize the chances of a security -breach. The implementation of this storage differs from platform to platform. [Table 1](#table-1) summarizes the secure -storage used to store keys and certificates for each platform. - -**Table 1. Secure storage details** - -| Platform | Key & Certificate Storage | Notes | Reference | -|:---------:|:-------------------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------| -| Android | Android System KeyStore |
    • Android KeyStore was introduced from Android API 18.
    • Android KeyStore security has evolved over time to provide more secure support. Please check [this document](https://source.android.com/security/keystore) for more info.
    | [link](https://developer.android.com/training/articles/keystore) | -| MacOS/iOS | KeyChain | Use `kSecAttrLabel` of the `SecCertificate` to store the `TLSIdentity`’s label | [link](https://developer.apple.com/documentation/security/keychain_services) | -| Java | User Specified KeyStore |
    • The KeyStore represents a storage facility for cryptographic keys and certificates. It’s users’ choice to decide whether to persist the KeyStore or not.
    • The supported KeyStore types are PKCS12 (Default from Java 9) and JKS (Default on Java 8 and below).
    | [link](https://docs.oracle.com/javase/7/docs/api/java/security/KeyStore.html) | +breach. The implementation of this storage differs from platform to platform. This table summarizes the secure storage +used to store keys and certificates. + +=== "Android" + + **Secure storage details** + + + + + + + + + + + + + + + + + + + + + + + +
    PlatformAndroid
    Key StorageAndroid System KeyStore
    Certificate StorageAndroid System KeyStore
    Notes
      +
    • Android KeyStore was introduced from Android API 18.
    • +
    • Android KeyStore security has evolved over time to provide more secure support. Please check this document for + more info: [Hardware-backed Keystore](https://source.android.com/security/keystore).
    • +
    Reference[Android Keystore system](https://developer.android.com/training/articles/keystore)
    + +=== "macOS/iOS" + + **Secure storage details** + + + + + + + + + + + + + + + + + + + + + + + +
    PlatformmacOS/iOS
    Key StorageKeyChain
    Certificate StorageKeyChain
    NotesUse kSecAttrLabel of the SecCertificate to store the TLSIdentity’s label
    Reference[Keychain services](https://developer.apple.com/documentation/security/keychain_services)
    + +=== "Java" + + **Secure storage details** + + + + + + + + + + + + + + + + + + + + + + + +
    PlatformJava
    Key StorageUser Specified KeyStore
    Certificate StorageUser Specified KeyStore
    Notes
      +
    • The KeyStore represents a storage facility for cryptographic keys and certificates. It’s users' choice to decide + whether to persist the KeyStore or not.
    • +
    • The supported KeyStore types are PKCS12 (Default from Java 9) and JKS (Default on Java 8 and below).
    • +
    Reference[Class KeyStore](https://docs.oracle.com/javase/7/docs/api/java/security/KeyStore.html)
    + +=== "Windows" + + **Secure storage details** + + + + + + + + + + + + + + + + + + +
    PlatformWindows
    Key StorageCNG Key Storage Provider
    Certificate StorageCNG Key Storage Provider
    Reference[Key Storage and Retrieval](https://learn.microsoft.com/en-us/windows/win32/seccng/key-storage-and-retrieval) +
    + +=== "Linux" + + As Linux-based operating systems do not have a standard or common secure key storage, Couchbase Lite C does not + support persisting generated identities with the specified label on this platform. + + As an alternative, Couchbase Lite C enables developers to implement their own cryptographic operations through a set + of callbacks, enabling certificate signing and other cryptographic tasks during the TLS handshake using a private + key stored in their preferred secure key storage. These callbacks allow operations like signing data, with the + private key remaining securely stored and never exposed. The key idea is that all cryptographic operations are + performed within secure key storage, ensuring that the private key is protected throughout the TLS handshake + process. diff --git a/docs/site/platforms.md b/docs/site/platforms.md index 250f2370e..9eef12696 100644 --- a/docs/site/platforms.md +++ b/docs/site/platforms.md @@ -69,11 +69,12 @@ https://www.couchbase.com/downloads/?family=couchbase-lite) or added via [Cartha https://docs.couchbase.com/couchbase-lite/current/objc/gs-install.html#lbl-install-tabs). The version should match the major and minor version of Kotbase, e.g. CouchbaseLite {{ version_short }}.x for Kotbase {{ version_full }}. -The [Kotlin CocoaPods Gradle plugin](https://kotlinlang.org/docs/native-cocoapods.html) can also be used to generate a -[Podspec](https://guides.cocoapods.org/syntax/podspec.html) for your project that includes the `CouchbaseLite` -dependency. Use `linkOnly = true` to link the dependency without generating Kotlin Objective-C interop: +The [Kotlin CocoaPods Gradle plugin]( +https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-cocoapods-overview.html) can also be used to +generate a [Podspec](https://guides.cocoapods.org/syntax/podspec.html) for your project that includes the +`CouchbaseLite` dependency. Use `linkOnly = true` to link the dependency without generating Kotlin Objective-C interop: -??? example "CocoaPods plugin" +!!! example "CocoaPods plugin" === "Enterprise Edition" @@ -150,6 +151,65 @@ https://github.com/unicode-org/icu/releases). | Raspberry Pi OS | 10+ | | :material-check: | | Ubuntu | 20.04+ | :material-check: | :material-check: | +#### Using APT + +Using the Advanced Package Tool (apt) is the easiest way to install Couchbase Lite on Ubuntu and Debian platforms. Just +download the meta package that apt requires to automatically get and install Couchbase Lite, including any dependencies. + +1. Download the meta package + + === "curl" + + ``` + curl -O https://packages.couchbase.com/releases/couchbase-release/couchbase-release-1.0-noarch.deb + ``` + + === "wget" + + ``` + wget https://packages.couchbase.com/releases/couchbase-release/couchbase-release-1.0-noarch.deb + ``` + +2. Install the meta package + + === "apt" + + ``` + sudo apt install ./couchbase-release-1.0-noarch.deb + ``` + + === "dpkg" + + ``` + sudo dpkg -i ./couchbase-release-1.0-noarch.deb + ``` + +3. Update the local package database + + ``` + sudo apt update + ``` + +4. Install the required release package(s) + + === "Enterprise" + + ```title="Runtime Only" + sudo apt install libcblite + ``` + ```title="Development" + sudo apt install libcblite-dev + ``` + + === "Community" + + ```title="Runtime Only" + sudo apt install libcblite-community + ``` + ```title="Development" + sudo apt install libcblite-dev-community + ``` + ### Windows | Version | x64 | diff --git a/docs/site/query-builder.md b/docs/site/query-builder.md index ae4174b60..c2db5f076 100644 --- a/docs/site/query-builder.md +++ b/docs/site/query-builder.md @@ -220,9 +220,9 @@ where the `public_likes` array property contains a value equal to "Armani Langwo ```json { - "_id": "hotel123", - "name": "Apple Droid", - "public_likes": ["Armani Langworth", "Elfrieda Gutkowski", "Maureen Ruecker"] + "_id": "hotel123", + "name": "Apple Droid", + "public_likes": ["Armani Langworth", "Elfrieda Gutkowski", "Maureen Ruecker"] } ``` @@ -504,11 +504,11 @@ country and timezone. ```json title="Data Model for Example" { - "_id": "airport123", - "type": "airport", - "country": "United States", - "geo": { "alt": 456 }, - "tz": "America/Anchorage" + "_id": "airport123", + "type": "airport", + "country": "United States", + "geo": { "alt": 456 }, + "tz": "America/Anchorage" } ``` diff --git a/docs/site/remote-sync-gateway.md b/docs/site/remote-sync-gateway.md index 0ab8bef16..91e461908 100644 --- a/docs/site/remote-sync-gateway.md +++ b/docs/site/remote-sync-gateway.md @@ -125,29 +125,26 @@ You should configure and initialize a replicator for each Couchbase Lite databas ```kotlin val repl = Replicator( // initialize the replicator configuration - ReplicatorConfigurationFactory.newConfig( - target = URLEndpoint("wss://listener.com:8954"), + ReplicatorConfiguration(URLEndpoint("wss://listener.com:8954")) + .addCollections(collections).apply { + // Set replicator type + type = ReplicatorType.PUSH_AND_PULL - collections = mapOf(collections to null), + // Configure Sync Mode + isContinuous = false // default value - // Set replicator type - type = ReplicatorType.PUSH_AND_PULL, + // set auto-purge behavior + // (here we override default) + isAutoPurgeEnabled = false - // Configure Sync Mode - continuous = false, // default value + // Configure Server Authentication -- + // only accept self-signed certs + isAcceptOnlySelfSignedServerCertificate = true - // set auto-purge behavior - // (here we override default) - enableAutoPurge = false, - - // Configure Server Authentication -- - // only accept self-signed certs - acceptOnlySelfSignedServerCertificate = true, - - // Configure the credentials the - // client will provide if prompted - authenticator = BasicAuthenticator("PRIVUSER", "let me in".toCharArray()) - ) + // Configure the credentials the + // client will provide if prompted + authenticator = BasicAuthenticator("PRIVUSER", "let me in".toCharArray()) + } ) // Optionally add a change listener @@ -279,7 +276,7 @@ limit (5 minutes). The REST API provides configurable control over this replication retry logic using a set of configurable properties — see [Table 1](#table-1). -**Table 1. Replication Retry Configuration Properties** +**Table 1. Replication Retry Configuration Properties** |
    Property
    | Use cases | Description | |:---------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -293,14 +290,14 @@ When necessary you can adjust any or all of those configurable values — see [E ```kotlin val repl = Replicator( - ReplicatorConfigurationFactory.newConfig( - target = URLEndpoint("ws://localhost:4984/mydatabase"), - collections = mapOf(collections to null), - // other config params as required . . - heartbeat = 150, - maxAttempts = 20, - maxAttemptWaitTime = 600 - ) + ReplicatorConfiguration(URLEndpoint("ws://localhost:4984/mydatabase")) + .addCollections(collections) + .apply { + // other config params as required . . + heartbeat = 150 + maxAttempts = 20 + maxAttemptWaitTime = 600 + } ) repl.start() this.replicator = repl @@ -417,11 +414,9 @@ shows how to initiate a one-shot replication as the user **username** with the p ```kotlin // Create replicator (be sure to hold a reference somewhere that will prevent the Replicator from being GCed) val repl = Replicator( - ReplicatorConfigurationFactory.newConfig( - target = URLEndpoint("ws://localhost:4984/mydatabase"), - collections = mapOf(collections to null), - authenticator = BasicAuthenticator("username", "password".toCharArray()) - ) + ReplicatorConfiguration(URLEndpoint("ws://localhost:4984/mydatabase")) + .addCollections(collections) + .setAuthenticator(BasicAuthenticator("username", "password".toCharArray())) ) repl.start() this.replicator = repl @@ -446,11 +441,9 @@ endpoint. ```kotlin // Create replicator (be sure to hold a reference somewhere that will prevent the Replicator from being GCed) val repl = Replicator( - ReplicatorConfigurationFactory.newConfig( - target = URLEndpoint("ws://localhost:4984/mydatabase"), - collections = mapOf(collections to null), - authenticator = SessionAuthenticator("904ac010862f37c8dd99015a33ab5a3565fd8447") - ) + ReplicatorConfiguration(URLEndpoint("ws://localhost:4984/mydatabase")) + .addCollections(collections) + .setAuthenticator(SessionAuthenticator("904ac010862f37c8dd99015a33ab5a3565fd8447")) ) repl.start() this.replicator = repl @@ -468,11 +461,9 @@ done by a proxy server (between Couchbase Lite and Sync Gateway) — see [Exampl ```kotlin // Create replicator (be sure to hold a reference somewhere that will prevent the Replicator from being GCed) val repl = Replicator( - ReplicatorConfigurationFactory.newConfig( - target = URLEndpoint("ws://localhost:4984/mydatabase"), - collections = mapOf(collections to null), - headers = mapOf("CustomHeaderName" to "Value") - ) + ReplicatorConfiguration(URLEndpoint("ws://localhost:4984/mydatabase")) + .addCollections(collections) + .setHeaders(mapOf("CustomHeaderName" to "Value")) ) repl.start() this.replicator = repl @@ -489,16 +480,14 @@ The push filter allows an app to push a subset of a database to the server. This high-priority documents could be pushed first, or documents in a "draft" state could be skipped. ```kotlin -val collectionConfig = CollectionConfigurationFactory.newConfig( +val collectionConfig = CollectionConfiguration( pushFilter = { _, flags -> flags.contains(DocumentFlag.DELETED) } ) // Create replicator (be sure to hold a reference somewhere that will prevent the Replicator from being GCed) val repl = Replicator( - ReplicatorConfigurationFactory.newConfig( - target = URLEndpoint("ws://localhost:4984/mydatabase"), - collections = mapOf(collections to collectionConfig) - ) + ReplicatorConfiguration(URLEndpoint("ws://localhost:4984/mydatabase")) + .addCollections(collections, collectionConfig) ) repl.start() this.replicator = repl @@ -520,16 +509,14 @@ important security mechanism in a peer-to-peer topology with peers that are not on the server) whereas a pull replication filter is applied to a document once it has been downloaded. ```kotlin -val collectionConfig = CollectionConfigurationFactory.newConfig( +val collectionConfig = CollectionConfiguration( pullFilter = { document, _ -> "draft" == document.getString("type") } ) // Create replicator (be sure to hold a reference somewhere that will prevent the Replicator from being GCed) val repl = Replicator( - ReplicatorConfigurationFactory.newConfig( - target = URLEndpoint("ws://localhost:4984/mydatabase"), - collections = mapOf(collections to collectionConfig) - ) + ReplicatorConfiguration(URLEndpoint("ws://localhost:4984/mydatabase")) + .addCollections(collections, collectionConfig) ) repl.start() this.replicator = repl @@ -743,7 +730,8 @@ in bandwidth consumption as well as throughput improvements, especially when net Replications to a Server (for example, a Sync Gateway, or passive listener) automatically use delta sync if the property is enabled at database level by the server — see Admin REST API [`delta_sync.enabled`]( https://docs.couchbase.com/sync-gateway/current/configuration-schema-database.html#delta_sync-enabled) or legacy JSON -configuration [`databases.$db.delta_sync.enabled`](https://docs.couchbase.com/sync-gateway/current/configuration-properties-legacy.html#databases-this_db-delta_sync-enabled). +configuration [`databases.$db.delta_sync.enabled`]( +https://docs.couchbase.com/sync-gateway/current/configuration-properties-legacy.html#databases-this_db-delta_sync-enabled). [Intra-Device](intra-device-sync.md) replications automatically **disable** delta sync, whilst [Peer-to-Peer]( peer-to-peer-sync.md) replications automatically **enable** delta sync. @@ -769,29 +757,27 @@ replicator running using [`start()`](/api/couchbase-lite-ee/kotbase/-replicator/ val repl = Replicator( // initialize the replicator configuration - ReplicatorConfigurationFactory.newConfig( - target = URLEndpoint("wss://listener.com:8954"), - - collections = mapOf(collections to null), - - // Set replicator type - type = ReplicatorType.PUSH_AND_PULL, - - // Configure Sync Mode - continuous = false, // default value - - // set auto-purge behavior - // (here we override default) - enableAutoPurge = false, - - // Configure Server Authentication -- - // only accept self-signed certs - acceptOnlySelfSignedServerCertificate = true, - - // Configure the credentials the - // client will provide if prompted - authenticator = BasicAuthenticator("PRIVUSER", "let me in".toCharArray()) - ) + ReplicatorConfiguration(URLEndpoint("wss://listener.com:8954")) + .addCollections(collections) + .apply { + // Set replicator type + type = ReplicatorType.PUSH_AND_PULL + + // Configure Sync Mode + isContinuous = false // default value + + // set auto-purge behavior + // (here we override default) + isAutoPurgeEnabled = false + + // Configure Server Authentication -- + // only accept self-signed certs + isAcceptOnlySelfSignedServerCertificate = true + + // Configure the credentials the + // client will provide if prompted + authenticator = BasicAuthenticator("PRIVUSER", "let me in".toCharArray()) + } ) // Start replicator @@ -1028,11 +1014,9 @@ methods: ```kotlin val repl = Replicator( - ReplicatorConfigurationFactory.newConfig( - target = URLEndpoint("ws://localhost:4984/mydatabase"), - collections = mapOf(setOf(collection) to null), - type = ReplicatorType.PUSH - ) + ReplicatorConfiguration(URLEndpoint("ws://localhost:4984/mydatabase")) + .addCollection(collection) + .setType(ReplicatorType.PUSH) ) val pendingDocs = repl.getPendingDocumentIds() @@ -1189,11 +1173,9 @@ This example loads the certificate from the application sandbox, then converts i ```kotlin val repl = Replicator( - ReplicatorConfigurationFactory.newConfig( - target = URLEndpoint("wss://localhost:4984/mydatabase"), - collections = mapOf(collections to null), - pinnedServerCertificate = PlatformUtils.getAsset("cert.cer")?.readByteArray() - ) + ReplicatorConfiguration(URLEndpoint("wss://localhost:4984/mydatabase")) + .addCollections(collections) + .setPinnedServerCertificate(PlatformUtils.getAsset("cert.cer")?.readByteArray()) ) repl.start() this.replicator = repl @@ -1219,11 +1201,10 @@ activity related to replication with Sync Gateway — see [Example 20](#example- !!! example "Example 20. Set logging verbosity" ```kotlin - Database.log.console.setDomains(LogDomain.REPLICATOR) - Database.log.console.level = LogLevel.DEBUG + LogSinks.console = ConsoleLogSink(LogLevel.DEBUG, LogDomain.REPLICATOR) ``` -For more on troubleshooting with logs, see [Using Logs](using-logs.md). +For more on troubleshooting with logs, see [Using Logs](new-logging-api.md). ### Authentication Errors diff --git a/docs/site/roadmap.md b/docs/site/roadmap.md index 776bd685b..96a6bf506 100644 --- a/docs/site/roadmap.md +++ b/docs/site/roadmap.md @@ -16,8 +16,9 @@ hide: * [ ] SwiftUI for Kotbase Notes * [x] Couchbase Lite [3.1 API](https://docs.couchbase.com/couchbase-lite/3.1/cbl-whatsnew.html) - Scopes and Collections * [x] Versioned docs -* [ ] Couchbase Lite [3.2 API](https://docs.couchbase.com/couchbase-lite/3.2/cbl-whatsnew.html) - [Vector Search]( +* [x] Couchbase Lite [3.2 API](https://docs.couchbase.com/couchbase-lite/3.2/cbl-whatsnew.html) - [Vector Search]( https://www.couchbase.com/products/vector-search/) +* [ ] Couchbase Lite [3.3 API](https://docs.couchbase.com/couchbase-lite/3.3/cbl-whatsnew.html) - Multipeer Replicator * [ ] Improve Swift API alignment with Couchbase Lite using [Swift export](https://youtrack.jetbrains.com/issue/KT-64572), `@ObjCName`, and/or `@ShouldRefineInSwift` * [ ] Async coroutines API diff --git a/docs/site/vector-search.md b/docs/site/vector-search.md new file mode 100644 index 000000000..5a9dbfb92 --- /dev/null +++ b/docs/site/vector-search.md @@ -0,0 +1,218 @@ +# Vector Search + +_Use Vector Search to build adaptive and user-focused applications using Generative AI._ + +## About Vector Search + +Vector Search is a technique to retrieve semantically similar items based on vector embedding representations of the +items in a multi-dimensional space. You can use Vector Search to find the top N items similar to a given item based on +their vector representations. Vector Search is an essential component of Generative AI and Predictive AI applications. + +Vector Search is a sophisticated data retrieval technique that focuses on matching the contextual meanings of search +queries and data entries, rather than simple text matching. Vectors are represented by arrays of numbers known as an +embedding, which are generated by Large Language Models (LLMs) to depict objects such as text, images, and audio. + +Once you choose the LLM you wish to integrate in your application, you can create vector indexes that will store these +embeddings for improved search performance and start querying against them. + +## Applications of Vector Search + +You can [use Vector](installation.md#vector-search) Search to enhance your mobile and edge applications in a variety of +use cases, these include: + +* Perform Semantic and Similarity Search on the Edge - Any offline-first mobile or IoT application can benefit from rich + semantic text capabilities offered by Vector Search to retrieve contextually relevant data and present it to users. +* Create Recommendation Engines - Vector Search enables the creation of advanced recommendation systems that analyze + semantic similarities between items, user behavior, and preferences. This approach delivers personalized + recommendations, improving user engagement and satisfaction. +* Enhance the contextual relevance of your applications with Retrieval Augmented Generation (RAG) - RAG is a technique + for enhancing the accuracy and reliability of generative AI models with facts fetched from external sources that are + contextual to your application, such as an internal vector database. The search results are then included as context + data to queries sent to the LLM in order to customize query responses. This results in the LLM returning a result that + is likely to be far more contextually relevant than the standalone prompt. + +Additionally, Vector Search in Couchbase Lite provides the following benefits: + +* Unified Cloud-to-Edge Support for Vector Similarity Search - Couchbase supports Vector Search from cloud to edge, + which enables applications to efficiently utilize cloud and edge computing’s strengths. +* Enhanced Data Privacy on the Edge - By performing Vector Search within the device, personal data and search queries of + a sensitive nature do not have to leave the device. +* Low Latency Application Support - You can run searches locally against a local dataset using a local embedded model. + This eliminates the network variability and results in more consistent execution speed. Even in the case where the + model is not embedded within the local device but is deployed at the edge location, the round trip time (RTT) + associated with queries can be significantly reduced compared to searches made over the Internet. +* Cost Per Query Reduction - When you have hundreds of thousands of connected clients querying against a cloud-based + LLM, the load on cloud model and operational costs of running the cloud based model can be considerably high. By + running queries locally on the device, you can save on data transfer costs and cloud egress charges while also + decentralizing the operational costs. + +## Key Concepts of Vector Search in Couchbase Lite + +When [working with Vector Search](working-with-vector-search.md), you should be aware of the core concepts below. + +### About Vector Embeddings + +Vector embeddings represent the output of a Machine Learning (ML) model as an array of numbers to capture semantic or +contextual relationships between data points. This representation encodes how a ML model understands the input or inputs +provided to it, based on how the model was initially trained and the internal structure of the model. When a model +considers the features of a given input as similar, the distance between the vector embeddings will be short. Vector +embeddings are stored within embedded vector indexes. + +The current supported formats for Vector embeddings are: + +* An array of 32-bit floats. +* A Base64 string that encodes a [Little-endian](https://en.wikipedia.org/wiki/Endianness) array of 32-bit floats. + +### About Vector Indexes + +Vector Indexes are used to store and manage vector representations of content in the form of vector embeddings. You can +use Vector Indexes to efficiently retrieve vectors, similar to a target vector. Before use, a Vector Index needs to be +trained to compute the centroids and parameters for encoding the vectors. + +You can configure both the minimum and maximum training sizes for your vector index by setting the relevant parameters +in your vector index configuration. For an example of how you can do so, see [create a vector index]( +working-with-vector-search.md#create-a-vector-index). + +Couchbase Lite Vector Search initiates training on the first Vector Search query automatically when the number of +vectors to be trained satisfies the `minimum-training-size` configuration. If the database does not contain the required +number of vectors, an error message will be logged indicating the required number of vectors. + +See [Vector Index Configuration](working-with-vector-search.md#vector-index-configuration) for more information about +configurations you can modify for your vector index. + +Be aware that vector index training can affect query performance. If a query is executed against the index before it is +trained, a full scan of the vectors will be performed. + +### About Lazy Vector Indexes + +!!! important + + Lazy index is not an automatic process, you will need to manually schedule the index updates. + +Lazy vector indexes (lazy index) is Couchbase Lite specific functionality that updates indexes asynchronously, +satisfying the following use cases: + +* Documents have been added to the application by the end-user with no available Machine Learning (ML) model to generate + the vectors. You can use lazy indexing to schedule updates to the index of such documents when a ML model is + available. +* The remote ML model used stops working or has intermittent availability, causing a failed update. With lazy indexing, + you can skip documents that fail to update and schedule the index process at a later date. + +Lazy indexing is an asynchronous process that provides developers full control of: + +* When to update the index. +* The number of vectors to update to the index. +* Whether to cancel or skip certain indexes when the model is unavailable or has failed. + +!!! note + + Updating in lazy index is an independent process from saving document operations. + +See here for examples of how to [use lazy index in your applications]( +working-with-vector-search.md#create-a-lazy-vector-index). + +Lazy indexing is an alternate approach to using the standard predictive model with regular vector indexes which handle +the indexing process automatically. The table below compares the two processes. + +**Table 1. Regular Vector Indexes vs Lazy Vector Indexes** + +| Feature | Regular Index | Lazy Index | +|:-----------------------------------------------------|:----------------:|:----------------:| +| Update when documents are changed | :material-check: | :material-close: | +| Update when documents are deleted or purged | :material-check: | :material-check: | +| The application has control when to update the index | :material-close: | :material-check: | +| The application can skip updating the index | :material-close: | :material-check: | +| Is an asynchronous process | :material-close: | :material-check: | + +### About Vector Encoding + +Vector encoding reduces the size of the vectors index by algorithmic compression. You can configure the Vector Encoding +in Couchbase Lite to address your application’s needs. + +This vector encoding compression reduces disk space required and I/O time during indexing and queries, but greater +compression can result in inaccurate results in distance calculations. + +Vector Search for Couchbase Lite supports the following encoding algorithms: + +* None - This will return the highest quality results but at high performance and disk space costs. +* Scalar Quantizer - This reduces the number of bits used for each number in a vector. The number of bits per component + can be set to 4, 6, or 8 bits. The default setting in Couchbase Lite is 8 bits Scalar Quantizer or SQ-8. +* Product Quantizer - This reduces the number of dimensions and bits per dimension. It splits the vectors into multiple + subspaces and performing scalar quantization on each space independently before compression. This can produce higher + quality results than Scalar Quantization at the cost of greater complexity. + +!!! note + + Quantizers are algorithmic processes that map input values from a larger set to output values in a smaller set, + common quantization processes can include operations such as rounding and truncation. + +### About Centroids + +Centroids are vectors that function as the center point of a vector cluster within the data set. Each vector is then +associated to the vector it is closest to by [k-means clustering](https://en.wikipedia.org/wiki/K-means_clustering). +Each Centroid is contained within a bucket along with its associated vectors. The greater the number of Centroids, the +greater the potential accuracy of the model. However, a greater number of Centroids will incur a longer indexing time. + +Choosing Centroids in Vector Search involves trade-offs that can impact clustering effectiveness and search efficiency. +The initial selection of Centroids, the number chosen, and their sensitivity to high dimensionality and outliers affect +the quality of vector clustering. + +The general guideline for the optimum number of Centroids is approximately the square root of the number of documents. + +### About Probes + +The number of Probes refers to the maximum number of Centroid buckets that the search algorithm will check to look for +similar vectors to a given query vector. You can change the number of Probes by altering the value of the `NumProbes` +variable [shown in the following example](working-with-vector-search.md#create-a-vector-index). Couchbase recommends +that when setting a custom number of probes, the number should be at least 8 or 0.5% the number of Centroids used. + +### About Dimensions + +Vector dimensions describes the amount of numbers in a given vector embedding, commonly known as its width. The greater +the number of dimensions, the greater accuracy of results. However, a greater number of dimensions also results in +greater compute and memory costs and an increase in the latency of the search. Vector dimensions are dependent on the +LLM used to generate the Vector Embeddings. + +!!! note + + Couchbase Lite supports dimension sizes in the range of `2 - 4096`. + +### About Distance Metrics + +Distance metrics are functions used to define how close an input query vector is to other vectors within a vector index. + +Couchbase Lite supports the following distance metrics: + +* Squared Euclidean Distance - This is the default distance metric. This measures the straight-line distance between two + points in Euclidean space which is defined by n dimensions, such as x,y,z. This metric focuses on the spatial + separation or distance between two vectors. Both the magnitude and direction of the vectors matter. The smaller the + distance value, the more similar the vectors are. You can use this metric to simplify computation in situations where + only the relative distance matters, rather than actual distance. +* Euclidean Distance - This measures the straight-line distance between two points in Euclidean space which is defined + by n dimensions, such as x,y,z. This metric focuses on the spatial separation or distance between two vectors. Both + the magnitude and direction of the vectors matter. The smaller the distance value, the more similar the vectors are. + This differs from Squared Euclidean Distance by taking the square root of the calculated distance between two point. + The result is a "true" geometric distance. You can use this metric when the actual geometric distance matters, such as + calculating distance between cities using GPS coordinates. +* Cosine Distance - This measures the cosine of the angle between two vectors in vector space. This metric focuses on + the alignment of two vectors, the similarity of direction. Only the direction of the vectors matter. The smaller the + distance value, the more similar the vectors are. You can use this metric when comparing similarity of document + content no matter the document size in text similarity or information retrieval applications. +* Dot Product Distance - This metric captures the overall similarity by comparing the magnitude and direction of + vectors. The result is larger when the vectors are aligned and have large magnitudes and smaller in the opposite case. + You can use this metric in recommendation systems to provide users with related content with preference to items the + most similar to frequently visited items. + +## Hybrid Vector Search + +Hybrid Vector Search (Hybrid Search) combines traditional keyword-based search such as [full text search (FTS)]( +full-text-search.md), which matches exact text or metadata with advanced methods such as Vector Search which matches +content based on semantic similarity. Hybrid Search aims to enhance search capabilities by using both exact matches and +contextual relevance to improve the overall accuracy and relevance of search results. See the [following examples]( +working-with-vector-search.md#use-hybrid-vector-search) for more information on how to use Hybrid Search. + +Vector Search will be performed on the documents that have been filtered based on the criteria specified in the WHERE +clause. No LIMIT clause is required for Hybrid Vector Search. + +See the [Hybrid Search blog post](https://www.couchbase.com/blog/hybrid-search/) for more information about Hybrid +Search. diff --git a/docs/site/working-with-vector-search.md b/docs/site/working-with-vector-search.md new file mode 100644 index 000000000..44246d181 --- /dev/null +++ b/docs/site/working-with-vector-search.md @@ -0,0 +1,384 @@ +_Use Vector Search with Full Text Search and Query._ + +## Use Vector Search + +To configure a project to use vector search, follow the [installation instructions](installation.md#vector-search) to +add the Vector Search extension. + +!!! note + + You must install Couchbase Lite to use the Vector Search extension. + +## Create a Vector Index + +This method shows how you can create a vector index using the Couchbase Lite Vector Search extension. + +```kotlin +// create the configuration for a vector index named "vector" +// with 3 dimensions, 100 centroids, no encoding, using cosine distance +// with a max training size 5000 and amin training size 2500 +// no vector encoding and using COSINE distance measurement +val config = VectorIndexConfiguration("vector", 3L, 100L).apply { + encoding = VectorEncoding.none() + metric = VectorIndexConfiguration.DistanceMetric.COSINE + numProbes = 8L + minTrainingSize = 2500L + maxTrainingSize = 5000L +} +``` + +First, initialize the `config` object with the `VectorIndexConfiguration()` method with the following parameters: + +* The expression of the data as a vector. +* The width or `dimensions` of the vector index is set to `3`. +* The amount of `centroids` is set to `100`. This means that there will be one hundred buckets with a single centroid + each that gathers together similar vectors. + +You can also alter some optional config settings such as `encoding`. From there, you create an index within a given +collection using the previously generated `config` object. + +!!! note + + The number of vectors, the width or dimensions of the vectors and the training size can incur high CPU and memory + costs as the size of each variable increases. This is because the training vectors have to be resident on the + machine. + +### Vector Index Configuration + +The table below displays the different configurations you can modify within your `VectorIndexConfiguration()` function. +For more information on specific configurations, see [Vector Search](vector-search.md). + +**Table 1. Vector Index Configuration Options** + +|
    Configuration Name
    |
    Is Required
    | Default Configuration | Further Information | +|:------------------------------------------------------|:---------------------------------------------:|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Expression | :material-check: | No default | A SQL++ expression indicating where to get the vectors. A document property for embedded vectors or `prediction()` to call a registered Predictive model. | +| Number of Dimensions | :material-check: | No default | 2-4096 | +| Number of Centroids | :material-check: | No default | 1-64000. The general guideline is an approximate square root of the number of documents. | +| Distance Metric | :material-close: | Squared Euclidean Distance (euclideanSquared) | You can set the following alternates as your Distance Metric:
    • cosine (1 - Cosine similarity)
    • Euclidean
    • dot (negated dot product)
    | +| Encoding | :material-close: | Scalar Quantizer(SQ) or SQ-8 bits | There are three possible configurations:
    • None No compression, No data loss
    • Scalar Quantizer (SQ) or SQ-8 bits (Default) Reduces the number of bits per dimension
    • Product Quantizer (PQ) Reduces the number of dimensions and bits per dimension
    | +| Training Size | :material-close: | The default values for both the minimum and maximum training size is zero. The training size is calculated based on the number of Centroids and the encoding type. | The guidelines for the minimum and maximum training size are as follows:
    • The minimum training size is set to 25x the number of Centroids or 2PQ’s bits when PQ is used
    • The maximum training size is set to 256x the number of Centroids or 2PQ’s bits when PQ is used
    | +| NumProbes | :material-close: | The default value is 0. The number of Probes is calculated based on the number of Centroids. | A guideline for setting a custom number of probes is at least 8 or 0.5% the number of Centroids. | +| isLazy | :material-close: | False | Setting the value to true will enable lazy mode for the vector index. | + +!!! warning "Caution" + + Altering the default training sizes could be detrimental to the accuracy of returned results produced by the model + and total computation time. + +## Generating Vectors + +You can use the following methods to generate vectors in Couchbase Lite: + +1. You can call a Machine Learning(ML) model, and embed the generated vectors inside the documents. +2. You can use the `prediction()` function to generate vectors to be indexed for each document at the indexing time. +3. You can use Lazy Vector Index (lazy index) to generate vectors asynchronously from remote ML models that may not always be reachable or functioning, skipping or scheduling retries for those specific cases. + +Below are example configurations of the previously mentioned methods. + +### Create a Vector Index with Embeddings + +This method shows you how to create a Vector Index with embeddings. + +```kotlin +// Get the collection named "colors" in the default scope. +val collection = database.getCollection("colors") + ?: throw IllegalStateException("No such collection: colors") + +// Create a vector index configuration with a document property named "vector", +// 3 dimensions, and 100 centroids. +val config = VectorIndexConfiguration("vector", 3, 100) +// Create a vector index from the configuration with the name "colors_index". +collection.createIndex("colors_index", config) +``` + +1. First, create the standard configuration, setting up an expression, number of dimensions and number of centroids for + the vector embedding. +2. Next, create a vector index, `colors_index`, on a collection and pass it the configuration. + +### Create Vector Index Embeddings from a Predictive Model + +This method generates vectors to be indexed for each document at the index time by using the `prediction()` function. +The key difference to note is that the `config` object uses the output of the `prediction()` function as the +`expression` parameter to generate the vector index. + +```kotlin +class ColorModel : PredictiveModel { + override fun predict(input: Dictionary): Dictionary? { + // Get the color input from the input dictionary + val color = input.getString("colorInput") + ?: throw IllegalStateException("No input color found") + + // Use ML model to get a vector (an array of floats) for the input color. + val vector = Color.getVector(color) ?: return null + + // Create an output dictionary by setting the vector result to + // the dictionary key named "vector". + val output = MutableDictionary() + output.setValue("vector", vector) + return output + } +} + +fun createVectorIndexFromPredictiveIndex() { + // Register the predictive model named "ColorModel". + Database.prediction.registerModel("ColorModel", ColorModel()) + + // Create a vector index configuration with an expression using the prediction + // function to get the vectors from the registered predictive model. + val expression = "prediction(ColorModel, {\"colorInput\": color}).vector" + val config = VectorIndexConfiguration(expression, 3, 100) + + // Create vector index from the configuration + collection.createIndex("colors_index", config) +} +``` + +!!! note + + You can use less storage by using the `prediction()` function as the encoded vectors will only be stored in the + index. However, the index time will be longer as vector embedding generation is occurring at run time. + +## Create a Lazy Vector Index + +Lazy indexing is an alternate approach to using the standard predictive model with regular vector indexes which handle +the indexing process automatically. You can use lazy indexing to use a ML model that is not available locally on the +device and to create vector indexes without having vector embeddings in the documents. + +```kotlin +// Creating a lazy vector index using the document's property named "color". +// The "color" property's value will be used to compute a vector when updating the index. +val config = VectorIndexConfiguration("color", 3, 100).apply { + isLazy = true +} +``` + +You can enable lazy vector indexing by setting the `isLazy` property to `true` in your vector index configuration. + +!!! note + + Lazy Vector Indexing is opt-in functionality, the `isLazy` property is set to `false` by default. + +### Updating the Lazy Index + +Below is an example of how you can update your lazy index. + +```kotlin +val index = collection.getIndex("colors_index") + ?: throw IllegalStateException("colors_index not found") + +while (true) { + // Start an update on it (in this case, limit to 50 entries at a time) + index.beginUpdate(50)?.use { updater -> + for (i in 0..? = Color.getVectorAsync(color) + // Set the computed vector here. If vector is null, calling setVector + // will cause the underlying document to NOT be indexed. + updater.setVector(embedding, i) + } catch (e: IOException) { + // Bad connection? Corrupted over the wire? Something bad happened + // and the vector cannot be generated at the moment: skip it. + // The next time beginUpdate() is called, we'll try it again. + updater.skipVector(i) + } + } + // This writes the vectors to the index. You MUST either have set or skipped each + // of the vectors in the updater or this call will throw an exception. + updater.finish() + } + // loop until there are no more vectors to update + ?: break +} +``` + +You procedurally update the vectors in the index by looping through the vectors in batches until you reach the value of +the `limit` parameter. + +The update process follows the following sequence: + +1. Get a value for the updater. + 1. If the there is no value for the vector, handle it. In this case, the vector will be skipped and considered the + next time `beginUpdate()` is called. + + !!! note + + A key benefit of lazy indexing is that the indexing process continues if a vector fails to generate. For + standard vector indexing, this will cause the affected documents to be dropped from the indexing process. + +2. Set the vector from the computed vector derived from the updater value and your ML model. + 1. If there is no value for the vector, this will result in the underlying document to not be indexed. +3. Once all vectors have completed the update loop, finish updating. + +!!! note + + `updater.finish()` will throw an error if any values inside the updater have not been set or skipped. + +## Vector Search SQL++ Support + +Couchbase Lite currently supports Hybrid Vector Search and the `APPROX_VECTOR_DISTANCE()` function. + +!!! important + + Similar to the [Full Text Search](full-text-search.md) `match()` function, the `APPROX_VECTOR_DISTANCE()` function + and Hybrid Vector Search cannot use the `OR` expression with the other expressions in the related `WHERE` clause. + +## Use Hybrid Vector Search + +You can use Hybrid Vector Search (Hybrid Search) to perform vector search in conjunction with regular SQL++ queries. +With Hybrid Search, you perform vector search on documents that have already been filtered based on criteria specified +in the `WHERE` clause. + +!!! note + + A `LIMIT` clause is required for non-hybrid Vector Search, this avoids a slow, exhaustive unlimited search of all + possible vectors. + +### Hybrid Vector Search with Full Text Match + +Below are examples of using Hybrid Search with the Full Text `match()` function. + +```kotlin +// Create a hybrid vector search query with full-text's match() that +// uses the the full-text index named "color_desc_index". +val sql = $$""" + SELECT meta().id, color + WHERE MATCH(color_desc_index, $text) + ORDER BY approx_vector_distance(vector, $vector) + LIMIT 8 +""".trimIndent() + +val query = database.createQuery(sql) + +// Get a vector, an array of float numbers, for the input color code (e.g. FF000AA). +// Normally, you will get the vector from your ML model. +val vector = Color.getVector("FF00AA") + ?: throw IllegalStateException("Vector not found") + +val parameters = Parameters() +// Set the vector array to the parameter "$vector" +parameters.setValue("vector", vector) +// Set the vector array to the parameter "$text". +parameters.setString("text", "vibrant") +query.parameters = parameters + +// Execute the query +query.execute().use { rs -> + // process results +} +``` + +### Prediction with Hybrid Vector Search + +Below are examples of using Hybrid Search with an array of vectors generated by the `Prediction()` function at index +time. + +```kotlin +// Create a hybrid vector search query that uses prediction() for computing vectors. +val sql = $$""" + SELECT meta().id, color + WHERE saturation > 0.5 + ORDER BY approx_vector_distance(prediction(ColorModel, {"colorInput": color}).vector, $vector) + LIMIT 8 +""".trimIndent() + +val query = database.createQuery(sql) + +// Get a vector, an array of float numbers, for the input color code (e.g. FF000AA). +// Normally, you will get the vector from your ML model. +val vector = Color.getVector("FF00AA") + ?: throw IllegalStateException("Vector not found") + +// Set the vector array to the parameter "$vector" +val parameters = Parameters() +parameters.setValue("vector", vector) +query.parameters = parameters + +// Execute the query +query.execute().use { rs -> + // process results +} +``` + +## `APPROX_VECTOR_DISTANCE(vector-expr, target-vector, [metric], [nprobes], [accurate])` + +!!! warning + + If you use a different distance metric in the `APPROX_VECTOR_DISTANCE()` function from the one configured in the + index, you will receive an error when compiling the query. + +|
    Parameter
    |
    Is Required
    | Description | +|:--------------------------------------------|:---------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| vector-expr | :material-check: | The expression returning a vector (NOT Index Name). Must match the expression specified in the vector index exactly. | +| target-vector | :material-check: | The target vector. | +| metric | :material-close: | Values : "EUCLIDEAN_SQUARED", “L2_SQUARED”, “EUCLIDEAN”, “L2”, ”COSINE”, “DOT”. If not specified, the metric set in the vector index is used. If specified, the metric must match with the metric set in the vector index. This optional parameter allows multiple indexes to be attached to the same field in a document. | +| nprobes | :material-close: | Number of buckets to search for the nearby vectors. If not specified, the nprobes set in the vector index is used. | +| accurate | :material-close: | If not present, false will be used, which means that the quantized/encoded vectors in the index will be used for calculating the distance.

    IMPORTANT: Only accurate = false is supported | + +### Use `APPROX_VECTOR_DISTANCE()` + +```kotlin +// Create a vector search query by using the approx_vector_distance() in WHERE clause. +val sql = $$""" + SELECT meta().id, color + FROM _default.colors + WHERE approx_vector_distance(vector, $vector) < 0.5 + LIMIT 8 +""".trimIndent() + +val query = database.createQuery(sql) + +// Get a vector, an array of float numbers, for the input color code (e.g. FF000AA). +// Normally, you will get the vector from your ML model. +val vector = Color.getVector("FF00AA") + ?: throw IllegalStateException("Vector not found") + +// Set the vector array to the parameter "$vector" +val parameters = Parameters() +parameters.setValue("vector", vector) +query.parameters = parameters + +// Execute the query +query.execute().use { rs -> + // process results +} +``` + +This function returns the approximate distance between a given vector, typically generated from your ML model, and an +array of vectors with size equal to the `LIMIT` parameter, collected by a SQL++ query using `APPROX_VECTOR_DISTANCE()`. + +### Prediction with `APPROX_VECTOR_DISTANCE()` + +Below are examples of using `APPROX_VECTOR_DISTANCE()` with an array of vectors generated by the `Prediction()` function +at index time. + +```kotlin +// Create a vector search query that uses prediction() for computing vectors. +val sql = $$""" + SELECT meta().id, color + FROM _default.colors + ORDER BY APPROX_VECTOR_DISTANCE(prediction(ColorModel, {"colorInput": color}).vector, $vector) + LIMIT 8 +""".trimIndent() + +val query = database.createQuery(sql) + +// Get a vector, an array of float numbers, for the input color code (e.g. FF000AA). +// Normally, you will get the vector from your ML model. +val vector = Color.getVector("FF00AA") + ?: throw IllegalStateException("Vector not found") + +// Set the vector array to the parameter "$vector" +val parameters = Parameters() +parameters.setValue("vector", vector) +query.parameters = parameters + +// Execute the query +query.execute().use { rs -> + // process results +} +``` diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 050d7d320..93e81a7ca 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ mockk = "1.14.6" [plugins] android-kotlin-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version = "8.12.3" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } -kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version = "2.2.20" } +kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version = "2.2.21" } kotlinx-atomicfu = { id = "org.jetbrains.kotlinx.atomicfu", version = "0.29.0" } kotlinx-binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version = "0.18.1" } vanniktech-maven-publish = { id = "com.vanniktech.maven.publish", version = "0.34.0" } diff --git a/mkdocs.yml b/mkdocs.yml index 833d188ae..1644f1ba0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -83,6 +83,7 @@ extra: default: current version_full: 3.1.11-1.1.2 version_objc: 3.1.10 + version_vector_search: 1.0.0 generator: false social: - icon: fontawesome/brands/github @@ -124,7 +125,11 @@ nav: - Query Result Sets: query-result-sets.md - Live Queries: live-queries.md - Query Troubleshooting: query-troubleshooting.md - - Full Text Search: full-text-search.md + - Search: + - Full Text Search: full-text-search.md + - Vector Search: + - About Vector Search: vector-search.md + - Working with Vector Search: working-with-vector-search.md - Indexing: indexing.md - Data Sync: - Remote Sync Gateway: remote-sync-gateway.md @@ -135,6 +140,8 @@ nav: - Active Peer: active-peer.md - Integrate Custom Listener: integrate-custom-listener.md - Handling Data Conflicts: handling-data-conflicts.md - - Using Logs: using-logs.md + - Using Logs: + - Legacy Logging API: legacy-logging-api.md + - New Logging API: new-logging-api.md - Kotlin Extensions: kotlin-extensions.md - API Reference: ../api" target="_blank diff --git a/testing-support/src/appleMain/kotlin/kotbase/test/Annotations.apple.kt b/testing-support/src/appleMain/kotlin/kotbase/test/Annotations.apple.kt index 89dd146b3..f278fd81e 100755 --- a/testing-support/src/appleMain/kotlin/kotbase/test/Annotations.apple.kt +++ b/testing-support/src/appleMain/kotlin/kotbase/test/Annotations.apple.kt @@ -23,3 +23,6 @@ actual typealias IgnoreApple = kotlin.test.Ignore @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreLinuxMingw + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreMingw diff --git a/testing-support/src/commonMain/kotlin/kotbase/test/Annotations.kt b/testing-support/src/commonMain/kotlin/kotbase/test/Annotations.kt index 87c4389c6..f2b2b42e6 100755 --- a/testing-support/src/commonMain/kotlin/kotbase/test/Annotations.kt +++ b/testing-support/src/commonMain/kotlin/kotbase/test/Annotations.kt @@ -26,3 +26,6 @@ expect annotation class IgnoreApple() @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) expect annotation class IgnoreLinuxMingw() + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +expect annotation class IgnoreMingw() diff --git a/testing-support/src/jvmCommonMain/kotlin/kotbase/test/Annotations.jvmCommon.kt b/testing-support/src/jvmCommonMain/kotlin/kotbase/test/Annotations.jvmCommon.kt index 5b688ec2a..52248b890 100755 --- a/testing-support/src/jvmCommonMain/kotlin/kotbase/test/Annotations.jvmCommon.kt +++ b/testing-support/src/jvmCommonMain/kotlin/kotbase/test/Annotations.jvmCommon.kt @@ -24,3 +24,6 @@ actual annotation class IgnoreApple @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) actual annotation class IgnoreLinuxMingw + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreMingw diff --git a/testing-support/src/linuxMain/kotlin/kotbase/test/Annotations.linux.kt b/testing-support/src/linuxMain/kotlin/kotbase/test/Annotations.linux.kt new file mode 100644 index 000000000..bdcd98166 --- /dev/null +++ b/testing-support/src/linuxMain/kotlin/kotbase/test/Annotations.linux.kt @@ -0,0 +1,4 @@ +package kotbase.test + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreMingw diff --git a/testing-support/src/mingwMain/kotlin/kotbase/test/Annotations.mingw.kt b/testing-support/src/mingwMain/kotlin/kotbase/test/Annotations.mingw.kt new file mode 100644 index 000000000..ba1d7fdd7 --- /dev/null +++ b/testing-support/src/mingwMain/kotlin/kotbase/test/Annotations.mingw.kt @@ -0,0 +1,3 @@ +package kotbase.test + +actual typealias IgnoreMingw = kotlin.test.Ignore