Repository: meilisearch/meilisearch-go

Issue:
[Bug] add sort to documents query
**Description**

Currently, when retrieving documents using the `POST` method, the request body does not support specifying a `sort` parameter.

https://github.com/meilisearch/meilisearch-go/blob/416cfe49d1d8044879f257c38a95bb728c1095c2/types.go#L499-L506

According to the Meilisearch API documentation, the `sort` field is supported in the request body for `POST` document retrieval:
[https://www.meilisearch.com/docs/reference/api/documents#body](https://www.meilisearch.com/docs/reference/api/documents#body)

We should update the SDK to include support for the `sort` parameter in `POST` queries.


Hints from issue comments:
@agam1092005: @ja7ad 
I would like to try this! thanks.

Solution patch:
diff --git a/index_document.go b/index_document.go
index 7005081..a125fb4 100644
--- a/index_document.go
+++ b/index_document.go
@@ -10,7 +10,6 @@ import (
 	"math"
 	"net/http"
 	"reflect"
-	"strconv"
 	"strings"
 )
 
@@ -274,35 +273,18 @@ func (i *index) GetDocuments(param *DocumentsQuery, resp *DocumentsResult) error
 }
 
 func (i *index) GetDocumentsWithContext(ctx context.Context, param *DocumentsQuery, resp *DocumentsResult) error {
+	if param == nil {
+		param = &DocumentsQuery{}
+	}
 	req := &internalRequest{
-		endpoint:            "/indexes/" + i.uid + "/documents",
-		method:              http.MethodGet,
+		endpoint:            "/indexes/" + i.uid + "/documents/fetch",
+		method:              http.MethodPost,
 		contentType:         contentTypeJSON,
-		withRequest:         nil,
+		withRequest:         param,
 		withResponse:        resp,
-		withQueryParams:     nil,
 		acceptedStatusCodes: []int{http.StatusOK},
 		functionName:        "GetDocuments",
 	}
-	if param != nil && param.Filter == nil {
-		req.withQueryParams = map[string]string{}
-		if param.Limit != 0 {
-			req.withQueryParams["limit"] = strconv.FormatInt(param.Limit, 10)
-		}
-		if param.Offset != 0 {
-			req.withQueryParams["offset"] = strconv.FormatInt(param.Offset, 10)
-		}
-		if len(param.Fields) != 0 {
-			req.withQueryParams["fields"] = strings.Join(param.Fields, ",")
-		}
-		if param.RetrieveVectors {
-			req.withQueryParams["retrieveVectors"] = "true"
-		}
-	} else if param != nil && param.Filter != nil {
-		req.withRequest = param
-		req.method = http.MethodPost
-		req.endpoint = req.endpoint + "/fetch"
-	}
 	if err := i.client.executeRequest(ctx, req); err != nil {
 		return VersionErrorHintMessage(err, req)
 	}
diff --git a/types.go b/types.go
index b9beab5..6822849 100644
--- a/types.go
+++ b/types.go
@@ -503,6 +503,7 @@ type DocumentsQuery struct {
 	Filter          interface{} `json:"filter,omitempty"`
 	RetrieveVectors bool        `json:"retrieveVectors,omitempty"`
 	Ids             []string    `json:"ids,omitempty"`
+	Sort            []string    `json:"sort,omitempty"`
 }
 
 // SimilarDocumentQuery is query parameters of similar documents



Test patch:
diff --git a/integration/index_document_test.go b/integration/index_document_test.go
index 5bac0c5..45c3b30 100644
--- a/integration/index_document_test.go
+++ b/integration/index_document_test.go
@@ -3,9 +3,10 @@ package integration
 import (
 	"bytes"
 	"crypto/tls"
+	"testing"
+
 	"github.com/meilisearch/meilisearch-go"
 	"github.com/stretchr/testify/require"
-	"testing"
 )
 
 func Test_GetDocumentsByIDs(t *testing.T) {
@@ -41,6 +42,179 @@ func Test_GetDocumentsByIDs(t *testing.T) {
 	require.Equal(t, results, documents.Results)
 }
 
+func Test_GetDocumentsWithQuery(t *testing.T) {
+	sv := setup(t, "")
+	t.Cleanup(cleanup(sv))
+
+	indexUID := "TestGetDocumentsWithQuery"
+	_, err := sv.CreateIndex(&meilisearch.IndexConfig{
+		Uid: indexUID,
+	})
+	require.NoError(t, err)
+
+	// Add test documents with sortable fields
+	testDocuments := []map[string]interface{}{
+		{"id": "1", "title": "Alice in Wonderland", "rating": 4.5, "year": 1865},
+		{"id": "2", "title": "Pride and Prejudice", "rating": 4.8, "year": 1813},
+		{"id": "3", "title": "The Great Gatsby", "rating": 4.2, "year": 1925},
+		{"id": "4", "title": "To Kill a Mockingbird", "rating": 4.9, "year": 1960},
+	}
+
+	index := sv.Index(indexUID)
+	task, err := index.AddDocuments(testDocuments, nil)
+	require.NoError(t, err)
+	testWaitForTask(t, index, task)
+
+	// Set sortable attributes for sorting tests
+	task, err = index.UpdateSortableAttributes(&[]string{"title", "rating", "year"})
+	require.NoError(t, err)
+	testWaitForTask(t, index, task)
+
+	tests := []struct {
+		name          string
+		query         *meilisearch.DocumentsQuery
+		expectedCount int
+		description   string
+	}{
+		{
+			name:          "Get all documents with nil query",
+			query:         nil,
+			expectedCount: 4,
+			description:   "Should return all documents when query is nil",
+		},
+		{
+			name:          "Get all documents with no query",
+			query:         &meilisearch.DocumentsQuery{},
+			expectedCount: 4,
+			description:   "Should return all documents when no filters applied",
+		},
+		{
+			name: "Get documents with limit",
+			query: &meilisearch.DocumentsQuery{
+				Limit: 2,
+			},
+			expectedCount: 2,
+			description:   "Should return only 2 documents when limit is set",
+		},
+		{
+			name: "Get documents with offset",
+			query: &meilisearch.DocumentsQuery{
+				Offset: 2,
+			},
+			expectedCount: 2,
+			description:   "Should return 2 documents when offset is 2",
+		},
+		{
+			name: "Get documents with specific fields",
+			query: &meilisearch.DocumentsQuery{
+				Fields: []string{"id", "title"},
+			},
+			expectedCount: 4,
+			description:   "Should return all documents but only specified fields",
+		},
+		{
+			name: "Get documents with IDs",
+			query: &meilisearch.DocumentsQuery{
+				Ids: []string{"1", "3"},
+			},
+			expectedCount: 2,
+			description:   "Should return only documents with specified IDs",
+		},
+		{
+			name: "Get documents with Sort parameter",
+			query: &meilisearch.DocumentsQuery{
+				Sort: []string{"year:asc"},
+			},
+			expectedCount: 4,
+			description:   "Should return all documents sorted by year ascending",
+		},
+		{
+			name: "Get documents with multiple Sort parameters",
+			query: &meilisearch.DocumentsQuery{
+				Sort: []string{"rating:desc", "year:asc"},
+			},
+			expectedCount: 4,
+			description:   "Should return all documents sorted by rating desc, then year asc",
+		},
+		{
+			name: "Get documents with combined parameters",
+			query: &meilisearch.DocumentsQuery{
+				Limit:  3,
+				Fields: []string{"id", "title", "rating"},
+				Sort:   []string{"rating:desc"},
+			},
+			expectedCount: 3,
+			description:   "Should return 3 documents with specified fields sorted by rating",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var documents meilisearch.DocumentsResult
+			err := index.GetDocuments(tt.query, &documents)
+			require.NoError(t, err, "GetDocuments should not return an error for: %s", tt.description)
+			require.Len(t, documents.Results, tt.expectedCount, "Expected %d documents but got %d for: %s", tt.expectedCount, len(documents.Results), tt.description)
+
+			// Additional validation for specific test cases
+			switch tt.name {
+			case "Get documents with specific fields":
+				// Verify only specified fields are returned
+				if len(documents.Results) > 0 {
+					doc := documents.Results[0]
+					require.Contains(t, doc, "id", "Should contain id field")
+					require.Contains(t, doc, "title", "Should contain title field")
+					require.NotContains(t, doc, "rating", "Should not contain rating field when not requested")
+				}
+
+			case "Get documents with Sort parameter":
+				// Verify documents are sorted by year ascending (1813, 1865, 1925, 1960)
+				if len(documents.Results) >= 4 {
+					expectedOrder := []string{"2", "1", "3", "4"} // IDs in order: 1813, 1865, 1925, 1960
+					for i, expectedID := range expectedOrder {
+						actualID := documents.Results[i]["id"]
+						require.Equal(t, toRawMessage(expectedID), actualID, "Document at position %d should have ID %s (sorted by year ascending)", i, expectedID)
+					}
+				}
+
+			case "Get documents with multiple Sort parameters":
+				// Verify documents are sorted by rating desc, then year asc
+				// Expected order: 4.9 (ID:4, 1960), 4.8 (ID:2, 1813), 4.5 (ID:1, 1865), 4.2 (ID:3, 1925)
+				if len(documents.Results) >= 4 {
+					expectedOrder := []string{"4", "2", "1", "3"} // Sorted by rating desc, then year asc
+					for i, expectedID := range expectedOrder {
+						actualID := documents.Results[i]["id"]
+						require.Equal(t, toRawMessage(expectedID), actualID, "Document at position %d should have ID %s (sorted by rating desc, then year asc)", i, expectedID)
+					}
+				}
+
+			case "Get documents with IDs":
+				// Verify only specified IDs are returned
+				for _, doc := range documents.Results {
+					id := doc["id"]
+					require.Contains(t, []interface{}{toRawMessage("1"), toRawMessage("3")}, id, "Should only return documents with IDs 1 or 3")
+				}
+
+			case "Get documents with combined parameters":
+				// Verify documents are sorted by rating desc and limited to 3, with only specified fields
+				if len(documents.Results) >= 3 {
+					expectedOrder := []string{"4", "2", "1"} // Top 3 by rating desc: 4.9, 4.8, 4.5
+					for i, expectedID := range expectedOrder {
+						doc := documents.Results[i]
+						actualID := doc["id"]
+						require.Equal(t, toRawMessage(expectedID), actualID, "Document at position %d should have ID %s (sorted by rating desc, limited to 3)", i, expectedID)
+
+						// Verify only specified fields are present
+						require.Contains(t, doc, "id", "Should contain id field")
+						require.Contains(t, doc, "title", "Should contain title field")
+						require.Contains(t, doc, "rating", "Should contain rating field")
+						require.NotContains(t, doc, "year", "Should not contain year field when not requested")
+					}
+				}
+			}
+		})
+	}
+}
+
 func Test_AddOrUpdateDocumentsWithContentEncoding(t *testing.T) {
 	tests := []struct {
 		Name            string
diff --git a/types_test.go b/types_test.go
index 3c1c2db..7f7f9ca 100644
--- a/types_test.go
+++ b/types_test.go
@@ -2,8 +2,9 @@ package meilisearch
 
 import (
 	"encoding/json"
-	"github.com/stretchr/testify/require"
 	"testing"
+
+	"github.com/stretchr/testify/require"
 )
 
 type sampleStructure struct {