<commit_before>package queryport

import "net/http"
import "encoding/json"
import "bytes"
import "fmt"
import "io/ioutil"
import "errors"
import "strings"

import "github.com/couchbase/indexing/secondary/common"

// TODO: figure out a logging mechanism.

// RequestType to indicate type of index-request.
type RequestType string

const (
	// Create index
	Create RequestType = "create"
	// Drop index
	Drop RequestType = "drop"
	// List index
	List RequestType = "list"
)

// ExprType to indicate who to interpret expression binary.
type ExprType string

const (
	// N1QLExpr specified using N1QL
	N1QLExpr ExprType = "N1QL"
	// JSExpr specified using JavaScript
	JSExpr ExprType = "JavaScript"
)

// ResponseStatus to indicate the success or error case for an index-request.
type ResponseStatus string

const (
	// Success says valid response.
	Success ResponseStatus = "success"
	// Error says index-response contain error code and message.
	Error ResponseStatus = "error"
	// InvalidCache says client should fetch list of index.
	InvalidCache ResponseStatus = "invalid_cache"
)

// IndexError for a failed index-request.
type IndexError struct {
	Code string `json:"code,omitempty"`
	Msg  string `json:"msg,omitempty"`
}

// IndexRequest message
type IndexRequest struct {
	Version uint64      `json:"version,omitempty"`
	Type    RequestType `json:"type,omitempty"`
	Index   IndexInfo   `json:"index,omitempty"`
}

// IndexInfo describes an index.
type IndexInfo struct {
	Name      string   `json:"name,omitempty"`
	Bucket    string   `json:"bucket,omitempty"`
	DefnID    string   `json:"defnID, omitempty"`
	Using     string   `json:"using,omitempty"`
	ExprType  string   `json:"exprType,omitempty"`
	PartnExpr string   `json:"partnExpr,omitempty"`
	SecExprs  []string `json:"secExprs,omitempty"`
	WhereExpr string   `json:"whereExpr,omitempty"`
	IsPrimary bool     `json:"isPrimary,omitempty"`
}

// IndexMetaResponse for an IndexRequest
type IndexMetaResponse struct {
	Version uint64         `json:"version,omitempty"`
	Status  ResponseStatus `json:"status,omitempty"`
	Indexes []IndexInfo    `json:"indexes,omitempty"`
	Errors  []IndexError   `json:"errors,omitempty"`
}

// ClusterClient to access cluster-agent for admin operation on index.
type ClusterClient struct {
	addr      string
	httpc     *http.Client
	logPrefix string
}

// NewClusterClient create cluster client.
func NewClusterClient(addr string) *ClusterClient {
	if !strings.HasPrefix(addr, "http") {
		addr = "http://" + addr
	}
	return &ClusterClient{
		addr:      addr,
		httpc:     http.DefaultClient,
		logPrefix: fmt.Sprintf("[cluster-client %v]", addr),
	}
}

// CreateIndex if successful, return index-info as returned by the
// server, also the unique id generated by the server.
func (c *ClusterClient) CreateIndex(
	name, bucket, using, exprType, partnExpr, whereExpr string,
	secExprs []string, isPrimary bool) (*IndexInfo, error) {

	// Construct request body.
	info := IndexInfo{
		Name:      name,
		Bucket:    bucket,
		Using:     using,
		ExprType:  exprType,
		PartnExpr: partnExpr,
		WhereExpr: whereExpr,
		SecExprs:  secExprs,
		IsPrimary: isPrimary,
	}
	req := IndexRequest{Type: Create, Index: info}
	body, err := json.Marshal(req)
	if err == nil { // Post HTTP request.
		bodybuf := bytes.NewBuffer(body)
		url := c.addr + "/create"
		common.Infof("%v posting %v to URL %v", c.logPrefix, bodybuf, url)
		resp, err := c.httpc.Post(url, "application/json", bodybuf)
		if err == nil {
			defer resp.Body.Close()
			mresp, err := c.metaResponse(resp)
			if err == nil {
				return &mresp.Indexes[0], nil
			}
			return nil, err
		}
	}
	return nil, err
}

