github.com/altipla-consulting/ravendb-go-client@v0.1.3/batch_command.go (about)

     1  package ravendb
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"mime/multipart"
     8  	"net/http"
     9  	"net/textproto"
    10  	"strconv"
    11  	"strings"
    12  )
    13  
    14  var (
    15  	_ RavenCommand = &BatchCommand{}
    16  )
    17  
    18  // BatchCommand represents batch command
    19  type BatchCommand struct {
    20  	RavenCommandBase
    21  
    22  	conventions       *DocumentConventions
    23  	commands          []ICommandData
    24  	attachmentStreams []io.Reader
    25  	options           *BatchOptions
    26  
    27  	Result *JSONArrayResult
    28  }
    29  
    30  // newBatchCommand returns new BatchCommand
    31  func newBatchCommand(conventions *DocumentConventions, commands []ICommandData, options *BatchOptions) (*BatchCommand, error) {
    32  	if conventions == nil {
    33  		return nil, newIllegalStateError("conventions cannot be nil")
    34  	}
    35  	if len(commands) == 0 {
    36  		return nil, newIllegalStateError("commands cannot be empty")
    37  	}
    38  
    39  	cmd := &BatchCommand{
    40  		RavenCommandBase: NewRavenCommandBase(),
    41  
    42  		commands:    commands,
    43  		options:     options,
    44  		conventions: conventions,
    45  	}
    46  
    47  	for i := 0; i < len(commands); i++ {
    48  		command := commands[i]
    49  		if putAttachmentCommandData, ok := command.(*PutAttachmentCommandData); ok {
    50  
    51  			stream := putAttachmentCommandData.getStream()
    52  			for _, existingStream := range cmd.attachmentStreams {
    53  				if stream == existingStream {
    54  					return nil, throwStreamAlready()
    55  				}
    56  			}
    57  			cmd.attachmentStreams = append(cmd.attachmentStreams, stream)
    58  		}
    59  	}
    60  
    61  	return cmd, nil
    62  }
    63  
    64  var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
    65  
    66  func escapeQuotes(s string) string {
    67  	return quoteEscaper.Replace(s)
    68  }
    69  
    70  func (c *BatchCommand) createRequest(node *ServerNode) (*http.Request, error) {
    71  	url := node.URL + "/databases/" + node.Database + "/bulk_docs"
    72  	url = c.appendOptions(url)
    73  
    74  	var a []interface{}
    75  	for _, cmd := range c.commands {
    76  		el, err := cmd.serialize(c.conventions)
    77  		if err != nil {
    78  			return nil, err
    79  		}
    80  		a = append(a, el)
    81  	}
    82  	v := map[string]interface{}{
    83  		"Commands": a,
    84  	}
    85  	js, err := jsonMarshal(v)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	if len(c.attachmentStreams) == 0 {
    90  		return newHttpPost(url, js)
    91  	}
    92  
    93  	body := &bytes.Buffer{}
    94  	writer := multipart.NewWriter(body)
    95  	err = writer.WriteField("main", string(js))
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	nameCounter := 1
   101  	for _, stream := range c.attachmentStreams {
   102  		name := "attachment" + strconv.Itoa(nameCounter)
   103  		nameCounter++
   104  		h := make(textproto.MIMEHeader)
   105  		h.Set("Content-Disposition",
   106  			fmt.Sprintf(`form-data; name="%s"`, escapeQuotes(name)))
   107  		h.Set("Command-Type", "AttachmentStream")
   108  		// Note: Java seems to set those by default
   109  		h.Set("Content-Type", "application/octet-stream")
   110  		h.Set("Content-Transfer-Encoding", "binary")
   111  
   112  		part, err2 := writer.CreatePart(h)
   113  		if err2 != nil {
   114  			return nil, err2
   115  		}
   116  		_, err = io.Copy(part, stream)
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  	}
   121  	err = writer.Close()
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	req, err := newHttpPostReader(url, body)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	contentType := writer.FormDataContentType()
   130  	req.Header.Set("Content-Type", contentType)
   131  
   132  	return req, nil
   133  }
   134  
   135  func (c *BatchCommand) setResponse(response []byte, fromCache bool) error {
   136  	if len(response) == 0 {
   137  		return newIllegalStateError("Got null response from the server after doing a batch, something is very wrong. Probably a garbled response.")
   138  	}
   139  
   140  	return jsonUnmarshal(response, &c.Result)
   141  }
   142  
   143  func (c *BatchCommand) appendOptions(sb string) string {
   144  	_options := c.options
   145  	if _options == nil {
   146  		return sb
   147  	}
   148  
   149  	sb += "?"
   150  
   151  	if _options.waitForReplicas {
   152  		ts := durationToTimeSpan(_options.waitForReplicasTimeout)
   153  		sb += "&waitForReplicasTimeout=" + ts
   154  
   155  		if _options.throwOnTimeoutInWaitForReplicas {
   156  			sb += "&throwOnTimeoutInWaitForReplicas=true"
   157  		}
   158  
   159  		sb += "&numberOfReplicasToWaitFor="
   160  		if _options.majority {
   161  			sb += "majority"
   162  		} else {
   163  			sb += strconv.Itoa(_options.numberOfReplicasToWaitFor)
   164  		}
   165  	}
   166  
   167  	if _options.waitForIndexes {
   168  		ts := durationToTimeSpan(_options.waitForIndexesTimeout)
   169  		sb += "&waitForIndexesTimeout=" + ts
   170  
   171  		if _options.throwOnTimeoutInWaitForIndexes {
   172  			sb += "&waitForIndexThrow=true"
   173  		} else {
   174  			sb += "&waitForIndexThrow=false"
   175  		}
   176  
   177  		for _, specificIndex := range _options.waitForSpecificIndexes {
   178  			sb += "&waitForSpecificIndex=" + specificIndex
   179  		}
   180  	}
   181  	return sb
   182  }
   183  
   184  func (c *BatchCommand) Close() error {
   185  	// no-op
   186  	return nil
   187  }
   188  
   189  // Note: in Java is in PutAttachmentCommandHelper.java
   190  func throwStreamAlready() error {
   191  	return newIllegalStateError("It is forbidden to re-use the same InputStream for more than one attachment. Use a unique InputStream per put attachment command.")
   192  }