diff --git a/.release-please-manifest.json b/.release-please-manifest.json index adf5fd76..4dedeaeb 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.4.1" + ".": "2.5.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 7ec9737b..dd607793 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 14 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/turbopuffer-benesch/turbopuffer-c47147eabda459ce775ffbab42a531bb3f2778c4cce15f88e05a4eed97013a21.yml -openapi_spec_hash: a981d1b889452ec8c0108ee0ec69dc6d -config_hash: 068c4c3b2277de182691cc65c1e50a7c +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/turbopuffer-benesch/turbopuffer-45f4429a37dcc469269006f05ea38ee3973087f22456b1cdb60b682902997372.yml +openapi_spec_hash: a606206d2ac6886fe31c3392a1f590ea +config_hash: d3fc3b93270f7ee8ae68224dafcfeb6d diff --git a/CHANGELOG.md b/CHANGELOG.md index d50fa76e..aceb03aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 2.5.0 (2026-07-03) + +Full Changelog: [v2.4.1...v2.5.0](https://github.com/turbopuffer/turbopuffer-java/compare/v2.4.1...v2.5.0) + +### Features + +* spec: add sharding config to the openapi spec ([80ab39d](https://github.com/turbopuffer/turbopuffer-java/commit/80ab39dc6139b94e054f2863f610a7a13aa58b8e)) +* support late_interaction parameter in sdks ([3ad0e51](https://github.com/turbopuffer/turbopuffer-java/commit/3ad0e51302ae70de600465b800ac9398bb055b62)) + + +### Chores + +* fix import ordering in generated tests ([#247](https://github.com/turbopuffer/turbopuffer-java/issues/247)) ([1e75734](https://github.com/turbopuffer/turbopuffer-java/commit/1e75734881ca2b8042608ace48450880a5dd03fd)) + ## 2.4.1 (2026-06-25) Full Changelog: [v2.4.0...v2.4.1](https://github.com/turbopuffer/turbopuffer-java/compare/v2.4.0...v2.4.1) diff --git a/README.md b/README.md index 02484ea7..f139bca3 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ -[![Maven Central](https://img.shields.io/maven-central/v/com.turbopuffer/turbopuffer-java)](https://central.sonatype.com/artifact/com.turbopuffer/turbopuffer-java/2.4.1) -[![javadoc](https://javadoc.io/badge2/com.turbopuffer/turbopuffer-java/2.4.1/javadoc.svg)](https://javadoc.io/doc/com.turbopuffer/turbopuffer-java/2.4.1) +[![Maven Central](https://img.shields.io/maven-central/v/com.turbopuffer/turbopuffer-java)](https://central.sonatype.com/artifact/com.turbopuffer/turbopuffer-java/2.5.0) +[![javadoc](https://javadoc.io/badge2/com.turbopuffer/turbopuffer-java/2.5.0/javadoc.svg)](https://javadoc.io/doc/com.turbopuffer/turbopuffer-java/2.5.0) @@ -13,7 +13,7 @@ It is generated with [Stainless](https://www.stainless.com/). -The HTTP API documentation can be found at [turbopuffer.com/docs/api-overview](https://turbopuffer.com/docs/api-overview). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.turbopuffer/turbopuffer-java/2.4.1). +The HTTP API documentation can be found at [turbopuffer.com/docs/api-overview](https://turbopuffer.com/docs/api-overview). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.turbopuffer/turbopuffer-java/2.5.0). @@ -24,7 +24,7 @@ The HTTP API documentation can be found at [turbopuffer.com/docs/api-overview](h ### Gradle ```kotlin -implementation("com.turbopuffer:turbopuffer-java:2.4.1") +implementation("com.turbopuffer:turbopuffer-java:2.5.0") ``` ### Maven @@ -33,7 +33,7 @@ implementation("com.turbopuffer:turbopuffer-java:2.4.1") com.turbopuffer turbopuffer-java - 2.4.1 + 2.5.0 ``` diff --git a/build.gradle.kts b/build.gradle.kts index 86e833f5..2bd71d69 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ repositories { allprojects { group = "com.turbopuffer" - version = "2.4.1" // x-release-please-version + version = "2.5.0" // x-release-please-version } subprojects { diff --git a/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/AttributeSchemaConfig.kt b/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/AttributeSchemaConfig.kt index 1503b678..14ba1aa7 100644 --- a/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/AttributeSchemaConfig.kt +++ b/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/AttributeSchemaConfig.kt @@ -729,6 +729,7 @@ private constructor( @JsonCreator(mode = JsonCreator.Mode.DISABLED) private constructor( private val distanceMetric: JsonField, + private val lateInteraction: JsonField, private val additionalProperties: MutableMap, ) { @@ -736,8 +737,11 @@ private constructor( private constructor( @JsonProperty("distance_metric") @ExcludeMissing - distanceMetric: JsonField = JsonMissing.of() - ) : this(distanceMetric, mutableMapOf()) + distanceMetric: JsonField = JsonMissing.of(), + @JsonProperty("late_interaction") + @ExcludeMissing + lateInteraction: JsonField = JsonMissing.of(), + ) : this(distanceMetric, lateInteraction, mutableMapOf()) /** * A function used to calculate vector similarity. @@ -748,6 +752,17 @@ private constructor( fun distanceMetric(): Optional = distanceMetric.getOptional("distance_metric") + /** + * Opt in to late-interaction (MUVERA) indexing. Only valid on fixed-dim `[][N]f32` + * vector array attributes, and is required to enable an ANN index on such attributes. + * Defaults to `false`. + * + * @throws TurbopufferInvalidDataException if the JSON field has an unexpected type + * (e.g. if the server responded with an unexpected value). + */ + fun lateInteraction(): Optional = + lateInteraction.getOptional("late_interaction") + /** * Returns the raw JSON value of [distanceMetric]. * @@ -758,6 +773,16 @@ private constructor( @ExcludeMissing fun _distanceMetric(): JsonField = distanceMetric + /** + * Returns the raw JSON value of [lateInteraction]. + * + * Unlike [lateInteraction], this method doesn't throw if the JSON field has an + * unexpected type. + */ + @JsonProperty("late_interaction") + @ExcludeMissing + fun _lateInteraction(): JsonField = lateInteraction + @JsonAnySetter private fun putAdditionalProperty(key: String, value: JsonValue) { additionalProperties.put(key, value) @@ -780,11 +805,13 @@ private constructor( class Builder internal constructor() { private var distanceMetric: JsonField = JsonMissing.of() + private var lateInteraction: JsonField = JsonMissing.of() private var additionalProperties: MutableMap = mutableMapOf() @JvmSynthetic internal fun from(annConfig: AnnConfig) = apply { distanceMetric = annConfig.distanceMetric + lateInteraction = annConfig.lateInteraction additionalProperties = annConfig.additionalProperties.toMutableMap() } @@ -803,6 +830,25 @@ private constructor( this.distanceMetric = distanceMetric } + /** + * Opt in to late-interaction (MUVERA) indexing. Only valid on fixed-dim `[][N]f32` + * vector array attributes, and is required to enable an ANN index on such + * attributes. Defaults to `false`. + */ + fun lateInteraction(lateInteraction: Boolean) = + lateInteraction(JsonField.of(lateInteraction)) + + /** + * Sets [Builder.lateInteraction] to an arbitrary JSON value. + * + * You should usually call [Builder.lateInteraction] with a well-typed [Boolean] + * value instead. This method is primarily for setting the field to an undocumented + * or not yet supported value. + */ + fun lateInteraction(lateInteraction: JsonField) = apply { + this.lateInteraction = lateInteraction + } + fun additionalProperties(additionalProperties: Map) = apply { this.additionalProperties.clear() putAllAdditionalProperties(additionalProperties) @@ -831,7 +877,7 @@ private constructor( * Further updates to this [Builder] will not mutate the returned instance. */ fun build(): AnnConfig = - AnnConfig(distanceMetric, additionalProperties.toMutableMap()) + AnnConfig(distanceMetric, lateInteraction, additionalProperties.toMutableMap()) } private var validated: Boolean = false @@ -852,6 +898,7 @@ private constructor( } distanceMetric().ifPresent { it.validate() } + lateInteraction() validated = true } @@ -870,7 +917,9 @@ private constructor( * Used for best match union deserialization. */ @JvmSynthetic - internal fun validity(): Int = (distanceMetric.asKnown().getOrNull()?.validity() ?: 0) + internal fun validity(): Int = + (distanceMetric.asKnown().getOrNull()?.validity() ?: 0) + + (if (lateInteraction.asKnown().isPresent) 1 else 0) override fun equals(other: Any?): Boolean { if (this === other) { @@ -879,15 +928,18 @@ private constructor( return other is AnnConfig && distanceMetric == other.distanceMetric && + lateInteraction == other.lateInteraction && additionalProperties == other.additionalProperties } - private val hashCode: Int by lazy { Objects.hash(distanceMetric, additionalProperties) } + private val hashCode: Int by lazy { + Objects.hash(distanceMetric, lateInteraction, additionalProperties) + } override fun hashCode(): Int = hashCode override fun toString() = - "AnnConfig{distanceMetric=$distanceMetric, additionalProperties=$additionalProperties}" + "AnnConfig{distanceMetric=$distanceMetric, lateInteraction=$lateInteraction, additionalProperties=$additionalProperties}" } } diff --git a/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/CustomTypes.kt b/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/CustomTypes.kt index 3e459744..638a7ade 100644 --- a/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/CustomTypes.kt +++ b/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/CustomTypes.kt @@ -1106,6 +1106,10 @@ sealed class RankBy() { @JvmStatic public fun knn(attr: String, value: List): RankByKnn = RankByKnn.create(attr, value) + @JvmStatic + public fun knn(attr: String, value: List>): RankByKnnMulti = + RankByKnnMulti.create(attr, value) + @JvmStatic public fun knn(attr: String, expr: Expr): RankByKnnExpr = RankByKnnExpr.create(attr, expr) @@ -1264,6 +1268,25 @@ class RankByKnnExpr private constructor(attr: String, expr: Expr) : RankBy() { } } +@JsonAutoDetect(fieldVisibility = Visibility.ANY) +@JsonFormat(shape = JsonFormat.Shape.ARRAY) +@JsonPropertyOrder("attr", "f0", "value") +class RankByKnnMulti private constructor(attr: String, value: List>) : RankBy() { + private val attr: String = attr + private val f0: String = "kNN" + private val value: List> = value + + override fun toString(): String { + return jsonMapper.writeValueAsString(this) + } + + companion object { + @JvmSynthetic + internal fun create(attr: String, value: List>): RankByKnnMulti = + RankByKnnMulti(attr, value) + } +} + @JsonAutoDetect(fieldVisibility = Visibility.ANY) @JsonFormat(shape = JsonFormat.Shape.ARRAY) @JsonPropertyOrder("attr", "f0", "value") diff --git a/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/NamespaceMetadata.kt b/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/NamespaceMetadata.kt index 2c46d3c6..36bbdff0 100644 --- a/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/NamespaceMetadata.kt +++ b/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/NamespaceMetadata.kt @@ -42,6 +42,7 @@ private constructor( private val schema: JsonField, private val updatedAt: JsonField, private val pinning: JsonField, + private val sharding: JsonField, private val additionalProperties: MutableMap, ) { @@ -65,6 +66,9 @@ private constructor( @ExcludeMissing updatedAt: JsonField = JsonMissing.of(), @JsonProperty("pinning") @ExcludeMissing pinning: JsonField = JsonMissing.of(), + @JsonProperty("sharding") + @ExcludeMissing + sharding: JsonField = JsonMissing.of(), ) : this( approxLogicalBytes, approxRowCount, @@ -74,6 +78,7 @@ private constructor( schema, updatedAt, pinning, + sharding, mutableMapOf(), ) @@ -139,6 +144,17 @@ private constructor( */ fun pinning(): Optional = pinning.getOptional("pinning") + /** + * Configuration for namespace sharding, which partitions a namespace's documents across + * multiple internal shards to scale indexing and query throughput beyond a single machine. + * Sharding can only be configured on a namespace's inaugural write, and cannot be added to or + * changed on an existing namespace. + * + * @throws TurbopufferInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun sharding(): Optional = sharding.getOptional("sharding") + /** * Returns the raw JSON value of [approxLogicalBytes]. * @@ -206,6 +222,13 @@ private constructor( */ @JsonProperty("pinning") @ExcludeMissing fun _pinning(): JsonField = pinning + /** + * Returns the raw JSON value of [sharding]. + * + * Unlike [sharding], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("sharding") @ExcludeMissing fun _sharding(): JsonField = sharding + @JsonAnySetter private fun putAdditionalProperty(key: String, value: JsonValue) { additionalProperties.put(key, value) @@ -248,6 +271,7 @@ private constructor( private var schema: JsonField? = null private var updatedAt: JsonField? = null private var pinning: JsonField = JsonMissing.of() + private var sharding: JsonField = JsonMissing.of() private var additionalProperties: MutableMap = mutableMapOf() @JvmSynthetic @@ -260,6 +284,7 @@ private constructor( schema = namespaceMetadata.schema updatedAt = namespaceMetadata.updatedAt pinning = namespaceMetadata.pinning + sharding = namespaceMetadata.sharding additionalProperties = namespaceMetadata.additionalProperties.toMutableMap() } @@ -376,6 +401,23 @@ private constructor( */ fun pinning(pinning: JsonField) = apply { this.pinning = pinning } + /** + * Configuration for namespace sharding, which partitions a namespace's documents across + * multiple internal shards to scale indexing and query throughput beyond a single machine. + * Sharding can only be configured on a namespace's inaugural write, and cannot be added to + * or changed on an existing namespace. + */ + fun sharding(sharding: ShardingConfig) = sharding(JsonField.of(sharding)) + + /** + * Sets [Builder.sharding] to an arbitrary JSON value. + * + * You should usually call [Builder.sharding] with a well-typed [ShardingConfig] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun sharding(sharding: JsonField) = apply { this.sharding = sharding } + fun additionalProperties(additionalProperties: Map) = apply { this.additionalProperties.clear() putAllAdditionalProperties(additionalProperties) @@ -423,6 +465,7 @@ private constructor( checkRequired("schema", schema), checkRequired("updatedAt", updatedAt), pinning, + sharding, additionalProperties.toMutableMap(), ) } @@ -450,6 +493,7 @@ private constructor( schema().validate() updatedAt() pinning().ifPresent { it.validate() } + sharding().ifPresent { it.validate() } validated = true } @@ -475,7 +519,8 @@ private constructor( (index.asKnown().getOrNull()?.validity() ?: 0) + (schema.asKnown().getOrNull()?.validity() ?: 0) + (if (updatedAt.asKnown().isPresent) 1 else 0) + - (pinning.asKnown().getOrNull()?.validity() ?: 0) + (pinning.asKnown().getOrNull()?.validity() ?: 0) + + (sharding.asKnown().getOrNull()?.validity() ?: 0) @JsonDeserialize(using = Index.Deserializer::class) @JsonSerialize(using = Index.Serializer::class) @@ -1515,6 +1560,7 @@ private constructor( schema == other.schema && updatedAt == other.updatedAt && pinning == other.pinning && + sharding == other.sharding && additionalProperties == other.additionalProperties } @@ -1528,6 +1574,7 @@ private constructor( schema, updatedAt, pinning, + sharding, additionalProperties, ) } @@ -1535,5 +1582,5 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "NamespaceMetadata{approxLogicalBytes=$approxLogicalBytes, approxRowCount=$approxRowCount, createdAt=$createdAt, encryption=$encryption, index=$index, schema=$schema, updatedAt=$updatedAt, pinning=$pinning, additionalProperties=$additionalProperties}" + "NamespaceMetadata{approxLogicalBytes=$approxLogicalBytes, approxRowCount=$approxRowCount, createdAt=$createdAt, encryption=$encryption, index=$index, schema=$schema, updatedAt=$updatedAt, pinning=$pinning, sharding=$sharding, additionalProperties=$additionalProperties}" } diff --git a/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/NamespaceWriteParams.kt b/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/NamespaceWriteParams.kt index 01524df5..7ce5c104 100644 --- a/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/NamespaceWriteParams.kt +++ b/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/NamespaceWriteParams.kt @@ -160,6 +160,17 @@ private constructor( */ fun schema(): Optional = body.schema() + /** + * Configuration for namespace sharding, which partitions a namespace's documents across + * multiple internal shards to scale indexing and query throughput beyond a single machine. + * Sharding can only be configured on a namespace's inaugural write, and cannot be added to or + * changed on an existing namespace. + * + * @throws TurbopufferInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun sharding(): Optional = body.sharding() + /** * A list of documents in columnar format. Each key is a column name, mapped to an array of * values for that column. @@ -296,6 +307,13 @@ private constructor( */ fun _schema(): JsonField = body._schema() + /** + * Returns the raw JSON value of [sharding]. + * + * Unlike [sharding], this method doesn't throw if the JSON field has an unexpected type. + */ + fun _sharding(): JsonField = body._sharding() + /** * Returns the raw JSON value of [upsertColumns]. * @@ -660,6 +678,23 @@ private constructor( */ fun schema(schema: JsonField) = apply { body.schema(schema) } + /** + * Configuration for namespace sharding, which partitions a namespace's documents across + * multiple internal shards to scale indexing and query throughput beyond a single machine. + * Sharding can only be configured on a namespace's inaugural write, and cannot be added to + * or changed on an existing namespace. + */ + fun sharding(sharding: ShardingConfig) = apply { body.sharding(sharding) } + + /** + * Sets [Builder.sharding] to an arbitrary JSON value. + * + * You should usually call [Builder.sharding] with a well-typed [ShardingConfig] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun sharding(sharding: JsonField) = apply { body.sharding(sharding) } + /** * A list of documents in columnar format. Each key is a column name, mapped to an array of * values for that column. @@ -877,6 +912,7 @@ private constructor( private val patchRows: JsonField>, private val returnAffectedIds: JsonField, private val schema: JsonField, + private val sharding: JsonField, private val upsertColumns: JsonField, private val upsertCondition: JsonField, private val upsertRows: JsonField>, @@ -931,6 +967,9 @@ private constructor( @ExcludeMissing returnAffectedIds: JsonField = JsonMissing.of(), @JsonProperty("schema") @ExcludeMissing schema: JsonField = JsonMissing.of(), + @JsonProperty("sharding") + @ExcludeMissing + sharding: JsonField = JsonMissing.of(), @JsonProperty("upsert_columns") @ExcludeMissing upsertColumns: JsonField = JsonMissing.of(), @@ -957,6 +996,7 @@ private constructor( patchRows, returnAffectedIds, schema, + sharding, upsertColumns, upsertCondition, upsertRows, @@ -1098,6 +1138,17 @@ private constructor( */ fun schema(): Optional = schema.getOptional("schema") + /** + * Configuration for namespace sharding, which partitions a namespace's documents across + * multiple internal shards to scale indexing and query throughput beyond a single machine. + * Sharding can only be configured on a namespace's inaugural write, and cannot be added to + * or changed on an existing namespace. + * + * @throws TurbopufferInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun sharding(): Optional = sharding.getOptional("sharding") + /** * A list of documents in columnar format. Each key is a column name, mapped to an array of * values for that column. @@ -1268,6 +1319,15 @@ private constructor( */ @JsonProperty("schema") @ExcludeMissing fun _schema(): JsonField = schema + /** + * Returns the raw JSON value of [sharding]. + * + * Unlike [sharding], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("sharding") + @ExcludeMissing + fun _sharding(): JsonField = sharding + /** * Returns the raw JSON value of [upsertColumns]. * @@ -1334,6 +1394,7 @@ private constructor( private var patchRows: JsonField>? = null private var returnAffectedIds: JsonField = JsonMissing.of() private var schema: JsonField = JsonMissing.of() + private var sharding: JsonField = JsonMissing.of() private var upsertColumns: JsonField = JsonMissing.of() private var upsertCondition: JsonField = JsonMissing.of() private var upsertRows: JsonField>? = null @@ -1357,6 +1418,7 @@ private constructor( patchRows = body.patchRows.map { it.toMutableList() } returnAffectedIds = body.returnAffectedIds schema = body.schema + sharding = body.sharding upsertColumns = body.upsertColumns upsertCondition = body.upsertCondition upsertRows = body.upsertRows.map { it.toMutableList() } @@ -1676,6 +1738,23 @@ private constructor( */ fun schema(schema: JsonField) = apply { this.schema = schema } + /** + * Configuration for namespace sharding, which partitions a namespace's documents across + * multiple internal shards to scale indexing and query throughput beyond a single + * machine. Sharding can only be configured on a namespace's inaugural write, and cannot + * be added to or changed on an existing namespace. + */ + fun sharding(sharding: ShardingConfig) = sharding(JsonField.of(sharding)) + + /** + * Sets [Builder.sharding] to an arbitrary JSON value. + * + * You should usually call [Builder.sharding] with a well-typed [ShardingConfig] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun sharding(sharding: JsonField) = apply { this.sharding = sharding } + /** * A list of documents in columnar format. Each key is a column name, mapped to an array * of values for that column. @@ -1778,6 +1857,7 @@ private constructor( (patchRows ?: JsonMissing.of()).map { it.toImmutable() }, returnAffectedIds, schema, + sharding, upsertColumns, upsertCondition, (upsertRows ?: JsonMissing.of()).map { it.toImmutable() }, @@ -1814,6 +1894,7 @@ private constructor( patchRows() returnAffectedIds() schema().ifPresent { it.values.forEach { it.validate() } } + sharding().ifPresent { it.validate() } upsertColumns() upsertRows() validated = true @@ -1848,6 +1929,7 @@ private constructor( (patchRows.asKnown().getOrNull()?.size ?: 0) + (if (returnAffectedIds.asKnown().isPresent) 1 else 0) + (schema.asKnown().getOrNull()?.values?.sumOf { it.validity() } ?: 0) + + (sharding.asKnown().getOrNull()?.validity() ?: 0) + (upsertColumns.asKnown().getOrNull()?.values?.sumOf { it.size } ?: 0) + (upsertRows.asKnown().getOrNull()?.size ?: 0) @@ -1873,6 +1955,7 @@ private constructor( patchRows == other.patchRows && returnAffectedIds == other.returnAffectedIds && schema == other.schema && + sharding == other.sharding && upsertColumns == other.upsertColumns && upsertCondition == other.upsertCondition && upsertRows == other.upsertRows && @@ -1897,6 +1980,7 @@ private constructor( patchRows, returnAffectedIds, schema, + sharding, upsertColumns, upsertCondition, upsertRows, @@ -1907,7 +1991,7 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "Body{branchFromNamespace=$branchFromNamespace, copyFromNamespace=$copyFromNamespace, deleteByFilter=$deleteByFilter, deleteByFilterAllowPartial=$deleteByFilterAllowPartial, deleteCondition=$deleteCondition, deletes=$deletes, disableBackpressure=$disableBackpressure, distanceMetric=$distanceMetric, encryption=$encryption, patchByFilter=$patchByFilter, patchByFilterAllowPartial=$patchByFilterAllowPartial, patchColumns=$patchColumns, patchCondition=$patchCondition, patchRows=$patchRows, returnAffectedIds=$returnAffectedIds, schema=$schema, upsertColumns=$upsertColumns, upsertCondition=$upsertCondition, upsertRows=$upsertRows, additionalProperties=$additionalProperties}" + "Body{branchFromNamespace=$branchFromNamespace, copyFromNamespace=$copyFromNamespace, deleteByFilter=$deleteByFilter, deleteByFilterAllowPartial=$deleteByFilterAllowPartial, deleteCondition=$deleteCondition, deletes=$deletes, disableBackpressure=$disableBackpressure, distanceMetric=$distanceMetric, encryption=$encryption, patchByFilter=$patchByFilter, patchByFilterAllowPartial=$patchByFilterAllowPartial, patchColumns=$patchColumns, patchCondition=$patchCondition, patchRows=$patchRows, returnAffectedIds=$returnAffectedIds, schema=$schema, sharding=$sharding, upsertColumns=$upsertColumns, upsertCondition=$upsertCondition, upsertRows=$upsertRows, additionalProperties=$additionalProperties}" } /** The patch and filter specifying which documents to patch. */ diff --git a/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/ShardingConfig.kt b/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/ShardingConfig.kt new file mode 100644 index 00000000..624c8a52 --- /dev/null +++ b/turbopuffer-java-core/src/main/kotlin/com/turbopuffer/models/namespaces/ShardingConfig.kt @@ -0,0 +1,187 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.turbopuffer.models.namespaces + +import com.fasterxml.jackson.annotation.JsonAnyGetter +import com.fasterxml.jackson.annotation.JsonAnySetter +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonProperty +import com.turbopuffer.core.ExcludeMissing +import com.turbopuffer.core.JsonField +import com.turbopuffer.core.JsonMissing +import com.turbopuffer.core.JsonValue +import com.turbopuffer.core.checkRequired +import com.turbopuffer.errors.TurbopufferInvalidDataException +import java.util.Collections +import java.util.Objects + +/** + * Configuration for namespace sharding, which partitions a namespace's documents across multiple + * internal shards to scale indexing and query throughput beyond a single machine. Sharding can only + * be configured on a namespace's inaugural write, and cannot be added to or changed on an existing + * namespace. + */ +class ShardingConfig +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val numShards: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("num_shards") @ExcludeMissing numShards: JsonField = JsonMissing.of() + ) : this(numShards, mutableMapOf()) + + /** + * The number of shards to partition the namespace into. + * + * @throws TurbopufferInvalidDataException if the JSON field has an unexpected type or is + * unexpectedly missing or null (e.g. if the server responded with an unexpected value). + */ + fun numShards(): Int = numShards.getRequired("num_shards") + + /** + * Returns the raw JSON value of [numShards]. + * + * Unlike [numShards], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("num_shards") @ExcludeMissing fun _numShards(): JsonField = numShards + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [ShardingConfig]. + * + * The following fields are required: + * ```java + * .numShards() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [ShardingConfig]. */ + class Builder internal constructor() { + + private var numShards: JsonField? = null + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(shardingConfig: ShardingConfig) = apply { + numShards = shardingConfig.numShards + additionalProperties = shardingConfig.additionalProperties.toMutableMap() + } + + /** The number of shards to partition the namespace into. */ + fun numShards(numShards: Int) = numShards(JsonField.of(numShards)) + + /** + * Sets [Builder.numShards] to an arbitrary JSON value. + * + * You should usually call [Builder.numShards] with a well-typed [Int] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun numShards(numShards: JsonField) = apply { this.numShards = numShards } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [ShardingConfig]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .numShards() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): ShardingConfig = + ShardingConfig( + checkRequired("numShards", numShards), + additionalProperties.toMutableMap(), + ) + } + + private var validated: Boolean = false + + /** + * Validates that the types of all values in this object match their expected types recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws TurbopufferInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): ShardingConfig = apply { + if (validated) { + return@apply + } + + numShards() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: TurbopufferInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = (if (numShards.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is ShardingConfig && + numShards == other.numShards && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { Objects.hash(numShards, additionalProperties) } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "ShardingConfig{numShards=$numShards, additionalProperties=$additionalProperties}" +} diff --git a/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/models/namespaces/NamespaceMetadataTest.kt b/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/models/namespaces/NamespaceMetadataTest.kt index 1b6dea03..1c616dc3 100644 --- a/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/models/namespaces/NamespaceMetadataTest.kt +++ b/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/models/namespaces/NamespaceMetadataTest.kt @@ -53,6 +53,7 @@ internal class NamespaceMetadataTest { ) .build() ) + .sharding(ShardingConfig.builder().numShards(1).build()) .build() assertThat(namespaceMetadata.approxLogicalBytes()).isEqualTo(0L) @@ -102,6 +103,8 @@ internal class NamespaceMetadataTest { ) .build() ) + assertThat(namespaceMetadata.sharding()) + .contains(ShardingConfig.builder().numShards(1).build()) } @Test @@ -147,6 +150,7 @@ internal class NamespaceMetadataTest { ) .build() ) + .sharding(ShardingConfig.builder().numShards(1).build()) .build() val roundtrippedNamespaceMetadata = diff --git a/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/models/namespaces/NamespaceWriteParamsTest.kt b/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/models/namespaces/NamespaceWriteParamsTest.kt index c7b6fdf9..b8c6da69 100644 --- a/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/models/namespaces/NamespaceWriteParamsTest.kt +++ b/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/models/namespaces/NamespaceWriteParamsTest.kt @@ -52,6 +52,7 @@ internal class NamespaceWriteParamsTest { .put("foo", AttributeSchemaConfig.builder().type("string").build()) .build() ) + .sharding(ShardingConfig.builder().numShards(1).build()) .upsertColumns( Columns.builder() .addId("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") @@ -121,6 +122,7 @@ internal class NamespaceWriteParamsTest { .put("foo", AttributeSchemaConfig.builder().type("string").build()) .build() ) + .sharding(ShardingConfig.builder().numShards(1).build()) .upsertColumns( Columns.builder() .addId("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") @@ -188,6 +190,7 @@ internal class NamespaceWriteParamsTest { .put("foo", AttributeSchemaConfig.builder().type("string").build()) .build() ) + assertThat(body.sharding()).contains(ShardingConfig.builder().numShards(1).build()) assertThat(body.upsertColumns()) .contains( Columns.builder() diff --git a/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/models/namespaces/ShardingConfigTest.kt b/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/models/namespaces/ShardingConfigTest.kt new file mode 100644 index 00000000..e893d221 --- /dev/null +++ b/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/models/namespaces/ShardingConfigTest.kt @@ -0,0 +1,32 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.turbopuffer.models.namespaces + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.turbopuffer.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class ShardingConfigTest { + + @Test + fun create() { + val shardingConfig = ShardingConfig.builder().numShards(1).build() + + assertThat(shardingConfig.numShards()).isEqualTo(1) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val shardingConfig = ShardingConfig.builder().numShards(1).build() + + val roundtrippedShardingConfig = + jsonMapper.readValue( + jsonMapper.writeValueAsString(shardingConfig), + jacksonTypeRef(), + ) + + assertThat(roundtrippedShardingConfig).isEqualTo(shardingConfig) + } +} diff --git a/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/services/ServiceParamsTest.kt b/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/services/ServiceParamsTest.kt index bfbd6489..fc9bf95d 100644 --- a/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/services/ServiceParamsTest.kt +++ b/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/services/ServiceParamsTest.kt @@ -26,6 +26,7 @@ import com.turbopuffer.models.namespaces.Filter import com.turbopuffer.models.namespaces.NamespaceWriteParams import com.turbopuffer.models.namespaces.Row import com.turbopuffer.models.namespaces.Schema +import com.turbopuffer.models.namespaces.ShardingConfig import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test @@ -115,6 +116,7 @@ internal class ServiceParamsTest { .put("age", AttributeSchemaConfig.builder().type("uint").build()) .build() ) + .sharding(ShardingConfig.builder().numShards(1).build()) .upsertColumns( Columns.builder() .put("id", listOf("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")) diff --git a/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/services/async/NamespaceServiceAsyncTest.kt b/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/services/async/NamespaceServiceAsyncTest.kt index d0e5b649..3550df4e 100644 --- a/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/services/async/NamespaceServiceAsyncTest.kt +++ b/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/services/async/NamespaceServiceAsyncTest.kt @@ -25,6 +25,7 @@ import com.turbopuffer.models.namespaces.NamespaceUpdateSchemaParams import com.turbopuffer.models.namespaces.NamespaceWriteParams import com.turbopuffer.models.namespaces.Row import com.turbopuffer.models.namespaces.Schema +import com.turbopuffer.models.namespaces.ShardingConfig import com.turbopuffer.models.namespaces.VectorEncoding import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test @@ -325,6 +326,7 @@ internal class NamespaceServiceAsyncTest { .put("age", AttributeSchemaConfig.builder().type("uint").build()) .build() ) + .sharding(ShardingConfig.builder().numShards(1).build()) .upsertColumns( Columns.builder() .put("id", listOf("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")) diff --git a/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/services/blocking/NamespaceServiceTest.kt b/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/services/blocking/NamespaceServiceTest.kt index 15ceae9b..65d6bc2e 100644 --- a/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/services/blocking/NamespaceServiceTest.kt +++ b/turbopuffer-java-core/src/test/kotlin/com/turbopuffer/services/blocking/NamespaceServiceTest.kt @@ -25,6 +25,7 @@ import com.turbopuffer.models.namespaces.NamespaceUpdateSchemaParams import com.turbopuffer.models.namespaces.NamespaceWriteParams import com.turbopuffer.models.namespaces.Row import com.turbopuffer.models.namespaces.Schema +import com.turbopuffer.models.namespaces.ShardingConfig import com.turbopuffer.models.namespaces.VectorEncoding import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test @@ -307,6 +308,7 @@ internal class NamespaceServiceTest { .put("age", AttributeSchemaConfig.builder().type("uint").build()) .build() ) + .sharding(ShardingConfig.builder().numShards(1).build()) .upsertColumns( Columns.builder() .put("id", listOf("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"))