diff --git a/src/routes/docs/products/databases/+layout.svelte b/src/routes/docs/products/databases/+layout.svelte index da40baa0e5..09b06b7817 100644 --- a/src/routes/docs/products/databases/+layout.svelte +++ b/src/routes/docs/products/databases/+layout.svelte @@ -105,6 +105,11 @@ label: 'CSV imports', href: '/docs/products/databases/csv-imports', new: isNewUntil('31 Jul 2025') + }, + { + label: 'Database operators', + href: '/docs/products/databases/db-operators', + new: isNewUntil('31 Dec 2025') } ] }, diff --git a/src/routes/docs/products/databases/db-operators/+page.markdoc b/src/routes/docs/products/databases/db-operators/+page.markdoc new file mode 100644 index 0000000000..2d7817826e --- /dev/null +++ b/src/routes/docs/products/databases/db-operators/+page.markdoc @@ -0,0 +1,1059 @@ +--- +layout: article +title: Database operators +description: Update multiple fields atomically without fetching the full row. Perform numeric, array, string, and date updates in a single, consistent workflow. +--- + +Database operators let you update fields directly on the server without fetching the full row. Instead of sending new values, you describe the action you want: increment, append, replace, or adjust. This eliminates race conditions and reduces bandwidth usage when updating any values that need to be modified atomically. The operation is applied atomically at the storage layer for safe, concurrent updates. + +- Atomic by field: Each operation is applied safely at the storage layer to prevent lost updates under concurrency. +- Multi-field updates: Apply multiple operations across different fields in a single request or transaction. +- Type-safe: Operators are exposed through typed SDK methods for clarity and safety. +- Transaction-ready: Operators can be staged and committed alongside other database actions for consistent writes. + +# How database operators work {% #how-database-operators-work %} + +Instead of the traditional **read-modify-write** pattern, database operators use dedicated methods to modify values directly on the server. The server applies the change atomically under concurrency control and returns the new value. + +Let's take an example of appending a value to an array field. + +**Traditional approach:** +1. Fetch row → `{ letters: ['a', 'b' ] }` +2. Update client-side → `letters: ['a', 'b', 'c']` +3. Write back → `{ letters: ['a', 'b', 'c'] }` + +**Operator approach:** +1. Update/upsert the row with the appropriate value to append +2. Server applies atomically → `letters: ['a', 'b', 'c']` + +Here's how you can do so programmatically: + +{% multicode %} +```client-web +import { Client, TablesDB, Operator } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const tablesDB = new TablesDB(client); + +await tablesDB.updateRow({ + databaseId: "", + tableId: "", + rowId: "", + data: { + letters: Operator.arrayAppend(['c']) + } +}); +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client(); +client + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const tablesDB = new sdk.TablesDB(client); + +const result = await tablesDB.updateRow({ + databaseId: '', + tableId: '', + rowId: '', + data: { + letters: sdk.Operator.arrayAppend(['c']) + } +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final tablesDB = TablesDB(client); + + try { + await tablesDB.updateRow( + '', + '', + '', + { + 'letters': Operator.arrayAppend(['c']) + }, + ); + } on AppwriteException catch (e) { + print(e); + } +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let tablesDB = TablesDB(client) + + do { + _ = try await tablesDB.updateRow( + databaseId: "", + tableId: "", + rowId: "", + data: [ + "letters": Operator.arrayAppend(["c"]) + ] + ) + } catch { + print(error.localizedDescription) + } +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.TablesDB +import io.appwrite.Operator + +suspend fun main() { + val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val tablesDB = TablesDB(client) + + tablesDB.updateRow( + databaseId = "", + tableId = "", + rowId = "", + data = mapOf( + "letters" to Operator.arrayAppend(listOf("c")) + ) + ) +} +``` +```server-go +package main + +import ( + "log" + "github.com/appwrite/sdk-for-go/appwrite" + operator "github.com/appwrite/sdk-for-go/operator" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + tablesDB := appwrite.NewTablesDB(client) + + _, err := tablesDB.UpdateRow( + "", + "", + "", + tablesDB.WithUpdateRowData(map[string]any{ + "letters": operator.ArrayAppend([]string{"c"}), + }), + ) + if err != nil { + log.Fatal(err) + } +} +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$tablesDB = new TablesDB($client); + +$result = $tablesDB->updateRow( + '', + '', + '', + [ 'letters' => Operator::arrayAppend(['c']) ] +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.tablesDB import TablesDB +from appwrite.operator import Operator + +client = Client() +(client + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('')) + +tablesDB = TablesDB(client) + +result = tablesDB.update_row( + '', + '', + '', + { 'letters': Operator.arrayAppend(['c']) } +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +var client = new Client() + .SetEndpoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +var tablesDB = new TablesDB(client); + +await tablesDB.UpdateRow( + databaseId: "", + tableId: "", + rowId: "", + data: new Dictionary + { + { "letters", Operator.ArrayAppend(new[] { "c" }) } + } +); +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new() + +client + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +tablesDB = TablesDB.new(client) + +result = tablesDB.update_row( + '', + '', + '', + { 'letters' => Operator.arrayAppend(['c']) } +) +``` +```server-java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.TablesDB; +import io.appwrite.Operator; +import java.util.*; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey(""); + +TablesDB tablesDB = new TablesDB(client); + +tablesDB.updateRow( + "", + "", + "", + Map.of("letters", Operator.arrayAppend(List.of("c"))), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + System.out.println(result); + }) +); +``` +{% /multicode %} + + +# When to use database operators {% #when-to-use %} + +Use operators when you need to: + +- Update fields frequently under concurrency (likes, scores, credits, inventory) +- Edit lists/tags without rewriting whole arrays +- Make small text changes in-place +- Adjust dates for lifecycle events or scheduling + +This keeps payloads small, avoids race conditions, and reduces round-trips. + +# Operator categories {% #operators %} + +The following operators are available, grouped by field type. Each operator updates the given column atomically on the server. + +## Numeric operators {% #numeric %} + +Perform arithmetic on numeric fields without reading the row first. + +### increment {% #increment %} + +Increase a numeric field by a specified value. Optionally cap the result at a maximum value. + +{% multicode %} +```client-web +Operator.increment(1) +``` +```server-python +Operator.increment(1) +``` +```server-php +Operator::increment(1) +``` +```client-apple +Operator.increment(1) +``` +```client-android-kotlin +Operator.increment(1) +``` +```server-go +operator.Increment(1) +``` +```client-flutter +Operator.increment(1) +``` +```server-dotnet +Operator.Increment(1) +``` +```server-ruby +Operator.increment(1) +``` +```server-java +Operator.increment(1) +``` +{% /multicode %} + +### decrement {% #decrement %} + +Decrease a numeric field by a specified value. Optionally cap the result at a minimum value. + +{% multicode %} +```client-web +Operator.decrement(1) +``` +```server-python +Operator.decrement(1) +``` +```server-php +Operator::decrement(1) +``` +```client-apple +Operator.decrement(1) +``` +```client-android-kotlin +Operator.decrement(1) +``` +```server-go +operator.Decrement(1) +``` +```client-flutter +Operator.decrement(1) +``` +```server-dotnet +Operator.Decrement(1) +``` +```server-ruby +Operator.decrement(1) +``` +```server-java +Operator.decrement(1) +``` +{% /multicode %} + +### multiply {% #multiply %} + +Multiply a numeric field by a specified factor. Optionally cap the result at a maximum value. + +{% multicode %} +```client-web +Operator.multiply(2) +``` +```server-python +Operator.multiply(2) +``` +```server-php +Operator::multiply(2) +``` +```client-apple +Operator.multiply(2) +``` +```client-android-kotlin +Operator.multiply(2) +``` +```server-go +operator.Multiply(2) +``` +```client-flutter +Operator.multiply(2) +``` +```server-dotnet +Operator.Multiply(2) +``` +```server-ruby +Operator.multiply(2) +``` +```server-java +Operator.multiply(2) +``` +{% /multicode %} + +### divide {% #divide %} + +Divide a numeric field by a specified divisor. Optionally cap the result at a minimum value. Divisor cannot be zero. + +{% multicode %} +```client-web +Operator.divide(5) +``` +```server-python +Operator.divide(5) +``` +```server-php +Operator::divide(5) +``` +```client-apple +Operator.divide(5) +``` +```client-android-kotlin +Operator.divide(5) +``` +```server-go +operator.Divide(5) +``` +```client-flutter +Operator.divide(5) +``` +```server-dotnet +Operator.Divide(5) +``` +```server-ruby +Operator.divide(5) +``` +```server-java +Operator.divide(5) +``` +{% /multicode %} + +### modulo {% #modulo %} + +Set a numeric field to the remainder of itself divided by a specified value. + +{% multicode %} +```client-web +Operator.modulo(3) +``` +```server-python +Operator.modulo(3) +``` +```server-php +Operator::modulo(3) +``` +```client-apple +Operator.modulo(3) +``` +```client-android-kotlin +Operator.modulo(3) +``` +```server-go +operator.Modulo(3) +``` +```client-flutter +Operator.modulo(3) +``` +```server-dotnet +Operator.Modulo(3) +``` +```server-ruby +Operator.modulo(3) +``` +```server-java +Operator.modulo(3) +``` +{% /multicode %} + +### power {% #power %} + +Raise a numeric field to a specified exponent. Optionally cap the result at a maximum value. + +{% multicode %} +```client-web +Operator.power(2) +``` +```server-python +Operator.power(2) +``` +```server-php +Operator::power(2) +``` +```client-apple +Operator.power(2) +``` +```client-android-kotlin +Operator.power(2) +``` +```server-go +operator.Power(2) +``` +```client-flutter +Operator.power(2) +``` +```server-dotnet +Operator.Power(2) +``` +```server-ruby +Operator.power(2) +``` +```server-java +Operator.power(2) +``` +{% /multicode %} + +## Array operators {% #array %} + +Edit lists in place: append, remove, or modify array items atomically. + +### arrayAppend {% #array-append %} + +Add one or more elements to the end of an array. + +{% multicode %} +```client-web +Operator.arrayAppend(['c']) +``` +```server-python +Operator.arrayAppend(['c']) +``` +```server-php +Operator::arrayAppend(['c']) +``` +```client-apple +Operator.arrayAppend(["c"]) +``` +```client-android-kotlin +Operator.arrayAppend(listOf("c")) +``` +```server-go +operator.ArrayAppend([]string{"c"}) +``` +```client-flutter +Operator.arrayAppend(['c']) +``` +```server-dotnet +Operator.ArrayAppend(new[] { "c" }) +``` +```server-ruby +Operator.arrayAppend(['c']) +``` +```server-java +Operator.arrayAppend(List.of("c")) +``` +{% /multicode %} + +### arrayPrepend {% #array-prepend %} + +Add one or more elements to the beginning of an array. + +{% multicode %} +```client-web +Operator.arrayPrepend(['z']) +``` +```server-python +Operator.arrayPrepend(['z']) +``` +```server-php +Operator::arrayPrepend(['z']) +``` +```client-apple +Operator.arrayPrepend(["z"]) +``` +```client-android-kotlin +Operator.arrayPrepend(listOf("z")) +``` +```server-go +operator.ArrayPrepend([]string{"z"}) +``` +```client-flutter +Operator.arrayPrepend(['z']) +``` +```server-dotnet +Operator.ArrayPrepend(new[] { "z" }) +``` +```server-ruby +Operator.arrayPrepend(['z']) +``` +```server-java +Operator.arrayPrepend(List.of("z")) +``` +{% /multicode %} + +### arrayInsert {% #array-insert %} + +Insert an element at a specific index in an array. + +{% multicode %} +```client-web +Operator.arrayInsert(1, 'x') +``` +```server-python +Operator.arrayInsert(1, 'x') +``` +```server-php +Operator::arrayInsert(1, 'x') +``` +```client-apple +Operator.arrayInsert(1, "x") +``` +```client-android-kotlin +Operator.arrayInsert(1, "x") +``` +```server-go +operator.ArrayInsert(1, "x") +``` +```client-flutter +Operator.arrayInsert(1, 'x') +``` +```server-dotnet +Operator.ArrayInsert(1, "x") +``` +```server-ruby +Operator.arrayInsert(1, 'x') +``` +```server-java +Operator.arrayInsert(1, "x") +``` +{% /multicode %} + +### arrayRemove {% #array-remove %} + +Remove a specified element from an array. + +{% multicode %} +```client-web +Operator.arrayRemove('b') +``` +```server-python +Operator.arrayRemove('b') +``` +```server-php +Operator::arrayRemove('b') +``` +```client-apple +Operator.arrayRemove("b") +``` +```client-android-kotlin +Operator.arrayRemove("b") +``` +```server-go +operator.ArrayRemove("b") +``` +```client-flutter +Operator.arrayRemove('b') +``` +```server-dotnet +Operator.ArrayRemove("b") +``` +```server-ruby +Operator.arrayRemove('b') +``` +```server-java +Operator.arrayRemove("b") +``` +{% /multicode %} + +### arrayUnique {% #array-unique %} + +Remove duplicate elements from an array. + +{% multicode %} +```client-web +Operator.arrayUnique() +``` +```server-python +Operator.arrayUnique() +``` +```server-php +Operator::arrayUnique() +``` +```client-apple +Operator.arrayUnique() +``` +```client-android-kotlin +Operator.arrayUnique() +``` +```server-go +operator.ArrayUnique() +``` +```client-flutter +Operator.arrayUnique() +``` +```server-dotnet +Operator.ArrayUnique() +``` +```server-ruby +Operator.arrayUnique() +``` +```server-java +Operator.arrayUnique() +``` +{% /multicode %} + +### arrayIntersect {% #array-intersect %} + +Keep only elements that exist in both arrays. + +{% multicode %} +```client-web +Operator.arrayIntersect(['news', 'tech']) +``` +```server-python +Operator.arrayIntersect(['news', 'tech']) +``` +```server-php +Operator::arrayIntersect(['news', 'tech']) +``` +```client-apple +Operator.arrayIntersect(["news", "tech"]) +``` +```client-android-kotlin +Operator.arrayIntersect(listOf("news", "tech")) +``` +```server-go +operator.ArrayIntersect([]string{"news", "tech"}) +``` +```client-flutter +Operator.arrayIntersect(['news', 'tech']) +``` +```server-dotnet +Operator.ArrayIntersect(new[] { "news", "tech" }) +``` +```server-ruby +Operator.arrayIntersect(['news', 'tech']) +``` +```server-java +Operator.arrayIntersect(new String[] {"news", "tech"}) +``` +{% /multicode %} + +### arrayDiff {% #array-diff %} + +Return elements that exist in the current array but not in the provided array. + +{% multicode %} +```client-web +Operator.arrayDiff(['old']) +``` +```server-python +Operator.arrayDiff(['old']) +``` +```server-php +Operator::arrayDiff(['old']) +``` +```client-apple +Operator.arrayDiff(["old"]) +``` +```client-android-kotlin +Operator.arrayDiff(listOf("old")) +``` +```server-go +operator.ArrayDiff([]string{"old"}) +``` +```client-flutter +Operator.arrayDiff(['old']) +``` +```server-dotnet +Operator.ArrayDiff(new[] { "old" }) +``` +```server-ruby +Operator.arrayDiff(['old']) +``` +```server-java +Operator.arrayDiff(new String[] {"old"}) +``` +{% /multicode %} + +### arrayFilter {% #array-filter %} + +Filter array elements based on a condition. + +{% multicode %} +```client-web +Operator.arrayFilter(Condition.GreaterThan, 10) +``` +```server-python +Operator.arrayFilter(Condition.GreaterThan, 10) +``` +```server-php +Operator::arrayFilter(Condition::GreaterThan, 10) +``` +```client-apple +Operator.arrayFilter(Condition.GreaterThan, 10) +``` +```client-android-kotlin +Operator.arrayFilter(Condition.GreaterThan, 10) +``` +```server-go +operator.ArrayFilter(ConditionGreaterThan, 10) +``` +```client-flutter +Operator.arrayFilter(Condition.GreaterThan, 10) +``` +```server-dotnet +Operator.ArrayFilter(Condition.GreaterThan, 10) +``` +```server-ruby +Operator.arrayFilter(Condition.GreaterThan, 10) +``` +```server-java +Operator.arrayFilter(Condition.GreaterThan, 10) +``` +{% /multicode %} + +## String operators {% #string %} + +Make lightweight text changes without rewriting the whole row. + +### stringConcat {% #string-concat %} + +Concatenate a value to a string or array field. + +{% multicode %} +```client-web +Operator.stringConcat('!') +``` +```server-python +Operator.stringConcat('!') +``` +```server-php +Operator::stringConcat('!') +``` +```client-apple +Operator.stringConcat("!") +``` +```client-android-kotlin +Operator.stringConcat("!") +``` +```server-go +operator.StringConcat("!") +``` +```client-flutter +Operator.stringConcat('!') +``` +```server-dotnet +Operator.StringConcat("!") +``` +```server-ruby +Operator.stringConcat('!') +``` +```server-java +Operator.stringConcat("!") +``` +{% /multicode %} + +### stringReplace {% #string-replace %} + +Replace occurrences of a substring with a new string. + +{% multicode %} +```client-web +Operator.stringReplace('old', 'new') +``` +```server-python +Operator.stringReplace('old', 'new') +``` +```server-php +Operator::stringReplace('old', 'new') +``` +```client-apple +Operator.stringReplace("old", "new") +``` +```client-android-kotlin +Operator.stringReplace("old", "new") +``` +```server-go +operator.StringReplace("old", "new") +``` +```client-flutter +Operator.stringReplace('old', 'new') +``` +```server-dotnet +Operator.StringReplace("old", "new") +``` +```server-ruby +Operator.stringReplace('old', 'new') +``` +```server-java +Operator.stringReplace("old", "new") +``` +{% /multicode %} + +## Date operators {% #date %} + +Adjust time-based fields for lifecycle and scheduling logic. + +### dateAddDays {% #date-add-days %} + +Add a specified number of days to a date field. + +{% multicode %} +```client-web +Operator.dateAddDays(7) +``` +```server-python +Operator.dateAddDays(7) +``` +```server-php +Operator::dateAddDays(7) +``` +```client-apple +Operator.dateAddDays(7) +``` +```client-android-kotlin +Operator.dateAddDays(7) +``` +```server-go +operator.DateAddDays(7) +``` +```client-flutter +Operator.dateAddDays(7) +``` +```server-dotnet +Operator.DateAddDays(7) +``` +```server-ruby +Operator.dateAddDays(7) +``` +```server-java +Operator.dateAddDays(7) +``` +{% /multicode %} + +### dateSubDays {% #date-sub-days %} + +Subtract a specified number of days from a date field. + +{% multicode %} +```client-web +Operator.dateSubDays(3) +``` +```server-python +Operator.dateSubDays(3) +``` +```server-php +Operator::dateSubDays(3) +``` +```client-apple +Operator.dateSubDays(3) +``` +```client-android-kotlin +Operator.dateSubDays(3) +``` +```server-go +operator.DateSubDays(3) +``` +```client-flutter +Operator.dateSubDays(3) +``` +```server-dotnet +Operator.DateSubDays(3) +``` +```server-ruby +Operator.dateSubDays(3) +``` +```server-java +Operator.dateSubDays(3) +``` +{% /multicode %} + +### dateSetNow {% #date-set-now %} + +Set a date field to the current time on the server. + +{% multicode %} +```client-web +Operator.dateSetNow() +``` +```server-python +Operator.dateSetNow() +``` +```server-php +Operator::dateSetNow() +``` +```client-apple +Operator.dateSetNow() +``` +```client-android-kotlin +Operator.dateSetNow() +``` +```server-go +operator.DateSetNow() +``` +```client-flutter +Operator.dateSetNow() +``` +```server-dotnet +Operator.DateSetNow() +``` +```server-ruby +Operator.dateSetNow() +``` +```server-java +Operator.dateSetNow() +``` +{% /multicode %} + +## Boolean operators {% #boolean %} + +Toggle boolean values in place. + +### toggle {% #toggle %} + +Toggle a boolean field between true and false. + +{% multicode %} +```client-web +Operator.toggle() +``` +```server-python +Operator.toggle() +``` +```server-php +Operator::toggle() +``` +```client-apple +Operator.toggle() +``` +```client-android-kotlin +Operator.toggle() +``` +```server-go +operator.Toggle() +``` +```client-flutter +Operator.toggle() +``` +```server-dotnet +Operator.Toggle() +``` +```server-ruby +Operator.toggle() +``` +```server-java +Operator.toggle() +``` +{% /multicode %}