Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changelog/3982.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```release-note:enhancement
resource/mongodbatlas_search_index: Adds `num_partitions` attribute
```

```release-note:enhancement
data-source/mongodbatlas_search_index: Adds `num_partitions` attribute
```

```release-note:enhancement
data-source/mongodbatlas_search_indexes: Adds `num_partitions` attribute
```
1 change: 1 addition & 0 deletions docs/data-sources/search_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,6 @@ data "mongodbatlas_search_index" "test" {
* `type_sets` - Set of type set definitions (when present). Each item includes:
* `name` - Type set name.
* `types` - JSON array string describing the types for the set.
* `num_partitions` - Number of index partitions, returns 0 if not set in the resource
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation states 'returns 0 if not set in the resource', but this phrasing is unclear as this is a data source, not a resource. Consider rephrasing to 'returns 0 if not configured' or 'returns 0 when not set' for clarity.

Copilot uses AI. Check for mistakes.

For more information see: [MongoDB Atlas API Reference.](https://docs.atlas.mongodb.com/atlas-search/) - [and MongoDB Atlas API - Search](https://docs.atlas.mongodb.com/reference/api/atlas-search/) Documentation for more information.
1 change: 1 addition & 0 deletions docs/data-sources/search_indexes.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ data "mongodbatlas_search_indexes" "test" {
* `type_sets` - Set of type set definitions (when present). Each item includes:
* `name` - Type set name.
* `types` - JSON array string describing the types for the set.
* `num_partitions` - Number of index partitions, returns 0 if not set in the resource
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation states 'returns 0 if not set in the resource', but this phrasing is unclear as this is a data source, not a resource. Consider rephrasing to 'returns 0 if not configured' or 'returns 0 when not set' for clarity.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

@oarbusi oarbusi Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if default value is 1(it says so in the API docs), why we return 0? seems like an inconsistency in the API?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oarbusi We receive an integer pointer as nil. Since SDKv2 does not natively handle nil values, and our num_partitions field is of type integer, the field will default to an undefined integer value, equal to 0.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it makes sense considering the API bahavior. What I find confusing is the fact that we have a "default is 1" statement but then it "returns 0 when not set", which contradicts the default is 1 statement

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Atlas, if this value is not explicitly set, it defaults to 1. However, this default value is not included in the response

For example:

  • If you set the value to 1, the response returns 1
  • If you don't set the value, Atlas internally defaults it to 1, but this default value will not appear in the response

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think it can be confusing, but good to resolve on my side

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you! Do you have any suggestions for rephrasing it? Or do you think we should remove the returns 0 note entirely since that is considered not-set in go? I don't have much experience with such customer-facing documentation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a hard one, and I am not sure whether there's an easy solution/or clearer way of explaining this


For more information see: [MongoDB Atlas API Reference.](https://docs.atlas.mongodb.com/atlas-search/) - [and MongoDB Atlas API - Search](https://docs.atlas.mongodb.com/reference/api/atlas-search/) Documentation for more information.
2 changes: 2 additions & 0 deletions docs/resources/search_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ EOF
EOF
```

* `num_partitions` - (Optional) Number of index partitions. Allowed values are [1, 2, 4]. Default value is 1.
Copy link
Member

@lantoli lantoli Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

qq: can it be used in both search and vectorSearch types?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you! added the a test for vectorSearch as well.


## Attributes Reference

In addition to all arguments above, the following attributes are exported:
Expand Down
8 changes: 8 additions & 0 deletions internal/service/searchindex/data_source_search_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ func returnSearchIndexDSSchema() map[string]*schema.Schema {
},
},
},
"num_partitions": {
Type: schema.TypeInt,
Computed: true,
Copy link
Member

@lantoli lantoli Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you confirm that if num_partitions is not used when creating the resource, we'll get null in the data sources (instead of default 1)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We receive an integer pointer as nil. Since SDKv2 does not natively handle nil values, and our num_partitions field is of type integer, the field will default to an undefined integer value, equal to 0.

},
}
}

Expand Down Expand Up @@ -223,6 +227,10 @@ func dataSourceMongoDBAtlasSearchIndexRead(ctx context.Context, d *schema.Resour
return diag.Errorf("error setting `stored_source` for search index (%s): %s", d.Id(), err)
}

if err := d.Set("num_partitions", searchIndex.LatestDefinition.NumPartitions); err != nil {
return diag.Errorf("error setting `num_partitions` for search index (%s): %s", d.Id(), err)
}

d.SetId(conversion.EncodeStateID(map[string]string{
"project_id": projectID.(string),
"cluster_name": clusterName.(string),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ func flattenSearchIndexes(searchIndexes []admin.SearchIndexResponse, projectID,
"status": searchIndexes[i].Status,
"synonyms": flattenSearchIndexSynonyms(searchIndexes[i].LatestDefinition.GetSynonyms()),
"type": searchIndexes[i].Type,
"num_partitions": searchIndexes[i].LatestDefinition.NumPartitions,
}

if searchIndexes[i].LatestDefinition.Mappings != nil {
Expand Down
14 changes: 14 additions & 0 deletions internal/service/searchindex/resource_search_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ func returnSearchIndexSchema() map[string]*schema.Schema {
},
},
},
"num_partitions": {
Type: schema.TypeInt,
Optional: true,
},
}
}

Expand Down Expand Up @@ -269,13 +273,18 @@ func resourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.
StoredSource: searchRead.LatestDefinition.StoredSource,
Synonyms: searchRead.LatestDefinition.Synonyms,
Fields: searchRead.LatestDefinition.Fields,
NumPartitions: searchRead.LatestDefinition.NumPartitions,
},
}

if d.HasChange("analyzer") {
searchIndex.Definition.Analyzer = conversion.StringPtr(d.Get("analyzer").(string))
}

if d.HasChange("num_partitions") {
searchIndex.Definition.NumPartitions = conversion.IntPtr(d.Get("num_partitions").(int))
}

if d.HasChange("search_analyzer") {
searchIndex.Definition.SearchAnalyzer = conversion.StringPtr(d.Get("search_analyzer").(string))
}
Expand Down Expand Up @@ -485,6 +494,10 @@ func resourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Di
return diag.Errorf("error setting `stored_source` for search index (%s): %s", d.Id(), err)
}

if err := d.Set("num_partitions", searchIndex.LatestDefinition.NumPartitions); err != nil {
return diag.Errorf("error setting `num_partitions` for search index (%s): %s", d.Id(), err)
}

return nil
}

Expand All @@ -501,6 +514,7 @@ func resourceCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.
Definition: &admin.BaseSearchIndexCreateRequestDefinition{
Analyzer: conversion.StringPtr(d.Get("analyzer").(string)),
SearchAnalyzer: conversion.StringPtr(d.Get("search_analyzer").(string)),
NumPartitions: conversion.IntPtr(d.Get("num_partitions").(int)),
},
}

Expand Down
192 changes: 187 additions & 5 deletions internal/service/searchindex/resource_search_index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"regexp"
"strconv"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
Expand Down Expand Up @@ -192,6 +193,57 @@ func TestAccSearchIndex_withVector(t *testing.T) {
resource.ParallelTest(t, *basicVectorTestCase(t))
}

func TestAccSearchIndex_withNumPartitions(t *testing.T) {
var (
projectID, clusterName = acc.ClusterNameExecution(t, true)
indexName = acc.RandomName()
)
resource.Test(t, resource.TestCase{
PreCheck: func() { acc.PreCheckBasic(t) },
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
CheckDestroy: acc.CheckDestroySearchIndex,
Steps: []resource.TestStep{
{
Config: configSearchWithNumPartitions(projectID, indexName, clusterName, nil),
Check: checkSearchWithNumPartitions(projectID, indexName, clusterName, nil),
},
{
Config: configSearchWithNumPartitions(projectID, indexName, clusterName, conversion.IntPtr(2)),
Check: checkSearchWithNumPartitions(projectID, indexName, clusterName, conversion.IntPtr(2)),
},
{
Config: configSearchWithNumPartitions(projectID, indexName, clusterName, nil),
Check: checkSearchWithNumPartitions(projectID, indexName, clusterName, nil),
},
},
})
}

func TestAccVectorSearchIndex_withNumPartitions(t *testing.T) {
var (
projectID, clusterName = acc.ClusterNameExecution(t, true)
indexName = acc.RandomName()
)
resource.Test(t, resource.TestCase{
PreCheck: func() { acc.PreCheckBasic(t) },
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
CheckDestroy: acc.CheckDestroySearchIndex,
Steps: []resource.TestStep{
{
Config: configVectorSearchWithNumPartitions(projectID, indexName, clusterName, nil),
Check: checkVectorSearchWithNumPartitions(projectID, indexName, clusterName, nil),
},
{
Config: configVectorSearchWithNumPartitions(projectID, indexName, clusterName, conversion.IntPtr(2)),
Check: checkVectorSearchWithNumPartitions(projectID, indexName, clusterName, conversion.IntPtr(2)),
},
{
Config: configVectorSearchWithNumPartitions(projectID, indexName, clusterName, nil),
Check: checkVectorSearchWithNumPartitions(projectID, indexName, clusterName, nil),
},
},
})
}
func basicTestCase(tb testing.TB) *resource.TestCase {
tb.Helper()
var (
Expand Down Expand Up @@ -372,7 +424,7 @@ func configBasic(projectID, clusterName, indexName, indexType, storedSource stri
data "mongodbatlas_search_index" "data_index" {
cluster_name = mongodbatlas_search_index.test.cluster_name
project_id = mongodbatlas_search_index.test.project_id
index_id = mongodbatlas_search_index.test.index_id
index_id = mongodbatlas_search_index.test.index_id
}
`, clusterName, projectID, indexName, database, collection, searchAnalyzer, extra)
}
Expand Down Expand Up @@ -409,7 +461,7 @@ func configWithMapping(projectID, indexName, clusterName string) string {
data "mongodbatlas_search_index" "data_index" {
cluster_name = mongodbatlas_search_index.test.cluster_name
project_id = mongodbatlas_search_index.test.project_id
index_id = mongodbatlas_search_index.test.index_id
index_id = mongodbatlas_search_index.test.index_id
}
`, clusterName, projectID, indexName, database, collection, searchAnalyzer, analyzersTF, mappingsFieldsTF)
}
Expand Down Expand Up @@ -451,7 +503,7 @@ func configWithSynonyms(projectID, indexName, clusterName string, has bool) stri
data "mongodbatlas_search_index" "data_index" {
cluster_name = mongodbatlas_search_index.test.cluster_name
project_id = mongodbatlas_search_index.test.project_id
index_id = mongodbatlas_search_index.test.index_id
index_id = mongodbatlas_search_index.test.index_id
}
`, clusterName, projectID, indexName, database, collection, searchAnalyzer, synonymsStr)
}
Expand Down Expand Up @@ -489,7 +541,7 @@ func configAdditional(projectID, indexName, clusterName, additional string) stri
data "mongodbatlas_search_index" "data_index" {
cluster_name = mongodbatlas_search_index.test.cluster_name
project_id = mongodbatlas_search_index.test.project_id
index_id = mongodbatlas_search_index.test.index_id
index_id = mongodbatlas_search_index.test.index_id
}
`, clusterName, projectID, indexName, database, collection, searchAnalyzer, additional)
}
Expand Down Expand Up @@ -533,11 +585,117 @@ func configVector(projectID, indexName, clusterName string) string {
data "mongodbatlas_search_index" "data_index" {
cluster_name = mongodbatlas_search_index.test.cluster_name
project_id = mongodbatlas_search_index.test.project_id
index_id = mongodbatlas_search_index.test.index_id
index_id = mongodbatlas_search_index.test.index_id
}
`, clusterName, projectID, indexName, database, collection, fieldsJSON)
}

