A comprehensive Go client library for interacting with MarkLogic's REST APIs. This library provides type-safe, idiomatic Go interfaces to MarkLogic's document, search, semantic, and data management capabilities.
- Full-text & Structured Search - Query text, use structured queries, get suggestions, and retrieve faceted results
- Document CRUD - Read, write, update, and delete documents with metadata management
- Semantic/Graph Operations - Work with RDF triples, SPARQL queries, and semantic data
- Lexicon Value Enumeration - Query distinct values from range indexes with aggregation and co-occurrence analysis
- Server-Side Code Execution - Execute XQuery and JavaScript on the server with external variables
- Query Suggestions - Get suggestions for queries and implement autocomplete
- Multi-Statement Transactions - Atomically execute multiple document or search operations
- User-Defined Resources - Call custom REST extensions (GET/POST/PUT/DELETE)
- Flexible Indexes - Create and manage range and field indexes for optimized performance
- Temporal Document Operations - Track document versions across time with temporal axes and system time management
- Metadata Extraction & Validation - Extract metadata from documents and validate against configurable rules
- Bulk Operations - Efficiently batch read/write multiple documents
- Optic Queries - Execute SQL-like queries on structured data with explain and query planning
- Query Management - Install and manage query options, transforms, and extensions
- Format Flexibility - Seamless JSON/XML serialization with format negotiation
- Authentication - Basic Auth, Digest Auth, or no authentication support
go get github.com/ryanjdew/go-marklogic-gopackage main
import (
"fmt"
"log"
marklogic "github.com/ryanjdew/go-marklogic-go"
)
func main() {
// Create a client with Digest authentication
client, err := marklogic.NewClient(
"localhost", // host
8050, // port (REST API port)
"admin", // username
"admin", // password
marklogic.DigestAuth, // authentication type
)
if err != nil {
log.Fatal(err)
}
// Use client to interact with MarkLogic
_ = client
}The library supports three authentication methods:
// Basic Authentication (username:password in HTTP header)
client, err := marklogic.NewClient(host, port, user, pass, marklogic.BasicAuth)
// Digest Authentication (challenge/response)
client, err := marklogic.NewClient(host, port, user, pass, marklogic.DigestAuth)
// No Authentication
client, err := marklogic.NewClient(host, port, "", "", marklogic.None)import (
"fmt"
handle "github.com/ryanjdew/go-marklogic-go/handle"
search "github.com/ryanjdew/go-marklogic-go/search"
)
// Perform a text search
respHandle := search.ResponseHandle{Format: handle.JSON}
err := client.Search().Search("Shakespeare", 1, 10, nil, &respHandle)
if err != nil {
log.Fatal(err)
}
// Access results
results := respHandle.Deserialized().(*search.Response)
fmt.Printf("Total results: %d\n", results.Total)
for _, result := range results.Results {
fmt.Printf(" - %s (score: %d)\n", result.URI, result.Score)
}
// Or get the raw JSON response
fmt.Println(respHandle.Serialized())// Build a structured query
query := search.Query{
Queries: []any{
search.TermQuery{
Terms: []string{"Shakespeare", "tragedy"},
},
},
}
// Serialize to XML or JSON
qh := search.QueryHandle{Format: handle.XML}
qh.Serialize(query)
// Execute the search
respHandle := search.ResponseHandle{Format: handle.JSON}
err := client.Search().StructuredSearch(&qh, 1, 10, nil, &respHandle)// Get suggestions for partial query
sugRespHandle := search.SuggestionsResponseHandle{Format: handle.JSON}
err := client.Search().StructuredSuggestions(&qh, "shake", 10, "", nil, &sugRespHandle)
suggestions := sugRespHandle.Deserialized().(*search.Suggestions)
for _, suggestion := range suggestions.Suggestions {
fmt.Println(suggestion)
}// Delete all documents matching a query
params := map[string]string{
"collection": "old-data",
}
err := client.Search().Delete(params, nil, nil)import (
handle "github.com/ryanjdew/go-marklogic-go/handle"
documents "github.com/ryanjdew/go-marklogic-go/documents"
)
// Read single or multiple documents by URI
uris := []string{"/docs/doc1.xml", "/docs/doc2.json"}
respHandle := documents.ResponseHandle{Format: handle.JSON}
// Get content only
err := client.Documents().Read(uris, []string{"content"}, nil, nil, &respHandle)
// Get content + metadata
err := client.Documents().Read(
uris,
[]string{"content", "metadata", "permissions", "collections"},
nil, nil, &respHandle,
)// Create documents to write
docs := []*documents.DocumentDescription{
{
URI: "/docs/doc1.xml",
Content: bytes.NewBufferString("<root>Content</root>"),
Format: handle.XML,
Metadata: &documents.Metadata{
Collections: []string{"my-collection"},
Quality: 1,
},
},
{
URI: "/docs/doc2.json",
Content: bytes.NewBufferString(`{"key": "value"}`),
Format: handle.JSON,
Metadata: &documents.Metadata{
Collections: []string{"my-collection"},
},
},
}
// Write documents
respHandle := documents.ResponseHandle{}
err := client.Documents().Write(docs, nil, nil, &respHandle)// Use PATCH to update (or PUT to replace)
doc := &documents.DocumentDescription{
URI: "/docs/doc1.xml",
Content: bytes.NewBufferString("<root>Updated</root>"),
Format: handle.XML,
}
// PATCH for partial update
err := client.Documents().Write(
[]*documents.DocumentDescription{doc},
nil, nil, &respHandle,
)import (
semantics "github.com/ryanjdew/go-marklogic-go/semantics"
)
// Get things (entities) from the triple index
iris := []string{
"http://example.org/person/john",
"http://example.org/person/jane",
}
respHandle := semantics.ResponseHandle{Format: handle.JSON}
err := client.Semantics().Things(iris, &respHandle)
things := respHandle.Deserialized()
fmt.Println(things)// Execute a SPARQL query
sparqlQuery := `
SELECT ?subject ?predicate ?object
WHERE {
?subject ?predicate ?object
}
LIMIT 10
`
respHandle := semantics.ResponseHandle{Format: handle.JSON}
err := client.Semantics().Sparql(sparqlQuery, &respHandle)import (
"github.com/ryanjdew/go-marklogic-go/values"
)
// List all distinct values from a range index
respHandle := values.ResponseHandle{Format: handle.JSON}
err := client.Values().ListValues(
"status", // Lexicon name (e.g., range index name)
nil, // Optional parameters (pageLength, start, etc.)
&respHandle,
)
values := respHandle.Deserialized()
fmt.Println(values)// Query values with structured constraints
queryBody := `<values-query xmlns="http://marklogic.com/appservices/search">
<range name="status" type="xs:string">
<range-operator>EQ</range-operator>
<value>active</value>
</range>
</values-query>`
queryHandle := handle.RawHandle{Format: handle.XML}
queryHandle.Write([]byte(queryBody))
respHandle := values.ResponseHandle{Format: handle.XML}
err := client.Values().QueryValues(
"status",
nil,
&queryHandle,
&respHandle,
)// Calculate aggregate operations on lexicon values (sum, count, min, max, etc.)
aggregateBody := `<values-query xmlns="http://marklogic.com/appservices/search">
<range name="count" type="xs:int">
<aggregate function="sum" />
</range>
</values-query>`
queryHandle := handle.RawHandle{Format: handle.XML}
queryHandle.Write([]byte(aggregateBody))
respHandle := values.ResponseHandle{Format: handle.XML}
err := client.Values().AggregateValues(
"count",
nil,
&queryHandle,
&respHandle,
)
// Results contain sum, min, max, count, average// Find correlated values across multiple lexicons
queryHandle := handle.RawHandle{Format: handle.XML}
queryHandle.Write([]byte(`<values-query xmlns="http://marklogic.com/appservices/search"></values-query>`))
respHandle := values.ResponseHandle{Format: handle.XML}
err := client.Values().CoOccurrenceValues(
[]string{"status", "priority"}, // Multiple lexicon names
nil,
&queryHandle,
&respHandle,
)
// Results show value pairs and their co-occurrence frequency// Generate tuples of related values from multiple lexicons
queryHandle := handle.RawHandle{Format: handle.XML}
queryHandle.Write([]byte(`<values-query xmlns="http://marklogic.com/appservices/search"></values-query>`))
respHandle := values.ResponseHandle{Format: handle.XML}
err := client.Values().TupleValues(
[]string{"status", "priority", "date"}, // Multiple lexicon names
nil,
&queryHandle,
&respHandle,
)
// Results contain tuples with positional values from each lexiconimport (
"github.com/ryanjdew/go-marklogic-go/eval"
)
// Execute XQuery code on the server
xqueryCode := `
declare variable $x as xs:integer external;
$x * 2 + 1
`
respHandle := eval.ResponseHandle{Format: handle.JSON}
err := client.Eval().EvalXQuery(xqueryCode, nil, &respHandle)
// Result is available as JSON
result := respHandle.Deserialized()
fmt.Println(result)// Pass external variables to XQuery
params := map[string]string{
"x": `{"type": "xs:integer", "value": 21}`,
"y": `{"type": "xs:string", "value": "hello"}`,
}
respHandle := eval.ResponseHandle{Format: handle.JSON}
err := client.Eval().EvalXQuery(xqueryCode, params, &respHandle)// Execute JavaScript code on the server
jsCode := `
var x = 42;
var result = { value: x, doubled: x * 2 };
result;
`
respHandle := eval.ResponseHandle{Format: handle.JSON}
err := client.Eval().EvalJavaScript(jsCode, nil, &respHandle)// Get suggestions based on a partial query string
respHandle := search.SuggestionsResponseHandle{Format: handle.JSON}
err := client.Search().Suggest(
"mark", // Partial query string
10, // Limit number of suggestions
"", // Optional query options name
&respHandle,
)
suggestions := respHandle.Get()
for _, suggestion := range suggestions.Suggestions {
fmt.Println(suggestion)
}// Get suggestions based on a structured query
query := search.Query{
Queries: []any{
search.TermQuery{Terms: []string{"active"}},
},
}
qh := search.QueryHandle{Format: handle.XML}
qh.Serialize(query)
respHandle := search.SuggestionsResponseHandle{Format: handle.JSON}
err := client.Search().StructuredSuggestions(
&qh,
"par", // Partial term to suggest
5, // Limit
"", // Query options
nil, // Transaction
&respHandle,
)import (
"github.com/ryanjdew/go-marklogic-go/transactions"
)
// Start a new transaction
respHandle := transactions.TransactionHandle{Format: handle.JSON}
err := client.Transactions().Begin(&respHandle)
// Extract transaction ID
txInfo := respHandle.Deserialized().(transactions.TransactionInfo)
txid := txInfo.TxID// Begin transaction
txResp := transactions.TransactionHandle{Format: handle.JSON}
client.Transactions().Begin(&txResp)
txid := txResp.Deserialized().(transactions.TransactionInfo).TxID
// Create transaction object for use in operations
txn := client.NewTransaction()
txn.ID = txid
// Now use txn in document or search operations
doc := &documents.DocumentDescription{
URI: "/txn-test/doc1.json",
Content: bytes.NewBufferString(`{"status":"active"}`),
Format: handle.JSON,
}
err := client.Documents().Write([]*documents.DocumentDescription{doc}, nil, txn, &respHandle)
// Commit the transaction
commitResp := transactions.TransactionHandle{Format: handle.JSON}
client.Transactions().Commit(txid, &commitResp)// Rollback instead of commit to discard all changes
rollbackResp := transactions.TransactionHandle{Format: handle.JSON}
err := client.Transactions().Rollback(txid, &rollbackResp)// Check the status of an active transaction
statusResp := transactions.TransactionHandle{Format: handle.JSON}
err := client.Transactions().Status(txid, &statusResp)
info := statusResp.Deserialized().(transactions.TransactionInfo)
fmt.Printf("Transaction %s is %s\n", info.TxID, info.Status)import (
"github.com/ryanjdew/go-marklogic-go/resources"
)
// Call a custom GET extension with parameters
params := map[string]string{
"format": "json",
"limit": "10",
}
respHandle := resources.ResourceExtensionHandle{Format: handle.JSON}
err := client.Resources().Get("my-resource", params, &respHandle)
resource := respHandle.Deserialized().(resources.ResourceExtension)
fmt.Printf("Resource: %s - %s\n", resource.Name, resource.Title)// POST data to a custom resource
payload := handle.RawHandle{Format: handle.JSON}
payload.Write([]byte(`{"action":"process","data":"input"}`))
resp := resources.ResourceExtensionHandle{Format: handle.JSON}
err := client.Resources().Post("my-resource", nil, &payload, &resp)// PUT (update) data in a custom resource
payload := handle.RawHandle{Format: handle.JSON}
payload.Write([]byte(`{"title":"Updated Title","status":"active"}`))
resp := resources.ResourceExtensionHandle{Format: handle.JSON}
err := client.Resources().Put("my-resource", nil, &payload, &resp)// DELETE a resource
resp := resources.ResourceExtensionHandle{Format: handle.JSON}
err := client.Resources().Delete("my-resource", nil, &resp)import (
"github.com/ryanjdew/go-marklogic-go/indexes"
)
// List all indexes in the system
respHandle := indexes.IndexHandle{Format: handle.JSON}
err := client.Indexes().ListIndexes(nil, &respHandle)
if err != nil {
log.Fatal(err)
}// Create an element range index for efficient value comparisons
indexConfig := handle.RawHandle{Format: handle.JSON}
indexConfig.Write([]byte(`{
"scalar-type": "xs:string",
"namespace": "http://example.com/",
"localname": "title",
"collation": "http://marklogic.com/collation/codepoint"
}`))
respHandle := indexes.IndexHandle{Format: handle.JSON}
err := client.Indexes().CreateIndex(&indexConfig, &respHandle)// Create a field index for faster value retrieval across multiple elements
fieldIndex := handle.RawHandle{Format: handle.JSON}
fieldIndex.Write([]byte(`{
"field-name": "text-search",
"scalar-types": ["xs:string"],
"tokenizer": "http://marklogic.com/xdmp/tokenizer/default",
"included-elements": ["title", "body"]
}`))
respHandle := indexes.IndexHandle{Format: handle.JSON}
err := client.Indexes().CreateIndex(&fieldIndex, &respHandle)// Retrieve configuration for a specific index
respHandle := indexes.IndexHandle{Format: handle.JSON}
err := client.Indexes().GetIndex("text-search", nil, &respHandle)// Update an existing index configuration
updatedIndex := handle.RawHandle{Format: handle.JSON}
updatedIndex.Write([]byte(`{
"scalar-type": "xs:string",
"collation": "http://marklogic.com/collation/en"
}`))
respHandle := indexes.IndexHandle{Format: handle.JSON}
err := client.Indexes().UpdateIndex("element-range-index-1", &updatedIndex, &respHandle)// Remove an index from the system
respHandle := indexes.IndexHandle{Format: handle.JSON}
err := client.Indexes().DeleteIndex("text-search", nil, &respHandle)import (
"github.com/ryanjdew/go-marklogic-go/temporal"
)
// Create an axis for tracking system time
axisConfig := handle.RawHandle{Format: handle.JSON}
axisConfig.Write([]byte(`{
"axis-name": "system-time",
"scalar-uri": "xs:dateTime"
}`))
respHandle := temporal.TemporalHandle{Format: handle.JSON}
err := client.Temporal().CreateAxis("system-time", &axisConfig, &respHandle)// Retrieve all configured temporal axes
respHandle := temporal.TemporalHandle{Format: handle.JSON}
err := client.Temporal().ListAxes(&respHandle)// Get details about a specific axis
respHandle := temporal.TemporalHandle{Format: handle.JSON}
err := client.Temporal().GetAxis("system-time", &respHandle)// Enable temporal versioning on a document collection
temporalConfig := handle.RawHandle{Format: handle.JSON}
temporalConfig.Write([]byte(`{
"system-axis": "system-time",
"valid-axis": "valid-dates"
}`))
respHandle := temporal.TemporalHandle{Format: handle.JSON}
err := client.Temporal().EnableCollectionTemporal("temporal-docs", &temporalConfig, &respHandle)// Get a document as it existed at a specific point in time
respHandle := temporal.TemporalHandle{Format: handle.JSON}
timestamp := "2024-06-15T12:00:00Z"
err := client.Temporal().GetTemporalDocument("/documents/doc1.json", timestamp, &respHandle)// Advance the system clock for temporal operations
newTime := "2024-12-31T23:59:59Z"
respHandle := temporal.TemporalHandle{Format: handle.JSON}
err := client.Temporal().AdvanceSystemTime(newTime, &respHandle)// Retrieve the current system time for temporal operations
respHandle := temporal.TemporalHandle{Format: handle.JSON}
err := client.Temporal().GetSystemTime(&respHandle)
sysTime := respHandle.Deserialized().(temporal.SystemTime)
fmt.Printf("Current system time: %s\n", sysTime.Timestamp)// Remove temporal versioning from a collection
respHandle := temporal.TemporalHandle{Format: handle.JSON}
err := client.Temporal().DisableCollectionTemporal("temporal-docs", &respHandle)// Remove a temporal axis from the system
respHandle := temporal.TemporalHandle{Format: handle.JSON}
err := client.Temporal().DeleteAxis("system-time", &respHandle)import (
"github.com/ryanjdew/go-marklogic-go/metadata"
)
// Extract metadata from specific URIs
options := map[string]string{
"extractors": "document-properties,element-values",
}
respHandle := metadata.MetadataHandle{Format: handle.JSON}
err := client.Metadata().ExtractMetadata([]string{"/doc1.json", "/doc2.json"}, options, &respHandle)
// Access the metadata results
results := respHandle.Deserialized()
fmt.Printf("Extraction results: %v\n", results)// Extract metadata from documents matching a query
queryPayload := handle.RawHandle{Format: handle.JSON}
queryPayload.Write([]byte(`{
"query": {
"text-query": {
"text": "important"
}
}
}`))
respHandle := metadata.MetadataHandle{Format: handle.JSON}
err := client.Metadata().ExtractMetadataFromQuery(&queryPayload, nil, &respHandle)// Extract metadata from one document
options := map[string]string{
"format": "json",
}
respHandle := metadata.MetadataHandle{Format: handle.JSON}
err := client.Metadata().ExtractMetadataFromURI("/doc1.json", options, &respHandle)
result := respHandle.Deserialized().(metadata.MetadataResult)
fmt.Printf("Document: %s\nMetadata: %v\n", result.URI, result.Metadata)// Configure validation rules for the database
rulesPayload := handle.RawHandle{Format: handle.JSON}
rulesPayload.Write([]byte(`{
"rules": [
{
"name": "required-title",
"rule-type": "xpath",
"xpath": "title",
"action": "fail",
"message": "Document must contain a title element"
},
{
"name": "valid-status",
"rule-type": "enum",
"xpath": "status",
"values": ["draft", "published", "archived"],
"action": "warn"
}
]
}`))
respHandle := metadata.MetadataHandle{Format: handle.JSON}
err := client.Metadata().SetValidationRules(&rulesPayload, &respHandle)// Retrieve the configured validation rules
respHandle := metadata.MetadataHandle{Format: handle.JSON}
err := client.Metadata().GetValidationRules(&respHandle)// Validate documents against rules
rulesPayload := handle.RawHandle{Format: handle.JSON}
rulesPayload.Write([]byte(`{
"rules": [{
"name": "required-title",
"rule-type": "xpath",
"xpath": "title",
"action": "fail"
}]
}`))
respHandle := metadata.MetadataHandle{Format: handle.JSON}
err := client.Metadata().ValidateDocuments([]string{"/doc1.json"}, &rulesPayload, &respHandle)
// Check validation results
results := respHandle.Deserialized()
fmt.Printf("Validation results: %v\n", results)// Validate one document
rulesPayload := handle.RawHandle{Format: handle.JSON}
rulesPayload.Write([]byte(`{
"rules": [{
"name": "required-title",
"xpath": "title",
"action": "fail"
}]
}`))
respHandle := metadata.MetadataHandle{Format: handle.JSON}
err := client.Metadata().ValidateURI("/doc1.json", &rulesPayload, &respHandle)
result := respHandle.Deserialized().(metadata.ValidationResult)
fmt.Printf("Document: %s - Valid: %v\n", result.URI, result.Valid)
if !result.Valid {
fmt.Printf("Errors: %v\n", result.Errors)
}// Validate documents matching a query against rules
queryPayload := handle.RawHandle{Format: handle.JSON}
queryPayload.Write([]byte(`{
"query": {
"text-query": {
"text": "important"
}
}
}`))
rulesPayload := handle.RawHandle{Format: handle.JSON}
rulesPayload.Write([]byte(`{
"rules": [{
"name": "required-title",
"xpath": "title",
"action": "fail"
}]
}`))
respHandle := metadata.MetadataHandle{Format: handle.JSON}
err := client.Metadata().ValidateQuery(&queryPayload, &rulesPayload, &respHandle)import (
config "github.com/ryanjdew/go-marklogic-go/config"
)
// Create query options
opts := config.QueryOptions{
ReturnQuery: true,
PageLength: 20,
// ... other options
}
// Install query options
err := client.Config().SetQueryOptions("my-options", &opts)// Retrieve installed query options
opts, err := client.Config().GetQueryOptions("my-options")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Page length: %d\n", opts.PageLength)// Create a transaction
txn := client.NewTransaction()
// Use transaction ID in operations
respHandle := search.ResponseHandle{Format: handle.JSON}
err := client.Search().StructuredSearch(&qh, 1, 10, txn, &respHandle)
// Other operations with same txn...
// Commit or rollback happens through the transaction APIimport (
datamovement "github.com/ryanjdew/go-marklogic-go/datamovement"
)
### Bulk Operations
```go
import (
datamovement "github.com/ryanjdew/go-marklogic-go/datamovement"
)
// Use DataMovement service for optimized batch operations
batcher := client.DataMovement().WriteBatcher()
// Add documents to batch
for i := 0; i < 1000; i++ {
doc := &documents.DocumentDescription{
URI: fmt.Sprintf("/docs/doc%d.xml", i),
Content: bytes.NewBufferString(fmt.Sprintf("<doc>%d</doc>", i)),
Format: handle.XML,
}
batcher.Add(doc)
}
// Flush and wait for completion
err := batcher.Flush()The QueryBatcher also provides a pull-style iterator API using Iterator(ctx) that returns
an object with Next(ctx) and Close() methods. This can be more ergonomic than channel listeners
for many use-cases, and it supports context.Context for cancellation.
import (
"context"
"io"
"fmt"
datamovement "github.com/ryanjdew/go-marklogic-go/datamovement"
handle "github.com/ryanjdew/go-marklogic-go/handle"
search "github.com/ryanjdew/go-marklogic-go/search"
)
// Build query (example using the search package)
query := search.Query{
Queries: []any{search.TermQuery{Terms: []string{"example"}}},
}
qh := search.QueryHandle{Format: handle.JSON}
qh.Serialize(query)
qbr := client.DataMovement().QueryBatcher().
WithBatchSize(100).
WithQuery(&qh)
ctx := context.Background()
it := qbr.Iterator(ctx)
defer it.Close()
for {
batch, err := it.Next(ctx)
if err == io.EOF {
break
}
if err != nil {
// handle error
panic(err)
}
fmt.Printf("Batch with %d URIs, timestamp %s\n", len(batch.URIs), batch.Timestamp())
// Process URIs
}Notes:
Next(ctx)returnsio.EOFwhen there are no more results.Close()cancels the underlying work and waits for goroutines to finish.- Existing listener (
WithListener) and channel-based APIs remain supported.
The Rows Management service executes MarkLogic's Optic API for advanced SQL-like queries on structured data.
import (
rowsManagement "github.com/ryanjdew/go-marklogic-go/rows-management"
)
// Execute an Optic plan
opticPlan := handle.RawHandle{Format: handle.JSON}
opticPlan.Write([]byte(`{
"_export": "EMPLOYEES",
"_module": {"_import": "marklogic-optic", "fn": "op"},
"_next": {
"_export": "FROM_EMPLOYEES",
"_invoke": {"_function": "from", "_args": ["EMPLOYEES", "employees"]}
}
}`))
respHandle := handle.RawHandle{Format: handle.JSON}
err := client.RowsManagement().Rows(&opticPlan, nil, &respHandle)
// Results contain the rows in JSON format
results := respHandle.Deserialized()// Get query plan and execution estimates
opticPlan := handle.RawHandle{Format: handle.JSON}
opticPlan.Write([]byte(`{"_export": "EMPLOYEES"}`))
respHandle := handle.RawHandle{Format: handle.JSON}
err := client.RowsManagement().Explain(&opticPlan, nil, &respHandle)
// Response contains query plan, estimated row counts, and cost analysis// Get a sample of results without computing full result set
opticPlan := handle.RawHandle{Format: handle.JSON}
opticPlan.Write([]byte(`{"_export": "EMPLOYEES"}`))
params := map[string]string{"size": "10"}
respHandle := handle.RawHandle{Format: handle.JSON}
err := client.RowsManagement().Sample(&opticPlan, params, &respHandle)
// Response contains sample rows// Get the optimized execution plan
opticPlan := handle.RawHandle{Format: handle.JSON}
opticPlan.Write([]byte(`{"_export": "EMPLOYEES"}`))
respHandle := handle.RawHandle{Format: handle.JSON}
err := client.RowsManagement().Plan(&opticPlan, nil, &respHandle)
// Response shows index usage and access path optimizationThe library uses a Handle abstraction for serialization/deserialization. This allows flexible format handling (JSON, XML, multipart/mixed) without format-specific code.
// JSON format
jsonHandle := search.ResponseHandle{Format: handle.JSON}
// XML format
xmlHandle := search.ResponseHandle{Format: handle.XML}
// Raw handle for binary or passthrough data
rawHandle := handle.RawHandle{Format: handle.UNKNOWN}// For requests: serialize your data
query := search.Query{...}
qh := search.QueryHandle{Format: handle.XML}
qh.Serialize(query) // Encodes to XML
// Service automatically uses the format
respHandle := search.ResponseHandle{Format: handle.JSON}
err := client.Search().StructuredSearch(&qh, 1, 10, nil, &respHandle)
## Handle Pattern
The library uses a **Handle** abstraction for serialization/deserialization. This allows flexible format handling (JSON, XML, multipart/mixed) without format-specific code.
### Creating Handles
```go
// JSON format
jsonHandle := search.ResponseHandle{Format: handle.JSON}
// XML format
xmlHandle := search.ResponseHandle{Format: handle.XML}
// Raw handle for binary or passthrough data
rawHandle := handle.RawHandle{Format: handle.UNKNOWN}
// For requests: serialize your data
query := search.Query{...}
qh := search.QueryHandle{Format: handle.XML}
qh.Serialize(query) // Encodes to XML
// Service automatically uses the format
respHandle := search.ResponseHandle{Format: handle.JSON}
err := client.Search().StructuredSearch(&qh, 1, 10, nil, &respHandle)
// For responses: access both serialized and deserialized forms
rawJSON := respHandle.Serialized() // Get raw JSON string
results := respHandle.Deserialized() // Get typed Response struct| Format | MIME Type | Use Case |
|---|---|---|
JSON |
application/json |
Structured data in JSON format |
XML |
application/xml |
Structured data in XML format |
MIXED |
multipart/mixed |
Multiple documents with metadata |
TEXTPLAIN |
text/plain |
Plain text content |
TEXT_URI_LIST |
text/uri-list |
Lists of document URIs |
UNKNOWN |
application/octet-stream |
Binary or unknown format |
All service methods return an error as the last return value. Check errors immediately:
respHandle := search.ResponseHandle{Format: handle.JSON}
if err := client.Search().Search("query", 1, 10, nil, &respHandle); err != nil {
log.Printf("Search failed: %v", err)
// Handle error (e.g., connection issues, HTTP 4xx/5xx)
}HTTP errors (status ≥400) are converted to Go errors. Common scenarios:
- Connection errors: Host unreachable, authentication failed
- 404 Not Found: Document URI doesn't exist, resource not found
- 400 Bad Request: Invalid query syntax, malformed input
- 500+ Server Errors: MarkLogic internal errors
# Install dependencies
go mod tidy
# Start MarkLogic in Docker (requires Docker)
task setup
# Build
task buildTests require a running MarkLogic instance:
# Run all integration tests
task test
# Or manually with go test
go test ./... -tags=integrationTo add a new service to the library:
- Create a package (e.g.,
myservice/) - Implement service.go with factory:
type Service struct { client *clients.Client } func NewService(c *clients.Client) *Service { return &Service{client: c} }
- Implement operations in
myservice.gousingutil.BuildRequestFromHandle()andutil.Execute() - Add factory to root Client in
clientAPI.go
The library uses three layers:
- Client Layer (
clients/): Low-level HTTP handling with authentication - Service Layer (
search/,documents/, etc.): Domain-specific operations - Handle Layer (
handle/): Format-agnostic serialization/deserialization
Service methods are thin wrappers delegating to package-level functions, enabling testability without mocking and clear separation of concerns.
This library maps to MarkLogic's REST API. For more details on specific operations:
- Search: /v1/search - Full-text, structured queries, faceting
- Documents: /v1/documents - CRUD operations
- Semantics: /v1/graphs, /v1/graphs/sparql - Triple store, SPARQL queries
- Values: /v1/values - Lexicon value enumeration with aggregation
- Eval: /v1/eval - Server-side XQuery/JavaScript execution
- Suggest: /v1/suggest - Query suggestions and autocomplete
- Indexes: /v1/config/indexes - Range and field index management
- Temporal: /v1/temporal - Temporal axes, collections, and document versioning
- Metadata: /v1/metadata - Metadata extraction and document validation
- Rows (Optic): /v1/rows - SQL-like queries with explain and sampling
- Configuration: /v1/config - Query options, transforms, extensions
- Transactions: /v1/transactions - Multi-statement transactions
- Resources: /v1/resources - Custom REST extensions
- Full API Reference: docs.marklogic.com/REST
Apache License 2.0 - See LICENSE.txt