// DropIndex `DefnID` identifies the index to be dropped but is unique-id.
func (c *ClusterClient) DropIndex(defnID string) error {
	// Construct request body.
	req := IndexRequest{Type: Drop, Index: IndexInfo{DefnID: defnID}}
	body, err := json.Marshal(req)
	if err == nil {
		// Post HTTP request.
		bodybuf := bytes.NewBuffer(body)
		url := c.addr + "/drop"
		common.Infof("%v posting %v to URL %v", c.logPrefix, bodybuf, url)
		resp, err := c.httpc.Post(url, "application/json", bodybuf)
		if err == nil {
			defer resp.Body.Close()
			_, err := c.metaResponse(resp)
			if err == nil {
				return nil
			}
			return err
		}
	}
	return err
}

// List all indexes.
func (c *ClusterClient) List() ([]IndexInfo, error) {
	// Construct request body.
	req := IndexRequest{Type: List}
	body, err := json.Marshal(req)
	if err == nil { // Post HTTP request.
		bodybuf := bytes.NewBuffer(body)
		url := c.addr + "/list"
		common.Infof("%v posting %v to URL %v", c.logPrefix, bodybuf, url)
		resp, err := c.httpc.Post(url, "application/json", bodybuf)
		if err == nil {
			defer resp.Body.Close()
			mresp, err := c.metaResponse(resp)
			if err == nil {
				return mresp.Indexes, nil
			}
			return nil, err
		}
	}
	return nil, err
}

// Gather index meta response from http response.
func (c *ClusterClient) metaResponse(
	resp *http.Response) (IndexMetaResponse, error) {

	var mresp IndexMetaResponse<commit_msg>Bugfix: handle error case gracefully.<commit_after>package queryport

import "net/http"
import "encoding/json"
import "bytes"
import "fmt"
import "io/ioutil"
import "errors"
import "strings"

import "github.com/couchbase/indexing/secondary/common"

// TODO: figure out a logging mechanism.

// RequestType to indicate type of index-request.
type RequestType string

const (
	// Create index
	Create RequestType = "create"
	// Drop index
	Drop RequestType = "drop"
	// List index
	List RequestType = "list"
)

// ExprType to indicate who to interpret expression binary.
type ExprType string

const (
	// N1QLExpr specified using N1QL
	N1QLExpr ExprType = "N1QL"
	// JSExpr specified using JavaScript
	JSExpr ExprType = "JavaScript"
)

// ResponseStatus to indicate the success or error case for an index-request.
type ResponseStatus string

const (
	// Success says valid response.
	Success ResponseStatus = "success"
	// Error says index-response contain error code and message.
	Error ResponseStatus = "error"
	// InvalidCache says client should fetch list of index.
	InvalidCache ResponseStatus = "invalid_cache"
)

// IndexError for a failed index-request.
type IndexError struct {
	Code string `json:"code,omitempty"`
	Msg  string `json:"msg,omitempty"`
}

// IndexRequest message
type IndexRequest struct {
	Version uint64      `json:"version,omitempty"`
	Type    RequestType `json:"type,omitempty"`
	Index   IndexInfo   `json:"index,omitempty"`
}

// IndexInfo describes an index.
type IndexInfo struct {
	Name      string   `json:"name,omitempty"`
	Bucket    string   `json:"bucket,omitempty"`
	DefnID    string   `json:"defnID, omitempty"`
	Using     string   `json:"using,omitempty"`
	ExprType  string   `json:"exprType,omitempty"`
	PartnExpr string   `json:"partnExpr,omitempty"`
	SecExprs  []string `json:"secExprs,omitempty"`
	WhereExpr string   `json:"whereExpr,omitempty"`
	IsPrimary bool     `json:"isPrimary,omitempty"`
}

// IndexMetaResponse for an IndexRequest
type IndexMetaResponse struct {
	Version uint64         `json:"version,omitempty"`
	Status  ResponseStatus `json:"status,omitempty"`
	Indexes []IndexInfo    `json:"indexes,omitempty"`
	Errors  []IndexError   `json:"errors,omitempty"`
}

// ClusterClient to access cluster-agent for admin operation on index.
type ClusterClient struct {
	addr      string
	httpc     *http.Client
	logPrefix string
}

// NewClusterClient create cluster client.
func NewClusterClient(addr string) *ClusterClient {
	if !strings.HasPrefix(addr, "http") {
		addr = "http://" + addr
	}
	return &ClusterClient{
		addr:      addr,
		httpc:     http.DefaultClient,
		logPrefix: fmt.Sprintf("[cluster-client %v]", addr),
	}
}

// CreateIndex if successful, return index-info as returned by the
// server, also the unique id generated by the server.
func (c *ClusterClient) CreateIndex(
	name, bucket, using, exprType, partnExpr, whereExpr string,
	secExprs []string, isPrimary bool) (*IndexInfo, error) {

	var resp *http.Response
	var mresp IndexMetaResponse

	// Construct request body.
	info := IndexInfo{
		Name:      name,
		Bucket:    bucket,
		Using:     using,
		ExprType:  exprType,
		PartnExpr: partnExpr,
		WhereExpr: whereExpr,
		SecExprs:  secExprs,
		IsPrimary: isPrimary,
	}
	req := IndexRequest{Type: Create, Index: info}
	body, err := json.Marshal(req)
	if err == nil { // Post HTTP request.
		bodybuf := bytes.NewBuffer(body)
		url := c.addr + "/create"
		common.Infof("%v posting %v to URL %v", c.logPrefix, bodybuf, url)
		resp, err = c.httpc.Post(url, "application/json", bodybuf)
		if err == nil {
			defer resp.Body.Close()
			mresp, err = c.metaResponse(resp)
			if err == nil {
				return &mresp.Indexes[0], nil
			}
			return nil, err
		}
	}
	return nil, err
}

// DropIndex `DefnID` identifies the index to be dropped but is unique-id.
func (c *ClusterClient) DropIndex(defnID string) error {
	var resp *http.Response

	// Construct request body.
	req := IndexRequest{Type: Drop, Index: IndexInfo{DefnID: defnID}}
	body, err := json.Marshal(req)
	if err == nil {
		// Post HTTP request.
		bodybuf := bytes.NewBuffer(body)
		url := c.addr + "/drop"
		common.Infof("%v posting %v to URL %v", c.logPrefix, bodybuf, url)
		resp, err = c.httpc.Post(url, "application/json", bodybuf)
		if err == nil {
			defer resp.Body.Close()
			_, err = c.metaResponse(resp)
			if err == nil {
				return nil
			}
			return err
		}
	}
	return err
}

// List all indexes.
func (c *ClusterClient) List() ([]IndexInfo, error) {
	var resp *http.Response
	var mresp IndexMetaResponse

	// Construct request body.
	req := IndexRequest{Type: List}
	body, err := json.Marshal(req)
	if err == nil { // Post HTTP request.
		bodybuf := bytes.NewBuffer(body)
		url := c.addr + "/list"
		common.Infof("%v posting %v to URL %v", c.logPrefix, bodybuf, url)
		resp, err = c.httpc.Post(url, "application/json", bodybuf)
		if err == nil {
			defer resp.Body.Close()
			mresp, err = c.metaResponse(resp)
			if err == nil {
				return mresp.Indexes, nil
			}
			return nil, err
		}
	}
	return nil, err
}

// Gather index meta response from http response.
func (c *ClusterClient) metaResponse(
	resp *http.Response) (IndexMetaResponse, error) {

	var mresp IndexMetaResponse
{'fraction_non_alphanumeric': 0.11896417445482867, 'fraction_numerical': 0.0017523364485981308, 'mean_word_length': 3.7736988847583643, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4108191', 'n_tokens_mistral': 3430, 'n_tokens_neox': 3269, 'n_words': 1024}