func configVectorSearchWithNumPartitions(projectID, indexName, clusterName string, numPartitions *int) string {
var numPartitionsLine string
hasNumPartitions := numPartitions != nil
if hasNumPartitions {
numPartitionsLine = fmt.Sprintf("num_partitions = %d", *numPartitions)
}
return fmt.Sprintf(`

resource "mongodbatlas_search_deployment" "test" {
cluster_name = %[1]q
project_id = %[2]q
specs = [
{
instance_size = "S20_HIGHCPU_NVME"
node_count = 2
}
]
}

resource "mongodbatlas_search_index" "test" {
cluster_name = %[1]q
project_id = %[2]q
name = %[3]q
database = %[4]q
collection_name = %[5]q

type = "vectorSearch"
%[6]s
fields = <<-EOF
%[7]s
EOF

depends_on = [mongodbatlas_search_deployment.test]
}

data "mongodbatlas_search_index" "data_index" {
cluster_name = mongodbatlas_search_index.test.cluster_name
project_id = mongodbatlas_search_index.test.project_id
index_id = mongodbatlas_search_index.test.index_id
}
`, clusterName, projectID, indexName, database, collection, numPartitionsLine, fieldsJSON)
}
func configSearchWithNumPartitions(projectID, indexName, clusterName string, numPartitions *int) string {
var numPartitionsLine string
hasNumPartitions := numPartitions != nil
if hasNumPartitions {
numPartitionsLine = fmt.Sprintf("num_partitions = %d", *numPartitions)
}
return fmt.Sprintf(`

resource "mongodbatlas_search_deployment" "test" {
cluster_name = %[1]q
project_id = %[2]q
specs = [
{
instance_size = "S20_HIGHCPU_NVME"
node_count = 2
}
]
}

resource "mongodbatlas_search_index" "test" {
cluster_name = %[1]q
project_id = %[2]q
name = %[3]q
database = %[4]q
collection_name = %[5]q
analyzer = "lucene.standard"
search_analyzer = "lucene.standard"
mappings_dynamic = true
type = "search"
%[6]s

depends_on = [mongodbatlas_search_deployment.test]
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation in the depends_on line. The line uses a tab character while other lines in the same configuration use spaces. Consider using consistent indentation throughout the configuration string.

Suggested change
depends_on = [mongodbatlas_search_deployment.test]
depends_on = [mongodbatlas_search_deployment.test]

Copilot uses AI. Check for mistakes.
}

data "mongodbatlas_search_index" "data_index" {
cluster_name = mongodbatlas_search_index.test.cluster_name
project_id = mongodbatlas_search_index.test.project_id
index_id = mongodbatlas_search_index.test.index_id
}
`, clusterName, projectID, indexName, database, collection, numPartitionsLine)
}
func checkVectorSearchWithNumPartitions(projectID, indexName, clusterName string, numPartitions *int) resource.TestCheckFunc {
indexType := "vectorSearch"
mappingsDynamic := "true"
checks := []resource.TestCheckFunc{
resource.TestCheckResourceAttrWith(resourceName, "fields", acc.JSONEquals(fieldsJSON)),
resource.TestCheckResourceAttrWith(datasourceName, "fields", acc.JSONEquals(fieldsJSON)),
}

if numPartitions != nil {
checks = append(checks,
resource.TestCheckResourceAttr(resourceName, "num_partitions", strconv.Itoa(*numPartitions)),
resource.TestCheckResourceAttr(datasourceName, "num_partitions", strconv.Itoa(*numPartitions)),
)
} else {
checks = append(checks,
resource.TestCheckResourceAttr(resourceName, "num_partitions", "0"),
resource.TestCheckResourceAttr(datasourceName, "num_partitions", "0"),
)
}

return checkAggr(projectID, clusterName, indexName, indexType, mappingsDynamic, checks...)
}

func checkVector(projectID, indexName, clusterName string) resource.TestCheckFunc {
indexType := "vectorSearch"
mappingsDynamic := "true"
Expand All @@ -546,6 +704,30 @@ func checkVector(projectID, indexName, clusterName string) resource.TestCheckFun
resource.TestCheckResourceAttrWith(datasourceName, "fields", acc.JSONEquals(fieldsJSON)))
}

func checkSearchWithNumPartitions(projectID, indexName, clusterName string, numPartitions *int) resource.TestCheckFunc {
indexType := "search"
mappingsDynamic := "true"
checks := []resource.TestCheckFunc{
resource.TestCheckResourceAttr(resourceName, "analyzer", "lucene.standard"),
resource.TestCheckResourceAttr(resourceName, "search_analyzer", "lucene.standard"),
resource.TestCheckResourceAttr(datasourceName, "analyzer", "lucene.standard"),
resource.TestCheckResourceAttr(datasourceName, "search_analyzer", "lucene.standard"),
}

if numPartitions != nil {
checks = append(checks,
resource.TestCheckResourceAttr(resourceName, "num_partitions", strconv.Itoa(*numPartitions)),
resource.TestCheckResourceAttr(datasourceName, "num_partitions", strconv.Itoa(*numPartitions)),
)
} else {
checks = append(checks,
resource.TestCheckResourceAttr(resourceName, "num_partitions", "0"),
resource.TestCheckResourceAttr(datasourceName, "num_partitions", "0"),
)
}
return checkAggr(projectID, clusterName, indexName, indexType, mappingsDynamic, checks...)
}

func importStateIDFunc(resourceName string) resource.ImportStateIdFunc {
return func(s *terraform.State) (string, error) {
rs, ok := s.RootModule().Resources[resourceName]
Expand Down