diff --git a/guides/advanced_index_actions.md b/guides/advanced_index_actions.md index 153753b81..45a12c6ad 100644 --- a/guides/advanced_index_actions.md +++ b/guides/advanced_index_actions.md @@ -10,82 +10,93 @@ Let's create a client instance, and an index named `movies`: package main import ( - "github.com/opensearch-project/opensearch-go/v2" - "log" + "context" + "fmt" + "os" + "strings" + + "github.com/opensearch-project/opensearch-go/v2/opensearchapi" ) func main() { - client, err := opensearch.NewDefaultClient() - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", client) -} - -movies := "movies" - -createMovieIndex, err := client.Indices.Create(movies) -if err != nil { -log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", createMovieIndex) + if err := example(); err != nil { + fmt.Println(fmt.Sprintf("Error: %s", err)) + os.Exit(1) + } +} + +func example() error { + client, err := opensearchapi.NewDefaultClient() + if err != nil { + return err + } + + ctx := context.Background() + exampleIndex := "movies" + + createResp, err := client.Indices.Create(ctx, opensearchapi.IndicesCreateReq{Index: exampleIndex}) + if err != nil { + return err + } + fmt.Printf("Index created: %t\n", createResp.Acknowledged) ``` ## API Actions ### Clear index cache -You can clear the cache of an index or indices by using the `indices.clear_cache` API action. The following example clears the cache of the `movies` index: +You can clear the cache of an index or indices by using the `Indices.ClearCache()` action. The following example clears the cache of the `movies` index: ```go -res, err := client.Indices.ClearCache(client.Indices.ClearCache.WithIndex(movies)) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) + clearCachResp, err := client.Indices.ClearCache(ctx, &opensearchapi.IndicesClearCacheReq{Indices: []string{exampleIndex}}) + if err != nil { + return err + } + fmt.Printf("Cach cleared for %s shards\n", clearCacheResp.Shards.Total) ``` -By default, the `indices.clear_cache` API action clears all types of cache. To clear specific types of cache pass the the `query`, `fielddata`, or `request` parameter to the API action: +By default, the `Indices.ClearCache()` action clears all types of cache. To clear specific types of cache pass the the `query`, `fielddata`, or `request` parameter to the action: ```go -res, err := client.Indices.ClearCache( - client.Indices.ClearCache.WithIndex(movies), - client.Indices.ClearCache.WithFielddata(true), - client.Indices.ClearCache.WithRequest(true), - client.Indices.ClearCache.WithQuery(true), -) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) + clearCachResp, err := client.Indices.ClearCache( + ctx, + &opensearchapi.IndicesClearCacheReq{ + Indices: []string{exampleIndex}, + Params: opensearchapi.IndicesClearCacheParams{ + Fielddata: opensearchapi.ToPointer(true), + Request: opensearchapi.ToPointer(true), + Query: opensearchapi.ToPointer(true), + }, + }, + ) + if err != nil { + return err + } + fmt.Printf("Cach cleared for %s shards\n", clearCacheResp.Shards.Total) ``` ### Flush index -Sometimes you might want to flush an index or indices to make sure that all data in the transaction log is persisted to the index. To flush an index or indices use the `indices.flush` API action. The following example flushes the `movies` index: +Sometimes you might want to flush an index or indices to make sure that all data in the transaction log is persisted to the index. To flush an index or indices use the `Indices.Flush()` action. The following example flushes the `movies` index: ```go -res, err := client.Indices.Flush( - client.Indices.Flush.WithIndex(movies), -) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) + flushResp, err := client.Indices.Flush(ctx, &opensearchapi.IndicesFlushReq{Indices: []string{exampleIndex}}) + if err != nil { + return err + } + fmt.Printf("Flushed shards: %d\n", flushResp.Shards.Total) ``` ### Refresh index -You can refresh an index or indices to make sure that all changes are available for search. To refresh an index or indices use the `indices.refresh` API action: +You can refresh an index or indices to make sure that all changes are available for search. To refresh an index or indices use the `Indices.Refresh()` action: ```go -res, err := client.Indices.Refresh( - client.Indices.Refresh.WithIndex(movies), -) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) + refreshResp, err := client.Indices.Refresh(ctx, &opensearchapi.IndicesRefreshReq{Indices: []string{exampleIndex}}) + if err != nil { + return err + } + fmt.Printf("Refreshed shards: %d\n", refreshResp.Shards.Total) ``` ### Open/Close index @@ -93,17 +104,17 @@ log.Printf("response: [%+v]", res) You can close an index to prevent read and write operations on the index. A closed index does not have to maintain certain data structures that an opened index require, reducing the memory and disk space required by the index. The following example closes and reopens the `movies` index: ```go -res, err := client.Indices.Close([]string{movies}) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) - -res, err = client.Indices.Open([]string{movies}) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) + closeResp, err := client.Indices.Close(ctx, opensearchapi.IndicesCloseReq{Index: exampleIndex}) + if err != nil { + return err + } + fmt.Printf("Index closed: %t\n", closeResp.Acknowledged) + + openResp, err := client.Indices.Open(ctx, opensearchapi.IndicesOpenReq{Index: exampleIndex}) + if err != nil { + return err + } + fmt.Printf("Index opended: %t\n", openResp.Acknowledged) ``` ### Force merge index @@ -111,13 +122,19 @@ log.Printf("response: [%+v]", res) You can force merge an index or indices to reduce the number of segments in the index. This can be useful if you have a large number of small segments in the index. Merging segments reduces the memory footprint of the index. Do note that this action is resource intensive and it is only recommended for read-only indices. The following example force merges the `movies` index: ```go -res, err := client.Indices.Forcemerge( - client.Indices.Forcemerge.WithIndex(movies), -) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) + mergeResp, err := client.Indices.Forcemerge( + ctx, + &opensearchapi.IndicesForcemergeReq{ + Indices: []string{exampleIndex}, + Params: opensearchapi.IndicesForcemergeParams{ + MaxNumSegments: opensearchapi.ToPointer(1), + }, + }, + ) + if err != nil { + return err + } + fmt.Printf("Forcemerged Shards: %d\n", mergeResp.Shards.Total) ``` ### Clone index @@ -125,26 +142,41 @@ log.Printf("response: [%+v]", res) You can clone an index to create a new index with the same mappings, data, and MOST of the settings. The source index must be in read-only state for cloning. The following example blocks write operations from `movies` index, clones the said index to create a new index named `movies_clone`, then re-enables write: ```go -res, err := client.Indices.AddBlock([]string{movies}, "write") -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) - -res, err = client.Indices.Clone(movies, "movies_clone") -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) - -res, err = client.Indices.PutSettings( - strings.NewReader(`{"index":{"blocks":{"write":false}}}`), - client.Indices.PutSettings.WithIndex(movies), -) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) + blockResp, err := client.Indices.Block( + ctx, + opensearchapi.IndicesBlockReq{ + Indices: []string{exampleIndex}, + Block: "write", + }, + ) + if err != nil { + return err + } + fmt.Printf("Index write blocked: %t\n", blockResp.Acknowledged) + + cloneResp, err := client.Indices.Clone( + ctx, + opensearchapi.IndicesCloneReq{ + Index: exampleIndex, + Target: "movies_cloned", + }, + ) + if err != nil { + return err + } + fmt.Printf("Cloned: %t\n", cloneResp.Acknowledged) + + settingResp, err := client.Indices.Settings.Put( + ctx, + opensearchapi.SettingsPutReq{ + Indices: []string{exampleIndex}, + Body: strings.NewReader(`{"index":{"blocks":{"write":null}}}`), + }, + ) + if err != nil { + return err + } + fmt.Printf("Settings updated: %t\n", settingResp.Acknowledged) ``` ### Split index @@ -152,44 +184,52 @@ log.Printf("response: [%+v]", res) You can split an index into another index with more primary shards. The source index must be in read-only state for splitting. The following example create the read-only `books` index with 30 routing shards and 5 shards (which is divisible by 30), splits index into `bigger_books` with 10 shards (which is also divisible by 30), then re-enables write: ```go -books := "books" - -res, err := client.Indices.Create(books, - client.Indices.Create.WithBody( - strings.NewReader(`{ - "settings": { - "index": { - "number_of_shards": 5, - "number_of_routing_shards": 30, - "blocks": { - "write": true - } + createResp, err = client.Indices.Create( + ctx, + opensearchapi.IndicesCreateReq{ + Index: "books", + Body: strings.NewReader(`{ + "settings": { + "index": { + "number_of_shards": 5, + "number_of_routing_shards": 30, + "blocks": { + "write": true } } - }`), - ), -) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) - -res, err = client.Indices.Split( - books, "bigger_books", - client.Indices.Split.WithBody(strings.NewReader(`{"settings":{"index":{"number_of_shards": 10}}}`))) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) - -res, err = client.Indices.PutSettings( - strings.NewReader(`{"index":{"blocks":{"write":false}}}`), - client.Indices.PutSettings.WithIndex(books), -) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) + } + }`), + }, + ) + if err != nil { + return err + } + fmt.Printf("Index created: %t\n", createResp.Acknowledged) + + splitResp, err := client.Indices.Split( + ctx, + opensearchapi.IndicesSplitReq{ + Index: "books", + Target: "books-large", + Body: strings.NewReader(`{"settings":{"index":{"number_of_shards": 10}}}`), + }, + ) + if err != nil { + return err + } + fmt.Printf("Index splited: %t\n", splitResp.Acknowledged) + + settingResp, err = client.Indices.Settings.Put( + ctx, + opensearchapi.SettingsPutReq{ + Indices: []string{"books"}, + Body: strings.NewReader(`{"index":{"blocks":{"write":null}}}`), + }, + ) + if err != nil { + return err + } + fmt.Printf("Settings updated: %t\n", settingResp.Acknowledged) ``` ## Cleanup @@ -197,10 +237,18 @@ log.Printf("response: [%+v]", res) Let's delete all the indices we created in this guide: ```go -// movies and books are assigned to variables in the previous examples -deleteIndexes, err = client.Indices.Delete([]string{movies, books, "bigger_books", "movies_clone"}) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) + delResp, err := client.Indices.Delete( + ctx, + opensearchapi.IndicesDeleteReq{ + Indices: []string{"movies*", "books*"}, + Params: opensearchapi.IndicesDeleteParams{IgnoreUnavailable: opensearchapi.ToPointer(true)}, + }, + ) + if err != nil { + return err + } + fmt.Printf("Deleted: %t\n", delResp.Acknowledged) + + return nil } -log.Printf("response: [%+v]", deleteIndexes) ``` diff --git a/guides/bulk.md b/guides/bulk.md index dca089ca3..8c3a9d16b 100644 --- a/guides/bulk.md +++ b/guides/bulk.md @@ -10,58 +10,79 @@ First, create a client instance with the following code: package main import ( - "github.com/opensearch-project/opensearch-go/v2" - "log" + "context" + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/opensearch-project/opensearch-go/v2/opensearchapi" ) func main() { - client, err := opensearch.NewDefaultClient() - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", client) + if err := example(); err != nil { + fmt.Println(fmt.Sprintf("Error: %s", err)) + os.Exit(1) + } } + +func example() error { + client, err := opensearchapi.NewDefaultClient() + if err != nil { + return err + } + + ctx := context.Background() ``` Next, create an index named `movies` and another named `books` with the default settings: ```go -movies := "movies" -books := "books" - -createMovieIndex, err := client.Indices.Create(movies) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", createMovieIndex) - -createBooksIndex, err := client.Indices.Create(books) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", createBooksIndex) + movies := "movies" + books := "books" + + createResp, err := client.Indices.Create(ctx, opensearchapi.IndicesCreateReq{Index: movies}) + if err != nil { + return err + } + fmt.Printf("Index created: %t\n", createResp.Acknowledged) + + createResp, err := client.Indices.Create(ctx, opensearchapi.IndicesCreateReq{Index: books}) + if err != nil { + return err + } + fmt.Printf("Index created: %t\n", createResp.Acknowledged) ``` ## Bulk API -The `bulk` API action allows you to perform document operations in a single request. The body of the request is an array of objects that contains the bulk operations and the target documents to index, create, update, or delete. +The `bulk` API action allows you to perform document operations in a single request. The body of the request consist of minimum two objects that contains the bulk operations and the target documents to index, create, update, or delete. ### Indexing multiple documents The following code creates two documents in the `movies` index and one document in the `books` index: ```go - res, err := client.Bulk(strings.NewReader(`{ "index": { "_index": "movies", "_id": 1 } } + bulkResp, err := client.Bulk( + ctx, + opensearchapi.BulkReq{ + Body: strings.NewReader(`{ "index": { "_index": "movies", "_id": 1 } } { "title": "Beauty and the Beast", "year": 1991 } { "index": { "_index": "movies", "_id": 2 } } { "title": "Beauty and the Beast - Live Action", "year": 2017 } { "index": { "_index": "books", "_id": 1 } } { "title": "The Lion King", "year": 1994 } -`)) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) +`), + }, + ) + if err != nil { + return err + } + respAsJson, err := json.MarshalIndent(bulkResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Bulk Resp:\n%s\n", string(respAsJson)) ``` ### Creating multiple documents @@ -69,7 +90,10 @@ The following code creates two documents in the `movies` index and one document Similarly, instead of calling the `create` method for each document, you can use the `bulk` API to create multiple documents in a single request. The following code creates three documents in the `movies` index and one in the `books` index: ```go - res, err = client.Bulk(strings.NewReader(`{ "create": { "_index": "movies" } } + bulkResp, err = client.Bulk( + ctx, + opensearchapi.BulkReq{ + Body: strings.NewReader(`{ "create": { "_index": "movies" } } { "title": "Beauty and the Beast 2", "year": 2030 } { "create": { "_index": "movies", "_id": 1 } } { "title": "Beauty and the Beast 3", "year": 2031 } @@ -77,11 +101,17 @@ Similarly, instead of calling the `create` method for each document, you can use { "title": "Beauty and the Beast 4", "year": 2049 } { "create": { "_index": "books" } } { "title": "The Lion King 2", "year": 1998 } -`)) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) +`), + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(bulkResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Bulk Resp:\n%s\n", string(respAsJson)) ``` We omit the `_id` for each document and let OpenSearch generate them for us in this example, just like we can with the `create` method. @@ -89,15 +119,24 @@ We omit the `_id` for each document and let OpenSearch generate them for us in t ### Updating multiple documents ```go - res, err = client.Bulk(strings.NewReader(`{ "update": { "_index": "movies", "_id": 1 } } + bulkResp, err = client.Bulk( + ctx, + opensearchapi.BulkReq{ + Body: strings.NewReader(`{ "update": { "_index": "movies", "_id": 1 } } { "doc": { "year": 1992 } } { "update": { "_index": "movies", "_id": 1 } } { "doc": { "year": 2018 } } -`)) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) +`), + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(bulkResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Bulk Resp:\n%s\n", string(respAsJson)) ``` Note that the updated data is specified in the `doc` with a full or partial JSON document, depending on how much of the document you want to update. @@ -107,13 +146,22 @@ Note that the updated data is specified in the `doc` with a full or partial JSON If the document doesn’t exist, OpenSearch doesn’t return an error, but instead returns not_found under result. Delete actions don’t require documents on the next line ```go - res, err = client.Bulk(strings.NewReader(`{ "delete": { "_index": "movies", "_id": 1 } } + bulkResp, err = client.Bulk( + ctx, + opensearchapi.BulkReq{ + Body: strings.NewReader(`{ "delete": { "_index": "movies", "_id": 1 } } { "delete": { "_index": "movies", "_id": 2 } } - `)) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) + `), + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(bulkResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Bulk Resp:\n%s\n", string(respAsJson)) ``` ### Mix and match operations @@ -121,71 +169,52 @@ If the document doesn’t exist, OpenSearch doesn’t return an error, but inste You can mix and match the different operations in a single request. The following code creates two documents, updates one document, and deletes another document: ```go - res, err = client.Bulk(strings.NewReader(`{ "create": { "_index": "movies", "_id": 3 } } + bulkResp, err = client.Bulk( + ctx, + opensearchapi.BulkReq{ + Body: strings.NewReader(`{ "create": { "_index": "movies", "_id": 3 } } { "title": "Beauty and the Beast 5", "year": 2050 } { "create": { "_index": "movies", "_id": 4 } } { "title": "Beauty and the Beast 6", "year": 2051 } { "update": { "_index": "movies", "_id": 3 } } { "doc": { "year": 2052 } } { "delete": { "_index": "movies", "_id": 4 } } -`)) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) +`), + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(bulkResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Bulk Resp:\n%s\n", string(respAsJson)) ``` ### Handling errors The `bulk` API returns an array of responses for each operation in the request body. Each response contains a `status` field that indicates whether the operation was successful or not. If the operation was successful, the `status` field is set to a `2xx` code. Otherwise, the response contains an error message in the `error` field. -The following code shows how to look for errors in the response: +The following code shows an example on how to look for errors in the response: ```go -type Response struct { - Took int `json:"took"` - Errors bool `json:"errors"` - Items []struct { - Delete struct { - Index string `json:"_index"` - Id string `json:"_id"` - Version int `json:"_version"` - Result string `json:"result"` - Shards struct { - Total int `json:"total"` - Successful int `json:"successful"` - Failed int `json:"failed"` - } `json:"_shards"` - SeqNo int `json:"_seq_no"` - PrimaryTerm int `json:"_primary_term"` - Status int `json:"status"` - } `json:"delete,omitempty"` - } `json:"items"` -} - - res, err = client.Bulk(strings.NewReader(`{ "delete": { "_index": "movies", "_id": 10 } } -`)) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - - body, err := io.ReadAll(res.Body) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - - var response Response - if err := json.Unmarshal(body, &response); err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - - for _, item := range response.Items { - if item.Delete.Status > 299 { - log.Printf("error occurred: [%s]", item.Delete.Result) - } else { - log.Printf("success: [%s]", item.Delete.Result) - } - } + bulkResp, err = client.Bulk( + ctx, + opensearchapi.BulkReq{ + Body: strings.NewReader("{\"delete\":{\"_index\":\"movies\",\"_id\":1}}\n"), + }, + ) + if err != nil { + return err + } + for _, item := range bulkResp.Items { + for operation, resp := range item { + if resp.Status > 299 { + fmt.Printf("Bulk %s Error: %s\n", operation, resp.Result) + } + } + } ``` ## Cleanup @@ -193,12 +222,18 @@ type Response struct { To clean up the resources created in this guide, delete the `movies` and `books` indices: ```go -deleteIndexes, err := client.Indices.Delete( - []string{movies, books}, - client.Indices.Delete.WithIgnoreUnavailable(true), -) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) + delResp, err := client.Indices.Delete( + ctx, + opensearchapi.IndicesDeleteReq{ + Indices: []string{"movies", "books"}, + Params: opensearchapi.IndicesDeleteParams{IgnoreUnavailable: opensearchapi.ToPointer(true)}, + }, + ) + if err != nil { + return err + } + fmt.Printf("Deleted: %t\n", delResp.Acknowledged) + + return nil } -log.Printf("response: [%+v]", deleteIndexes) ``` diff --git a/guides/data_streams.md b/guides/data_streams.md index 7a10b723c..a1732c3c3 100644 --- a/guides/data_streams.md +++ b/guides/data_streams.md @@ -1,179 +1,146 @@ -## Data Streams API +# Data Streams API -### Create Data Streams +## Setup -- Create new client +First, create a client instance with the following code: -``` -client, err := opensearch.NewDefaultClient() -if err != nil { - panic(err) -} -``` - -- Create template index - -``` -iPut := opensearchapi.IndicesPutIndexTemplateRequest{ - Name: "demo-data-template", - Pretty: true, - Human: true, - ErrorTrace: true, - Body: strings.NewReader(`{"index_patterns": ["demo-*"], "data_stream": {}, "priority": 100} }`), -} -iPutResponse, err := iPut.Do(context.Background(), client) -``` - -- Prepare request object +```go +package main -``` -es := opensearchapi.IndicesCreateDataStreamRequest{ - Name: "demo-name", - Human: true, - Pretty: true, - ErrorTrace: true, - Header: map[string][]string{ - "Content-Type": {"application/json"}, - }, -} -``` +import ( + "context" + "fmt" + "os" + "strings" -- Execute request + "github.com/opensearch-project/opensearch-go/v2/opensearchapi" +) -``` -res, err := es.Do(context.TODO(), client) -if err != nil { - // do not panic in production code - panic(err) +func main() { + if err := example(); err != nil { + fmt.Println(fmt.Sprintf("Error: %s", err)) + os.Exit(1) + } } -``` -- Try to read response +func example() error { + client, err := opensearchapi.NewDefaultClient() + if err != nil { + return err + } + ctx := context.Background() +``` + +Next, create an index template with a data_stream section: + +```go + tempCreateResp, err := client.IndexTemplate.Create( + ctx, + opensearchapi.IndexTemplateCreateReq{ + IndexTemplate: "books", + Body: strings.NewReader(`{ + "index_patterns": ["books-nonfiction"], + "template": { + "settings": { + "index": { + "number_of_shards": 3, + "number_of_replicas": 0 + } + } + }, + "data_stream": {}, + "priority": 50 + }`), + }, + ) + if err != nil { + return err + } + fmt.Printf("Index Tempalte created: %t\n", tempCreateResp.Acknowledged) ``` -defer res.Body.Close() -body, err := ioutil.ReadAll(res.Body) -if err != nil { - // do not panic in production code - panic(err) -} -fmt.Println("Response Status Code: ", res.StatusCode) -fmt.Println("Response Headers: ", res.Header) -fmt.Println("Response Body: ", string(body)) -``` +## Create Data Streams -- Successfully created data stream +The `DataStream.Create()` action allows you to create a new Data Stream: -``` -Response Status Code: 200 -Response Headers: map[Content-Length:[28] Content-Type:[application/json; charset=UTF-8]] -Response Body: {"acknowledged" : true} +```go + createResp, err := client.DataStream.Create(ctx, opensearchapi.DataStreamCreateReq{DataStream: "books-nonfiction"}) + if err != nil { + return err + } + fmt.Printf("Created: %t\n", createResp.Acknowledged) ``` -### Delete Data Streams +## Get Data Streams -- Create new client as previous example -- Prepare request object +The `DataStream.Get()` action allows you to get information about Data Streams. Omitting the Request struct will get all DataStreams: -``` -opensearchapi.IndicesDeleteDataStreamRequest{ - Name: "demo-name", - Pretty: true, - Human: true, - ErrorTrace: true, - Header: map[string][]string{ - "Content-Type": {"application/json"}, - }, -} +```go + getResp, err := client.DataStream.Get(ctx, nil) + if err != nil { + return err + } + respAsJson, err := json.MarshalIndent(getResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Get DataStream:\n%s\n", string(respAsJson)) ``` -- Execute request as previous example -- Try to read response as previous example -- Successfully deleted data stream +By specifying a Data Stream in the request you'll only see the requested Data Stream: +```go + getResp, err = client.DataStream.Get(ctx, &opensearchapi.DataStreamGetReq{DataStreams: []string{"books-nonfiction"}}) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(getResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Get DataStream:\n%s\n", string(respAsJson)) ``` -Response Status Code: 200 -Response Headers: map[Content-Length:[28] Content-Type:[application/json; charset=UTF-8]] -Response Body: {"acknowledged" : true} -``` - -### Get All Data Streams -- Create new client as previous example -- Prepare request object +## Get Data Stream Stats -``` -r := opensearchapi.IndicesGetDataStreamRequest{ - Pretty: true, - Human: true, - ErrorTrace: true, - Header: map[string][]string{ - "Content-Type": {"application/json"}, - }, -} -``` +The `DataStream.Stats()` action allows you to get stats about Data Streams: -- Execute request as previous example -- Try to read response as previous example -- Successfully retrieved data streams - -``` -Response Status Code: 200 -Response Headers: map[Content-Length:[28] Content-Type:[application/json; charset=UTF-8]] -Response Body: {"data_streams":[{"name":"demo-name","timestamp_field":{"name":"@timestamp"},"indices":[{"index_name":".ds-demo-2023-03-21-23-33-46-000001","index_uuid":"NnzgqnP0ThS7LOMHJuZ-VQ"}],"generation":1,"status":"YELLOW","template":"demo-data-template"}]} +```go + statsResp, err := client.DataStream.Stats(ctx, nil) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(statsResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Stats DataStream:\n%s\n", string(respAsJson)) ``` -### Get Specific Data Stream +## Delete Data Streams -- Create new client as previous example -- Prepare request object +The `DataStream.Delete()` action allows you to delete a Data Stream: -``` -r := opensearchapi.IndicesGetDataStreamRequest{ - Name: "demo-name", - Pretty: true, - Human: true, - ErrorTrace: true, - Header: map[string][]string{ - "Content-Type": {"application/json"}, - }, +```go + delResp, err := client.DataStream.Delete(ctx, opensearchapi.DataStreamDeleteReq{DataStream: "books-nonfiction"}) + if err != nil { + return err } + fmt.Printf("DataStream deleted: %t\n", delResp.Acknowledged) ``` -- Execute request as previous example -- Try to read response as previous example -- Successfully retrieved data stream - -``` -Response Status Code: 200 -Response Headers: map[Content-Length:[28] Content-Type:[application/json; charset=UTF-8]] -Response Body: {"data_streams":[{"name":"demo-name","timestamp_field":{"name":"@timestamp"},"indices":[{"index_name":".ds-demo-2023-03-21-23-31-50-000001","index_uuid":"vhsowqdeRFCmr1GgQ7mIsQ"}],"generation":1,"status":"YELLOW","template":"demo-data-template"}]} -``` +## Cleanup -### Get Specific Data Stream Stats +To clean up the resources created in this guide, delete the index template: -- Create new client as as previous example -- Prepare request object +```go + delTempResp, err := client.IndexTemplate.Delete(ctx, opensearchapi.IndexTemplateDeleteReq{IndexTemplate: "books"}) + if err != nil { + return err + } + fmt.Printf("Deleted templates: %t\n", delTempResp.Acknowledged) -``` -r := opensearchapi.IndicesGetDataStreamStatsRequest{ - Name: "demo-name", - Pretty: true, - Human: true, - ErrorTrace: true, - Header: map[string][]string{ - "Content-Type": {"application/json"}, - }, + return nil } ``` - -- Execute request as previous example -- Try to read response as previous example -- Successfully retrieved data stream stats - -``` -Response Status Code: 200 -Response Headers: map[Content-Length:[28] Content-Type:[application/json; charset=UTF-8]] -Response Body: {"_shards":{"total":2,"successful":1,"failed":0},"data_stream_count":1,"backing_indices":1,"total_store_size":"208b","total_store_size_bytes":208,"data_streams":[{"data_stream":"demo-name","backing_indices":1,"store_size":"208b","store_size_bytes":208,"maximum_timestamp":0}]} -``` diff --git a/guides/document_lifecycle.md b/guides/document_lifecycle.md index 6cfa40a61..682279957 100644 --- a/guides/document_lifecycle.md +++ b/guides/document_lifecycle.md @@ -7,42 +7,41 @@ This guide covers OpenSearch Golang Client API actions for Document Lifecycle. Y Assuming you have OpenSearch running locally on port 9200, you can create a client instance with the following code: ```go - package main - - import ( - "github.com/opensearch-project/opensearch-go/v2" - "log" - ) - - func main() { - client, err := opensearch.NewDefaultClient() - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", client) - } +package main + +import ( + "context" + "fmt" + "os" + "strings" + + "github.com/opensearch-project/opensearch-go/v2/opensearchapi" +) + +func main() { + if err := example(); err != nil { + fmt.Println(fmt.Sprintf("Error: %s", err)) + os.Exit(1) + } +} + +func example() error { + client, err := opensearchapi.NewDefaultClient() + if err != nil { + return err + } + + ctx := context.Background() ``` Next, create an index named `movies` with the default settings: ```go - movies := "movies" - - // delete the indexes if they exist - deleteIndexes, err := client.Indices.Delete( - []string{movies}, - client.Indices.Delete.WithIgnoreUnavailable(true), - ) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", deleteIndexes) - - createMovieIndex, err := client.Indices.Create(movies) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", createMovieIndex) + createResp, err := client.Indices.Create(ctx, opensearchapi.IndicesCreateReq{Index: "movies"}) + if err != nil { + return err + } + fmt.Printf("Created: %t\n", createResp.Acknowledged) ``` ## Document API Actions @@ -52,42 +51,64 @@ Next, create an index named `movies` with the default settings: To create a new document, use the `create` or `index` API action. The following code creates two new documents with IDs of `1` and `2`: ```go - res, err := client.Create(movies, "1", strings.NewReader(`{"title": "Beauty and the Beast", "year": 1991 }`)) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) - - res, err = client.Create(movies, "2", strings.NewReader(`{"title": "Beauty and the Beast - Live Action", "year": 2017 }`)) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) + docCreateResp, err := client.Document.Create( + ctx, + opensearchapi.DocumentCreateReq{ + Index: "movies", + DocumentID: "1", + Body: strings.NewReader(`{"title": "Beauty and the Beast", "year": 1991 }`), + }, + ) + if err != nil { + return err + } + fmt.Printf("Document: %s\n", docCreateResp.Result) + + docCreateResp, err = client.Document.Create( + ctx, + opensearchapi.DocumentCreateReq{ + Index: "movies", + DocumentID: "2", + Body: strings.NewReader(`{"title": "Beauty and the Beast - Live Action", "year": 2017 }`), + }, + ) + if err != nil { + return err + } + fmt.Printf("Document: %s\n", docCreateResp.Result) ``` Note that the `create` action is NOT idempotent. If you try to create a document with an ID that already exists, the request will fail: ```go - res, err = client.Create(movies, "2", strings.NewReader(`{"title": "Just Another Movie" }`)) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } + _, err = client.Document.Create( + ctx, + opensearchapi.DocumentCreateReq{ + Index: "movies", + DocumentID: "2", + Body: strings.NewReader(`{"title": "Just Another Movie" }`), + }, + ) + if err != nil { + fmt.Println(err) + } ``` The `index` action, on the other hand, is idempotent. If you try to index a document with an existing ID, the request will succeed and overwrite the existing document. Note that no new document will be created in this case. You can think of the `index` action as an upsert: ```go - res, err = client.Index(movies, strings.NewReader(`{"title": "Updated Title" }`), client.Index.WithDocumentID("2")) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) - - res, err = client.Index(movies, strings.NewReader(`{ "title": "The Lion King", "year": 1994}`), client.Index.WithDocumentID("2")) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) + indexResp, err := client.Index( + ctx, + opensearchapi.IndexReq{ + Index: "movies", + DocumentID: "2", + Body: strings.NewReader(`{"title": "Updated Title" }`), + }, + ) + if err != nil { + return err + } + fmt.Printf("Document: %s\n", indexResp.Result) ``` ### Create a new document with auto-generated ID @@ -95,11 +116,21 @@ The `index` action, on the other hand, is idempotent. If you try to index a docu You can also create a new document with an auto-generated ID by omitting the `id` parameter. The following code creates documents with an auto-generated IDs in the `movies` index: ```go - res, err = client.Index(movies, strings.NewReader(`{ "title": "The Lion King 2", "year": 1978}`)) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) + indexResp, err = client.Index( + ctx, + opensearchapi.IndexReq{ + Index: "movies", + Body: strings.NewReader(`{ "title": "The Lion King 2", "year": 1978}`), + }, + ) + if err != nil { + return err + } + respAsJson, err := json.MarshalIndent(indexResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Index:\n%s\n", respAsJson) ``` In this case, the ID of the created document in the `result` field of the response body: @@ -125,30 +156,53 @@ In this case, the ID of the created document in the `result` field of the respon To get a document, use the `get` API action. The following code gets the document with ID `1` from the `movies` index: ```go - res, err = client.Get(movies, "1") - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) - // OUTPUT: {"_index":"movies","_id":"1","_version":1,"_seq_no":0,"_primary_term":1,"found":true,"_source":{"title": "Beauty and the Beast", "year": 1991 }} + getResp, err := client.Document.Get(ctx, opensearchapi.DocumentGetReq{Index: "movies", DocumentID: "1"}) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(getResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Get Document:\n%s\n", respAsJson) ``` You can also use `_source_include` and `_source_exclude` parameters to specify which fields to include or exclude in the response: ```go - res, err = client.Get(movies, "1", client.Get.WithSourceIncludes("title")) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) - // OUTPUT: {"_index":"movies","_id":"1","_version":1,"_seq_no":0,"_primary_term":1,"found":true,"_source":{"title":"Beauty and the Beast"}} - - res, err = client.Get(movies, "1", client.Get.WithSourceExcludes("title")) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) - // OUTPUT: {"_index":"movies","_id":"1","_version":1,"_seq_no":0,"_primary_term":1,"found":true,"_source":{"year":1991}} + getResp, err = client.Document.Get( + ctx, + opensearchapi.DocumentGetReq{ + Index: "movies", + DocumentID: "1", + Params: opensearchapi.DocumentGetParams{SourceIncludes: []string{"title"}}, + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(getResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Get Document:\n%s\n", respAsJson) + + getResp, err = client.Document.Get( + ctx, + opensearchapi.DocumentGetReq{ + Index: "movies", + DocumentID: "1", + Params: opensearchapi.DocumentGetParams{SourceExcludes: []string{"title"}}, + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(getResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Get Document:\n%s\n", respAsJson) ``` ### Get multiple documents @@ -156,12 +210,21 @@ You can also use `_source_include` and `_source_exclude` parameters to specify w To get multiple documents, use the `mget` API action: ```go - res, err = client.Mget(strings.NewReader(`{ "docs": [{ "_id": "1" }, { "_id": "2" }] }`), client.Mget.WithIndex(movies)) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) - // OUTPUT: {"docs":[{"_index":"movies","_id":"1","_version":1,"_seq_no":0,"_primary_term":1,"found":true,"_source":{"title": "Beauty and the Beast", "year": 1991 }},{"_index":"movies","_id":"2","_version":3,"_seq_no":3,"_primary_term":1,"found":true,"_source":{ "title": "The Lion King", "year": 1994}}]} + mgetResp, err := client.MGet( + ctx, + opensearchapi.MGetReq{ + Index: "movies", + Body: strings.NewReader(`{ "docs": [{ "_id": "1" }, { "_id": "2" }] }`), + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(mgetResp, "", " ") + if err != nil { + return err + } + fmt.Printf("MGet Document:\n%s\n", respAsJson) ``` ### Check if a document exists @@ -169,11 +232,11 @@ To get multiple documents, use the `mget` API action: To check if a document exists, use the `exists` API action. The following code checks if the document with ID `1` exists in the `movies` index: ```go - res, err = client.Exists(movies, "1") - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) + existsResp, err := client.Document.Exists(ctx, opensearchapi.DocumentExistsReq{Index: "movies", DocumentID: "1"}) + if err != nil { + return err + } + fmt.Println(existsResp.Status()) ``` ### Update a document @@ -181,21 +244,43 @@ To check if a document exists, use the `exists` API action. The following code c To update a document, use the `update` API action. The following code updates the `year` field of the document with ID `1` in the `movies` index: ```go - res, err = client.Update(movies, "1", strings.NewReader(`{ "doc": { "year": 1995 } }`)) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) + updateResp, err := client.Update( + ctx, + opensearchapi.UpdateReq{ + Index: "movies", + DocumentID: "1", + Body: strings.NewReader(`{ "doc": { "year": 1995 } }`), + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(updateResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Update:\n%s\n", respAsJson) ``` Alternatively, you can use the `script` parameter to update a document using a script. The following code increments the `year` field of the of document with ID `1` by 5 using painless script, the default scripting language in OpenSearch: ```go - res, err = client.Update(movies, "1", strings.NewReader(`{ "script": { "source": "ctx._source.year += 5" } }`)) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) + updateResp, err = client.Update( + ctx, + opensearchapi.UpdateReq{ + Index: "movies", + DocumentID: "1", + Body: strings.NewReader(`{ "script": { "source": "ctx._source.year += 5" } }`), + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(updateResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Update:\n%s\n", respAsJson) ``` Note that while both `update` and `index` actions perform updates, they are not the same. The `update` action is a partial update, while the `index` action is a full update. The `update` action only updates the fields that are specified in the request body, while the `index` action overwrites the entire document with the new document. @@ -205,25 +290,27 @@ Note that while both `update` and `index` actions perform updates, they are not To update documents that match a query, use the `update_by_query` API action. The following code decreases the `year` field of all documents with `year` greater than 2023: ```go - res, err = client.Indices.Refresh( - client.Indices.Refresh.WithIndex(movies), - ) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) - - res, err = client.UpdateByQuery( - []string{movies}, - client.UpdateByQuery.WithQuery("year:<1990"), - client.UpdateByQuery.WithBody( - strings.NewReader(`{"script": { "source": "ctx._source.year -= 1" } }`), - ), - ) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) + _, err = client.Indices.Refresh(ctx, &opensearchapi.IndicesRefreshReq{Indices: []string{"movies"}}) + if err != nil { + return err + } + + upByQueryResp, err := client.UpdateByQuery( + ctx, + opensearchapi.UpdateByQueryReq{ + Indices: []string{"movies"}, + Params: opensearchapi.UpdateByQueryParams{Query: "year:<1990"}, + Body: strings.NewReader(`{"script": { "source": "ctx._source.year -= 1" } }`), + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(upByQueryResp, "", " ") + if err != nil { + return err + } + fmt.Printf("UpdateByQuery:\n%s\n", respAsJson) ``` Note that the `update_by_query` API action is needed to refresh the index before the query is executed. @@ -233,11 +320,15 @@ Note that the `update_by_query` API action is needed to refresh the index before To delete a document, use the `delete` API action. The following code deletes the document with ID `1`: ```go - res, err = client.Delete(movies, "1") - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) + docDelResp, err := client.Document.Delete(ctx, opensearchapi.DocumentDeleteReq{Index: "movies", DocumentID: "1"}) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(docDelResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Del Doc:\n%s\n", respAsJson) ``` ### Delete multiple documents by query @@ -245,23 +336,26 @@ To delete a document, use the `delete` API action. The following code deletes th To delete documents that match a query, use the `delete_by_query` API action. The following code deletes all documents with `year` greater than 2023: ```go - res, err = client.Indices.Refresh( - client.Indices.Refresh.WithIndex(movies), - ) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) - - res, err = client.DeleteByQuery( - []string{movies}, - strings.NewReader(`{ "query": { "match": { "title": "The Lion King" } } }`), - client.DeleteByQuery.WithQuery(`title: "The Lion King"`), - ) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) + _, err = client.Indices.Refresh(ctx, &opensearchapi.IndicesRefreshReq{Indices: []string{"movies"}}) + if err != nil { + return err + } + + delByQueryResp, err := client.Document.DeleteByQuery( + ctx, + opensearchapi.DocumentDeleteByQueryReq{ + Indices: []string{"movies"}, + Body: strings.NewReader(`{ "query": { "match": { "title": "The Lion King" } } }`), + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(delByQueryResp, "", " ") + if err != nil { + return err + } + fmt.Printf("DelByQuery Doc:\n%s\n", respAsJson) ``` Note that the `delete_by_query` API action is needed to refresh the index before the query is executed. @@ -271,12 +365,18 @@ Note that the `delete_by_query` API action is needed to refresh the index before To clean up the resources created in this guide, delete the `movies` index: ```go - deleteIndexes, err := client.Indices.Delete( - []string{movies}, - client.Indices.Delete.WithIgnoreUnavailable(true), - ) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", deleteIndexes) + delResp, err := client.Indices.Delete( + ctx, + opensearchapi.IndicesDeleteReq{ + Indices: []string{"movies"}, + Params: opensearchapi.IndicesDeleteParams{IgnoreUnavailable: opensearchapi.ToPointer(true)}, + }, + ) + if err != nil { + return err + } + fmt.Printf("Deleted: %t\n", delResp.Acknowledged) + + return nil +} ``` diff --git a/guides/index_lifecycle.md b/guides/index_lifecycle.md index b7f06c8b3..b07587d3d 100644 --- a/guides/index_lifecycle.md +++ b/guides/index_lifecycle.md @@ -1,6 +1,6 @@ # Index Lifecycle -This guide covers OpenSearch Golang Client API actions for Index Lifecycle. You'll learn how to create, read, update, and delete indices in your OpenSearch cluster. We will also leverage index templates to create default settings and mappings for indices of certain patterns. +This guide covers OpenSearch Golang Client API actions for Index Lifecycle. You'll learn how to create, get, update settings, update mapping, and delete indices in your OpenSearch cluster. We will also leverage index templates to create default settings and mappings for indices of certain patterns. ## Setup @@ -16,135 +16,173 @@ To start the cluster, run the following command: Let's create a client instance to access this cluster: ```go - package main - - import ( - "github.com/opensearch-project/opensearch-go/v2" - "log" - ) +package main + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "strings" + + "github.com/opensearch-project/opensearch-go/v2/opensearchapi" +) + +func main() { + if err := example(); err != nil { + fmt.Println(fmt.Sprintf("Error: %s", err)) + os.Exit(1) + } +} - func main() { - client, err := opensearch.NewDefaultClient() - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", client) - } +func example() error { + // Initialize the client with SSL/TLS enabled. + client, err := opensearchapi.NewClient( + opensearchapi.Config{ + Client: opensearch.Config{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // For testing only. Use certificate for validation. + }, + Addresses: []string{"https://localhost:9200"}, + Username: "admin", // For testing only. Don't store credentials in code. + Password: "admin", + }, + }, + ) + if err != nil { + return err + } + ctx := context.Background() ``` ## Index API Actions ### Create a new index -You can quickly create an index with default settings and mappings by using the `indices.create` API action. The following example creates an index named `paintings` with default settings and mappings: +You can quickly create an index with default settings and mappings by using the `client.Indices.Create` action. The following example creates an index named `paintings` with default settings and mappings: ```go - paintingsIndex, err := client.Indices.Create("paintings") - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", paintingsIndex) + createResp, err := client.Indices.Create(ctx, opensearchapi.IndicesCreateReq{Index: "paintings"}) + if err != nil { + return err + } + fmt.Printf("Created: %t\n", createResp.Acknowledged) ``` To specify settings and mappings, you can pass them as the `body` of the request. The following example creates an index named `movies` with custom settings and mappings: ```go - movies := "movies" - - createMovieIndex, err := client.Indices.Create(movies, - client.Indices.Create.WithBody(strings.NewReader(`{ - "settings": { - "index": { - "number_of_shards": 2, - "number_of_replicas": 1 - } - }, - "mappings": { - "properties": { - "title": { - "type": "text" - }, - "year": { - "type": "integer" - } - } - } - }`), - ), - ) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", createMovieIndex) + createResp, err = client.Indices.Create( + ctx, + opensearchapi.IndicesCreateReq{ + Index: "movies", + Body: strings.NewReader(`{ + "settings": { + "index": { + "number_of_shards": 2, + "number_of_replicas": 1 + } + }, + "mappings": { + "properties": { + "title": { + "type": "text" + }, + "year": { + "type": "integer" + } + } + } + }`), + }, + ) + if err != nil { + return err + } + fmt.Printf("Created: %t\n", createResp.Acknowledged) ``` When you create a new document for an index, OpenSearch will automatically create the index if it doesn't exist: ```go // return status code 404 Not Found - res, err := client.Indices.Exists([]string{"burner"}) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) - - res, err = client.Indices.Create( - "burner", - client.Indices.Create.WithBody(strings.NewReader(`{ "settings": {} }`)), - ) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) + existsResp, err := client.Indices.Exists(ctx, opensearchapi.IndicesExistsReq{Indices: []string{"burner"}}) + if err != nil { + return err + } + fmt.Printf("%s\n", existsResp) + + indexResp, err := client.Index(ctx, opensearchapi.IndexReq{Index: "burner", Body: strings.NewReader(`{"foo": "bar"}`)}) + if err != nil { + return err + } + fmt.Printf("Index: %s\n", indexResp.Result) // return status code 200 OK - res, err = client.Indices.Exists([]string{"burner"}) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) + existsResp, err = client.Indices.Exists(ctx, opensearchapi.IndicesExistsReq{Indices: []string{"burner"}}) + if err != nil { + return err + } + fmt.Printf("%s\n", existsResp) ``` ### Update an Index -You can update an index's settings and mappings by using the `indices.put_settings` and `indices.put_mapping` API actions. +You can update an index's settings and mappings by using the `client.Indices.Settings.Put()` and `client.Indices.Mapping.Put()` actions. The following example updates the `movies` index's number of replicas to `0`: ```go - res, err := client.Indices.PutSettings( - strings.NewReader(`{ "index": { "number_of_replicas": 0} }`), - client.Indices.PutSettings.WithIndex(movies), - ) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) + settingsPutResp, err := client.Indices.Settings.Put( + ctx, + opensearchapi.SettingsPutReq{ + Indices: []string{"burner"}, + Body: strings.NewReader(`{"index":{"number_of_replicas":0}}`), + }, + ) + if err != nil { + return err + } + fmt.Printf("Settings updated: %t\n", settingsPutResp.Acknowledged) ``` The following example updates the `movies` index's mappings to add a new field named `director`: ```go - res, err := client.Indices.PutMapping( - strings.NewReader(`{ "properties": { "director": { "type": "text" } } }`), - client.Indices.PutMapping.WithIndex(movies), - ) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) + mappingPutResp, err := client.Indices.Mapping.Put( + ctx, + opensearchapi.MappingPutReq{ + Indices: []string{"movies"}, + Body: strings.NewReader(`{"properties":{ "director":{"type":"text"}}}`), + }, + ) + if err != nil { + return err + } + fmt.Printf("Mappings updated: %t\n", mappingPutResp.Acknowledged) ``` ### Get Metadata for an Index -Let's check if the index's settings and mappings have been updated by using the `indices.get` API action: +Let's check if the index's settings and mappings have been updated by using the `client.Indices.Get()` action: ```go - res, err := client.Indices.Get([]string{movies}) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", res) + getResp, err := client.Indices.Get( + ctx, + opensearchapi.IndicesGetReq{ + Indices: []string{"movies"}, + }, + ) + if err != nil { + return err + } + // Json Marshal the struct to pretty print + respAsJson, err := json.MarshalIndent(getResp.Indices, "", " ") + if err != nil { + return err + } + fmt.Printf("Get Document:\n%s\n", string(respAsJson)) ``` The response body contains the index's settings and mappings: @@ -184,30 +222,36 @@ The response body contains the index's settings and mappings: ### Delete an Index -Let's delete the `movies` index by using the `indices.delete` API action: +Let's delete the `movies` index by using the `client.Indices.Delete()` action: ```go - deleteIndexes, err := client.Indices.Delete([]string{movies}) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", deleteIndexes) + delResp, err := client.Indices.Delete(ctx, opensearchapi.IndicesDeleteReq{Indices: []string{"movies"}}) + if err != nil { + return err + } + fmt.Printf("Deleted: %t\n", delResp.Acknowledged) ``` We can also delete multiple indices at once: ```go - deleteIndexes, err := client.Indices.Delete( - []string{movies, "burner", "paintings"}, - client.Indices.Delete.WithIgnoreUnavailable(true), - ) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", deleteIndexes) + delResp, err := client.Indices.Delete( + ctx, + opensearchapi.IndicesDeleteReq{ + Indices: []string{"movies", "paintings", "burner"}, + Params: opensearchapi.IndicesDeleteParams{IgnoreUnavailable: opensearchapi.ToPointer(true)}, + }, + ) + if err != nil { + return err + } + fmt.Printf("Deleted: %t\n", delResp.Acknowledged) + + return nil +} ``` -Notice that we are passing `ignore unavailable` to the request. This tells the client to ignore the `404` error if the index doesn't exist for deletion. Without it, the above `delete` request will throw an error because the `movies` index has already been deleted in the previous example. +Notice that we are passing `ignore unavailable` to the request. This tells the server to ignore the `404` error if the index doesn't exist for deletion. Without it, the above `delete` request will throw an error because the `movies` index has already been deleted in the previous example. ## Cleanup diff --git a/guides/index_template.md b/guides/index_template.md index f9e28a473..6f63af4f2 100644 --- a/guides/index_template.md +++ b/guides/index_template.md @@ -10,17 +10,43 @@ Assuming you have OpenSearch running locally on port 9200, you can create a clie package main import ( - "github.com/opensearch-project/opensearch-go/v2" - "log" + "context" + "crypto/tls" + "fmt" + "net/http" + "os" + "strings" + + "github.com/opensearch-project/opensearch-go/v2" + "github.com/opensearch-project/opensearch-go/v2/opensearchapi" ) func main() { - client, err := opensearch.NewDefaultClient() - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", client) + if err := example(); err != nil { + fmt.Println(fmt.Sprintf("Error: %s", err)) + os.Exit(1) + } } + +func example() error { + // Initialize the client with SSL/TLS enabled. + client, err := opensearchapi.NewClient( + opensearchapi.Config{ + Client: opensearch.Config{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // For testing only. Use certificate for validation. + }, + Addresses: []string{"https://localhost:9200"}, + Username: "admin", // For testing only. Don't store credentials in code. + Password: "admin", + }, + }, + ) + if err != nil { + return err + } + + ctx := context.Background() ``` ## Index Template API Actions @@ -30,223 +56,233 @@ func main() { You can create an index template to define default settings and mappings for indices of certain patterns. The following example creates an index template named `books` with default settings and mappings for indices of the `books-*` pattern: ```go -body := strings.NewReader(`{ - "index_patterns": ["books-*"], - "template": { - "settings": { - "index": { - "number_of_shards": 3, - "number_of_replicas": 0 - } - }, - "mappings": { - "properties": { - "title": { "type": "text" }, - "author": { "type": "text" }, - "published_on": { "type": "date" }, - "pages": { "type": "integer" } - } - } - } -}`) - -res, err := client.Indices.PutIndexTemplate("books", body) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) + tempCreateResp, err := client.IndexTemplate.Create( + ctx, + opensearchapi.IndexTemplateCreateReq{ + IndexTemplate: "books", + Body: strings.NewReader(`{ + "index_patterns": ["books-*"], + "template": { + "settings": { + "index": { + "number_of_shards": 3, + "number_of_replicas": 0 + } + }, + "mappings": { + "properties": { + "title": { "type": "text" }, + "author": { "type": "text" }, + "published_on": { "type": "date" }, + "pages": { "type": "integer" } + } + } + }, + "priority": 50 + }`), + }, + ) + if err != nil { + return err + } + fmt.Printf("Index Tempalte created: %t\n", tempCreateResp.Acknowledged) ``` Now, when you create an index that matches the `books-*` pattern, OpenSearch will automatically apply the template's settings and mappings to the index. Let's create an index named `books-nonfiction` and verify that its settings and mappings match those of the template: ```go -res, err = client.Indices.Create("books-nonfiction") -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) - -// check mappings properties -res, err = client.Indices.Get([]string{"books-nonfiction"}) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) + fmt.Printf("Index Tempalte created: %t\n", tempCreateResp.Acknowledged) + + createResp, err := client.Indices.Create(ctx, opensearchapi.IndicesCreateReq{Index: "books-nonfiction"}) + if err != nil { + return err + } + fmt.Printf("Created: %t\n", createResp.Acknowledged) + + getResp, err := client.Indices.Get(ctx, opensearchapi.IndicesGetReq{Indices: []string{"books-nonfiction"}}) + if err != nil { + return err + } + respAsJson, err := json.MarshalIndent(getResp.Indices, "", " ") + if err != nil { + return err + } + fmt.Printf("Get Document:\n%s\n", string(respAsJson)) ``` ### Multiple Index Templates -If multiple index templates match the index's name, OpenSearch will apply the template with the highest priority. The following example creates two index templates named `books-*` and `books-fiction-*` with different settings: +If multiple index templates match the index's name, OpenSearch will apply the template with the highest priority. The following example creates one more index templates named `books-fiction` with different settings: ```go -res, err := client.Indices.PutIndexTemplate("books", strings.NewReader(`{ - "index_patterns": ["books-*"], - "priority": 0, - "template": { - "settings": { - "index": { - "number_of_shards": 3, - "number_of_replicas": 0 - } - } - } -}`)) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) - -// higher priority than the `books` template -res, err = client.Indices.PutIndexTemplate("books-fiction", strings.NewReader(`{ - "index_patterns": ["books-fiction-*"], - "priority": 1, - "template": { - "settings": { - "index": { - "number_of_shards": 1, - "number_of_replicas": 1 - } - } - } -}`)) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) + // higher priority than the `books` template + tempCreateResp, err = client.IndexTemplate.Create( + ctx, + opensearchapi.IndexTemplateCreateReq{ + IndexTemplate: "books-fiction", + Body: strings.NewReader(`{ + "index_patterns": ["books-fiction-*"], + "template": { + "settings": { + "index": { + "number_of_shards": 1, + "number_of_replicas": 0 + } + } + }, + "priority": 60 + }`), + }, + ) + if err != nil { + return err + } + fmt.Printf("Index Tempalte created: %t\n", tempCreateResp.Acknowledged) ``` -When we create an index named `books-fiction-romance`, OpenSearch will apply the `books-fiction-*` template's settings to the index: +When we create an index named `books-fiction-romance`, OpenSearch will apply the `books-fiction` template's settings to the index: ```go -res, err = client.Indices.Create("books-fiction-romance") -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) + createResp, err = client.Indices.Create(ctx, opensearchapi.IndicesCreateReq{Index: "books-fiction-romance"}) + if err != nil { + return err + } + fmt.Printf("Created: %t\n", createResp.Acknowledged) + + getResp, err = client.Indices.Get(ctx, opensearchapi.IndicesGetReq{Indices: []string{"books-fiction-romance"}}) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(getResp.Indices, "", " ") + if err != nil { + return err + } + fmt.Printf("Get Document:\n%s\n", string(respAsJson)) +``` -res, err = client.Indices.Get([]string{"books-fiction-romance"}) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) +Let us clean up the created templates and indices: + +```go + delTempResp, err := client.IndexTemplate.Delete(ctx, opensearchapi.IndexTemplateDeleteReq{IndexTemplate: "books*"}) + if err != nil { + return err + } + fmt.Printf("Deleted templates: %t\n", delTempResp.Acknowledged) + + delResp, err := client.Indices.Delete( + ctx, + opensearchapi.IndicesDeleteReq{ + Indices: []string{"books-*"}, + Params: opensearchapi.IndicesDeleteParams{IgnoreUnavailable: opensearchapi.ToPointer(true)}, + }, + ) + if err != nil { + return err + } + fmt.Printf("Deleted indices: %t\n", delResp.Acknowledged) ``` -### Composable Index Templates +### Component Templates -Composable index templates are a new type of index template that allow you to define multiple component templates and compose them into a final template. The following example creates a component template named `books_mappings` with default mappings for indices of the `books-*` and `books-fiction-*` patterns: +Component templates are subsets of templates that can be used by index templates. This allows you do store duplicate index template parts in a Component template and reuse it across index templates. The following example creates a component template named `books` with default mappings and an index template with a `books-*` patterns referencing the component template: ```go -// delete index templates if they exist -res, err := client.Indices.DeleteIndexTemplate("books-*") -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) - -// delete indices if they exist -res, err = client.Indices.Delete([]string{"books-*", "books-fiction-*"}) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) - -// Composable Index Templates -res, err = client.Cluster.PutComponentTemplate("books_mappings", strings.NewReader(`{ - "template": { - "mappings": { - "properties": { - "title": { "type": "text" }, - "author": { "type": "text" }, - "published_on": { "type": "date" }, - "pages": { "type": "integer" } - } - } - } -}`)) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) - -// use the `books_mappings` component template with priority 0 -res, err = client.Indices.PutIndexTemplate("books", strings.NewReader(`{ - "index_patterns": ["books-*"], - "composed_of": ["books_mappings"], - "priority": 0, - "template": { - "settings": { - "index": { - "number_of_shards": 3, - "number_of_replicas": 0 - } - } - } -}`)) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) - -// use the `books_mappings` component template with priority 1 -res, err = client.Indices.PutIndexTemplate("books", strings.NewReader(`{ - "index_patterns": ["books-fiction-*"], - "composed_of": ["books_mappings"], - "priority": 1, - "template": { - "settings": { - "index": { - "number_of_shards": 3, - "number_of_replicas": 0 - } - } - } -}`)) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) + // Component templates + compTempCreateResp, err := client.ComponentTemplate.Create( + ctx, + opensearchapi.ComponentTemplateCreateReq{ + ComponentTemplate: "books", + Body: strings.NewReader(`{ + "template": { + "mappings": { + "properties": { + "title": { "type": "text" }, + "author": { "type": "text" }, + "published_on": { "type": "date" }, + "pages": { "type": "integer" } + } + } + } + }`), + }, + ) + if err != nil { + return err + } + fmt.Printf("Created: %t\n", compTempCreateResp.Acknowledged) + + // Index template composed of books component template + tempCreateResp, err := client.IndexTemplate.Create( + ctx, + opensearchapi.IndexTemplateCreateReq{ + IndexTemplate: "books", + Body: strings.NewReader(`{ + "index_patterns": ["books-*"], + "template": { + "settings": { + "index": { + "number_of_shards": 3, + "number_of_replicas": 0 + } + } + }, + "composed_of": ["books"], + "priority": 50 + }`), + }, + ) + if err != nil { + return err + } + fmt.Printf("Index Tempalte created: %t\n", tempCreateResp.Acknowledged) ``` -When we create an index named `books-fiction-horror`, OpenSearch will apply the `books-fiction-*` template's settings, and `books_mappings` template mappings to the index: +When we create an index named `books-fiction-horror`, OpenSearch will apply the `books` index template settings, and `books` component template mappings to the index: ```go -res, err = client.Indices.Create("books-fiction-horror") -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) - -res, err = client.Indices.Get([]string{"books-fiction-horror"}) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) + createResp, err = client.Indices.Create(ctx, opensearchapi.IndicesCreateReq{Index: "books-fiction-horror"}) + if err != nil { + return err + } + fmt.Printf("Index created: %t\n", createResp.Acknowledged) + + getResp, err = client.Indices.Get(ctx, opensearchapi.IndicesGetReq{Indices: []string{"books-fiction-horror"}}) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(getResp.Indices, "", " ") + if err != nil { + return err + } + fmt.Printf("Get Document:\n%s\n", string(respAsJson)) ``` ### Get an Index Template -You can get an index template with the `get_index_template` API action: +You can get an index template with the `IndexTemplate.Get()` action: ```go -res, err = client.Indices.GetIndexTemplate( - client.Indices.GetIndexTemplate.WithName("books"), -) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) + indexTempGetReq, err := client.IndexTemplate.Get(ctx, &opensearchapi.IndexTemplateGetReq{IndexTemplates: []string{"books"}}) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(indexTempGetReq, "", " ") + if err != nil { + return err + } + fmt.Printf("Get Index Template:\n%s\n", string(respAsJson)) ``` ### Delete an Index Template -You can delete an index template with the `delete_template` API action: +You can delete an index template with the `IndexTemplate.Delete()` action: ```go -res, err = client.Indices.DeleteIndexTemplate("books") -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) + delTempResp, err = client.IndexTemplate.Delete(ctx, opensearchapi.IndexTemplateDeleteReq{IndexTemplate: "books*"}) + if err != nil { + return err + } + fmt.Printf("Deleted templates: %t\n", delTempResp.Acknowledged) ``` ## Cleanup @@ -254,9 +290,24 @@ log.Printf("response: [%+v]", res) Let's delete all resources created in this guide: ```go -res, err = client.Indices.DeleteIndexTemplate("books-fiction") -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) + delResp, err = client.Indices.Delete( + ctx, + opensearchapi.IndicesDeleteReq{ + Indices: []string{"books-*"}, + Params: opensearchapi.IndicesDeleteParams{IgnoreUnavailable: opensearchapi.ToPointer(true)}, + }, + ) + if err != nil { + return err + } + fmt.Printf("Deleted indices: %t\n", delResp.Acknowledged) + + compTempDelResp, err := client.ComponentTemplate.Delete(ctx, opensearchapi.ComponentTemplateDeleteReq{ComponentTemplate: "books*"}) + if err != nil { + return err + } + fmt.Printf("Deleted templates: %t\n", compTempDelResp.Acknowledged) + + return nil } -log.Printf("response: [%+v]", res) ``` diff --git a/guides/search.md b/guides/search.md index e2b4fe2e0..9cb509f50 100644 --- a/guides/search.md +++ b/guides/search.md @@ -10,66 +10,77 @@ Let's start by creating an index and adding some documents to it: package main import ( - "context" - "fmt" - "github.com/opensearch-project/opensearch-go/v2" - "github.com/opensearch-project/opensearch-go/v2/opensearchapi" - "log" - "strings" + "context" + "fmt" + "os" + "strconv" + "strings" + + "github.com/opensearch-project/opensearch-go/v2/opensearchapi" ) func main() { - client, err := opensearch.NewDefaultClient() - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", client) - - movies := "movies" - - // create the index - createMovieIndex, err := client.Indices.Create(movies) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - log.Printf("response: [%+v]", createMovieIndex) - - for i := 1; i < 11; i++ { - req := opensearchapi.IndexRequest{ - Index: movies, - DocumentID: fmt.Sprintf("%d", i), - Body: strings.NewReader(fmt.Sprintf(`{"title": "The Dark Knight %d", "director": "Christopher Nolan", "year": %d}`, i, 2008+i)), - } - _, err := req.Do(context.Background(), client) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - } - - req := opensearchapi.IndexRequest{ - Index: movies, - Body: strings.NewReader(`{"title": "The Godfather", "director": "Francis Ford Coppola", "year": 1972}`), - } - _, err = req.Do(context.Background(), client) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - - req = opensearchapi.IndexRequest{ - Index: movies, - Body: strings.NewReader(`{"title": "The Shawshank Redemption", "director": "Frank Darabont", "year": 1994}`), - } - _, err = req.Do(context.Background(), client) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } - - // refresh the index to make the documents searchable - _, err = client.Indices.Refresh(client.Indices.Refresh.WithIndex(movies)) - if err != nil { - log.Printf("error occurred: [%s]", err.Error()) - } + if err := example(); err != nil { + fmt.Println(fmt.Sprintf("Error: %s", err)) + os.Exit(1) + } } + +func example() error { + client, err := opensearchapi.NewDefaultClient() + if err != nil { + return err + } + + ctx := context.Background() + exampleIndex := "movies" + + createResp, err := client.Indices.Create(ctx, opensearchapi.IndicesCreateReq{Index: exampleIndex}) + if err != nil { + return err + } + fmt.Printf("Created: %t\n", createResp.Acknowledged) + + for i := 1; i < 11; i++ { + _, err = client.Index( + ctx, + opensearchapi.IndexReq{ + Index: exampleIndex, + DocumentID: strconv.Itoa(i), + Body: strings.NewReader(fmt.Sprintf(`{"title": "The Dark Knight %d", "director": "Christopher Nolan", "year": %d}`, i, 2008+i)), + }, + ) + if err != nil { + return err + } + } + + _, err = client.Index( + ctx, + opensearchapi.IndexReq{ + Index: exampleIndex, + Body: strings.NewReader(`{"title": "The Godfather", "director": "Francis Ford Coppola", "year": 1972}`), + }, + ) + if err != nil { + return err + } + + _, err = client.Index( + ctx, + opensearchapi.IndexReq{ + Index: exampleIndex, + Body: strings.NewReader(`{"title": "The Shawshank Redemption", "director": "Frank Darabont", "year": 1994}`), + }, + ) + if err != nil { + return err + } + + _, err = client.Indices.Refresh(ctx, &opensearchapi.IndicesRefreshReq{Indices: []string{exampleIndex}}) + if err != nil { + return err + } ``` ## Search API @@ -79,26 +90,35 @@ func main() { The search API allows you to search for documents in an index. The following example searches for ALL documents in the `movies` index: ```go -res, err := client.Search( - client.Search.WithIndex(movies), -) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", res) + searchResp, err := client.Search(ctx, &opensearchapi.SearchReq{Indices: []string{exampleIndex}}) + if err != nil { + return err + } + respAsJson, err := json.MarshalIndent(searchResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Search Response:\n%s\n", string(respAsJson)) ``` You can also search for documents that match a specific query. The following example searches for documents that match the query `dark knight`: ```go -part, err := client.Search( - client.Search.WithIndex(movies), - client.Search.WithQuery(`title: "dark knight"`), -) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", part) + searchResp, err = client.Search( + ctx, + &opensearchapi.SearchReq{ + Indices: []string{exampleIndex}, + Params: opensearchapi.SearchParams{Query: `title: "dark knight"`}, + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(searchResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Search Response:\n%s\n", string(respAsJson)) ``` OpenSearch query DSL allows you to specify complex queries. Check out the [OpenSearch query DSL documentation](https://opensearch.org/docs/latest/query-dsl/) for more information. @@ -108,17 +128,26 @@ OpenSearch query DSL allows you to specify complex queries. Check out the [OpenS The search API allows you to paginate through the search results. The following example searches for documents that match the query `dark knight`, sorted by `year` in ascending order, and returns the first 2 results after skipping the first 5 results: ```go -sort, err := client.Search( - client.Search.WithIndex(movies), - client.Search.WithSize(2), - client.Search.WithFrom(5), - client.Search.WithSort("year:desc"), - client.Search.WithQuery(`title: "dark knight"`), -) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", sort) + searchResp, err = client.Search( + ctx, + &opensearchapi.SearchReq{ + Indices: []string{exampleIndex}, + Params: opensearchapi.SearchParams{ + Query: `title: "dark knight"`, + Size: opensearchapi.ToPointer(2), + From: opensearchapi.ToPointer(5), + Sort: []string{"year:desc"}, + }, + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(searchResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Search Response:\n%s\n", string(respAsJson)) ``` ### Pagination with scroll @@ -126,17 +155,26 @@ log.Printf("response: [%+v]", sort) When retrieving large amounts of non-real-time data, you can use the `scroll` parameter to paginate through the search results. ```go -page1, err := client.Search( - client.Search.WithIndex(movies), - client.Search.WithSize(2), - client.Search.WithQuery(`title: "dark knight"`), - client.Search.WithSort("year:asc"), - client.Search.WithScroll(time.Minute), -) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", page1) + searchResp, err = client.Search( + ctx, + &opensearchapi.SearchReq{ + Indices: []string{exampleIndex}, + Params: opensearchapi.SearchParams{ + Query: `title: "dark knight"`, + Size: opensearchapi.ToPointer(2), + Sort: []string{"year:desc"}, + Scroll: time.Minute, + }, + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(searchResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Search Response:\n%s\n", string(respAsJson)) ``` ### Pagination with Point in Time @@ -144,44 +182,59 @@ log.Printf("response: [%+v]", page1) The scroll example above has one weakness: if the index is updated while you are scrolling through the results, they will be paginated inconsistently. To avoid this, you should use the "Point in Time" feature. The following example demonstrates how to use the `point_in_time` and `pit_id` parameters to paginate through the search results: ```go -// create a point in time -_, pit, err := client.PointInTime.Create( - client.PointInTime.Create.WithIndex(movies), - client.PointInTime.Create.WithKeepAlive(time.Minute), -) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("created pit: [%+v]", pit) - -// run a search query with a pit.id -page1, err := client.Search( - client.Search.WithSize(5), - client.Search.WithBody(strings.NewReader(fmt.Sprintf(`{ "pit": { "id": "%s", "keep_alive": "1m" } }`, pit.PitID))), - client.Search.WithSort("year:asc"), -) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", page1) - -// to get the next set of documents, run the same query with the last document’s sort values as the search_after parameter, keeping the same sort and pit.id. -page2, err := client.Search( - client.Search.WithSize(5), - client.Search.WithBody(strings.NewReader(fmt.Sprintf(`{ "pit": { "id": "%s", "keep_alive": "1m" }, "search_after": [ "1994" ] }`, pit.PitID))), - client.Search.WithSort("year:asc"), -) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("response: [%+v]", page2) - -// to delete the point in time, run the following query -_, delpits, err := client.PointInTime.Delete(client.PointInTime.Delete.WithPitID(pit.PitID)) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("deleted pits: [%+v]", delpits) + pitCreateResp, err := client.PointInTime.Create( + ctx, + opensearchapi.PointInTimeCreateReq{ + Indices: []string{exampleIndex}, + Params: opensearchapi.PointInTimeCreateParams{KeepAlive: time.Minute}, + }, + ) + if err != nil { + return err + } + + searchResp, err = client.Search( + ctx, + &opensearchapi.SearchReq{ + Body: strings.NewReader(fmt.Sprintf(`{ "pit": { "id": "%s", "keep_alive": "1m" } }`, pitCreateResp.PitID)), + Params: opensearchapi.SearchParams{ + Size: opensearchapi.ToPointer(5), + Sort: []string{"year:desc"}, + }, + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(searchResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Search Response:\n%s\n", string(respAsJson)) + + searchResp, err = client.Search( + ctx, + &opensearchapi.SearchReq{ + Body: strings.NewReader(fmt.Sprintf(`{ "pit": { "id": "%s", "keep_alive": "1m" }, "search_after": [ "1994" ] }`, pitCreateResp.PitID)), + Params: opensearchapi.SearchParams{ + Size: opensearchapi.ToPointer(5), + Sort: []string{"year:desc"}, + }, + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(searchResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Search Response:\n%s\n", string(respAsJson)) + + _, err = client.PointInTime.Delete(ctx, opensearchapi.PointInTimeDeleteReq{PitID: []string{pitCreateResp.PitID}}) + if err != nil { + return err + } ``` Note that a point-in-time is associated with an index or a set of index. So, when performing a search with a point-in-time, you DO NOT specify the index in the search. @@ -191,43 +244,84 @@ Note that a point-in-time is associated with an index or a set of index. So, whe The source API returns the source of the documents with included or excluded fields. The following example returns all fields from document source in the `movies` index: ```go -getSourceRequest := opensearchapi.GetSourceRequest{ - Index: "movies", - DocumentID: "1", -} -getSourceResponse, err := getSourceRequest.Do(context.Background(), client) -if err != nil { - log.Printf("error occurred: [%s]", err.Error()) -} -log.Printf("source: [%+v]", getSourceResponse) + sourceResp, err := client.Document.Source( + ctx, + opensearchapi.DocumentSourceReq{ + Index: "movies", + DocumentID: "1", + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(sourceResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Source Response:\n%s\n", string(respAsJson)) ``` To include certain fields in the source response, use `SourceIncludes` or `Source`(this field is deprecated and `SourceIncludes` is recommended to be used instead). To get only required fields: ```go -getSourceRequest = opensearchapi.GetSourceRequest{ - Index: "movies", - DocumentID: "1", - SourceIncludes: []string{"title"}, -} + sourceResp, err := client.Document.Source( + ctx, + opensearchapi.DocumentSourceReq{ + Index: "movies", + DocumentID: "1", + Params: opensearchapi.DocumentSourceParams{ + SourceIncludes: []string{"title"}, + }, + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(sourceResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Source Response:\n%s\n", string(respAsJson)) ``` To exclude certain fields in the source response, use `SourceExcludes` as follows: ```go -getSourceRequest = opensearchapi.GetSourceRequest{ - Index: "movies", - DocumentID: "1", - SourceExcludes: []string{"title"}, -} + sourceResp, err = client.Document.Source( + ctx, + opensearchapi.DocumentSourceReq{ + Index: "movies", + DocumentID: "1", + Params: opensearchapi.DocumentSourceParams{ + SourceExcludes: []string{"title"}, + }, + }, + ) + if err != nil { + return err + } + respAsJson, err = json.MarshalIndent(sourceResp, "", " ") + if err != nil { + return err + } + fmt.Printf("Source Response:\n%s\n", string(respAsJson)) ``` ## Cleanup ```go -deleteIndex, err := client.Indices.Delete([]string{"movies"}) -if err != nil { - log.Printf("Error creating index: %s", err.Error()) + delResp, err := client.Indices.Delete( + ctx, + opensearchapi.IndicesDeleteReq{ + Indices: []string{"movies"}, + Params: opensearchapi.IndicesDeleteParams{IgnoreUnavailable: opensearchapi.ToPointer(true)}, + }, + ) + if err != nil { + return err + } + fmt.Printf("Deleted: %t\n", delResp.Acknowledged) + + return nil } -log.Printf("response: [%+v]", deleteIndex) ```