All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.couchbase.client.kotlin.kv.MutateInSpec.kt Maven / Gradle / Ivy

There is a newer version: 1.4.7
Show newest version
/*
 * Copyright 2021 Couchbase, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.couchbase.client.kotlin.kv

import com.couchbase.client.core.api.kv.CoreSubdocMutateCommand
import com.couchbase.client.core.error.subdoc.PathExistsException
import com.couchbase.client.core.error.subdoc.ValueInvalidException
import com.couchbase.client.core.json.Mapper
import com.couchbase.client.core.msg.kv.CodecFlags.JSON_COMPAT_FLAGS
import com.couchbase.client.core.msg.kv.SubdocCommandType
import com.couchbase.client.kotlin.codec.Content
import com.couchbase.client.kotlin.codec.JsonSerializer
import com.couchbase.client.kotlin.codec.TypeRef
import com.couchbase.client.kotlin.codec.typeRef
import java.io.ByteArrayOutputStream
import kotlin.DeprecationLevel.WARNING

public class SubdocLong internal constructor(
    public val path: String,
    public val xattr: Boolean,
    internal val spec: MutateInSpec,
    internal val index: Int,
) {
    public fun get(result: MutateInResult): Long = with(result) { value }

    override fun toString(): String {
        return "SubdocLong(path='$path', xattr=$xattr)"
    }
}

public class MutateInSpec {

    internal class Command(
        private val type: SubdocCommandType,
        private val path: String,
        private val xattr: Boolean,
        private val fragment: T,
        private val fragmentType: TypeRef,
        private val createParent: Boolean?,
        private val arrayElementType: TypeRef<*>?,
    ) {
        internal fun encode(defaultCreateParent: Boolean, serializer: JsonSerializer): CoreSubdocMutateCommand {
            if (fragment is MutateInMacro) {
                return CoreSubdocMutateCommand(
                    type,
                    path,
                    serializer.serialize(fragment.value, typeRef()),
                    createParent ?: defaultCreateParent,
                    true, // macro implies xattr
                    true
                )
            }

            val fragmentBytes = when {
                fragment is Content -> {
                    require(fragment.flags == JSON_COMPAT_FLAGS) { "Content must be JSON. Actual flags: ${fragment.flags}" }
                    fragment.bytes
                }
                arrayElementType != null -> serializeArrayFragment(fragment as List<*>, arrayElementType, serializer)
                else -> serializer.serialize(fragment, fragmentType)
            }

            return CoreSubdocMutateCommand(
                type,
                path,
                fragmentBytes,
                createParent ?: defaultCreateParent,
                xattr,
                false
            )
        }
    }

    internal var executed = false
    internal val commands: MutableList> = ArrayList()

    internal fun checkNotExecuted(): Unit =
        check(!executed) { "This MutateInSpec has already been executed. Create a fresh one for each call to Collection.mutateIn()." }

    @PublishedApi
    internal fun  addCommand(
        type: SubdocCommandType,
        path: String,
        xattr: Boolean,
        fragment: T,
        fragmentType: TypeRef,
        createParent: Boolean? = null,
        arrayElementType: TypeRef<*>? = null,
    ) {
        checkNotExecuted()
        commands.add(Command(type, path, xattr, fragment, fragmentType, createParent, arrayElementType))
    }

    /**
     * Inserts an Object node field. Fails if the field already exists.
     */
    public inline fun  insert(
        path: String,
        value: T,
        xattr: Boolean = false,
    ): Unit = addCommand(
        SubdocCommandType.DICT_ADD, path, xattr, value, typeRef(),
    )

    /**
     * Upserts an Object node field.
     */
    public inline fun  upsert(
        path: String,
        value: T,
        xattr: Boolean = false,
    ): Unit = addCommand(
        SubdocCommandType.DICT_UPSERT, path, xattr, value, typeRef(),
    )

    /**
     * Replaces an Object node field or Array element.
     */
    public inline fun  replace(
        path: String,
        value: T,
        xattr: Boolean = false,
    ): Unit {
        val type = if (path.isEmpty()) SubdocCommandType.SET_DOC else SubdocCommandType.REPLACE
        return addCommand(type, path, xattr, value, typeRef(), createParent = false)
    }

    /**
     * Removes the targeted element (or whole doc if path is empty)
     */
    public fun remove(
        path: String,
        xattr: Boolean = false,
    ): Unit {
        val type = if (path.isEmpty()) SubdocCommandType.DELETE_DOC else SubdocCommandType.DELETE
        addCommand(type, path, xattr, Content.json(byteArrayOf()), typeRef(), createParent = false)
    }

    public inline fun  arrayAppend(
        path: String, values: List,
        xattr: Boolean = false,
    ): Unit = addCommand(
        SubdocCommandType.ARRAY_PUSH_LAST, path, xattr, values, typeRef(), arrayElementType = typeRef()
    )

    public inline fun  arrayPrepend(
        path: String, values: List,
        xattr: Boolean = false,
    ): Unit = addCommand(
        SubdocCommandType.ARRAY_PUSH_FIRST, path, xattr, values, typeRef(), arrayElementType = typeRef()
    )

    public inline fun  arrayInsert(
        path: String, values: List,
        xattr: Boolean = false,
    ): Unit = addCommand(
        SubdocCommandType.ARRAY_INSERT,
        path,
        xattr,
        values,
        typeRef(),
        createParent = false,
        arrayElementType = typeRef()
    )

    /**
     * Adds the value to an array (creating the array if it doesn't already exist)
     * or fail with [PathExistsException] if the array already contains the value.
     *
     * @param path Path to the array to modify or create
     * @param value Value to add to the array if not already present
     */
    public fun arrayAddUnique(
        path: String,
        value: Boolean?,
        xattr: Boolean = false,
    ): Unit = doArrayAddUnique(path, value, xattr)

    /**
     * Adds the value to an array (creating the array if it doesn't already exist)
     * or fail with [PathExistsException] if the array already contains the value.
     *
     * @param path Path to the array to modify or create
     * @param value Value to add to the array if not already present
     */
    public fun arrayAddUnique(
        path: String,
        value: String?,
        xattr: Boolean = false,
    ): Unit = doArrayAddUnique(path, value, xattr)

    /**
     * Adds the value to an array (creating the array if it doesn't already exist)
     * or fail with [PathExistsException] if the array already contains the value.
     *
     * @param path Path to the array to modify or create
     * @param value Value to add to the array if not already present
     */
    public fun arrayAddUnique(
        path: String,
        value: Long?,
        xattr: Boolean = false,
    ): Unit = doArrayAddUnique(path, value, xattr)

    /**
     * Adds the value to an array (creating the array if it doesn't already exist)
     * or fail with [PathExistsException] if the array already contains the value.
     *
     * @param path Path to the array to modify or create
     * @param value Value to add to the array if not already present
     */
    public fun arrayAddUnique(
        path: String,
        value: Int?,
        xattr: Boolean = false,
    ): Unit = doArrayAddUnique(path, value, xattr)

    private fun doArrayAddUnique(
        path: String,
        value: Any?,
        xattr: Boolean,
    ): Unit = addCommand(
        SubdocCommandType.ARRAY_ADD_UNIQUE,
        path,
        xattr,
        Content.json(Mapper.encodeAsBytes(value)),
        typeRef()
    )

    /**
     * Adds [delta] to an integral number field. If the field does not exist,
     * creates it with a value of [delta].
     *
     * **Note** `mutateIn` throws [ValueInvalidException] if this operation
     * would overflow or underflow a signed 64-bit integer.
     *
     * @return A handle for getting the new value from the [MutateInResult].
     */
    public fun addAndGet(
        path: String,
        delta: Long,
        xattr: Boolean = false,
    ): SubdocLong {
        val subdoc = SubdocLong(path, xattr, this, commands.size)
        addCommand(SubdocCommandType.COUNTER, path, xattr, delta, typeRef())
        return subdoc
    }

    /**
     * Convenience method equivalent to [addAndGet] with a delta of 1.
     */
    public fun incrementAndGet(
        path: String,
        xattr: Boolean = false,
    ): SubdocLong = addAndGet(path, 1, xattr)

    /**
     * Convenience method equivalent to [addAndGet] with a delta of -1.
     */
    public fun decrementAndGet(
        path: String,
        xattr: Boolean = false,
    ): SubdocLong = addAndGet(path, -1, xattr)

    @Deprecated(
        level = WARNING,
        message = "Please call addAndGet instead of passing a custom delta to incrementAndGet",
        replaceWith = ReplaceWith("addAndGet(path, delta, xattr)")
    )
    public fun incrementAndGet(
        path: String,
        delta: Long = 1,
        xattr: Boolean = false,
    ): SubdocLong = addAndGet(path, delta, xattr)

    @Deprecated(
        level = WARNING,
        message = "Please call addAndGet with a negative delta instead of passing a custom delta to decrementAndGet. Note that negating Long.MIN_VALUE has no effect.",
        replaceWith = ReplaceWith("addAndGet(path, -delta, xattr)")
    )
    public fun decrementAndGet(
        path: String,
        delta: Long = 1,
        xattr: Boolean = false,
    ): SubdocLong = addAndGet(path, -delta, xattr)
}

private fun  serializeArrayFragment(
    values: List<*>,
    elementType: TypeRef,
    serializer: JsonSerializer
): ByteArray = values
    .map {
        require(it !is MutateInMacro) { "MutateInMacro in subdoc array not supported" }

        @Suppress("UNCHECKED_CAST")
        serializer.serialize(it as T, elementType)
    }
    .join(delimiter = byteArrayOf(','.code.toByte()))

private fun List.join(delimiter: ByteArray = byteArrayOf()): ByteArray {
    val resultSize = map { it.size }.sum() + delimiter.size * (size - 1)
    val result = ByteArrayOutputStream(resultSize)
    forEachIndexed { i, value ->
        result.write(value)
        if (i != size - 1) result.write(delimiter)
    }
    return result.toByteArray()
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy